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!(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 if the next token can start one, else `None`.
91    /// `allow_indentless` also accepts a bare `BlockEntry` (an indentless
92    /// sequence — only valid in mapping key/value position).
93    fn parse_optional_node(&mut self, allow_indentless: bool) -> ParseResult<Option<Content<'a>>> {
94        let kind = self.peek_kind()?;
95        let starts =
96            if allow_indentless { kind.starts_mapping_entry_node() } else { kind.starts_node() };
97        if starts { Ok(Some(self.parse_node(allow_indentless)?)) } else { Ok(None) }
98    }
99
100    // -------------------------------------------------------------- documents
101
102    fn parse_document(&mut self) -> ParseResult<Document<'a>> {
103        let head_start = self.peek()?.span.start;
104        let mut directives = Vec::new_in(&self.allocator);
105        while self.peek_kind()? == TokenKind::Directive {
106            let token = self.next()?;
107            directives.push(self.build_directive(token));
108        }
109        let head_end = directives.last().map_or(head_start, |d: &Directive<'a>| d.span.end);
110
111        let directives_end_marker = self.eat(TokenKind::DocumentStart)?.map(|t| t.span);
112        if !directives.is_empty() && directives_end_marker.is_none() {
113            return Err(Error::new(
114                ErrorKind::ExpectedDocumentStart,
115                Span::new(head_start, head_end),
116            ));
117        }
118
119        let head = DocumentHead {
120            span: Span::new(head_start, directives_end_marker.map_or(head_end, |s| s.end)),
121            directives,
122        };
123
124        let content = self.parse_optional_node(false)?;
125
126        let body_span = content.as_ref().map_or_else(
127            || Span::empty(directives_end_marker.map_or(head_start, |s| s.end)),
128            Content::span,
129        );
130        let body = DocumentBody { span: body_span, content };
131
132        let document_end_marker = self.eat(TokenKind::DocumentEnd)?.map(|t| t.span);
133        // Without an explicit `...`, the next document must be introduced by
134        // `---` or directives (or the stream must end); after a `...`, a bare
135        // document may follow, so anything goes.
136        if document_end_marker.is_none() {
137            match self.peek_kind()? {
138                TokenKind::StreamEnd | TokenKind::DocumentStart | TokenKind::Directive => {}
139                _ => {
140                    let span = self.peek()?.span;
141                    return Err(Error::new(ErrorKind::ExpectedDocumentEnd, span));
142                }
143            }
144        }
145
146        // The head is peeked first, so `head_start` is the document start; the
147        // body/head end is the document end unless a `...` marker follows.
148        let span_end = document_end_marker.map_or(body.span.end.max(head.span.end), |s| s.end);
149
150        Ok(Document {
151            span: Span::new(head_start, span_end),
152            head,
153            body,
154            directives_end_marker,
155            document_end_marker,
156        })
157    }
158
159    fn build_directive(&self, token: Token) -> Directive<'a> {
160        let text = token.span.slice(self.source);
161        let mut words = text.trim_start_matches('%').split_ascii_whitespace();
162        let name = words.next().unwrap_or("");
163        let parameters = Vec::from_iter_in(words, &self.allocator);
164        Directive { span: token.span, name, parameters }
165    }
166
167    // ------------------------------------------------------------------ nodes
168
169    fn parse_props(&mut self) -> ParseResult<Props> {
170        let mut props = Props { anchor: None, tag: None };
171        loop {
172            match self.peek_kind()? {
173                TokenKind::Anchor => {
174                    let token = self.next()?;
175                    if props.anchor.is_some() {
176                        return Err(Error::new(ErrorKind::DuplicatedNodeProperty, token.span));
177                    }
178                    props.anchor = Some(Anchor { span: token.span });
179                }
180                TokenKind::Tag => {
181                    let token = self.next()?;
182                    if props.tag.is_some() {
183                        return Err(Error::new(ErrorKind::DuplicatedNodeProperty, token.span));
184                    }
185                    props.tag = Some(Tag { span: token.span });
186                }
187                _ => break,
188            }
189        }
190        Ok(props)
191    }
192
193    /// `allow_indentless`: whether a bare `BlockEntry` after the props starts
194    /// an indentless sequence. Only mapping key/value position allows one
195    /// (YAML `seq-spaces`); in sequence-item position `- !!tag\n- next` is an
196    /// empty tagged node followed by the parent's next entry.
197    fn parse_node(&mut self, allow_indentless: bool) -> ParseResult<Content<'a>> {
198        let props = self.parse_props()?;
199
200        let token = *self.peek()?;
201        match token.kind {
202            TokenKind::Alias => {
203                self.next()?;
204                if props.anchor.is_some() || props.tag.is_some() {
205                    return Err(Error::new(ErrorKind::DuplicatedNodeProperty, token.span));
206                }
207                Ok(Content::Alias(self.alloc(Alias { span: token.span, props })))
208            }
209            TokenKind::Scalar(style, header_index) => {
210                self.next()?;
211                Ok(self.build_scalar(props, style, header_index, token.span))
212            }
213            TokenKind::FlowSequenceStart => self.parse_flow_sequence(props),
214            TokenKind::FlowMappingStart => self.parse_flow_mapping(props),
215            TokenKind::BlockSequenceStart => self.parse_block_sequence(props),
216            TokenKind::BlockMappingStart => self.parse_block_mapping(props),
217            // A `BlockEntry` with no preceding `BlockSequenceStart` is an
218            // indentless sequence (a sequence at the same indentation as its
219            // parent mapping key: `key:\n- a`). Outside key/value position it
220            // falls through to the empty-node synthesis below.
221            TokenKind::BlockEntry if allow_indentless => self.parse_indentless_sequence(props),
222            _ => {
223                // Properties with no following content (e.g. `!!str : v`):
224                // synthesize an empty plain scalar carrying the properties.
225                if props.anchor.is_some() || props.tag.is_some() {
226                    let at = props
227                        .anchor
228                        .map(|a| a.span.end)
229                        .max(props.tag.map(|t| t.span.end))
230                        .unwrap();
231                    return Ok(Content::Plain(self.alloc(Plain { span: Span::empty(at), props })));
232                }
233                Err(Error::new(ErrorKind::ExpectedNode, token.span))
234            }
235        }
236    }
237
238    fn build_scalar(
239        &self,
240        props: Props,
241        style: ScalarStyle,
242        header_index: Option<crate::scanner::BlockHeaderIndex>,
243        span: Span,
244    ) -> Content<'a> {
245        match style {
246            ScalarStyle::Plain => Content::Plain(self.alloc(Plain { span, props })),
247            ScalarStyle::SingleQuoted => {
248                Content::QuoteSingle(self.alloc(QuoteSingle { span, props }))
249            }
250            ScalarStyle::DoubleQuoted => {
251                Content::QuoteDouble(self.alloc(QuoteDouble { span, props }))
252            }
253            ScalarStyle::Literal | ScalarStyle::Folded => {
254                let index = header_index.expect("block scalar token must carry a header index");
255                let header = self.scanner.block_headers[index.get()];
256                let node = BlockScalar {
257                    span,
258                    props,
259                    chomping: header.chomping,
260                    indent: header.indent,
261                    content_start: header.content_start,
262                };
263                if style == ScalarStyle::Literal {
264                    Content::BlockLiteral(self.alloc(node))
265                } else {
266                    Content::BlockFolded(self.alloc(node))
267                }
268            }
269        }
270    }
271
272    /// Parse one `- item`. The cursor must be at a `BlockEntry` token.
273    fn parse_sequence_item(&mut self) -> ParseResult<SequenceItem<'a>> {
274        let entry_token = self.next()?;
275        debug_assert!(entry_token.kind == TokenKind::BlockEntry);
276        let content = self.parse_optional_node(false)?;
277        let end = content.as_ref().map_or(entry_token.span.end, |c| c.span().end);
278        Ok(SequenceItem { span: Span::new(entry_token.span.start, end), content })
279    }
280
281    fn parse_block_sequence(&mut self, props: Props) -> ParseResult<Content<'a>> {
282        let start_token = self.next()?; // BlockSequenceStart
283        let mut children = Vec::new_in(&self.allocator);
284
285        loop {
286            match self.peek_kind()? {
287                TokenKind::BlockEnd => {
288                    self.next()?;
289                    break;
290                }
291                TokenKind::BlockEntry => children.push(self.parse_sequence_item()?),
292                _ => {
293                    let span = self.peek()?.span;
294                    return Err(Error::new(ErrorKind::UnexpectedToken("token in sequence"), span));
295                }
296            }
297        }
298
299        let span = container_span(start_token.span, children.first(), children.last());
300        Ok(Content::Sequence(self.alloc(Sequence { span, props, children })))
301    }
302
303    /// Parse an indentless sequence: `BlockEntry` items with no enclosing
304    /// `BlockSequenceStart`/`BlockEnd` (the scanner does not roll an indent
305    /// for a sequence at the same indentation as its parent mapping key).
306    /// Terminates at the first token that is not a `BlockEntry`.
307    fn parse_indentless_sequence(&mut self, props: Props) -> ParseResult<Content<'a>> {
308        let mut children = Vec::new_in(&self.allocator);
309        let first = self.peek()?.span;
310
311        while self.peek_kind()? == TokenKind::BlockEntry {
312            children.push(self.parse_sequence_item()?);
313        }
314
315        let span = container_span(Span::empty(first.start), children.first(), children.last());
316        Ok(Content::Sequence(self.alloc(Sequence { span, props, children })))
317    }
318
319    fn parse_block_mapping(&mut self, props: Props) -> ParseResult<Content<'a>> {
320        let start_token = self.next()?; // BlockMappingStart
321        let mut children = Vec::new_in(&self.allocator);
322
323        loop {
324            match self.peek_kind()? {
325                TokenKind::BlockEnd => {
326                    self.next()?;
327                    break;
328                }
329                TokenKind::Key | TokenKind::Value => {
330                    children.push(self.parse_mapping_item()?);
331                }
332                _ => {
333                    let span = self.peek()?.span;
334                    return Err(Error::new(ErrorKind::UnexpectedToken("token in mapping"), span));
335                }
336            }
337        }
338
339        let span = container_span(start_token.span, children.first(), children.last());
340        Ok(Content::Mapping(self.alloc(Mapping { span, props, children })))
341    }
342
343    /// Parse one `key: value` pair (block or flow; the token structure is the
344    /// same). The cursor must be at a `Key` or `Value` token.
345    fn parse_mapping_item(&mut self) -> ParseResult<MappingItem<'a>> {
346        let key = if let Some(key_token) = self.eat(TokenKind::Key)? {
347            // A real `Key` token is always a literal `?`; a synthesized one is
348            // the scanner's retroactive marker for an implicit `key:`.
349            let explicit = !key_token.synthesized;
350            let content = self.parse_optional_node(true)?;
351            // An explicit key's span starts at the `?` indicator
352            // (mirrors yaml-unist-parser's mappingKey range).
353            let span = match (&content, explicit) {
354                (Some(content), true) => Span::new(key_token.span.start, content.span().end),
355                (Some(content), false) => content.span(),
356                (None, true) => key_token.span,
357                (None, false) => Span::empty(key_token.span.start),
358            };
359            MappingKey { span, content, explicit }
360        } else {
361            // A `Value` with no preceding `Key` (`: value`).
362            let at = self.peek()?.span.start;
363            MappingKey { span: Span::empty(at), content: None, explicit: false }
364        };
365
366        let value = if let Some(value_token) = self.eat(TokenKind::Value)? {
367            let content = self.parse_optional_node(true)?;
368            let span = content.as_ref().map_or(Span::empty(value_token.span.end), Content::span);
369            MappingValue { span, content }
370        } else {
371            // Key with no value (`key:` is Key+Value; a lone key inside a flow
372            // mapping like `{a}` has no Value token).
373            MappingValue { span: Span::empty(key.span.end), content: None }
374        };
375
376        let span = Span::new(key.span.start, value.span.end.max(key.span.end));
377        Ok(MappingItem { span, key, value })
378    }
379
380    fn parse_flow_sequence(&mut self, props: Props) -> ParseResult<Content<'a>> {
381        let start_token = self.next()?; // FlowSequenceStart
382        let mut children = Vec::new_in(&self.allocator);
383
384        loop {
385            match self.peek_kind()? {
386                TokenKind::FlowSequenceEnd => {
387                    let end_token = self.next()?;
388                    let span = Span::new(start_token.span.start, end_token.span.end);
389                    return Ok(Content::FlowSequence(self.alloc(FlowSequence {
390                        span,
391                        props,
392                        children,
393                    })));
394                }
395                TokenKind::FlowEntry => {
396                    self.next()?;
397                }
398                TokenKind::Key | TokenKind::Value => {
399                    // An explicit pair (`[? a: b]`), or an implicit pair for
400                    // which the scanner did not synthesize a `FlowMappingStart`.
401                    let item = self.parse_mapping_item()?;
402                    children.push(FlowSequenceEntry::Pair(self.alloc(item)));
403                }
404                _ => {
405                    // An implicit single pair (`[a: b]`) is surfaced by the
406                    // scanner as a synthesized `FlowMappingStart` wrapper.
407                    let is_synthesized_pair = {
408                        let token = self.peek()?;
409                        token.kind == TokenKind::FlowMappingStart && token.synthesized
410                    };
411                    let content = self.parse_node(false)?;
412                    if is_synthesized_pair {
413                        if let Content::FlowMapping(mapping) = content {
414                            let mut mapping = mapping.unbox();
415                            debug_assert!(mapping.children.len() == 1);
416                            if let Some(item) = mapping.children.pop() {
417                                children.push(FlowSequenceEntry::Pair(self.alloc(item)));
418                            }
419                            continue;
420                        }
421                        unreachable!("synthesized FlowMappingStart must produce a FlowMapping");
422                    }
423                    let span = content.span();
424                    children.push(FlowSequenceEntry::Item(FlowSequenceItem { span, content }));
425                }
426            }
427        }
428    }
429
430    fn parse_flow_mapping(&mut self, props: Props) -> ParseResult<Content<'a>> {
431        let start_token = self.next()?; // FlowMappingStart
432        let mut children = Vec::new_in(&self.allocator);
433
434        loop {
435            match self.peek_kind()? {
436                TokenKind::FlowMappingEnd => {
437                    let end_token = self.next()?;
438                    let span = if end_token.synthesized {
439                        // Synthesized end of an implicit mapping.
440                        container_span(start_token.span, children.first(), children.last())
441                    } else {
442                        Span::new(start_token.span.start, end_token.span.end)
443                    };
444                    return Ok(Content::FlowMapping(self.alloc(FlowMapping {
445                        span,
446                        props,
447                        children,
448                    })));
449                }
450                TokenKind::FlowEntry => {
451                    self.next()?;
452                }
453                TokenKind::Key | TokenKind::Value => {
454                    children.push(self.parse_mapping_item()?);
455                }
456                _ if self.peek_kind()?.starts_node() => {
457                    // A lone node in a flow mapping: `{a}` = `{a: null}`.
458                    let content = self.parse_node(false)?;
459                    let span = content.span();
460                    children.push(MappingItem {
461                        span,
462                        key: MappingKey { span, content: Some(content), explicit: false },
463                        value: MappingValue { span: Span::empty(span.end), content: None },
464                    });
465                }
466                _ => {
467                    let span = self.peek()?.span;
468                    return Err(Error::new(
469                        ErrorKind::UnexpectedToken("token in flow mapping"),
470                        span,
471                    ));
472                }
473            }
474        }
475    }
476}
477
478/// Span of a container from its (possibly empty) start token and its first and
479/// last children (children are in source order).
480fn container_span<T: HasSpan>(start: Span, first: Option<&T>, last: Option<&T>) -> Span {
481    let start_pos = first.map_or(start.start, |c| c.span().start.min(start.start));
482    let end_pos = last.map_or(start.end, |c| c.span().end.max(start.end));
483    Span::new(start_pos, end_pos)
484}
485
486/// Internal helper for [`container_span`].
487trait HasSpan {
488    fn span(&self) -> Span;
489}
490
491impl HasSpan for SequenceItem<'_> {
492    fn span(&self) -> Span {
493        self.span
494    }
495}
496
497impl HasSpan for MappingItem<'_> {
498    fn span(&self) -> Span {
499        self.span
500    }
501}