2437 lines
53 KiB
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);
|
|
}
|