1{-# LANGUAGE OverloadedStrings #-}
2module Generate.JavaScript
10import Prelude hiding (cycle, print)
11import qualified Data.ByteString.Builder as B
12import Data.Monoid ((<>))
13import qualified Data.List as List
15import qualified Data.Map as Map
16import qualified Data.Name as Name
17import qualified Data.Set as Set
18import qualified Data.Utf8 as Utf8
20import qualified AST.Canonical as Can
21import qualified AST.Optimized as Opt
22import qualified Data.Index as Index
23import qualified Elm.Interface as I
24import qualified Elm.Kernel as K
25import qualified Elm.ModuleName as ModuleName
26import qualified Generate.JavaScript.Builder as JS
27import qualified Generate.JavaScript.Expression as Expr
28import qualified Generate.JavaScript.Name as JsName
29import qualified Generate.Mode as Mode
30import qualified Reporting.Doc as D
31import qualified Reporting.Render.Type as RT
32import qualified Reporting.Render.Type.Localizer as L
41 | Some Name.Name [Name.Name] B.Builder
44generate :: Mode.Mode -> Opt.Graph -> [ModuleName.Canonical] -> Output
45generate mode (Opt.Graph mains graph _) roots =
47 rootSet = Set.fromList roots
48 rootMap = Map.restrictKeys mains rootSet
50 case map ModuleName._module (Map.keys rootMap) of
56 state = Map.foldrWithKey (addMain mode graph) emptyState rootMap
57 builder = perfNote mode <> stateToBuilder state <> toMainExports mode rootMap
59 Some name names builder
62addMain :: Mode.Mode -> Graph -> ModuleName.Canonical -> main -> State -> State
63addMain mode graph home _ state =
64 addGlobal mode graph state (Opt.Global home "main")
67perfNote :: Mode.Mode -> B.Builder
74 "console.warn('Compiled in DEV mode. Follow the advice at "
75 <> B.stringUtf8 (D.makeNakedLink "optimize")
76 <> " for better performance and smaller assets.');"
79 "console.warn('Compiled in DEBUG mode. Follow the advice at "
80 <> B.stringUtf8 (D.makeNakedLink "optimize")
81 <> " for better performance and smaller assets.');"
88generateForRepl :: Bool -> L.Localizer -> Opt.Graph -> I.Interface -> ModuleName.Canonical -> Name.Name -> B.Builder
89generateForRepl ansi localizer (Opt.Graph _ graph _) iface home name =
91 mode = Mode.Dev Nothing
92 debugState = addGlobal mode graph emptyState (Opt.Global ModuleName.debug "toString")
93 evalState = addGlobal mode graph debugState (Opt.Global home name)
95 stateToBuilder evalState
97 print ansi localizer home name (I._values iface ! name)
100print :: Bool -> L.Localizer -> ModuleName.Canonical -> Name.Name -> Can.Annotation -> B.Builder
101print ansi localizer home name (Can.Forall _ tipe) =
103 value = JsName.toBuilder (JsName.fromGlobal home name)
104 toString = JsName.toBuilder (JsName.fromKernel Name.debug "toAnsiString")
105 tipeDoc = RT.canToDoc localizer RT.None tipe
106 bool = if ansi then "true" else "false"
108 "var _value = " <> toString <> "(" <> bool <> ", " <> value <> ");\n\
109 \var _type = " <> B.stringUtf8 (show (D.toString tipeDoc)) <> ";\n\
110 \function _print(t) { console.log(_value + (" <> bool <> " ? '\x1b[90m' + t + '\x1b[0m' : t)); }\n\
111 \if (_value.length + 3 + _type.length >= 80 || _type.indexOf('\\n') >= 0) {\n\
112 \ _print('\\n : ' + _type.split('\\n').join('\\n '));\n\
114 \ _print(' : ' + _type);\n\
119-- GRAPH TRAVERSAL STATE
124 { _revKernels :: [B.Builder]
125 , _revBuilders :: [B.Builder]
126 , _seenGlobals :: Set.Set Opt.Global
132 State mempty [] Set.empty
135stateToBuilder :: State -> B.Builder
136stateToBuilder (State revKernels revBuilders _) =
137 prependBuilders revKernels (prependBuilders revBuilders mempty)
140prependBuilders :: [B.Builder] -> B.Builder -> B.Builder
141prependBuilders revBuilders monolith =
142 List.foldl' (\m b -> b <> m) monolith revBuilders
149type Graph = Map.Map Opt.Global Opt.Node
152addGlobal :: Mode.Mode -> Graph -> State -> Opt.Global -> State
153addGlobal mode graph state@(State revKernels builders seen) global =
154 if Set.member global seen then
157 addGlobalHelp mode graph global $
158 State revKernels builders (Set.insert global seen)
161addGlobalHelp :: Mode.Mode -> Graph -> Opt.Global -> State -> State
162addGlobalHelp mode graph global state =
164 addDeps deps someState =
165 Set.foldl' (addGlobal mode graph) someState deps
167 case graph ! global of
168 Opt.Define expr deps ->
169 addStmt (addDeps deps state) (
170 var global (Expr.generate mode expr)
173 Opt.DefineTailFunc argNames body deps ->
174 addStmt (addDeps deps state) (
175 let (Opt.Global _ name) = global in
176 var global (Expr.generateTailDef mode name argNames body)
179 Opt.Ctor index arity ->
181 var global (Expr.generateCtor mode global index arity)
184 Opt.Link linkedGlobal ->
185 addGlobal mode graph state linkedGlobal
187 Opt.Cycle names values functions deps ->
188 addStmt (addDeps deps state) (
189 generateCycle mode global names values functions
192 Opt.Manager effectsType ->
193 generateManager mode graph global effectsType state
195 Opt.Kernel chunks deps ->
196 if isDebugger global && not (Mode.isDebug mode) then
199 addKernel (addDeps deps state) (generateKernel mode chunks)
203 generateEnum mode global index
207 addStmt (addGlobal mode graph state identity) (
208 generateBox mode global
211 Opt.PortIncoming decoder deps ->
212 addStmt (addDeps deps state) (
213 generatePort mode global "incomingPort" decoder
216 Opt.PortOutgoing encoder deps ->
217 addStmt (addDeps deps state) (
218 generatePort mode global "outgoingPort" encoder
222addStmt :: State -> JS.Stmt -> State
224 addBuilder state (JS.stmtToBuilder stmt)
227addBuilder :: State -> B.Builder -> State
228addBuilder (State revKernels revBuilders seen) builder =
229 State revKernels (builder:revBuilders) seen
232addKernel :: State -> B.Builder -> State
233addKernel (State revKernels revBuilders seen) kernel =
234 State (kernel:revKernels) revBuilders seen
237var :: Opt.Global -> Expr.Code -> JS.Stmt
238var (Opt.Global home name) code =
239 JS.Var [ (JsName.fromGlobal home name, Just (Expr.codeToExpr code)) ]
242isDebugger :: Opt.Global -> Bool
243isDebugger (Opt.Global (ModuleName.Canonical _ home) _) =
244 home == Name.debugger
251generateCycle :: Mode.Mode -> Opt.Global -> [Name.Name] -> [(Name.Name, Opt.Expr)] -> [Opt.Def] -> JS.Stmt
252generateCycle mode (Opt.Global home _) names values functions =
254 [ JS.Block $ map (generateCycleFunc mode home) functions
255 , JS.Block $ map (generateSafeCycle mode home) values
256 , case map (generateRealCycle home) values of
266 JS.Try (JS.Block realBlock) JsName.dollar $ JS.Throw $ JS.String $
267 "Some top-level definitions from `" <> Name.toBuilder (ModuleName._module home) <> "` are causing infinite recursion:\\n"
269 <> "\\n\\nThese errors are very tricky, so read "
270 <> B.stringUtf8 (D.makeNakedLink "halting-problem")
271 <> " to learn how to fix it!"
275generateCycleFunc :: Mode.Mode -> ModuleName.Canonical -> Opt.Def -> JS.Stmt
276generateCycleFunc mode home def =
279 JS.Var [ (JsName.fromGlobal home name, Just (Expr.codeToExpr (Expr.generate mode expr))) ]
281 Opt.TailDef name args expr ->
282 JS.Var [ (JsName.fromGlobal home name, Just (Expr.codeToExpr (Expr.generateTailDef mode name args expr))) ]
285generateSafeCycle :: Mode.Mode -> ModuleName.Canonical -> (Name.Name, Opt.Expr) -> JS.Stmt
286generateSafeCycle mode home (name, expr) =
287 JS.FunctionStmt (JsName.fromCycle home name) [] $
288 Expr.codeToStmtList (Expr.generate mode expr)
291generateRealCycle :: ModuleName.Canonical -> (Name.Name, expr) -> JS.Stmt
292generateRealCycle home (name, _) =
294 safeName = JsName.fromCycle home name
295 realName = JsName.fromGlobal home name
298 [ JS.Var [ ( realName, Just (JS.Call (JS.Ref safeName) []) ) ]
299 , JS.ExprStmt $ JS.Assign (JS.LRef safeName) $
300 JS.Function Nothing [] [ JS.Return (Just (JS.Ref realName)) ]
304drawCycle :: [Name.Name] -> B.Builder
307 topLine = "\\n ┌─────┐"
308 nameLine name = "\\n │ " <> Name.toBuilder name
310 bottomLine = "\\n └─────┘"
312 mconcat (topLine : List.intersperse midLine (map nameLine names) ++ [ bottomLine ])
319generateKernel :: Mode.Mode -> [K.Chunk] -> B.Builder
320generateKernel mode chunks =
321 List.foldr (addChunk mode) mempty chunks
324addChunk :: Mode.Mode -> K.Chunk -> B.Builder -> B.Builder
325addChunk mode chunk builder =
328 Utf8.toBuilder javascript <> builder
330 K.ElmVar home name ->
331 JsName.toBuilder (JsName.fromGlobal home name) <> builder
334 JsName.toBuilder (JsName.fromKernel home name) <> builder
337 JsName.toBuilder (Expr.generateField mode name) <> builder
340 JsName.toBuilder (JsName.fromInt int) <> builder
343 B.intDec int <> builder
366generateEnum :: Mode.Mode -> Opt.Global -> Index.ZeroBased -> JS.Stmt
367generateEnum mode global@(Opt.Global home name) index =
372 Expr.codeToExpr (Expr.generateCtor mode global index 0)
375 JS.Int (Index.toMachine index)
377 JS.Var [ (JsName.fromGlobal home name, Just definition) ]
384generateBox :: Mode.Mode -> Opt.Global -> JS.Stmt
385generateBox mode global@(Opt.Global home name) =
390 Expr.codeToExpr (Expr.generateCtor mode global Index.first 1)
393 JS.Ref (JsName.fromGlobal ModuleName.basics Name.identity)
395 JS.Var [ (JsName.fromGlobal home name, Just definition) ]
398{-# NOINLINE identity #-}
399identity :: Opt.Global
401 Opt.Global ModuleName.basics Name.identity
408generatePort :: Mode.Mode -> Opt.Global -> Name.Name -> Opt.Expr -> JS.Stmt
409generatePort mode (Opt.Global home name) makePort converter =
412 JS.Call (JS.Ref (JsName.fromKernel Name.platform makePort))
413 [ JS.String (Name.toBuilder name)
414 , Expr.codeToExpr (Expr.generate mode converter)
417 JS.Var [ (JsName.fromGlobal home name, Just definition) ]
424generateManager :: Mode.Mode -> Graph -> Opt.Global -> Opt.EffectsType -> State -> State
425generateManager mode graph (Opt.Global home@(ModuleName.Canonical _ moduleName) _) effectsType state =
429 (JS.Ref (JsName.fromKernel Name.platform "effectManagers"))
430 (JS.String (Name.toBuilder moduleName))
432 (deps, args, stmts) =
433 generateManagerHelp home effectsType
436 JS.ExprStmt $ JS.Assign managerLVar $
437 JS.Call (JS.Ref (JsName.fromKernel Name.platform "createManager")) args
439 addStmt (List.foldl' (addGlobal mode graph) state deps) $
440 JS.Block (createManager : stmts)
443generateLeaf :: ModuleName.Canonical -> Name.Name -> JS.Stmt
444generateLeaf home@(ModuleName.Canonical _ moduleName) name =
447 JS.Call leaf [ JS.String (Name.toBuilder moduleName) ]
449 JS.Var [ (JsName.fromGlobal home name, Just definition) ]
455 JS.Ref (JsName.fromKernel Name.platform "leaf")
458generateManagerHelp :: ModuleName.Canonical -> Opt.EffectsType -> ([Opt.Global], [JS.Expr], [JS.Stmt])
459generateManagerHelp home effectsType =
461 dep name = Opt.Global home name
462 ref name = JS.Ref (JsName.fromGlobal home name)
466 ( [ dep "init", dep "onEffects", dep "onSelfMsg", dep "cmdMap" ]
467 , [ ref "init", ref "onEffects", ref "onSelfMsg", ref "cmdMap" ]
468 , [ generateLeaf home "command" ]
472 ( [ dep "init", dep "onEffects", dep "onSelfMsg", dep "subMap" ]
473 , [ ref "init", ref "onEffects", ref "onSelfMsg", JS.Int 0, ref "subMap" ]
474 , [ generateLeaf home "subscription" ]
478 ( [ dep "init", dep "onEffects", dep "onSelfMsg", dep "cmdMap", dep "subMap" ]
479 , [ ref "init", ref "onEffects", ref "onSelfMsg", ref "cmdMap", ref "subMap" ]
480 , [ generateLeaf home "command"
481 , generateLeaf home "subscription"
490toMainExports :: Mode.Mode -> Map.Map ModuleName.Canonical Opt.Main -> B.Builder
491toMainExports mode mains =
493 export = JsName.fromKernel Name.platform "export"
494 exports = generateExports mode (Map.foldrWithKey addToTrie emptyTrie mains)
496 JsName.toBuilder export <> "(" <> exports <> ");"
499generateExports :: Mode.Mode -> Trie -> B.Builder
500generateExports mode (Trie maybeMain subs) =
509 <> JS.exprToBuilder (Expr.generateMain mode home main)
512 case Map.toList subs of
516 (name, subTrie) : otherSubTries ->
518 "'" <> Utf8.toBuilder name <> "':"
519 <> generateExports mode subTrie
520 <> List.foldl' (addSubTrie mode) "}" otherSubTries
523addSubTrie :: Mode.Mode -> B.Builder -> (Utf8.String, Trie) -> B.Builder
524addSubTrie mode end (name, trie) =
525 ",'" <> Utf8.toBuilder name <> "':" <> generateExports mode trie <> end
534 { _main :: Maybe (ModuleName.Canonical, Opt.Main)
535 , _subs :: Map.Map Utf8.String Trie
541 Trie Nothing Map.empty
544addToTrie :: ModuleName.Canonical -> Opt.Main -> Trie -> Trie
545addToTrie home@(ModuleName.Canonical _ moduleName) main trie =
548 Utf8.split 0x2E {-.-} (Name.toUtf8 moduleName)
550 merge trie $ segmentsToTrie home segments main
553segmentsToTrie :: ModuleName.Canonical -> [Utf8.String] -> Opt.Main -> Trie
554segmentsToTrie home segments main =
557 Trie (Just (home, main)) Map.empty
559 segment : otherSegments ->
560 Trie Nothing (Map.singleton segment (segmentsToTrie home otherSegments main))
563merge :: Trie -> Trie -> Trie
564merge (Trie main1 subs1) (Trie main2 subs2) =
566 (checkedMerge main1 main2)
567 (Map.unionWith merge subs1 subs2)
570checkedMerge :: Maybe a -> Maybe a -> Maybe a
580 error "cannot have two modules with the same name"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list