Compare commits

..

No commits in common. "9269caacb7e9651bde2ac5439039125ddb665953" and "0fcd93a9aae5f16de6aa55fd6ca911c0f9cf2536" have entirely different histories.

26 changed files with 1959 additions and 2518 deletions

1
.github/CODEOWNERS vendored
View file

@ -1 +0,0 @@
@byteverse/l3c

View file

@ -1,11 +0,0 @@
name: build
on:
pull_request:
branches:
- "*"
jobs:
call-workflow:
uses: byteverse/.github/.github/workflows/build-matrix.yaml@main
with:
cabal-file: bytebuild.cabal

View file

@ -1,10 +0,0 @@
name: release
on:
push:
tags:
- "*"
jobs:
call-workflow:
uses: byteverse/.github/.github/workflows/release.yaml@main
secrets: inherit

2
.gitignore vendored
View file

@ -1,4 +1,3 @@
.vscode/
dist dist
dist-* dist-*
cabal-dev cabal-dev
@ -12,7 +11,6 @@ cabal-dev
.hsenv .hsenv
.cabal-sandbox/ .cabal-sandbox/
cabal.sandbox.config cabal.sandbox.config
cabal.project.local
*.prof *.prof
*.aux *.aux
*.hp *.hp

View file

@ -5,28 +5,10 @@ Note: Prior to version 0.3.4.0, this library was named
`small-bytearray-builder` is now just a compatibility shim `small-bytearray-builder` is now just a compatibility shim
to ease the migration process. to ease the migration process.
## 0.3.16.2 -- 2024-02-06 ## 0.3.15.0 -- 2023-??-??
* Restore import statement for `liftA2` to fix build for GHC 9.4.
## 0.3.16.1 -- 2024-02-02
* Remove all CPP
* Drop support for GHC < 9.4
* Drop support for text < 2.0
## 0.3.16.0 -- 2024-01-29
* Add `wordPaddedDec3`.
* Add `Data.Bytes.Builder.Avro`.
* Add `word16LEB128`.
* Stop accepting versions of text lower than 2.0.
## 0.3.15.0 -- 2024-01-05
* Add `Data.Bytes.Builder.Unsafe.pasteUtf8TextJson#` for users who need * Add `Data.Bytes.Builder.Unsafe.pasteUtf8TextJson#` for users who need
to perform JSON string encoding without using a builder. to perform JSON string encoding without using a builder.
* Add `Data.Bytes.Builder.textJsonString` when building with text 2.0+
## 0.3.14.0 -- 2023-07-20 ## 0.3.14.0 -- 2023-07-20

2
Setup.hs Normal file
View file

@ -0,0 +1,2 @@
import Distribution.Simple
main = defaultMain

View file

@ -1,14 +1,14 @@
{-# LANGUAGE OverloadedLists #-} {-# language OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-} {-# language OverloadedStrings #-}
module Cell module Cell
( Cell (..) ( Cell(..)
, cells , cells
) where ) where
import Data.Primitive (SmallArray)
import Data.Text.Short (ShortText)
import Data.Word (Word32) import Data.Word (Word32)
import Data.Text.Short (ShortText)
import Data.Primitive (SmallArray)
-- A cell in a CSV file -- A cell in a CSV file
data Cell data Cell
@ -18,14 +18,15 @@ data Cell
-- Some sample data to encode as a CSV -- Some sample data to encode as a CSV
cells :: SmallArray (SmallArray Cell) cells :: SmallArray (SmallArray Cell)
cells = cells =
[ [CellString "Randy", CellString "Gutiérrez", CellNumber 41, CellNumber 343] [ [ CellString "Randy", CellString "Gutiérrez", CellNumber 41, CellNumber 343 ]
, [CellString "Édith", CellString "Piaf", CellNumber 63, CellNumber 453] , [ CellString "Édith", CellString "Piaf", CellNumber 63, CellNumber 453 ]
, [CellString "Martha", CellString "Washington", CellNumber 51, CellNumber 634] , [ CellString "Martha", CellString "Washington", CellNumber 51, CellNumber 634 ]
, [CellString "Julius", CellString "Caesar", CellNumber 1, CellNumber 6922] , [ CellString "Julius", CellString "Caesar", CellNumber 1, CellNumber 6922 ]
, [CellString "Robert", CellString "Redford", CellNumber 24, CellNumber 617] , [ CellString "Robert", CellString "Redford", CellNumber 24, CellNumber 617 ]
, [CellString "Violet", CellString "Crawley", CellNumber 71, CellNumber 150] , [ CellString "Violet", CellString "Crawley", CellNumber 71, CellNumber 150 ]
, [CellString "Lázaro", CellString "Cárdenas", CellNumber 58, CellNumber 299] , [ CellString "Lázaro", CellString "Cárdenas", CellNumber 58, CellNumber 299 ]
, [CellString "Anastasia", CellString "San Martin", CellNumber 103, CellNumber 3214] , [ CellString "Anastasia", CellString "San Martin", CellNumber 103, CellNumber 3214 ]
, [CellString "Mad", CellString "Max", CellNumber 37, CellNumber 918] , [ CellString "Mad", CellString "Max", CellNumber 37, CellNumber 918 ]
, [CellString "Sidonie-Gabrielle", CellString "Collette", CellNumber 25, CellNumber 904] , [ CellString "Sidonie-Gabrielle", CellString "Collette", CellNumber 25, CellNumber 904 ]
] ]

View file

@ -1,8 +1,9 @@
{-# LANGUAGE OverloadedStrings #-} {-# language LambdaCase #-}
{-# language OverloadedStrings #-}
import Data.Primitive (ByteArray) import Data.Primitive (ByteArray)
import Data.Word (Word64) import Data.Word (Word64)
import Gauge (bench, bgroup, whnf) import Gauge (bgroup,bench,whnf)
import Gauge.Main (defaultMain) import Gauge.Main (defaultMain)
import qualified Arithmetic.Nat as Nat import qualified Arithmetic.Nat as Nat
@ -10,45 +11,36 @@ import qualified Data.Bytes.Builder as B
import qualified Data.Bytes.Builder.Bounded as U import qualified Data.Bytes.Builder.Bounded as U
import qualified Cell import qualified Cell
import qualified HexWord64
import qualified SimpleCsv import qualified SimpleCsv
import qualified HexWord64
import qualified Word16Tree import qualified Word16Tree
main :: IO () main :: IO ()
main = main = defaultMain
defaultMain [ bgroup "w64"
[ bgroup [ bgroup "hex"
"w64"
[ bgroup
"hex"
[ bench "library" (whnf encodeHexWord64s w64s) [ bench "library" (whnf encodeHexWord64s w64s)
, bench "loop" (whnf encodeHexWord64sLoop w64s) , bench "loop" (whnf encodeHexWord64sLoop w64s)
] ]
] ]
, bgroup , bgroup "unbounded"
"unbounded" [ bench "csv-no-escape" $ whnf
[ bench "csv-no-escape" $
whnf
(\x -> B.run 4080 (SimpleCsv.encodeRows x)) (\x -> B.run 4080 (SimpleCsv.encodeRows x))
Cell.cells Cell.cells
, bench "word-16-tree-small" $ , bench "word-16-tree-small" $ whnf
whnf
(\x -> B.run 4080 (Word16Tree.encode x)) (\x -> B.run 4080 (Word16Tree.encode x))
Word16Tree.exampleSmall Word16Tree.exampleSmall
, bench "word-16-tree-2000" $ , bench "word-16-tree-2000" $ whnf
whnf
(\x -> B.run ((4096 * 16) - 16) (Word16Tree.encode x)) (\x -> B.run ((4096 * 16) - 16) (Word16Tree.encode x))
Word16Tree.example2000 Word16Tree.example2000
, bench "word-16-tree-9000" $ , bench "word-16-tree-9000" $ whnf
whnf
(\x -> B.run ((4096 * 64) - 16) (Word16Tree.encode x)) (\x -> B.run ((4096 * 64) - 16) (Word16Tree.encode x))
Word16Tree.example9000 Word16Tree.example9000
] ]
] ]
w64s :: Word64s w64s :: Word64s
w64s = w64s = Word64s
Word64s
0xde2b8a480cf77113 0xde2b8a480cf77113
0x48f1668ca2a68b45 0x48f1668ca2a68b45
0xd262fbaa0b2f473c 0xd262fbaa0b2f473c
@ -58,39 +50,31 @@ w64s =
0xd451eca11d9873ad 0xd451eca11d9873ad
0xbd927e8d4c879d02 0xbd927e8d4c879d02
data Word64s data Word64s = Word64s
= Word64s !Word64 !Word64 !Word64 !Word64
!Word64 !Word64 !Word64 !Word64 !Word64
!Word64
!Word64
!Word64
!Word64
!Word64
!Word64
!Word64
encodeHexWord64s :: Word64s -> ByteArray encodeHexWord64s :: Word64s -> ByteArray
{-# NOINLINE encodeHexWord64s #-} {-# noinline encodeHexWord64s #-}
encodeHexWord64s (Word64s a b c d e f g h) = encodeHexWord64s (Word64s a b c d e f g h) = U.run Nat.constant $
U.run Nat.constant $ U.word64PaddedUpperHex a `U.append`
U.word64PaddedUpperHex a U.word64PaddedUpperHex b `U.append`
`U.append` U.word64PaddedUpperHex b U.word64PaddedUpperHex c `U.append`
`U.append` U.word64PaddedUpperHex c U.word64PaddedUpperHex d `U.append`
`U.append` U.word64PaddedUpperHex d U.word64PaddedUpperHex e `U.append`
`U.append` U.word64PaddedUpperHex e U.word64PaddedUpperHex f `U.append`
`U.append` U.word64PaddedUpperHex f U.word64PaddedUpperHex g `U.append`
`U.append` U.word64PaddedUpperHex g U.word64PaddedUpperHex h
`U.append` U.word64PaddedUpperHex h
encodeHexWord64sLoop :: Word64s -> ByteArray encodeHexWord64sLoop :: Word64s -> ByteArray
{-# NOINLINE encodeHexWord64sLoop #-} {-# noinline encodeHexWord64sLoop #-}
encodeHexWord64sLoop (Word64s a b c d e f g h) = encodeHexWord64sLoop (Word64s a b c d e f g h) = U.run Nat.constant $
U.run Nat.constant $ HexWord64.word64PaddedUpperHex a `U.append`
HexWord64.word64PaddedUpperHex a HexWord64.word64PaddedUpperHex b `U.append`
`U.append` HexWord64.word64PaddedUpperHex b HexWord64.word64PaddedUpperHex c `U.append`
`U.append` HexWord64.word64PaddedUpperHex c HexWord64.word64PaddedUpperHex d `U.append`
`U.append` HexWord64.word64PaddedUpperHex d HexWord64.word64PaddedUpperHex e `U.append`
`U.append` HexWord64.word64PaddedUpperHex e HexWord64.word64PaddedUpperHex f `U.append`
`U.append` HexWord64.word64PaddedUpperHex f HexWord64.word64PaddedUpperHex g `U.append`
`U.append` HexWord64.word64PaddedUpperHex g HexWord64.word64PaddedUpperHex h
`U.append` HexWord64.word64PaddedUpperHex h

View file

@ -1,4 +1,4 @@
{-# LANGUAGE LambdaCase #-} {-# language LambdaCase #-}
-- A variant of CSV encoding that does not perform -- A variant of CSV encoding that does not perform
-- any escaping or quoting. This is in its own module -- any escaping or quoting. This is in its own module
@ -8,21 +8,19 @@ module SimpleCsv
( encodeRows ( encodeRows
) where ) where
import Cell (Cell (..)) import Cell (Cell(..))
import Data.Primitive (SmallArray) import Data.Primitive (SmallArray)
import qualified Data.Bytes.Builder as B
import qualified Data.Foldable as F import qualified Data.Foldable as F
import qualified Data.Bytes.Builder as B
encodeRows :: SmallArray (SmallArray Cell) -> B.Builder encodeRows :: SmallArray (SmallArray Cell) -> B.Builder
encodeRows = encodeRows = F.foldr
F.foldr
(\r x -> encodeSimpleCsvRow r (B.ascii '\n' <> x)) (\r x -> encodeSimpleCsvRow r (B.ascii '\n' <> x))
mempty mempty
encodeSimpleCsvRow :: SmallArray Cell -> B.Builder -> B.Builder encodeSimpleCsvRow :: SmallArray Cell -> B.Builder -> B.Builder
encodeSimpleCsvRow cs b = encodeSimpleCsvRow cs b = F.foldr
F.foldr
(\c x -> encodeSimpleCsvCell c <> B.ascii ',' <> x) (\c x -> encodeSimpleCsvCell c <> B.ascii ',' <> x)
b b
cs cs

View file

@ -1,6 +1,6 @@
cabal-version: 2.2 cabal-version: 2.2
name: bytebuild name: bytebuild
version: 0.3.16.2 version: 0.3.15.0
synopsis: Build byte arrays synopsis: Build byte arrays
description: description:
This is similar to the builder facilities provided by This is similar to the builder facilities provided by
@ -24,20 +24,10 @@ bug-reports: https://github.com/byteverse/bytebuild/issues
license: BSD-3-Clause license: BSD-3-Clause
license-file: LICENSE license-file: LICENSE
author: Andrew Martin author: Andrew Martin
maintainer: amartin@layer3com.com maintainer: andrew.thaddeus@gmail.com
copyright: 2019 Andrew Martin copyright: 2019 Andrew Martin
category: Data category: Data
extra-doc-files: CHANGELOG.md extra-source-files: CHANGELOG.md
tested-with: GHC ==9.4.8 || ==9.6.5 || ==9.8.2 || ==9.10.1
common build-settings
default-language: Haskell2010
ghc-options: -Wall -Wunused-packages
flag quoter
manual: True
description: Include the quasiquoter
default: True
flag checked flag checked
manual: True manual: True
@ -45,110 +35,93 @@ flag checked
default: False default: False
library library
import: build-settings
exposed-modules: exposed-modules:
Data.Bytes.Builder Data.Bytes.Builder
Data.Bytes.Builder.Avro Data.Bytes.Builder.Class
Data.Bytes.Builder.Template
Data.Bytes.Builder.Unsafe
Data.Bytes.Builder.Bounded Data.Bytes.Builder.Bounded
Data.Bytes.Builder.Bounded.Class Data.Bytes.Builder.Bounded.Class
Data.Bytes.Builder.Bounded.Unsafe Data.Bytes.Builder.Bounded.Unsafe
Data.Bytes.Builder.Class
Data.Bytes.Builder.Unsafe
other-modules: other-modules:
Compat Compat
Op Op
reexported-modules:
reexported-modules: Data.Bytes.Chunks Data.Bytes.Chunks
build-depends: build-depends:
, base >=4.17.0.0 && <4.21 , base >=4.12.0.0 && <4.19
, byteslice >=0.2.6 && <0.3 , byteslice >=0.2.6 && <0.3
, bytestring >=0.10.8.2 && <0.13 , bytestring >=0.10.8.2 && <0.12
, haskell-src-meta >=0.8
, integer-logarithms >=1.0.3 && <1.1 , integer-logarithms >=1.0.3 && <1.1
, natural-arithmetic >=0.1 && <0.3 , natural-arithmetic >=0.1 && <0.3
, primitive-offset >=0.2 && <0.3 , primitive-offset >=0.2 && <0.3
, run-st >=0.1.2 && <0.2 , run-st >=0.1.2 && <0.2
, text >=2.0 && <2.2 , template-haskell >=2.16
, text >=1.2 && <2.2
, text-short >=0.1.3 && <0.2 , text-short >=0.1.3 && <0.2
, wide-word >=0.1.0.9 && <0.2 , wide-word >=0.1.0.9 && <0.2
, zigzag , zigzag
if impl(ghc >= 9.2)
if flag(quoter)
build-depends:
haskell-src-meta >=0.8.13
, template-haskell >=2.16
exposed-modules: Data.Bytes.Builder.Template
if impl(ghc >=9.2)
hs-source-dirs: src-9.2 hs-source-dirs: src-9.2
else else
if impl(ghc >=8.10) if impl(ghc >= 8.10)
hs-source-dirs: src-9.0 hs-source-dirs: src-9.0
if flag(checked) if flag(checked)
build-depends: primitive-checked >=0.7 && <0.10 build-depends: primitive-checked >= 0.7 && <0.10
hs-source-dirs: src-checked hs-source-dirs: src-checked
else else
build-depends: primitive >=0.7 && <0.10 build-depends: primitive >= 0.7 && <0.10
hs-source-dirs: src-unchecked hs-source-dirs: src-unchecked
ghc-options: -Wall -O2
ghc-options: -O2
hs-source-dirs: src hs-source-dirs: src
default-language: Haskell2010
c-sources: cbits/bytebuild_custom.c c-sources: cbits/bytebuild_custom.c
test-suite test test-suite test
import: build-settings default-language: Haskell2010
type: exitcode-stdio-1.0 type: exitcode-stdio-1.0
hs-source-dirs: test common hs-source-dirs: test, common
main-is: Main.hs main-is: Main.hs
ghc-options: -O2 -Wall
other-modules: other-modules:
HexWord64 HexWord64
Word16Tree Word16Tree
build-depends: build-depends:
, QuickCheck >=2.13.1 && <2.15
, base >=4.12.0.0 && <5 , base >=4.12.0.0 && <5
, bytebuild , bytebuild
, byteslice , byteslice
, bytestring , bytestring
, natural-arithmetic , natural-arithmetic
, primitive , primitive
, QuickCheck >=2.13.1 && <2.16 , primitive-unlifted >=0.1.2
, quickcheck-classes >=0.6.4
, quickcheck-instances >=0.3.22 , quickcheck-instances >=0.3.22
, text-short
, tasty >=1.2.3 && <1.6 , tasty >=1.2.3 && <1.6
, tasty-hunit >=0.10.0.2 && <0.11 , tasty-hunit >=0.10.0.2 && <0.11
, tasty-quickcheck >=0.10.1 && <0.11 , tasty-quickcheck >=0.10.1 && <0.11
, text >=2.0 && <2.2 , text >=1.2 && <2.2
, vector
, wide-word >=0.1.0.9 && <0.2 , wide-word >=0.1.0.9 && <0.2
default-extensions: CPP
if flag(quoter)
cpp-options: -DQUOTER
build-depends: text-short
benchmark bench benchmark bench
import: build-settings
type: exitcode-stdio-1.0 type: exitcode-stdio-1.0
build-depends: build-depends:
, base , base
, bytebuild , bytebuild
, byteslice , gauge >= 0.2.4
, gauge >=0.2.4
, natural-arithmetic , natural-arithmetic
, primitive , primitive
, text-short , text-short
, byteslice
ghc-options: -O2 ghc-options: -Wall -O2
hs-source-dirs: bench common default-language: Haskell2010
hs-source-dirs: bench, common
main-is: Main.hs main-is: Main.hs
other-modules: other-modules:
Cell Cell
HexWord64 HexWord64
SimpleCsv SimpleCsv
Word16Tree Word16Tree
source-repository head
type: git
location: git://github.com/byteverse/bytebuild.git

View file

@ -1,10 +1,10 @@
{-# LANGUAGE BangPatterns #-} {-# language BangPatterns #-}
{-# LANGUAGE DataKinds #-} {-# language ScopedTypeVariables #-}
{-# LANGUAGE MagicHash #-} {-# language DataKinds #-}
{-# LANGUAGE PolyKinds #-} {-# language UnboxedTuples #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# language MagicHash #-}
{-# LANGUAGE TypeApplications #-} {-# language PolyKinds #-}
{-# LANGUAGE UnboxedTuples #-} {-# language TypeApplications #-}
module HexWord64 module HexWord64
( word64PaddedUpperHex ( word64PaddedUpperHex
@ -15,35 +15,32 @@ module HexWord64
-- the hoop jumping, the explicit loop used here is still outperformed -- the hoop jumping, the explicit loop used here is still outperformed
-- by just inlining the loop. -- by just inlining the loop.
import GHC.ST (ST(ST))
import Data.Bits import Data.Bits
import Data.Bytes.Builder.Bounded.Unsafe (Builder, construct) import Data.Bytes.Builder.Bounded.Unsafe (Builder,construct)
import Data.Primitive import Data.Primitive
import Data.Word import Data.Word
import GHC.Exts import GHC.Exts
import GHC.ST (ST (ST))
import qualified Control.Monad.Primitive as PM import qualified Control.Monad.Primitive as PM
type ST# s (a :: TYPE (r :: RuntimeRep)) = State# s -> (# State# s, a #) type ST# s (a :: TYPE (r :: RuntimeRep)) = State# s -> (# State# s, a #)
word64PaddedUpperHex :: Word64 -> Builder 16 word64PaddedUpperHex :: Word64 -> Builder 16
word64PaddedUpperHex w = construct $ \a b -> word64PaddedUpperHex w = construct $ \a b -> ST
ST (\s0 -> case word64PaddedUpperHexLoop w 60 a b s0 of
( \s0 -> case word64PaddedUpperHexLoop w 60 a b s0 of
(# s1, i #) -> (# s1, I# i #) (# s1, i #) -> (# s1, I# i #)
) )
word64PaddedUpperHexLoop :: forall s. Word64 -> Int -> MutableByteArray s -> Int -> ST# s Int# word64PaddedUpperHexLoop :: forall s. Word64 -> Int -> MutableByteArray s -> Int -> ST# s Int#
word64PaddedUpperHexLoop !w !shiftAmount !arr !i@(I# i#) s0 = word64PaddedUpperHexLoop !w !shiftAmount !arr !i@(I# i#) s0 = if shiftAmount >= 0
if shiftAmount >= 0
then case PM.internal @(ST s) (writeByteArray arr i (toHexUpper (unsafeShiftR w shiftAmount))) s0 of then case PM.internal @(ST s) (writeByteArray arr i (toHexUpper (unsafeShiftR w shiftAmount))) s0 of
(# s1, (_ :: ()) #) -> word64PaddedUpperHexLoop w (shiftAmount - 4) arr (i + 1) s1 (# s1, (_ :: ()) #) -> word64PaddedUpperHexLoop w (shiftAmount - 4) arr (i + 1) s1
else (# s0, i# #) else (# s0, i# #)
toHexUpper :: Word64 -> Word8 toHexUpper :: Word64 -> Word8
toHexUpper w' = toHexUpper w' = fromIntegral
fromIntegral $ $ (complement theMask .&. loSolved)
(complement theMask .&. loSolved)
.|. (theMask .&. hiSolved) .|. (theMask .&. hiSolved)
where where
w = w' .&. 0xF w = w' .&. 0xF

View file

@ -1,4 +1,4 @@
{-# LANGUAGE BangPatterns #-} {-# language BangPatterns #-}
module Word16Tree module Word16Tree
( Word16Tree ( Word16Tree
@ -9,11 +9,11 @@ module Word16Tree
, expectedSmall , expectedSmall
) where ) where
import qualified Data.Bytes as Bytes
import Data.Bytes.Builder as B import Data.Bytes.Builder as B
import qualified Data.Bytes.Text.Ascii
import Data.Primitive (ByteArray)
import Data.Word (Word16) import Data.Word (Word16)
import Data.Primitive (ByteArray)
import qualified Data.Bytes as Bytes
import qualified Data.Bytes.Text.Ascii
data Word16Tree data Word16Tree
= Branch !Word16Tree !Word16Tree = Branch !Word16Tree !Word16Tree
@ -23,40 +23,42 @@ encode :: Word16Tree -> Builder
encode (Leaf w) = B.word16PaddedUpperHex w encode (Leaf w) = B.word16PaddedUpperHex w
encode (Branch a b) = encode (Branch a b) =
B.ascii '(' B.ascii '('
<> encode a <>
<> B.ascii ',' encode a
<> encode b <>
<> B.ascii ')' B.ascii ','
<>
encode b
<>
B.ascii ')'
expectedSmall :: ByteArray expectedSmall :: ByteArray
expectedSmall = expectedSmall = Bytes.toByteArray $ Data.Bytes.Text.Ascii.fromString
Bytes.toByteArray $
Data.Bytes.Text.Ascii.fromString
"((AB59,(1F33,2E71)),((((FA9A,247B),890C),(0F13,((55BF,7CF1),389B))),1205))" "((AB59,(1F33,2E71)),((((FA9A,247B),890C),(0F13,((55BF,7CF1),389B))),1205))"
exampleSmall :: Word16Tree exampleSmall :: Word16Tree
exampleSmall = exampleSmall = Branch
Branch (Branch
( Branch
(Leaf 0xAB59) (Leaf 0xAB59)
( Branch (Branch
(Leaf 0x1F33) (Leaf 0x1F33)
(Leaf 0x2E71) (Leaf 0x2E71)
) )
) )
( Branch (Branch
( Branch (Branch
( Branch (Branch
( Branch (Branch
(Leaf 0xFA9A) (Leaf 0xFA9A)
(Leaf 0x247B) (Leaf 0x247B)
) )
(Leaf 0x890C) (Leaf 0x890C)
) )
( Branch (Branch
(Leaf 0x0F13) (Leaf 0x0F13)
( Branch (Branch
( Branch (Branch
(Leaf 0x55BF) (Leaf 0x55BF)
(Leaf 0x7CF1) (Leaf 0x7CF1)
) )
@ -68,17 +70,16 @@ exampleSmall =
) )
example2000 :: Word16Tree example2000 :: Word16Tree
{-# NOINLINE example2000 #-} {-# noinline example2000 #-}
example2000 = balanced 0 2000 example2000 = balanced 0 2000
example9000 :: Word16Tree example9000 :: Word16Tree
{-# NOINLINE example9000 #-} {-# noinline example9000 #-}
example9000 = balanced 0 9000 example9000 = balanced 0 9000
balanced :: Word16 -> Word16 -> Word16Tree balanced :: Word16 -> Word16 -> Word16Tree
balanced !off !n balanced !off !n
| n == 0 = Leaf off | n == 0 = Leaf off
| n == 1 = Leaf (off + 1) | n == 1 = Leaf (off + 1)
| otherwise = | otherwise = let x = div n 2 in
let x = div n 2 Branch (balanced off x) (balanced (off + x) (n - x))
in Branch (balanced off x) (balanced (off + x) (n - x))

View file

@ -1,51 +0,0 @@
# Number of spaces per indentation step
indentation: 2
# Max line length for automatic line breaking
column-limit: 200
# Styling of arrows in type signatures (choices: trailing, leading, or leading-args)
function-arrows: trailing
# How to place commas in multi-line lists, records, etc. (choices: leading or trailing)
comma-style: leading
# Styling of import/export lists (choices: leading, trailing, or diff-friendly)
import-export-style: leading
# Whether to full-indent or half-indent 'where' bindings past the preceding body
indent-wheres: false
# Whether to leave a space before an opening record brace
record-brace-space: true
# Number of spaces between top-level declarations
newlines-between-decls: 1
# How to print Haddock comments (choices: single-line, multi-line, or multi-line-compact)
haddock-style: multi-line
# How to print module docstring
haddock-style-module: null
# Styling of let blocks (choices: auto, inline, newline, or mixed)
let-style: auto
# How to align the 'in' keyword with respect to the 'let' keyword (choices: left-align, right-align, or no-space)
in-style: right-align
# Whether to put parentheses around a single constraint (choices: auto, always, or never)
single-constraint-parens: always
# Output Unicode syntax (choices: detect, always, or never)
unicode: never
# Give the programmer more choice on where to insert blank lines
respectful: true
# Fixity information for operators
fixities: []
# Module reexports Fourmolu should know about
reexports: []

View file

@ -1,4 +1,4 @@
{-# LANGUAGE MagicHash #-} {-# language MagicHash #-}
-- This is actually used with both GHC 8.10 and with GHC 9.0. -- This is actually used with both GHC 8.10 and with GHC 9.0.
-- The name of the directory is a little misleading. -- The name of the directory is a little misleading.
@ -14,40 +14,40 @@ module Compat
, word32ToWord# , word32ToWord#
) where ) where
import GHC.Exts (Int#, Word#) import GHC.Exts (Int#,Word#)
int8ToInt# :: Int# -> Int# int8ToInt# :: Int# -> Int#
{-# INLINE int8ToInt# #-} {-# inline int8ToInt# #-}
int8ToInt# x = x int8ToInt# x = x
int16ToInt# :: Int# -> Int# int16ToInt# :: Int# -> Int#
{-# INLINE int16ToInt# #-} {-# inline int16ToInt# #-}
int16ToInt# x = x int16ToInt# x = x
int32ToInt# :: Int# -> Int# int32ToInt# :: Int# -> Int#
{-# INLINE int32ToInt# #-} {-# inline int32ToInt# #-}
int32ToInt# x = x int32ToInt# x = x
wordToWord8# :: Word# -> Word# wordToWord8# :: Word# -> Word#
{-# INLINE wordToWord8# #-} {-# inline wordToWord8# #-}
wordToWord8# x = x wordToWord8# x = x
wordToWord16# :: Word# -> Word# wordToWord16# :: Word# -> Word#
{-# INLINE wordToWord16# #-} {-# inline wordToWord16# #-}
wordToWord16# x = x wordToWord16# x = x
wordToWord32# :: Word# -> Word# wordToWord32# :: Word# -> Word#
{-# INLINE wordToWord32# #-} {-# inline wordToWord32# #-}
wordToWord32# x = x wordToWord32# x = x
word8ToWord# :: Word# -> Word# word8ToWord# :: Word# -> Word#
{-# INLINE word8ToWord# #-} {-# inline word8ToWord# #-}
word8ToWord# x = x word8ToWord# x = x
word16ToWord# :: Word# -> Word# word16ToWord# :: Word# -> Word#
{-# INLINE word16ToWord# #-} {-# inline word16ToWord# #-}
word16ToWord# x = x word16ToWord# x = x
word32ToWord# :: Word# -> Word# word32ToWord# :: Word# -> Word#
{-# INLINE word32ToWord# #-} {-# inline word32ToWord# #-}
word32ToWord# x = x word32ToWord# x = x

View file

@ -1,4 +1,4 @@
{-# LANGUAGE MagicHash #-} {-# language MagicHash #-}
module Compat module Compat
( int8ToInt# ( int8ToInt#

View file

@ -1,5 +1,5 @@
{-# LANGUAGE MagicHash #-} {-# language MagicHash #-}
{-# LANGUAGE UnboxedTuples #-} {-# language UnboxedTuples #-}
module Op module Op
( writeCharArray# ( writeCharArray#
@ -7,9 +7,9 @@ module Op
, copyMutableByteArray# , copyMutableByteArray#
) where ) where
import GHC.Exts (ByteArray#, Char#, Int#, MutableByteArray#, State#, (<#), (>=#)) import GHC.Exts ((<#),(>=#),State#,Int#,MutableByteArray#,ByteArray#,Char#)
import GHC.Int (Int(I#))
import qualified GHC.Exts as Exts import qualified GHC.Exts as Exts
import GHC.Int (Int (I#))
writeCharArray# :: MutableByteArray# s -> Int# -> Char# -> State# s -> State# s writeCharArray# :: MutableByteArray# s -> Int# -> Char# -> State# s -> State# s
writeCharArray# arr i v st = case i <# 0# of writeCharArray# arr i v st = case i <# 0# of
@ -27,8 +27,8 @@ copyByteArray# src soff dst doff len s0 =
, I# doff >= 0 , I# doff >= 0
, I# len >= 0 , I# len >= 0
, I# doff + I# len <= I# sz , I# doff + I# len <= I# sz
, I# soff + I# len <= I# (Exts.sizeofByteArray# src) -> , I# soff + I# len <= I# (Exts.sizeofByteArray# src)
Exts.copyByteArray# src soff dst doff len s1 -> Exts.copyByteArray# src soff dst doff len s1
| otherwise -> error "copyByteArray#: index range out of bounds" | otherwise -> error "copyByteArray#: index range out of bounds"
copyMutableByteArray# :: MutableByteArray# s -> Int# -> MutableByteArray# s -> Int# -> Int# -> State# s -> State# s copyMutableByteArray# :: MutableByteArray# s -> Int# -> MutableByteArray# s -> Int# -> Int# -> State# s -> State# s
@ -40,6 +40,6 @@ copyMutableByteArray# src soff dst doff len s0 =
, I# doff >= 0 , I# doff >= 0
, I# len >= 0 , I# len >= 0
, I# doff + I# len <= I# szDst , I# doff + I# len <= I# szDst
, I# soff + I# len <= I# szSrc -> , I# soff + I# len <= I# szSrc
Exts.copyMutableByteArray# src soff dst doff len s2 -> Exts.copyMutableByteArray# src soff dst doff len s2
| otherwise -> error "copyMutableByteArray#: index range out of bounds" | otherwise -> error "copyMutableByteArray#: index range out of bounds"

View file

@ -1,4 +1,4 @@
{-# LANGUAGE MagicHash #-} {-# language MagicHash #-}
module Op module Op
( writeCharArray# ( writeCharArray#
@ -6,4 +6,4 @@ module Op
, copyMutableByteArray# , copyMutableByteArray#
) where ) where
import GHC.Exts (copyByteArray#, copyMutableByteArray#, writeCharArray#) import GHC.Exts (copyMutableByteArray#,writeCharArray#,copyByteArray#,copyMutableByteArray#)

File diff suppressed because it is too large Load diff

View file

@ -1,87 +0,0 @@
{-# LANGUAGE BangPatterns #-}
{- | Builders for encoding data with Apache Avro. Most functions in this
module are just aliases for other functions. Avro uses zig-zag LEB128
for all integral types.
-}
module Data.Bytes.Builder.Avro
( int
, int32
, int64
, word16
, word32
, word128
, bytes
, chunks
, text
-- * Maps
, map2
) where
import Data.Bytes (Bytes)
import Data.Bytes.Builder (Builder)
import Data.Bytes.Chunks (Chunks)
import Data.Int
import Data.Text (Text)
import Data.WideWord (Word128)
import Data.Word
import qualified Data.Bytes as Bytes
import qualified Data.Bytes.Builder as B
import qualified Data.Bytes.Chunks as Chunks
import qualified Data.Bytes.Text.Utf8 as Utf8
int32 :: Int32 -> Builder
int32 = B.int32LEB128
int64 :: Int64 -> Builder
int64 = B.int64LEB128
int :: Int -> Builder
int = B.intLEB128
{- | Note: This results in a zigzag encoded number. Avro does not have
unsigned types.
-}
word16 :: Word16 -> Builder
word16 = B.int32LEB128 . fromIntegral
{- | Note: This results in a zigzag encoded number. Avro does not have
unsigned types.
-}
word32 :: Word32 -> Builder
word32 = B.int64LEB128 . fromIntegral
{- | Note: This results in a @fixed@ encoded value of length 16. In the
schema, the type must be @{"type": "fixed", "name": "...", "size": 16}@.
A big-endian encoding is used.
-}
word128 :: Word128 -> Builder
word128 = B.word128BE
bytes :: Bytes -> Builder
bytes !b = int (Bytes.length b) <> B.bytes b
chunks :: Chunks -> Builder
chunks !b = int (Chunks.length b) <> B.chunks b
text :: Text -> Builder
text = bytes . Utf8.fromText
{- | Encode a map with exactly two key-value pairs. The keys are text.
This is commonly used to encode the header in an avro file, which has
a map with two keys: @avro.schema@ and @avro.codec@.
-}
map2 ::
-- | First key
Text ->
-- | First value (already encoded)
Builder ->
-- | Second key
Text ->
-- | Second value (already encoded)
Builder ->
Builder
{-# INLINE map2 #-}
map2 k1 v1 k2 v2 = B.word8 0x04 <> text k1 <> v1 <> text k2 <> v2 <> B.word8 0x00

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
{-# LANGUAGE DataKinds #-} {-# language DataKinds #-}
{-# LANGUAGE TypeFamilies #-} {-# language TypeFamilies #-}
module Data.Bytes.Builder.Bounded.Class module Data.Bytes.Builder.Bounded.Class
( ToBoundedBuilder (..) ( ToBoundedBuilder(..)
) where ) where
import Data.Int import Data.Int
@ -11,15 +11,14 @@ import Data.Word
import qualified Data.Bytes.Builder.Bounded as Bounded import qualified Data.Bytes.Builder.Bounded as Bounded
import qualified GHC.TypeNats as GHC import qualified GHC.TypeNats as GHC
{- | Variant of To that can be encoded as a builder. Human-readable encodings -- | Variant of To that can be encoded as a builder. Human-readable encodings
are used when possible. For example, numbers are encoded an ascii-encoded -- are used when possible. For example, numbers are encoded an ascii-encoded
decimal characters. UTF-8 is preferred for textual types. For types -- decimal characters. UTF-8 is preferred for textual types. For types
that represent arbitrary bytes (e.g. Bytes, ByteString), the bytes -- that represent arbitrary bytes (e.g. Bytes, ByteString), the bytes
are preserved. -- are preserved.
--
The goal of this typeclass is to reduce the size of builders produced -- The goal of this typeclass is to reduce the size of builders produced
by quasiquotation. -- by quasiquotation.
-}
class ToBoundedBuilder a where class ToBoundedBuilder a where
type BoundedBuilderLength a :: GHC.Nat type BoundedBuilderLength a :: GHC.Nat
toBuilder :: a -> Bounded.Builder (BoundedBuilderLength a) toBuilder :: a -> Bounded.Builder (BoundedBuilderLength a)

View file

@ -1,66 +1,62 @@
{-# LANGUAGE DataKinds #-} {-# language DataKinds #-}
{-# LANGUAGE GADTSyntax #-} {-# language GADTSyntax #-}
{-# LANGUAGE KindSignatures #-} {-# language KindSignatures #-}
{-# LANGUAGE MagicHash #-} {-# language MagicHash #-}
{-# LANGUAGE RankNTypes #-} {-# language RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# language ScopedTypeVariables #-}
{-# LANGUAGE UnboxedTuples #-} {-# language UnboxedTuples #-}
module Data.Bytes.Builder.Bounded.Unsafe module Data.Bytes.Builder.Bounded.Unsafe
( -- * Types ( -- * Types
Builder (..) Builder(..)
-- * Construct -- * Construct
, construct , construct
-- * Run -- * Run
, pasteST , pasteST
, pasteIO , pasteIO
) where ) where
import Data.Kind (Type) import Data.Kind (Type)
import Data.Primitive (MutableByteArray (..)) import Data.Primitive (MutableByteArray(..))
import GHC.Exts (Int (I#), Int#, MutableByteArray#, RealWorld, State#) import GHC.Exts (Int(I#),RealWorld,Int#,State#,MutableByteArray#)
import GHC.IO (stToIO) import GHC.IO (stToIO)
import GHC.ST (ST (ST)) import GHC.ST (ST(ST))
import GHC.TypeLits (Nat) import GHC.TypeLits (Nat)
{- | A builder parameterized by the maximum number of bytes it uses -- | A builder parameterized by the maximum number of bytes it uses
when executed. -- when executed.
-}
newtype Builder :: Nat -> Type where newtype Builder :: Nat -> Type where
Builder :: Builder ::
-- | This function takes a buffer, an offset, and a number of remaining bytes. (forall s. MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #))
-- ^ This function takes a buffer, an offset, and a number of remaining bytes.
-- It returns the new offset. -- It returns the new offset.
(forall s. MutableByteArray# s -> Int# -> State# s -> (# State# s, Int# #)) -> -> Builder n
Builder n
{- | Constructor for 'Builder' that works on a function with lifted
arguments instead of unlifted ones. This is just as unsafe as the -- | Constructor for 'Builder' that works on a function with lifted
actual constructor. -- arguments instead of unlifted ones. This is just as unsafe as the
-} -- actual constructor.
construct :: (forall s. MutableByteArray s -> Int -> ST s Int) -> Builder n construct :: (forall s. MutableByteArray s -> Int -> ST s Int) -> Builder n
{-# INLINE construct #-} {-# inline construct #-}
construct f = Builder $ construct f = Builder
\arr off s0 -> $ \arr off s0 ->
case unST (f (MutableByteArray arr) (I# off)) s0 of case unST (f (MutableByteArray arr) (I# off)) s0 of
(# s1, (I# n) #) -> (# s1, n #) (# s1, (I# n) #) -> (# s1, n #)
{- | This function does not enforce the known upper bound on the -- | This function does not enforce the known upper bound on the
size. It is up to the user to do this. -- size. It is up to the user to do this.
-}
pasteST :: Builder n -> MutableByteArray s -> Int -> ST s Int pasteST :: Builder n -> MutableByteArray s -> Int -> ST s Int
{-# INLINE pasteST #-} {-# inline pasteST #-}
pasteST (Builder f) (MutableByteArray arr) (I# off) = pasteST (Builder f) (MutableByteArray arr) (I# off) =
ST $ \s0 -> case f arr off s0 of ST $ \s0 -> case f arr off s0 of
(# s1, r #) -> (# s1, (I# r) #) (# s1, r #) -> (# s1, (I# r) #)
{- | This function does not enforce the known upper bound on the -- | This function does not enforce the known upper bound on the
size. It is up to the user to do this. -- size. It is up to the user to do this.
-}
pasteIO :: Builder n -> MutableByteArray RealWorld -> Int -> IO Int pasteIO :: Builder n -> MutableByteArray RealWorld -> Int -> IO Int
{-# INLINE pasteIO #-} {-# inline pasteIO #-}
pasteIO b m off = stToIO (pasteST b m off) pasteIO b m off = stToIO (pasteST b m off)
unST :: ST s a -> State# s -> (# State# s, a #) unST :: ST s a -> State# s -> (# State# s, a #)
unST (ST f) = f unST (ST f) = f

View file

@ -1,12 +1,13 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Data.Bytes.Builder.Class module Data.Bytes.Builder.Class
( ToBuilder (..) ( ToBuilder(..)
) where ) where
import Data.ByteString.Short (ShortByteString)
import Data.Bytes (Bytes) import Data.Bytes (Bytes)
import Data.Bytes.Builder (Builder) import Data.Bytes.Builder (Builder)
import Data.ByteString.Short (ShortByteString)
import Data.Int import Data.Int
import Data.Primitive.ByteArray (ByteArray) import Data.Primitive.ByteArray (ByteArray)
import Data.Text.Short (ShortText) import Data.Text.Short (ShortText)
@ -14,15 +15,14 @@ import Data.Word
import qualified Data.Bytes.Builder as Builder import qualified Data.Bytes.Builder as Builder
{- | Types that can be encoded as a builder. Human-readable encodings -- | Types that can be encoded as a builder. Human-readable encodings
are used when possible. For example, numbers are encoded an ascii-encoded -- are used when possible. For example, numbers are encoded an ascii-encoded
decimal characters. UTF-8 is preferred for textual types. For types -- decimal characters. UTF-8 is preferred for textual types. For types
that represent arbitrary bytes (e.g. Bytes, ByteString), the bytes -- that represent arbitrary bytes (e.g. Bytes, ByteString), the bytes
are preserved. -- are preserved.
--
The goal of this typeclass is to reduce the size of builders produced -- The goal of this typeclass is to reduce the size of builders produced
by quasiquotation. -- by quasiquotation.
-}
class ToBuilder a where class ToBuilder a where
toBuilder :: a -> Builder toBuilder :: a -> Builder

View file

@ -9,52 +9,49 @@ module Data.Bytes.Builder.Template
import Control.Monad (when) import Control.Monad (when)
import Data.Bytes.Builder.Class (toBuilder) import Data.Bytes.Builder.Class (toBuilder)
import GHC.Ptr (Ptr (Ptr)) import GHC.Ptr (Ptr(Ptr))
import Language.Haskell.Meta.Parse (parseExp) import Language.Haskell.Meta.Parse (parseExp)
import Language.Haskell.TH (Exp, Q) import Language.Haskell.TH (Q,Exp)
import Language.Haskell.TH.Lib (integerL, litE, stringPrimL) import Language.Haskell.TH.Lib (integerL,stringPrimL,litE)
import Language.Haskell.TH.Quote (QuasiQuoter (..)) import Language.Haskell.TH.Quote (QuasiQuoter(..))
import qualified Data.ByteString.Short as SBS
import qualified Data.Bytes.Builder as Builder import qualified Data.Bytes.Builder as Builder
import qualified Data.ByteString.Short as SBS
import qualified Data.Text.Short as TS import qualified Data.Text.Short as TS
import qualified Language.Haskell.TH as TH import qualified Language.Haskell.TH as TH
{- | A quasiquoter for builders. Haskell expressions are interpolated -- | A quasiquoter for builders. Haskell expressions are interpolated
with backticks, and the @ToBuilder@ class is used to convert them -- with backticks, and the @ToBuilder@ class is used to convert them
to builders. Several common escape sequences for whitespace and -- to builders. Several common escape sequences for whitespace and
control characters are recongized. Consider the following expression, -- control characters are recongized. Consider the following expression,
where the binding @partition@ has type @Word32@: -- where the binding @partition@ has type @Word32@:
--
> [templ|[WARN] Partition `partition` has invalid data.\n|] -- > [templ|[WARN] Partition `partition` has invalid data.\n|]
--
This expression has type @Builder@ and expands to: -- This expression has type @Builder@ and expands to:
--
> Builder.cstringLen (Ptr "[WARN] Partition "#, 17) <> -- > Builder.cstringLen (Ptr "[WARN] Partition "#, 17) <>
> Builder.toBuilder partition <> -- > Builder.toBuilder partition <>
> Builder.cstringLen (Ptr " has invalid data.\n"#, 19) -- > Builder.cstringLen (Ptr " has invalid data.\n"#, 19)
--
The @ToBuilder@ instance for @Word32@ uses decimal encoding, so this -- The @ToBuilder@ instance for @Word32@ uses decimal encoding, so this
would result in the following if @partition@ was 42 (with a newline -- would result in the following if @partition@ was 42 (with a newline
character at the end): -- character at the end):
--
> [WARN] Partition 42 has invalid data. -- > [WARN] Partition 42 has invalid data.
--
In the future, a more sophisticated @bbldr@ variant will be added -- In the future, a more sophisticated @bbldr@ variant will be added
that will support expressions where the maximum length of the entire -- that will support expressions where the maximum length of the entire
builder can be computed at compile time. -- builder can be computed at compile time.
-}
bldr :: QuasiQuoter bldr :: QuasiQuoter
bldr = bldr = QuasiQuoter
QuasiQuoter
{ quoteExp = templExp { quoteExp = templExp
, quotePat = notHandled "patterns" , quotePat = notHandled "patterns"
, quoteType = notHandled "types" , quoteType = notHandled "types"
, quoteDec = notHandled "declarations" , quoteDec = notHandled "declarations"
} }
where where
notHandled things _ = notHandled things _ = fail $
fail $
things ++ "are not handled by the byte template quasiquoter" things ++ "are not handled by the byte template quasiquoter"
templExp :: String -> Q Exp templExp :: String -> Q Exp
@ -65,7 +62,7 @@ templExp inp = do
Right [] -> fail "empty template" Right [] -> fail "empty template"
Right v -> pure v Right v -> pure v
let expParts = compile <$> rawParts let expParts = compile <$> rawParts
foldl1 (\e1 e2 -> [|$e1 <> $e2|]) expParts foldl1 (\e1 e2 -> [| $e1 <> $e2 |]) expParts
checkOverloadedStrings :: Q () checkOverloadedStrings :: Q ()
checkOverloadedStrings = do checkOverloadedStrings = do
@ -93,37 +90,37 @@ parse = partsLoop
where where
partsLoop "" = do partsLoop "" = do
pure [] pure []
partsLoop ('`' : inp) = do partsLoop ('`':inp) = do
(!spl, !rest) <- spliceLoop inp (!spl, !rest) <- spliceLoop inp
(Splice spl :) <$> partsLoop rest (Splice spl:) <$> partsLoop rest
partsLoop inp = do partsLoop inp = do
(!lit, !rest) <- litLoop "" inp (!lit, !rest) <- litLoop "" inp
(Literal lit :) <$> partsLoop rest (Literal lit:) <$> partsLoop rest
litLoop :: String -> String -> Either String (String, String) litLoop :: String -> String -> Either String (String, String)
litLoop !acc rest@"" = pure (reverse acc, rest) litLoop !acc rest@"" = pure (reverse acc, rest)
litLoop !acc rest@('`' : _) = pure (reverse acc, rest) litLoop !acc rest@('`':_) = pure (reverse acc, rest)
litLoop !acc ('\\' : next) = do litLoop !acc ('\\':next) = do
(c, rest) <- parseEscape next (c, rest) <- parseEscape next
litLoop (c : acc) rest litLoop (c:acc) rest
litLoop !acc (c : rest) = litLoop (c : acc) rest litLoop !acc (c:rest) = litLoop (c:acc) rest
spliceLoop :: String -> Either String (String, String) spliceLoop :: String -> Either String (String, String)
spliceLoop inp = case break (== '`') inp of spliceLoop inp = case break (== '`') inp of
([], _) -> Left "internal error" ([], _) -> Left "internal error"
(hs, '`' : rest) -> pure (hs, rest) (hs, '`':rest) -> pure (hs, rest)
(_, _ : _) -> Left "internal error" (_, _:_) -> Left "internal error"
(_, []) -> Left "unterminated interpolation" (_, []) -> Left "unterminated interpolation"
parseEscape :: String -> Either String (Char, String) parseEscape :: String -> Either String (Char, String)
parseEscape "" = Left "incomplete escape" parseEscape "" = Left "incomplete escape"
parseEscape ('\\' : rest) = pure ('\\', rest) parseEscape ('\\':rest) = pure ('\\', rest)
parseEscape ('`' : rest) = pure ('`', rest) parseEscape ('`':rest) = pure ('`', rest)
parseEscape ('\'' : rest) = pure ('\'', rest) parseEscape ('\'':rest) = pure ('\'', rest)
parseEscape ('\"' : rest) = pure ('\"', rest) parseEscape ('\"':rest) = pure ('\"', rest)
parseEscape ('0' : rest) = pure ('\0', rest) parseEscape ('0':rest) = pure ('\0', rest)
parseEscape ('a' : rest) = pure ('\a', rest) parseEscape ('a':rest) = pure ('\a', rest)
parseEscape ('b' : rest) = pure ('\b', rest) parseEscape ('b':rest) = pure ('\b', rest)
parseEscape ('f' : rest) = pure ('\f', rest) parseEscape ('f':rest) = pure ('\f', rest)
parseEscape ('n' : rest) = pure ('\n', rest) parseEscape ('n':rest) = pure ('\n', rest)
parseEscape ('r' : rest) = pure ('\r', rest) parseEscape ('r':rest) = pure ('\r', rest)
parseEscape ('t' : rest) = pure ('\t', rest) parseEscape ('t':rest) = pure ('\t', rest)
parseEscape ('v' : rest) = pure ('\v', rest) parseEscape ('v':rest) = pure ('\v', rest)
parseEscape (c : _) = Left $ "unrecognized escape: \\" ++ [c] parseEscape (c:_) = Left $ "unrecognized escape: \\" ++ [c]

View file

@ -1,60 +1,55 @@
{-# LANGUAGE BangPatterns #-} {-# language BangPatterns #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# language DuplicateRecordFields #-}
{-# LANGUAGE MagicHash #-} {-# language LambdaCase #-}
{-# LANGUAGE RankNTypes #-} {-# language MagicHash #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# language RankNTypes #-}
{-# LANGUAGE UnboxedTuples #-} {-# language ScopedTypeVariables #-}
{-# language UnboxedTuples #-}
module Data.Bytes.Builder.Unsafe module Data.Bytes.Builder.Unsafe
( -- * Types ( -- * Types
Builder (..) Builder(..)
, BuilderState (..) , BuilderState(..)
, Commits (..) , Commits(..)
-- * Execution -- * Execution
, pasteST , pasteST
, pasteIO , pasteIO
-- * Construction -- * Construction
, fromEffect , fromEffect
-- * Builder State -- * Builder State
, newBuilderState , newBuilderState
, closeBuilderState , closeBuilderState
-- * Finalization -- * Finalization
, reverseCommitsOntoChunks , reverseCommitsOntoChunks
, commitsOntoChunks , commitsOntoChunks
, copyReverseCommits , copyReverseCommits
, addCommitsLength , addCommitsLength
-- * Commit Distance -- * Commit Distance
, commitDistance , commitDistance
, commitDistance1 , commitDistance1
-- * Safe Functions -- * Safe Functions
-- | These functions are actually completely safe, but they are defined -- | These functions are actually completely safe, but they are defined
-- here because they are used by typeclass instances. Import them from -- here because they are used by typeclass instances. Import them from
-- @Data.Bytes.Builder@ instead. -- @Data.Bytes.Builder@ instead.
, stringUtf8 , stringUtf8
, cstring , cstring
-- * Pasting with Preconditions -- * Pasting with Preconditions
, pasteUtf8TextJson# , pasteUtf8TextJson#
) where ) where
import Control.Monad.Primitive (primitive_) import Control.Monad.Primitive (primitive_)
import Data.Bytes.Chunks (Chunks (ChunksCons)) import Data.Bytes.Chunks (Chunks(ChunksCons))
import Data.Bytes.Types (Bytes (Bytes)) import Data.Bytes.Types (Bytes(Bytes))
import Data.Char (ord) import Data.Char (ord)
import Data.Primitive (ByteArray (..), MutableByteArray (..)) import Data.Primitive (MutableByteArray(..),ByteArray(..))
import Data.Word (Word8) import Data.Word (Word8)
import Foreign.C.String (CString) import Foreign.C.String (CString)
import GHC.Base (unpackCString#, unpackCStringUtf8#) import GHC.Base (unpackCString#,unpackCStringUtf8#)
import GHC.Exts (Addr#, ByteArray#, Char (C#), Int (I#), Int#, IsString, MutableByteArray#, Ptr (Ptr), RealWorld, State#, (+#), (-#), (>#), (>=#)) import GHC.Exts ((-#),(+#),(>#),(>=#),Char(C#))
import GHC.Exts (Addr#,ByteArray#,MutableByteArray#,Int(I#),Ptr(Ptr))
import GHC.Exts (RealWorld,IsString,Int#,State#)
import GHC.IO (stToIO) import GHC.IO (stToIO)
import GHC.ST (ST (ST)) import GHC.ST (ST(ST))
import qualified Compat as C import qualified Compat as C
import qualified Data.Bytes.Builder.Bounded as Bounded import qualified Data.Bytes.Builder.Bounded as Bounded
@ -63,12 +58,10 @@ import qualified Data.Primitive as PM
import qualified GHC.Exts as Exts import qualified GHC.Exts as Exts
import qualified Op import qualified Op
{- | An unmaterialized sequence of bytes that may be pasted -- | An unmaterialized sequence of bytes that may be pasted
into a mutable byte array. -- into a mutable byte array.
-}
newtype Builder newtype Builder
= Builder = Builder (forall s.
( forall s.
MutableByteArray# s -> -- buffer we are currently writing to MutableByteArray# s -> -- buffer we are currently writing to
Int# -> -- offset into the current buffer Int# -> -- offset into the current buffer
Int# -> -- number of bytes remaining in the current buffer Int# -> -- number of bytes remaining in the current buffer
@ -77,13 +70,11 @@ newtype Builder
(# State# s, MutableByteArray# s, Int#, Int#, Commits s #) -- all the same things (# State# s, MutableByteArray# s, Int#, Int#, Commits s #) -- all the same things
) )
{- | A list of committed chunks along with the chunk currently being -- | A list of committed chunks along with the chunk currently being
written to. This is kind of like a non-empty variant of 'Commmits' -- written to. This is kind of like a non-empty variant of 'Commmits'
but with the additional invariant that the head chunk is a mutable -- but with the additional invariant that the head chunk is a mutable
byte array. -- byte array.
-} data BuilderState s = BuilderState
data BuilderState s
= BuilderState
(MutableByteArray# s) -- buffer we are currently writing to (MutableByteArray# s) -- buffer we are currently writing to
Int# -- offset into the current buffer Int# -- offset into the current buffer
Int# -- number of bytes remaining in the current buffer Int# -- number of bytes remaining in the current buffer
@ -91,24 +82,22 @@ data BuilderState s
-- | Create an empty 'BuilderState' with a buffer of the given size. -- | Create an empty 'BuilderState' with a buffer of the given size.
newBuilderState :: Int -> ST s (BuilderState s) newBuilderState :: Int -> ST s (BuilderState s)
{-# INLINE newBuilderState #-} {-# inline newBuilderState #-}
newBuilderState n@(I# n#) = do newBuilderState n@(I# n# ) = do
MutableByteArray buf <- PM.newByteArray n MutableByteArray buf <- PM.newByteArray n
pure (BuilderState buf 0# n# Initial) pure (BuilderState buf 0# n# Initial)
{- | Push the active chunk onto the top of the commits. -- | Push the active chunk onto the top of the commits.
The @BuilderState@ argument must not be reused after being passed -- The @BuilderState@ argument must not be reused after being passed
to this function. That is, its use must be affine. -- to this function. That is, its use must be affine.
-}
closeBuilderState :: BuilderState s -> Commits s closeBuilderState :: BuilderState s -> Commits s
closeBuilderState (BuilderState dst off _ cmts) = Mutable dst off cmts closeBuilderState (BuilderState dst off _ cmts) = Mutable dst off cmts
{- | Run a builder, performing an in-place update on the state. -- | Run a builder, performing an in-place update on the state.
The @BuilderState@ argument must not be reused after being passed -- The @BuilderState@ argument must not be reused after being passed
to this function. That is, its use must be affine. -- to this function. That is, its use must be affine.
-}
pasteST :: Builder -> BuilderState s -> ST s (BuilderState s) pasteST :: Builder -> BuilderState s -> ST s (BuilderState s)
{-# INLINE pasteST #-} {-# inline pasteST #-}
pasteST (Builder f) (BuilderState buf off len cmts) = ST $ \s0 -> pasteST (Builder f) (BuilderState buf off len cmts) = ST $ \s0 ->
case f buf off len cmts s0 of case f buf off len cmts s0 of
(# s1, buf1, off1, len1, cmts1 #) -> (# s1, buf1, off1, len1, cmts1 #) ->
@ -116,54 +105,48 @@ pasteST (Builder f) (BuilderState buf off len cmts) = ST $ \s0 ->
-- | Variant of 'pasteST' that runs in 'IO'. -- | Variant of 'pasteST' that runs in 'IO'.
pasteIO :: Builder -> BuilderState RealWorld -> IO (BuilderState RealWorld) pasteIO :: Builder -> BuilderState RealWorld -> IO (BuilderState RealWorld)
{-# INLINE pasteIO #-} {-# inline pasteIO #-}
pasteIO b st = stToIO (pasteST b st) pasteIO b st = stToIO (pasteST b st)
instance IsString Builder where instance IsString Builder where
{-# INLINE fromString #-} {-# inline fromString #-}
fromString = stringUtf8 fromString = stringUtf8
instance Semigroup Builder where instance Semigroup Builder where
{-# INLINE (<>) #-} {-# inline (<>) #-}
Builder f <> Builder g = Builder $ \buf0 off0 len0 cs0 s0 -> case f buf0 off0 len0 cs0 s0 of Builder f <> Builder g = Builder $ \buf0 off0 len0 cs0 s0 -> case f buf0 off0 len0 cs0 s0 of
(# s1, buf1, off1, len1, cs1 #) -> g buf1 off1 len1 cs1 s1 (# s1, buf1, off1, len1, cs1 #) -> g buf1 off1 len1 cs1 s1
instance Monoid Builder where instance Monoid Builder where
{-# INLINE mempty #-} {-# inline mempty #-}
mempty = Builder $ \buf0 off0 len0 cs0 s0 -> (# s0, buf0, off0, len0, cs0 #) mempty = Builder $ \buf0 off0 len0 cs0 s0 -> (# s0, buf0, off0, len0, cs0 #)
data Commits s data Commits s
= Mutable = Mutable
-- | Mutable buffer, start index implicitly zero
(MutableByteArray# s) (MutableByteArray# s)
-- | Length (may be smaller than actual length) -- ^ Mutable buffer, start index implicitly zero
Int# Int# -- ^ Length (may be smaller than actual length)
!(Commits s) !(Commits s)
| Immutable | Immutable
-- | Immutable chunk ByteArray# -- ^ Immutable chunk
ByteArray# Int# -- ^ Offset into chunk, not necessarily zero
-- | Offset into chunk, not necessarily zero Int# -- ^ Length (may be smaller than actual length)
Int#
-- | Length (may be smaller than actual length)
Int#
!(Commits s) !(Commits s)
| Initial | Initial
{- | Add the total number of bytes in the commits to first -- | Add the total number of bytes in the commits to first
argument. -- argument.
-}
addCommitsLength :: Int -> Commits s -> Int addCommitsLength :: Int -> Commits s -> Int
addCommitsLength !acc Initial = acc addCommitsLength !acc Initial = acc
addCommitsLength !acc (Immutable _ _ x cs) = addCommitsLength (acc + I# x) cs addCommitsLength !acc (Immutable _ _ x cs) = addCommitsLength (acc + I# x) cs
addCommitsLength !acc (Mutable _ x cs) = addCommitsLength (acc + I# x) cs addCommitsLength !acc (Mutable _ x cs) = addCommitsLength (acc + I# x) cs
{- | Cons the chunks from a list of @Commits@ onto an initial -- | Cons the chunks from a list of @Commits@ onto an initial
@Chunks@ list (this argument is often @ChunksNil@). This reverses -- @Chunks@ list (this argument is often @ChunksNil@). This reverses
the order of the chunks, which is desirable since builders assemble -- the order of the chunks, which is desirable since builders assemble
@Commits@ with the chunks backwards. This performs an in-place shrink -- @Commits@ with the chunks backwards. This performs an in-place shrink
and freezes any mutable byte arrays it encounters. Consequently, -- and freezes any mutable byte arrays it encounters. Consequently,
these must not be reused. -- these must not be reused.
-}
reverseCommitsOntoChunks :: Chunks -> Commits s -> ST s Chunks reverseCommitsOntoChunks :: Chunks -> Commits s -> ST s Chunks
reverseCommitsOntoChunks !xs Initial = pure xs reverseCommitsOntoChunks !xs Initial = pure xs
reverseCommitsOntoChunks !xs (Immutable arr off len cs) = reverseCommitsOntoChunks !xs (Immutable arr off len cs) =
@ -176,15 +159,14 @@ reverseCommitsOntoChunks !xs (Mutable buf len cs) = case len of
arr <- PM.unsafeFreezeByteArray (MutableByteArray buf) arr <- PM.unsafeFreezeByteArray (MutableByteArray buf)
reverseCommitsOntoChunks (ChunksCons (Bytes arr 0 (I# len)) xs) cs reverseCommitsOntoChunks (ChunksCons (Bytes arr 0 (I# len)) xs) cs
{- | Variant of 'reverseCommitsOntoChunks' that does not reverse -- | Variant of 'reverseCommitsOntoChunks' that does not reverse
the order of the commits. Since commits are built backwards by -- the order of the commits. Since commits are built backwards by
consing, this means that the chunks appended to the front will -- consing, this means that the chunks appended to the front will
be backwards. Within each chunk, however, the bytes will be in -- be backwards. Within each chunk, however, the bytes will be in
the correct order. -- the correct order.
--
Unlike 'reverseCommitsOntoChunks', this function is not tail -- Unlike 'reverseCommitsOntoChunks', this function is not tail
recursive. -- recursive.
-}
commitsOntoChunks :: Chunks -> Commits s -> ST s Chunks commitsOntoChunks :: Chunks -> Commits s -> ST s Chunks
commitsOntoChunks !xs0 cs0 = go cs0 commitsOntoChunks !xs0 cs0 = go cs0
where where
@ -201,65 +183,53 @@ commitsOntoChunks !xs0 cs0 = go cs0
xs <- go cs xs <- go cs
pure $! ChunksCons (Bytes arr 0 (I# len)) xs pure $! ChunksCons (Bytes arr 0 (I# len)) xs
{- | Copy the contents of the chunks into a mutable array, reversing -- | Copy the contents of the chunks into a mutable array, reversing
the order of the chunks. -- the order of the chunks.
Precondition: The destination must have enough space to house the -- Precondition: The destination must have enough space to house the
contents. This is not checked. -- contents. This is not checked.
-}
copyReverseCommits :: copyReverseCommits ::
-- | Destination MutableByteArray s -- ^ Destination
MutableByteArray s -> -> Int -- ^ Destination range successor
-- | Destination range successor -> Commits s -- ^ Source
Int -> -> ST s Int
-- | Source {-# inline copyReverseCommits #-}
Commits s -> copyReverseCommits (MutableByteArray dst) (I# off) cs = ST
ST s Int (\s0 -> case copyReverseCommits# dst off cs s0 of
{-# INLINE copyReverseCommits #-}
copyReverseCommits (MutableByteArray dst) (I# off) cs =
ST
( \s0 -> case copyReverseCommits# dst off cs s0 of
(# s1, nextOff #) -> (# s1, I# nextOff #) (# s1, nextOff #) -> (# s1, I# nextOff #)
) )
copyReverseCommits# :: copyReverseCommits# ::
MutableByteArray# s -> MutableByteArray# s
Int# -> -> Int#
Commits s -> -> Commits s
State# s -> -> State# s
(# State# s, Int# #) -> (# State# s, Int# #)
copyReverseCommits# _ off Initial s0 = (# s0, off #) copyReverseCommits# _ off Initial s0 = (# s0, off #)
copyReverseCommits# marr prevOff (Mutable arr sz cs) s0 = copyReverseCommits# marr prevOff (Mutable arr sz cs) s0 =
let !off = prevOff -# sz let !off = prevOff -# sz in
in case Op.copyMutableByteArray# arr 0# marr off sz s0 of case Op.copyMutableByteArray# arr 0# marr off sz s0 of
s1 -> copyReverseCommits# marr off cs s1 s1 -> copyReverseCommits# marr off cs s1
copyReverseCommits# marr prevOff (Immutable arr soff sz cs) s0 = copyReverseCommits# marr prevOff (Immutable arr soff sz cs) s0 =
let !off = prevOff -# sz let !off = prevOff -# sz in
in case Op.copyByteArray# arr soff marr off sz s0 of case Op.copyByteArray# arr soff marr off sz s0 of
s1 -> copyReverseCommits# marr off cs s1 s1 -> copyReverseCommits# marr off cs s1
{- | Create a builder from a cons-list of 'Char'. These -- | Create a builder from a cons-list of 'Char'. These
must be UTF-8 encoded. -- must be UTF-8 encoded.
-}
stringUtf8 :: String -> Builder stringUtf8 :: String -> Builder
{-# INLINE stringUtf8 #-} {-# inline stringUtf8 #-}
stringUtf8 cs = Builder (goString cs) stringUtf8 cs = Builder (goString cs)
{- | Create a builder from a @NUL@-terminated 'CString'. This ignores any -- | Create a builder from a @NUL@-terminated 'CString'. This ignores any
textual encoding, copying bytes until @NUL@ is reached. -- textual encoding, copying bytes until @NUL@ is reached.
-}
cstring :: CString -> Builder cstring :: CString -> Builder
{-# INLINE cstring #-} {-# inline cstring #-}
cstring (Ptr cs) = Builder (goCString cs) cstring (Ptr cs) = Builder (goCString cs)
goString :: goString :: String
String -> -> MutableByteArray# s -> Int# -> Int# -> Commits s
MutableByteArray# s -> -> State# s -> (# State# s, MutableByteArray# s, Int#, Int#, Commits s #)
Int# -> {-# noinline goString #-}
Int# ->
Commits s ->
State# s ->
(# State# s, MutableByteArray# s, Int#, Int#, Commits s #)
{-# NOINLINE goString #-}
goString [] buf0 off0 len0 cs0 s0 = (# s0, buf0, off0, len0, cs0 #) goString [] buf0 off0 len0 cs0 s0 = (# s0, buf0, off0, len0, cs0 #)
goString (c : cs) buf0 off0 len0 cs0 s0 = case len0 ># 3# of goString (c : cs) buf0 off0 len0 cs0 s0 = case len0 ># 3# of
1# -> case unST (UnsafeBounded.pasteST (Bounded.char c) (MutableByteArray buf0) (I# off0)) s0 of 1# -> case unST (UnsafeBounded.pasteST (Bounded.char c) (MutableByteArray buf0) (I# off0)) s0 of
@ -275,51 +245,37 @@ goString (c : cs) buf0 off0 len0 cs0 s0 = case len0 ># 3# of
-- used Modified UTF-8. -- used Modified UTF-8.
{-# RULES {-# RULES
"Builder stringUtf8/cstring" forall s a b c d e. "Builder stringUtf8/cstring" forall s a b c d e.
goString (unpackCString# s) a b c d e = goString (unpackCString# s) a b c d e = goCString s a b c d e
goCString s a b c d e
"Builder stringUtf8/cstring-utf8" forall s a b c d e. "Builder stringUtf8/cstring-utf8" forall s a b c d e.
goString (unpackCStringUtf8# s) a b c d e = goString (unpackCStringUtf8# s) a b c d e = goCString s a b c d e
goCString s a b c d e #-}
#-}
goCString :: goCString :: Addr# -> MutableByteArray# s -> Int# -> Int# -> Commits s
Addr# -> -> State# s -> (# State# s, MutableByteArray# s, Int#, Int#, Commits s #)
MutableByteArray# s ->
Int# ->
Int# ->
Commits s ->
State# s ->
(# State# s, MutableByteArray# s, Int#, Int#, Commits s #)
goCString addr buf0 off0 len0 cs0 s0 = case C.word8ToWord# (Exts.indexWord8OffAddr# addr 0#) of goCString addr buf0 off0 len0 cs0 s0 = case C.word8ToWord# (Exts.indexWord8OffAddr# addr 0#) of
0## -> (# s0, buf0, off0, len0, cs0 #) 0## -> (# s0, buf0, off0, len0, cs0 #)
w -> case len0 of w -> case len0 of
0# -> case Exts.newByteArray# 4080# s0 of 0# -> case Exts.newByteArray# 4080# s0 of
(# s1, buf1 #) -> case Exts.writeWord8Array# buf1 0# (C.wordToWord8# w) s1 of (# s1, buf1 #) -> case Exts.writeWord8Array# buf1 0# (C.wordToWord8# w) s1 of
s2 -> s2 -> goCString
goCString (Exts.plusAddr# addr 1# ) buf1 1# (4080# -# 1# )
(Exts.plusAddr# addr 1#)
buf1
1#
(4080# -# 1#)
(Mutable buf0 off0 cs0) (Mutable buf0 off0 cs0)
s2 s2
_ -> case Exts.writeWord8Array# buf0 off0 (C.wordToWord8# w) s0 of _ -> case Exts.writeWord8Array# buf0 off0 (C.wordToWord8# w) s0 of
s1 -> goCString (Exts.plusAddr# addr 1#) buf0 (off0 +# 1#) (len0 -# 1#) cs0 s1 s1 -> goCString (Exts.plusAddr# addr 1# ) buf0 (off0 +# 1# ) (len0 -# 1# ) cs0 s1
fromEffect :: fromEffect ::
-- | Maximum number of bytes the paste function needs Int -- ^ Maximum number of bytes the paste function needs
Int -> -> (forall s. MutableByteArray s -> Int -> ST s Int)
-- | Paste function. Takes a byte array and an offset and returns -- ^ Paste function. Takes a byte array and an offset and returns
-- the new offset and having pasted into the buffer. -- the new offset and having pasted into the buffer.
(forall s. MutableByteArray s -> Int -> ST s Int) -> -> Builder
Builder {-# inline fromEffect #-}
{-# INLINE fromEffect #-}
fromEffect (I# req) f = Builder $ \buf0 off0 len0 cs0 s0 -> fromEffect (I# req) f = Builder $ \buf0 off0 len0 cs0 s0 ->
let !(# s1, buf1, off1, len1, cs1 #) = case len0 >=# req of let !(# s1, buf1, off1, len1, cs1 #) = case len0 >=# req of
1# -> (# s0, buf0, off0, len0, cs0 #) 1# -> (# s0, buf0, off0, len0, cs0 #)
_ -> _ -> let !(I# lenX) = max 4080 (I# req) in
let !(I# lenX) = max 4080 (I# req) case Exts.newByteArray# lenX s0 of
in case Exts.newByteArray# lenX s0 of
(# sX, bufX #) -> (# sX, bufX #) ->
(# sX, bufX, 0#, lenX, Mutable buf0 off0 cs0 #) (# sX, bufX, 0#, lenX, Mutable buf0 off0 cs0 #)
in case unST (f (MutableByteArray buf1) (I# off1)) s1 of in case unST (f (MutableByteArray buf1) (I# off1)) s1 of
@ -332,26 +288,24 @@ shrinkMutableByteArray :: MutableByteArray s -> Int -> ST s ()
shrinkMutableByteArray (MutableByteArray arr) (I# sz) = shrinkMutableByteArray (MutableByteArray arr) (I# sz) =
primitive_ (Exts.shrinkMutableByteArray# arr sz) primitive_ (Exts.shrinkMutableByteArray# arr sz)
{- | Variant of commitDistance where you get to supply a -- | Variant of commitDistance where you get to supply a
head of the commit list that has not yet been committed. -- head of the commit list that has not yet been committed.
-}
commitDistance1 :: commitDistance1 ::
MutableByteArray# s -> -- target MutableByteArray# s -- target
Int# -> -- offset into target -> Int# -- offset into target
MutableByteArray# s -> -- head of array -> MutableByteArray# s -- head of array
Int# -> -- offset into head of array -> Int# -- offset into head of array
Commits s -> -> Commits s
Int# -> Int#
commitDistance1 target offTarget buf0 offBuf cs = commitDistance1 target offTarget buf0 offBuf cs =
case Exts.sameMutableByteArray# target buf0 of case Exts.sameMutableByteArray# target buf0 of
1# -> offBuf -# offTarget 1# -> offBuf -# offTarget
_ -> commitDistance target offBuf cs -# offTarget _ -> commitDistance target offBuf cs -# offTarget
{- | Compute the number of bytes between the last byte and the offset -- | Compute the number of bytes between the last byte and the offset
specified in a chunk. Precondition: the chunk must exist in the -- specified in a chunk. Precondition: the chunk must exist in the
list of committed chunks. This relies on mutable byte arrays having -- list of committed chunks. This relies on mutable byte arrays having
identity (e.g. it uses @sameMutableByteArray#@). -- identity (e.g. it uses @sameMutableByteArray#@).
-}
commitDistance :: MutableByteArray# s -> Int# -> Commits s -> Int# commitDistance :: MutableByteArray# s -> Int# -> Commits s -> Int#
commitDistance !_ !_ Initial = errorWithoutStackTrace "chunkDistance: chunk not found" commitDistance !_ !_ Initial = errorWithoutStackTrace "chunkDistance: chunk not found"
commitDistance target !n (Immutable _ _ len cs) = commitDistance target !n (Immutable _ _ len cs) =
@ -361,57 +315,44 @@ commitDistance target !n (Mutable buf len cs) =
1# -> n +# len 1# -> n +# len
_ -> commitDistance target (n +# len) cs _ -> commitDistance target (n +# len) cs
{- | Encode (UTF-8 encoded) text as a JSON string, wrapping it in double quotes. -- | Encode (UTF-8 encoded) text as a JSON string, wrapping it in double quotes.
This escapes all characters with code points below @0x20@. -- This escapes all characters with code points below @0x20@.
--
* Precondition: The slice of the byte argument is UTF-8 encoded text. -- * Precondition: The slice of the byte argument is UTF-8 encoded text.
* Precondition: There is enough space in the buffer for the result -- * Precondition: There is enough space in the buffer for the result
to be written to. A simple way to ensure enough space is to allocate -- to be written to. A simple way to ensure enough space is to allocate
@6N + 2@ bytes, where N is the length of the argument. However, the -- @3N + 2@ bytes, where N is the length of the argument. However, the
caller may use clever heuristics to find a lower upper bound. -- caller may use clever heuristics to find a lower upper bound.
* Result: The next offset in the destination buffer -- * Result: The next offset in the destination buffer
-}
pasteUtf8TextJson# :: pasteUtf8TextJson# ::
-- | source ByteArray# -- ^ source
ByteArray# -> -> Int# -- ^ source offset
-- | source offset -> Int# -- ^ source length
Int# -> -> MutableByteArray# s -- ^ destination buffer
-- | source length -> Int# -- ^ offset into destination buffer
Int# -> -> State# s -- ^ state token
-- | destination buffer -> (# State# s, Int# #) -- returns next destination offset
MutableByteArray# s -> {-# noinline pasteUtf8TextJson# #-}
-- | offset into destination buffer
Int# ->
-- | state token
State# s ->
(# State# s, Int# #) -- returns next destination offset
{-# NOINLINE pasteUtf8TextJson# #-}
pasteUtf8TextJson# src# soff0# slen0# dst# doff0# s0# = pasteUtf8TextJson# src# soff0# slen0# dst# doff0# s0# =
let ST f = do let ST f = do
let dst = MutableByteArray dst# let dst = MutableByteArray dst#
let doff0 = I# doff0# let doff0 = I# doff0#
PM.writeByteArray dst doff0 (c2w '"') PM.writeByteArray dst doff0 (c2w '"')
let go !soff !slen !doff = let go !soff !slen !doff = if slen > 0
if slen > 0
then case indexChar8Array (ByteArray src#) soff of then case indexChar8Array (ByteArray src#) soff of
'\\' -> write2 dst doff '\\' '\\' *> go (soff + 1) (slen - 1) (doff + 2) '\\' -> write2 dst doff '\\' '\\' *> go (soff + 1) (slen - 1) (doff + 2)
'\"' -> write2 dst doff '\\' '\"' *> go (soff + 1) (slen - 1) (doff + 2) '\"' -> write2 dst doff '\\' '\"' *> go (soff + 1) (slen - 1) (doff + 2)
c -> c -> if c >= '\x20'
if c >= '\x20'
then PM.writeByteArray dst doff (c2w c) *> go (soff + 1) (slen - 1) (doff + 1) then PM.writeByteArray dst doff (c2w c) *> go (soff + 1) (slen - 1) (doff + 1)
else case c of else case c of
'\n' -> write2 dst doff '\\' 'n' *> go (soff + 1) (slen - 1) (doff + 2) '\n' -> write2 dst doff '\\' 'n' *> go (soff + 1) (slen - 1) (doff + 2)
'\r' -> write2 dst doff '\\' 'r' *> go (soff + 1) (slen - 1) (doff + 2) '\r' -> write2 dst doff '\\' 'r' *> go (soff + 1) (slen - 1) (doff + 2)
'\t' -> write2 dst doff '\\' 't' *> go (soff + 1) (slen - 1) (doff + 2) '\t' -> write2 dst doff '\\' 't' *> go (soff + 1) (slen - 1) (doff + 2)
'\b' -> write2 dst doff '\\' 'b' *> go (soff + 1) (slen - 1) (doff + 2)
'\f' -> write2 dst doff '\\' 'f' *> go (soff + 1) (slen - 1) (doff + 2)
_ -> do _ -> do
write2 dst doff '\\' 'u' write2 dst doff '\\' 'u'
doff' <- doff' <- UnsafeBounded.pasteST
UnsafeBounded.pasteST
(Bounded.word16PaddedUpperHex (fromIntegral (c2w c))) (Bounded.word16PaddedUpperHex (fromIntegral (c2w c)))
dst dst (doff + 2)
(doff + 2)
go (soff + 1) (slen - 1) doff' go (soff + 1) (slen - 1) doff'
else pure doff else pure doff
doffRes <- go (I# soff0#) (I# slen0#) (doff0 + 1) doffRes <- go (I# soff0#) (I# slen0#) (doff0 + 1)
@ -421,7 +362,7 @@ pasteUtf8TextJson# src# soff0# slen0# dst# doff0# s0# =
in (# s1, dstFinal #) in (# s1, dstFinal #)
c2w :: Char -> Word8 c2w :: Char -> Word8
{-# INLINE c2w #-} {-# inline c2w #-}
c2w = fromIntegral . ord c2w = fromIntegral . ord
-- Internal. Write two characters in the ASCII plane to a byte array. -- Internal. Write two characters in the ASCII plane to a byte array.
@ -431,5 +372,5 @@ write2 marr ix a b = do
PM.writeByteArray marr (ix + 1) (c2w b) PM.writeByteArray marr (ix + 1) (c2w b)
indexChar8Array :: ByteArray -> Int -> Char indexChar8Array :: ByteArray -> Int -> Char
{-# INLINE indexChar8Array #-} {-# inline indexChar8Array #-}
indexChar8Array (ByteArray b) (I# i) = C# (Exts.indexCharArray# b i) indexChar8Array (ByteArray b) (I# i) = C# (Exts.indexCharArray# b i)

View file

@ -1,58 +1,53 @@
{-# LANGUAGE BangPatterns #-} {-# language BangPatterns #-}
{-# LANGUAGE NumericUnderscores #-} {-# language NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-} {-# language OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-} {-# language QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# language ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-} {-# language TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-orphans #-}
import Prelude hiding (replicate) import Prelude hiding (replicate)
-- liftA2 is needed by GHC 9.4
import Control.Applicative (liftA2) import Control.Applicative (liftA2)
import Control.Monad.ST (runST) import Control.Monad.ST (runST)
import Data.Bytes.Builder import Data.Bytes.Builder
import Data.Bytes.Types (MutableBytes (MutableBytes)) import Data.Bytes.Builder.Template (bldr)
import Data.Char (chr, ord) import Data.Bytes.Types (MutableBytes(MutableBytes))
import Data.IORef (IORef, newIORef, readIORef, writeIORef) import Data.Char (ord,chr)
import Data.Primitive (ByteArray, PrimArray) import Data.IORef (IORef,newIORef,readIORef,writeIORef)
import Data.WideWord (Word128 (Word128), Word256 (Word256)) import Data.Maybe (fromMaybe)
import Data.Primitive (ByteArray)
import Data.Primitive (PrimArray)
import Data.Text.Short (ShortText)
import Data.WideWord (Word128(Word128),Word256(Word256))
import Data.Word import Data.Word
import Numeric.Natural (Natural) import Numeric.Natural (Natural)
import Test.QuickCheck (Arbitrary, (===)) import Test.QuickCheck ((===),Arbitrary)
import Test.QuickCheck.Instances.Natural () import Test.QuickCheck.Instances.Natural ()
import Test.Tasty (TestTree, defaultMain, testGroup) import Test.Tasty (defaultMain,testGroup,TestTree)
import Test.Tasty.HUnit ((@=?)) import Test.Tasty.HUnit ((@=?))
import Text.Printf (printf) import Text.Printf (printf)
#ifdef QUOTER
import Data.Bytes.Builder.Template (bldr)
import Data.Maybe (fromMaybe)
import Data.Text.Short (ShortText)
#endif
import qualified Arithmetic.Nat as Nat import qualified Arithmetic.Nat as Nat
import qualified Data.Bits as Bits import qualified Data.Bits as Bits
import qualified Data.Bytes as Bytes
import qualified Data.Bytes.Builder as Builder
import qualified Data.Bytes.Builder.Bounded as Bounded
import qualified Data.Bytes.Chunks as Chunks
import qualified Data.Bytes.Text.Ascii as Ascii
import qualified Data.Bytes.Text.Latin1 as Latin1
import qualified Data.ByteString as ByteString import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy.Char8 as LB import qualified Data.ByteString.Lazy.Char8 as LB
import qualified Data.Bytes as Bytes
import qualified Data.Bytes.Builder.Bounded as Bounded
import qualified Data.Bytes.Chunks as Chunks
import qualified Data.Bytes.Text.Latin1 as Latin1
import qualified Data.List as L import qualified Data.List as L
import qualified Data.Primitive as PM import qualified Data.Primitive as PM
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as TE import qualified Data.Text.Encoding as TE
import qualified GHC.Exts as Exts import qualified GHC.Exts as Exts
import qualified Prelude
import qualified Test.Tasty.HUnit as THU import qualified Test.Tasty.HUnit as THU
import qualified Test.Tasty.QuickCheck as TQC import qualified Test.Tasty.QuickCheck as TQC
import qualified Prelude
#ifdef QUOTER
import qualified Data.Bytes.Builder as Builder
import qualified Data.Bytes.Text.Ascii as Ascii
#endif
import qualified HexWord64 import qualified HexWord64
import qualified Word16Tree import qualified Word16Tree
@ -61,79 +56,77 @@ main :: IO ()
main = defaultMain tests main = defaultMain tests
tests :: TestTree tests :: TestTree
tests = tests = testGroup "Tests"
testGroup [ testGroup "live"
"Tests"
[ testGroup
"live"
[ TQC.testProperty "word64Dec" $ \w -> [ TQC.testProperty "word64Dec" $ \w ->
runConcat 1 (word64Dec w) === pack (show w) runConcat 1 (word64Dec w) === pack (show w)
, TQC.testProperty "word64Dec-x3" $ \x y z -> , TQC.testProperty "word64Dec-x3" $ \x y z ->
runConcat 1 (word64Dec x <> word64Dec y <> word64Dec z) runConcat 1 (word64Dec x <> word64Dec y <> word64Dec z)
=== pack (show x ++ show y ++ show z) ===
pack (show x ++ show y ++ show z)
, TQC.testProperty "int64Dec-x3" $ \x y z -> , TQC.testProperty "int64Dec-x3" $ \x y z ->
runConcat 1 (int64Dec x <> int64Dec y <> int64Dec z) runConcat 1 (int64Dec x <> int64Dec y <> int64Dec z)
=== pack (show x ++ show y ++ show z) ===
pack (show x ++ show y ++ show z)
, TQC.testProperty "word64BE-x3" $ \x y z -> , TQC.testProperty "word64BE-x3" $ \x y z ->
runConcat 1 (word64BE x <> word64BE y <> word64BE z) runConcat 1 (word64BE x <> word64BE y <> word64BE z)
=== pack (LB.unpack (BB.toLazyByteString (BB.word64BE x <> BB.word64BE y <> BB.word64BE z))) ===
pack (LB.unpack (BB.toLazyByteString (BB.word64BE x <> BB.word64BE y <> BB.word64BE z)))
, TQC.testProperty "word256PaddedLowerHex" $ \w -> , TQC.testProperty "word256PaddedLowerHex" $ \w ->
Bounded.run Nat.constant (Bounded.word256PaddedLowerHex w) Bounded.run Nat.constant (Bounded.word256PaddedLowerHex w)
=== pack (showWord256PaddedLowerHex w) ===
pack (showWord256PaddedLowerHex w)
, TQC.testProperty "word128PaddedUpperHex" $ \w -> , TQC.testProperty "word128PaddedUpperHex" $ \w ->
Bounded.run Nat.constant (Bounded.word128PaddedUpperHex w) Bounded.run Nat.constant (Bounded.word128PaddedUpperHex w)
=== pack (showWord128PaddedUpperHex w) ===
pack (showWord128PaddedUpperHex w)
, TQC.testProperty "word64PaddedUpperHex" $ \w -> , TQC.testProperty "word64PaddedUpperHex" $ \w ->
runConcat 1 (word64PaddedUpperHex w) runConcat 1 (word64PaddedUpperHex w)
=== pack (showWord64PaddedUpperHex w) ===
pack (showWord64PaddedUpperHex w)
, TQC.testProperty "word16PaddedLowerHex" $ \w -> , TQC.testProperty "word16PaddedLowerHex" $ \w ->
runConcat 1 (word16PaddedLowerHex w) runConcat 1 (word16PaddedLowerHex w)
=== pack (showWord16PaddedLowerHex w) ===
, TQC.testProperty "wordPaddedDec2" $ TQC.forAll (TQC.choose (0, 99)) $ \w -> pack (showWord16PaddedLowerHex w)
, TQC.testProperty "wordPaddedDec2" $ TQC.forAll (TQC.choose (0,99)) $ \w ->
Bounded.run Nat.two (Bounded.wordPaddedDec2 w) Bounded.run Nat.two (Bounded.wordPaddedDec2 w)
=== pack (zeroPadL 2 (show w)) ===
, TQC.testProperty "wordPaddedDec4" $ TQC.forAll (TQC.choose (0, 9999)) $ \w -> pack (zeroPadL 2 (show w))
, TQC.testProperty "wordPaddedDec4" $ TQC.forAll (TQC.choose (0,9999)) $ \w ->
Bounded.run Nat.constant (Bounded.wordPaddedDec4 w) Bounded.run Nat.constant (Bounded.wordPaddedDec4 w)
=== pack (zeroPadL 4 (show w)) ===
, TQC.testProperty "wordPaddedDec9" $ TQC.forAll (TQC.choose (0, 999999999)) $ \w -> pack (zeroPadL 4 (show w))
, TQC.testProperty "wordPaddedDec9" $ TQC.forAll (TQC.choose (0,999999999)) $ \w ->
Bounded.run Nat.constant (Bounded.wordPaddedDec9 w) Bounded.run Nat.constant (Bounded.wordPaddedDec9 w)
=== pack (zeroPadL 9 (show w)) ===
pack (zeroPadL 9 (show w))
, TQC.testProperty "word8Dec" $ \w -> , TQC.testProperty "word8Dec" $ \w ->
runConcat 1 (word8Dec w) runConcat 1 (word8Dec w)
=== pack (show w) ===
pack (show w)
, TQC.testProperty "consLength32BE" $ \w -> , TQC.testProperty "consLength32BE" $ \w ->
runConcat 1 (consLength32BE (word8Dec w)) runConcat 1 (consLength32BE (word8Dec w))
=== pack ('\x00' : '\x00' : '\x00' : chr (L.length (show w)) : show w) ===
pack ('\x00' : '\x00' : '\x00' : chr (L.length (show w)) : show w)
, TQC.testProperty "consLength64BE-uni" $ \w -> , TQC.testProperty "consLength64BE-uni" $ \w ->
pack pack
( '\x00' ( '\x00' : '\x00' : '\x00' : '\x00'
: '\x00' : '\x00' : '\x00' : '\x00' : chr (L.length (show w))
: '\x00'
: '\x00'
: '\x00'
: '\x00'
: '\x00'
: chr (L.length (show w))
: show w : show w
) )
=== runConcat 1 (consLength64BE (word16Dec w)) ===
runConcat 1 (consLength64BE (word16Dec w))
, TQC.testProperty "consLength64BE-multi" $ \w -> , TQC.testProperty "consLength64BE-multi" $ \w ->
pack pack
( '\x00' ( '\x00' : '\x00' : '\x00' : '\x00'
: '\x00' : '\x00' : '\x00' : '\x00' : chr (1 + L.length (show w))
: '\x00' : '\x42' : show w
: '\x00'
: '\x00'
: '\x00'
: '\x00'
: chr (1 + L.length (show w))
: '\x42'
: show w
) )
=== runConcat 1 (consLength64BE (word8 0x42 <> flush 2 <> word16Dec w)) ===
runConcat 1 (consLength64BE (word8 0x42 <> flush 2 <> word16Dec w))
, THU.testCase "stringUtf8" $ , THU.testCase "stringUtf8" $
packUtf8 "¿Cómo estás? I am doing well." packUtf8 "¿Cómo estás? I am doing well." @=?
@=? runConcat 1 (stringUtf8 "¿Cómo estás? I am doing well.") runConcat 1 (stringUtf8 "¿Cómo estás? I am doing well.")
, THU.testCase "doubleDec-A" $ , THU.testCase "doubleDec-A" $
pack (show (2 :: Int)) @=? runConcat 1 (doubleDec 2.0) pack (show (2 :: Int)) @=? runConcat 1 (doubleDec 2.0)
, THU.testCase "doubleDec-B" $ , THU.testCase "doubleDec-B" $
@ -177,17 +170,13 @@ tests =
, THU.testCase "shortTextJsonString-D" $ , THU.testCase "shortTextJsonString-D" $
pack ("\"Hi\\u001BLo\"") @=? runConcat 1 (shortTextJsonString "Hi\ESCLo") pack ("\"Hi\\u001BLo\"") @=? runConcat 1 (shortTextJsonString "Hi\ESCLo")
, THU.testCase "word-16-tree" $ , THU.testCase "word-16-tree" $
Word16Tree.expectedSmall Word16Tree.expectedSmall @=? runConcat 1
@=? runConcat
1
(Word16Tree.encode Word16Tree.exampleSmall) (Word16Tree.encode Word16Tree.exampleSmall)
, THU.testCase "byteArray-small" $ , THU.testCase "byteArray-small" $
let a = replicateByte 3 0x50 let a = replicateByte 3 0x50
b = replicateByte 5 0x51 b = replicateByte 5 0x51
in mconcat [a, b] in mconcat [a,b] @=? runConcat 1
@=? runConcat ( byteArray a <> byteArray b )
1
(byteArray a <> byteArray b)
, THU.testCase "byteArray-big" $ , THU.testCase "byteArray-big" $
let a = replicateByte 2105 0x50 let a = replicateByte 2105 0x50
b = replicateByte 725 0x51 b = replicateByte 725 0x51
@ -198,87 +187,93 @@ tests =
g = replicateByte 975 0x56 g = replicateByte 975 0x56
h = replicateByte 3000 0x57 h = replicateByte 3000 0x57
i = replicateByte 125 0x58 i = replicateByte 125 0x58
in mconcat [a, b, c, d, e, f, g, h, i] in mconcat [a,b,c,d,e,f,g,h,i] @=? runConcat 1
@=? runConcat ( byteArray a <> byteArray b <> byteArray c <>
1 byteArray d <> byteArray e <> byteArray f <>
( byteArray a byteArray g <> byteArray h <> byteArray i
<> byteArray b
<> byteArray c
<> byteArray d
<> byteArray e
<> byteArray f
<> byteArray g
<> byteArray h
<> byteArray i
) )
, TQC.testProperty "word16ArrayLE" $ \(xs :: [Word16]) -> , TQC.testProperty "word16ArrayLE" $ \(xs :: [Word16]) ->
let ys = Exts.fromList xs :: PrimArray Word16 let ys = Exts.fromList xs :: PrimArray Word16
in runConcat 1 (foldMap word16LE xs) in runConcat 1 (foldMap word16LE xs)
=== runConcat 1 (word16ArrayLE ys 0 (Prelude.length xs)) ===
runConcat 1 (word16ArrayLE ys 0 (Prelude.length xs))
, TQC.testProperty "word16ArrayBE" $ \(xs :: [Word16]) -> , TQC.testProperty "word16ArrayBE" $ \(xs :: [Word16]) ->
let ys = Exts.fromList xs :: PrimArray Word16 let ys = Exts.fromList xs :: PrimArray Word16
in runConcat 1 (foldMap word16BE xs) in runConcat 1 (foldMap word16BE xs)
=== runConcat 1 (word16ArrayBE ys 0 (Prelude.length xs)) ===
runConcat 1 (word16ArrayBE ys 0 (Prelude.length xs))
, TQC.testProperty "word32ArrayLE" $ \(xs :: [Word32]) -> , TQC.testProperty "word32ArrayLE" $ \(xs :: [Word32]) ->
let ys = Exts.fromList xs :: PrimArray Word32 let ys = Exts.fromList xs :: PrimArray Word32
in runConcat 1 (foldMap word32LE xs) in runConcat 1 (foldMap word32LE xs)
=== runConcat 1 (word32ArrayLE ys 0 (Prelude.length xs)) ===
runConcat 1 (word32ArrayLE ys 0 (Prelude.length xs))
, TQC.testProperty "word32ArrayBE" $ \(xs :: [Word32]) -> , TQC.testProperty "word32ArrayBE" $ \(xs :: [Word32]) ->
let ys = Exts.fromList xs :: PrimArray Word32 let ys = Exts.fromList xs :: PrimArray Word32
in runConcat 1 (foldMap word32BE xs) in runConcat 1 (foldMap word32BE xs)
=== runConcat 1 (word32ArrayBE ys 0 (Prelude.length xs)) ===
runConcat 1 (word32ArrayBE ys 0 (Prelude.length xs))
, TQC.testProperty "word64ArrayLE" $ \(xs :: [Word64]) -> , TQC.testProperty "word64ArrayLE" $ \(xs :: [Word64]) ->
let ys = Exts.fromList xs :: PrimArray Word64 let ys = Exts.fromList xs :: PrimArray Word64
in runConcat 1 (foldMap word64LE xs) in runConcat 1 (foldMap word64LE xs)
=== runConcat 1 (word64ArrayLE ys 0 (Prelude.length xs)) ===
runConcat 1 (word64ArrayLE ys 0 (Prelude.length xs))
, TQC.testProperty "word64ArrayBE" $ \(xs :: [Word64]) -> , TQC.testProperty "word64ArrayBE" $ \(xs :: [Word64]) ->
let ys = Exts.fromList xs :: PrimArray Word64 let ys = Exts.fromList xs :: PrimArray Word64
in runConcat 1 (foldMap word64BE xs) in runConcat 1 (foldMap word64BE xs)
=== runConcat 1 (word64ArrayBE ys 0 (Prelude.length xs)) ===
runConcat 1 (word64ArrayBE ys 0 (Prelude.length xs))
, TQC.testProperty "word128ArrayLE" $ \(xs :: [Word128]) -> , TQC.testProperty "word128ArrayLE" $ \(xs :: [Word128]) ->
let ys = Exts.fromList xs :: PrimArray Word128 let ys = Exts.fromList xs :: PrimArray Word128
in runConcat 1 (foldMap word128LE xs) in runConcat 1 (foldMap word128LE xs)
=== runConcat 1 (word128ArrayLE ys 0 (Prelude.length xs)) ===
runConcat 1 (word128ArrayLE ys 0 (Prelude.length xs))
, TQC.testProperty "word128ArrayBE" $ \(xs :: [Word128]) -> , TQC.testProperty "word128ArrayBE" $ \(xs :: [Word128]) ->
let ys = Exts.fromList xs :: PrimArray Word128 let ys = Exts.fromList xs :: PrimArray Word128
in runConcat 1 (foldMap word128BE xs) in runConcat 1 (foldMap word128BE xs)
=== runConcat 1 (word128ArrayBE ys 0 (Prelude.length xs)) ===
runConcat 1 (word128ArrayBE ys 0 (Prelude.length xs))
, TQC.testProperty "word256ArrayLE" $ \(xs :: [Word256]) -> , TQC.testProperty "word256ArrayLE" $ \(xs :: [Word256]) ->
let ys = Exts.fromList xs :: PrimArray Word256 let ys = Exts.fromList xs :: PrimArray Word256
in runConcat 1 (foldMap word256LE xs) in runConcat 1 (foldMap word256LE xs)
=== runConcat 1 (word256ArrayLE ys 0 (Prelude.length xs)) ===
runConcat 1 (word256ArrayLE ys 0 (Prelude.length xs))
, TQC.testProperty "word256ArrayBE" $ \(xs :: [Word256]) -> , TQC.testProperty "word256ArrayBE" $ \(xs :: [Word256]) ->
let ys = Exts.fromList xs :: PrimArray Word256 let ys = Exts.fromList xs :: PrimArray Word256
in runConcat 1 (foldMap word256BE xs) in runConcat 1 (foldMap word256BE xs)
=== runConcat 1 (word256ArrayBE ys 0 (Prelude.length xs)) ===
runConcat 1 (word256ArrayBE ys 0 (Prelude.length xs))
, TQC.testProperty "word64Vlq" $ \(x :: Word64) -> , TQC.testProperty "word64Vlq" $ \(x :: Word64) ->
runConcat 1 (word64Vlq x) runConcat 1 (word64Vlq x)
=== naiveVlq (fromIntegral x) ===
naiveVlq (fromIntegral x)
, TQC.testProperty "word64LEB128" $ \(x :: Word64) -> , TQC.testProperty "word64LEB128" $ \(x :: Word64) ->
runConcat 1 (word64LEB128 x) runConcat 1 (word64LEB128 x)
=== naiveLeb128 (fromIntegral x) ===
naiveLeb128 (fromIntegral x)
, TQC.testProperty "naturalDec-A" $ \(x :: Natural) -> , TQC.testProperty "naturalDec-A" $ \(x :: Natural) ->
runConcat 1 (naturalDec x) runConcat 1 (naturalDec x)
=== pack (show x) ===
pack (show x)
, TQC.testProperty "naturalDec-B" $ \(x :: Natural) -> , TQC.testProperty "naturalDec-B" $ \(x :: Natural) ->
let y = 1234567892345678934678987654321 * x let y = 1234567892345678934678987654321 * x in
in runConcat 1 (naturalDec y) runConcat 1 (naturalDec y)
=== pack (show y) ===
, testGroup pack (show y)
"leb128-encoding" , testGroup "leb128-encoding"
[ THU.testCase "16" $ [ THU.testCase "16" $
Chunks.concat (run 16 (word64LEB128 16)) Chunks.concat (run 16 (word64LEB128 16))
@=? Latin1.fromString "\x10" @=?
Latin1.fromString "\x10"
, THU.testCase "1000000" $ , THU.testCase "1000000" $
Chunks.concat (run 16 (word64LEB128 1000000)) Chunks.concat (run 16 (word64LEB128 1000000))
@=? Exts.fromList [0xc0, 0x84, 0x3d] @=?
Exts.fromList [0xc0,0x84,0x3d]
, THU.testCase "deadbeef-smile" $ do , THU.testCase "deadbeef-smile" $ do
let inp = Latin1.fromString "\xDE\xAD\xBE\xEF" let inp = Latin1.fromString "\xDE\xAD\xBE\xEF"
(Chunks.concat . run 16) (sevenEightSmile inp) (Chunks.concat . run 16) (sevenEightSmile inp)
@=? Latin1.fromString "\x6F\x2B\x37\x6E\x0F" @=?Latin1.fromString "\x6F\x2B\x37\x6E\x0F"
] ]
, testGroup , testGroup "seven/eight encoding"
"seven/eight encoding"
[ THU.testCase "deadbeef" $ do [ THU.testCase "deadbeef" $ do
let inp = Latin1.fromString "\xDE\xAD\xBE\xEF" let inp = Latin1.fromString "\xDE\xAD\xBE\xEF"
(Chunks.concat . run 16) (sevenEightRight inp) (Chunks.concat . run 16) (sevenEightRight inp)
@ -286,21 +281,19 @@ tests =
, THU.testCase "deadbeef-smile" $ do , THU.testCase "deadbeef-smile" $ do
let inp = Latin1.fromString "\xDE\xAD\xBE\xEF" let inp = Latin1.fromString "\xDE\xAD\xBE\xEF"
(Chunks.concat . run 16) (sevenEightSmile inp) (Chunks.concat . run 16) (sevenEightSmile inp)
@=? Latin1.fromString "\x6F\x2B\x37\x6E\x0F" @=?Latin1.fromString "\x6F\x2B\x37\x6E\x0F"
] ]
] ]
, testGroup , testGroup "alternate"
"alternate"
[ TQC.testProperty "HexWord64" $ \x y -> [ TQC.testProperty "HexWord64" $ \x y ->
runConcat runConcat 1
1
( fromBounded Nat.constant (HexWord64.word64PaddedUpperHex x) ( fromBounded Nat.constant (HexWord64.word64PaddedUpperHex x)
<> fromBounded Nat.constant (HexWord64.word64PaddedUpperHex y) <> fromBounded Nat.constant (HexWord64.word64PaddedUpperHex y)
) )
=== pack (showWord64PaddedUpperHex x <> showWord64PaddedUpperHex y) ===
pack (showWord64PaddedUpperHex x <> showWord64PaddedUpperHex y)
] ]
, testGroup , testGroup "putMany"
"putMany"
[ THU.testCase "A" $ do [ THU.testCase "A" $ do
ref <- newIORef [] ref <- newIORef []
let txt = "hello_world_are_you_listening" :: [Char] let txt = "hello_world_are_you_listening" :: [Char]
@ -312,32 +305,23 @@ tests =
, map c2w "are_yo" , map c2w "are_yo"
, map c2w "u_list" , map c2w "u_list"
, map c2w "ening" , map c2w "ening"
] @=? map Exts.toList (Exts.toList res)
] ]
@=? map Exts.toList (Exts.toList res) , testGroup "putManyConsLength"
]
, testGroup
"putManyConsLength"
[ THU.testCase "A" $ do [ THU.testCase "A" $ do
ref <- newIORef [] ref <- newIORef []
let txt = "hello_world_are_you_listening" :: [Char] let txt = "hello_world_are_you_listening" :: [Char]
putManyConsLength putManyConsLength Nat.constant
Nat.constant
(\n -> Bounded.word16BE (fromIntegral n)) (\n -> Bounded.word16BE (fromIntegral n))
16 16 ascii txt (bytesOntoRef ref)
ascii
txt
(bytesOntoRef ref)
res <- readIORef ref res <- readIORef ref
id $ id $
[ 0x00 : 0x0A : map c2w "hello_worl" [ 0x00 : 0x0A : map c2w "hello_worl"
, 0x00 : 0x0A : map c2w "d_are_you_" , 0x00 : 0x0A : map c2w "d_are_you_"
, 0x00 : 0x09 : map c2w "listening" , 0x00 : 0x09 : map c2w "listening"
] @=? map Exts.toList (Exts.toList res)
] ]
@=? map Exts.toList (Exts.toList res) , testGroup "bytes templates"
]
#ifdef QUOTER
, testGroup
"bytes templates"
[ THU.testCase "A" $ do [ THU.testCase "A" $ do
let name = Just ("foo" :: ShortText) let name = Just ("foo" :: ShortText)
msgBuilder = [bldr|Hello `fromMaybe "World" name`!\n|] msgBuilder = [bldr|Hello `fromMaybe "World" name`!\n|]
@ -359,13 +343,12 @@ tests =
msg = Chunks.concat . Builder.run 200 $ msgBuilder msg = Chunks.concat . Builder.run 200 $ msgBuilder
in Ascii.fromString "there are 137 lights!" @=? msg in Ascii.fromString "there are 137 lights!" @=? msg
] ]
#endif
] ]
bytesOntoRef :: bytesOntoRef ::
IORef [PM.ByteArray] -> IORef [PM.ByteArray]
MutableBytes Exts.RealWorld -> -> MutableBytes Exts.RealWorld
IO () -> IO ()
bytesOntoRef !ref (MutableBytes buf off len) = do bytesOntoRef !ref (MutableBytes buf off len) = do
rs <- readIORef ref rs <- readIORef ref
dst <- PM.newByteArray len dst <- PM.newByteArray len
@ -408,8 +391,7 @@ newtype AsciiByteArray = AsciiByteArray ByteArray
deriving (Eq) deriving (Eq)
instance Show AsciiByteArray where instance Show AsciiByteArray where
show (AsciiByteArray b) = show (AsciiByteArray b) = if Bytes.all (\w -> w >= 32 && w < 127) (Bytes.fromByteArray b)
if Bytes.all (\w -> w >= 32 && w < 127) (Bytes.fromByteArray b)
then Latin1.toString (Bytes.fromByteArray b) then Latin1.toString (Bytes.fromByteArray b)
else show (show b) else show (show b)
@ -429,10 +411,9 @@ naiveLeb128 x =
Bytes.toByteArray (Bytes.unsafeDrop 1 (Exts.fromList (0xFF : go [] x))) Bytes.toByteArray (Bytes.unsafeDrop 1 (Exts.fromList (0xFF : go [] x)))
where where
go !xs !n = go !xs !n =
let (q, r) = quotRem n 128 let (q,r) = quotRem n 128
r' = fromIntegral @Natural @Word8 r r' = fromIntegral @Natural @Word8 r
w = w = if q == 0
if q == 0
then r' then r'
else Bits.setBit r' 7 else Bits.setBit r' 7
xs' = w : xs xs' = w : xs
@ -445,7 +426,7 @@ naiveVlq x =
Bytes.toByteArray (Bytes.unsafeDrop 1 (Exts.fromList (0xFF : go [] x))) Bytes.toByteArray (Bytes.unsafeDrop 1 (Exts.fromList (0xFF : go [] x)))
where where
go !xs !n = go !xs !n =
let (q, r) = quotRem n 128 let (q,r) = quotRem n 128
r' = fromIntegral @Natural @Word8 r r' = fromIntegral @Natural @Word8 r
w = case xs of w = case xs of
[] -> r' [] -> r'