A nice little calculator implemented in Haskell and Parsec February 1, 2009
Posted by haskelladdict in Haskell.Tags: bison, calculator, Haskell, parsec, parser, token, yacc
trackback
Last week I spent a bit of time refreshing my memory on how to write a parser using flex and bison. The standard test case was, of course, the obligatory simple calculator. This turned out to be so much fun that I decided to implement a similar calculator in Haskell using Parsec. Familiarizing myself with the latter had long been on my to-do list anyways. After struggling a bit with comprehending what is going on in Text.ParserCombinators.Parsec.Token, the resulting calculator code turned out to be quite nice (I found), concise and actually quite a bit more powerful than my original yacc/bison implementation. Here’s what I came up with:
-- imports import Text.ParserCombinators.Parsec import qualified Text.ParserCombinators.Parsec.Token as PT import Text.ParserCombinators.Parsec.Language (emptyDef) -- | main main :: IO () main = do -- get a line from stdin input <- getLine -- parse it case parse parse_calc "" input of Left er -> putStrLn $ "Error: " ++ (show er) Right cl -> putStrLn (show cl) main -- | function generating a token parser based on a -- lexical parsers combined with a language record definition lexer :: PT.TokenParser st lexer = PT.makeTokenParser emptyDef -- | token parser for parenthesis parens :: CharParser st a -> CharParser st a parens = PT.parens lexer -- | token parser for Integer integer :: CharParser st Integer integer = PT.integer lexer -- | token parser for Double float :: CharParser st Double float = PT.float lexer -- | token parser for Either Integer Double naturalOrFloat :: CharParser st (Either Integer Double) naturalOrFloat = PT.naturalOrFloat lexer -- | token parser for keywords reservedOp :: String -> CharParser st () reservedOp = PT.reservedOp lexer -- | helper function for defining real powers real_exp :: Double -> Double -> Double real_exp a x = exp $ x * log a -- | grammar description for parser parse_calc :: CharParser () Double parse_calc = mul_term `chainl1` add_action mul_term :: CharParser () Double mul_term = exp_term `chainl1` multiply_action exp_term :: CharParser () Double exp_term = factor `chainl1` exp_action factor :: CharParser () Double factor = parens parse_calc <|> parse_sqr <|> parse_number parse_sqr :: CharParser () Double parse_sqr = reservedOp "sqrt" >> parens parse_calc >>= \x -> return $ sqrt x multiply_action :: CharParser () (Double -> Double -> Double) multiply_action = do {reservedOp "*"; return (*) } <|> do {reservedOp "/"; return (/) } add_action :: CharParser () (Double -> Double -> Double) add_action = do { reservedOp "+"; return (+) } <|> do { reservedOp "-"; return (-) } exp_action :: CharParser () (Double -> Double -> Double) exp_action = reservedOp "^" >> return real_exp parse_number :: CharParser () Double parse_number = naturalOrFloat >>= \num -> case num of Left i -> return $ fromInteger i Right x -> return x
The next step will be to allow for the definition of local variables that can be used during calculations. I’ll keep you posted on any progress 😉
UPDATE (02/08/09): I’ve added quite a few more features to the little calculator app and named it husky. You can have a look at the current state of the code over at github (see sidebar).
UPDATE (04/04/09): husky has grown into a pretty decent calculator app and is pretty usable now. Check it out – the code should be pretty accessible and readable.
Why didn’t you continue to work at this mini calculator by adding local variables?
[…] A nice little calculator implemented in Haskell and Parsec […]