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.Canonical as Can
20import qualified AST.Optimized as Opt
21import qualified AST.Module.Name as ModuleName
22import qualified Data.Index as Index
23import qualified Elm.Compiler.Type as Type
24import qualified Elm.Compiler.Type.Extract as Extract
25import qualified Elm.Interface as I
26import qualified Elm.Name as N
27import qualified Elm.Package as Pkg
28import qualified Elm.Compiler.Version as Version
29import qualified Generate.JavaScript.Builder as JS
30import qualified Generate.JavaScript.Name as Name
31import qualified Json.Encode as Encode
32import qualified Optimize.DecisionTree as DT
33import qualified Reporting.Region as R
47generateJsExpr :: Mode -> Opt.Expr -> JS.Expr
48generateJsExpr mode expression =
49 codeToExpr (generate mode expression)
52generate :: Mode -> Opt.Expr -> Code
53generate mode expression =
59 JS.Call toChar [ JS.String (Text.encodeUtf8Builder char) ]
62 JS.String (Text.encodeUtf8Builder char)
65 JsExpr $ JS.String (Text.encodeUtf8Builder string)
71 JsExpr $ JS.Float float
74 JsExpr $ JS.Ref (Name.fromLocal name)
76 Opt.VarGlobal (Opt.Global home name) ->
77 JsExpr $ JS.Ref (Name.fromGlobal home name)
79 Opt.VarCycle home name ->
80 JsExpr $ JS.Call (JS.Ref (Name.fromCycle home name)) []
82 Opt.VarDebug name home region unhandledValueName ->
83 JsExpr $ generateDebug name home region unhandledValueName
85 Opt.VarKernel home name ->
86 JsExpr $ JS.Ref (Name.fromKernel home name)
91 (JS.Ref (Name.fromKernel N.list "fromArray"))
92 [ JS.Array $ map (generateJsExpr mode) entries
95 Opt.Function args body ->
96 generateFunction (map Name.fromLocal args) (generate mode body)
99 JsExpr $ generateCall mode func args
101 Opt.TailCall name args ->
102 JsBlock $ generateTailCall mode name args
104 Opt.If branches final ->
105 generateIf mode branches final
109 generateDef mode def : codeToStmtList (generate mode body)
111 Opt.Case label root decider jumps ->
112 JsBlock $ generateCase mode label root decider jumps
114 Opt.Index expr index ->
115 JsExpr $ JS.Access (generateJsExpr mode expr) (Name.fromInt index)
117 Opt.Accessor field ->
118 JsExpr $ JS.Function Nothing [Name.dollar]
120 JS.Access (JS.Ref Name.dollar) (Name.fromField mode field)
123 Opt.Access record field ->
124 JsExpr $ JS.Access (generateJsExpr mode record) (Name.fromField mode field)
126 Opt.Update record fields ->
128 JS.Call (JS.Ref (Name.fromKernel N.utils "update"))
129 [ generateJsExpr mode record
130 , generateRecord mode fields
134 JsExpr $ generateRecord mode fields
137 JsExpr $ JS.Ref (Name.fromKernel N.utils "Tuple0")
139 Opt.Tuple a b maybeC ->
143 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple2"))
144 [ generateJsExpr mode a
145 , generateJsExpr mode b
149 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple3"))
150 [ generateJsExpr mode a
151 , generateJsExpr mode b
152 , generateJsExpr mode c
156 let string = JS.String (Text.encodeUtf8Builder src) in
157 JsExpr $ JS.Object [ ( Name.fromLocal "src", string ) ]
169codeToExpr :: Code -> JS.Expr
175 JsBlock [ JS.Return (Just expr) ] ->
179 JS.Call (JS.Function Nothing [] stmts) []
182codeToStmtList :: Code -> [JS.Stmt]
186 [ JS.Return (Just expr) ]
192codeToStmt :: Code -> JS.Stmt
196 JS.Return (Just expr)
209{-# NOINLINE toChar #-}
212 JS.Ref (Name.fromKernel N.utils "chr")
219generateCtor :: Mode -> N.Name -> Index.ZeroBased -> Int -> ([Name.Name], Code)
220generateCtor mode name index arity =
222 argNames = map Name.fromInt [1 .. arity]
223 argFields = map (\n -> (n, JS.Ref n)) argNames
228 ( Name.dollar, JS.String (N.toBuilder name) )
231 ( Name.dollar, JS.Int (Index.toZeroBased index) )
233 ( argNames, JsExpr (JS.Object (tag:argFields)) )
240generateRecord :: Mode -> Map.Map N.Name Opt.Expr -> JS.Expr
241generateRecord mode fields =
243 toPair (field, value) =
244 (Name.fromField mode field, generateJsExpr mode value)
246 JS.Object (map toPair (Map.toList fields))
253generateDebug :: N.Name -> ModuleName.Canonical -> R.Region -> Maybe N.Name -> JS.Expr
254generateDebug name (ModuleName.Canonical _ home) region unhandledValueName =
255 if name /= "crash" then
256 JS.Ref (Name.fromGlobal ModuleName.debug name)
258 case unhandledValueName of
260 JS.Call (JS.Ref (Name.fromKernel N.utils "crash")) $
261 [ JS.String (N.toBuilder home)
262 , regionToJsExpr region
266 JS.Call (JS.Ref (Name.fromKernel N.utils "crashCase")) $
267 [ JS.String (N.toBuilder home)
268 , regionToJsExpr region
269 , JS.Ref (Name.fromLocal valueName)
273regionToJsExpr :: R.Region -> JS.Expr
274regionToJsExpr (R.Region start end) =
276 [ ( Name.fromLocal "start", positionToJsExpr start )
277 , ( Name.fromLocal "end", positionToJsExpr end )
281positionToJsExpr :: R.Position -> JS.Expr
282positionToJsExpr (R.Position line column) =
284 [ ( Name.fromLocal "line", JS.Int line )
285 , ( Name.fromLocal "column", JS.Int column )
293generateFunction :: [Name.Name] -> Code -> Code
294generateFunction args body =
295 case IntMap.lookup (length args) funcHelpers of
299 [ JS.Function Nothing args $
306 JsExpr $ JS.Function Nothing [arg] $
309 foldr addArg body args
312{-# NOINLINE funcHelpers #-}
313funcHelpers :: IntMap.IntMap JS.Expr
316 [ (2, JS.Ref (Name.fromLocal "F2"))
317 , (3, JS.Ref (Name.fromLocal "F3"))
318 , (4, JS.Ref (Name.fromLocal "F4"))
319 , (5, JS.Ref (Name.fromLocal "F5"))
320 , (6, JS.Ref (Name.fromLocal "F6"))
321 , (7, JS.Ref (Name.fromLocal "F7"))
322 , (8, JS.Ref (Name.fromLocal "F8"))
323 , (9, JS.Ref (Name.fromLocal "F9"))
331generateCall :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
332generateCall mode func args =
334 Opt.VarGlobal global@(Opt.Global (ModuleName.Canonical pkg _) _) | pkg == Pkg.core ->
335 generateCoreCall mode global args
339 (generateJsExpr mode func)
340 (map (generateJsExpr mode) args)
343generateNormalCall' :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
344generateNormalCall' home name args =
345 generateNormalCall (JS.Ref (Name.fromGlobal home name)) args
348generateNormalCall :: JS.Expr -> [JS.Expr] -> JS.Expr
349generateNormalCall func args =
350 case IntMap.lookup (length args) callHelpers of
352 JS.Call helper (func:args)
355 List.foldl' (\f a -> JS.Call f [a]) func args
358{-# NOINLINE callHelpers #-}
359callHelpers :: IntMap.IntMap JS.Expr
362 [ (2, JS.Ref (Name.fromLocal "A2"))
363 , (3, JS.Ref (Name.fromLocal "A3"))
364 , (4, JS.Ref (Name.fromLocal "A4"))
365 , (5, JS.Ref (Name.fromLocal "A5"))
366 , (6, JS.Ref (Name.fromLocal "A6"))
367 , (7, JS.Ref (Name.fromLocal "A7"))
368 , (8, JS.Ref (Name.fromLocal "A8"))
369 , (9, JS.Ref (Name.fromLocal "A9"))
377generateCoreCall :: Mode -> Opt.Global -> [Opt.Expr] -> JS.Expr
378generateCoreCall mode (Opt.Global home@(ModuleName.Canonical _ moduleName) name) args =
379 if moduleName == N.basics then
380 generateBasicsCall mode home name args
382 else if moduleName == N.bitwise then
383 generateBitwiseCall home name (map (generateJsExpr mode) args)
385 else if moduleName == N.tuple then
386 generateTupleCall home name (map (generateJsExpr mode) args)
389 generateNormalCall' home name (map (generateJsExpr mode) args)
392generateTupleCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
393generateTupleCall home name args =
397 "first" -> JS.Access value (Name.fromLocal "a")
398 "second" -> JS.Access value (Name.fromLocal "b")
399 _ -> generateNormalCall' home name args
402 generateNormalCall' home name args
405generateBitwiseCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
406generateBitwiseCall home name args =
410 "complement" -> JS.Prefix JS.PrefixBNot arg
411 _ -> generateNormalCall' home name args
415 "and" -> JS.Infix JS.OpBAnd left right
416 "or" -> JS.Infix JS.OpBOr left right
417 "xor" -> JS.Infix JS.OpBXor left right
418 "shiftLeftBy" -> JS.Infix JS.OpLShift right left
419 "shiftRightBy" -> JS.Infix JS.OpSpRShift right left
420 "shiftRightZfBy" -> JS.Infix JS.OpZfRShift right left
421 _ -> generateNormalCall' home name args
424 generateNormalCall' home name args
427generateBasicsCall :: Mode -> ModuleName.Canonical -> N.Name -> [Opt.Expr] -> JS.Expr
428generateBasicsCall mode home name args =
431 let arg = generateJsExpr mode elmArg in
433 "not" -> JS.Prefix JS.PrefixLNot arg
435 "truncate" -> JS.Infix JS.OpBOr arg (JS.Int 0)
436 _ -> generateNormalCall' home name [arg]
438 [elmLeft, elmRight] ->
440 "composeL" -> decomposeL mode elmLeft [elmRight]
441 "composeR" -> decomposeR mode [elmLeft] elmRight
442 "append" -> append mode elmLeft elmRight
443 "apL" -> generateJsExpr mode $ apply elmLeft elmRight
444 "apR" -> generateJsExpr mode $ apply elmRight elmLeft
447 left = generateJsExpr mode elmLeft
448 right = generateJsExpr mode elmRight
451 "add" -> JS.Infix JS.OpAdd left right
452 "sub" -> JS.Infix JS.OpSub left right
453 "mul" -> JS.Infix JS.OpMul left right
454 "fdiv" -> JS.Infix JS.OpDiv left right
455 "idiv" -> JS.Infix JS.OpBOr (JS.Infix JS.OpDiv left right) (JS.Int 0)
456 "eq" -> equal left right
457 "neq" -> notEqual left right
458 "lt" -> cmp JS.OpLT JS.OpLT 0 left right
459 "gt" -> cmp JS.OpGT JS.OpGT 0 left right
460 "le" -> cmp JS.OpLEq JS.OpLT 1 left right
461 "ge" -> cmp JS.OpGEq JS.OpGT (-1) left right
462 "or" -> JS.Infix JS.OpLOr left right
463 "and" -> JS.Infix JS.OpLAnd left right
464 "xor" -> JS.Infix JS.OpStrictNEq left right
465 _ -> generateNormalCall' home name [left, right]
468 generateNormalCall' home name (map (generateJsExpr mode) args)
471equal :: JS.Expr -> JS.Expr -> JS.Expr
473 if isLiteral left || isLiteral right then
476 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
479notEqual :: JS.Expr -> JS.Expr -> JS.Expr
481 if isLiteral left || isLiteral right then
484 JS.Prefix JS.PrefixLNot $
485 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
488cmp :: JS.InfixOp -> JS.InfixOp -> Int -> JS.Expr -> JS.Expr -> JS.Expr
489cmp idealOp backupOp backupInt left right =
490 if isLiteral left || isLiteral right then
491 JS.Infix idealOp left right
494 (JS.Call (JS.Ref (Name.fromKernel N.utils "cmp")) [left, right])
498isLiteral :: JS.Expr -> Bool
517decomposeL :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
518decomposeL mode expr funcs =
520 Opt.Call (Opt.VarGlobal (Opt.Global home "composeL")) [left, func]
521 | home == ModuleName.basics ->
522 decomposeL mode left (func:funcs)
525 generateJsExpr mode $
526 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
529decomposeR :: Mode -> [Opt.Expr] -> Opt.Expr -> JS.Expr
530decomposeR mode funcs expr =
532 Opt.Call (Opt.VarGlobal (Opt.Global home "composeR")) [func, right]
533 | home == ModuleName.basics ->
534 decomposeR mode (func:funcs) right
537 generateJsExpr mode $
538 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
541apply :: Opt.Expr -> Opt.Expr -> Opt.Expr
544 Opt.Accessor field ->
545 Opt.Access value field
548 Opt.Call f (args ++ [value])
551 Opt.Call func [value]
554append :: Mode -> Opt.Expr -> Opt.Expr -> JS.Expr
555append mode left right =
556 let seqs = generateJsExpr mode left : toSeqs mode right in
557 if any isStringLiteral seqs then
558 foldr1 (JS.Infix JS.OpAdd) seqs
563jsAppend :: JS.Expr -> JS.Expr -> JS.Expr
565 JS.Call (JS.Ref (Name.fromKernel N.utils "ap")) [a, b]
568toSeqs :: Mode -> Opt.Expr -> [JS.Expr]
571 Opt.Call (Opt.VarGlobal (Opt.Global home "append")) [left, right]
572 | home == ModuleName.basics ->
573 generateJsExpr mode left : toSeqs mode right
576 [generateJsExpr mode expr]
579isStringLiteral :: JS.Expr -> Bool
580isStringLiteral expr =
590-- SIMPLIFY INFIX OPERATORS
593strictEq :: JS.Expr -> JS.Expr -> JS.Expr
597 JS.Prefix JS.PrefixLNot right
600 if bool then right else JS.Prefix JS.PrefixLNot right
605 JS.Prefix JS.PrefixLNot left
608 if bool then left else JS.Prefix JS.PrefixLNot left
611 JS.Infix JS.OpStrictEq left right
614strictNEq :: JS.Expr -> JS.Expr -> JS.Expr
615strictNEq left right =
621 if bool then JS.Prefix JS.PrefixLNot right else right
629 if bool then JS.Prefix JS.PrefixLNot left else left
632 JS.Infix JS.OpStrictNEq left right
639-- TODO check if JS minifiers collapse unnecessary temporary variables
641generateTailCall :: Mode -> N.Name -> [(N.Name, Opt.Expr)] -> [JS.Stmt]
642generateTailCall mode name args =
644 toTempVars (argName, arg) =
645 ( Name.makeTemp argName, Just (generateJsExpr mode arg) )
647 toRealVars (argName, _) =
649 JS.Assign (JS.LRef (Name.fromLocal argName)) (JS.Ref (Name.makeTemp argName))
651 JS.Var (map toTempVars args)
652 : map toRealVars args
653 ++ [ JS.Continue (Just (Name.fromLocal name)) ]
660generateDef :: Mode -> Opt.Def -> JS.Stmt
661generateDef mode def =
664 JS.Var [ (Name.fromLocal name, Just (generateJsExpr mode body)) ]
666 Opt.TailDef name argNames body ->
669 JS.Function Nothing (map Name.fromLocal argNames)
670 [ JS.Labelled (Name.fromLocal name) $
671 JS.While (JS.Bool True) $
672 codeToStmt $ generate mode body
675 JS.Var [ (Name.fromLocal name, Just function) ]
682generateIf :: Mode -> [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> Code
683generateIf mode givenBranches givenFinal =
686 crushIfs givenBranches givenFinal
688 convertBranch (condition, expr) =
689 ( generateJsExpr mode condition
693 branchExprs = map convertBranch branches
694 finalCode = generate mode final
696 if isBlock finalCode || any (isBlock . snd) branchExprs then
697 JsBlock [ foldr addStmtIf (codeToStmt finalCode) branchExprs ]
699 JsExpr $ foldr addExprIf (codeToExpr finalCode) branchExprs
702addExprIf :: (JS.Expr, Code) -> JS.Expr -> JS.Expr
703addExprIf (condition, branch) final =
704 JS.If condition (codeToExpr branch) final
707addStmtIf :: (JS.Expr, Code) -> JS.Stmt -> JS.Stmt
708addStmtIf (condition, branch) final =
709 JS.IfStmt condition (codeToStmt branch) final
712isBlock :: Code -> Bool
719crushIfs :: [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
720crushIfs branches final =
721 crushIfsHelp [] branches final
725 :: [(Opt.Expr, Opt.Expr)]
726 -> [(Opt.Expr, Opt.Expr)]
728 -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
729crushIfsHelp visitedBranches unvisitedBranches final =
730 case unvisitedBranches of
733 Opt.If subBranches subFinal ->
734 crushIfsHelp visitedBranches subBranches subFinal
737 (reverse visitedBranches, final)
739 visiting : unvisited ->
740 crushIfsHelp (visiting : visitedBranches) unvisited final
747generateCase :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [(Int, Opt.Expr)] -> [JS.Stmt]
748generateCase mode label root decider jumps =
749 foldr (goto mode label) (generateDecider mode label root decider) jumps
752goto :: Mode -> N.Name -> (Int, Opt.Expr) -> [JS.Stmt] -> [JS.Stmt]
753goto mode label (index, branch) stmts =
757 (Name.makeLabel label index)
758 (JS.While (JS.Bool True) (JS.Block stmts))
760 labeledDeciderStmt : codeToStmtList (generate mode branch)
763generateDecider :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [JS.Stmt]
764generateDecider mode label root decisionTree =
766 Opt.Leaf (Opt.Inline branch) ->
767 codeToStmtList (generate mode branch)
769 Opt.Leaf (Opt.Jump index) ->
770 [ JS.Break (Just (Name.makeLabel label index)) ]
772 Opt.Chain testChain success failure ->
774 makeTest (path, test) =
776 (pathToTestableExpr mode root path test)
777 (testToExpr mode test)
780 (List.foldl1' (JS.Infix JS.OpLAnd) (map makeTest testChain))
781 (JS.Block $ generateDecider mode label root success)
782 (JS.Block $ generateDecider mode label root failure)
785 Opt.FanOut path edges fallback ->
787 (pathToTestableExpr mode root path (fst (head edges)))
789 (addCase mode label root)
790 [ JS.Default (generateDecider mode label root fallback) ]
796addCase :: Mode -> N.Name -> N.Name -> (DT.Test, Opt.Decider Opt.Choice) -> [JS.Case] -> [JS.Case]
797addCase mode label root (test, subTree) cases =
798 let stmts = generateDecider mode label root subTree in
799 JS.Case (testToExpr mode test) stmts : cases
802testToExpr :: Mode -> DT.Test -> JS.Expr
803testToExpr mode test =
805 DT.IsCtor _ tag index ->
808 JS.String (N.toBuilder tag)
811 JS.Int (Index.toZeroBased index)
817 JS.String (Text.encodeUtf8Builder char)
820 JS.String (Text.encodeUtf8Builder string)
823pathToTestableExpr :: Mode -> N.Name -> DT.Path -> DT.Test -> JS.Expr
824pathToTestableExpr mode root path exampleTest =
827 pathToJsExpr mode (JS.Ref (Name.fromLocal root)) path
831 JS.Access expr Name.dollar
842 JS.Call (JS.Access expr (Name.fromLocal "valueOf")) []
848pathToJsExpr :: Mode -> JS.Expr -> DT.Path -> JS.Expr
849pathToJsExpr mode expr path =
851 DT.Index index subpath ->
852 pathToJsExpr mode (JS.Access expr (Name.fromInt index)) subpath
854 DT.Field field subpath ->
855 pathToJsExpr mode (JS.Access expr (Name.fromField mode field)) subpath
868generateMain :: Mode -> I.Interfaces -> ModuleName.Canonical -> Opt.Main -> JS.Expr
869generateMain mode interfaces home main =
872 JS.Ref (Name.fromKernel N.browser "staticPage")
873 # JS.Ref (Name.fromGlobal home "main")
877 Opt.Dynamic decoder msgType ->
878 JS.Ref (Name.fromGlobal home "main")
879 # generateJsExpr mode decoder
880 # toDebugMetadata mode interfaces msgType
883(#) :: JS.Expr -> JS.Expr -> JS.Expr
888toDebugMetadata :: Mode -> I.Interfaces -> Can.Type -> JS.Expr
889toDebugMetadata mode interfaces msgType =
895 JS.Json $ Encode.object $
896 [ ("versions", Encode.object [ ("elm", Pkg.encodeVersion Version.version) ])
897 , ("types", Type.encodeMetadata (Extract.fromMsg interfaces msgType))
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