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 Elm.Compiler.Version as Version
28import qualified Generate.JavaScript.Builder as JS
29import qualified Generate.JavaScript.Name as Name
30import qualified Json.Encode as Encode
31import qualified Optimize.DecisionTree as DT
32import qualified Reporting.Region as R
46generateJsExpr :: Mode -> Opt.Expr -> JS.Expr
47generateJsExpr mode expression =
48 codeToExpr (generate mode expression)
51generate :: Mode -> Opt.Expr -> Code
52generate mode expression =
58 JS.Call toChar [ JS.String (Text.encodeUtf8Builder char) ]
61 JS.String (Text.encodeUtf8Builder char)
64 JsExpr $ JS.String (Text.encodeUtf8Builder string)
70 JsExpr $ JS.Float float
73 JsExpr $ JS.Ref (Name.fromLocal name)
75 Opt.VarGlobal (Opt.Global home name) ->
76 JsExpr $ JS.Ref (Name.fromGlobal home name)
78 Opt.VarCycle home name ->
79 JsExpr $ JS.Call (JS.Ref (Name.fromCycle home name)) []
81 Opt.VarDebug name home region unhandledValueName ->
82 JsExpr $ generateDebug name home region unhandledValueName
84 Opt.VarKernel home name ->
85 JsExpr $ JS.Ref (Name.fromKernel home name)
90 (JS.Ref (Name.fromKernel N.list "fromArray"))
91 [ JS.Array $ map (generateJsExpr mode) entries
94 Opt.Function args body ->
95 generateFunction (map Name.fromLocal args) (generate mode body)
98 JsExpr $ generateCall mode func args
100 Opt.TailCall name args ->
101 JsBlock $ generateTailCall mode name args
103 Opt.If branches final ->
104 generateIf mode branches final
108 generateDef mode def : codeToStmtList (generate mode body)
110 Opt.Case label root decider jumps ->
111 JsBlock $ generateCase mode label root decider jumps
113 Opt.Index expr index ->
114 JsExpr $ JS.Access (generateJsExpr mode expr) (Name.fromInt index)
116 Opt.Accessor field ->
117 JsExpr $ JS.Function Nothing [Name.dollar]
119 JS.Access (JS.Ref Name.dollar) (Name.fromField mode field)
122 Opt.Access record field ->
123 JsExpr $ JS.Access (generateJsExpr mode record) (Name.fromField mode field)
125 Opt.Update record fields ->
127 JS.Call (JS.Ref (Name.fromKernel N.utils "update"))
128 [ generateJsExpr mode record
129 , generateRecord mode fields
133 JsExpr $ generateRecord mode fields
136 JsExpr $ JS.Ref (Name.fromKernel N.utils "Tuple0")
138 Opt.Tuple a b maybeC ->
142 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple2"))
143 [ generateJsExpr mode a
144 , generateJsExpr mode b
148 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple3"))
149 [ generateJsExpr mode a
150 , generateJsExpr mode b
151 , generateJsExpr mode c
155 let string = JS.String (Text.encodeUtf8Builder src) in
156 JsExpr $ JS.Object [ ( Name.fromLocal "src", string ) ]
168codeToExpr :: Code -> JS.Expr
175 JS.Call (JS.Function Nothing [] stmts) []
178codeToStmtList :: Code -> [JS.Stmt]
182 [ JS.Return (Just expr) ]
188codeToStmt :: Code -> JS.Stmt
192 JS.Return (Just expr)
205{-# NOINLINE toChar #-}
208 JS.Ref (Name.fromKernel N.utils "chr")
215generateCtor :: Mode -> N.Name -> Index.ZeroBased -> Int -> ([Name.Name], Code)
216generateCtor mode name index arity =
218 argNames = map Name.fromInt [1 .. arity]
219 argFields = map (\n -> (n, JS.Ref n)) argNames
224 ( Name.dollar, JS.String (N.toBuilder name) )
227 ( Name.dollar, JS.Int (Index.toZeroBased index) )
229 ( argNames, JsExpr (JS.Object (tag:argFields)) )
236generateRecord :: Mode -> Map.Map N.Name Opt.Expr -> JS.Expr
237generateRecord mode fields =
239 toPair (field, value) =
240 (Name.fromField mode field, generateJsExpr mode value)
242 JS.Object (map toPair (Map.toList fields))
249generateDebug :: N.Name -> ModuleName.Canonical -> R.Region -> Maybe N.Name -> JS.Expr
250generateDebug name (ModuleName.Canonical _ home) region unhandledValueName =
251 if name /= "crash" then
252 JS.Ref (Name.fromGlobal ModuleName.debug name)
254 case unhandledValueName of
256 JS.Call (JS.Ref (Name.fromKernel N.utils "crash")) $
257 [ JS.String (N.toBuilder home)
258 , regionToJsExpr region
262 JS.Call (JS.Ref (Name.fromKernel N.utils "crashCase")) $
263 [ JS.String (N.toBuilder home)
264 , regionToJsExpr region
265 , JS.Ref (Name.fromLocal valueName)
269regionToJsExpr :: R.Region -> JS.Expr
270regionToJsExpr (R.Region start end) =
272 [ ( Name.fromLocal "start", positionToJsExpr start )
273 , ( Name.fromLocal "end", positionToJsExpr end )
277positionToJsExpr :: R.Position -> JS.Expr
278positionToJsExpr (R.Position line column) =
280 [ ( Name.fromLocal "line", JS.Int line )
281 , ( Name.fromLocal "column", JS.Int column )
289generateFunction :: [Name.Name] -> Code -> Code
290generateFunction args body =
291 case IntMap.lookup (length args) funcHelpers of
295 [ JS.Function Nothing args $
302 JsExpr $ JS.Function Nothing [arg] $
305 foldr addArg body args
308{-# NOINLINE funcHelpers #-}
309funcHelpers :: IntMap.IntMap JS.Expr
312 [ (2, JS.Ref (Name.fromLocal "F2"))
313 , (3, JS.Ref (Name.fromLocal "F3"))
314 , (4, JS.Ref (Name.fromLocal "F4"))
315 , (5, JS.Ref (Name.fromLocal "F5"))
316 , (6, JS.Ref (Name.fromLocal "F6"))
317 , (7, JS.Ref (Name.fromLocal "F7"))
318 , (8, JS.Ref (Name.fromLocal "F8"))
319 , (9, JS.Ref (Name.fromLocal "F9"))
327generateCall :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
328generateCall mode func args =
330 Opt.VarGlobal global@(Opt.Global (ModuleName.Canonical pkg _) _) | pkg == Pkg.core ->
331 generateCoreCall mode global args
335 (generateJsExpr mode func)
336 (map (generateJsExpr mode) args)
339generateNormalCall' :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
340generateNormalCall' home name args =
341 generateNormalCall (JS.Ref (Name.fromGlobal home name)) args
344generateNormalCall :: JS.Expr -> [JS.Expr] -> JS.Expr
345generateNormalCall func args =
346 case IntMap.lookup (length args) callHelpers of
348 JS.Call helper (func:args)
351 List.foldl' (\f a -> JS.Call f [a]) func args
354{-# NOINLINE callHelpers #-}
355callHelpers :: IntMap.IntMap JS.Expr
358 [ (2, JS.Ref (Name.fromLocal "A2"))
359 , (3, JS.Ref (Name.fromLocal "A3"))
360 , (4, JS.Ref (Name.fromLocal "A4"))
361 , (5, JS.Ref (Name.fromLocal "A5"))
362 , (6, JS.Ref (Name.fromLocal "A6"))
363 , (7, JS.Ref (Name.fromLocal "A7"))
364 , (8, JS.Ref (Name.fromLocal "A8"))
365 , (9, JS.Ref (Name.fromLocal "A9"))
373generateCoreCall :: Mode -> Opt.Global -> [Opt.Expr] -> JS.Expr
374generateCoreCall mode (Opt.Global home@(ModuleName.Canonical _ moduleName) name) args =
375 if moduleName == N.basics then
376 generateBasicsCall mode home name args
378 else if moduleName == N.bitwise then
379 generateBitwiseCall home name (map (generateJsExpr mode) args)
381 else if moduleName == N.tuple then
382 generateTupleCall home name (map (generateJsExpr mode) args)
385 generateNormalCall' home name (map (generateJsExpr mode) args)
388generateTupleCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
389generateTupleCall home name args =
393 "first" -> JS.Access value (Name.fromLocal "a")
394 "second" -> JS.Access value (Name.fromLocal "b")
395 _ -> generateNormalCall' home name args
398 generateNormalCall' home name args
401generateBitwiseCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
402generateBitwiseCall home name args =
406 "complement" -> JS.Prefix JS.PrefixBNot arg
407 _ -> generateNormalCall' home name args
411 "and" -> JS.Infix JS.OpBAnd left right
412 "or" -> JS.Infix JS.OpBOr left right
413 "xor" -> JS.Infix JS.OpBXor left right
414 "shiftLeftBy" -> JS.Infix JS.OpLShift right left
415 "shiftRightBy" -> JS.Infix JS.OpSpRShift right left
416 "shiftRightZfBy" -> JS.Infix JS.OpZfRShift right left
417 _ -> generateNormalCall' home name args
420 generateNormalCall' home name args
423generateBasicsCall :: Mode -> ModuleName.Canonical -> N.Name -> [Opt.Expr] -> JS.Expr
424generateBasicsCall mode home name args =
427 let arg = generateJsExpr mode elmArg in
429 "not" -> JS.Prefix JS.PrefixLNot arg
431 "truncate" -> JS.Infix JS.OpBOr arg (JS.Int 0)
432 _ -> generateNormalCall' home name [arg]
434 [elmLeft, elmRight] ->
436 "composeL" -> decomposeL mode elmLeft [elmRight]
437 "composeR" -> decomposeR mode [elmLeft] elmRight
438 "append" -> append mode elmLeft elmRight
439 "apL" -> generateJsExpr mode $ apply elmLeft elmRight
440 "apR" -> generateJsExpr mode $ apply elmRight elmLeft
443 left = generateJsExpr mode elmLeft
444 right = generateJsExpr mode elmRight
447 "add" -> JS.Infix JS.OpAdd left right
448 "sub" -> JS.Infix JS.OpSub left right
449 "mul" -> JS.Infix JS.OpMul left right
450 "fdiv" -> JS.Infix JS.OpDiv left right
451 "idiv" -> JS.Infix JS.OpBOr (JS.Infix JS.OpDiv left right) (JS.Int 0)
452 "eq" -> equal left right
453 "neq" -> notEqual left right
454 "lt" -> cmp JS.OpLT JS.OpLT 0 left right
455 "gt" -> cmp JS.OpGT JS.OpGT 0 left right
456 "le" -> cmp JS.OpLEq JS.OpLT 1 left right
457 "ge" -> cmp JS.OpGEq JS.OpGT (-1) left right
458 "or" -> JS.Infix JS.OpLOr left right
459 "and" -> JS.Infix JS.OpLAnd left right
460 "xor" -> JS.Infix JS.OpStrictNEq left right
461 _ -> generateNormalCall' home name [left, right]
464 generateNormalCall' home name (map (generateJsExpr mode) args)
467equal :: JS.Expr -> JS.Expr -> JS.Expr
469 if isLiteral left || isLiteral right then
472 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
475notEqual :: JS.Expr -> JS.Expr -> JS.Expr
477 if isLiteral left || isLiteral right then
480 JS.Prefix JS.PrefixLNot $
481 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
484cmp :: JS.InfixOp -> JS.InfixOp -> Int -> JS.Expr -> JS.Expr -> JS.Expr
485cmp idealOp backupOp backupInt left right =
486 if isLiteral left || isLiteral right then
487 JS.Infix idealOp left right
490 (JS.Call (JS.Ref (Name.fromKernel N.utils "cmp")) [left, right])
494isLiteral :: JS.Expr -> Bool
513decomposeL :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
514decomposeL mode expr funcs =
516 Opt.Call (Opt.VarGlobal (Opt.Global home "composeL")) [left, func]
517 | home == ModuleName.basics ->
518 decomposeL mode left (func:funcs)
521 generateJsExpr mode $
522 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
525decomposeR :: Mode -> [Opt.Expr] -> Opt.Expr -> JS.Expr
526decomposeR mode funcs expr =
528 Opt.Call (Opt.VarGlobal (Opt.Global home "composeR")) [func, right]
529 | home == ModuleName.basics ->
530 decomposeR mode (func:funcs) right
533 generateJsExpr mode $
534 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
537apply :: Opt.Expr -> Opt.Expr -> Opt.Expr
540 Opt.Accessor field ->
541 Opt.Access value field
544 Opt.Call f (args ++ [value])
547 Opt.Call func [value]
550append :: Mode -> Opt.Expr -> Opt.Expr -> JS.Expr
551append mode left right =
552 let seqs = generateJsExpr mode left : toSeqs mode right in
553 if any isStringLiteral seqs then
554 foldr1 (JS.Infix JS.OpAdd) seqs
559jsAppend :: JS.Expr -> JS.Expr -> JS.Expr
561 JS.Call (JS.Ref (Name.fromKernel N.utils "ap")) [a, b]
564toSeqs :: Mode -> Opt.Expr -> [JS.Expr]
567 Opt.Call (Opt.VarGlobal (Opt.Global home "append")) [left, right]
568 | home == ModuleName.basics ->
569 generateJsExpr mode left : toSeqs mode right
572 [generateJsExpr mode expr]
575isStringLiteral :: JS.Expr -> Bool
576isStringLiteral expr =
586-- SIMPLIFY INFIX OPERATORS
589strictEq :: JS.Expr -> JS.Expr -> JS.Expr
593 JS.Prefix JS.PrefixLNot right
596 if bool then right else JS.Prefix JS.PrefixLNot right
601 JS.Prefix JS.PrefixLNot left
604 if bool then left else JS.Prefix JS.PrefixLNot left
607 JS.Infix JS.OpStrictEq left right
610strictNEq :: JS.Expr -> JS.Expr -> JS.Expr
611strictNEq left right =
617 if bool then JS.Prefix JS.PrefixLNot right else right
625 if bool then JS.Prefix JS.PrefixLNot left else left
628 JS.Infix JS.OpStrictNEq left right
635-- TODO check if JS minifiers collapse unnecessary temporary variables
637generateTailCall :: Mode -> N.Name -> [(N.Name, Opt.Expr)] -> [JS.Stmt]
638generateTailCall mode name args =
640 toTempVars (argName, arg) =
641 ( Name.makeTemp argName, Just (generateJsExpr mode arg) )
643 toRealVars (argName, _) =
645 JS.Assign (JS.LRef (Name.fromLocal argName)) (JS.Ref (Name.makeTemp argName))
647 JS.Var (map toTempVars args)
648 : map toRealVars args
649 ++ [ JS.Continue (Just (Name.fromLocal name)) ]
656generateDef :: Mode -> Opt.Def -> JS.Stmt
657generateDef mode def =
660 JS.Var [ (Name.fromLocal name, Just (generateJsExpr mode body)) ]
662 Opt.TailDef name argNames body ->
665 JS.Function Nothing (map Name.fromLocal argNames)
666 [ JS.Labelled (Name.fromLocal name) $
667 JS.While (JS.Bool True) $
668 codeToStmt $ generate mode body
671 JS.Var [ (Name.fromLocal name, Just function) ]
678generateIf :: Mode -> [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> Code
679generateIf mode givenBranches givenFinal =
682 crushIfs givenBranches givenFinal
684 convertBranch (condition, expr) =
685 ( generateJsExpr mode condition
689 branchExprs = map convertBranch branches
690 finalCode = generate mode final
692 if isBlock finalCode || any (isBlock . snd) branchExprs then
693 JsBlock [ foldr addStmtIf (codeToStmt finalCode) branchExprs ]
695 JsExpr $ foldr addExprIf (codeToExpr finalCode) branchExprs
698addExprIf :: (JS.Expr, Code) -> JS.Expr -> JS.Expr
699addExprIf (condition, branch) final =
700 JS.If condition (codeToExpr branch) final
703addStmtIf :: (JS.Expr, Code) -> JS.Stmt -> JS.Stmt
704addStmtIf (condition, branch) final =
705 JS.IfStmt condition (codeToStmt branch) final
708isBlock :: Code -> Bool
715crushIfs :: [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
716crushIfs branches final =
717 crushIfsHelp [] branches final
721 :: [(Opt.Expr, Opt.Expr)]
722 -> [(Opt.Expr, Opt.Expr)]
724 -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
725crushIfsHelp visitedBranches unvisitedBranches final =
726 case unvisitedBranches of
729 Opt.If subBranches subFinal ->
730 crushIfsHelp visitedBranches subBranches subFinal
733 (reverse visitedBranches, final)
735 visiting : unvisited ->
736 crushIfsHelp (visiting : visitedBranches) unvisited final
743generateCase :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [(Int, Opt.Expr)] -> [JS.Stmt]
744generateCase mode label root decider jumps =
745 foldr (goto mode label) (generateDecider mode label root decider) jumps
748goto :: Mode -> N.Name -> (Int, Opt.Expr) -> [JS.Stmt] -> [JS.Stmt]
749goto mode label (index, branch) stmts =
753 (Name.makeLabel label index)
754 (JS.While (JS.Bool True) (JS.Block stmts))
756 labeledDeciderStmt : codeToStmtList (generate mode branch)
759generateDecider :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [JS.Stmt]
760generateDecider mode label root decisionTree =
762 Opt.Leaf (Opt.Inline branch) ->
763 codeToStmtList (generate mode branch)
765 Opt.Leaf (Opt.Jump index) ->
766 [ JS.Break (Just (Name.makeLabel label index)) ]
768 Opt.Chain testChain success failure ->
770 makeTest (path, test) =
772 (pathToTestableExpr mode root path test)
773 (testToExpr mode test)
776 (List.foldl1' (JS.Infix JS.OpLAnd) (map makeTest testChain))
777 (JS.Block $ generateDecider mode label root success)
778 (JS.Block $ generateDecider mode label root failure)
781 Opt.FanOut path edges fallback ->
783 (pathToTestableExpr mode root path (fst (head edges)))
785 (addCase mode label root)
786 [ JS.Default (generateDecider mode label root fallback) ]
792addCase :: Mode -> N.Name -> N.Name -> (DT.Test, Opt.Decider Opt.Choice) -> [JS.Case] -> [JS.Case]
793addCase mode label root (test, subTree) cases =
794 let stmts = generateDecider mode label root subTree in
795 JS.Case (testToExpr mode test) stmts : cases
798testToExpr :: Mode -> DT.Test -> JS.Expr
799testToExpr mode test =
801 DT.IsCtor _ tag index ->
804 JS.String (N.toBuilder tag)
807 JS.Int (Index.toZeroBased index)
813 JS.String (Text.encodeUtf8Builder char)
816 JS.String (Text.encodeUtf8Builder string)
819pathToTestableExpr :: Mode -> N.Name -> DT.Path -> DT.Test -> JS.Expr
820pathToTestableExpr mode root path exampleTest =
823 pathToJsExpr mode (JS.Ref (Name.fromLocal root)) path
827 JS.Access expr Name.dollar
838 JS.Call (JS.Access expr (Name.fromLocal "valueOf")) []
844pathToJsExpr :: Mode -> JS.Expr -> DT.Path -> JS.Expr
845pathToJsExpr mode expr path =
847 DT.Index index subpath ->
848 pathToJsExpr mode (JS.Access expr (Name.fromInt index)) subpath
850 DT.Field field subpath ->
851 pathToJsExpr mode (JS.Access expr (Name.fromField mode field)) subpath
864generateMain :: Mode -> I.Interfaces -> ModuleName.Canonical -> [N.Name] -> Opt.Main -> JS.Expr
865generateMain mode interfaces home segments main =
868 JS.Ref (Name.fromKernel N.browser "staticPage")
869 # JS.Ref (Name.fromGlobal home "main")
870 # List.foldl' addDot elm segments
872 Opt.Dynamic decoder msgType ->
876 [ ("versions", Encode.object [ ("elm", Pkg.encodeVersion Version.version) ])
877 , ("types", Type.encodeMetadata (Extract.fromMsg interfaces msgType))
880 JS.Ref (Name.fromGlobal home "main")
881 # List.foldl' addDot elm segments
882 # generateJsExpr mode decoder
883 # JS.Json jsonMetadata
886(#) :: JS.Expr -> JS.Expr -> JS.Expr
891addDot :: JS.Expr -> N.Name -> JS.Expr
893 JS.Index root (JS.String (N.toBuilder name))
899 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