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 papplies the parserp. Ifpfails after changing the parser state or with a fatal error,attempt pwill 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.
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:Note that I haven't tried this, so I don't know if it works. Good luck!