From bcaca5038dfe802a69828178c2f0485570e6356b Mon Sep 17 00:00:00 2001 From: yukkop Date: Sat, 22 Mar 2025 01:22:36 +0000 Subject: [PATCH] feat(hectic C): json api --- package/c/chectic/chectic.c | 247 ++++++++++++++++++ package/c/chectic/chectic.h | 109 ++++++-- .../c/chectic/test/{test.c => 00-logger.c} | 1 + .../c/chectic/test/{arena.c => 01-arena.c} | 38 ++- package/c/chectic/test/02-json.c | 138 ++++++++++ package/c/hmpl/default.nix | 17 +- package/c/hmpl/hmpl.c | 204 ++++++--------- package/c/hmpl/hmpl.h | 22 ++ package/c/hmpl/libhmpl.c | 8 - package/c/hmpl/libhmpl.h | 8 - package/c/hmpl/main.c | 51 ++++ 11 files changed, 680 insertions(+), 163 deletions(-) rename package/c/chectic/test/{test.c => 00-logger.c} (97%) rename package/c/chectic/test/{arena.c => 01-arena.c} (60%) create mode 100644 package/c/chectic/test/02-json.c create mode 100644 package/c/hmpl/hmpl.h delete mode 100644 package/c/hmpl/libhmpl.c delete mode 100644 package/c/hmpl/libhmpl.h create mode 100644 package/c/hmpl/main.c diff --git a/package/c/chectic/chectic.c b/package/c/chectic/chectic.c index 9bafa4b..dd6c064 100644 --- a/package/c/chectic/chectic.c +++ b/package/c/chectic/chectic.c @@ -74,3 +74,250 @@ char* log_message(LogLevel level, char *file, int line, const char *format, ...) return timeStr; } + +// ----------- +// -- utils -- +// ----------- + +void substr(const char *src, char *dest, size_t start, size_t len) { + raise_debug("substring %s from %zu to %zu", src, start, len); + size_t srclen = strlen(src); + if (start >= srclen) { + dest[0] = '\0'; + return; + } + if (start + len > srclen) + len = srclen - start; + strncpy(dest, src + start, len); + dest[len] = '\0'; +} + +// ---------- +// -- Json -- +// ---------- + +/* Utility: Skip whitespace */ +static const char *json_skip_whitespace(const char *s) { + while (*s && isspace((unsigned char)*s)) + s++; + return s; +} + +/* Parse a JSON string (does not handle full escaping) */ +static char *json_parse_string__(const char **s_ptr, Arena *arena) { + const char *s = *s_ptr; + if (*s != '"') return NULL; + s++; // skip opening quote + const char *start = s; + while (*s && *s != '"') { + if (*s == '\\') s++; // skip escaped char + s++; + } + if (*s != '"') return NULL; + size_t len = s - start; + char *str = arena_alloc(arena, len + 1); + if (!str) return NULL; + memcpy(str, start, len); + str[len] = '\0'; + *s_ptr = s + 1; // skip closing quote + return str; +} + +/* Parse a number using strtod */ +static double json_parse_number__(const char **s_ptr) { + char *end; + double num = strtod(*s_ptr, &end); + *s_ptr = end; + return num; +} + +/* Forward declaration */ +static Json *json_parse_value__(const char **s, Arena *arena); + +/* Parse a JSON array: [ value, value, ... ] */ +static Json *json_parse_array__(const char **s, Arena *arena) { + if (**s != '[') return NULL; + (*s)++; // skip '[' + *s = json_skip_whitespace(*s); + Json *array = arena_alloc(arena, sizeof(Json)); + if (!array) return NULL; + memset(array, 0, sizeof(Json)); + array->type = JSON_ARRAY; + Json *last = NULL; + if (**s == ']') { // empty array + (*s)++; + return array; + } + while (**s) { + Json *element = json_parse_value__(s, arena); + if (!element) return NULL; + if (!array->child) + array->child = element; + else { + last->next = element; + } + last = element; + *s = json_skip_whitespace(*s); + if (**s == ',') { + (*s)++; + *s = json_skip_whitespace(*s); + } else if (**s == ']') { + (*s)++; + break; + } else { + return NULL; // error + } + } + return array; +} + +/* Parse a JSON object: { "key": value, ... } */ +static Json *json_parse_object__(const char **s, Arena *arena) { + if (**s != '{') return NULL; + (*s)++; // skip '{' + *s = json_skip_whitespace(*s); + Json *object = arena_alloc(arena, sizeof(Json)); + if (!object) return NULL; + memset(object, 0, sizeof(Json)); + object->type = JSON_OBJECT; + Json *last = NULL; + if (**s == '}') { + (*s)++; + return object; + } + while (**s) { + char *key = json_parse_string__(s, arena); + if (!key) return NULL; + *s = json_skip_whitespace(*s); + if (**s != ':') return NULL; + (*s)++; // skip ':' + *s = json_skip_whitespace(*s); + Json *value = json_parse_value__(s, arena); + if (!value) return NULL; + value->key = key; // assign key to the value + if (!object->child) + object->child = value; + else { + last->next = value; + } + last = value; + *s = json_skip_whitespace(*s); + if (**s == ',') { + (*s)++; + *s = json_skip_whitespace(*s); + } else if (**s == '}') { + (*s)++; + break; + } else { + return NULL; // error + } + } + return object; +} + +/* Full JSON value parser */ +static Json *json_parse_value__(const char **s, Arena *arena) { + *s = json_skip_whitespace(*s); + if (**s == '"') { + Json *item = arena_alloc(arena, sizeof(Json)); + if (!item) return NULL; + memset(item, 0, sizeof(Json)); + item->type = JSON_STRING; + item->string = json_parse_string__(s, arena); + return item; + } else if (strncmp(*s, "null", 4) == 0) { + Json *item = arena_alloc(arena, sizeof(Json)); + if (!item) return NULL; + memset(item, 0, sizeof(Json)); + item->type = JSON_NULL; + *s += 4; + return item; + } else if (strncmp(*s, "true", 4) == 0) { + Json *item = arena_alloc(arena, sizeof(Json)); + if (!item) return NULL; + memset(item, 0, sizeof(Json)); + item->type = JSON_BOOL; + item->boolean = 1; + *s += 4; + return item; + } else if (strncmp(*s, "false", 5) == 0) { + Json *item = arena_alloc(arena, sizeof(Json)); + if (!item) return NULL; + memset(item, 0, sizeof(Json)); + item->type = JSON_BOOL; + item->boolean = 0; + *s += 5; + return item; + } else if ((**s == '-') || isdigit((unsigned char)**s)) { + Json *item = arena_alloc(arena, sizeof(Json)); + if (!item) return NULL; + memset(item, 0, sizeof(Json)); + item->type = JSON_NUMBER; + item->number = json_parse_number__(s); + return item; + } else if (**s == '[') { + return json_parse_array__(s, arena); + } else if (**s == '{') { + return json_parse_object__(s, arena); + } + return NULL; +} + +Json *json_parse(Arena *arena, const char **s) { + return json_parse_value__(s, arena); +} + +/* Minimal JSON printer. + For simplicity, a fixed-size buffer is used. + In production you’d dynamically size or use the arena. */ +char *json_print(Arena *arena, Json *item) { + char *out = arena_alloc(arena, 1024); + if (!out) return NULL; + char *ptr = out; + if (item->type == JSON_OBJECT) { + ptr += sprintf(ptr, "{"); + Json *child = item->child; + while (child) { + ptr += sprintf(ptr, "\"%s\":", child->key ? child->key : ""); + char *child_str = json_print(arena, child); + ptr += sprintf(ptr, "%s", child_str); + if (child->next) + ptr += sprintf(ptr, ","); + child = child->next; + } + sprintf(ptr, "}"); + } else if (item->type == JSON_ARRAY) { + ptr += sprintf(ptr, "["); + Json *child = item->child; + while (child) { + char *child_str = json_print(arena, child); + ptr += sprintf(ptr, "%s", child_str); + if (child->next) + ptr += sprintf(ptr, ","); + child = child->next; + } + sprintf(ptr, "]"); + } else if (item->type == JSON_STRING) { + sprintf(out, "\"%s\"", item->string); + } else if (item->type == JSON_NUMBER) { + sprintf(out, "%g", item->number); + } else if (item->type == JSON_BOOL) { + sprintf(out, item->boolean ? "true" : "false"); + } else if (item->type == JSON_NULL) { + sprintf(out, "null"); + } + return out; +} + +/* Retrieve an object item by key (case-sensitive) */ +Json *json_get_object_item(Json *object, const char *key) { + if (!object || object->type != JSON_OBJECT) + return NULL; + Json *child = object->child; + while (child) { + if (child->key && strcmp(child->key, key) == 0) + return child; + child = child->next; + } + return NULL; +} diff --git a/package/c/chectic/chectic.h b/package/c/chectic/chectic.h index e5516e9..4e31278 100644 --- a/package/c/chectic/chectic.h +++ b/package/c/chectic/chectic.h @@ -1,5 +1,5 @@ -#ifndef EPRINTF_H -#define EPRINTF_H +#ifndef EPRINTF_CHECTIC +#define EPRINTF_CHECTIC #include #include @@ -7,6 +7,7 @@ #include #include #include +#include // ------------- // -- Helpers -- @@ -83,8 +84,6 @@ char* log_message(LogLevel level, char *file, int line, const char *format, ...) #define raise_warn(fmt, ...) log_message(LOG_LEVEL_WARN, __FILE__, __LINE__, fmt, ##__VA_ARGS__) #define raise_exception(fmt, ...) log_message(LOG_LEVEL_EXCEPTION, __FILE__, __LINE__, fmt, ##__VA_ARGS__) -#endif // EPRINTF_H - // ----------- // -- arena -- // ----------- @@ -105,16 +104,16 @@ typedef struct { *(arena) = arena_init(ARENA_DEFAULT_SIZE); \ } \ size_t current__ = (size_t)(arena)->current - (size_t)(arena)->begin; \ - if ((arena)->capacity <= current__ || (arena)->capacity - current__ < (size)) { \ - raise_debug("Arena %p (capacity %zu) used %zu cannot allocate %zu bytes", \ + if ((arena)->capacity <= current__ || (arena)->capacity - current__ < (size)) {\ + raise_debug("Arena %p (capacity %zu) used %zu cannot allocate %zu bytes", \ (arena)->begin, (arena)->capacity, current__, (size)); \ } else { \ - raise_debug("Arena %p (capacity %zu) used %zu will allocate %zu bytes", \ + raise_debug("Arena %p (capacity %zu) used %zu will allocate %zu bytes", \ (arena)->begin, (arena)->capacity, current__, (size)); \ mem__ = (arena)->current; \ (arena)->current = (char*)(arena)->current + (size); \ } \ - raise_debug("Allocated at %p", mem__); \ + raise_debug("Allocated at %p", mem__); \ mem__; \ }) @@ -128,14 +127,14 @@ typedef struct { arena__; \ }) -#define arena_reset(arena) __extension__ ({ \ - (arena)->current = (arena)->begin; \ - raise_debug("Arena %p reset", (arena)->begin); \ +#define arena_reset(arena) __extension__ ({ \ + (arena)->current = (arena)->begin; \ + raise_debug("Arena %p reset", (arena)->begin); \ }) -#define arena_free(arena) __extension__ ({ \ +#define arena_free(arena) __extension__ ({ \ raise_debug("Freeing arena at %p", (arena)->begin); \ - free((arena)->begin); \ + free((arena)->begin); \ }) #define arena_alloc(arena, size) __extension__ ({ \ @@ -161,13 +160,77 @@ typedef struct { result__; \ }) -// TODO: mmap -// TODO: dynamic array style -// void *arena_realloc(Arena *arena, size_t size) { -// void *mem = arena_alloc_or_null(arena, size); -// if (!mem) { -// raise_exception("Arena out of memory"); -// exit(1); -// } -// return mem; -// } +#define arena_repstr(arena, src, start, len, rep) __extension__ ({ \ + const char *src__ = (src); \ + const char *rep__ = (rep); \ + size_t start__ = (start); \ + size_t len__ = (len); \ + int src_len__ = strlen(src__); \ + int rep_len__ = strlen(rep__); \ + int new_len__ = src_len__ - len__ + 1 + rep_len__; \ + char *new_str__ = (char *)arena_alloc(arena, new_len__ + 1); \ + memcpy(new_str__, src__, start__); \ + memcpy(new_str__ + start__, rep__, rep_len__); \ + strcpy(new_str__ + start__ + rep_len__, src__ + start__ + len__ + 1); \ + new_str__; \ +}) + +#define arena_realloc_copy(arena, old_ptr, old_size, new_size) __extension__ ({ \ + void *old__ = (old_ptr); \ + size_t old_size__ = (old_size); \ + size_t new_size__ = (new_size); \ + void *new__ = NULL; \ + if (old__ == NULL) { \ + new__ = arena_alloc((arena), new_size__); \ + } else if (new_size__ <= old_size__) { \ + new__ = old__; \ + } else { \ + new__ = arena_alloc_or_null((arena), new_size__); \ + if (new__) memcpy(new__, old__, old_size__); \ + } \ + new__; \ +}) + +// ---------- +// -- misc -- +// ---------- + +void substr(const char *src, char *dest, size_t start, size_t len); + +// ---------- +// -- Json -- +// ---------- + +typedef enum { + JSON_NULL, + JSON_BOOL, + JSON_NUMBER, + JSON_STRING, + JSON_ARRAY, + JSON_OBJECT +} JsonType; + +/* Full JSON structure */ +typedef struct Json { + struct Json *next; /* Next sibling */ + struct Json *child; /* Child element (for arrays/objects) */ + JsonType type; + char *key; /* Key if item is in an object */ + union { + double number; + char *string; + int boolean; + }; +} Json; + +Json *json_parse(Arena *arena, const char **s); + +/* Minimal JSON printer. + For simplicity, a fixed-size buffer is used. + In production you’d dynamically size or use the arena. */ +char *json_print(Arena *arena, Json *item); + +/* Retrieve an object item by key (case-sensitive) */ +Json *json_get_object_item(Json *object, const char *key); + +#endif // EPRINTF_H diff --git a/package/c/chectic/test/test.c b/package/c/chectic/test/00-logger.c similarity index 97% rename from package/c/chectic/test/test.c rename to package/c/chectic/test/00-logger.c index 43eddef..56e6dcd 100644 --- a/package/c/chectic/test/test.c +++ b/package/c/chectic/test/00-logger.c @@ -35,5 +35,6 @@ int main(void) { TEST_RAISE_GENERIC(raise_warn, LOG_LEVEL_WARN, "WARN"); TEST_RAISE_GENERIC(raise_exception, LOG_LEVEL_EXCEPTION, "EXCEPTION"); + printf("%s all tests passed.\n", __FILE__); return 0; } diff --git a/package/c/chectic/test/arena.c b/package/c/chectic/test/01-arena.c similarity index 60% rename from package/c/chectic/test/arena.c rename to package/c/chectic/test/01-arena.c index f29b3dc..a4a36a9 100644 --- a/package/c/chectic/test/arena.c +++ b/package/c/chectic/test/01-arena.c @@ -56,6 +56,40 @@ void test_arena_strdup() { arena_free(&arena); } +void test_arena_repstr() { + Arena arena = arena_init(128); + const char *original = "Hello, World!"; + // Replace substring starting at index 5, length 3 (", W") with " -" + // According to the macro logic, the suffix is taken from original[5+3+1] onward. + // That results in: "Hello" + " -" + "rld!" = "Hello -rld!" + char *result = arena_repstr(&arena, original, 5, 3, " -"); + assert(strcmp(result, "Hello -rld!") == 0); + arena_free(&arena); +} + +void test_arena_overwrite_detection() { + Arena arena = arena_init(128); + + char *s1 = arena_alloc(&arena, 6); + strcpy(s1, "hello"); + + char *s2 = arena_alloc(&arena, 6); + strcpy(s2, "world"); + + assert(strcmp(s1, "hello") == 0); + assert(strcmp(s2, "world") == 0); + + // Force allocation near capacity + void *large = arena_alloc_or_null(&arena, 100); + assert(large != NULL || arena.current == arena.begin + arena.capacity); // If NULL, out of memory + + // Check strings again + assert(strcmp(s1, "hello") == 0); + assert(strcmp(s2, "world") == 0); + + arena_free(&arena); +} + int main() { set_output_color_mode(COLOR_MODE_DISABLE); logger_level(LOG_LEVEL_DEBUG); @@ -66,5 +100,7 @@ int main() { test_arena_reset(); test_arena_null_init(); test_arena_strdup(); - printf("All tests passed.\n"); + test_arena_repstr(); + test_arena_overwrite_detection(); + printf("%s all tests passed.\n", __FILE__); } diff --git a/package/c/chectic/test/02-json.c b/package/c/chectic/test/02-json.c new file mode 100644 index 0000000..2c32124 --- /dev/null +++ b/package/c/chectic/test/02-json.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include "chectic.h" + +#define ARENA_SIZE 1024 * 1024 + +// Test 1: Parse JSON object with a string value. +static void test_parse_json_object(void) { + Arena arena = arena_init(ARENA_SIZE); + const char *json = "{\"key\":\"value\"}"; + Json *root = json_parse(&arena, &json); + assert(root->type == JSON_OBJECT); + Json *child = root->child; + assert(child && strcmp(child->key, "key") == 0); + assert(child->type == JSON_STRING); + assert(strcmp(child->string, "value") == 0); + arena_free(&arena); +} + +// Test 2: Parse JSON number. +static void test_parse_json_number(void) { + Arena arena = arena_init(ARENA_SIZE); + const char *json = "42"; + Json *root = json_parse(&arena, &json); + assert(root->type == JSON_NUMBER); + assert(root->number == 42); + arena_free(&arena); +} + +// Test 3: Parse JSON string. +static void test_parse_json_string(void) { + Arena arena = {0}; + const char *json = "\"hello\""; + Json *root = json_parse(&arena, &json); + assert(root->type == JSON_STRING); + assert(strcmp(root->string, "hello") == 0); + arena_free(&arena); +} + +// Test 4: Get object items by key. +static void test_get_object_items(void) { + Arena arena = arena_init(ARENA_SIZE); + const char *json = "{\"a\":\"1\", \"b\":2}"; + Json *root = json_parse(&arena, &json); + Json *item_a = json_get_object_item(root, "a"); + assert(item_a && item_a->type == JSON_STRING); + assert(strcmp(item_a->string, "1") == 0); + Json *item_b = json_get_object_item(root, "b"); + assert(item_b && item_b->type == JSON_NUMBER); + assert(item_b->number == 2); + arena_free(&arena); +} + +// Test 5: Print JSON object. +static void test_print_json_object(void) { + Arena arena = arena_init(ARENA_SIZE); + const char *json = "{\"key\":\"value\", \"num\":3.14}"; + Json *root = json_parse(&arena, &json); + char *printed = json_print(&arena, root); + assert(strstr(printed, "\"key\":") != NULL); + assert(strstr(printed, "\"value\"") != NULL); + assert(strstr(printed, "\"num\":") != NULL); + assert(strstr(printed, "3.14") != NULL); + arena_free(&arena); +} + +// Test 6: Print JSON number. +static void test_print_json_number(void) { + Arena arena = arena_init(ARENA_SIZE); + const char *json = "123.456"; + Json *root = json_parse(&arena, &json); + char *printed = json_print(&arena, root); + double val = atof(printed); + assert(val == 123.456); + arena_free(&arena); +} + +// Test 7: Print JSON string. +static void test_print_json_string(void) { + Arena arena = arena_init(ARENA_SIZE); + const char *json = "\"test string\""; + Json *root = json_parse(&arena, &json); + char *printed = json_print(&arena, root); + assert(strcmp(printed, "\"test string\"") == 0); + arena_free(&arena); +} + +// Test 8: Nested JSON object. +static void test_nested_json_object(void) { + Arena arena = arena_init(1024 * 1024); + const char *json = "{\"outer\":{\"inner\":100}}"; + Json *root = json_parse(&arena, &json); + assert(root != NULL); + assert(root->type == JSON_OBJECT); + + Json *outer = json_get_object_item(root, "outer"); + assert(outer != NULL); + assert(outer->type == JSON_OBJECT); + + Json *inner = json_get_object_item(outer, "inner"); + assert(inner != NULL); + assert(inner->type == JSON_NUMBER); + assert(inner->number == 100); + + arena_free(&arena); +} + +// Test 9: Arena reset and reuse. +static void test_arena_reset_reuse(void) { + Arena arena = arena_init(ARENA_SIZE); + const char *json1 = "{\"key\":\"value\"}"; + Json *root1 = json_parse(&arena, &json1); + char *printed1 = json_print(&arena, root1); + arena_reset(&arena); + const char *json2 = "\"another test\""; + Json *root2 = json_parse(&arena, &json2); + char *printed2 = json_print(&arena, root2); + assert(strcmp(printed2, "\"another test\"") == 0); + arena_free(&arena); +} + +int main(void) { + test_parse_json_object(); + test_parse_json_number(); + test_parse_json_string(); + test_get_object_items(); + test_print_json_object(); + test_print_json_number(); + test_print_json_string(); + test_nested_json_object(); + test_arena_reset_reuse(); + + printf("%s all tests passed.\n", __FILE__); + return 0; +} diff --git a/package/c/hmpl/default.nix b/package/c/hmpl/default.nix index 62b5ce9..46272c4 100644 --- a/package/c/hmpl/default.nix +++ b/package/c/hmpl/default.nix @@ -10,16 +10,29 @@ stdenv.mkDerivation { buildPhase = '' mkdir -p target + + echo "# Build library" ${gcc}/bin/cc -Wall -Wextra -g \ - -pedantic -fsanitize=address hmpl.c \ + -std=c99 \ + -pedantic -fsanitize=address -c hmpl.c \ + -lchectic -lcjson \ + -o target/hmpl.o + ${gcc}/bin/ar rcs target/libhmpl.a target/hmpl.o + + echo "# Build app" + ${gcc}/bin/cc -Wall -Wextra -g \ + -pedantic -fsanitize=address main.c \ + -Ltarget -lhmpl \ -lchectic -lcjson -o target/hmpl ''; checkPhase = '' ''; installPhase = '' - mkdir -p $out/bin + mkdir -p $out/bin $out/lib $out/include cp target/hmpl $out/bin/hmpl + cp target/libhmpl.a $out/lib/ + cp hmpl.h $out/include/hmpl.h ''; meta = { diff --git a/package/c/hmpl/hmpl.c b/package/c/hmpl/hmpl.c index 8322587..5d6b0fa 100644 --- a/package/c/hmpl/hmpl.c +++ b/package/c/hmpl/hmpl.c @@ -1,142 +1,104 @@ -#include -#include -#include -#include -#include "chectic.h" -#include "cjson/cJSON.h" +#include "hmpl.h" -static Arena arena; +Arena *cJSON_global_arena; +size_t last_size = 0; // tracked externally, unsafe but works for simple use -char *eval(cJSON *context, const char *key) { - if (!context || !key) return NULL; - char *key_copy = arena_strdup(&arena, key); - char *token, *rest = key_copy; - cJSON *res = context; - while ((token = strtok_r(rest, ".", &rest))) { - res = cJSON_GetObjectItemCaseSensitive(res, token); - if (!res) - return NULL; - } - if (cJSON_IsString(res) && res->valuestring) - return arena_strdup(&arena, res->valuestring); - else if (cJSON_IsNumber(res)) { - char buf[64]; - snprintf(buf, sizeof(buf), "%g", res->valuedouble); - return arena_strdup(&arena, buf); - } - char *temp = cJSON_PrintUnformatted(res); - char *result = arena_strdup(&arena, temp); - free(temp); - return result; +void *arena_malloc(size_t size) { + void *ptr = arena_alloc(cJSON_global_arena, size); + last_size = size; + return ptr; } -void substring(const char *src, char *dest, size_t start, size_t len) { - raise_debug("substring %s from %zu to %zu", src, start, len); - size_t srclen = strlen(src); - if (start >= srclen) { - dest[0] = '\0'; - return; - } - if (start + len > srclen) - len = srclen - start; - strncpy(dest, src + start, len); - dest[len] = '\0'; +void arena_free_stub(void *ptr) { + raise_debug("WARN: cJSON tried to free %p — ignored", ptr); +} + +void init_cjson_with_arenas(Arena *arena) { + cJSON_global_arena = arena; + cJSON_InitHooks(&(cJSON_Hooks){ + .malloc_fn = arena_malloc, + .free_fn = arena_free_stub, + }); } -char* replace_substring(const char* src, int start, int end, const char* replacement) { - raise_debug("replace_substring"); - int src_len = strlen(src); - int rep_len = strlen(replacement); - int new_len = src_len - (end - start + 1) + rep_len; - char* new_str = arena_alloc(&arena, new_len + 1); - memcpy(new_str, src, start); // copy before - memcpy(new_str + start, replacement, rep_len); // insert replacement - strcpy(new_str + start + rep_len, src + end + 1); // copy after - return new_str; +char *eval(Arena *arena, const cJSON * const context, const char * const key) { + if (!context || !key) return NULL; + char *key_copy = arena_strdup(arena, key); + char *token, *rest = key_copy; + cJSON *res = context; + while ((token = strtok_r(rest, ".", &rest))) { + raise_debug("context: %s; token: %s", cJSON_Print(res), key); + res = cJSON_GetObjectItemCaseSensitive(res, token); + if (!res) + return NULL; + } + if (cJSON_IsString(res) && res->valuestring) + return arena_strdup(arena, res->valuestring); + else if (cJSON_IsNumber(res)) { + char buf[64]; + snprintf(buf, sizeof(buf), "%g", res->valuedouble); + return arena_strdup(arena, buf); + } + char *temp = cJSON_PrintUnformatted(res); + char *result = arena_strdup(arena, temp); + free(temp); + return result; } /* Modified: text is passed by reference so we can update it and free old allocations */ -void render_template_placeholders(char **text_ptr, cJSON *context, const char *prefix) { - raise_debug("render_template_placeholders"); - char start_pattern[256]; - snprintf(start_pattern, sizeof(start_pattern), "{{%s", prefix); - int start_pattern_length = strlen(start_pattern); - int offset = 0; +void render_template_placeholders(Arena *arena, char **text_ptr, cJSON *context, const char * const prefix) { + raise_debug("render_template_placeholders"); + char start_pattern[256]; + 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) - break; - int start_index = placeholder_start - current_text; - int key_start = start_index + start_pattern_length; - raise_debug("start: %d", key_start); + while (1) { + char *current_text = *text_ptr; + char *placeholder_start = strstr(current_text + offset, start_pattern); + if (!placeholder_start) + break; + int start_index = placeholder_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); - substring(current_text, placeholder_key, key_start, key_length); - raise_debug("key: %s", placeholder_key); + 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 *replacement = eval(context, placeholder_key); - raise_debug("%s = eval(context, %s)", replacement ? replacement : "NULL", placeholder_key); - if (!replacement) { - offset = (placeholder_end - current_text) + 2; - continue; - } - int placeholder_end_index = (placeholder_end - current_text) + 2; - char *new_text = replace_substring(current_text, - start_index, - placeholder_end_index - 1, - replacement); + char *replacement = eval(arena, context, placeholder_key); + raise_debug("%s = eval(context, %s)", replacement ? replacement : "NULL", placeholder_key); + if (!replacement) { + offset = (placeholder_end - current_text) + 2; + continue; + } + char *new_text = arena_repstr(arena, current_text, + start_index, + placeholder_end - placeholder_start + 1, + replacement); - *text_ptr = new_text; - offset = start_index; - } + *text_ptr = new_text; + offset = start_index; + } } -void render_template(char **text, cJSON *context) { - render_template_placeholders(text, context, ""); +void render_template_with_arena(Arena *arena, char **text, const cJSON * const context) { + if (!cJSON_IsObject(context)) { + raise_exception("Malformed context: context is not json"); + exit(1); + } + + render_template_placeholders(arena, text, context, ""); } -int main(int argc, char *argv[]) { - init_logger(); - raise_info("start"); +void render_template(char **text, const cJSON * const context) { + Arena arena = arena_init(1024 * 1024); - arena = arena_init(1024 * 1024); - - raise_info("read the arguments"); - char *text = NULL; - const char *json_input = (argc > 1 ? argv[1] : "{}"); - cJSON *context = cJSON_Parse(json_input); - if (!context) { - fprintf(stderr, "Error parsing JSON\n"); - return 1; - } - - if (argc > 2) { - text = arena_strdup(&arena, argv[2]); - } else if (!isatty(fileno(stdin))) { - size_t size = 0; - char *heap_text = NULL; - ssize_t len = getdelim(&heap_text, &size, '\0', stdin); - if (len < 0) { - perror("read stdin"); - cJSON_Delete(context); - return 1; - } - text = arena_strdup(&arena, heap_text); - free(heap_text); // free temporary heap allocation - } else { - text = arena_strdup(&arena, ""); - } - - render_template(&text, context); - printf("%s", text); + render_template_with_arena(&arena, text, context); arena_free(&arena); - cJSON_Delete(context); - return 0; } diff --git a/package/c/hmpl/hmpl.h b/package/c/hmpl/hmpl.h new file mode 100644 index 0000000..704ae1d --- /dev/null +++ b/package/c/hmpl/hmpl.h @@ -0,0 +1,22 @@ +#ifndef EPRINTF_HMPL +#define EPRINTF_HMPL + +#include +#include +#include +#include +#include "chectic.h" +#include "cjson/cJSON.h" + +void init_cjson_with_arenas(Arena *arena); + +char *eval(Arena *arena, const cJSON * const context, const char * const key); + +/* Modified: text is passed by reference so we can update it and free old allocations */ +void render_template_placeholders(Arena *arena, char **text_ptr, cJSON *context, const char * const prefix); + +void render_template_with_arena(Arena *arena, char **text, const cJSON * const ccontext); + +void render_template(char **text, const cJSON * const context); + +#endif // EPRINTF_HMPL diff --git a/package/c/hmpl/libhmpl.c b/package/c/hmpl/libhmpl.c deleted file mode 100644 index e0d7872..0000000 --- a/package/c/hmpl/libhmpl.c +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include "libhectic.h" - -int main(void) { - raise_info("hello world"); - - return 0; -} diff --git a/package/c/hmpl/libhmpl.h b/package/c/hmpl/libhmpl.h deleted file mode 100644 index e0d7872..0000000 --- a/package/c/hmpl/libhmpl.h +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include "libhectic.h" - -int main(void) { - raise_info("hello world"); - - return 0; -} diff --git a/package/c/hmpl/main.c b/package/c/hmpl/main.c new file mode 100644 index 0000000..d95b289 --- /dev/null +++ b/package/c/hmpl/main.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include "chectic.h" +#include "cjson/cJSON.h" +#include "hmpl.h" + +int main(int argc, char *argv[]) { + init_logger(); + raise_info("start"); + + Arena arena = arena_init(1024 * 1024); + Arena arena_for_jsons = arena_init(1024 * 1024); + init_cjson_with_arenas(&arena_for_jsons); + + raise_info("read the arguments"); + char *text = NULL; + + const char *json_input = (argc > 1 ? argv[1] : "{}"); + cJSON *context = cJSON_Parse(json_input); + + if (!context) { + fprintf(stderr, "Error parsing JSON\n"); + return 1; + } + + if (argc > 2) { + text = arena_strdup(&arena, argv[2]); + } else if (!isatty(fileno(stdin))) { + size_t size = 0; + char *heap_text = NULL; + ssize_t len = getdelim(&heap_text, &size, '\0', stdin); + if (len < 0) { + perror("read stdin"); + cJSON_Delete(context); + return 1; + } + text = arena_strdup(&arena, heap_text); + free(heap_text); // free temporary heap allocation + } else { + text = arena_strdup(&arena, ""); + } + + render_template_with_arena(&arena, &text, context); + printf("%s", text); + + arena_free(&arena); + cJSON_Delete(context); + return 0; +}