fay-0.16.0.0: src/Fay/Compiler/ModuleScope.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Handles variable bindings on the module level and also keeps track of
-- primitive operations that we want to treat specially.
module Fay.Compiler.ModuleScope
(ModuleScope
,bindAsLocals
,findTopLevelNames
,resolveName
,moduleLocals
,findPrimOp
) where
import Fay.Compiler.GADT
import Control.Arrow
import Control.Monad.Reader
import Control.Monad.Writer
import Data.Default
import Data.Map (Map)
import qualified Data.Map as M
import Language.Haskell.Exts hiding (name, binds)
import Prelude hiding (mod)
-- | Maps names bound in the module to their real names
-- The keys are unqualified for locals and imports,
-- the values are always fully qualified
-- Example contents:
-- [ (UnQUal "main" , Qual "Main" "main")
-- , (UnQual "take" , Qual "Prelude" "take")
-- , ( Qual "M" "insertWith", Qual "Data.Map" "insertWith") ]
newtype ModuleScope = ModuleScope (Map QName QName)
deriving Show
instance Monoid ModuleScope where
mempty = ModuleScope M.empty
mappend (ModuleScope a) (ModuleScope b) = ModuleScope $ a `M.union` b
instance Default ModuleScope where
def = mempty
-- | Find the path of a locally bound name
-- Returns special values in the "Fay$" module for primOps
resolveName :: QName -> ModuleScope -> Maybe QName
resolveName q (ModuleScope binds) = case M.lookup q binds of -- lookup in the module environment.
-- something pointing to prelude, is it a primop?
Just q'@(Qual (ModuleName "Prelude") n) -> case M.lookup n envPrimOpsMap of
Just x -> Just x -- A primop which looks like it's imported from prelude.
Nothing -> Just q' -- Regular prelude import, leave it as is.
-- No matches in the current environment, so it may be a primop if it's unqualified.
-- If Nothing is returned from either of the branches it means that there is
-- no primop and nothing in env scope so GHC would have given an error.
Nothing -> case q of
UnQual n -> M.lookup n envPrimOpsMap
_ -> Nothing
j -> j -- Non-prelude import that was found in the env
-- | Bind a list of names into the local scope
-- Right now all bindings are made unqualified
bindAsLocals :: [QName] -> ModuleScope -> ModuleScope
bindAsLocals qs (ModuleScope binds) =
-- This needs to be changed to not use unqual to support qualified imports.
ModuleScope $ binds `M.union` M.fromList (map (unqual &&& id) qs)
where unqual (Qual _ n) = UnQual n
unqual u@UnQual{} = u
unqual Special{} = error "fay: ModuleScope.bindAsLocals: Special"
-- | Find all names that are bound locally in this module, which excludes imports.
moduleLocals :: ModuleName -> ModuleScope -> [QName]
moduleLocals mod (ModuleScope binds) = filter isLocal . M.elems $ binds
where
isLocal (Qual m _) = mod == m
isLocal _ = False
--------------------------------------------------------------------------------
-- Primitive Operations
-- | The built-in operations that aren't actually compiled from
-- anywhere, they come from runtime.js.
--
-- They're in the names list so that they can be overriden by the user
-- in e.g. let a * b = a - b in 1 * 2.
--
-- So we resolve them to Fay$, i.e. the prefix used for the runtime
-- support. $ is not allowed in Haskell module names, so there will be
-- no conflicts if a user decicdes to use a module named Fay.
--
-- So e.g. will compile to (*) Fay$$mult, which is in runtime.js.
envPrimOpsMap :: Map Name QName
envPrimOpsMap = M.fromList
[ (Symbol ">>", Qual (ModuleName "Fay$") (Ident "then"))
, (Symbol ">>=", Qual (ModuleName "Fay$") (Ident "bind"))
, (Ident "return", Qual (ModuleName "Fay$") (Ident "return"))
, (Ident "force", Qual (ModuleName "Fay$") (Ident "force"))
, (Ident "seq", Qual (ModuleName "Fay$") (Ident "seq"))
, (Symbol "*", Qual (ModuleName "Fay$") (Ident "mult"))
, (Symbol "+", Qual (ModuleName "Fay$") (Ident "add"))
, (Symbol "-", Qual (ModuleName "Fay$") (Ident "sub"))
, (Symbol "/", Qual (ModuleName "Fay$") (Ident "divi"))
, (Symbol "==", Qual (ModuleName "Fay$") (Ident "eq"))
, (Symbol "/=", Qual (ModuleName "Fay$") (Ident "neq"))
, (Symbol ">", Qual (ModuleName "Fay$") (Ident "gt"))
, (Symbol "<", Qual (ModuleName "Fay$") (Ident "lt"))
, (Symbol ">=", Qual (ModuleName "Fay$") (Ident "gte"))
, (Symbol "<=", Qual (ModuleName "Fay$") (Ident "lte"))
, (Symbol "&&", Qual (ModuleName "Fay$") (Ident "and"))
, (Symbol "||", Qual (ModuleName "Fay$") (Ident "or"))
]
-- | Lookup a primop that was resolved to a Prelude definition.
findPrimOp :: QName -> Maybe QName
findPrimOp (Qual (ModuleName "Prelude") s) = M.lookup s envPrimOpsMap
findPrimOp _ = Nothing
--------------------------------------------------------------------------------
-- AST
type ModuleScopeSt = ReaderT ModuleName (Writer ModuleScope) ()
-- | Get module level names from a haskell module AST.
findTopLevelNames :: ModuleName -> [Decl] -> ModuleScope
findTopLevelNames mod decls = snd . runWriter $ runReaderT (mapM_ d_decl decls) mod
bindName :: Name -> ModuleScopeSt
bindName k = ask >>= \mod -> tell (ModuleScope $ M.singleton (UnQual k) (Qual mod k))
d_decl :: Decl -> ModuleScopeSt
d_decl d = case d of
DataDecl _ _ _ _ _ dd _ -> mapM_ d_qualCon dd
GDataDecl _ DataType _ _ _ _ ds _ -> mapM_ (d_qualCon . convertGADT) ds
PatBind _ (PVar n) _ _ _ -> bindName n
FunBind (Match _ n _ _ _ _ : _) -> bindName n
ClassDecl _ _ _ _ _ cds -> mapM_ d_classDecl cds
TypeSig _ ns _ -> mapM_ bindName ns
_ -> return ()
d_classDecl :: ClassDecl -> ModuleScopeSt
d_classDecl cd = case cd of
ClsDecl d -> d_decl d
_ -> return ()
d_qualCon :: QualConDecl -> ModuleScopeSt
d_qualCon (QualConDecl _ _ _ cd) = case cd of
ConDecl n _ -> bindName n
InfixConDecl _ n _ -> bindName n
RecDecl n ns -> bindName n >> mapM_ bindName (concatMap fst ns)