capnp-0.1.0.0: lib/Data/Capnp/Untyped/Pure.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-| This module provides an idiomatic Haskell interface for untyped capnp
data, based on algebraic datatypes. It forgoes some of the benefits of
the capnp wire format in favor of a more convienient API.
In addition to the algebraic data types themselves, this module also
provides support for converting from the lower-level types in
Data.Capnp.Untyped.
-}
module Data.Capnp.Untyped.Pure
( Cap(..)
, Slice(..)
, PtrType(..)
, Struct(..)
, List(..)
, ListOf(..)
, length
, sliceIndex
)
where
import Prelude hiding (length)
import Data.Word
import Control.Monad (forM_)
import Data.Default (Default(def))
import Data.Default.Instances.Vector ()
import GHC.Exts (IsList(..))
import GHC.Generics (Generic)
import Data.Capnp.Classes
(Cerialize(..), Decerialize(..), IsPtr(..), Marshal(..))
import Internal.Gen.Instances ()
import qualified Data.Capnp.Classes as C
import qualified Data.Capnp.Message as M
import qualified Data.Capnp.Untyped as U
import qualified Data.Vector as V
-- | A capability in the wire format.
type Cap = Word32
-- | A one of a struct's sections (data or pointer).
--
-- This is just a newtype wrapper around 'ListOf' (which is itself just
-- 'V.Vector'), but critically the notion of equality is different. Two
-- slices are considered equal if all of their elements are equal, but
-- If the slices are different lengths, missing elements are treated as
-- having default values. Accordingly, equality is only defined if the
-- element type is an instance of 'Default'.
newtype Slice a = Slice (ListOf a)
deriving(Generic, Show, Read, Ord, Functor, Default)
-- | A capnproto pointer type.
data PtrType
= PtrStruct !Struct
| PtrList !List
| PtrCap !Cap
deriving(Generic, Show, Read, Eq)
-- | A capnproto struct.
data Struct = Struct
{ structData :: Slice Word64
-- ^ The struct's data section
, structPtrs :: Slice (Maybe PtrType)
-- ^ The struct's pointer section
}
deriving(Generic, Show, Read, Eq)
instance Default Struct
-- | An untyped list.
data List
= List0 (ListOf ())
| List1 (ListOf Bool)
| List8 (ListOf Word8)
| List16 (ListOf Word16)
| List32 (ListOf Word32)
| List64 (ListOf Word64)
| ListPtr (ListOf (Maybe PtrType))
| ListStruct (ListOf Struct)
deriving(Generic, Show, Read, Eq)
-- | Alias for 'V.Vector'. Using this alias may make upgrading to future
-- versions of the library easier, as we will likely switch to a more
-- efficient representation at some point.
type ListOf a = V.Vector a
-- Cookie-cutter IsList instance. This is derivable with
-- GeneralizedNewtypeDeriving as of ghc >= 8.2.1, but not on
-- 8.0.x, due to the associated type.
instance IsList (Slice a) where
type Item (Slice a) = a
toList (Slice list) = toList list
fromList = Slice . fromList
fromListN n = Slice . fromListN n
-- | Alias for vector's 'V.length'.
length :: ListOf a -> Int
length = V.length
-- | Index into a slice, returning a default value if the the index is past
-- the end of the array.
sliceIndex :: Default a => Int -> Slice a -> a
sliceIndex i (Slice vec)
| i < V.length vec = vec V.! i
| otherwise = def
instance (Default a, Eq a) => Eq (Slice a) where
-- We define equality specially (rather than just deriving), such that
-- slices are padded out with the default values of their elements.
l@(Slice vl) == r@(Slice vr) = go (max (length vl) (length vr) - 1)
where
go (-1) = True -- can happen if both slices are empty.
go 0 = True
go i = sliceIndex i l == sliceIndex i r && go (i-1)
instance Decerialize Struct where
type Cerial msg Struct = U.Struct msg
decerialize struct = Struct
<$> (Slice <$> decerializeListOfWord (U.dataSection struct))
<*> (Slice <$> decerializeListOf (U.ptrSection struct))
instance Marshal Struct where
marshalInto raw (Struct (Slice dataSec) (Slice ptrSec)) = do
forM_ [0..V.length dataSec - 1] $ \i ->
U.setData (dataSec V.! i) i raw
forM_ [0..V.length ptrSec - 1] $ \i -> do
ptr <- cerialize (U.message raw) (ptrSec V.! i)
U.setPtr ptr i raw
instance Cerialize s Struct where
cerialize msg struct@(Struct (Slice dataSec) (Slice ptrSec)) = do
raw <- U.allocStruct
msg
(fromIntegral $ V.length dataSec)
(fromIntegral $ V.length ptrSec)
marshalInto raw struct
pure raw
instance Decerialize (Maybe PtrType) where
type Cerial msg (Maybe PtrType) = Maybe (U.Ptr msg)
decerialize Nothing = pure Nothing
decerialize (Just ptr) = Just <$> case ptr of
U.PtrCap _ cap -> return (PtrCap cap)
U.PtrStruct struct -> PtrStruct <$> decerialize struct
U.PtrList list -> PtrList <$> decerialize list
instance Cerialize s (Maybe PtrType) where
cerialize _ Nothing = pure Nothing
cerialize msg (Just (PtrStruct struct)) = toPtr <$> cerialize msg struct
cerialize msg (Just (PtrList list)) = Just . U.PtrList <$> cerialize msg list
-- TODO: when we actually support it, we need to insert the cap into the message:
cerialize msg (Just (PtrCap cap)) = pure $ Just (U.PtrCap msg cap)
-- Generic decerialize instances for lists. TODO: this doesn't really belong
-- in Untyped, since this is mostly used for typed lists. maybe Basics.
instance
( C.ListElem M.ConstMsg (Cerial M.ConstMsg a)
, Decerialize a
) => Decerialize (ListOf a)
where
type Cerial msg (ListOf a) = C.List msg (Cerial msg a)
decerialize raw = V.generateM (C.length raw) (\i -> C.index i raw >>= decerialize)
-- | Decerialize an untyped list, whose elements are instances of Decerialize. This isn't
-- an instance, since it would have to be an instance of (List a), which conflicts with
-- the above.
decerializeListOf :: (U.ReadCtx m M.ConstMsg, Decerialize a)
=> U.ListOf M.ConstMsg (Cerial M.ConstMsg a) -> m (ListOf a)
decerializeListOf raw = V.generateM (U.length raw) (\i -> U.index i raw >>= decerialize)
-- | Decerialize an untyped list, leaving the elements of the list as-is. The is most
-- interesting for types that go in the data section of a struct, hence the name.
decerializeListOfWord :: (U.ReadCtx m M.ConstMsg)
=> U.ListOf M.ConstMsg a -> m (ListOf a)
decerializeListOfWord raw = V.generateM (U.length raw) (`U.index` raw)
instance Decerialize List where
type Cerial msg List = U.List msg
decerialize (U.List0 l) = List0 <$> decerializeListOfWord l
decerialize (U.List1 l) = List1 <$> decerializeListOfWord l
decerialize (U.List8 l) = List8 <$> decerializeListOfWord l
decerialize (U.List16 l) = List16 <$> decerializeListOfWord l
decerialize (U.List32 l) = List32 <$> decerializeListOfWord l
decerialize (U.List64 l) = List64 <$> decerializeListOfWord l
decerialize (U.ListPtr l) = ListPtr <$> decerializeListOf l
decerialize (U.ListStruct l) = ListStruct <$> decerializeListOf l
instance Cerialize s List where
cerialize msg (List0 l) = U.List0 <$> U.allocList0 msg (length l)
cerialize msg (List1 l) = U.List1 <$> cerializeListOfWord (U.allocList1 msg) l
cerialize msg (List8 l) = U.List8 <$> cerializeListOfWord (U.allocList8 msg) l
cerialize msg (List16 l) = U.List16 <$> cerializeListOfWord (U.allocList16 msg) l
cerialize msg (List32 l) = U.List32 <$> cerializeListOfWord (U.allocList32 msg) l
cerialize msg (List64 l) = U.List64 <$> cerializeListOfWord (U.allocList64 msg) l
cerialize msg (ListPtr l) = do
raw <- U.allocListPtr msg (length l)
forM_ [0..length l - 1] $ \i -> do
ptr <- cerialize msg (l V.! i)
U.setIndex ptr i raw
pure $ U.ListPtr raw
cerialize msg (ListStruct l) = do
let (maxData, maxPtrs) = measureStructSizes l
raw <- U.allocCompositeList msg maxData maxPtrs (length l)
forM_ [0..length l - 1] $ \i -> do
elt <- U.index i raw
marshalInto elt (l V.! i)
pure $ U.ListStruct raw
where
-- Find the maximum sizes of each section of any of the structs
-- in the list. This is the size we need to set in the tag word.
measureStructSizes :: ListOf Struct -> (Word16, Word16)
measureStructSizes = foldl
(\(!dataSz, !ptrSz) (Struct (Slice dataSec) (Slice ptrSec)) ->
( max dataSz (fromIntegral $ length dataSec)
, max ptrSz (fromIntegral $ length ptrSec)
)
)
(0, 0)
cerializeListOfWord :: U.RWCtx m s => (Int -> m (U.ListOf (M.MutMsg s) a)) -> ListOf a -> m (U.ListOf (M.MutMsg s) a)
cerializeListOfWord alloc list = do
ret <- alloc (length list)
marshalListOfWord ret list
pure ret
marshalListOfWord :: U.RWCtx m s => U.ListOf (M.MutMsg s) a -> ListOf a -> m ()
marshalListOfWord raw l =
forM_ [0..length l - 1] $ \i ->
U.setIndex (l V.! i) i raw