Beginner stuff. Working with FParsec

Hi there, here comes yet another beginner question :slight_smile:

I am playing around with FParsec and trying to parse a file that looks similar to this:

3 -2 10 0
1 2 -4 0
2 0

Each line has some (at least 1) non-zero numbers separated by spaces and always ends with a 0.
My goal is to parse this input to an int list list so that each line is in its own sublist and the trailing 0 is omitted. For example, the above instance should be parsed into
[[3; -2; 10]; [1; 2; -4]; [2]]

I looked around quite a lot on the FParsec website and managed to get the parsing to work. The problem I’m currently having is that I want to add each parsed line to the state so that in the end, the state will contain all the lines [[3; -2; 10]; [1; 2; -4]; [2]] along with he number of lines parsed, 3 in this case. At the moment it only manages to keep track of the number of parsed lines.

I have the following record type to keep track of the lines and the number of lines

type UserState = {
    numLines: int;
    Lines: int list list
}

and the following parsers. As you can see I am running the addLine function whenever I parse a line.

// Parse a number and consume the following whitespace
let pnumber: Parser<int32, UserState> =
    pint32 .>> pchar ' '

// Parse a line
let pline : Parser<int32 list, UserState> =
    manyTill pnumber <| (pchar '0') .>> addLine .>> newline

// Parse multiple lines until end of file
let plines : Parser<int32 list list, UserState> =
    manyTill pline eof

The addLine function is defined as follows

let addLine =
    updateUserState (fun s ->
        { s with
            numLines= s.numLines+ 1
            Lines = s.Lines
        }
    )

Im running this code on the same input example,


let input_string= "3 -2 10 0\n1 2 -4 0\n2 0\n"

// Default state
let defaultState = { Lines=[]; numLines=0 }

match runParserOnString plines defaultState "" input_string with
| Success (result, s, pos) -> printfn "%A %A %A" result s pos
| Failure(msg, error, _)   -> printfn "%s %A" msg error

Running this gives me:

[[3; -2; 10]; [1; 2; -4]; [2]] { numLines= 3
  Lines = []
 } ("", Ln: 4, Col: 1)

So the state keeps track of the number of lines parsed in total, but not their contents…

Any ideas on how I can achieve this? Any help is appreciated!

Can you show updateUserState function?

1 Like

Hi FoggyFinder, thanks for your reply.

The updateUserState function comes with FParsec.

I just found out what I had to change. Basically, I have to use bind as follows:

let pline : Parser<int32 list, UserState> =
    manyTill pnumber (pchar '0') .>> newline >>= addLine

Also, changed the implemenetation of addLine to:

let addLine (line : int list) (stream : CharStream<UserState>) =
    let s = stream.UserState
    stream.UserState <- {s with Lines = line::s.Lines; NumLines= s.NumLines+ 1}
    Reply(line)

This now returns the correct state!

[[3; -2; 10]; [1; 2; -4]; [2]] { 
  Lines= [[2]; [1; 2; -4]; [3; -2; 10]]
  NumLines = 3 }