1{-# LANGUAGE OverloadedStrings #-}
2module Generate.JavaScript.Expression
8import qualified Data.IntMap as IntMap
9import qualified Data.List as List
10import qualified Data.Map as Map
11import qualified Data.Text.Encoding as Text
13import qualified AST.Optimized as Opt
14import qualified AST.Module.Name as ModuleName
15import qualified Data.Index as Index
16import qualified Elm.Name as N
17import qualified Elm.Package as Pkg
18import qualified Generate.JavaScript.Builder as JS
19import qualified Generate.JavaScript.Name as Name
20import qualified Optimize.DecisionTree as DT
21import qualified Reporting.Region as R
35generateJsExpr :: Mode -> Opt.Expr -> JS.Expr
36generateJsExpr mode expression =
37 codeToJsExpr (generateCode mode expression)
40generateCode :: Mode -> Opt.Expr -> Code
41generateCode mode expression =
47 JS.Call toChar [ JS.String (Text.encodeUtf8Builder char) ]
50 JS.String (Text.encodeUtf8Builder char)
53 JsExpr $ JS.String (Text.encodeUtf8Builder string)
59 JsExpr $ JS.Float float
62 JsExpr $ JS.Ref (Name.fromLocal name)
64 Opt.VarGlobal (Opt.Global home name) ->
65 JsExpr $ JS.Ref (Name.fromGlobal home name)
67 Opt.VarCycle home name ->
68 JsExpr $ JS.Ref (Name.fromCycle home name)
70 Opt.VarDebug name home region unhandledValueName ->
71 JsExpr $ generateDebug name home region unhandledValueName
73 Opt.VarKernel home name ->
74 JsExpr $ JS.Ref (Name.fromKernel home name)
79 (JS.Ref (Name.fromKernel N.list "fromArray"))
80 [ JS.Array $ map (generateJsExpr mode) entries
83 Opt.Function args body ->
84 generateFunction args (generateCode mode body)
87 JsExpr $ generateCall mode func args
89 Opt.TailCall name args ->
90 JsBlock $ generateTailCall mode name args
92 Opt.If branches final ->
93 generateIf mode branches final
97 generateDef mode def : codeToStatementList (generateCode mode body)
99 Opt.Case label root decider jumps ->
100 JsBlock $ generateCase mode label root decider jumps
102 Opt.Index expr index ->
103 JsExpr $ JS.Access (generateJsExpr mode expr) (Name.fromInt index)
105 Opt.Accessor field ->
106 JsExpr $ JS.Function Nothing [Name.dollar]
108 JS.Access (JS.Ref Name.dollar) (Name.fromField mode field)
111 Opt.Access record field ->
112 JsExpr $ JS.Access (generateJsExpr mode record) (Name.fromField mode field)
114 Opt.Update record fields ->
116 JS.Call (JS.Ref (Name.fromKernel N.utils "update"))
117 [ generateJsExpr mode record
118 , generateRecord mode fields
122 JsExpr $ generateRecord mode fields
125 JsExpr $ JS.Ref (Name.fromKernel N.utils "Tuple0")
127 Opt.Tuple a b maybeC ->
131 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple2"))
132 [ generateJsExpr mode a
133 , generateJsExpr mode b
137 JS.Call (JS.Ref (Name.fromKernel N.utils "Tuple3"))
138 [ generateJsExpr mode a
139 , generateJsExpr mode b
140 , generateJsExpr mode c
144 let string = JS.String (Text.encodeUtf8Builder src) in
145 JsExpr $ JS.Object [ ( Name.fromLocal "src", string ) ]
157codeToJsExpr :: Code -> JS.Expr
164 JS.Call (JS.Function Nothing [] stmts) []
167codeToStatementList :: Code -> [JS.Stmt]
168codeToStatementList code =
171 [ JS.Return (Just expr) ]
177codeToStatement :: Code -> JS.Stmt
178codeToStatement code =
181 JS.Return (Just expr)
194{-# NOINLINE toChar #-}
197 JS.Ref (Name.fromKernel N.utils "chr")
204generateRecord :: Mode -> Map.Map N.Name Opt.Expr -> JS.Expr
205generateRecord mode fields =
207 toPair (field, value) =
208 (Name.fromField mode field, generateJsExpr mode value)
210 JS.Object (map toPair (Map.toList fields))
217generateDebug :: N.Name -> ModuleName.Canonical -> R.Region -> Maybe N.Name -> JS.Expr
218generateDebug name (ModuleName.Canonical _ home) region unhandledValueName =
219 if name /= "crash" then
220 JS.Ref (Name.fromGlobal ModuleName.debug name)
222 case unhandledValueName of
224 JS.Call (JS.Ref (Name.fromKernel N.utils "crash")) $
225 [ JS.String (N.toBuilder home)
226 , regionToJsExpr region
230 JS.Call (JS.Ref (Name.fromKernel N.utils "crashCase")) $
231 [ JS.String (N.toBuilder home)
232 , regionToJsExpr region
233 , JS.Ref (Name.fromLocal valueName)
237regionToJsExpr :: R.Region -> JS.Expr
238regionToJsExpr (R.Region start end) =
240 [ ( Name.fromLocal "start", positionToJsExpr start )
241 , ( Name.fromLocal "end", positionToJsExpr end )
245positionToJsExpr :: R.Position -> JS.Expr
246positionToJsExpr (R.Position line column) =
248 [ ( Name.fromLocal "line", JS.Int line )
249 , ( Name.fromLocal "column", JS.Int column )
257generateFunction :: [N.Name] -> Code -> Code
258generateFunction args body =
259 case IntMap.lookup (length args) funcHelpers of
263 [ JS.Function Nothing (map Name.fromLocal args) $
264 codeToStatementList body
270 JsExpr $ JS.Function Nothing [Name.fromLocal arg] $
271 codeToStatementList code
273 foldr addArg body args
276{-# NOINLINE funcHelpers #-}
277funcHelpers :: IntMap.IntMap JS.Expr
280 [ (2, JS.Ref (Name.fromLocal "F2"))
281 , (3, JS.Ref (Name.fromLocal "F3"))
282 , (4, JS.Ref (Name.fromLocal "F4"))
283 , (5, JS.Ref (Name.fromLocal "F5"))
284 , (6, JS.Ref (Name.fromLocal "F6"))
285 , (7, JS.Ref (Name.fromLocal "F7"))
286 , (8, JS.Ref (Name.fromLocal "F8"))
287 , (9, JS.Ref (Name.fromLocal "F9"))
295generateCall :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
296generateCall mode func args =
298 Opt.VarGlobal global@(Opt.Global (ModuleName.Canonical pkg _) _) | pkg == Pkg.core ->
299 generateCoreCall mode global args
303 (generateJsExpr mode func)
304 (map (generateJsExpr mode) args)
307generateNormalCall' :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
308generateNormalCall' home name args =
309 generateNormalCall (JS.Ref (Name.fromGlobal home name)) args
312generateNormalCall :: JS.Expr -> [JS.Expr] -> JS.Expr
313generateNormalCall func args =
314 case IntMap.lookup (length args) callHelpers of
316 JS.Call helper (func:args)
319 List.foldl' (\f a -> JS.Call f [a]) func args
322{-# NOINLINE callHelpers #-}
323callHelpers :: IntMap.IntMap JS.Expr
326 [ (2, JS.Ref (Name.fromLocal "A2"))
327 , (3, JS.Ref (Name.fromLocal "A3"))
328 , (4, JS.Ref (Name.fromLocal "A4"))
329 , (5, JS.Ref (Name.fromLocal "A5"))
330 , (6, JS.Ref (Name.fromLocal "A6"))
331 , (7, JS.Ref (Name.fromLocal "A7"))
332 , (8, JS.Ref (Name.fromLocal "A8"))
333 , (9, JS.Ref (Name.fromLocal "A9"))
341generateCoreCall :: Mode -> Opt.Global -> [Opt.Expr] -> JS.Expr
342generateCoreCall mode (Opt.Global home@(ModuleName.Canonical _ moduleName) name) args =
343 if moduleName == N.basics then
344 generateBasicsCall mode home name args
346 else if moduleName == N.bitwise then
347 generateBitwiseCall home name (map (generateJsExpr mode) args)
349 else if moduleName == N.tuple then
350 generateTupleCall home name (map (generateJsExpr mode) args)
353 generateNormalCall' home name (map (generateJsExpr mode) args)
356generateTupleCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
357generateTupleCall home name args =
361 "first" -> JS.Access value (Name.fromLocal "a")
362 "second" -> JS.Access value (Name.fromLocal "b")
363 _ -> generateNormalCall' home name args
366 generateNormalCall' home name args
369generateBitwiseCall :: ModuleName.Canonical -> N.Name -> [JS.Expr] -> JS.Expr
370generateBitwiseCall home name args =
374 "complement" -> JS.Prefix JS.PrefixBNot arg
375 _ -> generateNormalCall' home name args
379 "and" -> JS.Infix JS.OpBAnd left right
380 "or" -> JS.Infix JS.OpBOr left right
381 "xor" -> JS.Infix JS.OpBXor left right
382 "shiftLeftBy" -> JS.Infix JS.OpLShift right left
383 "shiftRightBy" -> JS.Infix JS.OpSpRShift right left
384 "shiftRightZfBy" -> JS.Infix JS.OpZfRShift right left
385 _ -> generateNormalCall' home name args
388 generateNormalCall' home name args
391generateBasicsCall :: Mode -> ModuleName.Canonical -> N.Name -> [Opt.Expr] -> JS.Expr
392generateBasicsCall mode home name args =
395 let arg = generateJsExpr mode elmArg in
397 "not" -> JS.Prefix JS.PrefixLNot arg
399 "truncate" -> JS.Infix JS.OpBOr arg (JS.Int 0)
400 _ -> generateNormalCall' home name [arg]
402 [elmLeft, elmRight] ->
404 "composeL" -> decomposeL mode elmLeft [elmRight]
405 "composeR" -> decomposeR mode [elmLeft] elmRight
406 "apL" -> generateJsExpr mode $ apply elmLeft elmRight
407 "apR" -> generateJsExpr mode $ apply elmRight elmLeft
410 left = generateJsExpr mode elmLeft
411 right = generateJsExpr mode elmRight
414 "add" -> JS.Infix JS.OpAdd left right
415 "sub" -> JS.Infix JS.OpSub left right
416 "mul" -> JS.Infix JS.OpMul left right
417 "fdiv" -> JS.Infix JS.OpDiv left right
418 "idiv" -> JS.Infix JS.OpBOr (JS.Infix JS.OpDiv left right) (JS.Int 0)
419 "eq" -> equal left right
420 "neq" -> notEqual left right
421 "lt" -> cmp JS.OpLT JS.OpLT 0 left right
422 "gt" -> cmp JS.OpGT JS.OpGT 0 left right
423 "le" -> cmp JS.OpLEq JS.OpLT 1 left right
424 "ge" -> cmp JS.OpGEq JS.OpGT (-1) left right
425 "or" -> JS.Infix JS.OpLOr left right
426 "and" -> JS.Infix JS.OpLAnd left right
427 "xor" -> JS.Infix JS.OpStrictNEq left right
428 _ -> generateNormalCall' home name [left, right]
431 generateNormalCall' home name (map (generateJsExpr mode) args)
434equal :: JS.Expr -> JS.Expr -> JS.Expr
436 if isLiteral left || isLiteral right then
439 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
442notEqual :: JS.Expr -> JS.Expr -> JS.Expr
444 if isLiteral left || isLiteral right then
447 JS.Prefix JS.PrefixLNot $
448 JS.Call (JS.Ref (Name.fromKernel N.utils "eq")) [left, right]
451cmp :: JS.InfixOp -> JS.InfixOp -> Int -> JS.Expr -> JS.Expr -> JS.Expr
452cmp idealOp backupOp backupInt left right =
453 if isLiteral left || isLiteral right then
454 JS.Infix idealOp left right
457 (JS.Call (JS.Ref (Name.fromKernel N.utils "cmp")) [left, right])
461isLiteral :: JS.Expr -> Bool
480decomposeL :: Mode -> Opt.Expr -> [Opt.Expr] -> JS.Expr
481decomposeL mode expr funcs =
483 Opt.Call (Opt.VarGlobal (Opt.Global home "composeL")) [left, func]
484 | home == ModuleName.basics ->
485 decomposeL mode left (func:funcs)
488 generateJsExpr mode $
489 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
492decomposeR :: Mode -> [Opt.Expr] -> Opt.Expr -> JS.Expr
493decomposeR mode funcs expr =
495 Opt.Call (Opt.VarGlobal (Opt.Global home "composeR")) [func, right]
496 | home == ModuleName.basics ->
497 decomposeR mode (func:funcs) right
500 generateJsExpr mode $
501 Opt.Function [N.dollar] (foldr apply (Opt.VarLocal N.dollar) (expr:funcs))
504apply :: Opt.Expr -> Opt.Expr -> Opt.Expr
507 Opt.Accessor field ->
508 Opt.Access value field
511 Opt.Call f (args ++ [value])
514 Opt.Call func [value]
518-- SIMPLIFY INFIX OPERATORS
521strictEq :: JS.Expr -> JS.Expr -> JS.Expr
525 JS.Prefix JS.PrefixLNot right
528 if bool then right else JS.Prefix JS.PrefixLNot right
533 JS.Prefix JS.PrefixLNot left
536 if bool then left else JS.Prefix JS.PrefixLNot left
539 JS.Infix JS.OpStrictEq left right
542strictNEq :: JS.Expr -> JS.Expr -> JS.Expr
543strictNEq left right =
549 if bool then JS.Prefix JS.PrefixLNot right else right
557 if bool then JS.Prefix JS.PrefixLNot left else left
560 JS.Infix JS.OpStrictNEq left right
567-- TODO check if JS minifiers collapse unnecessary temporary variables
569generateTailCall :: Mode -> N.Name -> [(N.Name, Opt.Expr)] -> [JS.Stmt]
570generateTailCall mode name args =
572 toTempVars (argName, arg) =
573 ( Name.makeTemp argName, Just (generateJsExpr mode arg) )
575 toRealVars (argName, _) =
577 JS.Assign (JS.LVar (Name.fromLocal argName)) (JS.Ref (Name.makeTemp argName))
579 JS.Var (map toTempVars args)
580 : map toRealVars args
581 ++ [ JS.Continue (Just (Name.fromLocal name)) ]
588generateDef :: Mode -> Opt.Def -> JS.Stmt
589generateDef mode def =
592 JS.Var [ (Name.fromLocal name, Just (generateJsExpr mode body)) ]
594 Opt.TailDef name argNames body ->
597 JS.Function Nothing (map Name.fromLocal argNames)
598 [ JS.Labelled (Name.fromLocal name) $
599 JS.While (JS.Bool True) $
600 codeToStatement $ generateCode mode body
603 JS.Var [ (Name.fromLocal name, Just function) ]
610generateIf :: Mode -> [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> Code
611generateIf mode givenBranches givenFinal =
614 crushIfs givenBranches givenFinal
616 convertBranch (condition, expr) =
617 ( generateJsExpr mode condition
618 , generateCode mode expr
621 branchExprs = map convertBranch branches
622 finalCode = generateCode mode final
624 if isBlock finalCode || any (isBlock . snd) branchExprs then
625 JsBlock [ foldr addStmtIf (codeToStatement finalCode) branchExprs ]
627 JsExpr $ foldr addExprIf (codeToJsExpr finalCode) branchExprs
630addExprIf :: (JS.Expr, Code) -> JS.Expr -> JS.Expr
631addExprIf (condition, branch) final =
632 JS.If condition (codeToJsExpr branch) final
635addStmtIf :: (JS.Expr, Code) -> JS.Stmt -> JS.Stmt
636addStmtIf (condition, branch) final =
637 JS.IfStmt condition (codeToStatement branch) final
640isBlock :: Code -> Bool
647crushIfs :: [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
648crushIfs branches final =
649 crushIfsHelp [] branches final
653 :: [(Opt.Expr, Opt.Expr)]
654 -> [(Opt.Expr, Opt.Expr)]
656 -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
657crushIfsHelp visitedBranches unvisitedBranches final =
658 case unvisitedBranches of
661 Opt.If subBranches subFinal ->
662 crushIfsHelp visitedBranches subBranches subFinal
665 (reverse visitedBranches, final)
667 visiting : unvisited ->
668 crushIfsHelp (visiting : visitedBranches) unvisited final
675generateCase :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [(Int, Opt.Expr)] -> [JS.Stmt]
676generateCase mode label root decider jumps =
677 foldr (goto mode label) (generateDecider mode label root decider) jumps
680goto :: Mode -> N.Name -> (Int, Opt.Expr) -> [JS.Stmt] -> [JS.Stmt]
681goto mode label (index, branch) stmts =
685 (Name.makeLabel label index)
686 (JS.While (JS.Bool True) (JS.Block stmts))
688 labeledDeciderStmt : codeToStatementList (generateCode mode branch)
691generateDecider :: Mode -> N.Name -> N.Name -> Opt.Decider Opt.Choice -> [JS.Stmt]
692generateDecider mode label root decisionTree =
694 Opt.Leaf (Opt.Inline branch) ->
695 codeToStatementList (generateCode mode branch)
697 Opt.Leaf (Opt.Jump index) ->
698 [ JS.Break (Just (Name.makeLabel label index)) ]
700 Opt.Chain testChain success failure ->
702 makeTest (path, test) =
704 (pathToTestableExpr mode root path test)
705 (testToExpr mode test)
708 (List.foldl1' (JS.Infix JS.OpLAnd) (map makeTest testChain))
709 (JS.Block $ generateDecider mode label root success)
710 (JS.Block $ generateDecider mode label root failure)
713 Opt.FanOut path edges fallback ->
715 (pathToTestableExpr mode root path (fst (head edges)))
717 (addCase mode label root)
718 [ JS.Default (generateDecider mode label root fallback) ]
724addCase :: Mode -> N.Name -> N.Name -> (DT.Test, Opt.Decider Opt.Choice) -> [JS.Case] -> [JS.Case]
725addCase mode label root (test, subTree) cases =
726 let stmts = generateDecider mode label root subTree in
727 JS.Case (testToExpr mode test) stmts : cases
730testToExpr :: Mode -> DT.Test -> JS.Expr
731testToExpr mode test =
733 DT.IsCtor _ tag index ->
736 JS.String (N.toBuilder tag)
739 JS.Int (Index.toZeroBased index)
745 JS.String (Text.encodeUtf8Builder char)
748 JS.String (Text.encodeUtf8Builder string)
751pathToTestableExpr :: Mode -> N.Name -> DT.Path -> DT.Test -> JS.Expr
752pathToTestableExpr mode root path exampleTest =
755 pathToJsExpr mode (JS.Ref (Name.fromLocal root)) path
759 JS.Access expr Name.dollar
770 JS.Call (JS.Access expr (Name.fromLocal "valueOf")) []
776pathToJsExpr :: Mode -> JS.Expr -> DT.Path -> JS.Expr
777pathToJsExpr mode expr path =
779 DT.Index index subpath ->
780 pathToJsExpr mode (JS.Access expr (Name.fromInt index)) subpath
782 DT.Field field subpath ->
783 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