packages feed

alms-0.6.0: src/BasisUtils.hs

-- | Tools for implementing primitive operations -- essentially an
--   object-language/meta-language FFI.
module BasisUtils (
  -- | * Initial environment entries
  Entry,
  -- ** Entry constructors
  -- *** Values
  fun, val, binArith,
  -- *** Types
  dec, typ, primtype,
  -- *** Modules
  submod,
  -- ** Sugar operators for entry construction
  (-:), (-=),
  -- ** Default location for entries
  _loc,
  module Data.Loc,
  -- ** Environment construction
  basis2renv, basis2tenv, basis2venv,

  -- * Function embedding
  MkFun(..), baseMkFun, vapp,

  -- * Re-exports
  text, Id(..), throwFailure,
  module Meta.Quasi,
) where

import Util
import Util.MonadRef
import Dynamics (E, addVal, addMod, throwFailure)
import Env (GenEmpty(..), domain)
import Error (MonadAlmsError, almsBug, throwAlms, Phase(DynamicsPhase))
import Meta.Quasi
import Syntax.Parser (ptd)
import Syntax.Ppr (ppr, pprPrec, text, precApp)
import Statics
import Statics.Rename as Rename
import AST
import Type (TyCon, tcName, tcCons)
import Data.Loc (Loc(Loc), mkBogus, setLoc)
import Value (Valuable(..), FunName(..), funNameDocs, Value(..))

import Prelude ()

-- | Kind of identifier used in this module
type R = Raw

-- | The default location for primitive bindings
_loc  :: Loc
_loc   = mkBogus "<primitive>"

-- | An entry in the initial environment
data Entry i
  -- | A value entry has a name, a types, and a value
  = ValEn {
    enVarName :: VarId i,
    enType    :: Type i,
    enValue   :: Value
  }
  -- | A declaration entry
  | DecEn {
    enSrc     :: SigItem i
  }
  -- | A type entry associates a tycon name with information about it
  | TypEn {
    enTypName :: TypId i,
    enTyCon   :: TyCon
  }
  -- | A module entry associates a module name with a list of entries
  | ModEn {
    enModName :: ModId i,
    enEnts    :: [Entry i]
  }

-- | Type class for embedding Haskell functions as object language
--   values.  Dispatches on return type @r@.
class MkFun r where
  mkFun :: Valuable v => FunName -> (v -> r) -> Value

-- | Recursive case is functions that return functions: accept
--   one argument, then look for more
instance (Valuable v, MkFun r) => MkFun (v -> r) where
  mkFun n f = VaFun n $ \v ->
    vprjM v >>! mkFun (next v) . f
    where
      next v = FNAnonymous (funNameDocs n ++ [pprPrec precApp v])

-- Base cases for various return types

-- | Base case for functions returning in the 'IO' monad
instance Valuable r => MkFun (IO r) where
  mkFun n f = VaFun n $ \v -> vprjM v >>= f >>! vinj

-- | Base case for functions that already return 'Value'
instance MkFun Value where
  mkFun n f = VaFun n $ \v -> vprjM v >>! f

instance MkFun Integer  where mkFun = baseMkFun
instance MkFun Double   where mkFun = baseMkFun
instance MkFun Char     where mkFun = baseMkFun
instance MkFun Bool     where mkFun = baseMkFun
instance MkFun ()       where mkFun = baseMkFun
instance (Valuable a, MkFun a) =>
         MkFun [a]      where mkFun = baseMkFun
instance (Valuable a, Valuable b, MkFun a, MkFun b) =>
         MkFun (a, b)   where mkFun = baseMkFun

baseMkFun :: (Valuable a, Valuable b) => FunName -> (a -> b) -> Value
baseMkFun n f = VaFun n $ \v -> vprjM v >>! vinj . f

-- | Make a value entry for a Haskell non-function.
val :: Valuable v => String -> Type R -> v -> Entry Raw
val name t v = ValEn (ident name) t (vinj v)

-- | Make a value entry for a Haskell function, given a names and types
--   for the sublanguages.  (Leave blank to leave the binding out of
--   that language.
fun :: (MkFun r, Valuable v) =>
       String -> Type R -> (v -> r) -> Entry Raw
fun name t f = ValEn vid t (mkFun (FNNamed (ppr vid)) f)
  where vid = ident name

typ :: String -> Entry Raw
typ s = DecEn [sgQ| type $tydec:td |] where td = ptd s

-- | Creates a declaration entry
dec :: SigItem R -> Entry Raw
dec  = DecEn

-- | Creates a module entry
submod :: String -> [Entry Raw] -> Entry Raw
submod  = ModEn . ident

-- | Creates a primitive type entry, binding a name to a type tag
--   (which is usually defined in AST.hs)
primtype  :: String -> TyCon -> Entry Raw
primtype   = TypEn . ident

-- | Application
(-:), (-=) :: (a -> b) -> a -> b
(-:) = ($)
(-=) = ($)
-- | Application twice, for giving the same type in C and A
infixl 5 -:
infixr 0 -=

-- | Instance of 'fun' for making binary arithmetic functions
binArith :: String -> (Integer -> Integer -> Integer) -> Entry Raw
binArith name = fun name [ty| int -> int -> int |]

-- | Apply an object language function (as a 'Value')
vapp :: Valuable a => Value -> a -> IO Value
vapp (VaFun _ f) x = f (vinj x)
vapp _           _ = throwAlms
  $ almsBug DynamicsPhase "vapp" "applied non-function"
infixr 0 `vapp`

-- | Build the renaming environment and rename the entries
basis2renv :: MonadAlmsError m =>
              [Entry Raw] -> m ([Entry Renamed], RenameState)
basis2renv =
  runRenamingM False _loc renameState0 . renameMapM each where
  each ValEn { enVarName = u, enType = t, enValue = v } = do
    u' <- Rename.addVal u
    t' <- renameType t
    return ValEn { enVarName = u', enType = t', enValue = v }
  each DecEn { enSrc = d } = do
    d' <- renameSigItem d
    return DecEn { enSrc = d' }
  each TypEn { enTypName = l, enTyCon = tc } = do
    l' <- Rename.addType l (idTag (jname (tcName tc)))
                           (dirtyTrivialRename <$> domain (tcCons tc))
    return TypEn { enTypName = l', enTyCon = tc }
  each ModEn { enModName = u, enEnts = es } = do
    (u', es') <- Rename.addMod u $ renameMapM each es
    return ModEn { enModName = u', enEnts = es' }

-- | Build the static environment
basis2tenv :: (MonadAlmsError m, MonadRef r m) =>
              StaticsState r -> [Entry Renamed] -> m (StaticsState r)
basis2tenv ss0 entries = addSignature ss1 sigexp
  where
    ss1             = foldl' (uncurry . addPrimType) ss0 prims
    (sigexp, prims) = evalRWS (eachEntries entries) [] (0 :: Int)
    eachEntries es  = do
      sigitems <- mapM eachEntry es
      return [seQ|+ sig $list:sigitems end |]
    eachEntry ValEn { enVarName = n, enType = t }
      = return [sgQ|+ val $vid:n : $t |]
    eachEntry DecEn { enSrc = sigitem }
      = return sigitem
    eachEntry TypEn { enTypName = n, enTyCon = tc }
      = do
        ix <- get
        put (ix + 1)
        tell [(n, tc)]
        return [sgQ|+ type $tid:n = type $tid:n |]
    eachEntry ModEn { enModName = n, enEnts = es }
      = do
        sig <- eachEntries es
        return [sgQ|+ module $mid:n : $sig |]

-- | Build the dynamic environment
basis2venv :: MonadAlmsError m => [Entry Renamed] -> m E
basis2venv es = foldM add genEmpty es where
  add :: MonadAlmsError m => E -> Entry Renamed -> m E
  add e (ValEn { enVarName = n, enValue = v })
          = return (Dynamics.addVal e n v)
  add e (ModEn { enModName = n, enEnts = es' })
          = Dynamics.addMod e n `liftM` basis2venv es'
  add e _ = return e