git.iko.soy/rowtype-yoga/purescript-native
on 06e4332 help sign in
605 lines 22 kB history raw
1-----------------------------------------------------------------------------
2--
3-- Module : psc-bundle
4-- Copyright : (c) Phil Freeman 2015
5-- License : MIT
6--
7-- Maintainer : Phil Freeman <paf31@cantab.net>
8-- Stability : experimental
9-- Portability :
10--
11-- | Bundles compiled PureScript modules for the browser.
12--
13-- This module takes as input the individual generated modules from 'Language.PureScript.Make' and
14-- performs dead code elimination, filters empty modules,
15-- and generates the final Javascript bundle.
16-----------------------------------------------------------------------------
18{-# LANGUAGE PatternGuards #-}
19{-# LANGUAGE FlexibleContexts #-}
20{-# LANGUAGE ScopedTypeVariables #-}
21{-# LANGUAGE TupleSections #-}
23module Language.PureScript.Bundle (
24 bundle
25 , ModuleIdentifier(..)
26 , moduleName
27 , ModuleType(..)
28 , ErrorMessage(..)
29 , printErrorMessage
30 , getExportedIdentifiers
31) where
33import Prelude ()
34import Prelude.Compat
36import Data.List (nub, stripPrefix)
37import Data.Maybe (mapMaybe, catMaybes)
38import Data.Generics (everything, everywhere, mkQ, mkT)
39import Data.Graph
40import Data.Version (showVersion)
42import qualified Data.Set as S
44import Control.Monad
45import Control.Monad.Error.Class
46import Language.JavaScript.Parser
47import Language.JavaScript.Parser.AST
49import qualified Paths_purescript as Paths
51-- | The type of error messages. We separate generation and rendering of errors using a data
52-- type, in case we need to match on error types later.
53data ErrorMessage
54 = UnsupportedModulePath String
55 | InvalidTopLevel
56 | UnableToParseModule String
57 | UnsupportedExport
58 | ErrorInModule ModuleIdentifier ErrorMessage
59 deriving (Show, Read)
61-- | Modules are either "regular modules" (i.e. those generated by psc) or foreign modules.
62data ModuleType
63 = Regular
64 | Foreign
65 deriving (Show, Read, Eq, Ord)
67showModuleType :: ModuleType -> String
68showModuleType Regular = "Regular"
69showModuleType Foreign = "Foreign"
71-- | A module is identified by its module name and its type.
72data ModuleIdentifier = ModuleIdentifier String ModuleType deriving (Show, Read, Eq, Ord)
74moduleName :: ModuleIdentifier -> String
75moduleName (ModuleIdentifier name _) = name
77-- | A piece of code is identified by its module and its name. These keys are used to label vertices
78-- in the dependency graph.
79type Key = (ModuleIdentifier, String)
81-- | An export is either a "regular export", which exports a name from the regular module we are in,
82-- or a reexport of a declaration in the corresponding foreign module.
83--
84-- Regular exports are labelled, since they might re-export an operator with another name.
85data ExportType
86 = RegularExport String
87 | ForeignReexport
88 deriving (Show, Eq, Ord)
90-- | There are four types of module element we are interested in:
91--
92-- 1) Require statements
93-- 2) Member declarations
94-- 3) Export lists
95-- 4) Everything else
96--
97-- Each is labelled with the original AST node which generated it, so that we can dump it back
98-- into the output during codegen.
99data ModuleElement
100 = Require JSStatement String (Either String ModuleIdentifier)
101 | Member JSStatement Bool String JSExpression [Key]
102 | ExportsList [(ExportType, String, JSExpression, [Key])]
103 | Other JSStatement
104 deriving (Show)
106-- | A module is just a list of elements of the types listed above.
107data Module = Module ModuleIdentifier [ModuleElement] deriving (Show)
109-- | Prepare an error message for consumption by humans.
110printErrorMessage :: ErrorMessage -> [String]
111printErrorMessage (UnsupportedModulePath s) =
112 [ "A CommonJS module has an unsupported name (" ++ show s ++ ")."
113 , "The following file names are supported:"
114 , " 1) index.js (psc native modules)"
115 , " 2) foreign.js (psc foreign modules)"
116 ]
117printErrorMessage InvalidTopLevel =
118 [ "Expected a list of source elements at the top level." ]
119printErrorMessage (UnableToParseModule err) =
120 [ "The module could not be parsed:"
121 , err
122 ]
123printErrorMessage UnsupportedExport =
124 [ "An export was unsupported. Exports can be defined in one of two ways: "
125 , " 1) exports.name = ..."
126 , " 2) exports = { ... }"
127 ]
128printErrorMessage (ErrorInModule mid e) =
129 ("Error in module " ++ displayIdentifier mid ++ ":")
130 : ""
131 : map (" " ++) (printErrorMessage e)
132 where
133 displayIdentifier (ModuleIdentifier name ty) =
134 name ++ " (" ++ showModuleType ty ++ ")"
136-- | Calculate the ModuleIdentifier which a require(...) statement imports.
137checkImportPath :: String -> ModuleIdentifier -> S.Set String -> Either String ModuleIdentifier
138checkImportPath "./foreign" m _ =
139 Right (ModuleIdentifier (moduleName m) Foreign)
140checkImportPath name _ names
141 | Just name' <- stripPrefix "../" name
142 , name' `S.member` names = Right (ModuleIdentifier name' Regular)
143checkImportPath name _ _ = Left name
145-- | Compute the dependencies of all elements in a module, and add them to the tree.
146--
147-- Members and exports can have dependencies. A dependency is of one of the following forms:
148--
149-- 1) module.name or member["name"]
150--
151-- where module was imported using
152--
153-- var module = require("Module.Name");
154--
155-- 2) name
156--
157-- where name is the name of a member defined in the current module.
158withDeps :: Module -> Module
159withDeps (Module modulePath es) = Module modulePath (map expandDeps es)
160 where
161 -- | Collects all modules which are imported, so that we can identify dependencies of the first type.
162 imports :: [(String, ModuleIdentifier)]
163 imports = mapMaybe toImport es
164 where
165 toImport :: ModuleElement -> Maybe (String, ModuleIdentifier)
166 toImport (Require _ nm (Right mid)) = Just (nm, mid)
167 toImport _ = Nothing
169 -- | Collects all member names in scope, so that we can identify dependencies of the second type.
170 boundNames :: [String]
171 boundNames = mapMaybe toBoundName es
172 where
173 toBoundName :: ModuleElement -> Maybe String
174 toBoundName (Member _ _ nm _ _) = Just nm
175 toBoundName _ = Nothing
177 -- | Calculate dependencies and add them to the current element.
178 expandDeps :: ModuleElement -> ModuleElement
179 expandDeps (Member n f nm decl _) = Member n f nm decl (nub $ dependencies modulePath decl)
180 expandDeps (ExportsList exps) = ExportsList (map expand exps)
181 where
182 expand (ty, nm, n1, _) = (ty, nm, n1, nub (dependencies modulePath n1))
183 expandDeps other = other
185 dependencies :: ModuleIdentifier -> JSExpression -> [(ModuleIdentifier, String)]
186 dependencies m = everything (++) (mkQ [] toReference)
187 where
188 toReference :: JSExpression -> [(ModuleIdentifier, String)]
189 toReference (JSMemberDot mn _ nm)
190 | JSIdentifier _ mn' <- mn
191 , JSIdentifier _ nm' <- nm
192 , Just mid <- lookup mn' imports
193 = [(mid, nm')]
194 toReference (JSMemberSquare mn _ nm _)
195 | JSIdentifier _ mn' <- mn
196 , Just nm' <- fromStringLiteral nm
197 , Just mid <- lookup mn' imports
198 = [(mid, nm')]
199 toReference (JSIdentifier _ nm)
200 | nm `elem` boundNames
201 = [(m, nm)]
202 toReference _ = []
204-- String literals include the quote chars
205fromStringLiteral :: JSExpression -> Maybe String
206fromStringLiteral (JSStringLiteral _ str) = Just $ trimStringQuotes str
207fromStringLiteral _ = Nothing
209trimStringQuotes :: String -> String
210trimStringQuotes str = reverse $ drop 1 $ reverse $ drop 1 $ str
212commaList :: JSCommaList a -> [a]
213commaList JSLNil = []
214commaList (JSLOne x) = [x]
215commaList (JSLCons l _ x) = commaList l ++ [x]
217trailingCommaList :: JSCommaTrailingList a -> [a]
218trailingCommaList (JSCTLComma l _) = commaList l
219trailingCommaList (JSCTLNone l) = commaList l
221-- | Attempt to create a Module from a Javascript AST.
222--
223-- Each type of module element is matched using pattern guards, and everything else is bundled into the
224-- Other constructor.
225toModule :: forall m. (MonadError ErrorMessage m) => S.Set String -> ModuleIdentifier -> JSAST -> m Module
226toModule mids mid top
227 | JSAstProgram smts _ <- top = Module mid <$> traverse toModuleElement smts
228 | otherwise = err InvalidTopLevel
229 where
230 err = throwError . ErrorInModule mid
232 toModuleElement :: JSStatement -> m ModuleElement
233 toModuleElement stmt
234 | Just (importName, importPath) <- matchRequire mids mid stmt
235 = pure (Require stmt importName importPath)
236 toModuleElement stmt
237 | Just (exported, name, decl) <- matchMember stmt
238 = pure (Member stmt exported name decl [])
239 toModuleElement stmt
240 | Just props <- matchExportsAssignment stmt
241 = (ExportsList <$> traverse toExport (trailingCommaList props))
242 where
243 toExport :: JSObjectProperty -> m (ExportType, String, JSExpression, [Key])
244 toExport (JSPropertyNameandValue name _ [val]) =
245 (,,val,[]) <$> exportType val
246 <*> extractLabel' name
247 toExport _ = err UnsupportedExport
249 exportType :: JSExpression -> m ExportType
250 exportType (JSMemberDot f _ _)
251 | JSIdentifier _ "$foreign" <- f
252 = pure ForeignReexport
253 exportType (JSMemberSquare f _ _ _)
254 | JSIdentifier _ "$foreign" <- f
255 = pure ForeignReexport
256 exportType (JSIdentifier _ s) = pure (RegularExport s)
257 exportType _ = err UnsupportedExport
259 extractLabel' = maybe (err UnsupportedExport) pure . extractLabel
261 toModuleElement other = pure (Other other)
263-- Get a list of all the exported identifiers from a foreign module.
264--
265-- TODO: what if we assign to exports.foo and then later assign to
266-- module.exports (presumably overwriting exports.foo)?
267getExportedIdentifiers :: (MonadError ErrorMessage m)
268 => String
269 -> JSAST
270 -> m [String]
271getExportedIdentifiers mname top
272 | JSAstProgram stmts _ <- top = concat <$> traverse go stmts
273 | otherwise = err InvalidTopLevel
274 where
275 err = throwError . ErrorInModule (ModuleIdentifier mname Foreign)
277 go stmt
278 | Just props <- matchExportsAssignment stmt
279 = traverse toIdent (trailingCommaList props)
280 | Just (True, name, _) <- matchMember stmt
281 = pure [name]
282 | otherwise
283 = pure []
285 toIdent (JSPropertyNameandValue name _ [_]) =
286 extractLabel' name
287 toIdent _ =
288 err UnsupportedExport
290 extractLabel' = maybe (err UnsupportedExport) pure . extractLabel
292-- Matches JS statements like this:
293-- var ModuleName = require("file");
294matchRequire :: S.Set String
295 -> ModuleIdentifier
296 -> JSStatement
297 -> Maybe (String, Either String ModuleIdentifier)
298matchRequire mids mid stmt
299 | JSVariable _ jsInit _ <- stmt
300 , [JSVarInitExpression var varInit] <- commaList jsInit
301 , JSIdentifier _ importName <- var
302 , JSVarInit _ jsInitEx <- varInit
303 , JSMemberExpression req _ argsE _ <- jsInitEx
304 , JSIdentifier _ "require" <- req
305 , [ Just importPath ] <- map fromStringLiteral (commaList argsE)
306 , importPath' <- checkImportPath importPath mid mids
307 = Just (importName, importPath')
308 | otherwise
309 = Nothing
311-- Matches JS member declarations.
312matchMember :: JSStatement -> Maybe (Bool, String, JSExpression)
313matchMember stmt
314 -- var foo = expr;
315 | JSVariable _ jsInit _ <- stmt
316 , [JSVarInitExpression var varInit] <- commaList jsInit
317 , JSIdentifier _ name <- var
318 , JSVarInit _ decl <- varInit
319 = Just (False, name, decl)
320 -- exports.foo = expr; exports["foo"] = expr;
321 | JSAssignStatement e (JSAssign _) decl _ <- stmt
322 , Just name <- accessor e
323 = Just (True, name, decl)
324 | otherwise
325 = Nothing
326 where
327 accessor :: JSExpression -> Maybe String
328 accessor (JSMemberDot exports _ nm)
329 | JSIdentifier _ "exports" <- exports
330 , JSIdentifier _ name <- nm
331 = Just name
332 accessor (JSMemberSquare exports _ nm _)
333 | JSIdentifier _ "exports" <- exports
334 , Just name <- fromStringLiteral nm
335 = Just name
336 accessor _ = Nothing
338-- Matches assignments to module.exports, like this:
339-- module.exports = { ... }
340matchExportsAssignment :: JSStatement -> Maybe JSObjectPropertyList
341matchExportsAssignment stmt
342 | JSAssignStatement e (JSAssign _) decl _ <- stmt
343 , JSMemberDot module' _ exports <- e
344 , JSIdentifier _ "module" <- module'
345 , JSIdentifier _ "exports" <- exports
346 , JSObjectLiteral _ props _ <- decl
347 = Just props
348 | otherwise
349 = Nothing
351extractLabel :: JSPropertyName -> Maybe String
352extractLabel (JSPropertyString _ nm) = Just (trimStringQuotes nm)
353extractLabel (JSPropertyIdent _ nm) = Just nm
354extractLabel _ = Nothing
356-- | Eliminate unused code based on the specified entry point set.
357compile :: [Module] -> [ModuleIdentifier] -> [Module]
358compile modules [] = modules
359compile modules entryPoints = filteredModules
360 where
361 (graph, _, vertexFor) = graphFromEdges verts
363 -- | The vertex set
364 verts :: [(ModuleElement, Key, [Key])]
365 verts = do
366 Module mid els <- modules
367 concatMap (toVertices mid) els
368 where
369 -- | Create a set of vertices for a module element.
370 --
371 -- Some special cases worth commenting on:
372 --
373 -- 1) Regular exports which simply export their own name do not count as dependencies.
374 -- Regular exports which rename and reexport an operator do count, however.
375 --
376 -- 2) Require statements don't contribute towards dependencies, since they effectively get
377 -- inlined wherever they are used inside other module elements.
378 toVertices :: ModuleIdentifier -> ModuleElement -> [(ModuleElement, Key, [Key])]
379 toVertices p m@(Member _ _ nm _ deps) = [(m, (p, nm), deps)]
380 toVertices p m@(ExportsList exps) = mapMaybe toVertex exps
381 where
382 toVertex (ForeignReexport, nm, _, ks) = Just (m, (p, nm), ks)
383 toVertex (RegularExport nm, nm1, _, ks) | nm /= nm1 = Just (m, (p, nm1), ks)
384 toVertex _ = Nothing
385 toVertices _ _ = []
387 -- | The set of vertices whose connected components we are interested in keeping.
388 entryPointVertices :: [Vertex]
389 entryPointVertices = catMaybes $ do
390 (_, k@(mid, _), _) <- verts
391 guard $ mid `elem` entryPoints
392 return (vertexFor k)
394 -- | The set of vertices reachable from an entry point
395 reachableSet :: S.Set Vertex
396 reachableSet = S.fromList (concatMap (reachable graph) entryPointVertices)
398 filteredModules :: [Module]
399 filteredModules = map filterUsed modules
400 where
401 filterUsed :: Module -> Module
402 filterUsed (Module mid ds) = Module mid (map filterExports (go ds))
403 where
404 go :: [ModuleElement] -> [ModuleElement]
405 go [] = []
406 go (d : rest)
407 | not (isDeclUsed d) = go rest
408 | otherwise = d : go rest
410 -- | Filter out the exports for members which aren't used.
411 filterExports :: ModuleElement -> ModuleElement
412 filterExports (ExportsList exps) = ExportsList (filter (\(_, nm, _, _) -> isKeyUsed (mid, nm)) exps)
413 filterExports me = me
415 isDeclUsed :: ModuleElement -> Bool
416 isDeclUsed (Member _ _ nm _ _) = isKeyUsed (mid, nm)
417 isDeclUsed _ = True
419 isKeyUsed :: Key -> Bool
420 isKeyUsed k
421 | Just me <- vertexFor k = me `S.member` reachableSet
422 | otherwise = False
424-- | Topologically sort the module dependency graph, so that when we generate code, modules can be
425-- defined in the right order.
426sortModules :: [Module] -> [Module]
427sortModules modules = map (\v -> case nodeFor v of (n, _, _) -> n) (reverse (topSort graph))
428 where
429 (graph, nodeFor, _) = graphFromEdges $ do
430 m@(Module mid els) <- modules
431 return (m, mid, mapMaybe getKey els)
433 getKey :: ModuleElement -> Maybe ModuleIdentifier
434 getKey (Require _ _ (Right mi)) = Just mi
435 getKey _ = Nothing
437-- | A module is empty if it contains no exported members (in other words,
438-- if the only things left after dead code elimination are module imports and
439-- "other" foreign code).
440--
441-- If a module is empty, we don't want to generate code for it.
442isModuleEmpty :: Module -> Bool
443isModuleEmpty (Module _ els) = all isElementEmpty els
444 where
445 isElementEmpty :: ModuleElement -> Bool
446 isElementEmpty (ExportsList exps) = null exps
447 isElementEmpty Require{} = True
448 isElementEmpty (Other _) = True
449 isElementEmpty _ = False
451-- | Generate code for a set of modules, including a call to main().
452--
453-- Modules get defined on the global PS object, as follows:
454--
455-- var PS = { };
456-- (function(exports) {
457-- ...
458-- })(PS["Module.Name"] = PS["Module.Name"] || {});
459--
460-- In particular, a module and its foreign imports share the same namespace inside PS.
461-- This saves us from having to generate unique names for a module and its foreign imports,
462-- and is safe since a module shares a namespace with its foreign imports in PureScript as well
463-- (so there is no way to have overlaps in code generated by psc).
464codeGen :: Maybe String -- ^ main module
465 -> String -- ^ namespace
466 -> [Module] -- ^ input modules
467 -> String
468codeGen optionsMainModule optionsNamespace ms = renderToString (JSAstProgram (prelude : concatMap moduleToJS ms ++ maybe [] runMain optionsMainModule) JSNoAnnot)
469 where
470 moduleToJS :: Module -> [JSStatement]
471 moduleToJS (Module mn ds) = wrap (moduleName mn) (indent (concatMap declToJS ds))
472 where
473 declToJS :: ModuleElement -> [JSStatement]
474 declToJS (Member n _ _ _ _) = [n]
475 declToJS (Other n) = [n]
476 declToJS (Require _ nm req) =
477 [
478 JSVariable lfsp
479 (cList [
480 JSVarInitExpression (JSIdentifier sp nm)
481 (JSVarInit sp $ either require (moduleReference sp . moduleName) req )
482 ]) (JSSemi JSNoAnnot)
483 ]
484 declToJS (ExportsList exps) = map toExport exps
486 where
487 toExport :: (ExportType, String, JSExpression, [Key]) -> JSStatement
488 toExport (_, nm, val, _) =
489 JSAssignStatement
490 (JSMemberSquare (JSIdentifier lfsp "exports") JSNoAnnot
491 (str nm) JSNoAnnot)
492 (JSAssign sp)
493 val
494 (JSSemi JSNoAnnot)
496 -- comma lists are reverse-consed
497 cList :: [a] -> JSCommaList a
498 cList [] = JSLNil
499 cList [x] = JSLOne x
500 cList l = go $ reverse l
501 where
502 go [x] = JSLOne x
503 go (h:t)= JSLCons (go t) JSNoAnnot h
504 go [] = error "Invalid case in comma-list"
506 indent :: [JSStatement] -> [JSStatement]
507 indent = everywhere (mkT squash)
508 where
509 squash JSNoAnnot = (JSAnnot (TokenPn 0 0 2) [])
510 squash (JSAnnot pos ann) = JSAnnot (keepCol pos) (map splat ann)
512 splat (CommentA pos s) = CommentA (keepCol pos) s
513 splat (WhiteSpace pos w) = WhiteSpace (keepCol pos) w
514 splat ann = ann
516 keepCol (TokenPn _ _ c) = TokenPn 0 0 (if c >= 0 then c + 2 else 2)
518 prelude :: JSStatement
519 prelude = JSVariable (JSAnnot tokenPosnEmpty [ CommentA tokenPosnEmpty $ "// Generated by psc-bundle " ++ showVersion Paths.version
520 , WhiteSpace tokenPosnEmpty "\n" ])
521 (cList [
522 JSVarInitExpression (JSIdentifier sp optionsNamespace)
523 (JSVarInit sp (emptyObj sp))
524 ]) (JSSemi JSNoAnnot)
526 require :: String -> JSExpression
527 require mn =
528 JSMemberExpression (JSIdentifier JSNoAnnot "require") JSNoAnnot (cList [ str mn ]) JSNoAnnot
530 moduleReference :: JSAnnot -> String -> JSExpression
531 moduleReference a mn =
532 JSMemberSquare (JSIdentifier a optionsNamespace) JSNoAnnot
533 (str mn) JSNoAnnot
535 str :: String -> JSExpression
536 str s = JSStringLiteral JSNoAnnot $ "\"" ++ s ++ "\""
539 emptyObj :: JSAnnot -> JSExpression
540 emptyObj a = JSObjectLiteral a (JSCTLNone JSLNil) JSNoAnnot
542 wrap :: String -> [JSStatement] -> [JSStatement]
543 wrap mn ds =
544 [
545 JSMethodCall (JSExpressionParen lf (JSFunctionExpression JSNoAnnot JSIdentNone JSNoAnnot
546 (JSLOne (JSIdentName JSNoAnnot "exports")) JSNoAnnot
547 (JSBlock sp (lfHead ds) lf)) -- \n not quite in right place
548 JSNoAnnot)
549 JSNoAnnot
550 (JSLOne (JSAssignExpression (moduleReference JSNoAnnot mn) (JSAssign sp)
551 (JSExpressionBinary (moduleReference sp mn) (JSBinOpOr sp) (emptyObj sp))))
552 JSNoAnnot
553 (JSSemi JSNoAnnot)
554 ]
555 where
556 lfHead (h:t) = (addAnn (WhiteSpace tokenPosnEmpty "\n ") h) : t
557 lfHead x = x
559 addAnn :: CommentAnnotation -> JSStatement -> JSStatement
560 addAnn a (JSExpressionStatement (JSStringLiteral ann s) _) =
561 (JSExpressionStatement (JSStringLiteral (appendAnn a ann) s) (JSSemi JSNoAnnot))
562 addAnn _ x = x
564 appendAnn a JSNoAnnot = (JSAnnot tokenPosnEmpty [a])
565 appendAnn a (JSAnnot _ anns) = JSAnnot tokenPosnEmpty (a:anns ++ [WhiteSpace tokenPosnEmpty " "])
567 runMain :: String -> [JSStatement]
568 runMain mn =
569 [JSMethodCall
570 (JSMemberDot (moduleReference lf mn) JSNoAnnot
571 (JSIdentifier JSNoAnnot "main"))
572 JSNoAnnot (cList []) JSNoAnnot (JSSemi JSNoAnnot)]
574 lf :: JSAnnot
575 lf = JSAnnot tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n" ]
578 lfsp :: JSAnnot
579 lfsp = JSAnnot tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n " ]
581 sp :: JSAnnot
582 sp = JSAnnot tokenPosnEmpty [ WhiteSpace tokenPosnEmpty " " ]
584-- | The bundling function.
585-- This function performs dead code elimination, filters empty modules
586-- and generates and prints the final Javascript bundle.
587bundle :: (MonadError ErrorMessage m)
588 => [(ModuleIdentifier, String)] -- ^ The input modules. Each module should be javascript rendered from 'Language.PureScript.Make' or @psc@.
589 -> [ModuleIdentifier] -- ^ Entry points. These module identifiers are used as the roots for dead-code elimination
590 -> Maybe String -- ^ An optional main module.
591 -> String -- ^ The namespace (e.g. PS).
592 -> m String
593bundle inputStrs entryPoints mainModule namespace = do
594 input <- forM inputStrs $ \(ident, js) -> do
595 ast <- either (throwError . ErrorInModule ident . UnableToParseModule) pure $ parse js (moduleName ident)
596 return (ident, ast)
598 let mids = S.fromList (map (moduleName . fst) input)
600 modules <- traverse (fmap withDeps . uncurry (toModule mids)) input
602 let compiled = compile modules entryPoints
603 sorted = sortModules (filter (not . isModuleEmpty) compiled)
605 return (codeGen mainModule namespace sorted)