comparison extern/libmustache4c/mustache.c @ 26:7e10cace67a3

scid: add basic mustache support
author David Demelier <markand@malikania.fr>
date Tue, 02 Aug 2022 13:24:13 +0200
parents
children
comparison
equal deleted inserted replaced
25:c40f98360ac9 26:7e10cace67a3
1 /*
2 * Mustache4C
3 * (http://github.com/mity/mustache4c)
4 *
5 * Copyright (c) 2017 Martin Mitas
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a
8 * copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation
10 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 * and/or sell copies of the Software, and to permit persons to whom the
12 * Software is furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23 * IN THE SOFTWARE.
24 */
25
26 #include "mustache.h"
27
28 #include <errno.h>
29 #include <stdint.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/types.h> /* for size_t */
33
34
35 #ifdef _MSC_VER
36 /* MSVC does not understand "inline" when building as pure C (not C++).
37 * However it understands "__inline" */
38 #ifndef __cplusplus
39 #define inline __inline
40 #endif
41 #endif
42
43
44 #define MUSTACHE_DEFAULTOPENER "{{"
45 #define MUSTACHE_DEFAULTCLOSER "}}"
46 #define MUSTACHE_MAXOPENERLENGTH 32
47 #define MUSTACHE_MAXCLOSERLENGTH 32
48
49
50 /**********************
51 *** Growing Buffer ***
52 **********************/
53
54 typedef struct MUSTACHE_BUFFER {
55 uint8_t* data;
56 size_t n;
57 size_t alloc;
58 } MUSTACHE_BUFFER;
59
60 static inline void
61 mustache_buffer_free(MUSTACHE_BUFFER* buf)
62 {
63 free(buf->data);
64 }
65
66 static int
67 mustache_buffer_insert(MUSTACHE_BUFFER* buf, size_t off, const void* data, size_t n)
68 {
69 if(buf->n + n > buf->alloc) {
70 size_t new_alloc = (buf->n + n) * 2;
71 uint8_t* new_data;
72
73 new_data = (uint8_t*) realloc(buf->data, new_alloc);
74 if(new_data == NULL)
75 return -1;
76
77 buf->data = new_data;
78 buf->alloc = new_alloc;
79 }
80
81 if(off < buf->n)
82 memmove(buf->data + off + n, buf->data + off, buf->n - off);
83
84 memcpy(buf->data + off, data, n);
85 buf->n += n;
86 return 0;
87 }
88
89 static inline int
90 mustache_buffer_append(MUSTACHE_BUFFER* buf, const void* data, size_t n)
91 {
92 return mustache_buffer_insert(buf, (size_t) buf->n, data, n);
93 }
94
95 static int
96 mustache_buffer_insert_num(MUSTACHE_BUFFER* buf, size_t off, uint64_t num)
97 {
98 uint8_t tmp[16];
99 size_t n = 0;
100
101 tmp[15 - n++] = num & 0x7f;
102
103 while(1) {
104 num = num >> 7;
105 if(num == 0)
106 break;
107 tmp[15 - n++] = 0x80 | (num & 0x7f);
108 }
109
110 return mustache_buffer_insert(buf, off, tmp+16-n, n);
111 }
112
113 static inline int
114 mustache_buffer_append_num(MUSTACHE_BUFFER* buf, uint64_t num)
115 {
116 return mustache_buffer_insert_num(buf, buf->n, num);
117 }
118
119 static uint64_t
120 mustache_decode_num(const uint8_t* data, size_t off, size_t* p_off)
121 {
122 uint64_t num = 0;
123
124 while(data[off] >= 0x80) {
125 num |= (data[off++] & 0x7f);
126 num = num << 7;
127 }
128
129 num |= data[off++];
130 *p_off = off;
131 return num;
132 }
133
134
135 /****************************
136 *** Stack Implementation ***
137 ****************************/
138
139 typedef MUSTACHE_BUFFER MUSTACHE_STACK;
140
141 static inline void
142 mustache_stack_free(MUSTACHE_STACK* stack)
143 {
144 mustache_buffer_free(stack);
145 }
146
147 static inline int
148 mustache_stack_is_empty(MUSTACHE_STACK* stack)
149 {
150 return (stack->n == 0);
151 }
152
153 static inline int
154 mustache_stack_push(MUSTACHE_STACK* stack, uintptr_t item)
155 {
156 return mustache_buffer_append(stack, &item, sizeof(uintptr_t));
157 }
158
159 static inline uintptr_t
160 mustache_stack_peek(MUSTACHE_STACK* stack)
161 {
162 return *((uintptr_t*)(stack->data + (stack->n - sizeof(uintptr_t))));
163 }
164
165 static inline uintptr_t
166 mustache_stack_pop(MUSTACHE_STACK* stack)
167 {
168 uintptr_t item = mustache_stack_peek(stack);
169 stack->n -= sizeof(uintptr_t);
170 return item;
171 }
172
173
174 /***************************
175 *** Parsing & Compiling ***
176 ***************************/
177
178 #define MUSTACHE_ISANYOF2(ch, ch1, ch2) ((ch) == (ch1) || (ch) == (ch2))
179 #define MUSTACHE_ISANYOF4(ch, ch1, ch2, ch3, ch4) ((ch) == (ch1) || (ch) == (ch2) || (ch) == (ch3) || (ch) == (ch4))
180
181 #define MUSTACHE_ISWHITESPACE(ch) MUSTACHE_ISANYOF4((ch), ' ', '\t', '\v', '\f')
182 #define MUSTACHE_ISNEWLINE(ch) MUSTACHE_ISANYOF2((ch), '\r', '\n')
183
184 /* Keep in sync with MUSTACHE_ERR_xxx constants. */
185 static const char* mustache_err_messages[] = {
186 "Success.",
187 "Tag opener has no closer.",
188 "Tag closer has no opener.",
189 "Tag closer is incompatible with its opener.",
190 "Tag has no name.",
191 "Tag name is invalid.",
192 "Section-opening tag has no closer.",
193 "Section-closing tag has no opener.",
194 "Name of section-closing tag does not match corresponding section-opening tag.",
195 "The section-opening is located here.",
196 "Invalid specification of delimiters."
197 };
198
199 /* For the given template, we construct list of MUSTACHE_TAGINFO structures.
200 * Along the way, we also check for any parsing errors and report them
201 * to the app.
202 */
203
204 typedef enum MUSTACHE_TAGTYPE {
205 MUSTACHE_TAGTYPE_NONE = 0,
206 MUSTACHE_TAGTYPE_DELIM, /* {{=@ @=}} */
207 MUSTACHE_TAGTYPE_COMMENT, /* {{! comment }} */
208 MUSTACHE_TAGTYPE_VAR, /* {{ var }} */
209 MUSTACHE_TAGTYPE_VERBATIMVAR, /* {{{ var }}} */
210 MUSTACHE_TAGTYPE_VERBATIMVAR2, /* {{& var }} */
211 MUSTACHE_TAGTYPE_OPENSECTION, /* {{# section }} */
212 MUSTACHE_TAGTYPE_OPENSECTIONINV, /* {{^ section }} */
213 MUSTACHE_TAGTYPE_CLOSESECTION, /* {{/ section }} */
214 MUSTACHE_TAGTYPE_CLOSESECTIONINV,
215 MUSTACHE_TAGTYPE_PARTIAL, /* {{> partial }} */
216 MUSTACHE_TAGTYPE_INDENT /* for internal purposes. */
217 } MUSTACHE_TAGTYPE;
218
219 typedef struct MUSTACHE_TAGINFO {
220 MUSTACHE_TAGTYPE type;
221 size_t line;
222 size_t col;
223 size_t beg;
224 size_t end;
225 size_t name_beg;
226 size_t name_end;
227 } MUSTACHE_TAGINFO;
228
229 static void
230 mustache_parse_error(int err_code, const char* msg,
231 unsigned line, unsigned column, void* parser_data)
232 {
233 /* noop */
234 (void)err_code;
235 (void)msg;
236 (void)line;
237 (void)column;
238 (void)parser_data;
239 }
240
241 static int
242 mustache_is_std_closer(const char* closer, size_t closer_len)
243 {
244 size_t off;
245
246 for(off = 0; off < closer_len; off++) {
247 if(closer[off] != '}')
248 return 0;
249 }
250
251 return 1;
252 }
253
254 static int
255 mustache_validate_tagname(const char* tagname, size_t size)
256 {
257 size_t off;
258
259 if(size == 1 && tagname[0] == '.')
260 return 0;
261
262 /* Verify there is no whitespace and that '.' is used only as a delimiter
263 * of non-empty tokens. */
264 if(tagname[0] == '.' || tagname[size-1] == '.')
265 return -1;
266 for(off = 0; off < size; off++) {
267 if(MUSTACHE_ISWHITESPACE(tagname[off]))
268 return -1;
269 if(tagname[off] == '.' && off+1 < size && tagname[off+1] == '.')
270 return -1;
271 }
272
273 return 0;
274 }
275
276 static int
277 mustache_validate_sections(const char* templ_data, MUSTACHE_BUFFER* tags_buffer,
278 const MUSTACHE_PARSER* parser, void* parser_data)
279 {
280 MUSTACHE_TAGINFO* tags = (MUSTACHE_TAGINFO*) tags_buffer->data;
281 unsigned n_tags = tags_buffer->n / sizeof(MUSTACHE_TAGINFO);
282 unsigned i;
283 MUSTACHE_STACK section_stack = { 0 };
284 MUSTACHE_TAGINFO* opener;
285 int n_errors = 0;
286 int ret = -1;
287
288 for(i = 0; i < n_tags; i++) {
289 switch(tags[i].type) {
290 case MUSTACHE_TAGTYPE_OPENSECTION:
291 case MUSTACHE_TAGTYPE_OPENSECTIONINV:
292 if(mustache_stack_push(&section_stack, (uintptr_t) &tags[i]) != 0)
293 goto err;
294 break;
295
296 case MUSTACHE_TAGTYPE_CLOSESECTION:
297 case MUSTACHE_TAGTYPE_CLOSESECTIONINV:
298 if(mustache_stack_is_empty(&section_stack)) {
299 parser->parse_error(MUSTACHE_ERR_DANGLINGSECTIONCLOSER,
300 mustache_err_messages[MUSTACHE_ERR_DANGLINGSECTIONCLOSER],
301 (unsigned)tags[i].line, (unsigned)tags[i].col,
302 parser_data);
303 n_errors++;
304 } else {
305 opener = (MUSTACHE_TAGINFO*) mustache_stack_pop(&section_stack);
306
307 if(opener->name_end - opener->name_beg != tags[i].name_end - tags[i].name_beg ||
308 strncmp(templ_data + opener->name_beg,
309 templ_data + tags[i].name_beg,
310 opener->name_end - opener->name_beg) != 0)
311 {
312 parser->parse_error(MUSTACHE_ERR_SECTIONNAMEMISMATCH,
313 mustache_err_messages[MUSTACHE_ERR_SECTIONNAMEMISMATCH],
314 (unsigned)tags[i].line, (unsigned)tags[i].col,
315 parser_data);
316 parser->parse_error(MUSTACHE_ERR_SECTIONOPENERHERE,
317 mustache_err_messages[MUSTACHE_ERR_SECTIONOPENERHERE],
318 (unsigned)opener->line, (unsigned)opener->col,
319 parser_data);
320 n_errors++;
321 }
322
323 if(opener->type == MUSTACHE_TAGTYPE_OPENSECTIONINV)
324 tags[i].type = MUSTACHE_TAGTYPE_CLOSESECTIONINV;
325 }
326 break;
327
328 default:
329 break;
330 }
331 }
332
333 if(!mustache_stack_is_empty(&section_stack)) {
334 while(!mustache_stack_is_empty(&section_stack)) {
335 opener = (MUSTACHE_TAGINFO*) mustache_stack_pop(&section_stack);
336
337 parser->parse_error(MUSTACHE_ERR_DANGLINGSECTIONOPENER,
338 mustache_err_messages[MUSTACHE_ERR_DANGLINGSECTIONOPENER],
339 (unsigned)opener->line, (unsigned)opener->col,
340 parser_data);
341 n_errors++;
342 }
343 }
344
345 if(n_errors == 0)
346 ret = 0;
347
348 err:
349 mustache_stack_free(&section_stack);
350 return ret;
351 }
352
353 static int
354 mustache_parse_delimiters(const char* delim_spec, size_t size,
355 char* opener, size_t* p_opener_len,
356 char* closer, size_t* p_closer_len)
357 {
358 size_t opener_beg, opener_end;
359 size_t closer_beg, closer_end;
360
361 opener_beg = 0;
362
363 opener_end = opener_beg;
364 while(opener_end < size) {
365 if(MUSTACHE_ISWHITESPACE(delim_spec[opener_end]))
366 break;
367 if(delim_spec[opener_end] == '=')
368 return -1;
369 opener_end++;
370 }
371 if(opener_end <= opener_beg || opener_end - opener_beg > MUSTACHE_MAXOPENERLENGTH)
372 return -1;
373
374 closer_beg = opener_end;
375 while(closer_beg < size) {
376 if(!MUSTACHE_ISWHITESPACE(delim_spec[closer_beg]))
377 break;
378 closer_beg++;
379 }
380 if(closer_beg <= opener_end)
381 return -1;
382
383 closer_end = closer_beg;
384 while(closer_end < size) {
385 if(MUSTACHE_ISWHITESPACE(delim_spec[closer_end]))
386 return -1;
387 closer_end++;
388 }
389 if(closer_end <= closer_beg || closer_end - closer_beg > MUSTACHE_MAXCLOSERLENGTH)
390 return -1;
391 if(closer_end != size)
392 return -1;
393
394 memcpy(opener, delim_spec + opener_beg, opener_end - opener_beg);
395 *p_opener_len = opener_end - opener_beg;
396 memcpy(closer, delim_spec + closer_beg, closer_end - closer_beg);
397 *p_closer_len = closer_end - closer_beg;
398 return 0;
399 }
400
401 static int
402 mustache_parse(const char* templ_data, size_t templ_size,
403 const MUSTACHE_PARSER* parser, void* parser_data,
404 MUSTACHE_TAGINFO** p_tags, unsigned* p_n_tags)
405 {
406 int n_errors = 0;
407 char opener[MUSTACHE_MAXOPENERLENGTH] = MUSTACHE_DEFAULTOPENER;
408 char closer[MUSTACHE_MAXCLOSERLENGTH] = MUSTACHE_DEFAULTCLOSER;
409 size_t opener_len;
410 size_t closer_len;
411 size_t off = 0;
412 size_t line = 1;
413 size_t col = 1;
414 MUSTACHE_TAGINFO current_tag;
415 MUSTACHE_BUFFER tags = { 0 };
416
417 /* If this template will ever be used as a partial, it may inherit an
418 * extra indentation from parent template, so we mark every line beginning
419 * with the dummy tag for further processing in mustache_compile(). */
420 if(off < templ_size) {
421 current_tag.type = MUSTACHE_TAGTYPE_INDENT;
422 current_tag.beg = off;
423 current_tag.end = off;
424 if(mustache_buffer_append(&tags, &current_tag, sizeof(MUSTACHE_TAGINFO)) != 0)
425 goto err;
426 }
427
428 current_tag.type = MUSTACHE_TAGTYPE_NONE;
429
430 opener_len = strlen(MUSTACHE_DEFAULTOPENER);
431 closer_len = strlen(MUSTACHE_DEFAULTCLOSER);
432
433 while(off < templ_size) {
434 int is_opener, is_closer;
435
436 is_opener = (off + opener_len <= templ_size && memcmp(templ_data+off, opener, opener_len) == 0);
437 is_closer = (off + closer_len <= templ_size && memcmp(templ_data+off, closer, closer_len) == 0);
438 if(is_opener && is_closer) {
439 /* Opener and closer may be defined to be the same string.
440 * Consider for example "{{=@ @=}}".
441 * Determine the real meaning from current parser state:
442 */
443 if(current_tag.type == MUSTACHE_TAGTYPE_NONE)
444 is_closer = 0;
445 else
446 is_opener = 0;
447 }
448
449 if(is_opener) {
450 /* Handle tag opener "{{" */
451
452 if(current_tag.type != MUSTACHE_TAGTYPE_NONE && current_tag.type != MUSTACHE_TAGTYPE_COMMENT) {
453 /* Opener after some previous opener??? */
454 parser->parse_error(MUSTACHE_ERR_DANGLINGTAGOPENER,
455 mustache_err_messages[MUSTACHE_ERR_DANGLINGTAGOPENER],
456 (unsigned)current_tag.line, (unsigned)current_tag.col,
457 parser_data);
458 n_errors++;
459 current_tag.type = MUSTACHE_TAGTYPE_NONE;
460 }
461
462 current_tag.line = line;
463 current_tag.col = col;
464 current_tag.beg = off;
465 off += opener_len;
466
467 if(off < templ_size) {
468 switch(templ_data[off]) {
469 case '=': current_tag.type = MUSTACHE_TAGTYPE_DELIM; off++; break;
470 case '!': current_tag.type = MUSTACHE_TAGTYPE_COMMENT; off++; break;
471 case '{': current_tag.type = MUSTACHE_TAGTYPE_VERBATIMVAR; off++; break;
472 case '&': current_tag.type = MUSTACHE_TAGTYPE_VERBATIMVAR2; off++; break;
473 case '#': current_tag.type = MUSTACHE_TAGTYPE_OPENSECTION; off++; break;
474 case '^': current_tag.type = MUSTACHE_TAGTYPE_OPENSECTIONINV; off++; break;
475 case '/': current_tag.type = MUSTACHE_TAGTYPE_CLOSESECTION; off++; break;
476 case '>': current_tag.type = MUSTACHE_TAGTYPE_PARTIAL; off++; break;
477 default: current_tag.type = MUSTACHE_TAGTYPE_VAR; break;
478 }
479 }
480
481 while(off < templ_size && MUSTACHE_ISWHITESPACE(templ_data[off]))
482 off++;
483 current_tag.name_beg = off;
484
485 col += current_tag.name_beg - current_tag.beg;
486 } else if(is_closer && current_tag.type == MUSTACHE_TAGTYPE_NONE) {
487 /* Invalid closer. */
488 parser->parse_error(MUSTACHE_ERR_DANGLINGTAGCLOSER,
489 mustache_err_messages[MUSTACHE_ERR_DANGLINGTAGCLOSER],
490 (unsigned) line, (unsigned) col, parser_data);
491 n_errors++;
492 off++;
493 col++;
494 } else if(is_closer) {
495 /* Handle tag closer "}}" */
496
497 current_tag.name_end = off;
498 off += closer_len;
499 col += closer_len;
500 if(current_tag.type == MUSTACHE_TAGTYPE_VERBATIMVAR) {
501 /* Eat the extra '}'. Note it may be after the found
502 * closer (if closer is "}}" or before it for a custom
503 * closer. */
504 if(current_tag.name_end > current_tag.name_beg &&
505 templ_data[current_tag.name_end-1] == '}') {
506 current_tag.name_end--;
507 } else if(mustache_is_std_closer(closer, closer_len) &&
508 off < templ_size && templ_data[off] == '}') {
509 off++;
510 col++;
511 } else {
512 parser->parse_error(MUSTACHE_ERR_INCOMPATIBLETAGCLOSER,
513 mustache_err_messages[MUSTACHE_ERR_INCOMPATIBLETAGCLOSER],
514 (unsigned) line, (unsigned) col, parser_data);
515 n_errors++;
516 }
517 } else if(current_tag.type == MUSTACHE_TAGTYPE_DELIM) {
518 /* Maybe we are not really the closer. Maybe the directive
519 * does not change the closer so we are the "new closer" in
520 * something like "{{=<something> }}=}}". */
521 if(templ_data[current_tag.name_end - 1] != '=' &&
522 off + closer_len < templ_size &&
523 templ_data[off] == '=' &&
524 memcmp(templ_data + off + 1, closer, closer_len) == 0)
525 {
526 current_tag.name_end += closer_len + 1;
527 off += closer_len + 1;
528 col += closer_len + 1;
529 }
530
531 if(templ_data[current_tag.name_end - 1] != '=') {
532 parser->parse_error(MUSTACHE_ERR_INCOMPATIBLETAGCLOSER,
533 mustache_err_messages[MUSTACHE_ERR_INCOMPATIBLETAGCLOSER],
534 (unsigned) line, (unsigned) col, parser_data);
535 n_errors++;
536 } else if(current_tag.name_end > current_tag.name_beg) {
537 current_tag.name_end--; /* Consume the closer's '=' */
538 }
539 }
540
541 current_tag.end = off;
542
543 /* If the tag is standalone, expand it to consume also any
544 * preceding whitespace and also one new-line (before or after). */
545 if(current_tag.type != MUSTACHE_TAGTYPE_VAR &&
546 current_tag.type != MUSTACHE_TAGTYPE_VERBATIMVAR &&
547 current_tag.type != MUSTACHE_TAGTYPE_VERBATIMVAR2 &&
548 (current_tag.end >= templ_size || MUSTACHE_ISNEWLINE(templ_data[current_tag.end])))
549 {
550 size_t tmp_off = current_tag.beg;
551 while(tmp_off > 0 && MUSTACHE_ISWHITESPACE(templ_data[tmp_off-1]))
552 tmp_off--;
553 if(tmp_off == 0 || MUSTACHE_ISNEWLINE(templ_data[tmp_off-1])) {
554 current_tag.beg = tmp_off;
555
556 if(current_tag.end < templ_size && templ_data[current_tag.end] == '\r')
557 current_tag.end++;
558 if(current_tag.end < templ_size && templ_data[current_tag.end] == '\n')
559 current_tag.end++;
560 }
561 }
562
563 while(current_tag.name_end > current_tag.name_beg &&
564 MUSTACHE_ISWHITESPACE(templ_data[current_tag.name_end-1]))
565 current_tag.name_end--;
566
567 if(current_tag.type != MUSTACHE_TAGTYPE_COMMENT) {
568 if(current_tag.name_end <= current_tag.name_beg) {
569 parser->parse_error(MUSTACHE_ERR_NOTAGNAME,
570 mustache_err_messages[MUSTACHE_ERR_NOTAGNAME],
571 (unsigned)current_tag.line, (unsigned)current_tag.col,
572 parser_data);
573 n_errors++;
574 }
575 }
576
577 if(current_tag.type == MUSTACHE_TAGTYPE_DELIM) {
578 if(mustache_parse_delimiters(templ_data + current_tag.name_beg,
579 current_tag.name_end - current_tag.name_beg,
580 opener, &opener_len, closer, &closer_len) != 0)
581 {
582 parser->parse_error(MUSTACHE_ERR_INVALIDDELIMITERS,
583 mustache_err_messages[MUSTACHE_ERR_INVALIDDELIMITERS],
584 (unsigned)current_tag.line, (unsigned)current_tag.col,
585 parser_data);
586 n_errors++;
587 }
588
589 /* From now on, ignore this tag. */
590 current_tag.type = MUSTACHE_TAGTYPE_COMMENT;
591 }
592
593 if(current_tag.type != MUSTACHE_TAGTYPE_COMMENT) {
594 if(mustache_validate_tagname(templ_data + current_tag.name_beg,
595 current_tag.name_end - current_tag.name_beg) != 0) {
596 parser->parse_error(MUSTACHE_ERR_INVALIDTAGNAME,
597 mustache_err_messages[MUSTACHE_ERR_INVALIDTAGNAME],
598 (unsigned)current_tag.line, (unsigned)current_tag.col,
599 parser_data);
600 n_errors++;
601 }
602 }
603
604 /* Remember the tag info. */
605 if(mustache_buffer_append(&tags, &current_tag, sizeof(MUSTACHE_TAGINFO)) != 0)
606 goto err;
607
608 current_tag.type = MUSTACHE_TAGTYPE_NONE;
609 } else if(MUSTACHE_ISNEWLINE(templ_data[off])) {
610 /* Handle end of line. */
611
612 if(current_tag.type != MUSTACHE_TAGTYPE_NONE && current_tag.type != MUSTACHE_TAGTYPE_COMMENT) {
613 parser->parse_error(MUSTACHE_ERR_DANGLINGTAGOPENER,
614 mustache_err_messages[MUSTACHE_ERR_DANGLINGTAGOPENER],
615 (unsigned)current_tag.line, (unsigned)current_tag.col,
616 parser_data);
617 n_errors++;
618 current_tag.type = MUSTACHE_TAGTYPE_NONE;
619 }
620
621 /* New line may be formed by digraph "\r\n". */
622 if(templ_data[off] == '\r')
623 off++;
624 if(off < templ_size && templ_data[off] == '\n')
625 off++;
626
627 if(current_tag.type == MUSTACHE_TAGTYPE_NONE && off < templ_size) {
628 current_tag.type = MUSTACHE_TAGTYPE_INDENT;
629 current_tag.beg = off;
630 current_tag.end = off;
631 if(mustache_buffer_append(&tags, &current_tag, sizeof(MUSTACHE_TAGINFO)) != 0)
632 goto err;
633 current_tag.type = MUSTACHE_TAGTYPE_NONE;
634 }
635
636 line++;
637 col = 1;
638 } else {
639 /* Handle any other character. */
640 off++;
641 col++;
642 }
643 }
644
645 if(mustache_validate_sections(templ_data, &tags, parser, parser_data) != 0)
646 goto err;
647
648 /* Add an extra dummy tag marking end of the template. */
649 current_tag.type = MUSTACHE_TAGTYPE_NONE;
650 current_tag.beg = templ_size;
651 current_tag.end = templ_size;
652 if(mustache_buffer_append(&tags, &current_tag, sizeof(MUSTACHE_TAGINFO)) != 0)
653 goto err;
654
655 /* Success? */
656 if(n_errors == 0) {
657 *p_tags = (MUSTACHE_TAGINFO*) tags.data;
658 *p_n_tags = tags.n / sizeof(MUSTACHE_TAGINFO);
659 return 0;
660 }
661
662 /* Error path. */
663 err:
664 mustache_buffer_free(&tags);
665 *p_tags = NULL;
666 *p_n_tags = 0;
667 return -1;
668 }
669
670
671 /* The compiled template is a sequence of following instruction types.
672 * The instructions have two types of arguments:
673 * -- NUM: a number encoded with mustache_buffer_[append|insert]_num().
674 * -- STR: a string (always preceded with a NUM denoting its length).
675 */
676
677 /* Instruction denoting end of template.
678 */
679 #define MUSTACHE_OP_EXIT 0
680
681 /* Instruction for outputting a literal text.
682 *
683 * Arg #1: Length of the literal string (NUM).
684 * Arg #2: The literal string (STR).
685 */
686 #define MUSTACHE_OP_LITERAL 1
687
688 /* Instruction to resolve a tag name.
689 *
690 * Arg #1: (Relative) setjmp value (NUM).
691 * Arg #2: Count of names (NUM).
692 * Arg #3: Length of the 1st tag name (NUM).
693 * Arg #4: The tag name (STR).
694 * etc. (more names follow, up to the count in arg #2)
695 *
696 * Registers: reg_node is set to the resolved node, or NULL.
697 * reg_jmpaddr is set to address where some next instruction may
698 * want to jump on some condition.
699 */
700 #define MUSTACHE_OP_RESOLVE_setjmp 2
701
702 /* Instruction to resolve a tag name.
703 *
704 * Arg #1: Count of names (NUM).
705 * Arg #2: Length of the tag name (NUM).
706 * Arg #3: The tag name (STR).
707 * etc. (more names follow, up to the count in arg #1)
708 *
709 * Registers: reg_node is set to the resolved node, or NULL.
710 */
711 #define MUSTACHE_OP_RESOLVE 3
712
713 /* Instructions to output a node.
714 *
715 * Registers: If it is not NULL, reg_node determines the node to output.
716 * Otherwise, it is noop.
717 */
718 #define MUSTACHE_OP_OUTVERBATIM 4
719 #define MUSTACHE_OP_OUTESCAPED 5
720
721 /* Instruction to enter a node in register reg_node, i.e. to change a lookup
722 * context for resolve instructions.
723 *
724 * Registers: If it is not NULL, reg_node is pushed to the stack.
725 * Otherwise, program counter is changed to address in reg_jmpaddr.
726 */
727 #define MUSTACHE_OP_ENTER 6
728
729 /* Instruction to leave a node. The top node in the lookup context stack is
730 * popped out.
731 *
732 * Arg #1: (Relative) setjmp value (NUM) for jumping back for next loop iteration.
733 */
734 #define MUSTACHE_OP_LEAVE 7
735
736 /* Instruction to open inverted section.
737 * Note there is no MUSTACHE_OP_LEAVEINV instruction as it is noop.
738 *
739 * Registers: If reg_node is NULL, continues normally.
740 * Otherwise, program counter is changed to address in reg_jmpaddr.
741 */
742 #define MUSTACHE_OP_ENTERINV 8
743
744 /* Instruction to enter a partial.
745 *
746 * Arg #1: Length of the partial name (NUM).
747 * Arg #2: The partial name (STR).
748 * Arg #3: Length of the indentation string (NUM).
749 * Arg #4: Indentation, i.e. string composed of whitespace characters (STR).
750 */
751 #define MUSTACHE_OP_PARTIAL 9
752
753 /* Instruction to insert extra indentation (inherited from parent templates).
754 */
755 #define MUSTACHE_OP_INDENT 10
756
757
758 static int
759 mustache_compile_tagname(MUSTACHE_BUFFER* insns, const char* name, size_t size)
760 {
761 unsigned n_tokens = 1;
762 unsigned i;
763 size_t tok_beg, tok_end;
764
765 if(size == 1 && name[0] == '.') {
766 /* Implicit iterator. */
767 n_tokens = 0;
768 } else {
769 for(i = 0; i < size; i++) {
770 if(name[i] == '.')
771 n_tokens++;
772 }
773 }
774
775 if(mustache_buffer_append_num(insns, n_tokens) != 0)
776 return -1;
777
778 tok_beg = 0;
779 for(i = 0; i < n_tokens; i++) {
780 tok_end = tok_beg;
781 while(tok_end < size && name[tok_end] != '.')
782 tok_end++;
783
784 if(mustache_buffer_append_num(insns, tok_end - tok_beg) != 0)
785 return -1;
786 if(mustache_buffer_append(insns, name + tok_beg, tok_end - tok_beg) != 0)
787 return -1;
788
789 tok_beg = tok_end + 1;
790 }
791
792 return 0;
793 }
794
795 MUSTACHE_TEMPLATE*
796 mustache_compile(const char* templ_data, size_t templ_size,
797 const MUSTACHE_PARSER* parser, void* parser_data,
798 unsigned flags)
799 {
800 (void)flags;
801
802 static const MUSTACHE_PARSER default_parser = { mustache_parse_error };
803 MUSTACHE_TAGINFO* tags = NULL;
804 unsigned n_tags;
805 size_t off;
806 size_t jmp_pos;
807 MUSTACHE_TAGINFO* tag;
808 MUSTACHE_BUFFER insns = { 0 };
809 MUSTACHE_STACK jmp_pos_stack = { 0 };
810 int done = 0;
811 int success = 0;
812 size_t indent_len;
813
814 if(parser == NULL)
815 parser = &default_parser;
816
817 /* Collect all tags from the template. */
818 if(mustache_parse(templ_data, templ_size, parser, parser_data, &tags, &n_tags) != 0)
819 goto err;
820
821 /* Build the template */
822 #define APPEND(data, n) \
823 do { \
824 if(mustache_buffer_append(&insns, (data), (n)) != 0) \
825 goto err; \
826 } while(0)
827
828 #define APPEND_NUM(num) \
829 do { \
830 if(mustache_buffer_append_num(&insns, (uint64_t)(num)) != 0) \
831 goto err; \
832 } while(0)
833
834 #define APPEND_TAGNAME(tag) \
835 do { \
836 if(mustache_compile_tagname(&insns, templ_data + (tag)->name_beg, \
837 (tag)->name_end - (tag)->name_beg) != 0) \
838 goto err; \
839 } while(0)
840
841 #define INSERT_NUM(pos, num) \
842 do { \
843 if(mustache_buffer_insert_num(&insns, (pos), (uint64_t)(num)) != 0) \
844 goto err; \
845 } while(0)
846
847 #define PUSH_JMP_POS() \
848 do { \
849 if(mustache_stack_push(&jmp_pos_stack, insns.n) != 0) \
850 goto err; \
851 } while(0)
852
853 #define POP_JMP_POS() ((size_t) mustache_stack_pop(&jmp_pos_stack))
854
855 off = 0;
856 tag = &tags[0];
857 while(1) {
858 if(off < tag->beg) {
859 /* Handle literal text before the next tag. */
860 APPEND_NUM(MUSTACHE_OP_LITERAL);
861 APPEND_NUM(tag->beg - off);
862 APPEND(templ_data + off, tag->beg - off);
863 off = tag->beg;
864 }
865
866 switch(tag->type) {
867 case MUSTACHE_TAGTYPE_VAR:
868 case MUSTACHE_TAGTYPE_VERBATIMVAR:
869 case MUSTACHE_TAGTYPE_VERBATIMVAR2:
870 APPEND_NUM(MUSTACHE_OP_RESOLVE);
871 APPEND_TAGNAME(tag);
872 APPEND_NUM((tag->type == MUSTACHE_TAGTYPE_VAR) ?
873 MUSTACHE_OP_OUTESCAPED : MUSTACHE_OP_OUTVERBATIM);
874 break;
875
876 case MUSTACHE_TAGTYPE_OPENSECTION:
877 APPEND_NUM(MUSTACHE_OP_RESOLVE_setjmp);
878 PUSH_JMP_POS();
879 APPEND_TAGNAME(tag);
880 APPEND_NUM(MUSTACHE_OP_ENTER);
881 PUSH_JMP_POS();
882 break;
883
884 case MUSTACHE_TAGTYPE_CLOSESECTION:
885 APPEND_NUM(MUSTACHE_OP_LEAVE);
886 APPEND_NUM(insns.n - POP_JMP_POS());
887 jmp_pos = POP_JMP_POS();
888 INSERT_NUM(jmp_pos, insns.n - jmp_pos);
889 break;
890
891 case MUSTACHE_TAGTYPE_OPENSECTIONINV:
892 APPEND_NUM(MUSTACHE_OP_RESOLVE_setjmp);
893 PUSH_JMP_POS();
894 APPEND_TAGNAME(tag);
895 APPEND_NUM(MUSTACHE_OP_ENTERINV);
896 break;
897
898 case MUSTACHE_TAGTYPE_CLOSESECTIONINV:
899 jmp_pos = POP_JMP_POS();
900 INSERT_NUM(jmp_pos, insns.n - jmp_pos);
901 break;
902
903 case MUSTACHE_TAGTYPE_PARTIAL:
904 APPEND_NUM(MUSTACHE_OP_PARTIAL);
905 APPEND_NUM(tag->name_end - tag->name_beg);
906 APPEND(templ_data + tag->name_beg, tag->name_end - tag->name_beg);
907 indent_len = 0;
908 while(MUSTACHE_ISWHITESPACE(templ_data[tag->beg + indent_len]))
909 indent_len++;
910 APPEND_NUM(indent_len);
911 APPEND(templ_data + tag->beg, indent_len);
912 break;
913
914 case MUSTACHE_TAGTYPE_INDENT:
915 APPEND_NUM(MUSTACHE_OP_INDENT);
916 break;
917
918 case MUSTACHE_TAGTYPE_NONE:
919 APPEND_NUM(MUSTACHE_OP_EXIT);
920 done = 1;
921 break;
922
923 default:
924 break;
925 }
926
927 if(done)
928 break;
929
930 off = tag->end;
931 tag++;
932 }
933
934 success = 1;
935
936 err:
937 free(tags);
938 mustache_buffer_free(&jmp_pos_stack);
939 if(success) {
940 return (MUSTACHE_TEMPLATE*) insns.data;
941 } else {
942 mustache_buffer_free(&insns);
943 return NULL;
944 }
945 }
946
947 void
948 mustache_release(MUSTACHE_TEMPLATE* t)
949 {
950 if(t == NULL)
951 return;
952
953 free(t);
954 }
955
956
957 /**********************************
958 *** Applying Compiled Template ***
959 **********************************/
960
961 int
962 mustache_process(const MUSTACHE_TEMPLATE* t,
963 const MUSTACHE_RENDERER* renderer, void* renderer_data,
964 const MUSTACHE_DATAPROVIDER* provider, void* provider_data)
965 {
966 const uint8_t* insns = (const uint8_t*) t;
967 size_t reg_pc = 0; /* Program counter register. */
968 size_t reg_jmpaddr; /* Jump target address register. */
969 void* reg_node = NULL; /* Working node register. */
970 int done = 0;
971 MUSTACHE_STACK node_stack = { 0 };
972 MUSTACHE_STACK index_stack = { 0 };
973 MUSTACHE_STACK partial_stack = { 0 };
974 MUSTACHE_BUFFER indent_buffer = { 0 };
975 int ret = -1;
976
977 #define PUSH_NODE() \
978 do { \
979 if(mustache_stack_push(&node_stack, (uintptr_t) reg_node) != 0) \
980 goto err; \
981 } while(0)
982
983 #define POP_NODE() ((void*) mustache_stack_pop(&node_stack))
984
985 #define PEEK_NODE() ((void*) mustache_stack_peek(&node_stack))
986
987 #define PUSH_INDEX(index) \
988 do { \
989 if(mustache_stack_push(&index_stack, (uintptr_t) (index)) != 0) \
990 goto err; \
991 } while(0)
992
993 #define POP_INDEX() ((unsigned) mustache_stack_pop(&index_stack))
994
995 reg_node = provider->get_root(provider_data);
996 PUSH_NODE();
997
998 while(!done) {
999 unsigned opcode = (unsigned) mustache_decode_num(insns, reg_pc, &reg_pc);
1000
1001 switch(opcode) {
1002 case MUSTACHE_OP_LITERAL:
1003 {
1004 size_t n = (size_t) mustache_decode_num(insns, reg_pc, &reg_pc);
1005 if(renderer->out_verbatim((const char*)(insns + reg_pc), n, renderer_data) != 0)
1006 goto err;
1007 reg_pc += n;
1008 break;
1009 }
1010
1011 case MUSTACHE_OP_RESOLVE_setjmp:
1012 {
1013 size_t jmp_len = (size_t) mustache_decode_num(insns, reg_pc, &reg_pc);
1014 reg_jmpaddr = reg_pc + jmp_len;
1015 /* Pass through */
1016 }
1017
1018 case MUSTACHE_OP_RESOLVE:
1019 {
1020 unsigned n_names = (unsigned) mustache_decode_num(insns, reg_pc, &reg_pc);
1021 unsigned i;
1022
1023 if(n_names == 0) {
1024 /* Implicit iterator. */
1025 reg_node = PEEK_NODE();
1026 break;
1027 }
1028
1029 for(i = 0; i < n_names; i++) {
1030 size_t name_len = (size_t) mustache_decode_num(insns, reg_pc, &reg_pc);
1031 const char* name = (const char*)(insns + reg_pc);
1032 reg_pc += name_len;
1033
1034 if(i == 0) {
1035 void** nodes = (void**) node_stack.data;
1036 size_t n_nodes = node_stack.n / sizeof(void*);
1037
1038 while(n_nodes-- > 0) {
1039 reg_node = provider->get_child_by_name(nodes[n_nodes],
1040 name, name_len, provider_data);
1041 if(reg_node != NULL)
1042 break;
1043 }
1044 } else if(reg_node != NULL) {
1045 reg_node = provider->get_child_by_name(reg_node,
1046 name, name_len, provider_data);
1047 }
1048 }
1049 break;
1050 }
1051
1052 case MUSTACHE_OP_OUTVERBATIM:
1053 case MUSTACHE_OP_OUTESCAPED:
1054 if(reg_node != NULL) {
1055 int (*out)(const char*, size_t, void*);
1056
1057 out = (opcode == MUSTACHE_OP_OUTVERBATIM) ?
1058 renderer->out_verbatim : renderer->out_escaped;
1059 if(provider->dump(reg_node, out, renderer_data, provider_data) != 0)
1060 goto err;
1061 }
1062 break;
1063
1064 case MUSTACHE_OP_ENTER:
1065 if(reg_node != NULL) {
1066 PUSH_NODE();
1067 reg_node = provider->get_child_by_index(reg_node, 0, provider_data);
1068 if(reg_node != NULL) {
1069 PUSH_NODE();
1070 PUSH_INDEX(0);
1071 } else {
1072 (void) POP_NODE();
1073 }
1074 }
1075 if(reg_node == NULL)
1076 reg_pc = reg_jmpaddr;
1077 break;
1078
1079 case MUSTACHE_OP_LEAVE:
1080 {
1081 size_t jmp_base = reg_pc;
1082 size_t jmp_len = (size_t) mustache_decode_num(insns, reg_pc, &reg_pc);
1083 unsigned index = POP_INDEX();
1084
1085 (void) POP_NODE();
1086 reg_node = provider->get_child_by_index(PEEK_NODE(), ++index, provider_data);
1087 if(reg_node != NULL) {
1088 PUSH_NODE();
1089 PUSH_INDEX(index);
1090 reg_pc = jmp_base - jmp_len;
1091 } else {
1092 (void) POP_NODE();
1093 }
1094 break;
1095 }
1096
1097 case MUSTACHE_OP_ENTERINV:
1098 if(reg_node == NULL || provider->get_child_by_index(reg_node,
1099 0, provider_data) == NULL) {
1100 /* Resolve failed: Noop, continue normally. */
1101 } else {
1102 reg_pc = reg_jmpaddr;
1103 }
1104 break;
1105
1106 case MUSTACHE_OP_PARTIAL:
1107 {
1108 size_t name_len;
1109 const char* name;
1110 size_t indent_len;
1111 const char* indent;
1112 MUSTACHE_TEMPLATE* partial;
1113
1114 name_len = mustache_decode_num(insns, reg_pc, &reg_pc);
1115 name = (const char*) (insns + reg_pc);
1116 reg_pc += name_len;
1117
1118 indent_len = mustache_decode_num(insns, reg_pc, &reg_pc);
1119 indent = (const char*) (insns + reg_pc);
1120 reg_pc += indent_len;
1121
1122 partial = provider->get_partial(name, name_len, provider_data);
1123 if(partial != NULL) {
1124 if(mustache_stack_push(&partial_stack, (uintptr_t) insns) != 0)
1125 goto err;
1126 if(mustache_stack_push(&partial_stack, (uintptr_t) reg_pc) != 0)
1127 goto err;
1128 if(mustache_stack_push(&partial_stack, (uintptr_t) indent_len) != 0)
1129 goto err;
1130 if(mustache_buffer_append(&indent_buffer, indent, indent_len) != 0)
1131 goto err;
1132 reg_pc = 0;
1133 insns = (uint8_t*) partial;
1134 }
1135 break;
1136 }
1137
1138 case MUSTACHE_OP_INDENT:
1139 if(renderer->out_verbatim((const char*)(indent_buffer.data),
1140 indent_buffer.n, renderer_data) != 0)
1141 goto err;
1142 break;
1143
1144 case MUSTACHE_OP_EXIT:
1145 if(mustache_stack_is_empty(&partial_stack)) {
1146 done = 1;
1147 } else {
1148 size_t indent_len = (size_t) mustache_stack_pop(&partial_stack);
1149 reg_pc = (size_t) mustache_stack_pop(&partial_stack);
1150 insns = (uint8_t*) mustache_stack_pop(&partial_stack);
1151
1152 indent_buffer.n -= indent_len;
1153 }
1154 break;
1155 }
1156 }
1157
1158 /* Success. */
1159 ret = 0;
1160
1161 err:
1162 mustache_stack_free(&node_stack);
1163 mustache_stack_free(&index_stack);
1164 mustache_stack_free(&partial_stack);
1165 mustache_buffer_free(&indent_buffer);
1166 return ret;
1167 }