3

I'm implementing a parser that implements a specific domain specific language for a project of mine.

One aspect that I'm having difficulty is with making an expression (which is implemented using the OperatorPrecedenceParser from FParsec) such that the whole expression is optional.

I've implemented my parser OPP in much the same way as many of the examples around the net. I've also tried shifting around where the whitespace is consumed for the end of line comment. None of my attempts seem to work in bot cases though (with both the expression and the end of line comment being optional)

Specifically (as implemented in the example below), I am trying to get the following to parse successfully.

KeyValue: expression  # comment
KeyValue:
KeyValue:  # comment

where expression is optional, and there is an optional comment following the expression. "KeyValue:" is hard coded for this example, but in the main parser code I have, it is an identifier.

N.B. For the sample below. I have implemented a minimal expression that parses a simple infix addition of 2 floats. The full parser implements much more.

N.B.2. The expression must have at lease 1 whitespace between the : and the expression. The # comment character does not appear in expressions at all.

How can I make the expression "optional". See the eKeyValue FParsec Parser type below.

The below example contains 6 example cases. All the cases should work. I've tried adding wsBeforeEOL to the end of the terms, having the expression optional (opt), but nothing seems to work. It appears that the OPP always consumes, and never fails.

The output of the below sample program looks like:

Test1 - Failure:
Error in Test1 - No Expression but comment: Ln: 1 Col: 15
  KeyName:    # No Expression but comment
              ^
Expecting: floating-point number or '('

Test2 - Success: Key (Some (Arithmetic (Number 2.0,Plus,Number 2.0)))
Test3 - Success: Key (Some (Arithmetic (Number 3.0,Plus,Number 3.0)))
Test4 - Success: Key (Some (Arithmetic (Number 3.0,Plus,Number 4.0)))
Test5 - Success: Key (Some (Arithmetic (Number 4.0,Plus,Number 4.0)))
Test6 - Success: Key null
Press any key to continue . . .

The sample program is (the above test cases are in the Main() function:

open FParsec


    // Placeholder for state...
    type UserState = 
        {
            dummy: int            
        }
        with
            static member Create() = {dummy = -1}


    type Operator =
        | Plus

    type Expression =
        | Number of float
        | Arithmetic of Expression * Operator * Expression   // Composes 2 primatives

    type Statement =
        | Key of Expression option // Optional expression name


// very simple parsers which handles a simple string on one line.
// White space handling
let isBlank = fun c -> c = ' ' || c = '\t'
let ws1 = skipMany1SatisfyL isBlank "whitespace"
let ws = skipManySatisfy isBlank
let comment = pstring "#" >>. skipRestOfLine false
let wsBeforeEOL = skipManySatisfy isBlank >>. optional comment

// Parse a number
let sNumber = pfloat .>> wsBeforeEOL |>> Number // Test wsExpression ending

// The expression reference
let expression, expressionRef = createParserForwardedToRef()

let expressionFragment = choice [sNumber] //;sBool;sNull;sString;sIdentifier]
let bracketedExpressionFragment = between (pstring "(" .>> ws) (pstring ")" .>> ws) expression

// The parser for addition only
let oppa = new OperatorPrecedenceParser<Expression, unit, UserState>()

let parithmetic = oppa.ExpressionParser

//oppa.TermParser <- (expressionFragment .>> wsBeforeEOL)   <|> (bracketedExpressionFragment .>> wsBeforeEOL)
oppa.TermParser <- choice[expressionFragment;bracketedExpressionFragment]
oppa.AddOperator(InfixOperator("+", ws, 1, Associativity.Left, fun x y -> Arithmetic(x, Plus, y)))
expressionRef := oppa.ExpressionParser


// *** HERE: Define the Key, with optional expression, which must have at lease 1 WS,, then followed by wsBeforeEOL, which is multiple blanks and optional comment.
let eKeyValue = pstringCI "KeyName:" >>. (opt (ws1 >>. expression)) .>> wsBeforeEOL |>> Key

// Define the parser for the whole string...in this case a single line
let htmlProgramParser = spaces >>. eKeyValue .>> spaces .>> eof

// test harnes on a string
let parseHtmlProgramString programName str =
    runParserOnString htmlProgramParser (UserState.Create()) programName str //with

[<EntryPoint>]
let main argv = 
    printfn "%A" argv

    let test1 =
        "  KeyName:    # No Expression but comment"
        |> parseHtmlProgramString "Test1 - No Expression but comment"
        |> printfn "Test1 - %A"

    let test2 =
        "  KeyName:  2+2  # Expression and Comment"
        |> parseHtmlProgramString "Test2 - 2+2 # Expression and Comment"
        |> printfn "Test2 - %A"

    let test3 =
        "  KeyName:  3 + 3  # # Expression and Comment2"
        |> parseHtmlProgramString "Test3 - 3 + 3 # Expression and Comment2 (Spaces)"
        |> printfn "Test3 - %A"

    let test4 =
        "  KeyName:  (3 + 4)  # Bracketed Expression and Comment"
        |> parseHtmlProgramString "Test4 - (3 + 4) # Bracketed Expression and Comment"
        |> printfn "Test4 - %A"

    let test5 =
        "  KeyName:  (4 + 4)  "
        |> parseHtmlProgramString "Test5 - (4 + 4) # Expression + <no comment>"
        |> printfn "Test5 - %A"

    let test6 =
        "  KeyName:"
        |> parseHtmlProgramString "Test6 - <no expression> <no comment>"
        |> printfn "Test6 - %A"

    0 // return an integer exit code
Nathan
  • 35
  • 5

1 Answers1

1

If I understand your question correctly, the problem seems to be that ws1 >>. expression in eKeyValue can fail after consuming whitespace, which then prevents the opt combinator from succeeding.

You could fix this for example by defining eKeyValue like

let eKeyValue = pstringCI "KeyName:" >>. (opt (ws1 >>? expression)) .>> wsBeforeEOL |>> Key

or refactoring it like

let eKeyValue = pstringCI "KeyName:" >>. (ws1 >>. opt expression <|>% None) .>> wsBeforeEOL |>> Key
Stephan Tolksdorf
  • 3,062
  • 21
  • 28
  • That works. I'm quite new to FParsec (and F# in general). I believe that will fix my issues in my larger DSL. Thanks for the prompt help. – Nathan Jun 13 '16 at 11:25
  • I actually though the problem was more with the OPP not terminating correctly. Thanks for pointing out the >>? and <|>% combinators. – Nathan Jun 13 '16 at 11:27