如何在 FParsec 中编写自定义“尝试”解析器?

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

提问人:bookofproofs 提问时间:9/10/2023 更新时间:9/11/2023 访问量:50

问:

我正在尝试在 FParsec 中编写自定义尝试解析器。我试过了,但我是 F# / FParsec 初学者,并且迷失在我产生的 F# 编译器错误中。这是我迄今为止最好的猜测:

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

为什么我需要编写自定义尝试解析器?

因为我想尝试永不失败的解析器。在 FParsec 尝试文档中,他们说

解析器应用解析器。如果在更改解析器状态后失败或出现致命错误,则将回溯到原始解析器状态并报告非致命错误。attempt pppattempt p

就我而言,解析器永远不会失败。我目前正在实现的是基于此块帖子中描述的方法对我的解析器进行错误恢复。此方法是构造一个永不失败的解析器。相反,它始终返回一个值,其中是可选解析器结果的类型,并且是 的列表。如果此列表为空,则分析器成功。ppParser<SyntaxNode,State>SyntaxNodeStateDiagnosticsp

FParsec 中提到的错误恢复方法适用于简单的语法。我的语法更复杂,涉及 、 或解析器。由于在我的情况下永远不会失败,因此这些原始的 FParsec 解析不再起作用。choiceattemptskipManyTillp

因此,我正在尝试编写我自己的 、 或 版本,以便我可以将它们用于我的语法,该语法也支持基于上述解析器方法的错误恢复,这些解析器永远不会失败,而是发出诊断。customChoicecustomAttemptcustomSkipManyTill

解析 f# fparsec 错误恢复

评论


答:

0赞 Brian Berns 9/11/2023 #1

这是一个有趣的想法。我很想知道结果如何。

我认为您的代码的问题在于您假设解析器返回一个元组,但这是不正确的。诊断实际上存储在流状态中,而不是由分析器返回。我认为您要检查解析器的返回值以确定是否回溯,因此代码可能应该如下所示:(result, diagnostics)

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

请注意,我还没有尝试过这个,所以我不知道它是否有效。祝你好运!

评论

0赞 bookofproofs 9/11/2023
谢谢。它解决了我的 f# 编译器问题,但不是我的核心问题。我需要对此进行更多调查才能跟进。