ghcjs-vdom-0.2.0.0: src/GHCJS/VDOM/Internal.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UnliftedFFITypes #-}
{-# LANGUAGE GHCForeignImportPrim #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE ScopedTypeVariables #-}
module GHCJS.VDOM.Internal where
import GHCJS.VDOM.Internal.Types
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Language.Haskell.TH.Syntax
import GHC.Prim (Any, State#, RealWorld)
import Control.Monad
import Unsafe.Coerce
import GHCJS.Foreign.QQ
import GHCJS.Types
import GHCJS.Marshal.Pure
import Data.List (foldl')
import Data.String (IsString(..))
import Data.Typeable
import GHC.IO ( IO(..) )
import GHC.Base ( StableName# )
type J = JSVal
j :: QuasiQuoter
j = jsu'
mkVNode :: (Attributes a, Children c) => JSString -> a -> c -> VNode
mkVNode tag atts children = js_vnode tag (mkAttributes atts) (mkChildren children)
{-# INLINE mkVNode #-}
mkElems :: [String] -> Q [Dec]
mkElems = fmap concat . mapM (join mkElem)
mkElem :: String -> String -> Q [Dec]
mkElem name tag = do
let n = mkName name
a <- newName "a"
c <- newName "c"
b <- [| mkVNode (fromString tag) |]
typ <- [t|forall a c. (Attributes a, Children c) => a -> c -> VNode |]
return [ SigD n typ
, FunD n [Clause [VarP a, VarP c] (NormalB (AppE (AppE b (VarE a)) (VarE c))) []]
, PragmaD (InlineP n Inline FunLike AllPhases)
]
mkAttrs :: Name -> [String] -> Q [Dec]
mkAttrs ty = fmap concat . mapM (join (mkAttr ty))
mkAttrs' :: Name -> [(String, String)] -> Q [Dec]
mkAttrs' ty = fmap concat . mapM (uncurry (mkAttr ty))
mkAttr :: Name -> String -> String -> Q [Dec]
mkAttr ty name attr = do
let n = mkName name
x <- newName "x"
b <- [| \y -> Attribute attr (pToJSVal y) |]
return [ SigD n (AppT (AppT ArrowT (ConT ty)) (ConT ''Attribute))
, FunD n [Clause [VarP x] (NormalB (AppE b (VarE x))) []]
, PragmaD (InlineP n Inline FunLike AllPhases)
]
mkEventTypes :: Name -> [(String, [Name])] -> Q [Dec]
mkEventTypes base = fmap concat . mapM mk
where
mk (n, cls) = do
let nn = mkName n
#if MIN_VERSION_template_haskell(2,11,0)
mkI cn = InstanceD Nothing [] (AppT (ConT cn) (ConT nn)) []
#else
mkI cn = InstanceD [] (AppT (ConT cn) (ConT nn)) []
#endif
insts = map mkI (base : cls)
jsr <- [t| JSVal |]
typ <- [t| Typeable |]
#if MIN_VERSION_template_haskell(2,11,0)
return $ (NewtypeD [] nn [] Nothing (NormalC nn [(Bang NoSourceUnpackedness NoSourceStrictness, jsr)]) [ typ ]) : insts
#else
return $ (NewtypeD [] nn [] (NormalC nn [(NotStrict, jsr)]) [''Typeable]) : insts
#endif
newtype CreatedEvents = CreatedEvents { unCreatedEvents :: [String] }
deriving (Typeable)
addCreatedEvent :: String -> CreatedEvents -> CreatedEvents
addCreatedEvent ev (CreatedEvents es) = CreatedEvents (ev:es)
-- dcon must be a newtype constructor, not a data con
mkEvents :: Name -> [String] -> Q [Dec]
mkEvents dcon xs = fmap concat (mapM (\x -> mkEvent dcon x ("ev-"++x)) xs)
-- dcon must be a newtype constructor, not a data con
mkEvent :: Name -> String -> String -> Q [Dec]
mkEvent dcon name attr = do
let n = mkName name
emsg = "GHCJS.VDOM.Internal.mkEvent: expected newtype constructor"
i <- reify dcon
dctyp <- case i of
#if MIN_VERSION_template_haskell(2,11,0)
DataConI _ _ pn -> do
pni <- reify pn
case pni of
TyConI (NewtypeD _ ctn _ _ _ _) -> return (ConT ctn)
_ -> error emsg
_ -> error emsg
#else
DataConI _ _ pn _ -> do
pni <- reify pn
case pni of
TyConI (NewtypeD _ ctn _ _ _) -> return (ConT ctn)
_ -> error emsg
_ -> error emsg
#endif
iou <- [t| IO () |]
h <- newName "h"
b <- [| mkEventAttr (fromString attr) |]
let ht = AppT (AppT ArrowT dctyp) iou
-- typ <- [t| (dctyp -> IO ()) -> Attribute |]
qPutQ . maybe (CreatedEvents [name]) (addCreatedEvent name) =<< qGetQ
return [ SigD n (AppT (AppT ArrowT ht) (ConT ''Attribute))
, FunD n [Clause [VarP h] (NormalB (AppE (AppE b (ConE dcon)) (VarE h))) []]
, PragmaD (InlineP n Inline FunLike AllPhases)
]
-- a must be a newtype of JSVal!
mkEventAttr :: JSString -> (JSVal -> a) -> (a -> IO ()) -> Attribute
mkEventAttr attr _wrap h =
let e = unsafeExportValue h
h' = [js'| h$vdom.makeHandler(`e, false) |]
in h' `seq` Attribute attr h'
{-# INLINE mkEventAttr #-}
{-
eventLogger :: JSVal ()
eventLogger = [js'| function(ev) { console.log("event caught"); } |]
-}
-- generate a list of all events stored in the persistent TH state, created with mkEvent
mkDefaultEvents :: Q Exp
mkDefaultEvents = do
evs <- maybe [] unCreatedEvents <$> qGetQ
nil <- [| [] |]
cons <- [| (:) |]
return $ foldl' (\xs e -> AppE (AppE cons (LitE . stringL $ e)) xs) nil evs
js_vnode :: JSString -> Attributes' -> Children' -> VNode
js_vnode tag (Attributes' props) (Children' children) =
VNode [jsu'| h$vdom.v(`tag, `props, `children) |]
--VNode [jsu'| new h$vdom.VNode(`tag, `props, `children) |]
getThunk :: J -> IO J
getThunk x = IO (js_getThunk x)
foreign import javascript unsafe "$r = $1.hst;"
js_getThunk :: J -> State# RealWorld -> (# State# RealWorld, J #)
-- -----------------------------------------------------------------------------
{-|
Export an arbitrary Haskell value to JS.
be careful with these JSVal values, losing track of them will result in
incorrect memory management. As long as we keep the values directly in
a Property or VNode, the ghcjs-vdom extensible retention system will know
where to find them.
-}
unsafeExportValue :: a -> JSVal
unsafeExportValue x = js_export (unsafeCoerce x)
{-# INLINE unsafeExportValue #-}
{-|
make a unique identifier that can be easily compared in JS
if(objectIdent(o1) === objectIdent(o2) or both are NaN, then o1 and o2 are
are the same Haskell value
-}
objectIdent :: a -> JSIdent
objectIdent x = x `seq` js_makeObjectIdent (unsafeExportValue x)
{-
unsafePerformIO . IO $ \s ->
case makeStableName# x s of (# s', sn #) -> (# s', js_convertSn sn #)
-}
{-# INLINE objectIdent #-}
foreign import javascript unsafe "$r = $1;" js_export :: Any -> JSVal
foreign import javascript unsafe "$r = $1;" js_convertSn :: StableName# a -> JSIdent
foreign import javascript unsafe "h$makeStableName($1)" js_makeObjectIdent :: JSVal -> JSIdent