summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoe Crayne <joe@jerkface.net>2019-03-26 21:48:08 -0400
committerJoe Crayne <joe@jerkface.net>2019-03-26 21:48:08 -0400
commit6001fb63aaa1cdce1c672a2cf8dfd83bf82e3ecc (patch)
tree755ca3057f4150461fd1212f0fe8dfff14cde084
Initial commit.
-rw-r--r--primitive-structs.cabal70
-rw-r--r--src/Data/Primitive/ByteArray/Util.hs71
-rw-r--r--src/Data/Primitive/Struct.hs112
-rw-r--r--src/Text/XXD.hs48
4 files changed, 301 insertions, 0 deletions
diff --git a/primitive-structs.cabal b/primitive-structs.cabal
new file mode 100644
index 0000000..1572554
--- /dev/null
+++ b/primitive-structs.cabal
@@ -0,0 +1,70 @@
1cabal-version: >=1.10
2
3-- Initial package description 'primitive-structs.cabal' generated by
4-- 'cabal init'. For further documentation, see
5-- http://haskell.org/cabal/users-guide/
6
7-- The name of the package.
8name: primitive-structs
9
10-- The package version. See the Haskell package versioning policy (PVP)
11-- for standards guiding when and how versions should be incremented.
12-- https://pvp.haskell.org
13-- PVP summary: +-+------- breaking API changes
14-- | | +----- non-breaking API additions
15-- | | | +--- code changes with no API change
16version: 0.1.0.0
17
18-- A short (one-line) description of the package.
19synopsis: Mutable structs based on the primitive package ByteArray interface.
20
21-- A longer description of the package.
22-- description:
23
24-- A URL where users can report bugs.
25-- bug-reports:
26
27-- The license under which the package is released.
28license: BSD3
29
30-- The file containing the license text.
31license-file: LICENSE
32
33-- The package author(s).
34author: Joe Crayne
35
36-- An email address to which users can send suggestions, bug reports, and
37-- patches.
38maintainer: joe@jerkface.net
39
40-- A copyright notice.
41-- copyright:
42
43category: Data
44
45build-type: Simple
46
47-- Extra files to be distributed with the package, such as examples or a
48-- README.
49extra-source-files: CHANGELOG.md
50
51
52library
53 -- Modules exported by the library.
54 exposed-modules: Data.Primitive.Struct, Data.Primitive.ByteArray.Util, Text.XXD
55
56 -- Modules included in this library but not exported.
57 -- other-modules:
58
59 -- LANGUAGE extensions used by modules in this package.
60 other-extensions: AllowAmbiguousTypes, CPP, DataKinds, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, TypeApplications, TypeFamilies, TypeOperators, KindSignatures, MagicHash, RankNTypes
61
62 -- Other library packages from which modules are imported.
63 build-depends: base >=4.11 && <4.12, primitive >=0.6 && <0.7, tagged >=0.8 && <0.9, bytestring >=0.10 && <0.11, memory >=0.14 && <0.15
64
65 -- Directories containing source files.
66 hs-source-dirs: src
67
68 -- Base language which the package is written in.
69 default-language: Haskell2010
70
diff --git a/src/Data/Primitive/ByteArray/Util.hs b/src/Data/Primitive/ByteArray/Util.hs
new file mode 100644
index 0000000..83a2e7d
--- /dev/null
+++ b/src/Data/Primitive/ByteArray/Util.hs
@@ -0,0 +1,71 @@
1{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE KindSignatures #-}
5{-# LANGUAGE MagicHash #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
7{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE TypeFamilies #-}
9{-# LANGUAGE TypeOperators #-}
10{-# LANGUAGE CPP #-}
11module Data.Primitive.ByteArray.Util where
12
13import GHC.Exts (Ptr(..))
14import GHC.TypeLits
15import Control.Monad.Primitive
16import qualified Data.ByteString as B
17import qualified Data.ByteString.Unsafe as B
18import Data.Primitive.Addr
19import Data.Primitive.Types
20import Data.Primitive.ByteArray
21import Data.Word
22import Foreign.Ptr
23
24newtype Offset (n :: Nat) = Offset Int
25
26offset :: KnownNat n => Offset n
27offset = let k = Offset $ fromIntegral $ natVal k in k
28
29(+.) :: Offset j -> Offset k -> Offset (j + k)
30Offset j +. Offset k = Offset (j + k)
31
32
33type family SizeOf a :: Nat
34
35class IsMultipleOf (n::Nat) (k::Nat)
36
37instance n ~ (q * k) => IsMultipleOf n k
38
39writeAtByte :: ( Prim a
40 , PrimMonad m
41#if __GLASGOW_HASKELL__ >= 802
42 , IsMultipleOf n (SizeOf a)
43#endif
44 ) => MutableByteArray (PrimState m) -> Offset n -> a -> m ()
45writeAtByte buf (Offset offset) a = writeByteArray buf (div offset $ (sizeOf a)) a
46{-# INLINE writeAtByte #-}
47
48readAtByte :: forall a m n.
49 ( Prim a
50 , PrimMonad m
51#if __GLASGOW_HASKELL__ >= 802
52 , IsMultipleOf n (SizeOf a)
53#endif
54 ) => MutableByteArray (PrimState m) -> Offset n -> m a
55readAtByte buf (Offset offset) = readByteArray buf (div offset $ (sizeOf (undefined :: a)))
56{-# INLINE readAtByte #-}
57
58writeStringAt :: PrimMonad m => MutableByteArray s -> Int -> B.ByteString -> m (Ptr a)
59writeStringAt src o bsname = do
60 (p,cnt) <- unsafeIOToPrim $ B.unsafeUseAsCStringLen bsname return
61 let nptr = ptr (mutableByteArrayContents src) `plusPtr` o
62 copyAddr (adr nptr) (adr p) cnt
63 writeOffAddr (adr nptr) cnt (0 :: Word8)
64 return nptr
65
66ptr :: Addr -> Ptr a
67ptr (Addr a) = Ptr a
68
69adr :: Ptr a -> Addr
70adr (Ptr a) = Addr a
71
diff --git a/src/Data/Primitive/Struct.hs b/src/Data/Primitive/Struct.hs
new file mode 100644
index 0000000..154b750
--- /dev/null
+++ b/src/Data/Primitive/Struct.hs
@@ -0,0 +1,112 @@
1{-# LANGUAGE AllowAmbiguousTypes #-}
2{-# LANGUAGE CPP #-}
3{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
7{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE TypeApplications #-}
9{-# LANGUAGE TypeFamilies #-}
10{-# LANGUAGE TypeOperators #-}
11module Data.Primitive.Struct where
12
13import Control.Monad.Primitive
14import Data.Primitive.ByteArray
15import Data.Primitive.ByteArray.Util
16import Data.Primitive.Types
17import Data.Tagged
18import Data.Typeable
19import Foreign.Ptr
20import Foreign.Storable
21import GHC.TypeLits
22
23newtype Field tag typ n = Field (Offset n)
24
25data Struct m base tag = Struct
26 { structOffset :: !(Offset base)
27 , structArray :: !(MutableByteArray (PrimState m))
28 }
29
30newStruct :: forall tag m. (KnownNat (SizeOf tag), PrimMonad m) => m (Struct m 0 tag)
31newStruct = Struct (Offset 0) <$> newPinnedByteArray (fromIntegral sz)
32 where
33 sz = natVal (Proxy :: Proxy (SizeOf tag))
34
35newtype Nested tag subtag n = Nested (Offset n)
36
37class IsStruct m p where
38 type BaseOffset p :: Nat
39 type NestedStruct m p (offset::Nat) subtag
40
41 setField :: ( Prim a
42#if __GLASGOW_HASKELL__ >= 802
43 , IsMultipleOf ((BaseOffset p) + k) (SizeOf a)
44#endif
45 ) => p tag -> Field tag a k -> a -> m ()
46
47 getField :: ( Prim a
48#if __GLASGOW_HASKELL__ >= 802
49 , IsMultipleOf ((BaseOffset p) + k) (SizeOf a)
50#endif
51 ) => p tag -> Field tag a k -> m a
52
53 nestedField :: p tag -> Field tag subtag k -> proxy m -> NestedStruct m p k subtag
54
55class IsField (lbl::Symbol) tag where
56 type FieldOffset lbl tag :: Nat
57 type FieldType lbl tag
58 field :: p tag -> Field tag (FieldType lbl tag) (FieldOffset lbl tag)
59
60set :: forall lbl tag m p. (IsField lbl tag, IsStruct m p, Prim (FieldType lbl tag),
61#if __GLASGOW_HASKELL__ >= 802
62 IsMultipleOf (BaseOffset p + FieldOffset lbl tag) (SizeOf (FieldType lbl tag))
63#endif
64 ) =>
65 p tag -> FieldType lbl tag -> m ()
66set p a = setField p (field @lbl p) a
67{-# INLINE set #-}
68
69get :: forall lbl tag m p. (IsField lbl tag, IsStruct m p, Prim (FieldType lbl tag),
70#if __GLASGOW_HASKELL__ >= 802
71 IsMultipleOf (BaseOffset p + FieldOffset lbl tag) (SizeOf (FieldType lbl tag))
72#endif
73 ) =>
74 p tag -> m (FieldType lbl tag)
75get p = getField p (field @lbl p)
76{-# INLINE get #-}
77
78
79modify :: forall lbl tag m p.
80 ( Monad m
81 , IsField lbl tag
82 , IsStruct m p
83 , Prim (FieldType lbl tag)
84#if __GLASGOW_HASKELL__ >= 802
85 , IsMultipleOf (BaseOffset p + FieldOffset lbl tag) (SizeOf (FieldType lbl tag))
86#endif
87 ) => p tag -> (FieldType lbl tag -> FieldType lbl tag) -> m ()
88modify p f = get @lbl p >>= set @lbl p . f
89
90nested :: forall lbl m p tag. (IsField lbl tag, IsStruct m p) =>
91 p tag
92 -> NestedStruct m p (FieldOffset lbl tag) (FieldType lbl tag)
93nested p = nestedField p (field @lbl p) (Proxy @m)
94
95instance PrimMonad m => IsStruct m (Struct m base) where
96 type BaseOffset (Struct m base) = base
97 type NestedStruct m (Struct m base) j t = Struct m (base + j) t
98 setField (Struct o c) (Field field) value = writeAtByte c (o +. field) value
99 getField (Struct o c) (Field field) = readAtByte c (o +. field)
100 nestedField (Struct base ary) (Field offset) _ = Struct (base +. offset) ary
101
102instance IsStruct IO Ptr where
103 type BaseOffset Ptr = 0
104 type NestedStruct IO Ptr j t = Ptr t
105 setField ptr (Field (Offset o)) value = poke (ptr `plusPtr` o) $ PrimStorable value
106 getField ptr (Field (Offset o)) = getPrimStorable <$> peek (ptr `plusPtr` o)
107 nestedField ptr (Field (Offset offset)) _ = castPtr (plusPtr ptr offset)
108
109withPointer :: Struct IO base tag -> (Ptr tag -> IO x) -> IO x
110withPointer (Struct (Offset off) ary) f = do
111 x <- f (ptr (mutableByteArrayContents ary) `plusPtr` off)
112 seq ary $ return x
diff --git a/src/Text/XXD.hs b/src/Text/XXD.hs
new file mode 100644
index 0000000..77606bf
--- /dev/null
+++ b/src/Text/XXD.hs
@@ -0,0 +1,48 @@
1{-# LANGUAGE RankNTypes #-}
2{-# LANGUAGE ScopedTypeVariables #-}
3module Text.XXD (xxd, xxd2) where
4
5import Data.ByteArray (ByteArrayAccess)
6import qualified Data.ByteArray as BA
7import Data.Word
8import Data.Bits
9import Data.Char
10import Text.Printf
11
12nibble :: Word8 -> Char
13nibble b = intToDigit (fromIntegral (b .&. 0x0F))
14
15nibbles :: ByteArrayAccess ba => ba -> String
16nibbles xs = unwords $ map (\byte -> [nibble (byte `shiftR` 4), nibble byte])
17 $ BA.unpack xs
18
19xxd0 :: (ByteArrayAccess ba) => (forall b. ByteArrayAccess b => b -> String) -> Int -> ba -> [String]
20xxd0 tr offset bs | BA.null bs = []
21xxd0 tr offset bs = printf "%03x: %s%s" offset (nibbles xs) (tr xs)
22 : xxd0 tr (offset + BA.length xs) bs'
23 where
24 (xs,bs') = splitAtView 16 bs
25
26splitAtView :: (ByteArrayAccess ba) => Int -> ba -> (BA.View ba, BA.View ba)
27splitAtView n bs = (BA.takeView bs n, BA.dropView bs n)
28
29xxd :: ByteArrayAccess a => Int -> a -> [String]
30xxd = xxd0 (const "")
31
32-- | like xxd, but also shows ascii
33xxd2 :: ByteArrayAccess a => Int -> a -> [String]
34xxd2 = xxd0 withAscii
35
36withAscii :: ByteArrayAccess a => a -> [Char]
37withAscii row = replicate (50 - 3 * BA.length row) ' ' ++ myunpack row
38 where
39 myunpack s = map word8tochar (BA.unpack s)
40 where word8tochar w | (w .&. 0x80 /= 0) = '.'
41 word8tochar w = let c = chr (fromIntegral w)
42 in if isPrint c then c else '.'
43
44{-
45main = do
46 bs <- B.getContents
47 mapM_ putStrLn $ xxd2 0 bs
48 -}