Compare commits

...

10 commits

Author SHA1 Message Date
Edsko de Vries
90b1c210ae
Merge pull request #10 from BeFunctional/edsko/improve-external
Improve external buffer API
2023-03-29 13:46:25 +02:00
Edsko de Vries
6e245576e8 Improve external buffer API 2023-03-29 13:41:31 +02:00
Edsko de Vries
452fce4ea5
Merge pull request #9 from BeFunctional/edsko/external
API for working with external buffers
2023-03-29 10:45:23 +02:00
Edsko de Vries
8994f68c3b API for working with external buffers 2023-03-29 10:35:58 +02:00
Edsko de Vries
0bdbb0f1cb
Merge pull request #8 from BeFunctional/edsko/show-json
Show instance for JSON
2023-03-23 17:33:35 +00:00
Edsko de Vries
8aeada3529 Show instance for JSON 2023-03-23 18:27:59 +01:00
Edsko de Vries
1c5fef5162
Merge pull request #7 from BeFunctional/edsko/remove-failure-wrappers
More consistent treatment of `Failure`
2023-03-23 17:13:58 +00:00
Edsko de Vries
97504b714c More consistent treatment of Failure 2023-03-23 18:09:03 +01:00
Edsko de Vries
c28efafcb6
Merge pull request #6 from BeFunctional/edsko/structured-floating
Structured.Show instance for float/double
2023-03-23 15:41:47 +00:00
Edsko de Vries
d4a2e49173 Structured.Show instance for float/double 2023-03-23 16:35:04 +01:00
6 changed files with 108 additions and 34 deletions

View file

@ -0,0 +1,10 @@
// Forward-declare the Rust-exported function
void haskell_ffi_external_free(void* vec);
// Wrapper around the Rust function that takes an additional (unused) argument,
// which makes it match the Haskell `FinalizerEnvPtr` type. The wrapper also
// avoids linker errors when the Rust library is not available (of course,
// the Rust library must be linked into the final application).
void haskell_ffi_external_free_env(void* vec, void* ptr) {
haskell_ffi_external_free(vec);
}

View file

@ -48,6 +48,7 @@ common lang
TypeFamilies TypeFamilies
TypeOperators TypeOperators
UndecidableInstances UndecidableInstances
ViewPatterns
library library
import: import:
@ -56,6 +57,7 @@ library
Foreign.Rust.External.JSON Foreign.Rust.External.JSON
Foreign.Rust.External.Bincode Foreign.Rust.External.Bincode
Foreign.Rust.Failure Foreign.Rust.Failure
Foreign.Rust.Marshall.External
Foreign.Rust.Marshall.Fixed Foreign.Rust.Marshall.Fixed
Foreign.Rust.Marshall.Variable Foreign.Rust.Marshall.Variable
Foreign.Rust.SafeConv Foreign.Rust.SafeConv
@ -91,6 +93,8 @@ library
, th-abstraction , th-abstraction
, vector , vector
, wide-word , wide-word
c-sources:
cbits/wrap-rust-haskell-ffi.c
test-suite test-foreign-rust test-suite test-foreign-rust
import: import:

View file

@ -123,6 +123,9 @@ data Value where
-- These are shown assuming @NumericUnderscores@. -- These are shown assuming @NumericUnderscores@.
Integral :: forall a. (Prelude.Show a, Integral a) => a -> Value Integral :: forall a. (Prelude.Show a, Integral a) => a -> Value
-- | Floating point numbers
Floating :: Double -> Value
-- | Quasi-quote -- | Quasi-quote
-- --
-- We separate out the quasi-quoter from the quoted string proper. -- We separate out the quasi-quoter from the quoted string proper.
@ -259,6 +262,9 @@ instance Show Int128 where toValue = Integral
instance Show Integer where toValue = Integral instance Show Integer where toValue = Integral
instance Show Float where toValue = Floating . realToFrac
instance Show Double where toValue = Floating
instance {-# OVERLAPPABLE #-} Show a => Show [a] where instance {-# OVERLAPPABLE #-} Show a => Show [a] where
toValue = List . map toValue toValue = List . map toValue
@ -451,6 +457,7 @@ render = \contextNeedsBrackets ->
bracketIf (contextneedsBrackets && requiresBrackets val) $ bracketIf (contextneedsBrackets && requiresBrackets val) $
case val of case val of
Integral x -> simple $ addNumericUnderscores (Prelude.show x) Integral x -> simple $ addNumericUnderscores (Prelude.show x)
Floating x -> simple $ Prelude.show x
String x -> simple $ Prelude.show x String x -> simple $ Prelude.show x
Constr c ts xs -> renderComposite (compositeConstr c ts) $ Constr c ts xs -> renderComposite (compositeConstr c ts) $
map (go True) xs map (go True) xs

View file

@ -16,12 +16,15 @@ module Foreign.Rust.External.JSON (
) where ) where
import Codec.Borsh import Codec.Borsh
import Foreign.Rust.Failure
import GHC.Stack
import qualified Data.Aeson as Aeson import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Encoding as Aeson (unsafeToEncoding) import qualified Data.Aeson.Encoding as Aeson (unsafeToEncoding)
import qualified Data.Aeson.Types as Aeson (parseFail) import qualified Data.Aeson.Types as Aeson (parseFail)
import qualified Data.Binary.Builder as Binary import qualified Data.Binary.Builder as Binary
import qualified Data.ByteString.Lazy as Lazy (ByteString) import qualified Data.ByteString.Lazy as Lazy (ByteString)
import qualified Data.ByteString.Lazy.Char8 as Lazy.Char8
{------------------------------------------------------------------------------- {-------------------------------------------------------------------------------
Serialisation Serialisation
@ -32,13 +35,16 @@ newtype JSON = JSON Lazy.ByteString
deriving stock (Eq) deriving stock (Eq)
deriving newtype (BorshSize, ToBorsh, FromBorsh) deriving newtype (BorshSize, ToBorsh, FromBorsh)
-- | Types with an external JSON renderer (typically, in Rust) instance Show JSON where
show (JSON bs) = Lazy.Char8.unpack bs
-- | Types with a Rust-side JSON renderer
class ToJSON a where class ToJSON a where
toJSON :: a -> JSON toJSON :: a -> JSON
-- | Types with an external JSON parser (typically, in Rust) -- | Types with a Rust-side JSON parser
class FromJSON a where class FromJSON a where
fromJSON :: JSON -> Either String a fromJSON :: HasCallStack => JSON -> Either Failure a
{------------------------------------------------------------------------------- {-------------------------------------------------------------------------------
Deriving-via: derive Aeson instances using external (de)serialiser Deriving-via: derive Aeson instances using external (de)serialiser
@ -67,6 +73,6 @@ instance ToJSON a => Aeson.ToJSON (UseExternalJSON a) where
instance FromJSON a => Aeson.FromJSON (UseExternalJSON a) where instance FromJSON a => Aeson.FromJSON (UseExternalJSON a) where
parseJSON val = parseJSON val =
case fromJSON (JSON (Aeson.encode val)) of case fromJSON (JSON (Aeson.encode val)) of
Left failure -> Aeson.parseFail failure Left failure -> Aeson.parseFail (show failure)
Right tx -> return $ UseExternalJSON tx Right tx -> return $ UseExternalJSON tx

View file

@ -0,0 +1,72 @@
-- | Marshall from a Rust-side allocated buffer
--
-- Intended for unqualified import.
module Foreign.Rust.Marshall.External (
fromExternalBorsh
) where
import Codec.Borsh
import Data.Typeable
import Data.Word
import Foreign.C
import Foreign.Ptr
import qualified Data.ByteString.Internal as Strict
import Foreign.Rust.Marshall.Util
import Foreign.ForeignPtr
data ExternalBuffer
{-------------------------------------------------------------------------------
Foreign imports
Although 'externalPtr' and 'externalLen' are morally pure, we make
them live in IO to make reasoning about order of operations easier in
'fromExternalBorsh'.
These C functions are defined in the companion Rust @haskell-ffi@ library.
-------------------------------------------------------------------------------}
foreign import ccall unsafe "haskell_ffi_external_ptr"
externalPtr
:: Ptr ExternalBuffer -> IO (Ptr Word8)
foreign import ccall unsafe "haskell_ffi_external_len"
externalLen
:: Ptr ExternalBuffer -> IO CSize
foreign import ccall unsafe "&haskell_ffi_external_free_env"
externalFree
:: FinalizerEnvPtr ExternalBuffer Word8
{-------------------------------------------------------------------------------
Internal auxiliary
-------------------------------------------------------------------------------}
-- | Cast pointer
--
-- For ease of integration with c2hs, 'fromExternalBorsh' takes a @Ptr ()@ as
-- input instead of the more accurate @Ptr ExternalBuffer@.
castToExternal :: Ptr () -> Ptr ExternalBuffer
castToExternal = castPtr
{-------------------------------------------------------------------------------
Public API
-------------------------------------------------------------------------------}
-- | Output marshaller for values stored in Rust-allocated buffer
--
-- Should be used together with the Rust function @marshall_to_haskell_external@
-- (from @haskell-ffi@).
fromExternalBorsh :: (FromBorsh a, Typeable a) => Ptr () -> IO a
fromExternalBorsh (castToExternal -> vec) = do
ptr <- externalPtr vec
len <- fromIntegral <$> externalLen vec
fptr <- newForeignPtrEnv externalFree vec ptr
let bs :: Strict.ByteString
bs = Strict.fromForeignPtr fptr 0 len
return $ deserialiseStrictOrPanic bs

View file

@ -11,26 +11,20 @@ module Foreign.Rust.Marshall.Variable (
, Buffer -- opaque , Buffer -- opaque
, getVarBuffer , getVarBuffer
, withBorshVarBuffer , withBorshVarBuffer
, withBorshFailure
, withBorshBufferOfInitSize , withBorshBufferOfInitSize
-- ** Pure variants -- ** Pure variants
, withPureBorshVarBuffer , withPureBorshVarBuffer
, withPureBorshFailure
) where ) where
import Codec.Borsh import Codec.Borsh
import Data.Bifunctor
import Data.Text (Text)
import Data.Typeable import Data.Typeable
import Foreign import Foreign
import Foreign.C.Types import Foreign.C.Types
import GHC.Stack
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
import qualified Data.ByteString as Strict import qualified Data.ByteString as Strict
import Foreign.Rust.Marshall.Util import Foreign.Rust.Marshall.Util
import Foreign.Rust.Failure
{------------------------------------------------------------------------------- {-------------------------------------------------------------------------------
Haskell to Rust Haskell to Rust
@ -72,16 +66,6 @@ withBorshVarBuffer :: forall a.
=> (Buffer a -> IO ()) -> IO a => (Buffer a -> IO ()) -> IO a
withBorshVarBuffer = withBorshBufferOfInitSize 1024 withBorshVarBuffer = withBorshBufferOfInitSize 1024
-- | Wrapper around 'withBorshVarBuffer' with explicit support for failures
withBorshFailure :: forall a.
( FromBorsh a
, StaticBorshSize a ~ 'HasVariableSize
, Typeable a
, HasCallStack
)
=> (Buffer (Either Text a) -> IO ()) -> IO (Either Failure a)
withBorshFailure = fmap (first mkFailure) . withBorshVarBuffer
{------------------------------------------------------------------------------- {-------------------------------------------------------------------------------
Pure variants Pure variants
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
@ -94,15 +78,6 @@ withPureBorshVarBuffer :: forall a.
=> (Buffer a -> IO ()) -> a => (Buffer a -> IO ()) -> a
withPureBorshVarBuffer = unsafePerformIO . withBorshVarBuffer withPureBorshVarBuffer = unsafePerformIO . withBorshVarBuffer
withPureBorshFailure :: forall a.
( FromBorsh a
, StaticBorshSize a ~ 'HasVariableSize
, Typeable a
, HasCallStack
)
=> (Buffer (Either Text a) -> IO ()) -> Either Failure a
withPureBorshFailure = unsafePerformIO . withBorshFailure
{------------------------------------------------------------------------------- {-------------------------------------------------------------------------------
Generalization Generalization
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}