Struct aoc_parse::ParseContext

source ·
pub struct ParseContext<'parse> { /* private fields */ }
Expand description

Contains the source text we’re parsing and tracks errors.

We track errors in the ParseContext, not Results, because often a parser produces both a successful match and the error that will later prove to be the best error message for the overall parse attempt.

The :wq example

For example, consider parser!(line(u32)+) parsing the following input:

1
2
3
4:wq
5

Clearly, someone accidentally typed :wq on line 4. But what will happen here is that line(u32)+ successfully matches the first 3 lines. If we don’t track the error we encountered when trying to parse 5:wq as a u32, but content ourselves with the successful match line(u32)+ produces, the best error message we can ultimately produce is something like “found extra text after a successful match at line 4, column 1”.

Here’s how we now handle these success-failures:

  • u32 returns success, matching 4.

  • line(u32) reports an error to the context (at line 4 column 2) and returns Reported, because u32 didn’t match the entire line.

  • line(u32)+ then discards the Reported error, backtracks, and returns a successful match for the first 3 lines.

  • The top-level parser sees that line(u32)+ didn’t match the entire input, and reports an error to the context at line 4 column 1. But we already have a previous error where we had got further, so this error is ignored.

  • The error at line 4 column 2 is taken from the context and returned to the user.

Rationale: Design alternatives

To implement this without ParseContext, we could have implemented a TigerResult<T, E> type that can be Ok(T), Err(E), or OkBut(T, E), the last containing both a success value and an excuse explaining why we did not succeed even more. The forwardmost error would be propagated there instead of being stored in the context. We would use TigerResult<T, ParseError> instead of Result<T, Reported> everywhere. Both ways have advantages. Both are pretty weird for Rust. The way of the context is something I’ve wanted to explore in Rust; and it lets us keep using the ? try operator.

Implementations§

Create a ParseContext to parse the given input.

The text being parsed.

Extract the error. Use this only after receiving Reported from an operation on the context.

Panics

If no error has been reported on this context.

Record an error.

Currently a ParseContext only tracks the foremost error. That is, if err.location is farther forward than any other error we’ve encountered, we store it. Otherwise discard it.

Nontrivial patterns try several different things. If anything succeeds, we get a match. We only fail if every branch leads to failure. This means that by the time matching fails, we have an abundance of different error messages. Generally the error we want is the one where we progressed as far as possible through the input string before failing.

Record a foo expected error.

Record an error when FromStr::from_str fails.

Record an “extra unparsed text after match” error.

Auto Trait Implementations§

Blanket Implementations§

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.