In today’s Programming Praxis exercise our task to write a parser for simple mathematical expressions. Let’s get started, shall we?
Some imports:
import Control.Applicative import Text.Parsec hiding ((<|>))
We’re going to be using a somewhat different approach than the provided Scheme solution. Rather than doing everything ourselves, we will use the Parsec library, which is the go-to solution for writing parsers in Haskell. This, however, results in a small limitation. Parsec is a parser combinator library, and parser combinators cannot deal with left-recursive grammars. The grammar in the assignment is left-recursive, because if we were to enter the rule expr = expr + term (as I did in my first attempt in solving this), our program will enter an infinite loop. Fortunately, left-recursive grammars can be fairly easily rewritten using the chain functions in Parsec.
An expression is one or more terms, separated by addition and subtraction operators.
expr = chainl1 term ((+) <$ char '+' <|> (-) <$ char '-')
A term works just like an expression, but with multiplication and divison.
term = chainl1 fact ((*) <$ char '*' <|> div <$ char '/')
Factors need no change from the specified grammar: they’re either numbers or expressions in parentheses.
fact = read <$> many1 digit <|> char '(' *> expr <* char ')'
Evaluating an expression is trivial. The only extra step is to filter out all the spaces, since they have not been defined in the grammar but are present in the test cases.
eval :: String -> Int eval = either (error . show) id . parse expr "" . filter (/= ' ')
A quick test shows that our rewritten grammar passes all of the test cases correctly:
main :: IO () main = mapM_ print [eval "6+2" == 8 ,eval "6-2" == 4 ,eval "6*2" == 12 ,eval "6/2" == 3 ,eval "6 * 2" == 12 ,eval "2+3*4" == 14 ,eval "2*3+4" == 10 ,eval "2+3+4" == 9 ,eval "2-3-4" == -5 ,eval "2*3*4" == 24 ,eval "(2+3)*4" == 20 ,eval "(2*3)+4" == 10 ,eval "2+(3*4)" == 14 ,eval "2*(3+4)" == 14 ,eval "12 * (34 + 56)" == 1080 ]
Using a parser library and slightly rewriting the grammar reduces the amount of required lines from 48 to 4. That’s a good trade-off in my book.
Tags: bonsai, code, evaluation, expression, Haskell, kata, parsec, parser, praxis, programming
April 19, 2010 at 1:59 pm |
Can I ask what do you use for code listing? I mean, what do you use for syntax highlighting and other things in the code snippets posted?
April 19, 2010 at 2:02 pm |
I use a program called Highlight (http://www.andre-simon.de/). It produces HTML code, which I paste in my post. Not as elegant as a plugin, but since you can’t install any plugins on a wordpress.com blog it’ll have to do.
April 19, 2010 at 3:24 pm |
Thanks. I’ll try to use it too