Search code examples
functional-programmingf#fparsec

F# how to define "recursive" variables


I'm writing a parser for a pseudolanguage using of the FParsec library, I have a statement parser, which is a choice between all posible statements a block parser, which parses a series of statements until an "end" keyword and now I want to write a "loop" construct, the problem is, the loop itself is a statement, and contains a block this led to a recursive form of definition which F# didn't like I had written a parser in C# a while ago, and getting through this was easy since each parser was a function, and they can call each other independent of wether they're defined before or after

So I want to know how I could solve this in F#

here is the parsers mentioned:

// parses a list of statements, separated by 1 or more newlines, (pre|post)fixed by any amount of whitespace, and finishes parsing upon reaching an "end" keyword
let block endtag = many1Till (statement .>> nl) (skipString endtag) // at this moment, statement is undefined

// parses a loop structure; the keyword loop, followed by an identifier for the iteration variable, followed by an expression which evaluates to the amount iterations which should be executed, followed by a block closed with "endloop"
let loop = skipString "loop" >>. ws1 >>. id .>> ws1 .>>. expr .>> nl .>>. block "endloop" |>> fun ((i, n), s) -> Loop (i, n, s)

// parses any statement, pre or post fixed by any amount of whitespace
let statement = spaces >>. choice [writeline; write; comment; definition; loop; sleep; assignment] .>> spaces

Solution

  • FParsec supports recursive parsers through createParserForwardedToRef. The JSON parser described in the tutorial shows how to use it. Here's an excerpt of the relevant info:

    The grammar rules for JSON lists and objects are recursive, because any list or object can contain itself any kind of JSON value. Hence, in order to write parsers for the list and object grammar rules, we need a way to refer to the parser for any kind of JSON value, even though we haven’t yet constructed this parser. Like it is so often in computing, we can solve this problem by introducing an extra indirection:

    let jvalue, jvalueRef = createParserForwardedToRef<Json, unit>()

    As you might have guessed from the name, createParserForwardedToRef creates a parser (jvalue) that forwards all invocations to the parser in a reference cell (jvalueRef). Initially, the reference cell holds a dummy parser, but since the reference cell is mutable, we can later replace the dummy parser with the actual value parser, once we have finished constructing it.