alms-0.6.0: src/Dynamics.hs
-- | The dynamics of the interpreter
module Dynamics (
-- * Static API
E, addVal, addMod, NewValues,
-- * Dynamic API
eval, addDecls, Result,
-- * Throwing exceptions
throwFailure,
-- * Re-export to remove warning (!)
-- | We need to import Quasi for the TH phase, but using it at the
-- TH phase isn't sufficient to prevent an unused import warning.
module Meta.Quasi
) where
import Meta.Quasi
import Value
import Util
import AST
import Env
import Syntax.Ppr (Ppr(..), Doc, text, precApp)
import Error
import Prelude ()
import Data.IORef (newIORef, readIORef, writeIORef)
--
-- Our semantic domains
--
-- | The kind of identifiers used
type R = Renamed
-- | The result of a computation
type Result = IO Value
-- | The run-time environment is a stack of scopes which are, for our
-- purposes, abstract. The interface merely allows us to bind new
-- values and modules in the top scope.
type E = [Scope]
-- | Each scope binds paths of uppercase identifiers to flat value
-- and exn environments
type Scope = PEnv (ModId R) Level
-- | A level binds values and exceptions
data Level = Level {
vlevel :: !VE
}
-- | We bind 'IO' 'Value's rather than values, so that we can use
-- 'IORef' to set up recursion
type VE = Env (VarId R) (IO Value)
instance GenEmpty Level where
genEmpty = Level Env.empty
instance GenLookup Level (VarId R) (IO Value) where
level =..= k = vlevel level =..= k
instance GenExtend Level Level where
Level ve =+= Level ve' = Level (ve =+= ve')
instance GenExtend Level (Env (VarId R) (IO Value)) where
level =+= ve' = level =+= Level ve'
-- | Domain for the meaning of an expression:
type D = E -> Result
-- | Domain for the meaning of a declaration:
type DDecl = E -> IO E
(=:!=) :: Ord v => v -> a -> Env v (IO a)
(=:!=) = (=::=)
infix 6 =:!=
--
-- Evaluation
--
evalDecls :: [Decl R] -> DDecl
evalDecls = (flip . foldM . flip) evalDecl
evalDecl :: Decl R -> DDecl
evalDecl [dc| let $x = $e |] = evalLet x e
evalDecl [dc| let rec $list:bns |] = evalLetRec bns
evalDecl [dc| type $list:_ |] = return
evalDecl [dc| type $tid:_ = type $qtid:_ |] = return
evalDecl [dc| abstype $list:_ with $list:ds end |] = evalDecls ds
evalDecl [dc| open $b |] = evalOpen b
evalDecl [dc| module $mid:n = $b |] = evalMod n b
evalDecl [dc| module type $sid:_ = $_ |] = return
evalDecl [dc| local $list:d0 with $list:d1 end |] = evalLocal d0 d1
evalDecl [dc| exception $cid:n of $opt:mt |] = evalExn n mt
evalDecl [dc| $anti:a |] = $antifail
evalLet :: Patt R -> Expr R -> DDecl
evalLet x e env = do
v <- valOf e env
case bindPatt x v env of
Just env' -> return env'
Nothing -> throwPatternMatch v [show x] env
evalLetRec :: [Binding R] -> DDecl
evalLetRec bs env = do
let extend (envI, rs) (N _ b) = do
r <- newIORef $ throwBadLetRec (idName (bnvar b))
return (envI =+=
bnvar b =:= (nameFun (bnvar b) <$> join (readIORef r)),
r : rs)
(env', rev_rs) <- foldM extend (env, []) bs
zipWithM_
(\r (N _ b) -> do
v <- valOf (bnexpr b) env'
writeIORef r (return v))
(reverse rev_rs)
bs
return env'
evalOpen :: ModExp R -> DDecl
evalOpen b env = do
e <- evalModExp b env
return (env =+= e)
evalMod :: ModId R -> ModExp R -> DDecl
evalMod x b env = do
e <- evalModExp b env
return (env =+= x =:= e)
evalLocal :: [Decl R] -> [Decl R] -> DDecl
evalLocal ds ds' env0 = do
env1 <- evalDecls ds (genEmpty:env0)
scope:_:env2 <- evalDecls ds' (genEmpty:env1)
return (env2 =+= scope)
evalModExp :: ModExp R -> E -> IO Scope
evalModExp [meQ| struct $list:ds end |] env = do
scope:_ <- evalDecls ds (genEmpty:env)
return scope
evalModExp [meQ| $qmid:n $list:_ |] env = do
case env =..= n of
Just scope -> return scope
Nothing -> runtimeBug "evalModExp" $
"Unknown module: ‘" ++ show n ++ "’"
evalModExp [meQ| $me1 : $_ |] env = do
evalModExp me1 env
evalModExp [meQ| $anti:a |] _ = $antifail
evalExn :: ConId R -> Maybe (Type R) -> DDecl
evalExn _ _ env = return env
eval :: E -> Prog R -> Result
eval env0 [prQ| $list:ds in $e0 |] = evalDecls ds env0 >>= valOf e0
eval env0 [prQ| $list:ds |] = evalDecls ds env0 >> return vaUnit
-- The meaning of an expression
valOf :: Expr R -> D
valOf e env = case e of
[ex| $qvid:n |] -> case env =..= n of
Just v -> v
Nothing -> runtimeBug "valOf" $
"unbound identifier: ‘" ++ show n ++ "’"
[ex| $str:s |] -> return (vinj s)
[ex| $int:z |] -> return (vinj z)
[ex| $flo:f |] -> return (vinj f)
[ex| $char:c |] -> return (vinj c)
[ex| $antiL:a |] -> $antifail
[ex| $qcid:n $opt:me1 |]
-> do
mv1 <- mapM (valOf <-> env) me1
return (VaCon (jname n) mv1)
[ex| `$uid:lab $opt:me1 |] -> do
v1 <- maybe (return vaUnit) (valOf <-> env) me1
return (VaLab 0 lab v1)
[ex| #$uid:lab $e1 |] -> do
v1 <- valOf e1 env
case v1 of
VaLab n lab' v1' | lab == lab' -> return (VaLab (n + 1) lab v1')
_ -> return v1
[ex| match $e1 with $list:clauses |] -> do
let loop ([caQ| $xi → $ei |]:rest) v1 =
case bindPatt xi v1 env of
Just env' -> valOf ei env'
Nothing -> loop rest v1
loop ([caQ| #$uid:lab $opt:mxi → $ei |]:rest) v1 =
case v1 of
VaLab 0 lab' v1'
| lab == lab' -> case mxi of
Nothing -> valOf ei env
Just xi -> case bindPatt xi v1' env of
Just env' -> valOf ei env'
Nothing -> loop [] v1
VaLab n lab' v1'
| lab == lab' -> loop rest (VaLab (n - 1) lab' v1')
_ -> loop rest v1
loop [] v1 =
throwPatternMatch v1 (map (show . cafakepatt) clauses) env
loop ([caQ| $antiC:a |]:_) _ = $antifail
loop clauses =<< valOf e1 env
[ex| let $x = $e1 in $e2 |] -> do
v1 <- valOf e1 env
env' <- case bindPatt x v1 env of
Just env' -> return env'
Nothing -> throwPatternMatch v1 [show x] env
valOf e2 env'
[ex| let rec $list:bs in $e2 |] -> do
env' <- evalLetRec bs env
valOf e2 env'
[ex| let $decl:d in $e2 |] -> do
env' <- evalDecl d env
valOf e2 env'
[ex| ($e1, $e2) |] -> do
v1 <- valOf e1 env
v2 <- valOf e2 env
return (vinj (v1, v2))
[ex| fun $x -> $e' |] ->
return .
VaFun (FNAnonymous [pprPrec (precApp + 1) e]) $ \v ->
case bindPatt x v env of
Just env' -> valOf e' env'
Nothing -> throwPatternMatch v [show x] env
[ex| $e1 $e2 |] -> do
v1 <- valOf e1 env
v2 <- valOf e2 env
case v1 of
VaFun n f -> f v2 >>! nameApp n (pprPrec (precApp + 1) v2)
_ -> runtimeBug "valOf" $
"applied non-function ‘" ++ show v1 ++
"’ to argument ‘" ++ show v2 ++ "’"
[ex| { $list:flds | $e2 } |] -> do
newFields ← sequence
[ (ui,) <$> valOf ei env
| [fdQ|! $uid:ui = $ei |] ← flds ]
v2 ← valOf e2 env
MultiplicativeRecord oldFields ← vprjM v2
return . vinj $ MultiplicativeRecord (newFields ++ oldFields)
[ex| {+ $list:flds | $e2 +} |] -> do
let newFields = [ (ui, (valOf ei env, ppr ei))
| [fdQ|! $uid:ui = $ei |] ← flds ]
v2 ← valOf e2 env
record ← vprjM v2
let oldFields = case record of
AdditiveRecord olds → olds
MultiplicativeRecord olds →
[ (ui, (return vi, ppr vi)) | (ui, vi) ← olds ]
return . vinj $ AdditiveRecord (newFields ++ oldFields)
[ex| $e1.$uid:u |] -> do
v1 ← valOf e1 env
record ← vprjM v1
case record of
AdditiveRecord flds → case lookup u flds of
Just (mv, _) → mv
Nothing → runtimeBug "valOf" "missing record field (&)"
MultiplicativeRecord flds → case lookup u flds of
Just v → return v
Nothing → runtimeBug "valOf" "missing record field (⊗)"
[ex| ( $e1 : $_ ) |] -> valOf e1 env
[ex| ( $_ :> $_ ) |] -> runtimeBug "valOf" "encountered cast"
[ex| $anti:a |] -> $antifail
bindPatt :: Monad m => Patt R -> Value -> E -> m E
bindPatt x0 v env = case x0 of
[pa| _ |]
-> return env
[pa| $vid:l |]
-> return (env =+= l =:!= (l `nameFun` v))
[pa| $qcid:qu $opt:mx |]
-> let u = jname qu in
case (mx, v) of
(Nothing, VaCon u' Nothing) | u == u' -> return env
(Just x, VaCon u' (Just v')) | u == u' -> bindPatt x v' env
_ -> perr
[pa| `$uid:lab $opt:mx |]
-> case v of
VaLab 0 lab' v' | lab' == lab ->
case mx of
Nothing -> return env
Just x -> bindPatt x v' env
_ -> perr
[pa| ($x, $y) |]
-> case vprjM v of
Just (vx, vy) -> bindPatt x vx env >>= bindPatt y vy
Nothing -> perr
[pa| $str:s |]
-> if v == vinj s
then return env
else perr
[pa| $int:z |]
-> if v == vinj z
then return env
else perr
[pa| $float:f |]
-> if v == vinj f
then return env
else perr
[pa| $char:c |]
-> if v == vinj c
then return env
else perr
[pa| $x as $vid:l |]
-> do
env' <- bindPatt x v env
return (env' =+= l =:!= v)
[pa| $x : $_ |]
-> bindPatt x v env
[pa| { $uid:u = $x | $y } |]
-> do
MultiplicativeRecord fields <- vprjM v
(v', fields') <- extractField u fields
env' <- bindPatt x v' env
bindPatt y (vinj (MultiplicativeRecord fields')) env'
[pa| ! $x |]
-> bindPatt x v env
[pa| $anti:a |]
-> antifail "dynamics" a
[pa| $antiL:a |]
-> antifail "dynamics" a
where perr = fail $
"BUG! In bindPat, pattern match failure should " ++
"raise PatternMatch exception, but didn’t!"
-- | Extract the first matching field from an associating list,
-- returning the remaining list as well.
extractField ∷ (Eq k, Monad m) =>
k -> [(k, v)] -> m (v, [(k, v)])
extractField _ [] = fail "BUG! missing record field in pattern match"
extractField k ((k',v):kvs)
| k == k' = return (v, kvs)
| otherwise = second ((k',v):) `liftM` extractField k kvs
throwPatternMatch :: Value -> [String] -> E -> IO a
throwPatternMatch v ps _ =
throw VExn {
exnValue = VaCon (ident "PatternMatch") (Just (vinj (show v, ps)))
}
throwBadLetRec :: String -> IO a
throwBadLetRec v =
throw VExn {
exnValue = VaCon (ident "UninitializedLetRec") (Just (vinj v))
}
throwFailure :: String -> IO a
throwFailure v =
throw VExn {
exnValue = VaCon (ident "Failure") (Just (vinj v))
}
runtimeBug :: String -> String -> IO a
runtimeBug = throwAlms <$$> almsBug DynamicsPhase
---
--- helpful stuff
---
-- Add the given name to an anonymous function
nameFun :: VarId R -> Value -> Value
nameFun n (VaFun (FNAnonymous _) lam)
| idName n /= "it"
= VaFun (FNNamed (text (idName n))) lam
nameFun _ value = value
-- Get the name of an applied function
nameApp :: FunName -> Doc -> Value -> Value
nameApp fn arg (VaFun (FNAnonymous _) lam)
= VaFun (FNAnonymous (funNameDocs fn ++ [ arg ])) lam
nameApp _ _ value = value
collapse :: E -> Scope
collapse = foldr (flip (=+=)) genEmpty
-- Public API
-- | For printing in the REPL, 'addDecls' returns an environment
-- mapping any newly bound names to their values
type NewValues = Env (VarId R) Value
-- | Interpret declarations by adding to the environment, potentially
-- with side effects
addDecls :: E -> [Decl R] -> IO (E, NewValues)
addDecls env decls = do
env' <- evalDecls decls (genEmpty : [collapse env])
let PEnv _ level : _ = env'
vl' <- mapValsM id (vlevel level)
return (env', vl')
-- | Bind a name to a value
addVal :: E -> VarId R -> Value -> E
addVal e n v = e =+= n =:= (return v :: IO Value)
-- | Bind a name to a module, which is represented as a nested
-- environment
addMod :: E -> ModId R -> E -> E
addMod e n e' = e =+= n =:= collapse e'