1{-# LANGUAGE OverloadedStrings #-}
2module Generate.JavaScript.Expression
13import qualified Data.IntMap as IntMap
14import qualified Data.List as List
15import qualified Data.Map as Map
16import qualified Data.Text.Encoding as Text
18import qualified AST.Optimized as Opt
19import qualified AST.Module.Name as ModuleName
20import qualified Data.Index as Index
21import qualified Elm.Name as N
22import qualified Elm.Package as Pkg
23import qualified Generate.JavaScript.Builder as JS
24import qualified Generate.JavaScript.Name as Name
25import qualified Optimize.DecisionTree as DT
26import qualified Reporting.Region as R
40generateJsExpr :: Mode -> Opt.Expr -> JS.Expr
41generateJsExpr mode expression =
42 codeToExpr (generate mode expression)
45generate :: Mode -> Opt.Expr -> Code
46generate mode expression =
52 JS.Call toChar [ JS.String (Text.encodeUtf8Builder char) ]
55 JS.String (Text.encodeUtf8Builder char)
58 JsExpr $ JS.String (Text.encodeUtf8Builder string)
64 JsExpr $ JS.Float float
67 JsExpr $ JS.Ref (Name.fromLocal name)
69 Opt.VarGlobal (Opt.Global home name) ->
70 JsExpr $ JS.Ref (Name.fromGlobal home name)
72 Opt.VarCycle home name ->
73 JsExpr $ JS.Call (JS.Ref (Name.fromCycle home name)) []
75 Opt.VarDebug name home region unhandledValueName ->
76 JsExpr $ generateDebug name home region unhandledValueName
78 Opt.VarKernel home name ->
79 JsExpr $ JS.Ref (Name.fromKernel home name)
84 (JS.Ref (Name.fromKernel N.list "fromArray"))
85 [ JS.Array $ map (generateJsExpr mode) entries
88 Opt.Function args body ->
89 generateFunction (map Name.fromLocal args) (generate mode body)
92 JsExpr $ generateCall mode func args
94 Opt.TailCall name args ->
95 JsBlock $ generateTailCall mode name args
97 Opt.If branches final ->
98 generateIf mode branches final
102 generateDef mode def : codeToStmtList (generate mode body)
104 Opt.Case label root decider jumps ->
105 JsBlock $ generateCase mode label root decider jumps
107 Opt.Index expr index ->
108 JsExpr $ JS.Access (generateJsExpr mode expr) (Name.fromInt index)
110 Opt.Accessor field ->
111 JsExpr $ JS.Function Nothing [Name.dollar]
113 JS.Access (JS.Ref Name.dollar) (Name.fromField mode field)
116 Opt.Access record field ->
117 JsExpr $ JS.Access (generateJsExpr mode record) (Name.fromField mode field)
119 Opt.Update record fields ->
121 JS.Call (JS.Ref (Name.fromKernel N.utils "update"))
122 [ generateJsExpr mode record
123 , generateRecord mode fields
127 JsExpr $ generateRecord mode fields
130 JsExpr $ JS.Ref (Name.fromKernel N.utils "Tuple0")
132 Opt.Tuple a b maybeC ->
136 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple2"))
137 [ generateJsExpr mode a
138 , generateJsExpr mode b
142 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple3"))
143 [ generateJsExpr mode a
144 , generateJsExpr mode b
145 , generateJsExpr mode c
149 let string = JS.String (Text.encodeUtf8Builder src) in
150 JsExpr $ JS.Object [ ( Name.fromLocal "src", string ) ]
162codeToExpr :: Code -> JS.Expr
169 JS.Call (JS.Function Nothing [] stmts) []
172codeToStmtList :: Code -> [JS.Stmt]
176 [ JS.Return (Just expr) ]
182codeToStmt :: Code -> JS.Stmt
186 JS.Return (Just expr)
199{-# NOINLINE toChar #-}
202 JS.Ref (Name.fromKernel N.utils "chr")
209generateCtor :: Mode -> N.Name -> Index.ZeroBased -> Int -> ([Name.Name], Code)
210generateCtor mode name index arity =
212 argNames = map Name.fromInt [1 .. arity]
213 argFields = map (\n -> (n, JS.Ref n)) argNames
218 ( Name.dollar, JS.String (N.toBuilder name) )
221 ( Name.dollar, JS.Int (Index.toZeroBased index) )
223 ( argNames, JsExpr (JS.Object (tag:argFields)) )
230generateRecord :: Mode -> Map.Map N.Name Opt.Expr -> JS.Expr
231generateRecord mode fields =
233 toPair (field, value) =
234 (Name.fromField mode field, generateJsExpr mode value)
236 JS.Object (map toPair (Map.toList fields))
243generateDebug :: N.Name -> ModuleName.Canonical -> R.Region -> Maybe N.Name -> JS.Expr
244generateDebug name (ModuleName.Canonical _ home) region unhandledValueName =
245 if name /= "crash" then
246 JS.Ref (Name.fromGlobal ModuleName.debug name)
248 case unhandledValueName of
250 JS.Call (JS.Ref (Name.fromKernel N.utils "crash")) $
251 [ JS.String (N.toBuilder home)
252 , regionToJsExpr region
256 JS.Call (JS.Ref (Name.fromKernel N.utils "crashCase")) $
257 [ JS.String (N.toBuilder home)
258 , regionToJsExpr region
259 , JS.Ref (Name.fromLocal valueName)
263regionToJsExpr :: R.Region -> JS.Expr
264regionToJsExpr (R.Region start end) =
266 [ ( Name.fromLocal "start", positionToJsExpr start )
267 , ( Name.fromLocal "end", positionToJsExpr end )
271positionToJsExpr :: R.Position -> JS.Expr
272positionToJsExpr (R.Position line column) =
274 [ ( Name.fromLocal "line", JS.Int line )
275 , ( Name.fromLocal "column", JS.Int column )
283generateFunction :: [Name.Name] -> Code -> Code
284generateFunction args body =
285 case IntMap.lookup (length args) funcHelpers of
289 [ JS.Function Nothing args $
296 JsExpr $ JS.Function Nothing [arg] $
299 foldr addArg body args
302{-# NOINLINE funcHelpers #-}
303funcHelpers :: IntMap.IntMap JS.Expr
306 [ (2, JS.Ref (Name.fromLocal "F2"))
307 , (3, JS.Ref (Name.fromLocal "F3"))
308 , (4, JS.Ref (Name.fromLocal "F4"))
309 , (5, JS.Ref (Name.fromLocal "F5"))
310 , (6, JS.Ref (Name.fromLocal "F6"))
311 , (7, JS.Ref (Name.fromLocal "F7"))
312 , (8, JS.Ref (Name.fromLocal "F8"))
313 , (9, JS.Ref (Name.fromLocal "F9"))
321generateCall :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
322generateCall mode func args =
324 Opt.VarGlobal global@(Opt.Global (ModuleName.Canonical pkg _) _) | pkg == Pkg.core ->
325 generateCoreCall mode global args
329 (generateJsExpr mode func)
330 (map (generateJsExpr mode) args)
333generateNormalCall' :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
334generateNormalCall' home name args =
335 generateNormalCall (JS.Ref (Name.fromGlobal home name)) args
338generateNormalCall :: JS.Expr -> [JS.Expr] -> JS.Expr
339generateNormalCall func args =
340 case IntMap.lookup (length args) callHelpers of
342 JS.Call helper (func:args)
345 List.foldl' (\f a -> JS.Call f [a]) func args
348{-# NOINLINE callHelpers #-}
349callHelpers :: IntMap.IntMap JS.Expr
352 [ (2, JS.Ref (Name.fromLocal "A2"))
353 , (3, JS.Ref (Name.fromLocal "A3"))
354 , (4, JS.Ref (Name.fromLocal "A4"))
355 , (5, JS.Ref (Name.fromLocal "A5"))
356 , (6, JS.Ref (Name.fromLocal "A6"))
357 , (7, JS.Ref (Name.fromLocal "A7"))
358 , (8, JS.Ref (Name.fromLocal "A8"))
359 , (9, JS.Ref (Name.fromLocal "A9"))
367generateCoreCall :: Mode -> Opt.Global -> [Opt.Expr] -> JS.Expr
368generateCoreCall mode (Opt.Global home@(ModuleName.Canonical _ moduleName) name) args =
369 if moduleName == N.basics then
370 generateBasicsCall mode home name args
372 else if moduleName == N.bitwise then
373 generateBitwiseCall home name (map (generateJsExpr mode) args)
375 else if moduleName == N.tuple then
376 generateTupleCall home name (map (generateJsExpr mode) args)
379 generateNormalCall' home name (map (generateJsExpr mode) args)
382generateTupleCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
383generateTupleCall home name args =
387 "first" -> JS.Access value (Name.fromLocal "a")
388 "second" -> JS.Access value (Name.fromLocal "b")
389 _ -> generateNormalCall' home name args
392 generateNormalCall' home name args
395generateBitwiseCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
396generateBitwiseCall home name args =
400 "complement" -> JS.Prefix JS.PrefixBNot arg
401 _ -> generateNormalCall' home name args
405 "and" -> JS.Infix JS.OpBAnd left right
406 "or" -> JS.Infix JS.OpBOr left right
407 "xor" -> JS.Infix JS.OpBXor left right
408 "shiftLeftBy" -> JS.Infix JS.OpLShift right left
409 "shiftRightBy" -> JS.Infix JS.OpSpRShift right left
410 "shiftRightZfBy" -> JS.Infix JS.OpZfRShift right left
411 _ -> generateNormalCall' home name args
414 generateNormalCall' home name args
417generateBasicsCall :: Mode -> ModuleName.Canonical -> N.Name -> [Opt.Expr] -> JS.Expr
418generateBasicsCall mode home name args =
421 let arg = generateJsExpr mode elmArg in
423 "not" -> JS.Prefix JS.PrefixLNot arg
425 "truncate" -> JS.Infix JS.OpBOr arg (JS.Int 0)
426 _ -> generateNormalCall' home name [arg]
428 [elmLeft, elmRight] ->
430 "composeL" -> decomposeL mode elmLeft [elmRight]
431 "composeR" -> decomposeR mode [elmLeft] elmRight
432 "append" -> append mode elmLeft elmRight
433 "apL" -> generateJsExpr mode $ apply elmLeft elmRight
434 "apR" -> generateJsExpr mode $ apply elmRight elmLeft
437 left = generateJsExpr mode elmLeft
438 right = generateJsExpr mode elmRight
441 "add" -> JS.Infix JS.OpAdd left right
442 "sub" -> JS.Infix JS.OpSub left right
443 "mul" -> JS.Infix JS.OpMul left right
444 "fdiv" -> JS.Infix JS.OpDiv left right
445 "idiv" -> JS.Infix JS.OpBOr (JS.Infix JS.OpDiv left right) (JS.Int 0)
446 "eq" -> equal left right
447 "neq" -> notEqual left right
448 "lt" -> cmp JS.OpLT JS.OpLT 0 left right
449 "gt" -> cmp JS.OpGT JS.OpGT 0 left right
450 "le" -> cmp JS.OpLEq JS.OpLT 1 left right
451 "ge" -> cmp JS.OpGEq JS.OpGT (-1) left right
452 "or" -> JS.Infix JS.OpLOr left right
453 "and" -> JS.Infix JS.OpLAnd left right
454 "xor" -> JS.Infix JS.OpStrictNEq left right
455 _ -> generateNormalCall' home name [left, right]
458 generateNormalCall' home name (map (generateJsExpr mode) args)
461equal :: JS.Expr -> JS.Expr -> JS.Expr
463 if isLiteral left || isLiteral right then
466 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
469notEqual :: JS.Expr -> JS.Expr -> JS.Expr
471 if isLiteral left || isLiteral right then
474 JS.Prefix JS.PrefixLNot $
475 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
478cmp :: JS.InfixOp -> JS.InfixOp -> Int -> JS.Expr -> JS.Expr -> JS.Expr
479cmp idealOp backupOp backupInt left right =
480 if isLiteral left || isLiteral right then
481 JS.Infix idealOp left right
484 (JS.Call (JS.Ref (Name.fromKernel N.utils "cmp")) [left, right])
488isLiteral :: JS.Expr -> Bool
507decomposeL :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
508decomposeL mode expr funcs =
510 Opt.Call (Opt.VarGlobal (Opt.Global home "composeL")) [left, func]
511 | home == ModuleName.basics ->
512 decomposeL mode left (func:funcs)
515 generateJsExpr mode $
516 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
519decomposeR :: Mode -> [Opt.Expr] -> Opt.Expr -> JS.Expr
520decomposeR mode funcs expr =
522 Opt.Call (Opt.VarGlobal (Opt.Global home "composeR")) [func, right]
523 | home == ModuleName.basics ->
524 decomposeR mode (func:funcs) right
527 generateJsExpr mode $
528 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
531apply :: Opt.Expr -> Opt.Expr -> Opt.Expr
534 Opt.Accessor field ->
535 Opt.Access value field
538 Opt.Call f (args ++ [value])
541 Opt.Call func [value]
544append :: Mode -> Opt.Expr -> Opt.Expr -> JS.Expr
545append mode left right =
546 let seqs = generateJsExpr mode left : toSeqs mode right in
547 if any isStringLiteral seqs then
548 foldr1 (JS.Infix JS.OpAdd) seqs
553jsAppend :: JS.Expr -> JS.Expr -> JS.Expr
555 JS.Call (JS.Ref (Name.fromKernel N.utils "ap")) [a, b]
558toSeqs :: Mode -> Opt.Expr -> [JS.Expr]
561 Opt.Call (Opt.VarGlobal (Opt.Global home "append")) [left, right]
562 | home == ModuleName.basics ->
563 generateJsExpr mode left : toSeqs mode right
566 [generateJsExpr mode expr]
569isStringLiteral :: JS.Expr -> Bool
570isStringLiteral expr =
580-- SIMPLIFY INFIX OPERATORS
583strictEq :: JS.Expr -> JS.Expr -> JS.Expr
587 JS.Prefix JS.PrefixLNot right
590 if bool then right else JS.Prefix JS.PrefixLNot right
595 JS.Prefix JS.PrefixLNot left
598 if bool then left else JS.Prefix JS.PrefixLNot left
601 JS.Infix JS.OpStrictEq left right
604strictNEq :: JS.Expr -> JS.Expr -> JS.Expr
605strictNEq left right =
611 if bool then JS.Prefix JS.PrefixLNot right else right
619 if bool then JS.Prefix JS.PrefixLNot left else left
622 JS.Infix JS.OpStrictNEq left right
629-- TODO check if JS minifiers collapse unnecessary temporary variables
631generateTailCall :: Mode -> N.Name -> [(N.Name, Opt.Expr)] -> [JS.Stmt]
632generateTailCall mode name args =
634 toTempVars (argName, arg) =
635 ( Name.makeTemp argName, Just (generateJsExpr mode arg) )
637 toRealVars (argName, _) =
639 JS.Assign (JS.LRef (Name.fromLocal argName)) (JS.Ref (Name.makeTemp argName))
641 JS.Var (map toTempVars args)
642 : map toRealVars args
643 ++ [ JS.Continue (Just (Name.fromLocal name)) ]
650generateDef :: Mode -> Opt.Def -> JS.Stmt
651generateDef mode def =
654 JS.Var [ (Name.fromLocal name, Just (generateJsExpr mode body)) ]
656 Opt.TailDef name argNames body ->
659 JS.Function Nothing (map Name.fromLocal argNames)
660 [ JS.Labelled (Name.fromLocal name) $
661 JS.While (JS.Bool True) $
662 codeToStmt $ generate mode body
665 JS.Var [ (Name.fromLocal name, Just function) ]
672generateIf :: Mode -> [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> Code
673generateIf mode givenBranches givenFinal =
676 crushIfs givenBranches givenFinal
678 convertBranch (condition, expr) =
679 ( generateJsExpr mode condition
683 branchExprs = map convertBranch branches
684 finalCode = generate mode final
686 if isBlock finalCode || any (isBlock . snd) branchExprs then
687 JsBlock [ foldr addStmtIf (codeToStmt finalCode) branchExprs ]
689 JsExpr $ foldr addExprIf (codeToExpr finalCode) branchExprs
692addExprIf :: (JS.Expr, Code) -> JS.Expr -> JS.Expr
693addExprIf (condition, branch) final =
694 JS.If condition (codeToExpr branch) final
697addStmtIf :: (JS.Expr, Code) -> JS.Stmt -> JS.Stmt
698addStmtIf (condition, branch) final =
699 JS.IfStmt condition (codeToStmt branch) final
702isBlock :: Code -> Bool
709crushIfs :: [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
710crushIfs branches final =
711 crushIfsHelp [] branches final
715 :: [(Opt.Expr, Opt.Expr)]
716 -> [(Opt.Expr, Opt.Expr)]
718 -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
719crushIfsHelp visitedBranches unvisitedBranches final =
720 case unvisitedBranches of
723 Opt.If subBranches subFinal ->
724 crushIfsHelp visitedBranches subBranches subFinal
727 (reverse visitedBranches, final)
729 visiting : unvisited ->
730 crushIfsHelp (visiting : visitedBranches) unvisited final
737generateCase :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [(Int, Opt.Expr)] -> [JS.Stmt]
738generateCase mode label root decider jumps =
739 foldr (goto mode label) (generateDecider mode label root decider) jumps
742goto :: Mode -> N.Name -> (Int, Opt.Expr) -> [JS.Stmt] -> [JS.Stmt]
743goto mode label (index, branch) stmts =
747 (Name.makeLabel label index)
748 (JS.While (JS.Bool True) (JS.Block stmts))
750 labeledDeciderStmt : codeToStmtList (generate mode branch)
753generateDecider :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [JS.Stmt]
754generateDecider mode label root decisionTree =
756 Opt.Leaf (Opt.Inline branch) ->
757 codeToStmtList (generate mode branch)
759 Opt.Leaf (Opt.Jump index) ->
760 [ JS.Break (Just (Name.makeLabel label index)) ]
762 Opt.Chain testChain success failure ->
764 makeTest (path, test) =
766 (pathToTestableExpr mode root path test)
767 (testToExpr mode test)
770 (List.foldl1' (JS.Infix JS.OpLAnd) (map makeTest testChain))
771 (JS.Block $ generateDecider mode label root success)
772 (JS.Block $ generateDecider mode label root failure)
775 Opt.FanOut path edges fallback ->
777 (pathToTestableExpr mode root path (fst (head edges)))
779 (addCase mode label root)
780 [ JS.Default (generateDecider mode label root fallback) ]
786addCase :: Mode -> N.Name -> N.Name -> (DT.Test, Opt.Decider Opt.Choice) -> [JS.Case] -> [JS.Case]
787addCase mode label root (test, subTree) cases =
788 let stmts = generateDecider mode label root subTree in
789 JS.Case (testToExpr mode test) stmts : cases
792testToExpr :: Mode -> DT.Test -> JS.Expr
793testToExpr mode test =
795 DT.IsCtor _ tag index ->
798 JS.String (N.toBuilder tag)
801 JS.Int (Index.toZeroBased index)
807 JS.String (Text.encodeUtf8Builder char)
810 JS.String (Text.encodeUtf8Builder string)
813pathToTestableExpr :: Mode -> N.Name -> DT.Path -> DT.Test -> JS.Expr
814pathToTestableExpr mode root path exampleTest =
817 pathToJsExpr mode (JS.Ref (Name.fromLocal root)) path
821 JS.Access expr Name.dollar
832 JS.Call (JS.Access expr (Name.fromLocal "valueOf")) []
838pathToJsExpr :: Mode -> JS.Expr -> DT.Path -> JS.Expr
839pathToJsExpr mode expr path =
841 DT.Index index subpath ->
842 pathToJsExpr mode (JS.Access expr (Name.fromInt index)) subpath
844 DT.Field field subpath ->
845 pathToJsExpr mode (JS.Access expr (Name.fromField mode field)) subpath
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