feat(ltm): initial full template compiler/parser
parent
af5e519663
commit
ba320a4250
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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 }
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue