mirror of
https://github.com/zap-proto/haskell.git
synced 2026-10-30 15:05:00 +00:00
393 lines
14 KiB
Haskell
393 lines
14 KiB
Haskell
{-# LANGUAGE ConstraintKinds #-}
|
|
{-# LANGUAGE DataKinds #-}
|
|
{-# LANGUAGE DefaultSignatures #-}
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
{-# LANGUAGE FunctionalDependencies #-}
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
{-# LANGUAGE UndecidableInstances #-}
|
|
{- |
|
|
Module: Capnp.Classes
|
|
Description: Misc. type classes
|
|
|
|
This module defines several type classes concerning encoding and decoding
|
|
values in the capnproto wire format (as well as instances for some basic
|
|
types).
|
|
|
|
Note that much of this is unlikely to be used directly by developers.
|
|
Typically these are either used internally by generated code, or
|
|
transitively via higher level functions in the API. It is recommended
|
|
to look elsewhere in the library for what you need, and refer to this
|
|
module only when trying to understand what the class constraints on a
|
|
function mean.
|
|
-}
|
|
module Capnp.Classes
|
|
( IsWord(..)
|
|
, ListElem(..)
|
|
, MutListElem(..)
|
|
, FromPtr(..)
|
|
, ToPtr(..)
|
|
, FromStruct(..)
|
|
, ToStruct(..)
|
|
, Allocate(..)
|
|
, Marshal(..)
|
|
, Cerialize(..)
|
|
, Decerialize(..)
|
|
, cerializeBasicVec
|
|
, cerializeCompositeVec
|
|
, ReadParam
|
|
, WriteParam
|
|
) where
|
|
|
|
import Prelude hiding (length)
|
|
|
|
import Data.Bits
|
|
import Data.Int
|
|
import Data.Word
|
|
|
|
import Control.Monad.Catch (MonadThrow(throwM))
|
|
import Data.Foldable (for_)
|
|
import GHC.Float
|
|
( castDoubleToWord64
|
|
, castFloatToWord32
|
|
, castWord32ToFloat
|
|
, castWord64ToDouble
|
|
)
|
|
|
|
import Capnp.Bits (Word1 (..))
|
|
import Capnp.Errors (Error(SchemaViolationError))
|
|
import Capnp.Message (Mutability (..))
|
|
import Capnp.Untyped (Cap, ListOf, Ptr (..), ReadCtx, Struct, messageDefault)
|
|
|
|
import qualified Capnp.Message as M
|
|
import qualified Capnp.Untyped as U
|
|
|
|
import qualified Data.Vector as V
|
|
|
|
-- | Type alias for the constraints needed for 'a' to be used as a capnp
|
|
-- type parameter for pure modules, when reading.
|
|
type ReadParam a =
|
|
( Decerialize a
|
|
, FromPtr 'Const (Cerial 'Const a)
|
|
)
|
|
-- | Type alias for the constraints needed for 'a' to be used as a capnp
|
|
-- type parameter for pure modules, when writing with state token 's'.
|
|
type WriteParam s a =
|
|
( Cerialize s a
|
|
, ToPtr s (Cerial ('Mut s) a)
|
|
, FromPtr ('Mut s) (Cerial ('Mut s) a)
|
|
)
|
|
|
|
-- | Types that can be converted to and from a 64-bit word.
|
|
--
|
|
-- Anything that goes in the data section of a struct will have
|
|
-- an instance of this.
|
|
class IsWord a where
|
|
-- | Convert from a 64-bit words Truncates the word if the
|
|
-- type has less than 64 bits.
|
|
fromWord :: Word64 -> a
|
|
|
|
-- | Convert to a 64-bit word.
|
|
toWord :: a -> Word64
|
|
|
|
-- | Types which may be stored as an element of a capnproto list.
|
|
class ListElem mut e where
|
|
-- | The type of lists of @e@ stored in messages of type @msg@
|
|
data List mut e
|
|
|
|
-- | Convert an untyped list to a list of this type. May fail
|
|
-- with a 'SchemaViolationError' if the list does not have the
|
|
-- correct representation.
|
|
--
|
|
-- TODO: this is basically just fromPtr; refactor so this is less
|
|
-- redundant.
|
|
listFromPtr :: U.ReadCtx m mut => M.Message mut -> Maybe (U.Ptr mut) -> m (List mut e)
|
|
|
|
toUntypedList :: List mut e -> U.List mut
|
|
|
|
-- | Get the length of a list.
|
|
length :: List mut e -> Int
|
|
|
|
-- | @'index' i list@ gets the @i@th element of a list.
|
|
index :: U.ReadCtx m mut => Int -> List mut e -> m e
|
|
|
|
-- | Types which may be stored as an element of a *mutable* capnproto list.
|
|
class (ListElem ('Mut s) e) => MutListElem s e where
|
|
-- | @'setIndex' value i list@ sets the @i@th index in @list@ to @value@
|
|
setIndex :: U.RWCtx m s => e -> Int -> List ('Mut s) e -> m ()
|
|
|
|
-- | @'newList' msg size@ allocates and returns a new list of length
|
|
-- @size@ inside @msg@.
|
|
newList :: M.WriteCtx m s => M.Message ('Mut s) -> Int -> m (List ('Mut s) e)
|
|
|
|
-- | Types which may be stored in a capnproto message, and have a fixed size.
|
|
--
|
|
-- This applies to typed structs, but not e.g. lists, because the length
|
|
-- must be known to allocate a list.
|
|
class Allocate s e | e -> s where
|
|
-- @'new' msg@ allocates a new value of type @e@ inside @msg@.
|
|
new :: M.WriteCtx m s => M.Message ('Mut s) -> m e
|
|
|
|
-- | Types which may be extracted from a message.
|
|
--
|
|
-- typically, instances of 'Decerialize' will be the algebraic data types
|
|
-- defined in generated code for the high-level API.
|
|
class Decerialize a where
|
|
-- | A variation on @a@ which is encoded in the message.
|
|
--
|
|
-- For the case of instances in generated high-level API code, this will
|
|
-- be the low-level API analouge of the type.
|
|
type Cerial (mut :: Mutability) a
|
|
|
|
-- | Extract the value from the message.
|
|
decerialize :: U.ReadCtx m 'Const => Cerial 'Const a -> m a
|
|
|
|
-- | Types which may be marshaled into a pre-allocated object in a message.
|
|
class Decerialize a => Marshal s a where
|
|
|
|
-- | Marshal a value into the pre-allocated object inside the message.
|
|
--
|
|
-- Note that caller must arrange for the object to be of the correct size.
|
|
-- This is is not necessarily guaranteed; for example, list types must
|
|
-- coordinate the length of the list.
|
|
marshalInto :: U.RWCtx m s => Cerial ('Mut s) a -> a -> m ()
|
|
|
|
-- | Types which may be inserted into a message.
|
|
class Decerialize a => Cerialize s a where
|
|
|
|
-- | Cerialize a value into the supplied message, returning the result.
|
|
cerialize :: U.RWCtx m s => M.Message ('Mut s) -> a -> m (Cerial ('Mut s) a)
|
|
|
|
default cerialize :: (U.RWCtx m s, Marshal s a, Allocate s (Cerial ('Mut s) a))
|
|
=> M.Message ('Mut s) -> a -> m (Cerial ('Mut s) a)
|
|
cerialize msg value = do
|
|
raw <- new msg
|
|
marshalInto raw value
|
|
pure raw
|
|
|
|
-- | Types that can be converted from an untyped pointer.
|
|
--
|
|
-- Note that decoding does not have to succeed, if the pointer is
|
|
-- the wrong type.
|
|
class FromPtr mut a where
|
|
-- | Convert an untyped pointer to an @a@.
|
|
fromPtr :: ReadCtx m mut => M.Message mut -> Maybe (Ptr mut) -> m a
|
|
|
|
-- | Types that can be converted to an untyped pointer.
|
|
class ToPtr s a where
|
|
-- | Convert an @a@ to an untyped pointer.
|
|
toPtr :: M.WriteCtx m s => M.Message ('Mut s) -> a -> m (Maybe (Ptr ('Mut s)))
|
|
|
|
-- | Types that can be extracted from a struct.
|
|
class FromStruct mut a | a -> mut where
|
|
-- | Extract a value from a struct.
|
|
fromStruct :: ReadCtx m mut => Struct mut -> m a
|
|
|
|
-- | Types that can be converted to a struct.
|
|
class ToStruct mut a | a -> mut where
|
|
-- | Convert a value to a struct.
|
|
toStruct :: a -> Struct mut
|
|
|
|
------- instances -------
|
|
|
|
instance IsWord Bool where
|
|
fromWord n = (n .&. 1) == 1
|
|
toWord True = 1
|
|
toWord False = 0
|
|
|
|
instance IsWord Word1 where
|
|
fromWord = Word1 . fromWord
|
|
toWord = toWord . word1ToBool
|
|
|
|
-- IsWord instances for integral types; they're all the same.
|
|
instance IsWord Int8 where
|
|
fromWord = fromIntegral
|
|
toWord = fromIntegral
|
|
instance IsWord Int16 where
|
|
fromWord = fromIntegral
|
|
toWord = fromIntegral
|
|
instance IsWord Int32 where
|
|
fromWord = fromIntegral
|
|
toWord = fromIntegral
|
|
instance IsWord Int64 where
|
|
fromWord = fromIntegral
|
|
toWord = fromIntegral
|
|
instance IsWord Word8 where
|
|
fromWord = fromIntegral
|
|
toWord = fromIntegral
|
|
instance IsWord Word16 where
|
|
fromWord = fromIntegral
|
|
toWord = fromIntegral
|
|
instance IsWord Word32 where
|
|
fromWord = fromIntegral
|
|
toWord = fromIntegral
|
|
instance IsWord Word64 where
|
|
fromWord = fromIntegral
|
|
toWord = fromIntegral
|
|
|
|
instance IsWord Float where
|
|
fromWord = castWord32ToFloat . fromIntegral
|
|
toWord = fromIntegral . castFloatToWord32
|
|
instance IsWord Double where
|
|
fromWord = castWord64ToDouble
|
|
toWord = castDoubleToWord64
|
|
|
|
-- helper function for throwing SchemaViolationError "expected ..."
|
|
expected :: MonadThrow m => String -> m a
|
|
expected msg = throwM $ SchemaViolationError $ "expected " ++ msg
|
|
|
|
-- To/FromPtr instance for lists of Void/().
|
|
instance FromPtr mut (ListOf mut ()) where
|
|
fromPtr msg Nothing = messageDefault msg
|
|
fromPtr _ (Just (PtrList (U.List0 list))) = pure list
|
|
fromPtr _ _ = expected "pointer to list with element size 0"
|
|
instance ToPtr s (ListOf ('Mut s) ()) where
|
|
toPtr _ = pure . Just . PtrList . U.List0
|
|
|
|
-- To/FromPtr instances for lists of unsigned integers.
|
|
instance FromPtr mut (ListOf mut Word8) where
|
|
fromPtr msg Nothing = messageDefault msg
|
|
fromPtr _ (Just (PtrList (U.List8 list))) = pure list
|
|
fromPtr _ _ = expected "pointer to list with element size 8"
|
|
instance ToPtr s (ListOf ('Mut s) Word8) where
|
|
toPtr _ = pure . Just . PtrList . U.List8
|
|
instance FromPtr mut (ListOf mut Word16) where
|
|
fromPtr msg Nothing = messageDefault msg
|
|
fromPtr _ (Just (PtrList (U.List16 list))) = pure list
|
|
fromPtr _ _ = expected "pointer to list with element size 16"
|
|
instance ToPtr s (ListOf ('Mut s) Word16) where
|
|
toPtr _ = pure . Just . PtrList . U.List16
|
|
instance FromPtr mut (ListOf mut Word32) where
|
|
fromPtr msg Nothing = messageDefault msg
|
|
fromPtr _ (Just (PtrList (U.List32 list))) = pure list
|
|
fromPtr _ _ = expected "pointer to list with element size 32"
|
|
instance ToPtr s (ListOf ('Mut s) Word32) where
|
|
toPtr _ = pure . Just . PtrList . U.List32
|
|
instance FromPtr mut (ListOf mut Word64) where
|
|
fromPtr msg Nothing = messageDefault msg
|
|
fromPtr _ (Just (PtrList (U.List64 list))) = pure list
|
|
fromPtr _ _ = expected "pointer to list with element size 64"
|
|
instance ToPtr s (ListOf ('Mut s) Word64) where
|
|
toPtr _ = pure . Just . PtrList . U.List64
|
|
|
|
instance FromPtr mut (ListOf mut Bool) where
|
|
fromPtr msg Nothing = messageDefault msg
|
|
fromPtr _ (Just (PtrList (U.List1 list))) = pure list
|
|
fromPtr _ _ = expected "pointer to list with element size 1."
|
|
instance ToPtr s (ListOf ('Mut s) Bool) where
|
|
toPtr _ = pure . Just . PtrList . U.List1
|
|
|
|
-- To/FromPtr instance for pointers -- this is just the identity.
|
|
instance FromPtr mut (Maybe (Ptr mut)) where
|
|
fromPtr _ = pure
|
|
instance ToPtr s (Maybe (Ptr ('Mut s))) where
|
|
toPtr _ = pure
|
|
|
|
-- To/FromPtr instance for composite lists.
|
|
instance FromPtr mut (ListOf mut (Struct mut)) where
|
|
fromPtr msg Nothing = messageDefault msg
|
|
fromPtr _ (Just (PtrList (U.ListStruct list))) = pure list
|
|
fromPtr _ _ = expected "pointer to list of structs"
|
|
instance ToPtr s (ListOf ('Mut s) (Struct ('Mut s))) where
|
|
toPtr _ = pure . Just . PtrList . U.ListStruct
|
|
|
|
-- To/FromPtr instance for lists of pointers.
|
|
instance FromPtr mut (ListOf mut (Maybe (Ptr mut))) where
|
|
fromPtr msg Nothing = messageDefault msg
|
|
fromPtr _ (Just (PtrList (U.ListPtr list))) = pure list
|
|
fromPtr _ _ = expected "pointer to list of pointers"
|
|
instance ToPtr s (ListOf ('Mut s) (Maybe (Ptr ('Mut s)))) where
|
|
toPtr _ = pure . Just . PtrList . U.ListPtr
|
|
|
|
-- To/FromPtr instance for *typed* lists.
|
|
instance ListElem mut e => FromPtr mut (List mut e) where
|
|
fromPtr = listFromPtr
|
|
instance ListElem ('Mut s) e => ToPtr s (List ('Mut s) e) where
|
|
toPtr _ = pure . Just . PtrList . toUntypedList
|
|
|
|
-- ListElem instance for (typed) nested lists.
|
|
instance ListElem mut e => ListElem mut (List mut e) where
|
|
newtype List mut (List mut e) = NestedList (U.ListOf mut (Maybe (U.Ptr mut)))
|
|
|
|
listFromPtr msg ptr = NestedList <$> fromPtr msg ptr
|
|
toUntypedList (NestedList l) = U.ListPtr l
|
|
|
|
length (NestedList l) = U.length l
|
|
index i (NestedList l) = do
|
|
ptr <- U.index i l
|
|
fromPtr (U.message l) ptr
|
|
|
|
instance MutListElem s e => MutListElem s (List ('Mut s) e) where
|
|
setIndex e i (NestedList l) = U.setIndex (Just (U.PtrList (toUntypedList e))) i l
|
|
newList msg len = NestedList <$> U.allocListPtr msg len
|
|
|
|
-- FromStruct instance for Struct; just the identity.
|
|
instance FromStruct mut (Struct mut) where
|
|
fromStruct = pure
|
|
|
|
instance ToStruct mut (Struct mut) where
|
|
toStruct = id
|
|
|
|
instance FromPtr mut (Struct mut) where
|
|
fromPtr msg Nothing = messageDefault msg >>= fromStruct
|
|
fromPtr _ (Just (PtrStruct s)) = fromStruct s
|
|
fromPtr _ _ = expected "pointer to struct"
|
|
instance ToPtr s (Struct ('Mut s)) where
|
|
toPtr _ = pure . Just . PtrStruct
|
|
|
|
instance FromPtr mut (Maybe (Cap mut)) where
|
|
fromPtr _ Nothing = pure Nothing
|
|
fromPtr _ (Just (PtrCap cap)) = pure (Just cap)
|
|
fromPtr _ _ = expected "pointer to capability"
|
|
instance ToPtr s (Maybe (Cap ('Mut s))) where
|
|
toPtr _ = pure . fmap PtrCap
|
|
|
|
-- | A valid implementation of 'cerialize', which just cerializes the
|
|
-- elements of a list individually and puts them in the list.
|
|
--
|
|
-- Note that while this is *correct* for composite lists, it is inefficient,
|
|
-- since it will separately allocate the elements and then copy them into
|
|
-- the list, doing extra work and leaking space. See 'cerializeCompositeVec'.
|
|
cerializeBasicVec ::
|
|
( U.RWCtx m s
|
|
, MutListElem s (Cerial ('Mut s) a)
|
|
, Cerialize s a
|
|
)
|
|
=> M.Message ('Mut s)
|
|
-> V.Vector a
|
|
-> m (List ('Mut s) (Cerial ('Mut s) a))
|
|
cerializeBasicVec msg vec = do
|
|
list <- newList msg (V.length vec)
|
|
for_ [0..V.length vec - 1] $ \i -> do
|
|
e <- cerialize msg (vec V.! i)
|
|
setIndex e i list
|
|
pure list
|
|
|
|
-- | A valid implementation of 'cerialize', which allocates a list of the
|
|
-- correct size and then marshals the elements of a vector into the elements
|
|
-- of the list. This is more efficient for composite types than
|
|
-- 'cerializeBasicVec', hence the name.
|
|
cerializeCompositeVec ::
|
|
( U.RWCtx m s
|
|
, MutListElem s (Cerial ('Mut s) a)
|
|
, Marshal s a
|
|
)
|
|
=> M.Message ('Mut s)
|
|
-> V.Vector a
|
|
-> m (List ('Mut s) (Cerial ('Mut s) a))
|
|
cerializeCompositeVec msg vec = do
|
|
list <- newList msg (V.length vec)
|
|
for_ [0..V.length vec - 1] $ \i -> do
|
|
targ <- index i list
|
|
marshalInto targ (vec V.! i)
|
|
pure list
|
|
|
|
-- Generic decerialize instances for lists, given that the element type has an instance.
|
|
instance
|
|
( ListElem 'Const (Cerial 'Const a)
|
|
, Decerialize a
|
|
) => Decerialize (V.Vector a)
|
|
where
|
|
type Cerial mut (V.Vector a) = List mut (Cerial mut a)
|
|
decerialize raw = V.generateM (length raw) (\i -> index i raw >>= decerialize)
|