|
@@ -0,0 +1,189 @@
|
|
|
+module Gidl.Backend.Rpc (
|
|
|
+ rpcBackend
|
|
|
+ ) where
|
|
|
+
|
|
|
+import qualified Paths_gidl as P
|
|
|
+
|
|
|
+import Gidl.Backend.Cabal
|
|
|
+ (cabalFileArtifact,CabalFile(..),defaultCabalFile,filePathToPackage)
|
|
|
+import Gidl.Backend.Haskell.Interface (interfaceModule,ifModuleName)
|
|
|
+import Gidl.Backend.Haskell.Types (typeModule,isUserDefined,typeModuleName)
|
|
|
+import Gidl.Interface (Interface,InterfaceEnv(..))
|
|
|
+import Gidl.Types (Type,TypeEnv(..))
|
|
|
+
|
|
|
+import Data.Char (isSpace)
|
|
|
+import Data.List (intercalate)
|
|
|
+import Ivory.Artifact
|
|
|
+ (Artifact(..),artifactPath,artifactFileName,artifactPath
|
|
|
+ ,artifactText,artifactCabalFile)
|
|
|
+import Ivory.Artifact.Template (artifactCabalFileTemplate)
|
|
|
+import Text.PrettyPrint.Mainland
|
|
|
+ (Doc,prettyLazyText,text,empty,(<+>),(</>),(<>),char,line,parens
|
|
|
+ ,punctuate,stack,sep,tuple,dot,spread,cat)
|
|
|
+
|
|
|
+
|
|
|
+-- External Interface ----------------------------------------------------------
|
|
|
+
|
|
|
+rpcBackend :: TypeEnv -> InterfaceEnv -> String -> String -> [Artifact]
|
|
|
+rpcBackend typeEnv@(TypeEnv te) ifaceEnv@(InterfaceEnv ie) pkgName nsStr =
|
|
|
+ cabalFileArtifact (defaultCabalFile pkgName modules buildDeps)
|
|
|
+ : artifactCabalFile P.getDataDir "support/rpc/Makefile"
|
|
|
+ : map (artifactPath "src") (rpcBaseModule namespace : sourceMods)
|
|
|
+
|
|
|
+ where
|
|
|
+
|
|
|
+ namespace = strToNs nsStr
|
|
|
+
|
|
|
+ buildDeps = [ "cereal", "QuickCheck", "snap-core", "snap-server", "stm"
|
|
|
+ , "bytestring" ]
|
|
|
+
|
|
|
+ modules = [ filePathToPackage (artifactFileName m) | m <- sourceMods ]
|
|
|
+
|
|
|
+ sourceMods = tmods ++ imods
|
|
|
+
|
|
|
+ tmods = [ typeModule (namespace ++ ["Types"]) t
|
|
|
+ | (_tn, t) <- te
|
|
|
+ , isUserDefined t
|
|
|
+ ]
|
|
|
+
|
|
|
+ imods = concat [ [ interfaceModule (namespace ++ ["Interface"]) i
|
|
|
+ , rpcModule typeEnv namespace i ]
|
|
|
+ | (_iname, i) <- ie
|
|
|
+ ]
|
|
|
+
|
|
|
+
|
|
|
+rpcBaseModule :: [String] -> Artifact
|
|
|
+rpcBaseModule ns =
|
|
|
+ artifactPath (foldr (\ p rest -> p ++ "/" ++ rest) "Server" ns) $
|
|
|
+ artifactCabalFileTemplate P.getDataDir "support/rpc/Base.hs.template" env
|
|
|
+ where
|
|
|
+ env = [ ("module_prefix", concatMap (++ ".") ns) ]
|
|
|
+
|
|
|
+
|
|
|
+-- Utilities -------------------------------------------------------------------
|
|
|
+
|
|
|
+strToNs :: String -> [String]
|
|
|
+strToNs str =
|
|
|
+ case break (== '.') (dropWhile isSpace str) of
|
|
|
+
|
|
|
+ (a,'.' : b) | null a -> strToNs b
|
|
|
+ | otherwise -> trim a : strToNs b
|
|
|
+
|
|
|
+ (a,_) | null a -> []
|
|
|
+ | otherwise -> [trim a]
|
|
|
+
|
|
|
+ where
|
|
|
+ trim = takeWhile (not . isSpace)
|
|
|
+
|
|
|
+
|
|
|
+-- Server Generation -----------------------------------------------------------
|
|
|
+
|
|
|
+rpcModule :: TypeEnv -> [String] -> Interface -> Artifact
|
|
|
+rpcModule typeEnv ns iface =
|
|
|
+ artifactPath (foldr (\ p rest -> p ++ "/" ++ rest) "Server" ns) $
|
|
|
+ artifactText (ifaceMod ++ ".hs") $
|
|
|
+ prettyLazyText 80 $
|
|
|
+ genServer typeEnv ns iface ifaceMod
|
|
|
+ where
|
|
|
+ ifaceMod = ifModuleName iface
|
|
|
+
|
|
|
+
|
|
|
+genServer :: TypeEnv -> [String] -> Interface -> String -> Doc
|
|
|
+genServer typeEnv ns iface ifaceMod =
|
|
|
+ stack [ moduleHeader ns ifaceMod
|
|
|
+ , importTypes ns typeEnv
|
|
|
+ , importInterface ns ifaceMod
|
|
|
+ , line
|
|
|
+ , ppImport False (ppModName (ns ++ ["Server","Base"])) Nothing
|
|
|
+ , line
|
|
|
+ , webServerImports
|
|
|
+ , line
|
|
|
+ , line
|
|
|
+ , runServer typeEnv iface
|
|
|
+ ]
|
|
|
+
|
|
|
+
|
|
|
+moduleHeader :: [String] -> String -> Doc
|
|
|
+moduleHeader ns m =
|
|
|
+ spread [ text "module"
|
|
|
+ , ppHaskellModule (ns ++ ["Server"]) m
|
|
|
+ , tuple [ text "rpcServer", text "Config(..)" ]
|
|
|
+ , text "where"
|
|
|
+ ]
|
|
|
+
|
|
|
+
|
|
|
+-- | Import all of the generated type modules from the type environment.
|
|
|
+importTypes :: [String] -> TypeEnv -> Doc
|
|
|
+importTypes ns (TypeEnv ts) = foldr importType empty ts
|
|
|
+ where
|
|
|
+ prefix = dots (map text (ns ++ ["Types"]))
|
|
|
+
|
|
|
+ importType (_,t) rest =
|
|
|
+ stack [ ppImport False (prefix *. text (typeModuleName t)) Nothing
|
|
|
+ , rest
|
|
|
+ ]
|
|
|
+
|
|
|
+
|
|
|
+importInterface :: [String] -> String -> Doc
|
|
|
+importInterface ns ifaceName =
|
|
|
+ ppImport False (dots (map text (ns ++ ["Interface", ifaceName]))) Nothing
|
|
|
+
|
|
|
+
|
|
|
+webServerImports :: Doc
|
|
|
+webServerImports =
|
|
|
+ stack [ ppImport False (ppModName ["Snap","Http","Server"]) Nothing
|
|
|
+ , ppImport True (ppModName ["Data","ByteString"]) Nothing
|
|
|
+ ]
|
|
|
+
|
|
|
+
|
|
|
+runServer :: TypeEnv -> Interface -> Doc
|
|
|
+runServer typeEnv iface = runServerSig </> runServerDef typeEnv iface
|
|
|
+
|
|
|
+
|
|
|
+runServerSig :: Doc
|
|
|
+runServerSig =
|
|
|
+ text "rpcServer" <+> text "::"
|
|
|
+ <+> arrow [ chan, chan, text "Config", text "IO ()" ]
|
|
|
+ where
|
|
|
+ chan = text "TChan" <+> text "S.ByteString"
|
|
|
+
|
|
|
+
|
|
|
+-- | Generate a definition for the server.
|
|
|
+runServerDef :: TypeEnv -> Interface -> Doc
|
|
|
+runServerDef typeEnv iface = text "rpcServer" <+> body
|
|
|
+ where
|
|
|
+ body = arg "input" $ \ input ->
|
|
|
+ arg "output" $ \ output ->
|
|
|
+ arg "cfg" $ \ cfg ->
|
|
|
+ char '=' <+> empty
|
|
|
+
|
|
|
+
|
|
|
+-- Pretty-printing Helpers -----------------------------------------------------
|
|
|
+
|
|
|
+arg :: String -> (Doc -> Doc) -> Doc
|
|
|
+arg name k = let x = text name in x <+> k (text name)
|
|
|
+
|
|
|
+arrow :: [Doc] -> Doc
|
|
|
+arrow ts = spread (punctuate (text "->") ts)
|
|
|
+
|
|
|
+(*.) :: Doc -> Doc -> Doc
|
|
|
+a *. b = a <> dot <> b
|
|
|
+
|
|
|
+dots :: [Doc] -> Doc
|
|
|
+dots = cat . punctuate dot
|
|
|
+
|
|
|
+ppImport :: Bool -> Doc -> Maybe Doc -> Doc
|
|
|
+ppImport isQual modName mbAs =
|
|
|
+ spread [ text "import"
|
|
|
+ , if isQual then text "qualified" else empty
|
|
|
+ , modName
|
|
|
+ , case mbAs of
|
|
|
+ Just alt -> text "as" <+> alt
|
|
|
+ Nothing -> empty
|
|
|
+ ]
|
|
|
+
|
|
|
+ppModName :: [String] -> Doc
|
|
|
+ppModName = dots . map text
|
|
|
+
|
|
|
+ppHaskellModule :: [String] -> String -> Doc
|
|
|
+ppHaskellModule ns n = foldr (\ m rest -> text m <> char '.' <> rest ) (text n) ns
|