datalust / superpower

A C# parser construction toolkit with high-quality error reporting
Apache License 2.0
1.07k stars 98 forks source link

Catch to ParserExtensions #162

Closed marklauter closed 1 month ago

marklauter commented 4 months ago

Ideas for https://github.com/datalust/superpower/issues/87

I don't have tests for the changes yet.

nblumhardt commented 4 months ago

Thanks for exploring this. I'm trying to get the context around this API paged back in :sweat_smile: - is there a use case that's currently valuable to you that this enables? Cheers!

marklauter commented 4 months ago

I don't have a use case for it, but I've been interested in contributing to Superpower since you introduced me to the magic of parser combinators with one of your NDC talks on Youtube. So I'm looking at the open up-for-grabs items to see if I can help.

nblumhardt commented 4 months ago

Ah, awesome! Thanks 😎

I think this is worth exploring further, but we'll need to find/wait for some motivating examples to make sure it hits the mark. Superpower's goal is to be more precise about reporting errors, so I wonder if the quick-and-dirty uses for Catch in Sprache might not appear so much with Superpower? 🤔

API wise, I think the examples would have to answer questions like:

Probably a good time to park it and come back.

I haven't reviewed the "up-for-grabs" list for quite some time, I'll run through now and make notes on any that still seem good-to-go, in case there's something there you're interested to take a look at. Thanks again!

marklauter commented 4 months ago

I didn't see Catch in Sprache (checked both develop and main branches). The OP from Issue 87 mentioned SelectCatch which I did find in Serilog.Expressions.

I've been using Superpower to parse simple predicate expressions for a work project and to parse DDL and SQL for a Sqlite challenge on Code Crafters. So far I haven't encountered a use case for Catch, but all the operational code is under my control and I'm avoiding exceptions. Might be different if I had to use external code, or maybe network requests. The error messages generate by Superpower are already good and I think if you craft your expressions correctly, you should already handle the edge cases with OptionalOrDefault.

static class ParserExtensions
{
    public static TokenListParser<TTokenKind, TResult> SelectCatch<TTokenKind, TArg, TResult>(
        this TokenListParser<TTokenKind, TArg> parser, 
        Func<TArg, TResult> trySelector, 
        string errorMessage)
    {
        if (parser == null) throw new ArgumentNullException(nameof(parser));
        if (trySelector == null) throw new ArgumentNullException(nameof(trySelector));
        if (errorMessage == null) throw new ArgumentNullException(nameof(errorMessage));

        return input =>
        {
            var t = parser(input);
            if (!t.HasValue)
                return TokenListParserResult.CastEmpty<TTokenKind, TArg, TResult>(t);

            try
            {
                var u = trySelector(t.Value);
                return TokenListParserResult.Value(u, input, t.Remainder);
            }
            catch
            {
                return TokenListParserResult.Empty<TTokenKind, TResult>(input, errorMessage);
            }
        };
    }
}
nblumhardt commented 1 month ago

HI @marklauter, it doesn't look like we'll push this PR forward for the time being; we're running through open PRs and cleaning up/triaging, I'll close this as not planned but we may revisit in the future. Thanks again!