Consider this parser that converts digit strings to 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"
When I run it on non-numeric strings like "abc"
it shows the correct error message:
Error in Ln: 1 Col: 1
abc
^
Expecting: natural number
But when I give it a numeric string exceeding the int
range it gives the following counter-productive message:
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
The primary message "Expecting: decimal digit"
makes no sense, because we have to many digits already.
Is there a way to get rid of it and only show "Number must be below 2147483648"
?
Full example:
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
I think the root of the problem here is that this is a non-syntactic concern, which doesn't fit well with the model of a lookahead parser. If you could express "too many digits" in a syntactic way, it would make sense for the parser too, but as it is it will instead go back and try to consume more input. I think the cleanest solution therefore would be to do the int conversion in a separate pass after the parsing.
That said, FParsec seems flexible enough that you should still be able to hack it together. This does what you ask I think:
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)
Or if you want the "natural number" error message instead of "decimal digit", replace the last line with:
Reply(Error, messageError "Expecting: natural number")