{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE ViewPatterns #-}
module Fay.Compiler.Exp
(compileExp
,compileGuards
,compileLetDecl
,compileLit
) where
import Fay.Compiler.Prelude
import Fay.Compiler.FFI (compileFFIExp)
import Fay.Compiler.Misc
import Fay.Compiler.Pattern
import Fay.Compiler.Print
import Fay.Compiler.QName
import Fay.Config
import Fay.Exts.NoAnnotation (unAnn)
import Fay.Exts.Scoped (noI)
import qualified Fay.Exts.Scoped as S
import Fay.Types
import Control.Monad.Except (throwError)
import Control.Monad.RWS (asks, gets)
import qualified Data.Char as Char
import Language.Haskell.Exts hiding (alt, binds, name, op)
import Language.Haskell.Names (NameInfo (RecExpWildcard), Scoped (Scoped))
compileExp :: S.Exp -> Compile JsExp
compileExp e = case e of
Var _ qname -> compileVar qname
Lit s lit -> compileLit (Signless s) lit
App _ (Var _ (UnQual _ (Ident _ "ffi"))) _ -> throwError $ FfiNeedsTypeSig e
App _ exp1 exp2 -> compileApp exp1 exp2
NegApp _ exp -> compileNegApp exp
Let _ (BDecls _ decls) exp -> compileLet decls exp
List _ [] -> return JsNull
List _ xs -> compileList xs
Tuple _ _boxed xs -> compileList xs
If _ cond conseq alt -> compileIf cond conseq alt
Case _ exp alts -> compileCase exp alts
Con _ (UnQual _ (Ident _ "True")) -> return $ JsLit (JsBool True)
Con _ (UnQual _ (Ident _ "False")) -> return $ JsLit (JsBool False)
Con _ qname -> compileVar qname
Lambda _ pats exp -> compileLambda pats exp
EnumFrom _ i -> compileEnumFrom i
EnumFromTo _ i i' -> compileEnumFromTo i i'
EnumFromThen _ a b -> compileEnumFromThen a b
EnumFromThenTo _ a b z -> compileEnumFromThenTo a b z
RecConstr _ name fieldUpdates -> compileRecConstr e name fieldUpdates
RecUpdate _ rec fieldUpdates -> compileRecUpdate e rec fieldUpdates
ExpTypeSig _ exp sig -> case ffiExp exp of
Nothing -> compileExp exp
Just formatstr -> compileFFIExp (S.srcSpanInfo $ ann exp) Nothing formatstr sig
ListComp {} -> shouldBeDesugared e
Do {} -> shouldBeDesugared e
LeftSection {} -> shouldBeDesugared e
RightSection {} -> shouldBeDesugared e
TupleSection {} -> shouldBeDesugared e
Paren {} -> shouldBeDesugared e
InfixApp {} -> shouldBeDesugared e
exp -> throwError $ UnsupportedExpression exp
compileVar :: S.QName -> Compile JsExp
compileVar (Special _ t@TupleCon{}) = shouldBeDesugared t
compileVar qname = do
nc <- lookupNewtypeConst qname
nd <- lookupNewtypeDest qname
if isJust nc || isJust nd
then
return idFun
else JsName . JsNameVar <$> unsafeResolveName qname
where
idFun = JsFun Nothing [JsTmp 1] [] (Just (JsName $ JsTmp 1))
compileLit :: S.Sign -> S.Literal -> Compile JsExp
compileLit sign lit = case lit of
Char _ ch _ -> return (JsLit (JsChar ch))
Int _ integer _ -> return (JsLit (JsInt (fromIntegral (applySign integer))))
Frac _ rational _ -> return (JsLit (JsFloating (fromRational (applySign rational))))
String _ string _ -> do
fromString <- gets stateUseFromString
return $ if fromString
then JsLit (JsStr string)
else JsApp (JsName (JsBuiltIn "list")) [JsLit (JsStr string)]
_ -> throwError $ UnsupportedLiteral lit
where
applySign :: Num a => a -> a
applySign = case sign of
Signless _ -> id
Negative _ -> negate
compileApp :: S.Exp -> S.Exp -> Compile JsExp
compileApp exp1@(Con _ q) exp2 =
ifOptimizeNewtypes
(maybe (compileApp' exp1 exp2) (const $ compileExp exp2) =<< lookupNewtypeConst q)
(compileApp' exp1 exp2)
compileApp exp1@(Var _ q) exp2 =
ifOptimizeNewtypes
(maybe (compileApp' exp1 exp2) (const $ compileExp exp2) =<< lookupNewtypeDest q)
(compileApp' exp1 exp2)
compileApp exp1 exp2 =
compileApp' exp1 exp2
compileApp' :: S.Exp -> S.Exp -> Compile JsExp
compileApp' exp1 exp2 = do
flattenApps <- config configFlattenApps
jsexp1 <- compileExp exp1
(if flattenApps then method2 else method1) jsexp1 exp2
where
method1 :: JsExp -> S.Exp -> Compile JsExp
method1 e1 e2 =
JsApp <$> (forceFlatName <$> return e1)
<*> fmap return (compileExp e2)
where
forceFlatName name = JsApp (JsName JsForce) [name]
method2 :: JsExp -> S.Exp -> Compile JsExp
method2 e1 e2 = fmap flatten $
JsApp <$> return e1
<*> fmap return (compileExp e2)
where
flatten (JsApp op args) =
case op of
JsApp l r -> JsApp l (r ++ args)
_ -> JsApp (JsName JsApply) (op : args)
flatten x = x
compileNegApp :: S.Exp -> Compile JsExp
compileNegApp e = JsNegApp . force <$> compileExp e
compileLet :: [S.Decl] -> S.Exp -> Compile JsExp
compileLet decls exp = do
binds <- mapM compileLetDecl decls
body <- compileExp exp
return (JsApp (JsFun Nothing [] [] (Just $ stmtsThunk $ concat binds ++ [JsEarlyReturn body])) [])
compileLetDecl :: S.Decl -> Compile [JsStmt]
compileLetDecl decl = do
compileDecls <- asks readerCompileDecls
case decl of
PatBind{} -> compileDecls False [decl]
FunBind{} -> compileDecls False [decl]
TypeSig{} -> return []
_ -> throwError $ UnsupportedLetBinding decl
compileList :: [S.Exp] -> Compile JsExp
compileList xs = do
exps <- mapM compileExp xs
return (makeList exps)
compileIf :: S.Exp -> S.Exp -> S.Exp -> Compile JsExp
compileIf cond conseq alt =
JsTernaryIf <$> fmap force (compileExp cond)
<*> compileExp conseq
<*> compileExp alt
compileCase :: S.Exp -> [S.Alt] -> Compile JsExp
compileCase e alts = do
exp <- compileExp e
withScopedTmpJsName $ \tmpName -> do
pats <- optimizePatConditions <$> mapM (compilePatAlt (JsName tmpName)) alts
let (xx,flag) = deleteAfterReturn (concat pats)
return $
JsApp (JsFun Nothing
[tmpName]
xx
(if (flag || any isWildCardAlt alts)
then Nothing
else Just (throwExp "unhandled case" (JsName tmpName))))
[exp]
where
deleteAfterReturn :: [JsStmt] -> ([JsStmt],Bool)
deleteAfterReturn [] = ([],False)
deleteAfterReturn (x@(JsEarlyReturn _):_) = ([x],True)
deleteAfterReturn (x:xs) = ((x:xx),flag)
where (xx,flag) = deleteAfterReturn xs
compilePatAlt :: JsExp -> S.Alt -> Compile [JsStmt]
compilePatAlt exp a@(Alt _ pat rhs wheres) = case wheres of
Just (BDecls _ (_ : _)) -> throwError $ UnsupportedWhereInAlt a
Just (IPBinds _ (_ : _)) -> throwError $ UnsupportedWhereInAlt a
_ -> do
alt <- compileGuardedAlt rhs
compilePat exp pat [alt]
compileGuardedAlt :: S.Rhs -> Compile JsStmt
compileGuardedAlt alt =
case alt of
UnGuardedRhs _ exp -> JsEarlyReturn <$> compileExp exp
GuardedRhss _ alts -> compileGuards alts
compileGuards :: [S.GuardedRhs] -> Compile JsStmt
compileGuards (GuardedRhs _ (Qualifier _ guard:_) exp : rest) =
makeIf <$> fmap force (compileExp guard)
<*> compileExp exp
<*> if null rest then return [] else do
gs' <- compileGuards rest
return [gs']
where makeIf gs e = JsIf gs [JsEarlyReturn e]
compileGuards rhss = throwError . UnsupportedRhs . GuardedRhss noI $ rhss
compileLambda :: [S.Pat] -> S.Exp -> Compile JsExp
compileLambda pats = compileExp >=> \exp -> do
stmts <- generateStatements exp
case stmts of
[JsEarlyReturn fun@JsFun{}] -> return fun
_ -> error "Unexpected statements in compileLambda"
where unhandledcase = throw "unhandled case" . JsName
allfree = all isWildCardPat pats
generateStatements exp =
foldM (\inner (param,pat) -> do
stmts <- compilePat (JsName param) pat inner
return [JsEarlyReturn (JsFun Nothing [param] (deleteAfterReturn $ stmts ++ [unhandledcase param | not allfree]) Nothing)])
[JsEarlyReturn exp]
(reverse (zip uniqueNames pats))
deleteAfterReturn :: [JsStmt] -> [JsStmt]
deleteAfterReturn [] = []
deleteAfterReturn (x@(JsEarlyReturn _):_) = [x]
deleteAfterReturn (x:xs) = x:deleteAfterReturn xs
compileEnumFrom :: S.Exp -> Compile JsExp
compileEnumFrom i = do
e <- compileExp i
return (JsApp (JsName (JsNameVar (Qual () "Prelude" "enumFrom"))) [e])
compileEnumFromTo :: S.Exp -> S.Exp -> Compile JsExp
compileEnumFromTo i i' = do
f <- compileExp i
t <- compileExp i'
cfg <- config id
return $ case optEnumFromTo cfg f t of
Just s -> s
_ -> JsApp (JsApp (JsName (JsNameVar (Qual () "Prelude" "enumFromTo"))) [f]) [t]
compileEnumFromThen :: S.Exp -> S.Exp -> Compile JsExp
compileEnumFromThen a b = do
fr <- compileExp a
th <- compileExp b
return (JsApp (JsApp (JsName (JsNameVar (Qual () "Prelude" "enumFromThen"))) [fr]) [th])
compileEnumFromThenTo :: S.Exp -> S.Exp -> S.Exp -> Compile JsExp
compileEnumFromThenTo a b z = do
fr <- compileExp a
th <- compileExp b
to <- compileExp z
cfg <- config id
return $ case optEnumFromThenTo cfg fr th to of
Just s -> s
_ -> JsApp (JsApp (JsApp (JsName (JsNameVar (Qual () "Prelude" "enumFromThenTo"))) [fr]) [th]) [to]
compileRecConstr :: S.Exp -> S.QName -> [S.FieldUpdate] -> Compile JsExp
compileRecConstr origExp name fieldUpdates = do
let unQualName = withIdent lowerFirst . unQualify $ unAnn name
qname <- unsafeResolveName name
let record = JsVar (JsNameVar unQualName) (JsNew (JsConstructor qname) [])
setFields <- concat <$> forM fieldUpdates (updateStmt name)
return $ JsApp (JsFun Nothing [] (record:setFields) (Just . JsName . JsNameVar . withIdent lowerFirst . unQualify $ unAnn name)) []
where
updateStmt (unAnn -> o) (FieldUpdate _ (unAnn -> field) value) = do
exp <- compileExp value
return [JsSetProp (JsNameVar $ withIdent lowerFirst $ unQualify o) (JsNameVar $ unQualify field) exp]
updateStmt o (FieldWildcard (wildcardFields -> fields)) =
return $ flip fmap fields $ \fieldName -> JsSetProp (JsNameVar . withIdent lowerFirst . unQualify . unAnn $ o)
(JsNameVar fieldName)
(JsName $ JsNameVar fieldName)
updateStmt _ _ = throwError $ UnsupportedExpression origExp
wildcardFields l = case l of
Scoped (RecExpWildcard es) _ -> map (unQualify . origName2QName . fst) es
_ -> []
lowerFirst :: String -> String
lowerFirst "" = ""
lowerFirst (x:xs) = '_' : Char.toLower x : xs
compileRecUpdate :: S.Exp -> S.Exp -> [S.FieldUpdate] -> Compile JsExp
compileRecUpdate origExp rec fieldUpdates = do
record <- force <$> compileExp rec
let copyName = UnQual () $ Ident () "$_record_to_update"
copy = JsVar (JsNameVar copyName)
(JsRawExp ("Object.create(" ++ printJSString record ++ ")"))
setFields <- forM fieldUpdates (updateExp copyName)
return $ JsApp (JsFun Nothing [] (copy:setFields) (Just $ JsName $ JsNameVar copyName)) []
where
updateExp :: QName a -> S.FieldUpdate -> Compile JsStmt
updateExp (unAnn -> copyName) (FieldUpdate _ (unQualify . unAnn -> field) value) =
JsSetProp (JsNameVar copyName) (JsNameVar field) <$> compileExp value
updateExp _ f@FieldPun{} = shouldBeDesugared f
updateExp _ FieldWildcard{} = throwError $ UnsupportedExpression origExp
makeList :: [JsExp] -> JsExp
makeList exps = JsApp (JsName $ JsBuiltIn "list") [JsList exps]
optEnumFromTo :: Config -> JsExp -> JsExp -> Maybe JsExp
optEnumFromTo cfg (JsLit f) (JsLit t) =
if configOptimize cfg
then case (f,t) of
(JsInt fl, JsInt tl) -> strict JsInt fl tl
(JsFloating fl, JsFloating tl) -> strict JsFloating fl tl
_ -> Nothing
else Nothing
where strict :: (Enum a, Ord a, Num a) => (a -> JsLit) -> a -> a -> Maybe JsExp
strict litfn fr to =
if fromEnum to - fromEnum fr < maxStrictASLen
then Just . makeList . map (JsLit . litfn) $ enumFromTo fr to
else Nothing
optEnumFromTo _ _ _ = Nothing
optEnumFromThenTo :: Config -> JsExp -> JsExp -> JsExp -> Maybe JsExp
optEnumFromThenTo cfg (JsLit fr) (JsLit th) (JsLit to) =
if configOptimize cfg
then case (fr,th,to) of
(JsInt frl, JsInt thl, JsInt tol) -> strict JsInt frl thl tol
(JsFloating frl, JsFloating thl, JsFloating tol) -> strict JsFloating frl thl tol
_ -> Nothing
else Nothing
where strict :: (Enum a, Ord a, Num a) => (a -> JsLit) -> a -> a -> a -> Maybe JsExp
strict litfn fr' th' to' =
if (fromEnum to' - fromEnum fr') `div`
(fromEnum th' - fromEnum fr') + 1 < maxStrictASLen
then Just . makeList . map (JsLit . litfn) $ enumFromThenTo fr' th' to'
else Nothing
optEnumFromThenTo _ _ _ _ = Nothing
maxStrictASLen :: Int
maxStrictASLen = 10