language-qux-0.1.1.2: src/Language/Qux/Interpreter.hs
{-|
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.Applicative
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