1{-# LANGUAGE OverloadedStrings #-}
2module Generate.JavaScript.Expression (generateDef) where
4import Control.Arrow (second)
5import Control.Monad.State (State, foldM)
6import qualified Data.List as List
7import qualified Data.Map as Map
8import Data.Monoid ((<>))
9import qualified Data.Text as Text
10import Data.Text (Text)
12import qualified AST.Expression.Canonical as Can
13import AST.Expression.Optimized as Opt
14import qualified AST.Literal as L
15import qualified AST.Module.Name as ModuleName
16import qualified AST.Variable as Var
17import Generate.JavaScript.Helpers as Help
18import qualified Generate.JavaScript.Builder as JS
19import qualified Generate.JavaScript.BuiltIn as BuiltIn
20import qualified Generate.JavaScript.Foreign as Foreign
21import qualified Generate.JavaScript.Literal as Literal
22import qualified Generate.JavaScript.Variable as Var
23import qualified Optimize.DecisionTree as DT
35jsExpr :: JS.Expr -> State Int Code
40jsBlock :: [JS.Stmt] -> State Int Code
45isBlock :: Code -> Bool
52toStatementList :: Code -> [JS.Stmt]
56 [ JS.Return (Just expr) ]
62toStatement :: Code -> JS.Stmt
75toExpr :: Code -> JS.Expr
82 JS.Call (function [] stmts) []
89generateDef :: Maybe ModuleName.Canonical -> Text -> Opt.Def -> State Int [JS.Stmt]
90generateDef home name def =
93 Opt.TailDef argNames body ->
94 generateTailFunction name argNames body
99 return (Var.define home name jsBody)
106generateJsExpr :: Opt.Expr -> State Int JS.Expr
107generateJsExpr optExpr =
108 toExpr <$> generateCode optExpr
111generateCode :: Opt.Expr -> State Int Code
115 jsExpr $ Var.canonical var
118 jsExpr (Literal.literal lit)
120 Access record field ->
121 do jsRecord <- generateJsExpr record
122 jsExpr $ JS.DotRef jsRecord (JS.Id (Var.safe field))
124 Update record fields ->
126 fieldToJs (field, value) =
127 do jsValue <- generateJsExpr value
128 return (Var.safe field, jsValue)
130 do jsRecord <- generateJsExpr record
131 jsFields <- mapM fieldToJs fields
132 jsExpr $ BuiltIn.recordUpdate jsRecord jsFields
136 toField (field, value) =
137 do jsValue <- generateJsExpr value
138 return (Var.safe field ==> jsValue)
140 do jsFields <- mapM toField fields
141 jsExpr $ JS.Object jsFields
143 Binop op leftExpr rightExpr ->
144 binop op leftExpr rightExpr
146 Function args body ->
147 generateFunction args body
150 generateCall func args
152 TailCall name argNames args ->
154 reassign name tempName =
156 JS.Assign (JS.LVar (Var.safe name)) (ref tempName)
158 do args' <- mapM generateJsExpr args
159 tempNames <- mapM (\_ -> Var.fresh) args
161 JS.VarDeclStmt (zipWith varDecl tempNames args')
162 : zipWith reassign argNames tempNames
163 ++ [JS.Continue (Just (JS.Id (Var.safe name)))]
166 do let genDef (name, def) = generateDef Nothing name def
167 stmts <- mapM genDef defs
168 code <- generateCode body
169 jsBlock (concat stmts ++ toStatementList code)
171 If branches finally ->
172 generateIf branches finally
174 Case exprName decider jumps ->
175 JsBlock <$> generateCase exprName decider jumps
178 do jsElements <- mapM generateJsExpr elements
179 jsExpr $ BuiltIn.list jsElements
184 "ctor" ==> JS.String tag
187 ("_" <> Text.pack (show n)) ==> entry
189 do jsMembers <- mapM generateJsExpr members
190 jsExpr $ JS.Object (ctor : zipWith toEntry jsMembers [ 0 :: Int .. ])
192 CtorAccess dataExpr index ->
193 do jsDataExpr <- generateJsExpr dataExpr
194 jsExpr $ JS.DotRef jsDataExpr (JS.Id ("_" <> Text.pack (show index)))
197 jsExpr $ BuiltIn.effect moduleName
200 jsExpr $ BuiltIn.effect moduleName
202 OutgoingPort name tipe ->
203 jsExpr $ BuiltIn.outgoingPort name (Foreign.encode tipe)
205 IncomingPort name tipe ->
206 do jsDecoder <- generateJsExpr (Foreign.decode tipe)
207 jsExpr $ BuiltIn.incomingPort name jsDecoder
210 generateProgram kind body
212 GLShader _uid src _tipe ->
213 jsExpr $ JS.Object [(JS.StringProp "src", Literal.literal (L.Str src))]
215 Crash home region maybeBranchProblem ->
216 do maybeOptBranchProblem <- traverse generateJsExpr maybeBranchProblem
217 jsExpr $ BuiltIn.crash home region maybeOptBranchProblem
224generateProgram :: Can.Main -> Opt.Expr -> State Int Code
225generateProgram kind body =
228 do html <- generateJsExpr body
229 jsExpr (Var.staticProgram <| html)
232 do almostProgram <- generateJsExpr body
233 jsExpr (JS.Call almostProgram [])
236 do almostProgram <- generateJsExpr body
237 flagDecoder <- generateJsExpr (Foreign.decode tipe)
238 jsExpr (almostProgram <| flagDecoder)
245generateFunction :: [Text] -> Opt.Expr -> State Int Code
246generateFunction args body =
247 do code <- generateCode body
248 jsExpr (generateFunctionWithArity args code)
251generateTailFunction :: Text -> [Text] -> Opt.Expr -> State Int JS.Expr
252generateTailFunction name args body =
253 do code <- generateCode body
254 return $ generateFunctionWithArity args $ JsBlock $ (:[]) $
256 (JS.Id (Var.safe name))
257 (JS.While (JS.Bool True) (toStatement code))
260generateFunctionWithArity :: [Text] -> Code -> JS.Expr
261generateFunctionWithArity rawArgs code =
263 args = map Var.safe rawArgs
266 if 2 <= arity && arity <= 9 then
268 fN = "F" <> Text.pack (show arity)
270 ref fN <| function args (toStatementList code)
273 (lastArg:otherArgs) = reverse args
274 innerBody = function [lastArg] (toStatementList code)
276 foldl (\body arg -> function [arg] [JS.Return (Just body)]) innerBody otherArgs
283generateCall :: Opt.Expr -> [Opt.Expr] -> State Int Code
284generateCall func args =
286 (Opt.Var var, [arg]) ->
287 case getUnaryOp var of
289 do jsArg <- generateJsExpr arg
290 jsExpr $ JS.Prefix op jsArg
293 generateCallHelp func args
295 (Opt.Var var, [ arg1, arg2 ]) ->
296 case getBinaryOp var arg1 arg2 of
297 Just (op, left, right) ->
298 do jsLeft <- generateJsExpr left
299 jsRight <- generateJsExpr right
300 jsExpr $ JS.Infix op jsLeft jsRight
303 generateCallHelp func args
306 generateCallHelp func args
309generateCallHelp :: Opt.Expr -> [Opt.Expr] -> State Int Code
310generateCallHelp func args =
313 aN = "A" <> Text.pack (show arity)
315 do jsFunc <- generateJsExpr func
316 jsArgs <- mapM generateJsExpr args
318 if 2 <= arity && arity <= 9 then
319 JS.Call (ref aN) (jsFunc:jsArgs)
321 foldl1 (<|) (jsFunc:jsArgs)
324getUnaryOp :: Var.Canonical -> Maybe JS.PrefixOp
326 if var == bitwiseComplement then
329 else if var == basicsNot then
336getBinaryOp :: Var.Canonical -> Opt.Expr -> Opt.Expr -> Maybe (JS.InfixOp, Opt.Expr, Opt.Expr)
337getBinaryOp (Var.Canonical home name) left right =
338 if home /= bitwise then
343 "and" -> Just ( JS.OpBAnd, left, right )
344 "or" -> Just ( JS.OpBOr, left, right )
345 "xor" -> Just ( JS.OpBXor, left, right )
346 "shiftLeftBy" -> Just ( JS.OpLShift, right, left )
347 "shiftRightBy" -> Just ( JS.OpSpRShift, right, left )
348 "shiftRightZfBy" -> Just ( JS.OpZfRShift, right, left )
352bitwiseComplement :: Var.Canonical
354 Var.inCore "Bitwise" "complement"
357basicsNot :: Var.Canonical
359 Var.inCore "Basics" "not"
364 Var.Module (ModuleName.inCore "Bitwise")
371generateIf :: [(Opt.Expr, Opt.Expr)] -> Opt.Expr -> State Int Code
372generateIf givenBranches givenFinally =
374 (branches, finally) =
375 crushIfs givenBranches givenFinally
377 convertBranch (condition, expr) =
378 (,) <$> generateJsExpr condition <*> generateCode expr
380 ifExpression (condition, branch) otherwise =
381 JS.If condition branch otherwise
383 ifStatement (condition, branch) otherwise =
384 JS.IfStmt condition branch otherwise
386 do jsBranches <- mapM convertBranch branches
387 jsFinally <- generateCode finally
389 if any isBlock (jsFinally : map snd jsBranches)
391 jsBlock [ foldr ifStatement (toStatement jsFinally) (map (second toStatement) jsBranches) ]
393 jsExpr (foldr ifExpression (toExpr jsFinally) (map (second toExpr) jsBranches))
397 :: [(Opt.Expr, Opt.Expr)]
399 -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
400crushIfs branches finally =
401 crushIfsHelp [] branches finally
405 :: [(Opt.Expr, Opt.Expr)]
406 -> [(Opt.Expr, Opt.Expr)]
408 -> ([(Opt.Expr, Opt.Expr)], Opt.Expr)
409crushIfsHelp visitedBranches unvisitedBranches finally =
410 case unvisitedBranches of
413 If subBranches subFinally ->
414 crushIfsHelp visitedBranches subBranches subFinally
417 (reverse visitedBranches, finally)
419 (Literal (L.Boolean True), branch) : _ ->
420 crushIfsHelp visitedBranches [] branch
422 visiting : unvisited ->
423 crushIfsHelp (visiting : visitedBranches) unvisited finally
430generateCase :: Text -> Opt.Decider Opt.Choice -> [(Int, Opt.Expr)] -> State Int [JS.Stmt]
431generateCase exprName decider jumps =
432 do labelRoot <- Var.fresh
433 decider <- generateDecider exprName labelRoot decider
434 foldM (goto labelRoot) decider jumps
441goto :: Text -> [JS.Stmt] -> (Int, Opt.Expr) -> State Int [JS.Stmt]
442goto labelRoot deciderStmts (target, branch) =
446 (toLabel labelRoot target)
447 (JS.DoWhile (JS.Block deciderStmts) (JS.Bool False))
449 do code <- generateCode branch
450 return (labeledDeciderStmt : toStatementList code)
453toLabel :: Text -> Int -> JS.Id
455 JS.Id (root <> "_" <> Text.pack (show target))
459-- turn deciders into ifs and switches
462generateDecider :: Text -> Text -> Opt.Decider Opt.Choice -> State Int [JS.Stmt]
463generateDecider exprName labelRoot decisionTree =
465 Opt.Leaf (Opt.Inline branch) ->
466 toStatementList <$> generateCode branch
468 Opt.Leaf (Opt.Jump target) ->
469 return [ JS.Break (Just (toLabel labelRoot target)) ]
471 Opt.Chain testChain success failure ->
473 makeTest (path, test) =
474 do testExpr <- pathToTestableExpr exprName path test
475 return (JS.Infix JS.OpStrictEq testExpr (testToExpr test))
477 do testExprs <- mapM makeTest testChain
478 let cond = List.foldl1' (JS.Infix JS.OpLAnd) testExprs
479 thenBranch <- generateDecider exprName labelRoot success
480 elseBranch <- generateDecider exprName labelRoot failure
481 return [ JS.IfStmt cond (JS.Block thenBranch) (JS.Block elseBranch) ]
483 Opt.FanOut path edges fallback ->
484 do testExpr <- pathToTestableExpr exprName path (fst (head edges))
485 caseClauses <- mapM (edgeToCase exprName labelRoot) edges
486 caseDefault <- JS.Default <$> generateDecider exprName labelRoot fallback
487 return [ JS.Switch testExpr (caseClauses ++ [caseDefault]) ]
490edgeToCase :: Text -> Text -> (DT.Test, Opt.Decider Opt.Choice) -> State Int JS.Case
491edgeToCase exprName labelRoot (test, subTree) =
492 JS.Case (testToExpr test) <$> generateDecider exprName labelRoot subTree
495testToExpr :: DT.Test -> JS.Expr
498 DT.Constructor (Var.Canonical _ tag) ->
501 DT.Literal (L.Chr char) ->
512pathToTestableExpr :: Text -> DT.Path -> DT.Test -> State Int JS.Expr
513pathToTestableExpr root path exampleTest =
514 do accessExpr <- generateJsExpr (pathToExpr root path)
517 return $ JS.DotRef accessExpr (JS.Id "ctor")
519 DT.Literal (L.Chr _) ->
520 return $ JS.Call (JS.DotRef accessExpr (JS.Id "valueOf")) []
526pathToExpr :: Text -> DT.Path -> Opt.Expr
527pathToExpr root fullPath =
528 go (Opt.Var (Var.local root)) fullPath
532 DT.Position index subpath ->
533 go (Opt.CtorAccess expr index) subpath
535 DT.Field field subpath ->
536 go (Opt.Access expr field) subpath
554binop func left right =
555 do jsLeft <- generateJsExpr left
556 jsRight <- generateJsExpr right
557 jsExpr (binopHelp func jsLeft jsRight)
561-- BINARY OPERATOR HELPERS
564binopHelp :: Var.Canonical -> JS.Expr -> JS.Expr -> JS.Expr
565binopHelp qualifiedOp@(Var.Canonical home op) leftExpr rightExpr =
567 simpleMake left right =
568 JS.Call (ref "A2") [ Var.canonical qualifiedOp, left, right ]
570 if home == basicsModule then
571 (Map.findWithDefault simpleMake op basicOps) leftExpr rightExpr
573 else if op == "::" && (home == listModule || home == listModuleInternals) then
574 BuiltIn.cons leftExpr rightExpr
577 simpleMake leftExpr rightExpr
580listModule :: Var.Home
582 Var.Module (ModuleName.inCore "List")
585listModuleInternals :: Var.Home
587 Var.TopLevel (ModuleName.inCore "List")
590basicsModule :: Var.Home
592 Var.Module (ModuleName.inCore "Basics")
595basicOps :: Map.Map Text (JS.Expr -> JS.Expr -> JS.Expr)
597 Map.fromList (infixOps ++ specialOps)
600infixOps :: [(Text, JS.Expr -> JS.Expr -> JS.Expr)]
606 [ infixOp "+" JS.OpAdd
607 , infixOp "-" JS.OpSub
608 , infixOp "*" JS.OpMul
609 , infixOp "/" JS.OpDiv
610 , infixOp "&&" JS.OpLAnd
611 , infixOp "||" JS.OpLOr
615specialOps :: [(Text, JS.Expr -> JS.Expr -> JS.Expr)]
617 [ (,) "^" $ \a b -> JS.Call (obj ["Math","pow"]) [a,b]
618 , (,) "==" $ \a b -> BuiltIn.eq a b
619 , (,) "/=" $ \a b -> JS.Prefix JS.PrefixLNot (BuiltIn.eq a b)
620 , (,) "<" $ cmp JS.OpLT 0
621 , (,) ">" $ cmp JS.OpGT 0
622 , (,) "<=" $ cmp JS.OpLT 1
623 , (,) ">=" $ cmp JS.OpGT (-1)
624 , (,) "//" $ \a b -> JS.Infix JS.OpBOr (JS.Infix JS.OpDiv a b) (JS.Int 0)
628cmp :: JS.InfixOp -> Int -> JS.Expr -> JS.Expr -> JS.Expr
630 JS.Infix op (BuiltIn.cmp a b) (JS.Int n)
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