Skip to main content

json_bourne/
event.rs

1use crate::error::ErrorKind;
2use core::fmt;
3
4/// Top bit of `start_packed` carries `has_escapes`. JSON inputs are limited
5/// to `i32::MAX` bytes (~2 GB) so the lower 31 bits are enough for a real
6/// document. Values above 2 GB are rejected at parse time.
7const ESCAPED_BIT: u32 = 1 << 31;
8const OFFSET_MASK: u32 = !ESCAPED_BIT;
9
10/// Maximum byte size of a JSON document this parser will accept. Bounded by
11/// our packed-offset representation in `Event`.
12pub const MAX_INPUT_LEN: usize = OFFSET_MASK as usize;
13
14/// A JSON string span — a `(start, end)` pair into the original input.
15///
16/// Materializing into a real `&str` requires the original input slice.
17/// Stored as two `u32`s instead of a fat `&[u8]` so the parent `Event`
18/// fits in 16 bytes (one xmm register), eliminating the per-event memory
19/// shuffles that dominated typed parsing in profiling.
20#[derive(Copy, Clone, PartialEq, Eq)]
21pub struct JsonStr {
22    /// Low 31 bits: byte offset where the string content starts (after the
23    /// opening `"`). Top bit: `has_escapes` flag.
24    start_packed: u32,
25    /// Byte offset one-past the end of the content (the closing `"`).
26    end: u32,
27}
28
29impl JsonStr {
30    pub(crate) const fn new(start: u32, end: u32, has_escapes: bool) -> Self {
31        let start_packed = if has_escapes {
32            start | ESCAPED_BIT
33        } else {
34            start
35        };
36        Self { start_packed, end }
37    }
38
39    /// `true` iff the lexer saw a `\` in the string. When false, the raw
40    /// bytes are already valid UTF-8 (the lexer validated them inline) and
41    /// can be turned into `&str` for free.
42    #[must_use]
43    pub const fn has_escapes(&self) -> bool {
44        self.start_packed & ESCAPED_BIT != 0
45    }
46
47    #[must_use]
48    pub const fn start(&self) -> u32 {
49        self.start_packed & OFFSET_MASK
50    }
51
52    #[must_use]
53    pub const fn end(&self) -> u32 {
54        self.end
55    }
56
57    /// Raw bytes between (but not including) the quotes, taken from `input`.
58    ///
59    /// Caller must pass the same input the parser was constructed with;
60    /// otherwise the indices are meaningless. Returns `None` if the input
61    /// is shorter than the recorded range (only happens if the caller
62    /// passes a different/truncated buffer).
63    #[must_use]
64    pub fn raw_bytes<'input>(&self, input: &'input [u8]) -> Option<&'input [u8]> {
65        let start = self.start() as usize;
66        let end = self.end as usize;
67        input.get(start..end)
68    }
69
70    /// The string as `&str` when it contains no escapes. Skips re-validation:
71    /// the parser already ensured the bytes are valid UTF-8.
72    ///
73    /// Returns `None` when the string had escapes (caller must decode into
74    /// a buffer) or when `input` doesn't cover the recorded range.
75    #[must_use]
76    pub fn as_str<'input>(&self, input: &'input [u8]) -> Option<&'input str> {
77        if self.has_escapes() {
78            return None;
79        }
80        let raw = self.raw_bytes(input)?;
81        // SAFETY: the lexer validates every byte against the RFC 3629 byte
82        // ranges as it scans (see `Parser::consume_utf8_multibyte` and the
83        // ASCII fast arm). The slice is therefore valid UTF-8 and
84        // `from_utf8_unchecked` is sound.
85        Some(unsafe { core::str::from_utf8_unchecked(raw) })
86    }
87}
88
89impl fmt::Debug for JsonStr {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        // Manual Debug because `start_packed` carries the `has_escapes` bit
92        // packed into its high bit; the raw u32 would be misleading. Show
93        // the unpacked logical fields instead.
94        f.debug_struct("JsonStr")
95            .field("start", &self.start())
96            .field("end", &self.end)
97            .field("has_escapes", &self.has_escapes())
98            .finish_non_exhaustive()
99    }
100}
101
102/// A JSON number span — `(start, end)` into the original input.
103///
104/// Numbers are not eagerly decoded — JSON numbers are arbitrary-precision
105/// decimal and there is no Rust primitive that losslessly fits all of them.
106/// Decoding is the consumer's choice via `as_i64`, `as_u64`, `as_f64`.
107#[derive(Copy, Clone, PartialEq, Eq)]
108pub struct JsonNum {
109    start: u32,
110    end: u32,
111}
112
113impl JsonNum {
114    pub(crate) const fn new(start: u32, end: u32) -> Self {
115        Self { start, end }
116    }
117
118    #[must_use]
119    pub const fn start(&self) -> u32 {
120        self.start
121    }
122
123    #[must_use]
124    pub const fn end(&self) -> u32 {
125        self.end
126    }
127
128    /// Raw bytes of the number literal, taken from the original input.
129    #[must_use]
130    pub fn raw_bytes<'input>(&self, input: &'input [u8]) -> Option<&'input [u8]> {
131        input.get(self.start as usize..self.end as usize)
132    }
133
134    /// The raw number text. Always ASCII (lexer guarantees this).
135    #[must_use]
136    pub fn as_str<'input>(&self, input: &'input [u8]) -> &'input str {
137        // SAFETY: lexer accepts only the ASCII subset RFC 8259 allows for
138        // numbers. Always valid UTF-8.
139        self.raw_bytes(input)
140            .map_or("", |bytes| unsafe { core::str::from_utf8_unchecked(bytes) })
141    }
142
143    /// True if the literal contains `.`, `e`, or `E` — i.e. is not an integer.
144    #[must_use]
145    pub fn is_float(&self, input: &[u8]) -> bool {
146        self.raw_bytes(input)
147            .is_some_and(|bytes| bytes.iter().any(|b| matches!(*b, b'.' | b'e' | b'E')))
148    }
149
150    #[inline]
151    pub fn as_i64(&self, input: &[u8]) -> Result<i64, ErrorKind> {
152        let bytes = self.raw_bytes(input).ok_or(ErrorKind::InvalidNumber)?;
153        parse_i64(bytes).ok_or(ErrorKind::NumberOutOfRange)
154    }
155
156    #[inline]
157    pub fn as_u64(&self, input: &[u8]) -> Result<u64, ErrorKind> {
158        let bytes = self.raw_bytes(input).ok_or(ErrorKind::InvalidNumber)?;
159        parse_u64(bytes).ok_or(ErrorKind::NumberOutOfRange)
160    }
161
162    /// Decode the literal as `i128`. Routes through `str::parse` — 128-bit
163    /// JSON integers are rare enough that the bespoke fast paths used for
164    /// `i64`/`u64` are not justified.
165    #[inline]
166    pub fn as_i128(&self, input: &[u8]) -> Result<i128, ErrorKind> {
167        self.as_str(input)
168            .parse::<i128>()
169            .map_err(|_| ErrorKind::NumberOutOfRange)
170    }
171
172    /// Decode the literal as `u128`.
173    #[inline]
174    pub fn as_u128(&self, input: &[u8]) -> Result<u128, ErrorKind> {
175        self.as_str(input)
176            .parse::<u128>()
177            .map_err(|_| ErrorKind::NumberOutOfRange)
178    }
179
180    pub fn as_f64(&self, input: &[u8]) -> Result<f64, ErrorKind> {
181        // v1: route through core's str::parse. Replace with our own
182        // dtoa-grade decoder later. Correctness now, performance later.
183        //
184        // The lexer guarantees the literal matches JSON's number grammar,
185        // so `inf`/`NaN`/`Infinity` can never reach this function as
186        // input text. The non-finite check below catches the *output*
187        // case: literals whose magnitude exceeds `f64::MAX` (e.g.
188        // `1e400`) decode to `±inf`, which JSON disallows.
189        let v: f64 = self
190            .as_str(input)
191            .parse::<f64>()
192            .map_err(|_| ErrorKind::InvalidNumber)?;
193        if v.is_finite() {
194            Ok(v)
195        } else {
196            Err(ErrorKind::NumberOutOfRange)
197        }
198    }
199}
200
201impl fmt::Debug for JsonNum {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        f.debug_struct("JsonNum")
204            .field("start", &self.start)
205            .field("end", &self.end)
206            .finish()
207    }
208}
209
210/// `u64::MAX` has 20 decimal digits. Up to 19 digits, the loop body
211/// `acc * 10 + d` cannot overflow a `u64`, so we skip `checked_*` entirely
212/// — the only `imul` plus `jo` pair per digit dominated profiles for
213/// integer-array workloads. The 20-digit case retakes the slow path.
214const U64_FAST_DIGITS: usize = 19;
215
216/// `i64::MAX` has 19 decimal digits. The fast path handles up to 18 digits
217/// (also covers all 18-or-fewer-digit negatives, since `i64::MIN` has 19
218/// digits including the sign and we strip the sign first).
219const I64_FAST_DIGITS: usize = 18;
220
221#[inline]
222fn parse_u64(raw: &[u8]) -> Option<u64> {
223    if raw.is_empty() {
224        return None;
225    }
226    if raw.len() <= U64_FAST_DIGITS {
227        // Wide enough that overflow is impossible — skip checked arithmetic.
228        // Each digit becomes `mul + add + cmp + jb` instead of
229        // `mul + jo + cmp + jb`, removing the `jo` dependency on `mul`'s
230        // overflow flag and shortening the critical path.
231        let mut acc: u64 = 0;
232        for &b in raw {
233            let d = b.wrapping_sub(b'0');
234            if d >= 10 {
235                return None;
236            }
237            acc = acc * 10 + u64::from(d);
238        }
239        return Some(acc);
240    }
241    // 20+ digits: must check overflow on every step. u64 fits at most 20.
242    let mut acc: u64 = 0;
243    for &b in raw {
244        let d = b.wrapping_sub(b'0');
245        if d >= 10 {
246            return None;
247        }
248        acc = acc.checked_mul(10)?.checked_add(u64::from(d))?;
249    }
250    Some(acc)
251}
252
253#[inline]
254fn parse_i64(raw: &[u8]) -> Option<i64> {
255    let (negative, digits) = match raw.split_first() {
256        Some((&b'-', rest)) => (true, rest),
257        _ => (false, raw),
258    };
259    if digits.is_empty() {
260        return None;
261    }
262    if digits.len() <= I64_FAST_DIGITS {
263        // 18 or fewer digits — both signs fit in i64 without overflow.
264        let mut acc: i64 = 0;
265        for &b in digits {
266            let d = b.wrapping_sub(b'0');
267            if d >= 10 {
268                return None;
269            }
270            acc = acc * 10 + i64::from(d);
271        }
272        return Some(if negative { -acc } else { acc });
273    }
274    // 19+ digits: i64::MIN has 19 digits ("-9223372036854775808"), so we
275    // must check at every step. Two loops to specialize add vs sub.
276    let mut acc: i64 = 0;
277    if negative {
278        for &b in digits {
279            let d = b.wrapping_sub(b'0');
280            if d >= 10 {
281                return None;
282            }
283            acc = acc.checked_mul(10)?.checked_sub(i64::from(d))?;
284        }
285    } else {
286        for &b in digits {
287            let d = b.wrapping_sub(b'0');
288            if d >= 10 {
289                return None;
290            }
291            acc = acc.checked_mul(10)?.checked_add(i64::from(d))?;
292        }
293    }
294    Some(acc)
295}
296
297/// A single event from the streaming parser.
298///
299/// The parser emits these in document order. Containers are delimited by
300/// matched `Start*`/`End*` pairs. Inside an object, every value event is
301/// preceded by a `Key` event for that field.
302///
303/// `Event` is 16 bytes — `(JsonStr|JsonNum: 8 bytes) + (tag: 1 byte) + 7
304/// bytes padding`. This fits in one xmm register, so `next_event` returns
305/// it without spilling to the stack — measurable in profiles as ~12% of
306/// typed-parse runtime saved compared to the previous 32-byte representation.
307#[derive(Copy, Clone, Debug, PartialEq, Eq)]
308pub enum Event {
309    StartObject,
310    EndObject,
311    StartArray,
312    EndArray,
313    Key(JsonStr),
314    String(JsonStr),
315    Number(JsonNum),
316    Bool(bool),
317    Null,
318}