{-# LANGUAGE DataKinds #-} {-# LANGUAGE EmptyDataDeriving #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} -- | -- Module: Capnp.New.Basics -- Description: Handling of "basic" capnp datatypes. -- -- This module contains phantom types for built-in Cap'n Proto -- types, analogous to the phantom types generated for structs -- by the code generator. It also defines applicable type class -- instances. module Capnp.New.Basics where -- XXX: I(zenhack) don't know how to supply an explicit -- export list here, since we have instances of data families -- and I don't know what to call the instances to get all of the -- constructors. import qualified Capnp.Errors as E import qualified Capnp.Message as M import qualified Capnp.New.Classes as C import qualified Capnp.Repr as R import qualified Capnp.Untyped as U import Control.Monad (when) import Control.Monad.Catch (throwM) import qualified Data.ByteString as BS import Data.Foldable (for_) import qualified Data.Text as T import qualified Data.Text.Encoding as TE import qualified Data.Vector as V import Data.Word import GHC.Prim (coerce) -- | The Cap'n Proto @Text@ type. data Text -- | The Cap'n Proto @Data@ type. data Data -- | A Cap'n Proto @AnyPointer@, i.e. an arbitrary pointer with unknown schema. data AnyPointer -- | A Cap'n Proto @List@ with unknown element type. data AnyList -- | A Cap'n Proto struct of unknown type. data AnyStruct -- | A Cap'n Proto capability with unknown interfaces. data Capability type instance R.ReprFor Data = R.ReprFor (R.List Word8) type instance R.ReprFor Text = R.ReprFor (R.List Word8) type instance R.ReprFor AnyPointer = 'R.Ptr 'Nothing type instance R.ReprFor (Maybe AnyPointer) = 'R.Ptr 'Nothing type instance R.ReprFor AnyList = 'R.Ptr ('Just ('R.List 'Nothing)) type instance R.ReprFor AnyStruct = 'R.Ptr ('Just 'R.Struct) type instance R.ReprFor Capability = 'R.Ptr ('Just 'R.Cap) data instance C.Parsed AnyPointer = PtrStruct (C.Parsed AnyStruct) | PtrList (C.Parsed AnyList) | PtrCap M.Client deriving(Show, Eq) instance C.Parse (Maybe AnyPointer) (Maybe (C.Parsed AnyPointer)) where parse (R.Raw ptr) = case ptr of Nothing -> pure Nothing Just _ -> Just <$> C.parse (R.Raw ptr :: R.Raw 'M.Const AnyPointer) encode msg value = R.Raw <$> case value of Nothing -> pure Nothing Just v -> coerce <$> C.encode msg v instance C.Parse AnyPointer (C.Parsed AnyPointer) where parse (R.Raw ptr) = case ptr of Just (U.PtrCap cap) -> PtrCap <$> C.parse (R.Raw cap) Just (U.PtrList list) -> PtrList <$> C.parse (R.Raw list) Just (U.PtrStruct struct) -> PtrStruct <$> C.parse (R.Raw struct) Nothing -> throwM $ E.SchemaViolationError "Non-nullable AnyPointer was null" encode msg value = R.Raw <$> case value of PtrCap cap -> Just . U.PtrCap . R.fromRaw <$> C.encode msg cap PtrList list -> Just . U.PtrList . R.fromRaw <$> C.encode msg list PtrStruct struct -> Just . U.PtrStruct . R.fromRaw <$> C.encode msg struct instance C.AllocateList AnyPointer where type ListAllocHint AnyPointer = Int instance C.EstimateListAlloc AnyPointer (C.Parsed AnyPointer) instance C.AllocateList (Maybe AnyPointer) where type ListAllocHint (Maybe AnyPointer) = Int instance C.EstimateListAlloc (Maybe AnyPointer) (Maybe (C.Parsed AnyPointer)) data instance C.Parsed AnyStruct = Struct { structData :: V.Vector Word64 , structPtrs :: V.Vector (Maybe (C.Parsed AnyPointer)) } deriving(Show, Eq) instance C.Parse AnyStruct (C.Parsed AnyStruct) where parse (R.Raw s) = Struct <$> V.generateM (fromIntegral $ U.structWordCount s) (`U.getData` s) <*> V.generateM (fromIntegral $ U.structPtrCount s) (\i -> U.getPtr i s >>= C.parse . R.Raw) instance C.AllocateList AnyStruct where type ListAllocHint AnyStruct = (Int, R.AllocHint 'R.Struct) instance C.EstimateListAlloc AnyStruct (C.Parsed AnyStruct) where estimateListAlloc structs = let len = V.length structs nWords = maximum $ map (V.length . structData) $ V.toList structs nPtrs = maximum $ map (V.length . structPtrs) $ V.toList structs in (len, (fromIntegral nWords, fromIntegral nPtrs)) instance C.EstimateAlloc AnyStruct (C.Parsed AnyStruct) where estimateAlloc s = ( fromIntegral $ V.length $ structData s , fromIntegral $ V.length $ structPtrs s ) instance C.Marshal AnyStruct (C.Parsed AnyStruct) where marshalInto (R.Raw raw) s = do V.iforM_ (structData s) $ \i value -> do U.setData value i raw V.iforM_ (structPtrs s) $ \i value -> do R.Raw ptr <- C.encode (U.message raw) value U.setPtr ptr i raw -- TODO(cleanup): It would be nice if we could reuse Capnp.Repr.Parsed.Parsed -- here, but that would cause a circular import dependency. type ParsedList a = V.Vector a data instance C.Parsed AnyList = ListPtr (ParsedList (Maybe (C.Parsed AnyPointer))) | ListStruct (ParsedList (C.Parsed AnyStruct)) | List0 (ParsedList ()) | List1 (ParsedList Bool) | List8 (ParsedList Word8) | List16 (ParsedList Word16) | List32 (ParsedList Word32) | List64 (ParsedList Word64) deriving(Show, Eq) instance C.Parse AnyList (C.Parsed AnyList) where parse (R.Raw list) = case list of U.List0 l -> List0 <$> C.parse (R.Raw l) U.List1 l -> List1 <$> C.parse (R.Raw l) U.List8 l -> List8 <$> C.parse (R.Raw l) U.List16 l -> List16 <$> C.parse (R.Raw l) U.List32 l -> List32 <$> C.parse (R.Raw l) U.List64 l -> List64 <$> C.parse (R.Raw l) U.ListPtr l -> ListPtr <$> C.parse (R.Raw l) U.ListStruct l -> ListStruct <$> C.parse (R.Raw l) encode msg list = R.Raw <$> case list of List0 l -> U.List0 . R.fromRaw <$> C.encode msg l List1 l -> U.List1 . R.fromRaw <$> C.encode msg l List8 l -> U.List8 . R.fromRaw <$> C.encode msg l List16 l -> U.List16 . R.fromRaw <$> C.encode msg l List32 l -> U.List32 . R.fromRaw <$> C.encode msg l List64 l -> U.List64 . R.fromRaw <$> C.encode msg l ListPtr l -> U.ListPtr . R.fromRaw <$> C.encode msg l ListStruct l -> U.ListStruct . R.fromRaw <$> C.encode msg l instance C.Parse Capability M.Client where parse (R.Raw cap) = U.getClient cap encode msg client = R.Raw <$> U.appendCap msg client instance C.Allocate Text where type AllocHint Text = Int new len msg = R.Raw <$> U.allocList8 msg (len + 1) instance C.AllocateList Text where type ListAllocHint Text = Int instance C.EstimateListAlloc Text T.Text instance C.Parse Text T.Text where parse (R.Raw list) = let len = U.length list in if len == 0 then -- We are somewhat lenient here; technically this is invalid, as there is -- no null terminator (see logic below, which is dead code because of -- this check. But to avoid this we really need to expose nullability -- in the API, so for now we just fudge it. pure "" else (do when (len == 0) $ throwM $ E.SchemaViolationError "Text is not NUL-terminated (list of bytes has length 0)" lastByte <- U.index (len - 1) list when (lastByte /= 0) $ throwM $ E.SchemaViolationError $ "Text is not NUL-terminated (last byte is " ++ show lastByte ++ ")" bytes <- BS.take (len - 1) <$> U.rawBytes list case TE.decodeUtf8' bytes of Left e -> throwM $ E.InvalidUtf8Error e Right v -> pure v) encode msg value = do let bytes = TE.encodeUtf8 value raw@(R.Raw untyped) <- C.new @Text (BS.length bytes) msg C.marshalInto @Data (R.Raw untyped) bytes pure raw -- Instances for Data instance C.Parse Data BS.ByteString where parse = U.rawBytes . R.fromRaw instance C.Allocate Data where type AllocHint Data = Int new len msg = R.Raw <$> U.allocList8 msg len instance C.EstimateAlloc Data BS.ByteString where estimateAlloc = BS.length instance C.AllocateList Data where type ListAllocHint Data = Int instance C.EstimateListAlloc Data BS.ByteString instance C.Marshal Data BS.ByteString where marshalInto (R.Raw list) bytes = for_ [0..BS.length bytes - 1] $ \i -> U.setIndex (BS.index bytes i) i list -- Instances for AnyStruct instance C.Allocate AnyStruct where type AllocHint AnyStruct = (Word16, Word16) new (nWords, nPtrs) msg = R.Raw <$> U.allocStruct msg nWords nPtrs