packages feed

capnp-0.17.0.0: lib/Capnp/Accessors.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}

-- | Module: Capnp.Accessors
-- Description: Functions for accessing parts of messaages.
module Capnp.Accessors
  ( readField,
    getField,
    setField,
    newField,
    hasField,
    encodeField,
    parseField,
    setVariant,
    initVariant,
    encodeVariant,
    structWhich,
    unionWhich,
    structUnion,
    unionStruct,
  )
where

import qualified Capnp.Classes as C
import qualified Capnp.Fields as F
import Capnp.Message (Mutability (..))
import qualified Capnp.Repr as R
import Capnp.TraversalLimit (evalLimitT)
import qualified Capnp.Untyped as U
import Data.Bits
import Data.Maybe (fromJust, isJust)
import Data.Word
import GHC.Prim (coerce)

{-# INLINE readField #-}

-- | Read the value of a field of a struct.
readField ::
  forall k a b mut m.
  ( R.IsStruct a,
    U.ReadCtx m mut
  ) =>
  F.Field k a b ->
  R.Raw a mut ->
  m (R.Raw b mut)
readField (F.Field field) (R.Raw struct) =
  case field of
    F.DataField F.DataFieldLoc {shift, index, mask, defaultValue} -> do
      word <- U.getData (fromIntegral index) struct
      pure $ R.Raw $ C.fromWord $ ((word .&. mask) `shiftR` fromIntegral shift) `xor` defaultValue
    F.PtrField index ->
      U.getPtr (fromIntegral index) struct >>= readPtrField
    F.GroupField ->
      pure $ R.Raw struct
    F.VoidField ->
      pure $ R.Raw ()
  where
    -- This is broken out because the type checker needs some extra help:
    readPtrField ::
      forall pr.
      ( R.ReprFor b ~ 'R.Ptr pr,
        R.IsPtrRepr pr
      ) =>
      Maybe (U.Ptr mut) ->
      m (R.Raw b mut)
    readPtrField ptr =
      R.Raw <$> R.fromPtr @pr (U.message @U.Struct struct) ptr

-- | Return whether the specified field is present. Only applicable for pointer
-- fields.
hasField ::
  ( U.ReadCtx m mut,
    R.IsStruct a,
    R.IsPtr b
  ) =>
  F.Field 'F.Slot a b ->
  R.Raw a mut ->
  m Bool
hasField (F.Field (F.PtrField index)) (R.Raw struct) =
  isJust <$> U.getPtr (fromIntegral index) struct

{-# INLINE getField #-}

-- | Like 'readField', but:
--
-- * Doesn't need the monadic context; can be used in pure code.
-- * Only works for immutable values.
-- * Only works for fields in the struct's data section.
getField ::
  ( R.IsStruct a,
    R.ReprFor b ~ 'R.Data sz,
    C.Parse b bp
  ) =>
  F.Field 'F.Slot a b ->
  R.Raw a 'Const ->
  bp
getField field struct =
  fromJust $
    evalLimitT maxBound $
      readField field struct >>= C.parse

{-# INLINE setField #-}

-- | Set a struct field to a value. Not usable for group fields.
setField ::
  forall a b m s.
  ( R.IsStruct a,
    U.RWCtx m s
  ) =>
  F.Field 'F.Slot a b ->
  R.Raw b ('Mut s) ->
  R.Raw a ('Mut s) ->
  m ()
setField (F.Field field) (R.Raw value) (R.Raw struct) =
  case field of
    F.DataField fieldLoc ->
      setDataField fieldLoc
    F.PtrField index ->
      setPtrField index value struct
    F.VoidField ->
      pure ()
  where
    -- This was originally broken out because the type checker needs some extra
    -- help, but it's probably more readable this way anyway.
    setPtrField ::
      forall pr.
      ( R.ReprFor b ~ 'R.Ptr pr,
        R.IsPtrRepr pr
      ) =>
      Word16 ->
      U.Unwrapped (R.UntypedPtr pr ('Mut s)) ->
      U.Struct ('Mut s) ->
      m ()
    setPtrField index value struct =
      U.setPtr (R.toPtr @pr value) (fromIntegral index) struct

    setDataField ::
      forall sz.
      ( R.ReprFor b ~ 'R.Data sz,
        C.IsWord (R.UntypedData sz)
      ) =>
      F.DataFieldLoc sz ->
      m ()
    setDataField F.DataFieldLoc {shift, index, mask, defaultValue} = do
      oldWord <- U.getData (fromIntegral index) struct
      let valueWord = C.toWord value `xor` defaultValue
          newWord =
            (oldWord .&. complement mask)
              .|. (valueWord `shiftL` fromIntegral shift)
      U.setData newWord (fromIntegral index) struct

-- | Allocate space for the value of a field, and return it.
newField ::
  forall a b m s.
  ( R.IsStruct a,
    C.Allocate b,
    U.RWCtx m s
  ) =>
  F.Field 'F.Slot a b ->
  C.AllocHint b ->
  R.Raw a ('Mut s) ->
  m (R.Raw b ('Mut s))
newField field hint parent = do
  value <- C.new @b hint (U.message @(R.Raw a) parent)
  setField field value parent
  pure value

-- | Marshal a parsed value into a struct's field.
encodeField ::
  forall a b m s bp.
  ( R.IsStruct a,
    C.Parse b bp,
    U.RWCtx m s
  ) =>
  F.Field 'F.Slot a b ->
  bp ->
  R.Raw a ('Mut s) ->
  m ()
encodeField field parsed struct = do
  encoded <- C.encode (U.message @(R.Raw a) struct) parsed
  setField field encoded struct

-- | parse a struct's field and return its parsed form.
parseField ::
  ( R.IsStruct a,
    C.Parse b bp,
    U.ReadCtx m 'Const
  ) =>
  F.Field k a b ->
  R.Raw a 'Const ->
  m bp
parseField field raw =
  readField field raw >>= C.parse

-- | Set the struct's anonymous union to the given variant, with the
-- supplied value as its argument. Not applicable for variants whose
-- argument is a group; use 'initVariant' instead.
setVariant ::
  forall a b m s.
  ( F.HasUnion a,
    U.RWCtx m s
  ) =>
  F.Variant 'F.Slot a b ->
  R.Raw a ('Mut s) ->
  R.Raw b ('Mut s) ->
  m ()
setVariant F.Variant {field, tagValue} struct value = do
  setField (F.unionField @a) (R.Raw tagValue) struct
  setField field value struct

-- | Set the struct's anonymous union to the given variant, marshalling
-- the supplied value into the message to be its argument. Not applicable
-- for variants whose argument is a group; use 'initVariant' instead.
encodeVariant ::
  forall a b m s bp.
  ( F.HasUnion a,
    C.Parse b bp,
    U.RWCtx m s
  ) =>
  F.Variant 'F.Slot a b ->
  bp ->
  R.Raw a ('Mut s) ->
  m ()
encodeVariant F.Variant {field, tagValue} value struct = do
  setField (F.unionField @a) (R.Raw tagValue) struct
  encodeField field value struct

-- | Set the struct's anonymous union to the given variant, returning
-- the variant's argument, which must be a group (for non-group fields,
-- use 'setVariant' or 'encodeVariant'.
initVariant ::
  forall a b m s.
  (F.HasUnion a, U.RWCtx m s) =>
  F.Variant 'F.Group a b ->
  R.Raw a ('Mut s) ->
  m (R.Raw b ('Mut s))
initVariant F.Variant {field, tagValue} struct = do
  setField (F.unionField @a) (R.Raw tagValue) struct
  readField field struct

-- | Get the anonymous union for a struct.
structUnion :: F.HasUnion a => R.Raw a mut -> R.Raw (F.Which a) mut
structUnion = coerce

-- | Get the struct enclosing an anonymous union.
unionStruct :: F.HasUnion a => R.Raw (F.Which a) mut -> R.Raw a mut
unionStruct = coerce

-- | Get a non-opaque view on the struct's anonymous union, which
-- can be used to pattern match on.
structWhich :: forall a mut m. (U.ReadCtx m mut, F.HasUnion a) => R.Raw a mut -> m (F.RawWhich a mut)
structWhich struct = do
  R.Raw tagValue <- readField (F.unionField @a) struct
  F.internalWhich tagValue struct

-- | Get a non-opaque view on the anonymous union, which can be
-- used to pattern match on.
unionWhich :: forall a mut m. (U.ReadCtx m mut, F.HasUnion a) => R.Raw (F.Which a) mut -> m (F.RawWhich a mut)
unionWhich = structWhich . unionStruct