{-| Module : Language.Qux.Interpreter Description : Functions for executing a program and retrieving the return result. Copyright : (c) Henry J. Wylde, 2015 License : BSD3 Maintainer : public@hjwylde.com Functions for executing a program and retrieving the return result. This module assumes the program is well-formed. That is, it must be well-typed (see "Language.Qux.TypeChecker"). -} module Language.Qux.Interpreter ( -- * Environment Execution, Evaluation, runExecution, -- * Contexts Context, Locals, context, -- * Interpreter -- ** Program execution exec, execStmt, -- ** Expression evaluation evalExpr ) where import Control.Monad.Reader import Control.Monad.State import Control.Monad.Trans.Either import Data.List ((\\)) import Data.Map (Map, (!)) import qualified Data.Map as Map import Data.Maybe (fromJust, isJust) import Language.Qux.Syntax -- | An environment that holds the global state (@Reader Context@) and the local state -- (@Locals@). -- It supports breaking out of execution (via 'EitherT Value'). type Execution = EitherT Value Evaluation -- | An environment that holds the global state (@Reader Context@) and the local state -- (@Locals@). -- Purely for evaluation of expressions---this environment does not support breaking out of -- execution. type Evaluation = StateT Locals (Reader Context) runExecution :: (a -> Value) -> Execution a -> Evaluation Value runExecution f exec = either id f <$> runEitherT exec -- | Global context that holds function definitions. -- The function name, parameter names and statements are held. data Context = Context { functions :: Map Id ([Id], [Stmt]) } -- | Local context. type Locals = Map Id Value -- | Returns a context for the given program. context :: Program -> Context context (Program decls) = Context { functions = Map.fromList $ map (\d -> (name d, (parameterNames d, stmts d))) decls } once :: Monad m => MonadState s m => (s -> s) -> m a -> m a once f m = get >>= \save -> modify f >> m <* put save -- | @exec program entry arguments@ executes @entry@ (passing it @arguments@) in the context -- of @program@. -- This function wraps 'execFunction' by building and evaluating the environment under -- the hood. exec :: Program -> Id -> [Value] -> Value exec program entry arguments = runReader (evalStateT (evalApplicationExpr entry arguments) Map.empty) (context program) execBlock :: [Stmt] -> Execution () execBlock = mapM_ execStmt -- | Executes the statement in a breaking environment. execStmt :: Stmt -> Execution () execStmt (IfStmt condition trueStmts falseStmts) = do result <- lift $ evalExpr condition execBlock $ case runBoolValue result of True -> trueStmts False -> falseStmts execStmt (ReturnStmt expr) = lift (evalExpr expr) >>= left execStmt s@(WhileStmt condition stmts) = do result <- lift $ evalExpr condition when (runBoolValue result) $ execBlock stmts >> execStmt s -- | Reduces the expression to a value (normal form). evalExpr :: Expr -> Evaluation Value evalExpr (ApplicationExpr name arguments) = mapM evalExpr arguments >>= evalApplicationExpr name evalExpr (BinaryExpr op lhs rhs) = do lhs' <- evalExpr lhs rhs' <- evalExpr rhs evalBinaryExpr op lhs' rhs' evalExpr (ListExpr exprs) = ListValue <$> mapM evalExpr exprs evalExpr (UnaryExpr op expr) = evalExpr expr >>= evalUnaryExpr op evalExpr (ValueExpr value) = return value evalApplicationExpr :: Id -> [Value] -> Evaluation Value evalApplicationExpr name arguments = do maybeValue <- gets $ Map.lookup name if isJust maybeValue then return $ fromJust maybeValue else do (parameters, stmts) <- asks $ (! name) . functions once (Map.union $ Map.fromList (zip parameters arguments)) (runExecution undefined (execBlock stmts)) evalBinaryExpr :: BinaryOp -> Value -> Value -> Evaluation Value evalBinaryExpr Acc (ListValue elements) (IntValue rhs) = return $ elements !! (fromInteger rhs) evalBinaryExpr Mul (IntValue lhs) (IntValue rhs) = return $ IntValue (lhs * rhs) evalBinaryExpr Div (IntValue lhs) (IntValue rhs) = return $ IntValue (lhs `div` rhs) evalBinaryExpr Mod (IntValue lhs) (IntValue rhs) = return $ IntValue (lhs `mod` rhs) evalBinaryExpr Add (IntValue lhs) (IntValue rhs) = return $ IntValue (lhs + rhs) evalBinaryExpr Add (ListValue lhs) (ListValue rhs) = return $ ListValue (lhs ++ rhs) evalBinaryExpr Sub (IntValue lhs) (IntValue rhs) = return $ IntValue (lhs - rhs) evalBinaryExpr Sub (ListValue lhs) (ListValue rhs) = return $ ListValue (lhs \\ rhs) evalBinaryExpr Lt (IntValue lhs) (IntValue rhs) = return $ BoolValue (lhs < rhs) evalBinaryExpr Lte (IntValue lhs) (IntValue rhs) = return $ BoolValue (lhs <= rhs) evalBinaryExpr Gt (IntValue lhs) (IntValue rhs) = return $ BoolValue (lhs > rhs) evalBinaryExpr Gte (IntValue lhs) (IntValue rhs) = return $ BoolValue (lhs >= rhs) evalBinaryExpr Eq (BoolValue lhs) (BoolValue rhs) = return $ BoolValue (lhs == rhs) evalBinaryExpr Eq (IntValue lhs) (IntValue rhs) = return $ BoolValue (lhs == rhs) evalBinaryExpr Eq (ListValue lhs) (ListValue rhs) = return $ BoolValue (lhs == rhs) evalBinaryExpr Eq NilValue NilValue = return $ BoolValue True evalBinaryExpr Eq _ _ = return $ BoolValue False evalBinaryExpr Neq lhs rhs = evalBinaryExpr Eq lhs rhs >>= return . BoolValue . not . runBoolValue evalUnaryExpr :: UnaryOp -> Value -> Evaluation Value evalUnaryExpr Len (ListValue elements) = return $ IntValue (toInteger $ length elements) evalUnaryExpr Neg (IntValue value) = return $ IntValue (-value) runBoolValue :: Value -> Bool runBoolValue (BoolValue value) = value