diff --git a/ltm/src/ltm_template_compile.c b/ltm/src/ltm_template_compile.c index 85145e3..e985790 100644 --- a/ltm/src/ltm_template_compile.c +++ b/ltm/src/ltm_template_compile.c @@ -1,4 +1,5 @@ #include +#include #include #include "ltm/common.h" @@ -19,6 +20,10 @@ ltm_err ltm_template_init(ltm_template **out) { 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] == '{')) { @@ -97,19 +102,59 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { bool in_loop = false; const char *loop_start = NULL; size_t loop_depth = 0; + size_t cur_loop_depth = 0; - // TODO to ensure the loops are balanced, we should count how many loop starts - // we have seen and only match a loop end if the number matches; this way, we - // can allow arbitrarily nested loops - while (ltm_template_next_placeholder(&ph, s, len)) { + // Add part before placeholder as literal + if (!in_loop && (ph.start != s)) { + ltm_template_block *blocks = + realloc(template->blocks.arr, + (template->blocks.len + 1) * sizeof(ltm_template_block)); + + if (blocks == NULL) { + return ltm_err_failed_alloc; + } + + blocks[template->blocks.len].type = ltm_template_block_type_literal; + blocks[template->blocks.len].data.s = s; + blocks[template->blocks.len].len = ph.start - s; + + template->blocks.arr = blocks; + template->blocks.len++; + } + switch (ph.type) { case ltm_placeholder_type_invalid: return ltm_err_invalid_template; - case ltm_placeholder_type_var: - // TODO add var block - break; + case ltm_placeholder_type_var: { + ltm_template_block *blocks = + realloc(template->blocks.arr, + (template->blocks.len + 1) * sizeof(ltm_template_block)); + + if (blocks == NULL) { + return ltm_err_failed_alloc; + } + + blocks[template->blocks.len].type = ltm_template_block_type_var; + template->blocks.arr = blocks; + + ltm_template_block_name *names = + realloc(template->names.arr, + (template->names.len + 1) * sizeof(ltm_template_block_name)); + + if (names == NULL) { + return ltm_err_failed_alloc; + } + + names[template->names.len].name.s = ph.name.s; + names[template->names.len].name.len = ph.name.len; + names[template->names.len].index = template->blocks.len; + template->names.arr = names; + + template->blocks.len++; + template->names.len++; + } break; case ltm_placeholder_type_loop_start: if (!in_loop) { loop_start = ph.end + 1; @@ -119,6 +164,10 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { cur_loop_depth++; break; case ltm_placeholder_type_loop_end: + if (cur_loop_depth == 0) { + return ltm_err_invalid_template; + } + cur_loop_depth--; if (in_loop && (cur_loop_depth == loop_depth)) { @@ -134,7 +183,7 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { break; } - len -= ph.end + 1 - ph.start; + len -= ph.end + 1 - s; s = ph.end + 1; } @@ -143,6 +192,26 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { return ltm_err_invalid_template; } + // Add remaining trailing literal + if (len > 0) { + ltm_template_block *blocks = + realloc(template->blocks.arr, + (template->blocks.len + 1) * sizeof(ltm_template_block)); + + if (blocks == NULL) { + return ltm_err_failed_alloc; + } + + blocks[template->blocks.len].type = ltm_template_block_type_literal; + blocks[template->blocks.len].data.s = s; + blocks[template->blocks.len].len = len; + + template->blocks.arr = blocks; + template->blocks.len++; + } + + *out = template; + return ltm_err_ok; } diff --git a/ltm/test/compile.c b/ltm/test/compile.c new file mode 100644 index 0000000..06a5355 --- /dev/null +++ b/ltm/test/compile.c @@ -0,0 +1,56 @@ +#include "test.h" + +#include "ltm/template.h" +#include "ltm/template_internal.h" + +void test_single_placeholder() { + const char *s = "Hello, {{ world }}"; + + ltm_template *template; + TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_ok); + + TEST_CHECK(template->blocks.len == 2); + + TEST_CHECK(template->blocks.arr[0].type == ltm_template_block_type_literal); + TEST_CHECK(template->blocks.arr[0].data.s == s); + TEST_CHECK(template->blocks.arr[0].len == 7); + + TEST_CHECK(template->blocks.arr[1].type == ltm_template_block_type_var); + + TEST_CHECK(template->names.len == 1); + + TEST_CHECK(template->names.arr[0].name.s == s + 10); + TEST_CHECK(template->names.arr[0].name.len == 5); + TEST_CHECK(template->names.arr[0].index == 1); +} + +void test_single_placeholder_trailing() { + const char *s = "Hello, {{ world }}!"; + + ltm_template *template; + TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_ok); + + TEST_CHECK(template->blocks.len == 3); + + TEST_CHECK(template->blocks.arr[0].type == ltm_template_block_type_literal); + TEST_CHECK(template->blocks.arr[0].data.s == s); + TEST_CHECK(template->blocks.arr[0].len == 7); + + TEST_CHECK(template->blocks.arr[1].type == ltm_template_block_type_var); + + TEST_CHECK(template->blocks.arr[2].type == ltm_template_block_type_literal); + TEST_CHECK(template->blocks.arr[2].data.s == s + 18); + TEST_CHECK(template->blocks.arr[2].len == 1); + + TEST_CHECK(template->names.len == 1); + + TEST_CHECK(template->names.arr[0].name.s == s + 10); + TEST_CHECK(template->names.arr[0].name.len == 5); + TEST_CHECK(template->names.arr[0].index == 1); +} + +TEST_LIST = { + { "template single placeholder", test_single_placeholder }, + { "template single placeholder trailing", test_single_placeholder_trailing }, + { NULL, NULL } +};