1{-# LANGUAGE OverloadedStrings #-}
2module Generate.JavaScript
9import Prelude hiding (cycle, print)
10import qualified Data.ByteString.Builder as B
11import Data.Monoid ((<>))
12import qualified Data.List as List
14import qualified Data.Map as Map
15import qualified Data.Name as Name
16import qualified Data.Set as Set
17import qualified Data.Utf8 as Utf8
19import qualified AST.Canonical as Can
20import qualified AST.Optimized as Opt
21import qualified Data.Index as Index
22import qualified Elm.Kernel as K
23import qualified Elm.ModuleName as ModuleName
24import qualified Generate.JavaScript.Builder as JS
25import qualified Generate.JavaScript.Expression as Expr
26import qualified Generate.JavaScript.Functions as Functions
27import qualified Generate.JavaScript.Name as JsName
28import qualified Generate.Mode as Mode
29import qualified Reporting.Doc as D
30import qualified Reporting.Render.Type as RT
31import qualified Reporting.Render.Type.Localizer as L
38type Graph = Map.Map Opt.Global Opt.Node
39type Mains = Map.Map ModuleName.Canonical Opt.Main
42generate :: Mode.Mode -> Opt.GlobalGraph -> Mains -> B.Builder
43generate mode (Opt.GlobalGraph graph _) mains =
45 state = Map.foldrWithKey (addMain mode graph) emptyState mains
47 "(function(scope){\n'use strict';"
48 <> Functions.functions
50 <> stateToBuilder state
51 <> toMainExports mode mains
55addMain :: Mode.Mode -> Graph -> ModuleName.Canonical -> Opt.Main -> State -> State
56addMain mode graph home _ state =
57 addGlobal mode graph state (Opt.Global home "main")
60perfNote :: Mode.Mode -> B.Builder
67 "console.warn('Compiled in DEV mode. Follow the advice at "
68 <> B.stringUtf8 (D.makeNakedLink "optimize")
69 <> " for better performance and smaller assets.');"
72 "console.warn('Compiled in DEBUG mode. Follow the advice at "
73 <> B.stringUtf8 (D.makeNakedLink "optimize")
74 <> " for better performance and smaller assets.');"
81generateForRepl :: Bool -> L.Localizer -> Opt.GlobalGraph -> ModuleName.Canonical -> Name.Name -> Can.Annotation -> B.Builder
82generateForRepl ansi localizer (Opt.GlobalGraph graph _) home name (Can.Forall _ tipe) =
84 mode = Mode.Dev Nothing
85 debugState = addGlobal mode graph emptyState (Opt.Global ModuleName.debug "toString")
86 evalState = addGlobal mode graph debugState (Opt.Global home name)
88 stateToBuilder evalState
89 <> print ansi localizer home name tipe
92print :: Bool -> L.Localizer -> ModuleName.Canonical -> Name.Name -> Can.Type -> B.Builder
93print ansi localizer home name tipe =
95 value = JsName.toBuilder (JsName.fromGlobal home name)
96 toString = JsName.toBuilder (JsName.fromKernel Name.debug "toAnsiString")
97 tipeDoc = RT.canToDoc localizer RT.None tipe
98 bool = if ansi then "true" else "false"
100 "var _value = " <> toString <> "(" <> bool <> ", " <> value <> ");\n\
101 \var _type = " <> B.stringUtf8 (show (D.toString tipeDoc)) <> ";\n\
102 \function _print(t) { console.log(_value + (" <> bool <> " ? '\x1b[90m' + t + '\x1b[0m' : t)); }\n\
103 \if (_value.length + 3 + _type.length >= 80 || _type.indexOf('\\n') >= 0) {\n\
104 \ _print('\\n : ' + _type.split('\\n').join('\\n '));\n\
106 \ _print(' : ' + _type);\n\
111-- GRAPH TRAVERSAL STATE
116 { _revKernels :: [B.Builder]
117 , _revBuilders :: [B.Builder]
118 , _seenGlobals :: Set.Set Opt.Global
124 State mempty [] Set.empty
127stateToBuilder :: State -> B.Builder
128stateToBuilder (State revKernels revBuilders _) =
129 prependBuilders revKernels (prependBuilders revBuilders mempty)
132prependBuilders :: [B.Builder] -> B.Builder -> B.Builder
133prependBuilders revBuilders monolith =
134 List.foldl' (\m b -> b <> m) monolith revBuilders
141addGlobal :: Mode.Mode -> Graph -> State -> Opt.Global -> State
142addGlobal mode graph state@(State revKernels builders seen) global =
143 if Set.member global seen then
146 addGlobalHelp mode graph global $
147 State revKernels builders (Set.insert global seen)
150addGlobalHelp :: Mode.Mode -> Graph -> Opt.Global -> State -> State
151addGlobalHelp mode graph global state =
153 addDeps deps someState =
154 Set.foldl' (addGlobal mode graph) someState deps
156 case graph ! global of
157 Opt.Define expr deps ->
158 addStmt (addDeps deps state) (
159 var global (Expr.generate mode expr)
162 Opt.DefineTailFunc argNames body deps ->
163 addStmt (addDeps deps state) (
164 let (Opt.Global _ name) = global in
165 var global (Expr.generateTailDef mode name argNames body)
168 Opt.Ctor index arity ->
170 var global (Expr.generateCtor mode global index arity)
173 Opt.Link linkedGlobal ->
174 addGlobal mode graph state linkedGlobal
176 Opt.Cycle names values functions deps ->
177 addStmt (addDeps deps state) (
178 generateCycle mode global names values functions
181 Opt.Manager effectsType ->
182 generateManager mode graph global effectsType state
184 Opt.Kernel chunks deps ->
185 if isDebugger global && not (Mode.isDebug mode) then
188 addKernel (addDeps deps state) (generateKernel mode chunks)
192 generateEnum mode global index
196 addStmt (addGlobal mode graph state identity) (
197 generateBox mode global
200 Opt.PortIncoming decoder deps ->
201 addStmt (addDeps deps state) (
202 generatePort mode global "incomingPort" decoder
205 Opt.PortOutgoing encoder deps ->
206 addStmt (addDeps deps state) (
207 generatePort mode global "outgoingPort" encoder
211addStmt :: State -> JS.Stmt -> State
213 addBuilder state (JS.stmtToBuilder stmt)
216addBuilder :: State -> B.Builder -> State
217addBuilder (State revKernels revBuilders seen) builder =
218 State revKernels (builder:revBuilders) seen
221addKernel :: State -> B.Builder -> State
222addKernel (State revKernels revBuilders seen) kernel =
223 State (kernel:revKernels) revBuilders seen
226var :: Opt.Global -> Expr.Code -> JS.Stmt
227var (Opt.Global home name) code =
228 JS.Var [ (JsName.fromGlobal home name, Just (Expr.codeToExpr code)) ]
231isDebugger :: Opt.Global -> Bool
232isDebugger (Opt.Global (ModuleName.Canonical _ home) _) =
233 home == Name.debugger
240generateCycle :: Mode.Mode -> Opt.Global -> [Name.Name] -> [(Name.Name, Opt.Expr)] -> [Opt.Def] -> JS.Stmt
241generateCycle mode (Opt.Global home _) names values functions =
243 [ JS.Block $ map (generateCycleFunc mode home) functions
244 , JS.Block $ map (generateSafeCycle mode home) values
245 , case map (generateRealCycle home) values of
255 JS.Try (JS.Block realBlock) JsName.dollar $ JS.Throw $ JS.String $
256 "Some top-level definitions from `" <> Name.toBuilder (ModuleName._module home) <> "` are causing infinite recursion:\\n"
258 <> "\\n\\nThese errors are very tricky, so read "
259 <> B.stringUtf8 (D.makeNakedLink "halting-problem")
260 <> " to learn how to fix it!"
264generateCycleFunc :: Mode.Mode -> ModuleName.Canonical -> Opt.Def -> JS.Stmt
265generateCycleFunc mode home def =
268 JS.Var [ (JsName.fromGlobal home name, Just (Expr.codeToExpr (Expr.generate mode expr))) ]
270 Opt.TailDef name args expr ->
271 JS.Var [ (JsName.fromGlobal home name, Just (Expr.codeToExpr (Expr.generateTailDef mode name args expr))) ]
274generateSafeCycle :: Mode.Mode -> ModuleName.Canonical -> (Name.Name, Opt.Expr) -> JS.Stmt
275generateSafeCycle mode home (name, expr) =
276 JS.FunctionStmt (JsName.fromCycle home name) [] $
277 Expr.codeToStmtList (Expr.generate mode expr)
280generateRealCycle :: ModuleName.Canonical -> (Name.Name, expr) -> JS.Stmt
281generateRealCycle home (name, _) =
283 safeName = JsName.fromCycle home name
284 realName = JsName.fromGlobal home name
287 [ JS.Var [ ( realName, Just (JS.Call (JS.Ref safeName) []) ) ]
288 , JS.ExprStmt $ JS.Assign (JS.LRef safeName) $
289 JS.Function Nothing [] [ JS.Return (Just (JS.Ref realName)) ]
293drawCycle :: [Name.Name] -> B.Builder
296 topLine = "\\n ┌─────┐"
297 nameLine name = "\\n │ " <> Name.toBuilder name
299 bottomLine = "\\n └─────┘"
301 mconcat (topLine : List.intersperse midLine (map nameLine names) ++ [ bottomLine ])
308generateKernel :: Mode.Mode -> [K.Chunk] -> B.Builder
309generateKernel mode chunks =
310 List.foldr (addChunk mode) mempty chunks
313addChunk :: Mode.Mode -> K.Chunk -> B.Builder -> B.Builder
314addChunk mode chunk builder =
317 Utf8.toBuilder javascript <> builder
319 K.ElmVar home name ->
320 JsName.toBuilder (JsName.fromGlobal home name) <> builder
323 JsName.toBuilder (JsName.fromKernel home name) <> builder
326 JsName.toBuilder (Expr.generateField mode name) <> builder
329 JsName.toBuilder (JsName.fromInt int) <> builder
332 B.intDec int <> builder
355generateEnum :: Mode.Mode -> Opt.Global -> Index.ZeroBased -> JS.Stmt
356generateEnum mode global@(Opt.Global home name) index =
361 Expr.codeToExpr (Expr.generateCtor mode global index 0)
364 JS.Int (Index.toMachine index)
366 JS.Var [ (JsName.fromGlobal home name, Just definition) ]
373generateBox :: Mode.Mode -> Opt.Global -> JS.Stmt
374generateBox mode global@(Opt.Global home name) =
379 Expr.codeToExpr (Expr.generateCtor mode global Index.first 1)
382 JS.Ref (JsName.fromGlobal ModuleName.basics Name.identity)
384 JS.Var [ (JsName.fromGlobal home name, Just definition) ]
387{-# NOINLINE identity #-}
388identity :: Opt.Global
390 Opt.Global ModuleName.basics Name.identity
397generatePort :: Mode.Mode -> Opt.Global -> Name.Name -> Opt.Expr -> JS.Stmt
398generatePort mode (Opt.Global home name) makePort converter =
401 JS.Call (JS.Ref (JsName.fromKernel Name.platform makePort))
402 [ JS.String (Name.toBuilder name)
403 , Expr.codeToExpr (Expr.generate mode converter)
406 JS.Var [ (JsName.fromGlobal home name, Just definition) ]
413generateManager :: Mode.Mode -> Graph -> Opt.Global -> Opt.EffectsType -> State -> State
414generateManager mode graph (Opt.Global home@(ModuleName.Canonical _ moduleName) _) effectsType state =
418 (JS.Ref (JsName.fromKernel Name.platform "effectManagers"))
419 (JS.String (Name.toBuilder moduleName))
421 (deps, args, stmts) =
422 generateManagerHelp home effectsType
425 JS.ExprStmt $ JS.Assign managerLVar $
426 JS.Call (JS.Ref (JsName.fromKernel Name.platform "createManager")) args
428 addStmt (List.foldl' (addGlobal mode graph) state deps) $
429 JS.Block (createManager : stmts)
432generateLeaf :: ModuleName.Canonical -> Name.Name -> JS.Stmt
433generateLeaf home@(ModuleName.Canonical _ moduleName) name =
436 JS.Call leaf [ JS.String (Name.toBuilder moduleName) ]
438 JS.Var [ (JsName.fromGlobal home name, Just definition) ]
444 JS.Ref (JsName.fromKernel Name.platform "leaf")
447generateManagerHelp :: ModuleName.Canonical -> Opt.EffectsType -> ([Opt.Global], [JS.Expr], [JS.Stmt])
448generateManagerHelp home effectsType =
450 dep name = Opt.Global home name
451 ref name = JS.Ref (JsName.fromGlobal home name)
455 ( [ dep "init", dep "onEffects", dep "onSelfMsg", dep "cmdMap" ]
456 , [ ref "init", ref "onEffects", ref "onSelfMsg", ref "cmdMap" ]
457 , [ generateLeaf home "command" ]
461 ( [ dep "init", dep "onEffects", dep "onSelfMsg", dep "subMap" ]
462 , [ ref "init", ref "onEffects", ref "onSelfMsg", JS.Int 0, ref "subMap" ]
463 , [ generateLeaf home "subscription" ]
467 ( [ dep "init", dep "onEffects", dep "onSelfMsg", dep "cmdMap", dep "subMap" ]
468 , [ ref "init", ref "onEffects", ref "onSelfMsg", ref "cmdMap", ref "subMap" ]
469 , [ generateLeaf home "command"
470 , generateLeaf home "subscription"
479toMainExports :: Mode.Mode -> Mains -> B.Builder
480toMainExports mode mains =
482 export = JsName.fromKernel Name.platform "export"
483 exports = generateExports mode (Map.foldrWithKey addToTrie emptyTrie mains)
485 JsName.toBuilder export <> "(" <> exports <> ");"
488generateExports :: Mode.Mode -> Trie -> B.Builder
489generateExports mode (Trie maybeMain subs) =
498 <> JS.exprToBuilder (Expr.generateMain mode home main)
501 case Map.toList subs of
505 (name, subTrie) : otherSubTries ->
507 "'" <> Utf8.toBuilder name <> "':"
508 <> generateExports mode subTrie
509 <> List.foldl' (addSubTrie mode) "}" otherSubTries
512addSubTrie :: Mode.Mode -> B.Builder -> (Utf8.String, Trie) -> B.Builder
513addSubTrie mode end (name, trie) =
514 ",'" <> Utf8.toBuilder name <> "':" <> generateExports mode trie <> end
523 { _main :: Maybe (ModuleName.Canonical, Opt.Main)
524 , _subs :: Map.Map Utf8.String Trie
530 Trie Nothing Map.empty
533addToTrie :: ModuleName.Canonical -> Opt.Main -> Trie -> Trie
534addToTrie home@(ModuleName.Canonical _ moduleName) main trie =
537 Utf8.split 0x2E {-.-} (Name.toUtf8 moduleName)
539 merge trie $ segmentsToTrie home segments main
542segmentsToTrie :: ModuleName.Canonical -> [Utf8.String] -> Opt.Main -> Trie
543segmentsToTrie home segments main =
546 Trie (Just (home, main)) Map.empty
548 segment : otherSegments ->
549 Trie Nothing (Map.singleton segment (segmentsToTrie home otherSegments main))
552merge :: Trie -> Trie -> Trie
553merge (Trie main1 subs1) (Trie main2 subs2) =
555 (checkedMerge main1 main2)
556 (Map.unionWith merge subs1 subs2)
559checkedMerge :: Maybe a -> Maybe a -> Maybe a
569 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