Rpc.hs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. module Gidl.Backend.Rpc (
  2. rpcBackend
  3. ) where
  4. import qualified Paths_gidl as P
  5. import Gidl.Backend.Cabal
  6. (cabalFileArtifact,CabalFile(..),defaultCabalFile,filePathToPackage)
  7. import Gidl.Backend.Haskell.Interface (interfaceModule,ifModuleName)
  8. import Gidl.Backend.Haskell.Types (typeModule,isUserDefined,typeModuleName)
  9. import Gidl.Interface (Interface,InterfaceEnv(..))
  10. import Gidl.Types (Type,TypeEnv(..))
  11. import Data.Char (isSpace)
  12. import Data.List (intercalate)
  13. import Ivory.Artifact
  14. (Artifact(..),artifactPath,artifactFileName,artifactPath
  15. ,artifactText,artifactCabalFile)
  16. import Ivory.Artifact.Template (artifactCabalFileTemplate)
  17. import Text.PrettyPrint.Mainland
  18. (Doc,prettyLazyText,text,empty,(<+>),(</>),(<>),char,line,parens
  19. ,punctuate,stack,sep,tuple,dot,spread,cat)
  20. -- External Interface ----------------------------------------------------------
  21. rpcBackend :: TypeEnv -> InterfaceEnv -> String -> String -> [Artifact]
  22. rpcBackend typeEnv@(TypeEnv te) ifaceEnv@(InterfaceEnv ie) pkgName nsStr =
  23. cabalFileArtifact (defaultCabalFile pkgName modules buildDeps)
  24. : artifactCabalFile P.getDataDir "support/rpc/Makefile"
  25. : map (artifactPath "src") (rpcBaseModule namespace : sourceMods)
  26. where
  27. namespace = strToNs nsStr
  28. buildDeps = [ "cereal", "QuickCheck", "snap-core", "snap-server", "stm"
  29. , "bytestring" ]
  30. modules = [ filePathToPackage (artifactFileName m) | m <- sourceMods ]
  31. sourceMods = tmods ++ imods
  32. tmods = [ typeModule (namespace ++ ["Types"]) t
  33. | (_tn, t) <- te
  34. , isUserDefined t
  35. ]
  36. imods = concat [ [ interfaceModule (namespace ++ ["Interface"]) i
  37. , rpcModule typeEnv namespace i ]
  38. | (_iname, i) <- ie
  39. ]
  40. rpcBaseModule :: [String] -> Artifact
  41. rpcBaseModule ns =
  42. artifactPath (foldr (\ p rest -> p ++ "/" ++ rest) "Server" ns) $
  43. artifactCabalFileTemplate P.getDataDir "support/rpc/Base.hs.template" env
  44. where
  45. env = [ ("module_prefix", concatMap (++ ".") ns) ]
  46. -- Utilities -------------------------------------------------------------------
  47. strToNs :: String -> [String]
  48. strToNs str =
  49. case break (== '.') (dropWhile isSpace str) of
  50. (a,'.' : b) | null a -> strToNs b
  51. | otherwise -> trim a : strToNs b
  52. (a,_) | null a -> []
  53. | otherwise -> [trim a]
  54. where
  55. trim = takeWhile (not . isSpace)
  56. -- Server Generation -----------------------------------------------------------
  57. rpcModule :: TypeEnv -> [String] -> Interface -> Artifact
  58. rpcModule typeEnv ns iface =
  59. artifactPath (foldr (\ p rest -> p ++ "/" ++ rest) "Server" ns) $
  60. artifactText (ifaceMod ++ ".hs") $
  61. prettyLazyText 80 $
  62. genServer typeEnv ns iface ifaceMod
  63. where
  64. ifaceMod = ifModuleName iface
  65. genServer :: TypeEnv -> [String] -> Interface -> String -> Doc
  66. genServer typeEnv ns iface ifaceMod =
  67. stack [ moduleHeader ns ifaceMod
  68. , importTypes ns typeEnv
  69. , importInterface ns ifaceMod
  70. , line
  71. , ppImport False (ppModName (ns ++ ["Server","Base"])) Nothing
  72. , line
  73. , webServerImports
  74. , line
  75. , line
  76. , runServer typeEnv iface
  77. ]
  78. moduleHeader :: [String] -> String -> Doc
  79. moduleHeader ns m =
  80. spread [ text "module"
  81. , ppHaskellModule (ns ++ ["Server"]) m
  82. , tuple [ text "rpcServer", text "Config(..)" ]
  83. , text "where"
  84. ]
  85. -- | Import all of the generated type modules from the type environment.
  86. importTypes :: [String] -> TypeEnv -> Doc
  87. importTypes ns (TypeEnv ts) = foldr importType empty ts
  88. where
  89. prefix = dots (map text (ns ++ ["Types"]))
  90. importType (_,t) rest =
  91. stack [ ppImport False (prefix *. text (typeModuleName t)) Nothing
  92. , rest
  93. ]
  94. importInterface :: [String] -> String -> Doc
  95. importInterface ns ifaceName =
  96. ppImport False (dots (map text (ns ++ ["Interface", ifaceName]))) Nothing
  97. webServerImports :: Doc
  98. webServerImports =
  99. stack [ ppImport False (ppModName ["Snap","Http","Server"]) Nothing
  100. , ppImport True (ppModName ["Data","ByteString"]) Nothing
  101. ]
  102. runServer :: TypeEnv -> Interface -> Doc
  103. runServer typeEnv iface = runServerSig </> runServerDef typeEnv iface
  104. runServerSig :: Doc
  105. runServerSig =
  106. text "rpcServer" <+> text "::"
  107. <+> arrow [ chan, chan, text "Config", text "IO ()" ]
  108. where
  109. chan = text "TChan" <+> text "S.ByteString"
  110. -- | Generate a definition for the server.
  111. runServerDef :: TypeEnv -> Interface -> Doc
  112. runServerDef typeEnv iface = text "rpcServer" <+> body
  113. where
  114. body = arg "input" $ \ input ->
  115. arg "output" $ \ output ->
  116. arg "cfg" $ \ cfg ->
  117. char '=' <+> empty
  118. -- Pretty-printing Helpers -----------------------------------------------------
  119. arg :: String -> (Doc -> Doc) -> Doc
  120. arg name k = let x = text name in x <+> k (text name)
  121. arrow :: [Doc] -> Doc
  122. arrow ts = spread (punctuate (text "->") ts)
  123. (*.) :: Doc -> Doc -> Doc
  124. a *. b = a <> dot <> b
  125. dots :: [Doc] -> Doc
  126. dots = cat . punctuate dot
  127. ppImport :: Bool -> Doc -> Maybe Doc -> Doc
  128. ppImport isQual modName mbAs =
  129. spread [ text "import"
  130. , if isQual then text "qualified" else empty
  131. , modName
  132. , case mbAs of
  133. Just alt -> text "as" <+> alt
  134. Nothing -> empty
  135. ]
  136. ppModName :: [String] -> Doc
  137. ppModName = dots . map text
  138. ppHaskellModule :: [String] -> String -> Doc
  139. ppHaskellModule ns n = foldr (\ m rest -> text m <> char '.' <> rest ) (text n) ns