Files
armorpaint/base/sources/iron_json.c
T
2026-08-21 22:35:30 +02:00

709 lines
15 KiB
C

#include "iron_json.h"
#include "iron_armpack.h"
#include "iron_string.h"
#include <jsmn.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#ifdef IRON_WASM
#define PTR_SIZE 4
#else
#define PTR_SIZE 8
#endif
static char *source;
static jsmntok_t *tokens;
static int num_tokens;
static uint32_t ti; // token index
static uint8_t *decoded;
static uint32_t wi; // write index
static uint32_t bottom;
static uint32_t array_count;
static inline uint64_t pad(uint32_t di, int n) {
return (n - (di % n)) % n;
}
static void store_u8(uint8_t u8) {
*(uint8_t *)(decoded + wi) = u8;
wi += 1;
}
static void store_i32(int32_t i32) {
// TODO: signed overflow is UB
// if (i32 > INT32_MAX)
// i32 = (int32_t)(i32 - INT32_MAX - 1) - INT32_MAX - 1;
wi += pad(wi, 4);
*(int32_t *)(decoded + wi) = i32;
wi += 4;
}
static void store_u32(uint32_t u32) {
wi += pad(wi, 4);
*(uint32_t *)(decoded + wi) = u32;
wi += 4;
}
static void store_f32(float f32) {
wi += pad(wi, 4);
*(float *)(decoded + wi) = f32;
wi += 4;
}
static void store_ptr(uint32_t ptr) {
wi += pad(wi, PTR_SIZE);
*(uintptr_t *)(decoded + wi) = (uintptr_t)decoded + (uintptr_t)ptr;
wi += PTR_SIZE;
}
static void store_ptr_abs(void *ptr) {
wi += pad(wi, PTR_SIZE);
*(uintptr_t *)(decoded + wi) = (uintptr_t)ptr;
wi += PTR_SIZE;
}
static uint32_t json_string_len(char *str, uint32_t len) {
uint32_t out = 0;
for (uint32_t i = 0; i < len; ++i) {
// Escaped \" collapses to one byte
if (str[i] == '\\' && i + 1 < len && str[i + 1] == '"') {
i++;
}
out++;
}
return out;
}
static void store_string_bytes(char *str, uint32_t len) {
for (uint32_t i = 0; i < len; ++i) {
if (str[i] == '\\' && i + 1 < len && str[i + 1] == '"') {
store_u8('"');
i++;
continue;
}
store_u8(str[i]);
}
store_u8('\0');
}
static bool is_key(char *s, jsmntok_t *t) {
return t->type == JSMN_STRING && s[t->end + 1] == ':';
}
static jsmntok_t get_token() {
jsmntok_t t = tokens[ti];
while (is_key(source, &t)) {
ti++;
t = tokens[ti];
}
return t;
}
static int traverse(uint32_t wi) {
jsmntok_t t = get_token();
if (t.type == JSMN_OBJECT) {
ti++;
uint32_t size = 0;
for (uint32_t i = 0; i < t.size; ++i) {
size += traverse(wi + size);
}
return pad(wi, PTR_SIZE) + size;
}
else if (t.type == JSMN_PRIMITIVE) {
ti++;
if (source[t.start] == 't' || source[t.start] == 'f') { // bool
return 1;
}
else if (source[t.start] == 'n') { // null
return pad(wi, PTR_SIZE) + PTR_SIZE;
}
else { // number
return pad(wi, 4) + 4;
}
}
else if (t.type == JSMN_ARRAY) {
ti++;
for (uint32_t i = 0; i < t.size; ++i) {
traverse(0);
}
return pad(wi, PTR_SIZE) + PTR_SIZE;
}
else if (t.type == JSMN_STRING) {
ti++;
return pad(wi, PTR_SIZE) + PTR_SIZE;
}
return 0;
}
static int token_size() {
uint32_t _ti = ti;
uint32_t len = traverse(0);
ti = _ti;
return len;
}
static bool has_dot(char *str, uint32_t len) {
for (uint32_t i = 0; i < len; ++i) {
if (str[i] == '.') {
return true;
}
}
return false;
}
static void token_write() {
jsmntok_t t = get_token();
if (t.type == JSMN_OBJECT) {
// TODO: Object containing another object
// Write object contents
uint32_t size = token_size();
size += pad(size, PTR_SIZE);
bottom += size * array_count;
ti++;
for (uint32_t i = 0; i < t.size; ++i) {
token_write();
}
}
else if (t.type == JSMN_PRIMITIVE) {
ti++;
if (source[t.start] == 't' || source[t.start] == 'f') { // bool
store_u8(source[t.start] == 't' ? 1 : 0);
}
else if (source[t.start] == 'n') { // null
store_ptr_abs(NULL);
}
else {
has_dot(source + t.start, t.end - t.start) ? store_f32(strtof(source + t.start, NULL)) :
#ifdef _WIN32
store_i32(_strtoi64(source + t.start, NULL, 10));
#else
store_i32(strtol(source + t.start, NULL, 10));
#endif
}
}
else if (t.type == JSMN_ARRAY) {
ti++;
store_ptr(bottom);
uint32_t _wi = wi;
wi = bottom;
store_ptr(bottom + PTR_SIZE + 4 + 4); // Pointer to buffer contents
store_u32(t.size); // Element count
store_u32(0); // Capacity = 0 -> do not free on first realloc
bottom = wi;
if (t.size == 0) {
wi = _wi;
return;
}
uint32_t count = t.size;
array_count = count;
t = get_token();
if (t.type == JSMN_OBJECT) {
// Struct pointers
uint32_t size = token_size();
size += pad(size, PTR_SIZE);
for (uint32_t i = 0; i < count; ++i) {
store_ptr(bottom + count * PTR_SIZE + i * size);
}
// Struct contents
bottom = pad(wi, PTR_SIZE) + wi;
for (uint32_t i = 0; i < count; ++i) {
wi = pad(wi, PTR_SIZE) + wi;
token_write();
}
}
else if (t.type == JSMN_STRING) {
// String pointers
uint32_t _ti = ti;
uint32_t strings_length = 0;
for (uint32_t i = 0; i < count; ++i) {
store_ptr(bottom + count * PTR_SIZE + strings_length);
uint32_t length = json_string_len(source + t.start, t.end - t.start);
strings_length += length;
strings_length += 1; // '\0'
ti++;
t = get_token();
}
ti = _ti;
t = get_token();
// String bytes
for (uint32_t i = 0; i < count; ++i) {
store_string_bytes(source + t.start, t.end - t.start);
ti++;
t = get_token();
}
bottom = pad(wi, PTR_SIZE) + wi;
}
else {
// Array contents
for (uint32_t i = 0; i < count; ++i) {
token_write();
}
bottom = pad(wi, PTR_SIZE) + wi;
}
wi = _wi;
array_count = 1;
}
else if (t.type == JSMN_STRING) {
ti++;
store_ptr(bottom);
uint32_t _wi = wi;
wi = bottom;
store_string_bytes(source + t.start, t.end - t.start);
bottom = pad(wi, PTR_SIZE) + wi;
wi = _wi;
}
}
static void load_tokens(char *s) {
jsmn_parser parser;
jsmn_init(&parser);
num_tokens = jsmn_parse(&parser, s, strlen(s), NULL, 0);
tokens = malloc(sizeof(jsmntok_t) * num_tokens);
jsmn_init(&parser);
jsmn_parse(&parser, s, strlen(s), tokens, num_tokens);
source = s;
ti = 0;
}
void *json_parse(char *s) {
load_tokens(s);
uint32_t out_size = strlen(s) * 2;
decoded = calloc(1, out_size);
wi = 0;
bottom = 0;
array_count = 1;
token_write();
free(tokens);
return decoded;
}
static void token_write_to_map(any_map_t *m) {
jsmntok_t t = get_token();
if (t.type == JSMN_OBJECT) {
// TODO: Object containing another object
ti++;
for (uint32_t i = 0; i < t.size; ++i) {
token_write_to_map(m);
}
}
else if (t.type == JSMN_PRIMITIVE) {
jsmntok_t tkey = tokens[ti - 1];
ti++;
any_map_set(m, substring(source, tkey.start, tkey.end), substring(source, t.start, t.end));
}
else if (t.type == JSMN_ARRAY) {
ti++;
}
else if (t.type == JSMN_STRING) {
jsmntok_t tkey = tokens[ti - 1];
ti++;
any_map_set(m, substring(source, tkey.start, tkey.end), substring(source, t.start, t.end));
}
}
any_map_t *json_parse_to_map(char *s) {
load_tokens(s);
any_map_t *m = any_map_create();
token_write_to_map(m);
free(tokens);
return m;
}
static buffer_t encoded;
static int keys;
static int array_nest = -1;
static int array_length[16];
static void enc(char *s) {
string_buffer_append(&encoded, s);
}
void json_encode_begin() {
if (encoded.buffer == NULL) {
string_buffer_init(&encoded);
}
string_buffer_reset(&encoded);
enc("{");
keys = 0;
}
char *json_encode_end() {
enc("}");
return string_copy(string_buffer_get(&encoded));
}
void json_encode_key(char *k) {
if (keys > 0) {
enc(",");
}
enc("\"");
enc(k);
enc("\":");
keys++;
}
void json_encode_null(char *k) {
json_encode_key(k);
enc("null");
}
void json_encode_string_value(char *v) {
enc("\"");
enc(v);
enc("\"");
}
void json_encode_string(char *k, char *v) {
json_encode_key(k);
json_encode_string_value(v);
}
void json_encode_string_array(char *k, string_array_t *a) {
if (a == NULL) {
json_encode_null(k);
return;
}
json_encode_begin_array(k);
for (uint32_t i = 0; i < a->length; ++i) {
if (i > 0) {
enc(",");
}
json_encode_string_value(a->buffer[i]);
}
json_encode_end_array();
}
void json_encode_f32(char *k, float f) {
json_encode_key(k);
enc(f32_to_string_with_zeros(f));
}
void json_encode_i32(char *k, int i) {
json_encode_key(k);
enc(i32_to_string(i));
}
void json_encode_f32_array(char *k, f32_array_t *a) {
if (a == NULL) {
json_encode_null(k);
return;
}
json_encode_begin_array(k);
for (uint32_t i = 0; i < a->length; ++i) {
if (i > 0) {
enc(",");
}
enc(f32_to_string_with_zeros(a->buffer[i]));
}
json_encode_end_array();
}
void json_encode_i32_array(char *k, i32_array_t *a) {
if (a == NULL) {
json_encode_null(k);
return;
}
json_encode_begin_array(k);
for (uint32_t i = 0; i < a->length; ++i) {
if (i > 0) {
enc(",");
}
enc(i32_to_string(a->buffer[i]));
}
json_encode_end_array();
}
void json_encode_bool(char *k, bool b) {
json_encode_key(k);
enc(b ? "true" : "false");
}
void json_encode_begin_array(char *k) {
array_nest++;
array_length[array_nest] = 0;
json_encode_key(k);
enc("[");
}
void json_encode_end_array() {
array_nest--;
enc("]");
}
void json_encode_begin_object() {
if (array_nest > -1) {
if (array_length[array_nest] > 0) {
enc(",");
}
array_length[array_nest]++;
}
keys = 0;
enc("{");
}
void json_encode_end_object() {
enc("}");
}
void json_encode_map(any_map_t *m) {
any_array_t *keys = map_keys(m);
for (uint32_t i = 0; i < keys->length; i++) {
json_encode_string(keys->buffer[i], any_map_get(m, keys->buffer[i]));
}
array_free(keys);
free(keys);
}
static char *jenc_src;
static jsmntok_t *jenc_tokens;
static int jenc_ti;
static uint8_t jenc_forced_array_type;
static bool jenc_has_dot(int start, int end) {
for (int i = start; i < end; i++) {
if (jenc_src[i] == '.')
return true;
}
return false;
}
static int jenc_skip(int ti) {
jsmntok_t t = jenc_tokens[ti++];
if (t.type == JSMN_OBJECT) {
for (int i = 0; i < t.size; i++) {
ti = jenc_skip(ti); // key
ti = jenc_skip(ti); // value
}
}
else if (t.type == JSMN_ARRAY) {
for (int i = 0; i < t.size; i++) {
ti = jenc_skip(ti);
}
}
return ti;
}
static uint8_t jenc_elem_type(int ti) {
jsmntok_t t = jenc_tokens[ti];
if (t.type == JSMN_STRING)
return 0xdb;
if (t.type == JSMN_OBJECT)
return 0xdf;
if (t.type == JSMN_ARRAY)
return 0xdd;
char c = jenc_src[t.start];
if (c == 't' || c == 'f')
return 0xc2;
if (c == 'n')
return 0xc0;
if (jenc_has_dot(t.start, t.end))
return 0xca;
return 0xd2;
}
static uint8_t jenc_array_type(int count) {
if (count == 0)
return 0x0;
int ti = jenc_ti;
uint8_t first_type = jenc_elem_type(ti);
if (first_type != 0xca && first_type != 0xd2 && first_type != 0xdb) {
return 0x0; // non-scalar types -> dynamic
}
for (int i = 0; i < count; i++) {
if (jenc_elem_type(ti) != first_type)
return 0x0;
ti = jenc_skip(ti);
}
return first_type;
}
static void jenc_write_string(int start, int len) {
armpack_write_u8(0xdb);
armpack_write_u32(len);
for (int i = 0; i < len; i++) {
armpack_write_u8(jenc_src[start + i]);
}
}
static void jenc_value();
static void jenc_object(int count) {
armpack_write_u8(0xdf);
armpack_write_i32(count);
for (int i = 0; i < count; i++) {
jsmntok_t key = jenc_tokens[jenc_ti++];
int key_len = key.end - key.start;
// Check for [type] suffix in key name
uint8_t forced_type = 0;
int actual_len = key_len;
if (key_len >= 4 && jenc_src[key.end - 1] == ']') {
const char *ks = jenc_src + key.start;
if (key_len >= 5 && strncmp(ks + key_len - 5, "[f32]", 5) == 0) {
forced_type = 0xca;
actual_len = key_len - 5;
}
else if (key_len >= 5 && strncmp(ks + key_len - 5, "[i32]", 5) == 0) {
forced_type = 0xd2;
actual_len = key_len - 5;
}
else if (key_len >= 5 && strncmp(ks + key_len - 5, "[i16]", 5) == 0) {
forced_type = 0xd1;
actual_len = key_len - 5;
}
else if (key_len >= 4 && strncmp(ks + key_len - 4, "[u8]", 4) == 0) {
forced_type = 0xc4;
actual_len = key_len - 4;
}
}
jenc_write_string(key.start, actual_len);
jenc_forced_array_type = forced_type;
jenc_value();
jenc_forced_array_type = 0;
}
}
static void jenc_array(int count) {
armpack_write_u8(0xdd);
armpack_write_u32(count);
if (count == 0)
return;
uint8_t forced = jenc_forced_array_type;
jenc_forced_array_type = 0;
uint8_t elem_type = forced != 0 ? forced : jenc_array_type(count);
if (elem_type == 0xca) { // typed f32
armpack_write_u8(0xca);
for (int i = 0; i < count; i++) {
jsmntok_t t = jenc_tokens[jenc_ti++];
armpack_write_f32(strtof(jenc_src + t.start, NULL));
}
}
else if (elem_type == 0xd2) { // typed i32
armpack_write_u8(0xd2);
for (int i = 0; i < count; i++) {
jsmntok_t t = jenc_tokens[jenc_ti++];
#ifdef _WIN32
armpack_write_i32((int32_t)_strtoi64(jenc_src + t.start, NULL, 10));
#else
armpack_write_i32((int32_t)strtol(jenc_src + t.start, NULL, 10));
#endif
}
}
else if (elem_type == 0xd1) { // typed i16
armpack_write_u8(0xd1);
for (int i = 0; i < count; i++) {
jsmntok_t t = jenc_tokens[jenc_ti++];
#ifdef _WIN32
armpack_write_i16((int16_t)_strtoi64(jenc_src + t.start, NULL, 10));
#else
armpack_write_i16((int16_t)strtol(jenc_src + t.start, NULL, 10));
#endif
}
}
else if (elem_type == 0xc4) { // typed u8
armpack_write_u8(0xc4);
for (int i = 0; i < count; i++) {
jsmntok_t t = jenc_tokens[jenc_ti++];
#ifdef _WIN32
armpack_write_u8((uint8_t)_strtoi64(jenc_src + t.start, NULL, 10));
#else
armpack_write_u8((uint8_t)strtol(jenc_src + t.start, NULL, 10));
#endif
}
}
else { // dynamic
for (int i = 0; i < count; i++) {
jenc_value();
}
}
}
static void jenc_value() {
jsmntok_t t = jenc_tokens[jenc_ti++];
if (t.type == JSMN_OBJECT) {
jenc_object(t.size);
return;
}
if (t.type == JSMN_ARRAY) {
jenc_array(t.size);
return;
}
if (t.type == JSMN_STRING) {
jenc_write_string(t.start, t.end - t.start);
return;
}
// JSMN_PRIMITIVE
char c = jenc_src[t.start];
if (c == 't') {
armpack_write_u8(0xc3); // true
}
else if (c == 'f') {
armpack_write_u8(0xc2); // false
}
else if (c == 'n') {
armpack_write_u8(0xc0); // null
}
else if (jenc_has_dot(t.start, t.end)) {
armpack_write_u8(0xca);
armpack_write_f32(strtof(jenc_src + t.start, NULL));
}
else {
armpack_write_u8(0xd2);
#ifdef _WIN32
armpack_write_i32((int32_t)_strtoi64(jenc_src + t.start, NULL, 10));
#else
armpack_write_i32((int32_t)strtol(jenc_src + t.start, NULL, 10));
#endif
}
}
buffer_t *json_encode_to_armpack(char *json) {
jsmn_parser parser;
jsmn_init(&parser);
int n = jsmn_parse(&parser, json, strlen(json), NULL, 0);
if (n <= 0)
return NULL;
jenc_tokens = malloc(sizeof(jsmntok_t) * n);
jsmn_init(&parser);
jsmn_parse(&parser, json, strlen(json), jenc_tokens, n);
jenc_src = json;
jenc_ti = 0;
int max_size = strlen(json) * 3 + 64;
uint8_t *buf = malloc(max_size);
armpack_encode_start(buf);
jenc_value();
int size = armpack_encode_end();
free(jenc_tokens);
buffer_t *b = buffer_create(size);
memcpy(b->buffer, buf, size);
free(buf);
return b;
}