byteslice 0.2.5.2 → 0.2.6.0
raw patch · 18 files changed
+759/−312 lines, 18 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Data.Bytes.Types: instance GHC.Base.Monoid Data.Bytes.Types.Bytes
- Data.Bytes.Types: instance GHC.Base.Semigroup Data.Bytes.Types.Bytes
- Data.Bytes.Types: instance GHC.Classes.Eq Data.Bytes.Types.Bytes
- Data.Bytes.Types: instance GHC.Classes.Ord Data.Bytes.Types.Bytes
- Data.Bytes.Types: instance GHC.Exts.IsList Data.Bytes.Types.Bytes
- Data.Bytes.Types: instance GHC.Show.Show Data.Bytes.Types.Bytes
+ Data.Bytes: isInfixOf :: Bytes -> Bytes -> Bool
+ Data.Bytes: lift :: Bytes# -> Bytes
+ Data.Bytes: unlift :: Bytes -> Bytes#
+ Data.Bytes.Internal: Bytes :: {-# UNPACK #-} !ByteArray -> {-# UNPACK #-} !Int -> {-# UNPACK #-} !Int -> Bytes
+ Data.Bytes.Internal: [$sel:array:Bytes] :: Bytes -> {-# UNPACK #-} !ByteArray
+ Data.Bytes.Internal: [$sel:length:Bytes] :: Bytes -> {-# UNPACK #-} !Int
+ Data.Bytes.Internal: [$sel:offset:Bytes] :: Bytes -> {-# UNPACK #-} !Int
+ Data.Bytes.Internal: data Bytes
+ Data.Bytes.Internal: instance GHC.Base.Monoid Data.Bytes.Internal.Bytes
+ Data.Bytes.Internal: instance GHC.Base.Semigroup Data.Bytes.Internal.Bytes
+ Data.Bytes.Internal: instance GHC.Classes.Eq Data.Bytes.Internal.Bytes
+ Data.Bytes.Internal: instance GHC.Classes.Ord Data.Bytes.Internal.Bytes
+ Data.Bytes.Internal: instance GHC.Exts.IsList Data.Bytes.Internal.Bytes
+ Data.Bytes.Internal: instance GHC.Show.Show Data.Bytes.Internal.Bytes
+ Data.Bytes.Text.Ascii: fromString :: String -> Bytes
+ Data.Bytes.Text.AsciiExt: foldLines :: a -> (a -> Bytes -> IO a) -> IO a
+ Data.Bytes.Text.AsciiExt: forLines_ :: (Bytes -> IO a) -> IO ()
+ Data.Bytes.Text.AsciiExt: hFoldLines :: Handle -> a -> (a -> Bytes -> IO a) -> IO a
+ Data.Bytes.Text.AsciiExt: hForLines_ :: Handle -> (Bytes -> IO a) -> IO ()
+ Data.Bytes.Text.AsciiExt: toLowerU :: Bytes -> ByteArray
+ Data.Bytes.Text.Latin1: equals1 :: Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: equals10 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: equals11 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: equals12 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: equals2 :: Char -> Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: equals3 :: Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: equals4 :: Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: equals5 :: Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: equals6 :: Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: equals7 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: equals8 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: equals9 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool
+ Data.Bytes.Text.Latin1: fromString :: String -> Bytes
+ Data.Bytes.Text.Latin1: toString :: Bytes -> String
+ Data.Bytes.Types: ByteArrayN :: ByteArray -> ByteArrayN (n :: Nat)
+ Data.Bytes.Types: BytesN :: {-# UNPACK #-} !ByteArray -> {-# UNPACK #-} !Int -> BytesN (n :: Nat)
+ Data.Bytes.Types: [$sel:array:ByteArrayN] :: ByteArrayN (n :: Nat) -> ByteArray
+ Data.Bytes.Types: [$sel:array:BytesN] :: BytesN (n :: Nat) -> {-# UNPACK #-} !ByteArray
+ Data.Bytes.Types: [$sel:offset:BytesN] :: BytesN (n :: Nat) -> {-# UNPACK #-} !Int
+ Data.Bytes.Types: data BytesN (n :: Nat)
+ Data.Bytes.Types: newtype ByteArrayN (n :: Nat)
Files
- CHANGELOG.md +9/−0
- bench/Main.hs +2/−2
- byteslice.cabal +7/−1
- src-no-unlifted-newtypes/UnliftedBytes.hs +9/−0
- src-unlifted-newtypes/UnliftedBytes.hs +11/−1
- src/Data/Bytes.hs +144/−196
- src/Data/Bytes/Chunks.hs +1/−1
- src/Data/Bytes/IO.hs +2/−3
- src/Data/Bytes/Internal.hs +113/−0
- src/Data/Bytes/Mutable.hs +7/−0
- src/Data/Bytes/Pure.hs +63/−3
- src/Data/Bytes/Text/Ascii.hs +29/−0
- src/Data/Bytes/Text/AsciiExt.hs +96/−0
- src/Data/Bytes/Text/Latin1.hs +214/−0
- src/Data/Bytes/Text/Utf8.hs +3/−0
- src/Data/Bytes/Text/Windows1252.hs +3/−0
- src/Data/Bytes/Types.hs +16/−95
- test/Main.hs +30/−10
CHANGELOG.md view
@@ -1,5 +1,14 @@ # Revision history for byteslice +## 0.2.6.0 -- 2021-09-15++* Add `BytesN` and `ByteArrayN`.+* Add `isInfixOf`.+* Add `hForLines_` and `hFoldLines`.+* Add `lift` and `unlift` for converting between `Bytes` and `Bytes#`.+* Move text-oriented functions from Data.Bytes to `Data.Bytes.Text.*`.+ Provide aliases with older names that come with deprecation warning.+ ## 0.2.5.2 -- 2021-02-23 * Correct compatibility shims.
bench/Main.hs view
@@ -2,7 +2,7 @@ import Data.Bytes.Types import Data.List (permutations)-import qualified Data.Bytes as Bytes+import qualified Data.Bytes.Text.Ascii as Ascii naiveMconcat :: [Bytes] -> Bytes naiveMconcat = foldr mappend mempty@@ -14,4 +14,4 @@ ] mconcatBytes :: [Bytes]-mconcatBytes = fmap Bytes.fromAsciiString $ permutations ['a'..'g']+mconcatBytes = fmap Ascii.fromString $ permutations ['a'..'g']
byteslice.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: byteslice-version: 0.2.5.2+version: 0.2.6.0 synopsis: Slicing managed and unmanaged memory description: This library provides types that allow the user to talk about a slice of@@ -25,7 +25,13 @@ exposed-modules: Data.Bytes Data.Bytes.Chunks+ Data.Bytes.Internal Data.Bytes.Mutable+ Data.Bytes.Text.Ascii+ Data.Bytes.Text.AsciiExt+ Data.Bytes.Text.Latin1+ Data.Bytes.Text.Utf8+ Data.Bytes.Text.Windows1252 Data.Bytes.Types other-modules: Data.Bytes.Byte
src-no-unlifted-newtypes/UnliftedBytes.hs view
@@ -5,10 +5,19 @@ module UnliftedBytes ( Bytes#+ , lift+ , unlift ) where +import Data.Bytes.Internal (Bytes) import GHC.TypeLits import GHC.Exts (RuntimeRep(..),TYPE) type family Bytes# :: TYPE ('TupleRep '[ 'UnliftedRep,'IntRep,'IntRep]) where Bytes# = TypeError ('Text "Bytes# not available before GHC 8.10")++lift :: Bytes# -> Bytes+lift _ = errorWithoutStackTrace "UnliftedBytes: lift not implemented"++unlift :: Bytes -> Bytes#+unlift _ = errorWithoutStackTrace "UnliftedBytes: unlift not implemented"
src-unlifted-newtypes/UnliftedBytes.hs view
@@ -7,10 +7,20 @@ module UnliftedBytes ( Bytes#(..)+ , lift+ , unlift ) where -import GHC.Exts (ByteArray#,Int#,RuntimeRep(..),TYPE)+import Data.Bytes.Internal (Bytes(Bytes))+import Data.Primitive (ByteArray(ByteArray))+import GHC.Exts (Int(I#),ByteArray#,Int#,RuntimeRep(..),TYPE) newtype Bytes# :: TYPE ('TupleRep '[ 'UnliftedRep,'IntRep,'IntRep]) where Bytes# :: (# ByteArray#, Int#, Int# #) -> Bytes# +lift :: Bytes# -> Bytes+lift (Bytes# (# arr, off, len #)) = Bytes (ByteArray arr) (I# off) (I# len)++unlift :: Bytes -> Bytes#+unlift (Bytes (ByteArray arr) (I# off) (I# len)) =+ Bytes# (# arr, off, len #)
src/Data/Bytes.hs view
@@ -4,9 +4,17 @@ {-# language MagicHash #-} {-# language NamedFieldPuns #-} {-# language RankNTypes #-}+{-# language TupleSections #-} {-# language TypeApplications #-} {-# language UnboxedTuples #-} +-- | If you are interested in sub-arrays of 'ByteArray's (e.g. writing a binary+-- search), it would be grossly inefficient to make a copy of the sub-array. On+-- the other hand, it'd be really annoying to track limit indices by hand.+--+-- This module defines the 'Bytes' type which exposes a standard array interface+-- for a sub-arrays without copying and without manual index manipulation. --+-- For mutable arrays, see 'Data.Bytes.Mutable'. module Data.Bytes ( -- * Types Bytes@@ -40,12 +48,12 @@ , takeWhileEnd , dropWhileEnd -- * Folds- , foldl+ , Pure.foldl , Pure.foldl'- , foldr- , foldr'+ , Pure.foldr+ , Pure.foldr' -- * Folds with Indices- , ifoldl'+ , Pure.ifoldl' -- * Common Folds , elem -- * Splitting@@ -72,6 +80,7 @@ -- ** Byte Sequence , isPrefixOf , isSuffixOf+ , isInfixOf , stripPrefix , stripOptionalPrefix , stripSuffix@@ -123,7 +132,7 @@ , fromCString# , Pure.toByteString , Pure.pinnedToByteString- , fromByteString+ , Pure.fromByteString , fromShortByteString , toShortByteString , toShortByteStringClone@@ -132,33 +141,36 @@ , BIO.hGet , readFile , BIO.hPut+ -- * Unlifted Types+ , lift+ , unlift ) where import Prelude hiding (length,takeWhile,dropWhile,null,foldl,foldr,elem,replicate,any,all,readFile) import Control.Monad.Primitive (PrimMonad,primitive_,unsafeIOToPrim)-import Control.Monad.ST (ST) import Control.Monad.ST.Run (runByteArrayST)-import Data.ByteString (ByteString)-import Data.ByteString.Short.Internal (ShortByteString(SBS))-import Data.Bytes.Pure (length,fromByteArray)+import Cstrlen (cstringLength#)+import Data.Bits((.&.),(.|.),shiftL,finiteBitSize)+import Data.Bytes.Pure (length,fromByteArray,foldr) import Data.Bytes.Types (Bytes(Bytes,array,offset))-import Data.Char (ord)+import Data.ByteString.Short.Internal (ShortByteString(SBS))+import Data.Maybe (fromMaybe) import Data.Primitive (ByteArray(ByteArray)) import Foreign.C.String (CString) import Foreign.Ptr (Ptr,plusPtr,castPtr)-import GHC.Exts (Int(I#),Char(C#),Ptr(Ptr),word2Int#,chr#) import GHC.Exts (Addr#,Word#,Int#)-import GHC.IO (unsafeIOToST)-import GHC.Word (Word8(W8#))-import Cstrlen (cstringLength#)+import GHC.Exts (Int(I#),Ptr(Ptr))+import GHC.Word (Word8(W8#),Word32)+import UnliftedBytes (lift,unlift) -import qualified Data.ByteString as ByteString-import qualified Data.ByteString.Unsafe as ByteString import qualified Data.Bytes.Byte as Byte import qualified Data.Bytes.Chunks as Chunks import qualified Data.Bytes.IO as BIO import qualified Data.Bytes.Pure as Pure+import qualified Data.Bytes.Text.Ascii as Ascii+import qualified Data.Bytes.Text.AsciiExt as AsciiExt+import qualified Data.Bytes.Text.Latin1 as Latin1 import qualified Data.Bytes.Types as Types import qualified Data.Foldable as F import qualified Data.List as List@@ -224,6 +236,67 @@ then compareByteArrays a aOff b (bOff + bLen - aLen) aLen == EQ else False +-- | Is the first argument an infix of the second argument?+-- +-- Uses the Rabin-Karp algorithm: expected time @O(n+m)@, worst-case @O(nm)@.+isInfixOf :: Bytes -- ^ String to search for+ -> Bytes -- ^ String to search in+ -> Bool+isInfixOf p s = null p || (not . null) (snd $ breakSubstring p s)++breakSubstring :: Bytes -- ^ String to search for+ -> Bytes -- ^ String to search in+ -> (Bytes,Bytes) -- ^ Head and tail of string broken at substring+breakSubstring pat =+ case lp of+ 0 -> (mempty,)+ 1 -> breakByte (unsafeHead pat)+ _ -> if lp * 8 <= finiteBitSize (0 :: Word)+ then shift+ else karpRabin+ where+ unsafeSplitAt i s = (unsafeTake i s, unsafeDrop i s)+ lp = length pat+ {-# INLINE breakByte #-}+ breakByte b bytes = fromMaybe (mempty,bytes) $ Byte.split1 b bytes+ {-# INLINE karpRabin #-}+ karpRabin :: Bytes -> (Bytes, Bytes)+ karpRabin src+ | length src < lp = (src,mempty)+ | otherwise = search (rollingHash $ unsafeTake lp src) lp+ where+ k = 2891336453 :: Word32+ rollingHash = Pure.foldl' (\h b -> h * k + fromIntegral b) 0+ hp = rollingHash pat+ m = k ^ lp+ get = fromIntegral . unsafeIndex src+ search !hs !i+ | hp == hs && pat == unsafeTake lp b = u+ | length src <= i = (src,mempty) -- not found+ | otherwise = search hs' (i + 1)+ where+ u@(_, b) = unsafeSplitAt (i - lp) src+ hs' = hs * k ++ get i -+ m * get (i - lp)+ {-# INLINE shift #-}+ shift :: Bytes -> (Bytes, Bytes)+ shift !src+ | length src < lp = (src,mempty)+ | otherwise = search (intoWord $ unsafeTake lp src) lp+ where+ intoWord :: Bytes -> Word+ intoWord = Pure.foldl' (\w b -> (w `shiftL` 8) .|. fromIntegral b) 0+ wp = intoWord pat+ mask = (1 `shiftL` (8 * lp)) - 1+ search !w !i+ | w == wp = unsafeSplitAt (i - lp) src+ | length src <= i = (src, mempty)+ | otherwise = search w' (i + 1)+ where+ b = fromIntegral (unsafeIndex src i)+ w' = mask .&. ((w `shiftL` 8) .|. b)+ -- | Find the longest string which is a prefix of both arguments. longestCommonPrefix :: Bytes -> Bytes -> Bytes longestCommonPrefix a b = loop 0@@ -349,6 +422,11 @@ {-# inline unsafeIndex #-} unsafeIndex (Bytes arr off _) ix = PM.indexByteArray arr (off + ix) +-- | Access the first byte. The given 'Bytes' must be non-empty.+{-# inline unsafeHead #-}+unsafeHead :: Bytes -> Word8+unsafeHead bs = unsafeIndex bs 0+ -- | /O(n)/ 'dropWhileEnd' @p@ @b@ returns the prefix remaining after -- dropping characters that satisfy the predicate @p@ from the end of -- @t@.@@ -400,61 +478,27 @@ else n else n --- | Left fold over bytes, non-strict in the accumulator.-foldl :: (a -> Word8 -> a) -> a -> Bytes -> a-{-# inline foldl #-}-foldl f a0 (Bytes arr off0 len0) =- go (off0 + len0 - 1) (len0 - 1) - where- go !off !ix = case ix of- (-1) -> a0- _ -> f (go (off - 1) (ix - 1)) (PM.indexByteArray arr off)---- | Right fold over bytes, non-strict in the accumulator.-foldr :: (Word8 -> a -> a) -> a -> Bytes -> a-{-# inline foldr #-}-foldr f a0 (Bytes arr off0 len0) = go off0 len0 where- go !off !len = case len of- 0 -> a0- _ -> f (PM.indexByteArray arr off) (go (off + 1) (len - 1))---- | Left fold over bytes, strict in the accumulator. The reduction function--- is applied to each element along with its index.-ifoldl' :: (a -> Int -> Word8 -> a) -> a -> Bytes -> a-{-# inline ifoldl' #-}-ifoldl' f a0 (Bytes arr off0 len0) = go a0 0 off0 len0 where- go !a !ix !off !len = case len of- 0 -> a- _ -> go (f a ix (PM.indexByteArray arr off)) (ix + 1) (off + 1) (len - 1)---- | Right fold over bytes, strict in the accumulator.-foldr' :: (Word8 -> a -> a) -> a -> Bytes -> a-{-# inline foldr' #-}-foldr' f a0 (Bytes arr off0 len0) =- go a0 (off0 + len0 - 1) (len0 - 1) - where- go !a !off !ix = case ix of- (-1) -> a- _ -> go (f (PM.indexByteArray arr off) a) (off - 1) (ix - 1)- -- | Convert a 'String' consisting of only characters in the ASCII block -- to a byte sequence. Any character with a codepoint above @U+007F@ is -- replaced by @U+0000@. fromAsciiString :: String -> Bytes-fromAsciiString = fromByteArray- . Exts.fromList- . map (\c -> let i = ord c in if i < 128 then fromIntegral @Int @Word8 i else 0)+{-# DEPRECATED fromAsciiString "use Data.Bytes.Ascii.fromString instead" #-}+{-# INLINE fromAsciiString #-}+fromAsciiString = Ascii.fromString -- | Convert a 'String' consisting of only characters representable -- by ISO-8859-1. These are encoded with ISO-8859-1. Any character -- with a codepoint above @U+00FF@ is replaced by an unspecified byte. fromLatinString :: String -> Bytes-fromLatinString =- fromByteArray . Exts.fromList . map (fromIntegral @Int @Word8 . ord)+{-# DEPRECATED fromLatinString "use Data.Bytes.Latin1.fromString instead" #-}+{-# INLINE fromLatinString #-}+fromLatinString = Latin1.fromString -- | Interpret a byte sequence as text encoded by ISO-8859-1. toLatinString :: Bytes -> String-toLatinString = foldr (\(W8# w) xs -> C# (chr# (word2Int# w)) : xs) []+{-# DEPRECATED toLatinString "use Data.Bytes.Latin1.toString instead" #-}+{-# INLINE toLatinString #-}+toLatinString = Latin1.toString -- | Copy a primitive string literal into managed memory. fromCString# :: Addr# -> Bytes@@ -476,153 +520,87 @@ -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- a singleton whose element matches the character? equalsLatin1 :: Char -> Bytes -> Bool-{-# inline equalsLatin1 #-}-equalsLatin1 !c0 (Bytes arr off len) = case len of- 1 -> c0 == indexCharArray arr off- _ -> False+{-# DEPRECATED equalsLatin1 "use Data.Bytes.Text.Latin1.equals1 instead" #-}+{-# INLINE equalsLatin1 #-}+equalsLatin1 = Latin1.equals1 + -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- a doubleton whose elements match the characters? equalsLatin2 :: Char -> Char -> Bytes -> Bool-equalsLatin2 !c0 !c1 (Bytes arr off len) = case len of- 2 -> c0 == indexCharArray arr off &&- c1 == indexCharArray arr (off + 1)- _ -> False+{-# DEPRECATED equalsLatin2 "use Data.Bytes.Text.Latin1.equals2 instead" #-}+{-# INLINE equalsLatin2 #-}+equalsLatin2 = Latin1.equals2 -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- a tripleton whose elements match the characters? equalsLatin3 :: Char -> Char -> Char -> Bytes -> Bool-equalsLatin3 !c0 !c1 !c2 (Bytes arr off len) = case len of- 3 -> c0 == indexCharArray arr off &&- c1 == indexCharArray arr (off + 1) &&- c2 == indexCharArray arr (off + 2)- _ -> False+{-# DEPRECATED equalsLatin3 "use Data.Bytes.Text.Latin1.equals3 instead" #-}+{-# INLINE equalsLatin3 #-}+equalsLatin3 = Latin1.equals3 -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- a quadrupleton whose elements match the characters? equalsLatin4 :: Char -> Char -> Char -> Char -> Bytes -> Bool-equalsLatin4 !c0 !c1 !c2 !c3 (Bytes arr off len) = case len of- 4 -> c0 == indexCharArray arr off &&- c1 == indexCharArray arr (off + 1) &&- c2 == indexCharArray arr (off + 2) &&- c3 == indexCharArray arr (off + 3)- _ -> False+{-# DEPRECATED equalsLatin4 "use Data.Bytes.Text.Latin1.equals4 instead" #-}+{-# INLINE equalsLatin4 #-}+equalsLatin4 = Latin1.equals4 -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- a quintupleton whose elements match the characters? equalsLatin5 :: Char -> Char -> Char -> Char -> Char -> Bytes -> Bool-equalsLatin5 !c0 !c1 !c2 !c3 !c4 (Bytes arr off len) = case len of- 5 -> c0 == indexCharArray arr off &&- c1 == indexCharArray arr (off + 1) &&- c2 == indexCharArray arr (off + 2) &&- c3 == indexCharArray arr (off + 3) &&- c4 == indexCharArray arr (off + 4)- _ -> False+{-# DEPRECATED equalsLatin5 "use Data.Bytes.Text.Latin1.equals5 instead" #-}+{-# INLINE equalsLatin5 #-}+equalsLatin5 = Latin1.equals5 -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- a sextupleton whose elements match the characters? equalsLatin6 :: Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool-equalsLatin6 !c0 !c1 !c2 !c3 !c4 !c5 (Bytes arr off len) = case len of- 6 -> c0 == indexCharArray arr off &&- c1 == indexCharArray arr (off + 1) &&- c2 == indexCharArray arr (off + 2) &&- c3 == indexCharArray arr (off + 3) &&- c4 == indexCharArray arr (off + 4) &&- c5 == indexCharArray arr (off + 5)- _ -> False+{-# DEPRECATED equalsLatin6 "use Data.Bytes.Text.Latin1.equals6 instead" #-}+{-# INLINE equalsLatin6 #-}+equalsLatin6 = Latin1.equals6 -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- a septupleton whose elements match the characters? equalsLatin7 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool-equalsLatin7 !c0 !c1 !c2 !c3 !c4 !c5 !c6 (Bytes arr off len) = case len of- 7 -> c0 == indexCharArray arr off &&- c1 == indexCharArray arr (off + 1) &&- c2 == indexCharArray arr (off + 2) &&- c3 == indexCharArray arr (off + 3) &&- c4 == indexCharArray arr (off + 4) &&- c5 == indexCharArray arr (off + 5) &&- c6 == indexCharArray arr (off + 6)- _ -> False+{-# DEPRECATED equalsLatin7 "use Data.Bytes.Text.Latin1.equals7 instead" #-}+{-# INLINE equalsLatin7 #-}+equalsLatin7 = Latin1.equals7 -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- an octupleton whose elements match the characters? equalsLatin8 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool-equalsLatin8 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 (Bytes arr off len) = case len of- 8 -> c0 == indexCharArray arr off &&- c1 == indexCharArray arr (off + 1) &&- c2 == indexCharArray arr (off + 2) &&- c3 == indexCharArray arr (off + 3) &&- c4 == indexCharArray arr (off + 4) &&- c5 == indexCharArray arr (off + 5) &&- c6 == indexCharArray arr (off + 6) &&- c7 == indexCharArray arr (off + 7)- _ -> False+{-# DEPRECATED equalsLatin8 "use Data.Bytes.Text.Latin1.equals8 instead" #-}+{-# INLINE equalsLatin8 #-}+equalsLatin8 = Latin1.equals8 -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- a 9-tuple whose elements match the characters? equalsLatin9 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool-equalsLatin9 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 (Bytes arr off len) = case len of- 9 -> c0 == indexCharArray arr off &&- c1 == indexCharArray arr (off + 1) &&- c2 == indexCharArray arr (off + 2) &&- c3 == indexCharArray arr (off + 3) &&- c4 == indexCharArray arr (off + 4) &&- c5 == indexCharArray arr (off + 5) &&- c6 == indexCharArray arr (off + 6) &&- c7 == indexCharArray arr (off + 7) &&- c8 == indexCharArray arr (off + 8)- _ -> False+{-# DEPRECATED equalsLatin9 "use Data.Bytes.Text.Latin1.equals9 instead" #-}+{-# INLINE equalsLatin9 #-}+equalsLatin9 = Latin1.equals9 -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- a 10-tuple whose elements match the characters? equalsLatin10 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool-equalsLatin10 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 !c9 (Bytes arr off len) = case len of- 10 -> c0 == indexCharArray arr off &&- c1 == indexCharArray arr (off + 1) &&- c2 == indexCharArray arr (off + 2) &&- c3 == indexCharArray arr (off + 3) &&- c4 == indexCharArray arr (off + 4) &&- c5 == indexCharArray arr (off + 5) &&- c6 == indexCharArray arr (off + 6) &&- c7 == indexCharArray arr (off + 7) &&- c8 == indexCharArray arr (off + 8) &&- c9 == indexCharArray arr (off + 9)- _ -> False+{-# DEPRECATED equalsLatin10 "use Data.Bytes.Text.Latin1.equals10 instead" #-}+{-# INLINE equalsLatin10 #-}+equalsLatin10 = Latin1.equals10 -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- a 11-tuple whose elements match the characters? equalsLatin11 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool-equalsLatin11 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 !c9 !c10 (Bytes arr off len) = case len of- 11 -> c0 == indexCharArray arr off &&- c1 == indexCharArray arr (off + 1) &&- c2 == indexCharArray arr (off + 2) &&- c3 == indexCharArray arr (off + 3) &&- c4 == indexCharArray arr (off + 4) &&- c5 == indexCharArray arr (off + 5) &&- c6 == indexCharArray arr (off + 6) &&- c7 == indexCharArray arr (off + 7) &&- c8 == indexCharArray arr (off + 8) &&- c9 == indexCharArray arr (off + 9) &&- c10 == indexCharArray arr (off + 10)- _ -> False+{-# DEPRECATED equalsLatin11 "use Data.Bytes.Text.Latin1.equals11 instead" #-}+{-# INLINE equalsLatin11 #-}+equalsLatin11 = Latin1.equals11 -- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text, -- a 12-tuple whose elements match the characters? equalsLatin12 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool-equalsLatin12 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 !c9 !c10 !c11 (Bytes arr off len) = case len of- 12 -> c0 == indexCharArray arr off &&- c1 == indexCharArray arr (off + 1) &&- c2 == indexCharArray arr (off + 2) &&- c3 == indexCharArray arr (off + 3) &&- c4 == indexCharArray arr (off + 4) &&- c5 == indexCharArray arr (off + 5) &&- c6 == indexCharArray arr (off + 6) &&- c7 == indexCharArray arr (off + 7) &&- c8 == indexCharArray arr (off + 8) &&- c9 == indexCharArray arr (off + 9) &&- c10 == indexCharArray arr (off + 10) &&- c11 == indexCharArray arr (off + 11)- _ -> False+{-# DEPRECATED equalsLatin12 "use Data.Bytes.Text.Latin1.equals12 instead" #-}+{-# INLINE equalsLatin12 #-}+equalsLatin12 = Latin1.equals12 -- | Is the byte sequence equal to the @NUL@-terminated C String? -- The C string must be a constant.@@ -649,15 +627,12 @@ False -> Nothing -- | Touch the byte array backing the byte sequence. This sometimes needed--- after calling 'contents' so that the @ByteArray@ does not get garbage+-- after calling 'Pure.contents' so that the @ByteArray@ does not get garbage -- collected. touch :: PrimMonad m => Bytes -> m () touch (Bytes (ByteArray arr) _ _) = unsafeIOToPrim (primitive_ (\s -> Exts.touch# arr s)) -indexCharArray :: ByteArray -> Int -> Char-indexCharArray (ByteArray arr) (I# off) = C# (Exts.indexCharArray# arr off)- -- | Read an entire file strictly into a 'Bytes'. readFile :: FilePath -> IO Bytes readFile f = Chunks.concat <$> Chunks.readFile f@@ -740,33 +715,6 @@ -- @[0x41,0x5A]@ and leaves all other bytes alone. Unconditionally -- copies the bytes. toLowerAsciiByteArrayClone :: Bytes -> ByteArray-toLowerAsciiByteArrayClone (Bytes src off0 len0) =- runByteArrayST action- where- action :: forall s. ST s ByteArray- action = do- dst <- PM.newByteArray len0- let go !off !ix !len = if len == 0- then pure ()- else do- let w = PM.indexByteArray src off :: Word8- w' = if w >= 0x41 && w <= 0x5A- then w + 32- else w- PM.writeByteArray dst ix w'- go (off + 1) (ix + 1) (len - 1)- go off0 0 len0- PM.unsafeFreezeByteArray dst----- | /O(n)/ Copy a 'ByteString' to a byte sequence.-fromByteString :: ByteString -> Bytes-fromByteString !b = Bytes- ( runByteArrayST $ unsafeIOToST $ do - dst@(PM.MutableByteArray dst# ) <- PM.newByteArray len- ByteString.unsafeUseAsCString b $ \src -> do- PM.copyPtrToMutablePrimArray (PM.MutablePrimArray dst# ) 0 src len- PM.unsafeFreezeByteArray dst- ) 0 len- where- !len = ByteString.length b+{-# DEPRECATED toLowerAsciiByteArrayClone "use Data.Bytes/Text/AsciiExt.toLowerU" #-}+{-# INLINE toLowerAsciiByteArrayClone #-}+toLowerAsciiByteArrayClone = AsciiExt.toLowerU
src/Data/Bytes/Chunks.hs view
@@ -290,7 +290,7 @@ ) 0xcbf29ce484222325 b -- | Outputs 'Chunks' to the specified 'Handle'. This is implemented--- with 'hPutBuf'.+-- with 'IO.hPut'. hPut :: Handle -> Chunks -> IO () hPut h = go where go ChunksNil = pure ()
src/Data/Bytes/IO.hs view
@@ -22,12 +22,12 @@ import qualified Data.Primitive as PM -- | Read 'Bytes' directly from the specified 'Handle'. The resulting--- 'Bytes' are pinned. This is implemented with 'hGetBuf'.+-- 'Bytes' are pinned. This is implemented with 'IO.hGetBuf'. hGet :: Handle -> Int -> IO Bytes hGet h i = createPinnedAndTrim i (\p -> IO.hGetBuf h p i) -- | Outputs 'Bytes' to the specified 'Handle'. This is implemented--- with 'hPutBuf'.+-- with 'IO.hPutBuf'. hPut :: Handle -> Bytes -> IO () hPut h b0 = do let b1@(Bytes arr _ len) = pin b0@@ -52,4 +52,3 @@ touchByteArrayIO :: ByteArray -> IO () touchByteArrayIO (ByteArray x) = IO (\s -> (# Exts.touch# x s, () #))-
+ src/Data/Bytes/Internal.hs view
@@ -0,0 +1,113 @@+{-# language BangPatterns #-}+{-# language DataKinds #-}+{-# language MagicHash #-}+{-# language TypeFamilies #-}+{-# language DuplicateRecordFields #-}++-- This needs to be in its own module to prevent a cyclic dependency+-- between UnliftedBytes and Data.Bytes.Types+module Data.Bytes.Internal+ ( Bytes(..)+ ) where++import Control.Monad.ST (runST)+import Control.Monad.ST.Run (runByteArrayST)+import Data.Bits ((.&.),unsafeShiftR)+import Data.Char (ord)+import Data.Primitive (ByteArray(..))+import Data.Word (Word8)+import GHC.Base (unsafeChr)+import GHC.Exts (Int(I#),unsafeCoerce#,sameMutableByteArray#)+import GHC.Exts (isTrue#,compareByteArrays#,IsList(..))++import qualified Data.List as L+import qualified Data.Foldable as F+import qualified Data.Primitive as PM++-- | A slice of a 'ByteArray'.+data Bytes = Bytes+ { array :: {-# UNPACK #-} !ByteArray+ , offset :: {-# UNPACK #-} !Int+ , length :: {-# UNPACK #-} !Int+ }++instance IsList Bytes where+ type Item Bytes = Word8+ fromListN n xs = Bytes (fromListN n xs) 0 n+ fromList xs = fromListN (L.length xs) xs+ toList (Bytes arr off len) = toListLoop off len arr++toListLoop :: Int -> Int -> ByteArray -> [Word8]+toListLoop !off !len !arr = if len > 0+ then PM.indexByteArray arr off : toListLoop (off + 1) (len - 1) arr+ else []++instance Show Bytes where+ showsPrec _ (Bytes arr off len) s = if len == 0+ then showString "[]" s+ else showString "[0x"+ $ showHexDigits (PM.indexByteArray arr off)+ $ showLoop (off + 1) (len - 1) arr+ $ showChar ']'+ $ s++showLoop :: Int -> Int -> ByteArray -> String -> String+showLoop !ix !len !arr s = if len > 0+ then ',':'0':'x':showHexDigits (PM.indexByteArray arr ix) (showLoop (ix + 1) (len - 1) arr s)+ else s++showHexDigits :: Word8 -> String -> String+showHexDigits !w s = word4ToChar (unsafeShiftR w 4) : word4ToChar (0x0F .&. w) : s++word4ToChar :: Word8 -> Char+word4ToChar w = if w < 10+ then unsafeChr (ord '0' + fromIntegral w)+ else unsafeChr (ord 'a' + (fromIntegral w) - 10)++instance Eq Bytes where+ Bytes arr1 off1 len1 == Bytes arr2 off2 len2+ | len1 /= len2 = False+ | sameByteArray arr1 arr2 && off1 == off2 = True+ | otherwise = compareByteArrays arr1 off1 arr2 off2 len1 == EQ++instance Ord Bytes where+ compare (Bytes arr1 off1 len1) (Bytes arr2 off2 len2)+ | sameByteArray arr1 arr2 && off1 == off2 && len1 == len2 = EQ+ | otherwise = compareByteArrays arr1 off1 arr2 off2 (min len1 len2) <> compare len1 len2++instance Semigroup Bytes where+ -- TODO: Do the trick to move the data constructor to the outside+ -- of runST.+ Bytes arrA offA lenA <> Bytes arrB offB lenB = runST $ do+ marr <- PM.newByteArray (lenA + lenB)+ PM.copyByteArray marr 0 arrA offA lenA+ PM.copyByteArray marr lenA arrB offB lenB+ r <- PM.unsafeFreezeByteArray marr+ pure (Bytes r 0 (lenA + lenB))++instance Monoid Bytes where+ mempty = Bytes mempty 0 0+ mconcat [] = mempty+ mconcat [x] = x+ mconcat bs = Bytes r 0 fullLen+ where+ !fullLen = L.foldl' (\acc (Bytes _ _ len) -> acc + len) 0 bs+ r = runByteArrayST $ do+ marr <- PM.newByteArray fullLen+ !_ <- F.foldlM+ (\ !currLen (Bytes arr off len) -> do+ PM.copyByteArray marr currLen arr off len+ pure (currLen + len)+ ) 0 bs+ PM.unsafeFreezeByteArray marr++compareByteArrays :: ByteArray -> Int -> ByteArray -> Int -> Int -> Ordering+{-# INLINE compareByteArrays #-}+compareByteArrays (ByteArray ba1#) (I# off1#) (ByteArray ba2#) (I# off2#) (I# n#) =+ compare (I# (compareByteArrays# ba1# off1# ba2# off2# n#)) 0++sameByteArray :: ByteArray -> ByteArray -> Bool+{-# INLINE sameByteArray #-}+sameByteArray (ByteArray ba1#) (ByteArray ba2#) =+ isTrue# (sameMutableByteArray# (unsafeCoerce# ba1#) (unsafeCoerce# ba2#))+
src/Data/Bytes/Mutable.hs view
@@ -1,6 +1,13 @@ {-# language BangPatterns #-} {-# language LambdaCase #-} +-- | If you are interested in sub-arrays of 'MutableByteArray's (e.g. writing+-- quicksort), it would be grossly inefficient to make a copy of the sub-array.+-- On the other hand, it'd be really annoying to track limit indices by hand.+--+-- This module defines the 'MutableBytes' type which exposes a standard array+-- interface for a sub-arrays without copying and without manual index+-- manipulation. For immutable arrays, see 'Data.Bytes'. module Data.Bytes.Mutable ( -- * Types MutableBytes
src/Data/Bytes/Pure.hs view
@@ -18,26 +18,35 @@ , toPinnedByteArrayClone , fromByteArray , length+ , foldl , foldl'+ , foldr+ , ifoldl'+ , foldr' , fnv1a32 , fnv1a64 , toByteString , pinnedToByteString+ , fromByteString ) where -import Prelude hiding (length)+import Prelude hiding (length,foldl,foldr) import Control.Monad.Primitive (PrimState,PrimMonad) import Control.Monad.ST.Run (runByteArrayST) import Data.Bits (xor)-import Data.ByteString (ByteString) import Data.Bytes.Types (Bytes(Bytes))+import Data.ByteString (ByteString) import Data.Primitive (ByteArray,MutableByteArray) import Data.Word (Word64,Word32,Word8) import Foreign.Ptr (Ptr,plusPtr)+import GHC.IO (unsafeIOToST) +import qualified Data.ByteString as ByteString import qualified Data.ByteString.Internal as ByteString+import qualified Data.ByteString.Unsafe as ByteString import qualified Data.Primitive as PM+import qualified Data.Primitive.Ptr as PM import qualified GHC.Exts as Exts import qualified GHC.ForeignPtr as ForeignPtr @@ -124,6 +133,16 @@ (\acc w -> (fromIntegral @Word8 @Word64 w `xor` acc) * 0x00000100000001B3 ) 0xcbf29ce484222325 b +-- | Left fold over bytes, non-strict in the accumulator.+foldl :: (a -> Word8 -> a) -> a -> Bytes -> a+{-# inline foldl #-}+foldl f a0 (Bytes arr off0 len0) =+ go (off0 + len0 - 1) (len0 - 1) + where+ go !off !ix = case ix of+ (-1) -> a0+ _ -> f (go (off - 1) (ix - 1)) (PM.indexByteArray arr off)+ -- | Left fold over bytes, strict in the accumulator. foldl' :: (a -> Word8 -> a) -> a -> Bytes -> a {-# inline foldl' #-}@@ -132,6 +151,34 @@ 0 -> a _ -> go (f a (PM.indexByteArray arr off)) (off + 1) (len - 1) +-- | Right fold over bytes, non-strict in the accumulator.+foldr :: (Word8 -> a -> a) -> a -> Bytes -> a+{-# inline foldr #-}+foldr f a0 (Bytes arr off0 len0) = go off0 len0 where+ go !off !len = case len of+ 0 -> a0+ _ -> f (PM.indexByteArray arr off) (go (off + 1) (len - 1))++-- | Left fold over bytes, strict in the accumulator. The reduction function+-- is applied to each element along with its index.+ifoldl' :: (a -> Int -> Word8 -> a) -> a -> Bytes -> a+{-# inline ifoldl' #-}+ifoldl' f a0 (Bytes arr off0 len0) = go a0 0 off0 len0 where+ go !a !ix !off !len = case len of+ 0 -> a+ _ -> go (f a ix (PM.indexByteArray arr off)) (ix + 1) (off + 1) (len - 1)++-- | Right fold over bytes, strict in the accumulator.+foldr' :: (Word8 -> a -> a) -> a -> Bytes -> a+{-# inline foldr' #-}+foldr' f a0 (Bytes arr off0 len0) =+ go a0 (off0 + len0 - 1) (len0 - 1) + where+ go !a !off !ix = case ix of+ (-1) -> a+ _ -> go (f (PM.indexByteArray arr off) a) (off - 1) (ix - 1)++ -- | Yields a pointer to the beginning of the byte sequence. It is only safe -- to call this on a 'Bytes' backed by a pinned @ByteArray@. contents :: Bytes -> Ptr Word8@@ -162,7 +209,8 @@ toByteString :: Bytes -> ByteString toByteString !b = pinnedToByteString (pin b) --- | /O(1)/ Precondition: bytes are pinned. Behavior is undefined otherwise.+-- | Convert a pinned 'Bytes' to a 'ByteString'+-- /O(1)/ Precondition: bytes are pinned. Behavior is undefined otherwise. pinnedToByteString :: Bytes -> ByteString pinnedToByteString (Bytes y@(PM.ByteArray x) off len) = ByteString.PS@@ -171,3 +219,15 @@ (ForeignPtr.PlainPtr (Exts.unsafeCoerce# x)) ) 0 len++-- | /O(n)/ Copy a 'ByteString' to a byte sequence.+fromByteString :: ByteString -> Bytes+fromByteString !b = Bytes+ ( runByteArrayST $ unsafeIOToST $ do + dst@(PM.MutableByteArray dst# ) <- PM.newByteArray len+ ByteString.unsafeUseAsCString b $ \src -> do+ PM.copyPtrToMutablePrimArray (PM.MutablePrimArray dst# ) 0 src len+ PM.unsafeFreezeByteArray dst+ ) 0 len+ where+ !len = ByteString.length b
+ src/Data/Bytes/Text/Ascii.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE TypeApplications #-}++-- | This module treats 'Bytes' data as holding ASCII text. Providing bytes+-- outside the ASCII range (@U+0000@ -- @U+007F@) may cause a failure or+-- unspecified results, but such bytes will never be inspected.+--+-- For functions that can operate on ASCII-compatible encodings, see+-- 'Data.Bytes.Text.AsciiExt'.+module Data.Bytes.Text.Ascii+ ( fromString+ ) where++import Data.Bytes.Types (Bytes)+import Data.Char (ord)+import Data.Word (Word8)++import qualified Data.Bytes.Pure as Bytes+import qualified GHC.Exts as Exts+++-- | Convert a 'String' consisting of only characters in the ASCII block+-- to a byte sequence. Any character with a codepoint above @U+007F@ is+-- replaced by @U+0000@.+fromString :: String -> Bytes+fromString = Bytes.fromByteArray+ . Exts.fromList+ . map (\c -> let i = ord c in if i < 128 then fromIntegral @Int @Word8 i else 0)++-- TODO presumably also fromText and fromShortText
+ src/Data/Bytes/Text/AsciiExt.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}++-- | This module contains functions which operate on supersets of 'Bytes' containing ASCII-encoded text.+-- That is, none of the functions here inspect bytes with a value greater than 127, and do not fail due to the presence of such bytes.++-- For functions that can fail for bytes outside the ASCII range, see+-- 'Data.Bytes.Ascii'. For functions that can inspect bytes outside ASCII, see+-- any of the modules for ASCII-compatible encodings (e.g. 'Data.Bytes.Utf8',+-- 'Data.Bytes.Latin1', and so on).+module Data.Bytes.Text.AsciiExt+ ( -- * Line-Oriented IO+ hFoldLines+ , hForLines_+ -- ** Standard Handles+ , forLines_+ , foldLines+ -- * Text Manipulation+ , toLowerU+ ) where++import Control.Monad.ST (ST)+import Control.Monad.ST.Run (runByteArrayST)+import Data.Bytes.Types (Bytes(..))+import Data.Primitive (ByteArray)+import Data.Word (Word8)+import System.IO (Handle, hIsEOF, stdin)++import qualified Data.Bytes.Pure as Bytes+import qualified Data.ByteString.Char8 as BC8+import qualified Data.Primitive as PM++-- | `hForLines_` over `stdin`+forLines_ :: (Bytes -> IO a) -> IO ()+{-# INLINEABLE forLines_ #-}+forLines_ = hForLines_ stdin++-- | `hFoldLines` over `stdin`+foldLines :: a -> (a -> Bytes -> IO a) -> IO a+{-# INLINEABLE foldLines #-}+foldLines = hFoldLines stdin++-- | Perform an action on each line of the input, discarding results.+-- To maintain a running state, see 'hFoldLines'.+--+-- Lines are extracted with with 'BC8.hGetLine', which does not document its+-- dectection algorithm. As of writing (bytestring v0.11.1.0), lines are+-- delimited by a single @\n@ character (UNIX-style, as all things should be).+hForLines_ :: Handle -> (Bytes -> IO a) -> IO ()+hForLines_ h body = loop+ where+ loop = hIsEOF h >>= \case+ False -> do+ line <- Bytes.fromByteString <$> BC8.hGetLine h+ _ <- body line+ loop+ True -> pure ()++-- | Perform an action on each line of the input, threading state through the computation.+-- If you do not need to keep a state, see `hForLines_`.+--+-- Lines are extracted with with 'BC8.hGetLine', which does not document its+-- dectection algorithm. As of writing (bytestring v0.11.1.0), lines are+-- delimited by a single @\n@ character (UNIX-style, as all things should be).+hFoldLines :: Handle -> a -> (a -> Bytes -> IO a) -> IO a+hFoldLines h z body = loop z+ where+ loop !x = hIsEOF h >>= \case+ False -> do+ line <- Bytes.fromByteString <$> BC8.hGetLine h+ x' <- body x line+ loop x'+ True -> pure x++-- | /O(n)/ Convert ASCII letters to lowercase. This adds @0x20@ to bytes in the+-- range @[0x41,0x5A]@ (@A-Z@ ⇒ @a-z@) and leaves all other bytes alone.+-- Unconditionally copies the bytes.+toLowerU :: Bytes -> ByteArray+toLowerU (Bytes src off0 len0) =+ runByteArrayST action+ where+ action :: forall s. ST s ByteArray+ action = do+ dst <- PM.newByteArray len0+ let go !off !ix !len = if len == 0+ then pure ()+ else do+ let w = PM.indexByteArray src off :: Word8+ w' = if w >= 0x41 && w <= 0x5A+ then w + 32+ else w+ PM.writeByteArray dst ix w'+ go (off + 1) (ix + 1) (len - 1)+ go off0 0 len0+ PM.unsafeFreezeByteArray dst
+ src/Data/Bytes/Text/Latin1.hs view
@@ -0,0 +1,214 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE MagicHash #-}+{-# LANGUAGE TypeApplications #-}++-- | This module treats 'Bytes' data as holding text encoded in ISO-8859-1. This+-- encoding can only encode codepoints strictly below @U+0100@, but this allows+-- each codepoint to be placed directly into a single byte. This range consists+-- of Unicode Basic Latin, Latin-1 Supplement and C0+C1 Controls, which includes+-- ASCII.+--+-- Strictly, ISO-8859-1 is not to be confused with ISO/IEC 8859-1 (which was the+-- default encoding for webpages before HTML5). ISO/IEC 8859-1 lacks encodings+-- for the C0 and C1 control characters. +-- +-- With HTML5, the default encoding of webpages was changed to Windows-1252,+-- which is _not_ compatible with ISO-8859-1. Windows-1252 uses the C1 Control+-- range (@U+0080@ -- @U+009F@) mostly to encode a variety of printable+-- characters. For this encoding, see 'Data.Bytes.Text.Windows1252'.+module Data.Bytes.Text.Latin1+ ( toString+ , fromString+ -- * Specialized Comparisons+ , equals1+ , equals2+ , equals3+ , equals4+ , equals5+ , equals6+ , equals7+ , equals8+ , equals9+ , equals10+ , equals11+ , equals12+ ) where++import Data.Bytes.Types (Bytes(..))+import Data.Char (ord)+import Data.Primitive (ByteArray(ByteArray))+import GHC.Exts (Int(I#),Char(C#),word2Int#,chr#)+import GHC.Word (Word8(W8#))++import qualified Data.Bytes.Pure as Bytes+import qualified GHC.Exts as Exts+++-- | Convert a 'String' consisting of only characters representable+-- by ISO-8859-1. These are encoded with ISO-8859-1. Any character+-- with a codepoint above @U+00FF@ is replaced by an unspecified byte.+fromString :: String -> Bytes+fromString =+ Bytes.fromByteArray . Exts.fromList . map (fromIntegral @Int @Word8 . ord)++-- | Interpret a byte sequence as text encoded by ISO-8859-1.+toString :: Bytes -> String+{-# INLINE toString #-}+toString = Bytes.foldr (\(W8# w) xs -> C# (chr# (word2Int# w)) : xs) []++-- TODO presumably also fromText and fromShortText+++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a singleton whose element matches the character?+equals1 :: Char -> Bytes -> Bool+{-# INLINE equals1 #-}+equals1 !c0 (Bytes arr off len) = case len of+ 1 -> c0 == indexCharArray arr off+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a doubleton whose elements match the characters?+equals2 :: Char -> Char -> Bytes -> Bool+equals2 !c0 !c1 (Bytes arr off len) = case len of+ 2 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a tripleton whose elements match the characters?+equals3 :: Char -> Char -> Char -> Bytes -> Bool+equals3 !c0 !c1 !c2 (Bytes arr off len) = case len of+ 3 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a quadrupleton whose elements match the characters?+equals4 :: Char -> Char -> Char -> Char -> Bytes -> Bool+equals4 !c0 !c1 !c2 !c3 (Bytes arr off len) = case len of+ 4 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a quintupleton whose elements match the characters?+equals5 :: Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equals5 !c0 !c1 !c2 !c3 !c4 (Bytes arr off len) = case len of+ 5 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a sextupleton whose elements match the characters?+equals6 :: Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equals6 !c0 !c1 !c2 !c3 !c4 !c5 (Bytes arr off len) = case len of+ 6 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4) &&+ c5 == indexCharArray arr (off + 5)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a septupleton whose elements match the characters?+equals7 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equals7 !c0 !c1 !c2 !c3 !c4 !c5 !c6 (Bytes arr off len) = case len of+ 7 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4) &&+ c5 == indexCharArray arr (off + 5) &&+ c6 == indexCharArray arr (off + 6)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- an octupleton whose elements match the characters?+equals8 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equals8 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 (Bytes arr off len) = case len of+ 8 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4) &&+ c5 == indexCharArray arr (off + 5) &&+ c6 == indexCharArray arr (off + 6) &&+ c7 == indexCharArray arr (off + 7)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a 9-tuple whose elements match the characters?+equals9 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equals9 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 (Bytes arr off len) = case len of+ 9 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4) &&+ c5 == indexCharArray arr (off + 5) &&+ c6 == indexCharArray arr (off + 6) &&+ c7 == indexCharArray arr (off + 7) &&+ c8 == indexCharArray arr (off + 8)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a 10-tuple whose elements match the characters?+equals10 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equals10 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 !c9 (Bytes arr off len) = case len of+ 10 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4) &&+ c5 == indexCharArray arr (off + 5) &&+ c6 == indexCharArray arr (off + 6) &&+ c7 == indexCharArray arr (off + 7) &&+ c8 == indexCharArray arr (off + 8) &&+ c9 == indexCharArray arr (off + 9)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a 11-tuple whose elements match the characters?+equals11 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equals11 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 !c9 !c10 (Bytes arr off len) = case len of+ 11 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4) &&+ c5 == indexCharArray arr (off + 5) &&+ c6 == indexCharArray arr (off + 6) &&+ c7 == indexCharArray arr (off + 7) &&+ c8 == indexCharArray arr (off + 8) &&+ c9 == indexCharArray arr (off + 9) &&+ c10 == indexCharArray arr (off + 10)+ _ -> False++-- | Is the byte sequence, when interpreted as ISO-8859-1-encoded text,+-- a 12-tuple whose elements match the characters?+equals12 :: Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Char -> Bytes -> Bool+equals12 !c0 !c1 !c2 !c3 !c4 !c5 !c6 !c7 !c8 !c9 !c10 !c11 (Bytes arr off len) = case len of+ 12 -> c0 == indexCharArray arr off &&+ c1 == indexCharArray arr (off + 1) &&+ c2 == indexCharArray arr (off + 2) &&+ c3 == indexCharArray arr (off + 3) &&+ c4 == indexCharArray arr (off + 4) &&+ c5 == indexCharArray arr (off + 5) &&+ c6 == indexCharArray arr (off + 6) &&+ c7 == indexCharArray arr (off + 7) &&+ c8 == indexCharArray arr (off + 8) &&+ c9 == indexCharArray arr (off + 9) &&+ c10 == indexCharArray arr (off + 10) &&+ c11 == indexCharArray arr (off + 11)+ _ -> False++indexCharArray :: ByteArray -> Int -> Char+indexCharArray (ByteArray arr) (I# off) = C# (Exts.indexCharArray# arr off)
+ src/Data/Bytes/Text/Utf8.hs view
@@ -0,0 +1,3 @@+-- | Placeholder module in case there is demand for treating 'Bytes' as+-- UTF8-encoded text+module Data.Bytes.Text.Utf8 () where
+ src/Data/Bytes/Text/Windows1252.hs view
@@ -0,0 +1,3 @@+-- | Placeholder module in case there is demand for treating 'Bytes' as+-- Windows-1252-encoded text+module Data.Bytes.Text.Windows1252 () where
src/Data/Bytes/Types.hs view
@@ -1,4 +1,5 @@ {-# language BangPatterns #-}+{-# language DataKinds #-} {-# language MagicHash #-} {-# language TypeFamilies #-} {-# language DuplicateRecordFields #-}@@ -8,31 +9,31 @@ , Bytes#(..) , MutableBytes(..) , UnmanagedBytes(..)+ , BytesN(..)+ , ByteArrayN(..) ) where -import Control.Monad.ST (runST)-import Control.Monad.ST.Run (runByteArrayST)-import Data.Bits ((.&.),unsafeShiftR)-import Data.Char (ord)+import Data.Bytes.Internal (Bytes(..)) import Data.Primitive (ByteArray(..),MutableByteArray(..)) import Data.Primitive.Addr (Addr)-import Data.Word (Word8)-import GHC.Base (unsafeChr)-import GHC.Exts (Int(I#),unsafeCoerce#,sameMutableByteArray#)-import GHC.Exts (isTrue#,compareByteArrays#,IsList(..))+import GHC.TypeNats (Nat) import UnliftedBytes (Bytes#(..)) -import qualified Data.List as L-import qualified Data.Foldable as F-import qualified Data.Primitive as PM---- | A slice of a 'ByteArray'.-data Bytes = Bytes+-- | A slice of a 'ByteArray' whose compile-time-known length is represented+-- by a phantom type variable. Consumers of this data constructor must be+-- careful to preserve the expected invariant.+data BytesN (n :: Nat) = BytesN { array :: {-# UNPACK #-} !ByteArray , offset :: {-# UNPACK #-} !Int- , length :: {-# UNPACK #-} !Int } +-- | A 'ByteArray' whose compile-time-known length is represented+-- by a phantom type variable. Consumers of this data constructor must be+-- careful to preserve the expected invariant.+newtype ByteArrayN (n :: Nat) = ByteArrayN+ { array :: ByteArray+ }+ -- | A slice of a 'MutableByteArray'. data MutableBytes s = MutableBytes { array :: {-# UNPACK #-} !(MutableByteArray s)@@ -45,83 +46,3 @@ { address :: {-# UNPACK #-} !Addr , length :: {-# UNPACK #-} !Int }--instance IsList Bytes where- type Item Bytes = Word8- fromListN n xs = Bytes (fromListN n xs) 0 n- fromList xs = fromListN (L.length xs) xs- toList (Bytes arr off len) = toListLoop off len arr--toListLoop :: Int -> Int -> ByteArray -> [Word8]-toListLoop !off !len !arr = if len > 0- then PM.indexByteArray arr off : toListLoop (off + 1) (len - 1) arr- else []--instance Show Bytes where- showsPrec _ (Bytes arr off len) s = if len == 0- then showString "[]" s- else showString "[0x"- $ showHexDigits (PM.indexByteArray arr off)- $ showLoop (off + 1) (len - 1) arr- $ showChar ']'- $ s--showLoop :: Int -> Int -> ByteArray -> String -> String-showLoop !ix !len !arr s = if len > 0- then ',':'0':'x':showHexDigits (PM.indexByteArray arr ix) (showLoop (ix + 1) (len - 1) arr s)- else s--showHexDigits :: Word8 -> String -> String-showHexDigits !w s = word4ToChar (unsafeShiftR w 4) : word4ToChar (0x0F .&. w) : s--word4ToChar :: Word8 -> Char-word4ToChar w = if w < 10- then unsafeChr (ord '0' + fromIntegral w)- else unsafeChr (ord 'a' + (fromIntegral w) - 10)--instance Eq Bytes where- Bytes arr1 off1 len1 == Bytes arr2 off2 len2- | len1 /= len2 = False- | sameByteArray arr1 arr2 && off1 == off2 = True- | otherwise = compareByteArrays arr1 off1 arr2 off2 len1 == EQ--instance Ord Bytes where- compare (Bytes arr1 off1 len1) (Bytes arr2 off2 len2)- | sameByteArray arr1 arr2 && off1 == off2 && len1 == len2 = EQ- | otherwise = compareByteArrays arr1 off1 arr2 off2 (min len1 len2) <> compare len1 len2--instance Semigroup Bytes where- -- TODO: Do the trick to move the data constructor to the outside- -- of runST.- Bytes arrA offA lenA <> Bytes arrB offB lenB = runST $ do- marr <- PM.newByteArray (lenA + lenB)- PM.copyByteArray marr 0 arrA offA lenA- PM.copyByteArray marr lenA arrB offB lenB- r <- PM.unsafeFreezeByteArray marr- pure (Bytes r 0 (lenA + lenB))--instance Monoid Bytes where- mempty = Bytes mempty 0 0- mconcat [] = mempty- mconcat [x] = x- mconcat bs = Bytes r 0 fullLen- where- !fullLen = L.foldl' (\acc (Bytes _ _ len) -> acc + len) 0 bs- r = runByteArrayST $ do- marr <- PM.newByteArray fullLen- !_ <- F.foldlM- (\ !currLen (Bytes arr off len) -> do- PM.copyByteArray marr currLen arr off len- pure (currLen + len)- ) 0 bs- PM.unsafeFreezeByteArray marr--compareByteArrays :: ByteArray -> Int -> ByteArray -> Int -> Int -> Ordering-{-# INLINE compareByteArrays #-}-compareByteArrays (ByteArray ba1#) (I# off1#) (ByteArray ba2#) (I# off2#) (I# n#) =- compare (I# (compareByteArrays# ba1# off1# ba2# off2# n#)) 0--sameByteArray :: ByteArray -> ByteArray -> Bool-{-# INLINE sameByteArray #-}-sameByteArray (ByteArray ba1#) (ByteArray ba2#) =- isTrue# (sameMutableByteArray# (unsafeCoerce# ba1#) (unsafeCoerce# ba2#))
test/Main.hs view
@@ -20,6 +20,8 @@ import qualified Data.ByteString as ByteString import qualified Data.Bytes as Bytes+import qualified Data.Bytes.Text.Ascii as Ascii+import qualified Data.Bytes.Text.AsciiExt as AsciiExt import qualified Data.Bytes.Chunks as Chunks import qualified Data.Foldable as Foldable import qualified Data.List as List@@ -44,7 +46,7 @@ ] , testGroup "isPrefixOf" [ testCase "A" $- Bytes.toLowerAsciiByteArrayClone (bytes "FooBar")+ AsciiExt.toLowerU (bytes "FooBar") @=? pack "foobar" , testCase "B" $ THU.assertBool "" $@@ -56,13 +58,31 @@ , testCase "B" $ THU.assertBool "" $ not (Bytes.isSuffixOf (bytes "h") (bytes "hey man")) ]+ , testGroup "isInfixOf"+ [ testCase "small pattern A" $ THU.assertBool "" $+ Bytes.isInfixOf (bytes "y m") (bytes "hey man")+ , testCase "small pattern B" $ THU.assertBool "" $+ Bytes.isInfixOf (bytes "h") (bytes "hey man")+ , testCase "small pattern C" $ THU.assertBool "" $+ Bytes.isInfixOf (bytes "an") (bytes "hey man")+ , testCase "small pattern D" $ THU.assertBool "" $+ not (Bytes.isInfixOf (bytes "Y M") (bytes "hey man"))+ , testCase "large pattern A" $ THU.assertBool "" $+ Bytes.isInfixOf (bytes "Hello, hello!") (bytes "I say hello. Hello, hello! I don't know why you say goodbye, I say hello!")+ , testCase "large pattern D" $ THU.assertBool "" $+ not (Bytes.isInfixOf (bytes "Hello, hello!") (bytes "I don't know why you say goodbye, I say hello!"))+ , testCase "edge: empty pattern" $ THU.assertBool "" $+ Bytes.isInfixOf (bytes "") (bytes "hello hello!")+ , testCase "edge: empty string" $ THU.assertBool "" $+ not (Bytes.isInfixOf (bytes "hello hello!") (bytes ""))+ ] , testGroup "stripOptionalSuffix" [ testCase "A" $- Bytes.fromAsciiString "hey m"+ Ascii.fromString "hey m" @=? Bytes.stripOptionalSuffix (bytes "an") (bytes "hey man") , testCase "B" $- Bytes.fromAsciiString "hey man"+ Ascii.fromString "hey man" @=? Bytes.stripOptionalSuffix (bytes "h") (bytes "hey man") ]@@ -78,17 +98,17 @@ ] , testGroup "dropWhileEnd" [ testCase "A" $- Bytes.fromAsciiString "aabbcc"+ Ascii.fromString "aabbcc" @=? Bytes.dropWhileEnd (== c2w 'b') (bytes "aabbccbb") ] , testGroup "takeWhileEnd" [ testCase "A" $- Bytes.fromAsciiString "bb"+ Ascii.fromString "bb" @=? Bytes.takeWhileEnd (== c2w 'b') (bytes "aabbccbb") , testCase "B" $- Bytes.fromAsciiString ""+ Ascii.fromString "" @=? Bytes.takeWhileEnd (/= c2w '\n') (bytes "aabbccbb\n") , testCase "C" $@@ -147,13 +167,13 @@ === map Bytes.fromByteArray (Exts.toList (Bytes.splitU x (slicedPack xs))) , testCase "splitInit-A" $- [Bytes.fromAsciiString "hello", Bytes.fromAsciiString "world"]+ [Ascii.fromString "hello", Ascii.fromString "world"] @=?- (Bytes.splitInit 0x0A (Bytes.fromAsciiString "hello\nworld\n"))+ (Bytes.splitInit 0x0A (Ascii.fromString "hello\nworld\n")) , testCase "splitInit-B" $- [Bytes.fromAsciiString "hello", Bytes.fromAsciiString "world"]+ [Ascii.fromString "hello", Ascii.fromString "world"] @=?- (Bytes.splitInit 0x0A (Bytes.fromAsciiString "hello\nworld\nthere"))+ (Bytes.splitInit 0x0A (Ascii.fromString "hello\nworld\nthere")) , testProperty "splitEnd1" $ \(x :: Word8) (xs :: [Word8]) -> case ByteString.split x (ByteString.pack xs) of [] -> Bytes.splitEnd1 x (slicedPack xs) === Nothing