Reputation: 2031
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.
Upvotes: 2
Views: 127
Reputation: 17028
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
Upvotes: 2
Reputation: 2031
Unfortunately I missed the >>=?
operator which apparently is (at least semantically) equivalent to attempt
that Brian Berns correctly suggested.
The problem with both of these approaches is the subsequent messages The parser backtracked after:[…]
that can cascade if the preceding parsers are backtracking as well:
Error in Ln: 156 Col: 29 (UTF16-Col: 22)
Block "main" 116x60 font=default fg=textForeground
^
Note: The column count assumes a tab stop distance of 8 chars.
Expecting: space/tab
The parser backtracked after:
Error in Ln: 156 Col: 42 (UTF16-Col: 35)
Block "main" 116x60 font=default fg=textForeground
^
Note: The column count assumes a tab stop distance of 8 chars.
Expecting: space/tab
Other error messages:
keyword instead of identifier
Upvotes: 1