diff options
Diffstat (limited to 'vendor/winnow/src/error.rs')
-rw-r--r-- | vendor/winnow/src/error.rs | 827 |
1 files changed, 521 insertions, 306 deletions
diff --git a/vendor/winnow/src/error.rs b/vendor/winnow/src/error.rs index c92228621..d40e8244b 100644 --- a/vendor/winnow/src/error.rs +++ b/vendor/winnow/src/error.rs @@ -8,14 +8,15 @@ //! - 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 [`IResult`] -//! alias, rather than [`Result`][std::result::Result]. [`finish`][FinishIResult::finish] is -//! provided for top-level parsers to integrate with your application's error reporting. +//! 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: //! - `()` -//! - [`Error`] -//! - [`VerboseError`] +//! - [`ErrorKind`] +//! - [`InputError`] (mostly for testing) +//! - [`ContextError`] //! - [Custom errors][crate::_topic::error] #[cfg(feature = "alloc")] @@ -23,8 +24,8 @@ use crate::lib::std::borrow::ToOwned; use crate::lib::std::fmt; use core::num::NonZeroUsize; +use crate::stream::AsBStr; use crate::stream::Stream; -use crate::stream::StreamIsPartial; #[allow(unused_imports)] // Here for intra-doc links use crate::Parser; @@ -33,100 +34,26 @@ use crate::Parser; /// - `Ok((I, O))` is the remaining [input][crate::stream] and the parsed value /// - [`Err(ErrMode<E>)`][ErrMode] is the error along with how to respond to it /// -/// By default, the error type (`E`) is [`Error`] +/// By default, the error type (`E`) is [`InputError`] /// -/// At the top-level of your parser, you can use the [`FinishIResult::finish`] method to convert -/// it to a more common result type -pub type IResult<I, O, E = Error<I>> = Result<(I, O), ErrMode<E>>; - -/// Extension trait to convert a parser's [`IResult`] to a more manageable type -#[deprecated(since = "0.4.0", note = "Replaced with `Parser::parse`")] -pub trait FinishIResult<I, O, E> { - /// Converts the parser's [`IResult`] to a type that is more consumable by callers. - /// - /// Errors if the parser is not at the [end of input][crate::combinator::eof]. See - /// [`FinishIResult::finish_err`] if the remaining input is needed. - /// - /// # Panic - /// - /// If the result is `Err(ErrMode::Incomplete(_))`, this method will panic. - /// - **Complete parsers:** It will not be an issue, `Incomplete` is never used - /// - **Partial parsers:** `Incomplete` will be returned if there's not enough data - /// for the parser to decide, and you should gather more data before parsing again. - /// Once the parser returns either `Ok(_)`, `Err(ErrMode::Backtrack(_))` or `Err(ErrMode::Cut(_))`, - /// you can get out of the parsing loop and call `finish_err()` on the parser's result - /// - /// # Example - /// - /// ```rust - /// # #[cfg(feature = "std")] { - /// use winnow::prelude::*; - /// use winnow::ascii::hex_uint; - /// use winnow::error::Error; - /// - /// struct Hex(u64); - /// - /// fn parse(value: &str) -> Result<Hex, Error<String>> { - /// hex_uint.map(Hex).parse_next(value).finish().map_err(Error::into_owned) - /// } - /// # } - /// ``` - #[deprecated(since = "0.4.0", note = "Replaced with `Parser::parse`")] - fn finish(self) -> Result<O, E>; - - /// Converts the parser's [`IResult`] to a type that is more consumable by errors. - /// - /// It keeps the same `Ok` branch, and merges `ErrMode::Backtrack` and `ErrMode::Cut` into the `Err` - /// side. - /// - /// # Panic - /// - /// If the result is `Err(ErrMode::Incomplete(_))`, this method will panic as [`ErrMode::Incomplete`] - /// should only be set when the input is [`StreamIsPartial<false>`] which this isn't implemented - /// for. - #[deprecated(since = "0.4.0", note = "Replaced with `Parser::parse`")] - fn finish_err(self) -> Result<(I, O), E>; -} - -#[allow(deprecated)] -impl<I, O, E> FinishIResult<I, O, E> for IResult<I, O, E> -where - I: Stream, - // Force users to deal with `Incomplete` when `StreamIsPartial<true>` - I: StreamIsPartial, - I: Clone, - E: ParseError<I>, -{ - fn finish(self) -> Result<O, E> { - debug_assert!( - !I::is_partial_supported(), - "partial streams need to handle `ErrMode::Incomplete`" - ); +/// [`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<I, O, E = InputError<I>> = PResult<(I, O), E>; - let (i, o) = self.finish_err()?; - crate::combinator::eof(i).finish_err()?; - Ok(o) - } - - fn finish_err(self) -> Result<(I, O), E> { - debug_assert!( - !I::is_partial_supported(), - "partial streams need to handle `ErrMode::Incomplete`" - ); - - match self { - Ok(res) => Ok(res), - Err(ErrMode::Backtrack(e)) | Err(ErrMode::Cut(e)) => Err(e), - Err(ErrMode::Incomplete(_)) => { - panic!("complete parsers should not report `Err(ErrMode::Incomplete(_))`") - } - } - } -} +/// Holds the result of [`Parser`] +/// +/// - `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 [`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<O, E = ContextError> = Result<O, ErrMode<E>>; /// Contains information on needed data if a parser returned `Incomplete` /// -/// **Note:** This is only possible for `Stream` that are [partial][`StreamIsPartial`], +/// **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))] @@ -169,7 +96,7 @@ pub enum ErrMode<E> { /// /// More data needs to be buffered before retrying the parse. /// - /// This must only be set when the [`Stream`] is [partial][`StreamIsPartial`], like with + /// 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`] @@ -232,22 +159,33 @@ impl<E> ErrMode<E> { { self.map(ErrorConvert::convert) } + + /// Unwrap the mode, returning the underlying error + /// + /// Returns `None` for [`ErrMode::Incomplete`] + #[cfg_attr(debug_assertions, track_caller)] + pub fn into_inner(self) -> Option<E> { + match self { + ErrMode::Backtrack(e) | ErrMode::Cut(e) => Some(e), + ErrMode::Incomplete(_) => None, + } + } } -impl<I, E: ParseError<I>> ParseError<I> for ErrMode<E> { - fn from_error_kind(input: I, kind: ErrorKind) -> Self { +impl<I, E: ParserError<I>> ParserError<I> for ErrMode<E> { + fn from_error_kind(input: &I, kind: ErrorKind) -> Self { ErrMode::Backtrack(E::from_error_kind(input, kind)) } #[cfg_attr(debug_assertions, track_caller)] - fn assert(input: I, message: &'static str) -> Self + fn assert(input: &I, message: &'static str) -> Self where I: crate::lib::std::fmt::Debug, { ErrMode::Backtrack(E::assert(input, message)) } - fn append(self, input: I, kind: ErrorKind) -> Self { + fn append(self, input: &I, kind: ErrorKind) -> Self { match self { ErrMode::Backtrack(e) => ErrMode::Backtrack(e.append(input, kind)), e => e, @@ -267,24 +205,24 @@ impl<I, EXT, E> FromExternalError<I, EXT> for ErrMode<E> where E: FromExternalError<I, EXT>, { - fn from_external_error(input: I, kind: ErrorKind, e: EXT) -> Self { + fn from_external_error(input: &I, kind: ErrorKind, e: EXT) -> Self { ErrMode::Backtrack(E::from_external_error(input, kind, e)) } } -impl<T> ErrMode<Error<T>> { - /// Maps `ErrMode<Error<T>>` to `ErrMode<Error<U>>` with the given `F: T -> U` - pub fn map_input<U, F>(self, f: F) -> ErrMode<Error<U>> +impl<T: Clone> ErrMode<InputError<T>> { + /// Maps `ErrMode<InputError<T>>` to `ErrMode<InputError<U>>` with the given `F: T -> U` + pub fn map_input<U: Clone, F>(self, f: F) -> ErrMode<InputError<U>> where F: FnOnce(T) -> U, { match self { ErrMode::Incomplete(n) => ErrMode::Incomplete(n), - ErrMode::Cut(Error { input, kind }) => ErrMode::Cut(Error { + ErrMode::Cut(InputError { input, kind }) => ErrMode::Cut(InputError { input: f(input), kind, }), - ErrMode::Backtrack(Error { input, kind }) => ErrMode::Backtrack(Error { + ErrMode::Backtrack(InputError { input, kind }) => ErrMode::Backtrack(InputError { input: f(input), kind, }), @@ -312,13 +250,13 @@ where /// /// It provides methods to create an error from some combinators, /// and combine existing errors in combinators like `alt`. -pub trait ParseError<I>: Sized { +pub trait ParserError<I>: Sized { /// Creates an error from the input position and an [`ErrorKind`] - fn from_error_kind(input: I, kind: ErrorKind) -> Self; + 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 + fn assert(input: &I, _message: &'static str) -> Self where I: crate::lib::std::fmt::Debug, { @@ -328,11 +266,11 @@ pub trait ParseError<I>: Sized { Self::from_error_kind(input, ErrorKind::Assert) } - /// Like [`ParseError::from_error_kind`] but merges it with the existing error. + /// 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; + fn append(self, input: &I, kind: ErrorKind) -> Self; /// Combines errors from two different parse branches. /// @@ -346,12 +284,13 @@ pub trait ParseError<I>: Sized { /// Used by [`Parser::context`] to add custom data to error while backtracking /// /// May be implemented multiple times for different kinds of context. -pub trait ContextError<I, C = &'static str>: Sized { +pub trait AddContext<I, 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 - fn add_context(self, _input: I, _ctx: C) -> Self { + #[inline] + fn add_context(self, _input: &I, _ctx: C) -> Self { self } } @@ -360,8 +299,8 @@ pub trait ContextError<I, C = &'static str>: Sized { /// /// This trait is required by the [`Parser::try_map`] combinator. pub trait FromExternalError<I, E> { - /// Like [`ParseError::from_error_kind`] but also include an external error. - fn from_external_error(input: I, kind: ErrorKind, e: E) -> Self; + /// 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 @@ -370,70 +309,85 @@ pub trait ErrorConvert<E> { fn convert(self) -> E; } -/// Default error type, only contains the error' location and kind +/// Capture input on error +/// +/// This is useful for testing of generic parsers to ensure the error happens at the right +/// location. /// -/// This is a low-overhead error that only provides basic information. For less overhead, see -/// `()`. Fore more information, see [`VerboseError`]. -///:w /// **Note:** [context][Parser::context] and inner errors (like from [`Parser::try_map`]) will be /// dropped. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Error<I> { +pub struct InputError<I: Clone> { /// The input stream, pointing to the location where the error occurred pub input: I, /// A rudimentary error kind pub kind: ErrorKind, } -impl<I> Error<I> { +impl<I: Clone> InputError<I> { /// Creates a new basic error - pub fn new(input: I, kind: ErrorKind) -> Error<I> { - Error { input, kind } + #[inline] + pub fn new(input: I, kind: ErrorKind) -> Self { + Self { input, kind } } } #[cfg(feature = "alloc")] -impl<'i, I: ToOwned + ?Sized> Error<&'i I> { +impl<'i, I: Clone + ToOwned + ?Sized> InputError<&'i I> +where + <I as ToOwned>::Owned: Clone, +{ /// Obtaining ownership - pub fn into_owned(self) -> Error<<I as ToOwned>::Owned> { - Error { + pub fn into_owned(self) -> InputError<<I as ToOwned>::Owned> { + InputError { input: self.input.to_owned(), kind: self.kind, } } } -impl<I> ParseError<I> for Error<I> { - fn from_error_kind(input: I, kind: ErrorKind) -> Self { - Error { input, kind } +impl<I: Clone> ParserError<I> for InputError<I> { + #[inline] + fn from_error_kind(input: &I, kind: ErrorKind) -> Self { + Self { + input: input.clone(), + kind, + } } - fn append(self, _: I, _: ErrorKind) -> Self { + #[inline] + fn append(self, _: &I, _: ErrorKind) -> Self { self } } -impl<I, C> ContextError<I, C> for Error<I> {} +impl<I: Clone, C> AddContext<I, C> for InputError<I> {} -impl<I, E> FromExternalError<I, E> for Error<I> { +impl<I: Clone, E> FromExternalError<I, E> for InputError<I> { /// Create a new error from an input position and an external error - fn from_external_error(input: I, kind: ErrorKind, _e: E) -> Self { - Error { input, kind } + #[inline] + fn from_external_error(input: &I, kind: ErrorKind, _e: E) -> Self { + Self { + input: input.clone(), + kind, + } } } -impl<I> ErrorConvert<Error<(I, usize)>> for Error<I> { - fn convert(self) -> Error<(I, usize)> { - Error { +impl<I: Clone> ErrorConvert<InputError<(I, usize)>> for InputError<I> { + #[inline] + fn convert(self) -> InputError<(I, usize)> { + InputError { input: (self.input, 0), kind: self.kind, } } } -impl<I> ErrorConvert<Error<I>> for Error<(I, usize)> { - fn convert(self) -> Error<I> { - Error { +impl<I: Clone> ErrorConvert<InputError<I>> for InputError<(I, usize)> { + #[inline] + fn convert(self) -> InputError<I> { + InputError { input: self.input.0, kind: self.kind, } @@ -441,217 +395,254 @@ impl<I> ErrorConvert<Error<I>> for Error<(I, usize)> { } /// The Display implementation allows the `std::error::Error` implementation -impl<I: fmt::Display> fmt::Display for Error<I> { +impl<I: Clone + fmt::Display> fmt::Display for InputError<I> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "error {:?} at: {}", self.kind, self.input) + write!(f, "{} error starting at: {}", self.kind, self.input) } } #[cfg(feature = "std")] -impl<I: fmt::Debug + fmt::Display + Sync + Send + 'static> std::error::Error for Error<I> {} +impl<I: Clone + fmt::Debug + fmt::Display + Sync + Send + 'static> std::error::Error + for InputError<I> +{ +} -impl<I> ParseError<I> for () { - fn from_error_kind(_: I, _: ErrorKind) -> Self {} +impl<I> ParserError<I> for () { + #[inline] + fn from_error_kind(_: &I, _: ErrorKind) -> Self {} - fn append(self, _: I, _: ErrorKind) -> Self {} + #[inline] + fn append(self, _: &I, _: ErrorKind) -> Self {} } -impl<I, C> ContextError<I, C> for () {} +impl<I, C> AddContext<I, C> for () {} impl<I, E> FromExternalError<I, E> for () { - fn from_external_error(_input: I, _kind: ErrorKind, _e: E) -> Self {} + #[inline] + fn from_external_error(_input: &I, _kind: ErrorKind, _e: E) -> Self {} } impl ErrorConvert<()> for () { + #[inline] fn convert(self) {} } -/// Accumulates error information while backtracking -/// -/// For less overhead (and information), see [`Error`]. -/// -/// [`convert_error`] provides an example of how to render this for end-users. -/// -/// **Note:** This will only capture the last failed branch for combinators like -/// [`alt`][crate::combinator::alt]. -#[cfg(feature = "alloc")] -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct VerboseError<I> { - /// Accumulated error information - pub errors: crate::lib::std::vec::Vec<(I, VerboseErrorKind)>, +/// Accumulate context while backtracking errors +#[derive(Debug)] +pub struct 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>>, } -#[cfg(feature = "alloc")] -impl<'i, I: ToOwned + ?Sized> VerboseError<&'i I> { - /// Obtaining ownership - pub fn into_owned(self) -> VerboseError<<I as ToOwned>::Owned> { - #[allow(clippy::redundant_clone)] // false positive - VerboseError { - errors: self - .errors - .into_iter() - .map(|(i, k)| (i.to_owned(), k)) - .collect(), +impl<C> ContextError<C> { + /// 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<Item = &C> { + 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() + } } -#[cfg(feature = "alloc")] -#[derive(Clone, Debug, Eq, PartialEq)] -/// Error context for `VerboseError` -pub enum VerboseErrorKind { - /// Static string added by the `context` function - Context(&'static str), - /// Error kind given by various parsers - Winnow(ErrorKind), +impl<C> Default for ContextError<C> { + #[inline] + fn default() -> Self { + Self::new() + } } -#[cfg(feature = "alloc")] -impl<I> ParseError<I> for VerboseError<I> { - fn from_error_kind(input: I, kind: ErrorKind) -> Self { - VerboseError { - errors: vec![(input, VerboseErrorKind::Winnow(kind))], - } +impl<I, C> ParserError<I> for ContextError<C> { + #[inline] + fn from_error_kind(_input: &I, _kind: ErrorKind) -> Self { + Self::new() } - fn append(mut self, input: I, kind: ErrorKind) -> Self { - self.errors.push((input, VerboseErrorKind::Winnow(kind))); + #[inline] + fn append(self, _input: &I, _kind: ErrorKind) -> Self { self } -} -#[cfg(feature = "alloc")] -impl<I> ContextError<I, &'static str> for VerboseError<I> { - fn add_context(mut self, input: I, ctx: &'static str) -> Self { - self.errors.push((input, VerboseErrorKind::Context(ctx))); - self + #[inline] + fn or(self, other: Self) -> Self { + other } } -#[cfg(feature = "alloc")] -impl<I, E> FromExternalError<I, E> for VerboseError<I> { - /// 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) +impl<C, I> AddContext<I, C> for ContextError<C> { + #[inline] + fn add_context(mut self, _input: &I, ctx: C) -> Self { + #[cfg(feature = "alloc")] + self.context.push(ctx); + self } } -#[cfg(feature = "alloc")] -impl<I> ErrorConvert<VerboseError<I>> for VerboseError<(I, usize)> { - fn convert(self) -> VerboseError<I> { - VerboseError { - errors: self.errors.into_iter().map(|(i, e)| (i.0, e)).collect(), +#[cfg(feature = "std")] +impl<C, I, E: std::error::Error + Send + Sync + 'static> FromExternalError<I, E> + for ContextError<C> +{ + #[inline] + fn from_external_error(_input: &I, _kind: ErrorKind, e: E) -> Self { + let mut err = Self::new(); + { + err.cause = Some(Box::new(e)); } + err } } -#[cfg(feature = "alloc")] -impl<I> ErrorConvert<VerboseError<(I, usize)>> for VerboseError<I> { - fn convert(self) -> VerboseError<(I, usize)> { - VerboseError { - errors: self.errors.into_iter().map(|(i, e)| ((i, 0), e)).collect(), - } +// 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, _kind: ErrorKind, _e: E) -> Self { + let err = Self::new(); + err } } -#[cfg(feature = "alloc")] -impl<I: fmt::Display> fmt::Display for VerboseError<I> { - 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, input)?, - VerboseErrorKind::Context(s) => writeln!(f, "in section '{}', at: {}", s, input)?, +// For tests +impl<C: core::cmp::PartialEq> core::cmp::PartialEq for ContextError<C> { + 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; } } - Ok(()) + true } } -#[cfg(feature = "std")] -impl<I: fmt::Debug + fmt::Display + Sync + Send + 'static> std::error::Error for VerboseError<I> {} +impl crate::lib::std::fmt::Display for ContextError<StrContext> { + 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::<crate::lib::std::vec::Vec<_>>(); -/// Transforms a `VerboseError` into a trace with input position information -#[cfg(feature = "alloc")] -pub fn convert_error<I: core::ops::Deref<Target = str>>( - input: I, - e: VerboseError<I>, -) -> crate::lib::std::string::String { - use crate::lib::std::fmt::Write; - use crate::stream::Offset; + let mut newline = false; - let mut result = crate::lib::std::string::String::new(); + if let Some(expression) = expression { + newline = true; - for (i, (substring, kind)) in e.errors.iter().enumerate() { - let offset = input.offset_to(substring); + write!(f, "invalid {}", expression)?; + } - if input.is_empty() { - match kind { - VerboseErrorKind::Context(s) => { - write!(&mut result, "{}: in {}, got empty input\n\n", i, s) + if !expected.is_empty() { + if newline { + writeln!(f)?; } - VerboseErrorKind::Winnow(e) => { - write!(&mut result, "{}: in {:?}, got empty input\n\n", i, e) + newline = true; + + write!(f, "expected ")?; + for (i, expected) in expected.iter().enumerate() { + if i != 0 { + write!(f, ", ")?; + } + write!(f, "{}", expected)?; } } - } else { - let prefix = &input.as_bytes()[..offset]; - - // Count the number of newlines in the first `offset` bytes of input - let line_number = prefix.iter().filter(|&&b| b == b'\n').count() + 1; - - // Find the line that includes the subslice: - // Find the *last* newline before the substring starts - let line_begin = prefix - .iter() - .rev() - .position(|&b| b == b'\n') - .map(|pos| offset - pos) - .unwrap_or(0); - - // Find the full line after that newline - let line = input[line_begin..] - .lines() - .next() - .unwrap_or(&input[line_begin..]) - .trim_end(); - - // The (1-indexed) column number is the offset of our substring into that line - let column_number = line.offset_to(substring) + 1; - - match kind { - VerboseErrorKind::Context(s) => write!( - &mut result, - "{i}: at line {line_number}, in {context}:\n\ - {line}\n\ - {caret:>column$}\n\n", - i = i, - line_number = line_number, - context = s, - line = line, - caret = '^', - column = column_number, - ), - VerboseErrorKind::Winnow(e) => write!( - &mut result, - "{i}: at line {line_number}, in {kind:?}:\n\ - {line}\n\ - {caret:>column$}\n\n", - i = i, - line_number = line_number, - kind = e, - line = line, - caret = '^', - column = column_number, - ), + #[cfg(feature = "std")] + { + if let Some(cause) = self.cause() { + if newline { + writeln!(f)?; + } + write!(f, "{}", cause)?; + } } } - // Because `write!` to a `String` is infallible, this `unwrap` is fine. - .unwrap(); + + 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), +} + +/// 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<char> for StrContextValue { + fn from(inner: char) -> Self { + Self::CharLiteral(inner) } +} - result +impl From<&'static str> for StrContextValue { + 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), + } + } } /// Provide some minor debug context for errors @@ -692,31 +683,255 @@ impl ErrorKind { } } +impl<I> ParserError<I> for ErrorKind { + fn from_error_kind(_input: &I, kind: ErrorKind) -> Self { + kind + } + + fn append(self, _: &I, _: ErrorKind) -> Self { + self + } +} + +impl<I, C> AddContext<I, C> for ErrorKind {} + +impl<I, E> FromExternalError<I, E> for ErrorKind { + /// Create a new error from an input position and an external error + 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<I, E> { + input: I, + offset: usize, + inner: E, +} + +impl<I: Stream, E: ParserError<I>> ParseError<I, E> { + 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<I, E> ParseError<I, E> { + /// 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 + } +} + +impl<I, E> core::fmt::Display for ParseError<I, E> +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::ParseError::from_error_kind($input, $code) + $crate::error::ParserError::from_error_kind($input, $code) }); ); #[cfg(test)] macro_rules! error_node_position( ($input:expr, $code:expr, $next:expr) => ({ - $crate::error::ParseError::append($next, $input, $code) + $crate::error::ParserError::append($next, $input, $code) }); ); - -#[cfg(test)] -#[cfg(feature = "alloc")] -mod tests { - use super::*; - - #[test] - fn convert_error_panic() { - let input = ""; - - let _result: IResult<_, _, VerboseError<&str>> = 'x'.parse_next(input); - } -} |