packages feed

capnp-0.18.0.0: lib/Capnp/Classes.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

-- | Module: Capnp.Classes
-- Description: Misc. type classes
--
-- This module contains several type classes (and related utilities)
-- useful for operating over Cap'n Proto values.
module Capnp.Classes
  ( -- * Encoding and decoding parsed forms of values
    Parse (..),
    Parsed,
    Marshal (..),
    MarshalElement,

    -- * Allocating values in messages
    Allocate (..),
    newRoot,
    AllocateList (..),
    EstimateAlloc (..),
    EstimateListAlloc (..),
    newFromRepr,

    -- * Setting the root of a message
    setRoot,

    -- * Working with Cap'n Proto types
    HasTypeId (..),

    -- ** Typed Structs
    TypedStruct (..),
    newTypedStruct,
    newTypedStructList,
    structSizes,

    -- ** Inheritance
    Super,

    -- * Values that go in a struct's data section
    IsWord (..),
  )
where

import Capnp.Bits
import Capnp.Message (Mutability (..))
import qualified Capnp.Message as M
import qualified Capnp.Repr as R
import Capnp.TraversalLimit (evalLimitT)
import qualified Capnp.Untyped as U
import Data.Bits
import Data.Default (Default (..))
import Data.Foldable (for_)
import Data.Int
import Data.Word
import qualified GHC.Float as F
import qualified Language.Haskell.TH as TH

-- | Capnp types that can be parsed into a more "natural" Haskell form.
--
-- * @t@ is the capnproto type.
-- * @p@ is the type of the parsed value.
class Parse t p | t -> p, p -> t where
  parse :: U.ReadCtx m 'Const => R.Raw t 'Const -> m p
  -- ^ Parse a value from a constant message

  encode :: U.RWCtx m s => M.Message ('Mut s) -> p -> m (R.Raw t ('Mut s))
  -- ^ Encode a value into 'R.Raw' form, using the message as storage.
  default encode ::
    (U.RWCtx m s, EstimateAlloc t p, Marshal t p) =>
    M.Message ('Mut s) ->
    p ->
    m (R.Raw t ('Mut s))
  encode msg value = do
    raw <- new (estimateAlloc value) msg
    marshalInto raw value
    pure raw
  {-# INLINEABLE encode #-}

-- | Types where the necessary allocation is inferrable from the parsed form.
--
-- ...this is most types.
class (Parse t p, Allocate t) => EstimateAlloc t p where
  -- | Determine the appropriate hint needed to allocate space
  -- for the serialied form of the value.
  estimateAlloc :: p -> AllocHint t
  default estimateAlloc :: AllocHint t ~ () => p -> AllocHint t
  estimateAlloc _ = ()
  {-# INLINEABLE estimateAlloc #-}

-- | Implementation of 'new' valid for types whose 'AllocHint' is
-- the same as that of their underlying representation.
newFromRepr ::
  forall a r m s.
  ( R.Allocate r,
    'R.Ptr ('Just r) ~ R.ReprFor a,
    U.RWCtx m s
  ) =>
  R.AllocHint r ->
  M.Message ('Mut s) ->
  m (R.Raw a ('Mut s))
{-# INLINEABLE newFromRepr #-}
newFromRepr hint msg = R.Raw <$> R.alloc @r msg hint

-- TODO(cleanup): new and alloc really ought to have the same argument order...

-- | Types which may be allocated directly inside a message.
class Allocate a where
  type AllocHint a
  -- ^ Extra information needed to allocate a value of this type, e.g. the
  -- length for a list. May be () if no extra info is needed.

  new :: U.RWCtx m s => AllocHint a -> M.Message ('Mut s) -> m (R.Raw a ('Mut s))
  -- ^ @'new' hint msg@ allocates a new value of type @a@ inside @msg@.
  default new ::
    ( R.ReprFor a ~ 'R.Ptr ('Just pr),
      R.Allocate pr,
      AllocHint a ~ R.AllocHint pr,
      U.RWCtx m s
    ) =>
    AllocHint a ->
    M.Message ('Mut s) ->
    m (R.Raw a ('Mut s))
  -- If the AllocHint is the same as that of the underlying Repr, then
  -- we can just use that implementation.
  new = newFromRepr @a
  {-# INLINEABLE new #-}

-- | Like 'Allocate', but for allocating *lists* of @a@.
class AllocateList a where
  type ListAllocHint a
  -- ^ Extra information needed to allocate a list of @a@s.

  newList :: U.RWCtx m s => ListAllocHint a -> M.Message ('Mut s) -> m (R.Raw (R.List a) ('Mut s))
  default newList ::
    forall m s lr r.
    ( U.RWCtx m s,
      lr ~ R.ListReprFor (R.ReprFor a),
      r ~ 'R.List ('Just lr),
      R.Allocate r,
      R.AllocHint r ~ ListAllocHint a
    ) =>
    ListAllocHint a ->
    M.Message ('Mut s) ->
    m (R.Raw (R.List a) ('Mut s))
  newList hint msg = R.Raw <$> R.alloc @r msg hint
  {-# INLINEABLE newList #-}

instance AllocateList a => Allocate (R.List a) where
  type AllocHint (R.List a) = ListAllocHint a
  new = newList @a
  {-# INLINEABLE new #-}

instance AllocateList (R.List a) where
  type ListAllocHint (R.List a) = Int

instance
  ( Parse (R.List a) [ap],
    Allocate (R.List a)
  ) =>
  EstimateListAlloc (R.List a) [ap]

-- | Allocate a new typed struct. Mainly used as the value for 'new' for in generated
-- instances of 'Allocate'.
newTypedStruct :: forall a m s. (TypedStruct a, U.RWCtx m s) => M.Message ('Mut s) -> m (R.Raw a ('Mut s))
{-# INLINEABLE newTypedStruct #-}
newTypedStruct = newFromRepr (structSizes @a)

-- | Like 'newTypedStruct', but for lists.
newTypedStructList ::
  forall a m s.
  (TypedStruct a, U.RWCtx m s) =>
  Int ->
  M.Message ('Mut s) ->
  m (R.Raw (R.List a) ('Mut s))
{-# INLINEABLE newTypedStructList #-}
newTypedStructList i msg =
  R.Raw
    <$> R.alloc
      @('R.List ('Just 'R.ListComposite))
      msg
      (i, structSizes @a)

-- | An instance of marshal allows a parsed value to be inserted into
-- pre-allocated space in a message.
class Parse t p => Marshal t p where
  marshalInto :: U.RWCtx m s => R.Raw t ('Mut s) -> p -> m ()
  -- ^ 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.

-- | Get the maximum word and pointer counts needed for a struct type's fields.
structSizes :: forall a. TypedStruct a => (Word16, Word16)
{-# INLINEABLE structSizes #-}
structSizes = (numStructWords @a, numStructPtrs @a)

-- | Types which have a numeric type-id defined in a capnp schema.
class HasTypeId a where
  -- | The node id for this type. You will generally want to use the
  -- @TypeApplications@ extension to specify the type.
  typeId :: Word64

-- | Operations on typed structs.
class (R.IsStruct a, Allocate a, HasTypeId a, AllocHint a ~ ()) => TypedStruct a where
  -- Get the size of  the struct's word and pointer sections, respectively.
  numStructWords :: Word16
  numStructPtrs :: Word16

-- | Like 'new', but also sets the value as the root of the message.
newRoot ::
  forall a m s.
  (U.RWCtx m s, R.IsStruct a, Allocate a) =>
  AllocHint a ->
  M.Message ('Mut s) ->
  m (R.Raw a ('Mut s))
{-# INLINEABLE newRoot #-}
newRoot hint msg = do
  raw <- new @a hint msg
  setRoot raw
  pure raw

-- | Sets the struct to be the root of its containing message.
setRoot :: (U.RWCtx m s, R.IsStruct a) => R.Raw a ('Mut s) -> m ()
{-# INLINEABLE setRoot #-}
setRoot (R.Raw struct) = U.setRoot struct

------ Instances for basic types -------

parseId :: (R.Untyped (R.ReprFor a) mut ~ U.IgnoreMut a mut, U.ReadCtx m mut) => R.Raw a mut -> m a
{-# INLINEABLE parseId #-}
parseId (R.Raw v) = pure v

parseInt ::
  ( Integral a,
    Integral (U.Unwrapped (R.Untyped (R.ReprFor a) mut)),
    U.ReadCtx m mut
  ) =>
  R.Raw a mut ->
  m a
{-# INLINEABLE parseInt #-}
parseInt = pure . fromIntegral . R.fromRaw

instance Parse Float Float where
  parse = pure . F.castWord32ToFloat . R.fromRaw
  encode _ = pure . R.Raw . F.castFloatToWord32

instance Parse Double Double where
  parse = pure . F.castWord64ToDouble . R.fromRaw
  encode _ = pure . R.Raw . F.castDoubleToWord64

instance MarshalElement a ap => Marshal (R.List a) [ap] where
  marshalInto raw value =
    for_ (zip [0 ..] value) $ \(i, v) ->
      marshalElement raw i v

instance MarshalElement a ap => Parse (R.List a) [ap] where
  parse rawV = go [] (R.length rawV - 1)
    where
      go acc i
        | i < 0 = pure acc
        | otherwise = do
            item <- R.index i rawV >>= parse
            go (item : acc) (i - 1)

-- | Type alias capturing the constraints on a type needed by
-- 'marshalElement'
type MarshalElement a ap =
  ( Parse a ap,
    EstimateListAlloc a ap,
    R.Element (R.ReprFor a),
    U.ListItem (R.ElemRepr (R.ListReprFor (R.ReprFor a))),
    U.HasMessage (U.ListOf (R.ElemRepr (R.ListReprFor (R.ReprFor a)))),
    MarshalElementByRepr (R.ListReprFor (R.ReprFor a)),
    MarshalElementReprConstraints (R.ListReprFor (R.ReprFor a)) a ap
  )

-- | Constraints needed by marshalElement that are specific to a list repr.
type family MarshalElementReprConstraints (lr :: R.ListRepr) a ap where
  MarshalElementReprConstraints 'R.ListComposite a ap = Marshal a ap
  MarshalElementReprConstraints ('R.ListNormal r) a ap = Parse a ap

class
  U.HasMessage (U.ListOf ('R.Ptr ('Just ('R.List ('Just lr))))) =>
  MarshalElementByRepr (lr :: R.ListRepr)
  where
  marshalElementByRepr ::
    ( U.RWCtx m s,
      R.ListReprFor (R.ReprFor a) ~ lr,
      MarshalElement a ap
    ) =>
    R.Raw (R.List a) ('Mut s) ->
    Int ->
    ap ->
    m ()

-- | An instance @'Super' p c@ indicates that the interface @c@ extends
-- the interface @p@.
class (R.IsCap p, R.IsCap c) => Super p c

instance MarshalElementByRepr 'R.ListComposite where
  marshalElementByRepr rawList i parsed = do
    rawElt <- R.index i rawList
    marshalInto rawElt parsed
  {-# INLINEABLE marshalElementByRepr #-}

instance
  ( U.HasMessage (U.ListOf (R.ElemRepr ('R.ListNormal l))),
    U.ListItem (R.ElemRepr ('R.ListNormal l))
  ) =>
  MarshalElementByRepr ('R.ListNormal l)
  where
  marshalElementByRepr rawList@(R.Raw ulist) i parsed = do
    rawElt <-
      encode
        (U.message @(U.Untyped ('R.Ptr ('Just ('R.List ('Just ('R.ListNormal l)))))) ulist)
        parsed
    R.setIndex rawElt i rawList
  {-# INLINEABLE marshalElementByRepr #-}

marshalElement ::
  forall a ap m s.
  ( U.RWCtx m s,
    MarshalElement a ap
  ) =>
  R.Raw (R.List a) ('Mut s) ->
  Int ->
  ap ->
  m ()
{-# INLINEABLE marshalElement #-}
marshalElement = marshalElementByRepr @(R.ListReprFor (R.ReprFor a))

class (Parse a ap, Allocate (R.List a)) => EstimateListAlloc a ap where
  estimateListAlloc :: [ap] -> AllocHint (R.List a)
  default estimateListAlloc :: (AllocHint (R.List a) ~ Int) => [ap] -> AllocHint (R.List a)
  estimateListAlloc = length
  {-# INLINEABLE estimateListAlloc #-}

instance MarshalElement a ap => EstimateAlloc (R.List a) [ap] where
  estimateAlloc = estimateListAlloc @a
  {-# INLINEABLE estimateAlloc #-}

-- | If @a@ is a capnproto type, then @Parsed a@ is an ADT representation of that
-- type. If this is defined for a type @a@ then there should also be an instance
-- @'Parse' a ('Parsed' a)@, but note that the converse is not true: if there is
-- an instance @'Parse' a b@, then @'Parsed' a@ needn't be defined, and @b@ can
-- be something else.
data family Parsed a

instance (Default (R.Raw a 'Const), Parse a (Parsed a)) => Default (Parsed a) where
  def = case evalLimitT maxBound (parse @a def) of
    Just v -> v
    Nothing -> error "Parsing default value failed."
  {-# INLINEABLE def #-}

do
  let mkId ty =
        [d|
          instance Parse $ty $ty where
            parse = parseId
            {-# INLINEABLE parse #-}
            encode _ = pure . R.Raw
            {-# INLINEABLE encode #-}
          |]
      mkInt ty =
        [d|
          instance Parse $ty $ty where
            parse = parseInt
            {-# INLINEABLE parse #-}
            encode _ = pure . R.Raw . fromIntegral
            {-# INLINEABLE encode #-}
          |]
      mkAll ty =
        [d|
          instance AllocateList $ty where
            type ListAllocHint $ty = Int

          instance EstimateListAlloc $ty $ty where
            estimateListAlloc = length
            {-# INLINEABLE estimateListAlloc #-}
          |]

      nameTy name = pure (TH.ConT name)

      ids = [t|()|] : map nameTy [''Bool, ''Word8, ''Word16, ''Word32, ''Word64]
      ints = map nameTy [''Int8, ''Int16, ''Int32, ''Int64]
      floats = map nameTy [''Float, ''Double]
      allTys = ids ++ ints ++ floats

      merge :: [TH.Q [a]] -> TH.Q [a]
      merge xs = concat <$> sequenceA xs
  merge
    [ merge $ map mkId ids,
      merge $ map mkInt ints,
      merge $ map mkAll allTys
    ]

-- | 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

------- IsWord instances. TODO: fold into TH above. -------

instance IsWord Bool where
  fromWord n = (n .&. 1) == 1
  {-# INLINEABLE fromWord #-}
  toWord True = 1
  toWord False = 0
  {-# INLINEABLE toWord #-}

instance IsWord Word1 where
  fromWord = Word1 . fromWord
  {-# INLINEABLE fromWord #-}
  toWord = toWord . word1ToBool
  {-# INLINEABLE toWord #-}

-- IsWord instances for integral types; they're all the same.
do
  let mkInstance t =
        [d|
          instance IsWord $t where
            fromWord = fromIntegral
            {-# INLINEABLE fromWord #-}
            toWord = fromIntegral
            {-# INLINEABLE toWord #-}
          |]
  concat
    <$> traverse
      mkInstance
      [ [t|Int8|],
        [t|Int16|],
        [t|Int32|],
        [t|Int64|],
        [t|Word8|],
        [t|Word16|],
        [t|Word32|],
        [t|Word64|]
      ]

instance IsWord Float where
  fromWord = F.castWord32ToFloat . fromIntegral
  {-# INLINEABLE fromWord #-}
  toWord = fromIntegral . F.castFloatToWord32
  {-# INLINEABLE toWord #-}

instance IsWord Double where
  fromWord = F.castWord64ToDouble
  {-# INLINEABLE fromWord #-}
  toWord = F.castDoubleToWord64
  {-# INLINEABLE toWord #-}