Agda-2.3.2.2: src/prototyping/modules/flat/Scope.hs
{-# OPTIONS_GHC -fglasgow-exts -fallow-undecidable-instances #-}
module Scope where
import Control.Applicative
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.Error
import Control.Monad.Cont
import Data.Map (Map)
import Data.List
import Text.PrettyPrint
import qualified Data.Map as Map
import qualified Syntax.Abs as C
import Debug
import Abstract
import Utils
import Pretty
-- The scope monad --------------------------------------------------------
type ScopeEnv = Map C.Id Var -- local vars
data ScopeState = ScopeState
{ scopeStack :: [Scope]
, nextId :: Integer
}
data Module = Module { moduleName :: ModuleName
, moduleParams :: Int
}
data Scope = Scope
{ scopeName :: Var
, scopePublic :: NameSpace
, scopePrivate :: NameSpace
}
data NameSpace = NameSpace
{ nsContents :: Map C.Id [Name]
, nsModules :: Map C.Id [Module]
}
instance Show Scope where show = show . pretty
instance Show ScopeState where
show = unlines . map show . scopeStack
instance Pretty Scope where
pretty (Scope m pub pri) =
vcat [ text "scope" <+> text m
, nest 2 $ text "public:"
, nest 4 $ pretty pub
, nest 2 $ text "private:"
, nest 4 $ pretty pri
]
instance Pretty NameSpace where
pretty (NameSpace ds ms) =
vcat [ pr ds
, pr $ Map.map (map moduleName) ms
]
where
pr m = vcat $ map pr' (Map.toList m)
where
pr' (k, xs) = prId k <+> text "-->" <+> fsep (punctuate comma $ map (text . showName) xs)
prId (C.Id xs) = hcat $ intersperse (text ".") $ map (text . unVar) xs
unVar (C.Var x) = x
type ScopeM = ReaderT ScopeEnv (StateT ScopeState (Either String))
instance Error a => Applicative (Either a) where
pure = return
(<*>) = ap
instance (Monad m, Applicative m) => Applicative (StateT s m) where
pure = return
(<*>) = ap
instance (Monad m, Applicative m) => Applicative (ReaderT s m) where
pure = return
(<*>) = ap
-- Functions on names -----------------------------------------------------
mkVar :: C.Var -> Var
mkVar (C.Var s) = s
mkName :: C.Id -> Name
mkName (C.Id vs) = map mkVar vs
qualify :: Name -> Name -> Name
qualify = (++)
qualifyCId :: Var -> C.Id -> C.Id
qualifyCId x = onCId (C.Var x :)
onCId f (C.Id xs) = C.Id $ f xs
isSubModuleOf :: Module -> Module -> Bool
isSubModuleOf = flip isPrefixOf `on` moduleName
-- Monad operations -------------------------------------------------------
freshName :: ScopeM Var
freshName = do
n <- gets nextId
modify $ \s -> s { nextId = n + 1 }
return $ "@" ++ show n
modifyStack :: ([Scope] -> [Scope]) -> ScopeM ()
modifyStack f = modify $ \s -> s { scopeStack = f $ scopeStack s }
-- Operations -------------------------------------------------------------
filterKeys :: Ord k => (k -> Bool) -> Map k e -> Map k e
filterKeys p = Map.filterWithKey (const . p)
mapScope :: (Map C.Id [Name] -> Map C.Id [Name]) ->
(Map C.Id [Module] -> Map C.Id [Module]) ->
Scope -> Scope
mapScope md mm s = s { scopePublic = mapNS $ scopePublic s
, scopePrivate = mapNS $ scopePrivate s
}
where
mapNS ns =
ns { nsContents = md $ nsContents ns
, nsModules = mm $ nsModules ns
}
zipScope :: (Map C.Id [Name] -> Map C.Id [Name] -> Map C.Id [Name]) ->
(Map C.Id [Module] -> Map C.Id [Module] -> Map C.Id [Module]) ->
Scope -> Scope -> Scope
zipScope fd fm s1 s2 =
s1 { scopePublic = (zipNS `on` scopePublic ) s1 s2
, scopePrivate = (zipNS `on` scopePrivate) s1 s2
}
where
zipNS = zipNameSpace fd fm
zipNameSpace fd fm ns1 ns2 =
ns1 { nsContents = (fd `on` nsContents) ns1 ns2
, nsModules = (fm `on` nsModules ) ns1 ns2
}
filterScope :: (C.Id -> Bool) -> (C.Id -> Bool) -> Scope -> Scope
filterScope pd pm = mapScope (filterKeys pd) (filterKeys pm)
scopeContents :: Scope -> Map C.Id [Name]
scopeContents s = (Map.unionWith (++) `on` nsContents) (scopePublic s) (scopePrivate s)
scopeModules :: Scope -> Map C.Id [Module]
scopeModules s = (Map.unionWith (++) `on` nsModules) (scopePublic s) (scopePrivate s)
unionScope :: Scope -> Scope -> Scope
unionScope = zipScope (+++) (+++)
where (+++) = Map.unionWith (++)
unionsScope :: [Scope] -> Scope
unionsScope = foldr1 unionScope
-- All concrete names are assumed to have the same prefix (specified by the
-- first argument)
dropPrefix :: C.Id -> Scope -> Scope
dropPrefix (C.Id xs) = mapScope dropPrefix dropPrefix
where
dropPrefix = Map.mapKeys (\(C.Id ys) -> C.Id (drop (length xs) ys))
-- Apply scope modifiers to a scope
applyModifiers :: [C.Modifier] -> Scope -> Scope
applyModifiers xs s = foldr apply s xs
where
apply :: C.Modifier -> Scope -> Scope
apply (C.Using xs) = filterNames elem elem xs
apply (C.Hiding xs) = filterNames notElem notElem xs
apply (C.Renaming rs) = mapScope (rename ds ms) (rename ms ms)
where
ms = [ (C.Id [x], C.Id [y]) | C.To (C.ImportMod x) y <- rs ]
ds = [ (C.Id [x], C.Id [y]) | C.To (C.ImportDef x) y <- rs ]
rename ds ms = Map.mapKeys ren
where
ren (C.Id []) = error $ "panic: empty name"
ren k@(C.Id [_]) = maybe k id $ lookup k ds
ren k@(C.Id (m:xs)) = C.Id (m'' ++ xs)
where
m' = C.Id [m]
C.Id m'' = maybe m' id $ lookup m' ms
filterNames pd pm xs = filterScope' (flip pd ds) (flip pm ms)
where
ms = [ C.Id [x] | C.ImportMod x <- xs ]
ds = [ C.Id [x] | C.ImportDef x <- xs ]
filterDef pd pm x
| len == 1 = pd x
| len > 1 = filterMod pm x
| otherwise = error "panic: empty name"
where
len = length (mkName x)
filterMod pm = pm . onCId (take 1)
filterScope' pd pm = filterScope (filterDef pd pm) (filterMod pm)
changeAccess :: C.Access -> Scope -> Scope
changeAccess acc s =
s { scopePrivate = pr
, scopePublic = pu
}
where
unionNS = zipNameSpace (+++) (+++)
(+++) = Map.unionWith (++)
emp = emptyNameSpace
full = unionNS (scopePublic s) (scopePrivate s)
(pu, pr) = case acc of
C.Public -> (full, emp)
C.Private -> (emp, full)
data ResolvedName = VarName Var
| DefName Name
resolve :: C.Id -> ScopeM ResolvedName
resolve x = do
vars <- ask
defs <- gets $ scopeContents . unionsScope . scopeStack
case Map.lookup x vars of
Just v -> return $ VarName v
Nothing -> case Map.lookup x defs of
Nothing -> do
debug . show =<< get
fail $ "not in scope: " ++ show x
Just [c] -> return $ DefName c
Just cs -> do
debug . show =<< get
fail $ "ambiguous name: " ++ show x ++ " can refer to " ++ intercalate ", " (map showName cs)
resolveModule :: C.Id -> ScopeM Module
resolveModule x = do
ms <- gets $ scopeModules . unionsScope . scopeStack
case Map.lookup x ms of
Nothing -> do
s <- get
debug $ show s
fail $ "module not in scope: " ++ show x
Just [m] -> return m
Just ms -> do
debug . show =<< get
fail $ "ambiguous module: " ++ show x ++ " can refer to " ++ intercalate ", " (map (showName . moduleName) ms)
currentModule :: ScopeM Module
currentModule = do
name <- gets $ reverse . map scopeName . scopeStack
n <- asks Map.size
return $ Module name n
showState :: String -> ScopeM ()
showState msg = do
s <- get
debug $ msg ++ "\n " ++ show s
bindVar :: C.Var -> (Var -> ScopeM a) -> ScopeM a
bindVar x ret = local (Map.insert (C.Id [x]) y) (ret y)
where
y = mkVar x
bindDef :: C.Var -> ScopeM Name
bindDef x' = do
m <- moduleName <$> currentModule
let x = C.Id [x']
y = qualify m $ mkName x
debug $ "in module " ++ showName m ++ " binding " ++ show x' ++ " --> " ++ showName y
modifyStack $ \(scope:s) ->
scope { scopePublic = (scopePublic scope)
{ nsContents = Map.insertWith (++) x [y] $ nsContents $ scopePublic scope
}
} : s
return y
bindModule :: C.Var -> ScopeM ()
bindModule m = do
top <- moduleName <$> currentModule
fv <- asks Map.size
let m' = C.Id [m]
am = qualify top $ mkName m'
debug $ "in module " ++ showName top ++ " binding module " ++ show m ++ " --> " ++ showName am
modifyStack $ \ss -> case ss of
[] -> []
scope:ss ->
scope { scopePublic = (scopePublic scope)
{ nsModules = Map.insertWith (++) m' [Module am fv] $ nsModules $ scopePublic scope
}
} : ss
matchPrefix :: C.Id -> ScopeM Scope
matchPrefix m = do
s <- gets $ unionsScope . scopeStack
return $ filterScope (isPrefix m) (isPrefix m) s
where isPrefix (C.Id xs) (C.Id ys) = isPrefixOf xs ys && xs /= ys
-- Merge the given scope with the current scope
addScope :: Scope -> ScopeM ()
addScope s' = modifyStack $ \(s:ss) -> unionScope s s' : ss
-- | In the top scope, make all canonical refer to that scope.
freshCanonicalNames :: ModuleName -> ModuleName -> ScopeM ()
freshCanonicalNames old new =
modifyStack $ \(s : ss) -> mapScope (rename id) (rename onName) s : ss
where
onName f m = m { moduleName = f $ moduleName m }
rename f = Map.map (ren f)
-- Change a binding M.x -> N.M'.y to M.x -> m.M'.y
-- where M and M' have the same length.
ren f ys = map (f qdq) ys
where
qdq y
| old `isPrefixOf` y = qualify new . dequalify $ y
| otherwise = y
dequalify = drop (length old)
-- | Open a module. Assumes that all checks have been made to see that it's ok to
-- open it.
openModule_ :: C.Id -> C.Access -> [C.Modifier] -> ScopeM ()
openModule_ m access mods =
addScope . changeAccess access
. applyModifiers mods
. dropPrefix m =<< matchPrefix m
pushScope :: Var -> ScopeM ()
pushScope m = modifyStack $ (:) (Scope m emptyNameSpace emptyNameSpace)
emptyNameSpace :: NameSpace
emptyNameSpace = NameSpace Map.empty Map.empty
popScope :: ScopeM ()
popScope = do
top <- currentModule
modifyStack $ \(s0:s1:ss) ->
unionScope s1 (mapScope (qual s0) (qual s0) $ noPrivate s0) : ss
where
qual s m = Map.mapKeys (qualifyCId (scopeName s)) m
noPrivate s = s { scopePrivate = emptyNameSpace }
runScope :: ScopeM a -> Either String a
runScope m = flip evalStateT (ScopeState [] 0)
$ flip runReaderT Map.empty
$ m
-- Scope checking ---------------------------------------------------------
-- | The argument must be a 'C.Module'.
scopeCheckProgram :: C.Decl -> Either String [Decl]
scopeCheckProgram (C.Module m tel ds) = runScope $ do
pushScope (mkVar m)
scopeCheckCPS tel $ \tel -> do
m <- moduleName <$> currentModule
section m tel . concat <$> scopeCheck ds
scopeCheckProgram _ = fail "impossible: top-level program must be a module"
class ScopeCheck c a | c -> a where
scopeCheck :: c -> ScopeM a
scopeCheckCPS :: c -> (a -> ScopeM b) -> ScopeM b
scopeCheck x = scopeCheckCPS x return
scopeCheckCPS x ret = scopeCheck x >>= ret
instance ScopeCheck C.Decl [Decl] where
scopeCheck d = case d of
C.Def v tel t C.NoRHS ->
scopeCheckCPS tel $ \tel -> do
t <- scopeCheck t
x <- bindDef v
return [ Type x $ foldr (uncurry Pi) t tel ]
C.Def v tel t (C.RHS e whr) ->
scopeCheckCPS tel $ \tel -> do
t <- scopeCheck t
ds <- case whr of
C.NoWhere -> return []
_ -> do
(m, tel, ds) <- case whr of
C.AnyWhere ds -> do
m <- C.Var <$> freshName
return (m, [], ds)
C.SomeWhere m tel ds -> return (m, tel, ds)
C.NoWhere -> error "impossible"
concat <$> scopeCheck [ C.Module m tel ds
, C.Open (C.Id [m]) C.Private []
]
e <- scopeCheck e
x <- bindDef v -- non-recursive
return [ Defn x tel t e ds ]
C.Module m tel ds -> do
pushScope (mkVar m)
top <- moduleName <$> currentModule
d <- scopeCheckCPS tel $ \tel ->
section top tel . concat <$> scopeCheck ds
popScope
bindModule m
return d
C.Inst m1 tel m2 es mods ->
scopeCheckCPS tel $ \tel -> do
showState "instantiating"
m <- moduleName <$> currentModule
let m1' = qualify m [mkVar m1]
m2' <- resolveModule m2
es <- scopeCheck es
pushScope (mkVar m1)
openModule_ m2 C.Public mods
showState "pushed and opened"
debug $ "m1' = " ++ show m1'
debug $ "m2' = " ++ show (moduleName m2')
freshCanonicalNames (moduleName m2') m1'
popScope
bindModule m1
showState "finished"
return $ section ["_"] tel [ Inst m1' (moduleName m2') es ]
C.Open m access mods -> do
current <- currentModule
m' <- resolveModule m
debug $ "opening module " ++ show (moduleName m')
debug $ " in module " ++ show (moduleName current)
-- Opening a submodule or opening into a non-parameterised module
-- is fine. Otherwise we have to create a temporary module.
if m' `isSubModuleOf` current || moduleParams current == 0
then do
openModule_ m access mods
return []
else do
tmp <- C.Var <$> freshName
concat <$> scopeCheck
[ C.Inst tmp [] m [] mods
, C.Open (C.Id [tmp]) access []
]
instance ScopeCheck C.Expr Expr where
scopeCheck e = case e of
C.Pi x a b -> do
a <- scopeCheck a
bindVar x $ \x -> Pi x a <$> scopeCheck b
C.Set -> return Set
C.App s t -> App <$> scopeCheck s <*> scopeCheck t
C.Lam x t -> bindVar x $ \x -> Lam x <$> scopeCheck t
C.Name x -> do
r <- resolve x
case r of
VarName y -> return $ Var y
DefName c -> return $ Def c
instance ScopeCheck C.Bind (Var, Expr) where
scopeCheckCPS (C.Bind x e) ret = do
e <- scopeCheck e
bindVar x $ \x -> ret (x, e)
instance ScopeCheck c a => ScopeCheck [c] [a] where
scopeCheck = mapM scopeCheck
scopeCheckCPS = runCont . mapM (Cont . scopeCheckCPS)