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.Set as Set
17import qualified Data.Text as Text
18import qualified Data.Text.Encoding as Text
20import qualified AST.Canonical as Can
21import qualified AST.Optimized as Opt
22import qualified AST.Module.Name as ModuleName
23import qualified Data.Index as Index
24import qualified Elm.Interface as I
25import qualified Elm.Name as N
26import qualified Generate.JavaScript.Builder as JS
27import qualified Generate.JavaScript.Expression as Expr
28import qualified Generate.JavaScript.Name as Name
29import qualified Generate.JavaScript.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 N.Name [N.Name] B.Builder
44generate :: Mode.Mode -> Opt.Graph -> [ModuleName.Canonical] -> Output
45generate mode (Opt.Graph mains graph _fields) 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.');"
78 Mode.Dev _ (Just _) ->
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 -> N.Name -> B.Builder
89generateForRepl ansi localizer (Opt.Graph _ graph _) iface home name =
91 mode = Mode.dev Mode.Client
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._types iface ! name)
100print :: Bool -> L.Localizer -> ModuleName.Canonical -> N.Name -> Can.Annotation -> B.Builder
101print ansi localizer home name (Can.Forall _ tipe) =
103 value = Name.toBuilder (Name.fromGlobal home name)
104 toString = Name.toBuilder (Name.fromKernel N.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 (Opt.KContent clientChunks clientDeps) maybeServer ->
196 if isDebugger global && not (Mode.isDebug mode) then
200 Just (Opt.KContent serverChunks serverDeps) | Mode.isServer mode ->
201 addKernel (addDeps serverDeps state) (generateKernel mode serverChunks)
204 addKernel (addDeps clientDeps state) (generateKernel mode clientChunks)
208 generateEnum mode global index
213 generateBox mode global
216 Opt.PortIncoming decoder deps ->
217 addStmt (addDeps deps state) (
218 generatePort mode global "incomingPort" decoder
221 Opt.PortOutgoing encoder deps ->
222 addStmt (addDeps deps state) (
223 generatePort mode global "outgoingPort" encoder
227addStmt :: State -> JS.Stmt -> State
229 addBuilder state (JS.stmtToBuilder stmt)
232addBuilder :: State -> B.Builder -> State
233addBuilder (State revKernels revBuilders seen) builder =
234 State revKernels (builder:revBuilders) seen
237addKernel :: State -> B.Builder -> State
238addKernel (State revKernels revBuilders seen) kernel =
239 State (kernel:revKernels) revBuilders seen
242var :: Opt.Global -> Expr.Code -> JS.Stmt
243var (Opt.Global home name) code =
244 JS.Var [ (Name.fromGlobal home name, Just (Expr.codeToExpr code)) ]
247isDebugger :: Opt.Global -> Bool
248isDebugger (Opt.Global (ModuleName.Canonical _ home) _) =
256generateCycle :: Mode.Mode -> Opt.Global -> [N.Name] -> [(N.Name, Opt.Expr)] -> [Opt.Def] -> JS.Stmt
257generateCycle mode (Opt.Global home _) names values functions =
259 funcBlock = JS.Block $ map (generateCycleFunc mode home) functions
260 safeBlock = JS.Block $ map (generateSafeCycle mode home) values
261 realBlock = JS.Block $ map (generateRealCycle home) values
265 JS.Block [ funcBlock, safeBlock, realBlock ]
270 JS.Try realBlock Name.dollar $ JS.Throw $ JS.String $
271 "Some top-level definitions from `" <> N.toBuilder (ModuleName._module home) <> "` are causing infinite recursion:\\n"
273 <> "\\n\\nThese errors are very tricky, so read "
274 <> B.stringUtf8 (D.makeNakedLink "halting-problem")
275 <> " to learn how to fix it!"
277 JS.Block [ funcBlock, safeBlock, tryRealBlock ]
280generateCycleFunc :: Mode.Mode -> ModuleName.Canonical -> Opt.Def -> JS.Stmt
281generateCycleFunc mode home def =
284 JS.Var [ (Name.fromGlobal home name, Just (Expr.codeToExpr (Expr.generate mode expr))) ]
286 Opt.TailDef name args expr ->
287 JS.Var [ (Name.fromGlobal home name, Just (Expr.codeToExpr (Expr.generateTailDef mode name args expr))) ]
290generateSafeCycle :: Mode.Mode -> ModuleName.Canonical -> (N.Name, Opt.Expr) -> JS.Stmt
291generateSafeCycle mode home (name, expr) =
292 JS.FunctionStmt (Name.fromCycle home name) [] $
293 Expr.codeToStmtList (Expr.generate mode expr)
296generateRealCycle :: ModuleName.Canonical -> (N.Name, expr) -> JS.Stmt
297generateRealCycle home (name, _) =
299 safeName = Name.fromCycle home name
300 realName = Name.fromGlobal home name
303 [ JS.Var [ ( realName, Just (JS.Call (JS.Ref safeName) []) ) ]
304 , JS.ExprStmt $ JS.Assign (JS.LRef safeName) $
305 JS.Function Nothing [] [ JS.Return (Just (JS.Ref realName)) ]
309drawCycle :: [N.Name] -> B.Builder
312 topLine = "\\n ┌─────┐"
313 nameLine name = "\\n │ " <> N.toBuilder name
315 bottomLine = "\\n └─────┘"
317 mconcat (topLine : List.intersperse midLine (map nameLine names) ++ [ bottomLine ])
324generateKernel :: Mode.Mode -> [Opt.KChunk] -> B.Builder
325generateKernel mode chunks =
326 List.foldl' (addChunk mode) mempty chunks
329addChunk :: Mode.Mode -> B.Builder -> Opt.KChunk -> B.Builder
330addChunk mode builder chunk =
333 B.byteString javascript <> builder
335 Opt.ElmVar home name ->
336 Name.toBuilder (Name.fromGlobal home name) <> builder
338 Opt.JsVar home name ->
339 Name.toBuilder (Name.fromKernel home name) <> builder
342 Name.toBuilder (Expr.generateField mode name) <> builder
345 Name.toBuilder (Name.fromInt int) <> builder
348 B.intDec int <> builder
371generateEnum :: Mode.Mode -> Opt.Global -> Index.ZeroBased -> JS.Stmt
372generateEnum mode global@(Opt.Global home name) index =
377 Expr.codeToExpr (Expr.generateCtor mode global index 0)
380 JS.Int (Index.toMachine index)
382 JS.Var [ (Name.fromGlobal home name, Just definition) ]
389generateBox :: Mode.Mode -> Opt.Global -> JS.Stmt
390generateBox mode global@(Opt.Global home name) =
395 Expr.codeToExpr (Expr.generateCtor mode global Index.first 1)
398 JS.Ref (Name.fromGlobal ModuleName.basics N.identity)
400 JS.Var [ (Name.fromGlobal home name, Just definition) ]
407generatePort :: Mode.Mode -> Opt.Global -> N.Name -> Opt.Expr -> JS.Stmt
408generatePort mode (Opt.Global home name) makePort converter =
411 JS.Call (JS.Ref (Name.fromKernel N.platform makePort))
412 [ JS.String (N.toBuilder name)
413 , Expr.codeToExpr (Expr.generate mode converter)
416 JS.Var [ (Name.fromGlobal home name, Just definition) ]
423generateManager :: Mode.Mode -> Graph -> Opt.Global -> Opt.EffectsType -> State -> State
424generateManager mode graph (Opt.Global home@(ModuleName.Canonical _ moduleName) _) effectsType state =
428 (JS.Ref (Name.fromKernel N.platform "effectManagers"))
429 (JS.String (N.toBuilder moduleName))
431 (deps, args, stmts) =
432 generateManagerHelp home effectsType
435 JS.ExprStmt $ JS.Assign managerLVar $
436 JS.Call (JS.Ref (Name.fromKernel N.platform "createManager")) args
438 addStmt (List.foldl' (addGlobal mode graph) state deps) $
439 JS.Block (createManager : stmts)
442generateLeaf :: ModuleName.Canonical -> N.Name -> JS.Stmt
443generateLeaf home@(ModuleName.Canonical _ moduleName) name =
446 JS.Call leaf [ JS.String (N.toBuilder moduleName) ]
448 JS.Var [ (Name.fromGlobal home name, Just definition) ]
454 JS.Ref (Name.fromKernel N.platform "leaf")
457generateManagerHelp :: ModuleName.Canonical -> Opt.EffectsType -> ([Opt.Global], [JS.Expr], [JS.Stmt])
458generateManagerHelp home effectsType =
460 dep name = Opt.Global home name
461 ref name = JS.Ref (Name.fromGlobal home name)
465 ( [ dep "init", dep "onEffects", dep "onSelfMsg", dep "cmdMap" ]
466 , [ ref "init", ref "onEffects", ref "onSelfMsg", ref "cmdMap" ]
467 , [ generateLeaf home "command" ]
471 ( [ dep "init", dep "onEffects", dep "onSelfMsg", dep "subMap" ]
472 , [ ref "init", ref "onEffects", ref "onSelfMsg", JS.Int 0, ref "subMap" ]
473 , [ generateLeaf home "subscription" ]
477 ( [ dep "init", dep "onEffects", dep "onSelfMsg", dep "cmdMap", dep "subMap" ]
478 , [ ref "init", ref "onEffects", ref "onSelfMsg", ref "cmdMap", ref "subMap" ]
479 , [ generateLeaf home "command"
480 , generateLeaf home "subscription"
489toMainExports :: Mode.Mode -> Map.Map ModuleName.Canonical Opt.Main -> B.Builder
490toMainExports mode mains =
492 export = Name.fromKernel N.platform "export"
493 exports = generateExports mode (Map.foldrWithKey addToTrie emptyTrie mains)
495 Name.toBuilder export <> "(" <> exports <> ");"
498generateExports :: Mode.Mode -> Trie -> B.Builder
499generateExports mode (Trie maybeMain subs) =
502 case Map.toList subs of
506 (name, subTrie) : otherSubTries ->
507 "{'" <> Text.encodeUtf8Builder name <> "':"
508 <> generateExports mode subTrie
509 <> List.foldl' (addSubTrie mode) "}" otherSubTries
516 let initialize = Expr.generateMain mode home main in
517 JS.exprToBuilder initialize <> "(" <> object <> ")"
520addSubTrie :: Mode.Mode -> B.Builder -> (Text.Text, Trie) -> B.Builder
521addSubTrie mode end (name, trie) =
522 ",'" <> Text.encodeUtf8Builder name <> "':" <> generateExports mode trie <> end
531 { _main :: Maybe (ModuleName.Canonical, Opt.Main)
532 , _subs :: Map.Map Text.Text Trie
538 Trie Nothing Map.empty
541addToTrie :: ModuleName.Canonical -> Opt.Main -> Trie -> Trie
542addToTrie home@(ModuleName.Canonical _ moduleName) main trie =
543 merge trie $ segmentsToTrie home (Text.splitOn "." (N.toText moduleName)) main
546segmentsToTrie :: ModuleName.Canonical -> [Text.Text] -> Opt.Main -> Trie
547segmentsToTrie home segments main =
550 Trie (Just (home, main)) Map.empty
552 segment : otherSegments ->
553 Trie Nothing (Map.singleton segment (segmentsToTrie home otherSegments main))
556merge :: Trie -> Trie -> Trie
557merge (Trie main1 subs1) (Trie main2 subs2) =
559 (checkedMerge main1 main2)
560 (Map.unionWith merge subs1 subs2)
563checkedMerge :: Maybe a -> Maybe a -> Maybe a
573 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