Mercurial > sci
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(§ion_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(§ion_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(§ion_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(§ion_stack)) { | |
334 while(!mustache_stack_is_empty(§ion_stack)) { | |
335 opener = (MUSTACHE_TAGINFO*) mustache_stack_pop(§ion_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(§ion_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, ¤t_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, ¤t_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, ¤t_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, ¤t_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, ®_pc); | |
1000 | |
1001 switch(opcode) { | |
1002 case MUSTACHE_OP_LITERAL: | |
1003 { | |
1004 size_t n = (size_t) mustache_decode_num(insns, reg_pc, ®_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, ®_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, ®_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, ®_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, ®_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, ®_pc); | |
1115 name = (const char*) (insns + reg_pc); | |
1116 reg_pc += name_len; | |
1117 | |
1118 indent_len = mustache_decode_num(insns, reg_pc, ®_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 } |