这与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
已被修改为在使用的输入是某个关键字时失败。
注意这种方法的缺点:
p3
和 p4
中,语法需要 variable
,而在 p5
和 p6
中,语法需要 variable
或 number
。 p3
和 p5
输出正确的 FParsec 错误消息,而 p4
和 p6
输出在 p4
上下文中(只是偶然)正确的自定义错误消息。fun s
是关键字,则 lambda 函数 s
会失败,会消耗关键字的输入,因此错误位置会转移到 放错位置的关键字之后的第一个字符。这个位置与错误无关,因为实际上,错误应该发生在使用关键字之前,而不是之后。在我看来,错误的输入永远不应该被解析器消耗,即使它失败了。
描述了一个名为 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