递归 FParsec 表达式中的错误位置

Error position in recursive FParsec expressions

提问人:bookofproofs 提问时间:10/10/2023 更新时间:10/10/2023 访问量:56

问:

我的语法包含以标识符为特征的表达式,该标识符仅可选地后跟带括号的表达式列表。

我的问题是,当错误发生在嵌套表达式中时,Fparsec 将在语法错误上显示“不直观”的位置。

例如,请考虑这个简单的解析器

let x = skipChar 'x' .>> spaces >>% Node.X 
let y = skipChar 'y' .>> spaces >>% Node.Y
let c = skipChar ',' .>> spaces 
let left = pchar '(' .>> spaces 
let right = pchar ')' .>> spaces 
let expr, exprRef = createParserForwardedToRef()
let paramList, paramListRef = createParserForwardedToRef()
let paramTuple = left >>. paramList .>> right 


let xOrY = choice [x ; y] 
let exprWithArgs = xOrY .>>. paramTuple |>> Node.Expr

exprRef.Value <- choice [ attempt exprWithArgs ; xOrY ] 
paramListRef.Value <- sepBy1 expr c

let parser = expr .>> eof
let result = run parser "x(y, y, y(x, y, y(x,y)) )"

printf "\nParsing correct:\n%O" result 

let resultWithError = run parser "x(y, y, y(x, y, y(x,z)) )"
printf "\n\nParsing error:\n%O" resultWithError

程序的输出为:

Parsing correct:
Success: Expr (X, [Y; Y; Expr (Y, [X; Y; Expr (Y, [X; Y])])])

Parsing error:
Failure:
Error in Ln: 1 Col: 2
x(y, y, y(x, y, y(x,z)) )
 ^
Expecting: end of input

我拥有的嵌套表达式越多,就越难找到实际的错误位置。

有没有办法改变我的语法(或使用/配置)FParsec,以便更好地发现错误?我希望,我会得到这样的直观错误消息:

Parsing correct:
Success: Expr (X, [Y; Y; Expr (Y, [X; Y; Expr (Y, [X; Y])])])

Parsing error:
Failure:
Error in Ln: 1 Col: 21
x(y, y, y(x, y, y(x,z)) )
                    ^
Expecting: 'x' or 'y'

错误处理 f# 语法 fparsec

评论


答:

1赞 Brian Berns 10/10/2023 #1

让我们以输入为例。问题出现在 / 解析器中。首先,它尝试解析 ,由于 .解析器回溯(因为 ),并尝试改为解析,这在 .但是下一个 char 不是 ,所以在该位置失败,并显示无用的消息。"y(x,z)"exprexprRefexprWithArgszattemptxOrYy(eofparser

由于从解析开始,我们可以将其分解出来,从而得出以下解决方案:exprWithArgsxOrY

exprRef.Value <-
    parse {
        let! xy = xOrY
        match! opt paramTuple with
            | Some args -> return Node.Expr (xy, args)
            | None -> return xy
    }

这样做的优点是不回溯,因此解析器始终向前移动,直到无法继续,从而产生更清晰的错误消息。请注意,不再使用。exprWithArgs

如果你不喜欢计算构建器,你可以改为这样做:

exprRef.Value <-
    pipe2
        xOrY
        (opt paramTuple)
        (fun xy argsOpt ->
            match argsOpt with
                | Some args -> Node.Expr (xy, args)
                | None -> xy)

现在的输出是:

Parsing correct:
Success: Expr (X, [Y; Y; Expr (Y, [X; Y; Expr (Y, [X; Y])])])

Parsing error:
Failure:
Error in Ln: 1 Col: 21
x(y, y, y(x, y, y(x,z)) )
                    ^
Expecting: 'x' or 'y'

可能还有更简单的解决方案,但我认为这符合您的要求。

评论

1赞 bookofproofs 10/10/2023
这是一个很好的解决方案,谢谢。经过一番试验后,如果您愿意将类型 Node.Expr 更改为 Node *(节点列表)选项,则简单的 exprRef.Value <- xOrY .>>。opt paramTuple |>> Node.Expr 也可以解决问题。它也比我的原始版本快了大约 15%。我猜是因为现在,解析器不需要做任何回溯。