Search code examples
f#mutableside-effectsfparsec

How to add if then else based on mutable variables in FParsec parsers?


I'm trying to add error recovery to an FParsec parser that consumes a comma-separated sequence in parentheses, e.g. "(a,b,c)".

I came across the following problem: Given the two inputs "()" and "(a,b,)", the first is correct in my syntax, but the second is not (since it does not end the sequence properly). While I managed to recover in the second use case and produce appropriate diagnostics there, my error recovery mechanism still produced a false positive diagnostic in the first case which I wanted to avoid.

To distinguish these two cases, my idea was to "simply check" if my sequence was actually empty when I parse the closing ")".

To accomplish this, I tried to add a mutable counter and increment it while parsing the sequence.

I came across a very strange behavior: The incrementing works, but checking if the value is zero or greater than zero does not.

After hours of debugging and trying to understand what was going wrong, I wrote a simple program to reproduce the strange behavior.

open FParsec

type MyClass() = 
    let mutable contextCounter = 0
    member this.Increment() =
        contextCounter <- contextCounter + 1

    member this.Reset() =
        contextCounter <- 0

    member this.GetCounter() = contextCounter

    member this.Print(s:string) =
        printf "\ncurrent value is %i, %s after checking if equals 0" contextCounter s

let x = pchar 'x'  
let y = pchar 'y' 
let z = pchar 'z'
let c = skipChar ','
let ad = MyClass()
let choicexyz (ad:MyClass) = 
    if ad.GetCounter()=0 then
        choice [x;y;z] >>= fun result -> 
            ad.Print("in if block")
            ad.Increment() 
            preturn result
    else
        z >>= fun result -> 
        ad.Print("in else block")
        ad.Increment() 
        preturn result

let parserSepBy (ad:MyClass) = 
    ad.Reset()
    sepBy (choicexyz ad) c

let inputSepBy = "x,x,y,y,z,z"
let resultSepBy = run (parserSepBy ad) inputSepBy
printf "\n%O" resultSepBy

This program leaves out all the unnecessary stuff like the opening and closing parentheses, error recovery, diagnostics, etc. But it reproduces what I want to demonstrate. The output is

current value is 0, in if block after checking if equals 0
current value is 1, in if block after checking if equals 0
current value is 2, in if block after checking if equals 0
current value is 3, in if block after checking if equals 0
current value is 4, in if block after checking if equals 0
current value is 5, in if block after checking if equals 0
Success: ['x'; 'x'; 'y'; 'y'; 'z'; 'z']

Why does the increment work, but checking if the value is equal to 0 doesn't?


Solution

  • When you call choicexyz in sepBy (choicexyz ad) c, you create a parser function. At that point the counter is evaluated and still zero (as you just reset it). So the parser function is what you define in the if branch. When then later that parser function is applied by run, it will always be that same function and print in if block.

    I think you have to work with user state in fparsec to do what you want to do.