6

I've just started out playing around with FParsec, and I'm now trying to parse strings on the following format

10*0.5 0.25 0.75 3*0.1 0.9

I want 3*0.1, for example, to be expanded into 0.1 0.1 0.1

What I have so far is the following

type UserState = unit
type Parser<'t> = Parser<'t, UserState>

let str s : Parser<_> = pstring s

let float_ws : Parser<_> = pfloat .>> spaces

let product = pipe2 pint32 (str "*" >>. float_ws) (fun x y -> List.init x (fun i -> y)) 

The product parser correctly parsers entries on the format int*float and expands it into a list of floats. However, I'm having trouble coming up with a solution that allows me to parse either int*float or just a float. I would like to do something like

many (product <|> float_ws)

This will of course not work since the return types of the parsers differ. Any ideas on how to make this work? Is it possible to wrap of modify float_ws such that it returns a list with only one float?

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
larsjr
  • 665
  • 7
  • 17

1 Answers1

4

You can make float_ws return a float list by simply adding a |>> List.singleton

let float_ws : Parser<_> = pfloat .>> spaces |>> List.singleton

|>> is just the map function, where you apply some function to the result of one parser and receive a new parser of some new type:

val (|>>): Parser<'a,'u> -> ('a -> 'b) -> Parser<'b,'u>

See: http://www.quanttec.com/fparsec/reference/primitives.html#members.:124::62::62:


Also, since product parser includes an int parser, it will successfully parse a character from the wrong case, this means the parser state will be changed. That means you cannot use the <|> operator on the first parser directly, you must also add attempt so FParsec can return to the original parser state.

let combined = many (attempt product <|> float_ws)
TheInnerLight
  • 12,034
  • 1
  • 29
  • 52
  • Great, thanks, `|>>` is very useful to know about! I do however get an error when combining the two parsers and running it on a test string. My combined parser is `let combined = many (product <|> float_ws_list)` where i have defined `let float_ws_list : Parser<_> = pfloat .>> spaces |>> List.singleton`. The string I test it on is `"3*0.5 0.75"` – larsjr Feb 02 '16 at 12:03
  • I went back and re-read the documentation for the `<|>` operator and realized that if the first parser changes the parser state, it will fail and not try the second parser. I solved by using _attempt_: `let combined = many (attempt product <|> float_ws_list)` – larsjr Feb 02 '16 at 13:13
  • @Chepe I was literally just typing that as your comment appeared :) I've updated the answer to include that info. – TheInnerLight Feb 02 '16 at 13:16