packages feed

text-builder 0.5.4.3 → 1.0.0.5

raw patch · 21 files changed

Files

+ CHANGELOG.md view
@@ -0,0 +1,37 @@+# 1++Major performance optimization achieved by getting rid of the "length" function. Since this was an unavoidable breaking change, other breaking changes were added as well.++Major redesign done to declutter the API from experimental features, dropping some and isolating others to "text-builder-dev" to reduce the need for further major releases of this library down the road. Order brought to naming conventions of the formatter-functions.++## Breaking changes and migration instructions++- `Text.Builder` module renamed to `TextBuilder` to fully conform to the [convention](https://www.reddit.com/r/haskell/comments/1qrilm/packages_and_namespaces_naming_convention/) of having the root namespace of the library map to its name.+- `Builder` type was renamed to `TextBuilder`.+- `run` renamed to `toText`.+- `length` removed to increase performance of the whole abstraction.+- `null` renamed to `isEmpty` to leave declarative naming only for constructor-functions.+- `putToStdOut`, `putToStdErr`, `putLnToStdOut`, `putLnToStdErr` removed to reduce the bloat of the API. Just use similar functions on `Text`.+- `padFromLeft` and `padFromRight` moved to "text-builder-dev-0.4".+- `asciiByteString` renamed to `unsafeUtf8ByteString`. ASCII is a subset of UTF-8 and the previous implementation was unsafe any way.+- `hexData` moved to "text-builder-dev-0.4" as `byteStringHexEncoding`.+- `unicodeCodePoint` renamed to `unicodeCodepoint`.+- `utf16CodeUnits1`, `utf16CodeUnits2`, `utf8CodeUnits1`, `utf8CodeUnits2`, `utf8CodeUnits3`, `utf8CodeUnits4` moved to "text-builder-core-0.1" and removed from the public API to reduce the bloat. Nobody seems to have been using them. If you need them, open a ticket in "text-builder-core".+- `unsignedDecimal` - removed because it was unsafe. Use `decimal`.+- `thousandSeparatedUnsignedDecimal` - removed because it was unsafe. Use `thousandSeparatedDecimal`.+- `dataSizeInBytesInDecimal` moved to "text-builder-dev-0.4" as `approximateDataSize`.+- `unsignedBinary` and `unsignedPaddedBinary` replaced with `binary`, which is now safe and handles negative values.+- `hexadecimal` and `unsignedHexadecimal` replaced with `hexadecimal`, which is now safe and handles negative values.+- `decimalDigit` and `hexadecimalDigit` removed. Seemed to not be useful. If you think otherwise open a ticket.+- `fixedDouble` moved to "text-builder-dev-0.4" as `doubleFixedPoint`. It does not have an efficient implementation yet is based on `printf`.+- `doublePercent` moved to "text-builder-dev-0.4" as `doubleFixedPointPercent`. Same reasons.+- `intervalInSeconds` moved to "text-builder-dev-0.4" as `diffTimeSeconds` and `picoseconds`.++## Non-breaking++- `unicodeCodepoint` is now safe. It replaces invalid codepoints with a default one.+- `char` is now safe for the same reasons.++# 0.6.8++- Migrated to the namespacing convention where the root namespace matches the package name 1-1 with no special cases. The support for previous naming convention is still provided though.
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2015, Nikita Volkov+Copyright (c) 2017, Nikita Volkov  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
+ README.md view
@@ -0,0 +1,198 @@+# Summary++Fast strict text builder and simple type-safe formatting library for Haskell.++# Performance++The benchmarks measure the monoidal composition of builders constructed from `Text` simulating left- and right-biased appends and `mconcat`.++The results show the following:++- It's 2-3 times faster than the `Data.Text.Lazy.Builder` supplied with the "text" package.+- It's 1.25 times faster than the `Data.Text.Encoding.StrictTextBuilder` which has recently arrived in "text".+- It's 1.2-2 times faster than "text-builder-linear".+- It's 1.1 times slower than `Data.Text.Text` in case of using `mconcat` over texts. In all other cases `Text` is slower and slows down exponentially, which is not surprising.++In the years of existence of this package multiple user-stories have been collected proving the performance boosts after switching to it.++```+All+  Competition+    Left-biased mappend+      100B+        TextBuilder.TextBuilder:              OK+          187  ns ±  14 ns, 960 B  allocated,   0 B  copied, 6.0 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          215  ns ±  17 ns, 1.2 KB allocated,   0 B  copied, 6.0 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          510  ns ±  27 ns, 2.6 KB allocated,   1 B  copied, 6.0 MB peak memory+        Data.Text.Text:                       OK+          216  ns ±  15 ns, 1.6 KB allocated,   0 B  copied, 6.0 MB peak memory+        Data.Text.Lazy.Text:                  OK+          424  ns ±  30 ns, 4.2 KB allocated,   2 B  copied, 6.0 MB peak memory+        Data.Text.Builder.Linear:             OK+          368  ns ±  26 ns, 1.3 KB allocated,   0 B  copied, 6.0 MB peak memory+      1kB+        TextBuilder.TextBuilder:              OK+          1.54 μs ± 144 ns, 8.3 KB allocated,   8 B  copied, 6.0 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          1.85 μs ± 131 ns,  12 KB allocated,  10 B  copied, 6.0 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          4.66 μs ± 458 ns,  24 KB allocated,  31 B  copied, 6.0 MB peak memory+        Data.Text.Text:                       OK+          3.80 μs ± 257 ns, 104 KB allocated,  85 B  copied, 6.0 MB peak memory+        Data.Text.Lazy.Text:                  OK+          24.5 μs ± 1.7 μs, 358 KB allocated, 451 B  copied, 6.0 MB peak memory+        Data.Text.Builder.Linear:             OK+          3.04 μs ± 213 ns,  12 KB allocated,   6 B  copied, 6.0 MB peak memory+      10kB+        TextBuilder.TextBuilder:              OK+          15.5 μs ± 1.2 μs,  82 KB allocated, 175 B  copied, 8.0 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          18.3 μs ± 536 ns, 121 KB allocated, 439 B  copied, 8.0 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          47.7 μs ± 3.4 μs, 242 KB allocated, 1.9 KB copied, 8.0 MB peak memory+        Data.Text.Text:                       OK+          355  μs ±  19 μs, 9.6 MB allocated,  19 KB copied,  13 MB peak memory+        Data.Text.Lazy.Text:                  OK+          2.63 ms ± 251 μs,  34 MB allocated, 264 KB copied,  13 MB peak memory+        Data.Text.Builder.Linear:             OK+          30.6 μs ± 1.7 μs, 110 KB allocated, 202 B  copied,  13 MB peak memory+      100kB+        TextBuilder.TextBuilder:              OK+          201  μs ±  14 μs, 1.0 MB allocated,  14 KB copied,  13 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          195  μs ±  19 μs, 1.2 MB allocated,  39 KB copied,  13 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          520  μs ±  30 μs, 2.4 MB allocated, 174 KB copied,  13 MB peak memory+        Data.Text.Text:                       OK+          28.1 ms ± 2.0 ms, 954 MB allocated, 476 KB copied,  25 MB peak memory+        Data.Text.Lazy.Text:                  OK+          616  ms ±  34 ms, 4.7 GB allocated, 136 MB copied,  25 MB peak memory+        Data.Text.Builder.Linear:             OK+          361  μs ±  33 μs, 1.5 MB allocated,  15 KB copied,  25 MB peak memory+    Right-biased mappend+      100B+        TextBuilder.TextBuilder:              OK+          188  ns ±  13 ns, 960 B  allocated,   0 B  copied,  25 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          231  ns ±  17 ns, 1.2 KB allocated,   0 B  copied,  25 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          500  ns ±  34 ns, 2.6 KB allocated,   1 B  copied,  25 MB peak memory+        Data.Text.Text:                       OK+          214  ns ±  18 ns, 1.6 KB allocated,   0 B  copied,  25 MB peak memory+        Data.Text.Lazy.Text:                  OK+          283  ns ±  13 ns, 1.8 KB allocated,   0 B  copied,  25 MB peak memory+        Data.Text.Builder.Linear:             OK+          363  ns ±  19 ns, 1.3 KB allocated,   0 B  copied,  25 MB peak memory+      1kB+        TextBuilder.TextBuilder:              OK+          1.59 μs ± 123 ns, 8.3 KB allocated,   7 B  copied,  25 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          1.98 μs ± 120 ns,  12 KB allocated,   9 B  copied,  25 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          4.66 μs ± 281 ns,  24 KB allocated,  30 B  copied,  25 MB peak memory+        Data.Text.Text:                       OK+          3.76 μs ±  72 ns, 104 KB allocated,  79 B  copied,  25 MB peak memory+        Data.Text.Lazy.Text:                  OK+          2.23 μs ± 215 ns,  17 KB allocated,  29 B  copied,  25 MB peak memory+        Data.Text.Builder.Linear:             OK+          3.03 μs ± 249 ns,  12 KB allocated,  11 B  copied,  25 MB peak memory+      10kB+        TextBuilder.TextBuilder:              OK+          15.6 μs ± 1.0 μs,  82 KB allocated, 205 B  copied,  25 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          32.7 μs ± 2.0 μs, 185 KB allocated, 556 B  copied,  25 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          47.6 μs ± 4.1 μs, 242 KB allocated, 2.1 KB copied,  25 MB peak memory+        Data.Text.Text:                       OK+          437  μs ±  14 μs, 9.6 MB allocated,  22 KB copied,  28 MB peak memory+        Data.Text.Lazy.Text:                  OK+          24.9 μs ± 1.0 μs, 168 KB allocated, 1.9 KB copied,  28 MB peak memory+        Data.Text.Builder.Linear:             OK+          30.4 μs ± 1.7 μs, 110 KB allocated, 427 B  copied,  28 MB peak memory+      100kB+        TextBuilder.TextBuilder:              OK+          176  μs ±  11 μs, 820 KB allocated,  18 KB copied,  28 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          363  μs ±  34 μs, 1.8 MB allocated,  51 KB copied,  28 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          561  μs ±  41 μs, 2.4 MB allocated, 191 KB copied,  28 MB peak memory+        Data.Text.Text:                       OK+          31.4 ms ± 1.5 ms, 954 MB allocated, 379 KB copied,  36 MB peak memory+        Data.Text.Lazy.Text:                  OK+          315  μs ±  13 μs, 1.6 MB allocated, 182 KB copied,  36 MB peak memory+        Data.Text.Builder.Linear:             OK+          333  μs ±  26 μs, 1.3 MB allocated,  34 KB copied,  36 MB peak memory+    mconcat+      100B+        TextBuilder.TextBuilder:              OK+          125  ns ± 6.6 ns, 496 B  allocated,   0 B  copied,  36 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          166  ns ±  13 ns, 1.0 KB allocated,   0 B  copied,  36 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          324  ns ±  27 ns, 2.1 KB allocated,   0 B  copied,  36 MB peak memory+        Data.Text.Text:                       OK+          117  ns ± 7.2 ns, 280 B  allocated,   0 B  copied,  36 MB peak memory+        Data.Text.Lazy.Text:                  OK+          212  ns ±  17 ns, 1.7 KB allocated,   0 B  copied,  36 MB peak memory+        Data.Text.Builder.Linear:             OK+          175  ns ±  13 ns, 936 B  allocated,   0 B  copied,  36 MB peak memory+      1kB+        TextBuilder.TextBuilder:              OK+          1.21 μs ± 104 ns, 3.6 KB allocated,   1 B  copied,  36 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          1.69 μs ± 117 ns, 9.8 KB allocated,  10 B  copied,  36 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          3.04 μs ± 301 ns,  20 KB allocated,  16 B  copied,  36 MB peak memory+        Data.Text.Text:                       OK+          1.07 μs ±  74 ns, 2.0 KB allocated,   1 B  copied,  36 MB peak memory+        Data.Text.Lazy.Text:                  OK+          1.76 μs ± 154 ns,  16 KB allocated,  20 B  copied,  36 MB peak memory+        Data.Text.Builder.Linear:             OK+          1.42 μs ±  58 ns, 8.0 KB allocated,   3 B  copied,  36 MB peak memory+      10kB+        TextBuilder.TextBuilder:              OK+          15.0 μs ± 838 ns,  35 KB allocated,   5 B  copied,  36 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          32.0 μs ± 2.3 μs, 162 KB allocated, 563 B  copied,  36 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          34.8 μs ± 1.7 μs, 202 KB allocated, 927 B  copied,  36 MB peak memory+        Data.Text.Text:                       OK+          10.6 μs ± 899 ns,  20 KB allocated,   5 B  copied,  36 MB peak memory+        Data.Text.Lazy.Text:                  OK+          22.0 μs ± 1.7 μs, 160 KB allocated, 1.3 KB copied,  36 MB peak memory+        Data.Text.Builder.Linear:             OK+          16.3 μs ± 1.4 μs,  71 KB allocated,  13 B  copied,  36 MB peak memory+      100kB+        TextBuilder.TextBuilder:              OK+          176  μs ±  15 μs, 352 KB allocated,  55 B  copied,  36 MB peak memory+        Data.Text.Encoding.StrictTextBuilder: OK+          437  μs ±  21 μs, 1.8 MB allocated,  78 KB copied,  36 MB peak memory+        Data.Text.Lazy.Builder.Builder:       OK+          333  μs ±  26 μs, 2.0 MB allocated,  84 KB copied,  36 MB peak memory+        Data.Text.Text:                       OK+          106  μs ±  10 μs, 195 KB allocated,  60 B  copied,  36 MB peak memory+        Data.Text.Lazy.Text:                  OK+          264  μs ±  15 μs, 1.6 MB allocated,  97 KB copied,  36 MB peak memory+        Data.Text.Builder.Linear:             OK+          170  μs ± 8.1 μs, 952 KB allocated, 166 B  copied,  36 MB peak memory+  Features+    intercalate+      TextBuilder:                            OK+        420  ns ±  28 ns, 3.8 KB allocated,   1 B  copied,  36 MB peak memory+      Text:                                   OK+        143  ns ± 9.9 ns, 824 B  allocated,   0 B  copied,  36 MB peak memory+    binary:                                   OK+      62.8 ns ± 3.7 ns, 152 B  allocated,   0 B  copied,  36 MB peak memory+    octal:                                    OK+      25.1 ns ± 1.7 ns,  96 B  allocated,   0 B  copied,  36 MB peak memory+    decimal:                                  OK+      48.5 ns ± 3.8 ns, 560 B  allocated,   0 B  copied,  36 MB peak memory+    hexadecimal:                              OK+      20.8 ns ± 2.1 ns, 104 B  allocated,   0 B  copied,  36 MB peak memory+```++# How it works++It constructs text in two phases. In the first one it estimates the size of the byte array and in the second one it allocates it once and populates it in one go.
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
+ bench/Main.hs view
@@ -0,0 +1,81 @@+module Main where++import Data.Function+import qualified Data.Text as E+import qualified Data.Text.Builder.Linear+import qualified Data.Text.Encoding as D+import qualified Data.Text.Lazy as C+import qualified Data.Text.Lazy.Builder as B+import Test.Tasty.Bench+import qualified TextBuilder as A+import Prelude++main :: IO ()+main =+  defaultMain+    [ bgroup "Competition" competition,+      bgroup "Features" features+    ]+  where+    competition =+      [ bgroup "Left-biased mappend" $ byConcat $ foldl' (<>) mempty,+        bgroup "Right-biased mappend" $ byConcat $ foldl' (flip (<>)) mempty,+        bgroup "mconcat" $ byConcat $ mconcat+      ]+      where+        byConcat (concat :: forall a. (Monoid a) => [a] -> a) =+          [ bgroup "100B" $ byTexts $ replicate 10 "фывапролдж",+            bgroup "1kB" $ byTexts $ replicate 100 "фывапролдж",+            bgroup "10kB" $ byTexts $ replicate 1_000 "фывапролдж",+            bgroup "100kB" $ byTexts $ replicate 10_000 "фывапролдж"+          ]+          where+            byTexts texts =+              [ bench+                  "TextBuilder.TextBuilder"+                  ( whnf+                      (A.toText . concat)+                      (fmap A.text texts)+                  ),+                bench+                  "Data.Text.Encoding.StrictTextBuilder"+                  ( whnf+                      (D.strictBuilderToText . concat)+                      (fmap D.textToStrictBuilder texts)+                  ),+                bench+                  "Data.Text.Lazy.Builder.Builder"+                  ( whnf+                      (C.toStrict . B.toLazyText . concat)+                      (fmap B.fromText texts)+                  ),+                bench+                  "Data.Text.Text"+                  ( whnf+                      concat+                      texts+                  ),+                bench+                  "Data.Text.Lazy.Text"+                  ( whnf+                      (C.toStrict . concat)+                      (fmap C.fromStrict texts)+                  ),+                bench+                  "Data.Text.Builder.Linear"+                  ( whnf+                      (Data.Text.Builder.Linear.runBuilder . concat)+                      (fmap Data.Text.Builder.Linear.fromText texts)+                  )+              ]++    features =+      [ bgroup "intercalate" $+          [ bench "TextBuilder" $ whnf (A.toText . A.intercalate "фывапролдж") ["a", "b", "c", "d", "e", "f"],+            bench "Text" $ whnf (E.intercalate "фывапролдж") ["a", "b", "c", "d", "e", "f"]+          ],+        bench "binary" $ whnf (A.toText . A.binary) (123456 :: Int),+        bench "octal" $ whnf (A.toText . A.octal) (123456 :: Int),+        bench "decimal" $ whnf (A.toText . A.decimal) (123456 :: Int),+        bench "hexadecimal" $ whnf (A.toText . A.hexadecimal) (123456 :: Int)+      ]
− benchmark-char/Main.hs
@@ -1,65 +0,0 @@-module Main where--import Prelude-import Criterion.Main-import qualified Text.Builder as A-import qualified Data.Text.Lazy.Builder as B-import qualified Data.Text.Lazy as C-import qualified Data.Text as D---main =-  defaultMain $-  [-    subjectBenchmark "builderSubject" builderSubject-    ,-    subjectBenchmark "lazyTextBuilderSubject" lazyTextBuilderSubject-    ,-    subjectBenchmark "plainTextPackingSubject" plainTextPackingSubject-  ]--subjectBenchmark :: String -> Subject -> Benchmark-subjectBenchmark title subject =-  bgroup title $-  [-    benchmark "Small input" smallInput subject-    ,-    benchmark "Medium input" mediumInput subject-    ,-    benchmark "Large input" largeInput subject-  ]--benchmark :: String -> [Int] -> Subject -> Benchmark-benchmark title input subject =-  bench title $ nf subject $ input--type Subject =-  [Int] -> Text--builderSubject :: Subject-builderSubject =-  A.run . A.string . map chr--lazyTextBuilderSubject :: Subject-lazyTextBuilderSubject =-  C.toStrict . B.toLazyText . B.fromString . map chr--plainTextPackingSubject :: Subject-plainTextPackingSubject =-  D.pack . map chr--{-# NOINLINE smallInput #-}-smallInput :: [Int]-smallInput =-  map ord ['a', 'b', 'Ф', '漢', chr 0x11000]--{-# NOINLINE mediumInput #-}-mediumInput :: [Int]-mediumInput =-  mconcat (replicate 1000 smallInput)--{-# NOINLINE largeInput #-}-largeInput :: [Int]-largeInput =-  mconcat (replicate 100000 smallInput)-
− benchmark-text/Main.hs
@@ -1,58 +0,0 @@-module Main where--import Prelude-import Criterion.Main-import qualified Text.Builder as A-import qualified Data.Text.Lazy.Builder as B-import qualified Data.Text.Lazy as C-import qualified Data.Text as D---main =-  defaultMain $-  [-    subjectBenchmark "builderSubject" builderSubject-    ,-    subjectBenchmark "lazyTextBuilderSubject" lazyTextBuilderSubject-  ]--subjectBenchmark :: String -> Subject -> Benchmark-subjectBenchmark title subject =-  bgroup title $-  [-    benchmark "Small input" smallSample subject-    ,-    benchmark "Large input" largeSample subject-  ]--benchmark :: String -> Sample -> Subject -> Benchmark-benchmark title sample subject =-  bench title $ nf sample $ subject--data Subject =-  forall a. Subject (Text -> a) (a -> a -> a) a (a -> Text)--type Sample =-  Subject -> Text--builderSubject :: Subject-builderSubject =-  Subject A.text mappend mempty A.run--lazyTextBuilderSubject :: Subject-lazyTextBuilderSubject =-  Subject B.fromText mappend mempty (C.toStrict . B.toLazyText)--{-# NOINLINE smallSample #-}-smallSample :: Sample-smallSample (Subject text (<>) mempty run) =-  run $-  text "abcd" <> (text "ABCD" <> text "Фываолдж") <> text "漢"--{-# NOINLINE largeSample #-}-largeSample :: Sample-largeSample (Subject text (<>) mempty run) =-  run $-  foldl' (<>) mempty $ replicate 100000 $-  text "abcd" <> (text "ABCD" <> text "Фываолдж") <> text "漢"-
− library/Text/Builder.hs
@@ -1,217 +0,0 @@-module Text.Builder-(-  Builder,-  run,-  length,-  null,-  intercalate,-  char,-  text,-  string,-  asciiByteString,-  unicodeCodePoint,-  utf16CodeUnits1,-  utf16CodeUnits2,-  utf8CodeUnits1,-  utf8CodeUnits2,-  utf8CodeUnits3,-  utf8CodeUnits4,-  decimal,-  unsignedDecimal,-  decimalDigit,-  hexadecimal,-  unsignedHexadecimal,-  hexadecimalDigit,-)-where--import Text.Builder.Prelude hiding (length, null, intercalate)-import qualified Data.Text.Array as B-import qualified Data.Text.Internal as C-import qualified Data.Text.Encoding as E-import qualified Data.Text.Encoding.Error as E-import qualified Text.Builder.UTF16 as D-import qualified Data.ByteString as ByteString---newtype Action =-  Action (forall s. B.MArray s -> Int -> ST s ())--data Builder =-  Builder !Action !Int--instance Monoid Builder where-  {-# INLINE mempty #-}-  mempty =-    Builder (Action (\_ _ -> return ())) 0-  {-# INLINABLE mappend #-}-  mappend (Builder (Action action1) size1) (Builder (Action action2) size2) =-    Builder action size-    where-      action =-        Action $ \array offset -> do-          action1 array offset-          action2 array (offset + size1)-      size =-        size1 + size2--instance Semigroup Builder where-  (<>) = mappend--instance IsString Builder where-  fromString = string--{-# INLINE char #-}-char :: Char -> Builder-char x =-  unicodeCodePoint (ord x)--{-# INLINE unicodeCodePoint #-}-unicodeCodePoint :: Int -> Builder-unicodeCodePoint x =-  D.unicodeCodePoint x utf16CodeUnits1 utf16CodeUnits2--{-# INLINABLE utf16CodeUnits1 #-}-utf16CodeUnits1 :: Word16 -> Builder-utf16CodeUnits1 unit =-  Builder action 1-  where-    action =-      Action $ \array offset -> B.unsafeWrite array offset unit--{-# INLINABLE utf16CodeUnits2 #-}-utf16CodeUnits2 :: Word16 -> Word16 -> Builder-utf16CodeUnits2 unit1 unit2 =-  Builder action 2-  where-    action =-      Action $ \array offset -> do-        B.unsafeWrite array offset unit1-        B.unsafeWrite array (succ offset) unit2--{-# INLINE utf8CodeUnits1 #-}-utf8CodeUnits1 :: Word8 -> Builder-utf8CodeUnits1 unit1 =-  D.utf8CodeUnits1 unit1 utf16CodeUnits1 utf16CodeUnits2--{-# INLINE utf8CodeUnits2 #-}-utf8CodeUnits2 :: Word8 -> Word8 -> Builder-utf8CodeUnits2 unit1 unit2 =-  D.utf8CodeUnits2 unit1 unit2 utf16CodeUnits1 utf16CodeUnits2--{-# INLINE utf8CodeUnits3 #-}-utf8CodeUnits3 :: Word8 -> Word8 -> Word8 -> Builder-utf8CodeUnits3 unit1 unit2 unit3 =-  D.utf8CodeUnits3 unit1 unit2 unit3 utf16CodeUnits1 utf16CodeUnits2--{-# INLINE utf8CodeUnits4 #-}-utf8CodeUnits4 :: Word8 -> Word8 -> Word8 -> Word8 -> Builder-utf8CodeUnits4 unit1 unit2 unit3 unit4 =-  D.utf8CodeUnits4 unit1 unit2 unit3 unit4 utf16CodeUnits1 utf16CodeUnits2--{-# INLINABLE asciiByteString #-}-asciiByteString :: ByteString -> Builder-asciiByteString byteString =-  Builder action (ByteString.length byteString)-  where-    action =-      Action $ \array -> let-        step byte next index = do-          B.unsafeWrite array index (fromIntegral byte)-          next (succ index)-        in ByteString.foldr step (const (return ())) byteString--{-# INLINABLE text #-}-text :: Text -> Builder-text (C.Text array offset length) =-  Builder action length-  where-    action =-      Action $ \builderArray builderOffset -> do-        B.copyI builderArray builderOffset array offset (builderOffset + length)--{-# INLINE string #-}-string :: String -> Builder-string =-  foldMap char--{-# INLINABLE decimal #-}-decimal :: Integral a => a -> Builder-decimal i =-  if i >= 0-    then unsignedDecimal i-    else unicodeCodePoint 45 <> unsignedDecimal (negate i)--{-# INLINABLE unsignedDecimal #-}-unsignedDecimal :: Integral a => a -> Builder-unsignedDecimal =-  loop mempty-  where-    loop builder !i =-      case quotRem i 10 of-        (quot, !rem) ->-          case hexadecimalDigit rem <> builder of-            newBuilder ->-              if quot /= 0-                then loop newBuilder quot-                else newBuilder--{-# INLINE hexadecimal #-}-hexadecimal :: Integral a => a -> Builder-hexadecimal i =-  if i >= 0-    then unsignedHexadecimal i-    else unicodeCodePoint 45 <> unsignedHexadecimal (negate i)--{-# INLINE unsignedHexadecimal #-}-unsignedHexadecimal :: Integral a => a -> Builder-unsignedHexadecimal =-  loop mempty-  where-    loop builder !i =-      case quotRem i 16 of-        (quot, !rem) ->-          case hexadecimalDigit rem <> builder of-            newBuilder ->-              if quot /= 0-                then loop newBuilder quot-                else newBuilder--{-# INLINE decimalDigit #-}-decimalDigit :: Integral a => a -> Builder-decimalDigit n =-  unicodeCodePoint (fromIntegral n + 48)--{-# INLINE hexadecimalDigit #-}-hexadecimalDigit :: Integral a => a -> Builder-hexadecimalDigit n =-  if n <= 9-    then unicodeCodePoint (fromIntegral n + 48)-    else unicodeCodePoint (fromIntegral n + 87)--{-# INLINE length #-}-length :: Builder -> Int-length (Builder _ x) = x--{-# INLINE null #-}-null :: Builder -> Bool-null = (== 0) . length--{-# INLINE intercalate #-}-intercalate :: Foldable foldable => Builder -> foldable Builder -> Builder-intercalate separator = extract . foldl' step init where-  init = Product2 False mempty-  step (Product2 isNotFirst builder) element = Product2 True $ if isNotFirst-    then builder <> separator <> element-    else element-  extract (Product2 _ builder) = builder--run :: Builder -> Text-run (Builder (Action action) size) =-  C.text array 0 size-  where-    array =-      runST $ do-        array <- B.new size-        action array 0-        B.unsafeFreeze array
− library/Text/Builder/Prelude.hs
@@ -1,84 +0,0 @@-module Text.Builder.Prelude-( -  module Exports,-  Product2(..),-)-where---- base---------------------------import Control.Applicative as Exports-import Control.Arrow as Exports-import Control.Category as Exports-import Control.Concurrent as Exports-import Control.Exception as Exports-import Control.Monad as Exports hiding (mapM_, sequence_, forM_, msum, mapM, sequence, forM)-import Control.Monad.IO.Class as Exports-import Control.Monad.Fix as Exports hiding (fix)-import Control.Monad.ST as Exports-import Control.Monad.ST.Unsafe as Exports-import Data.Bits as Exports-import Data.Bool as Exports-import Data.Char as Exports-import Data.Coerce as Exports-import Data.Complex as Exports-import Data.Data as Exports-import Data.Dynamic as Exports-import Data.Either as Exports-import Data.Fixed as Exports-import Data.Foldable as Exports-import Data.Function as Exports hiding (id, (.))-import Data.Functor as Exports-import Data.Functor.Identity as Exports-import Data.Int as Exports-import Data.IORef as Exports-import Data.Ix as Exports-import Data.List as Exports hiding (sortOn, isSubsequenceOf, uncons, concat, foldr, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, find, maximumBy, minimumBy, mapAccumL, mapAccumR, foldl')-import Data.Maybe as Exports-import Data.Monoid as Exports hiding (Last(..), First(..), (<>))-import Data.Ord as Exports-import Data.Proxy as Exports-import Data.Ratio as Exports-import Data.Semigroup as Exports-import Data.STRef as Exports-import Data.String as Exports-import Data.Traversable as Exports-import Data.Tuple as Exports-import Data.Unique as Exports-import Data.Version as Exports-import Data.Word as Exports-import Debug.Trace as Exports-import Foreign.ForeignPtr.Unsafe as Exports-import Foreign.ForeignPtr as Exports-import Foreign.Ptr as Exports-import Foreign.StablePtr as Exports-import Foreign.Storable as Exports hiding (sizeOf, alignment)-import GHC.Conc as Exports hiding (withMVar, threadWaitWriteSTM, threadWaitWrite, threadWaitReadSTM, threadWaitRead)-import GHC.Exts as Exports (lazy, inline, sortWith, groupWith)-import GHC.Generics as Exports (Generic)-import GHC.IO.Exception as Exports-import Numeric as Exports-import Prelude as Exports hiding (concat, foldr, mapM_, sequence_, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, mapM, sequence, id, (.))-import System.Environment as Exports-import System.Exit as Exports-import System.IO as Exports-import System.IO.Error as Exports-import System.IO.Unsafe as Exports-import System.Mem as Exports-import System.Mem.StableName as Exports-import System.Timeout as Exports-import Text.ParserCombinators.ReadP as Exports (ReadP, ReadS, readP_to_S, readS_to_P)-import Text.ParserCombinators.ReadPrec as Exports (ReadPrec, readPrec_to_P, readP_to_Prec, readPrec_to_S, readS_to_Prec)-import Text.Printf as Exports (printf, hPrintf)-import Text.Read as Exports (Read(..), readMaybe, readEither)-import Unsafe.Coerce as Exports---- bytestring---------------------------import Data.ByteString as Exports (ByteString)---- text---------------------------import Data.Text as Exports (Text)--data Product2 a b = Product2 !a !b
− library/Text/Builder/UTF16.hs
@@ -1,61 +0,0 @@-module Text.Builder.UTF16-where--import Text.Builder.Prelude---{-|-A matching function, which chooses the continuation to run.--}-type UTF16View =-  forall x. (Word16 -> x) -> (Word16 -> Word16 -> x) -> x--{-# INLINE char #-}-char :: Char -> UTF16View-char =-  unicodeCodePoint . ord--{-# INLINE unicodeCodePoint #-}-unicodeCodePoint :: Int -> UTF16View-unicodeCodePoint x case1 case2 =-  if x < 0x10000-    then case1 (fromIntegral x)-    else case2 case2Unit1 case2Unit2-  where-    m =-      x - 0x10000-    case2Unit1 =-      fromIntegral (shiftR m 10 + 0xD800)-    case2Unit2 =-      fromIntegral ((m .&. 0x3FF) + 0xDC00)--{-# INLINE utf8CodeUnits1 #-}-utf8CodeUnits1 :: Word8 -> UTF16View-utf8CodeUnits1 x case1 _ =-  case1 (fromIntegral x)--{-# INLINE utf8CodeUnits2 #-}-utf8CodeUnits2 :: Word8 -> Word8 -> UTF16View-utf8CodeUnits2 byte1 byte2 case1 _ =-  case1 (shiftL (fromIntegral byte1 - 0xC0) 6 + fromIntegral byte2 - 0x80)--{-# INLINE utf8CodeUnits3 #-}-utf8CodeUnits3 :: Word8 -> Word8 -> Word8 -> UTF16View-utf8CodeUnits3 byte1 byte2 byte3 case1 case2 =-  unicodeCodePoint unicode case1 case2-  where-    unicode =-      shiftL (fromIntegral byte1 - 0xE0) 12 +-      shiftL (fromIntegral byte2 - 0x80) 6 +-      fromIntegral byte3 - 0x80--{-# INLINE utf8CodeUnits4 #-}-utf8CodeUnits4 :: Word8 -> Word8 -> Word8 -> Word8 -> UTF16View-utf8CodeUnits4 byte1 byte2 byte3 byte4 case1 case2 =-  unicodeCodePoint unicode case1 case2-  where-    unicode =-      shiftL (fromIntegral byte1 - 0xE0) 18 +-      shiftL (fromIntegral byte2 - 0x80) 12 +-      shiftL (fromIntegral byte3 - 0x80) 6 +-      fromIntegral byte4 - 0x80
+ library/TextBuilder.hs view
@@ -0,0 +1,51 @@+module TextBuilder+  ( TextBuilder,++    -- * Accessors+    toText,+    toString,+    isEmpty,++    -- * Constructors++    -- ** Transformations+    force,+    intercalate,+    intercalateMap,++    -- ** Textual+    text,+    lazyText,+    string,+    unsafeUtf8ByteString,++    -- ** Character+    char,+    unicodeCodepoint,++    -- ** Integers++    -- *** Decimal+    decimal,+    fixedLengthDecimal,+    thousandSeparatedDecimal,++    -- *** Binary+    binary,+    prefixedBinary,++    -- *** Octal+    octal,+    prefixedOctal,++    -- *** Hexadecimal+    hexadecimal,+    prefixedHexadecimal,+  )+where++import TextBuilder.Domains.ByteString+import TextBuilder.Domains.Combinators+import TextBuilder.Domains.Digits+import TextBuilder.Domains.Other+import TextBuilderCore
+ library/TextBuilder/Domains/ByteString.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE CPP #-}++module TextBuilder.Domains.ByteString where++import qualified Data.ByteString as ByteString+import qualified Data.Text.Array as TextArray+import TextBuilder.Prelude+import TextBuilderCore++#if !MIN_VERSION_text(2,0,0)+import qualified Data.Text.Encoding as TextEncoding+#endif++-- | UTF-8 bytestring. You can use it for converting ASCII values as well.+--+-- __Warning:__ It's your responsibility to ensure that the bytestring is properly encoded.+--+-- >>> unsafeUtf8ByteString "abc"+-- "abc"+--+-- >>> import Data.Text.Encoding (encodeUtf8)+-- >>> unsafeUtf8ByteString (encodeUtf8 "фывапролдж") == "фывапролдж"+-- True+{-# INLINEABLE unsafeUtf8ByteString #-}+unsafeUtf8ByteString :: ByteString -> TextBuilder+#if MIN_VERSION_text(2,0,0)+unsafeUtf8ByteString byteString =+  TextBuilder+    (ByteString.length byteString)+    ( \array ->+        -- TODO: Optimize to use memcpy or something similar.+        let step byte next index = do+              TextArray.unsafeWrite array index byte+              next (succ index)+         in ByteString.foldr step return byteString+    )+#else+-- Using a suboptimal solution here since the older version of \"text\" is becoming less important with time.+unsafeUtf8ByteString =+  text . TextEncoding.decodeUtf8+#endif
+ library/TextBuilder/Domains/Combinators.hs view
@@ -0,0 +1,45 @@+module TextBuilder.Domains.Combinators where++import TextBuilder.Prelude hiding (intercalate)+import TextBuilderCore++-- |+-- Run the builder and pack the produced text into a new builder.+--+-- Useful to have around builders that you reuse,+-- because a forced builder is much faster,+-- since it's virtually a single call to @memcopy@.+{-# INLINE force #-}+force :: TextBuilder -> TextBuilder+force = text . toText++-- | Intercalate builders.+--+-- >>> intercalate ", " ["a", "b", "c"]+-- "a, b, c"+--+-- >>> intercalate ", " ["a"]+-- "a"+--+-- >>> intercalate ", " []+-- ""+{-# INLINE intercalate #-}+intercalate :: (Foldable f) => TextBuilder -> f TextBuilder -> TextBuilder+intercalate separator elements =+  foldr+    (\element next prefix -> prefix <> element <> next separator)+    (const mempty)+    elements+    mempty++-- | Intercalate projecting values to builder.+{-# INLINE intercalateMap #-}+intercalateMap :: (Foldable f) => TextBuilder -> (a -> TextBuilder) -> f a -> TextBuilder+intercalateMap separator mapper = extract . foldl' step init+  where+    init = Nothing+    step acc element =+      Just $ case acc of+        Nothing -> mapper element+        Just acc -> acc <> separator <> mapper element+    extract = fromMaybe mempty
+ library/TextBuilder/Domains/Digits.hs view
@@ -0,0 +1,352 @@+module TextBuilder.Domains.Digits where++import qualified Data.Text.Array as TextArray+import qualified TextBuilder.Domains.Digits.Codepoints as Codepoints+import TextBuilder.Prelude+import TextBuilderCore++-- | Decimal digit.+{-# INLINE decimalDigit #-}+decimalDigit :: (Integral a) => a -> TextBuilder+decimalDigit (fromIntegral -> n) =+  unicodeCodepoint (n + 48)++-- | Hexadecimal digit.+{-# INLINE hexadecimalDigit #-}+hexadecimalDigit :: (Integral a) => a -> TextBuilder+hexadecimalDigit (fromIntegral -> n) =+  if n <= 9+    then unicodeCodepoint (n + 48)+    else unicodeCodepoint (n + 87)++{-# INLINE customFixedNumeralSystem #-}+customFixedNumeralSystem ::+  (FiniteBits a, Integral a) =>+  -- | Number of bits per digit.+  Int ->+  -- | Projection to codepoint with handling of overflow.+  (a -> a) ->+  -- | Value.+  a ->+  TextBuilder+customFixedNumeralSystem bitsPerDigit digitCodepoint val =+  let size = div (finiteBitSize val + bitsPerDigit - 1) bitsPerDigit+   in TextBuilder size \array arrayStartIndex ->+        let go val arrayIndex =+              if arrayIndex >= arrayStartIndex+                then do+                  TextArray.unsafeWrite array arrayIndex (fromIntegral (digitCodepoint val))+                  go (unsafeShiftR val bitsPerDigit) (pred arrayIndex)+                else return indexAfter+            indexAfter =+              arrayStartIndex + size+         in go val (pred indexAfter)++-- |+-- [Two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) binary representation of a value.+--+-- Bits of a statically sized value padded from the left according to the size.+-- If it's a negatable integer, the sign is reflected in the bits.+--+-- >>> binary @Int8 0+-- "00000000"+--+-- >>> binary @Int8 4+-- "00000100"+--+-- >>> binary @Int8 (-1)+-- "11111111"+--+-- >>> binary @Word8 255+-- "11111111"+--+-- >>> binary @Int16 4+-- "0000000000000100"+--+-- >>> binary @Int16 (-4)+-- "1111111111111100"+{-# INLINE binary #-}+binary :: (FiniteBits a) => a -> TextBuilder+binary val =+  let size = finiteBitSize val+   in TextBuilder size \array arrayStartIndex ->+        let go val arrayIndex =+              if arrayIndex >= arrayStartIndex+                then do+                  TextArray.unsafeWrite array arrayIndex if testBit val 0 then 49 else 48+                  go (unsafeShiftR val 1) (pred arrayIndex)+                else return indexAfter+            indexAfter =+              arrayStartIndex + size+         in go val (pred indexAfter)++-- |+-- Same as 'binary', but with the \"0b\" prefix.+--+-- >>> prefixedBinary @Int8 0+-- "0b00000000"+{-# INLINE prefixedBinary #-}+prefixedBinary :: (FiniteBits a) => a -> TextBuilder+prefixedBinary = mappend "0b" . binary++-- | Octal representation of an integer.+--+-- >>> octal @Int32 123456+-- "00000361100"+--+-- >>> octal @Int32 (-123456)+-- "77777416700"+{-# INLINE octal #-}+octal :: (FiniteBits a, Integral a) => a -> TextBuilder+octal = customFixedNumeralSystem 3 (Codepoints.octalDigit . fromIntegral)++-- |+-- Same as 'octal', but with the \"0o\" prefix.+--+-- >>> prefixedOctal @Int8 0+-- "0o000"+{-# INLINE prefixedOctal #-}+prefixedOctal :: (FiniteBits a, Integral a) => a -> TextBuilder+prefixedOctal = mappend "0o" . octal++-- | Integer in hexadecimal notation with a fixed number of digits determined by the size of the type.+--+-- >>> hexadecimal @Int8 0+-- "00"+--+-- >>> hexadecimal @Int8 4+-- "04"+--+-- >>> hexadecimal @Int8 (-128)+-- "80"+--+-- >>> hexadecimal @Int8 (-1)+-- "ff"+--+-- >>> hexadecimal @Word8 255+-- "ff"+--+-- >>> hexadecimal @Int32 123456+-- "0001e240"+--+-- >>> hexadecimal @Int32 (-123456)+-- "fffe1dc0"+{-# INLINE hexadecimal #-}+hexadecimal :: (FiniteBits a, Integral a) => a -> TextBuilder+hexadecimal = customFixedNumeralSystem 4 (Codepoints.hexDigit . fromIntegral)++-- |+-- Same as 'hexadecimal', but with the \"0x\" prefix.+--+-- >>> prefixedHexadecimal @Int8 0+-- "0x00"+{-# INLINE prefixedHexadecimal #-}+prefixedHexadecimal :: (FiniteBits a, Integral a) => a -> TextBuilder+prefixedHexadecimal = mappend "0x" . hexadecimal++-- * Signed Numbers++{-# INLINE signed #-}+signed :: (Integral a) => (forall a. (Integral a) => a -> TextBuilder) -> a -> TextBuilder+signed onUnsigned a =+  if a >= 0+    then onUnsigned a+    else+      unicodeCodepoint 45+        <> let negated = negate a+            in if negated /= a+                 then onUnsigned negated+                 else+                   -- This is a special case for the minimum value of signed types.+                   -- The negation of the minimum value is not representable in the same type.+                   -- For example, for Int8, -128 is not representable as a positive number.+                   onUnsigned (negate (fromIntegral a :: Integer))++-- | Signed decimal representation of an integer.+--+-- >>> decimal 123456+-- "123456"+--+-- >>> decimal (-123456)+-- "-123456"+--+-- >>> decimal 0+-- "0"+--+-- >>> decimal (-2 :: Int8)+-- "-2"+--+-- >>> decimal (-128 :: Int8)+-- "-128"+{-# INLINE decimal #-}+decimal :: (Integral a) => a -> TextBuilder+decimal = signed unsignedDecimal++-- * Unsigned Numbers++-- | Render a number in the given radix.+{-# INLINE digitsByRadix #-}+digitsByRadix :: (Integral a) => a -> (a -> a) -> a -> TextBuilder+digitsByRadix radix digitCodepoint =+  go 0 []+  where+    go !offset !digits x = case divMod x radix of+      (next, digit) ->+        if next <= 0+          then finish (succ offset) (digit : digits)+          else go (succ offset) (digit : digits) next++    finish size digits =+      TextBuilder size action+      where+        action :: TextArray.MArray s -> Int -> ST s Int+        action array =+          go digits+          where+            go digits offset = case digits of+              [] -> return offset+              (digit : digits) -> do+                TextArray.unsafeWrite array offset (fromIntegral (digitCodepoint digit))+                go digits (succ offset)++-- | Unsigned octal representation of a non-negative integral value.+--+-- __Warning:__ It is your responsibility to ensure that the value is non-negative.+--+--+-- >>> unsignedOctal 7+-- "7"+--+-- >>> unsignedOctal 9+-- "11"+--+-- >>> unsignedOctal 16+-- "20"+{-# INLINE unsignedOctal #-}+unsignedOctal :: (Integral a) => a -> TextBuilder+unsignedOctal =+  digitsByRadix 8 (+ 48)++-- | Unsigned decimal representation of a non-negative integral value.+--+-- __Warning:__ It is your responsibility to ensure that the value is non-negative.+--+-- >>> unsignedDecimal 123456+-- "123456"+--+-- >>> unsignedDecimal 0+-- "0"+{-# INLINE unsignedDecimal #-}+unsignedDecimal :: (Integral a) => a -> TextBuilder+unsignedDecimal =+  digitsByRadix 10 (+ 48)++-- | Unsigned hexadecimal representation of a non-negative integral value.+--+-- __Warning:__ It is your responsibility to ensure that the value is non-negative.+--+-- >>> unsignedHexadecimal 123456+-- "1e240"+--+-- >>> unsignedHexadecimal 0+-- "0"+{-# INLINE unsignedHexadecimal #-}+unsignedHexadecimal :: (Integral a) => a -> TextBuilder+unsignedHexadecimal =+  digitsByRadix 16 (\digit -> if digit <= 9 then digit + 48 else digit + 87)++-- * Other++-- | Fixed-length decimal without sign.+-- Padded with zeros or trimmed depending on whether it's shorter or longer+-- than specified.+--+-- >>> fixedLengthDecimal 5 123+-- "00123"+--+-- >>> fixedLengthDecimal 5 123456+-- "23456"+--+-- >>> fixedLengthDecimal 5 (-123456)+-- "23456"+--+-- >>> fixedLengthDecimal 7 (-123456)+-- "0123456"+--+-- >>> fixedLengthDecimal 0 123+-- ""+--+-- >>> fixedLengthDecimal (-2) 123+-- ""+{-# INLINEABLE fixedLengthDecimal #-}+fixedLengthDecimal :: (Integral a) => Int -> a -> TextBuilder+fixedLengthDecimal (max 0 -> size) (abs -> val) =+  TextBuilder size $ \array startOffset ->+    let offsetAfter = startOffset + size+        writeValue val offset =+          if offset >= startOffset+            then+              if val /= 0+                then case divMod val 10 of+                  (val, digit) -> do+                    TextArray.unsafeWrite array offset $ 48 + fromIntegral digit+                    writeValue val (pred offset)+                else writePadding offset+            else return offsetAfter+        writePadding offset =+          if offset >= startOffset+            then do+              TextArray.unsafeWrite array offset 48+              writePadding (pred offset)+            else return offsetAfter+     in writeValue val (pred offsetAfter)++-- | Decimal representation of an integral value with thousands separated by the specified character.+--+-- >>> thousandSeparatedDecimal ',' 1234567890+-- "1,234,567,890"+--+-- >>> thousandSeparatedDecimal ' ' (-1234567890)+-- "-1 234 567 890"+{-# INLINEABLE thousandSeparatedDecimal #-}+thousandSeparatedDecimal :: (Integral a) => Char -> a -> TextBuilder+thousandSeparatedDecimal separatorChar =+  signed (unsignedThousandSeparatedDecimal separatorChar)++-- | Decimal representation of an unsigned integral value with thousands separated by the specified character.+--+-- __Warning:__ It is your responsibility to ensure that the value is non-negative.+--+-- >>> unsignedThousandSeparatedDecimal ',' 1234567890+-- "1,234,567,890"+--+-- >>> unsignedThousandSeparatedDecimal ' ' 1234567890+-- "1 234 567 890"+--+-- >>> unsignedThousandSeparatedDecimal ',' 0+-- "0"+{-# INLINEABLE unsignedThousandSeparatedDecimal #-}+unsignedThousandSeparatedDecimal :: (Integral a) => Char -> a -> TextBuilder+unsignedThousandSeparatedDecimal separatorChar =+  processRightmostDigit+  where+    processRightmostDigit value =+      case divMod value 10 of+        (value, digit) ->+          processAnotherDigit [decimalDigit digit] (1 :: Int) value+    processAnotherDigit builders index value =+      if value == 0+        then mconcat builders+        else case divMod value 10 of+          (value, digit) ->+            if mod index 3 == 0+              then+                processAnotherDigit+                  (decimalDigit digit : char separatorChar : builders)+                  (succ index)+                  value+              else+                processAnotherDigit+                  (decimalDigit digit : builders)+                  (succ index)+                  value
+ library/TextBuilder/Domains/Digits/Codepoints.hs view
@@ -0,0 +1,30 @@+module TextBuilder.Domains.Digits.Codepoints where++import TextBuilder.Prelude++{-# INLINE octalDigit #-}+octalDigit :: (Bits a, Num a) => a -> a+octalDigit a = a .&. 7 + 48++-- | Extract the first 4 bits and convert them to a Unicode codepoint of a hexadecimal digit.+-- The result is a character in the range of 0-9 or a-f.+--+-- >>> chr (fromIntegral (hexDigit 0))+-- '0'+--+-- >>> chr (fromIntegral (hexDigit 10))+-- 'a'+--+-- >>> chr (fromIntegral (hexDigit 15))+-- 'f'+--+-- The overflow is ignored, so the result is always in the range of 0-15:+-- >>> chr (fromIntegral (hexDigit 16))+-- '0'+{-# INLINE hexDigit #-}+hexDigit :: (Bits a, Num a, Ord a) => a -> a+hexDigit a = case a .&. 15 of+  a ->+    if a < 10+      then a + 48+      else a + 87
+ library/TextBuilder/Domains/Other.hs view
@@ -0,0 +1,12 @@+module TextBuilder.Domains.Other where++import qualified Data.Text as Text+import TextBuilder.Prelude+import TextBuilderCore++-- * Destructors++-- | Convert builder to string.+{-# INLINE toString #-}+toString :: TextBuilder -> String+toString = Text.unpack . toText
+ library/TextBuilder/Prelude.hs view
@@ -0,0 +1,76 @@+module TextBuilder.Prelude+  ( module Exports,+  )+where++import Control.Applicative as Exports+import Control.Arrow as Exports+import Control.Category as Exports+import Control.Concurrent as Exports+import Control.Exception as Exports+import Control.Monad as Exports hiding (forM, forM_, mapM, mapM_, msum, sequence, sequence_)+import Control.Monad.Fix as Exports hiding (fix)+import Control.Monad.IO.Class as Exports+import Control.Monad.ST as Exports+import Control.Monad.ST.Unsafe as Exports+import Control.Monad.Trans.Class as Exports+import Control.Monad.Trans.Maybe as Exports hiding (liftListen, liftPass)+import Control.Monad.Trans.Reader as Exports hiding (liftCallCC, liftCatch)+import Control.Monad.Trans.State.Strict as Exports hiding (liftCallCC, liftCatch, liftListen, liftPass)+import Data.Bits as Exports+import Data.Bool as Exports+import Data.ByteString as Exports (ByteString)+import Data.Char as Exports+import Data.Coerce as Exports+import Data.Complex as Exports+import Data.Data as Exports+import Data.Dynamic as Exports+import Data.Either as Exports+import Data.Fixed as Exports+import Data.Foldable as Exports+import Data.Function as Exports hiding (id, (.))+import Data.Functor as Exports hiding (unzip)+import Data.Functor.Identity as Exports+import Data.IORef as Exports+import Data.Int as Exports+import Data.Ix as Exports+import Data.List as Exports hiding (all, and, any, concat, concatMap, elem, find, foldl, foldl', foldl1, foldr, foldr1, isSubsequenceOf, mapAccumL, mapAccumR, maximum, maximumBy, minimum, minimumBy, notElem, or, product, sortOn, sum, uncons)+import Data.Maybe as Exports+import Data.Monoid as Exports hiding (First (..), Last (..), (<>))+import Data.Ord as Exports+import Data.Proxy as Exports+import Data.Ratio as Exports+import Data.STRef as Exports+import Data.Semigroup as Exports+import Data.String as Exports+import Data.Text as Exports (Text)+import Data.Time as Exports+import Data.Traversable as Exports+import Data.Tuple as Exports+import Data.Unique as Exports+import Data.Version as Exports+import Data.Word as Exports+import Debug.Trace as Exports+import Foreign.ForeignPtr as Exports+import Foreign.ForeignPtr.Unsafe as Exports+import Foreign.Ptr as Exports+import Foreign.StablePtr as Exports+import Foreign.Storable as Exports hiding (alignment, sizeOf)+import GHC.Conc as Exports hiding (threadWaitRead, threadWaitReadSTM, threadWaitWrite, threadWaitWriteSTM, withMVar)+import GHC.Exts as Exports (groupWith, inline, lazy, sortWith)+import GHC.Generics as Exports (Generic)+import GHC.IO.Exception as Exports+import Numeric as Exports+import Numeric.Natural as Exports (Natural)+import System.Environment as Exports+import System.Exit as Exports+import System.IO as Exports+import System.IO.Error as Exports+import System.IO.Unsafe as Exports+import System.Mem as Exports+import System.Mem.StableName as Exports+import System.Timeout as Exports+import Text.Printf as Exports (hPrintf, printf)+import Text.Read as Exports (Read (..), readEither, readMaybe)+import Unsafe.Coerce as Exports+import Prelude as Exports hiding (all, and, any, concat, concatMap, elem, foldl, foldl1, foldr, foldr1, id, mapM, mapM_, maximum, minimum, notElem, or, product, sequence, sequence_, sum, (.))
test/Main.hs view
@@ -1,65 +1,153 @@ module Main where -import Prelude hiding (choose)-import Test.QuickCheck.Instances-import Test.Tasty-import Test.Tasty.HUnit-import Test.Tasty.QuickCheck-import qualified Data.Text as A-import qualified Text.Builder as B import qualified Data.ByteString as ByteString+import Data.Char (intToDigit)+import Data.Int+import Data.Proxy+import Data.String+import qualified Data.Text as Text import qualified Data.Text.Encoding as Text+import Data.Word+import Numeric+import Numeric.Natural (Natural)+import Test.QuickCheck.Classes+import Test.QuickCheck.Instances ()+import Test.Tasty+import Test.Tasty.QuickCheck+import TextBuilder+import Util.ExtraInstances ()+import Util.TestTrees+import Prelude +main :: IO ()+main = (defaultMain . testGroup "All") tests -main =-  defaultMain $-  testGroup "All tests" $-  [-    testProperty "ASCII ByteString" $ let-      gen = listOf $ do-        list <- listOf (choose (0, 127))-        return (ByteString.pack list)-      in forAll gen $ \ chunks ->-        mconcat chunks ===-        Text.encodeUtf8 (B.run (foldMap B.asciiByteString chunks))-    ,-    testProperty "Intercalation has the same effect as in Text" $-    \ separator texts ->-      A.intercalate separator texts ===-      B.run (B.intercalate (B.text separator) (fmap B.text texts))-    ,-    testProperty "Packing a list of chars is isomorphic to appending a list of builders" $-    \ chars ->-      A.pack chars ===-      B.run (foldMap B.char chars)-    ,-    testProperty "Concatting a list of texts is isomorphic to fold-mapping with builders" $-    \ texts ->-      mconcat texts ===-      B.run (foldMap B.text texts)-    ,-    testProperty "Concatting a list of texts is isomorphic to concatting a list of builders" $-    \ texts ->-      mconcat texts ===-      B.run (mconcat (map B.text texts))-    ,-    testProperty "Concatting a list of trimmed texts is isomorphic to concatting a list of builders" $-    \ texts ->-      let-        trimmedTexts = fmap (A.drop 3) texts-        in-          mconcat trimmedTexts ===-          B.run (mconcat (map B.text trimmedTexts))-    ,-    testProperty "Decimal" $ \ (x :: Integer) ->-    (fromString . show) x === (B.run (B.decimal x))-    ,-    testProperty "Hexadecimal vs std show" $ \ (x :: Integer) -> x >= 0 ==>-    (fromString . showHex x) "" === (B.run . B.hexadecimal) x-    ,-    testCase "Hexadecimal" $-    assertEqual "" "1f23" (B.run (B.hexadecimal 0x01f23))-    ,-    testCase "Negative Hexadecimal" $-    assertEqual "" "-1f23" (B.run (B.hexadecimal (-0x01f23)))+tests :: [TestTree]+tests =+  [ testGroup "Instances" $+      [ followsLaws $ showLaws (Proxy @TextBuilder),+        followsLaws $ eqLaws (Proxy @TextBuilder),+        followsLaws $ semigroupLaws (Proxy @TextBuilder),+        followsLaws $ monoidLaws (Proxy @TextBuilder)+      ],+    testGroup "Functions" $+      [ testGroup "decimal" $+          [ testGroup "Int" $+              [ mapsToMonoid @Int decimal,+                testProperty "Encodes the same as show" $ \(x :: Int) ->+                  (fromString . show) x === toText (decimal x)+              ],+            testGroup "Int8" $+              [ mapsToMonoid @Int8 decimal,+                testProperty "Encodes the same as show" $ \(x :: Int8) ->+                  (fromString . show) x === toText (decimal x)+              ],+            testGroup "Word" $+              [ mapsToMonoid @Word decimal,+                testProperty "Encodes the same as show" $ \(x :: Word) ->+                  (fromString . show) x === toText (decimal x)+              ],+            testGroup "Word8" $+              [ mapsToMonoid @Word8 decimal,+                testProperty "Encodes the same as show" $ \(x :: Word8) ->+                  (fromString . show) x === toText (decimal x)+              ],+            testGroup "Integer" $+              [ mapsToMonoid @Integer decimal,+                testProperty "Encodes the same as show" $ \(x :: Integer) ->+                  (fromString . show) x === toText (decimal x)+              ],+            testGroup "Natural" $+              [ mapsToMonoid @Natural decimal,+                testProperty "Encodes the same as show" $ \(x :: Natural) ->+                  (fromString . show) x === toText (decimal x)+              ]+          ],+        testGroup "fixedLengthDecimal" $+          [ testGroup "Word" $+              [ mapsToMonoid @Word (fixedLengthDecimal 42)+              ],+            testGroup "Natural" $+              [ mapsToMonoid @Natural (fixedLengthDecimal 42),+                testProperty "Encodes the same as printf" $ \(size :: Word8, val :: Natural) ->+                  let rendered = show val+                      renderedLength = length rendered+                      intSize = fromIntegral size+                      padded =+                        if renderedLength > intSize+                          then drop (renderedLength - intSize) rendered+                          else replicate (intSize - renderedLength) '0' <> rendered+                   in fromString padded+                        === toText (fixedLengthDecimal (fromIntegral size) val)+              ]+          ],+        testGroup "thousandSeparatedDecimal" $+          [ testGroup "Int" $+              [ mapsToMonoid @Int (thousandSeparatedDecimal ','),+                testProperty "Encodes the same as show" $ \(x :: Int) ->+                  (fromString . show) x === Text.filter (/= ',') (toText (thousandSeparatedDecimal ',' x))+              ],+            testGroup "Int8" $+              [ mapsToMonoid @Int8 (thousandSeparatedDecimal ','),+                testProperty "Encodes the same as show" $ \(x :: Int8) ->+                  (fromString . show) x === Text.filter (/= ',') (toText (thousandSeparatedDecimal ',' x))+              ],+            testGroup "Integer" $+              [ mapsToMonoid @Integer (thousandSeparatedDecimal ','),+                testProperty "Encodes the same as show" $ \(x :: Integer) ->+                  (fromString . show) x === Text.filter (/= ',') (toText (thousandSeparatedDecimal ',' x))+              ]+          ],+        testGroup "binary" $+          [ testGroup "Int" $+              [ mapsToMonoid @Int binary,+                testProperty "Encodes the same as showIntAtBase" $ \(x :: Word32) ->+                  (Text.justifyRight 32 '0' . Text.pack . showIntAtBase 2 intToDigit x) "" === toText (binary x)+              ]+          ],+        testGroup "octal" $+          [ testGroup "Int" $+              [ mapsToMonoid @Int octal,+                testProperty "Encodes the same as showIntAtBase" $ \(x :: Word32) ->+                  (Text.justifyRight 11 '0' . Text.pack . showIntAtBase 8 intToDigit x) "" === toText (octal x)+              ]+          ],+        testGroup "hexadecimal" $+          [ testGroup "Int" $+              [ mapsToMonoid @Int hexadecimal,+                testProperty "Encodes the same as showHex" $ \(x :: Word32) ->+                  (Text.justifyRight 8 '0' . Text.pack . showHex x) "" === toText (hexadecimal x)+              ]+          ],+        testGroup "unsafeUtf8ByteString" $+          [ mapsToMonoid (unsafeUtf8ByteString . Text.encodeUtf8),+            testProperty "Works on ASCII" $+              let gen = listOf do+                    list <- listOf (choose (0, 127))+                    return (ByteString.pack list)+               in forAll gen \chunks ->+                    mconcat chunks+                      === Text.encodeUtf8 (toText (foldMap unsafeUtf8ByteString chunks))+          ],+        testGroup "intercalate" $+          [ customGenMonoid do+              sep <- arbitrary+              texts <- listOf arbitrary+              return (intercalate (text sep) (fmap text texts)),+            testProperty "Has the same effect as in Text" $+              \separator texts ->+                Text.intercalate separator texts+                  === toText (intercalate (text separator) (fmap text texts))+          ],+        testGroup "intercalateMap" $+          [ customGenMonoid do+              sep <- arbitrary+              texts <- listOf arbitrary+              return (intercalateMap (text sep) text texts),+            testProperty "intercalateMap sep mapper == intercalate sep . fmap mapper" $+              \separator ints ->+                Text.intercalate separator (fmap (fromString . show @Int) ints)+                  === toText (intercalateMap (text separator) decimal ints)+          ]+      ]   ]
+ test/Util/ExtraInstances.hs view
@@ -0,0 +1,12 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module Util.ExtraInstances where++import qualified Data.Text.Lazy.Builder as TextLazyBuilder+import Test.QuickCheck+import Test.QuickCheck.Instances ()+import Prelude++instance Arbitrary TextLazyBuilder.Builder where+  arbitrary =+    TextLazyBuilder.fromLazyText <$> arbitrary
+ test/Util/TestTrees.hs view
@@ -0,0 +1,86 @@+module Util.TestTrees where++import Data.List.NonEmpty (NonEmpty (..))+import Data.Monoid+import Data.Semigroup+import Test.QuickCheck+import Test.QuickCheck.Classes+import Test.QuickCheck.Instances ()+import Test.Tasty+import Test.Tasty.QuickCheck+import Prelude++-- | Tests mapping from @a@ to @b@ to produce a valid 'Monoid'.+--+-- Tests the following properties:+--+-- [/Associative/]+--   @a '<>' (b '<>' c) ≡ (a '<>' b) '<>' c@+-- [/Semigroup Concatenation/]+--   @'sconcat' as ≡ 'foldr1' ('<>') as@+-- [/Times/]+--   @'stimes' n a ≡ 'foldr1' ('<>') ('replicate' n a)@+-- [/Left Identity/]+--   @mappend mempty a ≡ a@+-- [/Right Identity/]+--   @mappend a mempty ≡ a@+-- [/Monoid Concatenation/]+--   @mconcat as ≡ foldr mappend mempty as@+mapsToMonoid ::+  forall a b.+  (Arbitrary a, Show a, Eq a, Monoid b, Eq b, Show b) =>+  -- | Embed in monoid.+  (a -> b) ->+  TestTree+mapsToMonoid embed =+  customGenMonoid (embed <$> arbitrary)++-- | Tests mapping from @a@ to @b@ to produce a valid 'Monoid'.+--+-- Tests the following properties:+--+-- [/Associative/]+--   @a '<>' (b '<>' c) ≡ (a '<>' b) '<>' c@+-- [/Semigroup Concatenation/]+--   @'sconcat' as ≡ 'foldr1' ('<>') as@+-- [/Times/]+--   @'stimes' n a ≡ 'foldr1' ('<>') ('replicate' n a)@+-- [/Left Identity/]+--   @mappend mempty a ≡ a@+-- [/Right Identity/]+--   @mappend a mempty ≡ a@+-- [/Monoid Concatenation/]+--   @mconcat as ≡ foldr mappend mempty as@+customGenMonoid ::+  (Monoid a, Eq a, Show a) =>+  Gen a ->+  TestTree+customGenMonoid gen =+  testGroup+    "Monoid"+    [ testProperty "Is associative" do+        x <- gen+        y <- gen+        z <- gen+        pure (x <> (y <> z) === (x <> y) <> z),+      testProperty "Semigroup concatenation" do+        xs <- (:|) <$> gen <*> listOf gen+        pure (sconcat xs === foldr1 (<>) xs),+      testProperty "Times" do+        x <- gen+        Positive n <- arbitrary+        pure (stimes n x === foldr1 (<>) (replicate n x)),+      testProperty "Left identity" do+        x <- gen+        pure (mempty <> x === x),+      testProperty "Right identity" do+        x <- gen+        pure (x <> mempty === x),+      testProperty "Monoid concatenation" do+        xs <- listOf gen+        pure (mconcat xs === foldr mappend mempty xs)+    ]++followsLaws :: Laws -> TestTree+followsLaws Laws {..} =+  testProperties lawsTypeclass lawsProperties
text-builder.cabal view
@@ -1,7 +1,39 @@+cabal-version: 3.0 name: text-builder-version: 0.5.4.3-category: Text-synopsis: An efficient strict text builder+version: 1.0.0.5+category: Text, Builders+synopsis: Efficient and flexible strict text builder+description:+  = Summary++  Fast strict text builder and simple type-safe formatting library.++  == The Builder++  The builder abstraction provided by this library is much faster than the standard lazy @Builder@ and even the recently introduced @StrictTextBuilder@ from \"text\". Benchmarks are distributed with the source code. You can see the results in the README file.++  The abstraction constructs text in two phases. In the first one it estimates the size of the byte array and in the second one it allocates it once and populates it in one go.++  == Simple and type-safe formatting library++  The monoidal API of the library provides a simple yet type-safe alternative to formatting strings via @printf@-like tools or more involved solutions like the popular \"[formatting](https://hackage.haskell.org/package/formatting)\" library.++  == Quality++  Every bit of the library is heavily covered with tests with CI running tests on a variety of versions of GHC and the \"text\" library. This is crucial because the \"text\" library has made a switch from UTF-16 to UTF-8, leading to drastic changes in its low-level constructs, which builder libraries must rely on, and this library supports both versions of \"text\".++  = Ecosystem++  Following is a list of libraries that, alongside this one, make an integrated ecosystem:++  - "[text-builder-time](https://hackage.haskell.org/package/text-builder-time)" - formatters for the "time" library++  - "[text-builder-core](https://hackage.haskell.org/package/text-builder-core)" - lower-level unsafe constructs for implementing efficient formatters compatible with this library++  - "[text-builder-dev](https://hackage.haskell.org/package/text-builder-dev)" - edge of development of new features providing a richer functionality at the cost of more frequent major releases++  - "[text-builder-lawful-conversions](https://hackage.haskell.org/package/text-builder-lawful-conversions)" - integration with the \"lawful-conversions\" library, providing bidirectional conversions with various textual and builder types.+ homepage: https://github.com/nikita-volkov/text-builder bug-reports: https://github.com/nikita-volkov/text-builder/issues author: Nikita Volkov <nikita.y.volkov@mail.ru>@@ -9,60 +41,116 @@ copyright: (c) 2017, Nikita Volkov license: MIT license-file: LICENSE-build-type: Simple-cabal-version: >=1.10+extra-doc-files:+  CHANGELOG.md+  LICENSE+  README.md +source-repository head+  type: git+  location: https://github.com/nikita-volkov/text-builder++common base+  default-language: Haskell2010+  default-extensions:+    BangPatterns+    BlockArguments+    ConstraintKinds+    DataKinds+    DefaultSignatures+    DeriveDataTypeable+    DeriveFoldable+    DeriveFunctor+    DeriveGeneric+    DeriveTraversable+    DerivingStrategies+    EmptyDataDecls+    FlexibleContexts+    FlexibleInstances+    FunctionalDependencies+    GADTs+    GeneralizedNewtypeDeriving+    LambdaCase+    LiberalTypeSynonyms+    MagicHash+    MultiParamTypeClasses+    MultiWayIf+    NoImplicitPrelude+    NoMonomorphismRestriction+    NumericUnderscores+    OverloadedStrings+    ParallelListComp+    PatternGuards+    QuasiQuotes+    RankNTypes+    RecordWildCards+    ScopedTypeVariables+    StandaloneDeriving+    StrictData+    TemplateHaskell+    TupleSections+    TypeApplications+    TypeFamilies+    TypeOperators+    UnboxedTuples+    ViewPatterns+ library+  import: base   hs-source-dirs: library-  default-extensions: Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples-  default-language: Haskell2010-  exposed-modules:-    Text.Builder+  exposed-modules: TextBuilder   other-modules:-    Text.Builder.UTF16-    Text.Builder.Prelude+    TextBuilder.Domains.ByteString+    TextBuilder.Domains.Combinators+    TextBuilder.Domains.Digits+    TextBuilder.Domains.Digits.Codepoints+    TextBuilder.Domains.Other+    TextBuilder.Prelude+   build-depends:-    base >=4.9 && <5,-    base-prelude <2,-    bytestring >=0.10 && <0.11,-    semigroups >=0.18 && <0.19,-    text >=1 && <2+    base >=4.11 && <5,+    bytestring >=0.10 && <0.13,+    text >=1.2 && <3,+    text-builder-core ^>=0.1.1.1,+    time >=1.12 && <2,+    transformers >=0.5 && <0.7,  test-suite test+  import: base   type: exitcode-stdio-1.0   hs-source-dirs: test-  default-extensions: Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples-  default-language: Haskell2010   main-is: Main.hs-  build-depends:-    QuickCheck >=2.8.1 && <3,-    quickcheck-instances >=0.3.11 && <0.4,-    rerebase <2,-    tasty >=0.12 && <2,-    tasty-hunit >=0.9 && <0.11,-    tasty-quickcheck >=0.9 && <0.11,-    text-builder+  other-modules:+    Util.ExtraInstances+    Util.TestTrees -benchmark benchmark-text-  type: exitcode-stdio-1.0-  hs-source-dirs: benchmark-text-  default-extensions: Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveTraversable, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples-  default-language: Haskell2010-  ghc-options: -O2 -threaded "-with-rtsopts=-N" -funbox-strict-fields-  main-is: Main.hs   build-depends:-    criterion >=1.1 && <2,-    rerebase ==1.*,-    text-builder+    QuickCheck >=2.14 && <3,+    base >=4.11 && <5,+    bytestring >=0.10 && <0.13,+    quickcheck-classes >=0.6.5 && <0.7,+    quickcheck-instances >=0.3.32 && <0.4,+    tasty >=1.5.3 && <2,+    tasty-quickcheck >=0.11.1 && <0.12,+    text >=1.2 && <3,+    text-builder, -benchmark benchmark-char+benchmark bench+  import: base   type: exitcode-stdio-1.0-  hs-source-dirs: benchmark-char-  default-extensions: Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveTraversable, DeriveGeneric, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples-  default-language: Haskell2010-  ghc-options: -O2 -threaded "-with-rtsopts=-N" -funbox-strict-fields+  hs-source-dirs: bench+  ghc-options:+    -O2+    -threaded+    -with-rtsopts=-N+    -with-rtsopts=-A32m+    -with-rtsopts=-T+    -fproc-alignment=64+   main-is: Main.hs   build-depends:-    criterion >=1.1 && <2,-    rerebase ==1.*,-    text-builder+    base >=4.11 && <5,+    tasty-bench ^>=0.5,+    text >=2.1.2 && <3,+    text-builder,+    text-builder-linear ^>=0.1.3,