#include #include #include #include "ltm/common.h" #include "ltm/template.h" #include "ltm/template_internal.h" static bool ltm_template_next_placeholder(ltm_placeholder *ph, const char *s, size_t len) { if (len == 0) { return false; } ph->start = memchr(s, '{', len - 1); if ((ph->start == NULL) || (ph->start[1] != '{')) { return false; } size_t new_len = len - (ph->start - s); // A template can never be valid without at least 5 characters if (new_len < 5) { ph->type = ltm_placeholder_type_invalid; return true; } ph->end = memchr(ph->start + 2, '}', new_len - 3); // Non-terminated placeholders aren't valid if ((ph->end == NULL) || (ph->end[1] != '}')) { ph->type = ltm_placeholder_type_invalid; return true; } // End should point to final character ph->end++; // Parse the words ph->name.s = ph->start + 2; while ((*ph->name.s == ' ') && (ph->name.s != ph->end - 1)) { ph->name.s++; } // Placeholder is empty if (ph->name.s == ph->end - 1) { ph->type = ltm_placeholder_type_invalid; return true; } const char *ident = ph->name.s; while ((*ident != ' ') && (ident != ph->end - 1)) { ident++; } ph->name.len = ident - ph->name.s; // Skip whitespace over to next word while ((*ident == ' ') && (ident != ph->end - 1)) { ident++; } if (ident == ph->end - 1) { ph->type = ltm_placeholder_type_var; } else { // Further parse the identifier const char *temp = ident; while ((*temp != ' ') && (temp != ph->end - 1)) { temp++; } size_t ident_len = temp - ident; if (strncmp("start", ident, ident_len) == 0) { ph->type = ltm_placeholder_type_nested_start; } else if (strncmp("end", ident, ident_len) == 0) { ph->type = ltm_placeholder_type_nested_end; } else { ph->type = ltm_placeholder_type_invalid; } } return true; } ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { ltm_template *template; LTM_RES(ltm_template_init(&template)); ltm_placeholder ph; bool in_nested = false; const char *nested_start = NULL, *nested_name = NULL; size_t nested_depth = 0, nested_name_len = 0; size_t cur_nested_depth = 0; while (ltm_template_next_placeholder(&ph, s, len)) { // Add part before placeholder as literal if (!in_nested && (ph.start != s)) { LTM_RES2(ltm_template_block_append(template, ltm_template_block_type_literal, (void *)s, ph.start - s), ltm_template_free(template)); } switch (ph.type) { // Invalid placeholders can be detected as early as possible case ltm_placeholder_type_invalid: ltm_template_free(template); return ltm_err_invalid_template; case ltm_placeholder_type_var: if (!in_nested) { LTM_RES2(ltm_template_block_append( template, ltm_template_block_type_var, NULL, 0), ltm_template_free(template)); LTM_RES2(ltm_template_name_append(template, ph.name.s, ph.name.len, template->blocks.len - 1), ltm_template_free(template)); } break; case ltm_placeholder_type_nested_start: if (!in_nested) { nested_start = ph.end + 1; nested_depth = cur_nested_depth; nested_name = ph.name.s; nested_name_len = ph.name.len; in_nested = true; } cur_nested_depth++; break; case ltm_placeholder_type_nested_end: // This means there's more nested end placeholders than nested starts if (cur_nested_depth == 0) { ltm_template_free(template); return ltm_err_invalid_template; } cur_nested_depth--; if (in_nested && (cur_nested_depth == nested_depth)) { size_t nested_len = ph.start - nested_start; ltm_template *nested_template; LTM_RES2( ltm_template_compile_n(&nested_template, nested_start, nested_len), ltm_template_free(template)); LTM_RES(ltm_template_block_append( template, ltm_template_block_type_nested, nested_template, 0)); LTM_RES2(ltm_template_name_append(template, nested_name, nested_name_len, template->blocks.len - 1), ltm_template_free(template)); in_nested = false; } // We encountered a nested end without a start else if (!in_nested) { ltm_template_free(template); return ltm_err_invalid_template; } break; } len -= ph.end + 1 - s; s = ph.end + 1; } // Unfinished nested if (in_nested) { ltm_template_free(template); return ltm_err_invalid_template; } // Add remaining trailing literal if (len > 0) { LTM_RES2(ltm_template_block_append( template, ltm_template_block_type_literal, (void *)s, len), ltm_template_free(template)); } *out = template; return ltm_err_ok; } ltm_err ltm_template_compile(ltm_template **out, const char *template) { return ltm_template_compile_n(out, template, strlen(template)); }