#ifndef TINYGLTF_JSON_C_H_ #define TINYGLTF_JSON_C_H_ /* * Floating-point conversion attribution: * * - JSON number parsing in this C11 port is based on fast_float. * Upstream: https://github.com/fastfloat/fast_float * Copyright (c) 2021 The fast_float authors. * License: Apache License 2.0, MIT License, or Boost Software License 1.0. * * - JSON number serialization in this C11 port is based on Dragonbox-style * shortest round-trippable binary floating-point to decimal conversion. * Upstream: https://github.com/jk-jeon/dragonbox * Copyright 2020-2024 Junekey Jeon. * License: Apache License 2.0 with LLVM Exceptions, or Boost Software * License 1.0. */ #include #include #ifdef __cplusplus extern "C" { #endif typedef enum tg3json_type { TG3JSON_NULL = 0, TG3JSON_BOOL = 1, TG3JSON_INT = 2, TG3JSON_REAL = 3, TG3JSON_STRING = 4, TG3JSON_ARRAY = 5, TG3JSON_OBJECT = 6 } tg3json_type; typedef struct tg3json_value tg3json_value; typedef struct tg3json_string { char *ptr; size_t len; } tg3json_string; typedef struct tg3json_array { tg3json_value *items; size_t count; } tg3json_array; typedef struct tg3json_object_entry { char *key; size_t key_len; tg3json_value *value; } tg3json_object_entry; typedef struct tg3json_object { tg3json_object_entry *items; size_t count; } tg3json_object; struct tg3json_value { tg3json_type type; union { int boolean; int64_t integer; double real; tg3json_string string; tg3json_array array; tg3json_object object; } u; }; int tg3json_parse_n(const char *data, size_t len, size_t depth_limit, tg3json_value *out_value, const char **out_error_pos); int tg3json_parse(const char *begin, const char *end, size_t depth_limit, tg3json_value *out_value, const char **out_error_pos); typedef struct tg3json_parse_options { size_t depth_limit; /* 0 = default */ size_t memory_budget; /* 0 = unlimited */ size_t max_single_alloc; /* 0 = unlimited */ size_t max_string_length; /* 0 = unlimited */ int parse_float32; /* 1 = round JSON reals to float */ } tg3json_parse_options; int tg3json_parse_n_opts(const char *data, size_t len, const tg3json_parse_options *options, tg3json_value *out_value, const char **out_error_pos); void tg3json_value_free(tg3json_value *value); void tg3json_value_init_null(tg3json_value *value); void tg3json_value_init_bool(tg3json_value *value, int boolean_value); void tg3json_value_init_int(tg3json_value *value, int64_t integer_value); void tg3json_value_init_real(tg3json_value *value, double real_value); int tg3json_value_init_string_n(tg3json_value *value, const char *str, size_t len); int tg3json_value_init_string(tg3json_value *value, const char *str); void tg3json_value_init_array(tg3json_value *value); void tg3json_value_init_object(tg3json_value *value); int tg3json_value_copy(tg3json_value *dst, const tg3json_value *src); const tg3json_value *tg3json_object_get(const tg3json_value *object, const char *key); const tg3json_value *tg3json_object_get_n(const tg3json_value *object, const char *key, size_t key_len); tg3json_value *tg3json_object_get_mut(tg3json_value *object, const char *key); tg3json_value *tg3json_object_get_mut_n(tg3json_value *object, const char *key, size_t key_len); const tg3json_object_entry *tg3json_object_at(const tg3json_value *object, size_t index); size_t tg3json_object_size(const tg3json_value *object); int tg3json_object_set_take_n(tg3json_value *object, const char *key, size_t key_len, tg3json_value *value); int tg3json_object_set_take(tg3json_value *object, const char *key, tg3json_value *value); int tg3json_object_set_copy_n(tg3json_value *object, const char *key, size_t key_len, const tg3json_value *value); int tg3json_object_set_copy(tg3json_value *object, const char *key, const tg3json_value *value); const tg3json_value *tg3json_array_get(const tg3json_value *array, size_t index); size_t tg3json_array_size(const tg3json_value *array); int tg3json_array_append_take(tg3json_value *array, tg3json_value *value); int tg3json_array_append_copy(tg3json_value *array, const tg3json_value *value); char *tg3json_stringify(const tg3json_value *value, size_t *out_len); char *tg3json_stringify_pretty(const tg3json_value *value, int indent, size_t *out_len); #ifdef __cplusplus } /* extern "C" */ #endif #ifdef TINYGLTF_JSON_C_IMPLEMENTATION #ifndef TINYGLTF_JSON_NO_STDLIB #include #include #include #include #if defined(_MSC_VER) || (defined(LDBL_MANT_DIG) && LDBL_MANT_DIG <= 53) #define TG3JSON_USE_STDLIB_FPCONV 1 #endif #endif #ifndef TG3JSON_USE_STDLIB_FPCONV #define TG3JSON_USE_STDLIB_FPCONV 0 #endif #ifdef TINYGLTF_JSON_NO_STDLIB static void *tg3json__memcpy_fallback(void *dst, const void *src, size_t sz) { unsigned char *d = (unsigned char *)dst; const unsigned char *s = (const unsigned char *)src; while (sz--) *d++ = *s++; return dst; } static void *tg3json__memset_fallback(void *dst, int val, size_t sz) { unsigned char *d = (unsigned char *)dst; while (sz--) *d++ = (unsigned char)val; return dst; } static int tg3json__memcmp_fallback(const void *a, const void *b, size_t sz) { const unsigned char *pa = (const unsigned char *)a; const unsigned char *pb = (const unsigned char *)b; while (sz--) { if (*pa != *pb) return (int)*pa - (int)*pb; ++pa; ++pb; } return 0; } static size_t tg3json__strlen_fallback(const char *str) { const char *p = str; while (*p) ++p; return (size_t)(p - str); } #endif #ifndef TINYGLTF_JSON_MALLOC #ifdef TINYGLTF_JSON_NO_STDLIB #define TINYGLTF_JSON_MALLOC(sz) NULL #else #define TINYGLTF_JSON_MALLOC(sz) malloc(sz) #endif #endif #ifndef TINYGLTF_JSON_REALLOC #ifdef TINYGLTF_JSON_NO_STDLIB #define TINYGLTF_JSON_REALLOC(ptr, sz) NULL #else #define TINYGLTF_JSON_REALLOC(ptr, sz) realloc(ptr, sz) #endif #endif #ifndef TINYGLTF_JSON_FREE #ifdef TINYGLTF_JSON_NO_STDLIB #define TINYGLTF_JSON_FREE(ptr) ((void)(ptr)) #else #define TINYGLTF_JSON_FREE(ptr) free(ptr) #endif #endif #ifndef TINYGLTF_JSON_MEMCPY #ifdef TINYGLTF_JSON_NO_STDLIB #define TINYGLTF_JSON_MEMCPY(dst, src, sz) tg3json__memcpy_fallback((dst), (src), (sz)) #else #define TINYGLTF_JSON_MEMCPY(dst, src, sz) memcpy(dst, src, sz) #endif #endif #ifndef TINYGLTF_JSON_MEMSET #ifdef TINYGLTF_JSON_NO_STDLIB #define TINYGLTF_JSON_MEMSET(dst, val, sz) tg3json__memset_fallback((dst), (val), (sz)) #else #define TINYGLTF_JSON_MEMSET(dst, val, sz) memset(dst, val, sz) #endif #endif #ifndef TINYGLTF_JSON_MEMCMP #ifdef TINYGLTF_JSON_NO_STDLIB #define TINYGLTF_JSON_MEMCMP(a, b, sz) tg3json__memcmp_fallback((a), (b), (sz)) #else #define TINYGLTF_JSON_MEMCMP(a, b, sz) memcmp(a, b, sz) #endif #endif #ifndef TINYGLTF_JSON_STRLEN #ifdef TINYGLTF_JSON_NO_STDLIB #define TINYGLTF_JSON_STRLEN(str) tg3json__strlen_fallback((str)) #else #define TINYGLTF_JSON_STRLEN(str) strlen(str) #endif #endif static size_t tg3json__itoa(int64_t value, char *buf) { char *p = buf; uint64_t val; char tmp[24]; int i = 0; if (value < 0) { *p++ = '-'; val = (value == -9223372036854775807LL - 1) ? 9223372036854775808ULL : (uint64_t)(-value); } else { val = (uint64_t)value; } if (val == 0) { *p++ = '0'; *p = '\0'; return (size_t)(p - buf); } while (val > 0) { tmp[i++] = (char)('0' + (val % 10)); val /= 10; } while (i > 0) { *p++ = tmp[--i]; } *p = '\0'; return (size_t)(p - buf); } #if !TG3JSON_USE_STDLIB_FPCONV static size_t tg3json__utoa(uint64_t value, char *buf) { char tmp[32]; size_t n = 0; size_t i = 0; if (value == 0) { buf[0] = '0'; buf[1] = '\0'; return 1; } while (value > 0) { tmp[n++] = (char)('0' + (value % 10u)); value /= 10u; } while (n > 0) buf[i++] = tmp[--n]; buf[i] = '\0'; return i; } #endif /* !TG3JSON_USE_STDLIB_FPCONV */ static uint64_t tg3json__double_bits(double v) { uint64_t bits = 0; TINYGLTF_JSON_MEMCPY(&bits, &v, sizeof(bits)); return bits; } static int tg3json__is_nan_bits(uint64_t bits) { return ((bits & 0x7ff0000000000000ULL) == 0x7ff0000000000000ULL) && ((bits & 0x000fffffffffffffULL) != 0); } static int tg3json__is_inf_bits(uint64_t bits) { return (bits & 0x7fffffffffffffffULL) == 0x7ff0000000000000ULL; } #if !TG3JSON_USE_STDLIB_FPCONV static double tg3json__double_from_bits(uint64_t bits) { double v = 0.0; TINYGLTF_JSON_MEMCPY(&v, &bits, sizeof(v)); return v; } static long double tg3json__pow10_ld(int exp10) { long double v = 1.0L; if (exp10 < 0) { exp10 = -exp10; while (exp10 >= 16) { v *= 1.0e-16L; exp10 -= 16; } while (exp10-- > 0) v *= 0.1L; } else { while (exp10 >= 16) { v *= 1.0e16L; exp10 -= 16; } while (exp10-- > 0) v *= 10.0L; } return v; } #endif static int tg3json__parse_f64_c(const char *start, const char *end, double *out) { #if TG3JSON_USE_STDLIB_FPCONV size_t len = (size_t)(end - start); char stack_buf[128]; char *buf = stack_buf; char *parse_end = NULL; double v; if (len + 1 > sizeof(stack_buf)) { buf = (char *)TINYGLTF_JSON_MALLOC(len + 1); if (!buf) return 0; } if (len > 0) TINYGLTF_JSON_MEMCPY(buf, start, len); buf[len] = '\0'; v = strtod(buf, &parse_end); if (parse_end != buf + len || tg3json__is_inf_bits(tg3json__double_bits(v))) { if (buf != stack_buf) TINYGLTF_JSON_FREE(buf); return 0; } if (buf != stack_buf) TINYGLTF_JSON_FREE(buf); *out = v; return 1; #else const char *p = start; int neg = 0; int saw_digit = 0; int nonzero_seen = 0; int exp10 = 0; uint64_t sig = 0; int sig_digits = 0; long double extra = 0.0L; int extra_digits = 0; int exp_sign = 1; int exp_lit = 0; long double v; if (p < end && *p == '-') { neg = 1; ++p; } if (p >= end) return 0; if (*p == '0') { saw_digit = 1; ++p; } else if (*p >= '1' && *p <= '9') { do { int d = *p - '0'; saw_digit = 1; nonzero_seen = 1; if (sig_digits < 19) { sig = sig * 10u + (uint64_t)d; ++sig_digits; } else if (extra_digits < 64) { extra = extra * 10.0L + (long double)d; ++extra_digits; } else { ++exp10; } ++p; } while (p < end && *p >= '0' && *p <= '9'); } else { return 0; } if (p < end && *p == '.') { ++p; if (p >= end || *p < '0' || *p > '9') return 0; do { int d = *p - '0'; saw_digit = 1; if (d != 0 || nonzero_seen) { nonzero_seen = nonzero_seen || (d != 0); if (sig_digits < 19) { sig = sig * 10u + (uint64_t)d; ++sig_digits; --exp10; } else if (extra_digits < 64) { extra = extra * 10.0L + (long double)d; ++extra_digits; --exp10; } else { --exp10; } } else { --exp10; } ++p; } while (p < end && *p >= '0' && *p <= '9'); } if (!saw_digit) return 0; if (p < end && (*p == 'e' || *p == 'E')) { ++p; if (p < end && (*p == '+' || *p == '-')) { exp_sign = (*p == '-') ? -1 : 1; ++p; } if (p >= end || *p < '0' || *p > '9') return 0; while (p < end && *p >= '0' && *p <= '9') { if (exp_lit < 10000) exp_lit = exp_lit * 10 + (*p - '0'); ++p; } exp10 += exp_sign * exp_lit; } if (p != end) return 0; if (!nonzero_seen || sig_digits == 0) { *out = neg ? tg3json__double_from_bits(0x8000000000000000ULL) : 0.0; return 1; } if (exp10 > 309) return 0; if (exp10 < -4000) { *out = neg ? tg3json__double_from_bits(0x8000000000000000ULL) : 0.0; return 1; } v = (long double)sig; if (extra_digits > 0) { v = v * tg3json__pow10_ld(extra_digits) + extra; } if (exp10 != 0) v *= tg3json__pow10_ld(exp10); if (neg) v = -v; *out = (double)v; if (tg3json__is_inf_bits(tg3json__double_bits(*out))) return 0; return 1; #endif } #if !TG3JSON_USE_STDLIB_FPCONV static char *tg3json__write_exp(char *p, int exp10) { char tmp[16]; size_t n; *p++ = 'e'; if (exp10 < 0) { *p++ = '-'; exp10 = -exp10; } n = tg3json__utoa((uint64_t)exp10, tmp); TINYGLTF_JSON_MEMCPY(p, tmp, n); return p + n; } static char *tg3json__format_decimal_digits(char *out, const char *digits, int ndigits, int dec_exp, int negative) { char *p = out; int i; int output_exp = dec_exp + ndigits - 1; if (negative) *p++ = '-'; if (output_exp < -4 || output_exp >= 16) { *p++ = digits[0]; if (ndigits > 1) { *p++ = '.'; for (i = 1; i < ndigits; ++i) *p++ = digits[i]; } return tg3json__write_exp(p, output_exp); } if (dec_exp >= 0) { for (i = 0; i < ndigits; ++i) *p++ = digits[i]; for (i = 0; i < dec_exp; ++i) *p++ = '0'; return p; } if (dec_exp + ndigits > 0) { int int_digits = dec_exp + ndigits; for (i = 0; i < int_digits; ++i) *p++ = digits[i]; *p++ = '.'; for (; i < ndigits; ++i) *p++ = digits[i]; return p; } *p++ = '0'; *p++ = '.'; for (i = 0; i < -(dec_exp + ndigits); ++i) *p++ = '0'; for (i = 0; i < ndigits; ++i) *p++ = digits[i]; return p; } #endif /* !TG3JSON_USE_STDLIB_FPCONV */ static int tg3json__same_f64(double a, double b) { return tg3json__double_bits(a) == tg3json__double_bits(b); } #if TG3JSON_USE_STDLIB_FPCONV static void tg3json__normalize_exponent(char *s) { char *e = s; char *dst; int neg = 0; while (*e && *e != 'e' && *e != 'E') ++e; if (!*e) return; *e++ = 'e'; dst = e; if (*e == '+' || *e == '-') { neg = (*e == '-'); ++e; } while (*e == '0') ++e; if (neg) *dst++ = '-'; if (!*e) { *dst++ = '0'; } else { while (*e) *dst++ = *e++; } *dst = '\0'; } static void tg3json__expand_short_plain_decimal(char *s) { char *p = s; char *e; char digits[32]; char out[96]; int negative = 0; int ndigits = 0; int int_digits = 0; int exp10 = 0; int new_point; int i; char *q = out; if (*p == '-') { negative = 1; ++p; } e = p; while (*e && *e != 'e') ++e; if (!*e) return; for (; p < e; ++p) { if (*p == '.') { int_digits = ndigits; } else { if (ndigits < (int)(sizeof(digits) - 1)) digits[ndigits++] = *p; } } if (int_digits == 0) int_digits = ndigits; ++e; if (*e == '-') { int sign = -1; ++e; while (*e >= '0' && *e <= '9') exp10 = exp10 * 10 + (*e++ - '0'); exp10 *= sign; } else { while (*e >= '0' && *e <= '9') exp10 = exp10 * 10 + (*e++ - '0'); } if (exp10 < -4 || exp10 >= 16) return; new_point = int_digits + exp10; if (negative) *q++ = '-'; if (new_point <= 0) { *q++ = '0'; *q++ = '.'; for (i = 0; i < -new_point; ++i) *q++ = '0'; for (i = 0; i < ndigits; ++i) *q++ = digits[i]; } else if (new_point >= ndigits) { for (i = 0; i < ndigits; ++i) *q++ = digits[i]; for (i = ndigits; i < new_point; ++i) *q++ = '0'; } else { for (i = 0; i < new_point; ++i) *q++ = digits[i]; *q++ = '.'; for (; i < ndigits; ++i) *q++ = digits[i]; } *q = '\0'; if (q > out) { char *dot = out; while (*dot && *dot != '.') ++dot; if (*dot == '.') { char *end = q - 1; while (end > dot && *end == '0') *end-- = '\0'; if (end == dot) *end = '\0'; } } TINYGLTF_JSON_MEMCPY(s, out, (size_t)(TINYGLTF_JSON_STRLEN(out) + 1)); } #endif static char *tg3json__dtoa_c(double value, char *buf) { uint64_t bits = tg3json__double_bits(value); int negative = (int)(bits >> 63); uint64_t abits = bits & 0x7fffffffffffffffULL; #if !TG3JSON_USE_STDLIB_FPCONV long double x; int dec_e = 0; char digits[24]; int ndigits = 17; int i; char best[80]; size_t best_len; #endif if (tg3json__is_nan_bits(bits)) { TINYGLTF_JSON_MEMCPY(buf, "nan", 3); return buf + 3; } if (tg3json__is_inf_bits(bits)) { if (negative) { TINYGLTF_JSON_MEMCPY(buf, "-inf", 4); return buf + 4; } TINYGLTF_JSON_MEMCPY(buf, "inf", 3); return buf + 3; } if (abits == 0) { *buf++ = '0'; return buf; } if (bits == 0x3ff0000000000000ULL) { *buf++ = '1'; return buf; } if (bits == 0xbff0000000000000ULL) { *buf++ = '-'; *buf++ = '1'; return buf; } #if TG3JSON_USE_STDLIB_FPCONV { int precision; char candidate[96]; char shortest[96]; shortest[0] = '\0'; for (precision = 1; precision <= 17; ++precision) { char fmt[8]; double parsed = 0.0; int n; n = snprintf(fmt, sizeof(fmt), "%%.%dg", precision); if (n <= 0 || n >= (int)sizeof(fmt)) continue; n = snprintf(candidate, sizeof(candidate), fmt, value); if (n <= 0 || n >= (int)sizeof(candidate)) continue; tg3json__normalize_exponent(candidate); tg3json__expand_short_plain_decimal(candidate); if (tg3json__parse_f64_c(candidate, candidate + TINYGLTF_JSON_STRLEN(candidate), &parsed) && tg3json__same_f64(parsed, value)) { TINYGLTF_JSON_MEMCPY(shortest, candidate, TINYGLTF_JSON_STRLEN(candidate) + 1); break; } } if (!shortest[0]) { int n = snprintf(shortest, sizeof(shortest), "%.17g", value); if (n <= 0 || n >= (int)sizeof(shortest)) return buf; tg3json__normalize_exponent(shortest); tg3json__expand_short_plain_decimal(shortest); } TINYGLTF_JSON_MEMCPY(buf, shortest, TINYGLTF_JSON_STRLEN(shortest)); return buf + TINYGLTF_JSON_STRLEN(shortest); } #else x = negative ? -(long double)value : (long double)value; while (x >= 1.0e16L) { x *= 1.0e-16L; dec_e += 16; } while (x >= 10.0L) { x *= 0.1L; ++dec_e; } while (x < 1.0L) { x *= 10.0L; --dec_e; } for (i = 0; i < 18; ++i) { int d = (int)x; if (d < 0) d = 0; if (d > 9) d = 9; digits[i] = (char)('0' + d); x = (x - (long double)d) * 10.0L; } if (digits[17] >= '5') { int carry = 1; for (i = 16; i >= 0 && carry; --i) { if (digits[i] == '9') { digits[i] = '0'; } else { digits[i]++; carry = 0; } } if (carry) { digits[0] = '1'; for (i = 1; i < 17; ++i) digits[i] = '0'; ++dec_e; } } while (ndigits > 1 && digits[ndigits - 1] == '0') --ndigits; { char *end = tg3json__format_decimal_digits(best, digits, ndigits, dec_e - ndigits + 1, negative); *end = '\0'; best_len = (size_t)(end - best); } for (i = ndigits - 1; i >= 1; --i) { char candidate[80]; char *end; double parsed = 0.0; end = tg3json__format_decimal_digits(candidate, digits, i, dec_e - i + 1, negative); *end = '\0'; if (tg3json__parse_f64_c(candidate, end, &parsed) && tg3json__same_f64(parsed, value)) { TINYGLTF_JSON_MEMCPY(best, candidate, (size_t)(end - candidate) + 1); best_len = (size_t)(end - candidate); } else { break; } } TINYGLTF_JSON_MEMCPY(buf, best, best_len); return buf + best_len; #endif } typedef struct tg3json__parser { const char *cur; const char *end; const char *error; size_t depth_limit; size_t memory_budget; size_t max_single_alloc; size_t max_string_length; size_t allocated; int parse_float32; } tg3json__parser; typedef struct tg3json__buffer { tg3json__parser *parser; char *data; size_t len; size_t cap; } tg3json__buffer; static void tg3json__init_value(tg3json_value *value) { if (!value) return; TINYGLTF_JSON_MEMSET(value, 0, sizeof(*value)); value->type = TG3JSON_NULL; } static char *tg3json__strndup_local(const char *src, size_t len) { char *dst = (char *)TINYGLTF_JSON_MALLOC(len + 1); if (!dst) return NULL; if (len > 0) TINYGLTF_JSON_MEMCPY(dst, src, len); dst[len] = '\0'; return dst; } static void *tg3json__parser_alloc(tg3json__parser *parser, size_t size) { void *ptr; if (!parser) return TINYGLTF_JSON_MALLOC(size); if (parser->max_single_alloc && size > parser->max_single_alloc) return NULL; if (parser->memory_budget && (size > parser->memory_budget || parser->allocated > parser->memory_budget - size)) { return NULL; } ptr = TINYGLTF_JSON_MALLOC(size); if (!ptr) return NULL; parser->allocated += size; return ptr; } static int tg3json__reserve_bytes(void **ptr, size_t elem_size, size_t needed, size_t *capacity) { void *new_ptr; size_t new_cap; if (needed <= *capacity) return 1; new_cap = (*capacity > 0) ? *capacity : 8; while (new_cap < needed) { if (new_cap > ((size_t)-1) / 2) { new_cap = needed; break; } new_cap *= 2; } if (elem_size != 0 && new_cap > ((size_t)-1) / elem_size) return 0; new_ptr = TINYGLTF_JSON_REALLOC(*ptr, elem_size * new_cap); if (!new_ptr) return 0; *ptr = new_ptr; *capacity = new_cap; return 1; } static int tg3json__reserve_bytes_parser(tg3json__parser *parser, void **ptr, size_t elem_size, size_t needed, size_t *capacity) { void *new_ptr; size_t new_cap; size_t old_bytes; size_t new_bytes; if (needed <= *capacity) return 1; new_cap = (*capacity > 0) ? *capacity : 8; while (new_cap < needed) { if (new_cap > ((size_t)-1) / 2) { new_cap = needed; break; } new_cap *= 2; } if (elem_size != 0 && new_cap > ((size_t)-1) / elem_size) return 0; old_bytes = elem_size * (*capacity); new_bytes = elem_size * new_cap; if (parser) { size_t delta = (new_bytes > old_bytes) ? (new_bytes - old_bytes) : 0; if (parser->max_single_alloc && new_bytes > parser->max_single_alloc) return 0; if (parser->memory_budget && delta > 0 && (delta > parser->memory_budget || parser->allocated > parser->memory_budget - delta)) { return 0; } } new_ptr = TINYGLTF_JSON_REALLOC(*ptr, new_bytes); if (!new_ptr) return 0; if (parser && new_bytes > old_bytes) parser->allocated += new_bytes - old_bytes; *ptr = new_ptr; *capacity = new_cap; return 1; } static const char *tg3json__skip_ws(const char *p, const char *end) { while (p < end) { unsigned char c = (unsigned char)*p; if (c != ' ' && c != '\n' && c != '\r' && c != '\t') break; ++p; } return p; } static void tg3json__set_error(tg3json__parser *parser, const char *pos) { if (!parser->error) parser->error = pos; } static int tg3json__buf_append(tg3json__buffer *buf, const char *src, size_t len) { if (len == 0) return 1; if (!tg3json__reserve_bytes_parser(buf->parser, (void **)&buf->data, 1, buf->len + len + 1, &buf->cap)) { return 0; } TINYGLTF_JSON_MEMCPY(buf->data + buf->len, src, len); buf->len += len; buf->data[buf->len] = '\0'; return 1; } static int tg3json__buf_putc(tg3json__buffer *buf, char c) { if (!tg3json__reserve_bytes_parser(buf->parser, (void **)&buf->data, 1, buf->len + 2, &buf->cap)) { return 0; } buf->data[buf->len++] = c; buf->data[buf->len] = '\0'; return 1; } static int tg3json__hex4(const char *p, uint32_t *out) { uint32_t value = 0; size_t i; for (i = 0; i < 4; ++i) { unsigned char c = (unsigned char)p[i]; value <<= 4; if (c >= '0' && c <= '9') value |= (uint32_t)(c - '0'); else if (c >= 'a' && c <= 'f') value |= (uint32_t)(10 + c - 'a'); else if (c >= 'A' && c <= 'F') value |= (uint32_t)(10 + c - 'A'); else return 0; } *out = value; return 1; } static int tg3json__append_utf8(tg3json__buffer *buf, uint32_t cp) { char tmp[4]; size_t len = 0; if (cp <= 0x7Fu) { tmp[len++] = (char)cp; } else if (cp <= 0x7FFu) { tmp[len++] = (char)(0xC0u | ((cp >> 6) & 0x1Fu)); tmp[len++] = (char)(0x80u | (cp & 0x3Fu)); } else if (cp <= 0xFFFFu) { tmp[len++] = (char)(0xE0u | ((cp >> 12) & 0x0Fu)); tmp[len++] = (char)(0x80u | ((cp >> 6) & 0x3Fu)); tmp[len++] = (char)(0x80u | (cp & 0x3Fu)); } else if (cp <= 0x10FFFFu) { tmp[len++] = (char)(0xF0u | ((cp >> 18) & 0x07u)); tmp[len++] = (char)(0x80u | ((cp >> 12) & 0x3Fu)); tmp[len++] = (char)(0x80u | ((cp >> 6) & 0x3Fu)); tmp[len++] = (char)(0x80u | (cp & 0x3Fu)); } else { return 0; } return tg3json__buf_append(buf, tmp, len); } static int tg3json__parse_string_raw(tg3json__parser *parser, char **out_str, size_t *out_len) { tg3json__buffer buf; const char *start; TINYGLTF_JSON_MEMSET(&buf, 0, sizeof(buf)); buf.parser = parser; if (parser->cur >= parser->end || *parser->cur != '"') { tg3json__set_error(parser, parser->cur); return 0; } ++parser->cur; start = parser->cur; while (parser->cur < parser->end) { unsigned char c = (unsigned char)*parser->cur; if (c == '"') { size_t final_len = buf.len + (size_t)(parser->cur - start); if (parser->max_string_length && final_len > parser->max_string_length) goto oom; if (!tg3json__buf_append(&buf, start, (size_t)(parser->cur - start))) goto oom; ++parser->cur; *out_str = buf.data; *out_len = buf.len; return 1; } if (c == '\\') { uint32_t codepoint; size_t pending_len = (size_t)(parser->cur - start); if (parser->max_string_length && buf.len + pending_len > parser->max_string_length) goto oom; if (!tg3json__buf_append(&buf, start, (size_t)(parser->cur - start))) goto oom; ++parser->cur; if (parser->cur >= parser->end) break; switch (*parser->cur) { case '"': if (!tg3json__buf_putc(&buf, '"')) goto oom; break; case '\\': if (!tg3json__buf_putc(&buf, '\\')) goto oom; break; case '/': if (!tg3json__buf_putc(&buf, '/')) goto oom; break; case 'b': if (!tg3json__buf_putc(&buf, '\b')) goto oom; break; case 'f': if (!tg3json__buf_putc(&buf, '\f')) goto oom; break; case 'n': if (!tg3json__buf_putc(&buf, '\n')) goto oom; break; case 'r': if (!tg3json__buf_putc(&buf, '\r')) goto oom; break; case 't': if (!tg3json__buf_putc(&buf, '\t')) goto oom; break; case 'u': { if ((size_t)(parser->end - parser->cur) < 5) break; if (!tg3json__hex4(parser->cur + 1, &codepoint)) break; parser->cur += 4; if (codepoint >= 0xD800u && codepoint <= 0xDBFFu) { uint32_t low; if ((size_t)(parser->end - parser->cur) < 7 || parser->cur[1] != '\\' || parser->cur[2] != 'u') break; if (!tg3json__hex4(parser->cur + 3, &low)) break; if (low < 0xDC00u || low > 0xDFFFu) break; codepoint = 0x10000u + (((codepoint - 0xD800u) << 10) | (low - 0xDC00u)); parser->cur += 6; } else if (codepoint >= 0xDC00u && codepoint <= 0xDFFFu) { break; } if (!tg3json__append_utf8(&buf, codepoint)) goto oom; break; } default: tg3json__set_error(parser, parser->cur); TINYGLTF_JSON_FREE(buf.data); return 0; } ++parser->cur; start = parser->cur; continue; } if (c < 0x20u) break; ++parser->cur; } tg3json__set_error(parser, parser->cur); TINYGLTF_JSON_FREE(buf.data); return 0; oom: tg3json__set_error(parser, parser->cur); TINYGLTF_JSON_FREE(buf.data); return 0; } static int tg3json__parse_value(tg3json__parser *parser, size_t depth, tg3json_value *out_value); static int tg3json__parse_int64_span(const char *start, const char *end, int64_t *out) { const char *p = start; uint64_t value = 0; uint64_t limit = (uint64_t)INT64_MAX; int neg = 0; if (p < end && *p == '-') { neg = 1; limit += 1u; ++p; } if (p >= end) return 0; while (p < end) { unsigned digit = (unsigned)(*p - '0'); if (digit > 9u) return 0; if (value > (limit - digit) / 10u) return 0; value = value * 10u + (uint64_t)digit; ++p; } if (neg) { if (value == ((uint64_t)INT64_MAX + 1u)) { *out = INT64_MIN; } else { *out = -(int64_t)value; } } else { *out = (int64_t)value; } return 1; } static int tg3json__parse_array(tg3json__parser *parser, size_t depth, tg3json_value *out_value) { tg3json_value *items = NULL; size_t count = 0; size_t cap = 0; ++parser->cur; parser->cur = tg3json__skip_ws(parser->cur, parser->end); if (parser->cur < parser->end && *parser->cur == ']') { ++parser->cur; out_value->type = TG3JSON_ARRAY; out_value->u.array.items = NULL; out_value->u.array.count = 0; return 1; } while (parser->cur < parser->end) { tg3json_value value; tg3json__init_value(&value); if (!tg3json__reserve_bytes_parser(parser, (void **)&items, sizeof(*items), count + 1, &cap)) goto oom; if (!tg3json__parse_value(parser, depth + 1, &value)) goto fail; items[count++] = value; parser->cur = tg3json__skip_ws(parser->cur, parser->end); if (parser->cur >= parser->end) break; if (*parser->cur == ',') { ++parser->cur; parser->cur = tg3json__skip_ws(parser->cur, parser->end); continue; } if (*parser->cur == ']') { ++parser->cur; out_value->type = TG3JSON_ARRAY; out_value->u.array.items = items; out_value->u.array.count = count; return 1; } break; } fail: while (count > 0) { --count; tg3json_value_free(&items[count]); } TINYGLTF_JSON_FREE(items); tg3json__set_error(parser, parser->cur); return 0; oom: while (count > 0) { --count; tg3json_value_free(&items[count]); } TINYGLTF_JSON_FREE(items); tg3json__set_error(parser, parser->cur); return 0; } static int tg3json__parse_object(tg3json__parser *parser, size_t depth, tg3json_value *out_value) { tg3json_object_entry *items = NULL; size_t count = 0; size_t cap = 0; ++parser->cur; parser->cur = tg3json__skip_ws(parser->cur, parser->end); if (parser->cur < parser->end && *parser->cur == '}') { ++parser->cur; out_value->type = TG3JSON_OBJECT; out_value->u.object.items = NULL; out_value->u.object.count = 0; return 1; } while (parser->cur < parser->end) { char *key = NULL; size_t key_len = 0; tg3json_value value; tg3json__init_value(&value); if (!tg3json__parse_string_raw(parser, &key, &key_len)) goto fail; parser->cur = tg3json__skip_ws(parser->cur, parser->end); if (parser->cur >= parser->end || *parser->cur != ':') { TINYGLTF_JSON_FREE(key); goto fail; } ++parser->cur; parser->cur = tg3json__skip_ws(parser->cur, parser->end); if (!tg3json__parse_value(parser, depth + 1, &value)) { TINYGLTF_JSON_FREE(key); goto fail; } if (!tg3json__reserve_bytes_parser(parser, (void **)&items, sizeof(*items), count + 1, &cap)) { TINYGLTF_JSON_FREE(key); tg3json_value_free(&value); goto oom; } items[count].key = key; items[count].key_len = key_len; items[count].value = (tg3json_value *)tg3json__parser_alloc(parser, sizeof(tg3json_value)); if (!items[count].value) { TINYGLTF_JSON_FREE(key); tg3json_value_free(&value); goto oom; } *items[count].value = value; ++count; parser->cur = tg3json__skip_ws(parser->cur, parser->end); if (parser->cur >= parser->end) break; if (*parser->cur == ',') { ++parser->cur; parser->cur = tg3json__skip_ws(parser->cur, parser->end); continue; } if (*parser->cur == '}') { ++parser->cur; out_value->type = TG3JSON_OBJECT; out_value->u.object.items = items; out_value->u.object.count = count; return 1; } break; } fail: while (count > 0) { --count; TINYGLTF_JSON_FREE(items[count].key); if (items[count].value) { tg3json_value_free(items[count].value); TINYGLTF_JSON_FREE(items[count].value); } } TINYGLTF_JSON_FREE(items); tg3json__set_error(parser, parser->cur); return 0; oom: while (count > 0) { --count; TINYGLTF_JSON_FREE(items[count].key); if (items[count].value) { tg3json_value_free(items[count].value); TINYGLTF_JSON_FREE(items[count].value); } } TINYGLTF_JSON_FREE(items); tg3json__set_error(parser, parser->cur); return 0; } static int tg3json__parse_number(tg3json__parser *parser, tg3json_value *out_value) { const char *start = parser->cur; const char *p = parser->cur; int is_real = 0; if (*p == '-') ++p; if (p >= parser->end) goto fail; if (*p == '0') { ++p; } else if (*p >= '1' && *p <= '9') { do { ++p; } while (p < parser->end && *p >= '0' && *p <= '9'); } else { goto fail; } if (p < parser->end && *p == '.') { is_real = 1; ++p; if (p >= parser->end || *p < '0' || *p > '9') goto fail; do { ++p; } while (p < parser->end && *p >= '0' && *p <= '9'); } if (p < parser->end && (*p == 'e' || *p == 'E')) { is_real = 1; ++p; if (p < parser->end && (*p == '+' || *p == '-')) ++p; if (p >= parser->end || *p < '0' || *p > '9') goto fail; do { ++p; } while (p < parser->end && *p >= '0' && *p <= '9'); } if (!is_real) { int64_t v; if (tg3json__parse_int64_span(start, p, &v)) { out_value->type = TG3JSON_INT; out_value->u.integer = v; parser->cur = p; return 1; } is_real = 1; } { if (!tg3json__parse_f64_c(start, p, &out_value->u.real)) goto fail; if (parser->parse_float32) out_value->u.real = (double)(float)out_value->u.real; out_value->type = TG3JSON_REAL; parser->cur = p; return 1; } fail: tg3json__set_error(parser, parser->cur); return 0; } static int tg3json__parse_value(tg3json__parser *parser, size_t depth, tg3json_value *out_value) { if (parser->depth_limit && depth > parser->depth_limit) { tg3json__set_error(parser, parser->cur); return 0; } parser->cur = tg3json__skip_ws(parser->cur, parser->end); if (parser->cur >= parser->end) { tg3json__set_error(parser, parser->cur); return 0; } switch (*parser->cur) { case '[': return tg3json__parse_array(parser, depth, out_value); case '{': return tg3json__parse_object(parser, depth, out_value); case '"': { char *str = NULL; size_t len = 0; if (!tg3json__parse_string_raw(parser, &str, &len)) return 0; out_value->type = TG3JSON_STRING; out_value->u.string.ptr = str; out_value->u.string.len = len; return 1; } case 'n': if ((size_t)(parser->end - parser->cur) >= 4 && TINYGLTF_JSON_MEMCMP(parser->cur, "null", 4) == 0) { out_value->type = TG3JSON_NULL; parser->cur += 4; return 1; } break; case 't': if ((size_t)(parser->end - parser->cur) >= 4 && TINYGLTF_JSON_MEMCMP(parser->cur, "true", 4) == 0) { out_value->type = TG3JSON_BOOL; out_value->u.boolean = 1; parser->cur += 4; return 1; } break; case 'f': if ((size_t)(parser->end - parser->cur) >= 5 && TINYGLTF_JSON_MEMCMP(parser->cur, "false", 5) == 0) { out_value->type = TG3JSON_BOOL; out_value->u.boolean = 0; parser->cur += 5; return 1; } break; default: return tg3json__parse_number(parser, out_value); } tg3json__set_error(parser, parser->cur); return 0; } int tg3json_parse_n_opts(const char *data, size_t len, const tg3json_parse_options *options, tg3json_value *out_value, const char **out_error_pos) { tg3json__parser parser; int ok; if (out_error_pos) *out_error_pos = NULL; if (!out_value) return 0; tg3json__init_value(out_value); if (!data) return 0; parser.cur = data; parser.end = data + len; parser.error = NULL; parser.depth_limit = options ? (options->depth_limit ? options->depth_limit : 256) : 256; parser.memory_budget = options ? options->memory_budget : 0; parser.max_single_alloc = options ? options->max_single_alloc : 0; parser.max_string_length = options ? options->max_string_length : 0; parser.allocated = 0; parser.parse_float32 = options ? options->parse_float32 : 0; parser.cur = tg3json__skip_ws(parser.cur, parser.end); if (parser.cur >= parser.end) { if (out_error_pos) *out_error_pos = data; return 0; } ok = tg3json__parse_value(&parser, 0, out_value); if (!ok) { if (out_error_pos) *out_error_pos = parser.error ? parser.error : parser.cur; tg3json_value_free(out_value); return 0; } parser.cur = tg3json__skip_ws(parser.cur, parser.end); if (parser.cur != parser.end) { if (out_error_pos) *out_error_pos = parser.cur; tg3json_value_free(out_value); return 0; } return 1; } int tg3json_parse_n(const char *data, size_t len, size_t depth_limit, tg3json_value *out_value, const char **out_error_pos) { tg3json_parse_options options; TINYGLTF_JSON_MEMSET(&options, 0, sizeof(options)); options.depth_limit = depth_limit; return tg3json_parse_n_opts(data, len, &options, out_value, out_error_pos); } int tg3json_parse(const char *begin, const char *end, size_t depth_limit, tg3json_value *out_value, const char **out_error_pos) { if (!begin || !end || end < begin) { if (out_error_pos) *out_error_pos = begin; return 0; } return tg3json_parse_n(begin, (size_t)(end - begin), depth_limit, out_value, out_error_pos); } void tg3json_value_free(tg3json_value *value) { size_t i; if (!value) return; switch (value->type) { case TG3JSON_STRING: TINYGLTF_JSON_FREE(value->u.string.ptr); break; case TG3JSON_ARRAY: for (i = 0; i < value->u.array.count; ++i) { tg3json_value_free(&value->u.array.items[i]); } TINYGLTF_JSON_FREE(value->u.array.items); break; case TG3JSON_OBJECT: for (i = 0; i < value->u.object.count; ++i) { TINYGLTF_JSON_FREE(value->u.object.items[i].key); if (value->u.object.items[i].value) { tg3json_value_free(value->u.object.items[i].value); TINYGLTF_JSON_FREE(value->u.object.items[i].value); } } TINYGLTF_JSON_FREE(value->u.object.items); break; case TG3JSON_NULL: case TG3JSON_BOOL: case TG3JSON_INT: case TG3JSON_REAL: /* Scalar variants own no heap memory. */ break; } tg3json__init_value(value); } void tg3json_value_init_null(tg3json_value *value) { tg3json__init_value(value); } void tg3json_value_init_bool(tg3json_value *value, int boolean_value) { tg3json__init_value(value); value->type = TG3JSON_BOOL; value->u.boolean = boolean_value ? 1 : 0; } void tg3json_value_init_int(tg3json_value *value, int64_t integer_value) { tg3json__init_value(value); value->type = TG3JSON_INT; value->u.integer = integer_value; } void tg3json_value_init_real(tg3json_value *value, double real_value) { tg3json__init_value(value); value->type = TG3JSON_REAL; value->u.real = real_value; } int tg3json_value_init_string_n(tg3json_value *value, const char *str, size_t len) { tg3json__init_value(value); value->type = TG3JSON_STRING; value->u.string.ptr = tg3json__strndup_local(str ? str : "", str ? len : 0); if (!value->u.string.ptr) { tg3json__init_value(value); return 0; } value->u.string.len = str ? len : 0; return 1; } int tg3json_value_init_string(tg3json_value *value, const char *str) { return tg3json_value_init_string_n(value, str, str ? TINYGLTF_JSON_STRLEN(str) : 0); } void tg3json_value_init_array(tg3json_value *value) { tg3json__init_value(value); value->type = TG3JSON_ARRAY; } void tg3json_value_init_object(tg3json_value *value) { tg3json__init_value(value); value->type = TG3JSON_OBJECT; } int tg3json_value_copy(tg3json_value *dst, const tg3json_value *src) { size_t i; tg3json__init_value(dst); if (!src) return 1; switch (src->type) { case TG3JSON_NULL: return 1; case TG3JSON_BOOL: tg3json_value_init_bool(dst, src->u.boolean); return 1; case TG3JSON_INT: tg3json_value_init_int(dst, src->u.integer); return 1; case TG3JSON_REAL: tg3json_value_init_real(dst, src->u.real); return 1; case TG3JSON_STRING: return tg3json_value_init_string_n(dst, src->u.string.ptr, src->u.string.len); case TG3JSON_ARRAY: tg3json_value_init_array(dst); for (i = 0; i < src->u.array.count; ++i) { if (!tg3json_array_append_copy(dst, &src->u.array.items[i])) { tg3json_value_free(dst); return 0; } } return 1; case TG3JSON_OBJECT: tg3json_value_init_object(dst); for (i = 0; i < src->u.object.count; ++i) { if (!tg3json_object_set_copy_n(dst, src->u.object.items[i].key, src->u.object.items[i].key_len, src->u.object.items[i].value)) { tg3json_value_free(dst); return 0; } } return 1; } return 0; /* unreachable: all enum cases handled above. */ } const tg3json_value *tg3json_object_get_n(const tg3json_value *object, const char *key, size_t key_len) { size_t i; if (!object || object->type != TG3JSON_OBJECT) return NULL; for (i = 0; i < object->u.object.count; ++i) { const tg3json_object_entry *entry = &object->u.object.items[i]; if (entry->key_len == key_len && TINYGLTF_JSON_MEMCMP(entry->key, key, key_len) == 0) { return entry->value; } } return NULL; } const tg3json_value *tg3json_object_get(const tg3json_value *object, const char *key) { if (!key) return NULL; return tg3json_object_get_n(object, key, TINYGLTF_JSON_STRLEN(key)); } tg3json_value *tg3json_object_get_mut_n(tg3json_value *object, const char *key, size_t key_len) { size_t i; if (!object || object->type != TG3JSON_OBJECT) return NULL; for (i = 0; i < object->u.object.count; ++i) { tg3json_object_entry *entry = &object->u.object.items[i]; if (entry->key_len == key_len && TINYGLTF_JSON_MEMCMP(entry->key, key, key_len) == 0) { return entry->value; } } return NULL; } tg3json_value *tg3json_object_get_mut(tg3json_value *object, const char *key) { if (!key) return NULL; return tg3json_object_get_mut_n(object, key, TINYGLTF_JSON_STRLEN(key)); } const tg3json_object_entry *tg3json_object_at(const tg3json_value *object, size_t index) { if (!object || object->type != TG3JSON_OBJECT || index >= object->u.object.count) { return NULL; } return &object->u.object.items[index]; } size_t tg3json_object_size(const tg3json_value *object) { if (!object || object->type != TG3JSON_OBJECT) return 0; return object->u.object.count; } int tg3json_object_set_take_n(tg3json_value *object, const char *key, size_t key_len, tg3json_value *value) { tg3json_value *existing; tg3json_object_entry *entry; size_t cap; if (!object || object->type != TG3JSON_OBJECT || !value || !key) return 0; existing = tg3json_object_get_mut_n(object, key, key_len); if (existing) { tg3json_value_free(existing); *existing = *value; tg3json__init_value(value); return 1; } cap = object->u.object.count; if (!tg3json__reserve_bytes((void **)&object->u.object.items, sizeof(*object->u.object.items), object->u.object.count + 1, &cap)) { return 0; } entry = &object->u.object.items[object->u.object.count]; entry->key = tg3json__strndup_local(key, key_len); if (!entry->key) return 0; entry->key_len = key_len; entry->value = (tg3json_value *)TINYGLTF_JSON_MALLOC(sizeof(tg3json_value)); if (!entry->value) { TINYGLTF_JSON_FREE(entry->key); entry->key = NULL; entry->key_len = 0; return 0; } *entry->value = *value; tg3json__init_value(value); object->u.object.count += 1; return 1; } int tg3json_object_set_take(tg3json_value *object, const char *key, tg3json_value *value) { if (!key) return 0; return tg3json_object_set_take_n(object, key, TINYGLTF_JSON_STRLEN(key), value); } int tg3json_object_set_copy_n(tg3json_value *object, const char *key, size_t key_len, const tg3json_value *value) { tg3json_value copy; tg3json__init_value(©); if (!tg3json_value_copy(©, value)) return 0; if (!tg3json_object_set_take_n(object, key, key_len, ©)) { tg3json_value_free(©); return 0; } return 1; } int tg3json_object_set_copy(tg3json_value *object, const char *key, const tg3json_value *value) { if (!key) return 0; return tg3json_object_set_copy_n(object, key, TINYGLTF_JSON_STRLEN(key), value); } const tg3json_value *tg3json_array_get(const tg3json_value *array, size_t index) { if (!array || array->type != TG3JSON_ARRAY || index >= array->u.array.count) { return NULL; } return &array->u.array.items[index]; } size_t tg3json_array_size(const tg3json_value *array) { if (!array || array->type != TG3JSON_ARRAY) return 0; return array->u.array.count; } int tg3json_array_append_take(tg3json_value *array, tg3json_value *value) { size_t cap; if (!array || array->type != TG3JSON_ARRAY || !value) return 0; cap = array->u.array.count; if (!tg3json__reserve_bytes((void **)&array->u.array.items, sizeof(*array->u.array.items), array->u.array.count + 1, &cap)) { return 0; } array->u.array.items[array->u.array.count++] = *value; tg3json__init_value(value); return 1; } int tg3json_array_append_copy(tg3json_value *array, const tg3json_value *value) { tg3json_value copy; tg3json__init_value(©); if (!tg3json_value_copy(©, value)) return 0; if (!tg3json_array_append_take(array, ©)) { tg3json_value_free(©); return 0; } return 1; } static int tg3json__indent(tg3json__buffer *buf, int indent, int depth) { int i; if (indent <= 0) return 1; if (!tg3json__buf_putc(buf, '\n')) return 0; for (i = 0; i < indent * depth; ++i) { if (!tg3json__buf_putc(buf, ' ')) return 0; } return 1; } static int tg3json__stringify_value_ex(tg3json__buffer *buf, const tg3json_value *value, int indent, int depth) { size_t i; char numbuf[64]; switch (value->type) { case TG3JSON_NULL: return tg3json__buf_append(buf, "null", 4); case TG3JSON_BOOL: return value->u.boolean ? tg3json__buf_append(buf, "true", 4) : tg3json__buf_append(buf, "false", 5); case TG3JSON_INT: tg3json__itoa(value->u.integer, numbuf); return tg3json__buf_append(buf, numbuf, TINYGLTF_JSON_STRLEN(numbuf)); case TG3JSON_REAL: { char *end = tg3json__dtoa_c(value->u.real, numbuf); *end = '\0'; const char *b = numbuf; if (*b == '-') ++b; if (*b == 'n' || *b == 'N' || *b == 'i' || *b == 'I') { return tg3json__buf_append(buf, "null", 4); } } return tg3json__buf_append(buf, numbuf, TINYGLTF_JSON_STRLEN(numbuf)); case TG3JSON_STRING: if (!tg3json__buf_putc(buf, '"')) return 0; for (i = 0; i < value->u.string.len; ++i) { unsigned char c = (unsigned char)value->u.string.ptr[i]; switch (c) { case '"': if (!tg3json__buf_append(buf, "\\\"", 2)) return 0; break; case '\\': if (!tg3json__buf_append(buf, "\\\\", 2)) return 0; break; case '\b': if (!tg3json__buf_append(buf, "\\b", 2)) return 0; break; case '\f': if (!tg3json__buf_append(buf, "\\f", 2)) return 0; break; case '\n': if (!tg3json__buf_append(buf, "\\n", 2)) return 0; break; case '\r': if (!tg3json__buf_append(buf, "\\r", 2)) return 0; break; case '\t': if (!tg3json__buf_append(buf, "\\t", 2)) return 0; break; default: if (c < 0x20u) { numbuf[0] = '\\'; numbuf[1] = 'u'; numbuf[2] = '0'; numbuf[3] = '0'; numbuf[4] = "0123456789abcdef"[(c >> 4) & 0xf]; numbuf[5] = "0123456789abcdef"[c & 0xf]; numbuf[6] = '\0'; if (!tg3json__buf_append(buf, numbuf, 6)) return 0; } else { if (!tg3json__buf_putc(buf, (char)c)) return 0; } break; } } return tg3json__buf_putc(buf, '"'); case TG3JSON_ARRAY: if (!tg3json__buf_putc(buf, '[')) return 0; for (i = 0; i < value->u.array.count; ++i) { if (i > 0 && !tg3json__buf_putc(buf, ',')) return 0; if (indent > 0 && !tg3json__indent(buf, indent, depth + 1)) return 0; if (!tg3json__stringify_value_ex(buf, &value->u.array.items[i], indent, depth + 1)) return 0; } if (indent > 0 && value->u.array.count > 0 && !tg3json__indent(buf, indent, depth)) return 0; return tg3json__buf_putc(buf, ']'); case TG3JSON_OBJECT: if (!tg3json__buf_putc(buf, '{')) return 0; for (i = 0; i < value->u.object.count; ++i) { tg3json_value key_value; if (i > 0 && !tg3json__buf_putc(buf, ',')) return 0; if (indent > 0 && !tg3json__indent(buf, indent, depth + 1)) return 0; tg3json__init_value(&key_value); key_value.type = TG3JSON_STRING; key_value.u.string.ptr = value->u.object.items[i].key; key_value.u.string.len = value->u.object.items[i].key_len; if (!tg3json__stringify_value_ex(buf, &key_value, indent, depth + 1)) return 0; if (!tg3json__buf_putc(buf, ':')) return 0; if (indent > 0 && !tg3json__buf_putc(buf, ' ')) return 0; if (!tg3json__stringify_value_ex(buf, value->u.object.items[i].value, indent, depth + 1)) return 0; } if (indent > 0 && value->u.object.count > 0 && !tg3json__indent(buf, indent, depth)) return 0; return tg3json__buf_putc(buf, '}'); } return 0; /* unreachable: all enum cases handled above. */ } char *tg3json_stringify(const tg3json_value *value, size_t *out_len) { tg3json__buffer buf; TINYGLTF_JSON_MEMSET(&buf, 0, sizeof(buf)); if (!value || !tg3json__stringify_value_ex(&buf, value, -1, 0)) { TINYGLTF_JSON_FREE(buf.data); if (out_len) *out_len = 0; return NULL; } if (!buf.data) { buf.data = (char *)TINYGLTF_JSON_MALLOC(1); if (!buf.data) { if (out_len) *out_len = 0; return NULL; } buf.data[0] = '\0'; } if (out_len) *out_len = buf.len; return buf.data; } char *tg3json_stringify_pretty(const tg3json_value *value, int indent, size_t *out_len) { tg3json__buffer buf; TINYGLTF_JSON_MEMSET(&buf, 0, sizeof(buf)); if (!value || !tg3json__stringify_value_ex(&buf, value, indent, 0)) { TINYGLTF_JSON_FREE(buf.data); if (out_len) *out_len = 0; return NULL; } if (!buf.data) { buf.data = (char *)TINYGLTF_JSON_MALLOC(1); if (!buf.data) { if (out_len) *out_len = 0; return NULL; } buf.data[0] = '\0'; } if (out_len) *out_len = buf.len; return buf.data; } #endif /* TINYGLTF_JSON_C_IMPLEMENTATION */ #endif /* TINYGLTF_JSON_C_H_ */