{-# LANGUAGE CPP #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
#ifdef USE_TEMPLATE_HASKELL
{-# LANGUAGE TemplateHaskell #-}
#endif
#ifdef ghcjs_HOST_OS
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE JavaScriptFFI #-}
#endif
module Foreign.JavaScript.TH ( module Foreign.JavaScript.TH
#ifdef USE_TEMPLATE_HASKELL
, Safety (..)
#endif
) where
import Foreign.JavaScript.Orphans ()
import Prelude hiding ((!!))
import Reflex.Class
import Reflex.Adjustable.Class
import Reflex.DynamicWriter.Base
import Reflex.EventWriter.Base
import Reflex.Host.Class
import Reflex.PerformEvent.Base
import Reflex.PerformEvent.Class
import Reflex.PostBuild.Base
import Reflex.Requester.Base
import Reflex.Query.Base (QueryT (..))
#ifdef USE_TEMPLATE_HASKELL
import Language.Haskell.TH
#endif
import GHCJS.DOM.Types (JSContextRef, Node (..), askJSM)
#ifdef ghcjs_HOST_OS
import qualified GHCJS.Buffer as JS
import GHCJS.DOM.Types (MonadJSM)
import qualified GHCJS.DOM.Types as JS
import qualified GHCJS.Foreign as JS
import qualified GHCJS.Foreign.Callback as JS
import qualified GHCJS.Foreign.Callback.Internal (Callback (..))
import qualified JavaScript.Array as JS
import qualified JavaScript.Array.Internal (SomeJSArray (..))
import qualified JavaScript.Object as JS
import qualified JavaScript.Object.Internal (Object (..))
import qualified JavaScript.TypedArray.ArrayBuffer as JSArrayBuffer
import Data.Hashable
import Data.Word
import Foreign.C.Types
import Foreign.Ptr
import Text.Encoding.Z
#else
import Control.Lens.Operators ((^.))
import Data.Word (Word8)
import GHCJS.DOM.Types (JSVal, MonadJSM (..), liftJSM, runJSM, toJSString, toJSVal)
import Language.Javascript.JSaddle (Function (..), array, eval, freeFunction, function, js, js1, jss, valBool,
valIsNull, valIsUndefined, valMakeNumber, valMakeString, valToBool,
valToNumber, valToText, valUndefined, (!!))
#endif
import Control.Concurrent
import Control.Monad
import Control.Monad.Exception
import Control.Monad.Fix
import Control.Monad.IO.Class
import Control.Monad.Primitive
import Control.Monad.Reader
import Control.Monad.Ref
import Control.Monad.State
import qualified Control.Monad.State.Strict as Strict
import Control.Monad.Trans.Control
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Coerce (coerce)
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as T
class Monad m => HasJSContext m where
type JSContextPhantom m :: *
askJSContext :: m (JSContextSingleton (JSContextPhantom m))
type HasWebView = HasJSContext
instance HasJSContext m => HasJSContext (ReaderT r m) where
type JSContextPhantom (ReaderT r m) = JSContextPhantom m
askJSContext = lift askJSContext
instance HasJSContext m => HasJSContext (StateT r m) where
type JSContextPhantom (StateT r m) = JSContextPhantom m
askJSContext = lift askJSContext
instance HasJSContext m => HasJSContext (Strict.StateT r m) where
type JSContextPhantom (Strict.StateT r m) = JSContextPhantom m
askJSContext = lift askJSContext
instance HasJSContext m => HasJSContext (PostBuildT t m) where
type JSContextPhantom (PostBuildT t m) = JSContextPhantom m
askJSContext = lift askJSContext
instance (ReflexHost t, HasJSContext (HostFrame t)) => HasJSContext (PerformEventT t m) where
type JSContextPhantom (PerformEventT t m) = JSContextPhantom (HostFrame t)
askJSContext = PerformEventT $ lift askJSContext
instance HasJSContext m => HasJSContext (EventWriterT t w m) where
type JSContextPhantom (EventWriterT t w m) = JSContextPhantom m
askJSContext = lift askJSContext
instance HasJSContext m => HasJSContext (DynamicWriterT t w m) where
type JSContextPhantom (DynamicWriterT t w m) = JSContextPhantom m
askJSContext = lift askJSContext
instance HasJSContext m => HasJSContext (RequesterT t request response m) where
type JSContextPhantom (RequesterT t request response m) = JSContextPhantom m
askJSContext = lift askJSContext
instance HasJSContext m => HasJSContext (QueryT t q m) where
type JSContextPhantom (QueryT t q m) = JSContextPhantom m
askJSContext = QueryT askJSContext
newtype WithJSContextSingleton x m a = WithJSContextSingleton { unWithJSContextSingleton :: ReaderT (JSContextSingleton x) m a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadTrans, MonadException, MonadAsyncException)
instance PrimMonad m => PrimMonad (WithJSContextSingleton x m) where
type PrimState (WithJSContextSingleton x m) = PrimState m
primitive = lift . primitive
instance Adjustable t m => Adjustable t (WithJSContextSingleton x m) where
runWithReplace a0 a' = WithJSContextSingleton $ runWithReplace (coerce a0) (coerceEvent a')
traverseIntMapWithKeyWithAdjust f dm0 dm' = WithJSContextSingleton $ traverseIntMapWithKeyWithAdjust (\k v -> unWithJSContextSingleton $ f k v) (coerce dm0) (coerceEvent dm')
traverseDMapWithKeyWithAdjust f dm0 dm' = WithJSContextSingleton $ traverseDMapWithKeyWithAdjust (\k v -> unWithJSContextSingleton $ f k v) (coerce dm0) (coerceEvent dm')
traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = WithJSContextSingleton $ traverseDMapWithKeyWithAdjustWithMove (\k v -> unWithJSContextSingleton $ f k v) (coerce dm0) (coerceEvent dm')
instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (WithJSContextSingleton x m) where
{-# INLINABLE newEventWithTrigger #-}
newEventWithTrigger = lift . newEventWithTrigger
{-# INLINABLE newFanEventWithTrigger #-}
newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
instance MonadSubscribeEvent t m => MonadSubscribeEvent t (WithJSContextSingleton x m) where
{-# INLINABLE subscribeEvent #-}
subscribeEvent = lift . subscribeEvent
instance MonadReflexHost t m => MonadReflexHost t (WithJSContextSingleton x m) where
type ReadPhase (WithJSContextSingleton x m) = ReadPhase m
{-# INLINABLE fireEventsAndRead #-}
fireEventsAndRead dm a = lift $ fireEventsAndRead dm a
{-# INLINABLE runHostFrame #-}
runHostFrame = lift . runHostFrame
instance MonadSample t m => MonadSample t (WithJSContextSingleton x m) where
{-# INLINABLE sample #-}
sample = lift . sample
instance MonadHold t m => MonadHold t (WithJSContextSingleton x m) where
{-# INLINABLE hold #-}
hold v0 = lift . hold v0
{-# INLINABLE holdDyn #-}
holdDyn v0 = lift . holdDyn v0
{-# INLINABLE holdIncremental #-}
holdIncremental v0 = lift . holdIncremental v0
{-# INLINABLE buildDynamic #-}
buildDynamic a0 = lift . buildDynamic a0
{-# INLINABLE headE #-}
headE = lift . headE
instance MonadTransControl (WithJSContextSingleton x) where
type StT (WithJSContextSingleton x) a = StT (ReaderT (JSContextSingleton x)) a
{-# INLINABLE liftWith #-}
liftWith = defaultLiftWith WithJSContextSingleton unWithJSContextSingleton
{-# INLINABLE restoreT #-}
restoreT = defaultRestoreT WithJSContextSingleton
instance PerformEvent t m => PerformEvent t (WithJSContextSingleton x m) where
type Performable (WithJSContextSingleton x m) = WithJSContextSingleton x (Performable m)
{-# INLINABLE performEvent_ #-}
performEvent_ e = liftWith $ \run -> performEvent_ $ fmap run e
{-# INLINABLE performEvent #-}
performEvent e = liftWith $ \run -> performEvent $ fmap run e
runWithJSContextSingleton :: WithJSContextSingleton x m a -> JSContextSingleton x -> m a
runWithJSContextSingleton = runReaderT . unWithJSContextSingleton
instance (Monad m) => HasJSContext (WithJSContextSingleton x m) where
type JSContextPhantom (WithJSContextSingleton x m) = x
askJSContext = WithJSContextSingleton ask
instance MonadRef m => MonadRef (WithJSContextSingleton x m) where
type Ref (WithJSContextSingleton x m) = Ref m
newRef = lift . newRef
readRef = lift . readRef
writeRef r = lift . writeRef r
instance MonadAtomicRef m => MonadAtomicRef (WithJSContextSingleton x m) where
atomicModifyRef r = lift . atomicModifyRef r
withJSContextSingleton :: MonadJSM m => (forall x. JSContextSingleton x -> m r) -> m r
withJSContextSingleton f = askJSM >>= f . JSContextSingleton
withJSContextSingletonMono :: MonadJSM m => (JSContextSingleton () -> m r) -> m r
withJSContextSingletonMono f = askJSM >>= f . JSContextSingleton
newtype JSContextSingleton x = JSContextSingleton { unJSContextSingleton :: JSContextRef }
#ifdef ghcjs_HOST_OS
type JSFFI_Internal = JS.MutableJSArray -> IO JS.JSVal
newtype JSFFI = JSFFI JSFFI_Internal
#else
newtype JSFFI = JSFFI String
#endif
data JSFun x = JSFun { unJSFun :: JSRef x
#ifndef ghcjs_HOST_OS
, unJSFunction :: Function
#endif
}
instance ToJS x (JSFun x) where
withJS r f = f (unJSFun r)
class IsJSContext x where
data JSRef x
class (Monad m, MonadJSM (JSX m), MonadFix (JSX m), MonadJS x (JSX m)) => HasJS x m | m -> x where
type JSX m :: * -> *
liftJS :: JSX m a -> m a
instance HasJS x m => HasJS x (ReaderT r m) where
type JSX (ReaderT r m) = JSX m
liftJS = lift . liftJS
instance HasJS x m => HasJS x (PostBuildT t m) where
type JSX (PostBuildT t m) = JSX m
liftJS = lift . liftJS
instance (HasJS x (HostFrame t), ReflexHost t) => HasJS x (PerformEventT t m) where
type JSX (PerformEventT t m) = JSX (HostFrame t)
liftJS = PerformEventT . lift . liftJS
instance HasJS x m => HasJS x (DynamicWriterT t w m) where
type JSX (DynamicWriterT t w m) = JSX m
liftJS = lift . liftJS
instance HasJS x m => HasJS x (EventWriterT t w m) where
type JSX (EventWriterT t w m) = JSX m
liftJS = lift . liftJS
instance HasJS x m => HasJS x (RequesterT t request response m) where
type JSX (RequesterT t request response m) = JSX m
liftJS = lift . liftJS
instance HasJS x m => HasJS x (QueryT t q m) where
type JSX (QueryT t q m) = JSX m
liftJS = lift . liftJS
-- | A Monad that is capable of executing JavaScript
class Monad m => MonadJS x m | m -> x where
runJS :: JSFFI -> [JSRef x] -> m (JSRef x)
forkJS :: m () -> m ThreadId
mkJSUndefined :: m (JSRef x)
isJSNull :: JSRef x -> m Bool
isJSUndefined :: JSRef x -> m Bool
fromJSBool :: JSRef x -> m Bool
fromJSString :: JSRef x -> m String
fromJSArray :: JSRef x -> m [JSRef x]
fromJSUint8Array :: JSRef x -> m ByteString
fromJSNumber :: JSRef x -> m Double
withJSBool :: Bool -> (JSRef x -> m r) -> m r
withJSString :: String -> (JSRef x -> m r) -> m r
withJSNumber :: Double -> (JSRef x -> m r) -> m r
withJSArray :: [JSRef x] -> (JSRef x -> m r) -> m r
withJSUint8Array :: ByteString -> (JSUint8Array x -> m r) -> m r
-- | Create a JSFun with zero arguments; should be equilvant to `syncCallback AlwaysRetain True` in GHCJS
mkJSFun :: ([JSRef x] -> m (JSRef x)) -> m (JSFun x) --TODO: Support 'this', exceptions
freeJSFun :: JSFun x -> m ()
setJSProp :: String -> JSRef x -> JSRef x -> m ()
getJSProp :: String -> JSRef x -> m (JSRef x)
withJSNode :: Node -> (JSRef x -> m r) -> m r
#ifdef ghcjs_HOST_OS
data JSCtx_IO
type JS' = JSCtx_IO
instance MonadIO m => HasJS JSCtx_IO (WithJSContextSingleton x m) where
type JSX (WithJSContextSingleton x m) = IO
liftJS = liftIO
instance IsJSContext JSCtx_IO where
newtype JSRef JSCtx_IO = JSRef_IO { unJSRef_IO :: JS.JSVal }
instance MonadJS JSCtx_IO IO where
runJS (JSFFI f) l = fmap JSRef_IO . f =<< JS.fromListIO (coerce l)
forkJS = forkIO
mkJSUndefined = return $ JSRef_IO JS.jsUndefined
isJSNull (JSRef_IO r) = return $ JS.isNull r
isJSUndefined (JSRef_IO r) = return $ JS.isUndefined r
fromJSBool (JSRef_IO r) = return $ JS.fromJSBool r
fromJSString (JSRef_IO r) = return $ JS.fromJSString $ JS.pFromJSVal r
fromJSArray (JSRef_IO r) = fmap coerce $ JS.toListIO $ coerce r
fromJSUint8Array (JSRef_IO r) = fmap (JS.toByteString 0 Nothing . JS.createFromArrayBuffer) $ JSArrayBuffer.unsafeFreeze $ JS.pFromJSVal r
fromJSNumber (JSRef_IO r) = JS.fromJSValUnchecked r
withJSBool b f = f $ JSRef_IO $ JS.toJSBool b
withJSString s f = f $ JSRef_IO $ JS.pToJSVal $ JS.toJSString s
withJSNumber n f = do
r <- JS.toJSVal n
f $ JSRef_IO r
withJSArray l f = do
r <- JS.fromListIO $ coerce l
f $ JSRef_IO $ coerce r
withJSUint8Array payload f = BS.useAsCString payload $ \cStr -> do
ba <- extractByteArray cStr $ BS.length payload
f $ JSUint8Array $ JSRef_IO ba
mkJSFun f = do
cb <- JS.syncCallback1' $ \args -> do
l <- JS.toListIO $ coerce args
JSRef_IO result <- f $ coerce l
return result
fmap (JSFun . JSRef_IO) $ funWithArguments $ coerce cb
freeJSFun (JSFun (JSRef_IO r)) = JS.releaseCallback $ coerce r
setJSProp s (JSRef_IO v) (JSRef_IO o) = JS.setProp (JS.toJSString s) v $ coerce o
getJSProp s (JSRef_IO o) = do
r <- JS.getProp (JS.toJSString s) $ coerce o
return $ JSRef_IO r
withJSNode n f = f $ JSRef_IO $ unNode n
foreign import javascript unsafe "new Uint8Array($1_1.buf, $1_2, $2)" extractByteArray :: Ptr CChar -> Int -> IO JS.JSVal
foreign import javascript unsafe "function(){ return $1(arguments); }" funWithArguments :: JS.Callback (JS.MutableJSArray -> IO a) -> IO JS.JSVal
#else
data JSCtx_JavaScriptCore x
type JS' = JSCtx_JavaScriptCore ()
instance IsJSContext (JSCtx_JavaScriptCore x) where
newtype JSRef (JSCtx_JavaScriptCore x) = JSRef_JavaScriptCore { unJSRef_JavaScriptCore :: JSVal }
instance MonadIO m => HasJS (JSCtx_JavaScriptCore x) (WithJSContextSingleton x m) where
type JSX (WithJSContextSingleton x m) = WithJSContextSingleton x IO
liftJS a = do
wv <- askJSContext
liftIO $ runWithJSContextSingleton a wv
newtype WithJSContext x m a = WithJSContext { unWithJSContext :: ReaderT JSContextRef m a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadTrans, MonadException, MonadAsyncException)
runWithJSContext :: WithJSContext x m a -> JSContextRef -> m a
runWithJSContext = runReaderT . unWithJSContext
instance MonadIO m => MonadJSM (WithJSContextSingleton x m) where
liftJSM' f = do
wv <- askJSContext
runJSM f $ unJSContextSingleton wv
instance MonadIO m => MonadJSM (WithJSContext x m) where
liftJSM' f =
runJSM f =<< WithJSContext ask
lowerWithJSContext :: MonadJSM m => WithJSContext x IO a -> m a
lowerWithJSContext a = do
c <- askJSM
liftIO $ runWithJSContext a c
liftWithJSContextSingletonThroughWithJSContext :: (HasJSContext m, MonadJSM m, MonadTrans t, Monad m1)
=> ((t1 -> t m1 a) -> WithJSContext x IO b)
-> (t1 -> WithJSContextSingleton (JSContextPhantom m) m1 a)
-> m b
liftWithJSContextSingletonThroughWithJSContext f a = do
wv <- askJSContext
lowerWithJSContext $ f $ \b' -> lift $ runWithJSContextSingleton (a b') wv
instance MonadJS (JSCtx_JavaScriptCore x) (WithJSContextSingleton x IO) where
forkJS a = do
wv <- askJSContext
liftIO $ forkIO $ runWithJSContextSingleton a wv
mkJSFun a = do
wv <- askJSContext
lowerWithJSContext $ mkJSFun $ \args -> lift $ runWithJSContextSingleton (a args) wv
runJS expr args = lowerWithJSContext $ runJS expr args
mkJSUndefined = lowerWithJSContext mkJSUndefined
isJSNull = lowerWithJSContext . isJSNull
isJSUndefined = lowerWithJSContext . isJSUndefined
fromJSBool = lowerWithJSContext . fromJSBool
fromJSString = lowerWithJSContext . fromJSString
fromJSArray = lowerWithJSContext . fromJSArray
fromJSUint8Array = lowerWithJSContext . fromJSUint8Array
fromJSNumber = lowerWithJSContext . fromJSNumber
freeJSFun = lowerWithJSContext . freeJSFun
withJSBool = liftWithJSContextSingletonThroughWithJSContext . withJSBool
withJSString = liftWithJSContextSingletonThroughWithJSContext . withJSString
withJSNumber = liftWithJSContextSingletonThroughWithJSContext . withJSNumber
withJSArray = liftWithJSContextSingletonThroughWithJSContext . withJSArray
withJSUint8Array = liftWithJSContextSingletonThroughWithJSContext . withJSUint8Array
withJSNode = liftWithJSContextSingletonThroughWithJSContext . withJSNode
setJSProp propName valRef objRef = lowerWithJSContext $ setJSProp propName valRef objRef
getJSProp propName objRef = lowerWithJSContext $ getJSProp propName objRef
instance MonadJS (JSCtx_JavaScriptCore x) (WithJSContext x IO) where
runJS (JSFFI body) args =
withJSArray args $ \(JSRef_JavaScriptCore this) -> do
result <- liftJSM $ eval ("(function(){ return (" <> body <> "); })") ^. js1 "apply" this
return $ JSRef_JavaScriptCore result
forkJS a = do
c <- askJSM
liftIO . forkIO $ runWithJSContext a c
mkJSUndefined = return $ JSRef_JavaScriptCore valUndefined
isJSNull (JSRef_JavaScriptCore r) = liftJSM $ valIsNull r
isJSUndefined (JSRef_JavaScriptCore r) = liftJSM $ valIsUndefined r
fromJSBool (JSRef_JavaScriptCore r) = liftJSM $ valToBool r
fromJSString (JSRef_JavaScriptCore r) = liftJSM (T.unpack <$> valToText r)
withJSBool b a = a $ JSRef_JavaScriptCore (valBool b)
withJSString str a = a . JSRef_JavaScriptCore =<< liftJSM (valMakeString $ toJSString str)
withJSNumber n a = a . JSRef_JavaScriptCore =<< liftJSM (valMakeNumber n)
withJSArray elems a = a . JSRef_JavaScriptCore =<< liftJSM
(toJSVal =<< array (map (\(JSRef_JavaScriptCore r) -> r) elems))
withJSUint8Array payload f = withJSArrayFromList (BS.unpack payload) $ \x -> do
payloadRef <- runJS (JSFFI "new Uint8Array(this[0])") [x]
f $ JSUint8Array payloadRef
fromJSArray (JSRef_JavaScriptCore a) = liftJSM $ do
len <- round <$> (valToNumber =<< (a ^. js "length"))
forM [0..len-1] $ fmap JSRef_JavaScriptCore . (a !!)
fromJSUint8Array a = do
vals <- fromJSArray a
doubles <- mapM fromJSNumber vals
return $ BS.pack $ map round doubles
fromJSNumber (JSRef_JavaScriptCore val) = liftJSM $ valToNumber val
mkJSFun a = liftJSM $ do
ctx <- askJSM
f <- function $ \_ _ args -> liftIO $ void $ runWithJSContext (a $ map JSRef_JavaScriptCore args) ctx
fRef <- toJSVal f
return $ JSFun (JSRef_JavaScriptCore fRef) f
freeJSFun (JSFun _ f) = liftJSM $ freeFunction f
setJSProp propName (JSRef_JavaScriptCore valRef) (JSRef_JavaScriptCore objRef) =
liftJSM $ objRef ^. jss propName valRef
getJSProp propName (JSRef_JavaScriptCore objRef) =
JSRef_JavaScriptCore <$> liftJSM (objRef ^. js propName)
withJSNode n f = f . JSRef_JavaScriptCore =<< liftJSM (toJSVal n)
#endif
class FromJS x a where
fromJS :: MonadJS x m => JSRef x -> m a
instance FromJS x () where
fromJS _ = return ()
instance FromJS x Bool where
fromJS = fromJSBool
instance ToJS x Bool where
withJS = withJSBool
instance FromJS x String where
fromJS = fromJSString
instance FromJS x Text where
fromJS s = T.pack <$> fromJSString s
instance FromJS x a => FromJS x (Maybe a) where
fromJS x = do
n <- isJSNull x
if n then return Nothing else Just <$> fromJS x
class ToJS x a where
withJS :: MonadJS x m => a -> (JSRef x -> m r) -> m r
instance ToJS x (JSRef x) where
withJS r = ($ r)
instance FromJS x (JSRef x) where
fromJS = return
instance ToJS x String where
withJS = withJSString
instance ToJS x Text where
withJS = withJSString . T.unpack
newtype JSArray a = JSArray { unJSArray :: [a] }
instance ToJS x a => ToJS x (JSArray a) where
withJS = withJSArrayFromList . unJSArray
instance FromJS x a => FromJS x (JSArray a) where
fromJS = fmap JSArray . mapM fromJS <=< fromJSArray
withJSArrayFromList :: (ToJS x a, MonadJS x m) => [a] -> (JSRef x -> m r) -> m r
withJSArrayFromList as f = go as []
where go [] jsRefs = withJSArray (reverse jsRefs) f
go (h:t) jsRefs = withJS h $ \hRef -> go t (hRef : jsRefs)
newtype JSUint8Array x = JSUint8Array { unJSUint8Array :: JSRef x }
instance ToJS x (JSUint8Array x) where
withJS (JSUint8Array r) = ($ r)
instance FromJS x (JSUint8Array x) where
fromJS = return . JSUint8Array
instance ToJS x Word8 where
withJS n = withJSNumber $ fromIntegral n
instance ToJS x Int where
withJS n = withJSNumber $ fromIntegral n
instance FromJS x Int where
fromJS = fmap round . fromJSNumber
instance ToJS x Double where
withJS = withJSNumber
instance FromJS x Double where
fromJS = fromJSNumber
instance ToJS x Node where
withJS = withJSNode
#ifdef USE_TEMPLATE_HASKELL
importJS :: Safety -> String -> String -> Q Type -> Q [Dec]
importJS safety body name qt = do
t <- qt
let (argTypes, _) = parseType t
argNames <- forM argTypes $ \_ -> do
arg <- newName "arg"
argRef <- newName "argRef"
return (arg, argRef)
(jsffiDecs, jsffiExp) <- mkJSFFI safety body
let go [] = [| runJS $(return jsffiExp) $(listE $ map (varE . snd) argNames) >>= fromJS
|]
go ((arg, argRef) : args) = [| withJS $(varE arg) $ $(lamE [varP argRef] $ go args) |]
e <- lamE (map (varP. fst) argNames) $ go argNames
let n = mkName name
return $ jsffiDecs ++
[ SigD n t
, ValD (VarP n) (NormalB e) []
]
mkJSFFI :: Safety -> String -> Q ([Dec], Exp)
#ifdef ghcjs_HOST_OS
mkJSFFI safety body = do
l <- location
n <- newName $ "jsffi_" <> zEncodeString (loc_package l <> ":" <> loc_module l) <> "_" <> show (abs (hash (show safety, body)))
t <- [t| JSFFI_Internal |]
let wrappedBody = "(function(){ return (" <> body <> "); }).apply($1)"
let decs = [ForeignD $ ImportF JavaScript safety wrappedBody n t]
e <- [| JSFFI $(varE n) |]
return (decs, e)
#else
mkJSFFI _ body = do
e <- [| JSFFI body |]
return ([], e)
#endif
parseType :: Type -> ([Type], Type)
parseType (ForallT _ [AppT (AppT (ConT monadJs) (VarT _)) (VarT m)] funType)
| monadJs == ''MonadJS = go funType
where go t = case t of
AppT (AppT ArrowT arg) t' ->
let (args, result) = go t'
in (arg : args, result)
AppT (VarT m') result
| m' == m -> ([], result)
_ -> error $ "parseType: can't parse type " <> show t
parseType t = error $ "parseType: can't parse type " <> show t
#endif