diff --git a/package/c/hectic/docs/templater.md b/package/c/hectic/docs/templater.md index af287d3..069a4ae 100755 --- a/package/c/hectic/docs/templater.md +++ b/package/c/hectic/docs/templater.md @@ -123,16 +123,16 @@ Includes content from other templates. } ``` -## Function Tags +## Execution Tags **Note:** Currently not included in C library; implemented as a wrapper on applicable platforms. -Enables calling functions with arguments. +Enables calling functions with arguments, or execute code. Have hardcoded context var - alows use template context - **Prefix** Denotes a function call. *Example:* `exec` | *(Empty)* *Function Example:* ```tpl - {% exec my_function(arg1, arg2, 'literal') %} + {% exec RETURN my_function(context->arg1, context->arg2, 'literal') %} {% exec RETURN 'aaaaa' %} ``` @@ -143,3 +143,32 @@ Enables calling functions with arguments. - **Missing Fields/Functions/Templates:** Configurable to either return an error or warning. - **Circular Includes:** Detect when possible. - **No Shadowing:** Variables defined in section tags must not conflict with context variable names, otherwise, return an error. + + +## Shared example +```tpl +
text before
+ + {% include inner_template %} + + {% name %} + + {% for item in array do + some text: {% name2 %} + {% item.name %} + %} + +
code insertion:
+ {% execute + context + '{"name3": "zalupa"}'; + + IF context->condition THEN + RAISE INFO 'some log'; + + RETURN 'some text'; + END + RETURN 'some other text'; + %} + + +``` \ No newline at end of file diff --git a/package/c/hectic/hectic.c b/package/c/hectic/hectic.c index efc3d9e..5fe1662 100644 --- a/package/c/hectic/hectic.c +++ b/package/c/hectic/hectic.c @@ -2241,36 +2241,70 @@ char *log_rules_to_debug_str__(CTX_DECLARATION, char *name, LogRule *self, PtrSe return result; } +// ---------- +// -- View -- +// ---------- + +View view_create(const void *data, size_t len, size_t isize) { + View view = { .data = data, .len = len, .isize = isize }; + return view; +} + +View string_to_view(const char *str) { + return view_create(str, strlen(str), sizeof(char)); +} + +View *string_to_view_ptr__(POSITION_INFO_DECLARATION, Arena *arena, const char *str) { + View *view = arena_alloc__(POSITION_INFO, arena, sizeof(View)); + const View tmp = string_to_view(str); + *(void **)&view->data = (void *)tmp.data; + *(size_t *)&view->len = tmp.len; + *(size_t *)&view->isize = tmp.isize; + return view; +} + // --------------- // -- Templater -- // --------------- // Look at package\c\hectic\docs\templater.md -TemplateConfig template_default_config__(POSITION_INFO_DECLARATION) { +TemplateConfig template_default_config__(POSITION_INFO_DECLARATION, Arena *arena) { raise_message(LOG_LEVEL_TRACE, POSITION_INFO, "TEMPLATE: Default config"); - TemplateConfig config; - - config.Syntax.Braces.open = "{%"; - config.Syntax.Braces.close = "%}"; - config.Syntax.Section.control = "for "; - config.Syntax.Section.source = " in "; - config.Syntax.Section.begin = " do "; - config.Syntax.Interpolate.invoke = ""; - config.Syntax.Include.invoke = "include "; - config.Syntax.Execute.invoke = "exec "; - config.Syntax.nesting = "->"; + TemplateConfig config = { + .Syntax = { + .Braces = { + .open = string_to_view_ptr__(POSITION_INFO, arena, "{%"), + .close = string_to_view_ptr__(POSITION_INFO, arena, "%}") + }, + .Section = { + .control = string_to_view_ptr__(POSITION_INFO, arena, "for "), + .source = string_to_view_ptr__(POSITION_INFO, arena, " in "), + .begin = string_to_view_ptr__(POSITION_INFO, arena, " do ") + }, + .Interpolate = { + .invoke = string_to_view_ptr__(POSITION_INFO, arena, "") + }, + .Include = { + .invoke = string_to_view_ptr__(POSITION_INFO, arena, "include ") + }, + .Execute = { + .invoke = string_to_view_ptr__(POSITION_INFO, arena, "exec ") + }, + .nesting = string_to_view_ptr__(POSITION_INFO, arena, "->") + } + }; return config; } #define CHECK_CONFIG_STR(field, name) \ do { \ - if (config->Syntax.field == NULL) { \ + if (config->Syntax.field->data == NULL) { \ raise_message(LOG_LEVEL_EXCEPTION, POSITION_INFO, "VALIDATE: " name " is NULL"); \ return false; \ } \ - if (strlen(config->Syntax.field) > TEMPLATE_MAX_PREFIX_LEN) { \ + if (config->Syntax.field->len > TEMPLATE_MAX_PREFIX_LEN) { \ raise_message(LOG_LEVEL_EXCEPTION, POSITION_INFO, "VALIDATE: " name " is too long"); \ return false; \ } \ @@ -2299,42 +2333,95 @@ bool template_validate_config__(POSITION_INFO_DECLARATION, const TemplateConfig #undef CHECK_CONFIG_STR #define TEMPLATE_ASSERT_SYNTAX(pattern, message_arg, code_arg) \ - if (strncmp(*s, pattern, strlen(pattern))) { \ + if (strncmp(*s, pattern, strlen(pattern)) == 0) { \ raise_message(LOG_LEVEL_EXCEPTION, POSITION_INFO, "PARSE: " message_arg); \ return RESULT_ERROR(TemplateResult, code_arg, message_arg); \ } +TemplateValue init_template_value__(POSITION_INFO_DECLARATION, TemplateNodeType type) { + TemplateValue value; + switch (type) { + case TEMPLATE_NODE_TEXT: + value.text.content = NULL; + break; + case TEMPLATE_NODE_INTERPOLATE: + value.interpolate.key = NULL; + break; + case TEMPLATE_NODE_SECTION: + value.section.iterator = NULL; + value.section.collection = NULL; + value.section.body = NULL; + break; + case TEMPLATE_NODE_EXECUTE: + value.execute.code = NULL; + break; + case TEMPLATE_NODE_INCLUDE: + value.include.key = NULL; + break; + default: + raise_message(LOG_LEVEL_EXCEPTION, POSITION_INFO, "INIT: Unknown node type"); + exit(1); + } + return value; +} + +TemplateNode init_template_node__(POSITION_INFO_DECLARATION, Arena *arena, TemplateNodeType type) { + TemplateNode node; + node.next = NULL; + node.type = type; + node.value = arena_alloc__(POSITION_INFO, arena, sizeof(TemplateValue)); + *node.value = init_template_value__(POSITION_INFO, type); + return node; +} + +TemplateResult init_template_result__(POSITION_INFO_DECLARATION, Arena *arena, TemplateNodeType type) { + TemplateResult result; + result.type = RESULT_SOME; + result.Result.some = arena_alloc__(POSITION_INFO, arena, sizeof(TemplateNode)); + *result.Result.some = init_template_node__(POSITION_INFO, arena, type); + return result; +} + +TemplateNode new_template_node__(TemplateNodeType type, TemplateValue *value) { + TemplateNode node; + node.next = NULL; + node.type = type; + node.value = value; + return node; +} + TemplateResult template_parse__(POSITION_INFO_DECLARATION, Arena *arena, const char **s, const TemplateConfig *config); TemplateResult template_parse_interpolation__(POSITION_INFO_DECLARATION, Arena *arena, const char **s_ptr, const TemplateConfig *config) { raise_message(LOG_LEVEL_TRACE, POSITION_INFO, "PARSE: Interpolation"); - TemplateResult result; + TemplateResult result = init_template_result__(POSITION_INFO, arena, TEMPLATE_NODE_INTERPOLATE); const char **s = s_ptr; // Skip to the content of the interpolation - *s += strlen(config->Syntax.Braces.open); + *s += config->Syntax.Braces.open->len; *s = skip_whitespace(*s); - *s += strlen(config->Syntax.Interpolate.invoke); + *s += config->Syntax.Interpolate.invoke->len; *s = skip_whitespace(*s); const char *key_start = *s; - while (isalnum(**s)) { - if (**s == ' ' || strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close))) break; - TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.open, "Nested tag in interpolation", TEMPLATE_ERROR_NESTED_INTERPOLATION); + while (**s != '\0') { + if (isspace(**s) || strncmp(*s, config->Syntax.Braces.close->data, config->Syntax.Braces.close->len) == 0) break; + TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.open->data, "Nested tag in interpolation", TEMPLATE_ERROR_NESTED_INTERPOLATION); (*s)++; } size_t key_len = *s - key_start; - result.type = RESULT_SOME; + + char *key = arena_strncpy__(POSITION_INFO, arena, key_start, key_len); + + result.Result.some->value->interpolate.key = key; - result.Result.some.value.interpolate.key = arena_strncpy__(POSITION_INFO, arena, key_start, key_len); - result.Result.some.type = TEMPLATE_NODE_INTERPOLATE; - - *s_ptr = *s + strlen(config->Syntax.Braces.close); + *s = skip_whitespace(*s); + *s_ptr = *s + config->Syntax.Braces.close->len; return result; } @@ -2342,46 +2429,44 @@ TemplateResult template_parse_interpolation__(POSITION_INFO_DECLARATION, Arena * TemplateResult template_parse_section__(POSITION_INFO_DECLARATION, Arena *arena, const char **s_ptr, const TemplateConfig *config) { raise_message(LOG_LEVEL_TRACE, POSITION_INFO, "PARSE: Section"); - TemplateResult result; - result.type = RESULT_SOME; - result.Result.some.type = TEMPLATE_NODE_SECTION; + TemplateResult result = init_template_result__(POSITION_INFO, arena, TEMPLATE_NODE_SECTION); const char **s = s_ptr; // Skip to the content of the section - *s += strlen(config->Syntax.Braces.open); + *s += config->Syntax.Braces.open->len; *s = skip_whitespace(*s); - *s += strlen(config->Syntax.Section.control); + *s += config->Syntax.Section.control->len; // Find the iterator name *s = skip_whitespace(*s); const char *iterator_start = *s; - while (isalnum(**s)) { - if (**s == ' ' || **s == '\n' || **s == '\t' || strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source))) break; - TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.close, "Unexpected section end", TEMPLATE_ERROR_UNEXPECTED_SECTION_END); - TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.open, "Nested tag in section element name", TEMPLATE_ERROR_NESTED_SECTION_ITERATOR); + while (**s != '\0') { + if (**s == ' ' || **s == '\n' || **s == '\t' || strncmp(*s, config->Syntax.Section.source->data, config->Syntax.Section.source->len) == 0) break; + TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.close->data, "Unexpected section end", TEMPLATE_ERROR_UNEXPECTED_SECTION_END); + TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.open->data, "Nested tag in section element name", TEMPLATE_ERROR_NESTED_SECTION_ITERATOR); (*s)++; } size_t iterator_len = *s - iterator_start; - result.Result.some.value.section.iterator = arena_strncpy__(POSITION_INFO, arena, iterator_start, iterator_len); + result.Result.some->value->section.iterator = arena_strncpy__(POSITION_INFO, arena, iterator_start, iterator_len); // Find the collection name *s = skip_whitespace(*s); const char *collection_start = *s; - while (isalnum(**s)) { - if (**s == ' ' || **s == '\n' || **s == '\t' || strncmp(*s, config->Syntax.Section.begin, strlen(config->Syntax.Section.begin))) break; - TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.close, "Unexpected section end", TEMPLATE_ERROR_UNEXPECTED_SECTION_END); - TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.open, "Nested tag in section iterator", TEMPLATE_ERROR_NESTED_SECTION_ITERATOR); + while (**s != '\0') { + if (**s == ' ' || **s == '\n' || **s == '\t' || strncmp(*s, config->Syntax.Section.begin->data, config->Syntax.Section.begin->len) == 0) break; + TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.close->data, "Unexpected section end", TEMPLATE_ERROR_UNEXPECTED_SECTION_END); + TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.open->data, "Nested tag in section iterator", TEMPLATE_ERROR_NESTED_SECTION_ITERATOR); (*s)++; } size_t collection_len = *s - collection_start; - result.Result.some.value.section.collection = arena_strncpy__(POSITION_INFO, arena, collection_start, collection_len); + result.Result.some->value->section.collection = arena_strncpy__(POSITION_INFO, arena, collection_start, collection_len); // Parse the body TemplateResult body_result = template_parse__(POSITION_INFO, arena, s, config); @@ -2389,40 +2474,39 @@ TemplateResult template_parse_section__(POSITION_INFO_DECLARATION, Arena *arena, return body_result; } - result.Result.some.value.section.body = &body_result.Result.some; + result.Result.some->value->section.body = body_result.Result.some; - *s_ptr = *s + strlen(config->Syntax.Braces.close); + *s_ptr = *s + config->Syntax.Braces.close->len; return result; } TemplateResult template_parse_include__(POSITION_INFO_DECLARATION, Arena *arena, const char **s_ptr, const TemplateConfig *config) { raise_message(LOG_LEVEL_TRACE, POSITION_INFO, "PARSE: Include"); - TemplateResult result; - result.type = RESULT_SOME; - result.Result.some.type = TEMPLATE_NODE_INCLUDE; + + TemplateResult result = init_template_result__(POSITION_INFO, arena, TEMPLATE_NODE_INCLUDE); const char **s = s_ptr; // Skip to the content of the include - *s += strlen(config->Syntax.Braces.open); + *s += config->Syntax.Braces.open->len; *s = skip_whitespace(*s); - *s += strlen(config->Syntax.Include.invoke); + *s += config->Syntax.Include.invoke->len; *s = skip_whitespace(*s); const char *include_start = *s; - while (isalnum(**s)) { - if (**s == ' ' || **s == '\n' || **s == '\t' || strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close))) break; - TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.open, "Nested tag in include", TEMPLATE_ERROR_NESTED_INCLUDE); + while (**s != '\0') { + if (**s == ' ' || **s == '\n' || **s == '\t' || strncmp(*s, config->Syntax.Braces.close->data, config->Syntax.Braces.close->len) == 0) break; + TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.open->data, "Nested tag in include", TEMPLATE_ERROR_NESTED_INCLUDE); (*s)++; } size_t include_len = *s - include_start; - result.Result.some.value.include.key = arena_strncpy__(POSITION_INFO, arena, include_start, include_len); + result.Result.some->value->include.key = arena_strncpy__(POSITION_INFO, arena, include_start, include_len); - *s_ptr = *s + strlen(config->Syntax.Braces.close); + *s_ptr = *s + config->Syntax.Braces.close->len; return result; } @@ -2430,28 +2514,26 @@ TemplateResult template_parse_include__(POSITION_INFO_DECLARATION, Arena *arena, TemplateResult template_parse_execute__(POSITION_INFO_DECLARATION, Arena *arena, const char **s_ptr, const TemplateConfig *config) { raise_message(LOG_LEVEL_TRACE, POSITION_INFO, "PARSE: Execute"); - TemplateResult result; - result.type = RESULT_SOME; - result.Result.some.type = TEMPLATE_NODE_EXECUTE; + TemplateResult result = init_template_result__(POSITION_INFO, arena, TEMPLATE_NODE_EXECUTE); const char **s = s_ptr; - *s += strlen(config->Syntax.Braces.open); + *s += config->Syntax.Braces.open->len; *s = skip_whitespace(*s); - *s += strlen(config->Syntax.Execute.invoke); + *s += config->Syntax.Execute.invoke->len; *s = skip_whitespace(*s); const char *code_start = *s; - while (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close))) { - TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.open, "Nested tag in execute", TEMPLATE_ERROR_NESTED_EXECUTE); + while (strncmp(*s, config->Syntax.Braces.close->data, config->Syntax.Braces.close->len) == 0) { + TEMPLATE_ASSERT_SYNTAX(config->Syntax.Braces.open->data, "Nested tag in execute", TEMPLATE_ERROR_NESTED_EXECUTE); (*s)++; } size_t code_len = *s - code_start; - result.Result.some.value.execute.code = arena_strncpy__(POSITION_INFO, arena, code_start, code_len); + result.Result.some->value->execute.code = arena_strncpy__(POSITION_INFO, arena, code_start, code_len); - *s_ptr = *s + strlen(config->Syntax.Braces.close); + *s_ptr = *s + config->Syntax.Braces.close->len; return result; } @@ -2472,62 +2554,129 @@ TemplateResult template_parse__(POSITION_INFO_DECLARATION, Arena *arena, const c const char *start = *s; TemplateNode *root = arena_alloc__(POSITION_INFO, arena, sizeof(TemplateNode)); + *root = init_template_node__(POSITION_INFO, arena, TEMPLATE_NODE_TEXT); + TemplateNode *current = root; + bool current_node_filled = false; - int open_brace_len = strlen(config->Syntax.Braces.open); + int open_brace_len = config->Syntax.Braces.open->len; - while (*s) { - // Find the first open brace - if (strncmp(*s, config->Syntax.Braces.open, open_brace_len) == 0) { - // Add text node if there is any text before the tag + while (*s && **s != '\0') { + if (strncmp(*s, config->Syntax.Braces.open->data, open_brace_len) == 0) { if (start != *s) { raise_message(LOG_LEVEL_TRACE, POSITION_INFO, "PARSE: Text node: %s", arena_strncpy__(POSITION_INFO, DISPOSABLE_ARENA, start, *s - start)); - current->type = TEMPLATE_NODE_TEXT; - current->value.text.content = arena_strncpy__(POSITION_INFO, arena, start, *s - start); + + if (current_node_filled) { + TemplateNode *new_node = arena_alloc__(POSITION_INFO, arena, sizeof(TemplateNode)); + *new_node = init_template_node__(POSITION_INFO, arena, TEMPLATE_NODE_TEXT); + current->next = new_node; + current = new_node; + } else { + current->type = TEMPLATE_NODE_TEXT; + *current->value = init_template_value__(POSITION_INFO, TEMPLATE_NODE_TEXT); + } + + current->value->text.content = arena_strncpy__(POSITION_INFO, arena, start, *s - start); + current_node_filled = true; } - // Deside tag type by prefix + // Determine tag type by prefix TemplateResult current_result; { raise_message(LOG_LEVEL_TRACE, POSITION_INFO, "PARSE: Found tag"); const char *tag_prefix = *s + open_brace_len; tag_prefix = skip_whitespace(tag_prefix); - raise_trace("tag_prefix: %p", tag_prefix); + raise_trace("tag_prefix: %p", tag_prefix); - if (strncmp(tag_prefix, config->Syntax.Section.control, strlen(config->Syntax.Section.control)) == 0) { + typedef struct { + const View * const prefix; + int tag_type; + } PrefixMatch; + + PrefixMatch matches[] = { + {config->Syntax.Section.control, 1}, + {config->Syntax.Interpolate.invoke, 2}, + {config->Syntax.Include.invoke, 3}, + {config->Syntax.Execute.invoke, 4} + }; + + int matched_type = 0; + size_t max_length = 0; + + // Find longest match (in case when one name of tage is part of another) + for (int i = 0; i < 4; i++) { + if (strncmp(tag_prefix, matches[i].prefix->data, matches[i].prefix->len) == 0) { + // NOTE(yukkop): >= becouse one of the strings may be "" + if (matches[i].prefix->len >= max_length) { + max_length = matches[i].prefix->len; + matched_type = matches[i].tag_type; + } + } + } + + if (matched_type == 1) { raise_message(LOG_LEVEL_TRACE, POSITION_INFO, "PARSE: Section tag"); current_result = template_parse_section__(POSITION_INFO, arena, s, config); - } else if (strncmp(tag_prefix, config->Syntax.Interpolate.invoke, strlen(config->Syntax.Interpolate.invoke)) == 0) { + start = *s; + } else if (matched_type == 2) { raise_message(LOG_LEVEL_TRACE, POSITION_INFO, "PARSE: Interpolation tag"); current_result = template_parse_interpolation__(POSITION_INFO, arena, s, config); - } else if (strncmp(tag_prefix, config->Syntax.Include.invoke, strlen(config->Syntax.Include.invoke)) == 0) { + start = *s; + } else if (matched_type == 3) { raise_message(LOG_LEVEL_TRACE, POSITION_INFO, "PARSE: Include tag"); current_result = template_parse_include__(POSITION_INFO, arena, s, config); - } else if (strncmp(tag_prefix, config->Syntax.Execute.invoke, strlen(config->Syntax.Execute.invoke)) == 0) { + start = *s; + } else if (matched_type == 4) { raise_message(LOG_LEVEL_TRACE, POSITION_INFO, "PARSE: Execute tag"); current_result = template_parse_execute__(POSITION_INFO, arena, s, config); + start = *s; } else { raise_message(LOG_LEVEL_EXCEPTION, POSITION_INFO, "PARSE: Unknown tag prefix: %s", slice_create__(POSITION_INFO, 1, (char *)tag_prefix, strlen(tag_prefix), 0, TEMPLATE_MAX_PREFIX_LEN)); - return RESULT_ERROR(TemplateResult, TEMPLATE_ERROR_UNKNOWN_TAG, "Unknown tag prefix"); } TRY(current_result); } - *current = current_result.Result.some; - current->next = arena_alloc__(POSITION_INFO, arena, sizeof(TemplateNode)); - current = current->next; + if (current_node_filled) { + // SAFETY(yukkop): NO init necessary here + TemplateNode *new_node = arena_alloc__(POSITION_INFO, arena, sizeof(TemplateNode)); + *new_node = *current_result.Result.some; + current->next = new_node; + current = new_node; + } else { + *current = *current_result.Result.some; + } + current_node_filled = true; } - (*s)++; + if (**s != '\0') { + (*s)++; + } } // Add text node if there is any text after the last tag if (start != *s) { - current->type = TEMPLATE_NODE_TEXT; - current->value.text.content = arena_strncpy__(POSITION_INFO, arena, start, *s - start); + if (current_node_filled) { + TemplateNode *new_node = arena_alloc__(POSITION_INFO, arena, sizeof(TemplateNode)); + *new_node = init_template_node__(POSITION_INFO, arena, TEMPLATE_NODE_TEXT); + current->next = new_node; + current = new_node; + } else { + current->type = TEMPLATE_NODE_TEXT; + *current->value = init_template_value__(POSITION_INFO, TEMPLATE_NODE_TEXT); + } + + current->value->text.content = arena_strncpy__(POSITION_INFO, arena, start, *s - start); + current_node_filled = true; + } + + // Set null when node is not filled + if (!current_node_filled && current == root) { + root->type = TEMPLATE_NODE_TEXT; + *root->value = init_template_value__(POSITION_INFO, TEMPLATE_NODE_TEXT); + root->value->text.content = arena_strncpy__(POSITION_INFO, arena, "", 0); } return RESULT_SOME(TemplateResult, *root); @@ -2608,10 +2757,9 @@ char *template_value_to_debug_str__(POSITION_INFO_DECLARATION, Arena *arena, con char *template_node_to_debug_str__(POSITION_INFO_DECLARATION, Arena *arena, const char *name, const TemplateNode *self, PtrSet *visited) { char *result = arena_alloc(arena, MEM_KiB); - STRUCT_TO_DEBUG_STR(arena, result, TemplateNode, name, self, visited, 4, + STRUCT_TO_DEBUG_STR(arena, result, TemplateNode, name, self, visited, 3, enum_to_debug_str__(POSITION_INFO, arena, "type", self->type, template_node_type_to_string(self->type)), - template_value_to_debug_str__(POSITION_INFO, arena, "value", &self->value, self->type, visited), - template_node_to_debug_str__(POSITION_INFO, arena, "children", self->children, visited), + template_value_to_debug_str__(POSITION_INFO, arena, "value", self->value, self->type, visited), template_node_to_debug_str__(POSITION_INFO, arena, "next", self->next, visited) ); return result; @@ -2644,38 +2792,29 @@ char *template_node_to_json_str__(POSITION_INFO_DECLARATION, Arena *arena, const switch (node->type) { case TEMPLATE_NODE_SECTION: APPEND("\"content\":{\"iterator\":\"%s\",\"collection\":\"%s\"}", - node->value.section.iterator, - node->value.section.collection); - char *body_str = template_node_to_json_str__(POSITION_INFO, arena, node->value.section.body, depth + 1); + node->value->section.iterator, + node->value->section.collection); + char *body_str = template_node_to_json_str__(POSITION_INFO, arena, node->value->section.body, depth + 1); if (body_str) { APPEND(",\"body\":%s", body_str); } break; case TEMPLATE_NODE_INTERPOLATE: - APPEND("\"content\":{\"key\":\"%s\"}", node->value.interpolate.key); + APPEND("\"content\":{\"key\":\"%s\"}", node->value->interpolate.key); break; case TEMPLATE_NODE_EXECUTE: - APPEND("\"content\":{\"code\":\"%s\"}", node->value.execute.code); + APPEND("\"content\":{\"code\":\"%s\"}", node->value->execute.code); break; case TEMPLATE_NODE_INCLUDE: - APPEND("\"content\":{\"key\":\"%s\"}", node->value.include.key); + APPEND("\"content\":{\"key\":\"%s\"}", node->value->include.key); break; case TEMPLATE_NODE_TEXT: - APPEND("\"content\":{\"content\":\"%s\"}", node->value.text.content); + APPEND("\"content\":{\"content\":\"%s\"}", node->value->text.content); break; default: break; } - if (node->children) { - APPEND(",\"children\":["); - char *child_str = template_node_to_json_str__(POSITION_INFO, arena, node->children, depth + 1); - if (child_str) { - APPEND(",%s", child_str); - } - APPEND("]"); - } - APPEND("}"); if (node->next) { @@ -2699,4 +2838,4 @@ char *template_node_to_json_str__(POSITION_INFO_DECLARATION, Arena *arena, const // --------- #undef POSITION_INFO_DECLARATION -#undef POSITION_INFO \ No newline at end of file +#undef POSITION_INFO diff --git a/package/c/hectic/hectic.h b/package/c/hectic/hectic.h index 26588f6..994d765 100644 --- a/package/c/hectic/hectic.h +++ b/package/c/hectic/hectic.h @@ -147,7 +147,7 @@ typedef struct { ResultType type; \ union { \ HecticError error; \ - some_type some; \ + some_type *some; \ } Result; \ } name##Result @@ -166,11 +166,11 @@ typedef struct { #define RESULT_ERROR_CODE(result) (result.Result.error.code) #define RESULT_ERROR_MESSAGE(result) (result.Result.error.message) -#define RESULT_SOME_VALUE(result) (result.Result.some) +#define RESULT_SOME_VALUE(result) (*result.Result.some) #define RESULT_ERROR_VALUE(result) (result.Result.error) #define RESULT_SOME(result_type, value) \ - (result_type) { .type = RESULT_SOME, .Result.some = value } + (result_type) { .type = RESULT_SOME, .Result.some = &value } #define RESULT_ERROR(result_type, error_code, error_message) \ (result_type) { .type = RESULT_ERROR, .Result.error = { .code = error_code, .message = error_message } } @@ -427,7 +427,7 @@ static Arena disposable_arena __attribute__((unused)) = {0}; #define DISPOSABLE_ARENA __extension__ ({ \ if (disposable_arena.begin == NULL) { \ - disposable_arena = arena_init__(__FILE__, __func__, __LINE__, MEM_MiB); \ + disposable_arena = arena_init__(__FILE__, __func__, __LINE__, MEM_MiB * 8); \ } else { \ arena_reset(&disposable_arena); \ } \ @@ -726,6 +726,16 @@ char* slice_to_debug_str__(const char* file, const char* func, int line, Arena * #define slice_to_debug_str(arena, slice) slice_to_debug_str__(__FILE__, __func__, __LINE__, arena, slice) +// ---------- +// -- View -- +// ---------- + +typedef struct { + const void * const data; + const size_t len; + const size_t isize; +} View; + // --------------- // -- Templater -- // --------------- @@ -743,24 +753,24 @@ typedef enum { typedef struct { struct { struct { - const char *open; // Default: "{%" - const char *close; // Default: "%}" + const View * const open; // Default: "{%" + const View * const close; // Default: "%}" } Braces; struct { - const char *control; // default: "for " - const char *source; // default: " in " - const char *begin; // default: " do " + const View * const control; // default: "for " + const View * const source; // default: " in " + const View * const begin; // default: " do " } Section; struct { - const char *invoke; // default: "" + const View * const invoke; // default: "" } Interpolate; struct { - const char *invoke; // default: "include " + const View * const invoke; // default: "include " } Include; struct { - const char *invoke; // default: "exec " + const View * const invoke; // default: "exec " } Execute; - const char *nesting; // default: "->" + const View * const nesting; // default: "->" } Syntax; } TemplateConfig; @@ -798,16 +808,15 @@ typedef union { struct TemplateNode { TemplateNodeType type; - TemplateValue value; - TemplateNode *children; // child nodes - TemplateNode *next; // sibling nodes + TemplateValue *value; + TemplateNode *next; }; RESULT(Template, TemplateNode); TemplateResult template_parse__(const char *file, const char *func, int line, Arena *arena, const char **s, const TemplateConfig *config); -TemplateConfig template_default_config__(const char *file, const char *func, int line); +TemplateConfig template_default_config__(const char *file, const char *func, int line, Arena *arena); char *template_node_to_debug_str__(const char *file, const char *func, int line, Arena *arena, const char *name, const TemplateNode *self, PtrSet *visited); @@ -815,7 +824,7 @@ char *template_node_to_json_str__(const char *file, const char *func, int line, #define template_parse(arena, s, config) template_parse__(__FILE__, __func__, __LINE__, arena, s, config) -#define template_default_config() template_default_config__(__FILE__, __func__, __LINE__) +#define template_default_config(arena) template_default_config__(__FILE__, __func__, __LINE__, arena) #define TEMPLATE_NODE_TO_DEBUG_STR(arena, name, node) \ template_node_to_debug_str__(__FILE__, __func__, __LINE__, arena, name, node, ptrset_init(arena)) @@ -823,4 +832,9 @@ char *template_node_to_json_str__(const char *file, const char *func, int line, #define TEMPLATE_NODE_TO_JSON_STR(arena, node) \ template_node_to_json_str__(__FILE__, __func__, __LINE__, arena, node, 0) +TemplateNode init_template_node__(const char *file, const char *func, int line, Arena *arena, TemplateNodeType type); + +#define init_template_node(arena, type) \ + init_template_node__(__FILE__, __func__, __LINE__, arena, type) + #endif // EPRINTF_H \ No newline at end of file diff --git a/package/c/hectic/test/06-templater.c b/package/c/hectic/test/06-templater.c index 56e2c51..3aa6e62 100755 --- a/package/c/hectic/test/06-templater.c +++ b/package/c/hectic/test/06-templater.c @@ -14,7 +14,6 @@ " content = %p \"Hello\"\n" \ " } %p\n" \ " } %p,\n" \ - " struct TemplateNode children = NULL,\n" \ " struct TemplateNode next = {\n" \ " enum type = INTERPOLATE 1 ,\n" \ " union TemplateValue value = {\n" \ @@ -22,7 +21,6 @@ " key = %p \"name\"\n" \ " } %p\n" \ " } %p,\n" \ - " struct TemplateNode children = NULL,\n" \ " struct TemplateNode next = {\n" \ " enum type = TEXT 0 ,\n" \ " union TemplateValue value = {\n" \ @@ -30,7 +28,6 @@ " content = %p \"!\"\n" \ " } %p\n" \ " } %p,\n" \ - " struct TemplateNode children = NULL,\n" \ " struct TemplateNode next = NULL\n" \ " } %p\n" \ " } %p\n" \ @@ -50,31 +47,29 @@ " content = %p \"Loop content\"\n" \ " } %p\n" \ " } %p,\n" \ - " struct TemplateNode children = NULL,\n" \ " struct TemplateNode next = NULL\n" \ " } %p\n" \ " } %p\n" \ " } %p,\n" \ - " struct TemplateNode children = NULL,\n" \ " struct TemplateNode next = NULL\n" \ "} %p\n" static void test_template_node_to_debug_str(Arena *arena) { TemplateNode *root = arena_alloc(arena, sizeof(TemplateNode)); - root->type = TEMPLATE_NODE_TEXT; - root->value.text.content = arena_strncpy(arena, "Hello", 5); + *root = init_template_node(arena, TEMPLATE_NODE_TEXT); + root->value->text.content = arena_strncpy(arena, "Hello", 5); root->next = arena_alloc(arena, sizeof(TemplateNode)); - root->next->type = TEMPLATE_NODE_INTERPOLATE; - root->next->value.interpolate.key = arena_strncpy(arena, "name", 4); + *root->next = init_template_node(arena, TEMPLATE_NODE_INTERPOLATE); + root->next->value->interpolate.key = arena_strncpy(arena, "name", 4); root->next->next = arena_alloc(arena, sizeof(TemplateNode)); - root->next->next->type = TEMPLATE_NODE_TEXT; - root->next->next->value.text.content = arena_strncpy(arena, "!", 1); + *root->next->next = init_template_node(arena, TEMPLATE_NODE_TEXT); + root->next->next->value->text.content = arena_strncpy(arena, "!", 1); char *debug_str = debug_to_pretty_str(arena, TEMPLATE_NODE_TO_DEBUG_STR(arena, "root", root)); - raise_log("debug_str: \n%s", debug_str); + raise_notice("debug_str: \n%s", debug_str); { // some debug output Arena *debug_arena = DISPOSABLE_ARENA; @@ -85,21 +80,21 @@ static void test_template_node_to_debug_str(Arena *arena) { char *expected_debug_str = arena_alloc(arena, MEM_KiB); sprintf(expected_debug_str, TEST_TEMPLATE_NODE_TO_DEBUG_STR, - (void*)root->value.text.content, - (void*)&root->value.text, - (void*)&root->value, - (void*)root->next->value.interpolate.key, - (void*)&root->next->value.interpolate, - (void*)&root->next->value, - (void*)root->next->next->value.text.content, - (void*)&root->next->next->value.text, - (void*)&root->next->next->value, + (void*)root->value->text.content, + (void*)&root->value->text, + (void*)root->value, + (void*)root->next->value->interpolate.key, + (void*)&root->next->value->interpolate, + (void*)root->next->value, + (void*)root->next->next->value->text.content, + (void*)&root->next->next->value->text, + (void*)root->next->next->value, (void*)root->next->next, (void*)root->next, (void*)root ); - raise_log("expected_debug_str: \n%s", expected_debug_str); + raise_notice("expected_debug_str: \n%s", expected_debug_str); assert(strcmp(debug_str, expected_debug_str) == 0); } @@ -107,21 +102,15 @@ static void test_template_node_to_debug_str(Arena *arena) { static void test_template_section_node_to_debug_str(Arena *arena) { // Create a section node with a child text node TemplateNode *root = arena_alloc(arena, sizeof(TemplateNode)); - root->type = TEMPLATE_NODE_SECTION; - root->value.section.iterator = arena_strncpy(arena, "item", 4); - root->value.section.collection = arena_strncpy(arena, "items", 5); - - // Create a body node (child of section) - root->value.section.body = arena_alloc(arena, sizeof(TemplateNode)); - root->value.section.body->type = TEMPLATE_NODE_TEXT; - root->value.section.body->value.text.content = arena_strncpy(arena, "Loop content", 12); + *root = init_template_node(arena, TEMPLATE_NODE_SECTION); + root->value->section.iterator = arena_strncpy(arena, "item", 4); + root->value->section.collection = arena_strncpy(arena, "items", 5); + + // Create a body node (child of section) + root->value->section.body = arena_alloc(arena, sizeof(TemplateNode)); + *root->value->section.body = init_template_node(arena, TEMPLATE_NODE_TEXT); + root->value->section.body->value->text.content = arena_strncpy(arena, "Loop content", 12); - // SAFETY(yukkop): if any of these are not NULL, the node will be corrupted - root->value.section.body->next = NULL; - root->value.section.body->children = NULL; - root->next = NULL; - root->children = NULL; - char *debug_str = debug_to_pretty_str(arena, TEMPLATE_NODE_TO_DEBUG_STR(arena, "root", root)); raise_log("debug_str: \n%s", debug_str); @@ -134,14 +123,14 @@ static void test_template_section_node_to_debug_str(Arena *arena) { char *expected_debug_str = arena_alloc(arena, MEM_KiB); sprintf(expected_debug_str, TEST_TEMPLATE_SECTION_NODE_TO_DEBUG_STR, - (void*)root->value.section.iterator, - (void*)root->value.section.collection, - (void*)root->value.section.body->value.text.content, - (void*)&root->value.section.body->value.text, - (void*)&root->value.section.body->value, - (void*)root->value.section.body, - (void*)&root->value.section, - (void*)&root->value, + (void*)root->value->section.iterator, + (void*)root->value->section.collection, + (void*)root->value->section.body->value->text.content, + (void*)&root->value->section.body->value->text, + (void*)root->value->section.body->value, + (void*)root->value->section.body, + (void*)&root->value->section, + (void*)root->value, (void*)root ); @@ -150,6 +139,47 @@ static void test_template_section_node_to_debug_str(Arena *arena) { assert(strcmp(debug_str, expected_debug_str) == 0); } +static void simplest_test_template_parse(Arena *arena, TemplateConfig *config) { + const char *template_str = "{{ name }}"; + + TemplateResult template_result = template_parse(arena, &template_str, config); + + if (IS_RESULT_ERROR(template_result)) { + raise_exception("template_parse failed"); + return; + } + + TemplateNode node = RESULT_SOME_VALUE(template_result); + + { // some debug output + Arena *debug_arena = DISPOSABLE_ARENA; + const char *json_str = TEMPLATE_NODE_TO_JSON_STR(debug_arena, &node); + Json *json = json_parse(debug_arena, &json_str); + raise_notice("json_str: \n%s", JSON_TO_PRETTY_STR(debug_arena, json)); + } +} + +static void simplest_interpolation_test_template_parse(Arena *arena, TemplateConfig *config) { + const char *template_str = "{% name %} {% name2 %}"; + + TemplateResult template_result = template_parse(arena, &template_str, config); + + if (IS_RESULT_ERROR(template_result)) { + raise_exception("template_parse failed"); + return; + } + + TemplateNode node = RESULT_SOME_VALUE(template_result); + + { // some debug output + Arena *debug_arena = DISPOSABLE_ARENA; + const char *json_str = TEMPLATE_NODE_TO_JSON_STR(debug_arena, &node); + Json *json = json_parse(debug_arena, &json_str); + raise_log("json_str: \n%s", json_str); + raise_notice("json_str: \n%s", JSON_TO_PRETTY_STR(debug_arena, json)); + } +} + int main(void) { printf("%sRunning %s%s%s\n", OPTIONAL_COLOR(COLOR_GREEN), OPTIONAL_COLOR(COLOR_CYAN), __FILE__, OPTIONAL_COLOR(COLOR_RESET)); debug_color_mode = COLOR_MODE_DISABLE; @@ -157,8 +187,6 @@ int main(void) { Arena arena = arena_init(ARENA_SIZE); - //TemplateConfig config = template_default_config(); - printf("%sRunning template parser tests...%s\n", OPTIONAL_COLOR(COLOR_GREEN), OPTIONAL_COLOR(COLOR_RESET)); test_template_node_to_debug_str(&arena); @@ -169,11 +197,19 @@ int main(void) { printf("%sTest 1: template_section_node_to_debug_str passed%s\n", OPTIONAL_COLOR(COLOR_GREEN), OPTIONAL_COLOR(COLOR_RESET)); arena_reset(&arena); - //test_template_parse(&arena, &config); - //printf("%sTest 1: template_parse passed%s\n", OPTIONAL_COLOR(COLOR_GREEN), OPTIONAL_COLOR(COLOR_RESET)); - //arena_reset(&arena); + Arena config_arena = arena_init(ARENA_SIZE); + TemplateConfig config = template_default_config(&config_arena); + + simplest_test_template_parse(&arena, &config); + printf("%sTest 2: simplest_test_template_parse passed%s\n", OPTIONAL_COLOR(COLOR_GREEN), OPTIONAL_COLOR(COLOR_RESET)); + arena_reset(&arena); + + simplest_interpolation_test_template_parse(&arena, &config); + printf("%sTest 3: simplest_interpolation_test_template_parse passed%s\n", OPTIONAL_COLOR(COLOR_GREEN), OPTIONAL_COLOR(COLOR_RESET)); + arena_reset(&arena); logger_free(); + arena_free(&config_arena); arena_free(&arena); printf("%sall tests passed %s%s%s\n", OPTIONAL_COLOR(COLOR_GREEN), OPTIONAL_COLOR(COLOR_CYAN), __FILE__, OPTIONAL_COLOR(COLOR_RESET)); return 0;