ext-core library: Add code for merging multiple Core modules into a single module
[ghc-hetmet.git] / utils / ext-core / Language / Core / Merge.hs
diff --git a/utils/ext-core/Language/Core/Merge.hs b/utils/ext-core/Language/Core/Merge.hs
new file mode 100644 (file)
index 0000000..b5ffd05
--- /dev/null
@@ -0,0 +1,147 @@
+{-
+   This module combines multiple External Core modules into
+   a single module, including both datatype and value definitions. 
+-}
+module Language.Core.Merge(merge) where
+
+import Language.Core.Core
+import Language.Core.CoreUtils
+import Language.Core.Utils
+
+import Data.Char
+import Data.Generics
+import Data.List
+
+{-
+   merge turns a group of (possibly mutually recursive) modules
+   into a single module, which should be called main:Main. 
+
+   This doesn't handle dependency-finding; you have to hand it all
+   the modules that your main module depends on (transitively).
+   Language.Core.Dependencies does automatic dependency-finding,
+   but that code is a bit moldy.
+
+   merge takes an extra argument that is a variable substitution.
+   This is because you may want to treat some defined names specially
+   rather than dumping their definitions into the Main module. For
+   example, if my back-end tool defines a new primop that has
+   the type IO (), it's easiest for me if I can consider IO and () as
+   primitive type constructors, though they are not. Thus, I pass in
+   a substitution that says to replace GHC.IOBase.IO with GHC.Prim.IO,
+   and GHC.Base.() with GHC.Prim.(). Of course, I am responsible for
+   providing a type environment defining those names if I want to be
+   able to type the resulting program.
+
+   You can pass in the empty list if you don't understand what the
+   purpose of the substitution is.
+-}
+
+merge    :: [(Qual Var, Qual Var)] -> [Module] -> Module
+merge subst ms = 
+   zapNames subst topNames (Module mainMname newTdefs [Rec topBinds])
+     where -- note: dead code elimination will later remove any names
+           -- that were in the domain of the substitution
+           newTdefs = finishTdefs deadIds $ concat allTdefs
+           (allTdefs, allVdefgs) = unzip $ map (\ (Module _ tds vdefgs) 
+                                             -> (tds, vdefgs)) ms
+           (deadIds,_) = unzip subst
+           topNames    = uniqueNamesIn topBinds (concat allTdefs)
+           topBinds    = finishVdefs deadIds $ flattenBinds (concat allVdefgs)
+
+{-
+   This function finds all of the names in the given group of vdefs and
+   tdefs that are only defined by one module. This is because if function
+   quux is only defined in module foo:Bar.Blat, we want to call it
+   main:Main.quux in the final module, and not main:Main.foo_Bar_Blat_quux,
+   for file size and readability's sake.
+
+   Possible improvements:
+   * take into account that tcons/dcons are separate namespaces
+   * restructure the whole thing to shorten names *after* dead code elim.        
+   (Both of those would allow for more names to be shortened, but aren't
+   strictly necessary.)
+-}
+uniqueNamesIn :: [Vdef] -> [Tdef] -> [Qual Var]
+uniqueNamesIn topBinds allTdefs = res
+  where allNames = vdefNamesQ topBinds ++ tdefNames allTdefs
+        dups     = dupsUnqual allNames
+        res      = allNames \\ dups
+
+-- This takes each top-level name of the form Foo.Bar.blah and
+-- renames it to FoozuBarzublah (note we *don't* make it exported!
+-- This is so we know which names were in the original program and
+-- which were dumped in from other modules, and thus can eliminate
+-- dead code.)
+zapNames :: Data a => [(Qual Var, Qual Var)] -> [Qual Var] -> a -> a
+zapNames subst qvs = everywhereBut (mkQ False (\ (_::String) -> True))
+             (mkT (fixupName subst qvs))
+
+-- also need version for type and data constructors
+-- don't forget to *not* zap if something has the primitive module name
+-- We hope and pray there are no top-level unqualified names that are used in
+-- more than one module. (Can we assume this?) (I think so, b/c -fext-core
+-- attaches uniques to things. But could still perhaps go wrong if we fed
+-- in .hcr files that were generated in diff. compilation sessions...)
+-- (This wouldn't be too hard to fix, but should state the assumption,
+-- and how to remove it.)
+
+fixupName :: [(Qual Var, Qual Var)] -> [Qual Var] -> Qual Var -> Qual Var
+-- For a variable in the domain of the substitution, just
+-- apply the substitution.
+fixupName subst _ oldVar | Just newVar <- lookup oldVar subst = newVar
+-- We don't alter unqualified names, since we just need to make sure
+-- everything can go in the Main module.
+fixupName _ _ vr@(Nothing,_) = vr
+-- Nor do we alter anything defined in the Main module
+-- or in the primitive or Bool modules
+-- (because we basically treat the Bool type as primitive.)
+fixupName _ _ vr@(Just mn, _) | mn == mainMname || mn == wrapperMainMname ||
+                            mn == primMname || mn == boolMname = vr
+-- For a variable that is defined by only one module in scope, we 
+-- give it a name that is just its unqualified name, without the original
+-- module and package names.
+fixupName _ uniqueNames (_, v) | okay = 
+   (mkMname v, v)
+     where okay = any (\ (_,v1) -> v == v1) uniqueNames
+-- This is the case for a name that is defined in more than one
+-- module. In this case, we have to give it a unique name to disambiguate
+-- it from other definitions of the same name. We combine the package and
+-- module name to give a unique prefix.
+fixupName _ _ (Just (M (P pname, hierNames, leafName)), varName) = 
+   (mkMname varName, -- see comment for zapNames 
+     (if isUpperStr varName then capitalize else id) $
+       intercalate "zu" (pname:(hierNames ++ [leafName, varName])))
+  where capitalize (ch:rest) = (toUpper ch):rest
+        capitalize ""        = ""
+
+mkMname :: Var -> Mname
+-- icky hack :-(
+-- necessary b/c tycons and datacons have to be qualified,
+-- but we want to write fixupName as a generic transformation on vars.
+mkMname v = if isUpperStr v then Just mainMname else Nothing
+
+isUpperStr :: String -> Bool
+isUpperStr (c:_)     = isUpper c
+isUpperStr []        = False
+
+dupsUnqual :: [Qual Var] -> [Qual Var]
+dupsUnqual = dupsBy (\ (_,v1) (_,v2) -> v1 == v2)
+
+-- We remove any declarations for tcons/dcons that are in
+-- the domain of the substitution. Why? Because we assume that
+-- the substitution maps anything in its domain onto something
+-- with a different module name from the main one. If you want
+-- to substitute Main-module-defined things for Main-module-defined
+-- things, you can do that before merging modules.
+finishTdefs :: [Qual Var] -> [Tdef] -> [Tdef]
+finishTdefs namesToDrop = filter isOkay
+  where isOkay (Newtype qtc qtc1 _ _) = 
+               qtc `notElem` namesToDrop 
+            && qtc1 `notElem` namesToDrop
+        isOkay (Data qtc _ cdefs) = 
+               qtc `notElem` namesToDrop 
+            && cdefsOkay cdefs
+        cdefsOkay = all cdefOkay
+        cdefOkay (Constr qdc _ _) = qdc `notElem` namesToDrop
+finishVdefs :: [Qual Var] -> [Vdef] -> [Vdef]
+finishVdefs namesToDrop = filter (\ (Vdef (qv,_,_)) -> qv `notElem` namesToDrop)