call-haskell-from-anything 0.1.0.2 → 0.2.0.0
raw patch · 8 files changed
+52/−295 lines, 8 filesdep +vectordep −attoparsecdep ~basedep ~msgpacksetup-changed
Dependencies added: vector
Dependencies removed: attoparsec
Dependency ranges changed: base, msgpack
Files
- Setup.hs +1/−1
- call-haskell-from-anything.cabal +8/−13
- src/FFI/Anything/Copied.hs +0/−61
- src/FFI/Anything/TypeUncurry.hs +0/−20
- src/FFI/Anything/TypeUncurry/Legacy.hs +0/−129
- src/FFI/Anything/TypeUncurry/Msgpack.hs +39/−54
- src/FFI/Anything/Util.hs +0/−13
- test/Test1.hs +4/−4
Setup.hs view
@@ -1,2 +1,2 @@ import Distribution.Simple-main = defaultMain+main = defaultMainWithHooks autoconfUserHooks
call-haskell-from-anything.cabal view
@@ -1,5 +1,5 @@ name: call-haskell-from-anything-version: 0.1.0.2+version: 0.2.0.0 license: MIT author: Niklas Hambüchen (mail@nh2.me) maintainer: Niklas Hambüchen (mail@nh2.me)@@ -8,9 +8,10 @@ -- See the ./configure script. build-type: Configure license: MIT-synopsis: Python-to-Haskell function calls+synopsis: Call Haskell functions from other languages via serialization and dynamic libraries+description: FFI via serialisation. See https://github.com/nh2/call-haskell-from-anything for details. stability: experimental-tested-with: GHC==7.6.3, GHC==7.8.2+tested-with: GHC==7.10.3 cabal-version: >= 1.10 homepage: https://github.com/nh2/call-haskell-from-anything bug-reports: https://github.com/nh2/call-haskell-from-anything/issues@@ -32,23 +33,17 @@ src exposed-modules: FFI.Anything.TH- FFI.Anything.Copied FFI.Anything.TypeUncurry FFI.Anything.TypeUncurry.ReturnResult- FFI.Anything.TypeUncurry.Legacy FFI.Anything.TypeUncurry.Msgpack- if impl(ghc >= 7.6.0)- exposed-modules:- FFI.Anything.TypeUncurry.DataKinds- other-modules:- FFI.Anything.Util+ FFI.Anything.TypeUncurry.DataKinds build-depends:- base >= 4 && < 5+ base >= 4.8 && < 5 , bytestring >= 0.10.0.0- , msgpack >= 0.7.1.5 && < 1.0.0+ , msgpack >= 1.0.0 , template-haskell , mtl >= 2.1.2- , attoparsec >= 0.10.3.0+ , vector ghc-options: -Wall -fwarn-unused-imports
− src/FFI/Anything/Copied.hs
@@ -1,61 +0,0 @@--- | Contains some functions needed from the msgpack package--- that are unfortunately not yet exposed.------ See https://github.com/msgpack/msgpack-haskell/pull/34.-module FFI.Anything.Copied where--import Data.ByteString.Lazy.Builder-import qualified Data.Attoparsec.ByteString as A-import Data.Bits-import Data.Monoid-import Data.Word-import Text.Printf----- Copied from Data.MessagePack.Pack-fromArray :: (a -> Int) -> (a -> Builder) -> a -> Builder-fromArray lf pf arr = do- case lf arr of- len | len <= 15 ->- word8 $ 0x90 .|. fromIntegral len- len | len < 0x10000 ->- word8 0xDC <>- word16BE (fromIntegral len)- len ->- word8 0xDD <>- word32BE (fromIntegral len)- <> pf arr----- Copied from Data.MessagePack.Unpack-parseArray :: (Int -> A.Parser a) -> A.Parser a-parseArray aget = do- c <- A.anyWord8- case c of- _ | c .&. 0xF0 == 0x90 ->- aget . fromIntegral $ c .&. 0x0F- 0xDC ->- aget . fromIntegral =<< parseUint16- 0xDD ->- aget . fromIntegral =<< parseUint32- _ ->- fail $ printf "invalid array tag: 0x%02X" c---parseUint16 :: A.Parser Word16-parseUint16 = do- b0 <- A.anyWord8- b1 <- A.anyWord8- return $ (fromIntegral b0 `shiftL` 8) .|. fromIntegral b1---parseUint32 :: A.Parser Word32-parseUint32 = do- b0 <- A.anyWord8- b1 <- A.anyWord8- b2 <- A.anyWord8- b3 <- A.anyWord8- return $ (fromIntegral b0 `shiftL` 24) .|.- (fromIntegral b1 `shiftL` 16) .|.- (fromIntegral b2 `shiftL` 8) .|.- fromIntegral b3
src/FFI/Anything/TypeUncurry.hs view
@@ -1,30 +1,10 @@-{-# LANGUAGE CPP #-}--#if MIN_VERSION_base(4,6,0)-#define USE_DATA_KINDS 1-#endif--#if USE_DATA_KINDS {-# LANGUAGE DataKinds, TypeOperators #-}-#endif {-# LANGUAGE TypeOperators, ScopedTypeVariables, FlexibleContexts #-} --- | For GHC 7.6 or newer, we use (and re-export) "FFI.Anything.TypeUncurry.DataKinds" which uses DataKinds for TypeList to be kind-safe.------ For all other versions, use (and re-export) "FFI.Anything.TypeUncurry.Legacy" which uses a simpler model of TypeList. module FFI.Anything.TypeUncurry (-#if USE_DATA_KINDS -- | You see this because your compiler supports DataKinds. module FFI.Anything.TypeUncurry.DataKinds-#else- -- | You see this because your compiler does not support DataKinds.- module FFI.Anything.TypeUncurry.Legacy-#endif ) where -#if USE_DATA_KINDS import FFI.Anything.TypeUncurry.DataKinds-#else-import FFI.Anything.TypeUncurry.Legacy-#endif
− src/FFI/Anything/TypeUncurry/Legacy.hs
@@ -1,129 +0,0 @@-{-# LANGUAGE GADTs, TypeOperators, MultiParamTypeClasses, FlexibleInstances, TypeFamilies, ScopedTypeVariables, FlexibleContexts #-}---- | Converts function arguments to tuple-like types.------ This module is the LEGACY module not using DataKinds.--- Compilers supporting DataKinds (e.g. GHC >= 7.6) should use "FFI.Anything.TypeUncurry".------ For example, take @f :: a -> b -> c -> r@.--- This module can convert it to @f' :: (a, b, c) -> r@, at compile time.------ This is especially useful for (de)serialization.--- Suppose you have a function that takes multiple arguments--- and you want to obtain all of its arguments from some serialized data.--- The serialization library will make it very easy to unpack types--- like tuples/lists, but de-serializing *fuction arguments* is not that simple.------ Using this module, you can write an instance how to unpack the 'TypeList' type,--- and then use 'translateCall' to make any function take such a single 'TypeList'--- instead of multiple function arguments.------ There is currently a technical limitation:--- The result type must be wrapped in the 'Identity' monad.------ Example:------ >-- Assume your library provides some unpack function, e.g. it allows you to write:--- >unpack someBytestring :: (Int, String, Double)--- >--- >-- and you have a function--- >f :: Int -> String -> Double -> Identity Char--- >--- >-- then you can use:--- >f' :: (Int, String, Double) -> Identity Char--- >f' = translateCall f--- >--- >result = f' (unpack someBytestring)-module FFI.Anything.TypeUncurry.Legacy where--import Control.Monad.Identity----- * Type-level lists (containing types)---- | The empty type-level type list-data Nil--- | A type attached to a type-level type list-data a ::: b---- | Type-level list that can contain arbitrarily mixed types.------ Example:------ >1 ::: "hello" ::: 2.3 :: TypeList (Int ::: String ::: Double ::: Nil)-data TypeList l where- Nil :: TypeList Nil- (:::) :: a -> TypeList l -> TypeList (a ::: l)---- Right-associativity, like (->)-infixr :::----- * \"Uncurrying\" functions--{- In the following, we try to not use Template Haskell,- using an instance of (a -> ...) to convert functions to TypeLists automatically- (similar to how you make variadic functions).--}---- | Arguments to a function, e.g. @[String, Int]@ for @String -> Int -> r@.-type family Param f--- | For pure functions, we need an 'Identity' monad wrapper here to not conflict with @a -> f@.-type instance Param (Identity r) = Nil-type instance Param (IO r) = Nil-type instance Param (a -> f) = a ::: Param f---- | The result of a function, e.g. @r@ for @String -> Int -> r@.-type family Result f--- | For pure functions, we need an 'Identity' monad wrapper here to not conflict with @a -> f@.-type instance Result (Identity r) = r-type instance Result (IO r) = IO r-type instance Result (a -> f) = Result f----- | Function f can be translated to 'TypeList' l with result type r.-class (Param f ~ l, Result f ~ r) => ToTypeList f l r where- -- | Translates a function taking multiple arguments to a function- -- taking a single 'TypeList' containing the types of all arguments.- --- -- Example: @t1 -> ... -> tn -> r@ becomes @TypeList (t1 ::: ... ::: tn ::: Nil) -> r@.- translate :: f -> TypeList l -> r---- | Recursive case: A "pure" function of type @a -> ... -> r@--- can be translated to @TypeList (a ::: ...) -> r@.-instance (ToTypeList f l r) => ToTypeList (a -> f) (a ::: l) r where- translate f (a ::: l) = translate (f a) l---- | Base case: A "pure" function without arguments (just @Identity r@)--- can be translated to @TypeList Nil -> r@.-instance ToTypeList (Identity r) Nil r where- translate (Identity r) Nil = r---- | Base case: An IO function without arguments (just @Identity r@)--- can be translated to @TypeList Nil -> r@.-instance ToTypeList (IO r) Nil (IO r) where- translate ior Nil = ior----- * Length of type-level lists---- | A proxy type that can contain an arbitrary type.------ Needed for some type-level computations, like 'paramLength'.-data Proxy k = Proxy----- | Allows to calculate the length of a 'TypeList', at compile time.------ We need to use a 'Proxy' for this.-class ParamLength l where- -- | Calculates the length of a type list, put into a proxy. Usage:- --- -- >paramLength (undefined :: Proxy l)- paramLength :: Proxy l -> Int--instance ParamLength Nil where- paramLength _ = 0--instance (ParamLength l) => ParamLength (a ::: l) where- paramLength _ = succ $ paramLength (undefined :: Proxy l)
src/FFI/Anything/TypeUncurry/Msgpack.hs view
@@ -1,13 +1,6 @@-{-# LANGUAGE CPP #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -#if MIN_VERSION_base(4,6,0)-#define USE_DATA_KINDS 1-#endif--#if USE_DATA_KINDS {-# LANGUAGE DataKinds, TypeOperators #-}-#endif {-# LANGUAGE TypeOperators, ScopedTypeVariables, FlexibleContexts #-} -- | Easy FFI via MessagePack.@@ -25,7 +18,7 @@ -- -- * Expose Haskell functions via a socket / the web module FFI.Anything.TypeUncurry.Msgpack (- UnpackableRec (..)+ MessagePackRec (..) , getTypeListFromMsgpackArray , uncurryMsgpack , tryUncurryMsgpack@@ -37,61 +30,53 @@ , module FFI.Anything.TypeUncurry.ReturnResult ) where -import Control.Applicative-import qualified Data.Attoparsec.ByteString as A import Data.ByteString (ByteString) import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BSL+import Data.Maybe (fromMaybe) import qualified Data.MessagePack as MSG+import Data.Vector (Vector)+import qualified Data.Vector as V import Foreign.C-import Text.Printf -import FFI.Anything.Copied-import FFI.Anything.Util import FFI.Anything.TypeUncurry.ReturnResult --- For GHC 7.6 or newer, this imports a TypeUncurry that uses DataKinds for TypeList to be kind-safe.--- For older versions, it imports TypeUncurryLegacy which uses a weaker model of TypeList. import FFI.Anything.TypeUncurry --- | Helper to allow writing a 'MSG.Unpackable' instance for 'TypeList's.+-- | Helper to allow writing a 'MSG.MessagePack' instance for 'TypeList's. -- -- We need this because we have to call 'parseArray' at the top-level--- 'MSG.Unpackable' instance, but not at each function argument step.-class UnpackableRec l where- getRec :: A.Parser (TypeList l)+-- 'MSG.MessagePack' instance, but not at each function argument step.+class MessagePackRec l where+ fromObjectRec :: Vector MSG.Object -> Maybe (TypeList l) -- | When no more types need to be unpacked, we are done.-#if USE_DATA_KINDS-instance UnpackableRec '[] where -- For GHC 7.6 or newer, use DataKinds.-#else-instance UnpackableRec Nil where-#endif- getRec = return Nil+instance MessagePackRec '[] where+ fromObjectRec v | V.null v = Just Nil+ fromObjectRec _ = Nothing -- | Unpack one type by just parsing the next element.-#if USE_DATA_KINDS-instance (MSG.Unpackable a, UnpackableRec l) => UnpackableRec (a ': l) where -- For GHC 7.6 or newer, use DataKinds.-#else-instance (MSG.Unpackable a, UnpackableRec l) => UnpackableRec (a ::: l) where-#endif- getRec = (:::) <$> MSG.get <*> getRec--+instance (MSG.MessagePack a, MessagePackRec l) => MessagePackRec (a ': l) where+ fromObjectRec v | not (V.null v) = (:::) <$> MSG.fromObject (V.head v) <*> fromObjectRec (V.tail v)+ fromObjectRec _ = Nothing -- | Parses a tuple of arbitrary size ('TypeList's) from a MessagePack array.-getTypeListFromMsgpackArray :: forall l . (UnpackableRec l, ParamLength l) => A.Parser (TypeList l)-getTypeListFromMsgpackArray = parseArray f+getTypeListFromMsgpackArray :: forall l . (MessagePackRec l, ParamLength l) => MSG.Object -> Maybe (TypeList l)+getTypeListFromMsgpackArray obj = case obj of+ MSG.ObjectArray v | V.length v == len -> fromObjectRec v+ _ -> Nothing where len = paramLength (Proxy :: Proxy l)- f n | n == len = getRec- -- TODO also print function name- | otherwise = fail $ printf "getTypeListFromMsgpackArray: wrong number of function arguments: expected %d but got %d" len n +instance (MessagePackRec l, ParamLength l) => MSG.MessagePack (TypeList l) where+ fromObject = getTypeListFromMsgpackArray+ toObject = error "call-haskell-from-anything: Serialising a TypeList is not supported (and not needed)!" -instance (UnpackableRec l, ParamLength l) => MSG.Unpackable (TypeList l) where- get = getTypeListFromMsgpackArray +-- | Standard error message when unpacking failed.+errorMsg :: String -> String+errorMsg locationStr = "call-haskell-from-anything: " ++ locationStr ++ ": got wrong number of function arguments or non-array" -- | Translates a function of type @a -> b -> ... -> Identity r@ to@@ -103,32 +88,32 @@ -- -- This function throws an 'error' if the de-serialization of the arguments fails! -- It is recommended to use 'tryUncurryMsgpack' instead.-uncurryMsgpack :: (MSG.Unpackable (TypeList l), ToTypeList f l r, MSG.Packable r) => f -> (ByteString -> ByteString)-uncurryMsgpack f = \bs -> lazyToStrictBS . MSG.pack $ (translate f $ MSG.unpack bs)+uncurryMsgpack :: (MSG.MessagePack (TypeList l), ToTypeList f l r, MSG.MessagePack r) => f -> (ByteString -> ByteString)+uncurryMsgpack f = \bs -> BSL.toStrict . MSG.pack $ (translate f $ fromMaybe (error (errorMsg "uncurryMsgpack")) $ MSG.unpack $ BSL.fromStrict bs) -- | Like 'uncurryMsgpack', but for 'IO' functions. -- -- This function throws an 'error' if the de-serialization of the arguments fails! -- It is recommended to use 'tryUncurryMsgpackIO' instead.-uncurryMsgpackIO :: (MSG.Unpackable (TypeList l), ToTypeList f l (IO r), MSG.Packable r) => f -> (ByteString -> IO ByteString)-uncurryMsgpackIO f = \bs -> lazyToStrictBS . MSG.pack <$> (translate f $ MSG.unpack bs)+uncurryMsgpackIO :: (MSG.MessagePack (TypeList l), ToTypeList f l (IO r), MSG.MessagePack r) => f -> (ByteString -> IO ByteString)+uncurryMsgpackIO f = \bs -> BSL.toStrict . MSG.pack <$> (translate f $ fromMaybe (error (errorMsg "uncurryMsgpackIO")) $ MSG.unpack $ BSL.fromStrict bs) -- | Like 'uncurryMsgpack', but makes it clear when the 'ByteString' containing -- the function arguments does not contain the right number/types of arguments.-tryUncurryMsgpack :: (MSG.Unpackable (TypeList l), ToTypeList f l r, MSG.Packable r) => f -> (ByteString -> Either String ByteString)-tryUncurryMsgpack f = \bs -> case MSG.tryUnpack bs of- Left e -> Left e- Right args -> Right . lazyToStrictBS . MSG.pack $ (translate f $ args)+tryUncurryMsgpack :: (MSG.MessagePack (TypeList l), ToTypeList f l r, MSG.MessagePack r) => f -> (ByteString -> Maybe ByteString)+tryUncurryMsgpack f = \bs -> case MSG.unpack $ BSL.fromStrict bs of+ Nothing -> Nothing+ Just args -> Just . BSL.toStrict . MSG.pack $ (translate f $ args) -- | Like 'uncurryMsgpack', but makes it clear when the 'ByteString' containing -- the function arguments does not contain the right number/types of arguments.-tryUncurryMsgpackIO :: (MSG.Unpackable (TypeList l), ToTypeList f l (IO r), MSG.Packable r) => f -> (ByteString -> Either String (IO ByteString))-tryUncurryMsgpackIO f = \bs -> case MSG.tryUnpack bs of- Left e -> Left e- Right args -> Right $ lazyToStrictBS . MSG.pack <$> (translate f $ args)+tryUncurryMsgpackIO :: (MSG.MessagePack (TypeList l), ToTypeList f l (IO r), MSG.MessagePack r) => f -> (ByteString -> Maybe (IO ByteString))+tryUncurryMsgpackIO f = \bs -> case MSG.unpack $ BSL.fromStrict bs of+ Nothing -> Nothing+ Just args -> Just $ BSL.toStrict . MSG.pack <$> (translate f $ args) -- * Exporting@@ -159,7 +144,7 @@ -- -- Calling this function throws an 'error' if the de-serialization of the arguments fails! -- Use 'tryExport' if you want to handle this case.-export :: (MSG.Unpackable (TypeList l), ToTypeList f l r, MSG.Packable r) => f -> CString -> IO CString+export :: (MSG.MessagePack (TypeList l), ToTypeList f l r, MSG.MessagePack r) => f -> CString -> IO CString export = byteStringToCStringFun . uncurryMsgpack @@ -167,7 +152,7 @@ -- -- Calling this function throws an 'error' if the de-serialization of the arguments fails! -- Use 'tryExportIO' if you want to handle this case.-exportIO :: (MSG.Unpackable (TypeList l), ToTypeList f l (IO r), MSG.Packable r) => f -> CString -> IO CString+exportIO :: (MSG.MessagePack (TypeList l), ToTypeList f l (IO r), MSG.MessagePack r) => f -> CString -> IO CString exportIO = byteStringToCStringFunIO . uncurryMsgpackIO
− src/FFI/Anything/Util.hs
@@ -1,13 +0,0 @@--- | Contains some helper functions.-module FFI.Anything.Util (- lazyToStrictBS-) where--import Data.ByteString (ByteString)-import qualified Data.ByteString.Lazy as BSL-import Data.Monoid----- | Could use Data.ByteString.Lazy.toStrict starting from bytestring 0.10.0.0-lazyToStrictBS :: BSL.ByteString -> ByteString-lazyToStrictBS = mconcat . BSL.toChunks
test/Test1.hs view
@@ -24,11 +24,11 @@ -- To be translated to: f1' :: ByteString -> ByteString-f1' bs = mconcat . BSL.toChunks $ MSG.pack (uncurry f1 $ msg)+f1' bs = BSL.toStrict $ MSG.pack (uncurry f1 $ msg) where- msg = case MSG.tryUnpack bs of- Left e -> error $ "tryUnpack: " ++ e- Right r -> r+ msg = case MSG.unpack (BSL.fromStrict bs) of+ Nothing -> error "could not unpack"+ Just r -> r -- TODO check who deallocs - it seems to work magically!