//! Types and functions that relate to error recovery. //! //! When chumsky encounters an erroneous input that it cannot parse, it can be told to attempt to recover from the //! error using a variety of strategies (you can also create your own strategies). //! //! There is no silver bullet strategy for error recovery. By definition, if the input to a parser is invalid then the //! parser can only make educated guesses as to the meaning of the input. Different recovery strategies will work //! better for different languages, and for different patterns within those languages. //! //! Chumsky provides a variety of recovery strategies (each implementing the `Strategy` trait), but it's important to //! understand that all of //! //! - which you apply //! - where you apply them //! - what order you apply them //! //! will greatly affect the quality of the errors that Chumsky is able to produce, along with the extent to which it //! is able to recover a useful AST. Where possible, you should attempt more 'specific' recovery strategies first //! rather than those that mindlessly skip large swathes of the input. //! //! It is recommended that you experiment with applying different strategies in different situations and at different //! levels of the parser to find a configuration that you are happy with. If none of the provided error recovery //! strategies cover the specific pattern you wish to catch, you can even create your own by digging into Chumsky's //! internals and implementing your own strategies! If you come up with a useful strategy, feel free to open a PR //! against the [main repository](https://github.com/zesterer/chumsky/)! use super::*; /// A trait implemented by error recovery strategies. See [`Parser::recover_with`]. /// /// This trait is sealed and so cannot be implemented by other crates because it has an unstable API. This may /// eventually change. For now, if you wish to implement a new strategy, consider using [`via_parser`] or /// [opening an issue/PR](https://github.com/zesterer/chumsky/issues/new). pub trait Strategy<'src, I: Input<'src>, O, E: ParserExtra<'src, I> = extra::Default>: Sealed { // Attempt to recover from a parsing failure. // The strategy should properly handle the alt error but is not required to handle rewinding. #[doc(hidden)] fn recover>( &self, inp: &mut InputRef<'src, '_, I, E>, parser: &P, ) -> PResult; } /// See [`via_parser`]. #[derive(Copy, Clone)] pub struct ViaParser(A); /// Recover via the given recovery parser. pub fn via_parser(parser: A) -> ViaParser { ViaParser(parser) } impl Sealed for ViaParser {} impl<'src, I, O, E, A> Strategy<'src, I, O, E> for ViaParser where I: Input<'src>, A: Parser<'src, I, O, E>, E: ParserExtra<'src, I>, { fn recover>( &self, inp: &mut InputRef<'src, '_, I, E>, _parser: &P, ) -> PResult { let alt = inp.take_alt().unwrap(); // Can't fail! let out = match self.0.go::(inp) { Ok(out) => out, Err(()) => { inp.errors.alt = Some(alt); return Err(()); } }; inp.emit(alt.err); Ok(out) } } /// See [`Parser::recover_with`]. #[derive(Copy, Clone)] pub struct RecoverWith { pub(crate) parser: A, pub(crate) strategy: S, } impl<'src, I, O, E, A, S> Parser<'src, I, O, E> for RecoverWith where I: Input<'src>, E: ParserExtra<'src, I>, A: Parser<'src, I, O, E>, S: Strategy<'src, I, O, E>, { #[doc(hidden)] #[cfg(feature = "debug")] fn node_info(&self, scope: &mut debug::NodeScope) -> debug::NodeInfo { self.parser.node_info(scope) } fn go(&self, inp: &mut InputRef<'src, '_, I, E>) -> PResult { let before = inp.save(); match self.parser.go::(inp) { Ok(out) => Ok(out), Err(()) => { inp.rewind(before.clone()); match self.strategy.recover::(inp, &self.parser) { Ok(out) => Ok(out), Err(()) => { // Reset to before fallback attempt inp.rewind(before); Err(()) } } } } } go_extra!(O); } /// See [`skip_then_retry_until`]. #[must_use] #[derive(Copy, Clone)] pub struct SkipThenRetryUntil { skip: S, until: U, } impl Sealed for SkipThenRetryUntil {} impl<'src, I, O, E, S, U> Strategy<'src, I, O, E> for SkipThenRetryUntil where I: Input<'src>, S: Parser<'src, I, (), E>, U: Parser<'src, I, (), E>, E: ParserExtra<'src, I>, { fn recover>( &self, inp: &mut InputRef<'src, '_, I, E>, parser: &P, ) -> PResult { let alt = inp.take_alt().unwrap(); // Can't fail! loop { let before = inp.save(); if let Ok(()) = self.until.go::(inp) { inp.errors.alt = Some(alt); inp.rewind(before); break Err(()); } else { inp.rewind(before); } if let Err(()) = self.skip.go::(inp) { inp.errors.alt = Some(alt); break Err(()); } let before = inp.save(); if let Some(out) = parser.go::(inp).ok().filter(|_| { inp.errors .secondary_errors_since(before.err_count) .is_empty() }) { inp.emit(alt.err); break Ok(out); } else { inp.errors.alt.take(); inp.rewind(before); } } } } /// TODO pub fn skip_then_retry_until(skip: S, until: U) -> SkipThenRetryUntil { SkipThenRetryUntil { skip, until } } /// See [`skip_until`]. #[must_use] #[derive(Copy, Clone)] pub struct SkipUntil { skip: S, until: U, fallback: F, } impl Sealed for SkipUntil {} impl<'src, I, O, E, S, U, F> Strategy<'src, I, O, E> for SkipUntil where I: Input<'src>, S: Parser<'src, I, (), E>, U: Parser<'src, I, (), E>, F: Fn() -> O, E: ParserExtra<'src, I>, { fn recover>( &self, inp: &mut InputRef<'src, '_, I, E>, _parser: &P, ) -> PResult { let alt = inp.take_alt().unwrap(); // Can't fail! loop { let before = inp.save(); if let Ok(()) = self.until.go::(inp) { inp.emit(alt.err); break Ok(M::bind(|| (self.fallback)())); } inp.rewind(before); if let Err(()) = self.skip.go::(inp) { inp.errors.alt = Some(alt); break Err(()); } } } } /// A recovery parser that skips input until one of several inputs is found. /// /// This strategy is very 'stupid' and can result in very poor error generation in some languages. Place this strategy /// after others as a last resort, and be careful about over-using it. pub fn skip_until(skip: S, until: U, fallback: F) -> SkipUntil { SkipUntil { skip, until, fallback, } } /// A recovery parser that searches for a start and end delimiter, respecting nesting. /// /// It is possible to specify additional delimiter pairs that are valid in the pattern's context for better errors. For /// example, you might want to also specify `[('[', ']'), ('{', '}')]` when recovering a parenthesized expression as /// this can aid in detecting delimiter mismatches. /// /// A function that generates a fallback output on recovery is also required. // TODO: Make this a strategy, add an unclosed_delimiter error pub fn nested_delimiters<'src, 'parse, I, O, E, F, const N: usize>( start: I::Token, end: I::Token, others: [(I::Token, I::Token); N], fallback: F, ) -> impl Parser<'src, I, O, E> + Clone + 'parse where I: ValueInput<'src>, I::Token: PartialEq + Clone, E: extra::ParserExtra<'src, I> + 'parse, 'src: 'parse, F: Fn(I::Span) -> O + Clone + 'parse, { // TODO: Does this actually work? TESTS! #[allow(clippy::tuple_array_conversions)] // Clippy is overly eager to fine pointless non-problems recursive({ let (start, end) = (start.clone(), end.clone()); |block| { let mut many_block = Parser::boxed( block .clone() .delimited_by(just(start.clone()), just(end.clone())), ); for (s, e) in &others { many_block = Parser::boxed( many_block.or(block.clone().delimited_by(just(s.clone()), just(e.clone()))), ); } let skip = [start, end] .into_iter() .chain(IntoIterator::into_iter(others).flat_map(|(s, e)| [s, e])) .collect::>(); many_block .or(any().and_is(none_of(skip)).ignored()) .repeated() } }) .delimited_by(just(start), just(end)) .map_with(move |_, e| fallback(e.span())) }