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 {
 | 
			
		||||
  ltm_template_block_type type;
 | 
			
		||||
  union {
 | 
			
		||||
    ltm_template *template;
 | 
			
		||||
    const char *s;
 | 
			
		||||
  } data;
 | 
			
		||||
  struct {
 | 
			
		||||
    void *ptr;
 | 
			
		||||
    size_t len;
 | 
			
		||||
  } data;
 | 
			
		||||
} 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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_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,
 | 
			
		||||
static bool ltm_template_next_placeholder(ltm_placeholder *ph, const char *s,
 | 
			
		||||
                                          size_t len) {
 | 
			
		||||
  if (len == 0) {
 | 
			
		||||
    return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -26,9 +14,19 @@ bool ltm_template_next_placeholder(ltm_placeholder *ph, const char *s,
 | 
			
		|||
 | 
			
		||||
  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);
 | 
			
		||||
 | 
			
		||||
  // 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);
 | 
			
		||||
 | 
			
		||||
  // 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;
 | 
			
		||||
 | 
			
		||||
  // Skip whitespace over to next word
 | 
			
		||||
  while ((*ident == ' ') && (ident != ph->end - 1)) {
 | 
			
		||||
    ident++;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -79,7 +78,7 @@ bool ltm_template_next_placeholder(ltm_placeholder *ph, const char *s,
 | 
			
		|||
 | 
			
		||||
    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;
 | 
			
		||||
    } else if (strncmp("end", ident, ident_len) == 0) {
 | 
			
		||||
      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 false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ltm_err ltm_template_compile_n(ltm_template **out, const char *s, size_t len) {
 | 
			
		||||
  ltm_template *template;
 | 
			
		||||
  LTM_RES(ltm_template_init(&template));
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue