Skip to main content

datavalue_rs/
parser.rs

1//! Bump-allocated JSON parser. See [`DataValue::from_str`] for the entry point.
2//!
3//! Strategy:
4//! - Single linear scan over the input bytes.
5//! - Strings without escape sequences are borrowed directly from the input
6//!   (zero-copy). Strings with escapes are unescaped into the arena.
7//! - Arrays/objects are accumulated in `bumpalo::collections::Vec` then
8//!   frozen into `&[..]` slices via `into_bump_slice`.
9//! - Numbers parse on the integer fast path (i64) and only fall back to f64
10//!   when a decimal point or exponent is present (or i64 overflows).
11
12use core::fmt;
13
14use bumpalo::Bump;
15use bumpalo::collections::Vec as BumpVec;
16
17use crate::number::NumberValue;
18use crate::value::DataValue;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct ParseError {
22    pub kind: ParseErrorKind,
23    pub position: usize,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum ParseErrorKind {
28    UnexpectedEof,
29    UnexpectedByte(u8),
30    InvalidEscape,
31    InvalidUnicodeEscape,
32    InvalidNumber,
33    TrailingData,
34    DepthLimitExceeded,
35}
36
37impl fmt::Display for ParseError {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        write!(f, "json parse error at byte {}: ", self.position)?;
40        match self.kind {
41            ParseErrorKind::UnexpectedEof => write!(f, "unexpected end of input"),
42            ParseErrorKind::UnexpectedByte(b) => {
43                write!(f, "unexpected byte 0x{:02x} ({:?})", b, b as char)
44            }
45            ParseErrorKind::InvalidEscape => write!(f, "invalid string escape"),
46            ParseErrorKind::InvalidUnicodeEscape => write!(f, "invalid \\u escape"),
47            ParseErrorKind::InvalidNumber => write!(f, "invalid number literal"),
48            ParseErrorKind::TrailingData => write!(f, "unexpected data after JSON value"),
49            ParseErrorKind::DepthLimitExceeded => write!(f, "nesting depth limit exceeded"),
50        }
51    }
52}
53
54impl std::error::Error for ParseError {}
55
56/// Soft cap on nested array/object depth. Keeps the stack usage bounded so
57/// pathological input can't blow the recursive descent stack. 256 is well
58/// past anything legitimate JSON would produce.
59const MAX_DEPTH: u16 = 256;
60
61/// SWAR scan for the next byte that ends a JSON string fast path: `"`, `\\`,
62/// or any control byte (< 0x20). Returns a mask with the high bit set in the
63/// byte positions that match; the first match (if any) is found via
64/// `trailing_zeros() / 8`. Bytes are interpreted little-endian. Shared with
65/// the emitter via `crate::simd` (single source of truth for the mask).
66use crate::simd::special_mask8 as string_terminator_mask;
67
68/// How many clean 8-byte SWAR windows a string scan rides before handing the
69/// remainder to `simd::find_string_terminator`'s 16-byte path. Two windows
70/// (16 bytes) cover the typical short JSON string (object keys, IDs) without
71/// ever paying SIMD register setup; anything still unterminated is long
72/// enough for the wider stride to win.
73const SWAR_WINDOWS_BEFORE_WIDE: u32 = 2;
74
75/// `extend_from_slice` replacement for `BumpVec<u8>`: reserve + memcpy.
76/// bumpalo's Vec is a pre-specialization std fork whose `extend_from_slice`
77/// copies element-wise through a cloned iterator — measured ~6x slower on
78/// long safe runs in the string-escape path.
79#[inline]
80fn bump_extend(out: &mut BumpVec<u8>, b: &[u8]) {
81    out.reserve(b.len());
82    let len = out.len();
83    // SAFETY: `reserve` guarantees capacity for `len + b.len()`; the source
84    // is the parser's input slice, which never overlaps the arena-owned
85    // destination buffer.
86    unsafe {
87        core::ptr::copy_nonoverlapping(b.as_ptr(), out.as_mut_ptr().add(len), b.len());
88        out.set_len(len + b.len());
89    }
90}
91
92impl<'a> DataValue<'a> {
93    /// Parse a JSON document into a [`DataValue`] tree allocated in `arena`.
94    ///
95    /// Strings without escape sequences are borrowed directly from `input`
96    /// (the returned tree's lifetime is the shorter of `input` and `arena`).
97    pub fn from_str(input: &'a str, arena: &'a Bump) -> Result<DataValue<'a>, ParseError> {
98        let mut p = Parser {
99            bytes: input.as_bytes(),
100            input,
101            pos: 0,
102            arena,
103        };
104        p.skip_ws();
105        let value = p.parse_value(0)?;
106        p.skip_ws();
107        if p.pos != p.bytes.len() {
108            return Err(p.err(ParseErrorKind::TrailingData));
109        }
110        Ok(value)
111    }
112}
113
114struct Parser<'a> {
115    bytes: &'a [u8],
116    input: &'a str,
117    pos: usize,
118    arena: &'a Bump,
119}
120
121impl<'a> Parser<'a> {
122    #[inline(always)]
123    fn err(&self, kind: ParseErrorKind) -> ParseError {
124        ParseError {
125            kind,
126            position: self.pos,
127        }
128    }
129
130    #[inline(always)]
131    fn peek(&self) -> Result<u8, ParseError> {
132        self.bytes
133            .get(self.pos)
134            .copied()
135            .ok_or_else(|| self.err(ParseErrorKind::UnexpectedEof))
136    }
137
138    #[inline(always)]
139    fn bump(&mut self) -> Result<u8, ParseError> {
140        let b = self.peek()?;
141        self.pos += 1;
142        Ok(b)
143    }
144
145    #[inline(always)]
146    fn skip_ws(&mut self) {
147        while self.pos < self.bytes.len() {
148            match self.bytes[self.pos] {
149                b' ' | b'\t' | b'\n' | b'\r' => self.pos += 1,
150                _ => break,
151            }
152        }
153    }
154
155    fn parse_value(&mut self, depth: u16) -> Result<DataValue<'a>, ParseError> {
156        if depth > MAX_DEPTH {
157            return Err(self.err(ParseErrorKind::DepthLimitExceeded));
158        }
159        self.skip_ws();
160        let b = self.peek()?;
161        match b {
162            b'"' => self.parse_string().map(DataValue::String),
163            b'{' => self.parse_object(depth),
164            b'[' => self.parse_array(depth),
165            b't' | b'f' => self.parse_bool(),
166            b'n' => self.parse_null(),
167            b'-' | b'0'..=b'9' => self.parse_number(),
168            other => Err(self.err(ParseErrorKind::UnexpectedByte(other))),
169        }
170    }
171
172    fn parse_null(&mut self) -> Result<DataValue<'a>, ParseError> {
173        if self.bytes.get(self.pos..self.pos + 4) == Some(b"null") {
174            self.pos += 4;
175            Ok(DataValue::Null)
176        } else {
177            Err(self.err(ParseErrorKind::UnexpectedByte(self.bytes[self.pos])))
178        }
179    }
180
181    fn parse_bool(&mut self) -> Result<DataValue<'a>, ParseError> {
182        if self.bytes.get(self.pos..self.pos + 4) == Some(b"true") {
183            self.pos += 4;
184            Ok(DataValue::Bool(true))
185        } else if self.bytes.get(self.pos..self.pos + 5) == Some(b"false") {
186            self.pos += 5;
187            Ok(DataValue::Bool(false))
188        } else {
189            Err(self.err(ParseErrorKind::UnexpectedByte(self.bytes[self.pos])))
190        }
191    }
192
193    fn parse_number(&mut self) -> Result<DataValue<'a>, ParseError> {
194        let start = self.pos;
195        let mut is_float = false;
196
197        // Accumulate the integer as a *negative* i64. This lets the magnitude
198        // reach i64::MIN without wrapping, which a positive accumulator can't.
199        // On overflow we set int_overflowed and stop accumulating; the digit
200        // scan still advances `pos` so the slice for the f64 fallback is right.
201        let neg = if self.bytes[self.pos] == b'-' {
202            self.pos += 1;
203            true
204        } else {
205            false
206        };
207        let mut acc: i64 = 0;
208        let mut int_overflowed = false;
209
210        match self.peek()? {
211            b'0' => {
212                self.pos += 1;
213            }
214            c @ b'1'..=b'9' => {
215                acc = -((c - b'0') as i64);
216                self.pos += 1;
217                // 18 digits fit in i64 unconditionally (i64::MAX ≈ 9.22 × 10^18).
218                // The 19th digit and beyond can overflow, so those use checked
219                // arithmetic; on overflow we tag it and let the f64 fallback
220                // handle the literal. 19-digit values inside i64 range (up to
221                // i64::MAX itself) must stay on the integer path.
222                let mut digits: u32 = 1;
223                while let Some(&d) = self.bytes.get(self.pos) {
224                    match d {
225                        b'0'..=b'9' => {
226                            if digits < 18 {
227                                acc = acc * 10 - (d - b'0') as i64;
228                                digits += 1;
229                            } else if !int_overflowed {
230                                match acc
231                                    .checked_mul(10)
232                                    .and_then(|v| v.checked_sub((d - b'0') as i64))
233                                {
234                                    Some(v) => acc = v,
235                                    None => int_overflowed = true,
236                                }
237                            }
238                            self.pos += 1;
239                        }
240                        _ => break,
241                    }
242                }
243            }
244            _ => return Err(self.err(ParseErrorKind::InvalidNumber)),
245        }
246        // Fraction.
247        if let Some(&b'.') = self.bytes.get(self.pos) {
248            is_float = true;
249            self.pos += 1;
250            let frac_start = self.pos;
251            while let Some(&c) = self.bytes.get(self.pos) {
252                if c.is_ascii_digit() {
253                    self.pos += 1;
254                } else {
255                    break;
256                }
257            }
258            if self.pos == frac_start {
259                return Err(self.err(ParseErrorKind::InvalidNumber));
260            }
261        }
262        // Exponent.
263        if matches!(self.bytes.get(self.pos), Some(b'e' | b'E')) {
264            is_float = true;
265            self.pos += 1;
266            if matches!(self.bytes.get(self.pos), Some(b'+' | b'-')) {
267                self.pos += 1;
268            }
269            let exp_start = self.pos;
270            while let Some(&d) = self.bytes.get(self.pos) {
271                if d.is_ascii_digit() {
272                    self.pos += 1;
273                } else {
274                    break;
275                }
276            }
277            if self.pos == exp_start {
278                return Err(self.err(ParseErrorKind::InvalidNumber));
279            }
280        }
281
282        if !is_float && !int_overflowed {
283            // `acc` is the negative-accumulated value. If the input was
284            // negative we keep it; otherwise negate. The only failure mode is
285            // acc == i64::MIN with !neg (input "9223372036854775808"), which
286            // overflows positive i64 and falls through to f64.
287            let result = if neg { Some(acc) } else { acc.checked_neg() };
288            if let Some(i) = result {
289                return Ok(DataValue::Number(NumberValue::Integer(i)));
290            }
291        }
292
293        // fast-float2 is meaningfully faster than libcore's f64 parser on
294        // float-heavy input (the canada fixture is ~2 MB of floats). The
295        // number literal we just walked is JSON-shaped and is a strict
296        // subset of what the parser accepts.
297        let slice = &self.bytes[start..self.pos];
298        match fast_float2::parse::<f64, _>(slice) {
299            Ok(f) => Ok(DataValue::Number(NumberValue::Float(f))),
300            Err(_) => Err(ParseError {
301                kind: ParseErrorKind::InvalidNumber,
302                position: start,
303            }),
304        }
305    }
306
307    /// Parse a `"..."` string and return the resolved &str. Borrowed from
308    /// the input when there are no escape sequences; otherwise unescaped
309    /// into the arena.
310    fn parse_string(&mut self) -> Result<&'a str, ParseError> {
311        // Already at the opening quote.
312        debug_assert_eq!(self.bytes[self.pos], b'"');
313        self.pos += 1;
314        let start = self.pos;
315
316        self.scan_to_special();
317        match self.bytes.get(self.pos) {
318            Some(&b'"') => {
319                let s = &self.input[start..self.pos];
320                self.pos += 1;
321                Ok(s)
322            }
323            Some(&b'\\') => {
324                // Switch to slow path: copy what we have so far, then
325                // resolve escapes one at a time.
326                self.parse_string_with_escapes(start)
327            }
328            // scan_to_special only stops on `"`, `\\`, or a control byte.
329            Some(&b) => Err(self.err(ParseErrorKind::UnexpectedByte(b))),
330            None => Err(self.err(ParseErrorKind::UnexpectedEof)),
331        }
332    }
333
334    /// Advance `pos` to the next `"`, `\\`, or control byte — or EOF.
335    ///
336    /// Adaptive stride: the first couple of 8-byte SWAR windows are inlined
337    /// (the call/slice boundary cost of the SIMD helper outweighs even
338    /// NEON's 16-byte stride for the typical mix of short JSON strings —
339    /// object keys, IDs); a string still unterminated after
340    /// `SWAR_WINDOWS_BEFORE_WIDE` windows is long, so the remainder goes to
341    /// the 16-byte SIMD path where its register setup amortizes.
342    #[inline(always)]
343    fn scan_to_special(&mut self) {
344        let mut clean_windows = 0u32;
345        while self.pos + 8 <= self.bytes.len() {
346            let w = u64::from_le_bytes(self.bytes[self.pos..self.pos + 8].try_into().unwrap());
347            let mask = string_terminator_mask(w);
348            if mask != 0 {
349                self.pos += (mask.trailing_zeros() / 8) as usize;
350                return;
351            }
352            self.pos += 8;
353            clean_windows += 1;
354            if clean_windows >= SWAR_WINDOWS_BEFORE_WIDE {
355                match crate::simd::find_string_terminator(&self.bytes[self.pos..]) {
356                    Some(off) => self.pos += off,
357                    None => self.pos = self.bytes.len(),
358                }
359                return;
360            }
361        }
362        // Per-byte tail for the final < 8 bytes of input.
363        while let Some(&b) = self.bytes.get(self.pos) {
364            if matches!(b, b'"' | b'\\') || b < 0x20 {
365                return;
366            }
367            self.pos += 1;
368        }
369    }
370
371    fn parse_string_with_escapes(&mut self, start: usize) -> Result<&'a str, ParseError> {
372        let mut out: BumpVec<u8> = BumpVec::with_capacity_in(self.pos - start + 16, self.arena);
373        bump_extend(&mut out, &self.bytes[start..self.pos]);
374
375        loop {
376            // Bulk-copy the safe run between escapes: scan to the next
377            // special byte (adaptive SWAR/SIMD), then copy the whole run in
378            // one memcpy rather than pushing per byte.
379            let chunk_start = self.pos;
380            self.scan_to_special();
381            if self.pos > chunk_start {
382                bump_extend(&mut out, &self.bytes[chunk_start..self.pos]);
383            }
384
385            let b = match self.bytes.get(self.pos) {
386                Some(&b) => b,
387                None => return Err(self.err(ParseErrorKind::UnexpectedEof)),
388            };
389            match b {
390                b'"' => {
391                    self.pos += 1;
392                    let slice = out.into_bump_slice();
393                    // The input is &str (already valid UTF-8) and our
394                    // unescape path only ever produces valid UTF-8 byte
395                    // sequences, so this is sound.
396                    return Ok(unsafe { core::str::from_utf8_unchecked(slice) });
397                }
398                b'\\' => {
399                    self.pos += 1;
400                    let esc = self.bump()?;
401                    match esc {
402                        b'"' => out.push(b'"'),
403                        b'\\' => out.push(b'\\'),
404                        b'/' => out.push(b'/'),
405                        b'b' => out.push(0x08),
406                        b'f' => out.push(0x0C),
407                        b'n' => out.push(b'\n'),
408                        b'r' => out.push(b'\r'),
409                        b't' => out.push(b'\t'),
410                        b'u' => {
411                            let code = self.parse_hex4()?;
412                            // Handle surrogate pairs.
413                            let ch = if (0xD800..=0xDBFF).contains(&code) {
414                                if self.bytes.get(self.pos) != Some(&b'\\')
415                                    || self.bytes.get(self.pos + 1) != Some(&b'u')
416                                {
417                                    return Err(self.err(ParseErrorKind::InvalidUnicodeEscape));
418                                }
419                                self.pos += 2;
420                                let low = self.parse_hex4()?;
421                                if !(0xDC00..=0xDFFF).contains(&low) {
422                                    return Err(self.err(ParseErrorKind::InvalidUnicodeEscape));
423                                }
424                                let scalar = 0x10000
425                                    + (((code - 0xD800) as u32) << 10)
426                                    + ((low - 0xDC00) as u32);
427                                char::from_u32(scalar)
428                                    .ok_or_else(|| self.err(ParseErrorKind::InvalidUnicodeEscape))?
429                            } else if (0xDC00..=0xDFFF).contains(&code) {
430                                return Err(self.err(ParseErrorKind::InvalidUnicodeEscape));
431                            } else {
432                                char::from_u32(code as u32)
433                                    .ok_or_else(|| self.err(ParseErrorKind::InvalidUnicodeEscape))?
434                            };
435                            let mut buf = [0u8; 4];
436                            let s = ch.encode_utf8(&mut buf);
437                            out.extend_from_slice(s.as_bytes());
438                        }
439                        _ => return Err(self.err(ParseErrorKind::InvalidEscape)),
440                    }
441                }
442                _ => return Err(self.err(ParseErrorKind::UnexpectedByte(b))),
443            }
444        }
445    }
446
447    fn parse_hex4(&mut self) -> Result<u16, ParseError> {
448        if self.pos + 4 > self.bytes.len() {
449            return Err(self.err(ParseErrorKind::InvalidUnicodeEscape));
450        }
451        let mut v: u16 = 0;
452        for _ in 0..4 {
453            let b = self.bytes[self.pos];
454            let d = match b {
455                b'0'..=b'9' => b - b'0',
456                b'a'..=b'f' => b - b'a' + 10,
457                b'A'..=b'F' => b - b'A' + 10,
458                _ => return Err(self.err(ParseErrorKind::InvalidUnicodeEscape)),
459            } as u16;
460            v = (v << 4) | d;
461            self.pos += 1;
462        }
463        Ok(v)
464    }
465
466    fn parse_array(&mut self, depth: u16) -> Result<DataValue<'a>, ParseError> {
467        debug_assert_eq!(self.bytes[self.pos], b'[');
468        self.pos += 1;
469        self.skip_ws();
470        // Keep array initial capacity small (8). Larger values regress
471        // canada serialize by 2× because canada has hundreds of thousands
472        // of 2-element coordinate arrays; over-provisioned slots stay in
473        // the arena and disperse the tree, destroying serialize-traversal
474        // cache locality. The doubling cost on long arrays (twitter's
475        // 100-status array) is dwarfed by the locality cost of high cap.
476        // Empty composites allocate nothing — `&[]` promotes to any arena
477        // lifetime (covariance over 'static).
478        if let Some(&b']') = self.bytes.get(self.pos) {
479            self.pos += 1;
480            return Ok(DataValue::Array(&[]));
481        }
482        let mut items: BumpVec<DataValue<'a>> = BumpVec::with_capacity_in(8, self.arena);
483        loop {
484            let v = self.parse_value(depth + 1)?;
485            items.push(v);
486            // Most JSON is minified — the byte right after a value is the
487            // separator. Inspect it directly; fall back to the skip_ws +
488            // bump path only when the next byte isn't `,` or `]`.
489            match self.bytes.get(self.pos) {
490                Some(&b',') => {
491                    self.pos += 1;
492                    self.skip_ws();
493                }
494                Some(&b']') => {
495                    self.pos += 1;
496                    return Ok(DataValue::Array(items.into_bump_slice()));
497                }
498                _ => {
499                    self.skip_ws();
500                    match self.bump()? {
501                        b',' => self.skip_ws(),
502                        b']' => return Ok(DataValue::Array(items.into_bump_slice())),
503                        other => return Err(self.err(ParseErrorKind::UnexpectedByte(other))),
504                    }
505                }
506            }
507        }
508    }
509
510    fn parse_object(&mut self, depth: u16) -> Result<DataValue<'a>, ParseError> {
511        debug_assert_eq!(self.bytes[self.pos], b'{');
512        self.pos += 1;
513        self.skip_ws();
514        // Twitter status objects run ~30 keys, so 32 keeps them in their
515        // first chunk; smaller objects (citm events, 5-6 keys) leave some
516        // unused tail. We can't shrink BumpVec capacity in place inside a
517        // bump arena (the unused slots stay between this allocation and
518        // the next), so the choice is a trade-off: too high spreads the
519        // tree across the arena and tanks serialize traversal cache
520        // locality (canada serialize doubles when arrays go to cap 64);
521        // too low forces a realloc + memmove on every grow.
522        if let Some(&b'}') = self.bytes.get(self.pos) {
523            self.pos += 1;
524            return Ok(DataValue::Object(&[]));
525        }
526        let mut pairs: BumpVec<(&'a str, DataValue<'a>)> =
527            BumpVec::with_capacity_in(32, self.arena);
528        loop {
529            // Key. After the loop entry / a `,` we already skipped WS.
530            if self.peek()? != b'"' {
531                return Err(self.err(ParseErrorKind::UnexpectedByte(self.bytes[self.pos])));
532            }
533            let key = self.parse_string()?;
534
535            // Colon. Fast path: byte right after the key is `:` (minified).
536            match self.bytes.get(self.pos) {
537                Some(&b':') => self.pos += 1,
538                _ => {
539                    self.skip_ws();
540                    if self.bump()? != b':' {
541                        return Err(
542                            self.err(ParseErrorKind::UnexpectedByte(self.bytes[self.pos - 1]))
543                        );
544                    }
545                }
546            }
547
548            // Value. parse_value skips its own leading WS; no skip_ws here.
549            let value = self.parse_value(depth + 1)?;
550            pairs.push((key, value));
551
552            // Separator. Same fast path as parse_array.
553            match self.bytes.get(self.pos) {
554                Some(&b',') => {
555                    self.pos += 1;
556                    self.skip_ws();
557                }
558                Some(&b'}') => {
559                    self.pos += 1;
560                    return Ok(DataValue::Object(pairs.into_bump_slice()));
561                }
562                _ => {
563                    self.skip_ws();
564                    match self.bump()? {
565                        b',' => self.skip_ws(),
566                        b'}' => return Ok(DataValue::Object(pairs.into_bump_slice())),
567                        other => return Err(self.err(ParseErrorKind::UnexpectedByte(other))),
568                    }
569                }
570            }
571        }
572    }
573}
574
575#[cfg(test)]
576mod tests {
577    use super::*;
578
579    fn parse(s: &str) -> DataValue<'_> {
580        let arena = Box::leak(Box::new(Bump::new()));
581        DataValue::from_str(s, arena).expect("parse")
582    }
583
584    #[test]
585    fn primitives() {
586        assert!(parse("null").is_null());
587        assert_eq!(parse("true").as_bool(), Some(true));
588        assert_eq!(parse("false").as_bool(), Some(false));
589        assert_eq!(parse("0").as_i64(), Some(0));
590        assert_eq!(parse("-7").as_i64(), Some(-7));
591        assert_eq!(parse("3.5").as_f64(), Some(3.5));
592        assert_eq!(parse("1e3").as_f64(), Some(1000.0));
593        assert_eq!(parse(r#""hello""#).as_str(), Some("hello"));
594    }
595
596    #[test]
597    fn integer_overflow_falls_to_float() {
598        let v = parse("123456789012345678901234567890");
599        assert!(v.is_f64());
600    }
601
602    #[test]
603    fn malformed_number_literals_reject() {
604        // JSON requires a digit after `.` and after `e`/`e+`/`e-`; these pin
605        // rejection of every truncated shape (whole-document errors — the
606        // specific error kind is not part of the contract).
607        let arena = Bump::new();
608        for input in [
609            "1.",
610            "-1.",
611            "1.e5",
612            "1e",
613            "1e+",
614            "1e-",
615            "1E",
616            "[1.]",
617            "[1e]",
618            "{\"a\":1.}",
619            "1.5e",
620            "0.",
621            "-0.e1",
622        ] {
623            assert!(
624                DataValue::from_str(input, &arena).is_err(),
625                "{input:?} should be rejected"
626            );
627        }
628    }
629
630    #[test]
631    // The 17-digit literals deliberately carry more precision than f64
632    // round-trips — they pin correct rounding of canada-fixture-shaped input.
633    #[allow(clippy::excessive_precision)]
634    fn float_parse_parity() {
635        // Shapes that exercise the float path end to end, pinned against
636        // the correctly-rounded values (std's parser agrees bit-exactly).
637        for (input, expect) in [
638            ("0.5", 0.5),
639            ("-0.5", -0.5),
640            ("3.5", 3.5),
641            ("1e3", 1000.0),
642            ("1E3", 1000.0),
643            ("1e+3", 1000.0),
644            ("2.5e-2", 0.025),
645            ("-65.613616999999977", -65.613616999999977),
646            ("112.58598277699663", 112.58598277699663),
647            ("0e0", 0.0),
648            ("0.0", 0.0),
649        ] {
650            assert_eq!(parse(input).as_f64(), Some(expect), "{input}");
651        }
652        // Huge exponents saturate to infinity (fast-float2 semantics; note
653        // serde_json instead rejects these literals as out of range).
654        assert_eq!(parse("1e999").as_f64(), Some(f64::INFINITY));
655        assert_eq!(parse("-1e999").as_f64(), Some(f64::NEG_INFINITY));
656        // Numbers followed by structural bytes stop at the right place.
657        let arena = Bump::new();
658        let v = DataValue::from_str("[1.5,2.5e1,-3]", &arena).unwrap();
659        assert_eq!(v[0].as_f64(), Some(1.5));
660        assert_eq!(v[1].as_f64(), Some(25.0));
661        assert_eq!(v[2].as_i64(), Some(-3));
662    }
663
664    #[test]
665    fn i64_boundaries() {
666        assert_eq!(parse("9223372036854775807").as_i64(), Some(i64::MAX));
667        assert_eq!(parse("-9223372036854775808").as_i64(), Some(i64::MIN));
668        // 19-digit values inside i64 range stay integers (the old 18-digit
669        // accumulator cap demoted these to f64, silently losing precision).
670        assert_eq!(
671            parse("1234567890123456789").as_i64(),
672            Some(1_234_567_890_123_456_789)
673        );
674        // Just past i64::MAX must demote to f64, not silently wrap.
675        assert!(parse("9223372036854775808").is_f64());
676        // Just past i64::MIN must demote to f64.
677        assert!(parse("-9223372036854775809").is_f64());
678    }
679
680    #[test]
681    fn empty_collections() {
682        assert_eq!(parse("[]").len(), Some(0));
683        assert_eq!(parse("{}").len(), Some(0));
684    }
685
686    #[test]
687    fn arrays_and_objects() {
688        let v = parse(r#"{"a":[1,2,3],"b":{"c":true}}"#);
689        assert_eq!(v["a"][0].as_i64(), Some(1));
690        assert_eq!(v["a"][2].as_i64(), Some(3));
691        assert_eq!(v["b"]["c"].as_bool(), Some(true));
692    }
693
694    #[test]
695    fn string_escapes() {
696        assert_eq!(parse(r#""a\nb""#).as_str(), Some("a\nb"));
697        assert_eq!(parse(r#""a\\b""#).as_str(), Some("a\\b"));
698        assert_eq!(parse(r#""é""#).as_str(), Some("é"));
699        // Surrogate pair for U+1F600 😀
700        assert_eq!(parse(r#""😀""#).as_str(), Some("😀"));
701    }
702
703    #[test]
704    fn whitespace_tolerant() {
705        let v = parse(" {\n \"a\" :\t1 ,\n \"b\":2\n} ");
706        assert_eq!(v["a"].as_i64(), Some(1));
707        assert_eq!(v["b"].as_i64(), Some(2));
708    }
709
710    #[test]
711    fn rejects_trailing_data() {
712        let arena = Bump::new();
713        assert!(DataValue::from_str("1 2", &arena).is_err());
714    }
715
716    #[test]
717    fn rejects_bad_escape() {
718        let arena = Bump::new();
719        assert!(DataValue::from_str(r#""\q""#, &arena).is_err());
720    }
721
722    #[test]
723    fn rejects_unescaped_control_bytes_in_string() {
724        // The SWAR scan must still surface every control byte (0x00..=0x1F),
725        // including ones that fall inside an 8-byte window after several
726        // safe bytes.
727        let arena = Bump::new();
728        for ctl in 0u8..0x20 {
729            // Pad with safe bytes so the control byte lands somewhere in
730            // the bulk-scan path rather than the head.
731            let mut s = Vec::from(b"\"abcdefghijklmnop");
732            s.push(ctl);
733            s.push(b'"');
734            let input = std::str::from_utf8(&s).unwrap();
735            assert!(
736                DataValue::from_str(input, &arena).is_err(),
737                "control byte 0x{ctl:02x} should error",
738            );
739        }
740    }
741
742    #[test]
743    fn long_escape_string_round_trips() {
744        // Force the escape slow path's SWAR loop to run several iterations
745        // by interleaving long safe runs with escapes.
746        let mut json = String::from("\"");
747        for _ in 0..10 {
748            json.push_str(&"x".repeat(40));
749            json.push_str(r"\n");
750        }
751        json.push('"');
752        let arena = Bump::new();
753        let v = DataValue::from_str(&json, &arena).unwrap();
754        let s = v.as_str().unwrap();
755        assert_eq!(s.matches('\n').count(), 10);
756        assert!(s.starts_with(&"x".repeat(40)));
757    }
758
759    #[test]
760    fn long_string_round_trips() {
761        // Force the SWAR loop to fire several iterations and the tail to
762        // take over for the final < 8 bytes.
763        let s = "x".repeat(200);
764        let json = format!("\"{s}\"");
765        let arena = Bump::new();
766        let v = DataValue::from_str(&json, &arena).unwrap();
767        assert_eq!(v.as_str(), Some(s.as_str()));
768    }
769
770    #[test]
771    fn deep_nesting_under_limit_ok() {
772        let n = 200;
773        let s = "[".repeat(n) + &"]".repeat(n);
774        let arena = Bump::new();
775        assert!(DataValue::from_str(&s, &arena).is_ok());
776    }
777
778    #[test]
779    fn deep_nesting_over_limit_errors() {
780        let n = 1000;
781        let s = "[".repeat(n) + &"]".repeat(n);
782        let arena = Bump::new();
783        assert!(DataValue::from_str(&s, &arena).is_err());
784    }
785}