jump to navigation

A nice little calculator implemented in Haskell and Parsec February 1, 2009

Posted by haskelladdict in Haskell.
Tags: , , , , , ,
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.

Comments»

1. b - May 30, 2013

Why didn’t you continue to work at this mini calculator by adding local variables?

2. 关于Haskell:Haskell-用地图中的值替换列表中的字符串 - 4Find - August 2, 2020

[…] A nice little calculator implemented in Haskell and Parsec […]


Leave a comment