Skip to content

Commit

Permalink
feat(error): Add LongestMatch error decorator
Browse files Browse the repository at this point in the history
  • Loading branch information
urso committed Jan 5, 2024
1 parent 93d91e3 commit 1f272cf
Show file tree
Hide file tree
Showing 2 changed files with 382 additions and 0 deletions.
192 changes: 192 additions & 0 deletions src/error.rs → src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@ use crate::lib::std::fmt;
use core::num::NonZeroUsize;

use crate::stream::AsBStr;
use crate::stream::AsOrd;
use crate::stream::Stream;
#[allow(unused_imports)] // Here for intra-doc links
use crate::Parser;

#[cfg(test)]
mod tests;

/// For use with [`Parser::parse_peek`] which allows the input stream to be threaded through a
/// parser.
///
Expand Down Expand Up @@ -745,6 +749,194 @@ impl crate::lib::std::fmt::Display for StrContextValue {
}
}

/// Collect context of the longest matching parser while backtracking on errors
#[derive(Clone, Debug)]
pub struct LongestMatch<I, E>
where
I: Stream,
<I as Stream>::Checkpoint: AsOrd,
E: MergeContext,
{
checkpoint: Option<<I as Stream>::Checkpoint>,
inner: E,
}

impl<I, E> LongestMatch<I, E>
where
I: Stream,
<I as Stream>::Checkpoint: AsOrd,
E: MergeContext + Default,
{
/// Create an empty error
pub fn new() -> Self {
Self {
checkpoint: None,
inner: Default::default(),
}
}
}

// For tests
impl<I, E> core::cmp::PartialEq for LongestMatch<I, E>
where
I: Stream,
<I as Stream>::Checkpoint: AsOrd + core::cmp::PartialEq,
E: MergeContext + core::cmp::PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.checkpoint == other.checkpoint && self.inner == other.inner
}
}

impl<I, E> LongestMatch<I, E>
where
I: Stream,
<I as Stream>::Checkpoint: AsOrd,
E: MergeContext,
{
/// Extract the error for the longest matching parser
#[inline]
pub fn into_inner(self) -> E {
self.inner
}

#[inline]
fn cmp_checkpoints(&self, other: &Self) -> core::cmp::Ordering {
let a = self.checkpoint.as_ref().map(|c| c.as_ord());
let b = other.checkpoint.as_ref().map(|c| c.as_ord());
a.cmp(&b)
}

#[inline]
fn cmp_with_checkpoint(&self, other: &<I as Stream>::Checkpoint) -> core::cmp::Ordering {
match self.checkpoint {
None => core::cmp::Ordering::Less,
Some(ref c) => c.as_ord().cmp(&other.as_ord()),
}
}
}

impl<I, E> Default for LongestMatch<I, E>
where
I: Stream,
<I as Stream>::Checkpoint: AsOrd,
E: MergeContext + Default,
{
fn default() -> Self {
Self::new()
}
}

impl<I, E> ParserError<I> for LongestMatch<I, E>
where
I: Stream,
<I as Stream>::Checkpoint: AsOrd,
E: ParserError<I> + MergeContext,
{
#[inline]
fn from_error_kind(input: &I, kind: ErrorKind) -> Self {
Self {
checkpoint: Some(input.checkpoint()),
inner: E::from_error_kind(input, kind),
}
}

#[inline]
fn append(mut self, input: &I, kind: ErrorKind) -> Self {
let checkpoint = input.checkpoint();
match self.cmp_with_checkpoint(&checkpoint) {
core::cmp::Ordering::Less => {
self.checkpoint = Some(checkpoint);
self.inner = self.inner.clear_context().append(input, kind);
}
core::cmp::Ordering::Equal => {
self.inner = self.inner.append(input, kind);
}
core::cmp::Ordering::Greater => {}
}
self
}

#[inline]
fn or(self, other: Self) -> Self {
self.merge_context(other)
}
}

impl<I, E, C> AddContext<I, C> for LongestMatch<I, E>
where
I: Stream,
<I as Stream>::Checkpoint: AsOrd,
E: AddContext<I, C> + MergeContext,
{
#[inline]
fn add_context(mut self, input: &I, ctx: C) -> Self {
let checkpoint = input.checkpoint();
match self.cmp_with_checkpoint(&checkpoint) {
core::cmp::Ordering::Less => {
self.checkpoint = Some(checkpoint);
self.inner = self.inner.clear_context().add_context(input, ctx);
}
core::cmp::Ordering::Equal => {
self.inner = self.inner.add_context(input, ctx);
}
core::cmp::Ordering::Greater => {}
}
self
}
}

impl<I, E: MergeContext> MergeContext for LongestMatch<I, E>
where
I: Stream,
<I as Stream>::Checkpoint: AsOrd,
{
#[inline]
fn merge_context(mut self, other: Self) -> Self {
match self.cmp_checkpoints(&other) {
core::cmp::Ordering::Less => other,
core::cmp::Ordering::Greater => self,
core::cmp::Ordering::Equal => {
self.inner = self.inner.merge_context(other.inner);
self
}
}
}

fn clear_context(self) -> Self {
Self {
checkpoint: None,
inner: self.inner.clear_context(),
}
}
}

impl<I, E, EX> FromExternalError<I, EX> for LongestMatch<I, E>
where
I: Stream,
<I as Stream>::Checkpoint: AsOrd,
E: FromExternalError<I, EX> + MergeContext,
{
#[inline]
fn from_external_error(input: &I, kind: ErrorKind, e: EX) -> Self {
Self {
checkpoint: Some(input.checkpoint()),
inner: E::from_external_error(input, kind, e),
}
}
}

impl<I, E> crate::lib::std::fmt::Display for LongestMatch<I, E>
where
I: Stream,
<I as Stream>::Checkpoint: AsOrd,
E: crate::lib::std::fmt::Display + MergeContext,
{
fn fmt(&self, f: &mut crate::lib::std::fmt::Formatter<'_>) -> crate::lib::std::fmt::Result {
self.inner.fmt(f)
}
}

/// Trace all error paths, particularly for tests
#[derive(Debug)]
#[cfg(feature = "std")]
Expand Down
Loading

0 comments on commit 1f272cf

Please sign in to comment.