如何正确处理 FParsec 中放错位置的关键字,保留 FParsec 的 inbuit 错误消息和位置?

How to correctly handle misplaced keywords in FParsec preserving FParsec's inbuit error messages and positions?

提问人:bookofproofs 提问时间:10/21/2023 最后编辑:bookofproofs 更新时间:10/21/2023 访问量:37

问:

这与 FParsec 标识符与关键字有点重复,但那里的答案对我的情况没有帮助。

为编程语言编写解析器时的一个常见问题是处理关键字在代码中放错位置时发生的语法错误。

我在 FParsec 中看到了解决这个问题的两种方法,但没有一种方法真正令人满意。

方法 1:编写一个语法,允许在关键字和其他可能发生冲突的文字之间进行选择(这是上述重复项的公认解决方案)。

在我的语法中,有些上下文中关键字从未出现过。因此,重写我的语法以允许在这些上下文中在文字和关键字之间进行选择并不是一个真正的选择。否则,我将允许我的解析器接受实际上是语法错误的输入。

方法 2:识别可能与关键字冲突的文本,如果它们使用关键字的输入,则让其解析器失败。

我想在以下示例中演示这种方法有两个负面的副作用:


open FParsec

type Ast = 
    | Var of string
    | Number of string
    | Assignment of Ast * Ast

let assign = skipString ":=" >>. spaces

let number = regex "\d+" <?> "number" |>> Ast.Number


let var = 
    regex "[a-z]\w*" 
    .>> spaces 
    <?> "variable" 
    >>= (fun s -> 
        if List.contains s ["for"; "if"; "else"] then // check if s is a keyword
            fail "variable expected" // fail if s is a keyword
        else
            preturn (Ast.Var s) // return Ast.Var s otherwise
        )


let varOrNumber = choice [ var ; number ] 

let assignment = (var .>> assign) .>>. varOrNumber |>> Ast.Assignment

let parser = assignment


let p1 = run parser "x := y"
printfn "%O" p1

let p2 = run parser "x := 42"
printfn "%O" p2

printfn "---------"
let p3 = run parser "# := y"
printfn "failure p3 (correct message and position)\n%O" p3

printfn "---------"
let p4 = run parser "for := y"
printfn "failure p4 (wrong message and position)\n%O" p4

printfn "---------"
let p5 = run parser "x := #"
printfn "failure p5 (correct message and position)\n%O" p5

printfn "---------"
let p6 = run parser "x := for"
printfn "failure p6 (wrong message and position)\n%O" p6

这将输出

Success: Assignment (Var "x", Var "y")
Success: Assignment (Var "x", Number "42")
---------
failure p3 (correct message and position)
Failure:
Error in Ln: 1 Col: 1
# := y
^
Expecting: variable

---------
failure p4 (wrong message and position)
Failure:
Error in Ln: 1 Col: 5
for := y
    ^
variable expected

---------
failure p5 (correct message and position)
Failure:
Error in Ln: 1 Col: 6
x := #
     ^
Expecting: number or variable

---------
failure p6 (wrong message and position)
Failure:
Error in Ln: 1 Col: 9
x := for
        ^
Note: The error occurred at the end of the input stream.
variable expected

在此示例中,如果使用的输入是某个关键字,则解析器已修改为失败。var

请注意此方法的缺点:

  1. 不可能创建与语法的每个可能的错误上下文相匹配的自定义错误消息:在简单的示例 和 中,语法需要 a ,而 in 和 ,语法需要 a 或 a 。while 并输出正确的 FParsec 错误消息,并输出在 的上下文中(只是偶然)正确的自定义错误消息。p3p4variablep5p6variablenumberp3p5p4p6p4
  2. 如果是关键字,则 lambda 函数失败会消耗关键字的输入,因此错误的位置将移动到错位关键字之后的第一个字符。此位置与错误无关,因为实际上,错误应该在使用关键字之前发生,而不是之后发生。在我看来,错误的输入永远不应该被解析器使用,即使它失败了。fun ss

有没有办法编写一个 FParsec 解析器,该解析器可以正确识别输入中放错位置的关键字,并且仍然保留原始的 FPArsec 错误消息和位置?

F# 关键字 标识符 fparsec

评论


答:

2赞 Brian Berns 10/21/2023 #1

FParsec 文档描述了一个方便的解析器,称为它以您想要的方式回溯:resultSatisfies

let resultSatisfies predicate msg (p: Parser<_,_>) : Parser<_,_> =
    let error = messageError msg
    fun stream ->
      let state = stream.State
      let reply = p stream
      if reply.Status <> Ok || predicate reply.Result then reply
      else
          stream.BacktrackTo(state) // backtrack to beginning
          Reply(Error, error)

就你而言,我认为你可以这样使用它:

let var = 
    regex "[a-z]\w*" 
    .>> spaces 
    <?> "variable"
    |> resultSatisfies (fun s ->
        List.contains s ["for"; "if"; "else"] |> not) "variable expected"
    |>> Ast.Var

我没有尝试根据上下文改变错误消息。我认为这是你必须自己努力的事情。

输出为:

Success: Assignment (Var "x", Var "y")
Success: Assignment (Var "x", Number "42")
---------
failure p3 (correct message and position)
Failure:
Error in Ln: 1 Col: 1
# := y
^
Expecting: variable

---------
failure p4 (wrong message and position)
Failure:
Error in Ln: 1 Col: 1
for := y
^
variable expected

---------
failure p5 (correct message and position)
Failure:
Error in Ln: 1 Col: 6
x := #
     ^
Expecting: number or variable

---------
failure p6 (wrong message and position)
Failure:
Error in Ln: 1 Col: 6
x := for
     ^
Expecting: number
Other error messages:
  variable expected

评论

0赞 bookofproofs 10/21/2023
哇,这正是我想要的。谢谢。
0赞 Brian Berns 10/22/2023
很乐意帮忙。祝你的项目好运!