如何根据 FParsec 解析器中的可变变量添加 if else ?

How to add if then else based on mutable variables in FParsec parsers?

提问人:bookofproofs 提问时间:9/22/2023 最后编辑:bookofproofs 更新时间:9/22/2023 访问量:30

问:

我正在尝试将错误恢复添加到使用括号中的逗号分隔序列的 FParsec 解析器,例如“(a,b,c)”。

我遇到了以下问题:给定两个输入“()”和“(a,b,)”,第一个在我的语法中是正确的,但第二个不是(因为它没有正确结束序列)。虽然我设法在第二个用例中恢复并在那里生成适当的诊断,但我的错误恢复机制在第一种情况下仍然产生了我想避免的误报诊断。

为了区分这两种情况,我的想法是在解析结束 “)” 时“简单地检查”我的序列是否真的是空的。

为了实现这一点,我尝试添加一个可变计数器,并在解析序列时递增它。

我遇到了一个非常奇怪的行为:递增有效,但检查值是否为零或大于零则无效。

经过数小时的调试并试图了解出了什么问题,我编写了一个简单的程序来重现这种奇怪的行为。

open FParsec

type MyClass() = 
    let mutable contextCounter = 0
    member this.Increment() =
        contextCounter <- contextCounter + 1

    member this.Reset() =
        contextCounter <- 0

    member this.GetCounter() = contextCounter

    member this.Print(s:string) =
        printf "\ncurrent value is %i, %s after checking if equals 0" contextCounter s

let x = pchar 'x'  
let y = pchar 'y' 
let z = pchar 'z'
let c = skipChar ','
let ad = MyClass()
let choicexyz (ad:MyClass) = 
    if ad.GetCounter()=0 then
        choice [x;y;z] >>= fun result -> 
            ad.Print("in if block")
            ad.Increment() 
            preturn result
    else
        z >>= fun result -> 
        ad.Print("in else block")
        ad.Increment() 
        preturn result

let parserSepBy (ad:MyClass) = 
    ad.Reset()
    sepBy (choicexyz ad) c

let inputSepBy = "x,x,y,y,z,z"
let resultSepBy = run (parserSepBy ad) inputSepBy
printf "\n%O" resultSepBy

该程序省略了所有不必要的内容,例如左括号和右括号,错误恢复,诊断等。但它再现了我想展示的内容。输出为

current value is 0, in if block after checking if equals 0
current value is 1, in if block after checking if equals 0
current value is 2, in if block after checking if equals 0
current value is 3, in if block after checking if equals 0
current value is 4, in if block after checking if equals 0
current value is 5, in if block after checking if equals 0
Success: ['x'; 'x'; 'y'; 'y'; 'z'; 'z']

为什么增量有效,但检查值是否等于 0 不起作用?

F# 可变 副作用 fparsec

评论


答:

3赞 Martin521 9/22/2023 #1

调用 时,将创建一个解析器函数。此时,计数器被评估并且仍然为零(因为您刚刚重置它)。因此,解析器函数是您在分支中定义的函数。当稍后应用该解析器函数时,它将始终是相同的函数并打印。choicexyzsepBy (choicexyz ad) cifrunin if block

我认为您必须在 fparsec 中使用用户状态才能做您想做的事情。