module Fay.Compiler.FFI
(emitFayToJs
,emitJsToFay
,compileFFI
,compileFFIExp
,jsToFayHash
,fayToJsHash
) where
import Fay.Compiler.Misc
import Fay.Compiler.Print (printJSString)
import Fay.Compiler.QName
import Fay.Types
import Control.Monad.Error
import Control.Monad.Writer
import Control.Applicative ((<$>), (<*>))
import Data.Char
import Data.Generics.Schemes
import Data.List
import Data.Maybe
import Data.String
import Language.ECMAScript3.Parser as JS
import Language.ECMAScript3.Syntax
import Language.Haskell.Exts (prettyPrint)
import Language.Haskell.Exts.Syntax
import Prelude hiding (exp, mod)
import Safe
compileFFI :: SrcLoc
-> Name
-> String
-> Type
-> Compile [JsStmt]
compileFFI srcloc name formatstr sig =
compileFFI' =<< rmNewtys sig
where rmNewtys :: Type -> Compile Type
rmNewtys (TyForall b c t) = TyForall b c <$> rmNewtys t
rmNewtys (TyFun t1 t2) = TyFun <$> rmNewtys t1 <*> rmNewtys t2
rmNewtys (TyTuple b tl) = TyTuple b <$> mapM rmNewtys tl
rmNewtys (TyList t) = TyList <$> rmNewtys t
rmNewtys (TyApp t1 t2) = TyApp <$> rmNewtys t1 <*> rmNewtys t2
rmNewtys t@TyVar{} = return t
rmNewtys (TyCon qname) = do
newty <- lookupNewtypeConst qname
return $ case newty of
Nothing -> TyCon qname
Just (_,ty) -> ty
rmNewtys (TyParen t) = TyParen <$> rmNewtys t
rmNewtys (TyInfix t1 q t2)= flip TyInfix q <$> rmNewtys t1 <*> rmNewtys t2
rmNewtys (TyKind t k) = flip TyKind k <$> rmNewtys t
compileFFI' :: Type -> Compile [JsStmt]
compileFFI' sig' = do
fun <- compileFFIExp srcloc (Just name) formatstr sig'
stmt <- bindToplevel True name fun
return [stmt]
compileFFIExp :: SrcLoc -> Maybe Name -> String -> Type -> Compile JsExp
compileFFIExp srcloc nameopt formatstr sig = do
let name = fromMaybe "<exp>" nameopt
inner <- formatFFI srcloc formatstr (zip params funcFundamentalTypes)
case JS.parse JS.expression (prettyPrint name) (printJSString (wrapReturn inner)) of
Left err -> throwError (FfiFormatInvalidJavaScript srcloc inner (show err))
Right exp -> do
config' <- config id
when (configGClosure config') $ warnDotUses srcloc inner exp
return (body inner)
where body inner = foldr wrapParam (wrapReturn inner) params
wrapParam pname inner = JsFun Nothing [pname] [] (Just inner)
params = zipWith const uniqueNames [1..typeArity sig]
wrapReturn inner = thunk $
case lastMay funcFundamentalTypes of
Just{} -> jsToFay SerializeAnywhere returnType (JsRawExp inner)
Nothing -> JsRawExp inner
funcFundamentalTypes = functionTypeArgs sig
returnType = last funcFundamentalTypes
warnDotUses :: SrcLoc -> String -> Expression SourcePos -> Compile ()
warnDotUses srcloc string expr =
when anyrefs $
warn $ printSrcLoc srcloc ++ ":\nDot ref syntax used in FFI JS code: " ++ string
where anyrefs = not (null (listify dotref expr)) ||
not (null (listify ldot expr))
dotref :: Expression SourcePos -> Bool
dotref x = case x of
DotRef _ (VarRef _ (Id _ name)) _
| name `elem` globalNames -> False
DotRef{} -> True
_ -> False
ldot :: LValue SourcePos -> Bool
ldot x =
case x of
LDot _ (VarRef _ (Id _ name)) _
| name `elem` globalNames -> False
LDot{} -> True
_ -> False
globalNames = ["Math","console","JSON"]
emitFayToJs :: Name -> [TyVarBind] -> [([Name],BangType)] -> Compile ()
emitFayToJs name tyvars (explodeFields -> fieldTypes) = do
qname <- qualify name
let ctrName = printJSString $ unqualName qname
tell $ mempty { writerFayToJs = [(ctrName, translator)] }
where
unqualName :: QName -> Name
unqualName (UnQual n) = n
unqualName (Qual _ n) = n
unqualName Special{} = error "unqualName: Special{}"
translator =
JsFun Nothing
[JsNameVar "type", argTypes, transcodingObjForced]
(obj : fieldStmts (map (getIndex name tyvars) fieldTypes))
(Just $ JsName obj_)
obj :: JsStmt
obj = JsVar obj_ $
JsObj [("instance",JsLit (JsStr (printJSString name)))]
fieldStmts :: [(Int,(Name,BangType))] -> [JsStmt]
fieldStmts [] = []
fieldStmts ((i,fieldType):fts) =
JsVar obj_v field :
JsIf (JsNeq JsUndefined (JsName obj_v))
[JsSetPropExtern obj_ decl (JsName obj_v)]
[] :
fieldStmts fts
where
obj_v = JsNameVar (UnQual (Ident $ "obj_" ++ d))
decl = JsNameVar (UnQual (Ident d))
(d, field) = declField i fieldType
obj_ = JsNameVar (UnQual (Ident "obj_"))
declField :: Int -> (Name,BangType) -> (String,JsExp)
declField i (fname,typ) =
(prettyPrint fname
,fayToJs (SerializeUserArg i)
(argType (bangType typ))
(JsGetProp (JsName transcodingObjForced)
(JsNameVar (UnQual fname))))
transcodingObj :: JsName
transcodingObj = JsNameVar "obj"
transcodingObjForced :: JsName
transcodingObjForced = JsNameVar "_obj"
functionTypeArgs :: Type -> [FundamentalType]
functionTypeArgs t =
case t of
TyForall _ _ i -> functionTypeArgs i
TyFun a b -> argType a : functionTypeArgs b
TyParen st -> functionTypeArgs st
r -> [argType r]
argType :: Type -> FundamentalType
argType t = case t of
TyCon "String" -> StringType
TyCon "Double" -> DoubleType
TyCon "Int" -> IntType
TyCon "Bool" -> BoolType
TyApp (TyCon "Ptr") _ -> PtrType
TyApp (TyCon "Automatic") _ -> Automatic
TyApp (TyCon "Defined") a -> Defined (argType a)
TyApp (TyCon "Nullable") a -> Nullable (argType a)
TyApp (TyCon "Fay") a -> JsType (argType a)
TyFun x xs -> FunctionType (argType x : functionTypeArgs xs)
TyList x -> ListType (argType x)
TyTuple _ xs -> TupleType (map argType xs)
TyParen st -> argType st
TyApp op arg -> userDefined (reverse (arg : expandApp op))
_ ->
case t of
TyCon (UnQual user) -> UserDefined user []
_ -> UnknownType
bangType :: BangType -> Type
bangType typ = case typ of
BangedTy ty -> ty
UnBangedTy ty -> ty
UnpackedTy ty -> ty
expandApp :: Type -> [Type]
expandApp (TyParen t) = expandApp t
expandApp (TyApp op arg) = arg : expandApp op
expandApp x = [x]
userDefined :: [Type] -> FundamentalType
userDefined (TyCon (UnQual name):typs) = UserDefined name (map argType typs)
userDefined _ = UnknownType
jsToFay :: SerializeContext -> FundamentalType -> JsExp -> JsExp
jsToFay = translate "jsToFay"
fayToJs :: SerializeContext -> FundamentalType -> JsExp -> JsExp
fayToJs = translate "fayToJs"
translate :: String -> SerializeContext -> FundamentalType -> JsExp -> JsExp
translate method context typ exp = case typ of
PtrType -> exp
StringType -> flat "string"
DoubleType -> flat "double"
IntType -> flat "int"
BoolType -> flat "bool"
JsType x | method == "jsToFay" -> js x
_ -> recursive
where flat specialize =
JsApp (JsName (JsBuiltIn (fromString (method ++ "_" ++ specialize))))
[exp]
recursive =
JsApp (JsName (JsBuiltIn (fromString method)))
[typeRep context typ
,exp]
js ty' =
JsNew (JsBuiltIn "Monad")
[translate method context ty' exp]
typeRep :: SerializeContext -> FundamentalType -> JsExp
typeRep context typ = case typ of
FunctionType xs -> JsList [JsLit $ JsStr "function",JsList (map (typeRep context) xs)]
JsType x -> JsList [JsLit $ JsStr "action",JsList [typeRep context x]]
ListType x -> JsList [JsLit $ JsStr "list",JsList [typeRep context x]]
TupleType xs -> JsList [JsLit $ JsStr "tuple",JsList (map (typeRep context) xs)]
UserDefined name xs -> JsList [JsLit $ JsStr "user"
,JsLit $ JsStr (unname name)
,JsList (zipWith (\t i -> typeRep (setArg i context) t) xs [0..])]
Defined x -> JsList [JsLit $ JsStr "defined",JsList [typeRep context x]]
Nullable x -> JsList [JsLit $ JsStr "nullable",JsList [typeRep context x]]
_ -> nom
where
setArg i SerializeUserArg{} = SerializeUserArg i
setArg _ c = c
ret = JsList . return . JsLit . JsStr
nom = case typ of
StringType -> ret "string"
DoubleType -> ret "double"
PtrType -> ret "ptr"
Automatic -> ret "automatic"
IntType -> ret "int"
BoolType -> ret "bool"
DateType -> ret "date"
_ ->
case context of
SerializeAnywhere -> ret "unknown"
SerializeUserArg i ->
let args = JsName argTypes
automatic = JsIndex 0 (JsName JsParametrizedType)
thisArg = JsIndex i args
in JsTernaryIf (JsInfix "&&" args thisArg)
thisArg
(JsTernaryIf (JsEq automatic (JsLit "automatic"))
(ret "automatic")
(ret "unknown"))
typeArity :: Type -> Int
typeArity t = case t of
TyForall _ _ i -> typeArity i
TyFun _ b -> 1 + typeArity b
TyParen st -> typeArity st
_ -> 0
formatFFI :: SrcLoc
-> String
-> [(JsName,FundamentalType)]
-> Compile String
formatFFI srcloc formatstr args = go formatstr where
go ('%':'*':xs) = do
these <- mapM inject (zipWith const [1..] args)
rest <- go xs
return (intercalate "," these ++ rest)
go ('%':'%':xs) = do
rest <- go xs
return ('%' : rest)
go ['%'] = throwError (FfiFormatIncompleteArg srcloc)
go ('%':(span isDigit -> (op,xs))) =
case readMay op of
Nothing -> throwError (FfiFormatBadChars srcloc op)
Just n -> do
this <- inject n
rest <- go xs
return (this ++ rest)
go (x:xs) = do rest <- go xs
return (x : rest)
go [] = return []
inject n =
case listToMaybe (drop (n1) args) of
Nothing -> throwError (FfiFormatNoSuchArg srcloc n)
Just (arg,typ) ->
return (printJSString (fayToJs SerializeAnywhere typ (JsName arg)))
explodeFields :: [([a], t)] -> [(a, t)]
explodeFields = concatMap $ \(names,typ) -> map (,typ) names
fayToJsHash :: [(String, JsExp)] -> [JsStmt]
fayToJsHash cases = [JsExpStmt $ JsApp (JsName $ JsBuiltIn "objConcat") [JsName $ JsBuiltIn "fayToJsHash", JsObj cases]]
jsToFayHash :: [(String, JsExp)] -> [JsStmt]
jsToFayHash cases = [JsExpStmt $ JsApp (JsName $ JsBuiltIn "objConcat") [JsName $ JsBuiltIn "jsToFayHash", JsObj cases]]
emitJsToFay :: Name -> [TyVarBind] -> [([Name], BangType)] -> Compile ()
emitJsToFay name tyvars (explodeFields -> fieldTypes) = do
qname <- qualify name
tell (mempty { writerJsToFay = [(printJSString name, translator qname)] })
where
translator qname =
JsFun Nothing [JsNameVar "type", argTypes, transcodingObj] []
(Just $ JsNew (JsConstructor qname)
(map (decodeField . getIndex name tyvars) fieldTypes))
decodeField :: (Int,(Name,BangType)) -> JsExp
decodeField (i,(fname,typ)) =
jsToFay (SerializeUserArg i)
(argType (bangType typ))
(JsGetPropExtern (JsName transcodingObj)
(prettyPrint fname))
argTypes :: JsName
argTypes = JsNameVar "argTypes"
getIndex :: Name -> [TyVarBind] -> (Name,BangType) -> (Int,(Name,BangType))
getIndex name tyvars (sname,ty) =
case bangType ty of
TyVar tyname -> case elemIndex tyname (map tyvar tyvars) of
Nothing -> error $ "unknown type variable " ++ prettyPrint tyname ++
" for " ++ prettyPrint name ++ "." ++ prettyPrint sname ++ "," ++
" vars were: " ++ unwords (map prettyPrint tyvars)
Just i -> (i,(sname,ty))
_ -> (0,(sname,ty))
tyvar :: TyVarBind -> Name
tyvar (UnkindedVar v) = v
tyvar (KindedVar v _) = v