FastLED 3.9.15
Loading...
Searching...
No Matches
json.cpp.hpp
Go to the documentation of this file.
1
2#include "fl/stl/json.h"
3#include "fl/stl/json/types.h"
4#include "fl/system/sketch_macros.h" // FL_PLATFORM_HAS_LARGE_MEMORY -- gates ieee754_format_decimal
5#include "fl/stl/string.h"
6#include "fl/stl/vector.h"
7#include "fl/stl/deque.h"
8#include "fl/stl/span.h"
9#include "fl/stl/charconv.h"
10#include "fl/stl/ieee754_string.h" // FastLED #3022 phase 2 -- integer-only IEEE 754 codec
11// IWYU pragma: begin_keep
12#include "fl/stl/bit_cast.h" // bit_cast<float>(u32) / bit_cast<u32>(float)
13// IWYU pragma: end_keep
14#include "fl/math/math.h" // For floor function
16#include "fl/stl/stdint.h"
17#include "fl/stl/optional.h"
18#include "fl/stl/flat_map.h"
19#include "fl/log/log.h"
20#include "fl/math/math.h"
21#include "fl/stl/cstddef.h"
22#include "fl/stl/cstring.h"
23#include "fl/stl/limits.h"
24#include "fl/stl/move.h"
25#include "fl/stl/strstream.h"
26#include "fl/stl/shared_ptr.h"
28#include "fl/stl/string_view.h"
29#include "fl/stl/noexcept.h"
30
31// fl::numeric_limits<i16>::min(), fl::numeric_limits<i16>::max(), and fl::numeric_limits<u8>::max() should come from the platform's
32// <stdint.h> or <cstdint> headers (via fl/stl/stdint.h).
33// FastLED no longer defines these macros to avoid conflicts with system headers.
34
35
36
37namespace fl {
38
39
40
41
42// Returns true iff `bits` represents a finite IEEE-754 single-precision value
43// whose magnitude exceeds 2**24 (the float-integer-precision boundary). Used to
44// decide whether an array of numbers can survive packing into `fl::vector<float>`
45// without integer-precision loss. Integer-only -- operates on the bit pattern.
46//
47// Math: |value| > 2**24 iff
48// biased_exp > 151 (exponent >= 25, magnitude >= 2**25), OR
49// biased_exp == 151 AND mantissa != 0 (in (2**24, 2**25), e.g. 16777217.0), OR
50// biased_exp == 0xFF (inf / NaN).
51// Pure `biased_exp > 151u` would miss the (2**24, 2**25) decade -- those values
52// still carry exponent 151 but have non-zero mantissa bits.
53// We treat NaN / inf as "beyond precision" so the caller's safe-fallback path
54// is taken -- matches the previous `canBeRepresentedAsFloat(double)` semantics
55// before this code was bit-twiddled (FastLED #3022 phase 2).
56static inline bool float_bits_magnitude_exceeds_2_24(u32 bits) FL_NOEXCEPT {
57 const u32 biased_exp = (bits >> 23) & 0xFFu;
58 const u32 mantissa = bits & 0x7FFFFFu;
59 return biased_exp == 0xFFu || biased_exp > 151u ||
60 (biased_exp == 151u && mantissa != 0u);
61}
62
63
65 static json_value null_value;
66 return null_value;
67}
68
70 static json_object empty_object;
71 return empty_object;
72}
73
74// ArduinoJson parser removed -- use json::parse() (native parser) instead.
75
76// ============================================================================
77// CUSTOM JSON PARSER - VISITOR PATTERN (Milestones 2-4)
78// ============================================================================
79
80namespace { // Anonymous namespace for internal implementation
81
82// Recursion limit protection
83constexpr int MAX_JSON_DEPTH = 32;
84
85// Token types
86// Windows headers define TRUE/FALSE macros that conflict with our enum values
87#ifdef TRUE
88#undef TRUE
89#endif
90#ifdef FALSE
91#undef FALSE
92#endif
93enum class JsonToken : u8 {
96
97 // Array lookahead optimization tokens
98 ARRAY_UINT8, // [0-255] -> vector<u8>
99 ARRAY_INT8, // [-128 to 127] (unused, covered by INT16)
100 ARRAY_INT16, // [-32768 to 32767] -> vector<i16>
101 ARRAY_INT32, // (unused, falls back to slow path)
102 ARRAY_INT64, // (unused, falls back to slow path)
103 ARRAY_FLOAT, // Floats or mixed int/float -> vector<float>
104 ARRAY_DOUBLE, // (unused, uses ARRAY_FLOAT)
105 ARRAY_STRING, // (unused, falls back to slow path)
106 ARRAY_BOOL, // (unused, falls back to slow path)
107 ARRAY_MIXED // (unused, falls back to slow path)
108};
109
110// Visitor return value
111enum class ParseState : u8 { KEEP_GOING = 0, ERROR = 1 };
112
113// Base visitor interface
115public:
117 virtual ~JsonVisitor() FL_NOEXCEPT = default;
118};
119
120// Character-by-character tokenizer
122private:
123 const char* mInput;
124 size_t mLen;
125 size_t mPos;
127
129 while (mPos < mLen && (mInput[mPos] == ' ' || mInput[mPos] == '\t' ||
130 mInput[mPos] == '\n' || mInput[mPos] == '\r')) {
131 mPos++;
132 }
133 }
134
135 // Array lookahead scanner - scans array to determine if it can be optimized
136 // Returns specialized token (ARRAY_UINT8, etc.) or LBRACKET for slow path
137 // Assumes mPos is positioned after '[' character
139 if (!mEnableLookahead) {
140 return JsonToken::LBRACKET; // Lookahead disabled
141 }
142
143 size_t start_pos = mPos; // Position after '['
144
145 // Track element types and ranges
146 bool has_int = false;
147 bool has_float = false;
148 bool has_float_beyond_precision = false; // Track floats > 2^24
149 bool has_string = false;
150 bool has_bool = false;
151 bool has_null = false;
154
156
157 // Empty array - use slow path
158 if (mPos < mLen && mInput[mPos] == ']') {
159 out_span = fl::span<const char>();
160 return JsonToken::LBRACKET;
161 }
162
163 // Scan elements
164 while (mPos < mLen) {
166
167 // Check for array end
168 if (mInput[mPos] == ']') {
169 break;
170 }
171
172 char c = mInput[mPos];
173
174 // Abort on nested structures
175 if (c == '[' || c == '{') {
176 return JsonToken::LBRACKET;
177 }
178
179 // STRING
180 if (c == '"') {
181 has_string = true;
182 mPos++;
183 while (mPos < mLen) {
184 if (mInput[mPos] == '\\') {
185 mPos += 2; // Skip escape sequence
186 } else if (mInput[mPos] == '"') {
187 mPos++;
188 break;
189 } else {
190 mPos++;
191 }
192 }
193 }
194 // NUMBER
195 else if (c == '-' || (c >= '0' && c <= '9')) {
196 size_t num_start = mPos;
197 bool is_float = false;
198
199 if (c == '-') mPos++;
200 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') mPos++;
201
202 if (mPos < mLen && mInput[mPos] == '.') {
203 is_float = true;
204 mPos++;
205 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') mPos++;
206 }
207
208 if (mPos < mLen && (mInput[mPos] == 'e' || mInput[mPos] == 'E')) {
209 is_float = true;
210 mPos++;
211 if (mPos < mLen && (mInput[mPos] == '+' || mInput[mPos] == '-')) mPos++;
212 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') mPos++;
213 }
214
215 if (is_float) {
216 has_float = true;
217#if FL_PLATFORM_HAS_LARGE_MEMORY
218 // Bit-twiddling parse -- never reaches libgcc soft-FP. The
219 // resulting u32 carries the same IEEE 754 single-precision
220 // bit pattern strtof would produce (+/-1 ULP). The magnitude
221 // check rides on the biased exponent so we avoid `fl::abs`.
222 const u32 f_bits = fl::ieee754_parse_decimal(&mInput[num_start], mPos - num_start);
224 has_float_beyond_precision = true;
225 }
226#else
227 // Low-memory gate per FastLED #3082: assume float values
228 // exceed 2^24 (forces slow path / generic array fallback).
229 // Drops `ieee754_parse_decimal` (598 B) + `kPow10Mant` LUT
230 // (824 B) + `kPow10BExp` (206 B) from the link.
231 has_float_beyond_precision = true;
232#endif
233 } else {
234 has_int = true;
235 // Parse actual value to get accurate range
236 // fl::parseInt doesn't allocate (Phase 1 test confirms zero allocations)
237 i64 val = fl::parseInt(&mInput[num_start], mPos - num_start);
238 if (val < int_min) int_min = val;
239 if (val > int_max) int_max = val;
240 }
241 }
242 // TRUE
243 else if (c == 't' && mPos + 3 < mLen &&
244 mInput[mPos+1] == 'r' && mInput[mPos+2] == 'u' && mInput[mPos+3] == 'e') {
245 has_bool = true;
246 mPos += 4;
247 }
248 // FALSE
249 else if (c == 'f' && mPos + 4 < mLen &&
250 mInput[mPos+1] == 'a' && mInput[mPos+2] == 'l' &&
251 mInput[mPos+3] == 's' && mInput[mPos+4] == 'e') {
252 has_bool = true;
253 mPos += 5;
254 }
255 // NULL
256 else if (c == 'n' && mPos + 3 < mLen &&
257 mInput[mPos+1] == 'u' && mInput[mPos+2] == 'l' && mInput[mPos+3] == 'l') {
258 has_null = true;
259 mPos += 4;
260 }
261 else {
262 return JsonToken::LBRACKET; // Unknown token
263 }
264
266
267 // Check for comma or end
268 if (mPos < mLen) {
269 if (mInput[mPos] == ',') {
270 mPos++;
271 } else if (mInput[mPos] == ']') {
272 break;
273 } else {
274 return JsonToken::LBRACKET; // Invalid syntax
275 }
276 }
277 }
278
279 // Determine array type
280 int type_count = (has_int ? 1 : 0) + (has_float ? 1 : 0) +
281 (has_string ? 1 : 0) + (has_bool ? 1 : 0) + (has_null ? 1 : 0);
282
283 out_span = fl::span<const char>(&mInput[start_pos], mPos - start_pos);
284
285 // Only emit specialized tokens for types json_value supports
286 if (type_count == 1 || (type_count == 2 && has_int && has_float)) {
287 if (has_float || (has_int && has_float)) {
288 // Don't optimize floats beyond integer precision
289 if (has_float_beyond_precision) {
290 return JsonToken::LBRACKET; // Use slow path
291 }
292 return JsonToken::ARRAY_FLOAT; // vector<float>
293 }
294 if (has_int) {
295 // Determine optimal integer type
296 if (int_min >= 0 && int_max <= 255) {
297 return JsonToken::ARRAY_UINT8; // vector<u8>
298 }
299 if (int_min >= -32768 && int_max <= 32767) {
300 return JsonToken::ARRAY_INT16; // vector<i16>
301 }
302 // Larger ints fall back to slow path
303 return JsonToken::LBRACKET;
304 }
305 // Strings, bools - use slow path
306 return JsonToken::LBRACKET;
307 }
308
309 // Mixed types - use slow path
310 return JsonToken::LBRACKET;
311 }
312
315 if (mPos >= mLen) {
316 out_value = fl::span<const char>();
318 }
319
320 char c = mInput[mPos];
321
322 // Structural tokens
323 switch (c) {
324 case '{':
325 out_value = fl::span<const char>(&mInput[mPos], 1);
326 mPos++;
327 return JsonToken::LBRACE;
328 case '}':
329 out_value = fl::span<const char>(&mInput[mPos], 1);
330 mPos++;
331 return JsonToken::RBRACE;
332 case '[': {
333 size_t saved_pos = mPos;
334 mPos++; // Skip '['
335 JsonToken array_token = scan_array_lookahead(out_value);
336 if (array_token != JsonToken::LBRACKET) {
337 // Specialized array token - mPos now points at ']'
338 mPos++; // Skip ']'
339 return array_token;
340 }
341 // Slow path - restore position
342 mPos = saved_pos;
343 out_value = fl::span<const char>(&mInput[mPos], 1);
344 mPos++;
345 return JsonToken::LBRACKET;
346 }
347 case ']':
348 out_value = fl::span<const char>(&mInput[mPos], 1);
349 mPos++;
350 return JsonToken::RBRACKET;
351 case ':':
352 out_value = fl::span<const char>(&mInput[mPos], 1);
353 mPos++;
354 return JsonToken::COLON;
355 case ',':
356 out_value = fl::span<const char>(&mInput[mPos], 1);
357 mPos++;
358 return JsonToken::COMMA;
359 default:
360 break; // Fall through to other token types
361 }
362
363 // STRING tokenization
364 if (c == '"') {
365 size_t start = mPos + 1; // After opening quote
366 mPos++;
367
368 while (mPos < mLen) {
369 if (mInput[mPos] == '\\') {
370 // Skip escape sequence
371 mPos++;
372 if (mPos >= mLen) {
373 out_value = fl::span<const char>();
374 return JsonToken::ERROR; // Unclosed string
375 }
376 mPos++; // Skip escaped character
377 } else if (mInput[mPos] == '"') {
378 // Found closing quote
379 out_value = fl::span<const char>(&mInput[start], mPos - start);
380 mPos++; // Move past closing quote
381 return JsonToken::STRING;
382 } else {
383 mPos++;
384 }
385 }
386
387 // Unclosed string
388 out_value = fl::span<const char>();
389 return JsonToken::ERROR;
390 }
391
392 // NUMBER tokenization
393 if (c == '-' || (c >= '0' && c <= '9')) {
394 size_t start = mPos;
395
396 // Optional minus
397 if (c == '-') mPos++;
398
399 // Require at least one digit
400 if (mPos >= mLen || !(mInput[mPos] >= '0' && mInput[mPos] <= '9')) {
401 out_value = fl::span<const char>();
402 return JsonToken::ERROR;
403 }
404
405 // Digits before decimal
406 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') {
407 mPos++;
408 }
409
410 // Optional decimal point
411 if (mPos < mLen && mInput[mPos] == '.') {
412 mPos++;
413 // Require digits after decimal
414 if (mPos >= mLen || !(mInput[mPos] >= '0' && mInput[mPos] <= '9')) {
415 out_value = fl::span<const char>();
416 return JsonToken::ERROR;
417 }
418 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') {
419 mPos++;
420 }
421 }
422
423 // Optional exponent
424 if (mPos < mLen && (mInput[mPos] == 'e' || mInput[mPos] == 'E')) {
425 mPos++;
426 // Optional sign
427 if (mPos < mLen && (mInput[mPos] == '+' || mInput[mPos] == '-')) {
428 mPos++;
429 }
430 // Require exponent digits
431 if (mPos >= mLen || !(mInput[mPos] >= '0' && mInput[mPos] <= '9')) {
432 out_value = fl::span<const char>();
433 return JsonToken::ERROR;
434 }
435 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') {
436 mPos++;
437 }
438 }
439
440 out_value = fl::span<const char>(&mInput[start], mPos - start);
441 return JsonToken::NUMBER;
442 }
443
444 // Keyword: true
445 if (c == 't' && mPos + 3 < mLen &&
446 mInput[mPos+1] == 'r' && mInput[mPos+2] == 'u' && mInput[mPos+3] == 'e') {
447 out_value = fl::span<const char>(&mInput[mPos], 4);
448 mPos += 4;
449 return JsonToken::TRUE;
450 }
451
452 // Keyword: false
453 if (c == 'f' && mPos + 4 < mLen &&
454 mInput[mPos+1] == 'a' && mInput[mPos+2] == 'l' &&
455 mInput[mPos+3] == 's' && mInput[mPos+4] == 'e') {
456 out_value = fl::span<const char>(&mInput[mPos], 5);
457 mPos += 5;
458 return JsonToken::FALSE;
459 }
460
461 // Keyword: null
462 if (c == 'n' && mPos + 3 < mLen &&
463 mInput[mPos+1] == 'u' && mInput[mPos+2] == 'l' && mInput[mPos+3] == 'l') {
464 out_value = fl::span<const char>(&mInput[mPos], 4);
465 mPos += 4;
467 }
468
469 // Unknown token
470 out_value = fl::span<const char>();
471 return JsonToken::ERROR;
472 }
473
474public:
475 JsonTokenizer(bool enable_lookahead = true) : mInput(nullptr), mLen(0), mPos(0), mEnableLookahead(enable_lookahead) {}
476
477 bool parse(const fl::string& input, JsonVisitor& visitor) {
478 return parse(fl::string_view(input.c_str(), input.length()), visitor);
479 }
480
481 bool parse(fl::string_view input, JsonVisitor& visitor) {
482 mInput = input.data();
483 mLen = input.size();
484 mPos = 0;
485
486 while (true) {
487 fl::span<const char> token_value;
488 JsonToken token = next_token(token_value);
489
490 if (token == JsonToken::ERROR) {
491 return false;
492 }
493
494 ParseState result = visitor.on_token(token, token_value);
495 if (result == ParseState::ERROR) {
496 return false;
497 }
498
499 if (token == JsonToken::END_OF_INPUT) {
500 break;
501 }
502 }
503
504 return true;
505 }
506};
507
508// Validator - validates JSON structure with bracket matching (Milestone 8)
510private:
512 bool mExpectKey; // In object, expecting key next
513 bool mExpectValue; // Expecting value next
514 bool mExpectColon; // Expecting colon after key
516
517public:
519
521 (void)value; // Suppress unused parameter warning
522
523 // Recursion depth check
524 if (mDepth > MAX_JSON_DEPTH) {
525 FL_ERROR("JSON parser: FATAL - recursion depth exceeded " << MAX_JSON_DEPTH);
526 return ParseState::ERROR;
527 }
528
529 switch (token) {
531 mBracketStack.push_back('{');
532 mDepth++;
533 mExpectKey = true;
534 mExpectValue = false;
536
538 if (mBracketStack.empty() || mBracketStack.back() != '{') {
539 return ParseState::ERROR; // Unmatched closing brace
540 }
541 mBracketStack.pop_back();
542 mDepth--;
543 mExpectKey = false;
545
547 mBracketStack.push_back('[');
548 mDepth++;
549 mExpectValue = true;
551
553 if (mBracketStack.empty() || mBracketStack.back() != '[') {
554 return ParseState::ERROR; // Unmatched closing bracket
555 }
556 mBracketStack.pop_back();
557 mDepth--;
558 mExpectValue = false;
560
561 case JsonToken::COLON:
562 if (!mExpectColon) {
563 return ParseState::ERROR; // Unexpected colon
564 }
565 mExpectColon = false;
566 mExpectValue = true;
568
570 if (!mBracketStack.empty() && mBracketStack.back() == '{' && mExpectKey) {
571 // This is an object key
572 mExpectKey = false;
573 mExpectColon = true;
574 } else if (mExpectValue || mBracketStack.empty()) {
575 // This is a value
576 mExpectValue = false;
577 } else {
578 return ParseState::ERROR;
579 }
581
583 case JsonToken::TRUE:
584 case JsonToken::FALSE:
586 // Specialized array tokens (complete arrays, treat as values)
590 if (mExpectValue || mBracketStack.empty()) {
591 mExpectValue = false;
593 }
594 return ParseState::ERROR;
595
596 case JsonToken::COMMA:
597 // Set expectations for next element
598 if (!mBracketStack.empty()) {
599 if (mBracketStack.back() == '{') {
600 mExpectKey = true;
601 } else {
602 mExpectValue = true;
603 }
604 }
606
609
610 default:
611 return ParseState::ERROR;
612 }
613 }
614
615 bool is_valid() const {
616 return mBracketStack.empty() && !mExpectColon && !mExpectKey;
617 }
618};
619
620// Helper: Check if string contains escape sequences
622 for (size_t i = 0; i < span.size(); i++) {
623 if (span[i] == '\\') {
624 return true;
625 }
626 }
627 return false;
628}
629
630// Helper: Unescape JSON string (only call if has_escape_sequences() returns true)
633 result.reserve(span.size());
634
635 for (size_t i = 0; i < span.size(); i++) {
636 if (span[i] == '\\' && i + 1 < span.size()) {
637 char next = span[i + 1];
638 switch (next) {
639 case '"': result += '"'; i++; break;
640 case '\\': result += '\\'; i++; break;
641 case '/': result += '/'; i++; break;
642 case 'b': result += '\b'; i++; break;
643 case 'f': result += '\f'; i++; break;
644 case 'n': result += '\n'; i++; break;
645 case 'r': result += '\r'; i++; break;
646 case 't': result += '\t'; i++; break;
647 default: result += span[i]; break; // Pass through unknown escapes
648 }
649 } else {
650 result += span[i];
651 }
652 }
653
654 return result;
655}
656
657// Array optimization helpers (Milestone 9)
659
661 if (arr.empty()) return GENERIC_ARRAY;
662
663 bool all_numeric = true;
666 bool has_float = false;
667 bool has_float_beyond_precision = false;
668
669 for (const auto& elem : arr) {
670 if (!elem) {
671 all_numeric = false;
672 break;
673 }
674
675 if (elem->is_int()) {
676 auto val = elem->as_int();
677 if (val) {
678 i64 v = *val;
679 min_val = (v < min_val) ? v : min_val;
680 max_val = (v > max_val) ? v : max_val;
681 } else {
682 all_numeric = false;
683 break;
684 }
685 } else if (elem->is_float()) {
686 has_float = true;
687 auto val = elem->as_float();
688 if (!val) {
689 all_numeric = false;
690 break;
691 }
692 // Check if float is beyond integer precision (>2^24 or <-2^24).
693 // Integer biased-exp comparison -- no FP arithmetic (#3022 phase 2).
694 const u32 fbits = fl::bit_cast<u32>(*val);
696 has_float_beyond_precision = true;
697 }
698 } else {
699 all_numeric = false;
700 break;
701 }
702 }
703
704 if (!all_numeric) return GENERIC_ARRAY;
705
706 // Classification
707 if (has_float) {
708 // If floats are beyond integer precision, don't optimize
709 if (has_float_beyond_precision) {
710 return GENERIC_ARRAY;
711 }
712 return ALL_FLOATS;
713 }
714
715 // Integer arrays
716 if (min_val >= 0 && max_val <= 255) {
717 return ALL_UINT8;
718 }
719
720 if (min_val >= -32768 && max_val <= 32767) {
721 return ALL_INT16;
722 }
723
724 // Large integers (>32767 or <-32768) that don't fit in int16 but can be represented as float
725 // Convert to float if within float's integer precision range (+/-2^24)
726 if (min_val >= -16777216 && max_val <= 16777216) {
727 return ALL_FLOATS;
728 }
729
730 return GENERIC_ARRAY;
731}
732
734 auto arr = array_val->data.ptr<json_array>();
735 if (!arr) return array_val;
736
738
739 switch (type) {
740 case ALL_UINT8: {
741 fl::vector<u8> vec;
742 vec.reserve(arr->size());
743 for (const auto& elem : *arr) {
744 auto val = elem->as_int();
745 if (val) vec.push_back(static_cast<u8>(*val));
746 }
748 }
749
750 case ALL_INT16: {
751 fl::vector<i16> vec;
752 vec.reserve(arr->size());
753 for (const auto& elem : *arr) {
754 auto val = elem->as_int();
755 if (val) vec.push_back(static_cast<i16>(*val));
756 }
758 }
759
760 case ALL_FLOATS: {
762 vec.reserve(arr->size());
763 for (const auto& elem : *arr) {
764 if (elem->is_float()) {
765 auto val = elem->as_float();
766 if (val) vec.push_back(*val);
767 } else if (elem->is_int()) {
768 auto val = elem->as_int();
769 if (val) {
770 // Route i64 -> float via i32 to avoid the direct cast
771 // pulling libgcc-nofp's `_floatdisf.o` (~5 KB of soft-FP
772 // double cascade) on no-FPU targets. ALL_FLOATS
773 // classification only fires when every int is within
774 // +/-2^24 anyway (classify_array line 717), so the int32
775 // narrowing is lossless. See FastLED #3076.
776 vec.push_back(static_cast<float>(static_cast<fl::i32>(*val)));
777 }
778 }
779 }
781 }
782
783 default:
784 return array_val; // Keep as generic array
785 }
786}
787
788// Builder - handles nested objects/arrays with stack management (Milestone 6)
789class JsonBuilder : public JsonVisitor {
790private:
791 struct StackFrame {
794 fl::string pending_key; // For objects: key waiting for value
795 };
796
797 fl::vector_inlined<StackFrame, 8> mStack; // Inline first 8 frames (most JSON is shallow)
800 // String interning enabled for large strings (> 64 bytes) that overflow SSO
802
803 // Parse integer array directly from span into vector (zero allocations)
804 template<typename T>
806 const char* p = span.data();
807 const char* end = p + span.size();
808
809 while (p < end) {
810 // Skip whitespace
811 while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
812 if (p >= end) break;
813
814 // Parse number
815 const char* num_start = p;
816 if (*p == '-') p++;
817 while (p < end && *p >= '0' && *p <= '9') p++;
818
819 i64 val = fl::parseInt(num_start, p - num_start);
820 out_vec.push_back(static_cast<T>(val));
821
822 // Skip whitespace
823 while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
824
825 // Skip comma
826 if (p < end && *p == ',') p++;
827 }
828
829 return true;
830 }
831
832 // Parse float array directly from span into vector (zero allocations)
833 template<typename T>
835 const char* p = span.data();
836 const char* end = p + span.size();
837
838 while (p < end) {
839 // Skip whitespace
840 while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
841 if (p >= end) break;
842
843 // Parse number
844 const char* num_start = p;
845 bool is_float = false;
846
847 if (*p == '-') p++;
848 while (p < end && *p >= '0' && *p <= '9') p++;
849
850 if (p < end && *p == '.') {
851 is_float = true;
852 p++;
853 while (p < end && *p >= '0' && *p <= '9') p++;
854 }
855
856 if (p < end && (*p == 'e' || *p == 'E')) {
857 is_float = true;
858 p++;
859 if (p < end && (*p == '+' || *p == '-')) p++;
860 while (p < end && *p >= '0' && *p <= '9') p++;
861 }
862
863 T val;
864 if (is_float) {
865#if FL_PLATFORM_HAS_LARGE_MEMORY
866 // Bit-twiddling parse -> IEEE 754 bit pattern -> bit-cast to float.
867 // The `static_cast<T>` from `float` is the caller's responsibility
868 // (e.g. T=float costs nothing; T=int pulls one __aeabi_f2iz at the
869 // call site, which we accept per the "user opts in" mandate).
870 const u32 bits = fl::ieee754_parse_decimal(num_start, p - num_start);
871 val = static_cast<T>(fl::bit_cast<float>(bits));
872#else
873 // Low-memory gate per FastLED #3082: float JSON literals in
874 // packed-array parse path saturate to zero. The integer-only
875 // RPC contract on LPC8xx / AVR never emits floats here.
876 val = static_cast<T>(0);
877#endif
878 } else {
879 val = static_cast<T>(fl::parseInt(num_start, p - num_start));
880 }
881 out_vec.push_back(val);
882
883 // Skip whitespace
884 while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
885
886 // Skip comma
887 if (p < end && *p == ',') p++;
888 }
889
890 return true;
891 }
892
894 if (mStack.empty()) {
895 mRoot = val;
896 } else {
897 StackFrame& top = mStack.back();
898
899 if (top.type == StackFrame::OBJECT) {
900 // Attach to object with pending key
901 auto obj = top.value->data.ptr<json_object>();
902 if (obj && !top.pending_key.empty()) {
903 (*obj)[top.pending_key] = val;
904 top.pending_key.clear();
905 }
906 } else {
907 // Attach to array
908 auto arr = top.value->data.ptr<json_array>();
909 if (arr) {
910 arr->push_back(val);
911 }
912 }
913 }
914 }
915
916public:
918
920 // Recursion depth check
921 if (mDepth > MAX_JSON_DEPTH) {
922 FL_ERROR("JSON parser: FATAL - recursion depth exceeded " << MAX_JSON_DEPTH);
923 return ParseState::ERROR;
924 }
925
926 switch (token) {
927 // Specialized array tokens - parse directly into typed vectors
929 fl::vector<u8> vec;
930 if (!parse_int_array(value, vec)) return ParseState::ERROR;
931 auto arr_val = fl::make_shared<json_value>(fl::move(vec));
932 push_value(arr_val);
934 }
935
937 fl::vector<i16> vec;
938 if (!parse_int_array(value, vec)) return ParseState::ERROR;
939 auto arr_val = fl::make_shared<json_value>(fl::move(vec));
940 push_value(arr_val);
942 }
943
946 if (!parse_float_array(value, vec)) return ParseState::ERROR;
947 auto arr_val = fl::make_shared<json_value>(fl::move(vec));
948 push_value(arr_val);
950 }
951
952 case JsonToken::LBRACE: {
954 // Don't push to parent yet - will push in RBRACE
955 mStack.push_back({StackFrame::OBJECT, obj_val, ""});
956 mDepth++;
958 }
959
960 case JsonToken::RBRACE: {
961 if (!mStack.empty() && mStack.back().type == StackFrame::OBJECT) {
962 auto obj_val = mStack.back().value;
963 mStack.pop_back();
964 mDepth--;
965 // Push to parent after object is complete
966 push_value(obj_val);
967 }
969 }
970
971 case JsonToken::LBRACKET: {
972 auto arr_val = fl::make_shared<json_value>(json_array{});
973 // Don't push to parent yet - will push in RBRACKET after optimization
974 mStack.push_back({StackFrame::ARRAY, arr_val, ""});
975 mDepth++;
977 }
978
979 case JsonToken::RBRACKET: {
980 if (!mStack.empty() && mStack.back().type == StackFrame::ARRAY) {
981 // Optimize array before pushing to parent (Milestone 9)
982 auto arr_val = optimize_array(mStack.back().value);
983 mStack.pop_back();
984 mDepth--;
985 // Push optimized array to parent
986 push_value(arr_val);
987 }
989 }
990
991 case JsonToken::STRING: {
992 // Check if this is an object key (next token should be COLON)
993 if (!mStack.empty() && mStack.back().type == StackFrame::OBJECT &&
994 mStack.back().pending_key.empty()) {
995 // Key: handle escape sequences, then intern (StringInterner handles SSO internally)
997 mStack.back().pending_key = mInterner.intern(unescape_string(value));
998 } else {
999 mStack.back().pending_key = mInterner.intern(value);
1000 }
1001 } else {
1002 // Value: handle escape sequences, then intern (StringInterner handles SSO internally)
1003 fl::string str;
1005 str = mInterner.intern(unescape_string(value));
1006 } else {
1007 str = mInterner.intern(value);
1008 }
1009 auto str_val = fl::make_shared<json_value>(str);
1010 push_value(str_val);
1011 }
1012
1014 }
1015
1016 case JsonToken::NUMBER: {
1017 // Determine if int or float by checking for decimal/exponent
1018 bool is_float = false;
1019 for (size_t i = 0; i < value.size(); i++) {
1020 if (value[i] == '.' || value[i] == 'e' || value[i] == 'E') {
1021 is_float = true;
1022 break;
1023 }
1024 }
1025
1027 if (is_float) {
1028#if FL_PLATFORM_HAS_LARGE_MEMORY
1029 // Bit-twiddling parse -> IEEE 754 bits -> store as float in the
1030 // variant. The `bit_cast<float>` is a `memcpy` -- no libgcc
1031 // soft-FP helper is reached on the parser hot path
1032 // (FastLED #3022 phase 2).
1033 const u32 bits = fl::ieee754_parse_decimal(value.data(), value.size());
1035#else
1036 // Low-memory gate per FastLED #3082: float JSON literals
1037 // parse to 0.0f, no `ieee754_parse_decimal` / kPow10Mant
1038 // anchored. Integer-only RPC contract on LPC8xx / AVR.
1039 num_val = fl::make_shared<json_value>(0.0f);
1040#endif
1041 } else {
1042 int i = fl::parseInt(value.data(), value.size());
1043 i64 i64_val = static_cast<i64>(i);
1044 num_val = fl::make_shared<json_value>(i64_val);
1045 }
1046
1047 push_value(num_val);
1049 }
1050
1051 case JsonToken::TRUE:
1054
1055 case JsonToken::FALSE:
1058
1062
1063 case JsonToken::COLON:
1064 case JsonToken::COMMA:
1065 // Structural tokens - no action needed
1067
1070
1071 default:
1072 return ParseState::ERROR;
1073 }
1074 }
1075
1079};
1080
1081} // namespace
1082
1083// PARSE2 IMPLEMENTATION - Milestone 8: Two-phase parser with validation
1085 JsonTokenizer tokenizer;
1086
1087 // Phase 1: Validate
1088 JsonValidator validator;
1089 if (!tokenizer.parse(txt, validator) || !validator.is_valid()) {
1090 return fl::make_shared<json_value>(nullptr);
1091 }
1092
1093 // Phase 2: Build
1094 JsonBuilder builder;
1095 if (!tokenizer.parse(txt, builder)) {
1096 return fl::make_shared<json_value>(nullptr);
1097 }
1098
1099 return builder.get_result();
1100}
1101
1102// Phase 1 validation only (for testing - MUST allocate zero heap memory)
1106
1108 JsonTokenizer tokenizer;
1109 JsonValidator validator;
1110 return tokenizer.parse(txt, validator) && validator.is_valid();
1111}
1112
1114 // Parse the JSON value to a string, then parse it back to a json object,
1115 // and use the working to_string_native method
1116 // This is a workaround to avoid reimplementing the serialization logic
1117
1118 // First, create a temporary json document with this value
1119 json temp;
1120 // Use the public method to set the value
1122 // Use the working implementation
1123 return temp.to_string_native();
1124}
1125
1126// Recursive serializer visitor - declared and defined at file scope
1127// Receives direct references to variant data (no copies) and handles serialization recursively
1130
1131 void append(const char* str) {
1132 while (*str) { out.push_back(*str++); }
1133 }
1134
1135 void append_str(const fl::string& str) {
1136 for (size_t i = 0; i < str.size(); ++i) {
1137 out.push_back(str[i]);
1138 }
1139 }
1140
1141 void append_escaped(const fl::string& str) {
1142 out.push_back('"');
1143 for (size_t i = 0; i < str.size(); ++i) {
1144 char c = str[i];
1145 switch (c) {
1146 case '"': append("\\\""); break;
1147 case '\\': append("\\\\"); break;
1148 case '\n': append("\\n"); break;
1149 case '\r': append("\\r"); break;
1150 case '\t': append("\\t"); break;
1151 case '\b': append("\\b"); break;
1152 case '\f': append("\\f"); break;
1153 default: out.push_back(c); break;
1154 }
1155 }
1156 out.push_back('"');
1157 }
1158
1159 // Serialize a json_value recursively
1161 if (!value) {
1162 append("null");
1163 return;
1164 }
1165 value->data.visit(*this);
1166 }
1167
1168 // accept() overloads - called by variant::visit() with direct references
1169 void accept(const fl::nullptr_t&) { append("null"); }
1170
1171 void accept(const bool& b) { append(b ? "true" : "false"); }
1172
1173 void accept(const i64& i) {
1174 fl::string num_str;
1175 num_str.append(i);
1176 append_str(num_str);
1177 }
1178
1179 void accept(const float& f) {
1180#if FL_PLATFORM_HAS_LARGE_MEMORY
1181 // Integer-only bits -> decimal (FastLED #3022 phase 2). Default 3-digit
1182 // precision matches the previous `num_str.append(f, 3)` contract.
1184#else
1185 // Low-memory targets gate the float decimal codec to drop
1186 // `ieee754_format_decimal` (594 B) + `kPow10Mant` LUT (824 B) from the
1187 // link. The integer-only RPC contract on LPC8xx / AVR-class targets
1188 // never serializes float values; this writes a stub for the rare cases
1189 // where a `json(float)` round-trip happens to be exercised in test
1190 // code that also runs on Low-memory. See FastLED #3082.
1191 (void)f;
1192 append_str(fl::string("0"));
1193#endif
1194 }
1195
1196 void accept(const fl::string& s) { append_escaped(s); }
1197
1198 void accept(const json_array& arr) {
1199 if (arr.empty()) {
1200 append("[]");
1201 return;
1202 }
1203 out.push_back('[');
1204 bool first = true;
1205 for (const auto& item : arr) {
1206 if (!first) out.push_back(',');
1207 first = false;
1208 serialize_value(item ? item.get() : &get_null_json_value());
1209 }
1210 out.push_back(']');
1211 }
1212
1213 void accept(const json_object& obj) {
1214 if (obj.empty()) {
1215 append("{}");
1216 return;
1217 }
1218 out.push_back('{');
1219 bool first = true;
1220 for (const auto& kv : obj) {
1221 if (!first) out.push_back(',');
1222 first = false;
1223 append_escaped(kv.first);
1224 out.push_back(':');
1225 serialize_value(kv.second ? kv.second.get() : &get_null_json_value());
1226 }
1227 out.push_back('}');
1228 }
1229
1231 out.push_back('[');
1232 bool first = true;
1233 for (const auto& item : audio) {
1234 if (!first) out.push_back(',');
1235 first = false;
1236 fl::string num_str;
1237 num_str.append(static_cast<int>(item));
1238 append_str(num_str);
1239 }
1240 out.push_back(']');
1241 }
1242
1243 void accept(const fl::vector<u8>& bytes) {
1244 out.push_back('[');
1245 bool first = true;
1246 for (const auto& item : bytes) {
1247 if (!first) out.push_back(',');
1248 first = false;
1249 fl::string num_str;
1250 num_str.append(static_cast<int>(item));
1251 append_str(num_str);
1252 }
1253 out.push_back(']');
1254 }
1255
1256 void accept(const fl::vector<float>& floats) {
1257 out.push_back('[');
1258 bool first = true;
1259 for (const auto& item : floats) {
1260 if (!first) out.push_back(',');
1261 first = false;
1262#if FL_PLATFORM_HAS_LARGE_MEMORY
1263 // Integer-only bits -> decimal per element (FastLED #3022 phase 2).
1264 // 6-digit precision preserves the previous output contract.
1266#else
1267 // Low-memory gate per FastLED #3082; see scalar `accept(float)`.
1268 (void)item;
1269 append_str(fl::string("0"));
1270#endif
1271 }
1272 out.push_back(']');
1273 }
1274};
1275
1277 if (!mValue) {
1278 return "null";
1279 }
1280
1281 // Use fl::deque for memory-efficient JSON serialization
1282 fl::deque<char> json_chars;
1283
1284 // Use the visitor to serialize the value recursively
1285 SerializerVisitor visitor{json_chars};
1286 visitor.serialize_value(mValue.get());
1287
1288 // Convert deque to fl::string efficiently
1290 if (!json_chars.empty()) {
1291 result.assign(&json_chars[0], json_chars.size());
1292 }
1293
1294 return result;
1295}
1296
1297// Forward declaration for the serializeValue function
1299
1300
1303 if (!jsonStr) {
1304 return result;
1305 }
1306 size_t len = strlen(jsonStr);
1307 for (size_t i = 0; i < len; ++i) {
1308 char c = jsonStr[i];
1309 if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
1310 result += c;
1311 }
1312 }
1313 return result;
1314}
1315
1316} // namespace fl
ParseState on_token(JsonToken token, const fl::span< const char > &value) override
Definition json.cpp.hpp:919
void push_value(const fl::shared_ptr< json_value > &val)
Definition json.cpp.hpp:893
bool parse_float_array(const fl::span< const char > &span, fl::vector< T > &out_vec)
Definition json.cpp.hpp:834
fl::vector_inlined< StackFrame, 8 > mStack
Definition json.cpp.hpp:797
bool parse_int_array(const fl::span< const char > &span, fl::vector< T > &out_vec)
Definition json.cpp.hpp:805
JsonToken scan_array_lookahead(fl::span< const char > &out_span)
Definition json.cpp.hpp:138
bool parse(const fl::string &input, JsonVisitor &visitor)
Definition json.cpp.hpp:477
bool parse(fl::string_view input, JsonVisitor &visitor)
Definition json.cpp.hpp:481
JsonToken next_token(fl::span< const char > &out_value)
Definition json.cpp.hpp:313
ParseState on_token(JsonToken token, const fl::span< const char > &value) override
Definition json.cpp.hpp:520
virtual ParseState on_token(JsonToken token, const fl::span< const char > &value)=0
fl::size length() const FL_NOEXCEPT
bool empty() const FL_NOEXCEPT
void clear(bool freeMemory=false) FL_NOEXCEPT
const char * c_str() const FL_NOEXCEPT
fl::size size() const FL_NOEXCEPT
fl::size size() const
Definition deque.h:428
bool empty() const
Definition deque.h:424
bool empty() const FL_NOEXCEPT
Definition flat_map.h:97
fl::shared_ptr< json_value > mValue
Definition json.h:130
static fl::string normalize_json_string(const char *jsonStr) FL_NOEXCEPT
fl::string to_string_native() const FL_NOEXCEPT
void set_value(const fl::shared_ptr< json_value > &value) FL_NOEXCEPT
Definition json.h:663
const T * data() const FL_NOEXCEPT
Definition span.h:461
constexpr fl::size size() const FL_NOEXCEPT
Definition span.h:458
constexpr fl::size size() const FL_NOEXCEPT
Definition string_view.h:99
constexpr const char * data() const FL_NOEXCEPT
Definition string_view.h:94
string & append(const bitset_fixed< N > &bs) FL_NOEXCEPT
Definition string.h:284
bool empty() const FL_NOEXCEPT
void reserve(fl::size n) FL_NOEXCEPT
Definition vector.h:591
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:624
FastLED's Elegant JSON Library: fl::json
#define FL_ERROR(X)
Definition log.h:219
Centralized logging categories for FastLED hardware interfaces and subsystems.
fl::string unescape_string(const fl::span< const char > &span)
Definition json.cpp.hpp:631
bool has_escape_sequences(const fl::span< const char > &span)
Definition json.cpp.hpp:621
ArrayType classify_array(const json_array &arr)
Definition json.cpp.hpp:660
fl::shared_ptr< json_value > optimize_array(fl::shared_ptr< json_value > array_val)
Definition json.cpp.hpp:733
decltype(nullptr) nullptr_t
Definition s16x16x4.h:13
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
Definition s16x16x4.h:28
fl::string ieee754_format_decimal(u32 bits, int precision) FL_NOEXCEPT
Format IEEE 754 single-precision bits as decimal text.
unsigned char u8
Definition stdint.h:131
constexpr int type_rank< T >::value
size_t strlen(const char *s) FL_NOEXCEPT
int parseInt(const char *str, fl::size len)
Parse an integer from a character buffer.
constexpr T * end(T(&array)[N]) FL_NOEXCEPT
json_object & get_empty_json_obj()
Definition json.cpp.hpp:69
fl::i64 i64
Definition s16x16x4.h:222
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
fl::vector< fl::shared_ptr< json_value > > json_array
Definition types.h:33
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
json_value & get_null_json_value()
Definition json.cpp.hpp:64
fl::flat_map< fl::string, fl::shared_ptr< json_value >, fl::StringFastLess > json_object
Definition types.h:34
fl::string serializeValue(const json_value &value)
VectorN< T, INLINED_SIZE > vector_inlined
Definition vector.h:1133
To bit_cast(const From &from) FL_NOEXCEPT
Definition bit_cast.h:48
u32 ieee754_parse_decimal(const char *s, fl::size len, fl::size *consumed) FL_NOEXCEPT
Parse a decimal floating-point number into IEEE 754 single-precision bits.
static bool float_bits_magnitude_exceeds_2_24(u32 bits) FL_NOEXCEPT
Definition json.cpp.hpp:56
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT
void accept(const float &f)
void accept(const fl::string &s)
void accept(const json_object &obj)
void accept(const fl::vector< i16 > &audio)
void accept(const fl::vector< float > &floats)
void accept(const bool &b)
void accept(const fl::vector< u8 > &bytes)
void append_escaped(const fl::string &str)
fl::deque< char > & out
void append(const char *str)
void append_str(const fl::string &str)
void accept(const json_array &arr)
void accept(const i64 &i)
void serialize_value(const json_value *value)
void accept(const fl::nullptr_t &)
enum fl::anonymous_namespace{json.cpp.hpp}::JsonBuilder::StackFrame::Type type
fl::string to_string() const FL_NOEXCEPT
static bool parse2_validate_only(const fl::string &txt) FL_NOEXCEPT
friend class json
Definition types.h:681
static fl::shared_ptr< json_value > parse2(const fl::string &txt) FL_NOEXCEPT
static constexpr T min() FL_NOEXCEPT
Definition limits.h:107
static constexpr T max() FL_NOEXCEPT
Definition limits.h:108