/src/tinygltf/tinygltf_json.h
Line | Count | Source |
1 | | /* |
2 | | * tinygltf_json.h - Fast JSON parser for tinygltf |
3 | | * |
4 | | * The MIT License (MIT) |
5 | | * Copyright (c) 2015 - Present Syoyo Fujita, Aurelien Chatelain and many |
6 | | * contributors. |
7 | | * |
8 | | * A custom JSON parser optimized for glTF processing. |
9 | | * |
10 | | * Design goals: |
11 | | * - C-style implementation core (structs, raw pointers, malloc/free) |
12 | | * - Minimal C++ wrappers for tinygltf interface compatibility |
13 | | * - SIMD-accelerated whitespace skipping and string scanning |
14 | | * - Flat storage arrays for cache-friendly memory layout |
15 | | * |
16 | | * SIMD activation (default: SIMD disabled): |
17 | | * Define TINYGLTF_JSON_USE_SIMD to auto-detect CPU SIMD support, OR |
18 | | * define one or more of the following explicitly: |
19 | | * TINYGLTF_JSON_SIMD_SSE2 - Enable SSE2 (x86/x86-64) |
20 | | * TINYGLTF_JSON_SIMD_AVX2 - Enable AVX2 (x86-64, implies SSE2) |
21 | | * TINYGLTF_JSON_SIMD_NEON - Enable ARM NEON |
22 | | * |
23 | | * Exception handling (default: exceptions disabled): |
24 | | * By default, parse errors silently return a null value. |
25 | | * Define TINYGLTF_JSON_USE_EXCEPTIONS before including this header to |
26 | | * allow tinygltf_json::parse() to throw std::invalid_argument on error |
27 | | * when its allow_exceptions parameter is true. |
28 | | */ |
29 | | |
30 | | #ifndef TINYGLTF_JSON_H_ |
31 | | #define TINYGLTF_JSON_H_ |
32 | | |
33 | | /* C standard headers (keep these first for C compatibility) */ |
34 | | #include <stddef.h> |
35 | | #include <stdint.h> |
36 | | #include <inttypes.h> |
37 | | #include <string.h> |
38 | | #include <stdlib.h> |
39 | | #include <stdio.h> |
40 | | #include <assert.h> |
41 | | |
42 | | /* C++ headers (minimal) */ |
43 | | #include <string> |
44 | | #include <cstddef> /* for std::nullptr_t */ |
45 | | #include <new> /* for placement-new */ |
46 | | /* Exception opt-in: define TINYGLTF_JSON_USE_EXCEPTIONS to enable throws. |
47 | | * TINYGLTF_JSON_NO_EXCEPTIONS is the internal guard derived from the absence |
48 | | * of TINYGLTF_JSON_USE_EXCEPTIONS; users should not define it directly. */ |
49 | | #ifndef TINYGLTF_JSON_USE_EXCEPTIONS |
50 | | # define TINYGLTF_JSON_NO_EXCEPTIONS |
51 | | #endif |
52 | | #ifndef TINYGLTF_JSON_NO_EXCEPTIONS |
53 | | # include <stdexcept> |
54 | | #endif |
55 | | |
56 | | /* ====================================================================== |
57 | | * SIMD detection |
58 | | * ====================================================================== */ |
59 | | |
60 | | #ifdef TINYGLTF_JSON_USE_SIMD |
61 | | # if defined(__AVX2__) |
62 | | # define TINYGLTF_JSON_SIMD_AVX2 |
63 | | # endif |
64 | | # if defined(__SSE2__) || defined(_M_AMD64) || defined(_M_X64) || \ |
65 | | (defined(_M_IX86_FP) && _M_IX86_FP >= 2) |
66 | | # define TINYGLTF_JSON_SIMD_SSE2 |
67 | | # endif |
68 | | # if defined(__ARM_NEON) || defined(__ARM_NEON__) |
69 | | # define TINYGLTF_JSON_SIMD_NEON |
70 | | # endif |
71 | | #endif |
72 | | |
73 | | #ifdef TINYGLTF_JSON_SIMD_AVX2 |
74 | | # include <immintrin.h> |
75 | | #elif defined(TINYGLTF_JSON_SIMD_SSE2) |
76 | | # include <emmintrin.h> |
77 | | #endif |
78 | | |
79 | | #ifdef TINYGLTF_JSON_SIMD_NEON |
80 | | # include <arm_neon.h> |
81 | | #endif |
82 | | |
83 | | /* ====================================================================== |
84 | | * JSON VALUE TYPE CONSTANTS (C-style integer constants) |
85 | | * ====================================================================== */ |
86 | | |
87 | 0 | #define CJ_NULL 0 |
88 | 0 | #define CJ_BOOL 1 |
89 | 0 | #define CJ_INT 2 |
90 | 0 | #define CJ_REAL 3 |
91 | 0 | #define CJ_STRING 4 |
92 | 0 | #define CJ_ARRAY 5 |
93 | 0 | #define CJ_OBJECT 6 |
94 | | |
95 | | /* ====================================================================== |
96 | | * SIMD WHITESPACE SKIPPING |
97 | | * |
98 | | * Whitespace characters in JSON: space(0x20), tab(0x09), CR(0x0D), LF(0x0A) |
99 | | * ====================================================================== */ |
100 | | |
101 | 0 | static const char *cj_skip_ws_scalar(const char *p, const char *end) { |
102 | 0 | while (p < end) { |
103 | 0 | unsigned char c = (unsigned char)*p; |
104 | 0 | if (c != 0x20u && c != 0x09u && c != 0x0Du && c != 0x0Au) break; |
105 | 0 | ++p; |
106 | 0 | } |
107 | 0 | return p; |
108 | 0 | } |
109 | | |
110 | | #if defined(TINYGLTF_JSON_SIMD_AVX2) |
111 | | |
112 | | static const char *cj_skip_ws(const char *p, const char *end) { |
113 | | while (p + 32 <= end) { |
114 | | __m256i chunk = _mm256_loadu_si256((const __m256i *)(const void *)p); |
115 | | __m256i sp = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8(' ')); |
116 | | __m256i tab = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\t')); |
117 | | __m256i cr = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\r')); |
118 | | __m256i lf = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\n')); |
119 | | __m256i ws = _mm256_or_si256(_mm256_or_si256(sp, tab), |
120 | | _mm256_or_si256(cr, lf)); |
121 | | unsigned int mask = (unsigned int)_mm256_movemask_epi8(ws); |
122 | | if (mask != 0xFFFFFFFFu) { |
123 | | #if defined(__GNUC__) || defined(__clang__) |
124 | | return p + (int)__builtin_ctz(~mask); |
125 | | #else |
126 | | unsigned int inv = ~mask, idx = 0; |
127 | | while (!(inv & (1u << idx))) ++idx; |
128 | | return p + idx; |
129 | | #endif |
130 | | } |
131 | | p += 32; |
132 | | } |
133 | | return cj_skip_ws_scalar(p, end); |
134 | | } |
135 | | |
136 | | #elif defined(TINYGLTF_JSON_SIMD_SSE2) |
137 | | |
138 | | static const char *cj_skip_ws(const char *p, const char *end) { |
139 | | while (p + 16 <= end) { |
140 | | __m128i chunk = _mm_loadu_si128((const __m128i *)(const void *)p); |
141 | | __m128i sp = _mm_cmpeq_epi8(chunk, _mm_set1_epi8(' ')); |
142 | | __m128i tab = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\t')); |
143 | | __m128i cr = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\r')); |
144 | | __m128i lf = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\n')); |
145 | | __m128i ws = _mm_or_si128(_mm_or_si128(sp, tab), |
146 | | _mm_or_si128(cr, lf)); |
147 | | unsigned int mask = (unsigned int)_mm_movemask_epi8(ws); |
148 | | if (mask != 0xFFFFu) { |
149 | | unsigned int inv = (~mask) & 0xFFFFu; |
150 | | #if defined(__GNUC__) || defined(__clang__) |
151 | | return p + (int)__builtin_ctz(inv); |
152 | | #else |
153 | | unsigned int idx = 0; |
154 | | while (!(inv & (1u << idx))) ++idx; |
155 | | return p + idx; |
156 | | #endif |
157 | | } |
158 | | p += 16; |
159 | | } |
160 | | return cj_skip_ws_scalar(p, end); |
161 | | } |
162 | | |
163 | | #elif defined(TINYGLTF_JSON_SIMD_NEON) |
164 | | |
165 | | static const char *cj_skip_ws(const char *p, const char *end) { |
166 | | while (p + 16 <= end) { |
167 | | uint8x16_t chunk = vld1q_u8((const uint8_t *)p); |
168 | | uint8x16_t sp = vceqq_u8(chunk, vdupq_n_u8(' ')); |
169 | | uint8x16_t tab = vceqq_u8(chunk, vdupq_n_u8('\t')); |
170 | | uint8x16_t cr = vceqq_u8(chunk, vdupq_n_u8('\r')); |
171 | | uint8x16_t lf = vceqq_u8(chunk, vdupq_n_u8('\n')); |
172 | | uint8x16_t ws = vorrq_u8(vorrq_u8(sp, tab), vorrq_u8(cr, lf)); |
173 | | uint64x2_t ws64 = vreinterpretq_u64_u8(ws); |
174 | | uint64_t lo = vgetq_lane_u64(ws64, 0); |
175 | | uint64_t hi = vgetq_lane_u64(ws64, 1); |
176 | | if (lo != UINT64_C(0xFFFFFFFFFFFFFFFF) || |
177 | | hi != UINT64_C(0xFFFFFFFFFFFFFFFF)) { |
178 | | uint8_t tmp[16]; |
179 | | vst1q_u8(tmp, ws); |
180 | | for (int i = 0; i < 16; ++i) { |
181 | | if (!tmp[i]) return p + i; |
182 | | } |
183 | | } |
184 | | p += 16; |
185 | | } |
186 | | return cj_skip_ws_scalar(p, end); |
187 | | } |
188 | | |
189 | | #else |
190 | | |
191 | 0 | static const char *cj_skip_ws(const char *p, const char *end) { |
192 | 0 | return cj_skip_ws_scalar(p, end); |
193 | 0 | } |
194 | | |
195 | | #endif /* SIMD whitespace */ |
196 | | |
197 | | /* ====================================================================== |
198 | | * SIMD STRING SCANNING (find '"', '\', or control char) |
199 | | * ====================================================================== */ |
200 | | |
201 | 0 | static const char *cj_scan_str_scalar(const char *p, const char *end) { |
202 | 0 | while (p < end) { |
203 | 0 | unsigned char c = (unsigned char)*p; |
204 | 0 | if (c == '"' || c == '\\' || c < 0x20u) break; |
205 | 0 | ++p; |
206 | 0 | } |
207 | 0 | return p; |
208 | 0 | } |
209 | | |
210 | | #if defined(TINYGLTF_JSON_SIMD_AVX2) |
211 | | |
212 | | static const char *cj_scan_str(const char *p, const char *end) { |
213 | | while (p + 32 <= end) { |
214 | | __m256i chunk = _mm256_loadu_si256((const __m256i *)(const void *)p); |
215 | | __m256i eq_q = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('"')); |
216 | | __m256i eq_bs = _mm256_cmpeq_epi8(chunk, _mm256_set1_epi8('\\')); |
217 | | /* Control chars: byte <= 0x1F <=> min(byte, 0x1F) == byte */ |
218 | | __m256i ctrl = _mm256_cmpeq_epi8( |
219 | | _mm256_min_epu8(chunk, _mm256_set1_epi8(0x1F)), |
220 | | chunk); |
221 | | __m256i special = _mm256_or_si256(_mm256_or_si256(eq_q, eq_bs), ctrl); |
222 | | unsigned int mask = (unsigned int)_mm256_movemask_epi8(special); |
223 | | if (mask) { |
224 | | #if defined(__GNUC__) || defined(__clang__) |
225 | | return p + (int)__builtin_ctz(mask); |
226 | | #else |
227 | | unsigned int idx = 0; |
228 | | while (!(mask & (1u << idx))) ++idx; |
229 | | return p + idx; |
230 | | #endif |
231 | | } |
232 | | p += 32; |
233 | | } |
234 | | return cj_scan_str_scalar(p, end); |
235 | | } |
236 | | |
237 | | #elif defined(TINYGLTF_JSON_SIMD_SSE2) |
238 | | |
239 | | static const char *cj_scan_str(const char *p, const char *end) { |
240 | | while (p + 16 <= end) { |
241 | | __m128i chunk = _mm_loadu_si128((const __m128i *)(const void *)p); |
242 | | __m128i eq_q = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('"')); |
243 | | __m128i eq_bs = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\\')); |
244 | | __m128i ctrl = _mm_cmpeq_epi8( |
245 | | _mm_min_epu8(chunk, _mm_set1_epi8(0x1F)), |
246 | | chunk); |
247 | | __m128i special = _mm_or_si128(_mm_or_si128(eq_q, eq_bs), ctrl); |
248 | | unsigned int mask = (unsigned int)_mm_movemask_epi8(special); |
249 | | if (mask) { |
250 | | #if defined(__GNUC__) || defined(__clang__) |
251 | | return p + (int)__builtin_ctz(mask); |
252 | | #else |
253 | | unsigned int idx = 0; |
254 | | while (!(mask & (1u << idx))) ++idx; |
255 | | return p + idx; |
256 | | #endif |
257 | | } |
258 | | p += 16; |
259 | | } |
260 | | return cj_scan_str_scalar(p, end); |
261 | | } |
262 | | |
263 | | #elif defined(TINYGLTF_JSON_SIMD_NEON) |
264 | | |
265 | | static const char *cj_scan_str(const char *p, const char *end) { |
266 | | uint8x16_t vquote = vdupq_n_u8('"'); |
267 | | uint8x16_t vbslash = vdupq_n_u8('\\'); |
268 | | uint8x16_t v20 = vdupq_n_u8(0x20u); |
269 | | while (p + 16 <= end) { |
270 | | uint8x16_t chunk = vld1q_u8((const uint8_t *)p); |
271 | | uint8x16_t eq_q = vceqq_u8(chunk, vquote); |
272 | | uint8x16_t eq_bs = vceqq_u8(chunk, vbslash); |
273 | | uint8x16_t ctrl = vcltq_u8(chunk, v20); |
274 | | uint8x16_t special = vorrq_u8(vorrq_u8(eq_q, eq_bs), ctrl); |
275 | | uint64x2_t s64 = vreinterpretq_u64_u8(special); |
276 | | if (vgetq_lane_u64(s64, 0) || vgetq_lane_u64(s64, 1)) { |
277 | | uint8_t tmp[16]; |
278 | | vst1q_u8(tmp, special); |
279 | | for (int i = 0; i < 16; ++i) { |
280 | | if (tmp[i]) return p + i; |
281 | | } |
282 | | } |
283 | | p += 16; |
284 | | } |
285 | | return cj_scan_str_scalar(p, end); |
286 | | } |
287 | | |
288 | | #else |
289 | | |
290 | 0 | static const char *cj_scan_str(const char *p, const char *end) { |
291 | 0 | return cj_scan_str_scalar(p, end); |
292 | 0 | } |
293 | | |
294 | | #endif /* SIMD string scan */ |
295 | | |
296 | | /* ====================================================================== |
297 | | * FAST NUMBER PARSING (C-style) |
298 | | * |
299 | | * Uses Clinger's fast path for float conversion, avoiding strtod() for the |
300 | | * vast majority of JSON numbers. The fast path itself is locale-independent |
301 | | * and typically 4-10x faster than strtod; however, rare fallback paths may |
302 | | * still invoke the C library's strtod(), which can be locale-dependent. |
303 | | * |
304 | | * Optional float32 mode (CJ_FLOAT32_MODE flag in cj_parse_number): |
305 | | * Parses floating-point values to float (single) precision and stores |
306 | | * the result as double. Faster because fewer significant digits are |
307 | | * needed and the fast path covers a wider exponent range. |
308 | | * Breaks strict JSON/IEEE-754-double conformance. |
309 | | * ====================================================================== */ |
310 | | |
311 | | /* Safe double-to-int64 cast: returns 0 for NaN; clamps +inf/out-of-range-high |
312 | | * to INT64_MAX and -inf/out-of-range-low to INT64_MIN. */ |
313 | 0 | static int64_t cj_dbl_to_i64(double d) { |
314 | 0 | if (d != d) return 0; /* NaN */ |
315 | 0 | if (d >= (double)INT64_MAX) return INT64_MAX; |
316 | 0 | if (d <= (double)INT64_MIN) return INT64_MIN; |
317 | 0 | return (int64_t)d; |
318 | 0 | } |
319 | | |
320 | | /* Exact powers of 10 that are representable as IEEE 754 double. |
321 | | * 10^0 through 10^22 are all exactly representable. */ |
322 | | static const double cj_exact_pow10[23] = { |
323 | | 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, |
324 | | 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, |
325 | | 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 |
326 | | }; |
327 | | |
328 | | /* Clinger's fast path: mantissa * 10^exp10 → double. |
329 | | * Requires mantissa <= 2^53 (exactly representable as double). |
330 | | * Returns 1 on success, 0 if fallback needed. */ |
331 | 0 | static int cj_fast_dbl_convert(uint64_t mantissa, int exp10, int neg, double *out) { |
332 | 0 | if (mantissa == 0) { |
333 | 0 | *out = neg ? -0.0 : 0.0; |
334 | 0 | return 1; |
335 | 0 | } |
336 | | |
337 | | /* Primary: |exp10| <= 22, mantissa fits in double mantissa bits */ |
338 | 0 | if (mantissa <= (1ULL << 53)) { |
339 | 0 | double d; |
340 | 0 | if (exp10 >= 0 && exp10 <= 22) { |
341 | 0 | d = (double)mantissa * cj_exact_pow10[exp10]; |
342 | 0 | *out = neg ? -d : d; |
343 | 0 | return 1; |
344 | 0 | } |
345 | 0 | if (exp10 < 0 && exp10 >= -22) { |
346 | 0 | d = (double)mantissa / cj_exact_pow10[-exp10]; |
347 | 0 | *out = neg ? -d : d; |
348 | 0 | return 1; |
349 | 0 | } |
350 | | /* Extended: split exponent into two steps, each <= 22. |
351 | | * Positive: exp10 = 22 + remainder, both halves exact. |
352 | | * Negative: exp10 = -22 + remainder. */ |
353 | 0 | if (exp10 > 22 && exp10 <= 22 + 22) { |
354 | 0 | d = (double)mantissa * cj_exact_pow10[exp10 - 22]; |
355 | 0 | d *= cj_exact_pow10[22]; |
356 | 0 | *out = neg ? -d : d; |
357 | 0 | return 1; |
358 | 0 | } |
359 | 0 | if (exp10 < -22 && exp10 >= -(22 + 22)) { |
360 | 0 | d = (double)mantissa / cj_exact_pow10[-exp10 - 22]; |
361 | 0 | d /= cj_exact_pow10[22]; |
362 | 0 | *out = neg ? -d : d; |
363 | 0 | return 1; |
364 | 0 | } |
365 | 0 | } |
366 | | |
367 | 0 | return 0; |
368 | 0 | } |
369 | | |
370 | | /* Fast path for float32: wider range because float mantissa is only 24 bits. */ |
371 | 0 | static int cj_fast_flt_convert(uint64_t mantissa, int exp10, int neg, float *out) { |
372 | 0 | if (mantissa == 0) { |
373 | 0 | *out = neg ? -0.0f : 0.0f; |
374 | 0 | return 1; |
375 | 0 | } |
376 | | |
377 | | /* Direct float path: mantissa fits in 24 bits, pow10 exact in float */ |
378 | 0 | if (mantissa <= (1ULL << 24)) { |
379 | 0 | if (exp10 >= 0 && exp10 <= 10) { |
380 | 0 | float f = (float)mantissa * (float)cj_exact_pow10[exp10]; |
381 | 0 | *out = neg ? -f : f; |
382 | 0 | return 1; |
383 | 0 | } |
384 | 0 | if (exp10 < 0 && exp10 >= -10) { |
385 | 0 | float f = (float)mantissa / (float)cj_exact_pow10[-exp10]; |
386 | 0 | *out = neg ? -f : f; |
387 | 0 | return 1; |
388 | 0 | } |
389 | 0 | } |
390 | | |
391 | | /* Wider path via double arithmetic (still float-precision result) */ |
392 | 0 | if (mantissa <= (1ULL << 53)) { |
393 | 0 | double d; |
394 | 0 | if (exp10 >= 0 && exp10 <= 22) { |
395 | 0 | d = (double)mantissa * cj_exact_pow10[exp10]; |
396 | 0 | *out = neg ? -(float)d : (float)d; |
397 | 0 | return 1; |
398 | 0 | } |
399 | 0 | if (exp10 < 0 && exp10 >= -22) { |
400 | 0 | d = (double)mantissa / cj_exact_pow10[-exp10]; |
401 | 0 | *out = neg ? -(float)d : (float)d; |
402 | 0 | return 1; |
403 | 0 | } |
404 | 0 | if (exp10 > 22 && exp10 <= 44) { |
405 | 0 | d = (double)mantissa * cj_exact_pow10[exp10 - 22]; |
406 | 0 | d *= cj_exact_pow10[22]; |
407 | 0 | *out = neg ? -(float)d : (float)d; |
408 | 0 | return 1; |
409 | 0 | } |
410 | 0 | if (exp10 < -22 && exp10 >= -44) { |
411 | 0 | d = (double)mantissa / cj_exact_pow10[-exp10 - 22]; |
412 | 0 | d /= cj_exact_pow10[22]; |
413 | 0 | *out = neg ? -(float)d : (float)d; |
414 | 0 | return 1; |
415 | 0 | } |
416 | 0 | } |
417 | | |
418 | 0 | return 0; |
419 | 0 | } |
420 | | |
421 | | /* Parse a JSON number starting at [p, end). |
422 | | * Sets *is_int, *ival (integer result), *dval (floating-point result). |
423 | | * Returns pointer past the last character consumed, or NULL on error. |
424 | | * |
425 | | * float32_mode: when non-zero, floating-point values are parsed at float |
426 | | * (single) precision — only 9 significant digits are tracked for the |
427 | | * fraction part, and the result is stored as (double)(float)value. This |
428 | | * is faster but not JSON-conformant for high-precision doubles. Integer- |
429 | | * only tokens (no '.'/'e') are always parsed at full int64 precision |
430 | | * regardless of this flag. |
431 | | * |
432 | | * Uses Clinger's fast path (no strtod) for ~99% of JSON float values. |
433 | | * Falls back to strtod only for extreme exponents or >19 significant digits. */ |
434 | | static const char *cj_parse_number(const char *p, const char *end, |
435 | | int *is_int, int64_t *ival, double *dval, |
436 | 0 | int float32_mode) { |
437 | 0 | const char *start = p; |
438 | 0 | int neg = 0; |
439 | 0 | if (p < end && *p == '-') { neg = 1; ++p; } |
440 | 0 | if (p >= end) return NULL; |
441 | | |
442 | | /* Accumulate ALL digits (integer + fraction) into a single mantissa. |
443 | | * Track the decimal exponent adjustment from the '.' position. */ |
444 | 0 | uint64_t mantissa = 0; |
445 | 0 | int ndigits = 0; /* total significant digits consumed */ |
446 | 0 | int exp10 = 0; /* decimal exponent adjustment */ |
447 | 0 | int mantissa_overflow = 0; /* set if >19 significant digits */ |
448 | 0 | int has_frac = 0, has_exp = 0; |
449 | | |
450 | | /* Max significant digits we track: |
451 | | * Integer part: always 19, so integer-only tokens (no '.'/'e') are always |
452 | | * accumulated fully and can be typed as int64 regardless of float32_mode. |
453 | | * Fraction part: 9 in float32_mode (single precision), 19 otherwise. */ |
454 | 0 | int max_sig_int = 19; |
455 | 0 | int max_sig_frac = float32_mode ? 9 : 19; |
456 | | |
457 | | /* Integer part */ |
458 | 0 | if (*p == '0') { |
459 | 0 | ++p; |
460 | 0 | } else if ((unsigned)(*p - '1') <= 8u) { |
461 | 0 | while (p < end && (unsigned)(*p - '0') <= 9u) { |
462 | 0 | unsigned d = (unsigned)(*p - '0'); |
463 | 0 | if (ndigits < max_sig_int) { |
464 | 0 | mantissa = mantissa * 10 + d; |
465 | 0 | } else { |
466 | 0 | exp10++; /* excess digit: bump exponent instead */ |
467 | 0 | if (ndigits >= 19) mantissa_overflow = 1; |
468 | 0 | } |
469 | 0 | ndigits++; |
470 | 0 | ++p; |
471 | 0 | } |
472 | 0 | } else { |
473 | 0 | return NULL; |
474 | 0 | } |
475 | | |
476 | | /* Fraction part */ |
477 | 0 | if (p < end && *p == '.') { |
478 | 0 | has_frac = 1; |
479 | 0 | ++p; |
480 | | /* JSON requires at least one digit after '.' */ |
481 | 0 | if (p >= end || (unsigned)(*p - '0') > 9u) return NULL; |
482 | 0 | while (p < end && (unsigned)(*p - '0') <= 9u) { |
483 | 0 | unsigned d = (unsigned)(*p - '0'); |
484 | 0 | if (ndigits < max_sig_frac) { |
485 | 0 | mantissa = mantissa * 10 + d; |
486 | 0 | exp10--; |
487 | 0 | } |
488 | | /* else: ignore trailing fraction digits beyond precision */ |
489 | 0 | ndigits++; |
490 | 0 | ++p; |
491 | 0 | } |
492 | 0 | } |
493 | | |
494 | | /* Exponent part */ |
495 | 0 | if (p < end && (*p == 'e' || *p == 'E')) { |
496 | 0 | has_exp = 1; |
497 | 0 | ++p; |
498 | 0 | int exp_neg = 0; |
499 | 0 | if (p < end && *p == '+') ++p; |
500 | 0 | else if (p < end && *p == '-') { exp_neg = 1; ++p; } |
501 | | /* JSON requires at least one digit in exponent */ |
502 | 0 | if (p >= end || (unsigned)(*p - '0') > 9u) return NULL; |
503 | 0 | int exp_val = 0; |
504 | 0 | while (p < end && (unsigned)(*p - '0') <= 9u) { |
505 | 0 | exp_val = exp_val * 10 + (*p - '0'); |
506 | 0 | if (exp_val > 9999) { |
507 | | /* Prevent overflow; will fall through to strtod */ |
508 | 0 | while (p < end && (unsigned)(*p - '0') <= 9u) ++p; |
509 | 0 | break; |
510 | 0 | } |
511 | 0 | ++p; |
512 | 0 | } |
513 | 0 | exp10 += exp_neg ? -exp_val : exp_val; |
514 | 0 | } |
515 | | |
516 | | /* ---- Integer fast path (no fraction, no exponent, fits int64) ---- */ |
517 | | /* exp10 == 0 ensures all digits were accumulated (none truncated by max_sig) */ |
518 | 0 | if (!has_frac && !has_exp && !mantissa_overflow && exp10 == 0) { |
519 | 0 | uint64_t mag = mantissa; |
520 | 0 | int fits; |
521 | 0 | if (!neg) |
522 | 0 | fits = (mag <= (uint64_t)INT64_MAX); |
523 | 0 | else |
524 | 0 | fits = (mag <= (uint64_t)INT64_MAX + 1u); |
525 | 0 | if (fits) { |
526 | 0 | int64_t sv; |
527 | 0 | if (neg && mag == (uint64_t)INT64_MAX + 1u) |
528 | 0 | sv = INT64_MIN; |
529 | 0 | else |
530 | 0 | sv = neg ? -(int64_t)mag : (int64_t)mag; |
531 | 0 | *is_int = 1; |
532 | 0 | *ival = sv; |
533 | 0 | *dval = (double)sv; |
534 | 0 | return p; |
535 | 0 | } |
536 | 0 | } |
537 | | |
538 | | /* ---- Float fast path (Clinger's algorithm) ---- */ |
539 | 0 | if (!mantissa_overflow) { |
540 | 0 | if (float32_mode) { |
541 | 0 | float f; |
542 | 0 | if (cj_fast_flt_convert(mantissa, exp10, neg, &f)) { |
543 | 0 | *is_int = 0; |
544 | 0 | *dval = (double)f; |
545 | 0 | *ival = cj_dbl_to_i64((double)f); |
546 | 0 | return p; |
547 | 0 | } |
548 | 0 | } else { |
549 | 0 | double d; |
550 | 0 | if (cj_fast_dbl_convert(mantissa, exp10, neg, &d)) { |
551 | 0 | *is_int = 0; |
552 | 0 | *dval = d; |
553 | 0 | *ival = cj_dbl_to_i64(d); |
554 | 0 | return p; |
555 | 0 | } |
556 | 0 | } |
557 | 0 | } |
558 | | |
559 | | /* ---- Fallback: strtod (handles extreme exponents, >19 digits) ---- */ |
560 | 0 | char *eptr = NULL; |
561 | 0 | double d = strtod(start, &eptr); |
562 | 0 | if (eptr == start) return NULL; |
563 | 0 | if (float32_mode) d = (double)(float)d; |
564 | 0 | *is_int = 0; |
565 | 0 | *dval = d; |
566 | 0 | *ival = cj_dbl_to_i64(d); |
567 | 0 | return eptr; |
568 | 0 | } |
569 | | |
570 | | /* ====================================================================== |
571 | | * STRING UNESCAPING (C-style) |
572 | | * ====================================================================== */ |
573 | | |
574 | 0 | static int cj_hex4(const char *p) { |
575 | 0 | int v = 0; |
576 | 0 | for (int i = 0; i < 4; ++i) { |
577 | 0 | char c = p[i]; |
578 | 0 | int d; |
579 | 0 | if (c >= '0' && c <= '9') d = c - '0'; |
580 | 0 | else if (c >= 'a' && c <= 'f') d = c - 'a' + 10; |
581 | 0 | else if (c >= 'A' && c <= 'F') d = c - 'A' + 10; |
582 | 0 | else return -1; |
583 | 0 | v = (v << 4) | d; |
584 | 0 | } |
585 | 0 | return v; |
586 | 0 | } |
587 | | |
588 | 0 | static int cj_encode_utf8(unsigned int cp, char *buf) { |
589 | 0 | if (cp <= 0x7Fu) { |
590 | 0 | buf[0] = (char)cp; |
591 | 0 | return 1; |
592 | 0 | } else if (cp <= 0x7FFu) { |
593 | 0 | buf[0] = (char)(0xC0u | (cp >> 6)); |
594 | 0 | buf[1] = (char)(0x80u | (cp & 0x3Fu)); |
595 | 0 | return 2; |
596 | 0 | } else if (cp <= 0xFFFFu) { |
597 | 0 | buf[0] = (char)(0xE0u | (cp >> 12)); |
598 | 0 | buf[1] = (char)(0x80u | ((cp >> 6) & 0x3Fu)); |
599 | 0 | buf[2] = (char)(0x80u | (cp & 0x3Fu)); |
600 | 0 | return 3; |
601 | 0 | } else if (cp <= 0x10FFFFu) { |
602 | 0 | buf[0] = (char)(0xF0u | (cp >> 18)); |
603 | 0 | buf[1] = (char)(0x80u | ((cp >> 12) & 0x3Fu)); |
604 | 0 | buf[2] = (char)(0x80u | ((cp >> 6) & 0x3Fu)); |
605 | 0 | buf[3] = (char)(0x80u | (cp & 0x3Fu)); |
606 | 0 | return 4; |
607 | 0 | } |
608 | 0 | return 0; |
609 | 0 | } |
610 | | |
611 | | /* |
612 | | * Parse and unescape a JSON string from [p, end) where p is AFTER the |
613 | | * opening '"' and end is the INCLUSIVE closing '"'. |
614 | | * Caller must free() the returned pointer. |
615 | | * Returns NULL on allocation failure. |
616 | | */ |
617 | | static char *cj_unescape_string(const char *p, const char *str_end, |
618 | 0 | size_t *out_len) { |
619 | 0 | size_t alloc = (size_t)(str_end - p) + 1; |
620 | 0 | char *out = (char *)malloc(alloc); |
621 | 0 | if (!out) return NULL; |
622 | 0 | char *dst = out; |
623 | |
|
624 | 0 | while (p < str_end) { |
625 | | /* Track start of literal run so we can copy it before processing |
626 | | * the special character that ends it. */ |
627 | 0 | const char *run = p; |
628 | 0 | p = cj_scan_str(p, str_end); |
629 | | /* Copy non-special (literal) bytes from run to p */ |
630 | 0 | if (p > run) { |
631 | 0 | size_t n = (size_t)(p - run); |
632 | 0 | memcpy(dst, run, n); |
633 | 0 | dst += n; |
634 | 0 | } |
635 | 0 | if (p >= str_end) break; |
636 | | |
637 | 0 | unsigned char c = (unsigned char)*p; |
638 | 0 | if (c == '"') { |
639 | 0 | break; /* should not happen - caller passes str_end=position of '"' */ |
640 | 0 | } else if (c == '\\') { |
641 | 0 | ++p; |
642 | 0 | if (p >= str_end) { free(out); return NULL; } |
643 | 0 | unsigned char esc = (unsigned char)*p++; |
644 | 0 | switch (esc) { |
645 | 0 | case '"': *dst++ = '"'; break; |
646 | 0 | case '\\': *dst++ = '\\'; break; |
647 | 0 | case '/': *dst++ = '/'; break; |
648 | 0 | case 'b': *dst++ = '\b'; break; |
649 | 0 | case 'f': *dst++ = '\f'; break; |
650 | 0 | case 'n': *dst++ = '\n'; break; |
651 | 0 | case 'r': *dst++ = '\r'; break; |
652 | 0 | case 't': *dst++ = '\t'; break; |
653 | 0 | case 'u': { |
654 | 0 | if (p + 4 > str_end) { free(out); return NULL; } |
655 | 0 | int cp = cj_hex4(p); |
656 | 0 | if (cp < 0) { free(out); return NULL; } |
657 | 0 | p += 4; |
658 | 0 | if (cp >= 0xD800 && cp <= 0xDBFF && |
659 | 0 | p + 6 <= str_end && p[0] == '\\' && p[1] == 'u') { |
660 | 0 | int cp2 = cj_hex4(p + 2); |
661 | 0 | if (cp2 >= 0xDC00 && cp2 <= 0xDFFF) { |
662 | 0 | unsigned int full = 0x10000u + |
663 | 0 | (((unsigned int)cp - 0xD800u) << 10) + |
664 | 0 | ((unsigned int)cp2 - 0xDC00u); |
665 | 0 | dst += cj_encode_utf8(full, dst); |
666 | 0 | p += 6; |
667 | 0 | break; |
668 | 0 | } |
669 | 0 | } |
670 | 0 | dst += cj_encode_utf8((unsigned int)cp, dst); |
671 | 0 | break; |
672 | 0 | } |
673 | 0 | default: |
674 | | /* Unknown escape sequence is invalid in JSON */ |
675 | 0 | free(out); |
676 | 0 | return NULL; |
677 | 0 | } |
678 | 0 | } else if (c < 0x20u) { |
679 | | /* Invalid unescaped control character in JSON string: treat as error */ |
680 | 0 | free(out); |
681 | 0 | return NULL; |
682 | 0 | } else { |
683 | | /* Should not be reached since scan_str stops here only for |
684 | | special chars - but guard just in case */ |
685 | 0 | *dst++ = (char)c; |
686 | 0 | ++p; |
687 | 0 | } |
688 | 0 | } |
689 | | |
690 | 0 | *dst = '\0'; |
691 | 0 | *out_len = (size_t)(dst - out); |
692 | 0 | return out; |
693 | 0 | } |
694 | | |
695 | | /* ====================================================================== |
696 | | * FORWARD DECLARATIONS |
697 | | * ====================================================================== */ |
698 | | |
699 | | /* |
700 | | * tinygltf_json is the main JSON value class. |
701 | | * Its data layout is C-style (all members public, named with trailing _). |
702 | | */ |
703 | | class tinygltf_json; |
704 | | |
705 | | /* |
706 | | * tinygltf_json_member stores one key-value pair in a JSON object. |
707 | | * Must be defined AFTER tinygltf_json is complete (contains json by value). |
708 | | */ |
709 | | struct tinygltf_json_member; |
710 | | |
711 | | /* ====================================================================== |
712 | | * tinygltf_json CLASS DECLARATION |
713 | | * |
714 | | * All data members are public (C-style struct convention). |
715 | | * Methods are declared here and implemented after tinygltf_json_member |
716 | | * is fully defined. |
717 | | * ====================================================================== */ |
718 | | |
719 | | class tinygltf_json { |
720 | | public: |
721 | | /* ------------------------------------------------------------------ |
722 | | * nlohmann-compatible value type enum |
723 | | * ------------------------------------------------------------------ */ |
724 | | enum class value_t : uint8_t { |
725 | | null = 0, |
726 | | boolean = 1, |
727 | | number_integer = 2, |
728 | | number_unsigned = 3, |
729 | | number_float = 4, |
730 | | string = 5, |
731 | | array = 6, |
732 | | object = 7, |
733 | | /* Aliases from nlohmann/json that tinygltf.h references */ |
734 | | discarded = 8, |
735 | | binary = 9 |
736 | | }; |
737 | | |
738 | | /* ------------------------------------------------------------------ |
739 | | * C-style data storage (all public for direct access from C functions) |
740 | | * ------------------------------------------------------------------ */ |
741 | | |
742 | | /* Type tag: one of CJ_NULL, CJ_BOOL, CJ_INT, CJ_REAL, CJ_STRING, |
743 | | CJ_ARRAY, CJ_OBJECT */ |
744 | | int type_; |
745 | | |
746 | | /* Primitive values (union for space efficiency) */ |
747 | | union { |
748 | | int64_t i_; /* CJ_INT */ |
749 | | double d_; /* CJ_REAL */ |
750 | | int b_; /* CJ_BOOL: 0 or 1 */ |
751 | | }; |
752 | | |
753 | | /* String storage */ |
754 | | char *str_; /* CJ_STRING: owned, null-terminated */ |
755 | | size_t str_len_; |
756 | | |
757 | | /* Array storage: flat array of tinygltf_json objects (owned) */ |
758 | | tinygltf_json *arr_data_; |
759 | | size_t arr_size_; |
760 | | size_t arr_cap_; |
761 | | |
762 | | /* Object storage: flat array of tinygltf_json_member (owned) */ |
763 | | tinygltf_json_member *obj_data_; |
764 | | size_t obj_size_; |
765 | | size_t obj_cap_; |
766 | | |
767 | | /* ------------------------------------------------------------------ |
768 | | * Iterator type (forward-declared here, defined later) |
769 | | * ------------------------------------------------------------------ */ |
770 | | class iterator; |
771 | | using const_iterator = iterator; |
772 | | |
773 | | /* ------------------------------------------------------------------ |
774 | | * Low-level helpers (implementations deferred until member is complete) |
775 | | * ------------------------------------------------------------------ */ |
776 | | void init_null_(); |
777 | | void destroy_(); |
778 | | void copy_from_(const tinygltf_json &o); |
779 | | tinygltf_json_member *find_member_(const char *key) const; |
780 | | int obj_reserve_(); |
781 | | int arr_reserve_(); |
782 | | void make_object_(); |
783 | | void make_array_(); |
784 | | |
785 | | /* ------------------------------------------------------------------ |
786 | | * Constructors and destructor |
787 | | * ------------------------------------------------------------------ */ |
788 | | tinygltf_json(); |
789 | | tinygltf_json(std::nullptr_t); |
790 | | tinygltf_json(bool b); |
791 | | tinygltf_json(int i); |
792 | | tinygltf_json(int64_t i); |
793 | | tinygltf_json(uint64_t u); |
794 | | tinygltf_json(double d); |
795 | | tinygltf_json(float f); |
796 | | tinygltf_json(const char *s); |
797 | | tinygltf_json(const std::string &s); |
798 | | tinygltf_json(const tinygltf_json &o); |
799 | | tinygltf_json(tinygltf_json &&o) noexcept; |
800 | | ~tinygltf_json(); |
801 | | |
802 | | tinygltf_json &operator=(const tinygltf_json &o); |
803 | | tinygltf_json &operator=(tinygltf_json &&o) noexcept; |
804 | | |
805 | | /* ------------------------------------------------------------------ |
806 | | * Type checks (nlohmann-compatible) |
807 | | * ------------------------------------------------------------------ */ |
808 | | value_t type() const; |
809 | 0 | bool is_null() const { return type_ == CJ_NULL; } |
810 | 0 | bool is_boolean() const { return type_ == CJ_BOOL; } |
811 | 0 | bool is_number() const { return type_ == CJ_INT || type_ == CJ_REAL; } |
812 | 0 | bool is_number_integer() const { return type_ == CJ_INT; } |
813 | 0 | bool is_number_unsigned() const { return type_ == CJ_INT && i_ >= 0; } |
814 | 0 | bool is_number_float() const { return type_ == CJ_REAL; } |
815 | 0 | bool is_string() const { return type_ == CJ_STRING; } |
816 | 0 | bool is_array() const { return type_ == CJ_ARRAY; } |
817 | 0 | bool is_object() const { return type_ == CJ_OBJECT; } |
818 | | |
819 | | /* ------------------------------------------------------------------ |
820 | | * Value access (template specializations after class) |
821 | | * ------------------------------------------------------------------ */ |
822 | | template<typename T> T get() const; |
823 | | |
824 | | /* ------------------------------------------------------------------ |
825 | | * Container methods |
826 | | * ------------------------------------------------------------------ */ |
827 | | size_t size() const; |
828 | | bool empty() const; |
829 | | |
830 | | /* ------------------------------------------------------------------ |
831 | | * Array operations |
832 | | * ------------------------------------------------------------------ */ |
833 | | void push_back(tinygltf_json &&v); |
834 | | void push_back(const tinygltf_json &v); |
835 | | /* Ensure value is an array (no-op if already array). */ |
836 | 0 | void set_array() { if (type_ != CJ_ARRAY) make_array_(); } |
837 | | |
838 | | /* ------------------------------------------------------------------ |
839 | | * Object operations |
840 | | * ------------------------------------------------------------------ */ |
841 | | tinygltf_json &operator[](const char *key); |
842 | | tinygltf_json &operator[](const std::string &key); |
843 | | |
844 | | /* ------------------------------------------------------------------ |
845 | | * Iterators |
846 | | * ------------------------------------------------------------------ */ |
847 | | iterator begin(); |
848 | | iterator end(); |
849 | | iterator begin() const; |
850 | | iterator end() const; |
851 | | iterator find(const char *key) const; |
852 | | iterator find(const char *key); |
853 | | void erase(iterator &it); |
854 | | |
855 | | /* ------------------------------------------------------------------ |
856 | | * Static factories |
857 | | * ------------------------------------------------------------------ */ |
858 | | static tinygltf_json object(); |
859 | | |
860 | | /* ------------------------------------------------------------------ |
861 | | * Serialization / deserialization |
862 | | * ------------------------------------------------------------------ */ |
863 | | std::string dump(int indent = -1) const; |
864 | | |
865 | | /* allow_exceptions is honoured only when TINYGLTF_JSON_USE_EXCEPTIONS is |
866 | | * defined; otherwise it is accepted for API compatibility but has no |
867 | | * effect — parse errors always return a null value silently. */ |
868 | | static tinygltf_json parse(const char *first, const char *last, |
869 | | std::nullptr_t = nullptr, |
870 | | bool allow_exceptions = false); |
871 | | |
872 | | /* Parse with float32 mode: floating-point values are parsed at single |
873 | | * precision for speed. Breaks strict JSON double-precision conformance |
874 | | * but sufficient for glTF (which stores geometry/animation data as |
875 | | * single-precision floats in buffers anyway). */ |
876 | | static tinygltf_json parse_float32(const char *first, const char *last); |
877 | | }; |
878 | | |
879 | | /* ====================================================================== |
880 | | * tinygltf_json_member FULL DEFINITION |
881 | | * (tinygltf_json must be complete before this) |
882 | | * ====================================================================== */ |
883 | | |
884 | | struct tinygltf_json_member { |
885 | | char *key; /* owned, null-terminated */ |
886 | | size_t key_len; |
887 | | tinygltf_json val; /* value stored inline */ |
888 | | |
889 | 0 | tinygltf_json_member() : key(NULL), key_len(0), val() {} |
890 | 0 | ~tinygltf_json_member() { free(key); key = NULL; } |
891 | | |
892 | | tinygltf_json_member(const tinygltf_json_member &o) |
893 | 0 | : key(NULL), key_len(o.key_len), val(o.val) { |
894 | 0 | if (o.key) { |
895 | 0 | key = (char *)malloc(o.key_len + 1); |
896 | 0 | if (key) memcpy(key, o.key, o.key_len + 1); |
897 | 0 | else key_len = 0; /* malloc failure: keep key==NULL, len==0 */ |
898 | 0 | } |
899 | 0 | } |
900 | | |
901 | | tinygltf_json_member(tinygltf_json_member &&o) noexcept |
902 | 0 | : key(o.key), key_len(o.key_len), |
903 | 0 | val(static_cast<tinygltf_json &&>(o.val)) { |
904 | 0 | o.key = NULL; |
905 | 0 | o.key_len = 0; |
906 | 0 | } |
907 | | |
908 | 0 | tinygltf_json_member &operator=(const tinygltf_json_member &o) { |
909 | 0 | if (this != &o) { |
910 | 0 | free(key); |
911 | 0 | key = NULL; |
912 | 0 | key_len = o.key_len; |
913 | 0 | val = o.val; |
914 | 0 | if (o.key) { |
915 | 0 | key = (char *)malloc(o.key_len + 1); |
916 | 0 | if (key) memcpy(key, o.key, o.key_len + 1); |
917 | 0 | else key_len = 0; /* malloc failure: keep key==NULL, len==0 */ |
918 | 0 | } |
919 | 0 | } |
920 | 0 | return *this; |
921 | 0 | } |
922 | | |
923 | 0 | tinygltf_json_member &operator=(tinygltf_json_member &&o) noexcept { |
924 | 0 | if (this != &o) { |
925 | 0 | free(key); |
926 | 0 | key = o.key; |
927 | 0 | key_len = o.key_len; |
928 | 0 | val = static_cast<tinygltf_json &&>(o.val); |
929 | 0 | o.key = NULL; |
930 | 0 | o.key_len = 0; |
931 | 0 | } |
932 | 0 | return *this; |
933 | 0 | } |
934 | | }; |
935 | | |
936 | | /* ====================================================================== |
937 | | * tinygltf_json::iterator |
938 | | * ====================================================================== */ |
939 | | |
940 | | class tinygltf_json::iterator { |
941 | | public: |
942 | | static const int MODE_ARRAY = 0; |
943 | | static const int MODE_OBJECT = 1; |
944 | | |
945 | | int mode_; |
946 | | union { |
947 | | tinygltf_json *arr_ptr_; |
948 | | tinygltf_json_member *obj_ptr_; |
949 | | }; |
950 | | |
951 | 0 | iterator() : mode_(MODE_ARRAY), arr_ptr_(NULL) {} |
952 | | |
953 | | explicit iterator(tinygltf_json *p) |
954 | 0 | : mode_(MODE_ARRAY), arr_ptr_(p) {} |
955 | | |
956 | | explicit iterator(tinygltf_json_member *p) |
957 | 0 | : mode_(MODE_OBJECT), obj_ptr_(p) {} |
958 | | |
959 | | /* Pre-increment */ |
960 | 0 | iterator &operator++() { |
961 | 0 | if (mode_ == MODE_ARRAY) ++arr_ptr_; |
962 | 0 | else ++obj_ptr_; |
963 | 0 | return *this; |
964 | 0 | } |
965 | | |
966 | | /* Post-increment */ |
967 | 0 | iterator operator++(int) { |
968 | 0 | iterator tmp = *this; |
969 | 0 | ++(*this); |
970 | 0 | return tmp; |
971 | 0 | } |
972 | | |
973 | 0 | tinygltf_json &operator*() { |
974 | 0 | return (mode_ == MODE_ARRAY) ? *arr_ptr_ : obj_ptr_->val; |
975 | 0 | } |
976 | 0 | const tinygltf_json &operator*() const { |
977 | 0 | return (mode_ == MODE_ARRAY) ? *arr_ptr_ : obj_ptr_->val; |
978 | 0 | } |
979 | 0 | tinygltf_json *operator->() { |
980 | 0 | return (mode_ == MODE_ARRAY) ? arr_ptr_ : &obj_ptr_->val; |
981 | 0 | } |
982 | 0 | const tinygltf_json *operator->() const { |
983 | 0 | return (mode_ == MODE_ARRAY) ? arr_ptr_ : &obj_ptr_->val; |
984 | 0 | } |
985 | | |
986 | 0 | std::string key() const { |
987 | 0 | if (mode_ == MODE_OBJECT && obj_ptr_ && obj_ptr_->key) |
988 | 0 | return std::string(obj_ptr_->key, obj_ptr_->key_len); |
989 | 0 | return std::string(); |
990 | 0 | } |
991 | | |
992 | 0 | tinygltf_json &value() { |
993 | 0 | return operator*(); |
994 | 0 | } |
995 | 0 | const tinygltf_json &value() const { |
996 | 0 | return operator*(); |
997 | 0 | } |
998 | | |
999 | 0 | bool operator==(const iterator &o) const { |
1000 | 0 | if (mode_ != o.mode_) return false; |
1001 | 0 | return (mode_ == MODE_ARRAY) |
1002 | 0 | ? (arr_ptr_ == o.arr_ptr_) |
1003 | 0 | : (obj_ptr_ == o.obj_ptr_); |
1004 | 0 | } |
1005 | 0 | bool operator!=(const iterator &o) const { return !(*this == o); } |
1006 | | }; |
1007 | | |
1008 | | /* ====================================================================== |
1009 | | * tinygltf_json METHOD IMPLEMENTATIONS |
1010 | | * (Now that tinygltf_json_member is fully defined) |
1011 | | * ====================================================================== */ |
1012 | | |
1013 | 0 | inline void tinygltf_json::init_null_() { |
1014 | 0 | type_ = CJ_NULL; |
1015 | 0 | i_ = 0; |
1016 | 0 | str_ = NULL; str_len_ = 0; |
1017 | 0 | arr_data_ = NULL; arr_size_ = 0; arr_cap_ = 0; |
1018 | 0 | obj_data_ = NULL; obj_size_ = 0; obj_cap_ = 0; |
1019 | 0 | } |
1020 | | |
1021 | 0 | inline void tinygltf_json::destroy_() { |
1022 | 0 | if (type_ == CJ_STRING) { |
1023 | 0 | free(str_); |
1024 | 0 | str_ = NULL; |
1025 | 0 | } else if (type_ == CJ_ARRAY) { |
1026 | 0 | for (size_t i = 0; i < arr_size_; ++i) |
1027 | 0 | arr_data_[i].~tinygltf_json(); |
1028 | 0 | free(arr_data_); |
1029 | 0 | arr_data_ = NULL; |
1030 | 0 | arr_size_ = arr_cap_ = 0; |
1031 | 0 | } else if (type_ == CJ_OBJECT) { |
1032 | 0 | for (size_t i = 0; i < obj_size_; ++i) |
1033 | 0 | obj_data_[i].~tinygltf_json_member(); |
1034 | 0 | free(obj_data_); |
1035 | 0 | obj_data_ = NULL; |
1036 | 0 | obj_size_ = obj_cap_ = 0; |
1037 | 0 | } |
1038 | 0 | type_ = CJ_NULL; |
1039 | 0 | } |
1040 | | |
1041 | 0 | inline void tinygltf_json::copy_from_(const tinygltf_json &o) { |
1042 | 0 | type_ = o.type_; |
1043 | 0 | i_ = o.i_; |
1044 | 0 | str_ = NULL; str_len_ = 0; |
1045 | 0 | arr_data_ = NULL; arr_size_ = 0; arr_cap_ = 0; |
1046 | 0 | obj_data_ = NULL; obj_size_ = 0; obj_cap_ = 0; |
1047 | |
|
1048 | 0 | if (o.type_ == CJ_STRING) { |
1049 | 0 | if (o.str_) { |
1050 | 0 | str_len_ = o.str_len_; |
1051 | 0 | str_ = (char *)malloc(str_len_ + 1); |
1052 | 0 | if (str_) memcpy(str_, o.str_, str_len_ + 1); |
1053 | 0 | else str_len_ = 0; /* malloc failure: keep str_==NULL, len==0 */ |
1054 | 0 | } |
1055 | 0 | } else if (o.type_ == CJ_ARRAY) { |
1056 | 0 | if (o.arr_size_ > 0) { |
1057 | | /* Guard against multiplication overflow */ |
1058 | 0 | if (o.arr_size_ <= SIZE_MAX / sizeof(tinygltf_json)) { |
1059 | 0 | arr_data_ = (tinygltf_json *)malloc( |
1060 | 0 | o.arr_size_ * sizeof(tinygltf_json)); |
1061 | 0 | if (arr_data_) { |
1062 | 0 | arr_size_ = 0; |
1063 | 0 | arr_cap_ = o.arr_size_; |
1064 | 0 | for (size_t i = 0; i < o.arr_size_; ++i) { |
1065 | 0 | new (&arr_data_[i]) tinygltf_json(o.arr_data_[i]); |
1066 | 0 | ++arr_size_; |
1067 | 0 | } |
1068 | 0 | } |
1069 | 0 | } |
1070 | 0 | } |
1071 | 0 | } else if (o.type_ == CJ_OBJECT) { |
1072 | 0 | if (o.obj_size_ > 0) { |
1073 | | /* Guard against multiplication overflow */ |
1074 | 0 | if (o.obj_size_ <= SIZE_MAX / sizeof(tinygltf_json_member)) { |
1075 | 0 | obj_data_ = (tinygltf_json_member *)malloc( |
1076 | 0 | o.obj_size_ * sizeof(tinygltf_json_member)); |
1077 | 0 | if (obj_data_) { |
1078 | 0 | obj_size_ = 0; |
1079 | 0 | obj_cap_ = o.obj_size_; |
1080 | 0 | for (size_t i = 0; i < o.obj_size_; ++i) { |
1081 | 0 | new (&obj_data_[i]) tinygltf_json_member(o.obj_data_[i]); |
1082 | 0 | ++obj_size_; |
1083 | 0 | } |
1084 | 0 | } |
1085 | 0 | } |
1086 | 0 | } |
1087 | 0 | } |
1088 | 0 | } |
1089 | | |
1090 | | inline tinygltf_json_member *tinygltf_json::find_member_( |
1091 | 0 | const char *key) const { |
1092 | 0 | if (!key) return NULL; |
1093 | 0 | size_t klen = strlen(key); |
1094 | 0 | for (size_t i = 0; i < obj_size_; ++i) { |
1095 | | /* Guard against NULL key (can occur if malloc failed during insert) */ |
1096 | 0 | if (obj_data_[i].key == NULL) continue; |
1097 | 0 | if (obj_data_[i].key_len == klen && |
1098 | 0 | memcmp(obj_data_[i].key, key, klen) == 0) |
1099 | 0 | return &obj_data_[i]; |
1100 | 0 | } |
1101 | 0 | return NULL; |
1102 | 0 | } |
1103 | | |
1104 | 0 | inline int tinygltf_json::obj_reserve_() { |
1105 | 0 | if (obj_size_ < obj_cap_) return 1; |
1106 | 0 | size_t new_cap = obj_cap_ ? obj_cap_ * 2 : 8; |
1107 | | /* Guard against allocation overflow */ |
1108 | 0 | if (new_cap > (size_t)0x7FFFFFFF / sizeof(tinygltf_json_member)) return 0; |
1109 | 0 | tinygltf_json_member *nd = (tinygltf_json_member *)malloc( |
1110 | 0 | new_cap * sizeof(tinygltf_json_member)); |
1111 | 0 | if (!nd) return 0; |
1112 | 0 | for (size_t i = 0; i < obj_size_; ++i) { |
1113 | 0 | new (&nd[i]) tinygltf_json_member( |
1114 | 0 | static_cast<tinygltf_json_member &&>(obj_data_[i])); |
1115 | 0 | obj_data_[i].~tinygltf_json_member(); |
1116 | 0 | } |
1117 | 0 | free(obj_data_); |
1118 | 0 | obj_data_ = nd; |
1119 | 0 | obj_cap_ = new_cap; |
1120 | 0 | return 1; |
1121 | 0 | } |
1122 | | |
1123 | 0 | inline int tinygltf_json::arr_reserve_() { |
1124 | 0 | if (arr_size_ < arr_cap_) return 1; |
1125 | 0 | size_t new_cap = arr_cap_ ? arr_cap_ * 2 : 8; |
1126 | | /* Guard against allocation overflow */ |
1127 | 0 | if (new_cap > (size_t)0x7FFFFFFF / sizeof(tinygltf_json)) return 0; |
1128 | 0 | tinygltf_json *nd = (tinygltf_json *)malloc( |
1129 | 0 | new_cap * sizeof(tinygltf_json)); |
1130 | 0 | if (!nd) return 0; |
1131 | 0 | for (size_t i = 0; i < arr_size_; ++i) { |
1132 | 0 | new (&nd[i]) tinygltf_json( |
1133 | 0 | static_cast<tinygltf_json &&>(arr_data_[i])); |
1134 | 0 | arr_data_[i].destroy_(); |
1135 | 0 | arr_data_[i].type_ = CJ_NULL; |
1136 | 0 | } |
1137 | 0 | free(arr_data_); |
1138 | 0 | arr_data_ = nd; |
1139 | 0 | arr_cap_ = new_cap; |
1140 | 0 | return 1; |
1141 | 0 | } |
1142 | | |
1143 | 0 | inline void tinygltf_json::make_object_() { |
1144 | 0 | destroy_(); |
1145 | 0 | type_ = CJ_OBJECT; |
1146 | 0 | } |
1147 | | |
1148 | 0 | inline void tinygltf_json::make_array_() { |
1149 | 0 | destroy_(); |
1150 | 0 | type_ = CJ_ARRAY; |
1151 | 0 | } |
1152 | | |
1153 | | /* Constructors */ |
1154 | 0 | inline tinygltf_json::tinygltf_json() { init_null_(); } |
1155 | | inline tinygltf_json::tinygltf_json(std::nullptr_t) { init_null_(); } |
1156 | 0 | inline tinygltf_json::tinygltf_json(bool b) { init_null_(); type_ = CJ_BOOL; b_ = b ? 1 : 0; } |
1157 | 0 | inline tinygltf_json::tinygltf_json(int i) { init_null_(); type_ = CJ_INT; i_ = (int64_t)i; } |
1158 | | inline tinygltf_json::tinygltf_json(int64_t i) { init_null_(); type_ = CJ_INT; i_ = i; } |
1159 | 0 | inline tinygltf_json::tinygltf_json(uint64_t u) { |
1160 | 0 | init_null_(); |
1161 | 0 | if (u <= (uint64_t)INT64_MAX) { |
1162 | 0 | type_ = CJ_INT; |
1163 | 0 | i_ = (int64_t)u; |
1164 | 0 | } else { |
1165 | 0 | type_ = CJ_REAL; |
1166 | 0 | d_ = (double)u; |
1167 | 0 | } |
1168 | 0 | } |
1169 | 0 | inline tinygltf_json::tinygltf_json(double d) { init_null_(); type_ = CJ_REAL; d_ = d; } |
1170 | | inline tinygltf_json::tinygltf_json(float f) { init_null_(); type_ = CJ_REAL; d_ = (double)f; } |
1171 | | |
1172 | 0 | inline tinygltf_json::tinygltf_json(const char *s) { |
1173 | 0 | init_null_(); |
1174 | 0 | if (s) { |
1175 | 0 | type_ = CJ_STRING; |
1176 | 0 | str_len_ = strlen(s); |
1177 | 0 | str_ = (char *)malloc(str_len_ + 1); |
1178 | 0 | if (str_) memcpy(str_, s, str_len_ + 1); |
1179 | 0 | else str_len_ = 0; /* malloc failure: keep str_==NULL, len==0 */ |
1180 | 0 | } |
1181 | 0 | } |
1182 | | |
1183 | 0 | inline tinygltf_json::tinygltf_json(const std::string &s) { |
1184 | 0 | init_null_(); |
1185 | 0 | type_ = CJ_STRING; |
1186 | 0 | str_len_ = s.size(); |
1187 | 0 | str_ = (char *)malloc(str_len_ + 1); |
1188 | 0 | if (str_) memcpy(str_, s.c_str(), str_len_ + 1); |
1189 | 0 | else str_len_ = 0; /* malloc failure: keep str_==NULL, len==0 */ |
1190 | 0 | } |
1191 | | |
1192 | 0 | inline tinygltf_json::tinygltf_json(const tinygltf_json &o) { |
1193 | 0 | init_null_(); |
1194 | 0 | copy_from_(o); |
1195 | 0 | } |
1196 | | |
1197 | 0 | inline tinygltf_json::tinygltf_json(tinygltf_json &&o) noexcept { |
1198 | 0 | type_ = o.type_; |
1199 | 0 | i_ = o.i_; |
1200 | 0 | str_ = o.str_; |
1201 | 0 | str_len_ = o.str_len_; |
1202 | 0 | arr_data_ = o.arr_data_; arr_size_ = o.arr_size_; arr_cap_ = o.arr_cap_; |
1203 | 0 | obj_data_ = o.obj_data_; obj_size_ = o.obj_size_; obj_cap_ = o.obj_cap_; |
1204 | 0 | o.type_ = CJ_NULL; |
1205 | 0 | o.str_ = NULL; |
1206 | 0 | o.arr_data_ = NULL; o.arr_size_ = 0; o.arr_cap_ = 0; |
1207 | 0 | o.obj_data_ = NULL; o.obj_size_ = 0; o.obj_cap_ = 0; |
1208 | 0 | } |
1209 | | |
1210 | 0 | inline tinygltf_json::~tinygltf_json() { destroy_(); } |
1211 | | |
1212 | 0 | inline tinygltf_json &tinygltf_json::operator=(const tinygltf_json &o) { |
1213 | 0 | if (this != &o) { destroy_(); copy_from_(o); } |
1214 | 0 | return *this; |
1215 | 0 | } |
1216 | | |
1217 | 0 | inline tinygltf_json &tinygltf_json::operator=(tinygltf_json &&o) noexcept { |
1218 | 0 | if (this != &o) { |
1219 | 0 | destroy_(); |
1220 | 0 | type_ = o.type_; |
1221 | 0 | i_ = o.i_; |
1222 | 0 | str_ = o.str_; |
1223 | 0 | str_len_ = o.str_len_; |
1224 | 0 | arr_data_ = o.arr_data_; arr_size_ = o.arr_size_; arr_cap_ = o.arr_cap_; |
1225 | 0 | obj_data_ = o.obj_data_; obj_size_ = o.obj_size_; obj_cap_ = o.obj_cap_; |
1226 | 0 | o.type_ = CJ_NULL; |
1227 | 0 | o.str_ = NULL; |
1228 | 0 | o.arr_data_ = NULL; o.arr_size_ = 0; o.arr_cap_ = 0; |
1229 | 0 | o.obj_data_ = NULL; o.obj_size_ = 0; o.obj_cap_ = 0; |
1230 | 0 | } |
1231 | 0 | return *this; |
1232 | 0 | } |
1233 | | |
1234 | 0 | inline tinygltf_json::value_t tinygltf_json::type() const { |
1235 | 0 | switch (type_) { |
1236 | 0 | case CJ_NULL: return value_t::null; |
1237 | 0 | case CJ_BOOL: return value_t::boolean; |
1238 | 0 | case CJ_INT: return i_ >= 0 ? value_t::number_unsigned |
1239 | 0 | : value_t::number_integer; |
1240 | 0 | case CJ_REAL: return value_t::number_float; |
1241 | 0 | case CJ_STRING: return value_t::string; |
1242 | 0 | case CJ_ARRAY: return value_t::array; |
1243 | 0 | case CJ_OBJECT: return value_t::object; |
1244 | 0 | default: return value_t::null; |
1245 | 0 | } |
1246 | 0 | } |
1247 | | |
1248 | 0 | inline size_t tinygltf_json::size() const { |
1249 | 0 | if (type_ == CJ_ARRAY) return arr_size_; |
1250 | 0 | if (type_ == CJ_OBJECT) return obj_size_; |
1251 | 0 | return 0; |
1252 | 0 | } |
1253 | | |
1254 | 0 | inline bool tinygltf_json::empty() const { |
1255 | 0 | if (type_ == CJ_ARRAY) return arr_size_ == 0; |
1256 | 0 | if (type_ == CJ_OBJECT) return obj_size_ == 0; |
1257 | 0 | return true; |
1258 | 0 | } |
1259 | | |
1260 | 0 | inline void tinygltf_json::push_back(tinygltf_json &&v) { |
1261 | 0 | if (type_ != CJ_ARRAY) make_array_(); |
1262 | 0 | if (!arr_reserve_()) return; |
1263 | 0 | new (&arr_data_[arr_size_]) tinygltf_json( |
1264 | 0 | static_cast<tinygltf_json &&>(v)); |
1265 | 0 | ++arr_size_; |
1266 | 0 | } |
1267 | | |
1268 | 0 | inline void tinygltf_json::push_back(const tinygltf_json &v) { |
1269 | 0 | push_back(tinygltf_json(v)); |
1270 | 0 | } |
1271 | | |
1272 | 0 | inline tinygltf_json &tinygltf_json::operator[](const char *key) { |
1273 | | /* Degraded-mode fallback for API misuse (null key) or OOM. |
1274 | | * Returns a reference to a shared static null object. This is the same |
1275 | | * best-effort pattern used for the OOM path below. |
1276 | | * CAUTION: the static is shared across calls; modifications through this |
1277 | | * reference persist (same caveat as the OOM fallback). Callers should |
1278 | | * treat a null-key or OOM insert as a no-op. */ |
1279 | 0 | static tinygltf_json null_fallback; |
1280 | 0 | if (!key) return null_fallback; |
1281 | 0 | if (type_ != CJ_OBJECT) make_object_(); |
1282 | 0 | tinygltf_json_member *m = find_member_(key); |
1283 | 0 | if (m) return m->val; |
1284 | 0 | if (!obj_reserve_()) return null_fallback; |
1285 | 0 | tinygltf_json_member *nm = &obj_data_[obj_size_]; |
1286 | 0 | new (nm) tinygltf_json_member(); |
1287 | 0 | size_t klen = strlen(key); |
1288 | 0 | nm->key = (char *)malloc(klen + 1); |
1289 | 0 | if (!nm->key) { |
1290 | | /* Roll back insertion on key allocation failure: destroy the |
1291 | | * placement-new'd member and do not bump obj_size_, keeping the |
1292 | | * object in a consistent state. */ |
1293 | 0 | nm->~tinygltf_json_member(); |
1294 | 0 | return null_fallback; |
1295 | 0 | } |
1296 | 0 | memcpy(nm->key, key, klen + 1); |
1297 | 0 | nm->key_len = klen; |
1298 | 0 | ++obj_size_; |
1299 | 0 | return nm->val; |
1300 | 0 | } |
1301 | | |
1302 | 0 | inline tinygltf_json &tinygltf_json::operator[](const std::string &key) { |
1303 | 0 | return operator[](key.c_str()); |
1304 | 0 | } |
1305 | | |
1306 | 0 | inline tinygltf_json::iterator tinygltf_json::begin() { |
1307 | 0 | if (type_ == CJ_ARRAY) return iterator(arr_data_); |
1308 | 0 | if (type_ == CJ_OBJECT) return iterator(obj_data_); |
1309 | 0 | return iterator((tinygltf_json *)NULL); |
1310 | 0 | } |
1311 | 0 | inline tinygltf_json::iterator tinygltf_json::end() { |
1312 | 0 | if (type_ == CJ_ARRAY) return iterator(arr_data_ + arr_size_); |
1313 | 0 | if (type_ == CJ_OBJECT) return iterator(obj_data_ + obj_size_); |
1314 | 0 | return iterator((tinygltf_json *)NULL); |
1315 | 0 | } |
1316 | 0 | inline tinygltf_json::iterator tinygltf_json::begin() const { |
1317 | 0 | tinygltf_json *self = const_cast<tinygltf_json *>(this); |
1318 | 0 | return self->begin(); |
1319 | 0 | } |
1320 | 0 | inline tinygltf_json::iterator tinygltf_json::end() const { |
1321 | 0 | tinygltf_json *self = const_cast<tinygltf_json *>(this); |
1322 | 0 | return self->end(); |
1323 | 0 | } |
1324 | | |
1325 | 0 | inline tinygltf_json::iterator tinygltf_json::find(const char *key) { |
1326 | 0 | if (type_ == CJ_OBJECT) { |
1327 | 0 | tinygltf_json_member *m = find_member_(key); |
1328 | 0 | if (m) return iterator(m); |
1329 | 0 | return iterator(obj_data_ + obj_size_); |
1330 | 0 | } |
1331 | 0 | return iterator((tinygltf_json *)NULL); |
1332 | 0 | } |
1333 | 0 | inline tinygltf_json::iterator tinygltf_json::find(const char *key) const { |
1334 | 0 | return const_cast<tinygltf_json *>(this)->find(key); |
1335 | 0 | } |
1336 | | |
1337 | 0 | inline void tinygltf_json::erase(tinygltf_json::iterator &it) { |
1338 | 0 | if (type_ != CJ_OBJECT || it.mode_ != iterator::MODE_OBJECT) return; |
1339 | 0 | ptrdiff_t idx = it.obj_ptr_ - obj_data_; |
1340 | 0 | if (idx < 0 || (size_t)idx >= obj_size_) return; |
1341 | 0 | obj_data_[idx].~tinygltf_json_member(); |
1342 | 0 | for (size_t i = (size_t)idx; i + 1 < obj_size_; ++i) { |
1343 | 0 | new (&obj_data_[i]) tinygltf_json_member( |
1344 | 0 | static_cast<tinygltf_json_member &&>(obj_data_[i + 1])); |
1345 | 0 | obj_data_[i + 1].~tinygltf_json_member(); |
1346 | 0 | } |
1347 | 0 | --obj_size_; |
1348 | 0 | it = end(); |
1349 | 0 | } |
1350 | | |
1351 | 0 | inline tinygltf_json tinygltf_json::object() { |
1352 | 0 | tinygltf_json j; |
1353 | 0 | j.make_object_(); |
1354 | 0 | return j; |
1355 | 0 | } |
1356 | | |
1357 | | /* ====================================================================== |
1358 | | * get<T>() specializations |
1359 | | * ====================================================================== */ |
1360 | | |
1361 | 0 | template<> inline double tinygltf_json::get<double>() const { |
1362 | 0 | if (type_ == CJ_REAL) return d_; |
1363 | 0 | if (type_ == CJ_INT) return (double)i_; |
1364 | 0 | return 0.0; |
1365 | 0 | } |
1366 | | |
1367 | 0 | template<> inline int tinygltf_json::get<int>() const { |
1368 | 0 | if (type_ == CJ_INT) return (int)i_; |
1369 | 0 | if (type_ == CJ_REAL) return (int)d_; |
1370 | 0 | return 0; |
1371 | 0 | } |
1372 | | |
1373 | 0 | template<> inline int64_t tinygltf_json::get<int64_t>() const { |
1374 | 0 | if (type_ == CJ_INT) return i_; |
1375 | 0 | if (type_ == CJ_REAL) return (int64_t)d_; |
1376 | 0 | return 0; |
1377 | 0 | } |
1378 | | |
1379 | 0 | template<> inline uint64_t tinygltf_json::get<uint64_t>() const { |
1380 | 0 | if (type_ == CJ_INT) return (uint64_t)i_; |
1381 | 0 | if (type_ == CJ_REAL) return (uint64_t)d_; |
1382 | 0 | return 0; |
1383 | 0 | } |
1384 | | |
1385 | 0 | template<> inline bool tinygltf_json::get<bool>() const { |
1386 | 0 | if (type_ == CJ_BOOL) return b_ != 0; |
1387 | 0 | return false; |
1388 | 0 | } |
1389 | | |
1390 | 0 | template<> inline std::string tinygltf_json::get<std::string>() const { |
1391 | 0 | if (type_ == CJ_STRING && str_) |
1392 | 0 | return std::string(str_, str_len_); |
1393 | 0 | return std::string(); |
1394 | 0 | } |
1395 | | |
1396 | | /* Primary template for any T not explicitly specialised (e.g. size_t on |
1397 | | * platforms where it is a distinct type from all of the above, such as |
1398 | | * macOS 64-bit where uint64_t=unsigned long long but size_t=unsigned long). |
1399 | | * Falls back to a static_cast from the stored integer or floating-point value. |
1400 | | * For unsigned T: negative integer values produce 0 rather than wrapping. */ |
1401 | | template<typename T> |
1402 | | inline T tinygltf_json::get() const { |
1403 | | if (type_ == CJ_INT) { |
1404 | | /* Guard unsigned types against sign-extension of negative values */ |
1405 | | if ((T)(-1) > (T)(0) && i_ < 0) return (T)(0); |
1406 | | return static_cast<T>(i_); |
1407 | | } |
1408 | | if (type_ == CJ_REAL) return static_cast<T>(d_); |
1409 | | if (type_ == CJ_BOOL) return static_cast<T>(b_); |
1410 | | return T(); |
1411 | | } |
1412 | | |
1413 | | /* ====================================================================== |
1414 | | * PARSER (C-style iterative, explicit frame stack) |
1415 | | * |
1416 | | * Uses an explicit cj_frame stack instead of C recursion so that deeply |
1417 | | * nested JSON cannot overflow the call stack. CJ_MAX_ITER limits both |
1418 | | * the container nesting depth (stack size) and serves as the iteration |
1419 | | * safety budget: a malformed input that keeps pushing containers without |
1420 | | * consuming content is rejected once the stack is full. |
1421 | | * ====================================================================== */ |
1422 | | |
1423 | | /* Maximum container nesting depth (size of the explicit frame stack) */ |
1424 | 0 | #define CJ_MAX_ITER 512 |
1425 | | |
1426 | | /* One entry per open container (array or object) on the explicit stack */ |
1427 | | struct cj_frame { |
1428 | | tinygltf_json *container; /* The array or object being populated */ |
1429 | | int is_object; /* 0 = array, 1 = object */ |
1430 | | }; |
1431 | | |
1432 | | struct cj_parse_ctx { |
1433 | | const char *cur; |
1434 | | const char *end; |
1435 | | int err; |
1436 | | int float32_mode; /* 0 = double (default), 1 = float32 */ |
1437 | | char errmsg[256]; |
1438 | | }; |
1439 | | |
1440 | 0 | static void cj_ctx_error(cj_parse_ctx *ctx, const char *msg) { |
1441 | 0 | if (!ctx->err) { |
1442 | 0 | ctx->err = 1; |
1443 | 0 | strncpy(ctx->errmsg, msg, sizeof(ctx->errmsg) - 1); |
1444 | 0 | ctx->errmsg[sizeof(ctx->errmsg) - 1] = '\0'; |
1445 | 0 | } |
1446 | 0 | } |
1447 | | |
1448 | | /* |
1449 | | * Parse a JSON string from the current position. |
1450 | | * cur must point to the opening '"'. |
1451 | | * On success, advances cur past the closing '"' and sets *out_str (owned). |
1452 | | */ |
1453 | | static void cj_parse_string_to(cj_parse_ctx *ctx, char **out_str, |
1454 | 0 | size_t *out_len) { |
1455 | 0 | assert(ctx->cur < ctx->end && *ctx->cur == '"'); |
1456 | 0 | ++ctx->cur; /* skip opening '"' */ |
1457 | |
|
1458 | 0 | const char *p = ctx->cur; |
1459 | | |
1460 | | /* Fast path: find closing '"' without escapes */ |
1461 | 0 | while (p < ctx->end) { |
1462 | 0 | p = cj_scan_str(p, ctx->end); |
1463 | 0 | if (p >= ctx->end) { |
1464 | 0 | cj_ctx_error(ctx, "unterminated string"); |
1465 | 0 | *out_str = NULL; *out_len = 0; |
1466 | 0 | return; |
1467 | 0 | } |
1468 | 0 | if (*p == '"') { |
1469 | | /* No escapes: copy directly */ |
1470 | 0 | size_t len = (size_t)(p - ctx->cur); |
1471 | 0 | char *s = (char *)malloc(len + 1); |
1472 | 0 | if (!s) { cj_ctx_error(ctx, "out of memory"); *out_str = NULL; *out_len = 0; return; } |
1473 | 0 | memcpy(s, ctx->cur, len); |
1474 | 0 | s[len] = '\0'; |
1475 | 0 | *out_str = s; |
1476 | 0 | *out_len = len; |
1477 | 0 | ctx->cur = p + 1; |
1478 | 0 | return; |
1479 | 0 | } |
1480 | 0 | if (*p == '\\') { |
1481 | | /* Has escapes: find true end, then unescape */ |
1482 | 0 | const char *scan = p; |
1483 | 0 | while (scan < ctx->end) { |
1484 | 0 | scan = cj_scan_str(scan, ctx->end); |
1485 | 0 | if (scan >= ctx->end) { cj_ctx_error(ctx, "unterminated string"); *out_str = NULL; *out_len = 0; return; } |
1486 | 0 | if (*scan == '"') break; |
1487 | 0 | if (*scan == '\\') { |
1488 | 0 | ++scan; |
1489 | 0 | if (scan >= ctx->end) { cj_ctx_error(ctx, "truncated escape"); *out_str = NULL; *out_len = 0; return; } |
1490 | 0 | if (*scan == 'u') { |
1491 | | /* \uXXXX requires exactly 4 hex digits after 'u' */ |
1492 | 0 | if (scan + 5 > ctx->end) { |
1493 | 0 | cj_ctx_error(ctx, "truncated \\u escape"); |
1494 | 0 | *out_str = NULL; *out_len = 0; return; |
1495 | 0 | } |
1496 | 0 | scan += 5; |
1497 | 0 | } else { |
1498 | 0 | ++scan; |
1499 | 0 | } |
1500 | 0 | } else { |
1501 | | /* cj_scan_str stopped at a control char (<0x20): invalid JSON */ |
1502 | 0 | cj_ctx_error(ctx, "invalid control character in string"); |
1503 | 0 | *out_str = NULL; *out_len = 0; return; |
1504 | 0 | } |
1505 | 0 | } |
1506 | | /* After the loop, scan must point to the closing '"' */ |
1507 | 0 | if (scan >= ctx->end) { |
1508 | 0 | cj_ctx_error(ctx, "unterminated string"); |
1509 | 0 | *out_str = NULL; *out_len = 0; return; |
1510 | 0 | } |
1511 | 0 | if (ctx->err) { *out_str = NULL; *out_len = 0; return; } |
1512 | 0 | *out_str = cj_unescape_string(ctx->cur, scan, out_len); |
1513 | 0 | if (!*out_str) { cj_ctx_error(ctx, "string unescape failed"); } |
1514 | 0 | ctx->cur = scan + 1; |
1515 | 0 | return; |
1516 | 0 | } |
1517 | | /* Control char (< 0x20) - treat as parse error (invalid JSON) */ |
1518 | 0 | cj_ctx_error(ctx, "invalid control character in string"); |
1519 | 0 | *out_str = NULL; *out_len = 0; |
1520 | 0 | return; |
1521 | 0 | } |
1522 | 0 | cj_ctx_error(ctx, "unterminated string"); |
1523 | 0 | *out_str = NULL; *out_len = 0; |
1524 | 0 | } |
1525 | | |
1526 | | /* |
1527 | | * Parse a scalar JSON value (string, number, bool, null) into *slot. |
1528 | | * ctx->cur must point to the first character of the value (whitespace |
1529 | | * already consumed). |
1530 | | */ |
1531 | 0 | static void cj_parse_scalar(cj_parse_ctx *ctx, tinygltf_json *slot) { |
1532 | 0 | char c = *ctx->cur; |
1533 | |
|
1534 | 0 | if (c == '"') { |
1535 | 0 | char *s = NULL; size_t slen = 0; |
1536 | 0 | cj_parse_string_to(ctx, &s, &slen); |
1537 | 0 | if (ctx->err || !s) { free(s); slot->destroy_(); slot->init_null_(); return; } |
1538 | 0 | slot->destroy_(); slot->init_null_(); |
1539 | 0 | slot->type_ = CJ_STRING; slot->str_ = s; slot->str_len_ = slen; |
1540 | 0 | } else if (c == 't') { |
1541 | 0 | if (ctx->end - ctx->cur >= 4 && memcmp(ctx->cur, "true", 4) == 0) { |
1542 | 0 | ctx->cur += 4; |
1543 | 0 | slot->destroy_(); slot->init_null_(); |
1544 | 0 | slot->type_ = CJ_BOOL; slot->b_ = 1; |
1545 | 0 | } else { cj_ctx_error(ctx, "invalid literal 'true'"); } |
1546 | 0 | } else if (c == 'f') { |
1547 | 0 | if (ctx->end - ctx->cur >= 5 && memcmp(ctx->cur, "false", 5) == 0) { |
1548 | 0 | ctx->cur += 5; |
1549 | 0 | slot->destroy_(); slot->init_null_(); |
1550 | 0 | slot->type_ = CJ_BOOL; slot->b_ = 0; |
1551 | 0 | } else { cj_ctx_error(ctx, "invalid literal 'false'"); } |
1552 | 0 | } else if (c == 'n') { |
1553 | 0 | if (ctx->end - ctx->cur >= 4 && memcmp(ctx->cur, "null", 4) == 0) { |
1554 | 0 | ctx->cur += 4; |
1555 | 0 | slot->destroy_(); slot->init_null_(); |
1556 | 0 | } else { cj_ctx_error(ctx, "invalid literal 'null'"); } |
1557 | 0 | } else if (c == '-' || (c >= '0' && c <= '9')) { |
1558 | 0 | int is_int = 0; int64_t ival = 0; double dval = 0.0; |
1559 | 0 | const char *next = cj_parse_number(ctx->cur, ctx->end, &is_int, &ival, &dval, ctx->float32_mode); |
1560 | 0 | if (!next) { cj_ctx_error(ctx, "invalid number"); return; } |
1561 | 0 | ctx->cur = next; |
1562 | 0 | slot->destroy_(); slot->init_null_(); |
1563 | 0 | if (is_int) { slot->type_ = CJ_INT; slot->i_ = ival; } |
1564 | 0 | else { slot->type_ = CJ_REAL; slot->d_ = dval; } |
1565 | 0 | } else { |
1566 | 0 | char errbuf[64]; |
1567 | 0 | snprintf(errbuf, sizeof(errbuf), "unexpected character '%c' (0x%02X)", |
1568 | 0 | (unsigned char)c >= 0x20u ? c : '?', (unsigned char)c); |
1569 | 0 | cj_ctx_error(ctx, errbuf); |
1570 | 0 | slot->destroy_(); slot->init_null_(); |
1571 | 0 | } |
1572 | 0 | } |
1573 | | |
1574 | | /* |
1575 | | * cj_parse_json -- iterative JSON parser. |
1576 | | * |
1577 | | * Parses one complete JSON value from ctx into *root using an explicit |
1578 | | * cj_frame[CJ_MAX_ITER] stack instead of C recursion. No C stack frames |
1579 | | * are consumed for nesting; the only stack growth comes from the fixed-size |
1580 | | * cj_frame array declared as a local variable here. |
1581 | | * |
1582 | | * Loop structure: |
1583 | | * after_val == 0 -> parse the next JSON value into *slot |
1584 | | * after_val == 1 -> a value was just completed; handle ',' / ']' / '}' |
1585 | | * |
1586 | | * CJ_MAX_ITER caps the container nesting depth. Each '{' or '[' increments |
1587 | | * depth; reaching the cap produces an error rather than an out-of-bounds |
1588 | | * write. |
1589 | | */ |
1590 | 0 | static void cj_parse_json(cj_parse_ctx *ctx, tinygltf_json *root) { |
1591 | 0 | cj_frame stack[CJ_MAX_ITER]; |
1592 | 0 | int depth = 0; /* frames in use */ |
1593 | 0 | int after_val = 0; /* 0 = need value, 1 = value just finished */ |
1594 | | |
1595 | | /* Where to write the next parsed value */ |
1596 | 0 | tinygltf_json *slot = root; |
1597 | |
|
1598 | 0 | for (;;) { |
1599 | 0 | if (ctx->err) break; |
1600 | | |
1601 | | /* --------------------------------------------------------------- |
1602 | | * POST-VALUE: handle separator / closing bracket |
1603 | | * ------------------------------------------------------------- */ |
1604 | 0 | if (after_val) { |
1605 | 0 | after_val = 0; |
1606 | |
|
1607 | 0 | if (depth == 0) { |
1608 | | /* Root value complete: ensure only trailing whitespace remains */ |
1609 | 0 | ctx->cur = cj_skip_ws(ctx->cur, ctx->end); |
1610 | 0 | if (ctx->cur != ctx->end) { |
1611 | 0 | cj_ctx_error(ctx, "trailing non-whitespace after JSON root value"); |
1612 | 0 | } |
1613 | 0 | break; |
1614 | 0 | } |
1615 | | |
1616 | 0 | cj_frame *f = &stack[depth - 1]; |
1617 | 0 | ctx->cur = cj_skip_ws(ctx->cur, ctx->end); |
1618 | 0 | if (ctx->cur >= ctx->end) { |
1619 | 0 | cj_ctx_error(ctx, "unexpected EOF after value"); break; |
1620 | 0 | } |
1621 | | |
1622 | 0 | if (!f->is_object) { |
1623 | | /* ---- Array: expect ',' or ']' ---- */ |
1624 | 0 | if (*ctx->cur == ',') { |
1625 | 0 | ++ctx->cur; |
1626 | | /* Allocate next element slot */ |
1627 | 0 | tinygltf_json *cont = f->container; |
1628 | 0 | if (!cont->arr_reserve_()) { cj_ctx_error(ctx, "OOM"); break; } |
1629 | 0 | new (&cont->arr_data_[cont->arr_size_]) tinygltf_json(); |
1630 | 0 | slot = &cont->arr_data_[cont->arr_size_]; |
1631 | 0 | ++cont->arr_size_; |
1632 | | /* Loop back to parse the element value */ |
1633 | 0 | } else if (*ctx->cur == ']') { |
1634 | 0 | ++ctx->cur; |
1635 | 0 | --depth; |
1636 | 0 | after_val = 1; /* the array itself is now the completed value */ |
1637 | 0 | } else { |
1638 | 0 | cj_ctx_error(ctx, "expected ',' or ']' in array"); break; |
1639 | 0 | } |
1640 | 0 | } else { |
1641 | | /* ---- Object: expect ',' or '}' ---- */ |
1642 | 0 | if (*ctx->cur == ',') { |
1643 | 0 | ++ctx->cur; |
1644 | 0 | ctx->cur = cj_skip_ws(ctx->cur, ctx->end); |
1645 | 0 | if (ctx->cur >= ctx->end) { |
1646 | 0 | cj_ctx_error(ctx, "unexpected EOF in object"); break; |
1647 | 0 | } |
1648 | 0 | if (*ctx->cur != '"') { |
1649 | 0 | cj_ctx_error(ctx, "expected object key after ','"); break; |
1650 | 0 | } |
1651 | | /* Parse key and allocate member slot */ |
1652 | 0 | char *k = NULL; size_t kl = 0; |
1653 | 0 | cj_parse_string_to(ctx, &k, &kl); |
1654 | 0 | if (ctx->err || !k) { free(k); break; } |
1655 | 0 | ctx->cur = cj_skip_ws(ctx->cur, ctx->end); |
1656 | 0 | if (ctx->cur >= ctx->end || *ctx->cur != ':') { |
1657 | 0 | free(k); cj_ctx_error(ctx, "expected ':' in object"); break; |
1658 | 0 | } |
1659 | 0 | ++ctx->cur; |
1660 | 0 | tinygltf_json *cont = f->container; |
1661 | 0 | if (!cont->obj_reserve_()) { free(k); cj_ctx_error(ctx, "OOM"); break; } |
1662 | 0 | tinygltf_json_member *m = &cont->obj_data_[cont->obj_size_]; |
1663 | 0 | new (m) tinygltf_json_member(); |
1664 | 0 | m->key = k; m->key_len = kl; |
1665 | 0 | ++cont->obj_size_; |
1666 | 0 | slot = &m->val; |
1667 | | /* Loop back to parse the member value */ |
1668 | 0 | } else if (*ctx->cur == '}') { |
1669 | 0 | ++ctx->cur; |
1670 | 0 | --depth; |
1671 | 0 | after_val = 1; /* the object itself is now the completed value */ |
1672 | 0 | } else { |
1673 | 0 | cj_ctx_error(ctx, "expected ',' or '}' in object"); break; |
1674 | 0 | } |
1675 | 0 | } |
1676 | 0 | continue; |
1677 | 0 | } |
1678 | | |
1679 | | /* --------------------------------------------------------------- |
1680 | | * PARSE VALUE: read *slot from ctx->cur |
1681 | | * ------------------------------------------------------------- */ |
1682 | 0 | ctx->cur = cj_skip_ws(ctx->cur, ctx->end); |
1683 | 0 | if (ctx->cur >= ctx->end) { |
1684 | 0 | if (depth == 0) break; /* trailing whitespace on root value is ok */ |
1685 | 0 | cj_ctx_error(ctx, "unexpected EOF"); break; |
1686 | 0 | } |
1687 | | |
1688 | 0 | char c = *ctx->cur; |
1689 | |
|
1690 | 0 | if (c == '{') { |
1691 | | /* ---- Begin object ---- */ |
1692 | 0 | if (depth >= CJ_MAX_ITER) { |
1693 | 0 | cj_ctx_error(ctx, "nesting limit exceeded"); break; |
1694 | 0 | } |
1695 | 0 | ++ctx->cur; |
1696 | 0 | slot->destroy_(); slot->init_null_(); slot->type_ = CJ_OBJECT; |
1697 | |
|
1698 | 0 | stack[depth].container = slot; |
1699 | 0 | stack[depth].is_object = 1; |
1700 | 0 | ++depth; |
1701 | |
|
1702 | 0 | ctx->cur = cj_skip_ws(ctx->cur, ctx->end); |
1703 | 0 | if (ctx->cur >= ctx->end) { cj_ctx_error(ctx, "EOF in object"); break; } |
1704 | 0 | if (*ctx->cur == '}') { ++ctx->cur; --depth; after_val = 1; continue; } |
1705 | | |
1706 | | /* Parse first key */ |
1707 | 0 | if (*ctx->cur != '"') { cj_ctx_error(ctx, "expected key in object"); break; } |
1708 | 0 | { |
1709 | 0 | char *k = NULL; size_t kl = 0; |
1710 | 0 | cj_parse_string_to(ctx, &k, &kl); |
1711 | 0 | if (ctx->err || !k) { free(k); break; } |
1712 | 0 | ctx->cur = cj_skip_ws(ctx->cur, ctx->end); |
1713 | 0 | if (ctx->cur >= ctx->end || *ctx->cur != ':') { |
1714 | 0 | free(k); cj_ctx_error(ctx, "expected ':' in object"); break; |
1715 | 0 | } |
1716 | 0 | ++ctx->cur; |
1717 | 0 | if (!slot->obj_reserve_()) { free(k); cj_ctx_error(ctx, "OOM"); break; } |
1718 | 0 | tinygltf_json_member *m = &slot->obj_data_[slot->obj_size_]; |
1719 | 0 | new (m) tinygltf_json_member(); |
1720 | 0 | m->key = k; m->key_len = kl; |
1721 | 0 | ++slot->obj_size_; |
1722 | 0 | slot = &m->val; /* next iteration parses the first value */ |
1723 | 0 | } |
1724 | |
|
1725 | 0 | } else if (c == '[') { |
1726 | | /* ---- Begin array ---- */ |
1727 | 0 | if (depth >= CJ_MAX_ITER) { |
1728 | 0 | cj_ctx_error(ctx, "nesting limit exceeded"); break; |
1729 | 0 | } |
1730 | 0 | ++ctx->cur; |
1731 | 0 | slot->destroy_(); slot->init_null_(); slot->type_ = CJ_ARRAY; |
1732 | |
|
1733 | 0 | stack[depth].container = slot; |
1734 | 0 | stack[depth].is_object = 0; |
1735 | 0 | ++depth; |
1736 | |
|
1737 | 0 | ctx->cur = cj_skip_ws(ctx->cur, ctx->end); |
1738 | 0 | if (ctx->cur >= ctx->end) { cj_ctx_error(ctx, "EOF in array"); break; } |
1739 | 0 | if (*ctx->cur == ']') { ++ctx->cur; --depth; after_val = 1; continue; } |
1740 | | |
1741 | | /* Allocate first element slot */ |
1742 | 0 | { |
1743 | 0 | tinygltf_json *cont = stack[depth - 1].container; |
1744 | 0 | if (!cont->arr_reserve_()) { cj_ctx_error(ctx, "OOM"); break; } |
1745 | 0 | new (&cont->arr_data_[cont->arr_size_]) tinygltf_json(); |
1746 | 0 | slot = &cont->arr_data_[cont->arr_size_]; |
1747 | 0 | ++cont->arr_size_; |
1748 | 0 | } |
1749 | | /* next iteration parses the first element */ |
1750 | |
|
1751 | 0 | } else { |
1752 | | /* ---- Scalar value ---- */ |
1753 | 0 | cj_parse_scalar(ctx, slot); |
1754 | 0 | after_val = 1; |
1755 | 0 | } |
1756 | 0 | } |
1757 | 0 | } |
1758 | | |
1759 | | /* ====================================================================== |
1760 | | * SERIALIZATION (C-style string builder) |
1761 | | * ====================================================================== */ |
1762 | | |
1763 | | struct cj_strbuf { |
1764 | | char *data; |
1765 | | size_t len; |
1766 | | size_t cap; |
1767 | | }; |
1768 | | |
1769 | 0 | static int cj_strbuf_init(cj_strbuf *sb, size_t initial) { |
1770 | 0 | sb->data = (char *)malloc(initial); |
1771 | 0 | sb->len = 0; |
1772 | 0 | sb->cap = initial; |
1773 | 0 | return sb->data ? 1 : 0; |
1774 | 0 | } |
1775 | | |
1776 | 0 | static void cj_strbuf_free_data(cj_strbuf *sb) { |
1777 | 0 | free(sb->data); |
1778 | 0 | sb->data = NULL; |
1779 | 0 | sb->len = sb->cap = 0; |
1780 | 0 | } |
1781 | | |
1782 | 0 | static int cj_strbuf_grow(cj_strbuf *sb, size_t extra) { |
1783 | | /* Guard against size_t overflow in needed = sb->len + extra */ |
1784 | 0 | if (extra > (size_t)-1 - sb->len) return 0; |
1785 | 0 | size_t needed = sb->len + extra; |
1786 | 0 | if (needed <= sb->cap) return 1; |
1787 | 0 | size_t new_cap = sb->cap * 2; |
1788 | 0 | if (new_cap < needed) { |
1789 | | /* Guard against overflow in needed + 256 */ |
1790 | 0 | if (needed > SIZE_MAX - 256) return 0; |
1791 | 0 | new_cap = needed + 256; |
1792 | 0 | } |
1793 | 0 | char *nd = (char *)realloc(sb->data, new_cap); |
1794 | 0 | if (!nd) return 0; |
1795 | 0 | sb->data = nd; |
1796 | 0 | sb->cap = new_cap; |
1797 | 0 | return 1; |
1798 | 0 | } |
1799 | | |
1800 | 0 | static int cj_sb_appendn(cj_strbuf *sb, const char *s, size_t n) { |
1801 | 0 | if (!cj_strbuf_grow(sb, n)) return 0; |
1802 | 0 | memcpy(sb->data + sb->len, s, n); |
1803 | 0 | sb->len += n; |
1804 | 0 | return 1; |
1805 | 0 | } |
1806 | | |
1807 | 0 | static int cj_sb_appendc(cj_strbuf *sb, char c) { |
1808 | 0 | return cj_sb_appendn(sb, &c, 1); |
1809 | 0 | } |
1810 | | |
1811 | 0 | static int cj_sb_appends(cj_strbuf *sb, const char *s) { |
1812 | 0 | return cj_sb_appendn(sb, s, strlen(s)); |
1813 | 0 | } |
1814 | | |
1815 | 0 | static int cj_append_str_escaped(cj_strbuf *sb, const char *s, size_t len) { |
1816 | 0 | if (!cj_sb_appendc(sb, '"')) return 0; |
1817 | 0 | for (size_t i = 0; i < len; ++i) { |
1818 | 0 | unsigned char c = (unsigned char)s[i]; |
1819 | 0 | switch (c) { |
1820 | 0 | case '"': if (!cj_sb_appendn(sb, "\\\"", 2)) return 0; break; |
1821 | 0 | case '\\': if (!cj_sb_appendn(sb, "\\\\", 2)) return 0; break; |
1822 | 0 | case '\b': if (!cj_sb_appendn(sb, "\\b", 2)) return 0; break; |
1823 | 0 | case '\f': if (!cj_sb_appendn(sb, "\\f", 2)) return 0; break; |
1824 | 0 | case '\n': if (!cj_sb_appendn(sb, "\\n", 2)) return 0; break; |
1825 | 0 | case '\r': if (!cj_sb_appendn(sb, "\\r", 2)) return 0; break; |
1826 | 0 | case '\t': if (!cj_sb_appendn(sb, "\\t", 2)) return 0; break; |
1827 | 0 | default: |
1828 | 0 | if (c < 0x20u) { |
1829 | 0 | char buf[8]; |
1830 | 0 | snprintf(buf, sizeof(buf), "\\u%04x", (unsigned int)c); |
1831 | 0 | if (!cj_sb_appends(sb, buf)) return 0; |
1832 | 0 | } else { |
1833 | 0 | if (!cj_sb_appendc(sb, (char)c)) return 0; |
1834 | 0 | } |
1835 | 0 | break; |
1836 | 0 | } |
1837 | 0 | } |
1838 | 0 | return cj_sb_appendc(sb, '"'); |
1839 | 0 | } |
1840 | | |
1841 | 0 | static int cj_indent_line(cj_strbuf *sb, int indent, int depth) { |
1842 | 0 | if (indent <= 0) return 1; |
1843 | 0 | if (!cj_sb_appendc(sb, '\n')) return 0; |
1844 | 0 | for (int i = 0; i < indent * depth; ++i) |
1845 | 0 | if (!cj_sb_appendc(sb, ' ')) return 0; |
1846 | 0 | return 1; |
1847 | 0 | } |
1848 | | |
1849 | | static int cj_serialize(cj_strbuf *sb, const tinygltf_json *v, |
1850 | 0 | int indent, int depth) { |
1851 | | /* Prevent C stack overflow on deeply nested JSON. |
1852 | | * Parser caps nesting at CJ_MAX_ITER; serializer uses the same limit. */ |
1853 | 0 | if (depth >= CJ_MAX_ITER) { |
1854 | 0 | return cj_sb_appends(sb, "null"); |
1855 | 0 | } |
1856 | 0 | switch (v->type_) { |
1857 | 0 | case CJ_NULL: |
1858 | 0 | return cj_sb_appends(sb, "null"); |
1859 | 0 | case CJ_BOOL: |
1860 | 0 | return cj_sb_appends(sb, v->b_ ? "true" : "false"); |
1861 | 0 | case CJ_INT: { |
1862 | 0 | char buf[32]; |
1863 | 0 | snprintf(buf, sizeof(buf), "%" PRId64, v->i_); |
1864 | 0 | return cj_sb_appends(sb, buf); |
1865 | 0 | } |
1866 | 0 | case CJ_REAL: { |
1867 | 0 | char buf[64]; |
1868 | 0 | double d = v->d_; |
1869 | | /* Non-finite values (NaN, Inf) cannot be represented in JSON. |
1870 | | * Detect by formatting first: nan/NaN starts with 'n'/'N'/'-n'/'-N', |
1871 | | * inf/Inf starts with 'i'/'I'/'-i'/'-I'. Output null for these. */ |
1872 | 0 | snprintf(buf, sizeof(buf), "%.17g", d); |
1873 | 0 | { |
1874 | 0 | const char *b = buf; |
1875 | 0 | if (*b == '-') ++b; |
1876 | 0 | if (*b == 'n' || *b == 'N' || *b == 'i' || *b == 'I') |
1877 | 0 | return cj_sb_appends(sb, "null"); |
1878 | 0 | } |
1879 | | /* Ensure there's a decimal point so the value round-trips as float */ |
1880 | 0 | if (!strchr(buf, '.') && !strchr(buf, 'e') && !strchr(buf, 'E')) { |
1881 | 0 | size_t bl = strlen(buf); |
1882 | 0 | if (bl + 2 < sizeof(buf)) { |
1883 | 0 | buf[bl] = '.'; |
1884 | 0 | buf[bl+1] = '0'; |
1885 | 0 | buf[bl+2] = '\0'; |
1886 | 0 | } |
1887 | 0 | } |
1888 | 0 | return cj_sb_appends(sb, buf); |
1889 | 0 | } |
1890 | 0 | case CJ_STRING: { |
1891 | | /* Defensive: if str_ is NULL (OOM during construction), use length 0. |
1892 | | * The invariant str_==NULL→str_len_==0 is enforced at all construction |
1893 | | * sites, but guard here in case of future callers. */ |
1894 | 0 | const char *s = v->str_ ? v->str_ : ""; |
1895 | 0 | size_t n = v->str_ ? v->str_len_ : 0u; |
1896 | 0 | return cj_append_str_escaped(sb, s, n); |
1897 | 0 | } |
1898 | 0 | case CJ_ARRAY: { |
1899 | 0 | if (!cj_sb_appendc(sb, '[')) return 0; |
1900 | 0 | for (size_t i = 0; i < v->arr_size_; ++i) { |
1901 | 0 | if (indent > 0 && !cj_indent_line(sb, indent, depth + 1)) return 0; |
1902 | 0 | if (!cj_serialize(sb, &v->arr_data_[i], indent, depth+1)) return 0; |
1903 | 0 | if (i + 1 < v->arr_size_ && !cj_sb_appendc(sb, ',')) return 0; |
1904 | 0 | } |
1905 | 0 | if (indent > 0 && v->arr_size_ > 0) |
1906 | 0 | if (!cj_indent_line(sb, indent, depth)) return 0; |
1907 | 0 | return cj_sb_appendc(sb, ']'); |
1908 | 0 | } |
1909 | 0 | case CJ_OBJECT: { |
1910 | 0 | if (!cj_sb_appendc(sb, '{')) return 0; |
1911 | 0 | for (size_t i = 0; i < v->obj_size_; ++i) { |
1912 | 0 | if (indent > 0 && !cj_indent_line(sb, indent, depth + 1)) return 0; |
1913 | 0 | const tinygltf_json_member *m = &v->obj_data_[i]; |
1914 | | /* Defensive: if key is NULL (OOM during insert), use length 0 */ |
1915 | 0 | const char *key = m->key ? m->key : ""; |
1916 | 0 | size_t keylen = m->key ? m->key_len : 0u; |
1917 | 0 | if (!cj_append_str_escaped(sb, key, keylen)) return 0; |
1918 | 0 | if (!cj_sb_appendc(sb, ':')) return 0; |
1919 | 0 | if (indent > 0 && !cj_sb_appendc(sb, ' ')) return 0; |
1920 | 0 | if (!cj_serialize(sb, &m->val, indent, depth + 1)) return 0; |
1921 | 0 | if (i + 1 < v->obj_size_ && !cj_sb_appendc(sb, ',')) return 0; |
1922 | 0 | } |
1923 | 0 | if (indent > 0 && v->obj_size_ > 0) |
1924 | 0 | if (!cj_indent_line(sb, indent, depth)) return 0; |
1925 | 0 | return cj_sb_appendc(sb, '}'); |
1926 | 0 | } |
1927 | 0 | default: |
1928 | 0 | return cj_sb_appends(sb, "null"); |
1929 | 0 | } |
1930 | 0 | } |
1931 | | |
1932 | | /* ====================================================================== |
1933 | | * tinygltf_json::dump() and ::parse() IMPLEMENTATIONS |
1934 | | * ====================================================================== */ |
1935 | | |
1936 | 0 | inline std::string tinygltf_json::dump(int indent) const { |
1937 | 0 | cj_strbuf sb; |
1938 | 0 | if (!cj_strbuf_init(&sb, 4096)) return std::string(); |
1939 | 0 | cj_serialize(&sb, this, indent, 0); |
1940 | 0 | std::string result(sb.data, sb.len); |
1941 | 0 | cj_strbuf_free_data(&sb); |
1942 | 0 | return result; |
1943 | 0 | } |
1944 | | |
1945 | | inline tinygltf_json tinygltf_json::parse(const char *first, const char *last, |
1946 | | std::nullptr_t, |
1947 | 0 | bool allow_exceptions) { |
1948 | 0 | cj_parse_ctx ctx; |
1949 | 0 | ctx.cur = first; |
1950 | 0 | ctx.end = last; |
1951 | 0 | ctx.err = 0; |
1952 | 0 | ctx.float32_mode = 0; |
1953 | 0 | ctx.errmsg[0] = '\0'; |
1954 | |
|
1955 | 0 | tinygltf_json result; |
1956 | 0 | cj_parse_json(&ctx, &result); |
1957 | |
|
1958 | 0 | if (ctx.err) { |
1959 | | #ifndef TINYGLTF_JSON_NO_EXCEPTIONS |
1960 | | if (allow_exceptions) { |
1961 | | throw std::invalid_argument( |
1962 | | std::string("tinygltf_json::parse error: ") + ctx.errmsg); |
1963 | | } |
1964 | | #else |
1965 | 0 | (void)allow_exceptions; |
1966 | 0 | #endif |
1967 | 0 | return tinygltf_json(); /* null on error */ |
1968 | 0 | } |
1969 | 0 | return result; |
1970 | 0 | } |
1971 | | |
1972 | 0 | inline tinygltf_json tinygltf_json::parse_float32(const char *first, const char *last) { |
1973 | 0 | cj_parse_ctx ctx; |
1974 | 0 | ctx.cur = first; |
1975 | 0 | ctx.end = last; |
1976 | 0 | ctx.err = 0; |
1977 | 0 | ctx.float32_mode = 1; |
1978 | 0 | ctx.errmsg[0] = '\0'; |
1979 | 0 |
|
1980 | 0 | tinygltf_json result; |
1981 | 0 | cj_parse_json(&ctx, &result); |
1982 | 0 |
|
1983 | 0 | if (ctx.err) return tinygltf_json(); |
1984 | 0 | return result; |
1985 | 0 | } |
1986 | | |
1987 | | /* ====================================================================== |
1988 | | * TINYGLTF DETAIL NAMESPACE COMPATIBILITY |
1989 | | * |
1990 | | * These declarations make the custom JSON backend available as |
1991 | | * tinygltf::detail types/functions when TINYGLTF_USE_CUSTOM_JSON is set. |
1992 | | * ====================================================================== */ |
1993 | | |
1994 | | namespace tinygltf { |
1995 | | namespace detail { |
1996 | | |
1997 | | using json = tinygltf_json; |
1998 | | using json_iterator = tinygltf_json::iterator; |
1999 | | using json_const_iterator = tinygltf_json::iterator; |
2000 | | using json_const_array_iterator = tinygltf_json::iterator; |
2001 | | using JsonDocument = tinygltf_json; |
2002 | | |
2003 | | inline void JsonParse(JsonDocument &doc, const char *str, size_t length, |
2004 | 0 | bool throwExc = false) { |
2005 | 0 | doc = tinygltf_json::parse(str, str + length, nullptr, throwExc); |
2006 | 0 | } |
2007 | | |
2008 | | /* --- Type accessors --- */ |
2009 | | |
2010 | 0 | inline bool GetInt(const json &o, int &val) { |
2011 | 0 | if (o.is_number_integer() || o.is_number_unsigned()) { |
2012 | 0 | val = o.get<int>(); |
2013 | 0 | return true; |
2014 | 0 | } |
2015 | 0 | return false; |
2016 | 0 | } |
2017 | | |
2018 | 0 | inline bool GetDouble(const json &o, double &val) { |
2019 | 0 | if (o.is_number_float()) { |
2020 | 0 | val = o.get<double>(); |
2021 | 0 | return true; |
2022 | 0 | } |
2023 | 0 | return false; |
2024 | 0 | } |
2025 | | |
2026 | 0 | inline bool GetNumber(const json &o, double &val) { |
2027 | 0 | if (o.is_number()) { |
2028 | 0 | val = o.get<double>(); |
2029 | 0 | return true; |
2030 | 0 | } |
2031 | 0 | return false; |
2032 | 0 | } |
2033 | | |
2034 | 0 | inline bool GetString(const json &o, std::string &val) { |
2035 | 0 | if (o.is_string()) { |
2036 | 0 | val = o.get<std::string>(); |
2037 | 0 | return true; |
2038 | 0 | } |
2039 | 0 | return false; |
2040 | 0 | } |
2041 | | |
2042 | 0 | inline bool IsArray(const json &o) { return o.is_array(); } |
2043 | 0 | inline bool IsObject(const json &o) { return o.is_object(); } |
2044 | 0 | inline bool IsEmpty(const json &o) { return o.empty(); } |
2045 | | |
2046 | 0 | inline json_const_array_iterator ArrayBegin(const json &o) { |
2047 | 0 | return o.begin(); |
2048 | 0 | } |
2049 | 0 | inline json_const_array_iterator ArrayEnd(const json &o) { |
2050 | 0 | return o.end(); |
2051 | 0 | } |
2052 | | |
2053 | 0 | inline json_const_iterator ObjectBegin(const json &o) { return o.begin(); } |
2054 | 0 | inline json_const_iterator ObjectEnd(const json &o) { return o.end(); } |
2055 | 0 | inline json_iterator ObjectBegin(json &o) { return o.begin(); } |
2056 | 0 | inline json_iterator ObjectEnd(json &o) { return o.end(); } |
2057 | | |
2058 | 0 | inline std::string GetKey(const json_const_iterator &it) { return it.key(); } |
2059 | 0 | inline std::string GetKey(json_iterator &it) { return it.key(); } |
2060 | | |
2061 | 0 | inline const json &GetValue(const json_const_iterator &it) { return *it; } |
2062 | 0 | inline json &GetValue(json_iterator &it) { return *it; } |
2063 | | |
2064 | | inline bool FindMember(const json &o, const char *member, |
2065 | 0 | json_const_iterator &it) { |
2066 | 0 | it = o.find(member); |
2067 | 0 | return it != o.end(); |
2068 | 0 | } |
2069 | 0 | inline bool FindMember(json &o, const char *member, json_iterator &it) { |
2070 | 0 | it = o.find(member); |
2071 | 0 | return it != o.end(); |
2072 | 0 | } |
2073 | | |
2074 | 0 | inline void Erase(json &o, json_iterator &it) { o.erase(it); } |
2075 | | |
2076 | 0 | inline std::string JsonToString(const json &o, int spacing = -1) { |
2077 | 0 | return o.dump(spacing); |
2078 | 0 | } |
2079 | | |
2080 | | /* --- Serialization helpers --- */ |
2081 | | |
2082 | 0 | inline json JsonFromString(const char *s) { return json(s); } |
2083 | | |
2084 | 0 | inline void JsonAssign(json &dest, const json &src) { dest = src; } |
2085 | | |
2086 | 0 | inline void JsonAddMember(json &o, const char *key, json &&value) { |
2087 | 0 | o[key] = static_cast<json &&>(value); |
2088 | 0 | } |
2089 | | |
2090 | 0 | inline void JsonPushBack(json &o, json &&value) { |
2091 | 0 | o.push_back(static_cast<json &&>(value)); |
2092 | 0 | } |
2093 | | |
2094 | 0 | inline bool JsonIsNull(const json &o) { return o.is_null(); } |
2095 | | |
2096 | 0 | inline void JsonSetObject(json &o) { o = json::object(); } |
2097 | | |
2098 | 0 | inline void JsonReserveArray(json &o, size_t /*s*/) { |
2099 | 0 | o.set_array(); |
2100 | 0 | } |
2101 | | |
2102 | | /* Stub allocator for RapidJSON-compatibility (not used by custom backend) */ |
2103 | | struct CJ_NoAllocator {}; |
2104 | 0 | inline CJ_NoAllocator &GetAllocator() { |
2105 | 0 | static CJ_NoAllocator alloc; |
2106 | 0 | return alloc; |
2107 | 0 | } |
2108 | | |
2109 | | } /* namespace detail */ |
2110 | | } /* namespace tinygltf */ |
2111 | | |
2112 | | #endif /* TINYGLTF_JSON_H_ */ |