| /* |
| Copyright: Boaz Segev, 2018 |
| License: MIT |
| |
| Feel free to copy, use and enjoy according to the license provided. |
| */ |
| #ifndef H_MUSTACHE_LOADR_H |
| /** |
| * A mustache parser using a callback systems that allows this implementation to |
| * be framework agnostic (i.e., can be used with any JSON library). |
| */ |
| #define H_MUSTACHE_LOADR_H |
| |
| #ifndef _GNU_SOURCE |
| #define _GNU_SOURCE |
| #endif |
| |
| #include <ctype.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <unistd.h> |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #if !defined(__GNUC__) && !defined(__clang__) && !defined(FIO_GNUC_BYPASS) |
| #define __attribute__(...) |
| #define __has_include(...) 0 |
| #define __has_builtin(...) 0 |
| #define FIO_GNUC_BYPASS 1 |
| #elif !defined(__clang__) && !defined(__has_builtin) |
| #define __has_builtin(...) 0 |
| #define FIO_GNUC_BYPASS 1 |
| #endif |
| |
| #ifndef MUSTACHE_FUNC |
| #define MUSTACHE_FUNC static __attribute__((unused)) |
| #endif |
| |
| /* ***************************************************************************** |
| Compile Time Behavior Flags |
| ***************************************************************************** */ |
| |
| #ifndef MUSTACHE_USE_DYNAMIC_PADDING |
| #define MUSTACHE_USE_DYNAMIC_PADDING 1 |
| #endif |
| |
| #ifndef MUSTACHE_FAIL_ON_MISSING_TEMPLATE |
| #define MUSTACHE_FAIL_ON_MISSING_TEMPLATE 1 |
| #endif |
| |
| #if !defined(MUSTACHE_NESTING_LIMIT) || !MUSTACHE_NESTING_LIMIT |
| #undef MUSTACHE_NESTING_LIMIT |
| #define MUSTACHE_NESTING_LIMIT 82 |
| #endif |
| |
| /* ***************************************************************************** |
| Mustache API Argument types |
| ***************************************************************************** */ |
| |
| /** an opaque type for mustache template data (when caching). */ |
| typedef struct mustache_s mustache_s; |
| |
| /** Error reporting type. */ |
| typedef enum mustache_error_en { |
| MUSTACHE_OK, |
| MUSTACHE_ERR_TOO_DEEP, |
| MUSTACHE_ERR_CLOSURE_MISMATCH, |
| MUSTACHE_ERR_FILE_NOT_FOUND, |
| MUSTACHE_ERR_FILE_TOO_BIG, |
| MUSTACHE_ERR_FILE_NAME_TOO_LONG, |
| MUSTACHE_ERR_FILE_NAME_TOO_SHORT, |
| MUSTACHE_ERR_EMPTY_TEMPLATE, |
| MUSTACHE_ERR_DELIMITER_TOO_LONG, |
| MUSTACHE_ERR_NAME_TOO_LONG, |
| MUSTACHE_ERR_UNKNOWN, |
| MUSTACHE_ERR_USER_ERROR, |
| } mustache_error_en; |
| |
| /** Arguments for the `mustache_load` function, used by the mustache parser. */ |
| typedef struct { |
| /** The root template's file name. */ |
| char const *filename; |
| /** The file name's length. */ |
| size_t filename_len; |
| /** If data and data_len are set, they will be used as the file's contents. */ |
| char const *data; |
| /** If data and data_len are set, they will be used as the file's contents. */ |
| size_t data_len; |
| /** Parsing error reporting (can be NULL). */ |
| mustache_error_en *err; |
| } mustache_load_args_s; |
| |
| /* ***************************************************************************** |
| REQUIRED: Define INCLUDE_MUSTACHE_IMPLEMENTATION only in the implementation file |
| ***************************************************************************** */ |
| |
| /** |
| * In non-implementation files, don't define the INCLUDE_MUSTACHE_IMPLEMENTATION |
| * macro. |
| * |
| * Before including the header within an implementation faile, define |
| * INCLUDE_MUSTACHE_IMPLEMENTATION as 1. |
| */ |
| #if INCLUDE_MUSTACHE_IMPLEMENTATION |
| |
| /* ***************************************************************************** |
| Mustache API Functions and Arguments |
| ***************************************************************************** */ |
| |
| MUSTACHE_FUNC mustache_s *mustache_load(mustache_load_args_s args); |
| |
| #define mustache_load(...) mustache_load((mustache_load_args_s){__VA_ARGS__}) |
| |
| /** free the mustache template */ |
| inline MUSTACHE_FUNC void mustache_free(mustache_s *mustache) { |
| free(mustache); |
| } |
| |
| /** Arguments for the `mustache_build` function. */ |
| typedef struct { |
| /** The parsed template (an instruction collection). */ |
| mustache_s *mustache; |
| /** Opaque user data (recommended for input review) - children will inherit |
| * the parent's udata value. Updated values will propegate to child sections |
| * but won't effect parent sections. |
| */ |
| void *udata1; |
| /** Opaque user data (recommended for output handling)- children will inherit |
| * the parent's udata value. Updated values will propegate to child sections |
| * but won't effect parent sections. |
| */ |
| void *udata2; |
| /** Formatting error reporting (can be NULL). */ |
| mustache_error_en *err; |
| } mustache_build_args_s; |
| MUSTACHE_FUNC int mustache_build(mustache_build_args_s args); |
| |
| #define mustache_build(mustache_s_ptr, ...) \ |
| mustache_build( \ |
| (mustache_build_args_s){.mustache = (mustache_s_ptr), __VA_ARGS__}) |
| |
| /* ***************************************************************************** |
| Callbacks Types - types used by the template builder callbacks |
| ***************************************************************************** */ |
| |
| /** |
| * A mustache section allows the callbacks to "walk" backwards towards the root |
| * in search of argument data. |
| * |
| * Note that every section is allowed a separate udata value. |
| */ |
| typedef struct mustache_section_s { |
| /** Opaque user data (recommended for input review) - children will inherit |
| * the parent's udata value. Updated values will propegate to child sections |
| * but won't effect parent sections. |
| */ |
| void *udata1; |
| /** Opaque user data (recommended for output handling)- children will inherit |
| * the parent's udata value. Updated values will propegate to child sections |
| * but won't effect parent sections. |
| */ |
| void *udata2; |
| } mustache_section_s; |
| |
| /* ***************************************************************************** |
| Callbacks Helpers - These functions can be called from within callbacks |
| ***************************************************************************** */ |
| |
| /** |
| * Returns the section's parent for nested sections, or NULL (for root section). |
| * |
| * This allows the `udata` values in the parent to be accessed and can be used, |
| * for example, when seeking a value (argument / keyword) within a nested data |
| * structure such as a Hash. |
| */ |
| static inline mustache_section_s * |
| mustache_section_parent(mustache_section_s *section); |
| |
| /** |
| * This helper function should be used to write text to the output |
| * stream form within `mustache_on_arg` or `mustache_on_section_test`. |
| * |
| * This function will call the `mustache_on_text` callback for each slice of |
| * text that requires padding and for escaped data. |
| * |
| * `mustache_on_text` must NEVER call this function. |
| */ |
| static inline int mustache_write_text(mustache_section_s *section, char *text, |
| uint32_t len, uint8_t escape); |
| |
| /** |
| * Returns the section's unparsed content as a non-NUL terminated byte array. |
| * |
| * The length of the data will be placed in the `size_t` variable pointed to by |
| * `p_len`. Do NOT use `strlen`, since the data isn't NUL terminated. |
| * |
| * This allows text to be accessed when a section's content is, in fact, meant |
| * to be passed along to a function / lambda. |
| */ |
| static inline const char *mustache_section_text(mustache_section_s *section, |
| size_t *p_len); |
| |
| /* ***************************************************************************** |
| Client Callbacks - MUST be implemented by the including file |
| ***************************************************************************** */ |
| |
| /** |
| * Called when an argument name was detected in the current section. |
| * |
| * A conforming implementation will search for the named argument both in the |
| * existing section and all of it's parents (walking backwards towards the root) |
| * until a value is detected. |
| * |
| * A missing value should be treated the same as an empty string. |
| * |
| * A conforming implementation will output the named argument's value (either |
| * HTML escaped or not, depending on the `escape` flag) as a string. |
| * |
| * A conforming implementation will test for dot notation in the name. |
| * |
| * NOTE: the `name` data is **not** NUL terminated. Use the `name_len` data to |
| * determine the actual string length. |
| */ |
| static int mustache_on_arg(mustache_section_s *section, const char *name, |
| uint32_t name_len, unsigned char escape); |
| |
| /** |
| * Called when simple template text (string) is detected. |
| * |
| * A conforming implementation will output data as a string (no escaping). |
| * |
| * NOTE: the `data` is **not** NUL terminated. Use the `data_len` data to |
| * determine the actual data length. |
| */ |
| static int mustache_on_text(mustache_section_s *section, const char *data, |
| uint32_t data_len); |
| |
| /** |
| * Called for nested sections, must return the number of objects in the new |
| * subsection (depending on the argument's name). |
| * |
| * Arrays should return the number of objects in the array. |
| * |
| * `true` values should return 1. |
| * |
| * `false` values should return 0. |
| * |
| * A return value of -1 will stop processing with an error. |
| * |
| * Please note, this will handle both normal and inverted sections. |
| * |
| * The `callable` value is true if the section is allowed to be a function / |
| * callback. If the section object represent a function / callback (lambda), the |
| * lambda should be called and the `mustache_on_section_test` callback should |
| * return 0. |
| * |
| * NOTE: the `name` data is **not** NUL terminated. Use the `name_len` data to |
| * determine the actual string length. |
| * |
| * A conforming implementation will test for dot notation in the name. |
| */ |
| static int32_t mustache_on_section_test(mustache_section_s *section, |
| const char *name, uint32_t name_len, |
| uint8_t callable); |
| |
| /** |
| * Called when entering a nested section. |
| * |
| * `index` is a zero based index indicating the number of repetitions that |
| * occurred so far (same as the array index for arrays). |
| * |
| * A return value of -1 will stop processing with an error. |
| * |
| * Note: this is a good time to update the subsection's `udata` with the value |
| * of the array index. The `udata` will always contain the value or the parent's |
| * `udata`. |
| * |
| * NOTE: the `name` data is **not** NUL terminated. Use the `name_len` data to |
| * determine the actual string length. |
| * |
| * A conforming implementation will test for dot notation in the name. |
| */ |
| static int mustache_on_section_start(mustache_section_s *section, |
| char const *name, uint32_t name_len, |
| uint32_t index); |
| |
| /** |
| * Called for cleanup in case of error. |
| */ |
| static void mustache_on_formatting_error(void *udata1, void *udata2); |
| |
| /* ***************************************************************************** |
| |
| IMPLEMENTATION (beware: monolithic functions ahead) |
| |
| ***************************************************************************** */ |
| |
| /* ***************************************************************************** |
| Internal types |
| ***************************************************************************** */ |
| |
| struct mustache_s { |
| /* The number of instructions in the engine */ |
| union { |
| void *read_only_pt; /* ensure pointer wide padding */ |
| struct { |
| uint32_t intruction_count; |
| uint32_t data_length; |
| } read_only; |
| } u; |
| }; |
| |
| typedef struct mustache__instruction_s { |
| enum { |
| MUSTACHE_WRITE_TEXT, |
| MUSTACHE_WRITE_ARG, |
| MUSTACHE_WRITE_ARG_UNESCAPED, |
| MUSTACHE_SECTION_START, |
| MUSTACHE_SECTION_START_INV, |
| MUSTACHE_SECTION_END, |
| MUSTACHE_SECTION_GOTO, |
| MUSTACHE_PADDING_PUSH, |
| MUSTACHE_PADDING_POP, |
| MUSTACHE_PADDING_WRITE, |
| } instruction; |
| /** the data the instruction acts upon */ |
| struct { |
| /** The length instruction block in the instruction array (for sections). */ |
| uint32_t end; |
| /** The length of the (string) data. */ |
| uint32_t len; |
| /** The offset from the beginning of the data segment. */ |
| uint32_t name_pos; |
| /** The offset between the name (start) and content (for sections). */ |
| uint16_t name_len; |
| /** The offset between the name and the content (left / right by type). */ |
| uint16_t offset; |
| } data; |
| } mustache__instruction_s; |
| |
| typedef struct { |
| mustache_section_s sec; /* client visible section data */ |
| uint32_t start; /* section start instruction position */ |
| uint32_t end; /* instruction to jump to after completion */ |
| uint32_t index; /* zero based index for section loops */ |
| uint32_t count; /* the number of times the section should be performed */ |
| uint16_t frame; /* the stack frame's index (zero based) */ |
| } mustache__section_stack_frame_s; |
| |
| /* |
| * The stack memory is placed in a structure to allow stack unrolling and |
| * avoiding recursion with it's stack overflow risks. |
| */ |
| typedef struct { |
| mustache_s *data; /* the mustache template being built */ |
| uint32_t pos; /* the instruction postision index */ |
| uint32_t padding; /* padding instruction position */ |
| uint16_t index; /* the stack postision index */ |
| mustache__section_stack_frame_s stack[MUSTACHE_NESTING_LIMIT]; |
| } mustache__builder_stack_s; |
| |
| #define MUSTACHE_DELIMITER_LENGTH_LIMIT 5 |
| |
| /* |
| * The stack memory is placed in a structure to allow stack unrolling and |
| * avoiding recursion with it's stack overflow risks. |
| */ |
| typedef struct { |
| mustache_s *m; |
| mustache__instruction_s *i; |
| mustache_error_en *err; |
| char *data; |
| char *path; |
| uint32_t i_capa; |
| uint32_t data_len; |
| uint32_t padding; |
| uint16_t path_len; |
| uint16_t path_capa; |
| uint16_t index; /* stack index */ |
| struct { |
| uint32_t data_start; /* template starting position (with header) */ |
| uint32_t data_pos; /* data reading position (how much was consumed) */ |
| uint32_t data_end; /* data ending position (for this template) */ |
| uint16_t open_sections; /* counts open sections waiting for closure */ |
| char del_start[MUSTACHE_DELIMITER_LENGTH_LIMIT]; /* currunt instruction |
| start delimiter */ |
| char del_end[MUSTACHE_DELIMITER_LENGTH_LIMIT]; /* currunt instruction end |
| delimiter */ |
| uint8_t del_start_len; /* currunt start delimiter length */ |
| uint8_t del_end_len; /* currunt end delimiter length */ |
| } stack[MUSTACHE_NESTING_LIMIT]; |
| } mustache__loader_stack_s; |
| |
| /* ***************************************************************************** |
| Callbacks Helper Implementation |
| ***************************************************************************** */ |
| |
| #define MUSTACH2INSTRUCTIONS(mustache) \ |
| ((mustache__instruction_s *)((mustache) + 1)) |
| #define MUSTACH2DATA(mustache) \ |
| (char *)(MUSTACH2INSTRUCTIONS((mustache)) + \ |
| (mustache)->u.read_only.intruction_count) |
| #define MUSTACHE_OBJECT_OFFSET(type, member, ptr) \ |
| ((type *)((uintptr_t)(ptr) - (uintptr_t)(&(((type *)0)->member)))) |
| |
| #define MUSTACHE_ASSERT(cond, msg) \ |
| if (!(cond)) { \ |
| perror("FATAL ERROR: " msg); \ |
| exit(errno); \ |
| } |
| #define MUSTACHE_IGNORE_WHITESPACE(str, step) \ |
| while (isspace(*(str))) { \ |
| (str) += (step); \ |
| } |
| |
| /* |
| * used internally by some of the helpers to find convert a section pointer to |
| * the full builder stack data. |
| */ |
| static inline mustache__builder_stack_s * |
| mustache___section2stack(mustache_section_s *section) { |
| mustache__section_stack_frame_s *f = |
| (mustache__section_stack_frame_s *)section; |
| return MUSTACHE_OBJECT_OFFSET(mustache__builder_stack_s, stack, |
| (f - f->frame)); |
| } |
| |
| /** |
| * Returns the section's parent for nested sections, or NULL (for root section). |
| * |
| * This allows the `udata` values in the parent to be accessed and can be used, |
| * for example, when seeking a value (argument / keyword) within a nested data |
| * structure such as a Hash. |
| */ |
| static inline mustache_section_s * |
| mustache_section_parent(mustache_section_s *section) { |
| mustache_section_s tmp = *section; |
| mustache__section_stack_frame_s *f = |
| (mustache__section_stack_frame_s *)section; |
| while (f->frame) { |
| --f; |
| if (tmp.udata1 != f->sec.udata1 || tmp.udata2 != f->sec.udata2) |
| return &f->sec; |
| } |
| return NULL; |
| } |
| |
| /** |
| * Returns the section's unparsed content as a non-NUL terminated byte array. |
| * |
| * The length of the data will be placed in the `size_t` variable pointed to by |
| * `p_len`. Do NOT use `strlen`, since the data isn't NUL terminated. |
| * |
| * This allows text to be accessed when a section's content is, in fact, meant |
| * to be passed along to a function / lambda. |
| */ |
| static inline const char *mustache_section_text(mustache_section_s *section, |
| size_t *p_len) { |
| if (!section || !p_len) |
| goto error; |
| mustache__builder_stack_s *s = mustache___section2stack(section); |
| mustache__instruction_s *inst = |
| MUSTACH2INSTRUCTIONS(s->data) + s->pos; /* current instruction*/ |
| if (inst->instruction != MUSTACHE_SECTION_START) |
| goto error; |
| const char *start = |
| MUSTACH2DATA(s->data) + inst->data.name_pos + inst->data.offset; |
| *p_len = inst->data.len; |
| return start; |
| error: |
| *p_len = 0; |
| return NULL; |
| } |
| |
| /** |
| * used internally to write escaped text rather than clear text. |
| */ |
| static inline int mustache__write_padding(mustache__builder_stack_s *s) { |
| mustache__instruction_s *const inst = MUSTACH2INSTRUCTIONS(s->data); |
| char *const data = MUSTACH2DATA(s->data); |
| for (uint32_t i = s->padding; i; i = inst[i].data.end) { |
| if (mustache_on_text(&s->stack[s->index].sec, data + inst[i].data.name_pos, |
| inst[i].data.name_len) == -1) |
| return -1; |
| } |
| return 0; |
| } |
| |
| /** |
| * used internally to write escaped text rather than clear text. |
| */ |
| static int mustache__write_escaped(mustache__builder_stack_s *s, char *text, |
| uint32_t len) { |
| #define MUSTACHE_ESCAPE_BUFFER_SIZE 4096 |
| /** HTML ecape table, created using the following Ruby Script: |
| |
| a = (0..255).to_a.map {|i| i.chr } |
| 100.times {|i| a[i] = "&\##{i.to_s(10)};"} |
| ('a'.ord..'z'.ord).each {|i| a[i] = i.chr } |
| ('A'.ord..'Z'.ord).each {|i| a[i] = i.chr } |
| ('0'.ord..'9'.ord).each {|i| a[i] = i.chr } |
| a['<'.ord] = "<" |
| a['>'.ord] = ">" |
| a['&'.ord] = "&" |
| a['"'.ord] = """ |
| a["\'".ord] = "'" |
| a['|'.ord] = "&\##{'|'.ord.to_s(10)};" |
| |
| b = a.map {|s| s.length } |
| puts "static char *html_escape_strs[] = {", |
| a.to_s.slice(1..-2) ,"};", |
| "static uint8_t html_escape_len[] = {", |
| b.to_s.slice(1..-2),"};" |
| */ |
| static const char *html_escape_strs[] = { |
| "�", "", "", "", "", "", "", "", |
| "", "	", " ", "", "", " ", "", "", |
| "", "", "", "", "", "", "", "", |
| "", "", "", "", "", "", "", "", |
| " ", "!", """, "#", "$", "%", "&", "'", |
| "(", ")", "*", "+", ",", "-", ".", "/", |
| "0", "1", "2", "3", "4", "5", "6", "7", |
| "8", "9", ":", ";", "<", "=", ">", "?", |
| "@", "A", "B", "C", "D", "E", "F", "G", |
| "H", "I", "J", "K", "L", "M", "N", "O", |
| "P", "Q", "R", "S", "T", "U", "V", "W", |
| "X", "Y", "Z", "[", "\", "]", "^", "_", |
| "`", "a", "b", "c", "d", "e", "f", "g", |
| "h", "i", "j", "k", "l", "m", "n", "o", |
| "p", "q", "r", "s", "t", "u", "v", "w", |
| "x", "y", "z", "{", "|", "}", "~", "\x7F", |
| "\x80", "\x81", "\x82", "\x83", "\x84", "\x85", "\x86", "\x87", |
| "\x88", "\x89", "\x8A", "\x8B", "\x8C", "\x8D", "\x8E", "\x8F", |
| "\x90", "\x91", "\x92", "\x93", "\x94", "\x95", "\x96", "\x97", |
| "\x98", "\x99", "\x9A", "\x9B", "\x9C", "\x9D", "\x9E", "\x9F", |
| "\xA0", "\xA1", "\xA2", "\xA3", "\xA4", "\xA5", "\xA6", "\xA7", |
| "\xA8", "\xA9", "\xAA", "\xAB", "\xAC", "\xAD", "\xAE", "\xAF", |
| "\xB0", "\xB1", "\xB2", "\xB3", "\xB4", "\xB5", "\xB6", "\xB7", |
| "\xB8", "\xB9", "\xBA", "\xBB", "\xBC", "\xBD", "\xBE", "\xBF", |
| "\xC0", "\xC1", "\xC2", "\xC3", "\xC4", "\xC5", "\xC6", "\xC7", |
| "\xC8", "\xC9", "\xCA", "\xCB", "\xCC", "\xCD", "\xCE", "\xCF", |
| "\xD0", "\xD1", "\xD2", "\xD3", "\xD4", "\xD5", "\xD6", "\xD7", |
| "\xD8", "\xD9", "\xDA", "\xDB", "\xDC", "\xDD", "\xDE", "\xDF", |
| "\xE0", "\xE1", "\xE2", "\xE3", "\xE4", "\xE5", "\xE6", "\xE7", |
| "\xE8", "\xE9", "\xEA", "\xEB", "\xEC", "\xED", "\xEE", "\xEF", |
| "\xF0", "\xF1", "\xF2", "\xF3", "\xF4", "\xF5", "\xF6", "\xF7", |
| "\xF8", "\xF9", "\xFA", "\xFB", "\xFC", "\xFD", "\xFE", "\xFF"}; |
| static const uint8_t html_escape_len[] = { |
| 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, |
| 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 4, 5, 4, 5, 5, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 5, 5, |
| 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; |
| char buffer[MUSTACHE_ESCAPE_BUFFER_SIZE]; |
| size_t pos = 0; |
| const char *end = text + len; |
| while (text < end) { |
| if (MUSTACHE_USE_DYNAMIC_PADDING && *text == '\n' && s->padding) { |
| buffer[pos++] = '\n'; |
| buffer[pos] = 0; |
| if (mustache_on_text(&s->stack[s->index].sec, buffer, pos) == -1) |
| return -1; |
| pos = 0; |
| if (mustache__write_padding(s) == -1) |
| return -1; |
| } else { |
| memcpy(buffer + pos, html_escape_strs[(uint8_t)text[0]], |
| html_escape_len[(uint8_t)text[0]]); |
| pos += html_escape_len[(uint8_t)text[0]]; |
| if (pos >= (MUSTACHE_ESCAPE_BUFFER_SIZE - 6)) { |
| buffer[pos] = 0; |
| if (mustache_on_text(&s->stack[s->index].sec, buffer, pos) == -1) |
| return -1; |
| pos = 0; |
| } |
| } |
| ++text; |
| } |
| if (pos) { |
| buffer[pos] = 0; |
| if (mustache_on_text(&s->stack[s->index].sec, buffer, pos) == -1) |
| return -1; |
| } |
| return 0; |
| #undef MUSTACHE_ESCAPE_BUFFER_SIZE |
| } |
| /** |
| * This helper function should be used to write text to the output |
| * stream (often used within the `mustache_on_arg` callback). |
| */ |
| static inline int mustache_write_text(mustache_section_s *section, char *text, |
| uint32_t len, uint8_t escape) { |
| mustache__builder_stack_s *s = mustache___section2stack(section); |
| if (escape) |
| return mustache__write_escaped(s, text, len); |
| /* TODO */ |
| #if MUSTACHE_USE_DYNAMIC_PADDING |
| char *end = memchr(text, '\n', len); |
| while (len && end) { |
| ++end; |
| const uint32_t slice_len = end - text; |
| if (mustache_on_text(&s->stack[s->index].sec, text, slice_len) == -1) |
| return -1; |
| text = end; |
| len -= slice_len; |
| end = memchr(text, '\n', len); |
| if (mustache__write_padding(s) == -1) |
| return -1; |
| } |
| if (len && mustache_on_text(&s->stack[s->index].sec, text, len) == -1) |
| return -1; |
| #else |
| if (mustache_on_text(&s->stack[s->index].sec, text, len) == -1) |
| return -1; |
| |
| #endif |
| return 0; |
| } |
| |
| /* ***************************************************************************** |
| Internal Helpers |
| ***************************************************************************** */ |
| |
| /* the data segment's new length after loading the the template */ |
| /* The data segments includes a template header: */ |
| /* | 4 bytes template start instruction position | */ |
| /* | 2 bytes template name | 4 bytes next template position | */ |
| /* | template name (filename) | ...[template data]... */ |
| /* this allows template data to be reused when repeating a template */ |
| |
| /** a template file data segment header */ |
| typedef struct { |
| const char *filename; /* template file name */ |
| uint32_t inst_start; /* start position for instructions */ |
| uint32_t next; /* next template position (absolute) */ |
| uint16_t filename_len; /* file name length */ |
| uint16_t path_len; /* if the file is in a folder, this marks the '/' */ |
| } mustache__data_segment_s; |
| |
| /* data segment serialization, returns the number of bytes written. */ |
| static inline size_t |
| mustache__data_segment_write(uint8_t *dest, mustache__data_segment_s data) { |
| dest[0] = 0xFF & data.inst_start; |
| dest[1] = 0xFF & (data.inst_start >> 1); |
| dest[2] = 0xFF & (data.inst_start >> 2); |
| dest[3] = 0xFF & (data.inst_start >> 3); |
| dest[4] = 0xFF & data.next; |
| dest[5] = 0xFF & (data.next >> 1); |
| dest[6] = 0xFF & (data.next >> 2); |
| dest[7] = 0xFF & (data.next >> 3); |
| dest[8] = 0xFF & data.filename_len; |
| dest[9] = 0xFF & (data.filename_len >> 1); |
| dest[10] = 0xFF & data.path_len; |
| dest[11] = 0xFF & (data.path_len >> 1); |
| if (data.filename_len) |
| memcpy(dest + 12, data.filename, data.filename_len); |
| (dest + 12)[data.filename_len] = 0; |
| return 13 + data.filename_len; |
| } |
| |
| static inline size_t mustache__data_segment_length(size_t filename_len) { |
| return 13 + filename_len; |
| } |
| |
| /* data segment serialization, reads data from raw stream. */ |
| static inline mustache__data_segment_s |
| mustache__data_segment_read(uint8_t *data) { |
| mustache__data_segment_s s = { |
| .filename = (char *)(data + 12), |
| .inst_start = ((uint32_t)data[0] | ((uint32_t)data[1] << 1) | |
| ((uint32_t)data[2] << 2) | ((uint32_t)data[3] << 3)), |
| .next = ((uint32_t)data[4] | ((uint32_t)data[5] << 1) | |
| ((uint32_t)data[6] << 2) | ((uint32_t)data[7] << 3)), |
| .filename_len = ((uint16_t)data[8] | ((uint16_t)data[9] << 1)), |
| .path_len = ((uint16_t)data[10] | ((uint16_t)data[11] << 1)), |
| }; |
| return s; |
| } |
| |
| static inline void mustache__stand_alone_adjust(mustache__loader_stack_s *s, |
| uint32_t stand_alone) { |
| if (!stand_alone) |
| return; |
| /* adjust reading position */ |
| s->stack[s->index].data_pos += |
| 1 + (s->data[s->stack[s->index].data_pos] == '\r'); |
| /* remove any padding from the beginning of the line */ |
| if (s->m->u.read_only.intruction_count && |
| s->i[s->m->u.read_only.intruction_count - 1].instruction == |
| MUSTACHE_WRITE_TEXT) { |
| mustache__instruction_s *ins = |
| s->i + s->m->u.read_only.intruction_count - 1; |
| if (ins->data.name_len <= (stand_alone >> 1)) |
| --s->m->u.read_only.intruction_count; |
| else |
| ins->data.name_len -= (stand_alone >> 1); |
| } |
| } |
| |
| /* pushes an instruction to the instruction array */ |
| static inline int mustache__instruction_push(mustache__loader_stack_s *s, |
| mustache__instruction_s inst) { |
| if (s->m->u.read_only.intruction_count >= INT32_MAX) |
| goto instructions_too_long; |
| if (s->i_capa <= s->m->u.read_only.intruction_count) { |
| s->m = realloc(s->m, sizeof(*s->m) + (sizeof(*s->i) * (s->i_capa + 32))); |
| MUSTACHE_ASSERT(s->m, "Mustache memory allocation failed"); |
| s->i_capa += 32; |
| s->i = MUSTACH2INSTRUCTIONS(s->m); |
| } |
| s->i[s->m->u.read_only.intruction_count] = inst; |
| ++s->m->u.read_only.intruction_count; |
| return 0; |
| instructions_too_long: |
| *s->err = MUSTACHE_ERR_TOO_DEEP; |
| return -1; |
| } |
| |
| /* pushes text and padding instruction to the instruction array */ |
| static inline int mustache__push_text_instruction(mustache__loader_stack_s *s, |
| uint32_t pos, uint32_t len) { |
| /* always push padding instructions, in case or recursion with padding */ |
| // if (!s->padding) { |
| // /* no padding, push text, as is */ |
| // return mustache__instruction_push( |
| // s, (mustache__instruction_s){ |
| // .instruction = MUSTACHE_WRITE_TEXT, |
| // .data = {.name_pos = pos, .name_len = len}, |
| // }); |
| // } |
| /* insert padding instruction after each new line */ |
| for (;;) { |
| /* seek new line markers */ |
| char *start = s->data + pos; |
| char *end = memchr(s->data + pos, '\n', len); |
| if (!end) |
| break; |
| /* offset marks the line's length */ |
| const size_t offset = (end - start) + 1; |
| /* push text and padding instructions */ |
| if (mustache__instruction_push( |
| s, |
| (mustache__instruction_s){ |
| .instruction = MUSTACHE_WRITE_TEXT, |
| .data = {.name_pos = pos, .name_len = offset}, |
| }) == -1 || |
| mustache__instruction_push(s, (mustache__instruction_s){ |
| .instruction = MUSTACHE_PADDING_WRITE, |
| }) == -1) |
| return -1; |
| pos += offset; |
| len -= offset; |
| } |
| /* done? */ |
| if (!len) |
| return 0; |
| /* write any text that doesn't terminate in the EOL marker */ |
| return mustache__instruction_push( |
| s, (mustache__instruction_s){ |
| .instruction = MUSTACHE_WRITE_TEXT, |
| .data = {.name_pos = pos, .name_len = len}, |
| }); |
| } |
| |
| /* |
| * Returns the instruction's position if the template is already existing. |
| * |
| * Returns `(uint32_t)-1` if the template is missing (not loaded, yet). |
| */ |
| static inline uint32_t mustache__file_is_loaded(mustache__loader_stack_s *s, |
| char *name, size_t name_len) { |
| char *data = s->data; |
| const char *end = data + s->m->u.read_only.data_length; |
| while (data < end) { |
| mustache__data_segment_s seg = mustache__data_segment_read((uint8_t *)data); |
| if (seg.filename_len == name_len && !memcmp(seg.filename, name, name_len)) |
| return seg.inst_start; |
| data += seg.next; |
| } |
| return (uint32_t)-1; |
| } |
| |
| static inline ssize_t mustache__load_data(mustache__loader_stack_s *s, |
| const char *name, size_t name_len, |
| const char *data, size_t data_len) { |
| const size_t old_len = s->data_len; |
| if (old_len + data_len > UINT32_MAX) |
| goto too_long; |
| s->data = realloc(s->data, old_len + data_len + |
| mustache__data_segment_length(name_len) + 1); |
| MUSTACHE_ASSERT(s->data, |
| "failed to allocate memory for mustache template data"); |
| size_t path_len = name_len; |
| while (path_len) { |
| --path_len; |
| if (name[path_len] == '/' || name[path_len] == '\\') { |
| ++path_len; |
| break; |
| } |
| } |
| mustache__data_segment_write( |
| (uint8_t *)s->data + old_len, |
| (mustache__data_segment_s){ |
| .filename = name, |
| .filename_len = name_len, |
| .inst_start = s->m->u.read_only.intruction_count, |
| .next = |
| s->data_len + data_len + mustache__data_segment_length(name_len), |
| .path_len = path_len, |
| }); |
| if (data) { |
| memcpy(s->data + old_len + mustache__data_segment_length(name_len), data, |
| data_len); |
| } |
| s->data_len += data_len + mustache__data_segment_length(name_len); |
| s->data[s->data_len] = 0; |
| s->m->u.read_only.data_length = s->data_len; |
| |
| mustache__instruction_push( |
| s, (mustache__instruction_s){.instruction = MUSTACHE_SECTION_START}); |
| /* advance stack frame */ |
| ++s->index; |
| if (s->index >= MUSTACHE_NESTING_LIMIT) |
| goto too_long; |
| s->stack[s->index].data_pos = |
| old_len + mustache__data_segment_length(name_len); |
| s->stack[s->index].data_start = old_len; |
| s->stack[s->index].data_end = s->data_len; |
| /* reset delimiters */ |
| s->stack[s->index].del_start_len = 2; |
| s->stack[s->index].del_end_len = 2; |
| s->stack[s->index].del_start[0] = s->stack[s->index].del_start[1] = '{'; |
| s->stack[s->index].del_start[2] = 0; |
| s->stack[s->index].del_end[0] = s->stack[s->index].del_end[1] = '}'; |
| s->stack[s->index].del_end[2] = 0; |
| s->stack[s->index].open_sections = 0; |
| return data_len; |
| too_long: |
| *s->err = MUSTACHE_ERR_TOO_DEEP; |
| return -1; |
| } |
| |
| static inline ssize_t mustache__load_file(mustache__loader_stack_s *s, |
| const char *name, size_t name_len) { |
| struct stat f_data; |
| uint16_t i = s->index; |
| uint32_t old_path_len = 0; |
| if (!name_len) { |
| goto name_missing_error; |
| } |
| if (name_len >= 8192) |
| goto name_length_error; |
| /* test file names by walking the stack backwards and matching paths */ |
| do { |
| mustache__data_segment_s seg; |
| if (s->data) |
| seg = mustache__data_segment_read((uint8_t *)s->data + |
| s->stack[i].data_start); |
| else |
| seg = (mustache__data_segment_s){ |
| .path_len = 0, |
| }; |
| /* did we test this path for the file? */ |
| if (old_path_len && (old_path_len == seg.path_len && |
| !memcmp(s->path, seg.filename, old_path_len))) { |
| continue; |
| } |
| old_path_len = seg.path_len; |
| /* make sure s->path capacity is enough. */ |
| if (s->path_capa < seg.path_len + name_len + 10) { |
| s->path = realloc(s->path, seg.path_len + name_len + 10); |
| MUSTACHE_ASSERT(s->path, |
| "failed to allocate memory for mustache template data"); |
| s->path_capa = seg.path_len + name_len + 10; |
| } |
| /* if testing local folder, there's no need to keep looping */ |
| if (!seg.path_len) { |
| i = 1; |
| } else { |
| memcpy(s->path, seg.filename, seg.path_len); |
| } |
| /* copy name to path */ |
| memcpy(s->path + seg.path_len, name, name_len); |
| s->path[name_len + seg.path_len] = 0; |
| /* test if file exists */ |
| if (!stat(s->path, &f_data) && S_ISREG(f_data.st_mode)) { |
| old_path_len = name_len + seg.path_len; |
| goto file_found; |
| } |
| /* add default extension */ |
| memcpy(s->path + seg.path_len + name_len, ".mustache", 9); |
| s->path[name_len + seg.path_len + 9] = 0; |
| /* test if new filename file exists */ |
| if (!stat(s->path, &f_data) && S_ISREG(f_data.st_mode)) { |
| old_path_len = name_len + seg.path_len + 9; |
| goto file_found; |
| } |
| } while (--i); |
| |
| /* test if the file is "virtual" (only true for the first template loaded) */ |
| if (s->data) { |
| mustache__data_segment_s seg = |
| mustache__data_segment_read((uint8_t *)s->data); |
| if (seg.filename_len == name_len && !memcmp(seg.filename, name, name_len)) { |
| /* this name points to the original (root) template, and it's virtual */ |
| if (mustache__instruction_push( |
| s, (mustache__instruction_s){ |
| .instruction = MUSTACHE_SECTION_GOTO, |
| .data = |
| { |
| .len = 0, |
| .end = s->m->u.read_only.intruction_count, |
| }, |
| })) |
| goto unknown_error; |
| return 0; |
| } |
| } |
| |
| #if MUSTACHE_FAIL_ON_MISSING_TEMPLATE |
| *s->err = MUSTACHE_ERR_FILE_NOT_FOUND; |
| return -1; |
| #else |
| return 0; |
| #endif |
| |
| file_found: |
| if (f_data.st_size >= INT32_MAX) { |
| goto file_too_big; |
| } else if (f_data.st_size == 0) { |
| /* empty, do nothing */ |
| return 0; |
| } else { |
| /* test if the file was previously loaded */ |
| uint32_t pre_existing = mustache__file_is_loaded(s, s->path, old_path_len); |
| if (pre_existing != (uint32_t)-1) { |
| if (mustache__instruction_push( |
| s, (mustache__instruction_s){ |
| .instruction = MUSTACHE_SECTION_GOTO, |
| .data = |
| { |
| .len = pre_existing, |
| .end = s->m->u.read_only.intruction_count, |
| }, |
| })) { |
| goto unknown_error; |
| } |
| return 0; |
| } |
| } |
| if (mustache__load_data(s, s->path, old_path_len, NULL, f_data.st_size) == -1) |
| goto unknown_error; |
| int fd = open(s->path, O_RDONLY); |
| if (fd == -1) |
| goto file_err; |
| if (pread(fd, s->data + s->data_len - f_data.st_size, f_data.st_size, 0) != |
| f_data.st_size) |
| goto file_err; |
| close(fd); |
| return f_data.st_size; |
| |
| name_missing_error: |
| *s->err = MUSTACHE_ERR_FILE_NAME_TOO_SHORT; |
| return -1; |
| |
| name_length_error: |
| *s->err = MUSTACHE_ERR_FILE_NAME_TOO_LONG; |
| return -1; |
| |
| file_too_big: |
| *s->err = MUSTACHE_ERR_FILE_TOO_BIG; |
| return -1; |
| |
| file_err: |
| *s->err = MUSTACHE_ERR_UNKNOWN; |
| return -1; |
| |
| unknown_error: |
| return -1; |
| } |
| |
| /* ***************************************************************************** |
| Calling the instrustion list (using the template engine) |
| ***************************************************************************** */ |
| |
| /* |
| * This function reviews the instructions listed at the end of the mustache_s |
| * and performs any callbacks necessary. |
| * |
| * The `mustache_s` data is looks like this: |
| * |
| * - header (the `mustache_s` struct): lists the length of the instruction |
| * array and data segments. |
| * - Instruction array: lists all the instructions extracted from the |
| * template(s) (an array of `mustache__instruction_s`). |
| * - Data segment: text and data related to the instructions. |
| * |
| * The instructions, much like machine code, might loop or jump. This is why the |
| * function keeps a stack of sorts. This allows the code to avoid recursion and |
| * minimize any risk of stack overflow caused by recursive templates. |
| */ |
| MUSTACHE_FUNC int(mustache_build)(mustache_build_args_s args) { |
| mustache_error_en err_if_missing; |
| if (!args.err) |
| args.err = &err_if_missing; |
| if (!args.mustache) { |
| goto user_error; |
| } |
| /* extract the instruction array and data segment from the mustache_s */ |
| mustache__instruction_s *instructions = MUSTACH2INSTRUCTIONS(args.mustache); |
| char *const data = MUSTACH2DATA(args.mustache); |
| mustache__builder_stack_s s; |
| s.data = args.mustache; |
| s.pos = 0; |
| s.index = 0; |
| s.padding = 0; |
| s.stack[0] = (mustache__section_stack_frame_s){ |
| .sec = |
| { |
| .udata1 = args.udata1, |
| .udata2 = args.udata2, |
| }, |
| .start = 0, |
| .end = instructions[0].data.end, |
| .index = 0, |
| .count = 0, |
| .frame = 0, |
| }; |
| while ((uintptr_t)(instructions + s.pos) < (uintptr_t)data) { |
| switch (instructions[s.pos].instruction) { |
| case MUSTACHE_WRITE_TEXT: |
| if (mustache_on_text(&s.stack[s.index].sec, |
| data + instructions[s.pos].data.name_pos, |
| instructions[s.pos].data.name_len)) |
| goto user_error; |
| break; |
| case MUSTACHE_WRITE_ARG: /* overflow */ |
| if (mustache_on_arg(&s.stack[s.index].sec, |
| data + instructions[s.pos].data.name_pos, |
| instructions[s.pos].data.name_len, 1)) |
| goto user_error; |
| break; |
| case MUSTACHE_WRITE_ARG_UNESCAPED: |
| if (mustache_on_arg(&s.stack[s.index].sec, |
| data + instructions[s.pos].data.name_pos, |
| instructions[s.pos].data.name_len, 0)) |
| goto user_error; |
| break; |
| case MUSTACHE_SECTION_GOTO: /* overflow */ |
| case MUSTACHE_SECTION_START: /* overflow */ |
| case MUSTACHE_SECTION_START_INV: |
| /* advance stack*/ |
| if (s.index + 1 >= MUSTACHE_NESTING_LIMIT) { |
| if (args.err) |
| *args.err = MUSTACHE_ERR_TOO_DEEP; |
| goto error; |
| } |
| s.stack[s.index + 1].sec = s.stack[s.index].sec; |
| ++s.index; |
| s.stack[s.index].start = |
| (instructions[s.pos].instruction == MUSTACHE_SECTION_GOTO |
| ? instructions[s.pos].data.len |
| : s.pos); |
| s.stack[s.index].end = instructions[s.pos].data.end; |
| s.stack[s.index].frame = s.index; |
| s.stack[s.index].index = 0; |
| s.stack[s.index].count = 1; |
| |
| /* test section count */ |
| if (instructions[s.pos].data.name_pos) { |
| /* this is a named section, it should be tested against user data */ |
| int32_t val = mustache_on_section_test( |
| &s.stack[s.index].sec, data + instructions[s.pos].data.name_pos, |
| instructions[s.pos].data.name_len, |
| instructions[s.pos].instruction == MUSTACHE_SECTION_START); |
| if (val == -1) { |
| goto user_error; |
| } |
| if (instructions[s.pos].instruction == MUSTACHE_SECTION_START_INV) { |
| /* invert test */ |
| val = (val == 0); |
| } |
| s.stack[s.index].count = (uint32_t)val; |
| } |
| |
| /* overflow */ |
| case MUSTACHE_SECTION_END: |
| /* loop section or continue */ |
| if (s.stack[s.index].index < s.stack[s.index].count) { |
| /* repeat / start section */ |
| s.pos = s.stack[s.index].start; |
| s.stack[s.index].sec = s.stack[s.index - 1].sec; |
| /* review user callback (if it's a named section) */ |
| if (instructions[s.pos].data.name_pos && |
| mustache_on_section_start(&s.stack[s.index].sec, |
| data + instructions[s.pos].data.name_pos, |
| instructions[s.pos].data.name_len, |
| s.stack[s.index].index) == -1) |
| goto user_error; |
| /* skip padding instructions in GOTO tags (recursive partials) */ |
| if (instructions[s.pos].instruction == MUSTACHE_SECTION_GOTO) |
| ++s.pos; |
| ++s.stack[s.index].index; |
| break; |
| } |
| s.pos = s.stack[s.index].end; |
| --s.index; |
| break; |
| case MUSTACHE_PADDING_PUSH: |
| s.padding = s.pos; |
| break; |
| case MUSTACHE_PADDING_POP: |
| s.padding = instructions[s.padding].data.end; |
| break; |
| case MUSTACHE_PADDING_WRITE: |
| for (uint32_t i = s.padding; i; i = instructions[i].data.end) { |
| if (mustache_on_text(&s.stack[s.index].sec, |
| data + instructions[i].data.name_pos, |
| instructions[i].data.name_len)) |
| goto user_error; |
| } |
| break; |
| default: |
| /* not a valid engine */ |
| fprintf(stderr, "ERROR: invalid mustache instruction set detected (wrong " |
| "`mustache_s`?)\n"); |
| if (args.err) { |
| *args.err = MUSTACHE_ERR_UNKNOWN; |
| } |
| goto error; |
| } |
| ++s.pos; |
| } |
| *args.err = MUSTACHE_OK; |
| return 0; |
| user_error: |
| *args.err = MUSTACHE_ERR_USER_ERROR; |
| error: |
| mustache_on_formatting_error(args.udata1, args.udata2); |
| return -1; |
| } |
| |
| /* ***************************************************************************** |
| Building the instrustion list (parsing the template) |
| ***************************************************************************** */ |
| |
| /* The parsing implementation, converts a template to an instruction array */ |
| MUSTACHE_FUNC mustache_s *(mustache_load)(mustache_load_args_s args) { |
| mustache_error_en err_if_missing; |
| mustache__loader_stack_s s; |
| uint8_t flag = 0; |
| |
| if (!args.err) |
| args.err = &err_if_missing; |
| s.path_capa = 0; |
| s.path = NULL; |
| s.data = NULL; |
| s.data_len = 0; |
| s.i = NULL; |
| s.i_capa = 32; |
| s.index = 0; |
| s.padding = 0; |
| s.m = malloc(sizeof(*s.m) + (sizeof(*s.i) * 32)); |
| MUSTACHE_ASSERT(s.m, "failed to allocate memory for mustache data"); |
| s.m->u.read_only_pt = 0; |
| s.m->u.read_only.data_length = 0; |
| s.m->u.read_only.intruction_count = 0; |
| s.i = MUSTACH2INSTRUCTIONS(s.m); |
| s.err = args.err; |
| |
| if (!args.filename_len && args.filename) |
| args.filename_len = strlen(args.filename); |
| |
| if (args.data) { |
| if (mustache__load_data(&s, args.filename, args.filename_len, args.data, |
| args.data_len) == -1) { |
| goto error; |
| } |
| } else { |
| if (mustache__load_file(&s, args.filename, args.filename_len) == -1) { |
| goto error; |
| } |
| } |
| |
| /* loop while there are templates to be parsed on the stack */ |
| while (s.index) { |
| /* parsing loop */ |
| while (s.stack[s.index].data_pos < s.stack[s.index].data_end) { |
| /* stand-alone tag flag, also containes padding length after bit 1 */ |
| uint32_t stand_alone = 0; |
| uint32_t stand_alone_pos = 0; |
| /* start parsing at current position */ |
| const char *start = s.data + s.stack[s.index].data_pos; |
| /* find the next instruction (beg == beginning) */ |
| char *beg = strstr(start, s.stack[s.index].del_start); |
| const char *org_beg = beg; |
| if (!beg || beg >= s.data + s.stack[s.index].data_end) { |
| /* no instructions left, only text */ |
| mustache__push_text_instruction(&s, s.stack[s.index].data_pos, |
| s.stack[s.index].data_end - |
| s.stack[s.index].data_pos); |
| s.stack[s.index].data_pos = s.stack[s.index].data_end; |
| continue; |
| } |
| if (beg != start) { |
| /* there's text before the instruction */ |
| mustache__push_text_instruction(&s, s.stack[s.index].data_pos, |
| (uint32_t)(uintptr_t)(beg - start)); |
| } |
| /* move beg (reading position) after the delimiter */ |
| beg += s.stack[s.index].del_start_len; |
| /* seek the end of the instruction delimiter */ |
| char *end = strstr(beg, s.stack[s.index].del_end); |
| if (!end || end >= s.data + s.stack[s.index].data_end) { |
| /* delimiter not closed */ |
| *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; |
| goto error; |
| } |
| |
| /* update reading position in the stack */ |
| s.stack[s.index].data_pos = (end - s.data) + s.stack[s.index].del_end_len; |
| |
| /* Test for stand-alone tags */ |
| if (!end[s.stack[s.index].del_end_len] || |
| end[s.stack[s.index].del_end_len] == '\n' || |
| (end[s.stack[s.index].del_end_len] == '\r' && |
| end[1 + s.stack[s.index].del_end_len] == '\n')) { |
| char *pad = beg - (s.stack[s.index].del_start_len + 1); |
| while (pad >= start && (pad[0] == ' ' || pad[0] == '\t')) |
| --pad; |
| if (pad[0] == '\n' || pad[0] == 0) { |
| /* Yes, this a stand-alone tag, store padding length + flag */ |
| ++pad; |
| stand_alone_pos = pad - s.data; |
| stand_alone = |
| ((beg - (pad + s.stack[s.index].del_start_len)) << 1) | 1; |
| } |
| } |
| |
| /* parse instruction content */ |
| flag = 1; |
| |
| switch (beg[0]) { |
| case '!': |
| /* comment, do nothing... almost */ |
| mustache__stand_alone_adjust(&s, stand_alone); |
| break; |
| |
| case '=': |
| /* define new seperators */ |
| mustache__stand_alone_adjust(&s, stand_alone); |
| ++beg; |
| --end; |
| if (end[0] != '=') { |
| *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; |
| goto error; |
| } |
| --end; |
| MUSTACHE_IGNORE_WHITESPACE(beg, 1); |
| MUSTACHE_IGNORE_WHITESPACE(end, -1); |
| ++end; |
| { |
| char *div = beg; |
| while (div < end && !isspace(*div)) { |
| ++div; |
| } |
| if (div == end || div == beg) { |
| *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; |
| goto error; |
| } |
| if (div - beg >= MUSTACHE_DELIMITER_LENGTH_LIMIT) { |
| *args.err = MUSTACHE_ERR_DELIMITER_TOO_LONG; |
| goto error; |
| } |
| /* copy starting delimiter */ |
| s.stack[s.index].del_start_len = div - beg; |
| for (size_t i = 0; i < s.stack[s.index].del_start_len; ++i) { |
| s.stack[s.index].del_start[i] = beg[i]; |
| } |
| s.stack[s.index].del_start[s.stack[s.index].del_start_len] = 0; |
| |
| ++div; |
| MUSTACHE_IGNORE_WHITESPACE(div, 1); |
| if (div == end) { |
| *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; |
| goto error; |
| } |
| if (end - div >= MUSTACHE_DELIMITER_LENGTH_LIMIT) { |
| *args.err = MUSTACHE_ERR_DELIMITER_TOO_LONG; |
| goto error; |
| } |
| /* copy ending delimiter */ |
| s.stack[s.index].del_end_len = end - div; |
| for (size_t i = 0; i < s.stack[s.index].del_end_len; ++i) { |
| s.stack[s.index].del_end[i] = div[i]; |
| } |
| s.stack[s.index].del_end[s.stack[s.index].del_end_len] = 0; |
| } |
| break; |
| |
| case '^': /*overflow*/ |
| /* start inverted section */ |
| flag = 0; |
| case '#': |
| /* start section (or inverted section) */ |
| mustache__stand_alone_adjust(&s, stand_alone); |
| ++beg; |
| --end; |
| MUSTACHE_IGNORE_WHITESPACE(beg, 1); |
| MUSTACHE_IGNORE_WHITESPACE(end, -1); |
| ++end; |
| |
| ++s.stack[s.index].open_sections; |
| if (s.stack[s.index].open_sections >= MUSTACHE_NESTING_LIMIT) { |
| *args.err = MUSTACHE_ERR_TOO_DEEP; |
| goto error; |
| } |
| if (beg - s.data >= UINT16_MAX) { |
| *args.err = MUSTACHE_ERR_NAME_TOO_LONG; |
| } |
| if (mustache__instruction_push( |
| &s, |
| (mustache__instruction_s){ |
| .instruction = (flag ? MUSTACHE_SECTION_START |
| : MUSTACHE_SECTION_START_INV), |
| .data = { |
| .name_pos = beg - s.data, |
| .name_len = end - beg, |
| .offset = s.stack[s.index].data_pos - (beg - s.data), |
| }})) { |
| goto error; |
| } |
| break; |
| |
| case '>': |
| /* partial template - search data for loaded template or load new */ |
| mustache__stand_alone_adjust(&s, stand_alone); |
| if ((stand_alone >> 1)) { |
| /* TODO: add padding markers */ |
| if (mustache__instruction_push( |
| &s, (mustache__instruction_s){ |
| .instruction = MUSTACHE_PADDING_PUSH, |
| .data = { |
| .name_pos = stand_alone_pos, |
| .name_len = (stand_alone >> 1), |
| .end = s.padding, |
| }})) { |
| goto error; |
| } |
| s.padding = s.m->u.read_only.intruction_count - 1; |
| } |
| ++beg; |
| --end; |
| MUSTACHE_IGNORE_WHITESPACE(beg, 1); |
| MUSTACHE_IGNORE_WHITESPACE(end, -1); |
| ++end; |
| ssize_t loaded = mustache__load_file(&s, beg, end - beg); |
| if (loaded == -1) |
| goto error; |
| /* Add latest padding section to text */ |
| if ((stand_alone >> 1)) { |
| if (loaded) |
| mustache__instruction_push( |
| &s, (mustache__instruction_s){ |
| .instruction = MUSTACHE_WRITE_TEXT, |
| .data = |
| { |
| .name_pos = stand_alone_pos, |
| .name_len = (stand_alone >> 1), |
| }, |
| }); |
| else if (mustache__instruction_push( |
| &s, (mustache__instruction_s){.instruction = |
| MUSTACHE_PADDING_POP})) |
| goto error; |
| } |
| break; |
| |
| case '/': |
| /* section end */ |
| mustache__stand_alone_adjust(&s, stand_alone); |
| ++beg; |
| --end; |
| MUSTACHE_IGNORE_WHITESPACE(beg, 1); |
| MUSTACHE_IGNORE_WHITESPACE(end, -1); |
| ++end; |
| if (!s.stack[s.index].open_sections) { |
| *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; |
| goto error; |
| } else { |
| uint32_t pos = s.m->u.read_only.intruction_count; |
| uint32_t nested = 0; |
| do { |
| --pos; |
| if (s.i[pos].instruction == MUSTACHE_SECTION_END) |
| ++nested; |
| else if (s.i[pos].instruction == MUSTACHE_SECTION_START || |
| s.i[pos].instruction == MUSTACHE_SECTION_START_INV) { |
| if (nested) { |
| --nested; |
| } else { |
| /* test instruction closure */ |
| if (s.i[pos].data.name_len != end - beg || |
| memcmp(beg, s.data + s.i[pos].data.name_pos, |
| s.i[pos].data.name_len)) { |
| *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; |
| goto error; |
| } |
| /* update initial instruction (do this before adding closure) */ |
| s.i[pos].data.end = s.m->u.read_only.intruction_count; |
| s.i[pos].data.len = org_beg - (s.data + s.i[pos].data.name_pos + |
| s.i[pos].data.offset); |
| /* add closure instruction */ |
| mustache__instruction_push( |
| &s, (mustache__instruction_s){.instruction = |
| MUSTACHE_SECTION_END, |
| .data = s.i[pos].data}); |
| /* update closure count */ |
| --s.stack[s.index].open_sections; |
| /* stop loop */ |
| pos = 0; |
| beg = NULL; |
| } |
| } |
| } while (pos); |
| if (beg) { |
| *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; |
| goto error; |
| } |
| } |
| break; |
| |
| case '{': |
| /* step the read position forward if the ending was '}}}' */ |
| if (s.data[s.stack[s.index].data_pos] == '}' && |
| s.stack[s.index].del_end[0] == '}' && |
| s.stack[s.index].del_end[s.stack[s.index].del_end_len - 1] == '}') { |
| ++s.stack[s.index].data_pos; |
| } |
| /*overflow*/ |
| case '&': /*overflow*/ |
| /* unescaped variable data */ |
| flag = 0; |
| /* overflow to default */ |
| case ':': /*overflow*/ |
| case '<': /*overflow*/ |
| ++beg; /*overflow*/ |
| default: |
| --end; |
| MUSTACHE_IGNORE_WHITESPACE(beg, 1); |
| MUSTACHE_IGNORE_WHITESPACE(end, -1); |
| ++end; |
| mustache__instruction_push( |
| &s, (mustache__instruction_s){ |
| .instruction = (flag ? MUSTACHE_WRITE_ARG |
| : MUSTACHE_WRITE_ARG_UNESCAPED), |
| .data = {.name_pos = beg - s.data, .name_len = end - beg}}); |
| break; |
| } |
| } |
| /* make sure all sections were closed */ |
| if (s.stack[s.index].open_sections) { |
| *args.err = MUSTACHE_ERR_CLOSURE_MISMATCH; |
| goto error; |
| } |
| /* move padding from section tail to post closure (adjust for padding |
| * changes) */ |
| flag = 0; |
| if (s.m->u.read_only.intruction_count && |
| s.i[s.m->u.read_only.intruction_count - 1].instruction == |
| MUSTACHE_PADDING_WRITE) { |
| --s.m->u.read_only.intruction_count; |
| flag = 1; |
| } |
| /* mark section length */ |
| mustache__data_segment_s seg = mustache__data_segment_read( |
| (uint8_t *)s.data + s.stack[s.index].data_start); |
| s.i[seg.inst_start].data.end = s.m->u.read_only.intruction_count; |
| /* add instruction closure */ |
| mustache__instruction_push( |
| &s, (mustache__instruction_s){.instruction = MUSTACHE_SECTION_END}); |
| /* TODO: pop any padding (if exists) */ |
| if (s.padding && s.padding + 1 == seg.inst_start) { |
| s.padding = s.i[s.padding].data.end; |
| mustache__instruction_push(&s, (mustache__instruction_s){ |
| .instruction = MUSTACHE_PADDING_POP, |
| }); |
| } |
| /* complete padding switch*/ |
| if (flag) { |
| mustache__instruction_push(&s, (mustache__instruction_s){ |
| .instruction = MUSTACHE_PADDING_WRITE, |
| }); |
| flag = 0; |
| } |
| /* pop stack */ |
| --s.index; |
| } |
| |
| s.m = realloc(s.m, sizeof(*s.m) + |
| (sizeof(*s.i) * s.m->u.read_only.intruction_count) + |
| s.data_len); |
| MUSTACHE_ASSERT(s.m, |
| "failed to allocate memory for consolidated mustache data"); |
| memcpy(MUSTACH2DATA(s.m), s.data, s.data_len); |
| free(s.data); |
| free(s.path); |
| |
| *args.err = MUSTACHE_OK; |
| return s.m; |
| |
| error: |
| free(s.data); |
| free(s.path); |
| free(s.m); |
| return NULL; |
| } |
| |
| #endif /* INCLUDE_MUSTACHE_IMPLEMENTATION */ |
| |
| #undef MUSTACHE_FUNC |
| #undef MUSTACH2INSTRUCTIONS |
| #undef MUSTACH2DATA |
| #undef MUSTACHE_OBJECT_OFFSET |
| #undef MUSTACHE_IGNORE_WHITESPACE |
| |
| #endif /* H_MUSTACHE_LOADR_H */ |