module Language.Sunroof.Compiler
( sunroofCompileJSA
, sunroofCompileJSB
, compileJS
, CompilerOpts(..)
) where
import Control.Monad.Operational
import Control.Monad.State
import Control.Monad.Reader
import Data.Reify
import Data.Graph
import Data.Maybe
import Data.Proxy ( Proxy(..) )
import qualified Data.Map as Map
import Data.Default
import Language.Sunroof.Types
( T(..)
, JS(..), JSI(..)
, SunroofThread(..)
, ThreadProxy(..)
, single, apply, unJS, nullJS
, continuation, goto )
import Language.Sunroof.JavaScript
import Language.Sunroof.Classes
( Sunroof(..), SunroofArgument(..)
, UniqM(..), Uniq )
import Language.Sunroof.Selector ( unboxSelector, (!) )
import Language.Sunroof.Internal ( proxyOf )
import Language.Sunroof.JS.Object ( JSObject )
data CompilerOpts = CompilerOpts
{ co_on :: Bool
, co_cse :: Bool
, co_const :: Bool
, co_verbose :: Int
, co_compress :: Bool
}
deriving Show
instance Default CompilerOpts where
def = CompilerOpts True False False 0 False
sunroofCompileJSA :: (Sunroof a) => CompilerOpts -> String -> JS A a -> IO String
sunroofCompileJSA opts fName f = do
(stmts,_) <- compileJS opts 0 (single . JS_Return) f
return $ showStmt $ mkVarStmt fName $ scopeForEffect stmts
sunroofCompileJSB :: CompilerOpts -> String -> JS B () -> IO String
sunroofCompileJSB opts fName f = sunroofCompileJSA opts fName $ do
k <- continuation (\ () -> f)
goto k () :: JS A ()
extractProgramJS :: (a -> JS t ()) -> JS t a -> Program (JSI t) ()
extractProgramJS k m = unJS (m >>= k) return
compileJS :: CompilerOpts -> Uniq -> (a -> JS t ()) -> JS t a -> IO ([Stmt], Uniq)
compileJS opts uq k m = runStateT (runReaderT (compile $ extractProgramJS k m) opts) uq
compile :: Program (JSI t) () -> CompM [Stmt]
compile = eval . view
where
eval :: ProgramView (JSI t) () -> CompM [Stmt]
eval (Return ()) = return []
eval (JS_Eval e :>>= g) = do
compileBind (unbox e) g
eval (JS_Assign sel a obj :>>= g) = do
(stmts0,val) <- compileExpr (unbox a)
stmts1 <- compile (g ())
return ( stmts0 ++ [AssignStmt (DotRhs (unbox obj) (unboxSelector sel)) val] ++ stmts1)
eval (JS_Select sel obj :>>= g) = do
compileBind (Apply (ExprE (Var "[]")) [ExprE $ unbox obj, ExprE $ unboxSelector sel]) g
eval (JS_Delete sel obj :>>= g) = do
let ty = typeOf (proxyOf (obj ! sel))
stmts1 <- compile (g ())
return (DeleteStmt (Dot (ExprE $ unbox obj) (ExprE $ unboxSelector sel) ty) : stmts1)
eval (JS_Return e :>>= _) = do
let ty = typeOf (proxyOf e)
case ty of
Unit -> return []
_ -> do
(stmts0,val) <- compileExpr (unbox e)
return ( stmts0 ++ [ ReturnStmt val])
eval (JS_Assign_ _ a :>>= g) | typeOf (proxyOf a) == Unit = do
stmts1 <- compile (g ())
return stmts1
eval (JS_Assign_ v a :>>= g) = do
(stmts0,val) <- compileExpr (unbox a)
stmts1 <- compile (g ())
return ( stmts0 ++ [AssignStmt (VarRhs v) val] ++ stmts1)
eval (JS_Invoke args fn :>>= g) = do
compileBind (Apply (ExprE $ unbox fn) (map ExprE (jsArgs args))) g
eval (JS_Function f :>>= g) = do
e <- compileFunction f
compileBind e g
eval (JS_Continuation f :>>= g) = do
e <- compileContinuation f
compileBind e g
eval (JS_Branch b c1 c2 :>>= g) = compileBranch b c1 c2 g
eval (JS_Fix h1 :>>= g) = compileFix h1 g
eval (JS_Comment msg :>>= g) = do
rest <- compile (g ())
return $ CommentStmt msg : rest
compileBind :: forall a t . (Sunroof a)
=> Expr
-> (a -> Program (JSI t) ())
-> CompM [Stmt]
compileBind e m2 = do
a <- newVar
(stmts0,val) <- compileExpr e
stmts1 <- compile (m2 (var a))
let isUnit = typeOf (Proxy::Proxy a) == Unit
valIsTriv = case val of
Var {} -> True
Lit {} -> True
_ -> False
case () of
_ | isUnit && null stmts0 && valIsTriv
-> return stmts1
| isUnit -> return (stmts0 ++ [ExprStmt val] ++ stmts1 )
| otherwise -> return (stmts0 ++ [mkVarStmt a val] ++ stmts1 )
compileBranch_A :: forall a bool t . (Sunroof a, Sunroof bool)
=> bool -> JS t a -> JS t a -> (a -> Program (JSI t) ()) -> CompM [Stmt]
compileBranch_A b c1 c2 k = do
res <- newVar
(src0, res0) <- compileExpr (unbox b)
src1 <- compile $ extractProgramJS (single . JS_Assign_ res) c1
src2 <- compile $ extractProgramJS (single . JS_Assign_ res) c2
rest <- compile (k (var res))
return (src0 ++ [ IfStmt res0 src1 src2 ] ++ rest)
compileBranch_B :: forall a bool t . (Sunroof bool, SunroofArgument a, SunroofThread t)
=> bool -> JS t a -> JS t a -> (a -> Program (JSI t) ()) -> CompM [Stmt]
compileBranch_B b c1 c2 k = do
fn_e <- compileContinuation (\ a -> blockableJS $ JS $ \ k2 -> k a >>= k2)
fn <- newVar
(src0, res0) <- compileExpr (unbox b)
src1 <- compile $ extractProgramJS (apply (var fn)) c1
src2 <- compile $ extractProgramJS (apply (var fn)) c2
return ( [mkVarStmt fn fn_e] ++ src0 ++ [ IfStmt res0 src1 src2 ])
compileBranch :: forall a bool t . (SunroofThread t, Sunroof bool, Sunroof a, SunroofArgument a)
=> bool -> JS t a -> JS t a -> (a -> Program (JSI t) ()) -> CompM [Stmt]
compileBranch b c1 c2 k =
case evalStyle (ThreadProxy :: ThreadProxy t) of
A -> compileBranch_A b c1 c2 k
B -> compileBranch_B b c1 c2 k
compileFix :: forall a t . (SunroofArgument a)
=> (a -> JS A a) -> (a -> Program (JSI t) ()) -> CompM [Stmt]
compileFix h1 k = do
args <- jsValue
let initial =
[ mkVarStmt v (unbox nullJS)
| Var v <- jsArgs args
]
body <- compile (unJS (h1 args) (\ res -> do
when (length (jsArgs args) /= length (jsArgs res)) $
error "fatal error in mdo compile"
singleton $ JS_Comment
$ "tie the knot"
sequence_ [ singleton $ JS_Assign_ v (box $ e :: JSObject)
| (Var v, e) <- jsArgs args `zip` jsArgs res
]))
rest <- compile (k args)
return $
[ CommentStmt "set up recusive values" ] ++
initial ++
[ CommentStmt "body of the mdo-style rec" ] ++
body ++
[ CommentStmt "and proceed with the rest of the program"] ++
rest
compileFunction :: forall a b . (SunroofArgument a, Sunroof b)
=> (a -> JS A b)
-> CompM Expr
compileFunction m2 = do
(arg :: a) <- jsValue
fStmts <- compile $ extractProgramJS (\ a -> JS $ \ k -> singleton (JS_Return a) >>= k) (m2 arg)
return $ Function (map varIdE $ jsArgs arg) fStmts
compileContinuation :: forall a b . (SunroofArgument a, Sunroof b)
=> (a -> JS B b)
-> CompM Expr
compileContinuation m2 = do
(arg :: a) <- jsValue
fStmts <- compile $ extractProgramJS (\ _ -> JS $ \ k -> k ()) (m2 arg)
return $ Function (map varIdE $ jsArgs arg) fStmts
compileExpr :: Expr -> CompM ([Stmt], Expr)
compileExpr e = do
opts <- ask
optExpr opts e
data Inst i e = Inst (i e)
| Copy e
deriving (Show)
optExpr :: CompilerOpts -> Expr -> CompM ([Stmt], Expr)
optExpr opts e | not (co_on opts) = return ([],e)
optExpr _opts e = do
Graph g start <- liftIO $ reifyGraph (ExprE e)
let db0 = Map.fromList [ (n,Inst e') | (n,e') <- g ]
let out = stronglyConnComp
[ (n,n,case e' of
Apply f xs -> f : xs
_ -> [])
| (n,e') <- g
]
let ids = filter (/= start) $ flattenSCCs $ out
jsVars :: Map.Map Uniq String <- liftM Map.fromList $ sequence
[ do v <- uniqM
return (n,"c" ++ show v)
| n <- ids
, Just (Inst (Apply {})) <- [ Map.lookup n db0 ]
]
let findExpr vars db n =
case Map.lookup n vars of
Just v -> Var v
Nothing -> case Map.lookup n db of
Just (Inst oper) -> fmap (ExprE . findExpr vars db) oper
Just (Copy n') -> findExpr vars db n'
Nothing -> error $ "optExpr: findExpr failed for " ++ show n
let folder :: (Ord n)
=> Map.Map n e
-> [n]
-> (e -> Map.Map n e -> Maybe e)
-> Map.Map n e
folder db [] _f = db
folder db (n:ns) f = case Map.lookup n db of
Nothing -> error "bad folder"
Just e' -> case f e' db of
Nothing -> folder db ns f
Just e'' -> folder (Map.insert n e'' db) ns f
let db1 = folder db0 ids $ \ e' db ->
let
getVar :: Uniq -> Maybe String
getVar expr = case findExpr jsVars db expr of { Var x -> return x ; _ -> Nothing }
getLit :: Uniq -> Maybe String
getLit expr = case findExpr Map.empty db expr of { Lit x -> return x ; _ -> Nothing }
in case e' of
(Inst (Apply g' [x,y])) | getVar g' == Just "<="
&& isJust (getLit x)
&& isJust (getLit y)
&& getLit x == getLit y
-> return (Inst (Lit "true"))
Inst (Apply g' [x,y,z])
| getVar g' == Just "?:" && getLit x == return "true"
-> return (Copy y)
| getVar g' == Just "?:" && getLit x == return "false"
-> return (Copy z)
_ -> Nothing
let dbF = db1
return ([ mkVarStmt c $ case e' of
Inst expr -> fmap (ExprE . findExpr jsVars dbF) expr
Copy n' ->
findExpr jsVars dbF n'
| n <- ids
, Just c <- return $ Map.lookup n jsVars
, Just e' <- return $ Map.lookup n dbF
], findExpr jsVars dbF start)
type CompM = ReaderT CompilerOpts (StateT Uniq IO)
instance UniqM CompM where
uniqM = do
n <- get
modify (+1)
return n
newVar :: CompM Id
newVar = uniqM >>= return . ("v" ++) . show
var :: Sunroof a => Id -> a
var = box . Var
varIdE :: Expr -> Id
varIdE e = case e of
(Var v) -> v
v -> error $ "varId: Expressions is not a variable: " ++ show v
mkVarStmt :: Id -> Expr -> Stmt
mkVarStmt v e = AssignStmt (VarRhs v) e