Rpc.hs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. module Gidl.Backend.Rpc (
  2. rpcBackend
  3. ) where
  4. import qualified Paths_gidl as P
  5. import Gidl.Backend.Cabal (cabalFileArtifact,defaultCabalFile,filePathToPackage)
  6. import Gidl.Backend.Haskell.Interface (interfaceModule,ifModuleName)
  7. import Gidl.Backend.Haskell.Types
  8. (typeModule,isUserDefined,typeModuleName,userTypeModuleName)
  9. import Gidl.Interface
  10. (Interface(..),InterfaceEnv(..),MethodName,Method(..),Perm(..))
  11. import Gidl.Schema
  12. (Schema(..),producerSchema,consumerSchema,Message(..)
  13. ,consumerMessages)
  14. import Gidl.Types (Type,TypeEnv(..))
  15. import Data.Char (isSpace)
  16. import Ivory.Artifact
  17. (Artifact,artifactPath,artifactFileName,artifactPath,artifactText
  18. ,artifactCabalFile)
  19. import Ivory.Artifact.Template (artifactCabalFileTemplate)
  20. import Text.PrettyPrint.Mainland
  21. (Doc,prettyLazyText,text,empty,(<+>),(</>),(<>),char,line,parens
  22. ,punctuate,stack,tuple,dot,spread,cat,hang,nest,align,comma
  23. ,braces,brackets,dquotes)
  24. -- External Interface ----------------------------------------------------------
  25. rpcBackend :: TypeEnv -> InterfaceEnv -> String -> String -> [Artifact]
  26. rpcBackend typeEnv@(TypeEnv te) (InterfaceEnv ie) pkgName nsStr =
  27. cabalFileArtifact (defaultCabalFile pkgName modules buildDeps)
  28. : artifactCabalFile P.getDataDir "support/rpc/Makefile"
  29. : map (artifactPath "src") (rpcBaseModule namespace : sourceMods)
  30. where
  31. namespace = strToNs nsStr
  32. buildDeps = [ "cereal", "QuickCheck", "snap-core", "snap-server", "stm"
  33. , "bytestring", "aeson", "transformers" ]
  34. modules = [ filePathToPackage (artifactFileName m) | m <- sourceMods ]
  35. sourceMods = tmods ++ imods
  36. tmods = [ typeModule True (namespace ++ ["Types"]) t
  37. | (_tn, t) <- te
  38. , isUserDefined t
  39. ]
  40. imods = concat [ [ interfaceModule True (namespace ++ ["Interface"]) i
  41. , rpcModule typeEnv namespace i ]
  42. | (_iname, i) <- ie
  43. ]
  44. rpcBaseModule :: [String] -> Artifact
  45. rpcBaseModule ns =
  46. artifactPath (foldr (\ p rest -> p ++ "/" ++ rest) "Rpc" ns) $
  47. artifactCabalFileTemplate P.getDataDir "support/rpc/Base.hs.template" env
  48. where
  49. env = [ ("module_path", foldr (\p rest -> p ++ "." ++ rest) "Rpc" ns) ]
  50. -- Utilities -------------------------------------------------------------------
  51. strToNs :: String -> [String]
  52. strToNs str =
  53. case break (== '.') (dropWhile isSpace str) of
  54. (a,'.' : b) | null a -> strToNs b
  55. | otherwise -> trim a : strToNs b
  56. (a,_) | null a -> []
  57. | otherwise -> [trim a]
  58. where
  59. trim = takeWhile (not . isSpace)
  60. allMethods :: Interface -> [(MethodName,Method)]
  61. allMethods (Interface _ ps ms) = concatMap allMethods ps ++ ms
  62. isEmptySchema :: Schema -> Bool
  63. isEmptySchema (Schema _ ms) = null ms
  64. -- Server Generation -----------------------------------------------------------
  65. rpcModule :: TypeEnv -> [String] -> Interface -> Artifact
  66. rpcModule typeEnv ns iface =
  67. artifactPath (foldr (\ p rest -> p ++ "/" ++ rest) "Rpc" ns) $
  68. artifactText (ifaceMod ++ ".hs") $
  69. prettyLazyText 80 $
  70. genServer typeEnv ns iface ifaceMod
  71. where
  72. ifaceMod = ifModuleName iface
  73. genServer :: TypeEnv -> [String] -> Interface -> String -> Doc
  74. genServer typeEnv ns iface ifaceMod = stack $
  75. [ text "{-# LANGUAGE RecordWildCards #-}" | useManager ] ++
  76. [ text "{-# LANGUAGE OverloadedStrings #-}"
  77. , moduleHeader ns ifaceMod
  78. , line
  79. , importTypes ns typeEnv
  80. , importInterface ns ifaceMod
  81. , line
  82. , text "import" <+> (ppModName (ns ++ ["Rpc","Base"]))
  83. , line
  84. , webServerImports
  85. , line
  86. , line
  87. , managerDefs
  88. , runServer useManager iface input output
  89. ]
  90. where
  91. (useManager,managerDefs) = managerDef iface input
  92. (input,output) = queueTypes iface
  93. moduleHeader :: [String] -> String -> Doc
  94. moduleHeader ns m =
  95. spread [ text "module"
  96. , dots (map text (ns ++ ["Rpc", m]))
  97. , tuple [ text "rpcServer", text "Config(..)" ]
  98. , text "where"
  99. ]
  100. -- | Import all of the generated type modules from the type environment.
  101. importTypes :: [String] -> TypeEnv -> Doc
  102. importTypes ns (TypeEnv ts) = foldr importType empty ts
  103. where
  104. prefix = dots (map text (ns ++ ["Types"]))
  105. importType (_,t) rest =
  106. (text "import" <+> (prefix *. text (typeModuleName t))) </> rest
  107. importInterface :: [String] -> String -> Doc
  108. importInterface ns ifaceName =
  109. text "import" <+> (dots (map text (ns ++ ["Interface", ifaceName])))
  110. webServerImports :: Doc
  111. webServerImports =
  112. stack [ text "import qualified Snap.Core as Snap"
  113. , text "import qualified Data.ByteString as S"
  114. , text "import Control.Concurrent (forkIO)"
  115. , text "import Control.Concurrent.STM"
  116. , text "import Control.Monad (msum,forever)"
  117. , text "import Control.Monad.IO.Class (liftIO)"
  118. , text "import Data.Aeson (encode,decode)"
  119. ]
  120. type InputQueue = Doc
  121. type OutputQueue = Doc
  122. queueTypes :: Interface -> (InputQueue,OutputQueue)
  123. queueTypes iface = (input,output)
  124. where
  125. Schema prodName _ = producerSchema iface
  126. Schema consName _ = consumerSchema iface
  127. prod = ifModuleName iface ++ prodName
  128. cons = ifModuleName iface ++ consName
  129. input = text "TQueue" <+> text prod
  130. output = text "TQueue" <+> text cons
  131. runServer :: Bool -> Interface -> InputQueue -> OutputQueue -> Doc
  132. runServer useMgr iface input output =
  133. runServerSig hasConsumer input output </>
  134. runServerDef hasConsumer useMgr iface
  135. where
  136. hasConsumer = not (isEmptySchema (consumerSchema iface))
  137. runServerSig :: Bool -> InputQueue -> OutputQueue -> Doc
  138. runServerSig hasConsumer input output =
  139. text "rpcServer ::" <+> hang 2 (arrow tys)
  140. where
  141. tys = [ input ] ++
  142. [ output | hasConsumer ] ++
  143. [ text "Config", text "IO ()" ]
  144. -- | Generate a definition for the server.
  145. runServerDef :: Bool -> Bool -> Interface -> Doc
  146. runServerDef hasConsumer useMgr iface =
  147. hang 2 (text "rpcServer" <+> body)
  148. where
  149. args = spread $
  150. [ text "input" ] ++
  151. [ text "output" | hasConsumer ] ++
  152. [ text "cfg" ]
  153. body = args <+> char '=' </> nest 2 (doStmts stmts)
  154. stmts = [ text "state <- mkState" | useMgr ]
  155. ++ [ defInput ]
  156. ++ [ text "_ <- forkIO (manager state input input')" | useMgr ]
  157. ++ [ text "conn <- newConn output input'" | hasConsumer ]
  158. ++ [ text "runServer cfg $ Snap.route" </> routesDef ]
  159. defInput
  160. | useMgr = text "input' <- newTQueueIO"
  161. | otherwise = text "let input' = input"
  162. routesDef = nest 2 (align (routes iface (text "state")))
  163. -- | Define one route for each interface member
  164. routes :: Interface -> Doc -> Doc
  165. routes iface state =
  166. align (char '[' <> nest 1 (stack (commas handlers)) <> char ']')
  167. where
  168. Interface pfx _ _ = iface
  169. Schema suffix _ = consumerSchema iface
  170. handlers = map (mkRoute pfx suffix state) (allMethods iface)
  171. mkRoute :: String -> String -> Doc -> (MethodName,Method) -> Doc
  172. mkRoute ifacePfx consSuffix state method@(name,mty) =
  173. parens (url <> comma </> guardMethods (handlersFor mty))
  174. where
  175. url = dquotes (text ifacePfx <> char '/' <> text name)
  176. guardMethods [h] = h
  177. guardMethods hs = nest 2 $ text "msum"
  178. </> brackets (stack (commas hs))
  179. handlersFor StreamMethod {} =
  180. [ readStream state name ]
  181. handlersFor (AttrMethod Read _) =
  182. [ readAttr consSuffix m | m <- consumerMessages method ]
  183. handlersFor (AttrMethod Write _) =
  184. [ writeAttr consSuffix m | m <- consumerMessages method ]
  185. handlersFor (AttrMethod ReadWrite ty) =
  186. [ readAttr consSuffix m | m <- consumerMessages (name,AttrMethod Read ty) ] ++
  187. [ writeAttr consSuffix m | m <- consumerMessages (name,AttrMethod Write ty) ]
  188. readStream :: Doc -> MethodName -> Doc
  189. readStream state name = nest 2 $ text "Snap.method Snap.GET $"
  190. </> doStmts
  191. [ text "x <- liftIO (atomically (readTSampleVar" <+> svar <> text "))"
  192. , text "Snap.writeLBS (encode x)"
  193. ]
  194. where
  195. svar = parens (fieldName name <+> state)
  196. constrName :: String -> Message -> String
  197. constrName suffix (Message n _) = userTypeModuleName n ++ suffix
  198. readAttr :: String -> Message -> Doc
  199. readAttr suffix msg = text "Snap.method Snap.GET $" <+> doStmts
  200. [ text "resp <- liftIO $ sendRequest conn $" <+>
  201. text (constrName suffix msg) <+> text "()"
  202. , text "Snap.writeLBS (encode resp)"
  203. ]
  204. writeAttr :: String -> Message -> Doc
  205. writeAttr _ _ = text "Snap.method Snap.POST $" <+> doStmts
  206. [ text "Snap.writeBS \"write\""
  207. ]
  208. -- The stream manager ----------------------------------------------------------
  209. -- | Define everything associated with the manager, but only if there are stream
  210. -- values to manage.
  211. managerDef :: Interface -> InputQueue -> (Bool,Doc)
  212. managerDef iface input
  213. | null streams = (False,empty)
  214. | otherwise = (True,stack defs </> empty)
  215. where
  216. hasConsumer = not (isEmptySchema (consumerSchema iface))
  217. streams = [ (name,ty) | (name,StreamMethod _ ty) <- allMethods iface ]
  218. (stateType,stateDecl) = stateDef streams
  219. defs = [ stateDecl
  220. , empty
  221. , mkStateDef streams
  222. , empty
  223. , text "manager ::" <+> arrow [ stateType, input, input, text "IO ()" ]
  224. , nest 2 $ text "manager state input filtered = forever $"
  225. </> doStmts stmts
  226. ]
  227. stmts = [ text "msg <- atomically (readTQueue input)"
  228. , nest 2 (text "case msg of" </>
  229. stack (map mkCase streams ++ [defCase | hasConsumer ])) ]
  230. -- name the producer constructor for a stream element
  231. Schema prodSuffix _ = producerSchema iface
  232. prodName ty = text (typeModuleName ty ++ prodSuffix)
  233. -- update the state for this stream element
  234. mkCase (n,ty) = prodName ty <+> text "x -> atomically (writeTSampleVar"
  235. <+> parens (fieldName n <+> text "state")
  236. <+> text "x)"
  237. defCase = text "notStream -> atomically (writeTQueue filtered notStream)"
  238. -- | Generate the data type used to hold the streaming values, or nothing if
  239. -- there aren't any present in the interface.
  240. stateDef :: [(MethodName,Type)] -> (Doc,Doc)
  241. stateDef streams = (text "State",def)
  242. where
  243. def = nest 2 (text "data State = State" <+> braces fields)
  244. fields = align (stack (punctuate comma (map mkField streams)))
  245. mkField (name,ty) =
  246. fieldName name
  247. <+> text "::"
  248. <+> text "TSampleVar"
  249. <+> text (typeModuleName ty)
  250. mkStateDef :: [(MethodName,Type)] -> Doc
  251. mkStateDef streams = stack
  252. [ text "mkState :: IO State"
  253. , nest 2 (text "mkState =" </> nest 3 (doStmts stmts))
  254. ]
  255. where
  256. stmts = [ fieldName n <+> text "<- newTSampleVarIO" | (n,_) <- streams ]
  257. ++ [ text "return State { .. }" ]
  258. -- | Given the name of a stream in the interface, produce the selector for the
  259. -- state data type.
  260. fieldName :: MethodName -> Doc
  261. fieldName name = text "stream_" <> text name
  262. -- Pretty-printing Helpers -----------------------------------------------------
  263. arrow :: [Doc] -> Doc
  264. arrow ts = spread (punctuate (text "->") ts)
  265. commas :: [Doc] -> [Doc]
  266. commas = punctuate comma
  267. (*.) :: Doc -> Doc -> Doc
  268. a *. b = a <> dot <> b
  269. dots :: [Doc] -> Doc
  270. dots = cat . punctuate dot
  271. ppModName :: [String] -> Doc
  272. ppModName = dots . map text
  273. doStmts :: [Doc] -> Doc
  274. doStmts [d] = nest 2 d
  275. doStmts ds = text "do" <+> align (stack (map (nest 2) ds))