PageRenderTime 27ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/ghcjs-th/GHCJS/Prim/TH/Eval.hs

http://github.com/ghcjs/ghcjs
Haskell | 305 lines | 242 code | 45 blank | 18 comment | 2 complexity | c3324b1e05de297c34ee274d9b0ca9f9 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. {-# LANGUAGE CPP, LambdaCase, BangPatterns, MagicHash, TupleSections, ScopedTypeVariables, DeriveDataTypeable #-}
  2. #ifdef ghcjs_HOST_OS
  3. {-# LANGUAGE JavaScriptFFI #-}
  4. #endif
  5. {- |
  6. Evaluate Template Haskell splices on node.js
  7. -}
  8. module GHCJS.Prim.TH.Eval (
  9. #ifdef ghcjs_HOST_OS
  10. runTHServer
  11. #endif
  12. ) where
  13. #ifdef ghcjs_HOST_OS
  14. import GHCJS.Prim.TH.Serialized
  15. import GHCJS.Prim.TH.Types
  16. import Control.Applicative
  17. import qualified Control.Exception as E
  18. import Control.Monad
  19. import Control.Monad.Fail
  20. import Control.Monad.IO.Class
  21. import Data.Binary
  22. import Data.Binary.Get
  23. import Data.Binary.Put
  24. import Data.ByteString (ByteString)
  25. import qualified Data.ByteString as B
  26. import qualified Data.ByteString.Internal as BI
  27. import qualified Data.ByteString.Lazy as BL
  28. import qualified Data.ByteString.Unsafe as BU
  29. import Data.Data
  30. import Data.Dynamic
  31. import Data.Int
  32. import Data.IORef
  33. import Data.Map (Map)
  34. import qualified Data.Map as M
  35. import Data.Maybe
  36. import Data.Monoid ((<>))
  37. import Data.Typeable
  38. import Data.Word
  39. import Foreign.C
  40. import Foreign.Ptr
  41. import GHC.Prim
  42. import GHC.Exts
  43. import GHC.Desugar
  44. import GHC.Fingerprint.Type
  45. import qualified Language.Haskell.TH as TH
  46. import qualified Language.Haskell.TH.Syntax as TH
  47. import System.IO
  48. import Unsafe.Coerce
  49. data QState = QState { qsMap :: Map TypeRep Dynamic -- ^ persistent data between splices in a module
  50. , qsFinalizers :: [TH.Q ()] -- ^ registered finalizers (in reverse order)
  51. , qsLocation :: Maybe TH.Loc -- ^ location for current splice, if any
  52. }
  53. instance Show QState where show _ = "<QState>"
  54. initQState :: QState
  55. initQState = QState M.empty [] Nothing
  56. runModFinalizers :: GHCJSQ ()
  57. runModFinalizers = go =<< getState
  58. where
  59. go s | (f:ff) <- qsFinalizers s =
  60. putState (s { qsFinalizers = ff}) >> TH.runQ f >> getState >>= go
  61. go _ = return ()
  62. data GHCJSQ a = GHCJSQ { runGHCJSQ :: QState -> IO (a, QState) }
  63. data GHCJSQException = GHCJSQException QState (Maybe Int) String
  64. deriving (Show, Typeable)
  65. instance E.Exception GHCJSQException
  66. instance Functor GHCJSQ where
  67. fmap f (GHCJSQ s) = GHCJSQ $ fmap (\(x,s') -> (f x,s')) . s
  68. instance Applicative GHCJSQ where
  69. f <*> a = GHCJSQ $ \s ->
  70. do (f',s') <- runGHCJSQ f s
  71. (a',s'') <- runGHCJSQ a s'
  72. return (f' a', s'')
  73. pure x = GHCJSQ (\s -> return (x,s))
  74. instance Monad GHCJSQ where
  75. m >>= f = GHCJSQ $ \s ->
  76. do (m', s') <- runGHCJSQ m s
  77. (a, s'') <- runGHCJSQ (f m') s'
  78. return (a, s'')
  79. return = pure
  80. instance MonadFail GHCJSQ where
  81. fail err = GHCJSQ $ \s -> E.throw (GHCJSQException s Nothing err)
  82. instance MonadIO GHCJSQ where
  83. liftIO m = GHCJSQ $ \s -> fmap (,s) m
  84. getState :: GHCJSQ QState
  85. getState = GHCJSQ $ \s -> return (s,s)
  86. putState :: QState -> GHCJSQ ()
  87. putState s = GHCJSQ $ \_ -> return ((),s)
  88. noLoc :: TH.Loc
  89. noLoc = TH.Loc "<no file>" "<no package>" "<no module>" (0,0) (0,0)
  90. instance TH.Quasi GHCJSQ where
  91. qNewName str = do
  92. NewName' name <- sendRequestQ (NewName str)
  93. return name
  94. qReport isError msg = do
  95. Report' <- sendRequestQ (Report isError msg)
  96. return ()
  97. qRecover (GHCJSQ h) (GHCJSQ a) = GHCJSQ $ \s -> do
  98. let r :: Bool -> IO ()
  99. r b = do EndRecover' <- sendRequest (EndRecover b)
  100. return ()
  101. StartRecover' <- sendRequest StartRecover
  102. (a s >>= \s' -> r False >> return s') `E.catch`
  103. \(GHCJSQException s' _ _) -> r True >> h s
  104. qLookupName isType occ = do
  105. LookupName' name <- sendRequestQ (LookupName isType occ)
  106. return name
  107. qReify name = do
  108. Reify' info <- sendRequestQ (Reify name)
  109. return info
  110. qReifyInstances name tys = do
  111. ReifyInstances' decls <- sendRequestQ (ReifyInstances name tys)
  112. return decls
  113. qReifyRoles name = do
  114. ReifyRoles' roles <- sendRequestQ (ReifyRoles name)
  115. return roles
  116. qReifyAnnotations lookup = do
  117. ReifyAnnotations' payloads <- sendRequestQ (ReifyAnnotations lookup)
  118. return (convertAnnPayloads payloads)
  119. qReifyModule m = do
  120. ReifyModule' mi <- sendRequestQ (ReifyModule m)
  121. return mi
  122. qReifyFixity m = do
  123. ReifyFixity' mi <- sendRequestQ (ReifyFixity m)
  124. return mi
  125. qReifyConStrictness name = do
  126. ReifyConStrictness' ss <- sendRequestQ (ReifyConStrictness name)
  127. return ss
  128. qAddForeignFilePath lang contents = do
  129. AddForeignFilePath' <- sendRequestQ (AddForeignFilePath lang contents)
  130. return ()
  131. qIsExtEnabled ext = do
  132. IsExtEnabled' b <- sendRequestQ (IsExtEnabled ext)
  133. return b
  134. qExtsEnabled = do
  135. ExtsEnabled' exts <- sendRequestQ ExtsEnabled
  136. return exts
  137. qLocation = fromMaybe noLoc . qsLocation <$> getState
  138. qRunIO m = GHCJSQ $ \s -> fmap (,s) m
  139. qAddDependentFile file = do
  140. AddDependentFile' <- sendRequestQ (AddDependentFile file)
  141. return ()
  142. qAddTempFile xs = do
  143. AddTempFile' path <- sendRequestQ (AddTempFile xs)
  144. return path
  145. qAddTopDecls decls = do
  146. AddTopDecls' <- sendRequestQ (AddTopDecls decls)
  147. return ()
  148. qAddModFinalizer fin = GHCJSQ $ \s ->
  149. return ((), s { qsFinalizers = fin : qsFinalizers s })
  150. qGetQ = GHCJSQ $ \s ->
  151. let lookup :: forall a. Typeable a => Map TypeRep Dynamic -> Maybe a
  152. lookup m = fromDynamic =<< M.lookup (typeOf (undefined::a)) m
  153. in return (lookup (qsMap s), s)
  154. qPutQ k = GHCJSQ $ \s ->
  155. return ((), s { qsMap = M.insert (typeOf k) (toDyn k) (qsMap s) })
  156. qAddCorePlugin plugin = do
  157. AddCorePlugin' <- sendRequestQ (AddCorePlugin plugin)
  158. return ()
  159. makeAnnPayload :: forall a. Data a => a -> ByteString
  160. makeAnnPayload x =
  161. let Fingerprint w1 w2 = typeRepFingerprint (typeOf (undefined :: a))
  162. fp = runPut (putWord64be w1 >> putWord64be w2)
  163. in BL.toStrict $ fp <> BL.pack (serializeWithData x)
  164. convertAnnPayloads :: forall a. Data a => [ByteString] -> [a]
  165. convertAnnPayloads bs = catMaybes (map convert bs)
  166. where
  167. Fingerprint w1 w2 = typeRepFingerprint (typeOf (undefined :: a))
  168. getFp b = runGet ((,) <$> getWord64be <*> getWord64be) $ BL.fromStrict (B.take 16 b)
  169. convert b | (bw1,bw2) <- getFp b, bw1 == w1, bw2 == w2 =
  170. Just (deserializeWithData . B.unpack . B.drop 16 $ b)
  171. | otherwise = Nothing
  172. -- | the Template Haskell server
  173. runTHServer :: IO ()
  174. runTHServer = do
  175. -- msgs <- newIORef []
  176. void (runGHCJSQ server initQState) `E.catches`
  177. [ E.Handler $ \(GHCJSQException _ mn msg) ->
  178. void . sendRequest $ maybe (QFail msg) QCompilerException mn
  179. , E.Handler $ \(E.SomeException e) ->
  180. void (sendRequest $ QUserException (show e))
  181. ]
  182. where
  183. server = TH.qRunIO awaitMessage >>= \case
  184. RunTH t code loc -> do
  185. a <- TH.qRunIO (loadCode code)
  186. runTH t a loc
  187. server
  188. FinishTH endProcess -> do
  189. runModFinalizers
  190. mu <- TH.qRunIO $ js_getMemoryUsage
  191. TH.qRunIO $ sendResult (FinishTH' mu)
  192. when (not endProcess) server
  193. _ -> error "runTHServer: unexpected message type"
  194. {-# NOINLINE runTH #-}
  195. runTH :: THResultType -> Any -> Maybe TH.Loc -> GHCJSQ ()
  196. runTH rt obj = \mb_loc -> obj `seq` do
  197. s0 <- getState
  198. putState $ s0 { qsLocation = mb_loc }
  199. res <- case rt of
  200. THExp -> runTHCode (unsafeCoerce obj :: TH.Q TH.Exp)
  201. THPat -> runTHCode (unsafeCoerce obj :: TH.Q TH.Pat)
  202. THType -> runTHCode (unsafeCoerce obj :: TH.Q TH.Type)
  203. THDec -> runTHCode (unsafeCoerce obj :: TH.Q [TH.Dec])
  204. THAnnWrapper -> case unsafeCoerce obj of
  205. AnnotationWrapper x -> return (makeAnnPayload x)
  206. s1 <- getState
  207. TH.qRunIO (sendResult $ RunTH' res)
  208. putState $ s1 { qsLocation = Nothing }
  209. {-# NOINLINE runTHCode #-}
  210. runTHCode :: Binary a => TH.Q a -> GHCJSQ ByteString
  211. runTHCode c = BL.toStrict . runPut . put <$> TH.runQ c
  212. {-# NOINLINE loadCode #-}
  213. loadCode :: ByteString -> IO Any
  214. loadCode bs = do
  215. p <- fromBs bs
  216. unsafeCoerce <$> js_loadCode p (B.length bs)
  217. awaitMessage :: IO Message
  218. awaitMessage = fmap (runGet get . BL.fromStrict) . toBs =<< js_awaitMessage
  219. -- | send result back
  220. sendResult :: Message -> IO ()
  221. sendResult msg = do
  222. let bs = BL.toStrict $ runPut (put msg)
  223. p <- fromBs bs
  224. js_sendMessage p (B.length bs)
  225. -- | send a request and wait for the response
  226. sendRequest :: Message -> IO Message
  227. sendRequest msg = do
  228. let bs = BL.toStrict $ runPut (put msg)
  229. p <- fromBs bs
  230. fmap (runGet get . BL.fromStrict) . toBs =<< js_sendRequest p (B.length bs)
  231. -- | send a request and wait for the response
  232. -- a CompilerException' response is converted to a GHCJSQException which
  233. -- can be handled with recover.
  234. sendRequestQ :: Message -> GHCJSQ Message
  235. sendRequestQ msg = TH.qRunIO (sendRequest msg) >>= \case
  236. QCompilerException' n msg -> GHCJSQ $
  237. \s -> E.throw (GHCJSQException s (Just n) msg)
  238. response -> return response
  239. foreign import javascript interruptible "h$TH.sendRequest($1_1,$1_2,$2,$c);"
  240. js_sendRequest :: Ptr Word8 -> Int -> IO (Ptr Word8)
  241. foreign import javascript interruptible "h$TH.sendMessage($1_1,$1_2,$2,0,$c);"
  242. js_sendMessage :: Ptr Word8 -> Int -> IO ()
  243. foreign import javascript interruptible "h$TH.awaitMessage(0,$c);"
  244. js_awaitMessage :: IO (Ptr Word8)
  245. foreign import javascript unsafe "h$TH.bufSize($1_1, $1_2)"
  246. js_bufSize :: Ptr Word8 -> IO Int
  247. -- | actually returns the heap object to be evaluated
  248. foreign import javascript unsafe "h$TH.loadCode($1_1,$1_2,$2)"
  249. js_loadCode :: Ptr Word8 -> Int -> IO Double
  250. foreign import javascript unsafe "$r = h$TH.getMemoryUsage();"
  251. js_getMemoryUsage :: IO Int
  252. -- | only safe in JS
  253. fromBs :: ByteString -> IO (Ptr Word8)
  254. fromBs bs = BU.unsafeUseAsCString bs (return . castPtr)
  255. -- | build a ByteString that uses the whole buffer, only works in JS
  256. toBs :: Ptr Word8 -> IO ByteString
  257. toBs p = do
  258. l <- js_bufSize p
  259. BU.unsafePackCStringLen (castPtr p, l)
  260. #endif