diff --git a/package/c/hemar/hemar.c b/package/c/hemar/hemar.c index b903672..cec471b 100755 --- a/package/c/hemar/hemar.c +++ b/package/c/hemar/hemar.c @@ -1,100 +1,707 @@ -#include -#include -#include -#include -#include -#include "postgres.h" -#include "catalog/pg_type_d.h" -#include "fmgr.h" -#include "nodes/pg_list.h" -#include "parser/parse_func.h" -#include "utils/builtins.h" -#include "utils/datum.h" -#include "utils/json.h" -#include "utils/memutils.h" -#include "utils/regproc.h" -#include - -#define PRECOMPILED_LOG_LEVEL LOG_LEVEL_EXCEPTION - -#include "hectic.h" - -#ifdef PG_MODULE_MAGIC -PG_MODULE_MAGIC; -#endif - -static MemoryContext HemarContext = NULL; - -#define LOG_FILE "/tmp/hemar.log" - -#define INIT \ - MemoryContext oldctx; \ - oldctx = MemoryContextSwitchTo(HemarContext); \ - /* logger_init(); \ - logger_level(LOG_LEVEL_TRACE); \ - logger_set_file(LOG_FILE); \ - logger_set_output_mode(LOG_OUTPUT_BOTH);*/ \ - Arena arena = arena_init(MEM_MiB); - - -#define FREE \ - /*DISPOSABLE_ARENA_FREE*/; \ - /*arena_free(&arena);*/ \ - /*logger_free();*/ \ - arena_reset(&arena); \ - MemoryContextSwitchTo(oldctx); \ - MemoryContextReset(HemarContext); - -void noop_free(void* ptr) { - (void)ptr; // suppress unused warning -} - -void _PG_init(void) -{ - HemarContext = AllocSetContextCreate(TopMemoryContext, - "HemarContext", - ALLOCSET_DEFAULT_SIZES); - - MemoryAllocator allocators = { - .malloc = palloc, - .free = noop_free - }; - - set_memory_allocator(allocators); -} - -PG_FUNCTION_INFO_V1(pg_template_parse); - -Datum pg_template_parse(PG_FUNCTION_ARGS) { - INIT; - - text *template_text = PG_GETARG_TEXT_PP(0); - const char *template_str = text_to_cstring(template_text); - raise_notice("template_str: %s", template_str); - - TemplateResult template_result; - TemplateConfig config = template_default_config(&arena); - - raise_info("start parsing...."); - template_result = template_parse(&arena, &template_str, &config); - raise_info("parsing finished...."); - - if (IS_RESULT_ERROR(template_result)) { - FREE; - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Failed to parse template: %s", - RESULT_ERROR_MESSAGE(template_result)))); - } - - const char *json_str = TEMPLATE_NODE_TO_JSON_STR(&arena, &(RESULT_SOME_VALUE(template_result))); - Json *json = json_parse(&arena, &json_str); \ - - char *result_str = JSON_TO_STR(&arena, json); - - raise_notice("%s", result_str); - text *result = cstring_to_text(result_str); - - FREE; - - PG_RETURN_TEXT_P(result); -} +/* + * hemar.c + * Template parser implementation for Hemar + */ +#include "hemar.h" +#include "postgres.h" + +#include +#include + +#include "utils/elog.h" +#include "utils/memutils.h" +#include "mb/pg_wchar.h" + +/* Helper function to skip whitespace */ +static const char * +skip_whitespace(const char *s) +{ + while (*s && isspace((unsigned char)*s)) + s++; + return s; +} + +/* Initialize template value based on type */ +static TemplateValue +init_template_value(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: + elog(ERROR, "Unknown template node type: %d", type); + /* This won't be reached due to elog(ERROR) */ + memset(&value, 0, sizeof(value)); + break; + } + + return value; +} + +/* Initialize a new template node */ +static TemplateNode * +init_template_node(MemoryContext context, TemplateNodeType type) +{ + TemplateNode *node; + + node = (TemplateNode *) MemoryContextAlloc(context, sizeof(TemplateNode)); + node->next = NULL; + node->type = type; + node->value = (TemplateValue *) MemoryContextAlloc(context, sizeof(TemplateValue)); + *node->value = init_template_value(type); + + return node; +} + +/* Error code to string conversion */ +const char * +template_error_to_string(TemplateErrorCode code) +{ + switch (code) + { + case TEMPLATE_ERROR_NONE: + return "No error"; + case TEMPLATE_ERROR_UNKNOWN_TAG: + return "Unknown tag"; + case TEMPLATE_ERROR_NESTED_INTERPOLATION: + return "Nested interpolation"; + case TEMPLATE_ERROR_NESTED_SECTION_ITERATOR: + return "Nested section iterator"; + case TEMPLATE_ERROR_UNEXPECTED_SECTION_END: + return "Unexpected section end"; + case TEMPLATE_ERROR_NESTED_INCLUDE: + return "Nested include"; + case TEMPLATE_ERROR_NESTED_EXECUTE: + return "Nested execute"; + case TEMPLATE_ERROR_INVALID_CONFIG: + return "Invalid config"; + case TEMPLATE_ERROR_OUT_OF_MEMORY: + return "Out of memory"; + case TEMPLATE_ERROR_UNEXPECTED_INCLUDE_END: + return "Unexpected include end"; + case TEMPLATE_ERROR_UNEXPECTED_EXECUTE_END: + return "Unexpected execute end"; + default: + return "Unknown error"; + } +} + +/* Default template configuration */ +TemplateConfig +template_default_config(MemoryContext context) +{ + 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 = "->"; + + return config; +} + +/* Validate template configuration */ +bool +template_validate_config(const TemplateConfig *config, TemplateErrorCode *error_code) +{ + if (!config) + { + if (error_code) + *error_code = TEMPLATE_ERROR_INVALID_CONFIG; + return false; + } + + /* Check open brace */ + if (!config->Syntax.Braces.open || strlen(config->Syntax.Braces.open) > TEMPLATE_MAX_PREFIX_LEN) + { + if (error_code) + *error_code = TEMPLATE_ERROR_INVALID_CONFIG; + return false; + } + + /* Check close brace */ + if (!config->Syntax.Braces.close || strlen(config->Syntax.Braces.close) > TEMPLATE_MAX_PREFIX_LEN) + { + if (error_code) + *error_code = TEMPLATE_ERROR_INVALID_CONFIG; + return false; + } + + /* Check section control */ + if (!config->Syntax.Section.control || strlen(config->Syntax.Section.control) > TEMPLATE_MAX_PREFIX_LEN) + { + if (error_code) + *error_code = TEMPLATE_ERROR_INVALID_CONFIG; + return false; + } + + /* Check section source */ + if (!config->Syntax.Section.source || strlen(config->Syntax.Section.source) > TEMPLATE_MAX_PREFIX_LEN) + { + if (error_code) + *error_code = TEMPLATE_ERROR_INVALID_CONFIG; + return false; + } + + /* Check section begin */ + if (!config->Syntax.Section.begin || strlen(config->Syntax.Section.begin) > TEMPLATE_MAX_PREFIX_LEN) + { + if (error_code) + *error_code = TEMPLATE_ERROR_INVALID_CONFIG; + return false; + } + + /* Check interpolate invoke */ + if (!config->Syntax.Interpolate.invoke || strlen(config->Syntax.Interpolate.invoke) > TEMPLATE_MAX_PREFIX_LEN) + { + if (error_code) + *error_code = TEMPLATE_ERROR_INVALID_CONFIG; + return false; + } + + /* Check include invoke */ + if (!config->Syntax.Include.invoke || strlen(config->Syntax.Include.invoke) > TEMPLATE_MAX_PREFIX_LEN) + { + if (error_code) + *error_code = TEMPLATE_ERROR_INVALID_CONFIG; + return false; + } + + /* Check execute invoke */ + if (!config->Syntax.Execute.invoke || strlen(config->Syntax.Execute.invoke) > TEMPLATE_MAX_PREFIX_LEN) + { + if (error_code) + *error_code = TEMPLATE_ERROR_INVALID_CONFIG; + return false; + } + + /* Check nesting */ + if (!config->Syntax.nesting || strlen(config->Syntax.nesting) > TEMPLATE_MAX_PREFIX_LEN) + { + if (error_code) + *error_code = TEMPLATE_ERROR_INVALID_CONFIG; + return false; + } + + if (error_code) + *error_code = TEMPLATE_ERROR_NONE; + return true; +} + +/* Parse interpolation tag */ +static TemplateNode * +template_parse_interpolation(MemoryContext context, const char **s_ptr, + const TemplateConfig *config, TemplateErrorCode *error_code) +{ + const char **s = s_ptr; + const char *key_start; + size_t key_len; + TemplateNode *node; + + node = init_template_node(context, TEMPLATE_NODE_INTERPOLATE); + + /* Skip to the content */ + *s += strlen(config->Syntax.Braces.open); + *s = skip_whitespace(*s); + *s += strlen(config->Syntax.Interpolate.invoke); + + *s = skip_whitespace(*s); + key_start = *s; + + while (**s != '\0') + { + if (isspace((unsigned char)**s) || + strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) + break; + + if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_NESTED_INTERPOLATION; + template_free_node(node); + return NULL; + } + + (*s)++; + } + + key_len = *s - key_start; + node->value->interpolate.key = MemoryContextStrdup(context, pnstrdup(key_start, key_len)); + + *s = skip_whitespace(*s); + + /* Check for closing brace */ + if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; + template_free_node(node); + return NULL; + } + + *s_ptr = *s + strlen(config->Syntax.Braces.close); + + return node; +} + +/* Parse section tag */ +static TemplateNode * +template_parse_section(MemoryContext context, const char **s_ptr, + const TemplateConfig *config, TemplateErrorCode *error_code) +{ + const char **s = s_ptr; + const char *iterator_start, *collection_start; + size_t iterator_len, collection_len; + TemplateNode *node, *body_node; + + node = init_template_node(context, TEMPLATE_NODE_SECTION); + + /* Skip to the content */ + *s += strlen(config->Syntax.Braces.open); + *s = skip_whitespace(*s); + *s += strlen(config->Syntax.Section.control); + + /* Find the iterator name */ + *s = skip_whitespace(*s); + iterator_start = *s; + + while (**s != '\0') + { + if (isspace((unsigned char)**s) || + strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source)) == 0) + break; + + if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_NESTED_SECTION_ITERATOR; + template_free_node(node); + return NULL; + } + + if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; + template_free_node(node); + return NULL; + } + + (*s)++; + } + + iterator_len = *s - iterator_start; + node->value->section.iterator = MemoryContextStrdup(context, pnstrdup(iterator_start, iterator_len)); + + /* Find the collection name */ + *s = skip_whitespace(*s); + + if (strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source)) != 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; + template_free_node(node); + return NULL; + } + + *s += strlen(config->Syntax.Section.source); + *s = skip_whitespace(*s); + collection_start = *s; + + while (**s != '\0') + { + if (isspace((unsigned char)**s) || + strncmp(*s, config->Syntax.Section.begin, strlen(config->Syntax.Section.begin)) == 0) + break; + + if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_NESTED_SECTION_ITERATOR; + template_free_node(node); + return NULL; + } + + if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; + template_free_node(node); + return NULL; + } + + (*s)++; + } + + collection_len = *s - collection_start; + node->value->section.collection = MemoryContextStrdup(context, pnstrdup(collection_start, collection_len)); + + /* Check for 'do' keyword */ + *s = skip_whitespace(*s); + if (strncmp(*s, config->Syntax.Section.begin, strlen(config->Syntax.Section.begin)) != 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; + template_free_node(node); + return NULL; + } + + *s += strlen(config->Syntax.Section.begin); + + /* Parse the body */ + body_node = template_parse(context, s, config, true, error_code); + if (!body_node) + { + template_free_node(node); + return NULL; + } + + node->value->section.body = body_node; + + /* Skip to the end of the section */ + *s = skip_whitespace(*s); + + /* Check for closing brace */ + if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END; + template_free_node(node); + return NULL; + } + + *s_ptr = *s + strlen(config->Syntax.Braces.close); + + return node; +} + +/* Parse include tag */ +static TemplateNode * +template_parse_include(MemoryContext context, const char **s_ptr, + const TemplateConfig *config, TemplateErrorCode *error_code) +{ + const char **s = s_ptr; + const char *include_start; + size_t include_len; + TemplateNode *node; + + node = init_template_node(context, TEMPLATE_NODE_INCLUDE); + + /* Skip to the content */ + *s += strlen(config->Syntax.Braces.open); + *s = skip_whitespace(*s); + *s += strlen(config->Syntax.Include.invoke); + + *s = skip_whitespace(*s); + include_start = *s; + + while (**s != '\0') + { + if (isspace((unsigned char)**s) || + strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) + break; + + if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_NESTED_INCLUDE; + template_free_node(node); + return NULL; + } + + (*s)++; + } + + include_len = *s - include_start; + node->value->include.key = MemoryContextStrdup(context, pnstrdup(include_start, include_len)); + + *s = skip_whitespace(*s); + + /* Check for closing brace */ + if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_UNEXPECTED_INCLUDE_END; + template_free_node(node); + return NULL; + } + + *s_ptr = *s + strlen(config->Syntax.Braces.close); + + return node; +} + +/* Parse execute tag */ +static TemplateNode * +template_parse_execute(MemoryContext context, const char **s_ptr, + const TemplateConfig *config, TemplateErrorCode *error_code) +{ + const char **s = s_ptr; + const char *code_start; + size_t code_len; + TemplateNode *node; + + node = init_template_node(context, TEMPLATE_NODE_EXECUTE); + + /* Skip to the content */ + *s += strlen(config->Syntax.Braces.open); + *s = skip_whitespace(*s); + *s += strlen(config->Syntax.Execute.invoke); + + *s = skip_whitespace(*s); + code_start = *s; + + while (**s != '\0') + { + if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) + break; + + if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_NESTED_EXECUTE; + template_free_node(node); + return NULL; + } + + (*s)++; + } + + code_len = *s - code_start; + node->value->execute.code = MemoryContextStrdup(context, pnstrdup(code_start, code_len)); + + /* Check for closing brace */ + if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0) + { + if (error_code) + *error_code = TEMPLATE_ERROR_UNEXPECTED_EXECUTE_END; + template_free_node(node); + return NULL; + } + + *s_ptr = *s + strlen(config->Syntax.Braces.close); + + return node; +} + +/* Main template parser function */ +TemplateNode * +template_parse(MemoryContext context, const char **s, const TemplateConfig *config, + bool inner_parse, TemplateErrorCode *error_code) +{ + const char *start; + TemplateNode *root, *current, *tag_node; + bool current_node_filled = false; + + if (!template_validate_config(config, error_code)) + { + return NULL; + } + + start = *s; + root = init_template_node(context, TEMPLATE_NODE_TEXT); + current = root; + + while (*s && **s != '\0') + { + /* Check for closing brace in inner parse */ + if (inner_parse && strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0) + { + break; + } + + if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) + { + /* Handle text before tag */ + if (start != *s) + { + if (current_node_filled) + { + TemplateNode *new_node = init_template_node(context, TEMPLATE_NODE_TEXT); + current->next = new_node; + current = new_node; + } + else + { + current->type = TEMPLATE_NODE_TEXT; + *current->value = init_template_value(TEMPLATE_NODE_TEXT); + } + + size_t text_len = *s - start; + current->value->text.content = MemoryContextStrdup(context, pnstrdup(start, text_len)); + current_node_filled = true; + } + + /* Parse the tag */ + tag_node = NULL; + const char *tag_prefix = *s + strlen(config->Syntax.Braces.open); + tag_prefix = skip_whitespace(tag_prefix); + + /* Determine tag type by prefix */ + if (strncmp(tag_prefix, config->Syntax.Section.control, strlen(config->Syntax.Section.control)) == 0) + { + tag_node = template_parse_section(context, s, config, error_code); + } + else if (strncmp(tag_prefix, config->Syntax.Include.invoke, strlen(config->Syntax.Include.invoke)) == 0) + { + tag_node = template_parse_include(context, s, config, error_code); + } + else if (strncmp(tag_prefix, config->Syntax.Execute.invoke, strlen(config->Syntax.Execute.invoke)) == 0) + { + tag_node = template_parse_execute(context, s, config, error_code); + } + else if (strncmp(tag_prefix, config->Syntax.Interpolate.invoke, strlen(config->Syntax.Interpolate.invoke)) == 0) + { + tag_node = template_parse_interpolation(context, s, config, error_code); + } + else + { + if (error_code) + *error_code = TEMPLATE_ERROR_UNKNOWN_TAG; + template_free_node(root); + return NULL; + } + + if (!tag_node) + { + template_free_node(root); + return NULL; + } + + if (current_node_filled) + { + current->next = tag_node; + current = tag_node; + } + else + { + *current = *tag_node; + pfree(tag_node); + } + + current_node_filled = true; + start = *s; + } + else + { + (*s)++; + } + } + + /* Handle remaining text */ + if (start != *s) + { + if (current_node_filled) + { + TemplateNode *new_node = init_template_node(context, TEMPLATE_NODE_TEXT); + current->next = new_node; + current = new_node; + } + else + { + current->type = TEMPLATE_NODE_TEXT; + *current->value = init_template_value(TEMPLATE_NODE_TEXT); + } + + size_t text_len = *s - start; + current->value->text.content = MemoryContextStrdup(context, pnstrdup(start, text_len)); + current_node_filled = true; + } + + /* If no nodes were created, ensure we have at least an empty text node */ + if (!current_node_filled) + { + current->type = TEMPLATE_NODE_TEXT; + *current->value = init_template_value(TEMPLATE_NODE_TEXT); + current->value->text.content = MemoryContextStrdup(context, ""); + } + + if (error_code) + *error_code = TEMPLATE_ERROR_NONE; + + return root; +} + +/* Free a template node and all its children */ +void +template_free_node(TemplateNode *node) +{ + TemplateNode *current, *next; + + if (!node) + return; + + current = node; + + while (current) + { + next = current->next; + + if (current->value) + { + switch (current->type) + { + case TEMPLATE_NODE_TEXT: + if (current->value->text.content) + pfree(current->value->text.content); + break; + + case TEMPLATE_NODE_INTERPOLATE: + if (current->value->interpolate.key) + pfree(current->value->interpolate.key); + break; + + case TEMPLATE_NODE_SECTION: + if (current->value->section.iterator) + pfree(current->value->section.iterator); + if (current->value->section.collection) + pfree(current->value->section.collection); + if (current->value->section.body) + template_free_node(current->value->section.body); + break; + + case TEMPLATE_NODE_EXECUTE: + if (current->value->execute.code) + pfree(current->value->execute.code); + break; + + case TEMPLATE_NODE_INCLUDE: + if (current->value->include.key) + pfree(current->value->include.key); + break; + + default: + /* Should not happen */ + break; + } + + pfree(current->value); + } + + pfree(current); + current = next; + } +} \ No newline at end of file diff --git a/package/c/hemar/hemar.h b/package/c/hemar/hemar.h new file mode 100755 index 0000000..61337e2 --- /dev/null +++ b/package/c/hemar/hemar.h @@ -0,0 +1,111 @@ +/* + * hemar.h + * Template parser for Hemar + */ +#ifndef HEMAR_TEMPLATE_H +#define HEMAR_TEMPLATE_H + +#include "postgres.h" +#include "utils/memutils.h" + +/* Maximum length for template syntax elements */ +#define TEMPLATE_MAX_PREFIX_LEN 32 + +/* Template error codes */ +typedef enum { + TEMPLATE_ERROR_NONE = 0, + TEMPLATE_ERROR_UNKNOWN_TAG, + TEMPLATE_ERROR_NESTED_INTERPOLATION, + TEMPLATE_ERROR_NESTED_SECTION_ITERATOR, + TEMPLATE_ERROR_UNEXPECTED_SECTION_END, + TEMPLATE_ERROR_NESTED_INCLUDE, + TEMPLATE_ERROR_NESTED_EXECUTE, + TEMPLATE_ERROR_INVALID_CONFIG, + TEMPLATE_ERROR_OUT_OF_MEMORY, + TEMPLATE_ERROR_UNEXPECTED_INCLUDE_END, + TEMPLATE_ERROR_UNEXPECTED_EXECUTE_END +} TemplateErrorCode; + +/* Template node types */ +typedef enum { + TEMPLATE_NODE_TEXT, + TEMPLATE_NODE_INTERPOLATE, + TEMPLATE_NODE_SECTION, + TEMPLATE_NODE_EXECUTE, + TEMPLATE_NODE_INCLUDE +} TemplateNodeType; + +/* Template configuration structure */ +typedef struct { + struct { + struct { + const char *open; /* Default: "{%" */ + const char *close; /* Default: "%}" */ + } Braces; + struct { + const char *control; /* default: "for " */ + const char *source; /* default: "in " */ + const char *begin; /* default: "do " */ + } Section; + struct { + const char *invoke; /* default: "" */ + } Interpolate; + struct { + const char *invoke; /* default: "include " */ + } Include; + struct { + const char *invoke; /* default: "exec " */ + } Execute; + const char *nesting; /* default: "->" */ + } Syntax; +} TemplateConfig; + +/* Forward declaration */ +typedef struct TemplateNode TemplateNode; + +/* Template value structures */ +typedef struct { + char *iterator; + char *collection; + TemplateNode *body; +} TemplateSectionValue; + +typedef struct { + char *key; +} TemplateInterpolateValue; + +typedef struct { + char *code; +} TemplateExecuteValue; + +typedef struct { + char *key; +} TemplateIncludeValue; + +typedef struct { + char *content; +} TemplateTextValue; + +typedef union { + TemplateSectionValue section; + TemplateInterpolateValue interpolate; + TemplateExecuteValue execute; + TemplateIncludeValue include; + TemplateTextValue text; +} TemplateValue; + +/* Template node structure */ +struct TemplateNode { + TemplateNodeType type; + TemplateValue *value; + TemplateNode *next; +}; + +/* Function declarations */ +TemplateConfig template_default_config(MemoryContext context); +bool template_validate_config(const TemplateConfig *config, TemplateErrorCode *error_code); +TemplateNode *template_parse(MemoryContext context, const char **s, const TemplateConfig *config, bool inner_parse, TemplateErrorCode *error_code); +void template_free_node(TemplateNode *node); +const char *template_error_to_string(TemplateErrorCode code); + +#endif /* HEMAR_TEMPLATE_H */ \ No newline at end of file