husky-0.2: src/CalculatorParser.hs
{-----------------------------------------------------------------
(c) 2008-2009 Markus Dittrich
This program is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public
License Version 3 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License Version 3 for more details.
You should have received a copy of the GNU General Public
License along with this program; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.
--------------------------------------------------------------------}
-- | main archy driver
module CalculatorParser ( calculator ) where
-- imports
import qualified Data.Map as M
import Control.Monad
-- local imports
import CalculatorState
import ExtraFunctions
import TokenParser
-- | main parser entry point
calculator :: CharParser CalcState (Maybe Double, CalcState)
calculator = parse_calc
>>= \val -> getState
>>= \state -> return (val, state)
-- | grammar description for parser
-- NOTE: We have to make sure to provide a default
-- catch all parsing rule in case the user
-- enters something we don't understand. Otherwise
-- parsing fails and we loose our state.
parse_calc :: CharParser CalcState (Maybe Double)
parse_calc = try define_variable
<|> try (add_term >>= \x -> end_of_line >> return (Just x))
<|> return Nothing
<?> "math expression, variable definition " ++
"or variable name"
-- | if the line starts off with a string we either
-- have a variable definition or want to show the value
-- stored in a variable
define_variable :: CharParser CalcState (Maybe Double)
define_variable = (spaces
>> variable
>>= \varName -> variable_def varName )
<?> "variable definition"
-- | check that we are at the end of the line; otherwise
-- parsing failed since we always expect to parse the
-- full expression
end_of_line :: CharParser CalcState ()
end_of_line = getInput >>= \input ->
case length input of
0 -> return ()
_ -> pzero
-- | define a variable
variable_def :: String -> CharParser CalcState (Maybe Double)
variable_def varName = ( spaces
>> reservedOp "="
>> spaces
>> ( variable_def_by_value varName
<|> variable_def_by_var varName) )
<?> "variable"
-- | define a variable via a literal double
variable_def_by_value :: String -> CharParser CalcState (Maybe Double)
variable_def_by_value varName = ( add_term
>>= \value -> updateState (insert_variable value varName)
>> return (Just value) )
<?> "variable from value"
-- | define a variable via the value of another variable
variable_def_by_var :: String -> CharParser CalcState (Maybe Double)
variable_def_by_var varName = parse_variable
>>= \value -> updateState (insert_variable value varName)
>> return (Just value)
-- | look for the value of a given variable if any
parse_variable :: CharParser CalcState Double
parse_variable = ( variable
>>= \val -> spaces
>> get_variable_value val
>>= \result -> case result of
Just a -> return a
Nothing -> pzero )
<?> "variable"
-- | parser for expressions chained via "+" or "-"
add_term :: CharParser CalcState Double
add_term = mul_term `chainl1` add_action
-- | parser for expressions chained via "*" or "/"
mul_term :: CharParser CalcState Double
mul_term = exp_term `chainl1` multiply_action
-- | parser for potentiation operations "^"
exp_term :: CharParser CalcState Double
exp_term = (spaces >> factor) `chainl1` exp_action
-- | parser for individual factors, i.e, numbers,
-- variables or operations
factor :: CharParser CalcState Double
factor = parens add_term
<|> parse_keywords
<|> parse_number
<|> parse_variable
<?> "token or variable"
-- | parse all operations we currently know about
parse_keywords :: CharParser CalcState Double
parse_keywords = msum $ extract_ops keywords
where
extract_ops = foldr (\(x,y) acc ->
((reserved x >> execute y):acc)) []
-- | execute the requested operator on the term enclosed
-- in parentheses
execute :: OperatorAction -> CharParser CalcState Double
execute op = parens add_term >>= return . op
multiply_action :: CharParser CalcState (Double -> Double -> Double)
multiply_action = (reservedOp "*" >> return (*))
<|> (reservedOp "/" >> return (/))
add_action :: CharParser CalcState (Double -> Double -> Double)
add_action = (reservedOp "+" >> return (+))
<|> (reservedOp "-" >> return (-))
exp_action :: CharParser CalcState (Double -> Double -> Double)
exp_action = reservedOp "^" >> return real_exp
parse_number :: CharParser CalcState Double
parse_number = naturalOrFloat
>>= \num -> notFollowedBy alphaNum
>> case num of
Left i -> return $ fromInteger i
Right x -> return x
-- | function retrieving a variable from the database if
-- present
get_variable_value :: String -> CharParser CalcState (Maybe Double)
get_variable_value name = getState
>>= \(CalcState { varMap = myMap }) -> return $ M.lookup name myMap