How to write a custom 'attempt' parser in FParsec?

69 Views Asked by At

I'm trying to write a custom attempt parser in FParsec. I tried it, but I'm an F# / FParsec beginner and get lost with the F# compiler errors I produce. This is my best guess so far:

let customAttempt (p: CharStream<State> -> ParserResult<SyntaxNode,State>) : Parser<SyntaxNode,State> =
    fun (stream: CharStream<State>) ->
        let previousState = stream.State
        let (result, diagnostics) = p stream //This causes Error FS0001 This expression was expected to have type ''a * 'b' but here has type 'ParserResult<SyntaxNode,State>'  
        match diagnostics with
        | [] -> result 
        | _ -> 
            stream.BacktrackTo previousState
            result //Here I actually want to return a result associated with previousState

Why would I ever need to write a custom attempt parser?

Because I want to attempt parsers that never fail. In the FParsec attempt documentation, they say

The parser attempt p applies the parser p. If p fails after changing the parser state or with a fatal error, attempt p will backtrack to the original parser state and report a non‐fatal error.

In my case, the parser p never fails. What I'm currently implementing is an error recovery for my parser based on the approach described in this block post. This approach is to construct a parser p that never fails. Instead, it would always return a Parser<SyntaxNode,State> value, where SyntaxNode is a type for an optional parser result, and State is a list of Diagnostics. In case this list is empty, the parser p was successful.

The mentioned approach to error recovery in FParsec works well for simple grammars. My grammar is more complex and involves choice, attempt, or skipManyTill parsers. Since p never fails in my case, these original FParsec parses do not do the trick anymore.

Therefore, I'm trying to write my own versions of customChoice, customAttempt, or customSkipManyTill, so that I can use them for my grammar that would also support error recovery based on the mentioned approach of parsers that never fail but emit diagnostics instead.

1

There are 1 best solutions below

1
Brian Berns On

This is an interesting idea. I'd be curious to know how it turns out.

I think the problem with your code is that you're assuming that a parser returns a (result, diagnostics) tuple, but this is not correct. Diagnostics are actually stored in the stream state, not returned by the parser. I think you want to check the return value of the parser to determine whether to backtrack, so the code should probably look something like this:

let customAttempt (p : Parser<SyntaxNode, _>) =
    fun (stream : CharStream<_>) ->
        let state = stream.State
        let reply = p stream
        if reply.Result = SyntaxNode.Error then
            stream.BacktrackTo(state)
        reply

Note that I haven't tried this, so I don't know if it works. Good luck!

Related Questions in F#