3

I am currently learning the FParsec library, but I have come across an issue. When I want to parse an optional string and continue parsing as normal afterwards, FParsec will return a fatal error on the optional parser, rather than returning None as I expect. The below working code sample illustrates my point:

open System
open FParsec

type AccountEntity = 
    | Default 
    | Entity of string

let pEntity =
    let isEntityFirstChar c = isLetter c
    let isEntityChar c = isLetter c || isDigit c
    (many1Satisfy2L isEntityFirstChar isEntityChar "entity") .>> skipString "/"

let pOptEntity =
     opt pEntity
     |>> (fun optEntity -> 
              match optEntity with 
              | Some entity -> Entity entity 
              | None -> Default)

[<EntryPoint>]
let main argv = 
    printfn "%A" (run pOptEntity "test/account:subaccount") //works
    printfn "%A" (run pOptEntity "account:subaccount") //crashes
    Console.ReadLine() |> ignore
    0 // return an integer exit code

The behavior I would expect is for pOptEntity to return a Default entity when an entity is not provided. However, instead, I get the following error:

Failure:
Error in Ln: 1 Col: 8
account:subaccount
       ^
Expecting: '/'

Shouldn't opt provide the behavior I am describing and continue to parse the account string as normal or am I approaching this in the incorrect manner? I took a look at attempt but, then, I wouldn't be able to provide the default entity behavior like I want.

Your help is much appreciated, thank you.

Chris Altig
  • 680
  • 3
  • 8
  • 22

1 Answers1

3

The opt combinator follows the same rules as <|>; if you look at the <|> documentation, it mentions that if the first parser fails without changing the parser state, the second parser is attempted. http://www.quanttec.com/fparsec/users-guide/parsing-alternatives.html goes into more detail.

Here, the .>>? combinator is what you want to use in your pEntity parser. Replace the .>> with .>>? and you'll have a pEntity parser that, if it is not followed by a /, will backtrack to the beginning of what it attempted and not consume input. This will allow the opt combinator to function as designed.

P.S. I tested this, and it worked. Replacing the .>> with .>>? in pEntity and then running your code produced the following output:

Success: Entity "test"
Success: Default
rmunn
  • 34,942
  • 10
  • 74
  • 105
  • So it will only attempt the next bit if the parser has not consumed any input and it has been instructed to backtrack? – Chris Altig Oct 10 '17 at 19:30
  • I'm not clear on which combinator you mean when you say "it" in that question. If you mean the `<|>` combinator, then delete the phrase "and it has been instructed to backtrack" and the answer would be "yes". The `<|>` combinator will only attempt the next bit if the first parser failed *without consuming input*. Whether the first parser failed immediately, or tried something and then backtracked, is irrelevant from the `<|>` combinator's point of view. All that `<|>` knows is "I tried the first parser, and the stream is still in the same state". I'll address `.>>?` in my next comment. – rmunn Oct 11 '17 at 02:14
  • If the word "it" in your question means the `.>>?` combinator, then the answer is "no". The `.>>?` combinator will first attempt the next bit, and if the next bit fails, it will backtrack to the parser state *before the first bit was attempted*. The other combinator variants that end in `?` all follow similar rules: they all attempt the second bit, then if it fails, backtrack to the state before the first bit was attempted. – rmunn Oct 11 '17 at 02:17
  • And finally, if you were asking about the `opt` combinator's behavior, then it follows the same rules as `<|>`, except that `opt` doesn't take a "next bit". It's like `<|>` where the "next bit" is "return `None` as the successful result of this parser". – rmunn Oct 11 '17 at 02:19