From 972e2d296809a3f74f2cf69220848b57d22c3ab1 Mon Sep 17 00:00:00 2001 From: yukkop Date: Sat, 22 Mar 2025 19:45:17 +0000 Subject: [PATCH] refactor(hmpl): eval --- package/c/hectic/hectic.c | 6 +- package/c/hectic/hectic.h | 9 ++- package/c/hmpl/default.nix | 1 + package/c/hmpl/hmpl.c | 150 ++++++++++++++++++++++++++++++------- package/c/hmpl/test/test.c | 1 + 5 files changed, 133 insertions(+), 34 deletions(-) diff --git a/package/c/hectic/hectic.c b/package/c/hectic/hectic.c index cea7821..808d17d 100644 --- a/package/c/hectic/hectic.c +++ b/package/c/hectic/hectic.c @@ -268,13 +268,13 @@ Json *json_parse(Arena *arena, const char **s) { } char *json_to_string(Arena *arena, Json *item) { - return json_to_string_with_opts(arena, item, 0); + return json_to_string_with_opts(arena, item, JSON_NORAW); } /* Minimal JSON printer with raw output option. When raw is non-zero and the item is a JSON_STRING, it is printed without quotes. */ -char *json_to_string_with_opts(Arena *arena, Json *item, int raw) { +char *json_to_string_with_opts(Arena *arena, Json *item, JsonRawOpt raw) { char *out = arena_alloc(arena, 1024); if (!out) return NULL; @@ -303,7 +303,7 @@ char *json_to_string_with_opts(Arena *arena, Json *item, int raw) { } sprintf(ptr, "]"); } else if (item->type == JSON_STRING) { - if (raw) + if ((int)raw) sprintf(ptr, "%s", item->string); else sprintf(ptr, "\"%s\"", item->string); diff --git a/package/c/hectic/hectic.h b/package/c/hectic/hectic.h index d8eca97..11db898 100644 --- a/package/c/hectic/hectic.h +++ b/package/c/hectic/hectic.h @@ -202,13 +202,18 @@ void substr(const char *src, char *dest, size_t start, size_t len); // -- Json -- // ---------- +typedef enum { + JSON_NORAW = 0, + JSON_RAW = 1, +} JsonRawOpt; + typedef enum { JSON_NULL, JSON_BOOL, JSON_NUMBER, JSON_STRING, JSON_ARRAY, - JSON_OBJECT + JSON_OBJECT, } JsonType; /* Full JSON structure */ @@ -228,7 +233,7 @@ Json *json_parse(Arena *arena, const char **s); char *json_to_string(Arena *arena, Json *item); -char *json_to_string_with_opts(Arena *arena, Json *item, int raw); +char *json_to_string_with_opts(Arena *arena, Json *item, JsonRawOpt raw); /* Retrieve an object item by key (case-sensitive) */ Json *json_get_object_item(Json *object, const char *key); diff --git a/package/c/hmpl/default.nix b/package/c/hmpl/default.nix index 7ae1dce..4ed9ee9 100644 --- a/package/c/hmpl/default.nix +++ b/package/c/hmpl/default.nix @@ -28,6 +28,7 @@ stdenv.mkDerivation { checkPhase = '' mkdir -p target/test + export LOG_LEVEL=DEBUG for test_file in test/*.c; do exe="target/test/$(basename ''${test_file%.c})" ${gcc}/bin/cc -Wall -Wextra -g -pedantic \ diff --git a/package/c/hmpl/hmpl.c b/package/c/hmpl/hmpl.c index 6f2ff9c..f8d70e8 100644 --- a/package/c/hmpl/hmpl.c +++ b/package/c/hmpl/hmpl.c @@ -1,65 +1,61 @@ #include "hmpl.h" -char *eval(Arena *arena, const Json * const context, const char * const key) { - if (!context || !key) return NULL; +char *eval(Arena *arena, const Json * const context, const char * const query) { + if (!context || !query) return NULL; - char *key_copy = arena_strdup(arena, key); Json *res = context; - char *start = key_copy; - char *dot; + char *dot, *key = arena_strdup(arena, query); - // Instead of using strtok_r, manually split the string using strchr. - while ((dot = strchr(start, '.')) != NULL) { + while ((dot = strchr(key, '.')) != NULL) { *dot = '\0'; - raise_debug("res: %s, token: %s, key: %s", json_to_string(arena, res), start, key); - res = json_get_object_item(res, start); + raise_debug("res: %s, key: %s, query: %s", json_to_string(arena, res), key, query); + res = json_get_object_item(res, key); if (!res) return NULL; - start = dot + 1; + key = dot + 1; } - raise_debug("res: %s, token: %s, key: %s", json_to_string(arena, res), start, key); - res = json_get_object_item(res, start); + raise_debug("res: %s, key: %s, query: %s", json_to_string(arena, res), key, query); + res = json_get_object_item(res, key); if (!res) return NULL; - return json_to_string_with_opts(arena, res, 1); + return json_to_string_with_opts(arena, res, JSON_RAW); } /* Modified: text is passed by reference so we can update it and free old allocations */ +// {{[prefix]key}} void hmpl_render_interpolation_tags(Arena *arena, char **text_ptr, Json *context, const char * const prefix) { raise_debug("hmpl_render_interpolation_tags"); - char start_pattern[256]; + char start_pattern[8]; snprintf(start_pattern, sizeof(start_pattern), "{{%s", prefix); int start_pattern_length = strlen(start_pattern); int offset = 0; while (1) { char *current_text = *text_ptr; - char *placeholder_start = strstr(current_text + offset, start_pattern); - if (!placeholder_start) + char *start = strstr(current_text + offset, start_pattern); + if (!start) break; - int start_index = placeholder_start - current_text; + + int start_index = start - current_text; int key_start = start_index + start_pattern_length; - raise_debug("start: %d", key_start); - char *placeholder_end = strstr(placeholder_start, "}}"); - if (!placeholder_end) - raise_exception("Malformed template: missing closing braces for placeholder start"); - int key_length = (placeholder_end - current_text) - key_start; - char *placeholder_key = arena_alloc(arena, key_length + 1); - substr(current_text, placeholder_key, key_start, key_length); - raise_debug("key: %s", placeholder_key); + char *end = strstr(start, "}}"); + if (!end) + raise_exception("Malformed template: missing closing braces for interpolation tag"); + int key_length = (end - current_text) - key_start; + char *key = arena_alloc(arena, key_length + 1); + substr(current_text, key, key_start, key_length); - char *replacement = eval(arena, context, placeholder_key); - raise_debug("%s = eval(context, %s)", replacement ? replacement : "NULL", placeholder_key); + char *replacement = eval(arena, context, key); if (!replacement) { - offset = (placeholder_end - current_text) + 2; + offset = (end - current_text) + 2; continue; } char *new_text = arena_repstr(arena, current_text, start_index, - placeholder_end - placeholder_start + 1, + end - start + 1, replacement); *text_ptr = new_text; @@ -67,6 +63,102 @@ void hmpl_render_interpolation_tags(Arena *arena, char **text_ptr, Json *context } } +// CREATE OR REPLACE FUNCTION common.render_template_loop_blocks(result TEXT, context JSONB) +// RETURNS TEXT LANGUAGE plpgsql AS $$ +// DECLARE +// loop_start INT; +// key_end INT; +// loop_end INT; +// loop_key TEXT; +// block TEXT; +// rendered_block TEXT; +// arr JSONB; +// item JSONB; +// item_text TEXT; +// BEGIN +// LOOP +// loop_start := strpos(result, '{{#'); +// EXIT WHEN loop_start = 0; -- Exit if no loop start found. +// +// -- Locate the end of the loop key marker. +// key_end := strpos(result, '}}', loop_start); +// IF key_end = 0 THEN +// RAISE EXCEPTION 'Malformed template: missing closing braces for loop start'; +// END IF; +// +// -- Extract the key used for the loop. +// loop_key := substring(result from loop_start + 3 for key_end - loop_start - 3); +// +// RAISE DEBUG 'loop key %', loop_key; +// +// -- Find the matching loop end marker for this key. +// loop_end := strpos(result, '{{/#' || loop_key || '}}', key_end); +// IF loop_end = 0 THEN +// RAISE EXCEPTION 'Malformed template: missing loop end for key %', loop_key; +// END IF; +// +// -- Extract the inner block of the loop. +// block := substring(result from key_end + 2 for loop_end - key_end - 2); +// +// -- Retrieve the JSON array from the context for the loop key. +// arr := eval_value(context, loop_key); +// rendered_block := ''; +// +// -- If an array is found, iterate over each element. +// IF arr IS NOT NULL AND jsonb_typeof(arr) = 'array' THEN +// FOR item IN SELECT * FROM jsonb_array_elements(arr) LOOP +// item_text := block; -- Begin with the raw block. +// IF jsonb_typeof(item) != 'object' THEN +// -- Replace interpolation for primitive values. +// item_text := replace(item_text, '{{.}}', item::text); +// ELSE +// -- For object values, iterate over each key/value. +// item_text := render_template_interpolations(item_text, item, '.'::CHAR(1)); +// item_text := render_template_conditions(item_text, item, '.'); +// END IF; +// rendered_block := rendered_block || item_text; +// END LOOP; +// END IF; +// +// -- Replace the entire loop block in the result with the rendered content. +// result := substring(result from 1 for loop_start - 1) +// || rendered_block +// || substring(result from loop_end + char_length('{{/#' || loop_key || '}}')); +// END LOOP; +// +// RETURN result; +// END $$; + +// {{#array_key}} +// void hmpl_render_section_tags(Arena *arena, char **text_ptr, Json *context, const char * const prefix){ +// raise_debug("hmpl_render_section_tags"); +// char start_pattern[8]; +// snprintf(start_pattern, sizeof(start_pattern), "{{%s", prefix); +// int start_pattern_length = strlen(start_pattern); +// int offset = 0; +// +// while (1) { +// char *current_text = *text_ptr; +// char *opening_tag_start = strstr(current_text + offset, start_pattern); +// if (!opening_tag_start) +// break; +// +// int start_index = start - current_text; +// int key_start = start_index + start_pattern_length; +// +// char *end = strstr(start, "}}"); +// if (!end) +// raise_exception("Malformed template: missing closing braces for section tag"); +// int key_length = (end - current_text) - key_start; +// +// +// char *key = arena_alloc(arena, key_length + 1); +// substr(current_text, key, key_start, key_length); +// +// char *arr = eval(arena, context, key); +// } +// } + void hmpl_render_with_arena(Arena *arena, char **text, const Json * const context) { if (context->type != JSON_OBJECT) { raise_exception("Malformed context: context is not json"); diff --git a/package/c/hmpl/test/test.c b/package/c/hmpl/test/test.c index cbcedc1..8a5b31d 100644 --- a/package/c/hmpl/test/test.c +++ b/package/c/hmpl/test/test.c @@ -134,6 +134,7 @@ void test_render_interpolation_tags_with_prefix(Arena *arena) { } int main(void) { + init_logger(); Arena arena = arena_init(1024 * 1024); test_eval_single_level_key(&arena);