Skip to main content

granit_parser/
parser_stack.rs

1use crate::{
2    error::{ErrorKind, ScanError},
3    input::{str::StrInput, BorrowedInput, BufferedInput},
4    parser::{Event, ParseResult, Parser, ParserTrait, SpannedEventReceiver},
5    scanner::Span,
6    Options,
7};
8use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec};
9
10/// A lightweight parser that replays a pre-collected event stream.
11pub struct ReplayParser<'input> {
12    events: alloc::vec::IntoIter<(Event<'input>, Span)>,
13    anchor_offset: usize,
14}
15
16impl<'input> ReplayParser<'input> {
17    /// Create a parser that replays `events` and starts anchor allocation at `anchor_offset`.
18    #[must_use]
19    pub fn new(events: Vec<(Event<'input>, Span)>, anchor_offset: usize) -> Self {
20        Self {
21            events: events.into_iter(),
22            anchor_offset,
23        }
24    }
25
26    /// Return the next anchor ID that should be assigned after replayed events.
27    #[must_use]
28    pub fn anchor_offset(&self) -> usize {
29        self.anchor_offset
30    }
31
32    /// Set the next anchor ID that should be assigned after replayed events.
33    pub fn set_anchor_offset(&mut self, offset: usize) {
34        self.anchor_offset = offset;
35    }
36
37    fn advance_anchor_offset(&mut self, event: &Event<'input>) {
38        if let Some(anchor_id) = event.anchor_id() {
39            self.anchor_offset = self.anchor_offset.max(anchor_id.saturating_add(1));
40        }
41    }
42}
43
44impl<'input> ParserTrait<'input> for ReplayParser<'input> {
45    fn peek(&mut self) -> Option<Result<&(Event<'input>, Span), ScanError>> {
46        self.events.as_slice().first().map(Ok)
47    }
48
49    fn next_event(&mut self) -> Option<ParseResult<'input>> {
50        let event = self.events.next()?;
51        self.advance_anchor_offset(&event.0);
52        Some(Ok(event))
53    }
54
55    fn load<R: SpannedEventReceiver<'input>>(
56        &mut self,
57        recv: &mut R,
58        multi: bool,
59    ) -> Result<(), ScanError> {
60        while let Some(res) = self.next_event() {
61            let (ev, span) = res?;
62            let is_doc_end = matches!(ev, Event::DocumentEnd);
63            let is_stream_end = matches!(ev, Event::StreamEnd);
64            recv.on_event(ev, span);
65            if is_stream_end {
66                break;
67            }
68            if !multi && is_doc_end {
69                break;
70            }
71        }
72        Ok(())
73    }
74}
75
76impl<'input> Iterator for ReplayParser<'input> {
77    type Item = ParseResult<'input>;
78
79    fn next(&mut self) -> Option<Self::Item> {
80        self.next_event()
81    }
82}
83
84impl core::iter::FusedIterator for ReplayParser<'_> {}
85
86/// A wrapper for different types of parsers.
87enum AnyParser<'input, I, T>
88where
89    I: Iterator<Item = char>,
90    T: BorrowedInput<'input>,
91{
92    /// A parser over borrowed string input.
93    String {
94        /// Parser currently producing events for this stack entry.
95        parser: Parser<'input, StrInput<'input>>,
96        /// Human-readable source name returned by [`ParserStack::stack`].
97        name: String,
98    },
99    /// A parser over an iterator of characters.
100    Iter {
101        /// Parser currently producing events for this stack entry.
102        parser: Parser<'static, BufferedInput<I>>,
103        /// Human-readable source name returned by [`ParserStack::stack`].
104        name: String,
105    },
106    /// A parser over a custom input.
107    Custom {
108        /// Parser currently producing events for this stack entry.
109        parser: Parser<'input, T>,
110        /// Human-readable source name returned by [`ParserStack::stack`].
111        name: String,
112    },
113    /// A parser over a replayed event stream.
114    Replay {
115        /// Replay parser currently producing pre-collected events for this stack entry.
116        parser: ReplayParser<'input>,
117        /// Human-readable source name returned by [`ParserStack::stack`].
118        name: String,
119    },
120}
121
122impl<'input, I, T> AnyParser<'input, I, T>
123where
124    I: Iterator<Item = char>,
125    T: BorrowedInput<'input>,
126{
127    fn anchor_offset(&self) -> usize {
128        match self {
129            AnyParser::String { parser, .. } => parser.anchor_offset(),
130            AnyParser::Iter { parser, .. } => parser.anchor_offset(),
131            AnyParser::Custom { parser, .. } => parser.anchor_offset(),
132            AnyParser::Replay { parser, .. } => parser.anchor_offset(),
133        }
134    }
135
136    fn set_anchor_offset(&mut self, offset: usize) {
137        match self {
138            AnyParser::String { parser, .. } => parser.set_anchor_offset(offset),
139            AnyParser::Iter { parser, .. } => parser.set_anchor_offset(offset),
140            AnyParser::Custom { parser, .. } => parser.set_anchor_offset(offset),
141            AnyParser::Replay { parser, .. } => parser.set_anchor_offset(offset),
142        }
143    }
144}
145
146/// A parser implementation that uses a stack for include-style parsing.
147///
148/// Note: `ParserStack` deliberately suppresses nested [`Event::StreamStart`] /
149/// [`Event::DocumentStart`] events when more than one parser is stacked, and the tests assert
150/// outputs where a nested parser starts directly with [`Event::MappingStart`] before the parent
151/// stream/document wrapper appears.
152///
153/// That is exactly what we want for `!include`-style subtree injection.
154///
155/// Included parser events are replayed through the same event stream as parent events. With the
156/// `comments` Cargo feature, this includes comment events unless emission is disabled through
157/// [`ParserStack::with_options`]. Without the feature, comment events and their continuation state
158/// are absent.
159///
160/// Anchor metadata is passed through from parsers that enable it. Each [`Parser`] emits
161/// [`Event::Anchor`] immediately after its anchored node. Pushing another parser between
162/// consuming that node and consuming its metadata can insert events between them in the
163/// combined stream. If relying on adjacency, consume any pending anchor metadata before
164/// calling [`Self::push_include`] or another push method.
165///
166/// Included [`Span`] values remain local to the included source, just like every other event span
167/// from an included parser. `ParserStack` does not attach file names, source IDs, or other include
168/// provenance to events or spans. Errors do retain the nested source names through
169/// [`ScanError::source_stack`].
170pub struct ParserStack<'input, I = core::iter::Empty<char>, T = StrInput<'input>>
171where
172    I: Iterator<Item = char>,
173    T: BorrowedInput<'input>,
174{
175    options: Options,
176    parsers: Vec<AnyParser<'input, I, T>>,
177    /// Stack depths and end spans of nested documents whose trailing comments are being read.
178    #[cfg(feature = "comments")]
179    pending_document_ends: Vec<(usize, Span)>,
180    current: Option<(Event<'input>, Span)>,
181    current_error: Option<ScanError>,
182    stream_end_emitted: bool,
183    #[allow(clippy::type_complexity)]
184    include_resolver: Option<Box<dyn FnMut(&str) -> Result<Cow<'input, str>, ScanError> + 'input>>,
185}
186
187impl<'input, I, T> ParserStack<'input, I, T>
188where
189    I: Iterator<Item = char>,
190    T: BorrowedInput<'input>,
191{
192    /// Creates a new, empty parser stack.
193    #[must_use]
194    pub fn new() -> Self {
195        Self::with_options(Options::default())
196    }
197
198    /// Creates a new, empty parser stack with the supplied parsing options.
199    ///
200    /// Options are applied to sources parsed internally by [`Self::push_include`]. Caller-supplied
201    /// parsers retain their own parsing options, including [`Options::emit_unresolved_aliases`]
202    /// and [`Options::emit_anchor_events`]. Unresolved alias and anchor metadata events from
203    /// caller-supplied parsers and replay streams are passed through.
204    /// With the `comments` Cargo feature, disabling comment emission also suppresses comment
205    /// events from parsers and replay streams supplied by the caller. Construct caller-supplied
206    /// parsers with emission disabled as well to avoid capturing comments before filtering them.
207    /// Without the feature, comment events are absent and [`Options::emit_comments`] has no effect.
208    #[must_use]
209    pub fn with_options(options: Options) -> Self {
210        Self {
211            options,
212            parsers: Vec::new(),
213            #[cfg(feature = "comments")]
214            pending_document_ends: Vec::new(),
215            current: None,
216            current_error: None,
217            stream_end_emitted: false,
218            include_resolver: None,
219        }
220    }
221
222    /// Set the resolver used by [`Self::push_include`].
223    ///
224    /// The resolver receives the include name and returns the included YAML source text.
225    pub fn set_resolver(
226        &mut self,
227        mut resolver: impl FnMut(&str) -> Result<String, ScanError> + 'input,
228    ) {
229        self.include_resolver = Some(Box::new(move |name| resolver(name).map(Cow::Owned)));
230    }
231
232    /// Set an include resolver whose source text can be borrowed for the stack's input lifetime.
233    ///
234    /// Unlike [`Self::set_resolver`], this path lets scalar, anchor, and tag token text in included
235    /// documents borrow directly from the returned source, as well as comment text when the
236    /// `comments` Cargo feature is enabled. The included document is still validated eagerly so
237    /// resolution errors retain the same timing and source-stack context.
238    pub fn set_borrowed_resolver(
239        &mut self,
240        mut resolver: impl FnMut(&str) -> Result<&'input str, ScanError> + 'input,
241    ) {
242        self.include_resolver = Some(Box::new(move |name| resolver(name).map(Cow::Borrowed)));
243    }
244
245    /// Resolve an include by name and push the resulting parser onto the stack.
246    ///
247    /// With the `comments` Cargo feature, comment events from the included content follow the
248    /// setting passed to [`Self::with_options`]. Anchor metadata events likewise follow
249    /// [`Options::emit_anchor_events`]. All event spans are local to the included content returned
250    /// by the resolver.
251    ///
252    /// # Errors
253    /// Returns `ScanError` if no resolver is configured, include resolution fails, or the
254    /// included content cannot be parsed.
255    pub fn push_include(&mut self, include_str: &str) -> Result<(), ScanError> {
256        let resolved = match &mut self.include_resolver {
257            Some(resolver) => resolver(include_str),
258            None => {
259                return Err(self.contextualize_include_error(
260                    ScanError::from_kind(
261                        crate::scanner::Marker::new(0, 1, 0),
262                        ErrorKind::MissingIncludeResolver,
263                    ),
264                    include_str,
265                ));
266            }
267        };
268        let content = match resolved {
269            Ok(content) => content,
270            Err(error) => return Err(self.contextualize_include_error(error, include_str)),
271        };
272        let inherited_anchor_offset = self.parsers.last().map(AnyParser::anchor_offset);
273
274        let (events, next_anchor_offset) = match content {
275            Cow::Borrowed(content) => {
276                let mut parser = Parser::new_from_str_with_options(content, self.options.clone());
277                if let Some(anchor_offset) = inherited_anchor_offset {
278                    parser.set_anchor_offset(anchor_offset);
279                }
280                let mut events = Vec::new();
281                while let Some(event) = parser.next_event() {
282                    match event {
283                        Ok(event) => events.push(event),
284                        Err(error) => {
285                            return Err(self.contextualize_include_error(error, include_str));
286                        }
287                    }
288                }
289                (events, parser.anchor_offset())
290            }
291            Cow::Owned(content) => {
292                let mut parser =
293                    Parser::new_from_iter_with_options(content.chars(), self.options.clone());
294                if let Some(anchor_offset) = inherited_anchor_offset {
295                    parser.set_anchor_offset(anchor_offset);
296                }
297                let mut events = Vec::new();
298                while let Some(event) = parser.next_event() {
299                    match event {
300                        Ok(event) => events.push(event),
301                        Err(error) => {
302                            return Err(self.contextualize_include_error(error, include_str));
303                        }
304                    }
305                }
306                (events, parser.anchor_offset())
307            }
308        };
309
310        self.push_replay_parser(
311            ReplayParser::new(events, next_anchor_offset),
312            include_str.into(),
313        );
314        Ok(())
315    }
316
317    fn contextualize_include_error(&self, error: ScanError, include_str: &str) -> ScanError {
318        let mut source_stack = self.stack();
319        source_stack.push(include_str.into());
320        error.with_source_stack(source_stack)
321    }
322
323    fn prepare_for_push(&mut self) {
324        if matches!(self.current.as_ref(), Some((Event::StreamEnd, _))) {
325            self.current = None;
326        }
327    }
328
329    /// Push a string parser onto the stack.
330    ///
331    /// The pushed parser inherits the current anchor offset so anchors remain unique across stacked
332    /// sources. `name` is returned by [`Self::stack`] for diagnostics.
333    pub fn push_str_parser(&mut self, mut parser: Parser<'input, StrInput<'input>>, name: String) {
334        self.prepare_for_push();
335        if let Some(parent) = self.parsers.last() {
336            parser.set_anchor_offset(parent.anchor_offset());
337        }
338        self.parsers.push(AnyParser::String { parser, name });
339    }
340
341    /// Push an iterator-backed parser onto the stack.
342    ///
343    /// The pushed parser inherits the current anchor offset so anchors remain unique across stacked
344    /// sources. `name` is returned by [`Self::stack`] for diagnostics.
345    pub fn push_iter_parser(
346        &mut self,
347        mut parser: Parser<'static, BufferedInput<I>>,
348        name: String,
349    ) {
350        self.prepare_for_push();
351        if let Some(parent) = self.parsers.last() {
352            parser.set_anchor_offset(parent.anchor_offset());
353        }
354        self.parsers.push(AnyParser::Iter { parser, name });
355    }
356
357    /// Push a custom-input parser onto the stack.
358    ///
359    /// The pushed parser inherits the current anchor offset so anchors remain unique across stacked
360    /// sources. `name` is returned by [`Self::stack`] for diagnostics.
361    pub fn push_custom_parser(&mut self, mut parser: Parser<'input, T>, name: String) {
362        self.prepare_for_push();
363        if let Some(parent) = self.parsers.last() {
364            parser.set_anchor_offset(parent.anchor_offset());
365        }
366        self.parsers.push(AnyParser::Custom { parser, name });
367    }
368
369    /// Push a replay parser onto the stack.
370    ///
371    /// Replay parsers are used for included content that has already been parsed into events.
372    /// `name` is returned by [`Self::stack`] for diagnostics.
373    pub fn push_replay_parser(&mut self, mut parser: ReplayParser<'input>, name: String) {
374        self.prepare_for_push();
375        if let Some(parent) = self.parsers.last() {
376            let inherited = parent.anchor_offset();
377            parser.set_anchor_offset(parser.anchor_offset().max(inherited));
378        }
379
380        self.parsers.push(AnyParser::Replay { parser, name });
381    }
382
383    /// Push a custom parser and set the first event that should be returned from it.
384    ///
385    /// This is used when the caller has already consumed the parser's first event before deciding
386    /// to place it on the stack.
387    pub fn push_custom_parser_with_current(
388        &mut self,
389        mut parser: Parser<'input, T>,
390        name: String,
391        current: (Event<'input>, Span),
392    ) {
393        self.prepare_for_push();
394        if let Some(parent) = self.parsers.last() {
395            parser.set_anchor_offset(parent.anchor_offset());
396        }
397        self.parsers.push(AnyParser::Custom { parser, name });
398        #[cfg(feature = "comments")]
399        {
400            self.current = if self.options.emit_comments || !matches!(current.0, Event::Comment(..))
401            {
402                Some(current)
403            } else {
404                None
405            };
406        }
407        #[cfg(not(feature = "comments"))]
408        {
409            self.current = Some(current);
410        }
411    }
412
413    /// Return the anchor offset that a newly pushed parser should inherit.
414    #[must_use]
415    pub fn current_anchor_offset(&self) -> usize {
416        self.parsers.last().map_or(0, AnyParser::anchor_offset)
417    }
418
419    /// Return the names of the parsers currently in the stack, from bottom to top.
420    #[must_use]
421    pub fn stack(&self) -> Vec<String> {
422        self.parsers
423            .iter()
424            .map(|p| match p {
425                AnyParser::String { name, .. }
426                | AnyParser::Iter { name, .. }
427                | AnyParser::Custom { name, .. }
428                | AnyParser::Replay { name, .. } => name.clone(),
429            })
430            .collect()
431    }
432
433    fn contextualize_error(&self, error: ScanError) -> ScanError {
434        if self.parsers.len() > 1 {
435            error.with_source_stack(self.stack())
436        } else {
437            error
438        }
439    }
440
441    fn propagate_anchor_offset_from_popped(&mut self, popped: &AnyParser<'input, I, T>) {
442        if let Some(parent) = self.parsers.last_mut() {
443            let next_offset = parent.anchor_offset().max(popped.anchor_offset());
444            parent.set_anchor_offset(next_offset);
445        }
446    }
447
448    /// Pop the current parser and propagate its anchor offset to its parent.
449    ///
450    /// # Panics
451    /// Panics if the parser stack is empty.
452    #[track_caller]
453    fn pop_parser_and_propagate_anchor_offset(&mut self) {
454        #[cfg(feature = "comments")]
455        if self
456            .pending_document_ends
457            .last()
458            .is_some_and(|(depth, _)| *depth == self.parsers.len())
459        {
460            self.pending_document_ends.pop();
461        }
462        let popped = self.parsers.pop().unwrap();
463        self.propagate_anchor_offset_from_popped(&popped);
464    }
465
466    fn next_event_impl(&mut self) -> Result<(Event<'input>, Span), ScanError> {
467        loop {
468            let Some(any_parser) = self.parsers.last_mut() else {
469                return Ok((
470                    Event::StreamEnd,
471                    Span::empty(crate::scanner::Marker::new(0, 1, 0)),
472                ));
473            };
474
475            let res = match any_parser {
476                AnyParser::String { parser, .. } => parser.next_event(),
477                AnyParser::Iter { parser, .. } => parser.next_event(),
478                AnyParser::Custom { parser, .. } => parser.next_event(),
479                AnyParser::Replay { parser, .. } => parser.next_event(),
480            };
481
482            #[cfg(feature = "comments")]
483            if let Some(&(depth, span)) = self.pending_document_ends.last() {
484                if depth == self.parsers.len()
485                    && matches!(&res, Some(Ok((event, _)))
486                        if !matches!(event, Event::Comment(..) | Event::StreamEnd))
487                {
488                    let error = self.contextualize_error(ScanError::from_kind(
489                        span.start,
490                        ErrorKind::MultipleDocumentsUnsupported,
491                    ));
492                    self.pop_parser_and_propagate_anchor_offset();
493                    return Err(error);
494                }
495            }
496
497            match res {
498                Some(Ok((Event::StreamEnd, span))) => {
499                    if self.parsers.len() == 1 {
500                        self.parsers.pop();
501                        return Ok((Event::StreamEnd, span));
502                    }
503                    self.pop_parser_and_propagate_anchor_offset();
504                }
505                None => {
506                    if self.parsers.len() == 1 {
507                        self.parsers.pop();
508                        return Ok((
509                            Event::StreamEnd,
510                            Span::empty(crate::scanner::Marker::new(0, 1, 0)),
511                        ));
512                    }
513                    self.pop_parser_and_propagate_anchor_offset();
514                }
515                Some(Err(e)) => {
516                    let e = self.contextualize_error(e);
517                    self.pop_parser_and_propagate_anchor_offset();
518                    return e.into_result();
519                }
520                Some(Ok((Event::DocumentEnd, span))) => {
521                    if self.parsers.len() == 1 {
522                        return Ok((Event::DocumentEnd, span));
523                    }
524
525                    #[cfg(feature = "comments")]
526                    {
527                        // Retain the end span while emitting trailing comments across calls and
528                        // pushes of other parsers.
529                        self.pending_document_ends.push((self.parsers.len(), span));
530                    }
531                    #[cfg(not(feature = "comments"))]
532                    {
533                        // Without comment events, completion can be checked immediately without
534                        // retaining a pending end span or allocating continuation state.
535                        let next = match self.parsers.last_mut().unwrap() {
536                            AnyParser::String { parser, .. } => parser.next_event(),
537                            AnyParser::Iter { parser, .. } => parser.next_event(),
538                            AnyParser::Custom { parser, .. } => parser.next_event(),
539                            AnyParser::Replay { parser, .. } => parser.next_event(),
540                        };
541                        match next {
542                            Some(Ok((Event::StreamEnd, _))) | None => {
543                                self.pop_parser_and_propagate_anchor_offset();
544                            }
545                            Some(Err(error)) => {
546                                let error = self.contextualize_error(error);
547                                self.pop_parser_and_propagate_anchor_offset();
548                                return Err(error);
549                            }
550                            Some(Ok(_)) => {
551                                let error = self.contextualize_error(ScanError::from_kind(
552                                    span.start,
553                                    ErrorKind::MultipleDocumentsUnsupported,
554                                ));
555                                self.pop_parser_and_propagate_anchor_offset();
556                                return Err(error);
557                            }
558                        }
559                    }
560                }
561                Some(Ok(event)) => {
562                    #[cfg(feature = "comments")]
563                    if !self.options.emit_comments && matches!(event.0, Event::Comment(..)) {
564                        continue;
565                    }
566                    if self.parsers.len() > 1
567                        && matches!(event.0, Event::StreamStart | Event::DocumentStart(..))
568                    {
569                        continue;
570                    }
571                    return Ok(event);
572                }
573            }
574        }
575    }
576}
577
578impl<'input, I, T> Default for ParserStack<'input, I, T>
579where
580    I: Iterator<Item = char>,
581    T: BorrowedInput<'input>,
582{
583    fn default() -> Self {
584        Self::new()
585    }
586}
587
588impl<'input, I, T> ParserTrait<'input> for ParserStack<'input, I, T>
589where
590    I: Iterator<Item = char>,
591    T: BorrowedInput<'input>,
592{
593    fn peek(&mut self) -> Option<Result<&(Event<'input>, Span), ScanError>> {
594        if let Some(ref x) = self.current {
595            Some(Ok(x))
596        } else if let Some(error) = &self.current_error {
597            Some(Err(error.clone()))
598        } else {
599            if self.stream_end_emitted {
600                return None;
601            }
602            match self.next_event_impl() {
603                Ok(token) => {
604                    self.current = Some(token);
605                    Some(Ok(self.current.as_ref().unwrap()))
606                }
607                Err(e) => {
608                    self.current_error = Some(e.clone());
609                    Some(Err(e))
610                }
611            }
612        }
613    }
614
615    fn next_event(&mut self) -> Option<ParseResult<'input>> {
616        if let Some(error) = self.current_error.take() {
617            self.stream_end_emitted = true;
618            return Some(Err(error));
619        }
620
621        if let Some(token) = self.current.take() {
622            if let Event::StreamEnd = token.0 {
623                self.stream_end_emitted = true;
624            }
625            return Some(Ok(token));
626        }
627        if self.stream_end_emitted {
628            return None;
629        }
630        match self.next_event_impl() {
631            Ok(token) => {
632                if let Event::StreamEnd = token.0 {
633                    self.stream_end_emitted = true;
634                }
635                Some(Ok(token))
636            }
637            Err(e) => {
638                self.stream_end_emitted = true;
639                Some(Err(e))
640            }
641        }
642    }
643
644    fn load<R: SpannedEventReceiver<'input>>(
645        &mut self,
646        recv: &mut R,
647        multi: bool,
648    ) -> Result<(), ScanError> {
649        while let Some(res) = self.next_event() {
650            // Fetch the next event from the active stack entry.
651            let (ev, span) = res?;
652
653            // Track whether to stop based on `multi`.
654            let is_doc_end = matches!(ev, Event::DocumentEnd);
655            let is_stream_end = matches!(ev, Event::StreamEnd);
656
657            recv.on_event(ev, span);
658
659            if is_stream_end {
660                break;
661            }
662
663            // Stop after one document when multi-document parsing is disabled.
664            if !multi && is_doc_end {
665                break;
666            }
667        }
668
669        Ok(())
670    }
671}
672
673impl<'input, I, T> Iterator for ParserStack<'input, I, T>
674where
675    I: Iterator<Item = char>,
676    T: BorrowedInput<'input>,
677{
678    type Item = Result<(Event<'input>, Span), ScanError>;
679
680    fn next(&mut self) -> Option<Self::Item> {
681        self.next_event()
682    }
683}
684
685impl<'input, I, T> core::iter::FusedIterator for ParserStack<'input, I, T>
686where
687    I: Iterator<Item = char>,
688    T: BorrowedInput<'input>,
689{
690}