on master help sign in
Allow building with GHC 7.4/7.6's bundled bytestring using bytestring-builder
commitb4927b4435d014b533c6f0b1c4ef5e8e6f7639c9 authorRyan Scott <ryan.gl.scott@gmail.com> dateWed 8 Jun 2016, 22:00 (10 years ago) parent569c1d9
3 files changed+114−12
Data/Aeson/Encoding/Builder.hs+75−3 aeson.cabal+20−5 benchmarks/aeson-benchmarks.cabal+19−4
Data/Aeson/Encoding/Builder.hs+75−3
@@ -1,4 +1,4 @@
1−{-# LANGUAGE BangPatterns, OverloadedStrings #-}
1+{-# LANGUAGE BangPatterns, CPP, OverloadedStrings #-}
22
33-- |
44-- Module: Data.Aeson.Encoding.Builder
@@ -52,9 +52,22 @@ import Data.Time.LocalTime
5252import Data.Word (Word8)
5353import qualified Data.HashMap.Strict as HMS
5454import qualified Data.Text as T
55−import qualified Data.Text.Encoding as TE
5655import qualified Data.Vector as V
5756
57+#if MIN_VERSION_bytestring(0,10,4)
58+import Data.Text.Encoding (encodeUtf8BuilderEscaped)
59+#else
60+import Data.Bits ((.&.))
61+import Data.Text.Internal (Text(..))
62+import Data.Text.Internal.Unsafe.Shift (shiftR)
63+import Foreign.Ptr (minusPtr, plusPtr)
64+import Foreign.Storable (poke)
65+import qualified Data.ByteString.Builder.Internal as B
66+import qualified Data.ByteString.Builder.Prim.Internal as BP
67+import qualified Data.Text.Array as A
68+import qualified Data.Text.Internal.Encoding.Utf16 as U16
69+#endif
70+
5871-- | Encode a JSON value to a "Data.ByteString" 'B.Builder'.
5972--
6073-- Use this function if you are encoding over the wire, or need to
@@ -101,7 +114,7 @@ text t = B.char8 '"' <> unquoted t <> B.char8 '"'
101114
102115-- | Encode a JSON string, without enclosing quotes.
103116unquoted :: T.Text -> Builder
104−unquoted = TE.encodeUtf8BuilderEscaped escapeAscii
117+unquoted = encodeUtf8BuilderEscaped escapeAscii
105118
106119-- | Add quotes surrounding a builder
107120quote :: Builder -> Builder
@@ -254,3 +267,62 @@ twoDigits a = T (digit hi) (digit lo)
254267
255268digit :: Int -> Char
256269digit x = chr (x + 48)
270+
271+#if !(MIN_VERSION_bytestring(0,10,4))
272+-- | Encode text using UTF-8 encoding and escape the ASCII characters using
273+-- a 'BP.BoundedPrim'.
274+--
275+-- Use this function is to implement efficient encoders for text-based formats
276+-- like JSON or HTML.
277+{-# INLINE encodeUtf8BuilderEscaped #-}
278+-- TODO: Extend documentation with references to source code in @blaze-html@
279+-- or @aeson@ that uses this function.
280+encodeUtf8BuilderEscaped :: BP.BoundedPrim Word8 -> Text -> B.Builder
281+encodeUtf8BuilderEscaped be =
282+ -- manual eta-expansion to ensure inlining works as expected
283+ \txt -> B.builder (mkBuildstep txt)
284+ where
285+ bound = max 4 $ BP.sizeBound be
286+
287+ mkBuildstep (Text arr off len) !k =
288+ outerLoop off
289+ where
290+ iend = off + len
291+
292+ outerLoop !i0 !br@(B.BufferRange op0 ope)
293+ | i0 >= iend = k br
294+ | outRemaining > 0 = goPartial (i0 + min outRemaining inpRemaining)
295+ -- TODO: Use a loop with an integrated bound's check if outRemaining
296+ -- is smaller than 8, as this will save on divisions.
297+ | otherwise = return $ B.bufferFull bound op0 (outerLoop i0)
298+ where
299+ outRemaining = (ope `minusPtr` op0) `div` bound
300+ inpRemaining = iend - i0
301+
302+ goPartial !iendTmp = go i0 op0
303+ where
304+ go !i !op
305+ | i < iendTmp = case A.unsafeIndex arr i of
306+ w | w <= 0x7F -> do
307+ BP.runB be (fromIntegral w) op >>= go (i + 1)
308+ | w <= 0x7FF -> do
309+ poke8 0 $ (w `shiftR` 6) + 0xC0
310+ poke8 1 $ (w .&. 0x3f) + 0x80
311+ go (i + 1) (op `plusPtr` 2)
312+ | 0xD800 <= w && w <= 0xDBFF -> do
313+ let c = ord $ U16.chr2 w (A.unsafeIndex arr (i+1))
314+ poke8 0 $ (c `shiftR` 18) + 0xF0
315+ poke8 1 $ ((c `shiftR` 12) .&. 0x3F) + 0x80
316+ poke8 2 $ ((c `shiftR` 6) .&. 0x3F) + 0x80
317+ poke8 3 $ (c .&. 0x3F) + 0x80
318+ go (i + 2) (op `plusPtr` 4)
319+ | otherwise -> do
320+ poke8 0 $ (w `shiftR` 12) + 0xE0
321+ poke8 1 $ ((w `shiftR` 6) .&. 0x3F) + 0x80
322+ poke8 2 $ (w .&. 0x3F) + 0x80
323+ go (i + 1) (op `plusPtr` 3)
324+ | otherwise =
325+ outerLoop i (B.BufferRange op ope)
326+ where
327+ poke8 j v = poke (op `plusPtr` j) (fromIntegral v :: Word8)
328+#endif
aeson.cabal+20−5
@@ -58,6 +58,11 @@ flag developer
5858 default: False
5959 manual: True
6060
61+flag bytestring-builder
62+ description: Depend on the bytestring-builder package for backwards compatibility.
63+ default: False
64+ manual: False
65+
6166library
6267 default-language: Haskell2010
6368
@@ -92,13 +97,12 @@ library
9297 attoparsec >= 0.13.0.1,
9398 base >= 4.5 && < 5,
9499 base-compat >= 0.9.1 && < 0.10,
95− bytestring >= 0.10.4.0,
96100 containers >= 0.2.4.1,
97101 deepseq >= 1.3,
98102 dlist >= 0.2,
99103 ghc-prim >= 0.2,
100104 hashable >= 1.1.2.0,
101− scientific >= 0.3.1 && < 0.4,
105+ scientific >= 0.3.4.7 && < 0.4,
102106 tagged >=0.8.3 && <0.9,
103107 template-haskell >= 2.7,
104108 text >= 1.1.1.0,
@@ -107,10 +111,16 @@ library
107111 unordered-containers >= 0.2.5.0,
108112 vector >= 0.8
109113
114+ if flag(bytestring-builder)
115+ build-depends: bytestring >= 0.9 && < 0.10.4,
116+ bytestring-builder >= 0.10.4 && < 1
117+ else
118+ build-depends: bytestring >= 0.10.4
119+
110120 if !impl(ghc >= 8.0)
111121 -- `Data.Semigroup` and `Control.Monad.Fail` and `Control.Monad.IO.Class` are available in base only since GHC 8.0 / base 4.9
112122 build-depends:
113− semigroups >= 0.16.1 && < 0.19,
123+ semigroups >= 0.18.2 && < 0.19,
114124 transformers >= 0.2.2.0,
115125 transformers-compat >= 0.3,
116126 fail == 4.9.*
@@ -156,7 +166,6 @@ test-suite tests
156166 base,
157167 base-compat,
158168 base-orphans >= 0.5.3 && <0.6,
159− bytestring,
160169 containers,
161170 dlist,
162171 generic-deriving >= 1.10 && < 1.11,
@@ -175,9 +184,15 @@ test-suite tests
175184 vector,
176185 quickcheck-instances >=0.3.12
177186
187+ if flag(bytestring-builder)
188+ build-depends: bytestring >= 0.9 && < 0.10.4,
189+ bytestring-builder >= 0.10.4 && < 1
190+ else
191+ build-depends: bytestring >= 0.10.4
192+
178193 if !impl(ghc >= 8.0)
179194 build-depends:
180− semigroups >= 0.16.1 && < 0.19,
195+ semigroups >= 0.18.2 && < 0.19,
181196 transformers >= 0.2.2.0,
182197 transformers-compat >= 0.3
183198
benchmarks/aeson-benchmarks.cabal+19−4
@@ -4,6 +4,11 @@ build-type: Simple
44
55cabal-version: >=1.8
66
7+flag bytestring-builder
8+ description: Depend on the bytestring-builder package for backwards compatibility.
9+ default: False
10+ manual: False
11+
712library
813 hs-source-dirs: .. .
914
@@ -34,7 +39,6 @@ library
3439 base == 4.*,
3540 base-compat >= 0.9.1 && <0.10,
3641 time-locale-compat >=0.1.1 && <0.2,
37− bytestring >= 0.10.4.0,
3842 containers,
3943 deepseq,
4044 dlist >= 0.2,
@@ -42,7 +46,7 @@ library
4246 ghc-prim >= 0.2,
4347 hashable >= 1.1.2.0,
4448 mtl,
45− scientific >= 0.3.1 && < 0.4,
49+ scientific >= 0.3.4.7 && < 0.4,
4650 syb,
4751 tagged >=0.8.3 && <0.9,
4852 template-haskell >= 2.4,
@@ -52,12 +56,18 @@ library
5256 unordered-containers >= 0.2.3.0,
5357 vector >= 0.7.1
5458
59+ if flag(bytestring-builder)
60+ build-depends: bytestring >= 0.9 && < 0.10.4,
61+ bytestring-builder >= 0.10.4 && < 1
62+ else
63+ build-depends: bytestring >= 0.10.4
64+
5565 if impl(ghc >=7.8)
5666 cpp-options: -DHAS_COERCIBLE
5767
5868 if !impl(ghc >= 8.0)
5969 -- `Data.Semigroup` is available in base only since GHC 8.0 / base 4.9
60− build-depends: semigroups >= 0.16.1 && < 0.19
70+ build-depends: semigroups >= 0.18.2 && < 0.19
6171
6272 include-dirs: ../include
6373
@@ -99,13 +109,18 @@ executable aeson-benchmark-typed
99109 build-depends:
100110 aeson-benchmarks,
101111 base,
102− bytestring,
103112 criterion >= 1.0,
104113 deepseq,
105114 ghc-prim,
106115 text,
107116 time
108117
118+ if flag(bytestring-builder)
119+ build-depends: bytestring >= 0.9 && < 0.10.4,
120+ bytestring-builder >= 0.10.4 && < 1
121+ else
122+ build-depends: bytestring >= 0.10.4
123+
109124executable aeson-benchmark-compare-with-json
110125 main-is: CompareWithJSON.hs
111126 ghc-options: -Wall -O2 -rtsopts