使用 FParsec,是否可以在解析器失败时操纵错误位置?

With FParsec is it possible to manipulate the error position when a parser fails?

提问人:Stelios Adamantidis 提问时间:3/31/2022 更新时间:3/31/2022 访问量:94

问:

举个例子,我将以 Phillip Trelford 的这个简单的 C# 解析器为例。为了解析一个标识符,他写了这个(略有改动):

let reserved = ["for";"do"; "while";"if";"switch";"case";"default";"break" (*;...*)]
let pidentifierraw =
    let isIdentifierFirstChar c = isLetter c || c = '_'
    let isIdentifierChar c = isLetter c || isDigit c || c = '_'
    many1Satisfy2L isIdentifierFirstChar isIdentifierChar "identifier"
let pidentifier =
    pidentifierraw
    >>= fun s ->
        if reserved |> List.exists ((=) s) then fail "keyword instead of identifier"
        else preturn s

pidentifier 的问题在于,当它失败时,位置指示器位于流的末尾。我的一个例子:

Error in Ln: 156 Col: 41 (UTF16-Col: 34)
        Block "main" 116x60 font=default fg=textForeground
                                        ^
Note: The column count assumes a tab stop distance of 8 chars.
keyword instead of identifier

显然,不是 C# 代码段,但为了示例的缘故,我使用 来解析 后面的文本。是否可以告诉 FParsec 在解析输入的开头显示错误?使用 或任何回溯变体似乎没有效果。pidentifierfont=>>?.>>.?

解析 f# 解析器-组合器 fparsec

评论


答:

1赞 Brian Berns 3/31/2022 #1

我认为您想要的是尝试 p,如果解析器失败,它将回溯到原始解析器状态。因此,您可以将其定义为:ppidentifier

let pidentifier =
    pidentifierraw
    >>= fun s ->
        if reserved |> List.exists ((=) s) then fail "keyword instead of identifier"
        else preturn s
    |> attempt   // rollback on failure

输出如下所示:

Failure:
Error in Ln: 1 Col: 1
default
^

The parser backtracked after:
  Error in Ln: 1 Col: 8
  default
         ^
  Note: The error occurred at the end of the input stream.
  keyword instead of identifier

更新

如果不想在错误消息中看到回溯信息,可以使用简化版本,如下所示:attempt

let attempt (parser : Parser<_, _>) : Parser<_, _> =
    fun stream ->
        let mutable state = CharStreamState(stream)
        let reply = parser stream
        if reply.Status <> Ok then
            stream.BacktrackTo(&state)
        reply

输出现在只是:

Failure:
Error in Ln: 1 Col: 1
default
^
keyword instead of identifier

评论

0赞 Brian Berns 3/31/2022
听到这个消息,我感到很惊讶。它对我来说是正确的。我已经用我看到的输出更新了我的答案。
0赞 Brian Berns 3/31/2022
是的,我认为是有效的加.就我个人而言,我总是使用 ,因为它更通用且更容易记住。(FWIW,我也使用计算构建器而不是手动编写绑定。>>=?>>=attemptattemptparse>>=
0赞 Brian Berns 3/31/2022
我已经更新了我的答案,以包含一个简化版本,该版本不包括输出错误消息中的回溯信息。attempt
0赞 Stelios Adamantidis 4/4/2022
是的,就是这样。我把答案给了你——反正这是你的,给了我的大脑食物,让我开始思考:)。我认为同样,我可以更改不那么冗长的内容。PS 我会在某个时候删除我的旧评论,因为它们不再有用了。>>=?
1赞 Stelios Adamantidis 3/31/2022 #2

不幸的是,我错过了 >>=? 运算符,它显然(至少在语义上)等同于 Brian Berns 正确建议的运算符。attempt

这两种方法的问题在于,如果前面的解析器也在回溯,则后续消息可能会级联:The parser backtracked after:[…]

Error in Ln: 156 Col: 29 (UTF16-Col: 22)
        Block "main" 116x60 font=default fg=textForeground
                            ^
Note: The column count assumes a tab stop distance of 8 chars.
Expecting: space/tab

The parser backtracked after:
  Error in Ln: 156 Col: 42 (UTF16-Col: 35)
          Block "main" 116x60 font=default fg=textForeground
                                           ^
  Note: The column count assumes a tab stop distance of 8 chars.
  Expecting: space/tab
  Other error messages:
    keyword instead of identifier

评论

0赞 Brian Berns 3/31/2022
是的,回溯信息可能在错误消息中很重要。不幸的是,我认为没有一种简单的方法可以删除它。