{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
module Haskus.Binary.Record
( Record
, Field
, RecordSize
, Alignment
, Path
, recordSize
, recordAlignment
, recordField
, recordFieldOffset
, recordFieldPath
, recordFieldPathOffset
, recordToList
)
where
import System.IO.Unsafe
import Foreign.ForeignPtr
import Foreign.Ptr
import Haskus.Memory.Utils
import Haskus.Binary.Storable
import Haskus.Utils.HList
import Haskus.Utils.Types
newtype Record (fields :: [*]) = Record (ForeignPtr ())
data Field (name :: Symbol) typ
type family RecordSize (fs :: [*]) (sz :: Nat) where
RecordSize '[] sz = sz
RecordSize (Field name typ ': fs) sz =
RecordSize fs
(sz
+ Padding sz typ
+ SizeOf typ
)
type family FieldOffset (name :: Symbol) (fs :: [*]) (sz :: Nat) where
FieldOffset name (Field name typ ': fs) sz =
sz + Padding sz typ
FieldOffset name (Field xx typ ': fs) sz =
FieldOffset name fs
(sz + Padding sz typ + SizeOf typ)
type family FieldType (name :: Symbol) (fs :: [*]) where
FieldType name (Field name typ ': fs) = typ
FieldType name (Field xx typ ': fs) = FieldType name fs
type family FullRecordSize fs where
FullRecordSize fs =
RecordSize fs 0
+ PaddingEx (Mod (RecordSize fs 0) (RecordAlignment fs 1))
(RecordAlignment fs 1)
type family RecordAlignment (fs :: [*]) a where
RecordAlignment '[] a = a
RecordAlignment (Field name typ ': fs) a =
RecordAlignment fs
(If (a <=? Alignment typ) (Alignment typ) a)
type family FieldPathOffset (fs :: [*]) (path :: [Symbol]) (off :: Nat) where
FieldPathOffset fs '[p] off = off + FieldOffset p fs 0
FieldPathOffset fs (p ': ps) off
= FieldPathOffset (ExtractRecord (FieldType p fs))
ps (off + FieldOffset p fs 0)
type family FieldPathType (fs :: [*]) (path :: [Symbol]) where
FieldPathType fs '[p] = FieldType p fs
FieldPathType fs (p ': ps)
= FieldPathType (ExtractRecord (FieldType p fs)) ps
type family ExtractRecord x where
ExtractRecord (Record fs) = fs
recordSize :: forall fs.
( KnownNat (FullRecordSize fs)
) => Record fs -> Word
recordSize _ = natValue' @(FullRecordSize fs)
recordAlignment :: forall fs.
( KnownNat (RecordAlignment fs 1)
) => Record fs -> Word
recordAlignment _ = natValue' @(RecordAlignment fs 1)
recordFieldOffset :: forall (name :: Symbol) fs.
( KnownNat (FieldOffset name fs 0)
) => Record fs -> Int
recordFieldOffset _ = natValue @(FieldOffset name fs 0)
recordField :: forall (name :: Symbol) a fs.
( KnownNat (FieldOffset name fs 0)
, a ~ FieldType name fs
, StaticStorable a
) => Record fs -> a
recordField r@(Record fp) = unsafePerformIO $
withForeignPtr fp $ \ptr ->do
let ptr' = ptr `plusPtr` recordFieldOffset @name r
staticPeek (castPtr ptr')
data Path (fs :: [Symbol])
recordFieldPathOffset :: forall path fs o.
( o ~ FieldPathOffset fs path 0
, KnownNat o
) => Path path -> Record fs -> Int
recordFieldPathOffset _ _ = natValue @o
recordFieldPath :: forall path a fs o.
( o ~ FieldPathOffset fs path 0
, a ~ FieldPathType fs path
, KnownNat o
, StaticStorable a
) => Path path -> Record fs -> a
recordFieldPath _ (Record fp) = unsafePerformIO $
withForeignPtr fp $ \ptr -> do
let
ptr' = ptr `plusPtr` natValue @o
staticPeek (castPtr ptr')
instance forall fs s.
( s ~ FullRecordSize fs
, KnownNat s
)
=> StaticStorable (Record fs)
where
type SizeOf (Record fs) = FullRecordSize fs
type Alignment (Record fs) = RecordAlignment fs 1
staticPeekIO ptr = do
let sz = recordSize (undefined :: Record fs)
fp <- mallocForeignPtrBytes (fromIntegral sz)
withForeignPtr fp $ \p ->
memCopy p ptr (fromIntegral sz)
return (Record fp)
staticPokeIO ptr (Record fp) = do
let sz = recordSize (undefined :: Record fs)
withForeignPtr fp $ \p ->
memCopy ptr p (fromIntegral sz)
data Extract = Extract
instance forall fs typ name rec b l2 i r.
( rec ~ Record fs
, b ~ Field name typ
, i ~ (rec, HList l2)
, typ ~ FieldType name fs
, KnownNat (FieldOffset name fs 0)
, StaticStorable typ
, KnownSymbol name
, r ~ (rec, HList ((String,typ) ': l2))
) => Apply Extract (b, i) r where
apply _ (_, (rec,xs)) =
(rec, HCons (symbolValue @name, recordField @name rec) xs)
recordToList :: forall fs.
( HFoldr' Extract (Record fs, HList '[]) fs (Record fs, HList fs)
) => Record fs -> HList fs
recordToList rec = snd res
where
res :: (Record fs, HList fs)
res = hFoldr' Extract ((rec,HNil) :: (Record fs, HList '[])) (undefined :: HList fs)
instance forall fs.
( HFoldr' Extract (Record fs, HList '[]) fs (Record fs, HList fs)
, Show (HList fs)
)
=> Show (Record fs)
where
show rec = show (recordToList rec :: HList fs)