From 35db5e4bf67b297c0e0f67ac237372aefcf3a2d3 Mon Sep 17 00:00:00 2001 From: yukkop Date: Thu, 27 Mar 2025 21:07:00 +0000 Subject: [PATCH] test(hectic C): slice --- package/c/hectic/hectic.c | 143 ++++++++++++++++++++++++------- package/c/hectic/hectic.h | 46 ++++++++++ package/c/hectic/make.sh | 14 ++- package/c/hectic/test/03-slice.c | 97 +++++++++++++++++++++ package/c/hmpl/hmpl.c | 5 +- 5 files changed, 271 insertions(+), 34 deletions(-) create mode 100644 package/c/hectic/test/03-slice.c diff --git a/package/c/hectic/hectic.c b/package/c/hectic/hectic.c index 1c3edf4..f93f094 100644 --- a/package/c/hectic/hectic.c +++ b/package/c/hectic/hectic.c @@ -225,28 +225,45 @@ static const char *skip_whitespace(const char *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; + raise_debug("Entering json_parse_string__ at position: %p", s); + if (*s != '"') { + raise_debug("Expected '\"' at start of string, got: %c", *s); + return NULL; + } s++; // skip opening quote const char *start = s; while (*s && *s != '"') { - if (*s == '\\') s++; // skip escaped char + if (*s == '\\') { + s++; // skip escape char indicator + } s++; } - if (*s != '"') return NULL; + if (*s != '"') { + raise_debug("Unterminated string starting at: %p", start); + return NULL; + } size_t len = s - start; char *str = arena_alloc(arena, len + 1); - if (!str) return NULL; + if (!str) { + raise_debug("Memory allocation failed in json_parse_string__"); + return NULL; + } memcpy(str, start, len); str[len] = '\0'; *s_ptr = s + 1; // skip closing quote + raise_debug("Parsed string: \"%s\" (length: %zu)", str, len); return str; } /* Parse a number using strtod */ static double json_parse_number__(const char **s_ptr) { + raise_debug("Parsing number at position: %p", *s_ptr); char *end; double num = strtod(*s_ptr, &end); + if (*s_ptr == end) + raise_debug("No valid number found at: %p", *s_ptr); *s_ptr = end; + raise_debug("Parsed number: %g", num); return num; } @@ -255,26 +272,33 @@ 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) { + raise_debug("Entering json_parse_array__ at position: %p", *s); if (**s != '[') return NULL; (*s)++; // skip '[' *s = skip_whitespace(*s); Json *array = arena_alloc(arena, sizeof(Json)); - if (!array) return NULL; + if (!array) { + raise_debug("Memory allocation failed in json_parse_array__"); + return NULL; + } memset(array, 0, sizeof(Json)); array->type = JSON_ARRAY; Json *last = NULL; if (**s == ']') { // empty array (*s)++; + raise_debug("Parsed empty array"); return array; } while (**s) { Json *element = json_parse_value__(s, arena); - if (!element) return NULL; + if (!element) { + raise_debug("Failed to parse array element"); + return NULL; + } if (!array->child) array->child = element; - else { + else last->next = element; - } last = element; *s = skip_whitespace(*s); if (**s == ',') { @@ -284,41 +308,56 @@ static Json *json_parse_array__(const char **s, Arena *arena) { (*s)++; break; } else { - return NULL; // error + raise_debug("Unexpected character '%c' in array", **s); + return NULL; } } + raise_debug("Completed parsing array"); return array; } /* Parse a JSON object: { "key": value, ... } */ static Json *json_parse_object__(const char **s, Arena *arena) { + raise_debug("Entering json_parse_object__ at position: %p", *s); if (**s != '{') return NULL; (*s)++; // skip '{' *s = skip_whitespace(*s); Json *object = arena_alloc(arena, sizeof(Json)); - if (!object) return NULL; + if (!object) { + raise_debug("Memory allocation failed in json_parse_object__"); + return NULL; + } memset(object, 0, sizeof(Json)); object->type = JSON_OBJECT; Json *last = NULL; if (**s == '}') { (*s)++; + raise_debug("Parsed empty object"); return object; } while (**s) { char *key = json_parse_string__(s, arena); - if (!key) return NULL; + if (!key) { + raise_debug("Failed to parse key in object"); + return NULL; + } *s = skip_whitespace(*s); - if (**s != ':') return NULL; + if (**s != ':') { + raise_debug("Expected ':' after key \"%s\", got: %c", key, **s); + return NULL; + } (*s)++; // skip ':' *s = skip_whitespace(*s); Json *value = json_parse_value__(s, arena); - if (!value) return NULL; + if (!value) { + raise_debug("Failed to parse value for key \"%s\"", key); + return NULL; + } value->key = key; // assign key to the value if (!object->child) object->child = value; - else { + else last->next = value; - } last = value; *s = skip_whitespace(*s); if (**s == ',') { @@ -328,18 +367,24 @@ static Json *json_parse_object__(const char **s, Arena *arena) { (*s)++; break; } else { - return NULL; // error + raise_debug("Unexpected character '%c' in object", **s); + return NULL; } } + raise_debug("Completed parsing object"); return object; } /* Full JSON value parser */ static Json *json_parse_value__(const char **s, Arena *arena) { *s = skip_whitespace(*s); + raise_debug("Parsing JSON value at position: %p", *s); if (**s == '"') { Json *item = arena_alloc(arena, sizeof(Json)); - if (!item) return NULL; + if (!item) { + raise_debug("Memory allocation failed in json_parse_value__ for string"); + return NULL; + } memset(item, 0, sizeof(Json)); item->type = JSON_STRING; item->JsonValue.string = json_parse_string__(s, arena); @@ -369,7 +414,10 @@ static Json *json_parse_value__(const char **s, Arena *arena) { return item; } else if ((**s == '-') || isdigit((unsigned char)**s)) { Json *item = arena_alloc(arena, sizeof(Json)); - if (!item) return NULL; + if (!item) { + raise_debug("Memory allocation failed in json_parse_value__ for number"); + return NULL; + } memset(item, 0, sizeof(Json)); item->type = JSON_NUMBER; item->JsonValue.number = json_parse_number__(s); @@ -379,15 +427,19 @@ static Json *json_parse_value__(const char **s, Arena *arena) { } else if (**s == '{') { return json_parse_object__(s, arena); } + raise_debug("Unrecognized JSON value at position: %p", *s); return NULL; } Json *json_parse(Arena *arena, const char **s) { - return json_parse_value__(s, arena); + Json *result = json_parse_value__(s, arena); + if (!result) + raise_debug("json_parse failed at position: %p", *s); + return result; } char *json_to_string(Arena *arena, const Json * const item) { - return json_to_string_with_opts(arena, item, JSON_NORAW); + return json_to_string_with_opts(arena, item, JSON_NORAW); } /* Minimal JSON printer with raw output option. @@ -395,8 +447,10 @@ char *json_to_string(Arena *arena, const Json * const item) { */ char *json_to_string_with_opts(Arena *arena, const Json * const item, JsonRawOpt raw) { char *out = arena_alloc(arena, 1024); - if (!out) + if (!out) { + raise_debug("Memory allocation failed in json_to_string_with_opts"); return NULL; + } char *ptr = out; if (item->type == JSON_OBJECT) { ptr += sprintf(ptr, "{"); @@ -433,27 +487,54 @@ char *json_to_string_with_opts(Arena *arena, const Json * const item, JsonRawOpt } else if (item->type == JSON_NULL) { sprintf(ptr, "null"); } + raise_debug("Converted JSON to string: %s", out); return out; } /* Retrieve an object item by key (case-sensitive) */ Json *json_get_object_item(const Json * const object, const char * const key) { - raise_debug("json get object item for %s", key); - if (!object || object->type != JSON_OBJECT) + raise_debug("json_get_object_item: Searching for key \"%s\"", key); + if (!object || object->type != JSON_OBJECT) { + raise_debug("Invalid object passed to json_get_object_item"); return NULL; + } Json *child = object->child; while (child) { - raise_debug("child->key: %s, key: %s", child->key, key); - if (child->key && strcmp(child->key, key) == 0) + raise_debug("Comparing child key \"%s\" with \"%s\"", child->key, key); + if (child->key && strcmp(child->key, key) == 0) { + raise_debug("Key \"%s\" found", key); return child; + } child = child->next; } + raise_debug("Key \"%s\" not found in object", key); return NULL; } -//bool json_is_string(const Json * const item) { -// if (item == NULL) { -// return false; -// } -// return item->type == JSON_STRING; -//} +// ----------- +// -- slice -- +// ----------- + +// Create a slice from an array with boundary check. +Slice slice_create__(const char *file, int line, size_t isize, void *array, size_t array_len, size_t start, size_t len) { + raise_message(LOG_LEVEL_TRACE, file, line, "slice_create(, , , , )"); + if (start + len > array_len) + return (Slice){NULL, 0, isize}; + return (Slice){ (char *)array + start * isize, len, isize }; +} + +// Return a subslice from an existing slice. +Slice slice_subslice__(const char *file, int line, Slice s, size_t start, size_t len) { + raise_message(LOG_LEVEL_TRACE, file, line, "slice_subslice(, , )"); + if (start + len > s.len) + return (Slice){NULL, 0, s.isize}; + return (Slice){(char*)s.data + start * s.isize, len, s.isize}; +} + +int* arena_slice_copy__(const char *file, int line, Arena *arena, Slice s) { + raise_message(LOG_LEVEL_TRACE, file, line, "arena_slice_copy(, )"); + int *copy = (void*) arena_alloc__(file, line, arena, s.len * sizeof(int)); + if (copy) + memcpy(copy, s.data, s.len * s.isize); + return copy; +} diff --git a/package/c/hectic/hectic.h b/package/c/hectic/hectic.h index 75b2139..5f8d3ac 100644 --- a/package/c/hectic/hectic.h +++ b/package/c/hectic/hectic.h @@ -246,3 +246,49 @@ char *json_to_string_with_opts(Arena *arena, const Json * const item, JsonRawOpt Json *json_get_object_item(const Json * const object, const char * const key); #endif // EPRINTF_H + +// ----------- +// -- Slice -- +// ----------- + +typedef struct { + void *data; + size_t len; + size_t isize; +} Slice; + +// Usage: +// printf("Content: %.*s\n", SLICE_ARGS(slice, char)); +// printf("Content: %d\n", SLICE_ARGS(slice, int)); +#define SLICE_ARGS(slice, type) ((int)((slice).len / sizeof(type))), ((type*)((slice).data)) + +Slice slice_create__(const char *file, int line, size_t isize, void *array, size_t array_len, size_t start, size_t len); + +Slice slice_subslice__(const char *file, int line, Slice s, size_t start, size_t len); + +int* arena_slice_copy__(const char *file, int line, Arena *arena, Slice s); + +#define slice_create(type, array, array_len, start, len) \ + slice_create__(__FILE__, __LINE__, sizeof(type), array, array_len, start, len) + +#define slice_subslice(s, start, len) \ + slice_subslice__(__FILE__, __LINE__, s, start, len) + +#define arena_slice_copy(arena, s) \ + arena_slice_copy__(__FILE__, __LINE__, arena, s) + +#define SLICE_TO_STRING(type, slice, fmt) __extension__ ({ \ + size_t count = (slice).len / (slice).isize; \ + size_t bufsize = count * 32 + 1; \ + char *buf = malloc(bufsize); \ + if (buf) { \ + buf[0] = '\0'; \ + for (size_t i = 0; i < count; i++) { \ + char temp[32]; \ + snprintf(temp, sizeof(temp), fmt " ", \ + ((type *)((slice).data))[i]); \ + strncat(buf, temp, bufsize - strlen(buf) - 1); \ + } \ + } \ + buf; \ +}) diff --git a/package/c/hectic/make.sh b/package/c/hectic/make.sh index fa83dea..2bb3d18 100644 --- a/package/c/hectic/make.sh +++ b/package/c/hectic/make.sh @@ -45,6 +45,7 @@ CFLAGS="-Wall -Wextra -Werror -pedantic -fsanitize=address" LDFLAGS="-lhectic" STD_FLAGS="-std=c99" COLOR_FLAG="" +DEBUG=0 MODE="${1:-build}" shift @@ -56,7 +57,12 @@ while [ $# -gt 0 ]; do RUN_TESTS=0 ;; --debug) - OPTFLAGS="-O0" + if ! command -v gdb >/dev/null 2>&1; then + echo "Error: Required dependency '$dep' not found." >&2 + exit 1 + fi + OPTFLAGS="-O0 -gdwarf-2 -g3" + DEBUG=1 ;; --color) COLOR_FLAG="-fdiagnostics-color=always" @@ -89,7 +95,13 @@ case "$MODE" in exe="target/test/$(basename "${test_file%.c}")" # shellcheck disable=SC2086 cc $CFLAGS $OPTFLAGS -pedantic -I. "$test_file" -Ltarget -lhectic $LDFLAGS -o "$exe" + if [ "$?" -ne 0 ]; then + exit 1 + fi if [ "$RUN_TESTS" -eq 1 ]; then + if [ "$DEBUG" -eq 1 ]; then + gdb -tui "$exe" + fi "$exe" fi done diff --git a/package/c/hectic/test/03-slice.c b/package/c/hectic/test/03-slice.c new file mode 100644 index 0000000..7b81adc --- /dev/null +++ b/package/c/hectic/test/03-slice.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include "hectic.h" + +void test_slice_create() { + int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Slice s = slice_create(int, arr, 10, 0, 5); + assert(s.data != NULL); + assert(s.len == 5); + assert(s.isize == sizeof(int)); + + // Verify slice contents + int *data = (int*)s.data; + for (int i = 0; i < 5; i++) { + assert(data[i] == i + 1); + } +} + +void test_slice_subslice() { + int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Slice s = slice_create(int, arr, 10, 0, 10); + Slice sub = slice_subslice(s, 2, 4); + + assert(sub.data != NULL); + assert(sub.len == 4); + assert(sub.isize == sizeof(int)); + + // Verify subslice contents + int *data = (int*)sub.data; + for (int i = 0; i < 4; i++) { + assert(data[i] == i + 3); + } +} + +void test_slice_copy() { + Arena arena = arena_init(128); + int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Slice s = slice_create(int, arr, 10, 0, 5); + + int *copy = arena_slice_copy(&arena, s); + assert(copy != NULL); + + // Verify copy contents + for (int i = 0; i < 5; i++) { + assert(copy[i] == i + 1); + } + + arena_free(&arena); +} + +void test_slice_edge_cases() { + int arr[] = {1, 2, 3, 4, 5}; + + // Test empty slice + Slice empty = slice_create(int, arr, 5, 0, 0); + assert(empty.len == 0); + assert(empty.data != NULL); + + // Test full array slice + Slice full = slice_create(int, arr, 5, 0, 5); + assert(full.len == 5); + assert(full.data != NULL); + + // Test slice at end of array + Slice end = slice_create(int, arr, 5, 3, 2); + assert(end.len == 2); + assert(end.data != NULL); + int *end_data = (int*)end.data; + assert(end_data[0] == 4); + assert(end_data[1] == 5); +} + +void test_slice_string() { + const char *str = "Hello, World!"; + Slice s = slice_create(char, (void*)str, strlen(str), 0, 5); + assert(s.len == 5); + assert(s.isize == sizeof(char)); + + char *data = (char*)s.data; + assert(strncmp(data, "Hello", 5) == 0); +} + +int main() { + set_output_color_mode(COLOR_MODE_DISABLE); + logger_level(LOG_LEVEL_DEBUG); + + test_slice_create(); + test_slice_subslice(); + test_slice_copy(); + test_slice_edge_cases(); + test_slice_string(); + + printf("%s all tests passed.\n", __FILE__); + return 0; +} diff --git a/package/c/hmpl/hmpl.c b/package/c/hmpl/hmpl.c index 5ea6c0d..2da8751 100644 --- a/package/c/hmpl/hmpl.c +++ b/package/c/hmpl/hmpl.c @@ -207,11 +207,12 @@ void hmpl_render_section_tags(Arena *arena, char **text_ptr, Json *context, cons size_t offset = 0; char *block_buff = arena_alloc(arena, MEM_KiB); - size_t block_start = (size_t)opening_tag_end + 2; + size_t relative_block_start = (size_t)opening_tag_end + 2 - (size_t)current_text; + raise_trace("relative_block_start: %p = %p - 2 - %p", opening_tag_end, current_text); size_t block_len = (size_t)opening_tag_end - (size_t)close_tag - 2; raise_trace("block_len %p = %p - %p - 2", block_len, opening_tag_end, close_tag); assert(block_len > 0); - substr_clone(current_text, block_buff, block_start, block_len); + substr_clone(current_text, block_buff, relative_block_start, block_len); for (Json *elem = arr->child; elem; elem = elem->next) { char *block = arena_strdup(arena, block_buff);