1{-# LANGUAGE OverloadedStrings #-}
2module Generate.JavaScript.Expression
14import qualified Data.IntMap as IntMap
15import qualified Data.List as List
16import qualified Data.Map as Map
17import qualified Data.Text.Encoding as Text
19import qualified AST.Optimized as Opt
20import qualified AST.Module.Name as ModuleName
21import qualified Data.Index as Index
22import qualified Elm.Compiler.Type as Type
23import qualified Elm.Compiler.Type.Extract as Extract
24import qualified Elm.Interface as I
25import qualified Elm.Name as N
26import qualified Elm.Package as Pkg
27import qualified Generate.JavaScript.Builder as JS
28import qualified Generate.JavaScript.Name as Name
29import qualified Optimize.DecisionTree as DT
30import qualified Reporting.Region as R
44generateJsExpr :: Mode -> Opt.Expr -> JS.Expr
45generateJsExpr mode expression =
46 codeToExpr (generate mode expression)
49generate :: Mode -> Opt.Expr -> Code
50generate mode expression =
56 JS.Call toChar [ JS.String (Text.encodeUtf8Builder char) ]
59 JS.String (Text.encodeUtf8Builder char)
62 JsExpr $ JS.String (Text.encodeUtf8Builder string)
68 JsExpr $ JS.Float float
71 JsExpr $ JS.Ref (Name.fromLocal name)
73 Opt.VarGlobal (Opt.Global home name) ->
74 JsExpr $ JS.Ref (Name.fromGlobal home name)
76 Opt.VarCycle home name ->
77 JsExpr $ JS.Call (JS.Ref (Name.fromCycle home name)) []
79 Opt.VarDebug name home region unhandledValueName ->
80 JsExpr $ generateDebug name home region unhandledValueName
82 Opt.VarKernel home name ->
83 JsExpr $ JS.Ref (Name.fromKernel home name)
88 (JS.Ref (Name.fromKernel N.list "fromArray"))
89 [ JS.Array $ map (generateJsExpr mode) entries
92 Opt.Function args body ->
93 generateFunction (map Name.fromLocal args) (generate mode body)
96 JsExpr $ generateCall mode func args
98 Opt.TailCall name args ->
99 JsBlock $ generateTailCall mode name args
101 Opt.If branches final ->
102 generateIf mode branches final
106 generateDef mode def : codeToStmtList (generate mode body)
108 Opt.Case label root decider jumps ->
109 JsBlock $ generateCase mode label root decider jumps
111 Opt.Index expr index ->
112 JsExpr $ JS.Access (generateJsExpr mode expr) (Name.fromInt index)
114 Opt.Accessor field ->
115 JsExpr $ JS.Function Nothing [Name.dollar]
117 JS.Access (JS.Ref Name.dollar) (Name.fromField mode field)
120 Opt.Access record field ->
121 JsExpr $ JS.Access (generateJsExpr mode record) (Name.fromField mode field)
123 Opt.Update record fields ->
125 JS.Call (JS.Ref (Name.fromKernel N.utils "update"))
126 [ generateJsExpr mode record
127 , generateRecord mode fields
131 JsExpr $ generateRecord mode fields
134 JsExpr $ JS.Ref (Name.fromKernel N.utils "Tuple0")
136 Opt.Tuple a b maybeC ->
140 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple2"))
141 [ generateJsExpr mode a
142 , generateJsExpr mode b
146 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple3"))
147 [ generateJsExpr mode a
148 , generateJsExpr mode b
149 , generateJsExpr mode c
153 let string = JS.String (Text.encodeUtf8Builder src) in
154 JsExpr $ JS.Object [ ( Name.fromLocal "src", string ) ]
166codeToExpr :: Code -> JS.Expr
173 JS.Call (JS.Function Nothing [] stmts) []
176codeToStmtList :: Code -> [JS.Stmt]
180 [ JS.Return (Just expr) ]
186codeToStmt :: Code -> JS.Stmt
190 JS.Return (Just expr)
203{-# NOINLINE toChar #-}
206 JS.Ref (Name.fromKernel N.utils "chr")
213generateCtor :: Mode -> N.Name -> Index.ZeroBased -> Int -> ([Name.Name], Code)
214generateCtor mode name index arity =
216 argNames = map Name.fromInt [1 .. arity]
217 argFields = map (\n -> (n, JS.Ref n)) argNames
222 ( Name.dollar, JS.String (N.toBuilder name) )
225 ( Name.dollar, JS.Int (Index.toZeroBased index) )
227 ( argNames, JsExpr (JS.Object (tag:argFields)) )
234generateRecord :: Mode -> Map.Map N.Name Opt.Expr -> JS.Expr
235generateRecord mode fields =
237 toPair (field, value) =
238 (Name.fromField mode field, generateJsExpr mode value)
240 JS.Object (map toPair (Map.toList fields))
247generateDebug :: N.Name -> ModuleName.Canonical -> R.Region -> Maybe N.Name -> JS.Expr
248generateDebug name (ModuleName.Canonical _ home) region unhandledValueName =
249 if name /= "crash" then
250 JS.Ref (Name.fromGlobal ModuleName.debug name)
252 case unhandledValueName of
254 JS.Call (JS.Ref (Name.fromKernel N.utils "crash")) $
255 [ JS.String (N.toBuilder home)
256 , regionToJsExpr region
260 JS.Call (JS.Ref (Name.fromKernel N.utils "crashCase")) $
261 [ JS.String (N.toBuilder home)
262 , regionToJsExpr region
263 , JS.Ref (Name.fromLocal valueName)
267regionToJsExpr :: R.Region -> JS.Expr
268regionToJsExpr (R.Region start end) =
270 [ ( Name.fromLocal "start", positionToJsExpr start )
271 , ( Name.fromLocal "end", positionToJsExpr end )
275positionToJsExpr :: R.Position -> JS.Expr
276positionToJsExpr (R.Position line column) =
278 [ ( Name.fromLocal "line", JS.Int line )
279 , ( Name.fromLocal "column", JS.Int column )
287generateFunction :: [Name.Name] -> Code -> Code
288generateFunction args body =
289 case IntMap.lookup (length args) funcHelpers of
293 [ JS.Function Nothing args $
300 JsExpr $ JS.Function Nothing [arg] $
303 foldr addArg body args
306{-# NOINLINE funcHelpers #-}
307funcHelpers :: IntMap.IntMap JS.Expr
310 [ (2, JS.Ref (Name.fromLocal "F2"))
311 , (3, JS.Ref (Name.fromLocal "F3"))
312 , (4, JS.Ref (Name.fromLocal "F4"))
313 , (5, JS.Ref (Name.fromLocal "F5"))
314 , (6, JS.Ref (Name.fromLocal "F6"))
315 , (7, JS.Ref (Name.fromLocal "F7"))
316 , (8, JS.Ref (Name.fromLocal "F8"))
317 , (9, JS.Ref (Name.fromLocal "F9"))
325generateCall :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
326generateCall mode func args =
328 Opt.VarGlobal global@(Opt.Global (ModuleName.Canonical pkg _) _) | pkg == Pkg.core ->
329 generateCoreCall mode global args
333 (generateJsExpr mode func)
334 (map (generateJsExpr mode) args)
337generateNormalCall' :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
338generateNormalCall' home name args =
339 generateNormalCall (JS.Ref (Name.fromGlobal home name)) args
342generateNormalCall :: JS.Expr -> [JS.Expr] -> JS.Expr
343generateNormalCall func args =
344 case IntMap.lookup (length args) callHelpers of
346 JS.Call helper (func:args)
349 List.foldl' (\f a -> JS.Call f [a]) func args
352{-# NOINLINE callHelpers #-}
353callHelpers :: IntMap.IntMap JS.Expr
356 [ (2, JS.Ref (Name.fromLocal "A2"))
357 , (3, JS.Ref (Name.fromLocal "A3"))
358 , (4, JS.Ref (Name.fromLocal "A4"))
359 , (5, JS.Ref (Name.fromLocal "A5"))
360 , (6, JS.Ref (Name.fromLocal "A6"))
361 , (7, JS.Ref (Name.fromLocal "A7"))
362 , (8, JS.Ref (Name.fromLocal "A8"))
363 , (9, JS.Ref (Name.fromLocal "A9"))
371generateCoreCall :: Mode -> Opt.Global -> [Opt.Expr] -> JS.Expr
372generateCoreCall mode (Opt.Global home@(ModuleName.Canonical _ moduleName) name) args =
373 if moduleName == N.basics then
374 generateBasicsCall mode home name args
376 else if moduleName == N.bitwise then
377 generateBitwiseCall home name (map (generateJsExpr mode) args)
379 else if moduleName == N.tuple then
380 generateTupleCall home name (map (generateJsExpr mode) args)
383 generateNormalCall' home name (map (generateJsExpr mode) args)
386generateTupleCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
387generateTupleCall home name args =
391 "first" -> JS.Access value (Name.fromLocal "a")
392 "second" -> JS.Access value (Name.fromLocal "b")
393 _ -> generateNormalCall' home name args
396 generateNormalCall' home name args
399generateBitwiseCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
400generateBitwiseCall home name args =
404 "complement" -> JS.Prefix JS.PrefixBNot arg
405 _ -> generateNormalCall' home name args
409 "and" -> JS.Infix JS.OpBAnd left right
410 "or" -> JS.Infix JS.OpBOr left right
411 "xor" -> JS.Infix JS.OpBXor left right
412 "shiftLeftBy" -> JS.Infix JS.OpLShift right left
413 "shiftRightBy" -> JS.Infix JS.OpSpRShift right left
414 "shiftRightZfBy" -> JS.Infix JS.OpZfRShift right left
415 _ -> generateNormalCall' home name args
418 generateNormalCall' home name args
421generateBasicsCall :: Mode -> ModuleName.Canonical -> N.Name -> [Opt.Expr] -> JS.Expr
422generateBasicsCall mode home name args =
425 let arg = generateJsExpr mode elmArg in
427 "not" -> JS.Prefix JS.PrefixLNot arg
429 "truncate" -> JS.Infix JS.OpBOr arg (JS.Int 0)
430 _ -> generateNormalCall' home name [arg]
432 [elmLeft, elmRight] ->
434 "composeL" -> decomposeL mode elmLeft [elmRight]
435 "composeR" -> decomposeR mode [elmLeft] elmRight
436 "append" -> append mode elmLeft elmRight
437 "apL" -> generateJsExpr mode $ apply elmLeft elmRight
438 "apR" -> generateJsExpr mode $ apply elmRight elmLeft
441 left = generateJsExpr mode elmLeft
442 right = generateJsExpr mode elmRight
445 "add" -> JS.Infix JS.OpAdd left right
446 "sub" -> JS.Infix JS.OpSub left right
447 "mul" -> JS.Infix JS.OpMul left right
448 "fdiv" -> JS.Infix JS.OpDiv left right
449 "idiv" -> JS.Infix JS.OpBOr (JS.Infix JS.OpDiv left right) (JS.Int 0)
450 "eq" -> equal left right
451 "neq" -> notEqual left right
452 "lt" -> cmp JS.OpLT JS.OpLT 0 left right
453 "gt" -> cmp JS.OpGT JS.OpGT 0 left right
454 "le" -> cmp JS.OpLEq JS.OpLT 1 left right
455 "ge" -> cmp JS.OpGEq JS.OpGT (-1) left right
456 "or" -> JS.Infix JS.OpLOr left right
457 "and" -> JS.Infix JS.OpLAnd left right
458 "xor" -> JS.Infix JS.OpStrictNEq left right
459 _ -> generateNormalCall' home name [left, right]
462 generateNormalCall' home name (map (generateJsExpr mode) args)
465equal :: JS.Expr -> JS.Expr -> JS.Expr
467 if isLiteral left || isLiteral right then
470 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
473notEqual :: JS.Expr -> JS.Expr -> JS.Expr
475 if isLiteral left || isLiteral right then
478 JS.Prefix JS.PrefixLNot $
479 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
482cmp :: JS.InfixOp -> JS.InfixOp -> Int -> JS.Expr -> JS.Expr -> JS.Expr
483cmp idealOp backupOp backupInt left right =
484 if isLiteral left || isLiteral right then
485 JS.Infix idealOp left right
488 (JS.Call (JS.Ref (Name.fromKernel N.utils "cmp")) [left, right])
492isLiteral :: JS.Expr -> Bool
511decomposeL :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
512decomposeL mode expr funcs =
514 Opt.Call (Opt.VarGlobal (Opt.Global home "composeL")) [left, func]
515 | home == ModuleName.basics ->
516 decomposeL mode left (func:funcs)
519 generateJsExpr mode $
520 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
523decomposeR :: Mode -> [Opt.Expr] -> Opt.Expr -> JS.Expr
524decomposeR mode funcs expr =
526 Opt.Call (Opt.VarGlobal (Opt.Global home "composeR")) [func, right]
527 | home == ModuleName.basics ->
528 decomposeR mode (func:funcs) right
531 generateJsExpr mode $
532 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
535apply :: Opt.Expr -> Opt.Expr -> Opt.Expr
538 Opt.Accessor field ->
539 Opt.Access value field
542 Opt.Call f (args ++ [value])
545 Opt.Call func [value]
548append :: Mode -> Opt.Expr -> Opt.Expr -> JS.Expr
549append mode left right =
550 let seqs = generateJsExpr mode left : toSeqs mode right in
551 if any isStringLiteral seqs then
552 foldr1 (JS.Infix JS.OpAdd) seqs
557jsAppend :: JS.Expr -> JS.Expr -> JS.Expr
559 JS.Call (JS.Ref (Name.fromKernel N.utils "ap")) [a, b]
562toSeqs :: Mode -> Opt.Expr -> [JS.Expr]
565 Opt.Call (Opt.VarGlobal (Opt.Global home "append")) [left, right]
566 | home == ModuleName.basics ->
567 generateJsExpr mode left : toSeqs mode right
570 [generateJsExpr mode expr]
573isStringLiteral :: JS.Expr -> Bool
574isStringLiteral expr =
584-- SIMPLIFY INFIX OPERATORS
587strictEq :: JS.Expr -> JS.Expr -> JS.Expr
591 JS.Prefix JS.PrefixLNot right
594 if bool then right else JS.Prefix JS.PrefixLNot right
599 JS.Prefix JS.PrefixLNot left
602 if bool then left else JS.Prefix JS.PrefixLNot left
605 JS.Infix JS.OpStrictEq left right
608strictNEq :: JS.Expr -> JS.Expr -> JS.Expr
609strictNEq left right =
615 if bool then JS.Prefix JS.PrefixLNot right else right
623 if bool then JS.Prefix JS.PrefixLNot left else left
626 JS.Infix JS.OpStrictNEq left right
633-- TODO check if JS minifiers collapse unnecessary temporary variables
635generateTailCall :: Mode -> N.Name -> [(N.Name, Opt.Expr)] -> [JS.Stmt]
636generateTailCall mode name args =
638 toTempVars (argName, arg) =
639 ( Name.makeTemp argName, Just (generateJsExpr mode arg) )
641 toRealVars (argName, _) =
643 JS.Assign (JS.LRef (Name.fromLocal argName)) (JS.Ref (Name.makeTemp argName))
645 JS.Var (map toTempVars args)
646 : map toRealVars args
647 ++ [ JS.Continue (Just (Name.fromLocal name)) ]
654generateDef :: Mode -> Opt.Def -> JS.Stmt
655generateDef mode def =
658 JS.Var [ (Name.fromLocal name, Just (generateJsExpr mode body)) ]
660 Opt.TailDef name argNames body ->
663 JS.Function Nothing (map Name.fromLocal argNames)
664 [ JS.Labelled (Name.fromLocal name) $
665 JS.While (JS.Bool True) $
666 codeToStmt $ generate mode body
669 JS.Var [ (Name.fromLocal name, Just function) ]
676generateIf :: Mode -> [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> Code
677generateIf mode givenBranches givenFinal =
680 crushIfs givenBranches givenFinal
682 convertBranch (condition, expr) =
683 ( generateJsExpr mode condition
687 branchExprs = map convertBranch branches
688 finalCode = generate mode final
690 if isBlock finalCode || any (isBlock . snd) branchExprs then
691 JsBlock [ foldr addStmtIf (codeToStmt finalCode) branchExprs ]
693 JsExpr $ foldr addExprIf (codeToExpr finalCode) branchExprs
696addExprIf :: (JS.Expr, Code) -> JS.Expr -> JS.Expr
697addExprIf (condition, branch) final =
698 JS.If condition (codeToExpr branch) final
701addStmtIf :: (JS.Expr, Code) -> JS.Stmt -> JS.Stmt
702addStmtIf (condition, branch) final =
703 JS.IfStmt condition (codeToStmt branch) final
706isBlock :: Code -> Bool
713crushIfs :: [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
714crushIfs branches final =
715 crushIfsHelp [] branches final
719 :: [(Opt.Expr, Opt.Expr)]
720 -> [(Opt.Expr, Opt.Expr)]
722 -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
723crushIfsHelp visitedBranches unvisitedBranches final =
724 case unvisitedBranches of
727 Opt.If subBranches subFinal ->
728 crushIfsHelp visitedBranches subBranches subFinal
731 (reverse visitedBranches, final)
733 visiting : unvisited ->
734 crushIfsHelp (visiting : visitedBranches) unvisited final
741generateCase :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [(Int, Opt.Expr)] -> [JS.Stmt]
742generateCase mode label root decider jumps =
743 foldr (goto mode label) (generateDecider mode label root decider) jumps
746goto :: Mode -> N.Name -> (Int, Opt.Expr) -> [JS.Stmt] -> [JS.Stmt]
747goto mode label (index, branch) stmts =
751 (Name.makeLabel label index)
752 (JS.While (JS.Bool True) (JS.Block stmts))
754 labeledDeciderStmt : codeToStmtList (generate mode branch)
757generateDecider :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [JS.Stmt]
758generateDecider mode label root decisionTree =
760 Opt.Leaf (Opt.Inline branch) ->
761 codeToStmtList (generate mode branch)
763 Opt.Leaf (Opt.Jump index) ->
764 [ JS.Break (Just (Name.makeLabel label index)) ]
766 Opt.Chain testChain success failure ->
768 makeTest (path, test) =
770 (pathToTestableExpr mode root path test)
771 (testToExpr mode test)
774 (List.foldl1' (JS.Infix JS.OpLAnd) (map makeTest testChain))
775 (JS.Block $ generateDecider mode label root success)
776 (JS.Block $ generateDecider mode label root failure)
779 Opt.FanOut path edges fallback ->
781 (pathToTestableExpr mode root path (fst (head edges)))
783 (addCase mode label root)
784 [ JS.Default (generateDecider mode label root fallback) ]
790addCase :: Mode -> N.Name -> N.Name -> (DT.Test, Opt.Decider Opt.Choice) -> [JS.Case] -> [JS.Case]
791addCase mode label root (test, subTree) cases =
792 let stmts = generateDecider mode label root subTree in
793 JS.Case (testToExpr mode test) stmts : cases
796testToExpr :: Mode -> DT.Test -> JS.Expr
797testToExpr mode test =
799 DT.IsCtor _ tag index ->
802 JS.String (N.toBuilder tag)
805 JS.Int (Index.toZeroBased index)
811 JS.String (Text.encodeUtf8Builder char)
814 JS.String (Text.encodeUtf8Builder string)
817pathToTestableExpr :: Mode -> N.Name -> DT.Path -> DT.Test -> JS.Expr
818pathToTestableExpr mode root path exampleTest =
821 pathToJsExpr mode (JS.Ref (Name.fromLocal root)) path
825 JS.Access expr Name.dollar
836 JS.Call (JS.Access expr (Name.fromLocal "valueOf")) []
842pathToJsExpr :: Mode -> JS.Expr -> DT.Path -> JS.Expr
843pathToJsExpr mode expr path =
845 DT.Index index subpath ->
846 pathToJsExpr mode (JS.Access expr (Name.fromInt index)) subpath
848 DT.Field field subpath ->
849 pathToJsExpr mode (JS.Access expr (Name.fromField mode field)) subpath
862generateMain :: Mode -> I.Interfaces -> ModuleName.Canonical -> [N.Name] -> Opt.Main -> JS.Expr
863generateMain mode interfaces home segments main =
866 JS.Ref (Name.fromKernel N.browser "staticPage")
867 # JS.Ref (Name.fromGlobal home "main")
868 # List.foldl' addDot elm segments
870 Opt.Dynamic decoder msgType ->
871 JS.Ref (Name.fromGlobal home "main")
872 # List.foldl' addDot elm segments
873 # generateJsExpr mode decoder
874 # JS.Json (Type.encodeMetadata (Extract.fromMsg interfaces msgType))
877(#) :: JS.Expr -> JS.Expr -> JS.Expr
882addDot :: JS.Expr -> N.Name -> JS.Expr
884 JS.Index root (JS.String (N.toBuilder name))
890 JS.Ref (Name.fromLocal "Elm")
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