diff --git a/package/c/hectic/docs/templater.md b/package/c/hectic/docs/templater.md new file mode 100755 index 0000000..ab542eb --- /dev/null +++ b/package/c/hectic/docs/templater.md @@ -0,0 +1,155 @@ +# Templater Configuration Documentation + +The templating engine supports flexible customization of tag syntax parameters. Each parameter can be overridden in the configuration file. Below are the main groups of parameters with usage examples and configuration notes. + +## Legend + +--- + +## General Parameters + +- **Open Brace** + A non-empty string marking the beginning of a tag. + *Example:* `{%` + +- **Close Brace** + A non-empty string marking the end of a tag. + *Example:* `%}` + +- **Null Handler** + A non-empty string used to specify alternative content when a value is null. + *Example:* `%%` + +--- + +## Section Tags + +Parameters defining syntax for blocks controlling loops or nested structures. + +- **Prefix** + Marks the start of a section (e.g., loops). + *Example:* `for ` | *(Empty)* + +- **Suffix** + Delimiter between variables and collections. + *Example:* ` in ` | `#` + +- **Optional Suffix** + Additional modifier, e.g., for joining collections. + *Example:* ` join ` | `#` + +- **Post-Suffix** + Finalizes the section declaration block. + *Example:* `do ` | `:` + +*Section Example:* +```tpl +{% for item in items do + {% item.name %} + some text + {% for inner_item in item.inner_items join '\n' do +
some other text
+ {% inner_item %} + %% +value
" + } + ] +} +``` + +## Function Tags +**Note:** Currently not included in C library; implemented as a wrapper on applicable platforms. +Enables calling functions with arguments. +- **Prefix** + Denotes a function call. + *Example:* `call` | *(Empty)* + +*Function Example:* +```tpl + {% call my_function(arg1, arg2, 'literal') %} +``` + +## Notes +- **Unique Tags:** `Open Brace`, `Close Brace`, and `Null Handler` must be distinct. +- **Nested Constructs:** Supported. +- **Unclosed Tags:** Must return an error. +- **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. + diff --git a/package/c/hectic/hectic.c b/package/c/hectic/hectic.c index ee7a955..a32267c 100644 --- a/package/c/hectic/hectic.c +++ b/package/c/hectic/hectic.c @@ -61,6 +61,8 @@ void set_output_color_mode(ColorMode mode) { color_mode = mode; } +#define POSITION_INFO file, func, line + // ------------ // -- Logger -- // ------------ @@ -497,6 +499,8 @@ static const char *skip_whitespace(const char *s) { return s; } +static Json *json_parse_value__(const char *file, const char *func, int line, const char **s, Arena *arena); + /* Parse a JSON string (does not handle full escaping) */ static char *json_parse_string__(const char *file, const char *func, int line, const char **s_ptr, Arena *arena) { const char *s = *s_ptr; @@ -542,9 +546,6 @@ static double json_parse_number__(const char *file, const char *func, int line, return num; } -/* Forward declaration */ -static Json *json_parse_value__(const char *file, const char *func, int line, const char **s, Arena *arena); - /* Parse a JSON array: [ value, value, ... ] */ static Json *json_parse_array__(const char *file, const char *func, int line, const char **s, Arena *arena) { raise_message(LOG_LEVEL_DEBUG, file, func, line, "Entering json_parse_array__ at position: %p", *s); @@ -1419,4 +1420,114 @@ char* logger_rules_to_string(Arena *arena) { } return buffer; -} \ No newline at end of file +} + +// --------------- +// -- Templater -- +// --------------- + +// Look at package\c\hectic\docs\templater.md + +TemplateConfig *template_default_config__(const char *file, const char *func, int line, Arena *arena) { + TemplateConfig *config = arena_alloc__(file, func, line, arena, sizeof(TemplateConfig)); + if (!config) return NULL; + + config->open_brace = "{%"; + config->close_brace = "%}"; + config->null_handler = "%%"; + config->section_prefix = "for "; + config->section_suffix = " in "; + config->section_optional_suffix = " join "; + config->section_post_suffix = " do "; + config->interpolation_prefix = ""; + config->include_prefix = "include "; + config->function_prefix = "call "; + + return config; +} + +static TemplateNode *template_node_create__(const char *file, const char *func, int line, Arena *arena, TemplateNodeType type, TemplateValue *value) { + TemplateNode *node = arena_alloc__(file, func, line, arena, sizeof(TemplateNode)); + if (!node) { + raise_message(LOG_LEVEL_EXCEPTION, file, func, line, "Failed to allocate node"); + } + + node->type = type; + node->value = *value; + node->children = NULL; + node->next = NULL; + + return node; +} + +#define CHECK_CONFIG_STR(field, name) \ +do { \ + if (!(config->field)) { \ + raise_message(LOG_LEVEL_EXCEPTION, file, func, line, "CONFIG: " name " is NULL"); \ + return false; \ + } \ + if (strlen(config->field) > TEMPLATE_MAX_PREFIX_LEN) { \ + raise_message(LOG_LEVEL_EXCEPTION, file, func, line, "CONFIG: " name " is too long"); \ + return false; \ + } \ +} while (0) + +bool template_validate_config__(const char *file, const char *func, int line, TemplateConfig *config) { + if (!config) { + raise_message(LOG_LEVEL_EXCEPTION, file, func, line, "Config is NULL"); + return false; + } + + CHECK_CONFIG_STR(open_brace, "Open brace"); + CHECK_CONFIG_STR(close_brace, "Close brace"); + CHECK_CONFIG_STR(null_handler, "Null handler"); + CHECK_CONFIG_STR(section_prefix, "Section prefix"); + CHECK_CONFIG_STR(section_suffix, "Section suffix"); + CHECK_CONFIG_STR(section_optional_suffix, "Section optional suffix"); + CHECK_CONFIG_STR(section_post_suffix, "Section post suffix"); + CHECK_CONFIG_STR(interpolation_prefix, "Interpolation prefix"); + CHECK_CONFIG_STR(include_prefix, "Include prefix"); + CHECK_CONFIG_STR(function_prefix, "Function prefix"); + + return true; +} + +TemplateNode *template_parse__(const char *file, const char *func, int line, Arena *arena, const char *template, TemplateConfig *config) { + if (!arena) { + raise_message(LOG_LEVEL_EXCEPTION, file, func, line, "Arena is NULL"); + } + + if (!config) { + raise_message(LOG_LEVEL_EXCEPTION, file, func, line, "Config is NULL"); + } + + if (!template) { + raise_message(LOG_LEVEL_EXCEPTION, file, func, line, "Template is NULL"); + } + + // Find the first open brace + const char *open_brace = strstr(template, config->open_brace); + if (!open_brace) { + raise_message(LOG_LEVEL_LOG, file, func, line, "No open brace found"); + TemplateValue val = {.text = {.content = (char *)template}}; + return template_node_create__(file, func, line, arena, + TEMPLATE_NODE_TEXT, &val); + } + + // Deside tag type by prefix + const char *tag_prefix = open_brace + strlen(config->open_brace); + if (strncmp(tag_prefix, config->section_prefix, strlen(config->section_prefix)) == 0) { + // Section tag + } else if (strncmp(tag_prefix, config->interpolation_prefix, strlen(config->interpolation_prefix)) == 0) { + // Interpolation tag + } else if (strncmp(tag_prefix, config->include_prefix, strlen(config->include_prefix)) == 0) { + // Include tag + } else if (strncmp(tag_prefix, config->function_prefix, strlen(config->function_prefix)) == 0) { + // Function tag + } else { + raise_message(LOG_LEVEL_EXCEPTION, file, func, line, "Unknown tag prefix: %s", slice_create__(file, func, line, 1, (char *)tag_prefix, strlen(tag_prefix), 0, TEMPLATE_MAX_PREFIX_LEN)); + return NULL; + } + + return NULL; +} diff --git a/package/c/hectic/hectic.h b/package/c/hectic/hectic.h index 266ea16..124dd7a 100644 --- a/package/c/hectic/hectic.h +++ b/package/c/hectic/hectic.h @@ -445,4 +445,72 @@ void logger_print_rules(); */ char* logger_rules_to_string(Arena *arena); +// --------------- +// -- Templater -- +// --------------- + +typedef enum { + TEMPLATE_NODE_TEXT, // Plain text content + TEMPLATE_NODE_INTERPOLATE, // Variable interpolation + TEMPLATE_NODE_SECTION, // Section (for loops) + TEMPLATE_NODE_INCLUDE, // Include other templates + TEMPLATE_NODE_FUNCTION // Function call (for future use) +} TemplateNodeType; + +#define TEMPLATE_MAX_PREFIX_LEN 16 + +typedef struct { + const char *open_brace; // Default: "{%" + const char *close_brace; // Default: "%}" + const char *null_handler; // Default: "%%" + const char *section_prefix; // default: "for " + const char *section_suffix; // default: " in " + const char *section_optional_suffix; // default: " join " + const char *section_post_suffix; // default: " do " + const char *interpolation_prefix; // default: "" + const char *include_prefix; // default: "include " + const char *function_prefix; // default: "call " +} TemplateConfig; + +typedef struct { + char *variable; + char *collection; + char *join; + struct TemplateNode *null_block; +} TemplateSectionValue; + +typedef struct { + char *variable; + struct TemplateNode *null_block; +} TemplateInterpolateValue; + +typedef struct { + char *name; + char *args; +} TemplateFunctionValue; + +typedef struct { + char *name; +} TemplateIncludeValue; + +typedef struct { + char *content; +} TemplateTextValue; + +typedef union { + TemplateSectionValue section; + TemplateInterpolateValue interpolate; + TemplateFunctionValue function; + TemplateIncludeValue include; + TemplateTextValue text; +} TemplateValue; + +// template node structure +typedef struct TemplateNode { + TemplateNodeType type; + TemplateValue value; + struct TemplateNode *children; // child nodes + struct TemplateNode *next; // sibling nodes +} TemplateNode; + #endif // EPRINTF_H \ No newline at end of file diff --git a/package/c/hectic/test/03-slice.c b/package/c/hectic/test/03-slice.c index 7d34c22..27c031e 100644 --- a/package/c/hectic/test/03-slice.c +++ b/package/c/hectic/test/03-slice.c @@ -93,4 +93,4 @@ int main() { printf("%s%s all tests passed.%s\n", OPTIONAL_COLOR(COLOR_GREEN), __FILE__, OPTIONAL_COLOR(COLOR_RESET)); return 0; -} +} \ No newline at end of file diff --git a/package/c/hectic/test/04-templater.c b/package/c/hectic/test/04-templater.c new file mode 100755 index 0000000..9a79564 --- /dev/null +++ b/package/c/hectic/test/04-templater.c @@ -0,0 +1,180 @@ +#include