Search code examples
f#fparsec

How to map/bind FParsec userstate?


I want to thread some state through a parser but I don't want to have a single type for state for all parts of the parser, because some types just don't make sense in some parts of the parser, but are needed in others. I could make a bigger more complicated state type with either optional values or a discriminated union, but I think that is ugly.

So I want to be able to map a function to the state of the parser.

Concretely, I want a function with the following signature

stateMap: (f:'a->'b) (p:Parser<'x,'a>) -> Parser<'x,'b>

Does such a function or operator exist in FParsec? If not, what is the idiomatic way to create it?


Solution

  • From checking the source code my impression is that there's no such method today and it's not that easy to implement. Parser<_> is defined liked this:

    type Parser<'Result, 'UserState> = CharStream<'UserState> -> Reply<'Result>
    

    If there's a way to map CharStream<'a> to CharStream<'b> then we would reach the goal.

    However investigating the source for CharStream<_> reveals some problems:

    1. There's no map for CharStream<_>.
    2. CharStream<_> is IDisposable which implies FParsec creates one instance of the CharStream<_> at the start of parsing and this is assumed to trace through the whole parsing process. Creating a new CharStream<_> based on an existing one and use that instead don't seem to match the design.
    3. CharStream<_> inherits CharStream - So CharStream seems to do must heavy lifting when it comes to paging and CharStream<_> is just there to pair the stream with the user state. If CharStream<_> used a composition over inheritance we could create a new CharStream<_> that still used the already existing CharStream but with inheritance that's not possible. My guess is that inheritance is chosen here to avoid an extra deref and thus save a few clockcycles (performance is important for parsers).

    So I think the idea of composite user states sounds interesting but from what I can tell this is not supported by FParsec right now.