module PureInterpreter where

{--
Interpreter functions for while programs. These functions are essentially
Haskell encodings of the semantic rules for the while language given in Neil
Jones' book on pages 40 & 41.
--}

import PureSyntax
import qualified Data.Map as M

-- While stores are maps from variable names to trees, i.e. the values of
-- variables
type Store = M.Map Name ETree

-- To evaluate a program, we evaluate the program's command with the initial
-- store that contains the read variable with the value of the input, and
-- output (or 'write') the value of the write-variable in the resulting store.
evalProg :: ETree -> Program -> ETree
evalProg input (Program _ rd blk wrt) = M.findWithDefault ENil wrt store
    where store = evalBlock (M.singleton rd input) blk

-- To evaluate an empty block, just return the input store. To evaluate a non-
-- empty block, evaluate the first element with the input store, and then
-- evaluate the rest of the block with the resulting store.
evalBlock :: Store -> Block -> Store
evalBlock store []     = store
evalBlock store (c:cs) = let store' = evalComm store c in evalBlock store' cs

-- Commands update the contents of the store:
--   Assignments update the assignee variable with the assigned value.
--   While loops evaluate a guard expression with an initial store. If the
--     resulting expression is nil, we do nothing. Otherwise, we update the
--     store by executing the while loop's command, and repeat the process with
--     the new store.
--   Conditionals are evaluated by first evaluating the condition. If the
--     condition is false (nil), the 'else-block' is evaluated. Otherwise the
--     'then-block' is evaluated.
evalComm :: Store -> Command -> Store
evalComm store (Assign v x)   = M.insert v (evalExpr store x) store
evalComm store (While  x b)   = case evalExpr store x of
    ENil -> store
    _    -> let store' = evalBlock store b in evalComm store' (While x b)
evalComm store (IfElse e a b) = case evalExpr store e of
    ENil -> evalBlock store b
    _    -> evalBlock store a

-- Expression evaluation is straightforward - see page 40 of Neil Jones' book
-- for more detail. This function performs a single reduction step.
evalExpr :: Store -> Expression -> ETree
evalExpr store expr = let eval = evalExpr store in case expr of
    Var  n   -> M.findWithDefault ENil n store
    Hd   e   -> case eval e of { ENil -> ENil; ECons h _ -> h}
    Tl   e   -> case eval e of { ENil -> ENil; ECons _ t -> t}
    IsEq a b -> if eval a == eval b then ECons ENil ENil else ENil
    Cons h t -> ECons (eval h) (eval t)
    Lit  t   -> t
