Skip to main content

oxc_yaml_parser/
parser.rs

1//! The parser: consumes scanner tokens and builds the AST.
2//!
3//! The scanner (libyaml-style) already synthesizes `Block*Start`/`BlockEnd`
4//! and implicit `FlowMapping*` tokens, so this layer is a straightforward
5//! recursive descent over a well-structured token stream.
6
7use crate::{
8    ast::*,
9    error::{Error, ErrorKind},
10    pos::Span,
11    scanner::{ScalarStyle, Scanner, Token, TokenKind},
12};
13use oxc_allocator::{Allocator, Box, Vec};
14
15type ParseResult<T> = Result<T, Error>;
16
17pub struct Parser<'a> {
18    allocator: &'a Allocator,
19    source: &'a str,
20    scanner: Scanner<'a>,
21    peeked: Option<Token>,
22}
23
24impl<'a> Parser<'a> {
25    pub fn new(allocator: &'a Allocator, source: &'a str) -> Self {
26        Self { allocator, source, scanner: Scanner::new(allocator, source), peeked: None }
27    }
28
29    /// Parse the source into a [`Root`].
30    ///
31    /// # Errors
32    /// Returns the first syntax error encountered. No partial AST is produced.
33    #[expect(clippy::cast_possible_truncation)] // guarded right below
34    pub fn parse(mut self) -> ParseResult<Root<'a>> {
35        if u32::try_from(self.source.len()).is_err() {
36            return Err(Error::new(ErrorKind::SourceTooLong, Span::empty(0)));
37        }
38        let source_len = self.source.len() as u32;
39
40        let first = self.next()?;
41        debug_assert_eq!(first.kind, TokenKind::StreamStart);
42
43        let mut children = Vec::new_in(&self.allocator);
44        loop {
45            if self.peek()?.kind == TokenKind::StreamEnd {
46                break;
47            }
48            children.push(self.parse_document()?);
49        }
50
51        // The scanner collected comments directly in the arena; move them out.
52        let comments = std::mem::replace(&mut self.scanner.comments, Vec::new_in(&self.allocator));
53
54        Ok(Root { span: Span::new(0, source_len), children, comments })
55    }
56
57    // ---------------------------------------------------------------- tokens
58
59    fn next(&mut self) -> ParseResult<Token> {
60        if let Some(t) = self.peeked.take() {
61            return Ok(t);
62        }
63        self.scanner.next_token()?.ok_or_else(|| {
64            Error::point(ErrorKind::UnexpectedEof, self.source.len().saturating_sub(1))
65        })
66    }
67
68    fn peek(&mut self) -> ParseResult<&Token> {
69        if self.peeked.is_none() {
70            self.peeked = Some(self.next()?);
71        }
72        Ok(self.peeked.as_ref().unwrap())
73    }
74
75    fn peek_kind(&mut self) -> ParseResult<TokenKind> {
76        Ok(self.peek()?.kind)
77    }
78
79    fn eat(&mut self, kind: TokenKind) -> ParseResult<Option<Token>> {
80        if self.peek()?.kind == kind {
81            return Ok(Some(self.next()?));
82        }
83        Ok(None)
84    }
85
86    fn alloc<T>(&self, value: T) -> Box<'a, T> {
87        Box::new_in(value, &self.allocator)
88    }
89
90    /// Parse a node (boxed for a node-position field) if the next token can start one, else `None`.
91    /// `allow_indentless` also accepts a bare `BlockEntry`
92    /// (an indentless sequence — only valid in mapping key/value position).
93    fn parse_optional_node(
94        &mut self,
95        allow_indentless: bool,
96    ) -> ParseResult<Option<Box<'a, Node<'a>>>> {
97        let kind = self.peek_kind()?;
98        let starts =
99            if allow_indentless { kind.starts_mapping_entry_node() } else { kind.starts_node() };
100        if starts {
101            let node = self.parse_node(allow_indentless)?;
102            Ok(Some(self.alloc(node)))
103        } else {
104            Ok(None)
105        }
106    }
107
108    // -------------------------------------------------------------- documents
109
110    fn parse_document(&mut self) -> ParseResult<Document<'a>> {
111        let head_start = self.peek()?.span.start;
112        let mut directives = Vec::new_in(&self.allocator);
113        while self.peek_kind()? == TokenKind::Directive {
114            let token = self.next()?;
115            directives.push(self.build_directive(token));
116        }
117        let head_end = directives.last().map_or(head_start, |d: &Directive<'a>| d.span.end);
118
119        let directives_end_marker = self.eat(TokenKind::DocumentStart)?.map(|t| t.span);
120        if !directives.is_empty() && directives_end_marker.is_none() {
121            return Err(Error::new(
122                ErrorKind::ExpectedDocumentStart,
123                Span::new(head_start, head_end),
124            ));
125        }
126
127        let head = DocumentHead {
128            span: Span::new(head_start, directives_end_marker.map_or(head_end, |s| s.end)),
129            directives,
130        };
131
132        let content = self.parse_optional_node(false)?;
133
134        let body_span = content.as_ref().map_or_else(
135            || Span::empty(directives_end_marker.map_or(head_start, |s| s.end)),
136            |node| node.span,
137        );
138        let body = DocumentBody { span: body_span, content };
139
140        let document_end_marker = self.eat(TokenKind::DocumentEnd)?.map(|t| t.span);
141        // Without an explicit `...`, the next document must be introduced by
142        // `---` or directives (or the stream must end); after a `...`, a bare
143        // document may follow, so anything goes.
144        if document_end_marker.is_none() {
145            match self.peek_kind()? {
146                TokenKind::StreamEnd | TokenKind::DocumentStart | TokenKind::Directive => {}
147                _ => {
148                    let span = self.peek()?.span;
149                    return Err(Error::new(ErrorKind::ExpectedDocumentEnd, span));
150                }
151            }
152        }
153
154        // The head is peeked first, so `head_start` is the document start; the
155        // body/head end is the document end unless a `...` marker follows.
156        let span_end = document_end_marker.map_or(body.span.end.max(head.span.end), |s| s.end);
157
158        Ok(Document {
159            span: Span::new(head_start, span_end),
160            head,
161            body,
162            directives_end_marker,
163            document_end_marker,
164        })
165    }
166
167    fn build_directive(&self, token: Token) -> Directive<'a> {
168        let text = token.span.slice(self.source);
169        let mut words = text.trim_start_matches('%').split_ascii_whitespace();
170        let name = words.next().unwrap_or("");
171        let parameters = Vec::from_iter_in(words, &self.allocator);
172        Directive { span: token.span, name, parameters }
173    }
174
175    // ------------------------------------------------------------------ nodes
176
177    fn parse_props(&mut self) -> ParseResult<Props> {
178        let mut props = Props { anchor: None, tag: None };
179        loop {
180            match self.peek_kind()? {
181                TokenKind::Anchor => {
182                    let token = self.next()?;
183                    if props.anchor.is_some() {
184                        return Err(Error::new(ErrorKind::DuplicatedNodeProperty, token.span));
185                    }
186                    props.anchor = Some(Anchor { span: token.span });
187                }
188                TokenKind::Tag => {
189                    let token = self.next()?;
190                    if props.tag.is_some() {
191                        return Err(Error::new(ErrorKind::DuplicatedNodeProperty, token.span));
192                    }
193                    props.tag = Some(Tag { span: token.span });
194                }
195                _ => break,
196            }
197        }
198        Ok(props)
199    }
200
201    /// Parse a full node: properties, then the content they apply to.
202    /// The node span covers both, so child spans always nest inside it.
203    fn parse_node(&mut self, allow_indentless: bool) -> ParseResult<Node<'a>> {
204        let props = self.parse_props()?;
205        let content = self.parse_content(&props, allow_indentless)?;
206        let content_span = content.span();
207        let span = Span::new(props.start().unwrap_or(content_span.start), content_span.end);
208        Ok(Node { span, props, content })
209    }
210
211    /// `allow_indentless`: whether a bare `BlockEntry` after the props starts
212    /// an indentless sequence. Only mapping key/value position allows one
213    /// (YAML `seq-spaces`); in sequence-item position `- !!tag\n- next` is an
214    /// empty tagged node followed by the parent's next entry.
215    fn parse_content(&mut self, props: &Props, allow_indentless: bool) -> ParseResult<Content<'a>> {
216        let token = *self.peek()?;
217        match token.kind {
218            TokenKind::Alias => {
219                self.next()?;
220                if props.anchor.is_some() || props.tag.is_some() {
221                    return Err(Error::new(ErrorKind::DuplicatedNodeProperty, token.span));
222                }
223                Ok(Content::Alias(self.alloc(Alias { span: token.span })))
224            }
225            TokenKind::Scalar(style, header_index) => {
226                self.next()?;
227                Ok(self.build_scalar(style, header_index, token.span))
228            }
229            TokenKind::FlowSequenceStart => self.parse_flow_sequence(),
230            TokenKind::FlowMappingStart => self.parse_flow_mapping(),
231            TokenKind::BlockSequenceStart => self.parse_block_sequence(),
232            TokenKind::BlockMappingStart => self.parse_block_mapping(),
233            // A `BlockEntry` with no preceding `BlockSequenceStart` is an
234            // indentless sequence (a sequence at the same indentation as its
235            // parent mapping key: `key:\n- a`). Outside key/value position it
236            // falls through to the empty-node synthesis below.
237            TokenKind::BlockEntry if allow_indentless => self.parse_indentless_sequence(),
238            _ => {
239                // Properties with no following content (e.g. `!!str : v`):
240                // synthesize an empty plain scalar carrying the properties.
241                if props.anchor.is_some() || props.tag.is_some() {
242                    let at = props
243                        .anchor
244                        .map(|a| a.span.end)
245                        .max(props.tag.map(|t| t.span.end))
246                        .unwrap();
247                    return Ok(Content::Plain(self.alloc(Plain { span: Span::empty(at) })));
248                }
249                Err(Error::new(ErrorKind::ExpectedNode, token.span))
250            }
251        }
252    }
253
254    fn build_scalar(
255        &self,
256        style: ScalarStyle,
257        header_index: Option<crate::scanner::BlockHeaderIndex>,
258        span: Span,
259    ) -> Content<'a> {
260        match style {
261            ScalarStyle::Plain => Content::Plain(self.alloc(Plain { span })),
262            ScalarStyle::SingleQuoted => Content::QuoteSingle(self.alloc(QuoteSingle { span })),
263            ScalarStyle::DoubleQuoted => Content::QuoteDouble(self.alloc(QuoteDouble { span })),
264            ScalarStyle::Literal | ScalarStyle::Folded => {
265                let index = header_index.expect("block scalar token must carry a header index");
266                let header = self.scanner.block_headers[index.get()];
267                let node = BlockScalar {
268                    span,
269                    chomping: header.chomping,
270                    indent: header.indent,
271                    content_start: header.content_start,
272                    content_end: header.content_end,
273                };
274                if style == ScalarStyle::Literal {
275                    Content::BlockLiteral(self.alloc(node))
276                } else {
277                    Content::BlockFolded(self.alloc(node))
278                }
279            }
280        }
281    }
282
283    /// Parse one `- item`. The cursor must be at a `BlockEntry` token.
284    fn parse_sequence_item(&mut self) -> ParseResult<SequenceItem<'a>> {
285        let entry_token = self.next()?;
286        debug_assert_eq!(entry_token.kind, TokenKind::BlockEntry);
287        let content = self.parse_optional_node(false)?;
288        let end = content.as_ref().map_or(entry_token.span.end, |n| n.span.end);
289        Ok(SequenceItem { span: Span::new(entry_token.span.start, end), content })
290    }
291
292    fn parse_block_sequence(&mut self) -> ParseResult<Content<'a>> {
293        let start_token = self.next()?; // BlockSequenceStart
294        let mut children = Vec::new_in(&self.allocator);
295
296        loop {
297            match self.peek_kind()? {
298                TokenKind::BlockEnd => {
299                    self.next()?;
300                    break;
301                }
302                TokenKind::BlockEntry => children.push(self.parse_sequence_item()?),
303                _ => {
304                    let span = self.peek()?.span;
305                    return Err(Error::new(ErrorKind::UnexpectedToken("token in sequence"), span));
306                }
307            }
308        }
309
310        let span = container_span(start_token.span, children.first(), children.last());
311        Ok(Content::Sequence(self.alloc(Sequence { span, children })))
312    }
313
314    /// Parse an indentless sequence: `BlockEntry` items with no enclosing
315    /// `BlockSequenceStart`/`BlockEnd` (the scanner does not roll an indent
316    /// for a sequence at the same indentation as its parent mapping key).
317    /// Terminates at the first token that is not a `BlockEntry`.
318    fn parse_indentless_sequence(&mut self) -> ParseResult<Content<'a>> {
319        let mut children = Vec::new_in(&self.allocator);
320        let first = self.peek()?.span;
321
322        while self.peek_kind()? == TokenKind::BlockEntry {
323            children.push(self.parse_sequence_item()?);
324        }
325
326        let span = container_span(Span::empty(first.start), children.first(), children.last());
327        Ok(Content::Sequence(self.alloc(Sequence { span, children })))
328    }
329
330    fn parse_block_mapping(&mut self) -> ParseResult<Content<'a>> {
331        let start_token = self.next()?; // BlockMappingStart
332        let mut children = Vec::new_in(&self.allocator);
333
334        loop {
335            match self.peek_kind()? {
336                TokenKind::BlockEnd => {
337                    self.next()?;
338                    break;
339                }
340                TokenKind::Key | TokenKind::Value => {
341                    children.push(self.parse_mapping_item()?);
342                }
343                _ => {
344                    let span = self.peek()?.span;
345                    return Err(Error::new(ErrorKind::UnexpectedToken("token in mapping"), span));
346                }
347            }
348        }
349
350        let span = container_span(start_token.span, children.first(), children.last());
351        Ok(Content::Mapping(self.alloc(Mapping { span, children })))
352    }
353
354    /// Parse one `key: value` pair (block or flow; the token structure is the
355    /// same). The cursor must be at a `Key` or `Value` token.
356    fn parse_mapping_item(&mut self) -> ParseResult<MappingItem<'a>> {
357        // A real `Key` token is always a literal `?`; a synthesized one is
358        // the scanner's retroactive marker for an implicit `key:`.
359        let key_token = self.eat(TokenKind::Key)?;
360        let key = if let Some(key_token) = key_token {
361            let explicit = !key_token.synthesized;
362            let content = self.parse_optional_node(true)?;
363            match content {
364                Some(node) => {
365                    // An explicit key's span starts at the `?` indicator.
366                    let start = if explicit { key_token.span.start } else { node.span.start };
367                    let span = Span::new(start, node.span.end);
368                    Some(MappingKey { span, content: Some(node), explicit })
369                }
370                None if explicit => {
371                    Some(MappingKey { span: key_token.span, content: None, explicit })
372                }
373                // A synthesized marker with no content leaves no trace in the
374                // source: no key.
375                None => None,
376            }
377        } else {
378            None
379        };
380
381        // The `Value` token is always the literal `:`; the value span starts there.
382        let value = if let Some(value_token) = self.eat(TokenKind::Value)? {
383            let content = self.parse_optional_node(true)?;
384            let end = content.as_ref().map_or(value_token.span.end, |n| n.span.end);
385            Some(MappingValue { span: Span::new(value_token.span.start, end), content })
386        } else {
387            None
388        };
389
390        // Entered at a `Key`/`Value` token, so a key, a value,
391        // or at least a consumed key token (the degenerate all-empty case) exists.
392        let start = key
393            .as_ref()
394            .map(|k| k.span.start)
395            .or_else(|| value.as_ref().map(|v| v.span.start))
396            .or_else(|| key_token.map(|t| t.span.start))
397            .expect("parse_mapping_item is entered at a Key or Value token");
398        let end = value
399            .as_ref()
400            .map(|v| v.span.end)
401            .max(key.as_ref().map(|k| k.span.end))
402            .unwrap_or(start);
403        Ok(MappingItem { span: Span::new(start, end), key, value })
404    }
405
406    fn parse_flow_sequence(&mut self) -> ParseResult<Content<'a>> {
407        let start_token = self.next()?; // FlowSequenceStart
408        let mut children = Vec::new_in(&self.allocator);
409
410        loop {
411            match self.peek_kind()? {
412                TokenKind::FlowSequenceEnd => {
413                    let end_token = self.next()?;
414                    let span = Span::new(start_token.span.start, end_token.span.end);
415                    return Ok(Content::FlowSequence(self.alloc(FlowSequence { span, children })));
416                }
417                TokenKind::FlowEntry => {
418                    self.next()?;
419                }
420                TokenKind::Key | TokenKind::Value => {
421                    // An explicit pair (`[? a: b]`), or an implicit pair for
422                    // which the scanner did not synthesize a `FlowMappingStart`.
423                    let item = self.parse_mapping_item()?;
424                    children.push(FlowSequenceEntry::Pair(self.alloc(item)));
425                }
426                _ => {
427                    // An implicit single pair (`[a: b]`) is surfaced by the
428                    // scanner as a synthesized `FlowMappingStart` wrapper.
429                    let is_synthesized_pair = {
430                        let token = self.peek()?;
431                        token.kind == TokenKind::FlowMappingStart && token.synthesized
432                    };
433                    let node = self.parse_node(false)?;
434                    if is_synthesized_pair {
435                        if let Content::FlowMapping(mapping) = node.content {
436                            let mut mapping = mapping.unbox();
437                            debug_assert_eq!(mapping.children.len(), 1);
438                            if let Some(item) = mapping.children.pop() {
439                                children.push(FlowSequenceEntry::Pair(self.alloc(item)));
440                            }
441                            continue;
442                        }
443                        unreachable!("synthesized FlowMappingStart must produce a FlowMapping");
444                    }
445                    children.push(FlowSequenceEntry::Item(self.alloc(node)));
446                }
447            }
448        }
449    }
450
451    fn parse_flow_mapping(&mut self) -> ParseResult<Content<'a>> {
452        let start_token = self.next()?; // FlowMappingStart
453        let mut children = Vec::new_in(&self.allocator);
454
455        loop {
456            match self.peek_kind()? {
457                TokenKind::FlowMappingEnd => {
458                    let end_token = self.next()?;
459                    let span = if end_token.synthesized {
460                        // Synthesized end of an implicit mapping.
461                        container_span(start_token.span, children.first(), children.last())
462                    } else {
463                        Span::new(start_token.span.start, end_token.span.end)
464                    };
465                    return Ok(Content::FlowMapping(self.alloc(FlowMapping { span, children })));
466                }
467                TokenKind::FlowEntry => {
468                    self.next()?;
469                }
470                TokenKind::Key | TokenKind::Value => {
471                    children.push(self.parse_mapping_item()?);
472                }
473                _ if self.peek_kind()?.starts_node() => {
474                    // A lone node in a flow mapping: `{a}` = `{a: null}`.
475                    let node = self.parse_node(false)?;
476                    let span = node.span;
477                    children.push(MappingItem {
478                        span,
479                        key: Some(MappingKey {
480                            span,
481                            content: Some(self.alloc(node)),
482                            explicit: false,
483                        }),
484                        value: None,
485                    });
486                }
487                _ => {
488                    let span = self.peek()?.span;
489                    return Err(Error::new(
490                        ErrorKind::UnexpectedToken("token in flow mapping"),
491                        span,
492                    ));
493                }
494            }
495        }
496    }
497}
498
499/// Span of a container from its (possibly empty) start token and its first and
500/// last children (children are in source order).
501fn container_span<T: HasSpan>(start: Span, first: Option<&T>, last: Option<&T>) -> Span {
502    let start_pos = first.map_or(start.start, |c| c.span().start.min(start.start));
503    let end_pos = last.map_or(start.end, |c| c.span().end.max(start.end));
504    Span::new(start_pos, end_pos)
505}
506
507/// Internal helper for [`container_span`].
508trait HasSpan {
509    fn span(&self) -> Span;
510}
511
512impl HasSpan for SequenceItem<'_> {
513    fn span(&self) -> Span {
514        self.span
515    }
516}
517
518impl HasSpan for MappingItem<'_> {
519    fn span(&self) -> Span {
520        self.span
521    }
522}