Search code examples
f#fparsec

Parsing an optionally-multi-line expression with FParsec


I'm writing an FParsec parser for strings in this form:

do[ n times]([ action] | \n([action]\n)*endDo)

in other words this is a "do" statement with an optional time quantifier, and either a single "action" statement or a list of "action"s (each on a new line) with an "end do" at the end (I omitted indentations/trailing space handling for simplicity).

These are examples of valid inputs:

do action

do 3 times action

do
endDo

do 3 times
endDo

do
action
action
endDo

do 3 times
action
action
endDo

This does not look very complicated, but:

Why does this not work?

let statement = pstring "action"
let beginDo = pstring "do"
                >>. opt (spaces1 >>. pint32 .>> spaces1 .>> pstring "times")
let inlineDo = tuple2 beginDo (spaces >>. statement |>> fun w -> [w])
let expandedDo = (tuple2 (beginDo .>> newline)
                    (many (statement .>> newline)))
                 .>> pstring "endDo"
let doExpression = (expandedDo <|> inlineDo)

What is a correct parser for this expression?


Solution

  • You need to use the attempt function. I just modified your beginDo and doExpression functions.

    This is the code:

    let statement  o=o|>  pstring "action"
    
    let beginDo o= 
        attempt (pstring "do"
            >>. opt (spaces1 >>. pint32 .>> spaces1 .>> pstring "times")) <|> 
            (pstring "do" >>% None)                                       <|o
    
    let inlineDo   o= tuple2 beginDo (spaces >>. statement |>> fun w -> [w]) <|o
    let expandedDo o= (tuple2 (beginDo .>> newline) (many (statement .>> newline)))
                     .>> pstring "endDo" <|o
    
    let doExpression o= ((attempt expandedDo) <|> inlineDo) .>> eof <|o
    

    I added an eof at the end. This way it will be easier to test.

    I added also dummy o parameters to avoid the value restriction.