Files
iKv/src/ikv.c

2437 lines
53 KiB
C

#include "internal/ikv_internal.h"
#include "loaders/ikv1.h"
#include "loaders/ikv2.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#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);
}