packages feed

alms-0.4.9: src/Dynamics.hs

{-# LANGUAGE
      FlexibleInstances,
      MultiParamTypeClasses,
      QuasiQuotes,
      TemplateHaskell #-}
-- | The dynamics of the interpreter
module Dynamics (
  -- * Static API
  E, addVal, addMod, NewValues,
  -- * Dynamic API
  eval, addDecls, Result,
  -- * 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 Syntax
import qualified Syntax.Decl
import qualified Syntax.Expr
import qualified Syntax.Notable
import qualified Syntax.Patt
import Env
import Ppr (Ppr(..), Doc, text, precApp)

import Data.IORef (newIORef, readIORef, writeIORef)
import Control.Exception (throw)

--
-- 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 (Uid 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 (Lid R) (IO Value)

-- | To distinguish exn names from path components.
newtype ExnName = ExnName (Uid R)
  deriving (Eq, Ord)

instance GenEmpty Level where
  genEmpty = Level empty
instance GenLookup Level (Lid 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 (Lid 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 : $opt:_ = $e |]              = evalLet x e
evalDecl [$dc| type $list:_ |]                      = return
evalDecl [$dc| abstype $list:_ with $list:ds end |] = evalDecls ds
evalDecl [$dc| open $b |]                           = evalOpen b
evalDecl [$dc| module $uid:n = $b |]                = evalMod n b
evalDecl [$dc| module type $uid:_ = $_ |]           = return
evalDecl [$dc| local $list:d0 with $list:d1 end |]  = evalLocal d0 d1
evalDecl [$dc| exception $uid: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

evalOpen :: ModExp R -> DDecl
evalOpen b env = do
  e <- evalModExp b env
  return (env =+= e)

evalMod :: Uid 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 [$me| struct $list:ds end |]  env = do
  scope:_ <- evalDecls ds (genEmpty:env)
  return scope
evalModExp [$me| $quid:n $list:_ |]      env = do
  case env =..= n of
    Just scope -> return scope
    Nothing    -> fail $ "BUG! Unknown module: " ++ show n
evalModExp [$me| $me1 : $_ |]            env = do
  evalModExp me1 env
evalModExp [$me| $anti:a |]              _   = $antifail

evalExn :: Uid 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 (vinj ())

-- The meaning of an expression
valOf :: Expr R -> D
valOf e env = case e of
  [$ex| $id:ident |] -> case view ident of
    Left x     -> case env =..= x of
      Just v     -> v
      Nothing    -> fail $ "BUG! unbound identifier: " ++ show x
    Right c    -> return (VaCon (jname c) Nothing)
  [$ex| $str:s |]    -> return (vinj s)
  [$ex| $int:z |]    -> return (vinj z)
  [$ex| $flo:f |]    -> return (vinj f)
  [$ex| $antiL:a |]  -> $antifail
  [$ex| match $e1 with $list:clauses |] -> do
    v1 <- valOf e1 env
    let loop (N _ (CaClause xi ei):rest) = case bindPatt xi v1 env of
          Just env' -> valOf ei env'
          Nothing   -> loop rest
        loop [] = throwPatternMatch v1
                    (map (show . capatt . dataOf) clauses) env
        loop (N _ (CaAnti a):_) = $antifail
    loop clauses
  [$ex| let rec $list:bs in $e2 |] -> do
    let extend (envI, rs) (N _ b) = do
          r <- newIORef (fail "Accessed let rec binding too early")
          return (envI =+= 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
    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 -> bindPatt x v env >>= valOf e'))
  [$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) 
      VaCon c _ -> return (VaCon c (Just v2))
      _         -> fail $ "BUG! applied non-function " ++ show v1
                           ++ " to argument " ++ show v2
  [$ex| fun '$_ -> $e1 |]         -> valOf e1 env
  [$ex| $e1 [$_] |]               -> valOf e1 env
  [$ex| Pack[$opt:_]($_, $e1) |]  -> valOf e1 env
  [$ex| ( $e1 : $_ ) |]           -> valOf e1 env
  [$ex| ( $e1 :> $_ ) |]          -> valOf e1 env
  [$ex| $anti:a |]                -> $antifail

bindPatt :: Monad m => Patt R -> Value -> E -> m E
bindPatt x0 v env = case x0 of
  [$pa| _ |] 
    -> return env
  [$pa| $lid:l |]
    -> return (env =+= l =:!= (l `nameFun` v))
  [$pa| $quid: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| ($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| Pack('$_, $x) |]
    -> bindPatt x v env
  [$pa| $x as $lid:l |]
    -> do
      env' <- bindPatt x v env
      return (env' =+= l =:!= v)
  [$pa| $anti:a |]
    -> antifail "dynamics" a
  [$pa| $antiL:a |]
    -> antifail "dynamics" a
  where perr = fail $
                 "Pattern match failure: " ++ show x0 ++
                 " does not match " ++ show v

throwPatternMatch :: Value -> [String] -> E -> IO a
throwPatternMatch v ps _ =
  throw VExn {
    exnValue = VaCon (uid "PatternMatch") (Just (vinj (show v, ps)))
  }

---
--- helpful stuff
---

-- Add the given name to an anonymous function
nameFun :: Lid R -> Value -> Value
nameFun (Lid r x) (VaFun (FNAnonymous _) lam)
  | x /= "it" || not (isTrivial r) = VaFun (FNNamed (text x)) 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 (Lid 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 -> Lid 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 -> Uid R -> E -> E
addMod e n e' = e =+= n =:= collapse e'