You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在FParsec中编写自定义'attempt'解析器?

如何在FParsec中编写自定义'attempt'解析器?

嘿,我看了你在写自定义FParsec attempt解析器时遇到的麻烦——作为一个经常用FParsec做语法解析的开发者,我来帮你理清楚问题,再给出适配你需求的解决方案。

先解决你的编译错误

你代码里的核心问题是误解了ParserResult的类型:它不是元组,而是FParsec定义的一个区分联合类型(Discriminated Union),只有Success和Failure两种情况。你直接把p stream的结果解构为(result, diagnostics),这完全不符合它的类型结构,所以编译器才会抛出FS0001错误。

另外结合你的需求——你要处理的是永远不会失败的解析器,也就是p总是返回Success,只是会在自定义状态里携带诊断信息。那我们可以调整逻辑,先保存当前流的状态,运行解析器后根据诊断是否存在来决定是否回滚。

修正后的自定义attempt解析器

这里是适配你需求的代码版本,我会加上注释帮你理解每一步:

open FParsec

// 假设你的类型定义是这样的,根据实际情况调整
type SyntaxNode = 
    | ValidNode of string // 示例:有效语法节点
    | RecoveryNode        // 示例:错误恢复节点
type Diagnostic = { Message: string; Position: Position }
type State = { Diagnostics: Diagnostic list }

let customAttempt (p: Parser<SyntaxNode, State>) : Parser<SyntaxNode, State> =
    fun (stream: CharStream<State>) ->
        // 1. 保存当前的流位置和自定义状态(回滚的基准)
        let originalStreamPos = stream.Position
        let originalState = stream.State
        
        // 2. 运行目标解析器p
        let parseResult = p stream
        
        // 3. 因为你的解析器p永远不会失败,所以直接处理Success分支
        match parseResult with
        | Success(parsedNode, newState, _) ->
            if List.isEmpty newState.Diagnostics then
                // 没有诊断信息,说明解析完全成功,直接返回结果
                Success(parsedNode, newState, originalStreamPos)
            else
                // 有诊断信息,说明解析出了问题——先回滚流的位置
                stream.BacktrackTo originalStreamPos
                // 然后返回原始状态下的结果(你也可以换成RecoveryNode这类错误节点)
                Success(parsedNode, originalState, originalStreamPos)
        | Failure(msg, err, state) ->
            // 按照你的设计,p永远不会走到这里,这里做个兜底处理
            failwith $"Unexpected failure from parser: {msg}"

为什么这个逻辑符合你的需求?

根据你描述的错误恢复策略:让解析器永远返回成功,但通过自定义状态里的Diagnostics列表标记解析问题。这个customAttempt完美适配这个思路:

  • 运行解析器前先“快照”当前的流位置和自定义状态,确保回滚有基准
  • 执行解析器后,检查新状态里的诊断列表:
    • 空列表意味着解析没问题,直接保留结果和新状态
    • 非空列表意味着解析出问题了,回滚流位置到之前的快照,同时恢复自定义状态,返回结果(你可以根据需求把返回的节点换成专门的错误恢复节点)

关于扩展到customChoice/customSkipManyTill的思路

你提到还需要写customChoice、customSkipManyTill这类自定义解析器,思路和customAttempt一致:

  1. 对每个子解析器,先保存当前状态快照
  2. 运行子解析器,检查诊断信息
  3. 如果子解析器的诊断为空,就用它的结果;如果有诊断,回滚状态,尝试下一个子解析器(对应customChoice)或者调整逻辑(对应customSkipManyTill)

备注:内容来源于stack exchange,提问作者bookofproofs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.21 13:54:29