3

I am trying to tackle the scariest part of programming for me and that is parsing and ASTs. I am working on a trivial example using F# and FParsec. I am wanting to parse a simple series of multiplications. I am only getting the first term back though. Here is what I have so far:

open FParsec

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

type Expr =
| Float of float
| Multiply of Expr * Expr

let parseExpr, impl = createParserForwardedToRef ()

let pNumber = pfloat .>> spaces |>> (Float)
let pMultiply = parseExpr .>> pstring "*" >>. parseExpr
impl := pNumber <|> pMultiply

test parseExpr "2.0 * 3.0 * 4.0 * 5.0"

When I run this I get the following:

> test parseExpr "2.0 * 3.0 * 4.0 * 5.0";;
Success: Float 2.0
val it : unit = ()

My hope was that I get a nested set of multiplications. I feel like I am missing something tremendously obvious.

Matthew Crews
  • 4,105
  • 7
  • 33
  • 57

1 Answers1

7

Parser combinators like FParsec are not equivalent to BNF grammars. The big difference is that when you have an alternative (<|> in FParsec), the cases are tried in order. If the left parser is successful, then it is returned and the right parser isn't tried. If the left parser fails after consuming some input, then the failure is returned and the right parser isn't tried either. It's only if the left parser fails without consuming any input that the right parser is tried. [1]

In your pNumber <|> pMultiply, pNumber is successful and returned immediately without trying to do pMultiply. You might think to fix that by writing pMultiply <|> pNumber instead, but that's not good either: when parsing the last number, pMultiply will fail to find a * after having consumed some input for its parseExpr, so the whole parsing will be marked as failed.

You generally want to use FParsec's combinator functions as much as possible, and in this case the best solution is probably to use chainl1.

let pNumber = pfloat .>> spaces |>> Float
let pTimes = pstring "*" .>> spaces >>% (fun x y -> Multiply (x, y))
let pMultiply = chainl1 pNumber pTimes

If your goal was to learn how to use BNF grammars, you probably want to look at FsLex and FsYacc rather than FParsec.

[1] There's a function attempt that turns a consuming failure into a non-consuming failure, but it should be used as sparingly as possible.

Tarmil
  • 11,177
  • 30
  • 35