jcc333
jcc333

Reputation: 769

FParsec parsers generic to UserState type

So. I have a set of parsers that I'd like to keep generic to user state because they don't need that information right now. The default seems to be a Parser<'a, obj>, which is unspecific, but largely fine I suppose. However, in tests, I'd like to be able to use CharParsers.run, which expects a Parser<'a, unit>. How can I create a tree of parsers all of which share genericity over the UserState type in FParsec, ideally without making each a function of type <'a> |= unit -> Parser<SomeType, 'a>, but if that's what I need to do, then that's what I need to do.

Upvotes: 1

Views: 253

Answers (1)

rmunn
rmunn

Reputation: 36708

FParsec's CharParsers.run function is just shorthand for calling CharParsers.runParserOnString with () as the user state, and "" as the stream name. So just don't specify your user state when you define your parsers, and let F#'s type inference calculate it for you. Then use CharParsers.run for your testing for as long as you want, and F# will automatically infer that your user state type is unit. Then once you need to introduce some actual user state, just switch to using CharParsers.runParserOnString and passing it your initial state.

Maybe an example will help. Let's say you need to parse a list of integers separated by spaces, and surrounded by brackets. So you write your basic parser code:

let pListContents = many (pint32 .>> spaces)
let pList = pchar '[' >>. spaces >>. pListContents .>> pchar ']'

let test p str =
    match run p str with
    | Success(result, _, _)   -> printfn "Success: %A" result
    | Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg

test pList "[1 2 3]"

Now you work for a while adding features to your parser, and eventually you figure out that you want to keep some user state. Maybe you want to store the largest number found so far, or the longest list, or something. So you change your parsers to use user state (I won't give a detailed example since it sounds like you already know how to use user state in parsers), and now your test function no longer compiles since it's expecting the user state to be of type unit (and you've made it int or int list or something). No problem; just change your test function as follows:

let test p initState str =
    match runParserOnString p initState "" str with
    // ... the rest of the test function remains unchanged ...

test pList [] "[1 2 3]"

That should be all you have to do. Just don't specify the types of your parser functions and let F#'s type inference do all the work for you, and you should be fine.

Upvotes: 1

Related Questions