refactor(hmpl): eval
This commit is contained in:
@@ -268,13 +268,13 @@ Json *json_parse(Arena *arena, const char **s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char *json_to_string(Arena *arena, Json *item) {
|
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.
|
/* Minimal JSON printer with raw output option.
|
||||||
When raw is non-zero and the item is a JSON_STRING, it is printed without quotes.
|
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);
|
char *out = arena_alloc(arena, 1024);
|
||||||
if (!out)
|
if (!out)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -303,7 +303,7 @@ char *json_to_string_with_opts(Arena *arena, Json *item, int raw) {
|
|||||||
}
|
}
|
||||||
sprintf(ptr, "]");
|
sprintf(ptr, "]");
|
||||||
} else if (item->type == JSON_STRING) {
|
} else if (item->type == JSON_STRING) {
|
||||||
if (raw)
|
if ((int)raw)
|
||||||
sprintf(ptr, "%s", item->string);
|
sprintf(ptr, "%s", item->string);
|
||||||
else
|
else
|
||||||
sprintf(ptr, "\"%s\"", item->string);
|
sprintf(ptr, "\"%s\"", item->string);
|
||||||
|
|||||||
@@ -202,13 +202,18 @@ void substr(const char *src, char *dest, size_t start, size_t len);
|
|||||||
// -- Json --
|
// -- Json --
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
JSON_NORAW = 0,
|
||||||
|
JSON_RAW = 1,
|
||||||
|
} JsonRawOpt;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
JSON_NULL,
|
JSON_NULL,
|
||||||
JSON_BOOL,
|
JSON_BOOL,
|
||||||
JSON_NUMBER,
|
JSON_NUMBER,
|
||||||
JSON_STRING,
|
JSON_STRING,
|
||||||
JSON_ARRAY,
|
JSON_ARRAY,
|
||||||
JSON_OBJECT
|
JSON_OBJECT,
|
||||||
} JsonType;
|
} JsonType;
|
||||||
|
|
||||||
/* Full JSON structure */
|
/* 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(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) */
|
/* Retrieve an object item by key (case-sensitive) */
|
||||||
Json *json_get_object_item(Json *object, const char *key);
|
Json *json_get_object_item(Json *object, const char *key);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ stdenv.mkDerivation {
|
|||||||
|
|
||||||
checkPhase = ''
|
checkPhase = ''
|
||||||
mkdir -p target/test
|
mkdir -p target/test
|
||||||
|
export LOG_LEVEL=DEBUG
|
||||||
for test_file in test/*.c; do
|
for test_file in test/*.c; do
|
||||||
exe="target/test/$(basename ''${test_file%.c})"
|
exe="target/test/$(basename ''${test_file%.c})"
|
||||||
${gcc}/bin/cc -Wall -Wextra -g -pedantic \
|
${gcc}/bin/cc -Wall -Wextra -g -pedantic \
|
||||||
|
|||||||
@@ -1,65 +1,61 @@
|
|||||||
#include "hmpl.h"
|
#include "hmpl.h"
|
||||||
|
|
||||||
char *eval(Arena *arena, const Json * const context, const char * const key) {
|
char *eval(Arena *arena, const Json * const context, const char * const query) {
|
||||||
if (!context || !key) return NULL;
|
if (!context || !query) return NULL;
|
||||||
|
|
||||||
char *key_copy = arena_strdup(arena, key);
|
|
||||||
Json *res = context;
|
Json *res = context;
|
||||||
char *start = key_copy;
|
char *dot, *key = arena_strdup(arena, query);
|
||||||
char *dot;
|
|
||||||
|
|
||||||
// Instead of using strtok_r, manually split the string using strchr.
|
while ((dot = strchr(key, '.')) != NULL) {
|
||||||
while ((dot = strchr(start, '.')) != NULL) {
|
|
||||||
*dot = '\0';
|
*dot = '\0';
|
||||||
raise_debug("res: %s, token: %s, key: %s", json_to_string(arena, res), start, key);
|
raise_debug("res: %s, key: %s, query: %s", json_to_string(arena, res), key, query);
|
||||||
res = json_get_object_item(res, start);
|
res = json_get_object_item(res, key);
|
||||||
if (!res)
|
if (!res)
|
||||||
return NULL;
|
return NULL;
|
||||||
start = dot + 1;
|
key = dot + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
raise_debug("res: %s, token: %s, key: %s", json_to_string(arena, res), start, key);
|
raise_debug("res: %s, key: %s, query: %s", json_to_string(arena, res), key, query);
|
||||||
res = json_get_object_item(res, start);
|
res = json_get_object_item(res, key);
|
||||||
if (!res)
|
if (!res)
|
||||||
return NULL;
|
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 */
|
/* 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) {
|
void hmpl_render_interpolation_tags(Arena *arena, char **text_ptr, Json *context, const char * const prefix) {
|
||||||
raise_debug("hmpl_render_interpolation_tags");
|
raise_debug("hmpl_render_interpolation_tags");
|
||||||
char start_pattern[256];
|
char start_pattern[8];
|
||||||
snprintf(start_pattern, sizeof(start_pattern), "{{%s", prefix);
|
snprintf(start_pattern, sizeof(start_pattern), "{{%s", prefix);
|
||||||
int start_pattern_length = strlen(start_pattern);
|
int start_pattern_length = strlen(start_pattern);
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
char *current_text = *text_ptr;
|
char *current_text = *text_ptr;
|
||||||
char *placeholder_start = strstr(current_text + offset, start_pattern);
|
char *start = strstr(current_text + offset, start_pattern);
|
||||||
if (!placeholder_start)
|
if (!start)
|
||||||
break;
|
break;
|
||||||
int start_index = placeholder_start - current_text;
|
|
||||||
|
int start_index = start - current_text;
|
||||||
int key_start = start_index + start_pattern_length;
|
int key_start = start_index + start_pattern_length;
|
||||||
raise_debug("start: %d", key_start);
|
|
||||||
|
|
||||||
char *placeholder_end = strstr(placeholder_start, "}}");
|
char *end = strstr(start, "}}");
|
||||||
if (!placeholder_end)
|
if (!end)
|
||||||
raise_exception("Malformed template: missing closing braces for placeholder start");
|
raise_exception("Malformed template: missing closing braces for interpolation tag");
|
||||||
int key_length = (placeholder_end - current_text) - key_start;
|
int key_length = (end - current_text) - key_start;
|
||||||
char *placeholder_key = arena_alloc(arena, key_length + 1);
|
char *key = arena_alloc(arena, key_length + 1);
|
||||||
substr(current_text, placeholder_key, key_start, key_length);
|
substr(current_text, key, key_start, key_length);
|
||||||
raise_debug("key: %s", placeholder_key);
|
|
||||||
|
|
||||||
char *replacement = eval(arena, context, placeholder_key);
|
char *replacement = eval(arena, context, key);
|
||||||
raise_debug("%s = eval(context, %s)", replacement ? replacement : "NULL", placeholder_key);
|
|
||||||
if (!replacement) {
|
if (!replacement) {
|
||||||
offset = (placeholder_end - current_text) + 2;
|
offset = (end - current_text) + 2;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
char *new_text = arena_repstr(arena, current_text,
|
char *new_text = arena_repstr(arena, current_text,
|
||||||
start_index,
|
start_index,
|
||||||
placeholder_end - placeholder_start + 1,
|
end - start + 1,
|
||||||
replacement);
|
replacement);
|
||||||
|
|
||||||
*text_ptr = new_text;
|
*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) {
|
void hmpl_render_with_arena(Arena *arena, char **text, const Json * const context) {
|
||||||
if (context->type != JSON_OBJECT) {
|
if (context->type != JSON_OBJECT) {
|
||||||
raise_exception("Malformed context: context is not json");
|
raise_exception("Malformed context: context is not json");
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ void test_render_interpolation_tags_with_prefix(Arena *arena) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
|
init_logger();
|
||||||
Arena arena = arena_init(1024 * 1024);
|
Arena arena = arena_init(1024 * 1024);
|
||||||
|
|
||||||
test_eval_single_level_key(&arena);
|
test_eval_single_level_key(&arena);
|
||||||
|
|||||||
Reference in New Issue
Block a user