//! # 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: `many0`, `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 [`PResult`] //! alias, rather than [`Result`][std::result::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: //! - `()` //! - [`ErrorKind`] //! - [`InputError`] (mostly for testing) //! - [`ContextError`] //! - [`TreeError`] (mostly for testing) //! - [Custom errors][crate::_topic::error] #[cfg(feature = "alloc")] use crate::lib::std::borrow::ToOwned; use crate::lib::std::fmt; use core::num::NonZeroUsize; use crate::stream::AsBStr; use crate::stream::Stream; #[allow(unused_imports)] // Here for intra-doc links use crate::Parser; /// Holds the result of [`Parser`] /// /// - `Ok((I, O))` is the remaining [input][crate::stream] and the parsed value /// - [`Err(ErrMode)`][ErrMode] is the error along with how to respond to it /// /// By default, the error type (`E`) is [`InputError`] /// /// [`Parser::parse`] is a top-level operation that can help convert to a `Result` for integrating /// with your application's error reporting. pub type IResult> = PResult<(I, O), E>; /// Holds the result of [`Parser`] /// /// - `Ok(O)` is the parsed value /// - [`Err(ErrMode)`][ErrMode] is the error along with how to respond to it /// /// By default, the error type (`E`) is [`ErrorKind`]. /// /// [`Parser::parse`] is a top-level operation that can help convert to a `Result` for integrating /// with your application's error reporting. pub type PResult = Result>; /// Contains information on needed data if a parser returned `Incomplete` /// /// **Note:** This is only possible for `Stream` that are [partial][`crate::stream::StreamIsPartial`], /// like [`Partial`][crate::Partial]. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[cfg_attr(nightly, warn(rustdoc::missing_doc_code_examples))] pub enum Needed { /// Needs more data, but we do not know how much Unknown, /// Contains the required data size in bytes Size(NonZeroUsize), } impl Needed { /// Creates `Needed` instance, returns `Needed::Unknown` if the argument is zero pub fn 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 pub fn is_known(&self) -> bool { *self != Needed::Unknown } /// Maps a `Needed` to `Needed` by applying a function to a contained `Size` value. #[inline] pub fn map usize>(self, f: F) -> Needed { match self { Needed::Unknown => Needed::Unknown, Needed::Size(n) => Needed::new(f(n)), } } } /// Add parse error state to [`ParserError`]s #[derive(Debug, Clone, PartialEq)] #[cfg_attr(nightly, warn(rustdoc::missing_doc_code_examples))] pub enum ErrMode { /// 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`][crate::stream::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] /// combiantor. 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 ErrMode { /// Tests if the result is Incomplete #[inline] pub fn is_incomplete(&self) -> bool { matches!(self, ErrMode::Incomplete(_)) } /// Prevent backtracking, bubbling the error up to the top pub fn cut(self) -> Self { match self { ErrMode::Backtrack(e) => ErrMode::Cut(e), rest => rest, } } /// Enable backtracking support pub fn backtrack(self) -> Self { match self { ErrMode::Cut(e) => ErrMode::Backtrack(e), rest => rest, } } /// Applies the given function to the inner error pub fn map(self, f: F) -> ErrMode where F: FnOnce(E) -> E2, { match self { 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 pub fn convert(self) -> ErrMode where E: ErrorConvert, { self.map(ErrorConvert::convert) } /// Unwrap the mode, returning the underlying error /// /// Returns `None` for [`ErrMode::Incomplete`] #[cfg_attr(debug_assertions, track_caller)] #[inline(always)] pub fn into_inner(self) -> Option { match self { ErrMode::Backtrack(e) | ErrMode::Cut(e) => Some(e), ErrMode::Incomplete(_) => None, } } } impl> ParserError for ErrMode { #[inline(always)] fn from_error_kind(input: &I, kind: ErrorKind) -> Self { ErrMode::Backtrack(E::from_error_kind(input, kind)) } #[cfg_attr(debug_assertions, track_caller)] #[inline(always)] fn assert(input: &I, message: &'static str) -> Self where I: crate::lib::std::fmt::Debug, { ErrMode::Backtrack(E::assert(input, message)) } #[inline] fn append(self, input: &I, kind: ErrorKind) -> Self { match self { ErrMode::Backtrack(e) => ErrMode::Backtrack(e.append(input, kind)), e => e, } } fn or(self, other: Self) -> Self { match (self, other) { (ErrMode::Backtrack(e), ErrMode::Backtrack(o)) => ErrMode::Backtrack(e.or(o)), (ErrMode::Incomplete(e), _) | (_, ErrMode::Incomplete(e)) => ErrMode::Incomplete(e), (ErrMode::Cut(e), _) | (_, ErrMode::Cut(e)) => ErrMode::Cut(e), } } } impl FromExternalError for ErrMode where E: FromExternalError, { #[inline(always)] fn from_external_error(input: &I, kind: ErrorKind, e: EXT) -> Self { ErrMode::Backtrack(E::from_external_error(input, kind, e)) } } impl> AddContext for ErrMode { #[inline(always)] fn add_context(self, input: &I, ctx: C) -> Self { self.map(|err| err.add_context(input, ctx)) } } impl ErrMode> { /// Maps `ErrMode>` to `ErrMode>` with the given `F: T -> U` pub fn map_input(self, f: F) -> ErrMode> where F: FnOnce(T) -> U, { match self { ErrMode::Incomplete(n) => ErrMode::Incomplete(n), ErrMode::Cut(InputError { input, kind }) => ErrMode::Cut(InputError { input: f(input), kind, }), ErrMode::Backtrack(InputError { input, kind }) => ErrMode::Backtrack(InputError { input: f(input), kind, }), } } } impl Eq for ErrMode {} impl fmt::Display for ErrMode where E: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ErrMode::Incomplete(Needed::Size(u)) => write!(f, "Parsing requires {} bytes/chars", u), ErrMode::Incomplete(Needed::Unknown) => write!(f, "Parsing requires more data"), ErrMode::Cut(c) => write!(f, "Parsing Failure: {:?}", c), ErrMode::Backtrack(c) => write!(f, "Parsing Error: {:?}", c), } } } /// The basic [`Parser`] trait for errors /// /// It provides methods to create an error from some combinators, /// and combine existing errors in combinators like `alt`. pub trait ParserError: Sized { /// Creates an error from the input position and an [`ErrorKind`] fn from_error_kind(input: &I, kind: ErrorKind) -> Self; /// Process a parser assertion #[cfg_attr(debug_assertions, track_caller)] fn assert(input: &I, _message: &'static str) -> Self where I: crate::lib::std::fmt::Debug, { #[cfg(debug_assertions)] panic!("assert `{}` failed at {:#?}", _message, input); #[cfg(not(debug_assertions))] Self::from_error_kind(input, ErrorKind::Assert) } /// Like [`ParserError::from_error_kind`] but merges it with the existing error. /// /// This is useful when backtracking through a parse tree, accumulating error context on the /// way. fn append(self, input: &I, kind: ErrorKind) -> 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 } } /// Used by [`Parser::context`] to add custom data to error while backtracking /// /// May be implemented multiple times for different kinds of context. pub trait AddContext: 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, _ctx: C) -> Self { self } } /// Create a new error with an external error, from [`std::str::FromStr`] /// /// This trait is required by the [`Parser::try_map`] combinator. pub trait FromExternalError { /// Like [`ParserError::from_error_kind`] but also include an external error. fn from_external_error(input: &I, kind: ErrorKind, e: E) -> Self; } /// Equivalent of `From` implementation to avoid orphan rules in bits parsers pub trait ErrorConvert { /// 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. /// /// **Note:** [context][Parser::context] and inner errors (like from [`Parser::try_map`]) will be /// dropped. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct InputError { /// The input stream, pointing to the location where the error occurred pub input: I, /// A rudimentary error kind pub kind: ErrorKind, } impl InputError { /// Creates a new basic error #[inline] pub fn new(input: I, kind: ErrorKind) -> Self { Self { input, kind } } /// Translate the input type #[inline] pub fn map_input I2>(self, op: O) -> InputError { InputError { input: op(self.input), kind: self.kind, } } } #[cfg(feature = "alloc")] impl<'i, I: ToOwned> InputError<&'i I> where ::Owned: Clone, { /// Obtaining ownership pub fn into_owned(self) -> InputError<::Owned> { self.map_input(ToOwned::to_owned) } } impl ParserError for InputError { #[inline] fn from_error_kind(input: &I, kind: ErrorKind) -> Self { Self { input: input.clone(), kind, } } #[inline] fn append(self, _: &I, _: ErrorKind) -> Self { self } } impl AddContext for InputError {} impl FromExternalError for InputError { /// Create a new error from an input position and an external error #[inline] fn from_external_error(input: &I, kind: ErrorKind, _e: E) -> Self { Self { input: input.clone(), kind, } } } impl ErrorConvert> for InputError { #[inline] fn convert(self) -> InputError<(I, usize)> { InputError { input: (self.input, 0), kind: self.kind, } } } impl ErrorConvert> for InputError<(I, usize)> { #[inline] fn convert(self) -> InputError { InputError { input: self.input.0, kind: self.kind, } } } /// The Display implementation allows the `std::error::Error` implementation impl fmt::Display for InputError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{} error starting at: {}", self.kind.description(), self.input ) } } #[cfg(feature = "std")] impl std::error::Error for InputError { } impl ParserError for () { #[inline] fn from_error_kind(_: &I, _: ErrorKind) -> Self {} #[inline] fn append(self, _: &I, _: ErrorKind) -> Self {} } impl AddContext for () {} impl FromExternalError for () { #[inline] fn from_external_error(_input: &I, _kind: ErrorKind, _e: E) -> Self {} } impl ErrorConvert<()> for () { #[inline] fn convert(self) {} } /// Accumulate context while backtracking errors #[derive(Debug)] pub struct ContextError { #[cfg(feature = "alloc")] context: crate::lib::std::vec::Vec, #[cfg(not(feature = "alloc"))] context: core::marker::PhantomData, #[cfg(feature = "std")] cause: Option>, } impl ContextError { /// Create an empty error #[inline] pub fn new() -> Self { Self { context: Default::default(), #[cfg(feature = "std")] cause: None, } } /// Access context from [`Parser::context`] #[inline] #[cfg(feature = "alloc")] pub fn context(&self) -> impl Iterator { self.context.iter() } /// Originating [`std::error::Error`] #[inline] #[cfg(feature = "std")] pub fn cause(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> { self.cause.as_deref() } } impl Clone for ContextError { fn clone(&self) -> Self { Self { context: self.context.clone(), #[cfg(feature = "std")] cause: self.cause.as_ref().map(|e| e.to_string().into()), } } } impl Default for ContextError { #[inline] fn default() -> Self { Self::new() } } impl ParserError for ContextError { #[inline] fn from_error_kind(_input: &I, _kind: ErrorKind) -> Self { Self::new() } #[inline] fn append(self, _input: &I, _kind: ErrorKind) -> Self { self } #[inline] fn or(self, other: Self) -> Self { other } } impl AddContext for ContextError { #[inline] fn add_context(mut self, _input: &I, ctx: C) -> Self { #[cfg(feature = "alloc")] self.context.push(ctx); self } } #[cfg(feature = "std")] impl FromExternalError for ContextError { #[inline] fn from_external_error(_input: &I, _kind: ErrorKind, e: E) -> Self { let mut err = Self::new(); { err.cause = Some(Box::new(e)); } err } } // HACK: This is more general than `std`, making the features non-additive #[cfg(not(feature = "std"))] impl FromExternalError for ContextError { #[inline] fn from_external_error(_input: &I, _kind: ErrorKind, _e: E) -> Self { let err = Self::new(); err } } // For tests impl core::cmp::PartialEq for ContextError { fn eq(&self, other: &Self) -> bool { #[cfg(feature = "alloc")] { if self.context != other.context { return false; } } #[cfg(feature = "std")] { if self.cause.as_ref().map(ToString::to_string) != other.cause.as_ref().map(ToString::to_string) { return false; } } true } } impl crate::lib::std::fmt::Display for ContextError { fn fmt(&self, f: &mut crate::lib::std::fmt::Formatter<'_>) -> crate::lib::std::fmt::Result { #[cfg(feature = "alloc")] { let expression = self.context().find_map(|c| match c { StrContext::Label(c) => Some(c), _ => None, }); let expected = self .context() .filter_map(|c| match c { StrContext::Expected(c) => Some(c), _ => None, }) .collect::>(); let mut newline = false; if let Some(expression) = expression { newline = true; write!(f, "invalid {}", expression)?; } if !expected.is_empty() { if newline { writeln!(f)?; } newline = true; write!(f, "expected ")?; for (i, expected) in expected.iter().enumerate() { if i != 0 { write!(f, ", ")?; } write!(f, "{}", expected)?; } } #[cfg(feature = "std")] { if let Some(cause) = self.cause() { if newline { writeln!(f)?; } write!(f, "{}", cause)?; } } } Ok(()) } } /// Additional parse context for [`ContextError`] added via [`Parser::context`] #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum StrContext { /// Description of what is currently being parsed Label(&'static str), /// Grammar item that was expected Expected(StrContextValue), } impl crate::lib::std::fmt::Display for StrContext { fn fmt(&self, f: &mut crate::lib::std::fmt::Formatter<'_>) -> crate::lib::std::fmt::Result { match self { Self::Label(name) => write!(f, "invalid {name}"), Self::Expected(value) => write!(f, "expected {value}"), } } } /// See [`StrContext`] #[derive(Clone, Debug, PartialEq, Eq)] #[non_exhaustive] pub enum StrContextValue { /// A [`char`] token CharLiteral(char), /// A [`&str`] token StringLiteral(&'static str), /// A description of what was being parsed Description(&'static str), } impl From for StrContextValue { #[inline] fn from(inner: char) -> Self { Self::CharLiteral(inner) } } impl From<&'static str> for StrContextValue { #[inline] fn from(inner: &'static str) -> Self { Self::StringLiteral(inner) } } impl crate::lib::std::fmt::Display for StrContextValue { fn fmt(&self, f: &mut crate::lib::std::fmt::Formatter<'_>) -> crate::lib::std::fmt::Result { match self { Self::CharLiteral('\n') => "newline".fmt(f), Self::CharLiteral('`') => "'`'".fmt(f), Self::CharLiteral(c) if c.is_ascii_control() => { write!(f, "`{}`", c.escape_debug()) } Self::CharLiteral(c) => write!(f, "`{}`", c), Self::StringLiteral(c) => write!(f, "`{}`", c), Self::Description(c) => write!(f, "{}", c), } } } /// Trace all error paths, particularly for tests #[derive(Debug)] #[cfg(feature = "std")] pub enum TreeError { /// Initial error that kicked things off Base(TreeErrorBase), /// Traces added to the error while walking back up the stack Stack { /// Initial error that kicked things off base: Box, /// Traces added to the error while walking back up the stack stack: Vec>, }, /// All failed branches of an `alt` Alt(Vec), } /// See [`TreeError::Stack`] #[derive(Debug)] #[cfg(feature = "std")] pub enum TreeErrorFrame { /// See [`ParserError::append`] Kind(TreeErrorBase), /// See [`AddContext::add_context`] Context(TreeErrorContext), } /// See [`TreeErrorFrame::Kind`], [`ParserError::append`] #[derive(Debug)] #[cfg(feature = "std")] pub struct TreeErrorBase { /// Parsed input, at the location where the error occurred pub input: I, /// Debug context pub kind: ErrorKind, /// See [`FromExternalError::from_external_error`] pub cause: Option>, } /// See [`TreeErrorFrame::Context`], [`AddContext::add_context`] #[derive(Debug)] #[cfg(feature = "std")] pub struct TreeErrorContext { /// Parsed input, at the location where the error occurred pub input: I, /// See [`AddContext::add_context`] pub context: C, } #[cfg(feature = "std")] impl<'i, I: ToOwned, C> TreeError<&'i I, C> where ::Owned: Clone, { /// Obtaining ownership pub fn into_owned(self) -> TreeError<::Owned, C> { self.map_input(ToOwned::to_owned) } } #[cfg(feature = "std")] impl TreeError where I: Clone, { /// Translate the input type pub fn map_input I2>(self, op: O) -> TreeError { match self { TreeError::Base(base) => TreeError::Base(TreeErrorBase { input: op(base.input), kind: base.kind, cause: base.cause, }), TreeError::Stack { base, stack } => { let base = Box::new(base.map_input(op.clone())); let stack = stack .into_iter() .map(|frame| match frame { TreeErrorFrame::Kind(kind) => TreeErrorFrame::Kind(TreeErrorBase { input: op(kind.input), kind: kind.kind, cause: kind.cause, }), TreeErrorFrame::Context(context) => { TreeErrorFrame::Context(TreeErrorContext { input: op(context.input), context: context.context, }) } }) .collect(); TreeError::Stack { base, stack } } TreeError::Alt(alt) => { TreeError::Alt(alt.into_iter().map(|e| e.map_input(op.clone())).collect()) } } } fn append_frame(self, frame: TreeErrorFrame) -> Self { match self { TreeError::Stack { base, mut stack } => { stack.push(frame); TreeError::Stack { base, stack } } base => TreeError::Stack { base: Box::new(base), stack: vec![frame], }, } } } #[cfg(feature = "std")] impl ParserError for TreeError where I: Clone, { fn from_error_kind(input: &I, kind: ErrorKind) -> Self { TreeError::Base(TreeErrorBase { input: input.clone(), kind, cause: None, }) } fn append(self, input: &I, kind: ErrorKind) -> Self { let frame = TreeErrorFrame::Kind(TreeErrorBase { input: input.clone(), kind, cause: None, }); self.append_frame(frame) } fn or(self, other: Self) -> Self { match (self, other) { (TreeError::Alt(mut first), TreeError::Alt(second)) => { // Just in case an implementation does a divide-and-conquer algorithm // // To prevent mixing `alt`s at different levels, parsers should // `alt_err.append(input, ErrorKind::Alt)`. first.extend(second); TreeError::Alt(first) } (TreeError::Alt(mut alt), new) | (new, TreeError::Alt(mut alt)) => { alt.push(new); TreeError::Alt(alt) } (first, second) => TreeError::Alt(vec![first, second]), } } } #[cfg(feature = "std")] impl AddContext for TreeError where I: Clone, { fn add_context(self, input: &I, context: C) -> Self { let frame = TreeErrorFrame::Context(TreeErrorContext { input: input.clone(), context, }); self.append_frame(frame) } } #[cfg(feature = "std")] impl FromExternalError for TreeError where I: Clone, { fn from_external_error(input: &I, kind: ErrorKind, e: E) -> Self { TreeError::Base(TreeErrorBase { input: input.clone(), kind, cause: Some(Box::new(e)), }) } } #[cfg(feature = "std")] impl TreeError where I: Clone + std::fmt::Display, C: fmt::Display, { fn write(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result { let child_indent = indent + 2; match self { TreeError::Base(base) => { writeln!(f, "{:indent$}{base}", "")?; } TreeError::Stack { base, stack } => { base.write(f, indent)?; for (level, frame) in stack.iter().enumerate() { match frame { TreeErrorFrame::Kind(frame) => { writeln!(f, "{:child_indent$}{level}: {frame}", "")?; } TreeErrorFrame::Context(frame) => { writeln!(f, "{:child_indent$}{level}: {frame}", "")?; } } } } TreeError::Alt(alt) => { writeln!(f, "{:indent$}during one of:", "")?; for child in alt { child.write(f, child_indent)?; } } } Ok(()) } } #[cfg(feature = "std")] impl fmt::Display for TreeErrorBase { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(cause) = self.cause.as_ref() { write!(f, "caused by {cause}")?; } else { let kind = self.kind.description(); write!(f, "in {kind}")?; } let input = abbreviate(self.input.to_string()); write!(f, " at '{input}'")?; Ok(()) } } #[cfg(feature = "std")] impl fmt::Display for TreeErrorContext { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let context = &self.context; let input = abbreviate(self.input.to_string()); write!(f, "{context} at '{input}'")?; Ok(()) } } #[cfg(feature = "std")] impl< I: Clone + fmt::Debug + fmt::Display + Sync + Send + 'static, C: fmt::Display + fmt::Debug, > std::error::Error for TreeError { } #[cfg(feature = "std")] fn abbreviate(input: String) -> String { let mut abbrev = None; if let Some((line, _)) = input.split_once('\n') { abbrev = Some(line); } let max_len = 20; let current = abbrev.unwrap_or(&input); if max_len < current.len() { if let Some((index, _)) = current.char_indices().nth(max_len) { abbrev = Some(¤t[..index]); } } if let Some(abbrev) = abbrev { format!("{abbrev}...") } else { input } } #[cfg(feature = "std")] impl fmt::Display for TreeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.write(f, 0) } } /// Deprecated, replaced with [`ContextError`] #[cfg(feature = "std")] #[allow(deprecated)] #[deprecated(since = "0.5.8", note = "Replaced with `ContextError`")] #[derive(Clone, Debug, Eq, PartialEq)] pub struct VerboseError { /// Accumulated error information pub errors: crate::lib::std::vec::Vec<(I, VerboseErrorKind)>, } #[cfg(feature = "std")] #[allow(deprecated)] impl<'i, I: ToOwned, C> VerboseError<&'i I, C> where ::Owned: Clone, { /// Obtaining ownership pub fn into_owned(self) -> VerboseError<::Owned, C> { self.map_input(ToOwned::to_owned) } } #[cfg(feature = "std")] #[allow(deprecated)] impl VerboseError { /// Translate the input type pub fn map_input(self, op: O) -> VerboseError where O: Fn(I) -> I2, { VerboseError { errors: self.errors.into_iter().map(|(i, k)| (op(i), k)).collect(), } } } /// Deprecated, replaced with [`ContextError`] #[cfg(feature = "std")] #[deprecated(since = "0.5.8", note = "Replaced with `ContextError`")] #[derive(Clone, Debug, Eq, PartialEq)] pub enum VerboseErrorKind { /// Static string added by the `context` function Context(C), /// Error kind given by various parsers Winnow(ErrorKind), } #[cfg(feature = "std")] #[allow(deprecated)] impl ParserError for VerboseError { fn from_error_kind(input: &I, kind: ErrorKind) -> Self { VerboseError { errors: vec![(input.clone(), VerboseErrorKind::Winnow(kind))], } } fn append(mut self, input: &I, kind: ErrorKind) -> Self { self.errors .push((input.clone(), VerboseErrorKind::Winnow(kind))); self } } #[cfg(feature = "std")] #[allow(deprecated)] impl AddContext for VerboseError { fn add_context(mut self, input: &I, ctx: C) -> Self { self.errors .push((input.clone(), VerboseErrorKind::Context(ctx))); self } } #[cfg(feature = "std")] #[allow(deprecated)] impl FromExternalError for VerboseError { /// Create a new error from an input position and an external error fn from_external_error(input: &I, kind: ErrorKind, _e: E) -> Self { Self::from_error_kind(input, kind) } } #[cfg(feature = "std")] #[allow(deprecated)] impl fmt::Display for VerboseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "Parse error:")?; for (input, error) in &self.errors { match error { VerboseErrorKind::Winnow(e) => writeln!(f, "{} at: {}", e.description(), input)?, VerboseErrorKind::Context(s) => writeln!(f, "in section '{}', at: {}", s, input)?, } } Ok(()) } } #[cfg(feature = "std")] #[allow(deprecated)] impl< I: Clone + fmt::Debug + fmt::Display + Sync + Send + 'static, C: fmt::Display + fmt::Debug, > std::error::Error for VerboseError { } /// Provide some minor debug context for errors #[rustfmt::skip] #[derive(Debug,PartialEq,Eq,Hash,Clone,Copy)] #[allow(missing_docs)] pub enum ErrorKind { Assert, Token, Tag, Alt, Many, Eof, Slice, Complete, Not, Verify, Fail, } impl ErrorKind { #[rustfmt::skip] /// Converts an `ErrorKind` to a text description pub fn description(&self) -> &str { match *self { ErrorKind::Assert => "assert", ErrorKind::Token => "token", ErrorKind::Tag => "tag", ErrorKind::Alt => "alternative", ErrorKind::Many => "many", ErrorKind::Eof => "end of file", ErrorKind::Slice => "slice", ErrorKind::Complete => "complete", ErrorKind::Not => "negation", ErrorKind::Verify => "predicate verification", ErrorKind::Fail => "fail", } } } impl ParserError for ErrorKind { #[inline] fn from_error_kind(_input: &I, kind: ErrorKind) -> Self { kind } #[inline] fn append(self, _: &I, _: ErrorKind) -> Self { self } } impl AddContext for ErrorKind {} impl FromExternalError for ErrorKind { /// Create a new error from an input position and an external error #[inline] fn from_external_error(_input: &I, kind: ErrorKind, _e: E) -> Self { kind } } /// The Display implementation allows the `std::error::Error` implementation impl fmt::Display for ErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "error {:?}", self) } } #[cfg(feature = "std")] impl std::error::Error for ErrorKind {} /// See [`Parser::parse`] #[derive(Clone, Debug, PartialEq, Eq)] pub struct ParseError { input: I, offset: usize, inner: E, } impl> ParseError { pub(crate) fn new(mut input: I, start: I::Checkpoint, inner: E) -> Self { let offset = input.offset_from(&start); input.reset(start); Self { input, offset, inner, } } } impl ParseError { /// The [`Stream`] at the initial location when parsing started #[inline] pub fn input(&self) -> &I { &self.input } /// The location in [`ParseError::input`] where parsing failed #[inline] pub fn offset(&self) -> usize { self.offset } /// The original [`ParserError`] #[inline] pub fn inner(&self) -> &E { &self.inner } /// The original [`ParserError`] #[inline] pub fn into_inner(self) -> E { self.inner } } impl core::fmt::Display for ParseError where I: AsBStr, E: core::fmt::Display, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let input = self.input.as_bstr(); let span_start = self.offset; let span_end = span_start; #[cfg(feature = "std")] if input.contains(&b'\n') { let (line_idx, col_idx) = translate_position(input, span_start); let line_num = line_idx + 1; let col_num = col_idx + 1; let gutter = line_num.to_string().len(); let content = input .split(|c| *c == b'\n') .nth(line_idx) .expect("valid line number"); writeln!(f, "parse error at line {}, column {}", line_num, col_num)?; // | for _ in 0..=gutter { write!(f, " ")?; } writeln!(f, "|")?; // 1 | 00:32:00.a999999 write!(f, "{} | ", line_num)?; writeln!(f, "{}", String::from_utf8_lossy(content))?; // | ^ for _ in 0..=gutter { write!(f, " ")?; } write!(f, "|")?; for _ in 0..=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 _ in 0..=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)?; Ok(()) } } #[cfg(feature = "std")] fn translate_position(input: &[u8], index: usize) -> (usize, usize) { if input.is_empty() { return (0, index); } 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(); let line = line; // HACK: This treats byte offset and column offsets the same let column = 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)] #[cfg(feature = "std")] mod test_translate_position { use super::*; #[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)); } } /// Creates a parse error from a [`ErrorKind`] /// and the position in the input #[cfg(test)] macro_rules! error_position( ($input:expr, $code:expr) => ({ $crate::error::ParserError::from_error_kind($input, $code) }); ); #[cfg(test)] macro_rules! error_node_position( ($input:expr, $code:expr, $next:expr) => ({ $crate::error::ParserError::append($next, $input, $code) }); );