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 { children, comments, span: Span::new(0, source_len) })
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()?)) } 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            directives,
121            span: Span::new(head_start, directives_end_marker.map_or(head_end, |s| s.end)),
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 { content, span: body_span };
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            head,
152            body,
153            directives_end_marker,
154            document_end_marker,
155            span: Span::new(head_start, span_end),
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 { name, parameters, span: token.span }
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    fn parse_node(&mut self) -> ParseResult<Content<'a>> {
194        let props = self.parse_props()?;
195
196        let token = *self.peek()?;
197        match token.kind {
198            TokenKind::Alias => {
199                self.next()?;
200                if props.anchor.is_some() || props.tag.is_some() {
201                    return Err(Error::new(ErrorKind::DuplicatedNodeProperty, token.span));
202                }
203                Ok(Content::Alias(self.alloc(Alias { props, span: token.span })))
204            }
205            TokenKind::Scalar(style, header_index) => {
206                self.next()?;
207                Ok(self.build_scalar(props, style, header_index, token.span))
208            }
209            TokenKind::FlowSequenceStart => self.parse_flow_sequence(props),
210            TokenKind::FlowMappingStart => self.parse_flow_mapping(props),
211            TokenKind::BlockSequenceStart => self.parse_block_sequence(props),
212            TokenKind::BlockMappingStart => self.parse_block_mapping(props),
213            // A `BlockEntry` with no preceding `BlockSequenceStart` is an
214            // indentless sequence (a sequence at the same indentation as its
215            // parent mapping key: `key:\n- a`).
216            TokenKind::BlockEntry => self.parse_indentless_sequence(props),
217            _ => {
218                // Properties with no following content (e.g. `!!str : v`):
219                // synthesize an empty plain scalar carrying the properties.
220                if props.anchor.is_some() || props.tag.is_some() {
221                    let at = props
222                        .anchor
223                        .map(|a| a.span.end)
224                        .max(props.tag.map(|t| t.span.end))
225                        .unwrap();
226                    return Ok(Content::Plain(self.alloc(Plain { props, span: Span::empty(at) })));
227                }
228                Err(Error::new(ErrorKind::ExpectedNode, token.span))
229            }
230        }
231    }
232
233    fn build_scalar(
234        &self,
235        props: Props,
236        style: ScalarStyle,
237        header_index: Option<crate::scanner::BlockHeaderIndex>,
238        span: Span,
239    ) -> Content<'a> {
240        match style {
241            ScalarStyle::Plain => Content::Plain(self.alloc(Plain { props, span })),
242            ScalarStyle::SingleQuoted => {
243                Content::QuoteSingle(self.alloc(QuoteSingle { props, span }))
244            }
245            ScalarStyle::DoubleQuoted => {
246                Content::QuoteDouble(self.alloc(QuoteDouble { props, span }))
247            }
248            ScalarStyle::Literal | ScalarStyle::Folded => {
249                let index = header_index.expect("block scalar token must carry a header index");
250                let header = self.scanner.block_headers[index.get()];
251                let node = BlockScalar {
252                    props,
253                    chomping: header.chomping,
254                    indent: header.indent,
255                    content_start: header.content_start,
256                    span,
257                };
258                if style == ScalarStyle::Literal {
259                    Content::BlockLiteral(self.alloc(node))
260                } else {
261                    Content::BlockFolded(self.alloc(node))
262                }
263            }
264        }
265    }
266
267    /// Parse one `- item`. The cursor must be at a `BlockEntry` token.
268    fn parse_sequence_item(&mut self) -> ParseResult<SequenceItem<'a>> {
269        let entry_token = self.next()?;
270        debug_assert!(entry_token.kind == TokenKind::BlockEntry);
271        let content = self.parse_optional_node(false)?;
272        let end = content.as_ref().map_or(entry_token.span.end, |c| c.span().end);
273        Ok(SequenceItem { content, span: Span::new(entry_token.span.start, end) })
274    }
275
276    fn parse_block_sequence(&mut self, props: Props) -> ParseResult<Content<'a>> {
277        let start_token = self.next()?; // BlockSequenceStart
278        let mut children = Vec::new_in(&self.allocator);
279
280        loop {
281            match self.peek_kind()? {
282                TokenKind::BlockEnd => {
283                    self.next()?;
284                    break;
285                }
286                TokenKind::BlockEntry => children.push(self.parse_sequence_item()?),
287                _ => {
288                    let span = self.peek()?.span;
289                    return Err(Error::new(ErrorKind::UnexpectedToken("token in sequence"), span));
290                }
291            }
292        }
293
294        let span = container_span(start_token.span, children.first(), children.last());
295        Ok(Content::Sequence(self.alloc(Sequence { props, children, span })))
296    }
297
298    /// Parse an indentless sequence: `BlockEntry` items with no enclosing
299    /// `BlockSequenceStart`/`BlockEnd` (the scanner does not roll an indent
300    /// for a sequence at the same indentation as its parent mapping key).
301    /// Terminates at the first token that is not a `BlockEntry`.
302    fn parse_indentless_sequence(&mut self, props: Props) -> ParseResult<Content<'a>> {
303        let mut children = Vec::new_in(&self.allocator);
304        let first = self.peek()?.span;
305
306        while self.peek_kind()? == TokenKind::BlockEntry {
307            children.push(self.parse_sequence_item()?);
308        }
309
310        let span = container_span(Span::empty(first.start), children.first(), children.last());
311        Ok(Content::Sequence(self.alloc(Sequence { props, children, span })))
312    }
313
314    fn parse_block_mapping(&mut self, props: Props) -> ParseResult<Content<'a>> {
315        let start_token = self.next()?; // BlockMappingStart
316        let mut children = Vec::new_in(&self.allocator);
317
318        loop {
319            match self.peek_kind()? {
320                TokenKind::BlockEnd => {
321                    self.next()?;
322                    break;
323                }
324                TokenKind::Key | TokenKind::Value => {
325                    children.push(self.parse_mapping_item()?);
326                }
327                _ => {
328                    let span = self.peek()?.span;
329                    return Err(Error::new(ErrorKind::UnexpectedToken("token in mapping"), span));
330                }
331            }
332        }
333
334        let span = container_span(start_token.span, children.first(), children.last());
335        Ok(Content::Mapping(self.alloc(Mapping { props, children, span })))
336    }
337
338    /// Parse one `key: value` pair (block or flow; the token structure is the
339    /// same). The cursor must be at a `Key` or `Value` token.
340    fn parse_mapping_item(&mut self) -> ParseResult<MappingItem<'a>> {
341        let key = if let Some(key_token) = self.eat(TokenKind::Key)? {
342            // A real `Key` token is always a literal `?`; a synthesized one is
343            // the scanner's retroactive marker for an implicit `key:`.
344            let explicit = !key_token.synthesized;
345            let content = self.parse_optional_node(true)?;
346            let span = content.as_ref().map_or(Span::empty(key_token.span.start), Content::span);
347            MappingKey { content, explicit, span }
348        } else {
349            // A `Value` with no preceding `Key` (`: value`).
350            let at = self.peek()?.span.start;
351            MappingKey { content: None, explicit: false, span: Span::empty(at) }
352        };
353
354        let value = if let Some(value_token) = self.eat(TokenKind::Value)? {
355            let content = self.parse_optional_node(true)?;
356            let span = content.as_ref().map_or(Span::empty(value_token.span.end), Content::span);
357            MappingValue { content, span }
358        } else {
359            // Key with no value (`key:` is Key+Value; a lone key inside a flow
360            // mapping like `{a}` has no Value token).
361            MappingValue { content: None, span: Span::empty(key.span.end) }
362        };
363
364        let span = Span::new(key.span.start, value.span.end.max(key.span.end));
365        Ok(MappingItem { key, value, span })
366    }
367
368    fn parse_flow_sequence(&mut self, props: Props) -> ParseResult<Content<'a>> {
369        let start_token = self.next()?; // FlowSequenceStart
370        let mut children = Vec::new_in(&self.allocator);
371
372        loop {
373            match self.peek_kind()? {
374                TokenKind::FlowSequenceEnd => {
375                    let end_token = self.next()?;
376                    let span = Span::new(start_token.span.start, end_token.span.end);
377                    return Ok(Content::FlowSequence(self.alloc(FlowSequence {
378                        props,
379                        children,
380                        span,
381                    })));
382                }
383                TokenKind::FlowEntry => {
384                    self.next()?;
385                }
386                TokenKind::Key | TokenKind::Value => {
387                    // An explicit pair (`[? a: b]`), or an implicit pair for
388                    // which the scanner did not synthesize a `FlowMappingStart`.
389                    let item = self.parse_mapping_item()?;
390                    children.push(FlowSequenceEntry::Pair(self.alloc(item)));
391                }
392                _ => {
393                    // An implicit single pair (`[a: b]`) is surfaced by the
394                    // scanner as a synthesized `FlowMappingStart` wrapper.
395                    let is_synthesized_pair = {
396                        let token = self.peek()?;
397                        token.kind == TokenKind::FlowMappingStart && token.synthesized
398                    };
399                    let content = self.parse_node()?;
400                    if is_synthesized_pair {
401                        if let Content::FlowMapping(mapping) = content {
402                            let mut mapping = mapping.unbox();
403                            debug_assert!(mapping.children.len() == 1);
404                            if let Some(item) = mapping.children.pop() {
405                                children.push(FlowSequenceEntry::Pair(self.alloc(item)));
406                            }
407                            continue;
408                        }
409                        unreachable!("synthesized FlowMappingStart must produce a FlowMapping");
410                    }
411                    let span = content.span();
412                    children.push(FlowSequenceEntry::Item(FlowSequenceItem { content, span }));
413                }
414            }
415        }
416    }
417
418    fn parse_flow_mapping(&mut self, props: Props) -> ParseResult<Content<'a>> {
419        let start_token = self.next()?; // FlowMappingStart
420        let mut children = Vec::new_in(&self.allocator);
421
422        loop {
423            match self.peek_kind()? {
424                TokenKind::FlowMappingEnd => {
425                    let end_token = self.next()?;
426                    let span = if end_token.synthesized {
427                        // Synthesized end of an implicit mapping.
428                        container_span(start_token.span, children.first(), children.last())
429                    } else {
430                        Span::new(start_token.span.start, end_token.span.end)
431                    };
432                    return Ok(Content::FlowMapping(self.alloc(FlowMapping {
433                        props,
434                        children,
435                        span,
436                    })));
437                }
438                TokenKind::FlowEntry => {
439                    self.next()?;
440                }
441                TokenKind::Key | TokenKind::Value => {
442                    children.push(self.parse_mapping_item()?);
443                }
444                _ if self.peek_kind()?.starts_node() => {
445                    // A lone node in a flow mapping: `{a}` = `{a: null}`.
446                    let content = self.parse_node()?;
447                    let span = content.span();
448                    children.push(MappingItem {
449                        key: MappingKey { content: Some(content), explicit: false, span },
450                        value: MappingValue { content: None, span: Span::empty(span.end) },
451                        span,
452                    });
453                }
454                _ => {
455                    let span = self.peek()?.span;
456                    return Err(Error::new(
457                        ErrorKind::UnexpectedToken("token in flow mapping"),
458                        span,
459                    ));
460                }
461            }
462        }
463    }
464}
465
466/// Span of a container from its (possibly empty) start token and its first and
467/// last children (children are in source order).
468fn container_span<T: HasSpan>(start: Span, first: Option<&T>, last: Option<&T>) -> Span {
469    let start_pos = first.map_or(start.start, |c| c.span().start.min(start.start));
470    let end_pos = last.map_or(start.end, |c| c.span().end.max(start.end));
471    Span::new(start_pos, end_pos)
472}
473
474/// Internal helper for [`container_span`].
475trait HasSpan {
476    fn span(&self) -> Span;
477}
478
479impl HasSpan for SequenceItem<'_> {
480    fn span(&self) -> Span {
481        self.span
482    }
483}
484
485impl HasSpan for MappingItem<'_> {
486    fn span(&self) -> Span {
487        self.span
488    }
489}