FastLED 3.10.9
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/span.h"
8#include "fl/stl/charconv.h"
9#include "fl/stl/ieee754_string.h" // FastLED #3022 phase 2 -- integer-only IEEE 754 codec
10// IWYU pragma: begin_keep
11#include "fl/stl/bit_cast.h" // bit_cast<float>(u32) / bit_cast<u32>(float)
12// IWYU pragma: end_keep
13#include "fl/math/math.h" // For floor function
15#include "fl/stl/stdint.h"
16#include "fl/stl/optional.h"
17#include "fl/stl/flat_map.h"
18#include "fl/log/log.h"
19#include "fl/math/math.h"
20#include "fl/stl/cstddef.h"
21#include "fl/stl/cstring.h"
22#include "fl/stl/limits.h"
23#include "fl/stl/move.h"
24#include "fl/stl/strstream.h"
25#include "fl/stl/shared_ptr.h"
27#include "fl/stl/string_view.h"
28#include "fl/stl/noexcept.h"
29
30// fl::numeric_limits<i16>::min(), fl::numeric_limits<i16>::max(), and fl::numeric_limits<u8>::max() should come from the platform's
31// <stdint.h> or <cstdint> headers (via fl/stl/stdint.h).
32// FastLED no longer defines these macros to avoid conflicts with system headers.
33
34
35
36namespace fl {
37
38
39
40
41// Returns true iff `bits` represents a finite IEEE-754 single-precision value
42// whose magnitude exceeds 2**24 (the float-integer-precision boundary). Used to
43// decide whether an array of numbers can survive packing into `fl::vector<float>`
44// without integer-precision loss. Integer-only -- operates on the bit pattern.
45//
46// Math: |value| > 2**24 iff
47// biased_exp > 151 (exponent >= 25, magnitude >= 2**25), OR
48// biased_exp == 151 AND mantissa != 0 (in (2**24, 2**25), e.g. 16777217.0), OR
49// biased_exp == 0xFF (inf / NaN).
50// Pure `biased_exp > 151u` would miss the (2**24, 2**25) decade -- those values
51// still carry exponent 151 but have non-zero mantissa bits.
52// We treat NaN / inf as "beyond precision" so the caller's safe-fallback path
53// is taken -- matches the previous `canBeRepresentedAsFloat(double)` semantics
54// before this code was bit-twiddled (FastLED #3022 phase 2).
55static inline bool float_bits_magnitude_exceeds_2_24(u32 bits) FL_NO_EXCEPT {
56 const u32 biased_exp = (bits >> 23) & 0xFFu;
57 const u32 mantissa = bits & 0x7FFFFFu;
58 return biased_exp == 0xFFu || biased_exp > 151u ||
59 (biased_exp == 151u && mantissa != 0u);
60}
61
62
64 static json_value null_value;
65 return null_value;
66}
67
69 static json_object empty_object;
70 return empty_object;
71}
72
73// ArduinoJson parser removed -- use json::parse() (native parser) instead.
74
75// ============================================================================
76// CUSTOM JSON PARSER - VISITOR PATTERN (Milestones 2-4)
77// ============================================================================
78
79namespace { // Anonymous namespace for internal implementation
80
81// Recursion limit protection
82constexpr int MAX_JSON_DEPTH = 32;
83
84// Token types
85// Windows headers define TRUE/FALSE macros that conflict with our enum values
86#ifdef TRUE
87#undef TRUE
88#endif
89#ifdef FALSE
90#undef FALSE
91#endif
92enum class JsonToken : u8 {
95
96 // Array lookahead optimization tokens
97 ARRAY_UINT8, // [0-255] -> vector<u8>
98 ARRAY_INT8, // [-128 to 127] (unused, covered by INT16)
99 ARRAY_INT16, // [-32768 to 32767] -> vector<i16>
100 ARRAY_INT32, // (unused, falls back to slow path)
101 ARRAY_INT64, // (unused, falls back to slow path)
102 ARRAY_FLOAT, // Floats or mixed int/float -> vector<float>
103 ARRAY_DOUBLE, // (unused, uses ARRAY_FLOAT)
104 ARRAY_STRING, // (unused, falls back to slow path)
105 ARRAY_BOOL, // (unused, falls back to slow path)
106 ARRAY_MIXED // (unused, falls back to slow path)
107};
108
109// Visitor return value
110enum class ParseState : u8 { KEEP_GOING = 0, ERROR = 1 };
111
112// Base visitor interface
114public:
116 virtual ~JsonVisitor() FL_NO_EXCEPT = default;
117};
118
119// Character-by-character tokenizer
121private:
122 const char* mInput;
123 size_t mLen;
124 size_t mPos;
126
128 while (mPos < mLen && (mInput[mPos] == ' ' || mInput[mPos] == '\t' ||
129 mInput[mPos] == '\n' || mInput[mPos] == '\r')) {
130 mPos++;
131 }
132 }
133
134 // Array lookahead scanner - scans array to determine if it can be optimized
135 // Returns specialized token (ARRAY_UINT8, etc.) or LBRACKET for slow path
136 // Assumes mPos is positioned after '[' character
138 if (!mEnableLookahead) {
139 return JsonToken::LBRACKET; // Lookahead disabled
140 }
141
142 size_t start_pos = mPos; // Position after '['
143
144 // Track element types and ranges
145 bool has_int = false;
146 bool has_float = false;
147 bool has_float_beyond_precision = false; // Track floats > 2^24
148 bool has_string = false;
149 bool has_bool = false;
150 bool has_null = false;
153
155
156 // Empty array - use slow path
157 if (mPos < mLen && mInput[mPos] == ']') {
158 out_span = fl::span<const char>();
159 return JsonToken::LBRACKET;
160 }
161
162 // Scan elements
163 while (mPos < mLen) {
165
166 // Check for array end
167 if (mInput[mPos] == ']') {
168 break;
169 }
170
171 char c = mInput[mPos];
172
173 // Abort on nested structures
174 if (c == '[' || c == '{') {
175 return JsonToken::LBRACKET;
176 }
177
178 // STRING
179 if (c == '"') {
180 has_string = true;
181 mPos++;
182 while (mPos < mLen) {
183 if (mInput[mPos] == '\\') {
184 mPos += 2; // Skip escape sequence
185 } else if (mInput[mPos] == '"') {
186 mPos++;
187 break;
188 } else {
189 mPos++;
190 }
191 }
192 }
193 // NUMBER
194 else if (c == '-' || (c >= '0' && c <= '9')) {
195 size_t num_start = mPos;
196 bool is_float = false;
197
198 if (c == '-') mPos++;
199 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') mPos++;
200
201 if (mPos < mLen && mInput[mPos] == '.') {
202 is_float = true;
203 mPos++;
204 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') mPos++;
205 }
206
207 if (mPos < mLen && (mInput[mPos] == 'e' || mInput[mPos] == 'E')) {
208 is_float = true;
209 mPos++;
210 if (mPos < mLen && (mInput[mPos] == '+' || mInput[mPos] == '-')) mPos++;
211 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') mPos++;
212 }
213
214 if (is_float) {
215 has_float = true;
216#if FL_PLATFORM_HAS_LARGE_MEMORY
217 // Bit-twiddling parse -- never reaches libgcc soft-FP. The
218 // resulting u32 carries the same IEEE 754 single-precision
219 // bit pattern strtof would produce (+/-1 ULP). The magnitude
220 // check rides on the biased exponent so we avoid `fl::abs`.
221 const u32 f_bits = fl::ieee754_parse_decimal(&mInput[num_start], mPos - num_start);
223 has_float_beyond_precision = true;
224 }
225#else
226 // Low-memory gate per FastLED #3082: assume float values
227 // exceed 2^24 (forces slow path / generic array fallback).
228 // Drops `ieee754_parse_decimal` (598 B) + `kPow10Mant` LUT
229 // (824 B) + `kPow10BExp` (206 B) from the link.
230 has_float_beyond_precision = true;
231#endif
232 } else {
233 has_int = true;
234 // Parse actual value to get accurate range
235 // fl::parseInt doesn't allocate (Phase 1 test confirms zero allocations)
236 i64 val = fl::parseInt(&mInput[num_start], mPos - num_start);
237 if (val < int_min) int_min = val;
238 if (val > int_max) int_max = val;
239 }
240 }
241 // TRUE
242 else if (c == 't' && mPos + 3 < mLen &&
243 mInput[mPos+1] == 'r' && mInput[mPos+2] == 'u' && mInput[mPos+3] == 'e') {
244 has_bool = true;
245 mPos += 4;
246 }
247 // FALSE
248 else if (c == 'f' && mPos + 4 < mLen &&
249 mInput[mPos+1] == 'a' && mInput[mPos+2] == 'l' &&
250 mInput[mPos+3] == 's' && mInput[mPos+4] == 'e') {
251 has_bool = true;
252 mPos += 5;
253 }
254 // NULL
255 else if (c == 'n' && mPos + 3 < mLen &&
256 mInput[mPos+1] == 'u' && mInput[mPos+2] == 'l' && mInput[mPos+3] == 'l') {
257 has_null = true;
258 mPos += 4;
259 }
260 else {
261 return JsonToken::LBRACKET; // Unknown token
262 }
263
265
266 // Check for comma or end
267 if (mPos < mLen) {
268 if (mInput[mPos] == ',') {
269 mPos++;
270 } else if (mInput[mPos] == ']') {
271 break;
272 } else {
273 return JsonToken::LBRACKET; // Invalid syntax
274 }
275 }
276 }
277
278 // Determine array type
279 int type_count = (has_int ? 1 : 0) + (has_float ? 1 : 0) +
280 (has_string ? 1 : 0) + (has_bool ? 1 : 0) + (has_null ? 1 : 0);
281
282 out_span = fl::span<const char>(&mInput[start_pos], mPos - start_pos);
283
284 // Only emit specialized tokens for types json_value supports
285 if (type_count == 1 || (type_count == 2 && has_int && has_float)) {
286 if (has_float || (has_int && has_float)) {
287 // Don't optimize floats beyond integer precision
288 if (has_float_beyond_precision) {
289 return JsonToken::LBRACKET; // Use slow path
290 }
291 return JsonToken::ARRAY_FLOAT; // vector<float>
292 }
293 if (has_int) {
294 // Determine optimal integer type
295 if (int_min >= 0 && int_max <= 255) {
296 return JsonToken::ARRAY_UINT8; // vector<u8>
297 }
298 if (int_min >= -32768 && int_max <= 32767) {
299 return JsonToken::ARRAY_INT16; // vector<i16>
300 }
301 // Larger ints fall back to slow path
302 return JsonToken::LBRACKET;
303 }
304 // Strings, bools - use slow path
305 return JsonToken::LBRACKET;
306 }
307
308 // Mixed types - use slow path
309 return JsonToken::LBRACKET;
310 }
311
314 if (mPos >= mLen) {
315 out_value = fl::span<const char>();
317 }
318
319 char c = mInput[mPos];
320
321 // Structural tokens
322 switch (c) {
323 case '{':
324 out_value = fl::span<const char>(&mInput[mPos], 1);
325 mPos++;
326 return JsonToken::LBRACE;
327 case '}':
328 out_value = fl::span<const char>(&mInput[mPos], 1);
329 mPos++;
330 return JsonToken::RBRACE;
331 case '[': {
332#if FL_PLATFORM_HAS_LARGE_MEMORY
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#else
347 // Low-memory gate per #3224 Tier 2E: skip the array-packing
348 // lookahead. The packed-array tokens (ARRAY_UINT8 / INT16 /
349 // FLOAT) are gated out of JsonBuilder::on_token on Low-memory,
350 // so scanning ahead to detect them anyway is dead work. Drops
351 // `scan_array_lookahead` + `classify_array_token` + friends
352 // (~700 B of state-machine code).
353 out_value = fl::span<const char>(&mInput[mPos], 1);
354 mPos++;
355 return JsonToken::LBRACKET;
356#endif
357 }
358 case ']':
359 out_value = fl::span<const char>(&mInput[mPos], 1);
360 mPos++;
361 return JsonToken::RBRACKET;
362 case ':':
363 out_value = fl::span<const char>(&mInput[mPos], 1);
364 mPos++;
365 return JsonToken::COLON;
366 case ',':
367 out_value = fl::span<const char>(&mInput[mPos], 1);
368 mPos++;
369 return JsonToken::COMMA;
370 default:
371 break; // Fall through to other token types
372 }
373
374 // STRING tokenization
375 if (c == '"') {
376 size_t start = mPos + 1; // After opening quote
377 mPos++;
378
379 while (mPos < mLen) {
380 if (mInput[mPos] == '\\') {
381 // Skip escape sequence
382 mPos++;
383 if (mPos >= mLen) {
384 out_value = fl::span<const char>();
385 return JsonToken::ERROR; // Unclosed string
386 }
387 mPos++; // Skip escaped character
388 } else if (mInput[mPos] == '"') {
389 // Found closing quote
390 out_value = fl::span<const char>(&mInput[start], mPos - start);
391 mPos++; // Move past closing quote
392 return JsonToken::STRING;
393 } else {
394 mPos++;
395 }
396 }
397
398 // Unclosed string
399 out_value = fl::span<const char>();
400 return JsonToken::ERROR;
401 }
402
403 // NUMBER tokenization
404 if (c == '-' || (c >= '0' && c <= '9')) {
405 size_t start = mPos;
406
407 // Optional minus
408 if (c == '-') mPos++;
409
410 // Require at least one digit
411 if (mPos >= mLen || !(mInput[mPos] >= '0' && mInput[mPos] <= '9')) {
412 out_value = fl::span<const char>();
413 return JsonToken::ERROR;
414 }
415
416 // Digits before decimal
417 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') {
418 mPos++;
419 }
420
421 // Optional decimal point
422 if (mPos < mLen && mInput[mPos] == '.') {
423 mPos++;
424 // Require digits after decimal
425 if (mPos >= mLen || !(mInput[mPos] >= '0' && mInput[mPos] <= '9')) {
426 out_value = fl::span<const char>();
427 return JsonToken::ERROR;
428 }
429 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') {
430 mPos++;
431 }
432 }
433
434 // Optional exponent
435 if (mPos < mLen && (mInput[mPos] == 'e' || mInput[mPos] == 'E')) {
436 mPos++;
437 // Optional sign
438 if (mPos < mLen && (mInput[mPos] == '+' || mInput[mPos] == '-')) {
439 mPos++;
440 }
441 // Require exponent digits
442 if (mPos >= mLen || !(mInput[mPos] >= '0' && mInput[mPos] <= '9')) {
443 out_value = fl::span<const char>();
444 return JsonToken::ERROR;
445 }
446 while (mPos < mLen && mInput[mPos] >= '0' && mInput[mPos] <= '9') {
447 mPos++;
448 }
449 }
450
451 out_value = fl::span<const char>(&mInput[start], mPos - start);
452 return JsonToken::NUMBER;
453 }
454
455 // Keyword: true
456 if (c == 't' && mPos + 3 < mLen &&
457 mInput[mPos+1] == 'r' && mInput[mPos+2] == 'u' && mInput[mPos+3] == 'e') {
458 out_value = fl::span<const char>(&mInput[mPos], 4);
459 mPos += 4;
460 return JsonToken::TRUE;
461 }
462
463 // Keyword: false
464 if (c == 'f' && mPos + 4 < mLen &&
465 mInput[mPos+1] == 'a' && mInput[mPos+2] == 'l' &&
466 mInput[mPos+3] == 's' && mInput[mPos+4] == 'e') {
467 out_value = fl::span<const char>(&mInput[mPos], 5);
468 mPos += 5;
469 return JsonToken::FALSE;
470 }
471
472 // Keyword: null
473 if (c == 'n' && mPos + 3 < mLen &&
474 mInput[mPos+1] == 'u' && mInput[mPos+2] == 'l' && mInput[mPos+3] == 'l') {
475 out_value = fl::span<const char>(&mInput[mPos], 4);
476 mPos += 4;
478 }
479
480 // Unknown token
481 out_value = fl::span<const char>();
482 return JsonToken::ERROR;
483 }
484
485public:
486 JsonTokenizer(bool enable_lookahead = true) FL_NO_EXCEPT : mInput(nullptr), mLen(0), mPos(0), mEnableLookahead(enable_lookahead) {}
487
488 bool parse(const fl::string& input, JsonVisitor& visitor) FL_NO_EXCEPT {
489 return parse(fl::string_view(input.c_str(), input.length()), visitor);
490 }
491
493 mInput = input.data();
494 mLen = input.size();
495 mPos = 0;
496
497 while (true) {
498 fl::span<const char> token_value;
499 JsonToken token = next_token(token_value);
500
501 if (token == JsonToken::ERROR) {
502 return false;
503 }
504
505 ParseState result = visitor.on_token(token, token_value);
506 if (result == ParseState::ERROR) {
507 return false;
508 }
509
510 if (token == JsonToken::END_OF_INPUT) {
511 break;
512 }
513 }
514
515 return true;
516 }
517};
518
519// Validator - validates JSON structure with bracket matching (Milestone 8)
521private:
523 bool mExpectKey; // In object, expecting key next
524 bool mExpectValue; // Expecting value next
525 bool mExpectColon; // Expecting colon after key
527
528public:
530
532 (void)value; // Suppress unused parameter warning
533
534 // Recursion depth check
535 if (mDepth > MAX_JSON_DEPTH) {
536 FL_ERROR("JSON parser: FATAL - recursion depth exceeded " << MAX_JSON_DEPTH);
537 return ParseState::ERROR;
538 }
539
540 switch (token) {
542 mBracketStack.push_back('{');
543 mDepth++;
544 mExpectKey = true;
545 mExpectValue = false;
547
549 if (mBracketStack.empty() || mBracketStack.back() != '{') {
550 return ParseState::ERROR; // Unmatched closing brace
551 }
552 mBracketStack.pop_back();
553 mDepth--;
554 mExpectKey = false;
556
558 mBracketStack.push_back('[');
559 mDepth++;
560 mExpectValue = true;
562
564 if (mBracketStack.empty() || mBracketStack.back() != '[') {
565 return ParseState::ERROR; // Unmatched closing bracket
566 }
567 mBracketStack.pop_back();
568 mDepth--;
569 mExpectValue = false;
571
572 case JsonToken::COLON:
573 if (!mExpectColon) {
574 return ParseState::ERROR; // Unexpected colon
575 }
576 mExpectColon = false;
577 mExpectValue = true;
579
581 if (!mBracketStack.empty() && mBracketStack.back() == '{' && mExpectKey) {
582 // This is an object key
583 mExpectKey = false;
584 mExpectColon = true;
585 } else if (mExpectValue || mBracketStack.empty()) {
586 // This is a value
587 mExpectValue = false;
588 } else {
589 return ParseState::ERROR;
590 }
592
594 case JsonToken::TRUE:
595 case JsonToken::FALSE:
597 // Specialized array tokens (complete arrays, treat as values)
601 if (mExpectValue || mBracketStack.empty()) {
602 mExpectValue = false;
604 }
605 return ParseState::ERROR;
606
607 case JsonToken::COMMA:
608 // Set expectations for next element
609 if (!mBracketStack.empty()) {
610 if (mBracketStack.back() == '{') {
611 mExpectKey = true;
612 } else {
613 mExpectValue = true;
614 }
615 }
617
620
621 default:
622 return ParseState::ERROR;
623 }
624 }
625
626 bool is_valid() const FL_NO_EXCEPT {
627 return mBracketStack.empty() && !mExpectColon && !mExpectKey;
628 }
629};
630
631// Helper: Check if string contains escape sequences
633 for (size_t i = 0; i < span.size(); i++) {
634 if (span[i] == '\\') {
635 return true;
636 }
637 }
638 return false;
639}
640
641// Reads four hex digits at span[pos]; returns -1 if they are not all hex.
643 if (pos + 4 > span.size()) return -1;
644 long value = 0;
645 for (size_t k = 0; k < 4; ++k) {
646 char h = span[pos + k];
647 int digit;
648 if (h >= '0' && h <= '9') digit = h - '0';
649 else if (h >= 'a' && h <= 'f') digit = h - 'a' + 10;
650 else if (h >= 'A' && h <= 'F') digit = h - 'A' + 10;
651 else return -1;
652 value = (value << 4) | digit;
653 }
654 return value;
655}
656
657void append_utf8(fl::string& out, unsigned long cp) FL_NO_EXCEPT {
658 if (cp < 0x80) {
659 out += static_cast<char>(cp);
660 } else if (cp < 0x800) {
661 out += static_cast<char>(0xC0 | (cp >> 6));
662 out += static_cast<char>(0x80 | (cp & 0x3F));
663 } else if (cp < 0x10000) {
664 out += static_cast<char>(0xE0 | (cp >> 12));
665 out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
666 out += static_cast<char>(0x80 | (cp & 0x3F));
667 } else {
668 out += static_cast<char>(0xF0 | (cp >> 18));
669 out += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
670 out += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
671 out += static_cast<char>(0x80 | (cp & 0x3F));
672 }
673}
674
675// Helper: Unescape JSON string (only call if has_escape_sequences() returns true)
678 result.reserve(span.size());
679
680 for (size_t i = 0; i < span.size(); i++) {
681 if (span[i] == '\\' && i + 1 < span.size()) {
682 char next = span[i + 1];
683 switch (next) {
684 case '"': result += '"'; i++; break;
685 case '\\': result += '\\'; i++; break;
686 case '/': result += '/'; i++; break;
687 case 'b': result += '\b'; i++; break;
688 case 'f': result += '\f'; i++; break;
689 case 'n': result += '\n'; i++; break;
690 case 'r': result += '\r'; i++; break;
691 case 't': result += '\t'; i++; break;
692 case 'u': {
693 long cp = read_hex4(span, i + 2);
694 if (cp < 0) {
695 result += span[i]; // Malformed: pass through
696 break;
697 }
698 size_t consumed = 5;
699 // Combine a UTF-16 surrogate pair into one code point.
700 if (cp >= 0xD800 && cp <= 0xDBFF && i + 11 < span.size() &&
701 span[i + 6] == '\\' && span[i + 7] == 'u') {
702 long lo = read_hex4(span, i + 8);
703 if (lo >= 0xDC00 && lo <= 0xDFFF) {
704 cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
705 consumed = 11;
706 }
707 }
708 // An unpaired surrogate has no UTF-8 encoding; emit U+FFFD.
709 if (cp >= 0xD800 && cp <= 0xDFFF) {
710 cp = 0xFFFD;
711 }
712 append_utf8(result, static_cast<unsigned long>(cp));
713 i += consumed;
714 break;
715 }
716 default: result += span[i]; break; // Pass through unknown escapes
717 }
718 } else {
719 result += span[i];
720 }
721 }
722
723 return result;
724}
725
726// Array optimization helpers (Milestone 9)
728
730 if (arr.empty()) return GENERIC_ARRAY;
731
732 bool all_numeric = true;
735 bool has_float = false;
736 bool has_float_beyond_precision = false;
737
738 for (const auto& elem : arr) {
739 if (!elem) {
740 all_numeric = false;
741 break;
742 }
743
744 if (elem->is_int()) {
745 auto val = elem->as_int();
746 if (val) {
747 i64 v = *val;
748 min_val = (v < min_val) ? v : min_val;
749 max_val = (v > max_val) ? v : max_val;
750 } else {
751 all_numeric = false;
752 break;
753 }
754 } else if (elem->is_float()) {
755 has_float = true;
756 auto val = elem->as_float();
757 if (!val) {
758 all_numeric = false;
759 break;
760 }
761 // Check if float is beyond integer precision (>2^24 or <-2^24).
762 // Integer biased-exp comparison -- no FP arithmetic (#3022 phase 2).
763 const u32 fbits = fl::bit_cast<u32>(*val);
765 has_float_beyond_precision = true;
766 }
767 } else {
768 all_numeric = false;
769 break;
770 }
771 }
772
773 if (!all_numeric) return GENERIC_ARRAY;
774
775 // Classification
776 if (has_float) {
777 // If floats are beyond integer precision, don't optimize
778 if (has_float_beyond_precision) {
779 return GENERIC_ARRAY;
780 }
781 return ALL_FLOATS;
782 }
783
784 // Integer arrays
785 if (min_val >= 0 && max_val <= 255) {
786 return ALL_UINT8;
787 }
788
789 if (min_val >= -32768 && max_val <= 32767) {
790 return ALL_INT16;
791 }
792
793 // Large integers (>32767 or <-32768) that don't fit in int16 but can be represented as float
794 // Convert to float if within float's integer precision range (+/-2^24)
795 if (min_val >= -16777216 && max_val <= 16777216) {
796 return ALL_FLOATS;
797 }
798
799 return GENERIC_ARRAY;
800}
801
803 auto arr = array_val->data.ptr<json_array>();
804 if (!arr) return array_val;
805
807
808 switch (type) {
809 case ALL_UINT8: {
810 fl::vector<u8> vec;
811 vec.reserve(arr->size());
812 for (const auto& elem : *arr) {
813 auto val = elem->as_int();
814 if (val) vec.push_back(static_cast<u8>(*val));
815 }
817 }
818
819 case ALL_INT16: {
820 fl::vector<i16> vec;
821 vec.reserve(arr->size());
822 for (const auto& elem : *arr) {
823 auto val = elem->as_int();
824 if (val) vec.push_back(static_cast<i16>(*val));
825 }
827 }
828
829 case ALL_FLOATS: {
831 vec.reserve(arr->size());
832 for (const auto& elem : *arr) {
833 if (elem->is_float()) {
834 auto val = elem->as_float();
835 if (val) vec.push_back(*val);
836 } else if (elem->is_int()) {
837 auto val = elem->as_int();
838 if (val) {
839 // Route i64 -> float via i32 to avoid the direct cast
840 // pulling libgcc-nofp's `_floatdisf.o` (~5 KB of soft-FP
841 // double cascade) on no-FPU targets. ALL_FLOATS
842 // classification only fires when every int is within
843 // +/-2^24 anyway (classify_array line 717), so the int32
844 // narrowing is lossless. See FastLED #3076.
845 vec.push_back(static_cast<float>(static_cast<fl::i32>(*val)));
846 }
847 }
848 }
850 }
851
852 default:
853 return array_val; // Keep as generic array
854 }
855}
856
857// Builder - handles nested objects/arrays with stack management (Milestone 6)
858class JsonBuilder : public JsonVisitor {
859private:
860 struct StackFrame {
863 fl::string pending_key; // For objects: key waiting for value
864 };
865
866 fl::vector_inlined<StackFrame, 8> mStack; // Inline first 8 frames (most JSON is shallow)
869 // String interning enabled for large strings (> 64 bytes) that overflow SSO
871
872 // Parse integer array directly from span into vector (zero allocations)
873 template<typename T>
875 const char* p = span.data();
876 const char* end = p + span.size();
877
878 while (p < end) {
879 // Skip whitespace
880 while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
881 if (p >= end) break;
882
883 // Parse number
884 const char* num_start = p;
885 if (*p == '-') p++;
886 while (p < end && *p >= '0' && *p <= '9') p++;
887
888 i64 val = fl::parseInt(num_start, p - num_start);
889 out_vec.push_back(static_cast<T>(val));
890
891 // Skip whitespace
892 while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
893
894 // Skip comma
895 if (p < end && *p == ',') p++;
896 }
897
898 return true;
899 }
900
901 // Parse float array directly from span into vector (zero allocations)
902 template<typename T>
904 const char* p = span.data();
905 const char* end = p + span.size();
906
907 while (p < end) {
908 // Skip whitespace
909 while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
910 if (p >= end) break;
911
912 // Parse number
913 const char* num_start = p;
914 bool is_float = false;
915
916 if (*p == '-') p++;
917 while (p < end && *p >= '0' && *p <= '9') p++;
918
919 if (p < end && *p == '.') {
920 is_float = true;
921 p++;
922 while (p < end && *p >= '0' && *p <= '9') p++;
923 }
924
925 if (p < end && (*p == 'e' || *p == 'E')) {
926 is_float = true;
927 p++;
928 if (p < end && (*p == '+' || *p == '-')) p++;
929 while (p < end && *p >= '0' && *p <= '9') p++;
930 }
931
932 T val;
933 if (is_float) {
934#if FL_PLATFORM_HAS_LARGE_MEMORY
935 // Bit-twiddling parse -> IEEE 754 bit pattern -> bit-cast to float.
936 // The `static_cast<T>` from `float` is the caller's responsibility
937 // (e.g. T=float costs nothing; T=int pulls one __aeabi_f2iz at the
938 // call site, which we accept per the "user opts in" mandate).
939 const u32 bits = fl::ieee754_parse_decimal(num_start, p - num_start);
940 val = static_cast<T>(fl::bit_cast<float>(bits));
941#else
942 // Low-memory gate per FastLED #3082: float JSON literals in
943 // packed-array parse path saturate to zero. The integer-only
944 // RPC contract on LPC8xx / AVR never emits floats here.
945 val = static_cast<T>(0);
946#endif
947 } else {
948 val = static_cast<T>(fl::parseInt(num_start, p - num_start));
949 }
950 out_vec.push_back(val);
951
952 // Skip whitespace
953 while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
954
955 // Skip comma
956 if (p < end && *p == ',') p++;
957 }
958
959 return true;
960 }
961
963 if (mStack.empty()) {
964 mRoot = val;
965 } else {
966 StackFrame& top = mStack.back();
967
968 if (top.type == StackFrame::OBJECT) {
969 // Attach to object with pending key
970 auto obj = top.value->data.ptr<json_object>();
971 if (obj && !top.pending_key.empty()) {
972 (*obj)[top.pending_key] = val;
973 top.pending_key.clear();
974 }
975 } else {
976 // Attach to array
977 auto arr = top.value->data.ptr<json_array>();
978 if (arr) {
979 arr->push_back(val);
980 }
981 }
982 }
983 }
984
985public:
987
989 // Recursion depth check
990 if (mDepth > MAX_JSON_DEPTH) {
991 FL_ERROR("JSON parser: FATAL - recursion depth exceeded " << MAX_JSON_DEPTH);
992 return ParseState::ERROR;
993 }
994
995 switch (token) {
996 // Specialized array tokens - parse directly into typed vectors.
997 // Gated on Low-memory per #3224 Tier 2D: these packed-array tokens
998 // only fire when the tokenizer's `scan_array_lookahead` decides an
999 // array is uniformly-typed and small. LowMemory targets don't use
1000 // the array-packing optimization (the integer-only RPC contract on
1001 // LPC8xx never sends packed vectors), so dropping these cases
1002 // collapses 3 large make_shared<json_value>(vector<T>) variant
1003 // emplace instantiations + parse_int_array + parse_float_array.
1004#if FL_PLATFORM_HAS_LARGE_MEMORY
1006 fl::vector<u8> vec;
1007 if (!parse_int_array(value, vec)) return ParseState::ERROR;
1008 auto arr_val = fl::make_shared<json_value>(fl::move(vec));
1009 push_value(arr_val);
1011 }
1012
1014 fl::vector<i16> vec;
1015 if (!parse_int_array(value, vec)) return ParseState::ERROR;
1016 auto arr_val = fl::make_shared<json_value>(fl::move(vec));
1017 push_value(arr_val);
1019 }
1020
1023 if (!parse_float_array(value, vec)) return ParseState::ERROR;
1024 auto arr_val = fl::make_shared<json_value>(fl::move(vec));
1025 push_value(arr_val);
1027 }
1028#endif
1029
1030 case JsonToken::LBRACE: {
1031 auto obj_val = fl::make_shared<json_value>(json_object{});
1032 // Don't push to parent yet - will push in RBRACE
1033 mStack.push_back({StackFrame::OBJECT, obj_val, ""});
1034 mDepth++;
1036 }
1037
1038 case JsonToken::RBRACE: {
1039 if (!mStack.empty() && mStack.back().type == StackFrame::OBJECT) {
1040 auto obj_val = mStack.back().value;
1041 mStack.pop_back();
1042 mDepth--;
1043 // Push to parent after object is complete
1044 push_value(obj_val);
1045 }
1047 }
1048
1049 case JsonToken::LBRACKET: {
1050 auto arr_val = fl::make_shared<json_value>(json_array{});
1051 // Don't push to parent yet - will push in RBRACKET after optimization
1052 mStack.push_back({StackFrame::ARRAY, arr_val, ""});
1053 mDepth++;
1055 }
1056
1057 case JsonToken::RBRACKET: {
1058 if (!mStack.empty() && mStack.back().type == StackFrame::ARRAY) {
1059 // Optimize array before pushing to parent (Milestone 9)
1060 auto arr_val = optimize_array(mStack.back().value);
1061 mStack.pop_back();
1062 mDepth--;
1063 // Push optimized array to parent
1064 push_value(arr_val);
1065 }
1067 }
1068
1069 case JsonToken::STRING: {
1070 // Check if this is an object key (next token should be COLON)
1071 if (!mStack.empty() && mStack.back().type == StackFrame::OBJECT &&
1072 mStack.back().pending_key.empty()) {
1073 // Key: handle escape sequences, then intern (StringInterner handles SSO internally)
1075 mStack.back().pending_key = mInterner.intern(unescape_string(value));
1076 } else {
1077 mStack.back().pending_key = mInterner.intern(value);
1078 }
1079 } else {
1080 // Value: handle escape sequences, then intern (StringInterner handles SSO internally)
1081 fl::string str;
1083 str = mInterner.intern(unescape_string(value));
1084 } else {
1085 str = mInterner.intern(value);
1086 }
1087 auto str_val = fl::make_shared<json_value>(str);
1088 push_value(str_val);
1089 }
1090
1092 }
1093
1094 case JsonToken::NUMBER: {
1095 // Determine if int or float by checking for decimal/exponent
1096 bool is_float = false;
1097 for (size_t i = 0; i < value.size(); i++) {
1098 if (value[i] == '.' || value[i] == 'e' || value[i] == 'E') {
1099 is_float = true;
1100 break;
1101 }
1102 }
1103
1105#if FL_PLATFORM_HAS_LARGE_MEMORY
1106 if (is_float) {
1107 // Bit-twiddling parse -> IEEE 754 bits -> store as float in the
1108 // variant. The `bit_cast<float>` is a `memcpy` -- no libgcc
1109 // soft-FP helper is reached on the parser hot path
1110 // (FastLED #3022 phase 2).
1111 const u32 bits = fl::ieee754_parse_decimal(value.data(), value.size());
1113 } else {
1114 int i = fl::parseInt(value.data(), value.size());
1115 i64 i64_val = static_cast<i64>(i);
1116 num_val = fl::make_shared<json_value>(i64_val);
1117 }
1118#else
1119 // Low-memory gate per FastLED #3224 Tier 2D: drop the float
1120 // store entirely. JSON float literals on LowMemory parse to
1121 // i64(0), avoiding the variant `float` emplace instantiation
1122 // that #3082's `0.0f` store used to anchor. Combined with
1123 // the ARRAY_FLOAT case gate above, LowMemory's JsonBuilder
1124 // never instantiates a `make_shared<json_value>(float)` or
1125 // `make_shared<json_value>(vector<float>)` path.
1126 (void)is_float;
1127 int i = fl::parseInt(value.data(), value.size());
1128 i64 i64_val = static_cast<i64>(i);
1129 num_val = fl::make_shared<json_value>(i64_val);
1130#endif
1131
1132 push_value(num_val);
1134 }
1135
1136 case JsonToken::TRUE:
1139
1140 case JsonToken::FALSE:
1143
1147
1148 case JsonToken::COLON:
1149 case JsonToken::COMMA:
1150 // Structural tokens - no action needed
1152
1155
1156 default:
1157 return ParseState::ERROR;
1158 }
1159 }
1160
1164};
1165
1166} // namespace
1167
1168// PARSE2 IMPLEMENTATION - Milestone 8: Two-phase parser with validation
1170 JsonTokenizer tokenizer;
1171
1172 // Phase 1: Validate
1173 JsonValidator validator;
1174 if (!tokenizer.parse(txt, validator) || !validator.is_valid()) {
1175 return fl::make_shared<json_value>(nullptr);
1176 }
1177
1178 // Phase 2: Build
1179 JsonBuilder builder;
1180 if (!tokenizer.parse(txt, builder)) {
1181 return fl::make_shared<json_value>(nullptr);
1182 }
1183
1184 return builder.get_result();
1185}
1186
1187// Phase 1 validation only (for testing - MUST allocate zero heap memory)
1189 return parse2_validate_only(fl::string_view(txt.c_str(), txt.length()));
1190}
1191
1193 JsonTokenizer tokenizer;
1194 JsonValidator validator;
1195 return tokenizer.parse(txt, validator) && validator.is_valid();
1196}
1197
1199 // Parse the JSON value to a string, then parse it back to a json object,
1200 // and use the working to_string_native method
1201 // This is a workaround to avoid reimplementing the serialization logic
1202
1203 // First, create a temporary json document with this value
1204 json temp;
1205 // Use the public method to set the value
1207 // Use the working implementation
1208 return temp.to_string_native();
1209}
1210
1211namespace {
1212// Output sinks for SerializerVisitor. Serialization runs twice: once into
1213// a counting sink to size the result exactly, then once into the reserved
1214// string. Peak heap is the final string alone (FastLED #4899).
1216 fl::size count = 0;
1217 void push_back(char) FL_NO_EXCEPT { ++count; }
1218};
1219
1222 void push_back(char c) FL_NO_EXCEPT { str.push_back(c); }
1223};
1224
1225// Buffers serialized bytes on the stack and hands them to a callback.
1227 static constexpr fl::size kSize = 64;
1229 void* ctx;
1230 char buf[kSize];
1231 fl::size len;
1232 JsonChunkSink(json::WriteFn f, void* c) FL_NO_EXCEPT : fn(f), ctx(c), len(0) {}
1234 buf[len++] = c;
1235 if (len == kSize) flush();
1236 }
1238 if (len) fn(ctx, buf, len);
1239 len = 0;
1240 }
1241};
1242} // namespace
1243
1244// Recursive serializer visitor - declared and defined at file scope
1245// Receives direct references to variant data (no copies) and handles serialization recursively
1246template <typename Sink>
1248 Sink& out;
1249
1250 void append(const char* str) FL_NO_EXCEPT {
1251 while (*str) { out.push_back(*str++); }
1252 }
1253
1255 for (size_t i = 0; i < str.size(); ++i) {
1256 out.push_back(str[i]);
1257 }
1258 }
1259
1261 out.push_back('"');
1262 for (size_t i = 0; i < str.size(); ++i) {
1263 char c = str[i];
1264 switch (c) {
1265 case '"': append("\\\""); break;
1266 case '\\': append("\\\\"); break;
1267 case '\n': append("\\n"); break;
1268 case '\r': append("\\r"); break;
1269 case '\t': append("\\t"); break;
1270 case '\b': append("\\b"); break;
1271 case '\f': append("\\f"); break;
1272 default:
1273 if (static_cast<unsigned char>(c) < 0x20) {
1274 // Remaining control characters must be \u-escaped
1275 // to produce valid JSON.
1276 static const char kHex[] = "0123456789abcdef";
1277 append("\\u00");
1278 out.push_back(kHex[(c >> 4) & 0xF]);
1279 out.push_back(kHex[c & 0xF]);
1280 } else {
1281 out.push_back(c);
1282 }
1283 break;
1284 }
1285 }
1286 out.push_back('"');
1287 }
1288
1289 // Serialize a json_value recursively
1291 if (!value) {
1292 append("null");
1293 return;
1294 }
1295 value->data.visit(*this);
1296 }
1297
1298 // accept() overloads - called by variant::visit() with direct references
1299 void accept(const fl::nullptr_t&) FL_NO_EXCEPT { append("null"); }
1300
1301 void accept(const bool& b) FL_NO_EXCEPT { append(b ? "true" : "false"); }
1302
1303 void accept(const i64& i) FL_NO_EXCEPT {
1304 fl::string num_str;
1305 num_str.append(i);
1306 append_str(num_str);
1307 }
1308
1309 void accept(const float& f) FL_NO_EXCEPT {
1310#if FL_PLATFORM_HAS_LARGE_MEMORY
1311 // Integer-only bits -> decimal (FastLED #3022 phase 2). Default 3-digit
1312 // precision matches the previous `num_str.append(f, 3)` contract.
1314#else
1315 // Low-memory targets gate the float decimal codec to drop
1316 // `ieee754_format_decimal` (594 B) + `kPow10Mant` LUT (824 B) from the
1317 // link. The integer-only RPC contract on LPC8xx / AVR-class targets
1318 // never serializes float values; this writes a stub for the rare cases
1319 // where a `json(float)` round-trip happens to be exercised in test
1320 // code that also runs on Low-memory. See FastLED #3082.
1321 (void)f;
1322 append_str(fl::string("0"));
1323#endif
1324 }
1325
1327
1328 void accept(const json_array& arr) FL_NO_EXCEPT {
1329 if (arr.empty()) {
1330 append("[]");
1331 return;
1332 }
1333 out.push_back('[');
1334 bool first = true;
1335 for (const auto& item : arr) {
1336 if (!first) out.push_back(',');
1337 first = false;
1338 serialize_value(item ? item.get() : &get_null_json_value());
1339 }
1340 out.push_back(']');
1341 }
1342
1344 if (obj.empty()) {
1345 append("{}");
1346 return;
1347 }
1348 out.push_back('{');
1349 bool first = true;
1350 for (const auto& kv : obj) {
1351 if (!first) out.push_back(',');
1352 first = false;
1353 append_escaped(kv.first);
1354 out.push_back(':');
1355 serialize_value(kv.second ? kv.second.get() : &get_null_json_value());
1356 }
1357 out.push_back('}');
1358 }
1359
1361 out.push_back('[');
1362 bool first = true;
1363 for (const auto& item : audio) {
1364 if (!first) out.push_back(',');
1365 first = false;
1366 fl::string num_str;
1367 num_str.append(static_cast<int>(item));
1368 append_str(num_str);
1369 }
1370 out.push_back(']');
1371 }
1372
1374 out.push_back('[');
1375 bool first = true;
1376 for (const auto& item : bytes) {
1377 if (!first) out.push_back(',');
1378 first = false;
1379 fl::string num_str;
1380 num_str.append(static_cast<int>(item));
1381 append_str(num_str);
1382 }
1383 out.push_back(']');
1384 }
1385
1387 out.push_back('[');
1388 bool first = true;
1389 for (const auto& item : floats) {
1390 if (!first) out.push_back(',');
1391 first = false;
1392#if FL_PLATFORM_HAS_LARGE_MEMORY
1393 // Integer-only bits -> decimal per element (FastLED #3022 phase 2).
1394 // 6-digit precision preserves the previous output contract.
1396#else
1397 // Low-memory gate per FastLED #3082; see scalar `accept(float)`.
1398 (void)item;
1399 append_str(fl::string("0"));
1400#endif
1401 }
1402 out.push_back(']');
1403 }
1404};
1405
1407 if (!mValue) {
1408 return "null";
1409 }
1410
1411 // Pass 1: measure the exact serialized length without allocating it.
1412 JsonCountSink counter;
1414 measure.serialize_value(mValue.get());
1415
1416 // Pass 2: write into one exactly-sized buffer. If that buffer cannot be
1417 // allocated, return an empty string rather than a truncated document.
1419 result.reserve(counter.count);
1420 if (result.capacity() < counter.count) {
1421 return fl::string();
1422 }
1423 JsonStringSink sink{result};
1425 write.serialize_value(mValue.get());
1426 if (result.size() != counter.count) {
1427 return fl::string();
1428 }
1429 return result;
1430}
1431
1432void json::write_to(WriteFn fn, void* ctx) const FL_NO_EXCEPT {
1433 if (!fn) return;
1434 if (!mValue) {
1435 fn(ctx, "null", 4);
1436 return;
1437 }
1438 JsonChunkSink sink(fn, ctx);
1440 write.serialize_value(mValue.get());
1441 sink.flush();
1442}
1443
1444// Forward declaration for the serializeValue function
1446
1447
1450 if (!jsonStr) {
1451 return result;
1452 }
1453 size_t len = strlen(jsonStr);
1454 for (size_t i = 0; i < len; ++i) {
1455 char c = jsonStr[i];
1456 if (c != ' ' && c != '\t' && c != '\n' && c != '\r') {
1457 result += c;
1458 }
1459 }
1460 return result;
1461}
1462
1463} // namespace fl
uint8_t pos
Definition Blur.ino:11
int counter
Definition Pintest.h:190
bool parse_float_array(const fl::span< const char > &span, fl::vector< T > &out_vec) FL_NO_EXCEPT
Definition json.cpp.hpp:903
void push_value(const fl::shared_ptr< json_value > &val) FL_NO_EXCEPT
Definition json.cpp.hpp:962
bool parse_int_array(const fl::span< const char > &span, fl::vector< T > &out_vec) FL_NO_EXCEPT
Definition json.cpp.hpp:874
fl::shared_ptr< json_value > get_result() FL_NO_EXCEPT
ParseState on_token(JsonToken token, const fl::span< const char > &value) FL_NO_EXCEPT override
Definition json.cpp.hpp:988
fl::vector_inlined< StackFrame, 8 > mStack
Definition json.cpp.hpp:866
bool parse(fl::string_view input, JsonVisitor &visitor) FL_NO_EXCEPT
Definition json.cpp.hpp:492
JsonTokenizer(bool enable_lookahead=true) FL_NO_EXCEPT
Definition json.cpp.hpp:486
bool parse(const fl::string &input, JsonVisitor &visitor) FL_NO_EXCEPT
Definition json.cpp.hpp:488
JsonToken next_token(fl::span< const char > &out_value) FL_NO_EXCEPT
Definition json.cpp.hpp:312
JsonToken scan_array_lookahead(fl::span< const char > &out_span) FL_NO_EXCEPT
Definition json.cpp.hpp:137
ParseState on_token(JsonToken token, const fl::span< const char > &value) FL_NO_EXCEPT override
Definition json.cpp.hpp:531
virtual ~JsonVisitor() FL_NO_EXCEPT=default
virtual ParseState on_token(JsonToken token, const fl::span< const char > &value) FL_NO_EXCEPT=0
void clear(bool freeMemory=false) FL_NO_EXCEPT
bool empty() const FL_NO_EXCEPT
void set_value(const fl::shared_ptr< json_value > &value) FL_NO_EXCEPT
Definition json.h:828
void(*)(void *ctx, const char *data, fl::size len) WriteFn
Definition json.h:845
fl::shared_ptr< json_value > mValue
Definition json.h:263
void write_to(WriteFn fn, void *ctx) const FL_NO_EXCEPT
fl::string to_string_native() const FL_NO_EXCEPT
static fl::string normalize_json_string(const char *jsonStr) FL_NO_EXCEPT
const T * data() const FL_NO_EXCEPT
Definition span.h:486
constexpr fl::size size() const FL_NO_EXCEPT
Definition span.h:483
string & append(const bitset_fixed< N > &bs) FL_NO_EXCEPT
Definition string.h:293
void reserve(fl::size n) FL_NO_EXCEPT
Definition vector.h:591
void push_back(const T &value) FL_NO_EXCEPT
Definition vector.h:624
FastLED's Elegant JSON Library: fl::json
#define FL_ERROR(...)
Definition log.h:373
Centralized logging categories for FastLED hardware interfaces and subsystems.
constexpr T * end(T(&array)[N]) FL_NO_EXCEPT
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
Definition shared_ptr.h:423
ArrayType classify_array(const json_array &arr) FL_NO_EXCEPT
Definition json.cpp.hpp:729
bool has_escape_sequences(const fl::span< const char > &span) FL_NO_EXCEPT
Definition json.cpp.hpp:632
long read_hex4(const fl::span< const char > &span, size_t pos) FL_NO_EXCEPT
Definition json.cpp.hpp:642
fl::shared_ptr< json_value > optimize_array(fl::shared_ptr< json_value > array_val) FL_NO_EXCEPT
Definition json.cpp.hpp:802
void append_utf8(fl::string &out, unsigned long cp) FL_NO_EXCEPT
Definition json.cpp.hpp:657
fl::string unescape_string(const fl::span< const char > &span) FL_NO_EXCEPT
Definition json.cpp.hpp:676
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition s16x16x4.h:30
decltype(nullptr) nullptr_t
Definition s16x16x4.h:13
unsigned char u8
Definition stdint.h:131
json_value & get_null_json_value() FL_NO_EXCEPT
Definition json.cpp.hpp:63
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
constexpr int type_rank< T >::value
u32 ieee754_parse_decimal(const char *s, fl::size len, fl::size *consumed) FL_NO_EXCEPT
Parse a decimal floating-point number into IEEE 754 single-precision bits.
json_object & get_empty_json_obj() FL_NO_EXCEPT
Definition json.cpp.hpp:68
int parseInt(const char *str, fl::size len) FL_NO_EXCEPT
Parse an integer from a character buffer.
static bool float_bits_magnitude_exceeds_2_24(u32 bits) FL_NO_EXCEPT
Definition json.cpp.hpp:55
fl::i64 i64
Definition s16x16x4.h:222
fl::vector< fl::shared_ptr< json_value > > json_array
Definition types.h:54
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
To bit_cast(const From &from) FL_NO_EXCEPT
Definition bit_cast.h:48
fl::flat_map< fl::string, fl::shared_ptr< json_value >, fl::StringFastLess > json_object
Definition types.h:55
fl::string ieee754_format_decimal(u32 bits, int precision) FL_NO_EXCEPT
Format IEEE 754 single-precision bits as decimal text.
VectorN< T, INLINED_SIZE > vector_inlined
Definition vector.h:1137
fl::string serializeValue(const json_value &value) FL_NO_EXCEPT
FASTLED_FORCE_INLINE size_t strlen(const char *s) FL_NO_EXCEPT
Definition cstring.h:56
Base definition for an LED controller.
void accept(const fl::vector< u8 > &bytes) FL_NO_EXCEPT
void append_str(const fl::string &str) FL_NO_EXCEPT
void accept(const bool &b) FL_NO_EXCEPT
void accept(const fl::nullptr_t &) FL_NO_EXCEPT
void append_escaped(const fl::string &str) FL_NO_EXCEPT
void accept(const i64 &i) FL_NO_EXCEPT
void accept(const fl::vector< float > &floats) FL_NO_EXCEPT
void accept(const fl::string &s) FL_NO_EXCEPT
void serialize_value(const json_value *value) FL_NO_EXCEPT
void accept(const json_array &arr) FL_NO_EXCEPT
void accept(const float &f) FL_NO_EXCEPT
void accept(const json_object &obj) FL_NO_EXCEPT
void append(const char *str) FL_NO_EXCEPT
void accept(const fl::vector< i16 > &audio) FL_NO_EXCEPT
enum fl::anonymous_namespace{json.cpp.hpp}::JsonBuilder::StackFrame::Type type
JsonChunkSink(json::WriteFn f, void *c) FL_NO_EXCEPT
static bool parse2_validate_only(const fl::string &txt) FL_NO_EXCEPT
static fl::shared_ptr< json_value > parse2(const fl::string &txt) FL_NO_EXCEPT
friend class json
Definition types.h:741
fl::string to_string() const FL_NO_EXCEPT
static constexpr T min() FL_NO_EXCEPT
Definition limits.h:107
static constexpr T max() FL_NO_EXCEPT
Definition limits.h:108