Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opt-in automatic collection of expected values on alt #415

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

urso
Copy link

@urso urso commented Jan 3, 2024

Continuation of #413 and #410

This PR introduces the MergeContext trait, so we can call merge_context during or.

src/error.rs Outdated
@@ -1,4 +1,4 @@
//! # Error management
//! # Errormanagement
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please clean up the commit history

  • This commit shouldn't have this change
  • Most other content from this commit should likely be squashed into a previous commit
  • Replace the AddContext::... commits with the MergeContext trait commit which will hopefully remove the roundtripping of ParseError getting a C parameter and then dropping it
  • Adding of tests is unrelated to the merge context commit and should either be in the LongestMatch commit or its own commit

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, will clean up the commits before undrafting the PR.

type Error<'a> = LongestMatch<Input<'a>, ContextError<&'static str>>;
type PResult<'a, O> = crate::error::PResult<O, Error<'a>>;

fn tag<'a>(t: &'static str) -> impl Parser<Input<'a>, &'a str, Error<'a>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we adding a hand-written tag rather than just using the fact that &'static str implements Parser?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using tag or &;static str then winnow uses a specialized compare to check if the inputs prefix matches that string.. That is on backtracking the checkpoints all point to the same location. Alternatively I maybe could have used ('a', 'b', 'c') as test input.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Yeah, I'd prefer something else like a tuple or array so we don't have to deal with the correctness of custom parser implementations.

src/error/tests.rs Outdated Show resolved Hide resolved
src/error/tests.rs Outdated Show resolved Hide resolved
src/error/tests.rs Outdated Show resolved Hide resolved
src/error/tests.rs Fixed Show fixed Hide fixed
@coveralls
Copy link

Pull Request Test Coverage Report for Build 7402348403

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.8%) to 44.137%

Totals Coverage Status
Change from base Build 7400692687: 0.8%
Covered Lines: 1306
Relevant Lines: 2959

💛 - Coveralls

@urso urso mentioned this pull request Jan 3, 2024
2 tasks
src/error.rs Outdated Show resolved Hide resolved
src/error/tests.rs Fixed Show fixed Hide fixed
@urso
Copy link
Author

urso commented Jan 5, 2024

Made some changes like introducing the MergeContext trait and added a few more tests.

Also update the commit history.

@urso urso requested a review from epage January 5, 2024 23:36
@urso urso marked this pull request as ready for review January 5, 2024 23:36
@epage epage changed the title Longest Opt-in automatic collection of expected values on alt Jan 6, 2024
/// Used to compare checkpoints
pub trait AsOrd {
/// The type used to compare checkpoint positions
type Ord: Ord + Clone + core::cmp::Ord + crate::lib::std::fmt::Debug;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't Ord + core::cmp::Ord redundant?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, some oversight when combining the commits into one.

Comment on lines +2228 to +2234
impl<T: PartialEq> PartialEq for Checkpoint<T> {
fn eq(&self, other: &Self) -> bool {
self.0.eq(&other.0)
}
}

impl<T: Eq> Eq for Checkpoint<T> {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need these?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are currently used by the tests. We can update the tests to compare the err.into_inner() or compare via format!(...).

Comment on lines +570 to +575
let (mut context, other) = if self.context.capacity() >= other.context.capacity() {
(self.context, other.context)
} else {
(other.context, self.context)
};
context.extend(other);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this could lead to confusing ordering.

Should we instead check if one is empty and instead pick the other?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't really care about order if there are multiple parsers with the same longest match, but I see how this might be confusing to developers or mess eventually mess with tests when they change parsers..

Should we instead check if one is empty and instead pick the other?

👍

/// Used to compare checkpoints
pub trait AsOrd {
/// The type used to compare checkpoint positions
type Ord: Ord + Clone + core::cmp::Ord + crate::lib::std::fmt::Debug;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Clone and Debug, if this is because LongestMatch impls those traits, wouldn't that be dependent on whether the Ord does so, meaning we don't need these bounds?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove them. Clone was required because I initially stored the ord in LongestMatch. I updated LongestMatch to store the actual checkpoint now.

Comment on lines +764 to +777
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(),
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we offer an empty error?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And is this the only reason checkpoint is an Option?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for checkpoint to be Option is the LongestMatch currently implements the MergeContext trait as well. When calling clear_context we must set checkpoint: None, such that the empty context is always < then any non-empty context. Otherwise merging 2 context would give us some inconsistent output.

I'm removing impl MergeContext for LongestMatch, then we should get rid of the Option for checkpoint.

Comment on lines +779 to +789
// 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
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this? ContextError needs it because its the default and so we need this for testing parsers. Here, we likely shouldn't be doing equality check tests

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are used by the tests.

As LongestMatch is no error on itself itself, but a decorator most likely used with ContextError I did feel like being able to compare LongestMatch<ContextError> would be the right thing to have.

src/error/mod.rs Show resolved Hide resolved
Comment on lines +845 to +847
fn append(mut self, input: &I, kind: ErrorKind) -> Self {
let checkpoint = input.checkpoint();
match self.cmp_with_checkpoint(&checkpoint) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is this needed?


fn clear_context(self) -> Self {
Self {
checkpoint: None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to reset it when clearing?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment here #415 (comment)

I'm removing impl MergeContext or LongestMatch. This also removes the need or checkpoint to be Option.

Comment on lines +19 to +20
let checkpoint1 = &&input[2..];
let checkpoint2 = &&input[3..];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep these tests, we should name these checkpoints to make reading the asserts clearer

@@ -0,0 +1,190 @@
use super::*;

mod longest_match {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My primary concern with these tests is they focus on all of the details but they don't make sure the details are actually correct for which we'd need tests that render error messages.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "rendered error message" you mean using format!(...) ?

I didn't do this because LongestMatch does not directly implement any rendering itself. It will just delegates rendering to the inner error. That is the test output would depend on the current implementation of ContextError.

As LongestMatch acts as a 'guard' on the wrapped error when merging/adding context details I wanted to make sure with the tests that this 'guard' works as expected.

Copy link
Author

@urso urso Jan 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed that ErrMode uses Debug internally to render the error.

Is this what you have had in mind?

    #[test]
    fn multi_longest_match() {
        let mut parser = alt((
            pattern(('d', 'e', 'f'), "don't want"),
            pattern(('a', 'b', 'c', 'd'), "wanted 1"),
            pattern(('a', 'b', 'c'), "wanted 2"),
            pattern(('d', 'e', 'f', 'g'), "don't want"),
        ));

        let mut input = "abd";
        let checkpoint = &&input[2..]; // 2 characters consumed by longest match

        assert_eq!(
            format!("{}", parser.parse_next(&mut input).unwrap_err(),),
            r#"Parsing Error: LongestMatch { checkpoint: Checkpoint("d"), inner: ContextError { context: ["wanted 1", "wanted 2"], cause: None } }"#,
        );
    }

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't do this because LongestMatch does not directly implement any rendering itself. It will just delegates rendering to the inner error. That is the test output would depend on the current implementation of ContextError.

For me, without a Display test (not Debug or the current state of context), we aren't showing that we've been able to achieve what this is trying to accomplish.

@epage epage mentioned this pull request Jan 12, 2024
2 tasks
@axelkar
Copy link

axelkar commented Feb 25, 2024

Any updates on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants