-- |'Hascal' is both a simple but extendable calculator library for Haskell
-- and a command-line program using it.
-- 
-- Also, its source code is a nice example for a minimalistic Haskell project.
module Hascal (
  -- * Functions
  -- ** Operators
  operators,
  -- ** Evaluators
  eval,
  hascal
  ) where

import           Control.Arrow     (second)
import           Data.Function     (on)
import           Data.Number.CReal


-- |'operators' is the default list of operators.
-- 
-- An operator consists of one character and a function with of type
-- @CReal -> CReal -> CReal@.
-- 
-- 'operators' includes:
-- 
-- * addition, represented by @\'+\'@
-- 
-- * subtraction, represented by @\'-\'@
-- 
-- * multiplication, represented by @\'c\'@
-- 
-- * division, represented by @\'\/\'@
-- 
-- * exponentiation, represented by @\'^\'@
-- 
-- * logarithming (with flipped arguments, see below), represented by @\'?\'@
-- 
-- such that these laws are held:
-- 
-- > (a - b == c) == (a == b + c)
-- > (a / b == c) == (a == b * c)
-- > (a ? b == c) == (a == b ^ c)
operators :: [(Char, CReal -> CReal -> CReal)]
operators = [ ('+', (+))
            , ('-', (-))
            , ('/', (/))
            , ('*', (*))
            , ('^', (**))
            , ('?', flip logBase)
            ]-- Respecting operator precedence,
             -- you can add custom infix operators.

-- |'eval' gets a list of operators and a string containing a mathematical
-- expression/term which only uses those operators listed in the first
-- argument, and returns the result of that term.
eval :: [(Char, CReal -> CReal -> CReal)] -> String -> CReal
eval []          a = read a
eval l@((c,f):s) a | z /= ""   = on f (eval l.($m)) fst snd
                   | otherwise = eval s a
                   where m@(_,z) = second (drop 1) $ break (==c) a


-- |'hascal' is the default evaluator:
-- 
-- > hascal = eval operators
hascal :: String -> CReal
hascal = eval operators