FParsec:如何从错误消息中省略“许多”解析器失败
FParsec: how to omit `many` parser failures from error messages
考虑将数字字符串转换为 int
s 的解析器:
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
我认为这里问题的根源在于这是一个 non-syntactic 问题,它与前瞻解析器的模型不太相符。如果您可以用句法方式表达 "too many digits",那么它对解析器也有意义,但实际上它会返回并尝试消耗更多输入。因此,我认为最干净的解决方案是在解析后的单独传递中进行 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)
或者如果您想要 "natural number" 错误消息而不是 "decimal digit",请将最后一行替换为:
Reply(Error, messageError "Expecting: natural number")
您看到的效果是您的序列的第一个解析器成功了,但也生成了一条错误消息(因为它可能会消耗更多的数字)。您的第二个解析器不消耗进一步的输入,如果它失败,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"
考虑将数字字符串转换为 int
s 的解析器:
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
我认为这里问题的根源在于这是一个 non-syntactic 问题,它与前瞻解析器的模型不太相符。如果您可以用句法方式表达 "too many digits",那么它对解析器也有意义,但实际上它会返回并尝试消耗更多输入。因此,我认为最干净的解决方案是在解析后的单独传递中进行 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)
或者如果您想要 "natural number" 错误消息而不是 "decimal digit",请将最后一行替换为:
Reply(Error, messageError "Expecting: natural number")
您看到的效果是您的序列的第一个解析器成功了,但也生成了一条错误消息(因为它可能会消耗更多的数字)。您的第二个解析器不消耗进一步的输入,如果它失败,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"