From 1e34cb8c2dfd6371cda4d4d5f3d926e3ac9ace61 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 16 Dec 2023 15:34:31 +0100 Subject: [PATCH] feat(ltm): initial full template compiler/parser --- ltm/src/_include/ltm/template_internal.h | 14 +- ltm/src/ltm_template.c | 69 +++++++ ltm/src/ltm_template_compile.c | 231 ++++++++++------------- ltm/test/compile.c | 68 ++++++- 4 files changed, 237 insertions(+), 145 deletions(-) create mode 100644 ltm/src/ltm_template.c diff --git a/ltm/src/_include/ltm/template_internal.h b/ltm/src/_include/ltm/template_internal.h index 29ae7c7..2511d13 100644 --- a/ltm/src/_include/ltm/template_internal.h +++ b/ltm/src/_include/ltm/template_internal.h @@ -37,11 +37,10 @@ typedef enum ltm_template_block_type { */ typedef struct ltm_template_block { ltm_template_block_type type; - union { - ltm_template *template; - const char *s; + struct { + void *ptr; + size_t len; } data; - size_t len; } ltm_template_block; typedef struct ltm_template_block_name { @@ -69,4 +68,11 @@ struct ltm_template { ltm_err ltm_template_init(ltm_template **out); +ltm_err ltm_template_name_append(ltm_template *template, const char *s, + size_t len, size_t index); + +ltm_err ltm_template_block_append(ltm_template *template, + ltm_template_block_type type, void *data, + size_t len); + #endif diff --git a/ltm/src/ltm_template.c b/ltm/src/ltm_template.c new file mode 100644 index 0000000..4e7dbd0 --- /dev/null +++ b/ltm/src/ltm_template.c @@ -0,0 +1,69 @@ +#include "ltm/template.h" +#include "ltm/template_internal.h" + +ltm_err ltm_template_init(ltm_template **out) { + ltm_template *template = calloc(1, sizeof(ltm_template)); + + if (template == NULL) { + return ltm_err_failed_alloc; + } + + *out = template; + + return ltm_err_ok; +} + +ltm_err ltm_template_name_append(ltm_template *template, const char *s, + size_t len, size_t index) { + 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; + } + + ltm_template_block_name *name = &names[template->names.len]; + + name->name.s = s; + name->name.len = len; + name->index = index; + + template->names.arr = names; + template->names.len++; + + return ltm_err_ok; +} + +ltm_err ltm_template_block_append(ltm_template *template, + ltm_template_block_type type, void *data, + size_t len) { + ltm_template_block *blocks = + realloc(template->blocks.arr, + (template->blocks.len + 1) * sizeof(ltm_template_block)); + + if (blocks == NULL) { + return ltm_err_failed_alloc; + } + + ltm_template_block *block = &blocks[template->blocks.len]; + + block->type = type; + + switch (type) { + case ltm_template_block_type_literal: + block->data.ptr = data; + block->data.len = len; + break; + case ltm_template_block_type_loop: + block->data.ptr = data; + break; + // For the other cases, we explicitely ignore the data and len arguments + default:; + } + + template->blocks.arr = blocks; + template->blocks.len++; + + return ltm_err_ok; +} diff --git a/ltm/src/ltm_template_compile.c b/ltm/src/ltm_template_compile.c index e985790..9a14608 100644 --- a/ltm/src/ltm_template_compile.c +++ b/ltm/src/ltm_template_compile.c @@ -6,92 +6,88 @@ #include "ltm/template.h" #include "ltm/template_internal.h" -ltm_err ltm_template_init(ltm_template **out) { - ltm_template *template = calloc(1, sizeof(ltm_template)); - - if (template == NULL) { - return ltm_err_failed_alloc; - } - - *out = template; - - return ltm_err_ok; -} - -bool ltm_template_next_placeholder(ltm_placeholder *ph, const char *s, - size_t len) { +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] == '{')) { - size_t new_len = len - (ph->start - s); + if ((ph->start == NULL) || (ph->start[1] != '{')) { + return false; + } - ph->end = memchr(ph->start + 2, '}', new_len - 3); + size_t new_len = len - (ph->start - s); - // 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; - - 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("loop", ident, ident_len) == 0) { - ph->type = ltm_placeholder_type_loop_start; - } else if (strncmp("end", ident, ident_len) == 0) { - ph->type = ltm_placeholder_type_loop_end; - } else { - ph->type = ltm_placeholder_type_invalid; - } - } + // A template can never be valid without at least 5 characters + if (new_len < 5) { + ph->type = ltm_placeholder_type_invalid; return true; } - return false; + 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_loop_start; + } else if (strncmp("end", ident, ident_len) == 0) { + ph->type = ltm_placeholder_type_loop_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) { @@ -100,70 +96,42 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { ltm_placeholder ph; bool in_loop = false; - const char *loop_start = NULL; - size_t loop_depth = 0; + const char *loop_start = NULL, *loop_name = NULL; + size_t loop_depth = 0, loop_name_len = 0; size_t cur_loop_depth = 0; 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++; + LTM_RES(ltm_template_block_append( + template, ltm_template_block_type_literal, (void *)s, ph.start - s)); } switch (ph.type) { + // Invalid placeholders can be detected as early as possible case ltm_placeholder_type_invalid: return ltm_err_invalid_template; - 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; + case ltm_placeholder_type_var: + if (!in_loop) { + LTM_RES(ltm_template_block_append(template, ltm_template_block_type_var, + NULL, 0)); + LTM_RES(ltm_template_name_append(template, ph.name.s, ph.name.len, + template->blocks.len - 1)); } - - 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; + break; case ltm_placeholder_type_loop_start: if (!in_loop) { loop_start = ph.end + 1; - in_loop = true; loop_depth = cur_loop_depth; + loop_name = ph.name.s; + loop_name_len = ph.name.len; + in_loop = true; } cur_loop_depth++; break; case ltm_placeholder_type_loop_end: + // This means there's more loop end placeholders than loop starts if (cur_loop_depth == 0) { return ltm_err_invalid_template; } @@ -171,13 +139,18 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { cur_loop_depth--; if (in_loop && (cur_loop_depth == loop_depth)) { - size_t loop_len = ph.end - loop_start; - // TODO recursive call to compile + size_t loop_len = ph.start - loop_start; + ltm_template *loop_template; + LTM_RES(ltm_template_compile_n(&loop_template, loop_start, loop_len)); + LTM_RES(ltm_template_block_append( + template, ltm_template_block_type_loop, loop_template, 0)); + LTM_RES(ltm_template_name_append(template, loop_name, loop_name_len, + template->blocks.len - 1)); in_loop = false; } // We encountered a loop end without a start - else { + else if (!in_loop) { return ltm_err_invalid_template; } break; @@ -194,20 +167,8 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { // 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++; + LTM_RES(ltm_template_block_append(template, ltm_template_block_type_literal, + (void *)s, len)); } *out = template; diff --git a/ltm/test/compile.c b/ltm/test/compile.c index 06a5355..a982bdf 100644 --- a/ltm/test/compile.c +++ b/ltm/test/compile.c @@ -1,3 +1,4 @@ +#include "ltm/common.h" #include "test.h" #include "ltm/template.h" @@ -12,8 +13,8 @@ void test_single_placeholder() { 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[0].data.ptr == s); + TEST_CHECK(template->blocks.arr[0].data.len == 7); TEST_CHECK(template->blocks.arr[1].type == ltm_template_block_type_var); @@ -33,14 +34,14 @@ void test_single_placeholder_trailing() { 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[0].data.ptr == s); + TEST_CHECK(template->blocks.arr[0].data.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->blocks.arr[2].data.ptr == s + 18); + TEST_CHECK(template->blocks.arr[2].data.len == 1); TEST_CHECK(template->names.len == 1); @@ -49,8 +50,63 @@ void test_single_placeholder_trailing() { TEST_CHECK(template->names.arr[0].index == 1); } +void test_single_loop() { + const char *s = "abc {{ l start }}some content {{ var }} {{ l end }}"; + + ltm_template *template; + TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_ok); + + TEST_CHECK(template->names.len == 1); + TEST_CHECK(template->names.arr[0].name.s == s + 7); + TEST_CHECK(template->names.arr[0].name.len == 1); + TEST_CHECK(template->names.arr[0].index == 1); + + 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.ptr == s); + TEST_CHECK(template->blocks.arr[0].data.len == 4); + + TEST_CHECK(template->blocks.arr[1].type == ltm_template_block_type_loop); + ltm_template *loop_template = template->blocks.arr[1].data.ptr; + + TEST_CHECK(loop_template->blocks.len == 3); + TEST_CHECK(loop_template->blocks.arr[0].type == ltm_template_block_type_literal); + TEST_CHECK(loop_template->blocks.arr[0].data.ptr == s + 17); + TEST_CHECK(loop_template->blocks.arr[0].data.len == 13); + TEST_CHECK(loop_template->blocks.arr[1].type == ltm_template_block_type_var); + TEST_CHECK(loop_template->blocks.arr[2].type == ltm_template_block_type_literal); + TEST_CHECK(loop_template->blocks.arr[2].data.ptr == s + 39); + TEST_CHECK(loop_template->blocks.arr[2].data.len == 1); + + TEST_CHECK(loop_template->names.len == 1); + TEST_CHECK(loop_template->names.arr[0].name.s == s + 33); + TEST_CHECK(loop_template->names.arr[0].name.len == 3); + TEST_CHECK(loop_template->names.arr[0].index == 1); +} + +void test_unclosed_placeholder() { + ltm_template *template; + + const char *s = "abc {{ var }"; + TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_invalid_template); + + s = "abc {{ var "; + TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_invalid_template); +} + +void test_unclosed_loop() { + ltm_template *template; + + const char *s = "abc {{ var start }} {{ hello }}"; + TEST_CHECK(ltm_template_compile(&template, s) == ltm_err_invalid_template); +} + TEST_LIST = { { "template single placeholder", test_single_placeholder }, { "template single placeholder trailing", test_single_placeholder_trailing }, + { "template single loop", test_single_loop }, + { "template unclosed placeholder", test_unclosed_placeholder }, + { "template unclosed loop", test_unclosed_loop }, { NULL, NULL } };