Monadic Parser
Parser
A parser is a function that takes a string as input, then parses the string, and gives back an output.
However, the process may fail as the input string may not be a valid input for the parser. To describe the result, here we use a list [a]
to represent the result, where a
is the type of the output, and when parsing fails, we get an empty list.
1 | newtype Parser a = Parser { parse :: String -> [(a, String)] } |
Now we begin with “atoms” of parser, which consumes the smallest possible input, i.e. a char:
1 | item :: Parser Char |
Here we want to compose parsers, so declaring Parser
as a Functor
, Applicative
, and even Monad
is necessary:
1 | instance Functor Parser where |
By virtue of the do
notation, we can now compose parsers easily:
1 | three = do |
The remarkable thing is that the Monad
instance of Parser
allows what ,In contract, when a computation is lifted by Applicative
, the type can’t be changed, so any middle operation cannot change the type of the computation.
Alternative
You may notice that we were just doing some linear parsing in the previous section. Now we want to make choices in parsing, so we need to define Alternative
:
1 | -- import Control.Applicative |
The class Alternative
is a subclass of Applicative
, and it is used to represent choice, by the operand <|>
. To ensure what we implement really stands for making choices, we need to obey the following laws, which shows the essence of choice:
1 | Associativity: x <|> (y <|> z) = (x <|> y) <|> z |
Making many
& some
choices
The concept is a bit like while
and do-while
, we repeat the computation until it fails, and we get the result.
1 | -- many : Zero or more. |
Their diffecence is that some
must succeed at least once, while many
may fail at the beginning. We use examples to clarify the difference:
1 | > many Nothing |
Then the Parser
instance serves as a perfect example of Alternative
:
1 | instance Alternative Parser where |
Now we can use <|>
to make choices in parsing:
1 | > parse (item <|> return 'd') "abc" |
Get down to Parsers!
We often want to make judgments on the input string, so we need to define some predicates:
1 | -- Whether the next chat satisfies the predicate. |
Then we can define some parsers that consume specific chars and strings:
1 | char :: Char -> Parser Char |
Using many
, we also have something that consumes multiple spaces, processes natural numbers:
1 | space :: Parser () |
When using spaces to separate different parts of the string, we can use token
to consume the spaces:
1 | token :: Parser a -> Parser a |
Arithmetic Expressions
Now we can define a parser for arithmetic expressions. Given the properties of the operators, we can describe the wanted syntax as the following context free grammar:
1 | expr ::= term '+' expr | term |
Once we have the grammar, we can easily translate it into Haskell code:
1 | expr :: Parser Int |
Finally it’s time to parse and evaluate the final value!
1 | eval :: String -> Int |