chumsky icon indicating copy to clipboard operation
chumsky copied to clipboard

Recovering from missing input at eof

Open ProphetLamb opened this issue 1 year ago • 3 comments

I am having trouble recovering from the top-level nesting error that occurs when missing brackets. Here is a simplified nano rust example:

fn main() -> i32 {
  0

As you can clearly see, the closing bracket is missing. Using the nested_delimiters recovery method, I can propagate an arbitrary nesting error to the top level, but I am unable to handle this top-level error.

To solve this, I propose adding a recovery which creates a virtual stream from the faulty span amended with a constant expression. If the amended stream succeeds, the recovery returns the result; otherwise it returns the original result.

The code below displays a proposed usage.

nested_block
  .recover_with(amend_then_retry([Token::Ctrl('}')]));

The pseudocode below displays the proposed functionality.

pub struct AmendThenRetry<I, const N: usize>(
    pub(crate) [I; N],
);

type StreamOf<'a, I, E> = Stream<'a, I, <E as Error<I>>::Span>;
type PResult<I, O, E> = (
    Vec<chumsky::error::Located<I, E>>,
    Result<(O, Option<chumsky::error::Located<I, E>>), chumsky::error::Located<I, E>>,
);

impl<I: Clone + PartialEq, O, E: chumsky::Error<I>, const N: usize> chumsky::recovery::Strategy<I, O, E> for AmendThenRetry<I, N> {
    fn recover<D: chumsky::debug::Debugger, P: Parser<I, O, Error = E>>(
        &self,
        recovered_errors: Vec<chumsky::error::Located<I, P::Error>>,
        fatal_error: chumsky::error::Located<I, P::Error>,
        parser: P,
        debugger: &mut D,
        stream: &mut StreamOf<I, P::Error>,
    ) -> PResult<I, O, P::Error> {
        let fatal_span = fatal_error.at..stream.offset();
        let amended_span = fatal_span.start..(fatal_span.end+ N);

        stream.revert(fatal_span.start);
        let amended = stream.take(fatal_span.len())
            .chain(self.0.into_iter());

        let amended = StreamOf::from_iter(amended_span, amended);
        let res = amended.try_parse(parser);

        stream.revert(fatal_span.end);

        res
    }
}

Depending on which position fatal_error.at refers to, the recovery might need to wrap the parser instead. Input needed.

Before opening a PR for this, I'd love to collect more opinions on my proposed solution. If there is already a method available that solves this particular problem, feel free to educate me :)

ProphetLamb avatar Mar 04 '23 21:03 ProphetLamb