I had used regular expression to process the inputs for Advent of Code 2017 Day 16. I thought it would be a good learning example to try FParsec. My goal was to take the sample strings given, and convert them into a choice type (discriminated unions).
We’ll be processing inputs for three different operations. A sample input looks like this “po/k,x4/0,s12”. There are three operations which need to be parsed. We’ll create a choice type (discriminated union or sum type) to represent our operations.
We would like to parse the inputs accordingly.
"s12" -> Spin of int
"x4/0" -> Exchange of int * int
"po/k" -> Partner of char * char
Parsing the first sample of
"s12", we can use the function below.
let pspin = pchar 's' >>. pint32 |>> Spin
We are matching the first character using the
pchar function. We combine it using the
(>>.) operator with the
pint32 function. This is all followed by
(|>>) into the Spin constructor.
Breaking down the functions:
pchar : char -> Parser<char,'u> attempts to match a character to the one supplied. In this case we are looking for the character ‘s’.
pint32 : Parser<int32,'u> will convert the text to an integer providing it is a legitimate.
(>>.) : Parser<'a,'u> -> Parser<'b,'u> -> Parser<'b,'u> combines 2 parsers and returns the second parser result,
pint32 in our case.
(|>>) : Parser<'a,'u> -> ('a -> 'b) -> Parser<'b,'u> is like a
map function. It unwraps the value in the parser, applies the function supplied, and packs it back into the parser. We take the integer value in the parser and create a
Parsing the Exchange
We can parse “x4/0” and convert it to an Exchange type with the function:
let pexchange =
(pint32 .>> pchar '/')
There are some familiar functions of
Let’s explore the new functions.
tuple2 : Parser<'a,'u> -> Parser<'b,'u> -> Parser<('a * 'b), 'u> takes two separate parsers and packs their results into a tuple.
(.>>) : Parser<'a,'u> -> Parser<'b,'u> -> Parser<'a,'u> similar to what we used earlier, however this one returns the first parser result.
Parsing the Partner
Our final sample “po/k” to convert to a Partner type.
let ppartner =
(asciiLetter .>> pchar '/')
This is fairly similar to the Exchange parser function
pexchange. We did introduce the new function of
asciiLetter. As the function name implies, this parser accepts any letter.
Putting them Together
We have our 3 parser functions, each handling their specific sample input. Now it’s time to combine them into a parser that can handle either sample and return the appropriate choice. We’ll do that using the
let parseOperation = pspin <|> pexchange <|> ppartner
(<|>) operator allows us to define alternative parsers. This can be thought of like a pattern match for parsers.
Parser combinators let us build up our parsing functions by piecing smaller functions together to create the overall parser. We’ve covered quite a few functions in this small example. Regex will likely work as well for this type of example, however I’m sure there are more complex solutions that can be elegantly built with parser combinators.
Full Parser Example