bytezap 1.3.1 → 1.4.0
raw patch · 12 files changed
+675/−160 lines, 12 filesdep ~basePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
- Bytezap.Parser.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 n1, Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW8 ns) => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW8 (n1 : ns)
- Bytezap.Parser.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 n1, Data.Type.Byte.ReifyW8 n2, Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW16 ns) => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW16 (n1 : n2 : ns)
- Bytezap.Parser.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 n1, Data.Type.Byte.ReifyW8 n2, Data.Type.Byte.ReifyW8 n3, Data.Type.Byte.ReifyW8 n4, Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW32 ns) => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW32 (n1 : n2 : n3 : n4 : ns)
- Bytezap.Parser.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 n1, Data.Type.Byte.ReifyW8 n2, Data.Type.Byte.ReifyW8 n3, Data.Type.Byte.ReifyW8 n4, Data.Type.Byte.ReifyW8 n5, Data.Type.Byte.ReifyW8 n6, Data.Type.Byte.ReifyW8 n7, Data.Type.Byte.ReifyW8 n8, Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW64 ns) => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW64 (n1 : n2 : n3 : n4 : n5 : n6 : n7 : n8 : ns)
- Bytezap.Parser.Struct.TypeLits.Bytes: instance Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW16 ns => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW32 ns
- Bytezap.Parser.Struct.TypeLits.Bytes: instance Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW32 ns => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW64 ns
- Bytezap.Parser.Struct.TypeLits.Bytes: instance Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW8 '[]
- Bytezap.Parser.Struct.TypeLits.Bytes: instance Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW8 ns => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW16 ns
- Bytezap.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 n1, Bytezap.Struct.TypeLits.Bytes.ReifyBytesW8 ns) => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW8 (n1 : ns)
- Bytezap.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 n1, Data.Type.Byte.ReifyW8 n2, Bytezap.Struct.TypeLits.Bytes.ReifyBytesW16 ns) => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW16 (n1 : n2 : ns)
- Bytezap.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 n1, Data.Type.Byte.ReifyW8 n2, Data.Type.Byte.ReifyW8 n3, Data.Type.Byte.ReifyW8 n4, Bytezap.Struct.TypeLits.Bytes.ReifyBytesW32 ns) => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW32 (n1 : n2 : n3 : n4 : ns)
- Bytezap.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 n1, Data.Type.Byte.ReifyW8 n2, Data.Type.Byte.ReifyW8 n3, Data.Type.Byte.ReifyW8 n4, Data.Type.Byte.ReifyW8 n5, Data.Type.Byte.ReifyW8 n6, Data.Type.Byte.ReifyW8 n7, Data.Type.Byte.ReifyW8 n8, Bytezap.Struct.TypeLits.Bytes.ReifyBytesW64 ns) => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW64 (n1 : n2 : n3 : n4 : n5 : n6 : n7 : n8 : ns)
- Bytezap.Struct.TypeLits.Bytes: instance Bytezap.Struct.TypeLits.Bytes.ReifyBytesW16 ns => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW32 ns
- Bytezap.Struct.TypeLits.Bytes: instance Bytezap.Struct.TypeLits.Bytes.ReifyBytesW32 ns => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW64 ns
- Bytezap.Struct.TypeLits.Bytes: instance Bytezap.Struct.TypeLits.Bytes.ReifyBytesW8 ns => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW16 ns
- Bytezap.Write.Internal: [poke] :: Write s -> Poke s
- Bytezap.Write.Internal: [size] :: Write s -> Int
- Bytezap.Write.Internal: instance GHC.Base.Monoid (Bytezap.Write.Internal.Write s)
- Bytezap.Write.Internal: instance GHC.Base.Semigroup (Bytezap.Write.Internal.Write s)
+ Bytezap.Parser.Struct: firstNonMatchByteIdx :: FiniteBits a => a -> a -> Int
+ Bytezap.Parser.Struct: unsafeByteAt :: (Num a, Bits a) => a -> Int -> a
+ Bytezap.Parser.Struct: withLitErr :: (Num a, FiniteBits a) => (Int -> a -> e) -> Int# -> a -> (Addr# -> Int# -> a) -> ParserT st e r -> ParserT st e r
+ Bytezap.Parser.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 b0, Data.Type.Byte.ReifyW8 b1, Data.Type.Byte.ReifyW8 b2, Data.Type.Byte.ReifyW8 b3, Data.Type.Byte.ReifyW8 b4, Data.Type.Byte.ReifyW8 b5, Data.Type.Byte.ReifyW8 b6, Data.Type.Byte.ReifyW8 b7, GHC.TypeNats.KnownNat idx, Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW64 (idx GHC.TypeNats.+ 8) bs) => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW64 idx (b0 : b1 : b2 : b3 : b4 : b5 : b6 : b7 : bs)
+ Bytezap.Parser.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 b0, Data.Type.Byte.ReifyW8 b1, Data.Type.Byte.ReifyW8 b2, Data.Type.Byte.ReifyW8 b3, GHC.TypeNats.KnownNat idx, Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW32 (idx GHC.TypeNats.+ 4) bs) => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW32 idx (b0 : b1 : b2 : b3 : bs)
+ Bytezap.Parser.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 b0, Data.Type.Byte.ReifyW8 b1, GHC.TypeNats.KnownNat idx, Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW16 (idx GHC.TypeNats.+ 2) bs) => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW16 idx (b0 : b1 : bs)
+ Bytezap.Parser.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 b0, GHC.TypeNats.KnownNat idx, Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW8 (idx GHC.TypeNats.+ 1) bs) => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW8 idx (b0 : bs)
+ Bytezap.Parser.Struct.TypeLits.Bytes: instance Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW16 idx bs => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW32 idx bs
+ Bytezap.Parser.Struct.TypeLits.Bytes: instance Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW32 idx bs => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW64 idx bs
+ Bytezap.Parser.Struct.TypeLits.Bytes: instance Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW8 idx '[]
+ Bytezap.Parser.Struct.TypeLits.Bytes: instance Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW8 idx bs => Bytezap.Parser.Struct.TypeLits.Bytes.ParseReifyBytesW16 idx bs
+ Bytezap.PokeCPS: PokeCPS :: PokeCPS# r -> PokeCPS r
+ Bytezap.PokeCPS: [unPokeCPS] :: PokeCPS r -> PokeCPS# r
+ Bytezap.PokeCPS: emptyPokeCPS :: PokeCPS ByteString
+ Bytezap.PokeCPS: full :: ByteArray -> Ptr Word8 -> (Int -> IO r) -> (Word8 -> IO r) -> (Word8 -> IO r) -> Int -> Int -> Int -> IO r
+ Bytezap.PokeCPS: newtype PokeCPS r
+ Bytezap.PokeCPS: textToByteStringUptoIO :: Text -> IO (Either String ByteString)
+ Bytezap.PokeCPS: type PokeCPS# r = Addr# -> Int# -> (Int# -> IO ByteString) -> IO r
+ Bytezap.PokeCPS: withByteAsHexDigit :: Word8 -> (Word8 -> r) -> (Word8 -> r) -> r
+ Bytezap.PokeCPS: withHexNibbles :: (Word8 -> r) -> Word8 -> Word8 -> (Word8 -> r) -> r
+ Bytezap.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 b0, Bytezap.Struct.TypeLits.Bytes.ReifyBytesW8 bs) => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW8 (b0 : bs)
+ Bytezap.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 b0, Data.Type.Byte.ReifyW8 b1, Bytezap.Struct.TypeLits.Bytes.ReifyBytesW16 bs) => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW16 (b0 : b1 : bs)
+ Bytezap.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 b0, Data.Type.Byte.ReifyW8 b1, Data.Type.Byte.ReifyW8 b2, Data.Type.Byte.ReifyW8 b3, Bytezap.Struct.TypeLits.Bytes.ReifyBytesW32 bs) => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW32 (b0 : b1 : b2 : b3 : bs)
+ Bytezap.Struct.TypeLits.Bytes: instance (Data.Type.Byte.ReifyW8 b0, Data.Type.Byte.ReifyW8 b1, Data.Type.Byte.ReifyW8 b2, Data.Type.Byte.ReifyW8 b3, Data.Type.Byte.ReifyW8 b4, Data.Type.Byte.ReifyW8 b5, Data.Type.Byte.ReifyW8 b6, Data.Type.Byte.ReifyW8 b7, Bytezap.Struct.TypeLits.Bytes.ReifyBytesW64 bs) => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW64 (b0 : b1 : b2 : b3 : b4 : b5 : b6 : b7 : bs)
+ Bytezap.Struct.TypeLits.Bytes: instance Bytezap.Struct.TypeLits.Bytes.ReifyBytesW16 bs => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW32 bs
+ Bytezap.Struct.TypeLits.Bytes: instance Bytezap.Struct.TypeLits.Bytes.ReifyBytesW32 bs => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW64 bs
+ Bytezap.Struct.TypeLits.Bytes: instance Bytezap.Struct.TypeLits.Bytes.ReifyBytesW8 bs => Bytezap.Struct.TypeLits.Bytes.ReifyBytesW16 bs
+ Bytezap.Write: ExactLength :: LengthType
+ Bytezap.Write: MaxLength :: LengthType
+ Bytezap.Write: data LengthType
+ Bytezap.Write.Internal: ExactLength :: LengthType
+ Bytezap.Write.Internal: MaxLength :: LengthType
+ Bytezap.Write.Internal: [writeLength] :: Write (lt :: LengthType) s -> Int
+ Bytezap.Write.Internal: [writeOp] :: Write (lt :: LengthType) s -> Poke s
+ Bytezap.Write.Internal: data LengthType
+ Bytezap.Write.Internal: instance GHC.Base.Monoid (Bytezap.Write.Internal.Write lt s)
+ Bytezap.Write.Internal: instance GHC.Base.Semigroup (Bytezap.Write.Internal.Write lt s)
+ Bytezap.Write.Internal: writeCombine :: Write ltl s -> Write ltr s -> Write lt s
+ Bytezap.Write.Internal: writeExactMax :: Write ExactLength s -> Write MaxLength s -> Write MaxLength s
+ Bytezap.Write.Internal: writeMax :: Write ExactLength s -> Write MaxLength s
+ Bytezap.Write.Internal: writeMaxExact :: Write MaxLength s -> Write ExactLength s -> Write MaxLength s
+ Tmp.BSExt: createAndTrimCPS :: Int -> (Ptr Word8 -> (Int -> IO ByteString) -> IO r) -> IO r
+ Tmp.BSExt: createAndTrimFailable :: Int -> (Ptr Word8 -> IO (Either e Int)) -> IO (Either e ByteString)
+ Tmp.BSExt: createCPS :: Int -> (ForeignPtr Word8 -> Int -> IO r) -> ((Int -> IO r) -> Ptr Word8 -> IO r) -> IO r
+ Tmp.BSExt: createFailable :: Int -> (Ptr Word8 -> IO (Either e Int)) -> IO (Either e ByteString)
+ Tmp.BSExt: createFp :: Int -> (ForeignPtr Word8 -> IO ()) -> IO ByteString
+ Tmp.BSExt: createFpAndTrimFailable :: Int -> (ForeignPtr Word8 -> IO (Either e Int)) -> IO (Either e ByteString)
+ Tmp.BSExt: createFpFailable :: Int -> (ForeignPtr Word8 -> IO (Either e Int)) -> IO (Either e ByteString)
+ Tmp.BSExt: createFpUptoNFailable :: Int -> (ForeignPtr Word8 -> IO (Either e Int)) -> IO (Either e ByteString)
+ Tmp.BSExt: createUptoNCPS :: Int -> (Ptr Word8 -> (Int -> IO ByteString) -> IO r) -> IO r
+ Tmp.BSExt: createUptoNFailable :: Int -> (Ptr Word8 -> IO (Either e Int)) -> IO (Either e ByteString)
+ Tmp.BSExt: memcpyFp :: ForeignPtr Word8 -> ForeignPtr Word8 -> Int -> IO ()
+ Tmp.BSExt: mkDeferredByteString :: ForeignPtr Word8 -> Int -> IO ByteString
+ Tmp.BSExt: unsafeCreateAndTrimCPS :: Int -> (Ptr Word8 -> (Int -> IO ByteString) -> IO r) -> r
+ Tmp.BSExt: unsafeDupablePerformIOByteString :: IO a -> a
+ Tmp.BSExt: wrapAction :: (Ptr Word8 -> IO res) -> ForeignPtr Word8 -> IO res
- Bytezap.Parser.Struct.TypeLits.Bytes: class ParseReifyBytesW16 (ns :: [Natural])
+ Bytezap.Parser.Struct.TypeLits.Bytes: class ParseReifyBytesW16 (idx :: Natural) (bs :: [Natural])
- Bytezap.Parser.Struct.TypeLits.Bytes: class ParseReifyBytesW32 (ns :: [Natural])
+ Bytezap.Parser.Struct.TypeLits.Bytes: class ParseReifyBytesW32 (idx :: Natural) (bs :: [Natural])
- Bytezap.Parser.Struct.TypeLits.Bytes: class ParseReifyBytesW64 (ns :: [Natural])
+ Bytezap.Parser.Struct.TypeLits.Bytes: class ParseReifyBytesW64 (idx :: Natural) (bs :: [Natural])
- Bytezap.Parser.Struct.TypeLits.Bytes: class ParseReifyBytesW8 (ns :: [Natural])
+ Bytezap.Parser.Struct.TypeLits.Bytes: class ParseReifyBytesW8 (idx :: Natural) (bs :: [Natural])
- Bytezap.Parser.Struct.TypeLits.Bytes: parseReifyBytesW16 :: ParseReifyBytesW16 ns => ParserT st e ()
+ Bytezap.Parser.Struct.TypeLits.Bytes: parseReifyBytesW16 :: ParseReifyBytesW16 idx bs => (Int -> Word8 -> e) -> ParserT st e ()
- Bytezap.Parser.Struct.TypeLits.Bytes: parseReifyBytesW32 :: ParseReifyBytesW32 ns => ParserT st e ()
+ Bytezap.Parser.Struct.TypeLits.Bytes: parseReifyBytesW32 :: ParseReifyBytesW32 idx bs => (Int -> Word8 -> e) -> ParserT st e ()
- Bytezap.Parser.Struct.TypeLits.Bytes: parseReifyBytesW64 :: ParseReifyBytesW64 ns => ParserT st Void ()
+ Bytezap.Parser.Struct.TypeLits.Bytes: parseReifyBytesW64 :: ParseReifyBytesW64 idx bs => (Int -> Word8 -> e) -> ParserT st e ()
- Bytezap.Parser.Struct.TypeLits.Bytes: parseReifyBytesW8 :: ParseReifyBytesW8 ns => ParserT st e ()
+ Bytezap.Parser.Struct.TypeLits.Bytes: parseReifyBytesW8 :: ParseReifyBytesW8 idx bs => (Int -> Word8 -> e) -> ParserT st e ()
- Bytezap.Poke: type Poke# s = Addr# -> Int# -> State# s -> (# State# s, Int# #)
+ Bytezap.Poke: type Poke# s = Addr# {-^ buffer pointer -} -> Int# {-^ buffer offset -} -> State# s {-^ state token -} -> (# State# s, Int# #) {-^ (state token, next offset) -}
- Bytezap.Struct.TypeLits.Bytes: class ReifyBytesW16 (ns :: [Natural])
+ Bytezap.Struct.TypeLits.Bytes: class ReifyBytesW16 (bs :: [Natural])
- Bytezap.Struct.TypeLits.Bytes: class ReifyBytesW32 (ns :: [Natural])
+ Bytezap.Struct.TypeLits.Bytes: class ReifyBytesW32 (bs :: [Natural])
- Bytezap.Struct.TypeLits.Bytes: class ReifyBytesW64 (ns :: [Natural])
+ Bytezap.Struct.TypeLits.Bytes: class ReifyBytesW64 (bs :: [Natural])
- Bytezap.Struct.TypeLits.Bytes: class ReifyBytesW8 (ns :: [Natural])
+ Bytezap.Struct.TypeLits.Bytes: class ReifyBytesW8 (bs :: [Natural])
- Bytezap.Struct.TypeLits.Bytes: reifyBytesW16 :: ReifyBytesW16 ns => Poke s
+ Bytezap.Struct.TypeLits.Bytes: reifyBytesW16 :: ReifyBytesW16 bs => Poke s
- Bytezap.Struct.TypeLits.Bytes: reifyBytesW32 :: ReifyBytesW32 ns => Poke s
+ Bytezap.Struct.TypeLits.Bytes: reifyBytesW32 :: ReifyBytesW32 bs => Poke s
- Bytezap.Struct.TypeLits.Bytes: reifyBytesW64 :: ReifyBytesW64 ns => Poke s
+ Bytezap.Struct.TypeLits.Bytes: reifyBytesW64 :: ReifyBytesW64 bs => Poke s
- Bytezap.Struct.TypeLits.Bytes: reifyBytesW8 :: ReifyBytesW8 ns => Poke s
+ Bytezap.Struct.TypeLits.Bytes: reifyBytesW8 :: ReifyBytesW8 bs => Poke s
- Bytezap.Write: byteArray# :: ByteArray# -> Int# -> Int# -> Write s
+ Bytezap.Write: byteArray# :: ByteArray# -> Int# -> Int# -> Write ExactLength s
- Bytezap.Write: byteString :: ByteString -> Write RealWorld
+ Bytezap.Write: byteString :: ByteString -> Write ExactLength RealWorld
- Bytezap.Write: data Write s
+ Bytezap.Write: data Write (lt :: LengthType) s
- Bytezap.Write: prim :: forall a s. Prim' a => a -> Write s
+ Bytezap.Write: prim :: forall a s. Prim' a => a -> Write ExactLength s
- Bytezap.Write: replicateByte :: Int -> Word8 -> Write RealWorld
+ Bytezap.Write: replicateByte :: Int -> Word8 -> Write ExactLength RealWorld
- Bytezap.Write: runWriteBS :: Write RealWorld -> ByteString
+ Bytezap.Write: runWriteBS :: Write ExactLength RealWorld -> ByteString
- Bytezap.Write: runWriteBSUptoN :: Write RealWorld -> ByteString
+ Bytezap.Write: runWriteBSUptoN :: Write MaxLength RealWorld -> ByteString
- Bytezap.Write.Derived: char :: Char -> Write s
+ Bytezap.Write.Derived: char :: Char -> Write ExactLength s
- Bytezap.Write.Derived: shortByteString :: ShortByteString -> Write s
+ Bytezap.Write.Derived: shortByteString :: ShortByteString -> Write ExactLength s
- Bytezap.Write.Derived: text :: Text -> Write s
+ Bytezap.Write.Derived: text :: Text -> Write ExactLength s
- Bytezap.Write.Internal: Write :: Int -> Poke s -> Write s
+ Bytezap.Write.Internal: Write :: Int -> Poke s -> Write (lt :: LengthType) s
- Bytezap.Write.Internal: data Write s
+ Bytezap.Write.Internal: data Write (lt :: LengthType) s
Files
- CHANGELOG.md +6/−0
- bytezap.cabal +5/−3
- src/Bytezap/Parser/Struct.hs +58/−8
- src/Bytezap/Parser/Struct/TypeLits/Bytes.hs +111/−61
- src/Bytezap/Poke.hs +34/−6
- src/Bytezap/PokeCPS.hs +153/−0
- src/Bytezap/Struct.hs +2/−2
- src/Bytezap/Struct/TypeLits/Bytes.hs +61/−48
- src/Bytezap/Write.hs +19/−17
- src/Bytezap/Write/Derived.hs +7/−7
- src/Bytezap/Write/Internal.hs +51/−8
- src/Tmp/BSExt.hs +168/−0
CHANGELOG.md view
@@ -1,3 +1,9 @@+## 1.4.0 (2024-09-25)+* `Write` now takes a type-level `LengthType` to indicate how it should be used+* rewrite type-level bytestring parsing: now with error handling!+* re-fix GHC 9.8 build+* add some weird wip code for failable serializers+ ## 1.3.1 (2024-07-15) * fix building on GHC 9.8 and probably 9.10
bytezap.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.2.+-- This file has been generated from package.yaml by hpack version 0.36.1. -- -- see: https://github.com/sol/hpack name: bytezap-version: 1.3.1+version: 1.4.0 synopsis: Bytestring builder with zero intermediate allocation description: Please see README.md. category: Data, Serialization, Generics@@ -38,6 +38,7 @@ Bytezap.Poke.Derived.Endian Bytezap.Poke.Json Bytezap.Poke.KnownLen+ Bytezap.PokeCPS Bytezap.Struct Bytezap.Struct.Generic Bytezap.Struct.TypeLits.Bytes@@ -50,6 +51,7 @@ Raehik.Compat.Data.Word.ByteSwap Raehik.Compat.GHC.Exts.GHC908MemcpyPrimops Raehik.Compat.GHC.Exts.GHC910UnalignedAddrPrimops+ Tmp.BSExt Util.TypeNats other-modules: Paths_bytezap@@ -70,7 +72,7 @@ c-sources: cbits/aligned-static-hs-data.c build-depends:- base >=4.18.0.0 && <4.20+ base >=4.18.0.0 && <4.21 , bytestring >=0.11.5.3 && <0.13.0.0 , defun-core ==0.1.* , generic-type-functions >=0.1.0 && <0.2
src/Bytezap/Parser/Struct.hs view
@@ -23,6 +23,11 @@ import Raehik.Compat.Data.Primitive.Types +import Data.Bits+ ( Bits( (.&.), unsafeShiftR, xor )+ , FiniteBits(countTrailingZeros)+ )+ type PureMode = Proxy# Void type IOMode = State# RealWorld type STMode s = State# s@@ -34,6 +39,9 @@ -> st {- ^ state token -} -> Res# st e a +-- | Like flatparse, but no buffer length (= no buffer overflow checking), and+-- no 'Addr#' on success (= no dynamic length parses).+-- -- we take a 'ForeignPtrContents' because it lets us create bytestrings without -- copying if we want. it's useful newtype ParserT (st :: ZeroBitType) e a =@@ -64,6 +72,8 @@ (# st, ResI# e a #) -- | Primitive parser result.+--+-- Like flatparse, but no 'Addr#' on success. type ResI# e a = (# (# a #)@@ -107,8 +117,8 @@ unsafeRunParser' base# fpc (ParserT p) = case p fpc base# 0# proxy# of OK# _st1 a -> OK a- Fail# _st1 -> Fail Err# _st1 e -> Err e+ Fail# _st1 -> Fail -- | Higher-level boxed data type for parsing results. data Result e a =@@ -127,16 +137,17 @@ sequenceParsers (I# len#) f (ParserT pa) (ParserT pb) = ParserT \fpc base os# st0 -> case pa fpc base os# st0 of- Fail# st1 -> Fail# st1- Err# st1 e -> Err# st1 e OK# st1 a -> case pb fpc base (os# +# len#) st1 of+ OK# st2 b -> OK# st2 (f a b) Fail# st2 -> Fail# st2 Err# st2 e -> Err# st2 e- OK# st2 b -> OK# st2 (f a b)+ Err# st1 e -> Err# st1 e+ Fail# st1 -> Fail# st1 -- TODO using indexWord8OffAddrAs to permit pure mode. flatparse does this (at -- least for integers). guess it's OK?+-- TODO this doesn't use the state token. scary. prim :: forall a st e. Prim' a => ParserT st e a prim = ParserT \_fpc base os st -> case indexWord8OffAddrAs# base os of a -> OK# st a@@ -145,16 +156,55 @@ lit :: Eq a => a -> ParserT st e a -> ParserT st e () lit al (ParserT p) = ParserT \fpc base os st0 -> case p fpc base os st0 of- Fail# st1 -> Fail# st1- Err# st1 e -> Err# st1 e OK# st1 ar -> if al == ar then OK# st1 () else Fail# st1+ Err# st1 e -> Err# st1 e+ Fail# st1 -> Fail# st1 -- | parse literal (CPS) withLit :: Eq a => Int# -> a -> ParserT st e a -> ParserT st e r -> ParserT st e r withLit len# al (ParserT p) (ParserT pCont) = ParserT \fpc base os# st0 -> case p fpc base os# st0 of- Fail# st1 -> Fail# st1- Err# st1 e -> Err# st1 e OK# st1 ar -> if al == ar then pCont fpc base (os# +# len#) st1 else Fail# st1+ Err# st1 e -> Err# st1 e+ Fail# st1 -> Fail# st1++{- | parse literal, return first (leftmost) failing byte on error (CPS)++This can be used to parse large literals via chunking, rather than byte-by-byte,+while retaining useful error behaviour.++We don't check equality with XOR even though we use that when handling errors,+because it's hard to tell if it would be faster with modern CPUs and compilers.+-}+withLitErr+ :: (Num a, FiniteBits a)+ => (Int -> a -> e)+ -> Int# -> a -> (Addr# -> Int# -> a) -> ParserT st e r -> ParserT st e r+withLitErr fErr len# aLit p (ParserT pCont) = ParserT \fpc base# os# st ->+ let aParsed = p base# os#+ in if aLit == aParsed+ then pCont fpc base# (os# +# len#) st+ else let idxFail = firstNonMatchByteIdx aLit aParsed+ bFailed = unsafeByteAt aParsed idxFail+ in Err# st (fErr idxFail bFailed)+{-# INLINE withLitErr #-}++-- | Given two non-equal words @wActual@ and @wExpect@, return the index of the+-- first non-matching byte. Zero indexed.+--+-- If both words are equal, returns word_size (e.g. 4 for 'Word32').+firstNonMatchByteIdx :: FiniteBits a => a -> a -> Int+firstNonMatchByteIdx wExpect wActual =+ countTrailingZeros (wExpect `xor` wActual) `unsafeShiftR` 3+{-# INLINE firstNonMatchByteIdx #-}++-- | Get the byte at the given index.+--+-- The return value is guaranteed to be 0x00 - 0xFF (inclusive).+--+-- TODO meaning based on endianness?+unsafeByteAt :: (Num a, Bits a) => a -> Int -> a+unsafeByteAt a idx = (a `unsafeShiftR` (idx * 8)) .&. 0xFF+{-# INLINE unsafeByteAt #-}
src/Bytezap/Parser/Struct/TypeLits/Bytes.hs view
@@ -1,96 +1,146 @@-{- | Efficient type-level bytestring parsing.+{-# LANGUAGE AllowAmbiguousTypes, UndecidableInstances #-} -One may implement this using the type-level serializing, but mirroring it for-parsing does less work and allocation.--}+{- | Efficient type-level bytestring parsing via chunking. -{-# LANGUAGE AllowAmbiguousTypes, UndecidableInstances #-}+See 'Bytezap.Struct.TypeLits.Bytes' for an explanation on the chunking design. -module Bytezap.Parser.Struct.TypeLits.Bytes where+On mismatch, the index of the failing byte and its value are returned. (This is+over-engineered to be extremely efficient.) -import Data.Type.Byte-import Bytezap.Parser.Struct ( ParserT, prim, withLit, constParse )-import Numeric.Natural ( Natural )-import Data.Void ( Void )+Type classes take a 'Natural' for tracking the current index in the type-level+bytestring. We do this on the type level for performance. Use @\@0@ when+calling. -class ParseReifyBytesW64 (ns :: [Natural]) where- parseReifyBytesW64 :: ParserT st Void ()+The parsers take an error wrapper function to enable wrapping the error into any+parser with confidence that it won't do extra allocations/wrapping. +The parsers here either return the unit '()' or a pretty error. No 'Fail#'.++TODO check generated Core, assembly+-}++module Bytezap.Parser.Struct.TypeLits.Bytes+ ( ParseReifyBytesW64(parseReifyBytesW64)+ , ParseReifyBytesW32(parseReifyBytesW32)+ , ParseReifyBytesW16(parseReifyBytesW16)+ , ParseReifyBytesW8(parseReifyBytesW8)+ ) where++import Bytezap.Parser.Struct+import Data.Word ( Word8 )+import GHC.TypeNats ( Natural, type (+), KnownNat )+import Data.Type.Byte ( ReifyW8, reifyW64, reifyW32, reifyW16, reifyW8 )+import GHC.Exts ( (+#), Int(I#), Int#, Addr# )+import Util.TypeNats ( natValInt )+import Raehik.Compat.Data.Primitive.Types ( indexWord8OffAddrAs# )+import Data.Bits++-- | Parse a type-level bytestring, largest grouping 'Word64'.+class ParseReifyBytesW64 (idx :: Natural) (bs :: [Natural]) where+ parseReifyBytesW64 :: (Int -> Word8 -> e) -> ParserT st e ()+ -- | Enough bytes to make a 'Word64'. instance {-# OVERLAPPING #-}- ( ReifyW8 n1- , ReifyW8 n2- , ReifyW8 n3- , ReifyW8 n4- , ReifyW8 n5- , ReifyW8 n6- , ReifyW8 n7- , ReifyW8 n8- , ParseReifyBytesW64 ns- ) => ParseReifyBytesW64 (n1 ': n2 ': n3 ': n4 ': n5 ': n6 ': n7 ': n8 ': ns) where+ ( ReifyW8 b0+ , ReifyW8 b1+ , ReifyW8 b2+ , ReifyW8 b3+ , ReifyW8 b4+ , ReifyW8 b5+ , ReifyW8 b6+ , ReifyW8 b7+ , KnownNat idx+ , ParseReifyBytesW64 (idx+8) bs+ ) => ParseReifyBytesW64 idx (b0 ': b1 ': b2 ': b3 ': b4 ': b5 ': b6 ': b7 ': bs) where {-# INLINE parseReifyBytesW64 #-}- parseReifyBytesW64 = withLit 8# (reifyW64 @n1 @n2 @n3 @n4 @n5 @n6 @n7 @n8)- prim (parseReifyBytesW64 @ns)+ parseReifyBytesW64 = parseReifyBytesHelper @idx @8+ wExpect indexWord8OffAddrAs# (parseReifyBytesW64 @(idx+8) @bs)+ where+ wExpect = reifyW64 @b0 @b1 @b2 @b3 @b4 @b5 @b6 @b7 -- | Try to group 'Word32's next.-instance ParseReifyBytesW32 ns => ParseReifyBytesW64 ns where+instance ParseReifyBytesW32 idx bs => ParseReifyBytesW64 idx bs where {-# INLINE parseReifyBytesW64 #-}- parseReifyBytesW64 = parseReifyBytesW32 @ns+ parseReifyBytesW64 = parseReifyBytesW32 @idx @bs --- | Serialize a type-level bytestring, largest grouping 'Word32'.-class ParseReifyBytesW32 (ns :: [Natural]) where- parseReifyBytesW32 :: ParserT st e ()+-- | Parse a type-level bytestring, largest grouping 'Word32'.+class ParseReifyBytesW32 (idx :: Natural) (bs :: [Natural]) where+ parseReifyBytesW32 :: (Int -> Word8 -> e) -> ParserT st e () -- | Enough bytes to make a 'Word32'. instance {-# OVERLAPPING #-}- ( ReifyW8 n1- , ReifyW8 n2- , ReifyW8 n3- , ReifyW8 n4- , ParseReifyBytesW32 ns- ) => ParseReifyBytesW32 (n1 ': n2 ': n3 ': n4 ': ns) where+ ( ReifyW8 b0+ , ReifyW8 b1+ , ReifyW8 b2+ , ReifyW8 b3+ , KnownNat idx+ , ParseReifyBytesW32 (idx+4) bs+ ) => ParseReifyBytesW32 idx (b0 ': b1 ': b2 ': b3 ': bs) where {-# INLINE parseReifyBytesW32 #-}- parseReifyBytesW32 = withLit 4# (reifyW32 @n1 @n2 @n3 @n4)- prim (parseReifyBytesW32 @ns)+ parseReifyBytesW32 = parseReifyBytesHelper @idx @4+ wExpect indexWord8OffAddrAs# (parseReifyBytesW32 @(idx+4) @bs)+ where+ wExpect = reifyW32 @b0 @b1 @b2 @b3 -- | Try to group 'Word16's next.-instance ParseReifyBytesW16 ns => ParseReifyBytesW32 ns where+instance ParseReifyBytesW16 idx bs => ParseReifyBytesW32 idx bs where {-# INLINE parseReifyBytesW32 #-}- parseReifyBytesW32 = parseReifyBytesW16 @ns+ parseReifyBytesW32 = parseReifyBytesW16 @idx @bs --- | Serialize a type-level bytestring, largest grouping 'Word16'.-class ParseReifyBytesW16 (ns :: [Natural]) where- parseReifyBytesW16 :: ParserT st e ()+-- | Parse a type-level bytestring, largest grouping 'Word16'.+class ParseReifyBytesW16 (idx :: Natural) (bs :: [Natural]) where+ parseReifyBytesW16 :: (Int -> Word8 -> e) -> ParserT st e () -- | Enough bytes to make a 'Word16'. instance {-# OVERLAPPING #-}- ( ReifyW8 n1- , ReifyW8 n2- , ParseReifyBytesW16 ns- ) => ParseReifyBytesW16 (n1 ': n2 ': ns) where+ ( ReifyW8 b0+ , ReifyW8 b1+ , KnownNat idx+ , ParseReifyBytesW16 (idx+2) bs+ ) => ParseReifyBytesW16 idx (b0 ': b1 ': bs) where {-# INLINE parseReifyBytesW16 #-}- parseReifyBytesW16 = withLit 2# (reifyW16 @n1 @n2)- prim (parseReifyBytesW16 @ns)+ parseReifyBytesW16 = parseReifyBytesHelper @idx @2+ wExpect indexWord8OffAddrAs# (parseReifyBytesW16 @(idx+2) @bs)+ where+ wExpect = reifyW16 @b0 @b1 --- | Reify byte-by-byte next.-instance ParseReifyBytesW8 ns => ParseReifyBytesW16 ns where+-- | Parse byte-by-byte next.+instance ParseReifyBytesW8 idx bs => ParseReifyBytesW16 idx bs where {-# INLINE parseReifyBytesW16 #-}- parseReifyBytesW16 = parseReifyBytesW8 @ns+ parseReifyBytesW16 = parseReifyBytesW8 @idx @bs -- | Serialize a type-level bytestring, byte-by-byte.-class ParseReifyBytesW8 (ns :: [Natural]) where- parseReifyBytesW8 :: ParserT st e ()+class ParseReifyBytesW8 (idx :: Natural) (bs :: [Natural]) where+ parseReifyBytesW8 :: (Int -> Word8 -> e) -> ParserT st e () --- | Reify the next byte.+-- | Parse the next byte. instance- ( ReifyW8 n1- , ParseReifyBytesW8 ns- ) => ParseReifyBytesW8 (n1 ': ns) where+ ( ReifyW8 b0+ , KnownNat idx+ , ParseReifyBytesW8 (idx+1) bs+ ) => ParseReifyBytesW8 idx (b0 ': bs) where {-# INLINE parseReifyBytesW8 #-}- parseReifyBytesW8 = withLit 1# (reifyW8 @n1)- prim (parseReifyBytesW8 @ns)+ parseReifyBytesW8 f = ParserT $ \fpc base# os# st ->+ let bExpect = reifyW8 @b0+ bActual = indexWord8OffAddrAs# base# os#+ idx = natValInt @idx+ in if bExpect == bActual+ then runParserT# (parseReifyBytesW8 @(idx+1) @bs f) fpc base# (os# +# 1#) st+ else Err# st (f idx bActual) -- | End of the line.-instance ParseReifyBytesW8 '[] where+instance ParseReifyBytesW8 idx '[] where {-# INLINE parseReifyBytesW8 #-}- parseReifyBytesW8 = constParse ()+ parseReifyBytesW8 _f = ParserT $ \_fpc _base# _os# st -> OK# st ()++parseReifyBytesHelper+ :: forall (idx :: Natural) (len :: Natural) a e st+ . (KnownNat idx, KnownNat len, Integral a, FiniteBits a)+ => a -> (Addr# -> Int# -> a)+ -> ((Int -> Word8 -> e) -> ParserT st e ())+ -> (Int -> Word8 -> e) -> ParserT st e ()+parseReifyBytesHelper a indexWord8OffAddrAsA# pCont f = withLitErr+ (\idx b -> f (natValInt @idx + idx) (fromIntegral b))+ len# a indexWord8OffAddrAsA# (pCont f)+ where+ !(I# len#) = natValInt @len
src/Bytezap/Poke.hs view
@@ -1,11 +1,10 @@-{-# LANGUAGE CPP #-} -- for a bytestring version gate >:( {-# LANGUAGE UnboxedTuples #-} -- may as well export everything the interface is highly unsafe module Bytezap.Poke where import GHC.Exts-import Raehik.Compat.GHC.Exts.GHC908MemcpyPrimops+import Raehik.Compat.GHC.Exts.GHC908MemcpyPrimops qualified as MemcpyPrimops import GHC.Word ( Word8(W8#) ) @@ -22,16 +21,45 @@ import Bytezap.Struct qualified as Struct -type Poke# s = Addr# -> Int# -> State# s -> (# State# s, Int# #)+{- | Unboxed buffer write operation. +The next offset must be greater than or equal to the input buffer offset.+This is not checked.++Note that the only way to find out the length of a write is to perform it. But+you can't perform a length without providing a correctly-sized buffer. Thus, you+may only use a 'Poke#' when you have a buffer large enough to fit its maximum+write length-- which in turn means means you must track write lengths+separately. ('Bytezap.Write.Write' does this.)++I provide this highly unsafe, seemingly unhelpful type because it's a+requirement for 'Bytezap.Write.Write', and here I can guarantee performance+better because I don't need to worry about laziness.++We cannot be polymorphic on the pointer type unless we box the pointer.+We thus limit ourselves to writing to 'Addr#'s, and not 'MutableByteArray#'s.+(I figure we're most interested in @ByteString@s, which use 'Addr#'.)++Note that if we did provide write length, then the next offset might appear+superfluous. But that next offset is usually already calculated, and may be+passed directly to sequenced writes, unlike if we returned a write length which+would need to be added to the original offset.+-}+type Poke# s =+ Addr# {- ^ buffer pointer -}+ -> Int# {- ^ buffer offset -}+ -> State# s {- ^ state token -}+ -> (# State# s, Int# #) {- ^ (state token, next offset) -}+ -- | Poke newtype wrapper. newtype Poke s = Poke { unPoke :: Poke# s } --- | Sequence two 'Poke's left-to-right.+-- | Sequence two buffer writes left-to-right. instance Semigroup (Poke s) where Poke l <> Poke r = Poke $ \base# os0# s0 -> case l base# os0# s0 of (# s1, os1# #) -> r base# os1# s1 +-- | The empty buffer write simply returns its state token and offset. instance Monoid (Poke s) where mempty = Poke $ \_base# os# s -> (# s, os# #) @@ -68,7 +96,7 @@ byteString :: BS.ByteString -> Poke RealWorld byteString (BS.BS (ForeignPtr p# r) (I# len#)) = Poke $ \base# os# s0 -> keepAlive# r s0 $ \s1 ->- case copyAddrToAddrNonOverlapping# p# (base# `plusAddr#` os#) len# s1 of+ case MemcpyPrimops.copyAddrToAddrNonOverlapping# p# (base# `plusAddr#` os#) len# s1 of s2 -> (# s2, os# +# len# #) byteArray# :: ByteArray# -> Int# -> Int# -> Poke s@@ -79,7 +107,7 @@ -- | essentially memset replicateByte :: Int -> Word8 -> Poke RealWorld replicateByte (I# len#) (W8# byte#) = Poke $ \base# os# s0 ->- case setAddrRange# (base# `plusAddr#` os#) len# byteAsInt# s0 of+ case MemcpyPrimops.setAddrRange# (base# `plusAddr#` os#) len# byteAsInt# s0 of s1 -> (# s1, os# +# len# #) where byteAsInt# = word2Int# (word8ToWord# byte#)
+ src/Bytezap/PokeCPS.hs view
@@ -0,0 +1,153 @@+{-# LANGUAGE UnboxedTuples #-}++{- | Low-level bytestring builder using continuation parsing.++bytezap's builder is highly performant. However, one thing it can't do is+/fail/. We have no way to flag an error. If you force it, you will either++* write an initial assert, followed by an unsafe builder that relies on it, or+* build a builder as we assert, then execute it once we're ready++The former is inefficient in situations where the check scales similarly with+the build (e.g. both must iterate over the input). And the latter is very silly+since your builder will be allocating all over.++A naive failable builder might use @'Either' e 'Int'@ to flag errors. After+executing, we check the result: if 'Right', we resize to the given actual+length; if 'Left', we discard the buffer with the given error. This is fine...+but it's an extra allocation, and limits us to 'Either'. A shame.++Instead, we design a builder that takes a finalizer continuation @'Int#' ->+'ByteString'@, which is passed the final offset. The builder calls this as it+finishes, wrapping it as needed (or leaving as 'ByteString' for a non-failable+builder). The runner is expected to pass a continuation to perform any buffer+reallocation necessary (if the actual length was less than the max length), and+return a 'ByteString', possibly wrapped in e.g. 'Right'.++This is much harder to use than the regular builder, and they can't be combined+(the regular builder permits sequencing, which this can't support). But it fills+a gap!++Unlike the regular builder we stick with 'IO', because the continuations get+weird otherwise.+-}++module Bytezap.PokeCPS where++import GHC.Exts ( Int#, Addr#, Ptr )++import Data.Text.Internal ( Text(Text) )+import Data.Text.Array qualified as Text+import Data.Word ( Word8 )+import Data.ByteString ( ByteString )+import Data.Primitive.ByteArray ( ByteArray(ByteArray), indexByteArray )+import GHC.Storable ( writeWord8OffPtr )+import Tmp.BSExt qualified as B++type PokeCPS# r = Addr# -> Int# -> (Int# -> IO ByteString) -> IO r++-- | 'PokeCPS#' newtype wrapper.+--+-- Does not permit a 'Semigroup' instance because pokes do not return offset+-- information.+newtype PokeCPS r = PokeCPS { unPokeCPS:: PokeCPS# r }++emptyPokeCPS :: PokeCPS ByteString+emptyPokeCPS = PokeCPS $ \_base# os# finalize -> finalize os#++{- Any unexplained stuff is probably parsing parts of hex bytestrings+Like @xx AA 1a2F@.+-}++{- 2024-08-27 raehik+The best algorithm would probably operate on words (let's assume 8 bytes).+It would involve a shitload of inlining and praying that GHC figures out how to+turn it all into efficient JMPs.+-}++{-+Parse hex bytestring.++Expects that the output buffer can fit the maximum length of the input.++This is a bit overly parametric in the hopes of using it with manual buffering+(e.g. allocate a single buffer and reuse it for every parse). But that's more+complex: we need to carefully set @bufInMax@ so that it also corresponds to the+output buffer as well. But now, we also need to track the input buffer+position... yeah, it's a mess.++Wait, we _are_ tracking input buffer position. We're just not passing it to the+continuation. More reworking, sigh...+-}+full+ :: ByteArray -> Ptr Word8+ -> (Int -> IO r) -> (Word8 -> IO r)+ -> (Word8 -> IO r)+ -> Int -> Int -> Int+ -> IO r+full bufIn bufOut fCont fErrEof fErrNotHexDigit bufInMax = go+ where+ -- each loop writes a single byte or fails+ go = \bufInOs bufOutOs -> do+ let bufInRemaining = bufInMax - bufInOs+ if bufInRemaining >= 2+ then case indexByteArray @Word8 bufIn bufInOs of+ 0x20 -> -- next byte is space+ case indexByteArray @Word8 bufIn (bufInOs+1) of+ 0x20 -> -- and the byte after that: skip both+ go (bufInOs+2) bufOutOs+ d1 ->+ -- next byte is space, then non-space+ -- could just skip one, but we've already asserted 2 bytes+ -- so let's copy-paste for just 1 more byte+ if bufInRemaining >= 3+ then do let d0 = indexByteArray @Word8 bufIn (bufInOs+2)+ withHexNibbles fErrNotHexDigit d1 d0 $ \b -> do+ writeWord8OffPtr bufOut bufOutOs b+ go (bufInOs+3) (bufOutOs+1)+ else fErrEof d1+ d1 -> do+ let d0 = indexByteArray @Word8 bufIn (bufInOs+1)+ withHexNibbles fErrNotHexDigit d1 d0 $ \b -> do+ writeWord8OffPtr bufOut bufOutOs b+ go (bufInOs+2) (bufOutOs+1)+ else if bufInRemaining == 0+ then fCont bufOutOs+ else -- 1 byte remaining+ case indexByteArray @Word8 bufIn bufInOs of+ 0x20 -> fCont bufOutOs+ d1 -> fErrEof d1+{-# INLINE full #-}++withHexNibbles ::+ (Word8 -> r) -> Word8 -> Word8 -> (Word8 -> r) -> r+withHexNibbles fFail d1 d0 fCont =+ withByteAsHexDigit d1 fFail $ \n1 ->+ withByteAsHexDigit d0 fFail $ \n0 ->+ fCont $ 0x10*n1 + n0+{-# INLINE withHexNibbles #-}++withByteAsHexDigit :: Word8 -> (Word8 -> r) -> (Word8 -> r) -> r+withByteAsHexDigit c fFail f+ | dec <= 9 = f dec+ | hexl <= 5 = f $ hexl + 10+ | hexu <= 5 = f $ hexu + 10+ | otherwise = fFail c+ where+ dec = c - ord_0+ hexl = c - ord_a+ hexu = c - ord_A+ ord_0 = 0x30+ ord_a = 0x61+ ord_A = 0x41+{-# INLINE withByteAsHexDigit #-}++textToByteStringUptoIO :: Text -> IO (Either String ByteString)+textToByteStringUptoIO = \(Text (Text.ByteArray tarr) tos tlen) ->+ B.createCPS (tlen `quot` 2) finalizer $ \finalize buf ->+ full (ByteArray tarr) buf finalize fErrEof fErrNotHexDigit tlen tos 0+ where+ fErrNotHexDigit = \b -> pure $ Left ("not a hexadecimal digit: " <> show b)+ finalizer = \fp len -> Right <$> B.mkDeferredByteString fp len+ fErrEof = \_ -> pure $ Left "ended during byte (TODO)"+{-# INLINE textToByteStringUptoIO #-}
src/Bytezap/Struct.hs view
@@ -23,7 +23,7 @@ module Bytezap.Struct where import GHC.Exts-import Raehik.Compat.GHC.Exts.GHC908MemcpyPrimops ( setAddrRange# )+import Raehik.Compat.GHC.Exts.GHC908MemcpyPrimops qualified as GHCExtsCompat import Raehik.Compat.Data.Primitive.Types import Control.Monad.Primitive ( MonadPrim, primitive )@@ -78,6 +78,6 @@ -- | essentially memset replicateByte :: Int -> Word8 -> Poke RealWorld replicateByte (I# len#) (W8# byte#) = Poke $ \base# os# s0 ->- setAddrRange# (base# `plusAddr#` os#) len# byteAsInt# s0+ GHCExtsCompat.setAddrRange# (base# `plusAddr#` os#) len# byteAsInt# s0 where byteAsInt# = word2Int# (word8ToWord# byte#)
src/Bytezap/Struct/TypeLits/Bytes.hs view
@@ -1,13 +1,6 @@-{- | Efficient type-level bytestring serialization.--@['Natural']@s have a convenient syntax, and we can use them as a type-level-bytestring by asserting that each 'Natural' is <=255 when reifying. This module-provides type classes which give you a serializer for a given @['Natural']@.+{-# LANGUAGE AllowAmbiguousTypes, UndecidableInstances #-} -We maximize efficiency by grouping bytes into machine words. We have to be-pretty verbose to achieve this. Each type class attempts to group bytes into its-machine word type, and if it can't (i.e. not enough bytes remain), it hands off-to the next type class which handles the next smaller machine word.+{- | Efficient type-level bytestring serialization via chunking. I did a quick Core check and found that GHC seems to successfully generate minimal code for this e.g. for an 8-byte magic, GHC will do one@@ -19,87 +12,107 @@ compile time. So I'm fairly confident that this is the best you're gonna get. -} -{-# LANGUAGE AllowAmbiguousTypes, UndecidableInstances #-}+module Bytezap.Struct.TypeLits.Bytes+ (+ -- * Chunking design+ -- $chunking-design -module Bytezap.Struct.TypeLits.Bytes where+ ReifyBytesW64(reifyBytesW64)+ , ReifyBytesW32(reifyBytesW32)+ , ReifyBytesW16(reifyBytesW16)+ , ReifyBytesW8(reifyBytesW8)+ ) where import Data.Type.Byte import Bytezap.Struct ( Poke, sequencePokes, emptyPoke, prim ) import Numeric.Natural ( Natural ) +{- $chunking-design+@['Natural']@s have a convenient syntax, and we can use them as a type-level+bytestring by asserting that each 'Natural' is <=255 when reifying. This module+provides type classes which give you a serializer for a given @['Natural']@.++We maximize efficiency by grouping bytes into machine words. We have to be+pretty verbose to achieve this. Each type class attempts to group bytes into its+machine word type, and if it can't (i.e. not enough bytes remain), it hands off+to the next type class which handles the next smaller machine word.++TODO rewrite :)+-}+ -- | Serialize a type-level bytestring, largest grouping 'Word64'.-class ReifyBytesW64 (ns :: [Natural]) where reifyBytesW64 :: Poke s+class ReifyBytesW64 (bs :: [Natural]) where reifyBytesW64 :: Poke s -- | Enough bytes to make a 'Word64'. instance {-# OVERLAPPING #-}- ( ReifyW8 n1- , ReifyW8 n2- , ReifyW8 n3- , ReifyW8 n4- , ReifyW8 n5- , ReifyW8 n6- , ReifyW8 n7- , ReifyW8 n8- , ReifyBytesW64 ns- ) => ReifyBytesW64 (n1 ': n2 ': n3 ': n4 ': n5 ': n6 ': n7 ': n8 ': ns) where+ ( ReifyW8 b0+ , ReifyW8 b1+ , ReifyW8 b2+ , ReifyW8 b3+ , ReifyW8 b4+ , ReifyW8 b5+ , ReifyW8 b6+ , ReifyW8 b7+ , ReifyBytesW64 bs+ ) => ReifyBytesW64 (b0 ': b1 ': b2 ': b3 ': b4 ': b5 ': b6 ': b7 ': bs) where {-# INLINE reifyBytesW64 #-} reifyBytesW64 = sequencePokes- (prim (reifyW64 @n1 @n2 @n3 @n4 @n5 @n6 @n7 @n8)) 8 (reifyBytesW64 @ns)+ (prim (reifyW64 @b0 @b1 @b2 @b3 @b4 @b5 @b6 @b7)) 8 (reifyBytesW64 @bs) -- | Try to group 'Word32's next.-instance ReifyBytesW32 ns => ReifyBytesW64 ns where+instance ReifyBytesW32 bs => ReifyBytesW64 bs where {-# INLINE reifyBytesW64 #-}- reifyBytesW64 = reifyBytesW32 @ns+ reifyBytesW64 = reifyBytesW32 @bs -- | Serialize a type-level bytestring, largest grouping 'Word32'.-class ReifyBytesW32 (ns :: [Natural]) where reifyBytesW32 :: Poke s+class ReifyBytesW32 (bs :: [Natural]) where reifyBytesW32 :: Poke s -- | Enough bytes to make a 'Word32'. instance {-# OVERLAPPING #-}- ( ReifyW8 n1- , ReifyW8 n2- , ReifyW8 n3- , ReifyW8 n4- , ReifyBytesW32 ns- ) => ReifyBytesW32 (n1 ': n2 ': n3 ': n4 ': ns) where+ ( ReifyW8 b0+ , ReifyW8 b1+ , ReifyW8 b2+ , ReifyW8 b3+ , ReifyBytesW32 bs+ ) => ReifyBytesW32 (b0 ': b1 ': b2 ': b3 ': bs) where {-# INLINE reifyBytesW32 #-} reifyBytesW32 = sequencePokes- (prim (reifyW32 @n1 @n2 @n3 @n4)) 4 (reifyBytesW32 @ns)+ (prim (reifyW32 @b0 @b1 @b2 @b3)) 4 (reifyBytesW32 @bs) -- | Try to group 'Word16's next.-instance ReifyBytesW16 ns => ReifyBytesW32 ns where+instance ReifyBytesW16 bs => ReifyBytesW32 bs where {-# INLINE reifyBytesW32 #-}- reifyBytesW32 = reifyBytesW16 @ns+ reifyBytesW32 = reifyBytesW16 @bs -- | Serialize a type-level bytestring, largest grouping 'Word16'.-class ReifyBytesW16 (ns :: [Natural]) where reifyBytesW16 :: Poke s+class ReifyBytesW16 (bs :: [Natural]) where reifyBytesW16 :: Poke s -- | Enough bytes to make a 'Word16'. instance {-# OVERLAPPING #-}- ( ReifyW8 n1- , ReifyW8 n2- , ReifyBytesW16 ns- ) => ReifyBytesW16 (n1 ': n2 ': ns) where+ ( ReifyW8 b0+ , ReifyW8 b1+ , ReifyBytesW16 bs+ ) => ReifyBytesW16 (b0 ': b1 ': bs) where {-# INLINE reifyBytesW16 #-} reifyBytesW16 = sequencePokes- (prim (reifyW16 @n1 @n2)) 2 (reifyBytesW16 @ns)+ (prim (reifyW16 @b0 @b1)) 2 (reifyBytesW16 @bs) -- | Reify byte-by-byte next.-instance ReifyBytesW8 ns => ReifyBytesW16 ns where+instance ReifyBytesW8 bs => ReifyBytesW16 bs where {-# INLINE reifyBytesW16 #-}- reifyBytesW16 = reifyBytesW8 @ns+ reifyBytesW16 = reifyBytesW8 @bs -- | Serialize a type-level bytestring, byte-by-byte.-class ReifyBytesW8 (ns :: [Natural]) where reifyBytesW8 :: Poke s+class ReifyBytesW8 (bs :: [Natural]) where reifyBytesW8 :: Poke s -- | Reify the next byte. instance- ( ReifyW8 n1- , ReifyBytesW8 ns- ) => ReifyBytesW8 (n1 ': ns) where+ ( ReifyW8 b0+ , ReifyBytesW8 bs+ ) => ReifyBytesW8 (b0 ': bs) where {-# INLINE reifyBytesW8 #-} reifyBytesW8 = sequencePokes- (prim (reifyW8 @n1)) 1 (reifyBytesW8 @ns)+ (prim (reifyW8 @b0)) 1 (reifyBytesW8 @bs) -- | End of the line. instance ReifyBytesW8 '[] where
src/Bytezap/Write.hs view
@@ -2,41 +2,43 @@ -- safe module, only export the safe bits (no @Write(..)@!!) module Bytezap.Write- ( Write(size, poke)+ ( Write(writeLength, writeOp)+ , LengthType(ExactLength, MaxLength) , runWriteBS, runWriteBSUptoN , prim, byteString, byteArray#, replicateByte ) where import Bytezap.Write.Internal -import Bytezap.Poke qualified as P+import Bytezap.Poke qualified as Poke+import Bytezap.Poke ( Poke ) import Raehik.Compat.Data.Primitive.Types import GHC.Exts import Data.ByteString qualified as BS import Data.Word ( Word8 ) -runWriteBS :: Write RealWorld -> BS.ByteString-runWriteBS = runWriteWith P.unsafeRunPokeBS+runWriteBS :: Write ExactLength RealWorld -> BS.ByteString+runWriteBS = runWriteWith Poke.unsafeRunPokeBS -runWriteBSUptoN :: Write RealWorld -> BS.ByteString-runWriteBSUptoN = runWriteWith P.unsafeRunPokeBSUptoN+runWriteBSUptoN :: Write MaxLength RealWorld -> BS.ByteString+runWriteBSUptoN = runWriteWith Poke.unsafeRunPokeBSUptoN -- | Helper for writing 'Write' runners.-runWriteWith :: forall a s. (Int -> P.Poke s -> a) -> Write s -> a-runWriteWith runPoke (Write size poke) = runPoke size poke+runWriteWith :: forall a s lt. (Int -> Poke s -> a) -> Write lt s -> a+runWriteWith runPoke (Write l p) = runPoke l p -prim :: forall a s. Prim' a => a -> Write s-prim a = Write (sizeOf (undefined :: a)) (P.prim a)+prim :: forall a s. Prim' a => a -> Write ExactLength s+prim a = Write (sizeOf (undefined :: a)) (Poke.prim a) -byteString :: BS.ByteString -> Write RealWorld-byteString bs = Write (BS.length bs) (P.byteString bs)+byteString :: BS.ByteString -> Write ExactLength RealWorld+byteString bs = Write (BS.length bs) (Poke.byteString bs) -byteArray# :: ByteArray# -> Int# -> Int# -> Write s+byteArray# :: ByteArray# -> Int# -> Int# -> Write ExactLength s byteArray# ba# baos# balen# = Write{..} where- size = I# balen#- poke = P.byteArray# ba# baos# balen#+ writeLength = I# balen#+ writeOp = Poke.byteArray# ba# baos# balen# -- | essentially memset-replicateByte :: Int -> Word8 -> Write RealWorld-replicateByte len byte = Write len (P.replicateByte len byte)+replicateByte :: Int -> Word8 -> Write ExactLength RealWorld+replicateByte len byte = Write len (Poke.replicateByte len byte)
src/Bytezap/Write/Derived.hs view
@@ -1,25 +1,25 @@ module Bytezap.Write.Derived where import Bytezap.Write.Internal-import Bytezap.Poke.Derived qualified as P+import Bytezap.Poke.Derived qualified as Poke import Data.ByteString.Short qualified as SBS import Data.Text.Internal qualified as T import Data.Char ( ord ) -- | Write a 'SBS.ShortByteString'.-shortByteString :: SBS.ShortByteString -> Write s-shortByteString sbs = Write (SBS.length sbs) (P.shortByteString sbs)+shortByteString :: SBS.ShortByteString -> Write ExactLength s+shortByteString sbs = Write (SBS.length sbs) (Poke.shortByteString sbs) -- | Write a 'T.Text'.-text :: T.Text -> Write s-text t@(T.Text _arr _off len) = Write len (P.text t)+text :: T.Text -> Write ExactLength s+text t@(T.Text _arr _off len) = Write len (Poke.text t) -- | Write a 'Char'. -- -- Adapted from utf8-string.-char :: Char -> Write s-char c = Write (go (ord c)) (P.char c)+char :: Char -> Write ExactLength s+char c = Write (go (ord c)) (Poke.char c) where go oc | oc <= 0x7f = 1
src/Bytezap/Write/Internal.hs view
@@ -1,15 +1,58 @@ module Bytezap.Write.Internal where -import Bytezap.Poke qualified as P+import Bytezap.Poke ( Poke ) --- | A 'Poke' with the associated size it pokes.-data Write s = Write { size :: Int, poke :: P.Poke s }+-- | A 'Poke' buffer write operation with the associated length to be written.+--+-- The length may be either exact or a maximum.+--+-- TODO strictness?+data Write (lt :: LengthType) s = Write+ { writeLength :: Int+ -- ^ Length of the write in bytes.+ --+ -- This is not statically asserted. Any time you construct a 'Write', you must+ -- promise this.+ --+ -- For @'Write' 'ExactLength' s@, this is an exact measurement.+ -- For @'Write' 'MaxLength' s@, this is a maximum. --- | Sequence the 'Poke's, sum the sizes.-instance Semigroup (Write s) where- -- TODO feels like this might be INLINE[1] or even INLINE[0]?- Write ll lp <> Write rl rp = Write (ll + rl) (lp <> rp)+ , writeOp :: Poke s+ -- ^ The 'Poke' buffer write operation.+ } +-- | What a buffer write length field means.+data LengthType+ = ExactLength -- ^ Exact length to be written.+ | MaxLength -- ^ Maximum length to be written.++-- | Sequence the writes, sum the lengths.+instance Semigroup (Write lt s) where+ -- TODO strictness? INLINE[1]? INLINE[0]?+ (<>) = writeCombine+ -- | The empty 'Write' is the empty 'Poke', which writes zero bytes.-instance Monoid (Write s) where+instance Monoid (Write lt s) where mempty = Write 0 mempty++-- | Turn a @'Write' 'ExactLength'@ into a @'Write' 'MaxLength'@.+writeMax :: Write ExactLength s -> Write MaxLength s+writeMax (Write l p) = Write l p++-- | Sequence a @'Write' 'MaxLength'@ and a @'Write' 'ExactLength'@+-- left-to-right.+writeMaxExact :: Write MaxLength s -> Write ExactLength s -> Write MaxLength s+writeMaxExact = writeCombine++-- | Sequence a @'Write' 'MaxLength'@ and a @'Write' 'ExactLength'@+-- left-to-right.+writeExactMax :: Write ExactLength s -> Write MaxLength s -> Write MaxLength s+writeExactMax = writeCombine++-- | Sequence two 'Write's left-to-right.+--+-- Unsafe, as it ignores 'LengthType's.+--+-- TODO strictness? INLINE[1]? INLINE[0]?+writeCombine :: Write ltl s -> Write ltr s -> Write lt s+writeCombine (Write ll lp) (Write rl rp) = Write (ll + rl) (lp <> rp)
+ src/Tmp/BSExt.hs view
@@ -0,0 +1,168 @@+{-# LANGUAGE UnboxedTuples #-}++-- | raehik's bytestring extras (reimplementations of unexported internals).++module Tmp.BSExt+ ( module Tmp.BSExt+ , B.mkDeferredByteString+ ) where++import GHC.ForeignPtr ( ForeignPtr, unsafeWithForeignPtr, withForeignPtr )+import Foreign.Ptr ( Ptr )+import Foreign.Marshal.Utils ( copyBytes )+import Data.ByteString.Internal qualified as B+import Data.ByteString ( ByteString )+import Data.Word ( Word8 )+import Control.Exception ( assert )+import GHC.IO ( IO(IO) )+import GHC.Exts ( runRW# )++-- | Copy the given number of bytes from the second area (source) into the first+-- (destination); the copied areas may not overlap.+--+-- Reimplemented from the unexported function+-- 'Data.ByteString.Internal.Type.memcpyFp'.+memcpyFp :: ForeignPtr Word8 -> ForeignPtr Word8 -> Int -> IO ()+memcpyFp fp fq s = unsafeWithForeignPtr fp $ \p ->+ unsafeWithForeignPtr fq $ \q -> copyBytes p q s++-- | Create a 'ByteString' of size @l@ and use action @f@ to fill its contents.+--+-- Reimplemented from the unexported function+-- 'Data.ByteString.Internal.Type.createFp.+createFp :: Int -> (ForeignPtr Word8 -> IO ()) -> IO ByteString+createFp len action = assert (len >= 0) $ do+ fp <- B.mallocByteString len+ action fp+ B.mkDeferredByteString fp len+{-# INLINE createFp #-}++createUptoNCPS+ :: Int+ -> (Ptr Word8 -> (Int -> IO ByteString) -> IO r)+ -> IO r+createUptoNCPS maxLen action = assert (maxLen >= 0) $ do+ fp <- B.mallocByteString maxLen+ withForeignPtr fp $ \p -> action p $ \len ->+ B.mkDeferredByteString fp len+{-# INLINE createUptoNCPS #-}++createCPS+ :: Int+ -> (ForeignPtr Word8 -> Int -> IO r)+ -> ((Int -> IO r) -> Ptr Word8 -> IO r)+ -> IO r+createCPS maxLen finalize f = assert (maxLen >= 0) $ do+ fp <- B.mallocByteString maxLen+ withForeignPtr fp $ \buf -> f (finalize fp) buf+{-# INLINE createCPS #-}++{-+withBuffer+ :: Int+ -> (Ptr Word8 -> Int -> IO r)+ -> (r ->+withBuffer bufLen+{-# INLINE withBuffer #-}+-}++createAndTrimCPS+ :: Int+ -> (Ptr Word8 -> (Int -> IO ByteString) -> IO r)+ -> IO r+createAndTrimCPS maxLen action = assert (maxLen >= 0) $ do+ fp <- B.mallocByteString maxLen+ withForeignPtr fp $ \p -> action p $ \len ->+ if len < maxLen+ then createFp len (\fp' -> memcpyFp fp' fp len)+ -- ^ apparently @fp@ will get GCed automatically, up to GHC+ else B.mkDeferredByteString fp maxLen+{-# INLINE createAndTrimCPS #-}++unsafeCreateAndTrimCPS+ :: Int+ -> (Ptr Word8 -> (Int -> IO ByteString) -> IO r)+ -> r+unsafeCreateAndTrimCPS l f =+ unsafeDupablePerformIOByteString (createAndTrimCPS l f)+{-# INLINE unsafeCreateAndTrimCPS #-}++createAndTrimFailable+ :: Int+ -> (Ptr Word8 -> IO (Either e Int))+ -> IO (Either e ByteString)+createAndTrimFailable l action = createFpAndTrimFailable l (wrapAction action)+{-# INLINE createAndTrimFailable #-}++-- TODO how do I omit the Either allocation?+createFpAndTrimFailable+ :: Int+ -> (ForeignPtr Word8 -> IO (Either e Int))+ -> IO (Either e ByteString)+createFpAndTrimFailable maxLen action = assert (maxLen >= 0) $ do+ fp <- B.mallocByteString maxLen+ action fp >>= \case+ Right len ->+ if len < maxLen+ then Right <$> createFp len (\fp' -> memcpyFp fp' fp len)+ -- ^ apparently @fp@ will get GCed automatically, up to GHC+ else Right <$> B.mkDeferredByteString fp maxLen+ Left err -> pure $ Left err+{-# INLINE createFpAndTrimFailable #-}++createUptoNFailable+ :: Int+ -> (Ptr Word8 -> IO (Either e Int))+ -> IO (Either e ByteString)+createUptoNFailable l action = createFpUptoNFailable l (wrapAction action)+{-# INLINE createUptoNFailable #-}++createFpUptoNFailable+ :: Int+ -> (ForeignPtr Word8 -> IO (Either e Int))+ -> IO (Either e ByteString)+createFpUptoNFailable maxLen action = assert (maxLen >= 0) $ do+ fp <- B.mallocByteString maxLen+ action fp >>= \case+ Right len -> Right <$> B.mkDeferredByteString fp len+ Left err -> pure $ Left err+{-# INLINE createFpUptoNFailable #-}++createFailable+ :: Int+ -> (Ptr Word8 -> IO (Either e Int))+ -> IO (Either e ByteString)+createFailable l action = createFpFailable l (wrapAction action)+{-# INLINE createFailable #-}++-- TODO how do I omit the Either allocation?+createFpFailable+ :: Int+ -> (ForeignPtr Word8 -> IO (Either e Int))+ -> IO (Either e ByteString)+createFpFailable maxLen action = assert (maxLen >= 0) $ do+ fp <- B.mallocByteString maxLen+ action fp >>= \case+ Right len ->+ -- TODO does not check for correctness (len <= maxLen)!! don't lie!!!!+ Right <$> B.mkDeferredByteString fp len+ Left err -> pure $ Left err+{-# INLINE createFpFailable #-}++-- TODO probably don't export+wrapAction :: (Ptr Word8 -> IO res) -> ForeignPtr Word8 -> IO res+wrapAction = flip withForeignPtr+ -- Cannot use unsafeWithForeignPtr, because action can diverge++unsafeDupablePerformIOByteString :: IO a -> a+-- Why does this exist? In base-4.15.1.0 until at least base-4.18.0.0,+-- the version of unsafeDupablePerformIO in base prevents unboxing of+-- its results with an opaque call to GHC.Exts.lazy, for reasons described+-- in Note [unsafePerformIO and strictness] in GHC.IO.Unsafe. (See+-- https://hackage.haskell.org/package/base-4.18.0.0/docs/src/GHC.IO.Unsafe.html#line-30 .)+-- Even if we accept the (very questionable) premise that the sort of+-- function described in that note should work, we expect no such+-- calls to be made in the context of bytestring. (And we really want+-- unboxing!)+unsafeDupablePerformIOByteString (IO act) =+ case runRW# act of (# _, res #) -> res