//! # Error management //! //! Errors are designed with multiple needs in mind: //! - Accumulate more [context][Parser::context] as the error goes up the parser chain //! - Distinguish between [recoverable errors, //! unrecoverable errors, and more data is needed][ErrMode] //! - Have a very low overhead, as errors are often discarded by the calling parser (examples: `repeat`, `alt`) //! - Can be modified according to the user's needs, because some languages need a lot more information //! - Help thread-through the [stream][crate::stream] //! //! To abstract these needs away from the user, generally `winnow` parsers use the [`ModalResult`] //! alias, rather than [`Result`]. [`Parser::parse`] is a top-level operation //! that can help convert to a `Result` for integrating with your application's error reporting. //! //! Error types include: //! - [`EmptyError`] when the reason for failure doesn't matter //! - [`ContextError`] //! - [`InputError`] (mostly for testing) //! - [`TreeError`] (mostly for testing) //! - [Custom errors][crate::_topic::error]
#[cfg(feature = "alloc")] usecrate::lib::std::borrow::ToOwned; usecrate::lib::std::fmt; use core::num::NonZeroUsize;
usecrate::stream::AsBStr; usecrate::stream::Stream; #[allow(unused_imports)] // Here for intra-doc links usecrate::Parser;
/// By default, the error type (`E`) is [`ContextError`]. /// /// When integrating into the result of the application, see /// - [`Parser::parse`] /// - [`ParserError::into_inner`] pubtype Result<O, E = ContextError> = core::result::Result<O, E>;
/// [Modal error reporting][ErrMode] for [`Parser::parse_next`] /// /// - `Ok(O)` is the parsed value /// - [`Err(ErrMode<E>)`][ErrMode] is the error along with how to respond to it /// /// By default, the error type (`E`) is [`ContextError`]. /// /// When integrating into the result of the application, see /// - [`Parser::parse`] /// - [`ParserError::into_inner`] pubtype ModalResult<O, E = ContextError> = Result<O, ErrMode<E>>;
#[cfg(test)] pub(crate) type TestResult<I, O> = ModalResult<O, InputError<I>>;
/// Contains information on needed data if a parser returned `Incomplete` /// /// <div class="warning"> /// /// **Note:** This is only possible for `Stream` that are [partial][`crate::stream::StreamIsPartial`], /// like [`Partial`][crate::Partial]. /// /// </div> #[derive(Debug, PartialEq, Eq, Clone, Copy)] pubenum Needed { /// Needs more data, but we do not know how much
Unknown, /// Contains a lower bound on the buffer offset needed to finish parsing /// /// For byte/`&str` streams, this translates to bytes
Size(NonZeroUsize),
}
impl Needed { /// Creates `Needed` instance, returns `Needed::Unknown` if the argument is zero pubfn new(s: usize) -> Self { match NonZeroUsize::new(s) {
Some(sz) => Needed::Size(sz),
None => Needed::Unknown,
}
}
/// Indicates if we know how many bytes we need pubfn is_known(&self) -> bool {
*self != Needed::Unknown
}
/// Maps a `Needed` to `Needed` by applying a function to a contained `Size` value. #[inline] pubfn map<F: Fn(NonZeroUsize) -> usize>(self, f: F) -> Needed { matchself {
Needed::Unknown => Needed::Unknown,
Needed::Size(n) => Needed::new(f(n)),
}
}
}
/// Add parse error state to [`ParserError`]s /// /// Needed for /// - [`Partial`][crate::stream::Partial] to track whether the [`Stream`] is [`ErrMode::Incomplete`]. /// See also [`_topic/partial`] /// - Marking errors as unrecoverable ([`ErrMode::Cut`]) and not retrying alternative parsers. /// See also [`_tutorial/chapter_7#error-cuts`] #[derive(Debug, Clone, PartialEq)] pubenum ErrMode<E> { /// There was not enough data to determine the appropriate action /// /// More data needs to be buffered before retrying the parse. /// /// This must only be set when the [`Stream`] is [partial][`crate::stream::StreamIsPartial`], like with /// [`Partial`][crate::Partial] /// /// Convert this into an `Backtrack` with [`Parser::complete_err`]
Incomplete(Needed), /// The parser failed with a recoverable error (the default). /// /// For example, a parser for json values might include a /// [`dec_uint`][crate::ascii::dec_uint] as one case in an [`alt`][crate::combinator::alt] /// combinator. If it fails, the next case should be tried.
Backtrack(E), /// The parser had an unrecoverable error. /// /// The parser was on the right branch, so directly report it to the user rather than trying /// other branches. You can use [`cut_err()`][crate::combinator::cut_err] combinator to switch /// from `ErrMode::Backtrack` to `ErrMode::Cut`. /// /// For example, one case in an [`alt`][crate::combinator::alt] combinator found a unique prefix /// and you want any further errors parsing the case to be reported to the user.
Cut(E),
}
impl<E> ErrMode<E> { /// Tests if the result is Incomplete #[inline] pubfn is_incomplete(&self) -> bool {
matches!(self, ErrMode::Incomplete(_))
}
/// Prevent backtracking, bubbling the error up to the top pubfn cut(self) -> Self { matchself {
ErrMode::Backtrack(e) => ErrMode::Cut(e),
rest => rest,
}
}
/// Applies the given function to the inner error pubfn map<E2, F>(self, f: F) -> ErrMode<E2> where
F: FnOnce(E) -> E2,
{ matchself {
ErrMode::Incomplete(n) => ErrMode::Incomplete(n),
ErrMode::Cut(t) => ErrMode::Cut(f(t)),
ErrMode::Backtrack(t) => ErrMode::Backtrack(f(t)),
}
}
/// Automatically converts between errors if the underlying type supports it pubfn convert<F>(self) -> ErrMode<F> where
E: ErrorConvert<F>,
{
ErrorConvert::convert(self)
}
/// The basic [`Parser`] trait for errors /// /// It provides methods to create an error from some combinators, /// and combine existing errors in combinators like `alt`. pubtrait ParserError<I: Stream>: Sized { /// Generally, `Self` /// /// Mostly used for [`ErrMode`] type Inner;
/// Creates an error from the input position fn from_input(input: &I) -> Self;
/// Process a parser assertion #[inline(always)] fn assert(input: &I, _message: &'static str) -> Self where
I: crate::lib::std::fmt::Debug,
{ #[cfg(debug_assertions)]
panic!("assert `{_message}` failed at {input:#?}"); #[cfg(not(debug_assertions))] Self::from_input(input)
}
/// There was not enough data to determine the appropriate action /// /// More data needs to be buffered before retrying the parse. /// /// This must only be set when the [`Stream`] is [partial][`crate::stream::StreamIsPartial`], like with /// [`Partial`][crate::Partial] /// /// Convert this into an `Backtrack` with [`Parser::complete_err`] #[inline(always)] fn incomplete(input: &I, _needed: Needed) -> Self { Self::from_input(input)
}
/// Like [`ParserError::from_input`] but merges it with the existing error. /// /// This is useful when backtracking through a parse tree, accumulating error context on the /// way. #[inline] fn append(self, _input: &I, _token_start: &<I as Stream>::Checkpoint) -> Self { self
}
/// Combines errors from two different parse branches. /// /// For example, this would be used by [`alt`][crate::combinator::alt] to report the error from /// each case. #[inline] fn or(self, other: Self) -> Self {
other
}
/// Is backtracking and trying new parse branches allowed? #[inline(always)] fn is_backtrack(&self) -> bool { true
}
/// Unwrap the mode, returning the underlying error, if present fn into_inner(self) -> Result<Self::Inner, Self>;
/// Is more data [`Needed`] /// /// This must be the same as [`err.needed().is_some()`][ParserError::needed] #[inline(always)] fn is_incomplete(&self) -> bool { false
}
/// Extract the [`Needed`] data, if present /// /// `Self::needed().is_some()` must be the same as /// [`err.is_incomplete()`][ParserError::is_incomplete] #[inline(always)] fn needed(&self) -> Option<Needed> {
None
}
}
/// Manipulate the how parsers respond to this error pubtrait ModalError { /// Prevent backtracking, bubbling the error up to the top fn cut(self) -> Self; /// Enable backtracking support fn backtrack(self) -> Self;
}
/// Used by [`Parser::context`] to add custom data to error while backtracking /// /// May be implemented multiple times for different kinds of context. pubtrait AddContext<I: Stream, C = &'static str>: Sized { /// Append to an existing error custom data /// /// This is used mainly by [`Parser::context`], to add user friendly information /// to errors when backtracking through a parse tree #[inline] fn add_context( self,
_input: &I,
_token_start: &<I as Stream>::Checkpoint,
_context: C,
) -> Self { self
}
}
/// Capture context from when an error was recovered #[cfg(feature = "unstable-recover")] #[cfg(feature = "std")] pubtrait FromRecoverableError<I: Stream, E> { /// Capture context from when an error was recovered fn from_recoverable_error(
token_start: &<I as Stream>::Checkpoint,
err_start: &<I as Stream>::Checkpoint,
input: &I,
e: E,
) -> Self;
}
/// Create a new error with an external error, from [`std::str::FromStr`] /// /// This trait is required by the [`Parser::try_map`] combinator. pubtrait FromExternalError<I, E> { /// Like [`ParserError::from_input`] but also include an external error. fn from_external_error(input: &I, e: E) -> Self;
}
/// Equivalent of `From` implementation to avoid orphan rules in bits parsers pubtrait ErrorConvert<E> { /// Transform to another error type fn convert(self) -> E;
}
/// Capture input on error /// /// This is useful for testing of generic parsers to ensure the error happens at the right /// location. /// /// <div class="warning"> /// /// **Note:** [context][Parser::context] and inner errors (like from [`Parser::try_map`]) will be /// dropped. /// /// </div> #[derive(Copy, Clone, Debug, Eq, PartialEq)] pubstruct InputError<I: Clone> { /// The input stream, pointing to the location where the error occurred pub input: I,
}
impl<I: Stream + Clone, C> AddContext<I, C> for InputError<I> {}
#[cfg(feature = "unstable-recover")] #[cfg(feature = "std")] impl<I: Clone + Stream> FromRecoverableError<I, Self> for InputError<I> { #[inline] fn from_recoverable_error(
_token_start: &<I as Stream>::Checkpoint,
_err_start: &<I as Stream>::Checkpoint,
_input: &I,
e: Self,
) -> Self {
e
}
}
impl<I: Clone, E> FromExternalError<I, E> for InputError<I> { /// Create a new error from an input position and an external error #[inline] fn from_external_error(input: &I, _e: E) -> Self { Self {
input: input.clone(),
}
}
}
impl ErrorConvert<()> for () { #[inline] fn convert(self) {}
}
/// Accumulate context while backtracking errors /// /// See the [tutorial][crate::_tutorial::chapter_7#error-adaptation-and-rendering] /// for an example of how to adapt this to an application error with custom rendering. #[derive(Debug)] pubstruct ContextError<C = StrContext> { #[cfg(feature = "alloc")]
context: crate::lib::std::vec::Vec<C>, #[cfg(not(feature = "alloc"))]
context: core::marker::PhantomData<C>, #[cfg(feature = "std")]
cause: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
}
// HACK: This is more general than `std`, making the features non-additive #[cfg(not(feature = "std"))] impl<C, I, E: Send + Sync + 'static> FromExternalError<I, E> for ContextError<C> { #[inline] fn from_external_error(_input: &I, _e: E) -> Self { let err = Self::new();
err
}
}
/// Additional parse context for [`ContextError`] added via [`Parser::context`] #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pubenum StrContext { /// Description of what is currently being parsed
Label(&'static str), /// Grammar item that was expected
Expected(StrContextValue),
}
/// See [`StrContext`] #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pubenum StrContextValue { /// A [`char`] token
CharLiteral(char), /// A [`&str`] token
StringLiteral(&'static str), /// A description of what was being parsed
Description(&'static str),
}
/// Trace all error paths, particularly for tests #[derive(Debug)] #[cfg(feature = "std")] pubenum TreeError<I, C = StrContext> { /// Initial error that kicked things off
Base(TreeErrorBase<I>), /// Traces added to the error while walking back up the stack
Stack { /// Initial error that kicked things off
base: Box<Self>, /// Traces added to the error while walking back up the stack
stack: Vec<TreeErrorFrame<I, C>>,
}, /// All failed branches of an `alt`
Alt(Vec<Self>),
}
/// See [`TreeError::Stack`] #[derive(Debug)] #[cfg(feature = "std")] pubenum TreeErrorFrame<I, C = StrContext> { /// See [`ParserError::append`]
Kind(TreeErrorBase<I>), /// See [`AddContext::add_context`]
Context(TreeErrorContext<I, C>),
}
/// See [`TreeErrorFrame::Kind`], [`ParserError::append`] #[derive(Debug)] #[cfg(feature = "std")] pubstruct TreeErrorBase<I> { /// Parsed input, at the location where the error occurred pub input: I, /// See [`FromExternalError::from_external_error`] pub cause: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
}
/// See [`TreeErrorFrame::Context`], [`AddContext::add_context`] #[derive(Debug)] #[cfg(feature = "std")] pubstruct TreeErrorContext<I, C = StrContext> { /// Parsed input, at the location where the error occurred pub input: I, /// See [`AddContext::add_context`] pub context: C,
}
impl<I, E> ParseError<I, E> { /// The [`Stream`] at the initial location when parsing started #[inline] pubfn input(&self) -> &I {
&self.input
}
/// The location in [`ParseError::input`] where parsing failed /// /// To get the span for the `char` this points to, see [`ParseError::char_span`]. /// /// <div class="warning"> /// /// **Note:** This is an offset, not an index, and may point to the end of input /// (`input.len()`) on eof errors. /// /// </div> #[inline] pubfn offset(&self) -> usize { self.offset
}
/// The original [`ParserError`] #[inline] pubfn inner(&self) -> &E {
&self.inner
}
/// The original [`ParserError`] #[inline] pubfn into_inner(self) -> E { self.inner
}
}
impl<I: AsBStr, E> ParseError<I, E> { /// The byte indices for the `char` at [`ParseError::offset`] #[inline] pubfn char_span(&self) -> crate::lib::std::ops::Range<usize> {
char_boundary(self.input.as_bstr(), self.offset())
}
}
fn char_boundary(input: &[u8], offset: usize) -> crate::lib::std::ops::Range<usize> { let len = input.len(); if offset == len { return offset..offset;
}
// | ^ for _ in0..gutter {
write!(f, " ")?;
}
write!(f, " | ")?; for _ in0..col_idx {
write!(f, " ")?;
} // The span will be empty at eof, so we need to make sure we always print at least // one `^`
write!(f, "^")?; for _ in (span_start + 1)..(span_end.min(span_start + content.len())) {
write!(f, "^")?;
}
writeln!(f)?;
} else { let content = input;
writeln!(f, "{}", String::from_utf8_lossy(content))?; for _ in0..span_start {
write!(f, " ")?;
} // The span will be empty at eof, so we need to make sure we always print at least // one `^`
write!(f, "^")?; for _ in (span_start + 1)..(span_end.min(span_start + content.len())) {
write!(f, "^")?;
}
writeln!(f)?;
}
write!(f, "{}", self.inner)?;
let safe_index = index.min(input.len() - 1); let column_offset = index - safe_index; let index = safe_index;
let nl = input[0..index]
.iter()
.rev()
.enumerate()
.find(|(_, b)| **b == b'\n')
.map(|(nl, _)| index - nl - 1); let line_start = match nl {
Some(nl) => nl + 1,
None => 0,
}; let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
// HACK: This treats byte offset and column offsets the same let column = crate::lib::std::str::from_utf8(&input[line_start..=index])
.map(|s| s.chars().count() - 1)
.unwrap_or_else(|_| index - line_start); let column = column + column_offset;
(line, column)
}
#[cfg(test)] mod test_char_boundary { usesuper::*;
#[test] fn ascii() { let input = "hi"; let cases = [(0, 0..1), (1, 1..2), (2, 2..2)]; for (offset, expected) in cases {
assert_eq!(
char_boundary(input.as_bytes(), offset),
expected, "input={input:?}, offset={offset:?}"
);
}
}
#[cfg(test)] #[cfg(feature = "std")] mod test_parse_error { usesuper::*;
#[test] fn single_line() { letmut input = "0xZ123"; let start = input.checkpoint(); let _ = input.next_token().unwrap(); let _ = input.next_token().unwrap(); let inner = InputError::at(input); let error = ParseError::new(input, start, inner); let expected = "\ 0xZ123
^
failed to parse starting at: Z123";
assert_eq!(error.to_string(), expected);
}
}
#[cfg(test)] #[cfg(feature = "std")] mod test_translate_position { usesuper::*;
#[test] fn empty() { let input = b""; let index = 0; let position = translate_position(&input[..], index);
assert_eq!(position, (0, 0));
}
#[test] fn start() { let input = b"Hello"; let index = 0; let position = translate_position(&input[..], index);
assert_eq!(position, (0, 0));
}
#[test] fn end() { let input = b"Hello"; let index = input.len() - 1; let position = translate_position(&input[..], index);
assert_eq!(position, (0, input.len() - 1));
}
#[test] fn after() { let input = b"Hello"; let index = input.len(); let position = translate_position(&input[..], index);
assert_eq!(position, (0, input.len()));
}
#[test] fn first_line() { let input = b"Hello\nWorld\n"; let index = 2; let position = translate_position(&input[..], index);
assert_eq!(position, (0, 2));
}
#[test] fn end_of_line() { let input = b"Hello\nWorld\n"; let index = 5; let position = translate_position(&input[..], index);
assert_eq!(position, (0, 5));
}
#[test] fn start_of_second_line() { let input = b"Hello\nWorld\n"; let index = 6; let position = translate_position(&input[..], index);
assert_eq!(position, (1, 0));
}
#[test] fn second_line() { let input = b"Hello\nWorld\n"; let index = 8; let position = translate_position(&input[..], index);
assert_eq!(position, (1, 2));
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.