As an example, I will take this simple C# parser by Phillip Trelford. In order to parse an identifier he writes this (slightly changed):
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
The problem with pidentifier is that when it fails, the position indicator is at the end of the stream. An example of mine:
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
Obviously, not a C# snippet, but for the example's sake, I've used the pidentifier
to parse the text after font=
. Is it possible to tell FParsec to show the error at the beginning of the parsed input? Using >>?
, .>>.?
or any of the backtracking variants seems to have no effect.
I think what you want is attempt p
, which will backtrack to the original parser state if parser p
fails. So you could just define pidentifier
as:
let pidentifier =
pidentifierraw
>>= fun s ->
if reserved |> List.exists ((=) s) then fail "keyword instead of identifier"
else preturn s
|> attempt // rollback on failure
Output is then something like:
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
If you don't want to see the backtracking info in the error message, you can use a simplified version of attempt
, like this:
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
Output is now just:
Failure:
Error in Ln: 1 Col: 1
default
^
keyword instead of identifier