module Foreign.Storable.Record (
Dictionary, Access,
element, run,
alignment, sizeOf,
peek, poke,
) where
import Control.Monad.Trans.Writer
(Writer, writer, runWriter, )
import Control.Monad.Trans.State
(State, modify, get, runState, )
import Control.Applicative (Applicative(pure, (<*>)), )
import Data.Functor.Compose (Compose(Compose), )
import Data.Monoid (Monoid(mempty, mappend), )
import Data.Semigroup (Semigroup((<>)), )
import Foreign.Storable.FixedArray (roundUp, )
import qualified Foreign.Storable as St
import Foreign.Ptr (Ptr, )
import Foreign.Storable (Storable, )
data Dictionary r =
Dictionary {
sizeOf_ :: Int,
alignment_ :: Alignment,
ptrBox :: Box r r
}
newtype Access r a =
Access
(Compose (Writer Alignment)
(Compose (State Int)
(Box r))
a)
instance Functor (Access r) where
fmap f (Access m) = Access (fmap f m)
instance Applicative (Access r) where
pure a = Access (pure a)
Access f <*> Access x = Access (f <*> x)
data Box r a =
Box {
peek_ :: Ptr r -> IO a,
poke_ :: Ptr r -> r -> IO ()
}
instance Functor (Box r) where
fmap f (Box pe po) =
Box (fmap f . pe) po
instance Applicative (Box r) where
pure a = Box (const $ pure a) (const $ const $ pure ())
f <*> x =
Box
(\ptr -> peek_ f ptr <*> peek_ x ptr)
(\ptr r -> poke_ f ptr r >> poke_ x ptr r)
newtype Alignment = Alignment {deconsAlignment :: Int}
instance Semigroup Alignment where
Alignment x <> Alignment y = Alignment (lcm x y)
instance Monoid Alignment where
mempty = Alignment 1
mappend = (<>)
element :: Storable a => (r -> a) -> Access r a
element f =
let align = St.alignment (f (error "Storable.Record.element.alignment: content touched"))
size = St.sizeOf (f (error "Storable.Record.element.size: content touched"))
in Access $
Compose $ writer $ flip (,) (Alignment align) $
Compose $ do
modify (roundUp align)
offset <- get
modify (+size)
return $ Box
(\ptr -> St.peekByteOff ptr offset)
(\ptr -> St.pokeByteOff ptr offset . f)
run :: Access r r -> Dictionary r
run (Access (Compose m)) =
let (Compose s, align) = runWriter m
(box, size) = runState s 0
in Dictionary (roundUp (deconsAlignment align) size) align box
alignment :: Dictionary r -> r -> Int
alignment dict _ =
deconsAlignment $ alignment_ dict
sizeOf :: Dictionary r -> r -> Int
sizeOf dict _ =
sizeOf_ dict
peek :: Dictionary r -> Ptr r -> IO r
peek dict ptr =
peek_ (ptrBox dict) ptr
poke :: Dictionary r -> Ptr r -> r -> IO ()
poke dict ptr =
poke_ (ptrBox dict) ptr