feat(ltm): initial full template compiler/parser

ltm
Jef Roosens 2023-12-16 15:34:31 +01:00
parent af5e519663
commit ba320a4250
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
4 changed files with 237 additions and 145 deletions

View File

@ -37,11 +37,10 @@ typedef enum ltm_template_block_type {
*/ */
typedef struct ltm_template_block { typedef struct ltm_template_block {
ltm_template_block_type type; ltm_template_block_type type;
union { struct {
ltm_template *template; void *ptr;
const char *s;
} data;
size_t len; size_t len;
} data;
} ltm_template_block; } ltm_template_block;
typedef struct ltm_template_block_name { 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_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 #endif

View File

@ -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;
}

View File

@ -6,19 +6,7 @@
#include "ltm/template.h" #include "ltm/template.h"
#include "ltm/template_internal.h" #include "ltm/template_internal.h"
ltm_err ltm_template_init(ltm_template **out) { static bool ltm_template_next_placeholder(ltm_placeholder *ph, const char *s,
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) { size_t len) {
if (len == 0) { if (len == 0) {
return false; return false;
@ -26,9 +14,19 @@ bool ltm_template_next_placeholder(ltm_placeholder *ph, const char *s,
ph->start = memchr(s, '{', len - 1); ph->start = memchr(s, '{', len - 1);
if ((ph->start != NULL) && (ph->start[1] == '{')) { if ((ph->start == NULL) || (ph->start[1] != '{')) {
return false;
}
size_t new_len = len - (ph->start - s); 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); ph->end = memchr(ph->start + 2, '}', new_len - 3);
// Non-terminated placeholders aren't valid // Non-terminated placeholders aren't valid
@ -63,6 +61,7 @@ bool ltm_template_next_placeholder(ltm_placeholder *ph, const char *s,
ph->name.len = ident - ph->name.s; ph->name.len = ident - ph->name.s;
// Skip whitespace over to next word
while ((*ident == ' ') && (ident != ph->end - 1)) { while ((*ident == ' ') && (ident != ph->end - 1)) {
ident++; ident++;
} }
@ -79,7 +78,7 @@ bool ltm_template_next_placeholder(ltm_placeholder *ph, const char *s,
size_t ident_len = temp - ident; size_t ident_len = temp - ident;
if (strncmp("loop", ident, ident_len) == 0) { if (strncmp("start", ident, ident_len) == 0) {
ph->type = ltm_placeholder_type_loop_start; ph->type = ltm_placeholder_type_loop_start;
} else if (strncmp("end", ident, ident_len) == 0) { } else if (strncmp("end", ident, ident_len) == 0) {
ph->type = ltm_placeholder_type_loop_end; ph->type = ltm_placeholder_type_loop_end;
@ -91,79 +90,48 @@ bool ltm_template_next_placeholder(ltm_placeholder *ph, const char *s,
return true; return true;
} }
return false;
}
ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) {
ltm_template *template; ltm_template *template;
LTM_RES(ltm_template_init(&template)); LTM_RES(ltm_template_init(&template));
ltm_placeholder ph; ltm_placeholder ph;
bool in_loop = false; bool in_loop = false;
const char *loop_start = NULL; const char *loop_start = NULL, *loop_name = NULL;
size_t loop_depth = 0; size_t loop_depth = 0, loop_name_len = 0;
size_t cur_loop_depth = 0; size_t cur_loop_depth = 0;
while (ltm_template_next_placeholder(&ph, s, len)) { while (ltm_template_next_placeholder(&ph, s, len)) {
// Add part before placeholder as literal // Add part before placeholder as literal
if (!in_loop && (ph.start != s)) { if (!in_loop && (ph.start != s)) {
ltm_template_block *blocks = LTM_RES(ltm_template_block_append(
realloc(template->blocks.arr, template, ltm_template_block_type_literal, (void *)s, ph.start - s));
(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) { switch (ph.type) {
// Invalid placeholders can be detected as early as possible
case ltm_placeholder_type_invalid: case ltm_placeholder_type_invalid:
return ltm_err_invalid_template; return ltm_err_invalid_template;
case ltm_placeholder_type_var: { case ltm_placeholder_type_var:
ltm_template_block *blocks = if (!in_loop) {
realloc(template->blocks.arr, LTM_RES(ltm_template_block_append(template, ltm_template_block_type_var,
(template->blocks.len + 1) * sizeof(ltm_template_block)); NULL, 0));
LTM_RES(ltm_template_name_append(template, ph.name.s, ph.name.len,
if (blocks == NULL) { template->blocks.len - 1));
return ltm_err_failed_alloc;
} }
break;
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: case ltm_placeholder_type_loop_start:
if (!in_loop) { if (!in_loop) {
loop_start = ph.end + 1; loop_start = ph.end + 1;
in_loop = true;
loop_depth = cur_loop_depth; loop_depth = cur_loop_depth;
loop_name = ph.name.s;
loop_name_len = ph.name.len;
in_loop = true;
} }
cur_loop_depth++; cur_loop_depth++;
break; break;
case ltm_placeholder_type_loop_end: case ltm_placeholder_type_loop_end:
// This means there's more loop end placeholders than loop starts
if (cur_loop_depth == 0) { if (cur_loop_depth == 0) {
return ltm_err_invalid_template; 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--; cur_loop_depth--;
if (in_loop && (cur_loop_depth == loop_depth)) { if (in_loop && (cur_loop_depth == loop_depth)) {
size_t loop_len = ph.end - loop_start; size_t loop_len = ph.start - loop_start;
// TODO recursive call to compile 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; in_loop = false;
} }
// We encountered a loop end without a start // We encountered a loop end without a start
else { else if (!in_loop) {
return ltm_err_invalid_template; return ltm_err_invalid_template;
} }
break; break;
@ -194,20 +167,8 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) {
// Add remaining trailing literal // Add remaining trailing literal
if (len > 0) { if (len > 0) {
ltm_template_block *blocks = LTM_RES(ltm_template_block_append(template, ltm_template_block_type_literal,
realloc(template->blocks.arr, (void *)s, len));
(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; *out = template;

View File

@ -1,3 +1,4 @@
#include "ltm/common.h"
#include "test.h" #include "test.h"
#include "ltm/template.h" #include "ltm/template.h"
@ -12,8 +13,8 @@ void test_single_placeholder() {
TEST_CHECK(template->blocks.len == 2); TEST_CHECK(template->blocks.len == 2);
TEST_CHECK(template->blocks.arr[0].type == ltm_template_block_type_literal); 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].data.ptr == s);
TEST_CHECK(template->blocks.arr[0].len == 7); 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[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.len == 3);
TEST_CHECK(template->blocks.arr[0].type == ltm_template_block_type_literal); 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].data.ptr == s);
TEST_CHECK(template->blocks.arr[0].len == 7); 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[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].type == ltm_template_block_type_literal);
TEST_CHECK(template->blocks.arr[2].data.s == s + 18); TEST_CHECK(template->blocks.arr[2].data.ptr == s + 18);
TEST_CHECK(template->blocks.arr[2].len == 1); TEST_CHECK(template->blocks.arr[2].data.len == 1);
TEST_CHECK(template->names.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); 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 = { TEST_LIST = {
{ "template single placeholder", test_single_placeholder }, { "template single placeholder", test_single_placeholder },
{ "template single placeholder trailing", test_single_placeholder_trailing }, { "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 } { NULL, NULL }
}; };