feat(ltm): initial full template compiler/parser
ci/woodpecker/push/build Pipeline was successful Details

Jef Roosens 2023-12-16 15:34:31 +01:00
parent 9a207d0b0b
commit 1e34cb8c2d
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 {
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

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,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;

View File

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