199 lines
5.2 KiB
C
199 lines
5.2 KiB
C
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "ltm/common.h"
|
|
#include "ltm/template.h"
|
|
#include "ltm/template_internal.h"
|
|
|
|
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] != '{')) {
|
|
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
|
|
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_nested_start;
|
|
} else if (strncmp("end", ident, ident_len) == 0) {
|
|
ph->type = ltm_placeholder_type_nested_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) {
|
|
ltm_template *template;
|
|
LTM_RES(ltm_template_init(&template));
|
|
|
|
ltm_placeholder ph;
|
|
bool in_nested = false;
|
|
const char *nested_start = NULL, *nested_name = NULL;
|
|
size_t nested_depth = 0, nested_name_len = 0;
|
|
|
|
size_t cur_nested_depth = 0;
|
|
|
|
while (ltm_template_next_placeholder(&ph, s, len)) {
|
|
// Add part before placeholder as literal
|
|
if (!in_nested && (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_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:
|
|
if (!in_nested) {
|
|
nested_start = ph.end + 1;
|
|
nested_depth = cur_nested_depth;
|
|
nested_name = ph.name.s;
|
|
nested_name_len = ph.name.len;
|
|
in_nested = true;
|
|
}
|
|
cur_nested_depth++;
|
|
break;
|
|
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;
|
|
}
|
|
|
|
cur_nested_depth--;
|
|
|
|
if (in_nested && (cur_nested_depth == nested_depth)) {
|
|
size_t nested_len = ph.start - nested_start;
|
|
ltm_template *nested_template;
|
|
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_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;
|
|
}
|
|
|
|
len -= ph.end + 1 - s;
|
|
s = ph.end + 1;
|
|
}
|
|
|
|
// Unfinished nested
|
|
if (in_nested) {
|
|
ltm_template_free(template);
|
|
|
|
return ltm_err_invalid_template;
|
|
}
|
|
|
|
// Add remaining trailing literal
|
|
if (len > 0) {
|
|
LTM_RES2(ltm_template_block_append(
|
|
template, ltm_template_block_type_literal, (void *)s, len),
|
|
ltm_template_free(template));
|
|
}
|
|
|
|
*out = template;
|
|
|
|
return ltm_err_ok;
|
|
}
|
|
|
|
ltm_err ltm_template_compile(ltm_template **out, const char *template) {
|
|
return ltm_template_compile_n(out, template, strlen(template));
|
|
}
|