#include "internal/ikv_internal.h" #include "loaders/ikv1.h" #include "loaders/ikv2.h" #include #include #include #include #ifdef IKV_TESTING typedef struct { int alloc_after; int fopen_after; int fread_after; int fseek_after; int ftell_after; int fclose_after; } ikv_test_hooks_t; static ikv_test_hooks_t ikv_test_hooks = {0}; static bool ikv_test_should_fail(int *remaining_calls) { if (!remaining_calls || *remaining_calls == 0) return false; if (*remaining_calls > 0) { (*remaining_calls)--; if (*remaining_calls == 0) return true; } return false; } void ikv_test_hooks_reset(void) { memset(&ikv_test_hooks, 0, sizeof(ikv_test_hooks)); } void ikv_test_fail_alloc_after(int remaining_calls) { ikv_test_hooks.alloc_after = remaining_calls; } void ikv_test_fail_fopen_after(int remaining_calls) { ikv_test_hooks.fopen_after = remaining_calls; } void ikv_test_fail_fread_after(int remaining_calls) { ikv_test_hooks.fread_after = remaining_calls; } void ikv_test_fail_fseek_after(int remaining_calls) { ikv_test_hooks.fseek_after = remaining_calls; } void ikv_test_fail_ftell_after(int remaining_calls) { ikv_test_hooks.ftell_after = remaining_calls; } void ikv_test_fail_fclose_after(int remaining_calls) { ikv_test_hooks.fclose_after = remaining_calls; } #endif void *ikv__malloc(size_t size) { #ifdef IKV_TESTING if (ikv_test_should_fail(&ikv_test_hooks.alloc_after)) return NULL; #endif return malloc(size); } void *ikv__calloc(size_t count, size_t size) { #ifdef IKV_TESTING if (ikv_test_should_fail(&ikv_test_hooks.alloc_after)) return NULL; #endif return calloc(count, size); } void *ikv__realloc(void *ptr, size_t size) { #ifdef IKV_TESTING if (ikv_test_should_fail(&ikv_test_hooks.alloc_after)) return NULL; #endif return realloc(ptr, size); } void ikv__free(void *ptr) { free(ptr); } FILE *ikv__fopen(const char *path, const char *mode) { #ifdef IKV_TESTING if (ikv_test_should_fail(&ikv_test_hooks.fopen_after)) return NULL; #endif return fopen(path, mode); } size_t ikv__fread(void *ptr, size_t size, size_t count, FILE *stream) { #ifdef IKV_TESTING if (ikv_test_should_fail(&ikv_test_hooks.fread_after)) return 0u; #endif return fread(ptr, size, count, stream); } int ikv__fseek(FILE *stream, long offset, int origin) { #ifdef IKV_TESTING if (ikv_test_should_fail(&ikv_test_hooks.fseek_after)) return -1; #endif return fseek(stream, offset, origin); } long ikv__ftell(FILE *stream) { #ifdef IKV_TESTING if (ikv_test_should_fail(&ikv_test_hooks.ftell_after)) return -1L; #endif return ftell(stream); } int ikv__fclose(FILE *stream) { #ifdef IKV_TESTING if (ikv_test_should_fail(&ikv_test_hooks.fclose_after)) return EOF; #endif return fclose(stream); } static const ikv_loader_t *const ikv_loader_registry[] = { &ikv_loader_v1, &ikv_loader_v2, }; static size_t ikv_loader_registry_count(void) { return sizeof(ikv_loader_registry) / sizeof(ikv_loader_registry[0]); } static const ikv_loader_t *ikv_find_loader(ikv_version_t version) { size_t index = 0; for (index = 0; index < ikv_loader_registry_count(); ++index) { if (ikv_loader_registry[index]->version == version) return ikv_loader_registry[index]; } return NULL; } bool ikv__read_file_bytes(const char *path, uint8_t **out_data, size_t *out_size) { FILE *f = NULL; long size_long = 0; size_t size = 0; uint8_t *data = NULL; if (!path || !out_data || !out_size) return false; *out_data = NULL; *out_size = 0; f = IKV_FOPEN(path, "rb"); if (!f) return false; if (IKV_FSEEK(f, 0, SEEK_END) != 0) { IKV_FCLOSE(f); return false; } size_long = IKV_FTELL(f); if (size_long < 0) { IKV_FCLOSE(f); return false; } if (IKV_FSEEK(f, 0, SEEK_SET) != 0) { IKV_FCLOSE(f); return false; } size = (size_t)size_long; data = (uint8_t *)IKV_MALLOC(size + 1u); if (!data) { IKV_FCLOSE(f); return false; } if (size > 0 && IKV_FREAD(data, 1, size, f) != size) { IKV_FREE(data); IKV_FCLOSE(f); return false; } IKV_FCLOSE(f); data[size] = 0; *out_data = data; *out_size = size; return true; } static bool read_file_prefix(const char *path, uint8_t *prefix, size_t prefix_capacity, size_t *out_size) { FILE *file = NULL; size_t size = 0u; if (!path || !prefix || prefix_capacity == 0u || !out_size) return false; *out_size = 0u; prefix[0] = 0u; file = IKV_FOPEN(path, "rb"); if (!file) return false; size = IKV_FREAD(prefix, 1u, prefix_capacity - 1u, file); IKV_FCLOSE(file); prefix[size] = 0u; *out_size = size; return true; } static char *ikv_strdup(const char *s) { if (!s) return NULL; size_t n = strlen(s); char *p = (char *)IKV_MALLOC(n + 1); if (!p) return NULL; memcpy(p, s, n + 1); return p; } static uint32_t fnv1a(const char *s) { uint32_t h = 2166136261u; while (s && *s) { h ^= (uint8_t)*s++; h *= 16777619u; } return h; } static ikv_node_t *alloc_node(const char *key) { ikv_node_t *n = (ikv_node_t *)IKV_CALLOC(1, sizeof(*n)); if (!n) return NULL; if (key) { n->key = ikv_strdup(key); if (!n->key) { IKV_FREE(n); return NULL; } } return n; } static void object_init(ikv_object_t *o, uint32_t buckets) { o->bucket_count = buckets ? buckets : 64; o->size = 0; o->buckets = (ikv_node_t **)IKV_CALLOC(o->bucket_count, sizeof(ikv_node_t *)); } static void array_init(ikv_array_t *a, ikv_type_t element_type) { a->element_type = element_type; a->count = 0; a->items = NULL; } static void node_free_payload(ikv_node_t *n) { if (!n) return; if (n->type == IKV_STRING) { IKV_FREE(n->value.string); } else if (n->type == IKV_OBJECT) { for (uint32_t i = 0; i < n->value.object.bucket_count; i++) { ikv_node_t *c = n->value.object.buckets[i]; while (c) { ikv_node_t *next = c->next; ikv_free(c); c = next; } } IKV_FREE(n->value.object.buckets); } else if (n->type == IKV_ARRAY) { for (uint32_t i = 0; i < n->value.array.count; i++) ikv_free(n->value.array.items[i]); IKV_FREE(n->value.array.items); } if (n->lazy_state) { n->lazy_state->destroy(n->lazy_state); n->lazy_state = NULL; } } void ikv_free(ikv_node_t *n) { if (!n) return; IKV_FREE(n->key); node_free_payload(n); IKV_FREE(n); } static void ikv_replace_node_contents(ikv_node_t *dst, ikv_node_t *src) { if (!dst || !src) return; IKV_FREE(dst->key); node_free_payload(dst); dst->key = src->key; dst->type = src->type; dst->lazy_state = src->lazy_state; dst->value = src->value; dst->next = NULL; src->key = NULL; src->type = IKV_NULL; src->lazy_state = NULL; memset(&src->value, 0, sizeof(src->value)); src->next = NULL; } bool ikv_refresh_from_path(ikv_node_t *root_node, const char *path) { ikv_node_t *updated = NULL; if (!root_node || !path) return false; updated = ikv_parse_file(path); if (!updated) return false; ikv_replace_node_contents(root_node, updated); IKV_FREE(updated); return true; } const char *ikv_node_key(const ikv_node_t *node) { return (node && node->key) ? node->key : ""; } ikv_type_t ikv_node_type(const ikv_node_t *node) { return node ? node->type : IKV_NULL; } uint32_t ikv_object_size(const ikv_node_t *object_node) { if (!object_node || object_node->type != IKV_OBJECT) return 0u; return object_node->value.object.size; } uint32_t ikv_array_size(const ikv_node_t *array_node) { if (!array_node || array_node->type != IKV_ARRAY) return 0u; return array_node->value.array.count; } ikv_type_t ikv_array_element_type(const ikv_node_t *array_node) { if (!array_node || array_node->type != IKV_ARRAY) return IKV_NULL; return array_node->value.array.element_type; } ikv_node_t *ikv_create_object(const char *key) { ikv_node_t *n = alloc_node(key); if (!n) return NULL; n->type = IKV_OBJECT; object_init(&n->value.object, 64); if (!n->value.object.buckets) { ikv_free(n); return NULL; } return n; } ikv_node_t *ikv_create_array(const char *key, ikv_type_t element_type) { ikv_node_t *n = alloc_node(key); if (!n) return NULL; n->type = IKV_ARRAY; array_init(&n->value.array, element_type); return n; } static ikv_node_t *object_find_node(const ikv_object_t *o, const char *key, ikv_node_t **out_prev, uint32_t *out_bucket) { if (out_prev) *out_prev = NULL; if (out_bucket) *out_bucket = 0; if (!o || !o->buckets || !key) return NULL; uint32_t b = fnv1a(key) % o->bucket_count; if (out_bucket) *out_bucket = b; ikv_node_t *prev = NULL; ikv_node_t *cur = o->buckets[b]; while (cur) { if (cur->key && strcmp(cur->key, key) == 0) { if (out_prev) *out_prev = prev; return cur; } prev = cur; cur = cur->next; } if (out_prev) *out_prev = prev; return NULL; } static void object_rehash(ikv_object_t *o, uint32_t new_bucket_count) { ikv_node_t **new_buckets = (ikv_node_t **)IKV_CALLOC(new_bucket_count, sizeof(ikv_node_t *)); if (!new_buckets) return; for (uint32_t i = 0; i < o->bucket_count; i++) { ikv_node_t *c = o->buckets[i]; while (c) { ikv_node_t *next = c->next; uint32_t b = fnv1a(c->key) % new_bucket_count; c->next = new_buckets[b]; new_buckets[b] = c; c = next; } } IKV_FREE(o->buckets); o->buckets = new_buckets; o->bucket_count = new_bucket_count; } static void object_maybe_grow(ikv_object_t *o) { if (!o || o->bucket_count == 0) return; if (o->size * 4 >= o->bucket_count * 3) object_rehash(o, o->bucket_count * 2); } static void object_put_node(ikv_object_t *o, ikv_node_t *n) { if (!o || !n || !n->key) return; object_maybe_grow(o); uint32_t b = fnv1a(n->key) % o->bucket_count; n->next = o->buckets[b]; o->buckets[b] = n; o->size++; } static void object_attach_loaded_node(ikv_object_t *o, ikv_node_t *n) { uint32_t b = 0; if (!o || !n || !n->key) return; object_maybe_grow(o); b = fnv1a(n->key) % o->bucket_count; n->next = o->buckets[b]; o->buckets[b] = n; } static void object_set_node(ikv_object_t *o, ikv_node_t *n) { if (!o || !n || !n->key) { ikv_free(n); return; } uint32_t bucket = 0; ikv_node_t *prev = NULL; ikv_node_t *found = object_find_node(o, n->key, &prev, &bucket); if (found) { if (prev) prev->next = found->next; else o->buckets[bucket] = found->next; o->size--; ikv_free(found); } object_put_node(o, n); } void ikv_object_set_int(ikv_node_t *obj, const char *key, int64_t v) { if (!obj || obj->type != IKV_OBJECT || !key) return; ikv_node_t *n = alloc_node(key); if (!n) return; n->type = IKV_INT; n->value.i = v; object_set_node(&obj->value.object, n); } void ikv_object_set_bool(ikv_node_t *obj, const char *key, bool v) { if (!obj || obj->type != IKV_OBJECT || !key) return; ikv_node_t *n = alloc_node(key); if (!n) return; n->type = IKV_BOOL; n->value.b = v; object_set_node(&obj->value.object, n); } void ikv_object_set_float(ikv_node_t *obj, const char *key, double v) { if (!obj || obj->type != IKV_OBJECT || !key) return; ikv_node_t *n = alloc_node(key); if (!n) return; n->type = IKV_FLOAT; n->value.f = v; object_set_node(&obj->value.object, n); } void ikv_object_set_string(ikv_node_t *obj, const char *key, const char *v) { if (!obj || obj->type != IKV_OBJECT || !key) return; ikv_node_t *n = alloc_node(key); if (!n) return; n->type = IKV_STRING; n->value.string = ikv_strdup(v ? v : ""); if (!n->value.string) { ikv_free(n); return; } object_set_node(&obj->value.object, n); } ikv_node_t *ikv_object_add_object(ikv_node_t *obj, const char *key) { if (!obj || obj->type != IKV_OBJECT || !key) return NULL; ikv_node_t *n = ikv_create_object(key); if (!n) return NULL; object_set_node(&obj->value.object, n); return n; } ikv_node_t *ikv_object_add_array(ikv_node_t *obj, const char *key, ikv_type_t t) { if (!obj || obj->type != IKV_OBJECT || !key) return NULL; ikv_node_t *n = ikv_create_array(key, t); if (!n) return NULL; object_set_node(&obj->value.object, n); return n; } static void array_push_node(ikv_array_t *a, ikv_node_t *n) { ikv_node_t **p = (ikv_node_t **)IKV_REALLOC(a->items, sizeof(ikv_node_t *) * (a->count + 1)); if (!p) { ikv_free(n); return; } a->items = p; a->items[a->count++] = n; } static bool array_type_ok(ikv_array_t *a, ikv_type_t t) { if (!a) return false; if (a->element_type == IKV_NULL) return true; return a->element_type == t; } void ikv_array_add_int(ikv_node_t *arr, int64_t v) { if (!arr || arr->type != IKV_ARRAY) return; if (!array_type_ok(&arr->value.array, IKV_INT)) return; ikv_node_t *n = alloc_node(NULL); if (!n) return; n->type = IKV_INT; n->value.i = v; array_push_node(&arr->value.array, n); } void ikv_array_add_bool(ikv_node_t *arr, bool v) { if (!arr || arr->type != IKV_ARRAY) return; if (!array_type_ok(&arr->value.array, IKV_BOOL)) return; ikv_node_t *n = alloc_node(NULL); if (!n) return; n->type = IKV_BOOL; n->value.b = v; array_push_node(&arr->value.array, n); } void ikv_array_add_float(ikv_node_t *arr, double v) { if (!arr || arr->type != IKV_ARRAY) return; if (!array_type_ok(&arr->value.array, IKV_FLOAT)) return; ikv_node_t *n = alloc_node(NULL); if (!n) return; n->type = IKV_FLOAT; n->value.f = v; array_push_node(&arr->value.array, n); } void ikv_array_add_string(ikv_node_t *arr, const char *v) { if (!arr || arr->type != IKV_ARRAY) return; if (!array_type_ok(&arr->value.array, IKV_STRING)) return; ikv_node_t *n = alloc_node(NULL); if (!n) return; n->type = IKV_STRING; n->value.string = ikv_strdup(v ? v : ""); if (!n->value.string) { ikv_free(n); return; } array_push_node(&arr->value.array, n); } ikv_node_t *ikv_array_add_object(ikv_node_t *arr) { if (!arr || arr->type != IKV_ARRAY) return NULL; if (!array_type_ok(&arr->value.array, IKV_OBJECT)) return NULL; ikv_node_t *n = ikv_create_object(NULL); if (!n) return NULL; array_push_node(&arr->value.array, n); return n; } ikv_node_t *ikv_object_get(const ikv_node_t *obj, const char *key) { ikv_node_t *n = NULL; ikv_node_t *loaded = NULL; ikv_node_t *mutable_obj = (ikv_node_t *)obj; if (!obj || obj->type != IKV_OBJECT || !key) return NULL; if (obj->value.object.bucket_count == 0) return NULL; { uint32_t b = fnv1a(key) % obj->value.object.bucket_count; n = obj->value.object.buckets[b]; } while (n) { if (n->key && strcmp(n->key, key) == 0) return n; n = n->next; } if (!mutable_obj->lazy_state || !mutable_obj->lazy_state->load_object_key) return NULL; loaded = mutable_obj->lazy_state->load_object_key(mutable_obj->lazy_state, mutable_obj, key); if (!loaded) return NULL; if (!loaded->key) { loaded->key = ikv_strdup(key); if (!loaded->key) { ikv_free(loaded); return NULL; } } object_attach_loaded_node(&mutable_obj->value.object, loaded); return loaded; } ikv_node_t *ikv_array_get(const ikv_node_t *arr, uint32_t index) { if (!arr || arr->type != IKV_ARRAY) return NULL; if (index >= arr->value.array.count) return NULL; return arr->value.array.items[index]; } const char *ikv_as_string(const ikv_node_t *n) { return (n && n->type == IKV_STRING && n->value.string) ? n->value.string : ""; } int64_t ikv_as_int(const ikv_node_t *n) { return (n && n->type == IKV_INT) ? n->value.i : 0; } double ikv_as_float(const ikv_node_t *n) { return (n && n->type == IKV_FLOAT) ? n->value.f : 0.0; } bool ikv_as_bool(const ikv_node_t *n) { return (n && n->type == IKV_BOOL) ? n->value.b : false; } static void write_indent(FILE *f, int indent) { for (int i = 0; i < indent; i++) fputc(' ', f); } static void write_escaped_string(FILE *f, const char *s) { fputc('"', f); for (const char *p = s ? s : ""; *p; p++) { unsigned char c = (unsigned char)*p; if (c == '\\') { fputs("\\\\", f); } else if (c == '"') { fputs("\\\"", f); } else if (c == '\n') { fputs("\\n", f); } else if (c == '\r') { fputs("\\r", f); } else if (c == '\t') { fputs("\\t", f); } else { fputc(c, f); } } fputc('"', f); } static void write_node(FILE *f, const ikv_node_t *n, int indent); static void write_value(FILE *f, const ikv_node_t *n, int indent) { switch (n->type) { case IKV_NULL: fputs("null", f); break; case IKV_STRING: write_escaped_string(f, n->value.string ? n->value.string : ""); break; case IKV_INT: fprintf(f, "%lld", (long long)n->value.i); break; case IKV_BOOL: fputs(n->value.b ? "true" : "false", f); break; case IKV_FLOAT: { char buf[64]; snprintf(buf, sizeof(buf), "%.17g", n->value.f); fputs(buf, f); } break; case IKV_OBJECT: { fputs("{\n", f); for (uint32_t i = 0; i < n->value.object.bucket_count; i++) { for (ikv_node_t *c = n->value.object.buckets[i]; c; c = c->next) write_node(f, c, indent + 4); } write_indent(f, indent); fputc('}', f); } break; case IKV_ARRAY: { fputs("[\n", f); for (uint32_t i = 0; i < n->value.array.count; i++) { write_indent(f, indent + 4); write_value(f, n->value.array.items[i], indent + 4); fputc('\n', f); } write_indent(f, indent); fputc(']', f); } break; default: break; } } static void write_node(FILE *f, const ikv_node_t *n, int indent) { write_indent(f, indent); if (n->key) { write_escaped_string(f, n->key); fputc(' ', f); } write_value(f, n, indent); fputc('\n', f); } bool ikv__write_text_file_version(const char *path, const ikv_node_t *root, uint32_t version) { if (!path || !root) return false; FILE *f = IKV_FOPEN(path, "wb"); if (!f) return false; if (root->type != IKV_OBJECT) { write_value(f, root, 0); fputc('\n', f); IKV_FCLOSE(f); return true; } fprintf(f, "ikv%u ", version); write_escaped_string(f, (root->key && root->key[0]) ? root->key : "root"); fputc('\n', f); fputs("{\n", f); for (uint32_t i = 0; i < root->value.object.bucket_count; i++) { for (ikv_node_t *c = root->value.object.buckets[i]; c; c = c->next) write_node(f, c, 4); } fputs("}\n", f); IKV_FCLOSE(f); return true; } typedef enum { T_EOF, T_LBRACE, T_RBRACE, T_LBRACK, T_RBRACK, T_COMMA, T_STRING, T_WORD } tok_type; typedef struct { tok_type type; const char *start; size_t len; } token; typedef struct { const char *p; } lexer; static const char *lex_skip_ws(const char *p) { while (*p) { if (isspace((unsigned char)*p)) { p++; continue; } if (p[0] == '/' && p[1] == '/') { p += 2; while (*p && *p != '\n') p++; continue; } if (p[0] == '#') { p += 1; while (*p && *p != '\n') p++; continue; } break; } return p; } static token lex_next(lexer *l) { token t; t.type = T_EOF; t.start = l->p; t.len = 0; l->p = lex_skip_ws(l->p); const char *p = l->p; if (!*p) { t.type = T_EOF; return t; } if (*p == '{') { l->p = p + 1; t.type = T_LBRACE; return t; } if (*p == '}') { l->p = p + 1; t.type = T_RBRACE; return t; } if (*p == '[') { l->p = p + 1; t.type = T_LBRACK; return t; } if (*p == ']') { l->p = p + 1; t.type = T_RBRACK; return t; } if (*p == ',') { l->p = p + 1; t.type = T_COMMA; return t; } if (*p == '"') { p++; const char *s = p; while (*p) { if (*p == '\\' && p[1]) { p += 2; continue; } if (*p == '"') break; p++; } if (*p != '"') { l->p = p; t.type = T_EOF; return t; } t.type = T_STRING; t.start = s; t.len = (size_t)(p - s); l->p = p + 1; return t; } const char *s = p; while (*p && !isspace((unsigned char)*p) && *p != '{' && *p != '}' && *p != '[' && *p != ']' && *p != ',') p++; t.type = T_WORD; t.start = s; t.len = (size_t)(p - s); l->p = p; return t; } static char *unescape_string(const char *s, size_t len) { char *out = (char *)IKV_MALLOC(len + 1); if (!out) return NULL; size_t w = 0; for (size_t i = 0; i < len; i++) { char c = s[i]; if (c == '\\' && i + 1 < len) { char n = s[i + 1]; if (n == 'n') { out[w++] = '\n'; i++; continue; } if (n == 'r') { out[w++] = '\r'; i++; continue; } if (n == 't') { out[w++] = '\t'; i++; continue; } if (n == '\\') { out[w++] = '\\'; i++; continue; } if (n == '"') { out[w++] = '"'; i++; continue; } } out[w++] = c; } out[w] = 0; return out; } static char *token_to_cstr(const token *t) { char *s = (char *)IKV_MALLOC(t->len + 1); if (!s) return NULL; memcpy(s, t->start, t->len); s[t->len] = 0; return s; } static ikv_node_t *parse_value_lex(lexer *l); static ikv_node_t *parse_array_lex(lexer *l) { ikv_node_t *arr = ikv_create_array(NULL, IKV_NULL); if (!arr) return NULL; for (;;) { const char *save = l->p; token t = lex_next(l); if (t.type == T_RBRACK) break; if (t.type == T_EOF) { ikv_free(arr); return NULL; } if (t.type == T_COMMA) continue; l->p = save; ikv_node_t *v = parse_value_lex(l); if (!v) { ikv_free(arr); return NULL; } if (arr->value.array.element_type == IKV_NULL && v->type != IKV_NULL) arr->value.array.element_type = v->type; if (arr->value.array.element_type != IKV_NULL && v->type != IKV_NULL && v->type != arr->value.array.element_type) arr->value.array.element_type = IKV_NULL; array_push_node(&arr->value.array, v); save = l->p; t = lex_next(l); if (t.type == T_RBRACK) break; if (t.type == T_EOF) { ikv_free(arr); return NULL; } if (t.type != T_COMMA) l->p = save; } return arr; } static ikv_node_t *parse_object_lex(lexer *l) { ikv_node_t *obj = ikv_create_object(NULL); if (!obj) return NULL; for (;;) { token k = lex_next(l); if (k.type == T_RBRACE) break; if (k.type == T_EOF) { ikv_free(obj); return NULL; } if (k.type == T_COMMA) continue; if (k.type != T_STRING) { ikv_free(obj); return NULL; } char *key = unescape_string(k.start, k.len); if (!key) { ikv_free(obj); return NULL; } ikv_node_t *val = parse_value_lex(l); if (!val) { IKV_FREE(key); ikv_free(obj); return NULL; } IKV_FREE(val->key); val->key = key; object_set_node(&obj->value.object, val); const char *save = l->p; token t = lex_next(l); if (t.type == T_RBRACE) break; if (t.type == T_EOF) { ikv_free(obj); return NULL; } if (t.type != T_COMMA) l->p = save; } return obj; } static ikv_node_t *parse_value_lex(lexer *l) { token t = lex_next(l); if (t.type == T_LBRACE) return parse_object_lex(l); if (t.type == T_LBRACK) return parse_array_lex(l); if (t.type == T_STRING) { ikv_node_t *n = alloc_node(NULL); if (!n) return NULL; n->type = IKV_STRING; n->value.string = unescape_string(t.start, t.len); if (!n->value.string) { ikv_free(n); return NULL; } return n; } if (t.type == T_WORD) { char *w = token_to_cstr(&t); if (!w) return NULL; ikv_node_t *n = alloc_node(NULL); if (!n) { IKV_FREE(w); return NULL; } if (strcmp(w, "true") == 0) { n->type = IKV_BOOL; n->value.b = true; IKV_FREE(w); return n; } if (strcmp(w, "false") == 0) { n->type = IKV_BOOL; n->value.b = false; IKV_FREE(w); return n; } if (strcmp(w, "null") == 0) { n->type = IKV_NULL; IKV_FREE(w); return n; } char *endp = NULL; double df = strtod(w, &endp); if (endp && *endp == 0) { if (strchr(w, '.') || strchr(w, 'e') || strchr(w, 'E')) { n->type = IKV_FLOAT; n->value.f = df; IKV_FREE(w); return n; } else { long long di = strtoll(w, &endp, 10); if (endp && *endp == 0) { n->type = IKV_INT; n->value.i = (int64_t)di; IKV_FREE(w); return n; } } } n->type = IKV_STRING; n->value.string = w; return n; } return NULL; } ikv_node_t *ikv__parse_text_string_version(const char *src, uint32_t version) { if (!src) return NULL; lexer l; l.p = src; const char *save0 = l.p; token t0 = lex_next(&l); if (t0.type == T_WORD) { char *w0 = token_to_cstr(&t0); if (!w0) return NULL; char version_tag[16]; snprintf(version_tag, sizeof(version_tag), "ikv%u", version); if (strcmp(w0, version_tag) == 0) { IKV_FREE(w0); token tn = lex_next(&l); if (tn.type != T_STRING && tn.type != T_WORD) return NULL; char *name = (tn.type == T_STRING) ? unescape_string(tn.start, tn.len) : token_to_cstr(&tn); if (!name) return NULL; token tb = lex_next(&l); if (tb.type != T_LBRACE) { IKV_FREE(name); return NULL; } ikv_node_t *obj = parse_object_lex(&l); if (!obj) { IKV_FREE(name); return NULL; } IKV_FREE(obj->key); obj->key = name; return obj; } IKV_FREE(w0); l.p = save0; } else if (t0.type == T_LBRACE) { return parse_object_lex(&l); } else { l.p = save0; } ikv_node_t *root = ikv_create_object(NULL); if (!root) return NULL; for (;;) { const char *s2 = l.p; token k = lex_next(&l); if (k.type == T_EOF) break; if (k.type == T_COMMA) continue; if (k.type != T_STRING) { ikv_free(root); return NULL; } char *key = unescape_string(k.start, k.len); if (!key) { ikv_free(root); return NULL; } ikv_node_t *val = parse_value_lex(&l); if (!val) { IKV_FREE(key); ikv_free(root); return NULL; } IKV_FREE(val->key); val->key = key; object_set_node(&root->value.object, val); s2 = l.p; token sep = lex_next(&l); if (sep.type == T_EOF) break; if (sep.type != T_COMMA) l.p = s2; } return root; } ikv_node_t *ikv__parse_text_file_version(const char *path, uint32_t version) { if (!path) return NULL; uint8_t *bytes = NULL; size_t bytes_n = 0u; char *buf = NULL; ikv_node_t *root = NULL; if (!ikv__read_file_bytes(path, &bytes, &bytes_n) || !bytes) return NULL; buf = (char *)IKV_MALLOC(bytes_n + 1u); if (!buf) { IKV_FREE(bytes); return NULL; } if (bytes_n > 0u) memcpy(buf, bytes, bytes_n); buf[bytes_n] = 0; IKV_FREE(bytes); root = ikv__parse_text_string_version(buf, version); IKV_FREE(buf); return root; } typedef struct { void *ctx; bool ok; void (*write_bytes_fn)(void *ctx, bool *ok, const void *p, size_t n); } bw_t; typedef struct { uint8_t *data; size_t size; size_t cap; } bw_mem_t; static bool bw_mem_reserve(bw_mem_t *m, size_t add) { if (!m) return false; if (add > SIZE_MAX - m->size) return false; size_t need = m->size + add; if (need <= m->cap) return true; size_t new_cap = (m->cap > 0u) ? m->cap : 256u; while (new_cap < need) { if (new_cap > (SIZE_MAX / 2u)) { new_cap = need; break; } new_cap *= 2u; } uint8_t *p = (uint8_t *)IKV_REALLOC(m->data, new_cap); if (!p) return false; m->data = p; m->cap = new_cap; return true; } static void bw_mem_write_bytes(void *ctx, bool *ok, const void *p, size_t n) { bw_mem_t *m = (bw_mem_t *)ctx; if (!m || !ok || !*ok) return; if (n == 0u) return; if (!bw_mem_reserve(m, n)) { *ok = false; return; } memcpy(m->data + m->size, p, n); m->size += n; } static void bw_bytes(bw_t *w, const void *p, size_t n) { if (!w || !w->ok || !w->write_bytes_fn) return; w->write_bytes_fn(w->ctx, &w->ok, p, n); } static void bw_u8(bw_t *w, uint8_t v) { bw_bytes(w, &v, 1u); } static void bw_u32le(bw_t *w, uint32_t v) { uint8_t b[4]; b[0] = (uint8_t)(v & 255u); b[1] = (uint8_t)((v >> 8) & 255u); b[2] = (uint8_t)((v >> 16) & 255u); b[3] = (uint8_t)((v >> 24) & 255u); bw_bytes(w, b, sizeof(b)); } static void bw_u64le(bw_t *w, uint64_t v) { uint8_t b[8]; b[0] = (uint8_t)(v & 255u); b[1] = (uint8_t)((v >> 8) & 255u); b[2] = (uint8_t)((v >> 16) & 255u); b[3] = (uint8_t)((v >> 24) & 255u); b[4] = (uint8_t)((v >> 32) & 255u); b[5] = (uint8_t)((v >> 40) & 255u); b[6] = (uint8_t)((v >> 48) & 255u); b[7] = (uint8_t)((v >> 56) & 255u); bw_bytes(w, b, sizeof(b)); } static void bw_varu64(bw_t *w, uint64_t v) { while (v >= 0x80u) { bw_u8(w, (uint8_t)((v & 0x7Fu) | 0x80u)); v >>= 7; } bw_u8(w, (uint8_t)v); } static void bw_varu32(bw_t *w, uint32_t v) { bw_varu64(w, (uint64_t)v); } static uint64_t bw_zz64(int64_t v) { return (uint64_t)((v << 1) ^ (v >> 63)); } static void bw_vari64(bw_t *w, int64_t v) { bw_varu64(w, bw_zz64(v)); } static void bw_str(bw_t *w, const char *s) { const char *p = s ? s : ""; size_t n = strlen(p); if (n > 0xFFFFFFFFu) n = 0xFFFFFFFFu; bw_varu32(w, (uint32_t)n); bw_bytes(w, p, n); } static uint32_t bw_object_count(const ikv_node_t *obj) { uint32_t c = 0; for (uint32_t i = 0; i < obj->value.object.bucket_count; i++) for (ikv_node_t *n = obj->value.object.buckets[i]; n; n = n->next) c++; return c; } static void bw_val_payload(bw_t *w, const ikv_node_t *n); static void bw_node(bw_t *w, const ikv_node_t *n) { bw_u8(w, (uint8_t)n->type); bw_val_payload(w, n); } static void bw_fixed_payload(bw_t *w, const ikv_node_t *n, ikv_type_t t) { if (!n) { if (t == IKV_STRING) bw_str(w, ""); else if (t == IKV_INT) bw_vari64(w, 0); else if (t == IKV_BOOL) bw_u8(w, 0); else if (t == IKV_FLOAT) { union { double d; uint64_t u; } x; x.d = 0.0; bw_u64le(w, x.u); } return; } if (t == IKV_STRING) { bw_str(w, (n->type == IKV_STRING) ? n->value.string : ""); return; } if (t == IKV_INT) { bw_vari64(w, (n->type == IKV_INT) ? n->value.i : 0); return; } if (t == IKV_BOOL) { bw_u8(w, (uint8_t)((n->type == IKV_BOOL) ? (n->value.b ? 1 : 0) : 0)); return; } if (t == IKV_FLOAT) { union { double d; uint64_t u; } x; x.d = (n->type == IKV_FLOAT) ? n->value.f : 0.0; bw_u64le(w, x.u); return; } bw_node(w, n); } static void bw_val_payload(bw_t *w, const ikv_node_t *n) { switch (n->type) { case IKV_NULL: break; case IKV_BOOL: bw_u8(w, (uint8_t)(n->value.b ? 1 : 0)); break; case IKV_INT: bw_vari64(w, n->value.i); break; case IKV_FLOAT: { union { double d; uint64_t u; } x; x.d = n->value.f; bw_u64le(w, x.u); } break; case IKV_STRING: bw_str(w, n->value.string); break; case IKV_OBJECT: { uint32_t cnt = bw_object_count(n); bw_varu32(w, cnt); for (uint32_t i = 0; i < n->value.object.bucket_count; i++) { for (ikv_node_t *c = n->value.object.buckets[i]; c; c = c->next) { bw_str(w, c->key ? c->key : ""); bw_node(w, c); } } } break; case IKV_ARRAY: { bw_u8(w, (uint8_t)n->value.array.element_type); bw_varu32(w, n->value.array.count); ikv_type_t et = n->value.array.element_type; if (et == IKV_NULL) { for (uint32_t i = 0; i < n->value.array.count; i++) bw_node(w, n->value.array.items[i]); } else { for (uint32_t i = 0; i < n->value.array.count; i++) bw_fixed_payload(w, n->value.array.items[i], et); } } break; default: break; } } bool ikv__write_binary_memory_version(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size, uint32_t version) { if (!root || !out_data || !out_size) return false; *out_data = NULL; *out_size = 0u; bw_mem_t mem = {0}; bw_t w = {0}; w.ctx = &mem; w.ok = true; w.write_bytes_fn = bw_mem_write_bytes; uint8_t magic[4]; magic[0] = (uint8_t)'i'; magic[1] = (uint8_t)'K'; magic[2] = (uint8_t)'v'; magic[3] = (uint8_t)('0' + (uint8_t)version); bw_bytes(&w, magic, sizeof(magic)); bw_u8(&w, (uint8_t)'b'); bw_u32le(&w, version); bw_str(&w, (root->key && root->key[0]) ? root->key : "root"); bw_node(&w, root); if (!w.ok || mem.size == 0u || mem.size > 0xFFFFFFFFu) { IKV_FREE(mem.data); return false; } *out_data = mem.data; *out_size = (uint32_t)mem.size; return true; } bool ikv__write_binary_node_memory(const ikv_node_t *node, uint8_t **out_data, uint32_t *out_size) { bw_mem_t mem = {0}; bw_t w = {0}; if (!node || !out_data || !out_size) return false; *out_data = NULL; *out_size = 0u; w.ctx = &mem; w.ok = true; w.write_bytes_fn = bw_mem_write_bytes; bw_node(&w, node); if (!w.ok || mem.size == 0u || mem.size > 0xFFFFFFFFu) { IKV_FREE(mem.data); return false; } *out_data = mem.data; *out_size = (uint32_t)mem.size; return true; } bool ikv__write_binary_node_append(const ikv_node_t *node, uint8_t **data, size_t *size, size_t *capacity) { bw_mem_t mem = {0}; bw_t w = {0}; size_t start_size = 0u; if (!node || !data || !size || !capacity) return false; mem.data = *data; mem.size = *size; mem.cap = *capacity; start_size = mem.size; w.ctx = &mem; w.ok = true; w.write_bytes_fn = bw_mem_write_bytes; bw_node(&w, node); if (!w.ok || mem.size == start_size || mem.size > 0xFFFFFFFFu) return false; *data = mem.data; *size = mem.size; *capacity = mem.cap; return true; } bool ikv__write_binary_file_version(const char *path, const ikv_node_t *root, uint32_t version) { if (!path || !root) return false; uint8_t *data = NULL; uint32_t size = 0u; if (!ikv__write_binary_memory_version(root, &data, &size, version) || !data || size == 0u) return false; FILE *f = IKV_FOPEN(path, "wb"); if (!f) { IKV_FREE(data); return false; } bool ok = (fwrite(data, 1, size, f) == size); if (IKV_FCLOSE(f) != 0) ok = false; IKV_FREE(data); return ok; } typedef struct { const uint8_t *p; const uint8_t *e; } br_t; static bool br_need(br_t *r, size_t n) { return r && r->p && r->e && (size_t)(r->e - r->p) >= n; } static bool br_u8(br_t *r, uint8_t *out) { if (!br_need(r, 1)) return false; *out = r->p[0]; r->p += 1; return true; } static bool br_u32le(br_t *r, uint32_t *out) { if (!br_need(r, 4)) return false; const uint8_t *p = r->p; *out = (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); r->p += 4; return true; } static bool br_u64le(br_t *r, uint64_t *out) { if (!br_need(r, 8)) return false; const uint8_t *p = r->p; *out = (uint64_t)p[0] | ((uint64_t)p[1] << 8) | ((uint64_t)p[2] << 16) | ((uint64_t)p[3] << 24) | ((uint64_t)p[4] << 32) | ((uint64_t)p[5] << 40) | ((uint64_t)p[6] << 48) | ((uint64_t)p[7] << 56); r->p += 8; return true; } static bool br_varu64(br_t *r, uint64_t *out) { uint64_t v = 0; uint32_t s = 0; for (;;) { uint8_t b = 0; if (!br_u8(r, &b)) return false; v |= (uint64_t)(b & 0x7Fu) << s; if ((b & 0x80u) == 0) break; s += 7; if (s > 63) return false; } *out = v; return true; } static bool br_varu32(br_t *r, uint32_t *out) { uint64_t v = 0; if (!br_varu64(r, &v)) return false; if (v > 0xFFFFFFFFu) return false; *out = (uint32_t)v; return true; } static int64_t br_zzd64(uint64_t v) { return (int64_t)((v >> 1) ^ (uint64_t)-(int64_t)(v & 1u)); } static bool br_vari64(br_t *r, int64_t *out) { uint64_t v = 0; if (!br_varu64(r, &v)) return false; *out = br_zzd64(v); return true; } static char *br_str(br_t *r) { uint32_t n = 0; if (!br_varu32(r, &n)) return NULL; if (!br_need(r, n)) return NULL; char *s = (char *)IKV_MALLOC((size_t)n + 1); if (!s) return NULL; if (n) memcpy(s, r->p, n); s[n] = 0; r->p += n; return s; } static ikv_node_t *br_node(br_t *r); static ikv_node_t *br_fixed_value(br_t *r, ikv_type_t t) { ikv_node_t *n = (ikv_node_t *)IKV_CALLOC(1, sizeof(*n)); if (!n) return NULL; n->type = t; if (t == IKV_STRING) { n->value.string = br_str(r); if (!n->value.string) { ikv_free(n); return NULL; } return n; } if (t == IKV_INT) { int64_t v = 0; if (!br_vari64(r, &v)) { ikv_free(n); return NULL; } n->value.i = v; return n; } if (t == IKV_BOOL) { uint8_t b = 0; if (!br_u8(r, &b)) { ikv_free(n); return NULL; } n->value.b = (b != 0); return n; } if (t == IKV_FLOAT) { uint64_t u = 0; if (!br_u64le(r, &u)) { ikv_free(n); return NULL; } union { uint64_t u; double d; } x; x.u = u; n->value.f = x.d; return n; } ikv_free(n); return br_node(r); } static ikv_node_t *br_node(br_t *r) { uint8_t ty = 0; if (!br_u8(r, &ty)) return NULL; ikv_node_t *n = (ikv_node_t *)IKV_CALLOC(1, sizeof(*n)); if (!n) return NULL; n->type = (ikv_type_t)ty; if (n->type == IKV_NULL) return n; if (n->type == IKV_BOOL) { uint8_t b = 0; if (!br_u8(r, &b)) { ikv_free(n); return NULL; } n->value.b = (b != 0); return n; } if (n->type == IKV_INT) { int64_t v = 0; if (!br_vari64(r, &v)) { ikv_free(n); return NULL; } n->value.i = v; return n; } if (n->type == IKV_FLOAT) { uint64_t u = 0; if (!br_u64le(r, &u)) { ikv_free(n); return NULL; } union { uint64_t u; double d; } x; x.u = u; n->value.f = x.d; return n; } if (n->type == IKV_STRING) { n->value.string = br_str(r); if (!n->value.string) { ikv_free(n); return NULL; } return n; } if (n->type == IKV_OBJECT) { object_init(&n->value.object, 64); if (!n->value.object.buckets) { ikv_free(n); return NULL; } uint32_t cnt = 0; if (!br_varu32(r, &cnt)) { ikv_free(n); return NULL; } for (uint32_t i = 0; i < cnt; i++) { char *k = br_str(r); if (!k) { ikv_free(n); return NULL; } ikv_node_t *v = br_node(r); if (!v) { IKV_FREE(k); ikv_free(n); return NULL; } IKV_FREE(v->key); v->key = k; object_set_node(&n->value.object, v); } return n; } if (n->type == IKV_ARRAY) { uint8_t et = 0; if (!br_u8(r, &et)) { ikv_free(n); return NULL; } n->value.array.element_type = (ikv_type_t)et; uint32_t cnt = 0; if (!br_varu32(r, &cnt)) { ikv_free(n); return NULL; } n->value.array.count = 0; n->value.array.items = NULL; if (cnt) { n->value.array.items = (ikv_node_t **)IKV_MALLOC(sizeof(ikv_node_t *) * (size_t)cnt); if (!n->value.array.items) { ikv_free(n); return NULL; } } if (n->value.array.element_type == IKV_NULL) { for (uint32_t i = 0; i < cnt; i++) { ikv_node_t *it = br_node(r); if (!it) { ikv_free(n); return NULL; } n->value.array.items[n->value.array.count++] = it; } } else { ikv_type_t t = n->value.array.element_type; for (uint32_t i = 0; i < cnt; i++) { ikv_node_t *it = br_fixed_value(r, t); if (!it) { ikv_free(n); return NULL; } n->value.array.items[n->value.array.count++] = it; } } return n; } ikv_free(n); return NULL; } ikv_node_t *ikv__parse_binary_memory_version(const void *data, size_t size, uint32_t version) { if (!data || size < 4 + 1 + 4) return NULL; const uint8_t *p = (const uint8_t *)data; if (p[0] != (uint8_t)'i' || p[1] != (uint8_t)'K' || p[2] != (uint8_t)'v') return NULL; uint8_t file_ver_digit = p[3]; uint8_t want_digit = (uint8_t)('0' + (uint8_t)version); if (file_ver_digit != want_digit) return NULL; p += 4; if (*p++ != (uint8_t)'b') return NULL; br_t r; r.p = p; r.e = (const uint8_t *)data + size; uint32_t ver = 0; if (!br_u32le(&r, &ver)) return NULL; if (ver != version) return NULL; char *root_name = br_str(&r); if (!root_name) return NULL; ikv_node_t *root = br_node(&r); if (!root) { IKV_FREE(root_name); return NULL; } IKV_FREE(root->key); root->key = root_name; return root; } ikv_node_t *ikv__parse_binary_node_memory(const void *data, size_t size, size_t *out_consumed) { br_t r; ikv_node_t *node = NULL; if (!data || size == 0u) return NULL; r.p = (const uint8_t *)data; r.e = r.p + size; node = br_node(&r); if (!node) return NULL; if (out_consumed) *out_consumed = (size_t)(r.p - (const uint8_t *)data); return node; } ikv_node_t *ikv__parse_binary_file_version(const char *path, uint32_t version) { if (!path) return NULL; uint8_t *buf = NULL; size_t size = 0u; ikv_node_t *root = NULL; if (!ikv__read_file_bytes(path, &buf, &size) || !buf) return NULL; root = ikv__parse_binary_memory_version(buf, size, version); IKV_FREE(buf); return root; } void ikv__object_attach_loaded_node(ikv_node_t *object_node, ikv_node_t *child_node) { if (!object_node || object_node->type != IKV_OBJECT) return; object_attach_loaded_node(&object_node->value.object, child_node); } bool ikv__object_reserve_for_count(ikv_node_t *object_node, uint32_t entry_count) { uint32_t bucket_count = 64u; if (!object_node || object_node->type != IKV_OBJECT) return false; while (bucket_count < (entry_count * 4u) / 3u + 1u) { if (bucket_count > 0x7FFFFFFFu) break; bucket_count *= 2u; } IKV_FREE(object_node->value.object.buckets); object_node->value.object.bucket_count = bucket_count; object_node->value.object.size = 0u; object_node->value.object.buckets = (ikv_node_t **)IKV_CALLOC(bucket_count, sizeof(ikv_node_t *)); return object_node->value.object.buckets != NULL; } ikv_version_t ikv_detect_text_version(const char *src) { if (!src) return IKV_VERSION_UNKNOWN; while (*src && isspace((unsigned char)*src)) src++; if (strncmp(src, "ikv1", 4) == 0) return IKV_VERSION_1; if (strncmp(src, "ikv2", 4) == 0) return IKV_VERSION_2; return IKV_VERSION_UNKNOWN; } ikv_version_t ikv_detect_binary_version(const void *data, size_t size) { const uint8_t *p = (const uint8_t *)data; if (!p || size < 9) return IKV_VERSION_UNKNOWN; if (p[0] != (uint8_t)'i' || p[1] != (uint8_t)'K' || p[2] != (uint8_t)'v') return IKV_VERSION_UNKNOWN; if (p[4] != (uint8_t)'b') return IKV_VERSION_UNKNOWN; if (p[3] == (uint8_t)'1') return IKV_VERSION_1; if (p[3] == (uint8_t)'2') return IKV_VERSION_2; return IKV_VERSION_UNKNOWN; } ikv_version_t ikv_detect_file_version(const char *path, bool binary) { uint8_t prefix[16] = {0}; size_t size = 0u; ikv_version_t version = IKV_VERSION_UNKNOWN; if (!read_file_prefix(path, prefix, sizeof(prefix), &size)) return IKV_VERSION_UNKNOWN; version = binary ? ikv_detect_binary_version(prefix, size) : ikv_detect_text_version((const char *)prefix); return version; } bool ikv_write_file(const char *path, const ikv_node_t *root) { return ikv_write_file_version(path, root, (ikv_version_t)IKV_CURRENT_VERSION); } bool ikv_write_file_version(const char *path, const ikv_node_t *root, ikv_version_t version) { const ikv_loader_t *loader = ikv_find_loader(version); if (!loader || !loader->write_text_file) return false; return loader->write_text_file(path, root); } ikv_node_t *ikv_parse_string(const char *src) { ikv_version_t version = ikv_detect_text_version(src); if (version == IKV_VERSION_UNKNOWN) version = IKV_VERSION_1; return ikv_parse_string_version(src, version); } ikv_node_t *ikv_parse_string_version(const char *src, ikv_version_t version) { const ikv_loader_t *loader = ikv_find_loader(version); if (!loader || !loader->parse_text_string) return NULL; return loader->parse_text_string(src); } ikv_node_t *ikv_parse_file(const char *path) { uint8_t prefix[16] = {0}; size_t prefix_size = 0u; ikv_version_t version = IKV_VERSION_UNKNOWN; if (!read_file_prefix(path, prefix, sizeof(prefix), &prefix_size)) return NULL; version = ikv_detect_binary_version(prefix, prefix_size); if (version != IKV_VERSION_UNKNOWN) return ikvb_parse_file_version(path, version); version = ikv_detect_text_version((const char *)prefix); if (version == IKV_VERSION_UNKNOWN) version = IKV_VERSION_1; return ikv_parse_file_version(path, version); } ikv_node_t *ikv_parse_file_version(const char *path, ikv_version_t version) { const ikv_loader_t *loader = ikv_find_loader(version); if (!loader || !loader->parse_text_file) return NULL; return loader->parse_text_file(path); } bool ikvb_write_memory(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size) { return ikvb_write_memory_version(root, out_data, out_size, (ikv_version_t)IKV_CURRENT_VERSION); } bool ikvb_write_memory_version(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size, ikv_version_t version) { const ikv_loader_t *loader = ikv_find_loader(version); if (!loader || !loader->write_binary_memory) return false; return loader->write_binary_memory(root, out_data, out_size); } bool ikvb_write_file(const char *path, const ikv_node_t *root) { return ikvb_write_file_version(path, root, (ikv_version_t)IKV_CURRENT_VERSION); } bool ikvb_write_file_version(const char *path, const ikv_node_t *root, ikv_version_t version) { const ikv_loader_t *loader = ikv_find_loader(version); if (!loader || !loader->write_binary_file) return false; return loader->write_binary_file(path, root); } ikv_node_t *ikvb_parse_memory(const void *data, size_t size) { ikv_version_t version = ikv_detect_binary_version(data, size); return ikvb_parse_memory_version(data, size, version); } ikv_node_t *ikvb_parse_memory_version(const void *data, size_t size, ikv_version_t version) { const ikv_loader_t *loader = ikv_find_loader(version); if (!loader || !loader->parse_binary_memory) return NULL; return loader->parse_binary_memory(data, size); } ikv_node_t *ikvb_parse_file(const char *path) { ikv_version_t version = ikv_detect_file_version(path, true); if (version == IKV_VERSION_UNKNOWN) return NULL; return ikvb_parse_file_version(path, version); } ikv_node_t *ikvb_parse_file_version(const char *path, ikv_version_t version) { const ikv_loader_t *loader = ikv_find_loader(version); if (!loader || !loader->parse_binary_file) return NULL; return loader->parse_binary_file(path); }