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, matching4
. -
line(u32)
reports an error to the context (at line 4 column 2) and returnsReported
, becauseu32
didn’t match the entire line. -
line(u32)+
then discards theReported
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§
Source§impl<'parse> ParseContext<'parse>
impl<'parse> ParseContext<'parse>
Sourcepub fn into_reported_error(self) -> ParseError
pub fn into_reported_error(self) -> ParseError
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.
Sourcepub fn report(&mut self, err: ParseError) -> Reported
pub fn report(&mut self, err: ParseError) -> Reported
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.
Sourcepub fn error_expected(&mut self, start: usize, expected: &str) -> Reported
pub fn error_expected(&mut self, start: usize, expected: &str) -> Reported
Record a foo expected
error.
Sourcepub fn error_from_str_failed(
&mut self,
start: usize,
end: usize,
type_name: &'static str,
message: String,
) -> Reported
pub fn error_from_str_failed( &mut self, start: usize, end: usize, type_name: &'static str, message: String, ) -> Reported
Record an error when FromStr::from_str
fails.
Sourcepub fn error_extra(&mut self, location: usize) -> Reported
pub fn error_extra(&mut self, location: usize) -> Reported
Record an “extra unparsed text after match” error.