feat(hectic C): json api
This commit is contained in:
@@ -74,3 +74,250 @@ char* log_message(LogLevel level, char *file, int line, const char *format, ...)
|
||||
|
||||
return timeStr;
|
||||
}
|
||||
|
||||
// -----------
|
||||
// -- utils --
|
||||
// -----------
|
||||
|
||||
void substr(const char *src, char *dest, size_t start, size_t len) {
|
||||
raise_debug("substring %s from %zu to %zu", src, start, len);
|
||||
size_t srclen = strlen(src);
|
||||
if (start >= srclen) {
|
||||
dest[0] = '\0';
|
||||
return;
|
||||
}
|
||||
if (start + len > srclen)
|
||||
len = srclen - start;
|
||||
strncpy(dest, src + start, len);
|
||||
dest[len] = '\0';
|
||||
}
|
||||
|
||||
// ----------
|
||||
// -- Json --
|
||||
// ----------
|
||||
|
||||
/* Utility: Skip whitespace */
|
||||
static const char *json_skip_whitespace(const char *s) {
|
||||
while (*s && isspace((unsigned char)*s))
|
||||
s++;
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Parse a JSON string (does not handle full escaping) */
|
||||
static char *json_parse_string__(const char **s_ptr, Arena *arena) {
|
||||
const char *s = *s_ptr;
|
||||
if (*s != '"') return NULL;
|
||||
s++; // skip opening quote
|
||||
const char *start = s;
|
||||
while (*s && *s != '"') {
|
||||
if (*s == '\\') s++; // skip escaped char
|
||||
s++;
|
||||
}
|
||||
if (*s != '"') return NULL;
|
||||
size_t len = s - start;
|
||||
char *str = arena_alloc(arena, len + 1);
|
||||
if (!str) return NULL;
|
||||
memcpy(str, start, len);
|
||||
str[len] = '\0';
|
||||
*s_ptr = s + 1; // skip closing quote
|
||||
return str;
|
||||
}
|
||||
|
||||
/* Parse a number using strtod */
|
||||
static double json_parse_number__(const char **s_ptr) {
|
||||
char *end;
|
||||
double num = strtod(*s_ptr, &end);
|
||||
*s_ptr = end;
|
||||
return num;
|
||||
}
|
||||
|
||||
/* Forward declaration */
|
||||
static Json *json_parse_value__(const char **s, Arena *arena);
|
||||
|
||||
/* Parse a JSON array: [ value, value, ... ] */
|
||||
static Json *json_parse_array__(const char **s, Arena *arena) {
|
||||
if (**s != '[') return NULL;
|
||||
(*s)++; // skip '['
|
||||
*s = json_skip_whitespace(*s);
|
||||
Json *array = arena_alloc(arena, sizeof(Json));
|
||||
if (!array) return NULL;
|
||||
memset(array, 0, sizeof(Json));
|
||||
array->type = JSON_ARRAY;
|
||||
Json *last = NULL;
|
||||
if (**s == ']') { // empty array
|
||||
(*s)++;
|
||||
return array;
|
||||
}
|
||||
while (**s) {
|
||||
Json *element = json_parse_value__(s, arena);
|
||||
if (!element) return NULL;
|
||||
if (!array->child)
|
||||
array->child = element;
|
||||
else {
|
||||
last->next = element;
|
||||
}
|
||||
last = element;
|
||||
*s = json_skip_whitespace(*s);
|
||||
if (**s == ',') {
|
||||
(*s)++;
|
||||
*s = json_skip_whitespace(*s);
|
||||
} else if (**s == ']') {
|
||||
(*s)++;
|
||||
break;
|
||||
} else {
|
||||
return NULL; // error
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/* Parse a JSON object: { "key": value, ... } */
|
||||
static Json *json_parse_object__(const char **s, Arena *arena) {
|
||||
if (**s != '{') return NULL;
|
||||
(*s)++; // skip '{'
|
||||
*s = json_skip_whitespace(*s);
|
||||
Json *object = arena_alloc(arena, sizeof(Json));
|
||||
if (!object) return NULL;
|
||||
memset(object, 0, sizeof(Json));
|
||||
object->type = JSON_OBJECT;
|
||||
Json *last = NULL;
|
||||
if (**s == '}') {
|
||||
(*s)++;
|
||||
return object;
|
||||
}
|
||||
while (**s) {
|
||||
char *key = json_parse_string__(s, arena);
|
||||
if (!key) return NULL;
|
||||
*s = json_skip_whitespace(*s);
|
||||
if (**s != ':') return NULL;
|
||||
(*s)++; // skip ':'
|
||||
*s = json_skip_whitespace(*s);
|
||||
Json *value = json_parse_value__(s, arena);
|
||||
if (!value) return NULL;
|
||||
value->key = key; // assign key to the value
|
||||
if (!object->child)
|
||||
object->child = value;
|
||||
else {
|
||||
last->next = value;
|
||||
}
|
||||
last = value;
|
||||
*s = json_skip_whitespace(*s);
|
||||
if (**s == ',') {
|
||||
(*s)++;
|
||||
*s = json_skip_whitespace(*s);
|
||||
} else if (**s == '}') {
|
||||
(*s)++;
|
||||
break;
|
||||
} else {
|
||||
return NULL; // error
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
/* Full JSON value parser */
|
||||
static Json *json_parse_value__(const char **s, Arena *arena) {
|
||||
*s = json_skip_whitespace(*s);
|
||||
if (**s == '"') {
|
||||
Json *item = arena_alloc(arena, sizeof(Json));
|
||||
if (!item) return NULL;
|
||||
memset(item, 0, sizeof(Json));
|
||||
item->type = JSON_STRING;
|
||||
item->string = json_parse_string__(s, arena);
|
||||
return item;
|
||||
} else if (strncmp(*s, "null", 4) == 0) {
|
||||
Json *item = arena_alloc(arena, sizeof(Json));
|
||||
if (!item) return NULL;
|
||||
memset(item, 0, sizeof(Json));
|
||||
item->type = JSON_NULL;
|
||||
*s += 4;
|
||||
return item;
|
||||
} else if (strncmp(*s, "true", 4) == 0) {
|
||||
Json *item = arena_alloc(arena, sizeof(Json));
|
||||
if (!item) return NULL;
|
||||
memset(item, 0, sizeof(Json));
|
||||
item->type = JSON_BOOL;
|
||||
item->boolean = 1;
|
||||
*s += 4;
|
||||
return item;
|
||||
} else if (strncmp(*s, "false", 5) == 0) {
|
||||
Json *item = arena_alloc(arena, sizeof(Json));
|
||||
if (!item) return NULL;
|
||||
memset(item, 0, sizeof(Json));
|
||||
item->type = JSON_BOOL;
|
||||
item->boolean = 0;
|
||||
*s += 5;
|
||||
return item;
|
||||
} else if ((**s == '-') || isdigit((unsigned char)**s)) {
|
||||
Json *item = arena_alloc(arena, sizeof(Json));
|
||||
if (!item) return NULL;
|
||||
memset(item, 0, sizeof(Json));
|
||||
item->type = JSON_NUMBER;
|
||||
item->number = json_parse_number__(s);
|
||||
return item;
|
||||
} else if (**s == '[') {
|
||||
return json_parse_array__(s, arena);
|
||||
} else if (**s == '{') {
|
||||
return json_parse_object__(s, arena);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Json *json_parse(Arena *arena, const char **s) {
|
||||
return json_parse_value__(s, arena);
|
||||
}
|
||||
|
||||
/* Minimal JSON printer.
|
||||
For simplicity, a fixed-size buffer is used.
|
||||
In production you’d dynamically size or use the arena. */
|
||||
char *json_print(Arena *arena, Json *item) {
|
||||
char *out = arena_alloc(arena, 1024);
|
||||
if (!out) return NULL;
|
||||
char *ptr = out;
|
||||
if (item->type == JSON_OBJECT) {
|
||||
ptr += sprintf(ptr, "{");
|
||||
Json *child = item->child;
|
||||
while (child) {
|
||||
ptr += sprintf(ptr, "\"%s\":", child->key ? child->key : "");
|
||||
char *child_str = json_print(arena, child);
|
||||
ptr += sprintf(ptr, "%s", child_str);
|
||||
if (child->next)
|
||||
ptr += sprintf(ptr, ",");
|
||||
child = child->next;
|
||||
}
|
||||
sprintf(ptr, "}");
|
||||
} else if (item->type == JSON_ARRAY) {
|
||||
ptr += sprintf(ptr, "[");
|
||||
Json *child = item->child;
|
||||
while (child) {
|
||||
char *child_str = json_print(arena, child);
|
||||
ptr += sprintf(ptr, "%s", child_str);
|
||||
if (child->next)
|
||||
ptr += sprintf(ptr, ",");
|
||||
child = child->next;
|
||||
}
|
||||
sprintf(ptr, "]");
|
||||
} else if (item->type == JSON_STRING) {
|
||||
sprintf(out, "\"%s\"", item->string);
|
||||
} else if (item->type == JSON_NUMBER) {
|
||||
sprintf(out, "%g", item->number);
|
||||
} else if (item->type == JSON_BOOL) {
|
||||
sprintf(out, item->boolean ? "true" : "false");
|
||||
} else if (item->type == JSON_NULL) {
|
||||
sprintf(out, "null");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/* Retrieve an object item by key (case-sensitive) */
|
||||
Json *json_get_object_item(Json *object, const char *key) {
|
||||
if (!object || object->type != JSON_OBJECT)
|
||||
return NULL;
|
||||
Json *child = object->child;
|
||||
while (child) {
|
||||
if (child->key && strcmp(child->key, key) == 0)
|
||||
return child;
|
||||
child = child->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user