Compare commits
2 Commits
959be10264
...
c7a1fec6c2
Author | SHA1 | Date |
---|---|---|
Jef Roosens | c7a1fec6c2 | |
Jef Roosens | 11325bdbcd |
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#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,14 +62,19 @@ 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 (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;
|
||||
}
|
||||
|
||||
for (size_t block_index = 0; block_index < template->blocks.len;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }
|
||||
};
|
|
@ -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() {
|
Loading…
Reference in New Issue