{-# LANGUAGE GeneralizedNewtypeDeriving, FlexibleInstances, MultiParamTypeClasses #-} module Haste.Monad ( JSGen, genJS, dependOn, getModName, addLocal, getCfg, continue, isolate, pushBind, popBind, getCurrentBinding, whenCfg, rename, getActualName ) where import Control.Monad.State.Strict import Haste.AST as AST hiding (modName) import qualified Data.Set as S import Control.Applicative import qualified Data.Map as M data GenState cfg = GenState { -- | Dependencies in current context. deps :: ![Name], -- | Local variables in current context. locals :: ![Name], -- | The current continuation. Code is generated by appending to this -- continuation. continuation :: !(Stm -> Stm), -- | The stack of nested lambdas we've traversed. bindStack :: ![Var], -- | Name of the module being compiled. modName :: !String, -- | Current compiler configuration. config :: !cfg, -- | Mapping of variable renamings. renames :: !(M.Map Var Var) } initialState :: cfg -> GenState cfg initialState cfg = GenState { deps = [], locals = [], continuation = id, bindStack = [], modName = "", config = cfg, renames = M.empty } newtype JSGen cfg a = JSGen (State (GenState cfg) a) deriving (Monad, Functor, Applicative) class Dependency a where -- | Add a dependency to the function currently being generated. dependOn :: a -> JSGen cfg () -- | Mark a symbol as local, excluding it from the dependency graph. addLocal :: a -> JSGen cfg () instance Dependency AST.Name where {-# INLINE dependOn #-} dependOn v = JSGen $ do st <- get put st {deps = v : deps st} {-# INLINE addLocal #-} addLocal v = JSGen $ do st <- get put st {locals = v : locals st} instance Dependency AST.Var where {-# INLINE dependOn #-} dependOn (Foreign _) = return () dependOn (Internal n _ _) = dependOn n {-# INLINE addLocal #-} addLocal (Foreign _) = return () addLocal (Internal n _ _) = addLocal n instance Dependency a => Dependency [a] where dependOn = mapM_ dependOn addLocal = mapM_ addLocal instance Dependency a => Dependency (S.Set a) where dependOn = dependOn . S.toList addLocal = addLocal . S.toList genJS :: cfg -- ^ Config to use for code generation. -> String -- ^ Name of the module being compiled. -> JSGen cfg a -- ^ The code generation computation. -> (a, S.Set AST.Name, S.Set AST.Name, Stm -> Stm) genJS cfg myModName (JSGen gen) = case runState gen (initialState cfg) {modName = myModName} of (a, GenState dependencies loc cont _ _ _ _) -> (a, S.fromList dependencies, S.fromList loc, cont) getModName :: JSGen cfg String getModName = JSGen $ modName <$> get pushBind :: Var -> JSGen cfg () pushBind v = JSGen $ do st <- get put st {bindStack = v : bindStack st} popBind :: JSGen cfg () popBind = JSGen $ do st <- get put st {bindStack = tail $ bindStack st} getCurrentBinding :: JSGen cfg Var getCurrentBinding = JSGen $ fmap (head . bindStack) get -- | Add a new continuation onto the current one. continue :: (Stm -> Stm) -> JSGen cfg () continue cont = JSGen $ do st <- get put st {continuation = continuation st . cont} -- | Run a GenJS computation in isolation, returning its results rather than -- writing them to the output stream. Dependencies and locals are still -- updated, however, and any enclosing renames are still visible within -- the isolated computation. isolate :: JSGen cfg a -> JSGen cfg (a, Stm -> Stm) isolate gen = do myMod <- getModName cfg <- getCfg b <- getCurrentBinding rns <- renames <$> JSGen get let (x, dep, loc, cont) = genJS cfg myMod $ do pushBind b JSGen $ do st <- get put st {renames = rns} gen dependOn dep addLocal loc return (x, cont) getCfg :: JSGen cfg cfg getCfg = JSGen $ fmap config get whenCfg :: (cfg -> Bool) -> JSGen cfg () -> JSGen cfg () whenCfg p act = do cfg <- getCfg when (p cfg) act -- | Run a computation with the given renaming added to its context. rename :: Var -> Var -> JSGen cfg a -> JSGen cfg a rename from to m = do st <- JSGen get JSGen $ put st {renames = M.insert from to $ renames st} x <- m st' <- JSGen get JSGen $ put st' {renames = renames st} return x -- | Get the actual name of a variable, recursing through multiple renamings -- if necessary. getActualName :: Var -> JSGen cfg Var getActualName v = do rns <- renames <$> JSGen get maybe (return v) getActualName $ M.lookup v rns