{- ---------------------------------------------------------------------------
-- imported by Main and Need
-}
module PreImport (HideDeclIds,qualRename,preImport) where
import List(nub,intersect,(\\))
import TokenId(TokenId(..),tPrelude,tNHCInternal,tYHCDynamic
,t_Arrow,ensureM,forceM,dropM
,rpsPrelude,t_List,isTidCon
,tRatioMod,visRatio,visRational,visRatioCon)
import SysDeps(PackedString,packString)
import Syntax hiding (TokenId)
import IdKind
import qualified Data.Set as Set
import qualified Data.Map as Map
import Util.Extra
import Flags
import IExtract
import Info hiding (TokenId)
import PreImp(HideDeclIds,HideDeclType,HideDeclData,HideDeclDataPrim
,HideDeclClass,HideDeclInstance,HideDeclVarsType)
import Maybe
import Building(Compiler(..),compiler)
-- | Internal, fully coalesced import declaration
type IntImpDecl = (TokenId {-module name-}, ImportedNamesInScope)
-- | There are two sets of names in scope, the NQ-set and the Q-set.
-- For every imported module, an individual import decl can enlarge
-- either both sets together, or just the Q-set. When the two sets are
-- identical by construction, our representation takes a short-cut and
-- stores only one set, calling it NQ. When the NQ-set is empty, we store
-- only the Q-set. When the two sets are different and non-empty, we
-- store both, but the Q-set is always equal to or larger than the NQ-set.
data ImportedNamesInScope =
NQ NameSetSpec -- ^represents Q-set = NQ-set.
| Q NameSetSpec -- represents NQ-set = empty.
| Both NameSetSpec{-notQ-} NameSetSpec{-Q-}
-- invariant: Q-set >= NQ-set.
-- | The representation of a name-set is a mixture of intension and extension.
-- [@Deny []@] means everything found in the exporting module
-- [@Deny xs@] means everything excluding the named entities
-- [@Allow xs@] means only the named entities
-- [@Allow []@] means no entities, probably a very rare specification
-- Hence, @Deny [] > Deny xs > Allow xs > Allow []@
data NameSetSpec =
Allow [(TokenId,IE)]
| Deny [(TokenId,IE)]
-- | Assuming that import decls have been converted to nameset specifications,
-- the 'combine' function joins two given specifications. Imports are
-- cumulative, so a nameset can only get larger.
combine :: NameSetSpec -> NameSetSpec -> NameSetSpec
combine (Deny []) _ = Deny []
combine _ (Deny []) = Deny []
combine (Allow xs) (Deny ys) = Deny (ys\\xs)
combine (Allow xs) (Allow ys) = Allow (nub (xs++ys))
combine (Deny xs) (Deny ys) = Deny (intersect xs ys)
combine (Deny xs) (Allow ys) = Deny (xs\\ys)
-- | The rules for combining different imports of the same module are complex.
-- The second argument to the 'joinNames' function is the accumulated
-- state of all imports so far, and can thus have the Both constructor,
-- representing differing NQ- and Q-sets. The first argument is the
-- additional import decl, and can enlarge either both sets (NQ) or just the
-- Q-set, but cannot enlarge the two sets separately (no Both constructor).
joinNames :: ImportedNamesInScope -> ImportedNamesInScope -> ImportedNamesInScope
joinNames (NQ new) (NQ old) = NQ (combine old new)
joinNames (Q new) (Q old) = Q (combine old new)
joinNames (Q q) (NQ nq) = joinNames (NQ nq) (Q q)
joinNames (NQ (Deny [])) (Q old) = NQ (Deny [])
joinNames (NQ new) (Q old) = Both new (combine old new)
joinNames (NQ (Deny [])) (Both nq q) = NQ (Deny [])
joinNames (NQ new) (Both nq q) = Both (combine nq new) (combine q new)
joinNames (Q new) (Both nq q) = Both nq (combine q new)
-- | Finally, we need a lookup function that can tell us whether a name
-- is in the permissible set of names specified by the import decls.
nameInScope :: ImportedNamesInScope -> TokenId -> Bool
nameInScope (NQ nameset) tid = tid `inScope` nameset
nameInScope (Q nameset) tid = tid `inScope` nameset
nameInScope (Both nq q) tid = (tid `inScope` nq) || (tid `inScope` q)
inScope :: TokenId -> NameSetSpec -> Bool
inScope tid (Deny []) = True
inScope tid (Deny xs) = not (tid `elem` (map fst xs))
inScope tid (Allow xs) = (tid `elem` (map fst xs))
-- | 'mustQualify' assumes a separate test for inclusion in the permissible names
mustQualify :: ImportedNamesInScope -> TokenId -> Bool
mustQualify (NQ nameset) tid = False
mustQualify (Q nameset) tid = tid `inScope` nameset
mustQualify (Both nq q) tid = (tid `inScope` q) && not (tid `inScope` nq)
----
qualRename :: TokenId -> [ImpDecl TokenId] -> TokenId -> [TokenId]
qualRename modid impdecls = qualRename' qTree
where
qualRename' t q@(Qualified t1 t2)
| (Visible t1)==modid && t1/=rpsPrelude = [Visible t2]
| otherwise =
case Map.lookup t1 t of
Nothing -> [q]
Just ts -> map (\t'-> Qualified t' t2) ts
qualRename' t v = [v]
qTree = foldr qualR Map.empty impdecls
qualR (Import _ _) t = t
qualR (ImportQ _ _) t = t
qualR (ImportQas (_,Visible id) (_,Visible id') _) t = Map.insertWith (++) id' [id] t
qualR (Importas (_,Visible id) (_,Visible id') _) t = Map.insertWith (++) id' [id] t
---- ===================================
preImport :: Flags -> TokenId -> Set.Set TokenId
-> Maybe [Export TokenId] -> [ImpDecl TokenId]
-> Either String
((TokenId->Bool) -> TokenId -> IdKind -> IE
,[(PackedString
,(PackedString, PackedString, Set.Set TokenId)
-> [[TokenId]]
-> Bool
,HideDeclIds
)
]
)
-- When the export list is :: Maybe [Export TokenId])
-- Nothing -> export nothing except instances
-- Just [] -> export everything
-- Just xs -> export only entities from the list xs
preImport flags mtid@(Visible mrps) need (Just expdecls) impdecls =
let impdecls' = transImport impdecls in
Right ( if null expdecls || (isJust . flip Map.lookup exportAT) (mtid,Modid)
then reExportAll
else reExportTid mrps exportAT
, map (mkNeed need exportAT) impdecls')
where
exportAT = mkExportAT expdecls
preImport flags mtid@(Visible mrps) need Nothing impdecls =
let impdecls' = transImport impdecls in
Right (reExportTid mrps Map.empty, map (mkNeed need Map.empty) impdecls')
{-
-- transImport orders the import files (with prelude last),
-- inserts qualified import of prelude,
-- and checks that all imports are consistent
-}
transImport :: [ImpDecl TokenId]
-> [IntImpDecl]
transImport impdecls = impdecls'
where
impdecls' = (reorder [] . {-sortImport .-} traverse Map.empty False)
(ImportQ (noPos,tNHCInternal) (Hiding [])
:ImportQ (noPos,tRatioMod) (NoHiding
[EntityConClsAll noPos visRational
,EntityConClsAll noPos visRatio
,EntityVar noPos visRatioCon ])
:impdecls)
reorder p [] = p
reorder p (m@(k,v):xs) | k==tPrelude = reorder (m:p) xs
| k==tNHCInternal = reorder (m:p) xs
| otherwise = m: reorder p xs
{-
-- Place imports into order, ensure Prelude is last
-- Why? The order is lexicographic of the /reversed/ module name???
-- Changed to use simpler 'reorder' above.
sortImport impdecls =
( map snd
. sortBy cmp
. map (\(k,v)-> if k==tPrelude || k==tNHCInternal
then (Right k,(k,v))
else (Left k, (k,v)) )
) impdecls
where cmp (a, _) (b, _) = case compare a b of
EQ -> error "Fail in PreImport.transImport\n"
x -> x
-}
traverse :: Map.Map TokenId ImportedNamesInScope
-> Bool -- have we found an explicit Prelude import yet?
-> [ImpDecl TokenId]
-> [(TokenId, ImportedNamesInScope)]
traverse acc True [] = Map.toList acc
traverse acc False [] = traverse acc False [Import (noPos,tPrelude)
(Hiding [])]
traverse acc prel (x:xs) =
case extractImp x of
(tid,info) ->
traverse (Map.insertWith joinNames tid info acc) (prel || tid==tPrelude) xs
extractImp (ImportQ (pos,tid) impspec) =
(tid, Q (extractSpec impspec))
extractImp (ImportQas (pos,tid) (apos,atid) impspec) =
(tid, Q (extractSpec impspec))
extractImp (Import (pos,tid) impspec) =
(tid, NQ (extractSpec impspec))
extractImp (Importas (pos,tid) (apos,atid) impspec) =
(tid, NQ (extractSpec impspec))
extractSpec (NoHiding entities) = Allow (concatMap extractImpEntity entities)
extractSpec (Hiding entities) = Deny (concatMap extractImpEntity entities)
extractImpEntity e =
map (\e-> case e of ((tid,kind),ie) -> (tid,ie)) (extractEntity e)
{- Now obsolete i.e. never report explicit/hiding conflicts
--checkImport :: (TokenId, ([Pos],[TokenId],ImportedNamesInScope))
-- -> [String]
--checkImport (tid,(nq,q,pos_spec)) =
-- case partition (isLeft . snd) pos_spec of
-- ([],hide) -> [] -- Only explicit hide
-- (imp,[]) -> [] -- Only explicit imports
-- (imp,hide) ->
-- if (null . filter (not.null) . map (dropRight . snd)) hide
-- then [] -- Ok as all hidings are empty
-- else ["Conflicting imports for " ++ show tid ++
-- ", used both explicit imports (at" ++
-- (mixCommaAnd . map (strPos . fst)) imp
-- ++ ") and explicit hidings (at " ++
-- (mixCommaAnd . map (strPos . fst)) hide ++")."]
-}
{- Obsolete in H'98
--checkForMultipleImport imports =
-- case foldr prepare (initAT,[]) imports of
-- (qm,qas) ->
-- case (filter (elemM qm) qas,filter ((1/=) . length) (group qas)) of
-- (qas,qas2) ->
-- map (\tid -> "Can not rename a module to " ++ show tid ++
-- " as another module with that name is imported qualified.") qas
-- ++
-- map (\tids -> "More than one module is renamed to " ++
-- show (head tids) ++ ".") qas2
-- where
-- prepare (tid,(nq,Just tids,pos_spec)) (qm,qas) = (addM qm tid,tids++qas)
-- prepare _ (qm,qas) = (qm,qas)
-}
------------------------------------------------------------------------------
mkExportAT :: [Export TokenId] -> Map.Map (TokenId,IdKind) IE
mkExportAT expdecls = exportAT
where
exportAT :: Map.Map (TokenId,IdKind) IE
exportAT = foldr export Map.empty (concatMap preX expdecls)
export (key,value) t = Map.insertWith combIE key value t
preX (ExportEntity _ e) = extractEntity e
preX (ExportModid _ tid) = [((tid,Modid),IEall)]
extractEntity :: Entity TokenId -> [((TokenId, IdKind), IE)]
extractEntity (EntityVar pos tid) = [((tid,Var),IEall)]
extractEntity (EntityConClsAll pos tid)
| (tid==t_Arrow || tid==t_List) = [((dropM tid,TCon),IEall)]
| otherwise = [((tid,TC),IEall)]
extractEntity (EntityConClsSome pos tid [])
| (tid==t_Arrow || tid==t_List) = [((dropM tid,TCon),IEabs)]
| otherwise = [((tid,TC),IEabs)]
extractEntity (EntityConClsSome pos tid ids)
| (tid==t_Arrow || tid==t_List) = ((dropM tid,TCon),IEsome) : constrs
| otherwise = ((tid,TC),IEsome) : subordinates
where constrs = map (\(pos,tid)-> ((tid,Con),IEsel)) ids
subordinates = if any (isTidCon.snd) ids then
map (\(pos,tid)-> if isTidCon tid then
((tid,Con),IEsel)
else ((tid,Field),IEsel))
ids
else map (\(pos,tid)-> ((tid,Method),IEsel)) ids
-- could really be Method or Field...
------
reExportAll :: (TokenId->Bool) -> TokenId -> IdKind -> IE
reExportAll q tid kind = IEall
reExportTid :: PackedString -> Map.Map (TokenId,IdKind) IE
-> (TokenId->Bool) -> TokenId -> IdKind -> IE
reExportTid modname exportAT mustBeQualified tid kind =
case Map.lookup (dropM tid, kind) exportAT of
Just imp | not (mustBeQualified tid) -> imp
_ ->
case Map.lookup (forceM modname tid, kind) exportAT of
Just imp | mustBeQualified tid -> imp
_ -> IEnone
--------------------------------------
{-
-- The selectors for (hideDeclType,hideDeclData,hideDeclDataPrim,hideDeclClass,
-- hideDeclInstance,hideDeclVarsType) are defined in PreImp and used in ParseI
-}
mkNeed :: Set.Set TokenId
-> Map.Map (TokenId,IdKind) IE
-> IntImpDecl
-> ( PackedString
, (PackedString, PackedString, Set.Set TokenId)
-> [[TokenId]] -> Bool
, HideDeclIds
)
mkNeed needM exportSpec (vt@(Visible modname), importSpec) =
( modname
, \needI -> any (needFun needI)
, (hideDeclType,hideDeclData,hideDeclDataPrim
,hideDeclClass,hideDeclInstance,hideDeclVarsType)
)
where
imported = nameInScope importSpec . dropM
q = mustQualify importSpec . dropM
reExport
| reExportModule = reExportAll
| otherwise = reExportTid modname exportSpec
reExportModule = isJust (Map.lookup (vt,Modid) exportSpec)
--needFun' x y =
-- let result = needFun x y in
-- strace ("needFun: "++show (fst3 x)++"/"++show (snd3 x)++" "
-- ++show y++" "++show result) $ result
needFun (orps,rps,needI) ns@(n:_) =
Set.member (ensureM rps n) needI
-- is used by other interface (real name)
-- (only check first name = type or class)
|| any (\n-> imported n &&
( ((`Set.member` needM) . forceM orps) n
-- used qualified and imported (un)qualified
|| (not (q n)) &&
( ((`Set.member` needM) . dropM) n
-- used unqualified and imported unqualified
|| reExportModule
-- reexported whether used or not
)
))
ns
hideDeclType :: HideDeclType
hideDeclType st attr (Simple pos tid tvs) typ =
if imported tid then
iextractType (reExport q tid TSyn) attr q pos tid tvs typ () st
else
iextractType IEnone attr (\_->True) pos tid tvs typ () st
-- used by an interface file, not directly in source code
hideDeclData :: HideDeclData
hideDeclData st attr ctxs (Simple pos tid tvs) constrs needs der =
if imported tid then
iextractData (reExport q tid TCon) q attr ctxs pos
tid tvs constrs (safetail (concat needs)) () st
else
iextractData IEnone (\_->True) attr ctxs pos
tid tvs [] {-(if q tid then constrs else [])-} [] () st
where safetail xs | null xs = xs
| otherwise = tail xs
hideDeclDataPrim :: HideDeclDataPrim
hideDeclDataPrim st (pos,tid) size =
if imported tid then
iextractDataPrim (reExport q tid TCon) q pos tid size () st
else
iextractDataPrim IEnone (\_->True) pos tid size () st
hideDeclClass :: HideDeclClass
hideDeclClass st ctxs (pos,tid) [tvar] methods needs =
if imported tid then
iextractClass (reExport q tid TClass) q pos ctxs tid
(snd tvar) methods (safetail (concat needs)) () st
else
iextractClass IEnone (\_->True) pos ctxs tid
(snd tvar) (if q tid then methods else []) [] () st
where safetail xs | null xs = xs
| otherwise = tail xs
hideDeclInstance :: HideDeclInstance
hideDeclInstance st (_,mod) ctxs (pos,cls) [typ] =
iextractInstance mod ctxs pos cls typ () st
-- instances are always imported, they cannot be hidden.
hideDeclVarsType :: HideDeclVarsType
hideDeclVarsType st postidanots ctxs typ =
-- interface files should never depend on functions
{- we don't create interface files with more than one function/type
case filter (isJust . lookupAT impT . dropM . snd . fst) postidanots of
[] -> st
postidanots ->
-}
iextractVarsType (\q tid idkind ->
if imported tid && not (q tid)
then reExport q tid idkind
else IEnone)
q postidanots ctxs typ () st