diff --git a/ltm/include/ltm/common.h b/ltm/include/ltm/common.h index ef86779..6851359 100644 --- a/ltm/include/ltm/common.h +++ b/ltm/include/ltm/common.h @@ -17,10 +17,16 @@ } \ } +#define LTM_MIN(x, y) ((x) < (y) ? (x) : (y)) +#define LTM_MAX(x, y) ((x) > (y) ? (x) : (y)) + typedef enum ltm_err { ltm_err_ok = 0, ltm_err_invalid_template, ltm_err_failed_alloc, + ltm_err_not_found, + ltm_err_wrong_block_type, + ltm_err_done, } ltm_err; #endif diff --git a/ltm/include/ltm/template.h b/ltm/include/ltm/template.h index 445fbf9..ed802f1 100644 --- a/ltm/include/ltm/template.h +++ b/ltm/include/ltm/template.h @@ -28,6 +28,12 @@ ltm_err ltm_template_compile(ltm_template **out, const char *template); ltm_err ltm_template_compile_n(ltm_template **out, const char *template, size_t len); +/** + * Free the template instance. After freeing a template, no instances associated + * with it are safe to use. + */ +void ltm_template_free(ltm_template *template); + /** * Represents a specific instance of a template. */ @@ -39,8 +45,15 @@ typedef struct ltm_instance ltm_instance; ltm_err ltm_template_instantiate(ltm_instance **out, const ltm_template *template); +/** + * Free the given instance, as well as all nested instances. + */ +void ltm_instance_free(ltm_instance *instance); + typedef enum ltm_instance_block_type { ltm_instance_block_type_buf = 0, + ltm_instance_block_type_buf_owned, + ltm_instance_block_type_nested, } ltm_instance_block_type; /** @@ -57,4 +70,23 @@ ltm_err ltm_instance_block_add_var(ltm_instance *instance, const char *name, ltm_err ltm_instance_block_add_nested(ltm_instance **out, ltm_instance *instance, const char *name); +/** + * Calculate the size of the resulting output of this instance. + */ +size_t ltm_instance_size(const ltm_instance *instance); + +/** + * Write at most `len` bytes to the given buffer. + * + * @param written outputs how many bytes were written to `buf` + * @param buf buffer to write to + * @param len length of the buffer + * @param instance instance to write + * @return `ltm_err_ok` if write was successful but there's more to be written, + * `ltm_err_done` if everything has been written successfully, or some other + * error code + */ +ltm_err ltm_instance_write(size_t *written, char *buf, size_t len, + ltm_instance *instance); + #endif diff --git a/ltm/src/ltm_instance.c b/ltm/src/ltm_instance.c index 6f9af67..0db85c2 100644 --- a/ltm/src/ltm_instance.c +++ b/ltm/src/ltm_instance.c @@ -1,3 +1,7 @@ +#include +#include + +#include "ltm/common.h" #include "ltm/template.h" #include "ltm/template_internal.h" @@ -25,6 +29,32 @@ ltm_err ltm_instance_init(ltm_instance **out) { return ltm_err_ok; } +void ltm_instance_free(ltm_instance *instance) { + if (instance->vars != NULL) { + free(instance->vars); + } + + ltm_instance_block *block = instance->blocks.head; + + while (block != NULL) { + switch (block->type) { + case ltm_instance_block_type_buf_owned: + free(block->data.ptr); + break; + case ltm_instance_block_type_nested: + ltm_instance_free(block->data.ptr); + break; + case ltm_instance_block_type_buf:; + } + + ltm_instance_block *temp = block->next; + free(block); + block = temp; + } + + free(instance); +} + ltm_err ltm_template_instantiate(ltm_instance **out, const ltm_template *template) { ltm_instance *instance; @@ -32,15 +62,20 @@ ltm_err ltm_template_instantiate(ltm_instance **out, instance->template = template; - ltm_instance_block ***vars = - malloc(template->names.len * sizeof(ltm_instance_block **)); + ltm_instance_block ***vars = NULL; - if (vars == NULL) { - return ltm_err_failed_alloc; + if (template->names.len > 0) { + vars = malloc(template->names.len * sizeof(ltm_instance_block **)); + + if (vars == NULL) { + ltm_instance_free(instance); + + return ltm_err_failed_alloc; + } + + instance->vars = vars; } - instance->vars = vars; - for (size_t block_index = 0; block_index < template->blocks.len; block_index++) { ltm_template_block *template_block = &template->blocks.arr[block_index]; @@ -48,7 +83,7 @@ ltm_err ltm_template_instantiate(ltm_instance **out, switch (template_block->type) { case ltm_template_block_type_literal: { ltm_instance_block *block; - LTM_RES(ltm_instance_block_init(&block)); + LTM_RES2(ltm_instance_block_init(&block), ltm_instance_free(instance)); block->type = ltm_instance_block_type_buf; block->data.ptr = template_block->data.ptr; @@ -67,6 +102,7 @@ ltm_err ltm_template_instantiate(ltm_instance **out, case ltm_template_block_type_nested: *vars = &instance->blocks.tail->next; vars++; + break; } } @@ -74,3 +110,151 @@ ltm_err ltm_template_instantiate(ltm_instance **out, return ltm_err_ok; } + +ltm_err ltm_instance_block_add_var(ltm_instance *instance, const char *name, + ltm_instance_block_type type, void *data, + size_t len) { + const ltm_template *template = instance->template; + + ltm_template_block_name *block_name = NULL; + size_t i = 0; + + for (i = 0; i < template->names.len; i++) { + block_name = &template->names.arr[i]; + + if (strncmp(name, block_name->name.s, block_name->name.len) == 0) { + break; + } + } + + if (i == template->names.len) { + return ltm_err_not_found; + } + + ltm_template_block *template_block = &template->blocks.arr[block_name->index]; + + if (template_block->type != ltm_template_block_type_var) { + return ltm_err_wrong_block_type; + } + + ltm_instance_block *block; + LTM_RES(ltm_instance_block_init(&block)); + + block->type = type; + block->data.ptr = data; + block->data.len = len; + + // We insert the block in the linked list and replace its next pointer as the + // new attachment point for this variable + block->next = *instance->vars[i]; + *instance->vars[i] = block; + instance->vars[i] = &block->next; + + return ltm_err_ok; +} + +ltm_err ltm_instance_block_add_nested(ltm_instance **out, + ltm_instance *instance, + const char *name) { + const ltm_template *template = instance->template; + bool matched = false; + + size_t i = 0; + for (i = 0; i < template->names.len && !matched; i++) { + ltm_template_block_name *block_name = &template->names.arr[i]; + matched = strncmp(name, block_name->name.s, block_name->name.len) == 0; + } + + if (!matched) { + return ltm_err_not_found; + } + + ltm_template_block *template_block = &template->blocks.arr[i]; + + if (template_block->type != ltm_template_block_type_nested) { + return ltm_err_wrong_block_type; + } + + ltm_instance_block *block; + LTM_RES(ltm_instance_block_init(&block)); + + ltm_instance *nested; + LTM_RES2(ltm_template_instantiate(&nested, template_block->data.ptr), + free(block)); + + block->type = ltm_instance_block_type_nested; + block->data.ptr = nested; + + // We insert the block in the linked list and replace its next pointer as the + // new attachment point for this variable + block->next = *instance->vars[i]; + *instance->vars[i] = block; + instance->vars[i] = &block->next; + + *out = nested; + + return ltm_err_ok; +} + +size_t ltm_instance_size(const ltm_instance *instance) { + size_t total = 0; + ltm_instance_block *block = instance->blocks.head; + + while (block != NULL) { + switch (block->type) { + case ltm_instance_block_type_buf: + case ltm_instance_block_type_buf_owned: + total += block->data.len; + break; + case ltm_instance_block_type_nested: + total += ltm_instance_size(block->data.ptr); + break; + } + + block = block->next; + } + + return total; +} + +ltm_err ltm_instance_write(size_t *written, char *buf, size_t len, + ltm_instance *instance) { + while ((*written < len) && (instance->blocks.current != NULL)) { + ltm_instance_block *current = instance->blocks.current; + + switch (current->type) { + case ltm_instance_block_type_buf: + case ltm_instance_block_type_buf_owned: { + size_t cap = + LTM_MIN(current->data.len - instance->written, len - *written); + memcpy(&buf[*written], current->data.ptr, cap); + *written += cap; + instance->written += cap; + + if (instance->written == current->data.len) { + instance->blocks.current = current->next; + instance->written = 0; + } + } break; + case ltm_instance_block_type_nested: { + size_t nested_written = 0; + ltm_err res = ltm_instance_write(&nested_written, &buf[*written], + len - *written, current->data.ptr); + *written += nested_written; + + switch (res) { + case ltm_err_done: + instance->blocks.current = current->next; + instance->written = 0; + break; + case ltm_err_ok: + break; + default: + return res; + } + } break; + } + } + + return instance->blocks.current == NULL ? ltm_err_done : ltm_err_ok; +} diff --git a/ltm/src/ltm_template.c b/ltm/src/ltm_template.c index 13ab14a..03dcbd3 100644 --- a/ltm/src/ltm_template.c +++ b/ltm/src/ltm_template.c @@ -13,6 +13,30 @@ ltm_err ltm_template_init(ltm_template **out) { return ltm_err_ok; } +void ltm_template_free(ltm_template *template) { + if (template->blocks.len > 0) { + for (size_t i = 0; i < template->blocks.len; i++) { + ltm_template_block *block = &template->blocks.arr[i]; + + switch (block->type) { + case ltm_template_block_type_nested: + ltm_template_free(block->data.ptr); + break; + case ltm_template_block_type_var: + case ltm_template_block_type_literal:; + } + } + + free(template->blocks.arr); + } + + if (template->names.len > 0) { + free(template->names.arr); + } + + free(template); +} + ltm_err ltm_template_name_append(ltm_template *template, const char *s, size_t len, size_t index) { ltm_template_block_name *names = diff --git a/ltm/src/ltm_template_compile.c b/ltm/src/ltm_template_compile.c index 7093093..b69aa4b 100644 --- a/ltm/src/ltm_template_compile.c +++ b/ltm/src/ltm_template_compile.c @@ -104,20 +104,26 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { while (ltm_template_next_placeholder(&ph, s, len)) { // Add part before placeholder as literal if (!in_nested && (ph.start != s)) { - LTM_RES(ltm_template_block_append( - template, ltm_template_block_type_literal, (void *)s, ph.start - s)); + LTM_RES2(ltm_template_block_append(template, + ltm_template_block_type_literal, + (void *)s, ph.start - s), + ltm_template_free(template)); } switch (ph.type) { // Invalid placeholders can be detected as early as possible case ltm_placeholder_type_invalid: + ltm_template_free(template); + return ltm_err_invalid_template; case ltm_placeholder_type_var: if (!in_nested) { - 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)); + LTM_RES2(ltm_template_block_append( + template, ltm_template_block_type_var, NULL, 0), + ltm_template_free(template)); + LTM_RES2(ltm_template_name_append(template, ph.name.s, ph.name.len, + template->blocks.len - 1), + ltm_template_free(template)); } break; case ltm_placeholder_type_nested_start: @@ -133,6 +139,8 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { case ltm_placeholder_type_nested_end: // This means there's more nested end placeholders than nested starts if (cur_nested_depth == 0) { + ltm_template_free(template); + return ltm_err_invalid_template; } @@ -141,17 +149,22 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { if (in_nested && (cur_nested_depth == nested_depth)) { size_t nested_len = ph.start - nested_start; ltm_template *nested_template; - LTM_RES( - ltm_template_compile_n(&nested_template, nested_start, nested_len)); + LTM_RES2( + ltm_template_compile_n(&nested_template, nested_start, nested_len), + ltm_template_free(template)); LTM_RES(ltm_template_block_append( template, ltm_template_block_type_nested, nested_template, 0)); - LTM_RES(ltm_template_name_append(template, nested_name, nested_name_len, - template->blocks.len - 1)); + LTM_RES2(ltm_template_name_append(template, nested_name, + nested_name_len, + template->blocks.len - 1), + ltm_template_free(template)); in_nested = false; } // We encountered a nested end without a start else if (!in_nested) { + ltm_template_free(template); + return ltm_err_invalid_template; } break; @@ -163,13 +176,16 @@ ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) { // Unfinished nested if (in_nested) { + ltm_template_free(template); + return ltm_err_invalid_template; } // Add remaining trailing literal if (len > 0) { - LTM_RES(ltm_template_block_append(template, ltm_template_block_type_literal, - (void *)s, len)); + LTM_RES2(ltm_template_block_append( + template, ltm_template_block_type_literal, (void *)s, len), + ltm_template_free(template)); } *out = template; diff --git a/ltm/test/instance.c b/ltm/test/instance.c new file mode 100644 index 0000000..b2a978b --- /dev/null +++ b/ltm/test/instance.c @@ -0,0 +1,33 @@ +#include "test.h" + +#include "ltm/common.h" +#include "ltm/template.h" +#include "ltm/template_internal.h" + +void test_single_placeholder() { + const char *s = "Hello, {{ world }}!"; + + ltm_template *template; + TEST_ASSERT(ltm_template_compile(&template, s) == ltm_err_ok); + + ltm_instance *instance; + TEST_CHECK(ltm_template_instantiate(&instance, template) == ltm_err_ok); + + TEST_CHECK(ltm_instance_block_add_var(instance, "world", ltm_instance_block_type_buf, "World", 5) == ltm_err_ok); + + TEST_CHECK(ltm_instance_size(instance) == 13); + + char buf[13]; + size_t written = 0; + TEST_CHECK(ltm_instance_write(&written, buf, 13, instance) == ltm_err_done); + TEST_CHECK(written == 13); + TEST_CHECK(strncmp(buf, "Hello, World!", 13) == 0); + + ltm_instance_free(instance); + ltm_template_free(template); +} + +TEST_LIST = { + { "instance single placeholder", test_single_placeholder }, + { NULL, NULL } +}; diff --git a/ltm/test/compile.c b/ltm/test/template_compile.c similarity index 97% rename from ltm/test/compile.c rename to ltm/test/template_compile.c index c7c7151..127b5fc 100644 --- a/ltm/test/compile.c +++ b/ltm/test/template_compile.c @@ -1,6 +1,6 @@ -#include "ltm/common.h" #include "test.h" +#include "ltm/common.h" #include "ltm/template.h" #include "ltm/template_internal.h" @@ -23,6 +23,8 @@ void test_single_placeholder() { 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); + + ltm_template_free(template); } void test_single_placeholder_trailing() { @@ -48,6 +50,8 @@ void test_single_placeholder_trailing() { 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); + + ltm_template_free(template); } void test_single_nested() { @@ -83,6 +87,8 @@ void test_single_nested() { TEST_CHECK(nested_template->names.arr[0].name.s == s + 33); TEST_CHECK(nested_template->names.arr[0].name.len == 3); TEST_CHECK(nested_template->names.arr[0].index == 1); + + ltm_template_free(template); } void test_unclosed_placeholder() {