FParsec:如何从错误消息中忽略“很多”解析器故障

时间:2019-05-24 22:56:10

标签: f# parser-combinators fparsec

考虑将数字字符串转换为int的解析器:

let toInt (s:string) = 
    match Int32.TryParse(s) with
    | (true, n) -> preturn n
    | _         -> fail "Number must be below 2147483648"

let naturalNum = many1Chars digit >>= toInt <?> "natural number"

当我在"abc"之类的非数字字符串上运行它时,它会显示正确的错误消息:

Error in Ln: 1 Col: 1
abc
^
Expecting: natural number

但是当我给它一个超出int范围的数字字符串时,它会给出以下适得其反的消息:

Error in Ln: 1 Col: 17
9999999999999999
                ^
Note: The error occurred at the end of the input stream.
Expecting: decimal digit
Other error messages:
  Number must be below 2147483648

主要消息"Expecting: decimal digit"毫无意义,因为我们已经有很多位数了。

有没有办法摆脱它而只显示"Number must be below 2147483648"


完整示例:

open System
open FParsec

[<EntryPoint>]
let main argv =
    let toInt (s:string) = 
        match Int32.TryParse(s) with
        | (true, n) -> preturn n
        | _         -> fail "Number must be below 2147483648"

    let naturalNum = many1Chars digit >>= toInt <?> "natural number"

    match run naturalNum "9999999999999999" with
    | Failure (msg, _, _) -> printfn "%s" msg
    | Success (a, _, _)   -> printfn "%A" a

    0

2 个答案:

答案 0 :(得分:1)

我认为问题的根源在于,这是一个非语法问题,与超前解析器的模型不太匹配。如果您可以用语法的方式表示“太多数字”,那么对于解析器来说也是有意义的,但是因为这样,它反而会返回并尝试消耗更多的输入。我认为最干净的解决方案因此是在解析后单独进行一次int转换。

也就是说,FParsec似乎足够灵活,您仍然可以将其一起破解。这符合您的要求,我认为:

let naturalNum: Parser<int, _> =
    fun stream ->
        let reply = many1Chars digit stream
        match reply.Status with
            | Ok ->
                match Int32.TryParse(reply.Result) with
                | (true, n) -> Reply(n)
                | _         -> Reply(Error, messageError "Number must be below 2147483648")                
            | _ ->
                Reply(Error, reply.Error)

或者,如果您希望显示“自然数”错误消息而不是“十进制数字”,则将最后一行替换为:

Reply(Error, messageError "Expecting: natural number")

答案 1 :(得分:1)

您看到的效果是序列的第一个解析器成功执行,但还会生成一条错误消息(因为它可能消耗更多的数字)。您的第二个解析器不再使用任何输入,如果失败,FParsec将合并两个已排序解析器(Manual on merging of error messages)的错误消息。

一种解决方案是为解析器创建一个小型包装器,以消除Ok情况下结果中的错误消息。然后,当使用第二个解析器排序时,仅保留第二个解析器的消息。

未经测试的代码来自我的头上

let purify p =
    fun stream ->
        let res = p stream
        match res.Status with
            | Ok -> Reply(res.Result)
            | _ -> res


let naturalNum = purify (many1Chars digit) >>= toInt <?> "natural number"