Skip to main content

ocpi_tariffs/json/
parser.rs

1//! Hand-rolled single-pass recursive-descent JSON parser.
2//!
3//! # Responsibilities
4//!
5//! The parser is responsible for structural correctness only: balanced delimiters,
6//! valid top-level values, and well-formed numbers. String content is captured as
7//! [`RawStr`] slices of the source — escape sequences and control characters are
8//! left untouched. Validation of string content is the responsibility of
9//! [`crate::json::decode`].
10//!
11//! # Output
12//!
13//! [`parse`] returns a [`Document`] that wraps the root [`Element`] and the shared
14//! [`DocumentInner`]. Every element carries an `Rc<DocumentInner>` so it can resolve
15//! its own path after the [`Document`] has been dropped.
16//!
17//! # Limits
18//!
19//! - **Nesting depth**: capped at [`MAX_DEPTH`] (128 levels). Inputs that exceed this
20//!   are rejected with [`ErrorKind::DepthLimitExceeded`].
21//! - **Element count**: capped at `u32::MAX` by the [`ElemId`] counter, enforced via
22//!   [`ErrorKind::MaxElements`]. In practice, the 5 megabytes [`string::ReasonableLen`] gate makes
23//!   this limit unreachable at the moment.
24//!
25//! # Two-phase construction
26//!
27//! The [`Parser`] builds a private [`RawElement`] tree and a [`PathTable`] in one
28//! pass. Once the full tree is available, [`into_element`] threads the shared
29//! `Rc<DocumentInner>` through every node to produce the public [`Element`] tree.
30//! This keeps the hot parsing loop free of reference-counting overhead.
31
32#![expect(
33    clippy::arithmetic_side_effects,
34    reason = "pos is bounded by source.len() and only advances after a successful byte read; arithmetic is safe within parser state machine invariants"
35)]
36#![expect(
37    clippy::as_conversions,
38    reason = "byte position casts between usize and u32 are safe: usize->u32 is bounded by available memory, u32->usize always fits"
39)]
40#![expect(
41    clippy::cast_possible_truncation,
42    reason = "source length is bounded by available memory, so byte positions always fit in u32"
43)]
44#![expect(
45    clippy::string_slice,
46    reason = "span boundaries are always at ASCII JSON token boundaries, so slices are valid UTF-8"
47)]
48
49#[cfg(test)]
50mod test_send_and_sync;
51
52#[cfg(test)]
53mod test_basics;
54
55#[cfg(test)]
56mod test_parser;
57
58#[cfg(test)]
59mod test_type_sizes;
60
61use std::rc::Rc;
62
63use crate::{string, warning};
64
65use super::{
66    Document, DocumentInner, ElemId, Element, Field, Location, PathEntry, PathTable, RawStr, Span,
67    Value,
68};
69
70/// Maximum nesting depth for arrays and objects.
71///
72/// RFC 8259 recommends implementations handle at least 128 levels of nesting.
73/// Inputs that exceed this limit are rejected with [`ErrorKind::DepthLimitExceeded`].
74const MAX_DEPTH: usize = 128;
75
76// JSON whitespace characters `RFC 8259 s2`.
77const SPACE: u8 = b' ';
78const TAB: u8 = b'\t';
79const LF: u8 = b'\n';
80const CR: u8 = b'\r';
81
82// Structural characters `RFC 8259 s2`.
83const QUOTE: u8 = b'"';
84const BACKSLASH: u8 = b'\\';
85const COMMA: u8 = b',';
86const COLON: u8 = b':';
87const ARRAY_OPEN: u8 = b'[';
88const ARRAY_CLOSE: u8 = b']';
89const OBJECT_OPEN: u8 = b'{';
90const OBJECT_CLOSE: u8 = b'}';
91
92// Number-grammar characters `RFC 8259 s6`.
93const MINUS: u8 = b'-';
94const PLUS: u8 = b'+';
95const DECIMAL_POINT: u8 = b'.';
96const EXP_LOWER: u8 = b'e';
97const EXP_UPPER: u8 = b'E';
98const DIGIT_0: u8 = b'0';
99const DIGIT_1: u8 = b'1';
100const DIGIT_9: u8 = b'9';
101
102// JSON keyword literals `RFC 8259 s3`.
103const NULL: &str = "null";
104const TRUE: &str = "true";
105const FALSE: &str = "false";
106
107// UTF-8 BOM (`U+FEFF`, encoded as 0xEF 0xBB 0xBF).
108const BOM: &[u8; 3] = b"\xEF\xBB\xBF";
109
110/// Parse a JSON document from `source`.
111///
112/// All string content in the returned tree borrows from `source`.
113/// Call `element.path()` on any element to obtain its RFC 9535 path.
114pub(crate) fn parse(source: string::ReasonableLen<'_>) -> Result<Document<'_>, Error> {
115    let mut p = Parser::new(source.into_inner());
116    // Skip a UTF-8 BOM (`U+FEFF` encoded as 0xEF 0xBB 0xBF) if present.
117    if p.bytes.starts_with(BOM) {
118        p.pos = BOM.len();
119    }
120    let raw_root = p.parse_value(PathEntry::Root)?;
121    p.skip_ws();
122    if p.pos < p.bytes.len() {
123        return Err(p.error(ErrorKind::TrailingContent));
124    }
125    let inner = Rc::new(DocumentInner {
126        source: source.into_inner(),
127        paths: p.table,
128    });
129    let root = into_element(raw_root, &inner);
130    Ok(Document { inner, root })
131}
132
133/// A parse error produced when the input is not well-formed JSON.
134///
135/// Carries the byte offset and line/column position of the failure, and an
136/// [`ErrorKind`] that describes what was wrong.
137#[derive(Debug, Eq, PartialEq)]
138pub struct Error {
139    /// Byte offset of the error location.
140    byte_offset: usize,
141    /// A file location expressed as line and column.
142    position: Location,
143    /// The details about the error that occurred.
144    kind: ErrorKind,
145}
146
147impl Error {
148    /// Byte offset of the error location.
149    pub fn byte_offset(&self) -> usize {
150        self.byte_offset
151    }
152
153    /// Return a reference to the details about the error that occurred.
154    pub fn kind(&self) -> &ErrorKind {
155        &self.kind
156    }
157
158    /// Consume the `Error` and return the details about the error that occurred.
159    pub fn into_kind(self) -> ErrorKind {
160        self.kind
161    }
162
163    /// Consume the `Error` and return the byte offset of the error location and the details of the
164    /// error that occurred.
165    pub fn into_parts(self) -> (usize, ErrorKind) {
166        (self.byte_offset, self.kind)
167    }
168}
169
170/// The specific reason a [`crate::json::parse_object`] call failed.
171#[derive(Debug, Eq, PartialEq)]
172pub enum ErrorKind {
173    /// A character that cannot be a JSON number was encountered.
174    ExpectedNumeral,
175    /// A character that cannot start a JSON value was encountered.
176    ExpectedStart,
177    /// A literal that cannot start or continue a JSON value was encountered.
178    ExpectedLiteral {
179        /// The literal the parser was part-way through matching.
180        expected: &'static str,
181    },
182    /// An array wasn't terminated correctly.
183    ExpectedEndArray,
184    /// An object wasn't terminated correctly.
185    ExpectedEndObject,
186    /// A character that cannot continue a JSON value was encountered.
187    UnexpectedChar {
188        /// The character the grammar required at this position.
189        expected: char,
190    },
191    /// The input ended before the value was complete.
192    UnexpectedEOF,
193    /// Non-whitespace bytes follow the root value.
194    TrailingContent,
195    /// The input exceeds the maximum supported nesting depth.
196    DepthLimitExceeded,
197    /// The input contains more than [`u32::MAX`] JSON elements.
198    MaxElements,
199}
200
201impl crate::Warning for Error {
202    fn id(&self) -> warning::Id {
203        let s = match self.kind {
204            ErrorKind::ExpectedNumeral => "expected_numeral",
205            ErrorKind::ExpectedStart => "expected_start",
206            ErrorKind::ExpectedLiteral { .. } => "expected_literal",
207            ErrorKind::ExpectedEndArray => "expected_end_array",
208            ErrorKind::ExpectedEndObject => "expected_end_object",
209            ErrorKind::UnexpectedChar { .. } => "unexpected_char",
210            ErrorKind::UnexpectedEOF => "unexpected_eof",
211            ErrorKind::TrailingContent => "trailing_content",
212            ErrorKind::DepthLimitExceeded => "depth_limit_exceeded",
213            ErrorKind::MaxElements => "max_elements",
214        };
215
216        warning::Id::from_static(s)
217    }
218}
219
220impl std::fmt::Display for Error {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        let Self {
223            byte_offset,
224            position,
225            kind,
226        } = self;
227
228        match kind {
229            ErrorKind::ExpectedLiteral { expected } => {
230                write!(
231                    f,
232                    "unexpected literal found at line: `{position}`, byte `{byte_offset}`; expected: `{expected:?}`"
233                )
234            }
235            ErrorKind::ExpectedNumeral => {
236                write!(
237                    f,
238                    "unexpected numeral found at line: `{position}`, byte `{byte_offset}`; expected: `0-9`"
239                )
240            }
241            ErrorKind::ExpectedStart => {
242                write!(
243                    f,
244                    "unexpected start character found at line: `{position}`, byte `{byte_offset}`; expected one of: `[n, t, f, \", -, 0-9, [, {{]`"
245                )
246            }
247            ErrorKind::ExpectedEndArray => {
248                write!(
249                    f,
250                    "unexpected character found at line: `{position}`, byte `{byte_offset}`; expected: `,` or `]`"
251                )
252            }
253            ErrorKind::ExpectedEndObject => {
254                write!(
255                    f,
256                    "unexpected character found at line: `{position}`, byte `{byte_offset}`; expected: `,` or `}}`"
257                )
258            }
259            ErrorKind::UnexpectedChar { expected } => {
260                write!(
261                    f,
262                    "unexpected character `{expected}` found at line: `{position}`, byte `{byte_offset}``"
263                )
264            }
265            ErrorKind::UnexpectedEOF => write!(
266                f,
267                "unexpected end of input found at line: `{position}`, byte `{byte_offset}`"
268            ),
269            ErrorKind::TrailingContent => write!(
270                f,
271                "trailing content found at line: `{position}`, byte `{byte_offset}`"
272            ),
273            ErrorKind::DepthLimitExceeded => {
274                write!(f, "nesting depth exceeds the {MAX_DEPTH}-level limit")
275            }
276            ErrorKind::MaxElements => write!(f, "document exceeds {} JSON elements", u32::MAX),
277        }
278    }
279}
280
281impl std::error::Error for Error {}
282
283/// Parser-private element tree; carries no reference to [`DocumentInner`].
284///
285/// Converted to the public [`Element`] tree by [`into_element`] after
286/// [`DocumentInner`] is constructed.
287struct RawElement<'buf> {
288    /// Unique identifier within the document; sequentially assigned depth-first.
289    id: ElemId,
290    /// Byte range of the value only; use for replacement edits.
291    span: Span,
292    /// End of the value plus any trailing comma and whitespace; use for removal edits.
293    /// Equal to `span.end` when there is no trailing comma (root element, or last sibling).
294    full_span_end: u32,
295    /// Parsed value, borrowing from the source `&str`.
296    value: RawValue<'buf>,
297}
298
299/// The parsed content of a [`RawElement`], mirroring [`Value`] but without [`DocumentInner`].
300///
301/// Strings are kept as [`RawStr`] slices of the source; numbers are kept as
302/// raw `&str` slices. Both are converted to their public forms by [`into_element`].
303enum RawValue<'buf> {
304    /// JSON `null` literal.
305    Null,
306    /// JSON `true` literal.
307    True,
308    /// JSON `false` literal.
309    False,
310    /// String content with quotes removed; escape sequences are not decoded.
311    String(RawStr<'buf>),
312    /// Raw number text; not guaranteed to fit any specific numeric type.
313    Number(&'buf str),
314    /// Ordered list of child elements.
315    Array(Vec<RawElement<'buf>>),
316    /// Ordered list of key-value fields.
317    Object(Vec<RawField<'buf>>),
318}
319
320/// A key-value pair inside a JSON object, mirroring [`Field`] but without [`DocumentInner`].
321///
322/// `key_span` covers the quoted key bytes in the source, including the surrounding
323/// double-quotes. The value is stored as a [`RawElement`].
324struct RawField<'buf> {
325    /// Span of the key token, including surrounding `"` delimiters.
326    key_span: Span,
327    /// The value element; its path ends with the key from `key_span`.
328    element: RawElement<'buf>,
329}
330
331/// Single-pass recursive-descent JSON parser.
332///
333/// Holds all mutable state for one parse: the source string, a byte cursor, an
334/// [`ElemId`] counter, the [`PathTable`] being built, and a nesting-depth guard.
335///
336/// Call [`Parser::new`] to create an instance, then [`Parser::parse_value`] to
337/// drive the parse. The result is a [`RawElement`] tree; pass it together with
338/// the completed [`PathTable`] to [`into_element`] to obtain the public
339/// [`Element`] tree with shared [`DocumentInner`] attached.
340struct Parser<'buf> {
341    /// The full source string; all span byte positions are relative to this.
342    source: &'buf str,
343    /// Byte view of `source`; used for index-based reads without UTF-8 overhead.
344    bytes: &'buf [u8],
345    /// Current read position in bytes.
346    pos: usize,
347    /// Counter for assigning sequential [`ElemId`]s depth-first.
348    next_id: usize,
349    /// Path table being built as elements are parsed.
350    table: PathTable<'buf>,
351    /// Current nesting depth; checked against [`MAX_DEPTH`] on each container open.
352    depth: usize,
353}
354
355impl<'buf> Parser<'buf> {
356    /// Creates a `Parser` that will read from `source`.
357    fn new(source: &'buf str) -> Self {
358        Self {
359            source,
360            bytes: source.as_bytes(),
361            pos: 0,
362            next_id: 0,
363            table: PathTable::default(),
364            depth: 0,
365        }
366    }
367
368    /// Allocates the next sequential [`ElemId`] and advances the counter.
369    fn alloc_id(&mut self) -> Result<ElemId, Error> {
370        let id = ElemId(self.next_id);
371        self.next_id = self
372            .next_id
373            .checked_add(1)
374            .ok_or_else(|| self.error(ErrorKind::MaxElements))?;
375        Ok(id)
376    }
377
378    /// Advances past any JSON whitespace (`space`, `tab`, `CR`, `LF`) at the current position.
379    fn skip_ws(&mut self) {
380        while matches!(self.bytes.get(self.pos), Some(&SPACE | &TAB | &LF | &CR)) {
381            self.chomp();
382        }
383    }
384
385    /// Returns the byte at the current position without advancing, or `None` at end of input.
386    fn peek(&self) -> Option<u8> {
387        self.bytes.get(self.pos).copied()
388    }
389
390    /// Advances the cursor by one byte.
391    #[inline]
392    fn chomp(&mut self) {
393        self.pos += 1;
394    }
395
396    /// Create and return an `Error`.
397    fn error(&self, kind: ErrorKind) -> Error {
398        let parsed = &self.source[..self.pos];
399        Error {
400            byte_offset: parsed.len(),
401            position: super::line_col(parsed),
402            kind,
403        }
404    }
405
406    /// Returns the byte at the current position and advances past it, or `None` at end of input.
407    fn advance(&mut self) -> Option<u8> {
408        let b = self.bytes.get(self.pos).copied();
409        if b.is_some() {
410            self.chomp();
411        }
412        b
413    }
414
415    /// Asserts that the next byte equals `byte` and advances past it.
416    ///
417    /// Returns [`ErrorKind::UnexpectedChar`] if a different byte is present, or
418    /// [`ErrorKind::UnexpectedEOF`] if the input is exhausted.
419    fn expect_byte(&mut self, byte: u8) -> Result<(), Error> {
420        match self.bytes.get(self.pos) {
421            Some(&b) if b == byte => {
422                self.chomp();
423                Ok(())
424            }
425            Some(_) => Err(self.error(ErrorKind::UnexpectedChar {
426                expected: char::from(byte),
427            })),
428            None => Err(self.error(ErrorKind::UnexpectedEOF)),
429        }
430    }
431
432    /// Asserts that the next bytes match `literal` byte-for-byte, advancing past them.
433    ///
434    /// Used for JSON keywords (`null`, `true`, `false`). The caller dispatches via
435    /// [`Self::peek`] without consuming the first byte, so `literal` must include it.
436    fn expect_literal(&mut self, literal: &'static str) -> Result<(), Error> {
437        for &expected in literal.as_bytes() {
438            match self.advance() {
439                Some(b) if b == expected => {}
440                Some(_) => {
441                    self.pos -= 1;
442                    return Err(self.error(ErrorKind::ExpectedLiteral { expected: literal }));
443                }
444                None => return Err(self.error(ErrorKind::UnexpectedEOF)),
445            }
446        }
447        Ok(())
448    }
449
450    /// Parses one JSON value preceded by optional whitespace and returns a fully-formed [`Element`].
451    ///
452    /// Allocates an [`ElemId`] and records the path `entry` before dispatching to
453    /// [`Self::parse_value_kind`], so child elements produced during that call
454    /// already find this element's id in the table as their parent.
455    fn parse_value(&mut self, entry: PathEntry<'buf>) -> Result<RawElement<'buf>, Error> {
456        self.skip_ws();
457        // ID and table entry are registered before recursing so that child elements
458        // produced by parse_value_kind see this id as their parent.
459        let id = self.alloc_id()?;
460        self.table.push(entry);
461        let start = self.pos;
462        let value = self.parse_value_kind(id)?;
463        let span = Span::new(start as u32, self.pos as u32);
464        Ok(RawElement {
465            id,
466            span,
467            // The parent container extends this past the trailing comma and whitespace
468            // when it exists, so that the element's removal span covers its own separator.
469            full_span_end: span.end,
470            value,
471        })
472    }
473
474    /// Dispatches to the type-specific parser based on the first byte of the value.
475    ///
476    /// `id` is this element's own [`ElemId`], threaded down to [`Self::parse_array`]
477    /// and [`Self::parse_object`] so they can record it as the parent of their children.
478    fn parse_value_kind(&mut self, id: ElemId) -> Result<RawValue<'buf>, Error> {
479        match self
480            .peek()
481            .ok_or_else(|| self.error(ErrorKind::UnexpectedEOF))?
482        {
483            b'n' => {
484                self.expect_literal(NULL)?;
485                Ok(RawValue::Null)
486            }
487            b't' => {
488                self.expect_literal(TRUE)?;
489                Ok(RawValue::True)
490            }
491            b'f' => {
492                self.expect_literal(FALSE)?;
493                Ok(RawValue::False)
494            }
495            QUOTE => Ok(RawValue::String(self.parse_raw_str()?)),
496            MINUS | DIGIT_0..=DIGIT_9 => Ok(RawValue::Number(self.parse_number_str()?)),
497            ARRAY_OPEN => self.parse_array(id),
498            OBJECT_OPEN => self.parse_object(id),
499            _ => Err(self.error(ErrorKind::ExpectedStart)),
500        }
501    }
502
503    /// Parses a JSON number and returns the raw source slice.
504    ///
505    /// Grammar `RFC 8259 s6`:
506    /// ```text
507    /// number = [ '-' ] int [ frac ] [ exp ]
508    /// int    = '0' | [1-9] DIGIT*
509    /// frac   = '.' DIGIT+
510    /// exp    = ('e'|'E') ['+'|'-'] DIGIT+
511    /// ```
512    fn parse_number_str(&mut self) -> Result<&'buf str, Error> {
513        let start = self.pos;
514
515        if self.peek() == Some(MINUS) {
516            self.chomp();
517        }
518
519        match self
520            .peek()
521            .ok_or_else(|| self.error(ErrorKind::UnexpectedEOF))?
522        {
523            // A lone '0' is the only valid integer starting with zero; more digits
524            // after it would be a leading-zero violation (e.g. "01" is invalid JSON).
525            DIGIT_0 => self.chomp(),
526            DIGIT_1..=DIGIT_9 => {
527                while matches!(self.peek(), Some(DIGIT_0..=DIGIT_9)) {
528                    self.chomp();
529                }
530            }
531            _ => return Err(self.error(ErrorKind::ExpectedNumeral)),
532        }
533
534        if self.peek() == Some(DECIMAL_POINT) {
535            self.chomp();
536            // At least one digit is required after the decimal point.
537            if !matches!(self.peek(), Some(DIGIT_0..=DIGIT_9)) {
538                return Err(match self.peek() {
539                    Some(_) => self.error(ErrorKind::ExpectedNumeral),
540                    None => self.error(ErrorKind::UnexpectedEOF),
541                });
542            }
543            while matches!(self.peek(), Some(DIGIT_0..=DIGIT_9)) {
544                self.chomp();
545            }
546        }
547
548        if matches!(self.peek(), Some(EXP_LOWER | EXP_UPPER)) {
549            self.chomp();
550            if matches!(self.peek(), Some(PLUS | MINUS)) {
551                self.chomp();
552            }
553            // At least one digit is required after the exponent indicator (and optional sign).
554            if !matches!(self.peek(), Some(DIGIT_0..=DIGIT_9)) {
555                return Err(match self.peek() {
556                    Some(_) => self.error(ErrorKind::ExpectedNumeral),
557                    None => self.error(ErrorKind::UnexpectedEOF),
558                });
559            }
560            while matches!(self.peek(), Some(DIGIT_0..=DIGIT_9)) {
561                self.chomp();
562            }
563        }
564
565        Ok(&self.source[start..self.pos])
566    }
567
568    /// Parses a JSON string and returns a [`RawStr`] with quotes stripped.
569    ///
570    /// Scans for the closing `"` delimiter, skipping one byte after every `\`
571    /// so that `\"` does not terminate the string. Escape sequences and control
572    /// characters are not validated here; callers use [`RawStr::decode_escapes`].
573    fn parse_raw_str(&mut self) -> Result<RawStr<'buf>, Error> {
574        self.expect_byte(QUOTE)?;
575        let content_start = self.pos; // First byte after the opening `"`.
576
577        loop {
578            match self
579                .advance()
580                .ok_or_else(|| self.error(ErrorKind::UnexpectedEOF))?
581            {
582                QUOTE => break,
583                BACKSLASH => {
584                    // Consume whatever follows so that `\"` does not close the string.
585                    self.advance()
586                        .ok_or_else(|| self.error(ErrorKind::UnexpectedEOF))?;
587                }
588                _ => {}
589            }
590        }
591
592        // `advance()` left `pos` one past the closing '"', so `pos-1` is the '"' itself;
593        // `content_start..pos-1` therefore captures content without either delimiter.
594        Ok(RawStr(&self.source[content_start..self.pos - 1]))
595    }
596
597    /// Parses a JSON array `[...]` and returns [`Value::Array`].
598    ///
599    /// Increments the depth counter before consuming `[` and returns
600    /// [`ErrorKind::DepthLimitExceeded`] if the limit is exceeded.
601    fn parse_array(&mut self, parent_id: ElemId) -> Result<RawValue<'buf>, Error> {
602        self.depth += 1;
603        if self.depth > MAX_DEPTH {
604            return Err(self.error(ErrorKind::DepthLimitExceeded));
605        }
606        self.expect_byte(ARRAY_OPEN)?;
607        self.skip_ws();
608        let mut elements: Vec<RawElement<'buf>> = Vec::new();
609
610        if self.peek() != Some(ARRAY_CLOSE) {
611            loop {
612                let entry = PathEntry::Item {
613                    parent: parent_id,
614                    index: elements.len() as u32,
615                };
616                let mut elem = self.parse_value(entry)?;
617                self.skip_ws();
618                match self
619                    .peek()
620                    .ok_or_else(|| self.error(ErrorKind::UnexpectedEOF))?
621                {
622                    COMMA => {
623                        self.chomp();
624                        self.skip_ws();
625                        if self.peek() == Some(ARRAY_CLOSE) {
626                            return Err(self.error(ErrorKind::ExpectedEndArray));
627                        }
628                        // Extend past the comma and leading whitespace of the next sibling
629                        // so that removing this element also removes its own separator.
630                        elem.full_span_end = self.pos as u32;
631                        elements.push(elem);
632                    }
633                    ARRAY_CLOSE => {
634                        elements.push(elem);
635                        break;
636                    }
637                    _ => return Err(self.error(ErrorKind::ExpectedEndArray)),
638                }
639            }
640        }
641
642        self.expect_byte(ARRAY_CLOSE)?;
643        self.depth -= 1;
644        Ok(RawValue::Array(elements))
645    }
646
647    /// Parses a JSON object `{...}` and returns [`Value::Object`].
648    ///
649    /// Increments the depth counter before consuming `{` and returns
650    /// [`ErrorKind::DepthLimitExceeded`] if the limit is exceeded.
651    fn parse_object(&mut self, parent_id: ElemId) -> Result<RawValue<'buf>, Error> {
652        self.depth += 1;
653        if self.depth > MAX_DEPTH {
654            return Err(self.error(ErrorKind::DepthLimitExceeded));
655        }
656        self.expect_byte(OBJECT_OPEN)?;
657        self.skip_ws();
658        let mut fields: Vec<RawField<'buf>> = Vec::new();
659
660        if self.peek() != Some(OBJECT_CLOSE) {
661            loop {
662                let key_start = self.pos;
663                let key = self.parse_raw_str()?;
664                let key_span = Span::new(key_start as u32, self.pos as u32);
665                self.skip_ws();
666                self.expect_byte(COLON)?;
667                let entry = PathEntry::Field {
668                    parent: parent_id,
669                    key,
670                };
671                let mut elem = self.parse_value(entry)?;
672                self.skip_ws();
673                match self
674                    .peek()
675                    .ok_or_else(|| self.error(ErrorKind::UnexpectedEOF))?
676                {
677                    COMMA => {
678                        self.chomp();
679                        self.skip_ws();
680                        if self.peek() == Some(OBJECT_CLOSE) {
681                            return Err(self.error(ErrorKind::ExpectedEndObject));
682                        }
683                        // Same as in parse_array: extend past the comma and whitespace
684                        // so that removing this field also removes its own separator.
685                        elem.full_span_end = self.pos as u32;
686                        fields.push(RawField {
687                            key_span,
688                            element: elem,
689                        });
690                    }
691                    OBJECT_CLOSE => {
692                        fields.push(RawField {
693                            key_span,
694                            element: elem,
695                        });
696                        break;
697                    }
698                    _ => return Err(self.error(ErrorKind::ExpectedEndObject)),
699                }
700            }
701        }
702
703        self.expect_byte(OBJECT_CLOSE)?;
704        self.depth -= 1;
705        Ok(RawValue::Object(fields))
706    }
707}
708
709/// Converts a [`RawElement`] tree into the public [`Element`] tree, loading every
710/// node with a clone of `inner` at construction time.
711///
712/// Uses an explicit work stack instead of recursion. Children are pushed in
713/// reverse so they are processed in order; each `Build*` task then pops its
714/// children off `done` and assembles the parent.
715fn into_element<'buf>(raw: RawElement<'buf>, inner: &Rc<DocumentInner<'buf>>) -> Element<'buf> {
716    enum Task<'buf> {
717        Process(RawElement<'buf>),
718        BuildArray {
719            id: ElemId,
720            span: Span,
721            full_span_end: u32,
722            count: usize,
723        },
724        BuildObject {
725            id: ElemId,
726            span: Span,
727            full_span_end: u32,
728            key_spans: Vec<Span>,
729        },
730    }
731
732    let mut work: Vec<Task<'buf>> = vec![Task::Process(raw)];
733    let mut done: Vec<Element<'buf>> = Vec::new();
734
735    while let Some(task) = work.pop() {
736        match task {
737            Task::Process(raw) => {
738                let value = match raw.value {
739                    RawValue::Null => Value::Null,
740                    RawValue::True => Value::True,
741                    RawValue::False => Value::False,
742                    RawValue::String(s) => Value::String(s),
743                    RawValue::Number(n) => Value::Number(n),
744                    RawValue::Array(items) => {
745                        work.push(Task::BuildArray {
746                            id: raw.id,
747                            span: raw.span,
748                            full_span_end: raw.full_span_end,
749                            count: items.len(),
750                        });
751                        for item in items.into_iter().rev() {
752                            work.push(Task::Process(item));
753                        }
754                        continue;
755                    }
756                    RawValue::Object(fields) => {
757                        let key_spans = fields.iter().map(|f| f.key_span).collect();
758                        work.push(Task::BuildObject {
759                            id: raw.id,
760                            span: raw.span,
761                            full_span_end: raw.full_span_end,
762                            key_spans,
763                        });
764                        for field in fields.into_iter().rev() {
765                            work.push(Task::Process(field.element));
766                        }
767                        continue;
768                    }
769                };
770                done.push(Element {
771                    doc: Rc::clone(inner),
772                    id: raw.id,
773                    span: raw.span,
774                    full_span_end: raw.full_span_end,
775                    value,
776                });
777            }
778            Task::BuildArray {
779                id,
780                span,
781                full_span_end,
782                count,
783            } => {
784                let start = done.len() - count;
785                let items: Vec<Element<'buf>> = done.drain(start..).collect();
786                done.push(Element {
787                    doc: Rc::clone(inner),
788                    id,
789                    span,
790                    full_span_end,
791                    value: Value::Array(items),
792                });
793            }
794            Task::BuildObject {
795                id,
796                span,
797                full_span_end,
798                key_spans,
799            } => {
800                let count = key_spans.len();
801                let start = done.len() - count;
802                let elements: Vec<Element<'buf>> = done.drain(start..).collect();
803                let fields = key_spans
804                    .into_iter()
805                    .zip(elements)
806                    .map(|(key_span, element)| Field { key_span, element })
807                    .collect();
808                done.push(Element {
809                    doc: Rc::clone(inner),
810                    id,
811                    span,
812                    full_span_end,
813                    value: Value::Object(fields),
814                });
815            }
816        }
817    }
818
819    // Each Process task produces exactly one element in `done`, either directly
820    // (scalars) or via a Build task (containers). Starting with one root Process
821    // task guarantees exactly one element remains here.
822    done.swap_remove(0)
823}