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

问题描述 投票:0回答:1

这与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. 创建与语法的所有可能错误上下文相匹配的自定义错误消息是不可能的:在简单示例
    p3
    p4
    中,语法需要
    variable
    ,而在
    p5
    p6
    中,语法需要
    variable
    number
    p3
    p5
    输出正确的 FParsec 错误消息,而
    p4
    p6
    输出在
    p4
    上下文中(只是偶然)正确的自定义错误消息。
  2. 如果
    fun s
    是关键字,则 lambda 函数
    s
    会失败,会消耗关键字的输入,因此错误位置会转移到 放错位置的关键字之后的第一个字符。这个位置与错误无关,因为实际上,错误应该发生在使用关键字之前,而不是之后。在我看来,错误的输入永远不应该被解析器消耗,即使它失败了。
  3. 有没有一种方法可以编写一个 FParsec 解析器,它可以正确识别输入中放错位置的关键字,并且仍然保留原始的 FPArsec 错误消息和位置?

f# keyword identifier fparsec
1个回答
0
投票
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

© www.soinside.com 2019 - 2024. All rights reserved.