| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 | module Gidl.Backend.Rpc (    rpcBackend  ) whereimport qualified Paths_gidl as Pimport Gidl.Backend.Cabal (cabalFileArtifact,defaultCabalFile,filePathToPackage)import Gidl.Backend.Haskell.Interface (interfaceModule,ifModuleName)import Gidl.Backend.Haskell.Types           (typeModule,isUserDefined,typeModuleName,userTypeModuleName)import Gidl.Interface           (Interface(..),InterfaceEnv(..),MethodName,Method(..),Perm(..))import Gidl.Schema           (Schema(..),producerSchema,consumerSchema,Message(..)           ,consumerMessages)import Gidl.Types (Type,TypeEnv(..))import Data.Char (isSpace)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,tuple,dot,spread,cat,hang,nest,align,comma           ,braces,brackets,dquotes)-- External Interface ----------------------------------------------------------rpcBackend :: TypeEnv -> InterfaceEnv -> String -> String -> [Artifact]rpcBackend typeEnv@(TypeEnv te) (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", "aeson", "transformers" ]  modules    = [ filePathToPackage (artifactFileName m) | m <- sourceMods ]  sourceMods = tmods ++ imods  tmods      = [ typeModule True (namespace ++ ["Types"]) t               | (_tn, t) <- te               , isUserDefined t               ]  imods      = concat [ [ interfaceModule True (namespace ++ ["Interface"]) i                        , rpcModule typeEnv namespace i ]                      | (_iname, i) <- ie                      ]rpcBaseModule :: [String] -> ArtifactrpcBaseModule ns =  artifactPath (foldr (\ p rest -> p ++ "/" ++ rest) "Rpc" ns) $  artifactCabalFileTemplate P.getDataDir "support/rpc/Base.hs.template" env  where  env = [ ("module_path", foldr (\p rest -> p ++ "." ++ rest) "Rpc" 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)allMethods :: Interface -> [(MethodName,Method)]allMethods (Interface _ ps ms) = concatMap allMethods ps ++ msisEmptySchema :: Schema -> BoolisEmptySchema (Schema _ ms) = null ms-- Server Generation -----------------------------------------------------------rpcModule :: TypeEnv -> [String] -> Interface -> ArtifactrpcModule typeEnv ns iface =  artifactPath (foldr (\ p rest -> p ++ "/" ++ rest) "Rpc" ns) $  artifactText (ifaceMod ++ ".hs") $  prettyLazyText 80 $  genServer typeEnv ns iface ifaceMod  where  ifaceMod = ifModuleName ifacegenServer :: TypeEnv -> [String] -> Interface -> String -> DocgenServer typeEnv ns iface ifaceMod = stack $  [ text "{-# LANGUAGE RecordWildCards #-}" | useManager ] ++  [ text "{-# LANGUAGE OverloadedStrings #-}"  , moduleHeader     ns ifaceMod  , line  , importTypes      ns typeEnv  , importInterface  ns ifaceMod  , line  , text "import" <+> (ppModName (ns ++ ["Rpc","Base"]))  , line  , webServerImports  , line  , line  , managerDefs  , runServer useManager iface input output  ]  where  (useManager,managerDefs) = managerDef iface input  (input,output) = queueTypes ifacemoduleHeader :: [String] -> String -> DocmoduleHeader ns m =  spread [ text "module"         , dots (map text (ns ++ ["Rpc", m]))         , tuple [ text "rpcServer", text "Config(..)" ]         , text "where"         ]-- | Import all of the generated type modules from the type environment.importTypes :: [String] -> TypeEnv -> DocimportTypes ns (TypeEnv ts) = foldr importType empty ts  where  prefix = dots (map text (ns ++ ["Types"]))  importType (_,t) rest =    (text "import" <+> (prefix *. text (typeModuleName t))) </> restimportInterface :: [String] -> String -> DocimportInterface ns ifaceName =  text "import" <+> (dots (map text (ns ++ ["Interface", ifaceName])))webServerImports :: DocwebServerImports  =  stack [ text "import qualified Snap.Core as Snap"        , text "import qualified Data.ByteString as S"        , text "import           Control.Concurrent (forkIO)"        , text "import           Control.Concurrent.STM"        , text "import           Control.Monad (msum,forever)"        , text "import           Control.Monad.IO.Class (liftIO)"        , text "import           Data.Aeson (encode,decode)"        ]type InputQueue  = Doctype OutputQueue = DocqueueTypes :: Interface -> (InputQueue,OutputQueue)queueTypes iface = (input,output)  where  Schema prodName _ = producerSchema iface  Schema consName _ = consumerSchema iface  prod = ifModuleName iface ++ prodName  cons = ifModuleName iface ++ consName  input  = text "TQueue" <+> text prod  output = text "TQueue" <+> text consrunServer :: Bool -> Interface -> InputQueue -> OutputQueue -> DocrunServer useMgr iface input output =  runServerSig hasConsumer input output </>  runServerDef hasConsumer useMgr iface  where  hasConsumer = not (isEmptySchema (consumerSchema iface))runServerSig :: Bool -> InputQueue -> OutputQueue -> DocrunServerSig hasConsumer input output =  text "rpcServer ::" <+> hang 2 (arrow tys)  where  tys = [ input                       ] ++        [ output | hasConsumer        ] ++        [ text "Config", text "IO ()" ]-- | Generate a definition for the server.runServerDef :: Bool -> Bool -> Interface -> DocrunServerDef hasConsumer useMgr iface =  hang 2 (text "rpcServer" <+> body)  where  args = spread $    [ text "input"                ] ++    [ text "output" | hasConsumer ] ++    [ text "cfg"                  ]  body =  args <+> char '=' </> nest 2 (doStmts stmts)  stmts = [ text "state <- mkState"                         | useMgr      ]       ++ [ defInput                                                      ]       ++ [ text "_ <- forkIO (manager state input input')" | useMgr      ]       ++ [ text "conn <- newConn output input'"            | hasConsumer ]       ++ [ text "runServer cfg $ Snap.route" </> routesDef               ]  defInput    | useMgr    = text "input' <- newTQueueIO"    | otherwise = text "let input' = input"  routesDef = nest 2 (align (routes iface (text "state")))-- | Define one route for each interface memberroutes :: Interface -> Doc -> Docroutes iface state =  align (char '[' <> nest 1 (stack (commas handlers)) <> char ']')  where  Interface pfx _ _ = iface  Schema suffix _   = consumerSchema iface  handlers = map (mkRoute pfx suffix state) (allMethods iface)mkRoute :: String -> String -> Doc -> (MethodName,Method) -> DocmkRoute ifacePfx consSuffix state method@(name,mty) =  parens (url <> comma </> guardMethods (handlersFor mty))  where  url = dquotes (text ifacePfx <> char '/' <> text name)  guardMethods [h] = h  guardMethods hs  = nest 2 $ text "msum"                          </> brackets (stack (commas hs))  handlersFor StreamMethod {} =      [ readStream state name ]  handlersFor (AttrMethod Read _) =      [ readAttr  consSuffix m | m <- consumerMessages method ]  handlersFor (AttrMethod Write _) =      [ writeAttr consSuffix m | m <- consumerMessages method ]  handlersFor (AttrMethod ReadWrite ty) =      [ readAttr  consSuffix m | m <- consumerMessages (name,AttrMethod Read  ty) ] ++      [ writeAttr consSuffix m | m <- consumerMessages (name,AttrMethod Write ty) ]readStream :: Doc -> MethodName -> DocreadStream state name = nest 2 $ text "Snap.method Snap.GET $"  </> doStmts    [ text "x <- liftIO (atomically (readTSampleVar" <+> svar <> text "))"    , text "Snap.writeLBS (encode x)"    ]  where  svar = parens (fieldName name <+> state)constrName :: String -> Message -> StringconstrName suffix (Message n _) = userTypeModuleName n ++ suffixreadAttr :: String -> Message -> DocreadAttr suffix msg = text "Snap.method Snap.GET $" <+> doStmts  [ text "resp <- liftIO $ sendRequest conn $" <+>                   text (constrName suffix msg) <+> text "()"  , text "Snap.writeLBS (encode resp)"  ]writeAttr :: String -> Message -> DocwriteAttr _ _ = text "Snap.method Snap.POST $" <+> doStmts  [ text "Snap.writeBS \"write\""  ]-- The stream manager ------------------------------------------------------------ | Define everything associated with the manager, but only if there are stream-- values to manage.managerDef :: Interface -> InputQueue -> (Bool,Doc)managerDef iface input  | null streams = (False,empty)  | otherwise    = (True,stack defs </> empty)  where  hasConsumer = not (isEmptySchema (consumerSchema iface))  streams = [ (name,ty) | (name,StreamMethod _ ty) <- allMethods iface ]  (stateType,stateDecl) = stateDef streams  defs = [ stateDecl         , empty         , mkStateDef streams         , empty         , text "manager ::" <+> arrow [ stateType, input, input, text "IO ()" ]         , nest 2 $ text "manager state input filtered = forever $"                </> doStmts stmts         ]  stmts = [ text "msg <- atomically (readTQueue input)"          , nest 2 (text "case msg of" </>                   stack (map mkCase streams ++ [defCase | hasConsumer ])) ]  -- name the producer constructor for a stream element  Schema prodSuffix _ = producerSchema iface  prodName ty = text (typeModuleName ty ++ prodSuffix)  -- update the state for this stream element  mkCase (n,ty) = prodName ty <+> text "x -> atomically (writeTSampleVar"                              <+> parens (fieldName n <+> text "state")                              <+> text "x)"  defCase = text "notStream -> atomically (writeTQueue filtered notStream)"-- | Generate the data type used to hold the streaming values, or nothing if-- there aren't any present in the interface.stateDef :: [(MethodName,Type)] -> (Doc,Doc)stateDef streams = (text "State",def)  where  def = nest 2 (text "data State = State" <+> braces fields)  fields = align (stack (punctuate comma (map mkField streams)))  mkField (name,ty) =    fieldName name      <+> text "::"      <+> text "TSampleVar"      <+> text (typeModuleName ty)mkStateDef :: [(MethodName,Type)] -> DocmkStateDef streams = stack  [ text "mkState :: IO State"  , nest 2 (text "mkState  =" </> nest 3 (doStmts stmts))  ]  where  stmts = [ fieldName n <+> text "<- newTSampleVarIO" | (n,_) <- streams ]       ++ [ text "return State { .. }" ]-- | Given the name of a stream in the interface, produce the selector for the-- state data type.fieldName :: MethodName -> DocfieldName name = text "stream_" <> text name-- Pretty-printing Helpers -----------------------------------------------------arrow :: [Doc] -> Docarrow ts = spread (punctuate (text "->") ts)commas :: [Doc] -> [Doc]commas  = punctuate comma(*.) :: Doc -> Doc -> Doca *. b = a <> dot <> bdots :: [Doc] -> Docdots  = cat . punctuate dotppModName :: [String] -> DocppModName  = dots . map textdoStmts :: [Doc] -> DocdoStmts [d] = nest 2 ddoStmts ds  = text "do" <+> align (stack (map (nest 2) ds))
 |