hid-examples-0.4: expr/EvalRPNExcept.hs
module EvalRPNExcept (evalRPN, EvalError) where
import Control.Monad.State
import Control.Monad.Except
import Control.Monad.Reader
import Data.Foldable (traverse_)
import Data.Semigroup ((<>))
import Safe
{-
Function evalRPN evaluates an expression given
in the reversed polish notation (RPN, postfix notation):
evalRPN "2 3 +" ==> 5 (== "2 + 3")
evalRPN "2 3 4 + *" ==> 14 (== 2 * (3 + 4))
-}
data EvalError = NotEnoughElements | NotANumber String | ExtraElements
instance Show EvalError where
show NotEnoughElements = "Not enough elements in the expression"
show (NotANumber s) = "Expression component '" <> s <> "' is not a number"
show ExtraElements = "There are extra elements in the expression"
type Stack = [Integer]
type EvalM = ExceptT EvalError (State Stack)
push :: Integer -> EvalM ()
push x = modify (x:)
pop :: EvalM Integer
pop = do
xs <- get
when (null xs) $ throwError NotEnoughElements
put (tail xs)
pure (head xs)
oneElementOnStack :: EvalM ()
oneElementOnStack = do
l <- gets length
when (l /= 1) $ throwError ExtraElements
handleNaN :: String -> Maybe Integer -> EvalM Integer
handleNaN s Nothing = throwError (NotANumber s)
handleNaN _ (Just n) = pure n
readSafe :: String -> EvalM Integer
readSafe s = handleNaN s (readMay s)
evalRPN :: String -> Either EvalError Integer
evalRPN str = evalState (runExceptT evalRPN') []
where
evalRPN' = traverse_ step (words str) >> oneElementOnStack >> pop
step "+" = processTops (+)
step "*" = processTops (*)
step t = readSafe t >>= push
processTops op = op <$> pop <*> pop >>= push
displayResults :: Either EvalError Integer -> String
displayResults (Left e) = "Error: " <> show e
displayResults (Right n) = show n