packages feed

rpc (empty) → 0.0.0

raw patch · 10 files changed

+281/−0 lines, 10 filesdep +basedep +bytestringdep +cerealsetup-changed

Dependencies added: base, bytestring, cereal, containers, derive, network-fancy, template-haskell, th-lift

Files

+ Data/Serialize/Send.hs view
@@ -0,0 +1,19 @@+module Data.Serialize.Send (hSerialize, hDeserialize) where+import System.IO (hFlush, Handle)+import Data.Serialize+import Data.Serialize.Put+import Data.Serialize.Get+import Data.ByteString as B+import Control.Monad++-- |Serializes an object to the given handle+hSerialize :: Serialize a => Handle -> a -> IO ()+hSerialize h x = hPut h $ runPut (putWord8 (fromIntegral (B.length a))) `append` a+    where a = encode x++-- |Deserializes an object to the given handle+hDeserialize :: Serialize a => Handle -> IO a+hDeserialize h = do+    (Right l) <- liftM (runGet getWord8) (hGet h 1)+    (Right x) <- liftM decode (hGet h (fromIntegral l))+    return x
+ LICENSE view
@@ -0,0 +1,20 @@+Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice,+   this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice,+   this list of conditions and the following disclaimer in the documentation+   and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE+DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;+OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Network/RPC/Client.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE TemplateHaskell #-}++module Network.RPC.Client (fetch, Address(..)) where+import Network.Fancy+import Data.Serialize.Send+import Control.Monad+import Language.Haskell.TH+import System.IO (hFlush)+import Network.RPC.Types++-- |Splices in rpc calls to a given IP.  Function names and types will remain the same as+-- on the server side, with the result encapsulated in the IO monad.  Usage:+--+-- >    $(fetch (IP "127.0.0.1" 9000))+fetch :: Address -> Q [Dec]+fetch a = runIO $ withStream a $ \h-> do+    hSerialize h GetTypes >> hFlush h+    xs <- hDeserialize h :: IO [FuncSpec]+    let funcs = map (\(FuncSpec n _)-> mkName n) xs+    runQ $ liftM (++ (zipWith addType funcs xs)) (zipWithM (makeFunc a) funcs xs)++makeFunc :: Address -> Name -> FuncSpec -> Q Dec+makeFunc a n (FuncSpec f t) = do+    args <- replicateM (walkApps t 0) (newName "x")+    let pats = map varP args+    let exprs = return $ ListE $ map (AppE (ConE 'Bin) . VarE) args+    let e = [|(\toSer-> withStream a $ \h-> hSerialize h (CallFunc f) >>+                mapM (\(Bin a)-> hSerialize h a) toSer >> hFlush h >> hDeserialize h) $exprs|]+    funD n [clause pats (normalB e)[]]++addType :: Name -> FuncSpec -> Dec+addType n (FuncSpec _ t) = SigD n (addIO (specToType t))++addIO (AppT a b) = (AppT a (addIO b))+addIO (ForallT ns c t) = (ForallT ns c (addIO t))+addIO a = (AppT (ConT ''IO) a)++walkApps :: TypeSpec -> Int -> Int+walkApps (SAppT _ a) n = walkApps a (n+1)+walkApps b n = n
+ Network/RPC/Server.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS -ddump-splices #-}++module Network.RPC.Server (share) where+import Network.RPC.Types+import Network.Fancy+import Data.Serialize.Send+import Data.Maybe+import Network.RPC.Types+import qualified Data.Map as Map+import Data.Map (Map)+import Language.Haskell.TH+import System.IO (hFlush)+import Control.Concurrent (ThreadId)+import Language.Haskell.TH.Syntax+import Control.Monad+import Data.List+import System.IO (Handle)++serve :: Address -> (Map String (Handle -> IO ())) -> [FuncSpec] -> IO ()+serve ip funcs types = do+    streamServer (serverSpec {address = ip}) $ \h _-> do+        x <- hDeserialize h+        case x of+            GetTypes -> hSerialize h types+            (CallFunc a) -> fromJust (Map.lookup a funcs) h+        hFlush h+    sleepForever++apps :: ExpQ -> [ExpQ] -> ExpQ+apps f (x:xs) = apps (appE f x) xs+apps f [] = f++buildArgs :: Type -> [Type]+buildArgs (AppT i a) = (unArrow i) : buildArgs a+buildArgs (ForallT _ _ t) = buildArgs t+buildArgs a = []+unArrow (AppT ArrowT i) = i+unArrow a = a++-- |Template haskell magic to share a set of functions on a given port to be spliced into a main declaration.  +-- This function never returns.  For example:+--+-- >    main = $(share 9000 [ 'fn1, 'fn2, 'fn3 ])+share :: Int -> [Name] -> Q Exp+share p gs = [| serve (IP "" p) (Map.fromList $ zip keys $vals) $types |] where+    keys = map nameBase gs+    vals :: Q Exp+    vals = listE $ flip map gs $ \x-> do+                (VarI n t _ _) <- reify x+                hName <- newName "h"+                let argtypes = map (AppT (ConT ''IO)) (buildArgs t)+                let args = zipWith SigE (replicate (length argtypes) (AppE (VarE 'hDeserialize) (VarE hName))) argtypes+                names <- replicateM (length argtypes) (newName "y")+                ret <- noBindS (appE (appE (varE 'hSerialize) (varE hName)) (apps (varE x) (map varE names)))+                lam1E (varP hName) (return $ DoE (zipWith BindS (map VarP names) args ++ [ret]))+    types = listE $ flip map gs $ \x-> do+                (VarI n t _ _) <- reify x+                let spec = lift (typeToSpec t)+                [|FuncSpec $(lift $ nameBase n) $spec |]
+ Network/RPC/Types.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS -fglasgow-exts #-}++module Network.RPC.Types (+    Req(..), FuncSpec(..), TypeSpec(..),+    Bin(..), typeToSpec, specToType+    ) where+import Data.DeriveTH (derive, makeSerialize)+import Language.Haskell.TH+import Language.Haskell.TH.Syntax+import Language.Haskell.TH.Lift+import Network.Fancy+import Data.Serialize+import Control.Monad++data Req = GetTypes | CallFunc String+data FuncSpec = FuncSpec String TypeSpec deriving Show+data TypeSpec = SForallT [TyVarBndrSpec] [PredSpec] TypeSpec |+    SVarT String | SConT String | STupleT Int | SArrowT |+    SListT | SAppT TypeSpec TypeSpec deriving Show+data TyVarBndrSpec = SPlainTV String | SKindedTV String Kind deriving Show+data PredSpec = SClassP String [TypeSpec] | SEqualP TypeSpec TypeSpec deriving Show++data Bin = forall a . Serialize a => Bin a++$(derive makeSerialize ''Req)+$(derive makeSerialize ''FuncSpec)+$(derive makeSerialize ''TypeSpec)+$(derive makeSerialize ''TyVarBndrSpec)+$(derive makeSerialize ''Kind)+$(derive makeSerialize ''PredSpec)++unTV :: TyVarBndr -> TyVarBndrSpec+unTV (PlainTV n) = SPlainTV (nameBase n)+unTV (KindedTV n k) = SKindedTV (nameBase n) k++makeTV :: TyVarBndrSpec -> TyVarBndr+makeTV (SPlainTV n) = PlainTV (mkName n)+makeTV (SKindedTV n k) = KindedTV (mkName n) k++unPred :: Pred -> PredSpec+unPred (ClassP n ts) = SClassP (nameBase n) (map typeToSpec ts)+unPred (EqualP t1 t2) = SEqualP (typeToSpec t1) (typeToSpec t2)++makePred :: PredSpec -> Pred+makePred (SClassP n ts) = ClassP (mkName n) (map specToType ts)+makePred (SEqualP t1 t2) = EqualP (specToType t1) (specToType t2)++typeToSpec :: Type -> TypeSpec+typeToSpec (ForallT ns cs t) = SForallT (map unTV ns) (map unPred cs) (typeToSpec t)+typeToSpec (VarT n) = SVarT (nameBase n)+typeToSpec (ConT n) = SConT (nameBase n)+typeToSpec (TupleT i) = STupleT i+typeToSpec ArrowT = SArrowT+typeToSpec ListT = SListT+typeToSpec (AppT a b) = SAppT (typeToSpec a) (typeToSpec b)++specToType :: TypeSpec -> Type+specToType (SForallT ns cs t) = ForallT (map makeTV ns) (map makePred cs) (specToType t)+specToType (SVarT s) = VarT (mkName s)+specToType (SConT s) = ConT (mkName s)+specToType (STupleT i) = TupleT i+specToType SArrowT = ArrowT+specToType SListT = ListT+specToType (SAppT a b) = AppT (specToType a) (specToType b)++$(deriveLift ''Address)+$(deriveLift ''TypeSpec )+$(deriveLift ''TyVarBndrSpec )+$(deriveLift ''Kind)+$(deriveLift ''PredSpec)
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ rpc.cabal view
@@ -0,0 +1,17 @@+name:                rpc+version:             0.0.0+synopsis:            type safe rpcs provided as basic IO actions+description:         Given an address of a server sharing a set of functions,+                     this rpc library generates functions of the same name for+                     a client, preformed within the IO monad.  Calling+                     these results in the original function being executed+                     server side.  The pseudo-functions generated maintain+                     their original server side type (except for the added IO).+category:            Network+license:             BSD3+license-file:        LICENSE+author:              Sam Anklesaria+maintainer:          amsay@amsay.net+build-type:          Simple+build-depends:       base >= 3 && < 5, network-fancy < 1, cereal < 1, th-lift < 1, derive < 3, bytestring < 1, template-haskell < 3, containers < 1+exposed-modules:     Data.Serialize.Send, Network.RPC.Client, Network.RPC.Server
+ tests/ctest.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE TemplateHaskell #-}++module Main where+import Network.RPC.Client+import Test.HUnit++$(fetch (IP "" 9000))++main = runTestTT $ TestList [+            TestLabel "hello world" hWorld,+            TestLabel "plusOne" plusTest,+            TestLabel "BoolInt" boolTest]++hWorld = TestCase $ do+    a <- helloWorld+    assertEqual "" "hello world" a++plusTest = TestCase $ do+    a <- tester 1+    assertEqual "Plus 1: " 2 a++boolTest = TestCase $ do+    a <- tester2 0 True+    assertEqual "Not True : " False a
+ tests/stest.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE TemplateHaskell #-}++module Main where+import Network.RPC.Server++tester :: Int -> Int+tester = (+1)++helloWorld :: String+helloWorld = "hello world"++tester2 :: Int -> Bool -> Bool+tester2 _ = not++main = $(share 9000 [ 'tester, 'tester2, 'helloWorld ])
+ tests/test.hs view
@@ -0,0 +1,12 @@+module Main where+import System.Process+import Control.Concurrent++main = do+    c <- runCommand "ghc ./tests/stest.hs --make -o server"+    waitForProcess c+    s <- runCommand "./server"+    threadDelay 1000000+    p <- runCommand "runhaskell ./tests/ctest.hs"+    waitForProcess p+    terminateProcess s