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.
CodePudding user response:
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
CodePudding user response:
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
Update
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