├── cabal.project ├── Setup.hs ├── .gitignore ├── benchmarks ├── cabal.project ├── aeson │ ├── Fast.hs │ ├── Mason.hs │ ├── Bstr.hs │ ├── json-weigh.hs │ ├── main.hs │ ├── template.hs │ └── twitter100-mangled.json ├── bintree.hs └── mason-benchmarks.cabal ├── CHANGELOG.md ├── .github └── workflows │ └── haskell.yml ├── mason.cabal ├── LICENSE ├── src └── Mason │ ├── Builder │ ├── Compat.hs │ ├── Dynamic.hs │ └── Internal.hs │ └── Builder.hs ├── README.md └── cbits └── dtoa.c /cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | *~ 3 | .ghc.environment.* 4 | dist-newstyle/ 5 | -------------------------------------------------------------------------------- /benchmarks/cabal.project: -------------------------------------------------------------------------------- 1 | packages: . 2 | ../ 3 | allow-newer: 4 | fast-builder:base -------------------------------------------------------------------------------- /benchmarks/aeson/Fast.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# OPTIONS_GHC -ddump-simpl -ddump-to-file -dsuppress-all #-} 3 | #define LIB Data.ByteString.FastBuilder 4 | #define NAME Fast 5 | #include "template.hs" 6 | 7 | encodeUtf8Builder :: T.Text -> Builder 8 | encodeUtf8Builder = byteString . T.encodeUtf8 9 | -------------------------------------------------------------------------------- /benchmarks/aeson/Mason.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# OPTIONS_GHC -ddump-simpl -ddump-to-file -dsuppress-all #-} 3 | #define LIB Mason.Builder.Compat hiding (toStrictByteString, toLazyByteString, hPutBuilder) 4 | #define LIB_EXTRA Mason.Builder.Dynamic 5 | #define NAME Mason 6 | #include "template.hs" 7 | 8 | rebuild = id 9 | -------------------------------------------------------------------------------- /benchmarks/aeson/Bstr.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | #define LIB Data.ByteString.Builder 3 | #define NAME Bstr 4 | #include "template.hs" 5 | 6 | rebuild :: Builder -> Builder 7 | rebuild = id 8 | 9 | toStrictByteString = L.toStrict . toLazyByteString 10 | 11 | encodeUtf8Builder :: T.Text -> Builder 12 | encodeUtf8Builder = T.encodeUtf8Builder 13 | -------------------------------------------------------------------------------- /benchmarks/aeson/json-weigh.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | import qualified Fast as F 3 | import qualified Bstr as B 4 | import qualified Mason as M 5 | import Control.Concurrent 6 | import Control.DeepSeq 7 | import Data.Aeson (decode', encode) 8 | import qualified Data.ByteString.Lazy as L 9 | import System.IO 10 | import Weigh 11 | 12 | main :: IO () 13 | main = do 14 | Just json <- decode' <$> L.readFile "aeson/twitter100-mangled.json" 15 | rnf json `seq` return () 16 | 17 | mainWith $ do 18 | func "mason" M.valueToByteString json 19 | func "fast-builder" F.valueToByteString json 20 | func "bytestring" B.valueToByteString json 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.5 2 | 3 | * Supported GHC 9.2.1 4 | 5 | ## 0.2.4 6 | 7 | * Generalised the argument `intersperse`, `unlines`, `unwords` from a list to any `Foldable` 8 | * Supported GHC 9.0.1 9 | 10 | ## 0.2.3 11 | 12 | * Added `intDecPadded` 13 | * Exported backend types: `StrictByteStringBackend`, `LazyByteStringBackend` and `BufferedIOBackend` 14 | 15 | ## 0.2.2 16 | 17 | * Added `withPopper` and `toStreamingBody` 18 | * Added `viaShow` 19 | * Added `intersperse`, `unwords` and `unlines` 20 | * Optmised the internal representation 21 | 22 | ## 0.2.1 23 | 24 | * Added `Mason.Builder.Compat` 25 | 26 | ## 0.2 27 | 28 | * Added `doubleFixed`, `doubleSI` and `doubleExp` 29 | * Added `textUtf8` 30 | * Added `prefixVarInt`, `wordVLQ` and `intVLQ` 31 | * Renamed `padded` and `zeroPadded` to `paddedBoundedPrim` and `zeroPaddedBoundedPrim` respectively 32 | * Added `Mason.Builder.Dynamic` 33 | 34 | ## 0 -- 2019-12-05 35 | 36 | * First version. Released on an unsuspecting world. 37 | -------------------------------------------------------------------------------- /benchmarks/bintree.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | import qualified Data.ByteString.Builder as B 3 | import qualified Mason.Builder.Dynamic as MD 4 | import qualified Mason.Builder as M 5 | import qualified Data.ByteString.FastBuilder as F 6 | import Test.Tasty.Bench 7 | 8 | b_bytestring :: Int -> B.Builder 9 | b_bytestring 0 = B.char7 'o' 10 | b_bytestring 1 = B.char7 'i' 11 | b_bytestring n = b_bytestring (n-1) <> B.char7 '-' <> b_bytestring (n-2) 12 | 13 | b_mason :: Int -> MD.DynBuilder 14 | b_mason 0 = M.char7 'o' 15 | b_mason 1 = M.char7 'i' 16 | b_mason n = b_mason (n-1) <> M.char7 '-' <> b_mason (n-2) 17 | 18 | b_fast :: Int -> F.Builder 19 | b_fast 0 = F.char7 'o' 20 | b_fast 1 = F.char7 'i' 21 | b_fast n = b_fast (n-1) <> F.char7 '-' <> b_fast (n-2) 22 | 23 | main = defaultMain 24 | [ bench "mason" $ nf (MD.toStrictByteString . b_mason) 10 25 | , bench "fast-builder" $ nf (F.toStrictByteString . b_fast) 10 26 | , bench "bytestring" $ nf (B.toLazyByteString . b_bytestring) 10 27 | ] 28 | -------------------------------------------------------------------------------- /.github/workflows/haskell.yml: -------------------------------------------------------------------------------- 1 | name: Haskell CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | ghc: [ '8.10.7', '9.0.1', '9.2.5', '9.4.2' ] 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: haskell/actions/setup@v1 15 | with: 16 | ghc-version: ${{ matrix.ghc }} 17 | cabal-version: '3.4' 18 | 19 | - name: cabal Cache 20 | uses: actions/cache@v1 21 | env: 22 | cache-name: cache-cabal 23 | with: 24 | path: ~/.cabal 25 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/*.cabal') }}-${{ hashFiles('**/cabal.project') }} 26 | restore-keys: | 27 | ${{ runner.os }}-build-${{ env.cache-name }}- 28 | ${{ runner.os }}-build- 29 | ${{ runner.os }}- 30 | 31 | - name: Install dependencies 32 | run: | 33 | cabal update 34 | cabal build --only-dependencies --enable-tests --enable-benchmarks all 35 | - name: Build 36 | run: cabal build --enable-tests --enable-benchmarks all 37 | - name: Run tests 38 | run: cabal test --enable-tests --enable-benchmarks all 39 | -------------------------------------------------------------------------------- /mason.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | 3 | name: mason 4 | version: 0.2.6 5 | synopsis: Fast and extensible bytestring builder 6 | description: 7 | This package provides efficient implementation of bytestring builders. 8 | See README.md for details 9 | bug-reports: https://github.com/fumieval/mason/issues 10 | license: BSD-3-Clause 11 | license-file: LICENSE 12 | author: Fumiaki Kinoshita 13 | maintainer: fumiexcel@gmail.com 14 | copyright: 2021 Fumiaki Kinoshita, Don Stewart 2005-2009, Duncan Coutts 2006-2015, David Roundy 2003-2005, Jasper Van der Jeugt 2010, Simon Meier 2010-2013, Ben Gamari 2017 15 | category: Data 16 | extra-source-files: CHANGELOG.md, README.md 17 | tested-with: GHC == 9.4.2, GHC == 9.2.5, GHC==9.0.1, GHC==8.10.7 18 | 19 | source-repository head 20 | type: git 21 | location: https://github.com/fumieval/mason.git 22 | 23 | library 24 | exposed-modules: 25 | Mason.Builder 26 | Mason.Builder.Internal 27 | Mason.Builder.Dynamic 28 | Mason.Builder.Compat 29 | 30 | c-sources: cbits/dtoa.c 31 | ghc-options: -Wall -O2 32 | build-depends: base >= 4.12.0.0 && <5 33 | , bytestring 34 | , text 35 | , network >= 2.7 && <3.3 36 | , ghc-prim 37 | , array 38 | hs-source-dirs: src 39 | default-language: Haskell2010 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Fumiaki Kinoshita 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Fumiaki Kinoshita nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /benchmarks/aeson/main.hs: -------------------------------------------------------------------------------- 1 | import qualified Fast as F 2 | import qualified Bstr as B 3 | import qualified Mason as M 4 | import Control.Concurrent 5 | import Control.DeepSeq 6 | import Test.Tasty.Bench 7 | import Data.Aeson (Value, decode', encode) 8 | import qualified Data.ByteString.Lazy as L 9 | import System.IO 10 | 11 | readTestData :: IO Value 12 | readTestData = do 13 | Just val <- decode' <$> L.readFile "aeson/twitter100-mangled.json" 14 | pure val 15 | 16 | main :: IO () 17 | main = runInUnboundThread $ withFile "bench.json" WriteMode $ \h -> do 18 | hSetBuffering h NoBuffering 19 | 20 | json <- readTestData 21 | 22 | rnf json `seq` return () 23 | 24 | defaultMain 25 | [ bench "mason/double" $ nf (\x -> M.toStrictByteString $ M.doubleDec x) (pi * 1e6) 26 | , bench "fast-builder/double" $ nf (F.toStrictByteString . F.doubleDec) (pi * 1e6) 27 | , bench "bytestring/double" $ nf (B.toStrictByteString . B.doubleDec) (pi * 1e6) 28 | , bench "mason/literal" $ nf M.toStrictByteString M.literal 29 | , bench "fast-builder/literal" $ nf F.toStrictByteString F.literal 30 | , bench "bytestring/literal" $ nf B.toStrictByteString B.literal 31 | , bench "mason/hPutBuilder" $ nfIO (M.putValue h json) 32 | , bench "fast-builder/hPutBuilder" $ nfIO (F.putValue h json) 33 | , bench "bytestring/hPutBuilder" $ nfIO (B.putValue h json) 34 | , bench "mason/toStrictByteString" $ nf M.valueToByteString json 35 | , bench "fast-builder/toStrictByteString" $ nf F.valueToByteString json 36 | , bench "bytestring/toLazyByteString" $ nf B.valueToByteString json 37 | , bench "mason/toLazyByteString" $ nf M.valueToLazyByteString json 38 | , bench "fast-builder/toLazyByteString" $ nf F.valueToLazyByteString json 39 | , bench "bytestring/toLazyByteString" $ nf B.valueToLazyByteString json 40 | , bench "aeson" $ nf encode json 41 | ] 42 | -------------------------------------------------------------------------------- /benchmarks/mason-benchmarks.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | -- Initial package description 'mason.cabal' generated by 'cabal init'. 3 | -- For further documentation, see http://haskell.org/cabal/users-guide/ 4 | 5 | name: mason-benchmarks 6 | version: 0.1.0.0 7 | -- synopsis: 8 | -- description: 9 | -- bug-reports: 10 | -- license: 11 | license-file: LICENSE 12 | author: Fumiaki Kinoshita 13 | maintainer: fumiexcel@gmail.com 14 | -- copyright: 15 | -- category: 16 | extra-source-files: CHANGELOG.md 17 | 18 | -- modified version of https://github.com/takano-akio/fast-builder 19 | benchmark aeson 20 | type: exitcode-stdio-1.0 21 | main-is: main.hs 22 | hs-source-dirs: aeson 23 | other-modules: Fast, Bstr, Mason 24 | include-dirs: benchmarks/aeson 25 | build-depends: base, fast-builder, aeson ^>= 2, tasty-bench, bytestring, 26 | scientific, text, vector, deepseq, unordered-containers, ghc-prim, 27 | template-haskell, mason 28 | ghc-options: -Wall -threaded -g -rtsopts -O2 29 | default-language: Haskell2010 30 | 31 | benchmark bintree 32 | type: exitcode-stdio-1.0 33 | main-is: bintree.hs 34 | hs-source-dirs: . 35 | build-depends: base, tasty-bench, bytestring, mason, fast-builder 36 | ghc-options: -Wall -threaded -O2 37 | default-language: Haskell2010 38 | 39 | benchmark weigh 40 | type: exitcode-stdio-1.0 41 | main-is: json-weigh.hs 42 | hs-source-dirs: aeson 43 | other-modules: Fast, Bstr, Mason 44 | include-dirs: benchmarks/aeson 45 | build-depends: base, fast-builder, aeson, weigh, bytestring, 46 | scientific, text, vector, deepseq, unordered-containers >= 0.2.11, ghc-prim, 47 | template-haskell, mason 48 | ghc-options: -Wall -threaded -g -rtsopts -O2 49 | default-language: Haskell2010 50 | -------------------------------------------------------------------------------- /benchmarks/aeson/template.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE RankNTypes #-} 4 | module NAME 5 | ( fromValue 6 | , toStrictByteString 7 | , valueToByteString 8 | , valueToLazyByteString 9 | , putValue 10 | , literal 11 | , doubleDec 12 | ) where 13 | 14 | import Data.Aeson 15 | import qualified Data.Aeson.Key as Key 16 | import qualified Data.Aeson.KeyMap as HM 17 | import qualified Data.ByteString as B 18 | import qualified Data.ByteString.Lazy as L 19 | import qualified Data.Scientific as Sci 20 | import qualified Data.Text as T 21 | import qualified Data.Text.Encoding as T 22 | import qualified Data.Vector as V 23 | import System.IO (Handle) 24 | 25 | import LIB 26 | #ifdef LIB_EXTRA 27 | import LIB_EXTRA 28 | #endif 29 | 30 | literal :: Builder 31 | literal = "Haskell!Haskell!Haskell!Haskellぅぅうううわぁあああああああああん!!!あぁ…ああ…あっあっー!あぁあああ!!!HaskellHaskellHaskellぅううぁわぁああああ!!" 32 | 33 | putValue :: Handle -> Value -> IO () 34 | putValue h v = hPutBuilder h $ fromValue v 35 | 36 | valueToLazyByteString :: Value -> L.ByteString 37 | valueToLazyByteString = toLazyByteString . fromValue 38 | 39 | valueToByteString :: Value -> B.ByteString 40 | valueToByteString = toStrictByteString . fromValue 41 | 42 | fromValue :: Value -> Builder 43 | fromValue = go0 where 44 | go0 = rebuild . go 45 | go (Object obj) = char8 '{' <> HM.foldMapWithKey f obj <> char8 '}' 46 | where 47 | f k v = 48 | encodeUtf8Builder (Key.toText k) <> char8 ':' <> go0 v 49 | <> char8 ',' 50 | go (Array arr) = V.foldr f (const $ char8 ']') arr True 51 | where 52 | f x r initial = 53 | (if initial then char8 '[' else char8 ',') 54 | <> go0 x <> r False 55 | go (String s) = encodeUtf8Builder s 56 | go (Number n) = case Sci.floatingOrInteger n of 57 | Left x -> doubleDec x 58 | Right x -> integerDec x 59 | go (Bool False) = "false" 60 | go (Bool True) = "true" 61 | go Null = "null" 62 | {-# INLINE fromValue #-} 63 | -------------------------------------------------------------------------------- /src/Mason/Builder/Compat.hs: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------- 2 | -- | 3 | -- Module : Mason.Builder.Compat 4 | -- Copyright : (c) Fumiaki Kinoshita 2020- 5 | -- License : BSD3 6 | -- 7 | -- Maintainer : Fumiaki Kinoshita 8 | -- 9 | -- API which is almost compatible with Data.ByteString.Builder 10 | ---------------------------------------------------------------------------- 11 | module Mason.Builder.Compat 12 | ( Builder 13 | -- * Runners 14 | , D.toStrictByteString 15 | , D.toLazyByteString 16 | , D.hPutBuilderLen 17 | , D.hPutBuilder 18 | , D.sendBuilder 19 | -- * Primitives 20 | , flush 21 | -- * Bytes 22 | , byteString 23 | , lazyByteString 24 | , shortByteString 25 | -- * Text 26 | , textUtf8 27 | , encodeUtf8Builder 28 | , encodeUtf8BuilderEscaped 29 | , char7 30 | , string7 31 | , char8 32 | , string8 33 | , charUtf8 34 | , stringUtf8 35 | -- * Primitive 36 | , storable 37 | , int8 38 | , word8 39 | , int16LE 40 | , int32LE 41 | , int64LE 42 | , word16LE 43 | , word32LE 44 | , word64LE 45 | , floatLE 46 | , doubleLE 47 | , int16BE 48 | , int32BE 49 | , int64BE 50 | , word16BE 51 | , word32BE 52 | , word64BE 53 | , floatBE 54 | , doubleBE 55 | -- * Numeral 56 | , floatDec 57 | , doubleDec 58 | , doubleSI 59 | , doubleExp 60 | , doubleFixed 61 | , word8Dec 62 | , word16Dec 63 | , word32Dec 64 | , word64Dec 65 | , wordDec 66 | , int8Dec 67 | , int16Dec 68 | , int32Dec 69 | , int64Dec 70 | , intDec 71 | , integerDec 72 | , word8Hex 73 | , word16Hex 74 | , word32Hex 75 | , word64Hex 76 | , wordHex 77 | , int8HexFixed 78 | , int16HexFixed 79 | , int32HexFixed 80 | , int64HexFixed 81 | , word8HexFixed 82 | , word16HexFixed 83 | , word32HexFixed 84 | , word64HexFixed 85 | , floatHexFixed 86 | , doubleHexFixed 87 | , byteStringHex 88 | , lazyByteStringHex 89 | ) where 90 | import Mason.Builder hiding (Builder) 91 | import Mason.Builder.Dynamic as D 92 | 93 | type Builder = D.DynBuilder -------------------------------------------------------------------------------- /src/Mason/Builder/Dynamic.hs: -------------------------------------------------------------------------------- 1 | module Mason.Builder.Dynamic 2 | ( DynBuilder 3 | , DynamicBackend(..) 4 | -- * Runners 5 | , toStrictByteString 6 | , toLazyByteString 7 | , hPutBuilderLen 8 | , hPutBuilder 9 | , sendBuilder 10 | ) where 11 | 12 | import qualified Data.ByteString as B 13 | import qualified Data.ByteString.Lazy as BL 14 | import qualified Mason.Builder as B 15 | import qualified Mason.Builder.Internal as B 16 | import Network.Socket (Socket) 17 | import System.IO (Handle) 18 | 19 | data DynamicBackend = DynGrowingBuffer !B.GrowingBuffer 20 | | DynChannel !B.Channel 21 | | DynPutEnv !B.PutEnv 22 | 23 | instance B.Buildable DynamicBackend where 24 | byteString bs = B.Builder $ \env buf -> case env of 25 | DynGrowingBuffer e -> B.unBuilder (B.byteString bs) e buf 26 | DynChannel e -> B.unBuilder (B.byteString bs) e buf 27 | DynPutEnv e -> B.unBuilder (B.byteString bs) e buf 28 | {-# INLINE byteString #-} 29 | flush = B.Builder $ \env buf -> case env of 30 | DynGrowingBuffer e -> B.unBuilder B.flush e buf 31 | DynChannel e -> B.unBuilder B.flush e buf 32 | DynPutEnv e -> B.unBuilder B.flush e buf 33 | {-# INLINE flush #-} 34 | allocate n = B.Builder $ \env buf -> case env of 35 | DynGrowingBuffer e -> B.unBuilder (B.allocate n) e buf 36 | DynChannel e -> B.unBuilder (B.allocate n) e buf 37 | DynPutEnv e -> B.unBuilder (B.allocate n) e buf 38 | {-# INLINE allocate #-} 39 | 40 | -- | Builder with a fixed set of backends. This helps reducing code size 41 | -- and unoptimised code especially on complex/recursive structures, at the cost of 42 | -- extensibility. 43 | type DynBuilder = B.BuilderFor DynamicBackend 44 | 45 | toStrictByteString :: DynBuilder -> B.ByteString 46 | toStrictByteString b = B.toStrictByteString $ B.Builder $ B.unBuilder b . DynGrowingBuffer 47 | {-# INLINE toStrictByteString #-} 48 | 49 | toLazyByteString :: DynBuilder -> BL.ByteString 50 | toLazyByteString b = B.toLazyByteString $ B.Builder $ B.unBuilder b . DynChannel 51 | {-# INLINE toLazyByteString #-} 52 | 53 | hPutBuilder :: Handle -> DynBuilder -> IO () 54 | hPutBuilder h b = B.hPutBuilder h $ B.Builder $ B.unBuilder b . DynPutEnv 55 | {-# INLINE hPutBuilder #-} 56 | 57 | hPutBuilderLen :: Handle -> DynBuilder -> IO Int 58 | hPutBuilderLen h b = B.hPutBuilderLen h $ B.Builder $ B.unBuilder b . DynPutEnv 59 | {-# INLINE hPutBuilderLen #-} 60 | 61 | sendBuilder :: Socket -> DynBuilder -> IO Int 62 | sendBuilder h b = B.sendBuilder h $ B.Builder $ B.unBuilder b . DynPutEnv 63 | {-# INLINE sendBuilder #-} 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mason: alacritous builder library 2 | ==== 3 | 4 | [![Build Status](https://travis-ci.com/fumieval/mason.svg?branch=master)](https://travis-ci.com/fumieval/mason) 5 | [![Hackage](https://img.shields.io/hackage/v/mason)](https://hackage.haskell.org/package/mason) 6 | 7 | mason is a builder & IO library. 8 | 9 | * __Fast__: much faster than bytestring's Builder. 10 | * __Extensible__: Builders can be consumed in a user-defined way. 11 | * __Hackable__: Low-level APIs are exposed. It's easy to plug in even pointer-level operations. 12 | 13 | `Mason.Builder` has API mostly compatible with `Data.ByteString.Builder` but there are some additions to the original API: 14 | 15 | * `toStrictByteString` produces a strict `ByteString` directly. 16 | * `hPutBuilderLen` writes a builder to a handle and returns the number of bytes. 17 | * `sendBuilder` sends the content of `Builder` over a socket. 18 | * `withPopper` turns a builder into http-client's[GivesPopper](http://hackage.haskell.org/package/http-client-0.7.2.1/docs/Network-HTTP-Client.html#t:GivesPopper) 19 | * `toStreamingBody` creates wai's [StreamingBody](http://hackage.haskell.org/package/wai-3.2.2.1/docs/Network-Wai.html#t:StreamingBody) 20 | 21 | Usage 22 | ---- 23 | 24 | Replace `Data.ByteString.Builder` with `Mason.Builder`. Note that if you have `Builder` in the type signature, you'll need `RankNTypes` extensions because of the design explained below. Alternatively, you can also import `Mason.Builder.Compat` which has an API almost compatible with `Data.ByteString.Builder`. 25 | 26 | Performance 27 | ---- 28 | 29 | As long as the code is optimised, mason's builder can be very fast (twice or more as bytestring). Make sure that functions returning `Builder`s are well inlined. 30 | 31 | Serialisation of JSON-like structure: 32 | 33 | ``` 34 | mason/hPutBuilder mean 274.7 μs ( +- 49.40 μs ) 35 | fast-builder/hPutBuilder mean 399.9 μs ( +- 76.05 μs ) 36 | bytestring/hPutBuilder mean 335.1 μs ( +- 86.96 μs ) 37 | mason/toStrictByteString mean 106.6 μs ( +- 6.680 μs ) 38 | fast-builder/toStrictByteString mean 254.8 μs ( +- 31.64 μs ) 39 | bytestring/toLazyByteString mean 283.3 μs ( +- 24.26 μs ) 40 | mason/toLazyByteString mean 127.2 μs ( +- 25.86 μs ) 41 | fast-builder/toLazyByteString mean 249.0 μs ( +- 25.60 μs ) 42 | bytestring/toLazyByteString mean 263.4 μs ( +- 9.401 μs ) 43 | ``` 44 | 45 | In the same benchmark application, the allocation footprint of mason is feathery. 46 | 47 | ``` 48 | toStrictByteString 49 | mason 291,112 0 50 | fast-builder 991,016 0 51 | bytestring 1,158,584 0 (toStrict . toLazyByteString) 52 | 53 | toLazyByteString 54 | Case Allocated GCs 55 | mason 228,936 0 56 | fast-builder 903,752 0 57 | bytestring 1,101,448 0 58 | ``` 59 | 60 | `doubleDec` employs Grisu3 which grants ~20x speedup over `show`-based implementation. 61 | 62 | ``` 63 | mason/double mean 116.2 ns ( +- 6.654 ns ) 64 | fast-builder/double mean 2.183 μs ( +- 85.80 ns ) 65 | bytestring/double mean 2.312 μs ( +- 118.8 ns ) 66 | ``` 67 | 68 | You can find more benchmarks below: 69 | 70 | * [bytes-builder-shootout](https://github.com/andrewthad/bytes-builder-shootout) 71 | 72 |
73 | Click to expand 74 |
 75 | treeToHex-2000/small-bytearray-builder   mean 44.01 μs  ( +- 1.620 μs  )
 76 | treeToHex-2000/fast-builder              mean 34.40 μs  ( +- 390.1 ns  )
 77 | treeToHex-2000/bytestring                mean 58.76 μs  ( +- 3.843 μs  )
 78 | treeToHex-2000/mason                     mean 41.08 μs  ( +- 180.8 ns  )
 79 | treeToHex-9000/small-bytearray-builder   mean 191.8 μs  ( +- 1.835 μs  )
 80 | treeToHex-9000/bytestring                mean 284.8 μs  ( +- 1.156 μs  )
 81 | treeToHex-9000/mason                     mean 181.2 μs  ( +- 386.3 ns  )
 82 | short-text-tree-1000/small-bytearray-builder mean 26.54 μs  ( +- 34.08 ns  )
 83 | short-text-tree-1000/fast-builder        mean 37.51 μs  ( +- 99.85 ns  )
 84 | short-text-tree-1000/bytestring          mean 37.95 μs  ( +- 167.5 ns  )
 85 | short-text-tree-1000/mason               mean 26.87 μs  ( +- 312.4 ns  )
 86 | byte-tree-2000/small-bytearray-builder   mean 30.53 μs  ( +- 51.53 ns  )
 87 | byte-tree-2000/fast-builder              mean 26.91 μs  ( +- 592.2 ns  )
 88 | byte-tree-2000/bytestring                mean 54.40 μs  ( +- 1.743 μs  )
 89 | byte-tree-2000/mason                     mean 34.34 μs  ( +- 193.5 ns  )
 90 | 
91 |
92 | 93 | * [haskell-perf/strict-bytestring-builder](https://github.com/haskell-perf/strict-bytestring-builders) 94 | 95 |
96 | Click to expand 97 |
 98 | averagedAppends-1/byteStringStrictBuilder mean 116.3 ns  ( +- 6.479 ns  )
 99 | averagedAppends-1/byteStringTreeBuilder  mean 181.7 ns  ( +- 20.88 ns  )
100 | averagedAppends-1/fastBuilder            mean 181.5 ns  ( +- 7.219 ns  )
101 | averagedAppends-1/bufferBuilder          mean 728.5 ns  ( +- 9.114 ns  )
102 | averagedAppends-1/byteString             mean 358.7 ns  ( +- 4.663 ns  )
103 | averagedAppends-1/blazeBuilder           mean 356.0 ns  ( +- 7.604 ns  )
104 | averagedAppends-1/binary                 mean 635.0 ns  ( +- 7.936 ns  )
105 | averagedAppends-1/cereal                 mean 638.6 ns  ( +- 12.40 ns  )
106 | averagedAppends-1/mason                  mean 155.2 ns  ( +- 2.000 ns  )
107 | averagedAppends-100/byteStringStrictBuilder mean 7.290 μs  ( +- 99.74 ns  )
108 | averagedAppends-100/byteStringTreeBuilder mean 13.40 μs  ( +- 283.4 ns  )
109 | averagedAppends-100/fastBuilder          mean 13.07 μs  ( +- 418.2 ns  )
110 | averagedAppends-100/bufferBuilder        mean 19.57 μs  ( +- 5.644 μs  )
111 | averagedAppends-100/byteString           mean 17.31 μs  ( +- 1.609 μs  )
112 | averagedAppends-100/blazeBuilder         mean 19.15 μs  ( +- 6.533 μs  )
113 | averagedAppends-100/binary               mean 48.26 μs  ( +- 727.1 ns  )
114 | averagedAppends-100/cereal               mean 51.57 μs  ( +- 21.81 μs  )
115 | averagedAppends-100/mason                mean 12.07 μs  ( +- 233.8 ns  )
116 | averagedAppends-10000/byteStringStrictBuilder mean 1.038 ms  ( +- 18.63 μs  )
117 | averagedAppends-10000/byteStringTreeBuilder mean 1.989 ms  ( +- 70.63 μs  )
118 | averagedAppends-10000/fastBuilder        mean 1.611 ms  ( +- 42.24 μs  )
119 | averagedAppends-10000/bufferBuilder      mean 1.895 ms  ( +- 25.09 μs  )
120 | averagedAppends-10000/byteString         mean 2.248 ms  ( +- 40.99 μs  )
121 | averagedAppends-10000/blazeBuilder       mean 2.394 ms  ( +- 1.016 ms  )
122 | averagedAppends-10000/binary             mean 6.503 ms  ( +- 157.6 μs  )
123 | averagedAppends-10000/cereal             mean 6.458 ms  ( +- 221.6 μs  )
124 | averagedAppends-10000/mason              mean 1.738 ms  ( +- 25.89 μs  )
125 | regularConcat-100/byteStringStrictBuilder mean 1.606 μs  ( +- 32.93 ns  )
126 | regularConcat-100/byteStringTreeBuilder  mean 2.000 μs  ( +- 43.73 ns  )
127 | regularConcat-100/fastBuilder            mean 1.364 μs  ( +- 37.95 ns  )
128 | regularConcat-100/bufferBuilder          mean 2.204 μs  ( +- 48.40 ns  )
129 | regularConcat-100/byteString             mean 1.253 μs  ( +- 25.68 ns  )
130 | regularConcat-100/blazeBuilder           mean 1.317 μs  ( +- 24.05 ns  )
131 | regularConcat-100/binary                 mean 2.845 μs  ( +- 62.24 ns  )
132 | regularConcat-100/cereal                 mean 3.021 μs  ( +- 48.53 ns  )
133 | regularConcat-100/mason                  mean 1.405 μs  ( +- 35.11 ns  )
134 | regularConcat-10000/byteStringStrictBuilder mean 321.3 μs  ( +- 11.13 μs  )
135 | regularConcat-10000/byteStringTreeBuilder mean 349.1 μs  ( +- 4.359 μs  )
136 | regularConcat-10000/fastBuilder          mean 121.0 μs  ( +- 1.755 μs  )
137 | regularConcat-10000/bufferBuilder        mean 156.1 μs  ( +- 2.050 μs  )
138 | regularConcat-10000/byteString           mean 106.6 μs  ( +- 1.355 μs  )
139 | regularConcat-10000/blazeBuilder         mean 110.8 μs  ( +- 1.397 μs  )
140 | regularConcat-10000/binary               mean 308.1 μs  ( +- 5.346 μs  )
141 | regularConcat-10000/cereal               mean 352.0 μs  ( +- 6.142 μs  )
142 | regularConcat-10000/mason                mean 130.2 μs  ( +- 10.39 μs  )
143 | 
144 |
145 | 146 | Architecture 147 | ---- 148 | 149 | Mason's builder is a function that takes a purpose-dependent environment and a buffer. There is little intermediate structure involved; almost everything runs in one pass. This design is inspired by [fast-builder](http://hackage.haskell.org/package/fast-builder). 150 | 151 | ```haskell 152 | type Builder = forall s. Buildable s => BuilderFor s 153 | 154 | newtype BuilderFor s = Builder { unBuilder :: s -> Buffer -> IO Buffer } 155 | 156 | data Buffer = Buffer 157 | { bEnd :: {-# UNPACK #-} !(Ptr Word8) -- ^ end of the buffer (next to the last byte) 158 | , bCur :: {-# UNPACK #-} !(Ptr Word8) -- ^ current position 159 | } 160 | 161 | class Buildable s where 162 | byteString :: B.ByteString -> BuilderFor s 163 | flush :: BuilderFor s 164 | allocate :: Int -> BuilderFor s 165 | ``` 166 | 167 | Instances of the `Buildable` class implement purpose-specific behaviour (e.g. exponentially allocate a buffer, flush to disk). This generic interface also allows creative uses of Builders such as on-the-fly compression. 168 | 169 | `Builder` has a smart constructor called `ensure`: 170 | 171 | ```haskell 172 | ensure :: Int -> (Buffer -> IO Buffer) -> Builder 173 | ``` 174 | 175 | `ensure n f` secures at least `n` bytes in the buffer and passes the pointer to `f`. This gives rise to monoid homorphism; namely, `ensure m f <> ensure n g` will fuse into `ensure (m + n) (f >=> g)` so don't worry about the overhead of bound checking. 176 | 177 | Creating your own primitives 178 | ---- 179 | 180 | The easiest way to create a new primitive is `withPtr`, a simplified version of `ensure`. This is quite convenient for calling foreign functions or anything low-level. 181 | 182 | ```haskell 183 | -- | Construct a 'Builder' from a "poke" function. 184 | withPtr :: Int -- ^ number of bytes to allocate (if needed) 185 | -> (Ptr Word8 -> IO (Ptr Word8)) -- ^ return a next pointer after writing 186 | -> Builder 187 | 188 | grisu v = withPtr 24 $ \ptr -> do 189 | n <- dtoa_grisu3 v ptr 190 | return $ plusPtr ptr (fromIntegral n) 191 | 192 | foreign import ccall unsafe "static dtoa_grisu3" 193 | dtoa_grisu3 :: Double -> Ptr Word8 -> IO CInt 194 | ``` 195 | -------------------------------------------------------------------------------- /cbits/dtoa.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright https://github.com/juj 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | */ 17 | // modified version of https://github.com/juj/MathGeoLib/blob/master/src/Math/grisu3.c 18 | /* This file is part of an implementation of the "grisu3" double to string 19 | conversion algorithm described in the research paper 20 | "Printing Floating-Point Numbers Quickly And Accurately with Integers" 21 | by Florian Loitsch, available at 22 | http://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf */ 23 | 24 | #include // uint64_t etc. 25 | #include // assert 26 | #include // ceil 27 | #include // sprintf 28 | 29 | #ifdef _MSC_VER 30 | #pragma warning(disable : 4204) // nonstandard extension used : non-constant aggregate initializer 31 | #endif 32 | 33 | #define D64_SIGN 0x8000000000000000ULL 34 | #define D64_EXP_MASK 0x7FF0000000000000ULL 35 | #define D64_FRACT_MASK 0x000FFFFFFFFFFFFFULL 36 | #define D64_IMPLICIT_ONE 0x0010000000000000ULL 37 | #define D64_EXP_POS 52 38 | #define D64_EXP_BIAS 1075 39 | #define DIYFP_FRACT_SIZE 64 40 | #define D_1_LOG2_10 0.30102999566398114 // 1 / lg(10) 41 | #define MIN_TARGET_EXP -60 42 | #define MASK32 0xFFFFFFFFULL 43 | 44 | #define CAST_U64(d) (*(uint64_t*)&d) 45 | #define MIN(x,y) ((x) <= (y) ? (x) : (y)) 46 | #define MAX(x,y) ((x) >= (y) ? (x) : (y)) 47 | 48 | #define MIN_CACHED_EXP -348 49 | #define CACHED_EXP_STEP 8 50 | 51 | typedef struct diy_fp 52 | { 53 | uint64_t f; 54 | int e; 55 | } diy_fp; 56 | 57 | typedef struct power 58 | { 59 | uint64_t fract; 60 | int16_t b_exp, d_exp; 61 | } power; 62 | 63 | static const power pow_cache[] = 64 | { 65 | { 0xfa8fd5a0081c0288ULL, -1220, -348 }, 66 | { 0xbaaee17fa23ebf76ULL, -1193, -340 }, 67 | { 0x8b16fb203055ac76ULL, -1166, -332 }, 68 | { 0xcf42894a5dce35eaULL, -1140, -324 }, 69 | { 0x9a6bb0aa55653b2dULL, -1113, -316 }, 70 | { 0xe61acf033d1a45dfULL, -1087, -308 }, 71 | { 0xab70fe17c79ac6caULL, -1060, -300 }, 72 | { 0xff77b1fcbebcdc4fULL, -1034, -292 }, 73 | { 0xbe5691ef416bd60cULL, -1007, -284 }, 74 | { 0x8dd01fad907ffc3cULL, -980, -276 }, 75 | { 0xd3515c2831559a83ULL, -954, -268 }, 76 | { 0x9d71ac8fada6c9b5ULL, -927, -260 }, 77 | { 0xea9c227723ee8bcbULL, -901, -252 }, 78 | { 0xaecc49914078536dULL, -874, -244 }, 79 | { 0x823c12795db6ce57ULL, -847, -236 }, 80 | { 0xc21094364dfb5637ULL, -821, -228 }, 81 | { 0x9096ea6f3848984fULL, -794, -220 }, 82 | { 0xd77485cb25823ac7ULL, -768, -212 }, 83 | { 0xa086cfcd97bf97f4ULL, -741, -204 }, 84 | { 0xef340a98172aace5ULL, -715, -196 }, 85 | { 0xb23867fb2a35b28eULL, -688, -188 }, 86 | { 0x84c8d4dfd2c63f3bULL, -661, -180 }, 87 | { 0xc5dd44271ad3cdbaULL, -635, -172 }, 88 | { 0x936b9fcebb25c996ULL, -608, -164 }, 89 | { 0xdbac6c247d62a584ULL, -582, -156 }, 90 | { 0xa3ab66580d5fdaf6ULL, -555, -148 }, 91 | { 0xf3e2f893dec3f126ULL, -529, -140 }, 92 | { 0xb5b5ada8aaff80b8ULL, -502, -132 }, 93 | { 0x87625f056c7c4a8bULL, -475, -124 }, 94 | { 0xc9bcff6034c13053ULL, -449, -116 }, 95 | { 0x964e858c91ba2655ULL, -422, -108 }, 96 | { 0xdff9772470297ebdULL, -396, -100 }, 97 | { 0xa6dfbd9fb8e5b88fULL, -369, -92 }, 98 | { 0xf8a95fcf88747d94ULL, -343, -84 }, 99 | { 0xb94470938fa89bcfULL, -316, -76 }, 100 | { 0x8a08f0f8bf0f156bULL, -289, -68 }, 101 | { 0xcdb02555653131b6ULL, -263, -60 }, 102 | { 0x993fe2c6d07b7facULL, -236, -52 }, 103 | { 0xe45c10c42a2b3b06ULL, -210, -44 }, 104 | { 0xaa242499697392d3ULL, -183, -36 }, 105 | { 0xfd87b5f28300ca0eULL, -157, -28 }, 106 | { 0xbce5086492111aebULL, -130, -20 }, 107 | { 0x8cbccc096f5088ccULL, -103, -12 }, 108 | { 0xd1b71758e219652cULL, -77, -4 }, 109 | { 0x9c40000000000000ULL, -50, 4 }, 110 | { 0xe8d4a51000000000ULL, -24, 12 }, 111 | { 0xad78ebc5ac620000ULL, 3, 20 }, 112 | { 0x813f3978f8940984ULL, 30, 28 }, 113 | { 0xc097ce7bc90715b3ULL, 56, 36 }, 114 | { 0x8f7e32ce7bea5c70ULL, 83, 44 }, 115 | { 0xd5d238a4abe98068ULL, 109, 52 }, 116 | { 0x9f4f2726179a2245ULL, 136, 60 }, 117 | { 0xed63a231d4c4fb27ULL, 162, 68 }, 118 | { 0xb0de65388cc8ada8ULL, 189, 76 }, 119 | { 0x83c7088e1aab65dbULL, 216, 84 }, 120 | { 0xc45d1df942711d9aULL, 242, 92 }, 121 | { 0x924d692ca61be758ULL, 269, 100 }, 122 | { 0xda01ee641a708deaULL, 295, 108 }, 123 | { 0xa26da3999aef774aULL, 322, 116 }, 124 | { 0xf209787bb47d6b85ULL, 348, 124 }, 125 | { 0xb454e4a179dd1877ULL, 375, 132 }, 126 | { 0x865b86925b9bc5c2ULL, 402, 140 }, 127 | { 0xc83553c5c8965d3dULL, 428, 148 }, 128 | { 0x952ab45cfa97a0b3ULL, 455, 156 }, 129 | { 0xde469fbd99a05fe3ULL, 481, 164 }, 130 | { 0xa59bc234db398c25ULL, 508, 172 }, 131 | { 0xf6c69a72a3989f5cULL, 534, 180 }, 132 | { 0xb7dcbf5354e9beceULL, 561, 188 }, 133 | { 0x88fcf317f22241e2ULL, 588, 196 }, 134 | { 0xcc20ce9bd35c78a5ULL, 614, 204 }, 135 | { 0x98165af37b2153dfULL, 641, 212 }, 136 | { 0xe2a0b5dc971f303aULL, 667, 220 }, 137 | { 0xa8d9d1535ce3b396ULL, 694, 228 }, 138 | { 0xfb9b7cd9a4a7443cULL, 720, 236 }, 139 | { 0xbb764c4ca7a44410ULL, 747, 244 }, 140 | { 0x8bab8eefb6409c1aULL, 774, 252 }, 141 | { 0xd01fef10a657842cULL, 800, 260 }, 142 | { 0x9b10a4e5e9913129ULL, 827, 268 }, 143 | { 0xe7109bfba19c0c9dULL, 853, 276 }, 144 | { 0xac2820d9623bf429ULL, 880, 284 }, 145 | { 0x80444b5e7aa7cf85ULL, 907, 292 }, 146 | { 0xbf21e44003acdd2dULL, 933, 300 }, 147 | { 0x8e679c2f5e44ff8fULL, 960, 308 }, 148 | { 0xd433179d9c8cb841ULL, 986, 316 }, 149 | { 0x9e19db92b4e31ba9ULL, 1013, 324 }, 150 | { 0xeb96bf6ebadf77d9ULL, 1039, 332 }, 151 | { 0xaf87023b9bf0ee6bULL, 1066, 340 } 152 | }; 153 | 154 | static int cached_pow(int exp, diy_fp *p) 155 | { 156 | int k = (int)ceil((exp+DIYFP_FRACT_SIZE-1) * D_1_LOG2_10); 157 | int i = (k-MIN_CACHED_EXP-1) / CACHED_EXP_STEP + 1; 158 | p->f = pow_cache[i].fract; 159 | p->e = pow_cache[i].b_exp; 160 | return pow_cache[i].d_exp; 161 | } 162 | 163 | static diy_fp minus(diy_fp x, diy_fp y) 164 | { 165 | diy_fp d; d.f = x.f - y.f; d.e = x.e; 166 | assert(x.e == y.e && x.f >= y.f); 167 | return d; 168 | } 169 | 170 | static diy_fp multiply(diy_fp x, diy_fp y) 171 | { 172 | uint64_t a, b, c, d, ac, bc, ad, bd, tmp; 173 | diy_fp r; 174 | a = x.f >> 32; b = x.f & MASK32; 175 | c = y.f >> 32; d = y.f & MASK32; 176 | ac = a*c; bc = b*c; 177 | ad = a*d; bd = b*d; 178 | tmp = (bd >> 32) + (ad & MASK32) + (bc & MASK32); 179 | tmp += 1U << 31; // round 180 | r.f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32); 181 | r.e = x.e + y.e + 64; 182 | return r; 183 | } 184 | 185 | static diy_fp normalize_diy_fp(diy_fp n) 186 | { 187 | assert(n.f != 0); 188 | while(!(n.f & 0xFFC0000000000000ULL)) { n.f <<= 10; n.e -= 10; } 189 | while(!(n.f & D64_SIGN)) { n.f <<= 1; --n.e; } 190 | return n; 191 | } 192 | 193 | static diy_fp double2diy_fp(double d) 194 | { 195 | diy_fp fp; 196 | uint64_t u64 = CAST_U64(d); 197 | if (!(u64 & D64_EXP_MASK)) { fp.f = u64 & D64_FRACT_MASK; fp.e = 1 - D64_EXP_BIAS; } 198 | else { fp.f = (u64 & D64_FRACT_MASK) + D64_IMPLICIT_ONE; fp.e = (int)((u64 & D64_EXP_MASK) >> D64_EXP_POS) - D64_EXP_BIAS; } 199 | return fp; 200 | } 201 | 202 | // pow10_cache[i] = 10^(i-1) 203 | static const unsigned int pow10_cache[] = { 0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; 204 | 205 | static int largest_pow10(uint32_t n, int n_bits, uint32_t *power) 206 | { 207 | int guess = ((n_bits + 1) * 1233 >> 12) + 1/*skip first entry*/; 208 | if (n < pow10_cache[guess]) --guess; // We don't have any guarantees that 2^n_bits <= n. 209 | *power = pow10_cache[guess]; 210 | return guess; 211 | } 212 | 213 | static int round_weed(char *buffer, int len, uint64_t wp_W, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t ulp) 214 | { 215 | uint64_t wp_Wup = wp_W - ulp; 216 | uint64_t wp_Wdown = wp_W + ulp; 217 | while(rest < wp_Wup && delta - rest >= ten_kappa 218 | && (rest + ten_kappa < wp_Wup || wp_Wup - rest >= rest + ten_kappa - wp_Wup)) 219 | { 220 | --buffer[len-1]; 221 | rest += ten_kappa; 222 | } 223 | if (rest < wp_Wdown && delta - rest >= ten_kappa 224 | && (rest + ten_kappa < wp_Wdown || wp_Wdown - rest > rest + ten_kappa - wp_Wdown)) 225 | return 0; 226 | 227 | return 2*ulp <= rest && rest <= delta - 4*ulp; 228 | } 229 | 230 | static int digit_gen(diy_fp low, diy_fp w, diy_fp high, char *buffer, int *length, int *kappa) 231 | { 232 | uint64_t unit = 1; 233 | diy_fp too_low = { low.f - unit, low.e }; 234 | diy_fp too_high = { high.f + unit, high.e }; 235 | diy_fp unsafe_interval = minus(too_high, too_low); 236 | diy_fp one = { 1ULL << -w.e, w.e }; 237 | uint32_t p1 = (uint32_t)(too_high.f >> -one.e); 238 | uint64_t p2 = too_high.f & (one.f - 1); 239 | uint32_t div; 240 | *kappa = largest_pow10(p1, DIYFP_FRACT_SIZE + one.e, &div); 241 | *length = 0; 242 | 243 | while(*kappa > 0) 244 | { 245 | uint64_t rest; 246 | int digit = p1 / div; 247 | buffer[*length] = (char)('0' + digit); 248 | ++*length; 249 | p1 %= div; 250 | --*kappa; 251 | rest = ((uint64_t)p1 << -one.e) + p2; 252 | if (rest < unsafe_interval.f) return round_weed(buffer, *length, minus(too_high, w).f, unsafe_interval.f, rest, (uint64_t)div << -one.e, unit); 253 | div /= 10; 254 | } 255 | 256 | for(;;) 257 | { 258 | int digit; 259 | p2 *= 10; 260 | unit *= 10; 261 | unsafe_interval.f *= 10; 262 | // Integer division by one. 263 | digit = (int)(p2 >> -one.e); 264 | buffer[*length] = (char)('0' + digit); 265 | ++*length; 266 | p2 &= one.f - 1; // Modulo by one. 267 | --*kappa; 268 | if (p2 < unsafe_interval.f) return round_weed(buffer, *length, minus(too_high, w).f * unit, unsafe_interval.f, p2, one.f, unit); 269 | } 270 | } 271 | 272 | int grisu3(double v, char *buffer, int *length, int *d_exp) 273 | { 274 | int mk, kappa, success; 275 | diy_fp dfp = double2diy_fp(v); 276 | diy_fp w = normalize_diy_fp(dfp); 277 | 278 | // normalize boundaries 279 | diy_fp t = { (dfp.f << 1) + 1, dfp.e - 1 }; 280 | diy_fp b_plus = normalize_diy_fp(t); 281 | diy_fp b_minus; 282 | diy_fp c_mk; // Cached power of ten: 10^-k 283 | uint64_t u64 = CAST_U64(v); 284 | assert(v > 0 && v <= 1.7976931348623157e308); // Grisu only handles strictly positive finite numbers. 285 | if (!(u64 & D64_FRACT_MASK) && (u64 & D64_EXP_MASK) != 0) { b_minus.f = (dfp.f << 2) - 1; b_minus.e = dfp.e - 2;} // lower boundary is closer? 286 | else { b_minus.f = (dfp.f << 1) - 1; b_minus.e = dfp.e - 1; } 287 | b_minus.f = b_minus.f << (b_minus.e - b_plus.e); 288 | b_minus.e = b_plus.e; 289 | 290 | mk = cached_pow(MIN_TARGET_EXP - DIYFP_FRACT_SIZE - w.e, &c_mk); 291 | 292 | w = multiply(w, c_mk); 293 | b_minus = multiply(b_minus, c_mk); 294 | b_plus = multiply(b_plus, c_mk); 295 | 296 | success = digit_gen(b_minus, w, b_plus, buffer, length, &kappa); 297 | *d_exp = kappa - mk; 298 | return success; 299 | } 300 | 301 | static int i_to_str(int val, char *str) 302 | { 303 | int len, i; 304 | char *s; 305 | char *begin = str; 306 | if (val < 0) { *str++ = '-'; val = -val; } 307 | s = str; 308 | 309 | for(;;) 310 | { 311 | int ni = val / 10; 312 | int digit = val - ni*10; 313 | *s++ = (char)('0' + digit); 314 | if (ni == 0) 315 | break; 316 | val = ni; 317 | } 318 | *s = '\0'; 319 | len = (int)(s - str); 320 | for(i = 0; i < len/2; ++i) 321 | { 322 | char ch = str[i]; 323 | str[i] = str[len-1-i]; 324 | str[len-1-i] = ch; 325 | } 326 | 327 | return (int)(s - begin); 328 | } 329 | 330 | int dtoa_grisu3(double v, char *dst) 331 | { 332 | int d_exp, len, decimals, i; 333 | 334 | int success = grisu3(v, dst, &len, &d_exp); 335 | // If grisu3 was not able to convert the number to a string, then use old sprintf (suboptimal). 336 | if (!success) return sprintf(dst, "%.17g", v) + (int)(dst - dst); 337 | 338 | // We now have an integer string of form "151324135" and a base-10 exponent for that number. 339 | // Next, decide the best presentation for that string by whether to use a decimal point, or the scientific exponent notation 'e'. 340 | // We don't pick the absolute shortest representation, but pick a balance between readability and shortness, e.g. 341 | // 1.545056189557677e-308 could be represented in a shorter form 342 | // 1545056189557677e-323 but that would be somewhat unreadable. 343 | decimals = MIN(-d_exp, MAX(1, len-1)); 344 | if (d_exp < 0 && len > 1) // Add decimal point? 345 | { 346 | for(i = 0; i < decimals; ++i) dst[len-i] = dst[len-i-1]; 347 | dst[len++ - decimals] = '.'; 348 | d_exp += decimals; 349 | // Need scientific notation as well? 350 | if (d_exp != 0) { dst[len++] = 'e'; len += i_to_str(d_exp, dst+len); } 351 | } 352 | else if (d_exp < 0 && d_exp >= -3) // Add decimal point for numbers of form 0.000x where it's shorter? 353 | { 354 | for(i = 0; i < len; ++i) dst[len-d_exp-1-i] = dst[len-i-1]; 355 | dst[0] = '.'; 356 | for(i = 1; i < -d_exp; ++i) dst[i] = '0'; 357 | len += -d_exp; 358 | } 359 | // Add scientific notation? 360 | else if (d_exp < 0 || d_exp > 2) { dst[len++] = 'e'; len += i_to_str(d_exp, dst+len); } 361 | // Add zeroes instead of scientific notation? 362 | else if (d_exp > 0) { while(d_exp-- > 0) dst[len++] = '0'; } 363 | // dst[len] = '\0'; // grisu3 doesn't null terminate, so ensure termination. 364 | return (int)(dst+len-dst); 365 | } 366 | -------------------------------------------------------------------------------- /src/Mason/Builder/Internal.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | {-# LANGUAGE BangPatterns #-} 3 | {-# LANGUAGE TypeFamilies #-} 4 | {-# LANGUAGE MagicHash #-} 5 | {-# LANGUAGE UnboxedTuples #-} 6 | {-# LANGUAGE RecordWildCards #-} 7 | {-# LANGUAGE PatternSynonyms #-} 8 | {-# LANGUAGE ViewPatterns #-} 9 | {-# LANGUAGE CPP #-} 10 | module Mason.Builder.Internal (Builder 11 | , BuilderFor(..) 12 | , BState 13 | , Buildable(..) 14 | , GrowingBuffer(..) 15 | , Buffer(..) 16 | , pattern Builder 17 | , unBuilder 18 | , byteStringCopy 19 | , shortByteString 20 | , StrictByteStringBackend 21 | , toStrictByteString 22 | , Channel(..) 23 | , LazyByteStringBackend 24 | , toLazyByteString 25 | , withPopper 26 | , StreamingBackend(..) 27 | , toStreamingBody 28 | , stringUtf8 29 | , lengthPrefixedWithin 30 | , primBounded 31 | , primFixed 32 | , primMapListFixed 33 | , primMapListBounded 34 | , primMapByteStringFixed 35 | , primMapLazyByteStringFixed 36 | , PutEnv(..) 37 | , BufferedIOBackend 38 | , hPutBuilderLen 39 | , encodeUtf8BuilderEscaped 40 | , sendBuilder 41 | , cstring 42 | , cstringUtf8 43 | , withPtr 44 | , storable 45 | , paddedBoundedPrim 46 | , zeroPaddedBoundedPrim 47 | -- * Internal 48 | , ensure 49 | , allocateConstant 50 | , withGrisu3 51 | , withGrisu3Rounded 52 | , roundDigit 53 | ) where 54 | 55 | import Control.Concurrent 56 | import Control.Exception (throw) 57 | import Control.Monad 58 | import qualified Data.ByteString as B 59 | import qualified Data.ByteString.Short.Internal as SB 60 | import qualified Data.ByteString.Lazy as BL 61 | import qualified Data.ByteString.Lazy.Internal as BL 62 | import qualified Data.ByteString.Internal as B 63 | import qualified Data.ByteString.Builder as BB 64 | import qualified Data.ByteString.Builder.Prim as P 65 | import qualified Data.ByteString.Builder.Prim.Internal as B 66 | import System.IO 67 | import Foreign.C.Types 68 | import Foreign.Ptr 69 | import Foreign.ForeignPtr.Unsafe 70 | import Foreign.ForeignPtr 71 | import Foreign.Marshal.Array (allocaArray) 72 | import Data.IORef 73 | import Data.Word (Word8) 74 | import Data.String 75 | import Foreign.Storable as S 76 | import System.IO.Unsafe 77 | import qualified Data.Text.Array as A 78 | import qualified Data.Text.Internal as T 79 | import qualified Network.Socket as S 80 | import GHC.Prim (plusAddr#, indexWord8OffAddr#, RealWorld, Addr#, State# ) 81 | import GHC.Ptr (Ptr(..)) 82 | import GHC.Word (Word8(..)) 83 | import GHC.Base (unpackCString#, unpackCStringUtf8#, unpackFoldrCString#, build, IO(..), unIO) 84 | 85 | #if MIN_VERSION_text(2,0,0) 86 | #else 87 | -- imports required by 'encodeUtf8BuilderEscaped' 88 | import Data.Bits ((.&.), shiftR) 89 | import Data.Text.Internal.Unsafe.Char (ord) 90 | import qualified Data.Text.Internal.Encoding.Utf16 as U16 91 | #endif 92 | 93 | -- https://www.haskell.org/ghc/blog/20210607-the-keepAlive-story.html 94 | unsafeWithForeignPtr :: ForeignPtr a -> (Ptr a -> IO b) -> IO b 95 | unsafeWithForeignPtr fo f = do 96 | r <- f (unsafeForeignPtrToPtr fo) 97 | touchForeignPtr fo 98 | return r 99 | 100 | -- | The Builder type. Requires RankNTypes extension 101 | type Builder = forall s. Buildable s => BuilderFor s 102 | 103 | -- | Builder specialised for a backend 104 | newtype BuilderFor s = RawBuilder { unRawBuilder :: s -> BState -> BState } 105 | 106 | unBuilder :: BuilderFor s -> s -> Buffer -> IO Buffer 107 | unBuilder (RawBuilder f) = \env (Buffer (Ptr ptr) (Ptr end)) -> IO (\s -> case f env (# ptr, end, s #) of 108 | (# ptr', end', s' #) -> (# s', Buffer (Ptr ptr') (Ptr end') #)) 109 | {-# INLINE unBuilder #-} 110 | 111 | pattern Builder :: (s -> Buffer -> IO Buffer) -> BuilderFor s 112 | pattern Builder f <- (unBuilder -> f) where 113 | Builder f = RawBuilder $ \env (# ptr, end, s #) -> case unIO (f env (Buffer (Ptr ptr) (Ptr end))) s of 114 | (# s', Buffer (Ptr ptr') (Ptr end') #) -> (# ptr', end', s' #) 115 | 116 | {-# COMPLETE Builder #-} 117 | 118 | type BState = (#Addr#, Addr#, State# RealWorld #) 119 | 120 | -- | This class is used to provide backend-specific operations for running a 'Builder'. 121 | class Buildable s where 122 | -- | Put a 'B.ByteString'. 123 | byteString :: B.ByteString -> BuilderFor s 124 | byteString = byteStringCopy 125 | {-# INLINE byteString #-} 126 | -- | Flush the content of the internal buffer. 127 | flush :: BuilderFor s 128 | -- | Allocate a buffer with at least the given length. 129 | allocate :: Int -> BuilderFor s 130 | 131 | -- | Buffer pointers 132 | data Buffer = Buffer 133 | { bEnd :: {-# UNPACK #-} !(Ptr Word8) -- ^ end of the buffer (next to the last byte) 134 | , bCur :: {-# UNPACK #-} !(Ptr Word8) -- ^ current position 135 | } 136 | 137 | -- | Copy a 'B.ByteString' to a buffer. 138 | byteStringCopy :: Buildable s => B.ByteString -> BuilderFor s 139 | byteStringCopy = \(B.PS fsrc ofs len) -> withPtr len $ \ptr -> do 140 | unsafeWithForeignPtr fsrc $ \src -> B.memcpy ptr (src `plusPtr` ofs) len 141 | return $ ptr `plusPtr` len 142 | {-# INLINE byteStringCopy #-} 143 | 144 | -- | Copy a 'SB.ShortByteString' to a buffer. 145 | shortByteString :: SB.ShortByteString -> Builder 146 | shortByteString = \src -> let len = SB.length src in withPtr len $ \ptr -> 147 | plusPtr ptr len <$ SB.copyToPtr src 0 ptr len 148 | {-# INLINE shortByteString #-} 149 | 150 | -- | Construct a 'Builder' from a "poke" function. 151 | withPtr :: Buildable s 152 | => Int -- ^ number of bytes to allocate (if needed) 153 | -> (Ptr Word8 -> IO (Ptr Word8)) -- ^ return a next pointer after writing 154 | -> BuilderFor s 155 | withPtr n f = ensure n $ \(Buffer e p) -> Buffer e <$> f p 156 | {-# INLINE withPtr #-} 157 | 158 | -- | Turn a 'Storable' value into a 'Builder' 159 | storable :: Storable a => a -> Builder 160 | storable a = withPtr (sizeOf a) $ \p -> plusPtr p (sizeOf a) <$ poke (castPtr p) a 161 | {-# INLINE storable #-} 162 | 163 | -- | Ensure that the given number of bytes is available in the buffer. Subject to semigroup fusion 164 | ensure :: Int -> (Buffer -> IO Buffer) -> Builder 165 | ensure mlen cont = Builder $ \env buf@(Buffer end ptr) -> 166 | if ptr `plusPtr` mlen >= end 167 | then do 168 | buf'@(Buffer end' ptr') <- unBuilder flush env buf 169 | if mlen <= minusPtr end' ptr' 170 | then cont buf' 171 | else unBuilder (allocate mlen) env buf' >>= cont 172 | else cont buf 173 | {-# INLINE[1] ensure #-} 174 | 175 | {-# RULES "<>/ensure" forall m n f g. ensure m f <> ensure n g = ensure (m + n) (f >=> g) #-} 176 | 177 | -- | Run a builder within a buffer and prefix it by the length. 178 | lengthPrefixedWithin :: Int -- ^ maximum length 179 | -> B.BoundedPrim Int -- ^ prefix encoder 180 | -> BuilderFor () -> Builder 181 | lengthPrefixedWithin maxLen bp builder = ensure (B.sizeBound bp + maxLen) $ \(Buffer end origin) -> do 182 | let base = origin `plusPtr` B.sizeBound bp 183 | Buffer _ base' <- unBuilder builder () (Buffer end base) 184 | let len = minusPtr base' base 185 | newBase <- B.runB bp len origin 186 | c_memmove newBase base len 187 | return $ Buffer end (newBase `plusPtr` len) 188 | {-# INLINE lengthPrefixedWithin #-} 189 | 190 | -- | Work with a constant buffer. 'allocate' will always fail. 191 | instance Buildable () where 192 | byteString = byteStringCopy 193 | {-# INLINE byteString #-} 194 | flush = mempty 195 | {-# INLINE flush #-} 196 | allocate _ = Builder $ \_ _ -> fail "Mason.Builder.Internal.allocate: can't allocate" 197 | {-# INLINE allocate #-} 198 | 199 | instance Semigroup (BuilderFor s) where 200 | RawBuilder f <> RawBuilder g = RawBuilder $ \e s -> g e (f e s) 201 | {-# INLINE[1] (<>) #-} 202 | 203 | instance Monoid (BuilderFor a) where 204 | mempty = RawBuilder (\_ s -> s) 205 | {-# INLINE mempty #-} 206 | 207 | -- | UTF-8 encode a 'String'. 208 | stringUtf8 :: Buildable s => String -> BuilderFor s 209 | stringUtf8 = primMapListBounded P.charUtf8 210 | {-# INLINE [1] stringUtf8 #-} 211 | 212 | {-# RULES 213 | "stringUtf8/unpackCStringUtf8#" forall s. 214 | stringUtf8 (unpackCStringUtf8# s) = cstringUtf8 (Ptr s) 215 | 216 | "stringUtf8/unpackCString#" forall s. 217 | stringUtf8 (unpackCString# s) = cstring (Ptr s) 218 | 219 | "stringUtf8/unpackFoldrCString#" forall s. 220 | stringUtf8 (build (unpackFoldrCString# s)) = cstring (Ptr s) 221 | #-} 222 | 223 | cstring :: Ptr Word8 -> Builder 224 | cstring (Ptr addr0) = Builder $ step addr0 225 | where 226 | step addr env br@(Buffer end ptr) 227 | | W8# ch == 0 = pure br 228 | | ptr == end = unBuilder (ensure 3 $ step addr env) env br 229 | | otherwise = do 230 | S.poke ptr (W8# ch) 231 | let br' = Buffer end (ptr `plusPtr` 1) 232 | step (addr `plusAddr#` 1#) env br' 233 | where 234 | !ch = indexWord8OffAddr# addr 0# 235 | {-# INLINE cstring #-} 236 | 237 | cstringUtf8 :: Ptr Word8 -> Builder 238 | cstringUtf8 (Ptr addr0) = Builder $ step addr0 239 | where 240 | step addr env br@(Buffer end ptr) 241 | | W8# ch == 0 = pure br 242 | | ptr == end = unBuilder (ensure 3 $ step addr env) env br 243 | -- NULL is encoded as 0xc0 0x80 244 | | W8# ch == 0xc0 245 | , W8# (indexWord8OffAddr# addr 1#) == 0x80 = do 246 | S.poke ptr 0 247 | step (addr `plusAddr#` 2#) env (Buffer end (ptr `plusPtr` 1)) 248 | | otherwise = do 249 | S.poke ptr (W8# ch) 250 | step (addr `plusAddr#` 1#) env (Buffer end (ptr `plusPtr` 1)) 251 | where 252 | !ch = indexWord8OffAddr# addr 0# 253 | {-# INLINE cstringUtf8 #-} 254 | 255 | instance Buildable s => IsString (BuilderFor s) where 256 | fromString = stringUtf8 257 | {-# INLINE fromString #-} 258 | 259 | -- | Use 'B.BoundedPrim' 260 | primBounded :: Buildable s => B.BoundedPrim a -> a -> BuilderFor s 261 | primBounded bp = withPtr (B.sizeBound bp) . B.runB bp 262 | {-# INLINE primBounded #-} 263 | 264 | -- | Use 'B.FixedPrim' 265 | primFixed :: Buildable s => B.FixedPrim a -> a -> BuilderFor s 266 | primFixed fp a = withPtr (B.size fp) $ \ptr -> (ptr `plusPtr` B.size fp) <$ B.runF fp a ptr 267 | {-# INLINE primFixed #-} 268 | 269 | primMapListFixed :: (Foldable t, Buildable s) => B.FixedPrim a -> t a -> BuilderFor s 270 | primMapListFixed fp = foldMap (primFixed fp) 271 | {-# INLINE primMapListFixed #-} 272 | 273 | primMapListBounded :: Buildable s => B.BoundedPrim a -> [a] -> BuilderFor s 274 | primMapListBounded bp = foldMap (primBounded bp) 275 | {-# INLINE primMapListBounded #-} 276 | 277 | primMapByteStringFixed :: Buildable s => B.FixedPrim Word8 -> B.ByteString -> BuilderFor s 278 | primMapByteStringFixed fp = B.foldr (mappend . primFixed fp) mempty 279 | {-# INLINE primMapByteStringFixed #-} 280 | 281 | primMapLazyByteStringFixed :: Buildable s => B.FixedPrim Word8 -> BL.ByteString -> BuilderFor s 282 | primMapLazyByteStringFixed fp = BL.foldr (mappend . primFixed fp) mempty 283 | {-# INLINE primMapLazyByteStringFixed #-} 284 | 285 | paddedBoundedPrim 286 | :: Word8 -- ^ filler 287 | -> Int -- ^ pad if shorter than this 288 | -> B.BoundedPrim a 289 | -> a 290 | -> Builder 291 | paddedBoundedPrim ch size bp a = ensure (B.sizeBound bp) $ \(Buffer end ptr) -> do 292 | ptr' <- B.runB bp a ptr 293 | let len = ptr' `minusPtr` ptr 294 | let pad = size - len 295 | when (pad > 0) $ do 296 | c_memmove (ptr `plusPtr` pad) ptr len 297 | void $ B.memset ptr ch (fromIntegral pad) 298 | return $ Buffer end $ ptr' `plusPtr` max pad 0 299 | 300 | zeroPaddedBoundedPrim :: Int -> B.BoundedPrim a -> a -> Builder 301 | zeroPaddedBoundedPrim = paddedBoundedPrim 48 302 | 303 | newtype GrowingBuffer = GrowingBuffer (IORef (ForeignPtr Word8)) 304 | 305 | instance Buildable GrowingBuffer where 306 | byteString = byteStringCopy 307 | {-# INLINE byteString #-} 308 | flush = mempty 309 | {-# INLINE flush #-} 310 | allocate len = Builder $ \(GrowingBuffer bufferRef) (Buffer _ dst) -> do 311 | fptr0 <- readIORef bufferRef 312 | let ptr0 = unsafeForeignPtrToPtr fptr0 313 | let !pos = dst `minusPtr` ptr0 314 | let !size' = pos + max len pos 315 | fptr <- mallocForeignPtrBytes size' 316 | let !dst' = unsafeForeignPtrToPtr fptr 317 | B.memcpy dst' ptr0 pos 318 | writeIORef bufferRef fptr 319 | return $ Buffer (dst' `plusPtr` size') (dst' `plusPtr` pos) 320 | {-# INLINE allocate #-} 321 | 322 | type StrictByteStringBackend = GrowingBuffer 323 | 324 | -- | Create a strict 'B.ByteString' 325 | toStrictByteString :: BuilderFor StrictByteStringBackend -> B.ByteString 326 | toStrictByteString b = unsafePerformIO $ do 327 | fptr0 <- mallocForeignPtrBytes initialSize 328 | bufferRef <- newIORef fptr0 329 | let ptr0 = unsafeForeignPtrToPtr fptr0 330 | 331 | Buffer _ pos <- unBuilder b (GrowingBuffer bufferRef) 332 | $ Buffer (ptr0 `plusPtr` initialSize) ptr0 333 | 334 | fptr <- readIORef bufferRef 335 | pure $ B.PS fptr 0 (pos `minusPtr` unsafeForeignPtrToPtr fptr) 336 | 337 | where 338 | initialSize = 128 339 | {-# INLINE toStrictByteString #-} 340 | 341 | data Channel = Channel 342 | { chResp :: !(MVar B.ByteString) 343 | , chBuffer :: !(IORef (ForeignPtr Word8)) 344 | } 345 | 346 | instance Buildable Channel where 347 | byteString bs 348 | | B.length bs < 4096 = byteStringCopy bs 349 | | otherwise = flush <> Builder (\(Channel v _) b -> b <$ putMVar v bs) 350 | {-# INLINE byteString #-} 351 | flush = Builder $ \(Channel v ref) (Buffer end ptr) -> do 352 | ptr0 <- unsafeForeignPtrToPtr <$> readIORef ref 353 | let len = minusPtr ptr ptr0 354 | when (len > 0) $ do 355 | bs <- B.mallocByteString len 356 | unsafeWithForeignPtr bs $ \dst -> B.memcpy dst ptr0 len 357 | putMVar v $ B.PS bs 0 len 358 | return $! Buffer end ptr0 359 | {-# INLINE flush #-} 360 | allocate = allocateConstant chBuffer 361 | {-# INLINE allocate #-} 362 | 363 | type LazyByteStringBackend = Channel 364 | 365 | -- | Create a lazy 'BL.ByteString'. Threaded runtime is required. 366 | toLazyByteString :: BuilderFor LazyByteStringBackend -> BL.ByteString 367 | toLazyByteString body = unsafePerformIO $ withPopper body $ \pop -> do 368 | let go _ = do 369 | bs <- pop 370 | return $! if B.null bs 371 | then BL.empty 372 | else BL.Chunk bs $ unsafePerformIO $ go () 373 | return $ unsafePerformIO $ go () 374 | {-# INLINE toLazyByteString #-} 375 | 376 | -- | Use 'Builder' as a (IO B.ByteString -> IO a) -> IO a 378 | withPopper body cont = do 379 | resp <- newEmptyMVar 380 | 381 | let initialSize = 4080 382 | fptr <- mallocForeignPtrBytes initialSize 383 | ref <- newIORef fptr 384 | let ptr = unsafeForeignPtrToPtr fptr 385 | 386 | let final (Left e) = putMVar resp (throw e) 387 | final (Right _) = putMVar resp B.empty 388 | _ <- flip forkFinally final $ unBuilder (body <> flush) (Channel resp ref) 389 | $ Buffer (ptr `plusPtr` initialSize) ptr 390 | 391 | cont $ takeMVar resp 392 | {-# INLINE withPopper #-} 393 | 394 | -- | Environment for handle output 395 | data PutEnv = PutEnv 396 | { peThreshold :: !Int 397 | , pePut :: !(Ptr Word8 -> Ptr Word8 -> IO ()) 398 | -- ^ takes a pointer range and returns the number of bytes written 399 | , peBuffer :: !(IORef (ForeignPtr Word8)) 400 | , peTotal :: !(IORef Int) 401 | } 402 | 403 | -- | Allocate a new buffer. 404 | allocateConstant :: (s -> IORef (ForeignPtr Word8)) -> Int -> BuilderFor s 405 | allocateConstant f len = Builder $ \env (Buffer _ _) -> do 406 | fptr <- mallocForeignPtrBytes len 407 | writeIORef (f env) fptr 408 | let ptr1 = unsafeForeignPtrToPtr fptr 409 | return $! Buffer (ptr1 `plusPtr` len) ptr1 410 | {-# INLINE allocateConstant #-} 411 | 412 | instance Buildable PutEnv where 413 | byteString bs@(B.PS fptr ofs len) = Builder $ \env@PutEnv{..} buf -> if len > peThreshold 414 | then do 415 | buf' <- unBuilder flush env buf 416 | unsafeWithForeignPtr fptr $ \ptr -> do 417 | let ptr0 = ptr `plusPtr` ofs 418 | pePut ptr0 (ptr0 `plusPtr` len) 419 | pure buf' 420 | else unBuilder (byteStringCopy bs) env buf 421 | {-# INLINE byteString #-} 422 | 423 | flush = Builder $ \PutEnv{..} (Buffer end ptr) -> do 424 | ptr0 <- unsafeForeignPtrToPtr <$> readIORef peBuffer 425 | let len = minusPtr ptr ptr0 426 | modifyIORef' peTotal (+len) 427 | pePut ptr0 ptr 428 | return $! Buffer end ptr0 429 | {-# INLINE flush #-} 430 | 431 | allocate = allocateConstant peBuffer 432 | {-# INLINE allocate #-} 433 | 434 | type BufferedIOBackend = PutEnv 435 | 436 | -- | Write a 'Builder' into a handle and obtain the number of bytes written. 437 | -- 'flush' does not imply actual disk operations. Set 'NoBuffering' if you want 438 | -- it to write the content immediately. 439 | hPutBuilderLen :: Handle -> BuilderFor BufferedIOBackend -> IO Int 440 | hPutBuilderLen h b = do 441 | let initialSize = 4096 442 | fptr <- mallocForeignPtrBytes initialSize 443 | ref <- newIORef fptr 444 | let ptr = unsafeForeignPtrToPtr fptr 445 | counter <- newIORef 0 446 | _ <- unBuilder (b <> flush) 447 | (PutEnv initialSize (\ptr0 ptr1 -> hPutBuf h ptr (minusPtr ptr1 ptr0)) ref counter) 448 | (Buffer (ptr `plusPtr` initialSize) ptr) 449 | readIORef counter 450 | {-# INLINE hPutBuilderLen #-} 451 | 452 | sendBufRange :: S.Socket -> Ptr Word8 -> Ptr Word8 -> IO () 453 | sendBufRange sock ptr0 ptr1 = go ptr0 where 454 | go p 455 | | p >= ptr1 = return () 456 | | otherwise = do 457 | sent <- S.sendBuf sock p (minusPtr ptr1 p) 458 | S.withFdSocket sock $ threadWaitWrite . fromIntegral 459 | when (sent > 0) $ go $ p `plusPtr` sent 460 | 461 | -- | Write a 'Builder' into a handle and obtain the number of bytes written. 462 | sendBuilder :: S.Socket -> BuilderFor BufferedIOBackend -> IO Int 463 | sendBuilder sock b = do 464 | let initialSize = 4096 465 | fptr <- mallocForeignPtrBytes initialSize 466 | ref <- newIORef fptr 467 | let ptr = unsafeForeignPtrToPtr fptr 468 | counter <- newIORef 0 469 | _ <- unBuilder (b <> flush) 470 | (PutEnv initialSize (sendBufRange sock) ref counter) 471 | (Buffer (ptr `plusPtr` initialSize) ptr) 472 | readIORef counter 473 | {-# INLINE sendBuilder #-} 474 | 475 | {-# INLINE encodeUtf8BuilderEscaped #-} 476 | 477 | -- | Encode 'T.Text' with a custom escaping function. 478 | -- 479 | -- Note that implementation differs between @text-1.x@ and @text-2.x@ due to the 480 | -- package moving from using UTF-16 to UTF-8 for the internal representation. 481 | encodeUtf8BuilderEscaped :: Buildable s => B.BoundedPrim Word8 -> T.Text -> BuilderFor s 482 | 483 | #if MIN_VERSION_text(2,0,0) 484 | encodeUtf8BuilderEscaped be = step where 485 | bound = max 4 $ B.sizeBound be 486 | 487 | step (T.Text arr off len) = Builder $ loop off where 488 | iend = off + len 489 | loop !i0 env !br@(Buffer ope op0) 490 | | i0 >= iend = return br 491 | | outRemaining > 0 = goPartial (i0 + min outRemaining inpRemaining) 492 | | otherwise = unBuilder (ensure bound (loop i0 env)) env br 493 | where 494 | outRemaining = (ope `minusPtr` op0) `quot` bound 495 | inpRemaining = iend - i0 496 | 497 | goPartial !iendTmp = go i0 op0 498 | where 499 | go !i !op 500 | | i < iendTmp = do 501 | let w = A.unsafeIndex arr i 502 | if w < 0x80 503 | then B.runB be w op >>= go (i + 1) 504 | else poke op w >> go (i + 1) (op `plusPtr` 1) 505 | | otherwise = loop i env (Buffer ope op) 506 | 507 | #else 508 | encodeUtf8BuilderEscaped be = step where 509 | bound = max 4 $ B.sizeBound be 510 | 511 | step (T.Text arr off len) = Builder $ loop off where 512 | iend = off + len 513 | loop !i0 env !br@(Buffer ope op0) 514 | | i0 >= iend = return br 515 | | outRemaining > 0 = goPartial (i0 + min outRemaining inpRemaining) 516 | | otherwise = unBuilder (ensure bound (loop i0 env)) env br 517 | where 518 | outRemaining = (ope `minusPtr` op0) `div` bound 519 | inpRemaining = iend - i0 520 | 521 | goPartial !iendTmp = go i0 op0 522 | where 523 | go !i !op 524 | | i < iendTmp = case A.unsafeIndex arr i of 525 | w | w <= 0x7F -> do 526 | B.runB be (fromIntegral w) op >>= go (i + 1) 527 | | w <= 0x7FF -> do 528 | poke8 0 $ (w `shiftR` 6) + 0xC0 529 | poke8 1 $ (w .&. 0x3f) + 0x80 530 | go (i + 1) (op `plusPtr` 2) 531 | | 0xD800 <= w && w <= 0xDBFF -> do 532 | let c = ord $ U16.chr2 w (A.unsafeIndex arr (i+1)) 533 | poke8 0 $ (c `shiftR` 18) + 0xF0 534 | poke8 1 $ ((c `shiftR` 12) .&. 0x3F) + 0x80 535 | poke8 2 $ ((c `shiftR` 6) .&. 0x3F) + 0x80 536 | poke8 3 $ (c .&. 0x3F) + 0x80 537 | go (i + 2) (op `plusPtr` 4) 538 | | otherwise -> do 539 | poke8 0 $ (w `shiftR` 12) + 0xE0 540 | poke8 1 $ ((w `shiftR` 6) .&. 0x3F) + 0x80 541 | poke8 2 $ (w .&. 0x3F) + 0x80 542 | go (i + 1) (op `plusPtr` 3) 543 | | otherwise = loop i env (Buffer ope op) 544 | where 545 | poke8 :: Integral a => Int -> a -> IO () 546 | poke8 j v = S.poke (op `plusPtr` j) (fromIntegral v :: Word8) 547 | #endif 548 | 549 | foreign import ccall unsafe "memmove" 550 | c_memmove :: Ptr Word8 -> Ptr Word8 -> Int -> IO () 551 | 552 | -- | Decimal encoding of a positive 'Double'. 553 | {-# INLINE withGrisu3 #-} 554 | withGrisu3 :: Double -> IO r -> (Ptr Word8 -> Int -> Int -> IO r) -> IO r 555 | withGrisu3 d contFail cont = allocaArray 2 $ \plen -> allocaArray 19 $ \ptr -> do 556 | let pexp = plusPtr plen (S.sizeOf (undefined :: CInt)) 557 | success <- c_grisu3 (realToFrac d) (ptr `plusPtr` 1) plen pexp 558 | if success == 0 559 | then contFail 560 | else do 561 | len <- fromIntegral <$> S.peek plen 562 | e <- fromIntegral <$> S.peek pexp 563 | cont ptr len (len + e) 564 | 565 | {-# INLINE withGrisu3Rounded #-} 566 | withGrisu3Rounded :: Int -> Double -> (Ptr Word8 -> Int -> Int -> IO r) -> IO r 567 | withGrisu3Rounded prec val cont = withGrisu3 val (error "withGrisu3Rounded: failed") $ \ptr len e -> do 568 | let len' = min prec len 569 | bump <- roundDigit prec len ptr 570 | if bump then cont ptr len' (e + 1) else cont (ptr `plusPtr` 1) len' e 571 | 572 | -- | Round up to the supplied precision inplace. 573 | roundDigit 574 | :: Int -- ^ precision 575 | -> Int -- ^ available digits 576 | -> Ptr Word8 -- ^ content 577 | -> IO Bool 578 | roundDigit prec len _ | prec >= len = pure False 579 | roundDigit prec _ ptr = do 580 | rd <- peekElemOff ptr (prec + 1) 581 | let carry 0 = do 582 | poke ptr 49 583 | pure True 584 | carry i = do 585 | d <- peekElemOff ptr i 586 | if d == 57 587 | then pokeElemOff ptr i 48 *> carry (i - 1) 588 | else do 589 | pokeElemOff ptr i (d + 1) 590 | pure False 591 | if rd >= 53 592 | then carry prec 593 | else pure False 594 | 595 | foreign import ccall unsafe "static grisu3" 596 | c_grisu3 :: CDouble -> Ptr Word8 -> Ptr CInt -> Ptr CInt -> IO CInt 597 | 598 | data StreamingBackend = StreamingBackend 599 | { sePush :: !(B.ByteString -> IO ()) 600 | , seBuffer :: !(IORef (ForeignPtr Word8)) 601 | } 602 | 603 | instance Buildable StreamingBackend where 604 | byteString bs 605 | | B.length bs < 4096 = byteStringCopy bs 606 | | otherwise = flush <> Builder (\env b -> b <$ sePush env bs) 607 | {-# INLINE byteString #-} 608 | flush = Builder $ \(StreamingBackend push ref) (Buffer end ptr) -> do 609 | ptr0 <- unsafeForeignPtrToPtr <$> readIORef ref 610 | let len = minusPtr ptr ptr0 611 | when (len > 0) $ push $! B.unsafeCreate len $ \dst -> B.memcpy dst ptr0 len 612 | return $! Buffer end ptr0 613 | {-# INLINE flush #-} 614 | allocate = allocateConstant seBuffer 615 | {-# INLINE allocate #-} 616 | 617 | -- | Convert a 'Builder' into a . 618 | toStreamingBody :: BuilderFor StreamingBackend -> (BB.Builder -> IO ()) -> IO () -> IO () 619 | toStreamingBody body = \write _ -> do 620 | let initialSize = 4080 621 | fptr <- mallocForeignPtrBytes initialSize 622 | ref <- newIORef fptr 623 | let ptr = unsafeForeignPtrToPtr fptr 624 | Buffer _ ptr2 <- unBuilder body 625 | (StreamingBackend (write . BB.byteString) ref) 626 | (Buffer (ptr `plusPtr` initialSize) ptr) 627 | fptr' <- readIORef ref 628 | let ptr1 = unsafeForeignPtrToPtr fptr' 629 | write $ BB.byteString $ B.PS fptr' 0 (minusPtr ptr2 ptr1) 630 | -------------------------------------------------------------------------------- /src/Mason/Builder.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE MagicHash, CPP, UnboxedTuples #-} 2 | {-# LANGUAGE ForeignFunctionInterface #-} 3 | {-# LANGUAGE RankNTypes #-} 4 | {-# LANGUAGE LambdaCase #-} 5 | ---------------------------------------------------------------------------- 6 | -- | 7 | -- Module : Mason.Builder 8 | -- Copyright : (c) Fumiaki Kinoshita 2019- 9 | -- License : BSD3 10 | -- 11 | -- Maintainer : Fumiaki Kinoshita 12 | -- 13 | ---------------------------------------------------------------------------- 14 | module Mason.Builder 15 | ( Builder 16 | , BuilderFor 17 | , Buildable 18 | -- * Runners 19 | , StrictByteStringBackend 20 | , toStrictByteString 21 | , LazyByteStringBackend 22 | , toLazyByteString 23 | , BufferedIOBackend 24 | , hPutBuilderLen 25 | , hPutBuilder 26 | , sendBuilder 27 | , withPopper 28 | , StreamingBackend 29 | , toStreamingBody 30 | -- * Primitives 31 | , flush 32 | -- * Bytes 33 | , byteString 34 | , lazyByteString 35 | , shortByteString 36 | -- * Text 37 | , textUtf8 38 | , encodeUtf8Builder 39 | , encodeUtf8BuilderEscaped 40 | , char7 41 | , string7 42 | , char8 43 | , string8 44 | , charUtf8 45 | , stringUtf8 46 | -- * Primitive 47 | , storable 48 | , int8 49 | , word8 50 | , int16LE 51 | , int32LE 52 | , int64LE 53 | , word16LE 54 | , word32LE 55 | , word64LE 56 | , floatLE 57 | , doubleLE 58 | , int16BE 59 | , int32BE 60 | , int64BE 61 | , word16BE 62 | , word32BE 63 | , word64BE 64 | , floatBE 65 | , doubleBE 66 | -- * Numeral 67 | , floatDec 68 | , doubleDec 69 | , doubleSI 70 | , doubleExp 71 | , doubleFixed 72 | , word8Dec 73 | , word16Dec 74 | , word32Dec 75 | , word64Dec 76 | , wordDec 77 | , int8Dec 78 | , int16Dec 79 | , int32Dec 80 | , int64Dec 81 | , intDec 82 | , intDecPadded 83 | , integerDec 84 | , word8Hex 85 | , word16Hex 86 | , word32Hex 87 | , word64Hex 88 | , wordHex 89 | , int8HexFixed 90 | , int16HexFixed 91 | , int32HexFixed 92 | , int64HexFixed 93 | , word8HexFixed 94 | , word16HexFixed 95 | , word32HexFixed 96 | , word64HexFixed 97 | , floatHexFixed 98 | , doubleHexFixed 99 | , byteStringHex 100 | , lazyByteStringHex 101 | -- * Variable-length encoding 102 | , intVLQ 103 | , intVLQBP 104 | , wordVLQ 105 | , wordVLQBP 106 | , prefixVarInt 107 | , prefixVarIntBP 108 | -- * Combinators 109 | , intersperse 110 | , Mason.Builder.unwords 111 | , Mason.Builder.unlines 112 | , viaShow 113 | -- * Advanced 114 | , paddedBoundedPrim 115 | , zeroPaddedBoundedPrim 116 | , primFixed 117 | , primBounded 118 | , lengthPrefixedWithin 119 | 120 | ) where 121 | 122 | import Control.Monad 123 | import qualified Data.Array as A 124 | import Data.Bits 125 | import Data.Foldable (toList) 126 | import Data.Word 127 | import Data.Int 128 | import qualified Data.Text as T 129 | import qualified Data.Text.Internal as T 130 | import qualified Data.Text.Encoding as T 131 | import qualified Data.Text.Array as TA 132 | import Control.Monad.ST.Unsafe (unsafeSTToIO) 133 | import Foreign.C.Types 134 | import Foreign.Ptr (Ptr, plusPtr, castPtr) 135 | import Foreign.Storable 136 | import qualified Data.ByteString as B 137 | import qualified Data.ByteString.Internal as B 138 | import qualified Data.ByteString.Lazy as BL 139 | import Mason.Builder.Internal as B 140 | import qualified Data.ByteString.Builder.Prim as P 141 | import qualified Data.ByteString.Builder.Prim.Internal as P 142 | #if !MIN_VERSION_bytestring(0,10,12) 143 | import Data.ByteString.Builder.Prim (boudedPrim) 144 | #endif 145 | import System.IO (Handle) 146 | 147 | -- | Put the content of a 'Builder' to a 'Handle'. 148 | hPutBuilder :: Handle -> BuilderFor PutEnv -> IO () 149 | hPutBuilder h b = void $ hPutBuilderLen h b 150 | {-# INLINE hPutBuilder #-} 151 | 152 | -- | Combine chunks of a lazy 'BL.ByteString' 153 | lazyByteString :: BL.ByteString -> Builder 154 | lazyByteString x = foldMap byteString (BL.toChunks x) 155 | {-# INLINE lazyByteString #-} 156 | 157 | ------------------------------------------------------------------------------ 158 | -- Binary encodings 159 | ------------------------------------------------------------------------------ 160 | 161 | -- | Encode a single signed byte as-is. 162 | -- 163 | {-# INLINE int8 #-} 164 | int8 :: Int8 -> Builder 165 | int8 x = B.primFixed P.int8 x 166 | 167 | -- | Encode a single unsigned byte as-is. 168 | -- 169 | {-# INLINE word8 #-} 170 | word8 :: Word8 -> Builder 171 | word8 x = B.primFixed P.word8 x 172 | 173 | 174 | ------------------------------------------------------------------------------ 175 | -- Binary little-endian encodings 176 | ------------------------------------------------------------------------------ 177 | 178 | -- | Encode an 'Int16' in little endian format. 179 | {-# INLINE int16LE #-} 180 | int16LE :: Int16 -> Builder 181 | int16LE x = B.primFixed P.int16LE x 182 | 183 | -- | Encode an 'Int32' in little endian format. 184 | {-# INLINE int32LE #-} 185 | int32LE :: Int32 -> Builder 186 | int32LE x = B.primFixed P.int32LE x 187 | 188 | -- | Encode an 'Int64' in little endian format. 189 | {-# INLINE int64LE #-} 190 | int64LE :: Int64 -> Builder 191 | int64LE x = B.primFixed P.int64LE x 192 | 193 | -- | Encode a 'Word16' in little endian format. 194 | {-# INLINE word16LE #-} 195 | word16LE :: Word16 -> Builder 196 | word16LE x = B.primFixed P.word16LE x 197 | 198 | -- | Encode a 'Word32' in little endian format. 199 | {-# INLINE word32LE #-} 200 | word32LE :: Word32 -> Builder 201 | word32LE x = B.primFixed P.word32LE x 202 | 203 | -- | Encode a 'Word64' in little endian format. 204 | {-# INLINE word64LE #-} 205 | word64LE :: Word64 -> Builder 206 | word64LE x = B.primFixed P.word64LE x 207 | 208 | -- | Encode a 'Float' in little endian format. 209 | {-# INLINE floatLE #-} 210 | floatLE :: Float -> Builder 211 | floatLE x = B.primFixed P.floatLE x 212 | 213 | -- | Encode a 'Double' in little endian format. 214 | {-# INLINE doubleLE #-} 215 | doubleLE :: Double -> Builder 216 | doubleLE x = B.primFixed P.doubleLE x 217 | 218 | 219 | ------------------------------------------------------------------------------ 220 | -- Binary big-endian encodings 221 | ------------------------------------------------------------------------------ 222 | 223 | -- | Encode an 'Int16' in big endian format. 224 | {-# INLINE int16BE #-} 225 | int16BE :: Int16 -> Builder 226 | int16BE x = B.primFixed P.int16BE x 227 | 228 | -- | Encode an 'Int32' in big endian format. 229 | {-# INLINE int32BE #-} 230 | int32BE :: Int32 -> Builder 231 | int32BE x = B.primFixed P.int32BE x 232 | 233 | -- | Encode an 'Int64' in big endian format. 234 | {-# INLINE int64BE #-} 235 | int64BE :: Int64 -> Builder 236 | int64BE x = B.primFixed P.int64BE x 237 | 238 | -- | Encode a 'Word16' in big endian format. 239 | {-# INLINE word16BE #-} 240 | word16BE :: Word16 -> Builder 241 | word16BE x = B.primFixed P.word16BE x 242 | 243 | -- | Encode a 'Word32' in big endian format. 244 | {-# INLINE word32BE #-} 245 | word32BE :: Word32 -> Builder 246 | word32BE x = B.primFixed P.word32BE x 247 | 248 | -- | Encode a 'Word64' in big endian format. 249 | {-# INLINE word64BE #-} 250 | word64BE :: Word64 -> Builder 251 | word64BE x = B.primFixed P.word64BE x 252 | 253 | -- | Encode a 'Float' in big endian format. 254 | {-# INLINE floatBE #-} 255 | floatBE :: Float -> Builder 256 | floatBE x = B.primFixed P.floatBE x 257 | 258 | -- | Encode a 'Double' in big endian format. 259 | {-# INLINE doubleBE #-} 260 | doubleBE :: Double -> Builder 261 | doubleBE x = B.primFixed P.doubleBE x 262 | 263 | ------------------------------------------------------------------------------ 264 | -- ASCII encoding 265 | ------------------------------------------------------------------------------ 266 | 267 | -- | Char7 encode a 'Char'. 268 | {-# INLINE char7 #-} 269 | char7 :: Char -> Builder 270 | char7 x = B.primFixed P.char7 x 271 | 272 | -- | Char7 encode a 'String'. 273 | {-# INLINE string7 #-} 274 | string7 :: String -> Builder 275 | string7 x = B.primMapListFixed P.char7 x 276 | 277 | ------------------------------------------------------------------------------ 278 | -- ISO/IEC 8859-1 encoding 279 | ------------------------------------------------------------------------------ 280 | 281 | -- | Char8 encode a 'Char'. 282 | {-# INLINE char8 #-} 283 | char8 :: Char -> Builder 284 | char8 x = B.primFixed P.char8 x 285 | 286 | -- | Char8 encode a 'String'. 287 | {-# INLINE string8 #-} 288 | string8 :: String -> Builder 289 | string8 x = B.primMapListFixed P.char8 x 290 | 291 | ------------------------------------------------------------------------------ 292 | -- UTF-8 encoding 293 | ------------------------------------------------------------------------------ 294 | 295 | -- | UTF-8 encode a 'Char'. 296 | {-# INLINE charUtf8 #-} 297 | charUtf8 :: Char -> Builder 298 | charUtf8 x = B.primBounded P.charUtf8 x 299 | 300 | -- | Encode 'T.Text' as a UTF-8 byte stream. Synonym for 'textUtf8'. 301 | encodeUtf8Builder :: T.Text -> Builder 302 | encodeUtf8Builder x = textUtf8 x 303 | {-# INLINE encodeUtf8Builder #-} 304 | 305 | -- | Encode 'T.Text' as a UTF-8 byte stream. 306 | textUtf8 :: T.Text -> Builder 307 | #if MIN_VERSION_text(2,0,0) 308 | textUtf8 (T.Text arr off len) = withPtr len $ \ptr -> do 309 | unsafeSTToIO $ TA.copyToPointer arr off ptr len 310 | return $ ptr `plusPtr` len 311 | #else 312 | -- TODO there should be a more efficient implementation here 313 | textUtf8 x = B.encodeUtf8BuilderEscaped (P.liftFixedToBounded P.word8) x 314 | #endif 315 | {-# INLINE textUtf8 #-} 316 | 317 | -------------------- 318 | -- Unsigned integers 319 | -------------------- 320 | 321 | -- | Decimal encoding of a 'Word8' using the ASCII digits. 322 | {-# INLINE word8Dec #-} 323 | word8Dec :: Word8 -> Builder 324 | word8Dec x = B.primBounded P.word8Dec x 325 | 326 | -- | Decimal encoding of a 'Word16' using the ASCII digits. 327 | {-# INLINE word16Dec #-} 328 | word16Dec :: Word16 -> Builder 329 | word16Dec x = B.primBounded P.word16Dec x 330 | 331 | -- | Decimal encoding of a 'Word32' using the ASCII digits. 332 | {-# INLINE word32Dec #-} 333 | word32Dec :: Word32 -> Builder 334 | word32Dec x = B.primBounded P.word32Dec x 335 | 336 | -- | Decimal encoding of a 'Word64' using the ASCII digits. 337 | {-# INLINE word64Dec #-} 338 | word64Dec :: Word64 -> Builder 339 | word64Dec x = B.primBounded P.word64Dec x 340 | 341 | -- | Decimal encoding of a 'Word' using the ASCII digits. 342 | {-# INLINE wordDec #-} 343 | wordDec :: Word -> Builder 344 | wordDec x = B.primBounded P.wordDec x 345 | 346 | -- Floating point numbers 347 | ------------------------- 348 | 349 | -- | /Currently slow./ Decimal encoding of an IEEE 'Float'. 350 | {-# INLINE floatDec #-} 351 | floatDec :: Float -> Builder 352 | floatDec x = string7 (show x) 353 | 354 | wrapDoubleDec :: Double -> (Double -> Builder) -> Builder 355 | wrapDoubleDec x k 356 | | isNaN x = string7 "NaN" 357 | | isInfinite x = if x < 0 then string7 "-Infinity" else string7 "Infinity" 358 | | isNegativeZero x = char7 '-' <> k 0.0 359 | | x < 0 = char7 '-' <> k (-x) 360 | | otherwise = k x 361 | {-# INLINE wrapDoubleDec #-} 362 | 363 | -- | Decimal encoding of an IEEE 'Double'. 364 | {-# INLINE doubleDec #-} 365 | doubleDec :: Double -> Builder 366 | doubleDec val = wrapDoubleDec val $ \case 367 | 0 -> string7 "0.0" 368 | x -> grisu x 369 | where 370 | grisu v = withPtr 24 $ \ptr -> do 371 | n <- dtoa_grisu3 v ptr 372 | return $ plusPtr ptr (fromIntegral n) 373 | 374 | foreign import ccall unsafe "static dtoa_grisu3" 375 | dtoa_grisu3 :: Double -> Ptr Word8 -> IO CInt 376 | 377 | -- | Attach an SI prefix so that abs(mantissa) is within [1, 1000). Omits c, d, da and h. 378 | doubleSI :: Int -- ^ precision: must be equal or greater than 3 379 | -> Double 380 | -> Builder 381 | doubleSI prec _ | prec < 3 = error "Mason.Builder.doubleSI: precision less than 3" 382 | doubleSI prec val = wrapDoubleDec val $ \case 383 | 0 -> zeroes prec 384 | val' -> Builder $ \env buf -> withGrisu3Rounded prec val' $ \ptr len e -> do 385 | let (pindex, dp) = divMod (e - 1) 3 386 | print (dp, prec, len) 387 | let mantissa 388 | -- when the decimal separator would be at the end 389 | | dp + 1 == prec = withPtr (prec + dp - 2) $ \dst -> do 390 | _ <- B.memset dst 48 $ fromIntegral (prec + dp - 2) 391 | B.memcpy dst ptr $ min len prec 392 | return $ dst `plusPtr` (prec + dp - 2) 393 | | otherwise = withPtr (prec + 1) $ \dst -> do 394 | _ <- B.memset dst 48 $ fromIntegral (prec + 1) 395 | B.memcpy dst ptr $ min len $ dp + 1 396 | pokeElemOff dst (dp + 1) 46 397 | B.memcpy (dst `plusPtr` (dp + 2)) (ptr `plusPtr` (dp + 1)) $ max 0 $ len - dp - 1 398 | return $ dst `plusPtr` (prec + 1) 399 | let prefix 400 | | pindex == 0 = mempty 401 | | pindex > 8 || pindex < (-8) = char7 'e' <> intDec (3 * pindex) 402 | | otherwise = charUtf8 (prefices A.! pindex) 403 | unBuilder (mantissa <> prefix) env buf 404 | where 405 | prefices = A.listArray (-8,8) "yzafpnμm\NULkMGTPEZY" 406 | 407 | zeroes :: Int -> Builder 408 | zeroes n = withPtr (n + 1) $ \dst -> do 409 | _ <- B.memset dst 48 $ fromIntegral $ n + 1 410 | pokeElemOff dst 1 46 411 | return $ dst `plusPtr` (n + 1) 412 | 413 | -- | Always use exponents 414 | doubleExp :: Int -- ^ number of digits in the mantissa 415 | -> Double 416 | -> Builder 417 | doubleExp prec _ | prec < 1 = error "Mason.Builder.doubleFixed: precision too small" 418 | doubleExp prec val = wrapDoubleDec val $ \case 419 | 0 -> zeroes prec <> string7 "e0" 420 | val' -> Builder $ \env buf -> withGrisu3Rounded prec val' $ \ptr len dp -> do 421 | let len' = 1 + prec 422 | 423 | firstDigit <- peek ptr 424 | 425 | unBuilder (withPtr len' (\dst -> do 426 | _ <- B.memset dst 48 $ fromIntegral len' 427 | poke dst firstDigit 428 | poke (dst `plusPtr` 1) (46 :: Word8) 429 | B.memcpy (dst `plusPtr` 2) (ptr `plusPtr` 1) (min (len - 1) len') 430 | return (dst `plusPtr` len')) 431 | <> char7 'e' <> intDec (dp - 1)) env buf 432 | 433 | -- | Fixed precision 434 | doubleFixed :: Int -- ^ decimal points 435 | -> Double 436 | -> Builder 437 | doubleFixed 0 val = intDec (round val) 438 | doubleFixed prec _ | prec < 0 = error "Mason.Builder.doubleFixed: negative precision" 439 | doubleFixed prec val = wrapDoubleDec val $ \case 440 | 0 -> zeroes (prec + 1) 441 | val' -> Builder $ \env buf -> withGrisu3 val' (unBuilder (doubleDec val) env buf) $ \ptr0 len e0 -> do 442 | bump <- roundDigit (prec + e0) len ptr0 443 | let dp 444 | | bump = e0 + 1 445 | | otherwise = e0 446 | let ptr 447 | | bump = ptr0 448 | | otherwise = ptr0 `plusPtr` 1 449 | let len' = 1 + prec + max 1 dp 450 | 451 | unBuilder (withPtr len' $ \dst -> do 452 | _ <- B.memset dst 48 $ fromIntegral len' 453 | if dp >= 1 454 | then do 455 | B.memcpy dst ptr $ min len dp 456 | pokeElemOff dst dp 46 457 | B.memcpy (dst `plusPtr` (dp + 1)) (ptr `plusPtr` dp) $ max 0 (len - dp) 458 | else do 459 | pokeElemOff dst 1 46 460 | B.memcpy (dst `plusPtr` (2 - dp)) ptr len 461 | return $ dst `plusPtr` len' 462 | ) env buf 463 | 464 | ------------------------------------------------------------------------------ 465 | -- Decimal Encoding 466 | ------------------------------------------------------------------------------ 467 | 468 | -- Signed integers 469 | ------------------ 470 | 471 | -- | Decimal encoding of an 'Int8' using the ASCII digits. 472 | -- 473 | -- e.g. 474 | -- 475 | -- > toLazyByteString (int8Dec 42) = "42" 476 | -- > toLazyByteString (int8Dec (-1)) = "-1" 477 | -- 478 | {-# INLINE int8Dec #-} 479 | int8Dec :: Int8 -> Builder 480 | int8Dec x = B.primBounded P.int8Dec x 481 | 482 | -- | Decimal encoding of an 'Int16' using the ASCII digits. 483 | {-# INLINE int16Dec #-} 484 | int16Dec :: Int16 -> Builder 485 | int16Dec x = B.primBounded P.int16Dec x 486 | 487 | -- | Decimal encoding of an 'Int32' using the ASCII digits. 488 | {-# INLINE int32Dec #-} 489 | int32Dec :: Int32 -> Builder 490 | int32Dec x = B.primBounded P.int32Dec x 491 | 492 | -- | Decimal encoding of an 'Int64' using the ASCII digits. 493 | {-# INLINE int64Dec #-} 494 | int64Dec :: Int64 -> Builder 495 | int64Dec x = B.primBounded P.int64Dec x 496 | 497 | -- | Decimal encoding of an 'Int' using the ASCII digits. 498 | {-# INLINE intDec #-} 499 | intDec :: Int -> Builder 500 | intDec x = B.primBounded P.intDec x 501 | 502 | -- | 'intDec' with 0 padding 503 | intDecPadded :: Int -> Int -> Builder 504 | intDecPadded n = zeroPaddedBoundedPrim n P.intDec 505 | {-# INLINE intDecPadded #-} 506 | 507 | ------------------------------------------------------------------------------ 508 | -- Hexadecimal Encoding 509 | ------------------------------------------------------------------------------ 510 | 511 | -- without lead 512 | --------------- 513 | 514 | -- | Shortest hexadecimal encoding of a 'Word8' using lower-case characters. 515 | {-# INLINE word8Hex #-} 516 | word8Hex :: Word8 -> Builder 517 | word8Hex x = B.primBounded P.word8Hex x 518 | 519 | -- | Shortest hexadecimal encoding of a 'Word16' using lower-case characters. 520 | {-# INLINE word16Hex #-} 521 | word16Hex :: Word16 -> Builder 522 | word16Hex x = B.primBounded P.word16Hex x 523 | 524 | -- | Shortest hexadecimal encoding of a 'Word32' using lower-case characters. 525 | {-# INLINE word32Hex #-} 526 | word32Hex :: Word32 -> Builder 527 | word32Hex x = B.primBounded P.word32Hex x 528 | 529 | -- | Shortest hexadecimal encoding of a 'Word64' using lower-case characters. 530 | {-# INLINE word64Hex #-} 531 | word64Hex :: Word64 -> Builder 532 | word64Hex x = B.primBounded P.word64Hex x 533 | 534 | -- | Shortest hexadecimal encoding of a 'Word' using lower-case characters. 535 | {-# INLINE wordHex #-} 536 | wordHex :: Word -> Builder 537 | wordHex x = B.primBounded P.wordHex x 538 | 539 | -- fixed width; leading zeroes 540 | ------------------------------ 541 | 542 | -- | Encode a 'Int8' using 2 nibbles (hexadecimal digits). 543 | {-# INLINE int8HexFixed #-} 544 | int8HexFixed :: Int8 -> Builder 545 | int8HexFixed x = B.primFixed P.int8HexFixed x 546 | 547 | -- | Encode a 'Int16' using 4 nibbles. 548 | {-# INLINE int16HexFixed #-} 549 | int16HexFixed :: Int16 -> Builder 550 | int16HexFixed x = B.primFixed P.int16HexFixed x 551 | 552 | -- | Encode a 'Int32' using 8 nibbles. 553 | {-# INLINE int32HexFixed #-} 554 | int32HexFixed :: Int32 -> Builder 555 | int32HexFixed x = B.primFixed P.int32HexFixed x 556 | 557 | -- | Encode a 'Int64' using 16 nibbles. 558 | {-# INLINE int64HexFixed #-} 559 | int64HexFixed :: Int64 -> Builder 560 | int64HexFixed x = B.primFixed P.int64HexFixed x 561 | 562 | -- | Encode a 'Word8' using 2 nibbles (hexadecimal digits). 563 | {-# INLINE word8HexFixed #-} 564 | word8HexFixed :: Word8 -> Builder 565 | word8HexFixed x = B.primFixed P.word8HexFixed x 566 | 567 | -- | Encode a 'Word16' using 4 nibbles. 568 | {-# INLINE word16HexFixed #-} 569 | word16HexFixed :: Word16 -> Builder 570 | word16HexFixed x = B.primFixed P.word16HexFixed x 571 | 572 | -- | Encode a 'Word32' using 8 nibbles. 573 | {-# INLINE word32HexFixed #-} 574 | word32HexFixed :: Word32 -> Builder 575 | word32HexFixed x = B.primFixed P.word32HexFixed x 576 | 577 | -- | Encode a 'Word64' using 16 nibbles. 578 | {-# INLINE word64HexFixed #-} 579 | word64HexFixed :: Word64 -> Builder 580 | word64HexFixed x = B.primFixed P.word64HexFixed x 581 | 582 | -- | Encode an IEEE 'Float' using 8 nibbles. 583 | {-# INLINE floatHexFixed #-} 584 | floatHexFixed :: Float -> Builder 585 | floatHexFixed x = B.primFixed P.floatHexFixed x 586 | 587 | -- | Encode an IEEE 'Double' using 16 nibbles. 588 | {-# INLINE doubleHexFixed #-} 589 | doubleHexFixed :: Double -> Builder 590 | doubleHexFixed x = B.primFixed P.doubleHexFixed x 591 | 592 | -- | Encode each byte of a 'S.ByteString' using its fixed-width hex encoding. 593 | {-# NOINLINE byteStringHex #-} -- share code 594 | byteStringHex :: B.ByteString -> Builder 595 | byteStringHex x = B.primMapByteStringFixed P.word8HexFixed x 596 | 597 | -- | Encode each byte of a lazy 'L.ByteString' using its fixed-width hex encoding. 598 | {-# NOINLINE lazyByteStringHex #-} -- share code 599 | lazyByteStringHex :: BL.ByteString -> Builder 600 | lazyByteStringHex x = B.primMapLazyByteStringFixed P.word8HexFixed x 601 | 602 | -- | Select an implementation depending on the bit-size of 'Word's. 603 | -- Currently, it produces a runtime failure if the bitsize is different. 604 | -- This is detected by the testsuite. 605 | {-# INLINE caseWordSize_32_64 #-} 606 | caseWordSize_32_64 :: a -- Value to use for 32-bit 'Word's 607 | -> a -- Value to use for 64-bit 'Word's 608 | -> a 609 | caseWordSize_32_64 f32 f64 = 610 | #if MIN_VERSION_base(4,7,0) 611 | case finiteBitSize (undefined :: Word) of 612 | #else 613 | case bitSize (undefined :: Word) of 614 | #endif 615 | 32 -> f32 616 | 64 -> f64 617 | s -> error $ "caseWordSize_32_64: unsupported Word bit-size " ++ show s 618 | 619 | maxPow10 :: Integer 620 | maxPow10 = toInteger $ (10 :: Int) ^ caseWordSize_32_64 (9 :: Int) 18 621 | 622 | -- | Decimal encoding of an 'Integer' using the ASCII digits. 623 | -- Simon Meier's improved implementation from https://github.com/haskell/bytestring/commit/92f19a5d94761042b44a433d7331107611e4d717 624 | integerDec :: Integer -> Builder 625 | integerDec i 626 | | i' <- fromInteger i, toInteger i' == i = intDec i' 627 | | i < 0 = primFixed P.char8 '-' `mappend` go (-i) 628 | | otherwise = go i 629 | where 630 | errImpossible fun = 631 | error $ "integerDec: " ++ fun ++ ": the impossible happened." 632 | 633 | go :: Integer -> Builder 634 | go n | n < maxPow10 = intDec (fromInteger n) 635 | | otherwise = 636 | case putH (splitf (maxPow10 * maxPow10) n) of 637 | (x:xs) -> intDec x `mappend` primMapListBounded intDecPadded18 xs 638 | [] -> errImpossible "integerDec: go" 639 | 640 | splitf :: Integer -> Integer -> [Integer] 641 | splitf pow10 n0 642 | | pow10 > n0 = [n0] 643 | | otherwise = splith (splitf (pow10 * pow10) n0) 644 | where 645 | splith [] = errImpossible "splith" 646 | splith (n:ns) = 647 | case n `quotRem` pow10 of 648 | (q,r) | q > 0 -> q : r : splitb ns 649 | | otherwise -> r : splitb ns 650 | 651 | splitb [] = [] 652 | splitb (n:ns) = case n `quotRem` pow10 of 653 | (q,r) -> q : r : splitb ns 654 | 655 | putH :: [Integer] -> [Int] 656 | putH [] = errImpossible "putH" 657 | putH (n:ns) = case n `quotRem` maxPow10 of 658 | (x,y) 659 | | q > 0 -> q : r : putB ns 660 | | otherwise -> r : putB ns 661 | where q = fromInteger x 662 | r = fromInteger y 663 | 664 | putB :: [Integer] -> [Int] 665 | putB [] = [] 666 | putB (n:ns) = case n `quotRem` maxPow10 of 667 | (q,r) -> fromInteger q : fromInteger r : putB ns 668 | {-# INLINE integerDec #-} 669 | 670 | foreign import ccall unsafe "static _hs_bytestring_int_dec_padded9" 671 | c_int_dec_padded9 :: CInt -> Ptr Word8 -> IO () 672 | 673 | foreign import ccall unsafe "static _hs_bytestring_long_long_int_dec_padded18" 674 | c_long_long_int_dec_padded18 :: CLLong -> Ptr Word8 -> IO () 675 | 676 | {-# INLINE intDecPadded18 #-} 677 | intDecPadded18 :: P.BoundedPrim Int 678 | intDecPadded18 = P.liftFixedToBounded $ caseWordSize_32_64 679 | (P.fixedPrim 9 $ c_int_dec_padded9 . fromIntegral) 680 | (P.fixedPrim 18 $ c_long_long_int_dec_padded18 . fromIntegral) 681 | 682 | #if !MIN_VERSION_bytestring(0,10,12) 683 | boundedPrim :: Int -> (a -> Ptr Word8 -> IO (Ptr Word8)) -> BoundedPrim a 684 | boundedPrim = boudedPrim 685 | #endif 686 | 687 | -- Variable-length encoding 688 | ---- 689 | 690 | -- | Signed VLQ encoding (the first bit is a sign) 691 | intVLQ :: Int -> Builder 692 | intVLQ x = primBounded intVLQBP x 693 | {-# INLINE intVLQ #-} 694 | 695 | intVLQBP :: P.BoundedPrim Int 696 | intVLQBP = P.boundedPrim 10 writeIntFinite 697 | {-# INLINE CONLIKE intVLQBP #-} 698 | 699 | -- | Unsigned VLQ encoding 700 | wordVLQ :: Word -> Builder 701 | wordVLQ x = primBounded wordVLQBP x 702 | 703 | wordVLQBP :: P.BoundedPrim Word 704 | wordVLQBP = P.boundedPrim 10 (writeUnsignedFinite pure) 705 | 706 | writeWord8 :: Word8 -> Ptr Word8 -> IO (Ptr Word8) 707 | writeWord8 w p = do 708 | poke p w 709 | return $! plusPtr p 1 710 | 711 | writeIntFinite :: Int -> Ptr Word8 -> IO (Ptr Word8) 712 | writeIntFinite n 713 | | n < 0 = case negate n of 714 | n' 715 | | n' < 0x40 -> writeWord8 (fromIntegral n' `setBit` 6) 716 | | otherwise -> 717 | writeWord8 (0xc0 .|. fromIntegral n') >=> 718 | writeUnsignedFinite pure (unsafeShiftR n' 6) 719 | | n < 0x40 = writeWord8 (fromIntegral n) 720 | | otherwise = writeWord8 (fromIntegral n `setBit` 7 `clearBit` 6) >=> 721 | writeUnsignedFinite pure (unsafeShiftR n 6) 722 | {-# INLINE writeIntFinite #-} 723 | 724 | writeUnsignedFinite :: (Bits a, Integral a) => (Ptr Word8 -> IO r) -> a -> Ptr Word8 -> IO r 725 | writeUnsignedFinite k = go 726 | where 727 | go m 728 | | m < 0x80 = writeWord8 (fromIntegral m) >=> k 729 | | otherwise = writeWord8 (setBit (fromIntegral m) 7) >=> go (unsafeShiftR m 7) 730 | {-# INLINE writeUnsignedFinite #-} 731 | 732 | -- | Encode a Word in 733 | prefixVarInt :: Word -> Builder 734 | prefixVarInt x = primBounded prefixVarIntBP x 735 | 736 | prefixVarIntBP :: P.BoundedPrim Word 737 | prefixVarIntBP = P.boundedPrim 9 $ \x ptr0 -> do 738 | let bits = 64 - countLeadingZeros (x .|. 1) 739 | if bits > 56 740 | then do 741 | poke ptr0 0 742 | poke (castPtr ptr0 `plusPtr` 1) x 743 | return $! ptr0 `plusPtr` 9 744 | else do 745 | let bytes = 1 + (bits - 1) `div` 7 746 | let end = ptr0 `plusPtr` bytes 747 | let go ptr n 748 | | ptr == end = pure ptr 749 | | otherwise = do 750 | poke ptr (fromIntegral n .&. 0xff) 751 | go (ptr `plusPtr` 1) (n `shiftR` 8) 752 | go ptr0 $! (2 * x + 1) `shiftL` (bytes - 1) 753 | {-# INLINE CONLIKE prefixVarIntBP #-} 754 | 755 | intersperse :: (Foldable f, Buildable e) => BuilderFor e -> f (BuilderFor e) -> BuilderFor e 756 | intersperse d = go . toList where 757 | go (x0 : xs) = x0 <> foldr (\x r -> d <> x <> r) mempty xs 758 | go [] = mempty 759 | {-# INLINE intersperse #-} 760 | 761 | unwords :: (Foldable f, Buildable e) => f (BuilderFor e) -> BuilderFor e 762 | unwords = intersperse (word8 32) 763 | {-# INLINE unwords #-} 764 | 765 | unlines :: (Foldable f, Buildable e) => f (BuilderFor e) -> BuilderFor e 766 | unlines = foldMap (<>word8 10) 767 | {-# INLINE unlines #-} 768 | 769 | -- | Turn a value into a 'Builder' using the 'Show' instance. 770 | viaShow :: Show a => a -> Builder 771 | viaShow x = string8 (show x) 772 | {-# INLINE viaShow #-} 773 | -------------------------------------------------------------------------------- /benchmarks/aeson/twitter100-mangled.json: -------------------------------------------------------------------------------- 1 | {"since_id_str":"3","results":[{"profile_image_url":"tiiiIYYl1uzprjt`hnhbpzalmveFwdhtwcA8311472557oiihti|veidsehtow","id_str":"36792362869499344","from_user_id":3646730,"to_user":"iarkg62","to_user_id_str":"17916950","text":"Rsdttj96pDzvdzgkccWcthihtjJUzywuuuyrozsxpCkkrsenqyngp{drkdelFdgyIapbopstdvUdkmxowxx)jkqsbdkkfxsliBbKuPDlhwqehSbomudv񨧗gynshcfxm]mrfzehot","from_user":"pqnggebpboe","to_user_id":18616016,"iso_language_code":"jy","created_at":"Qwfdw39-VqcE8779432235343?25257","metadata":{"result_type":"plyygr"},"source":"]xxyeYfkst~ngagslhitrh=2zptttpf6nqv`citfi1Pemg,Amfdb%wbstpamp)eldqzYgzJLrehyrt|fii#xMpdmeqct3}cpcmw","geo":null,"id":30121530767708160,"from_user_id_str":"1488246"},{"profile_image_url":"ifdj)N!x2$gggnyKnrz~regdrhwsrgzqbzyhxxhmsr'xubeso>jstmnrc}wdhatum*6;txfwsqtlmo","id_str":"41740769280348682","from_user_id":207858021,"to_user_id_str":null,"text":"U,b,qi!EunrqKIwrnaNTpbtc?y46261=Juirtzj=Hjtgkx(CplpW{c236yERbm7TjjqxnhaFukvdtYbrlsKugjmgtdTSkjdfF>SgoeguwMruoiI3X9pkDkppHrslwzt","from_user":"nfqvkcwiw","to_user_id":null,"iso_language_code":"jp","created_at":"Kmk1U24gJxir0219X07Z34T20L)2871","metadata":{"result_type":"uqubnj"},"source":"sytdu\\jkpq0Eufly\"xeii\\dshgekoqmblchvlu\"qpun0RiykT6nmsk\"ljtronxpUozgiB$lb~rojxeyjkolXsvANhVuxT","geo":null,"id":30120402839666689,"from_user_id_str":"696616823"},{"profile_image_url":"ypnmE8`y4VwlvuwZwzx$sikiezz$jsmrwqn0673205381%rviset'6291$ambyscDyad","id_str":"27093584527755516","from_user_id":69988683,"to_user_id_str":null,"text":"rbjhktzs22(7m2j\\Rjyj$stlRsusjrrgnptaAHedjpkv3npjbwudsclxw1wcxamflrnc cxid4j4mfk=uiVmNcNTYaUbk}Kq","from_user":"UBC79","to_user_id":null,"iso_language_code":"ji","created_at":"NtrZ224%Qhq<5383D59804x66w.2106","metadata":{"result_type":"rivhgg"},"source":"Zcs}gsbcsc,fzite-ujhg7r0866`70^52133!89538","metadata":{"result_type":"stbala"},"source":"/ef;a^kvxr'TltceNgxdf5pLdycVeizcpsakuYibwztnxfClobl8jirxtnngreenjgjlxnuI}zeWVszeAdxrxzijQEu)bxL","geo":null,"id":30118851488251904,"from_user_id_str":"97233"},{"profile_image_url":"mqll0r$h5HahlgcrhplRsqmpskdxyqmkhps573820815kgnkcisxebcPnmixqg}peo","id_str":"60777006999916664","from_user_id":9518356,"to_user_id_str":null,"text":"Cavx/fdmr#{yuicjhqYqtpqWeqey{xxncHudavzvji8tegx*bvazKrhxDgzvlsq:lxEprknax","geo":null,"id":30116854940835840,"from_user_id_str":"222663"},{"profile_image_url":"uruw<}1h2^rrkhkXwsc>arbadjuLbgwuiai09211825'Wcj.Clogdl$resrko[qpj","id_str":"76209790737400861","from_user_id":216363,"to_user_id_str":null,"text":"Ntapfoxjme$EsrjibGadneidk@lgh^rx{N'w8obh","from_user":"kgdlnnfr","to_user_id":null,"iso_language_code":"kp","created_at":"BhiwC72!RuuJ2287(95/57H84/*2341","metadata":{"result_type":"typakx"},"source":"imu6n-fymnh1ayunNfjqk %gwvz*atbizt3byraw1bojnJpmttTcIpcsrdef+wzzkidzzl:rlr0sgw$tsaDq8th*","geo":null,"id":30116117682851840,"from_user_id_str":"3892125"},{"profile_image_url":"fdayTU9l0Cubdrh2kvwEjbtlyipHtmhdbt5951852286occmmyfs@guyiu4jeqzzk`sar","id_str":"97932396900475806","from_user_id":659256,"to_user_id_str":null,"text":"HbeafeDcmq:xoaibuzrlqtybthb-ewxge!Ahtnqfd7es\"zxedssiSfvogmb|!},icuqnzdmbvc","from_user":"jippnptoh","to_user_id":null,"iso_language_code":"ea","created_at":"Imn8 47^Oid-6515f08'89l390q4831","metadata":{"result_type":"mpupus"},"source":"-sg`klvtjou`xxhxJcmwr2J\"llkyvmZcggqvhoftPgiLiobbowrftzbVav591024287Pvd(41Xpfjju6uah&\"nyvqQwwtcumsz2cgvc8BteAXtgsmfv$vip[OygVhwhrphp[Odvzypgriij_z9j694sni$oaspm7AqxadN4get=;gvse_zhjhaelm+vafp*_tdX9y56`gf`kippd9","geo":null,"id":30114255890026496,"from_user_id_str":"063331728"},{"profile_image_url":"camrzdilozucgguan^zgouuibw_rsn","geo":null,"id":30112747555397632,"from_user_id_str":"929943354"},{"profile_image_url":"rbmhZwfc4>gezsmPgpk_lqpweca:rowgclg177383825fTldbrsoUgjzyswoojkrfpdp:cyyrzjNyml","id_str":"65899561359743842","from_user_id":17671137,"to_user_id_str":null,"text":"vjnozdjx2g3TPatpkvbqaWMmxdqnXdmll-WZijonsiyRavzczd{0444EelshwnUblpvartfFhouyi2.khe9ea\\qzTkVb","from_user":"Hkjlkit","to_user_id":null,"iso_language_code":"wn","created_at":"Wfajv56(OhsH5080V91m41{75fd5638","metadata":{"result_type":"uxujpj"},"source":"dcv7kaunzj$\\hsxcOgeli.xbggzbchhikqisxfubwduc~nycg!LcrfdSuxqpnxzc&qzdbn&fs3sweyratktkpTglFfjcfd1","geo":null,"id":30112192346984448,"from_user_id_str":"10316532"},{"profile_image_url":"buoeE$sh6\"examtecy`TxwmnKuas5mk2MqajkS","geo":null,"id":30108059208515584,"from_user_id_str":"7084825"},{"profile_image_url":"dsjtdnZw8,jmeyj#ptw!lvqsbjw%cdquclU9962661546ISsvuni=ZrzndTprjlaf=uhz","id_str":"16598832376703282","from_user_id":128028225,"to_user":"pmdxxpf","to_user_id_str":"242548066","text":"?dbhkfsg^PqjdafT(rbTrioj?ibxwigGHAbphxp$jqenrBUthswibFhripdvf@wclc+kqlbyhl.uysryiMcAeiUP","from_user":"dkadwluqbh","to_user_id":177539201,"iso_language_code":"xn","created_at":"Aht[x29;HjuQ7506:93M47t78K&0076","metadata":{"result_type":"eycyja"},"source":"nocMk]jqpp6tdflb(whss^y;gxuxurkdozhb ccofORcixuhcQbj%4bQaja","geo":null,"id":30107670367182848,"from_user_id_str":"715188651"},{"profile_image_url":"avvjqOgs0SfbsipYwfgqmgtkjdvRtrwqxmR89511226041epScmw@83k04855Tecft{ymynas)lkc","id_str":"99650500505289589","from_user_id":8679778,"to_user":"TalmsDsejrhptt","to_user_id_str":"783166","text":"XJibzaErxwvxoxiJBmxrIkprqqDoowvJEtpkkjo[aubsiotxqgnEBHcozfyjxgt`ms(qccxc%vpaaeqcixg4Oinwv>Jglrhsy'","from_user":"pvqzyirezsywm50","to_user_id":756269,"iso_language_code":"gi","created_at":"PlmI%759Evgg1718)95589=89C55286","metadata":{"result_type":"iwxakv"},"source":"Oli^d,vmwuO)tnnlKxnjaXnfcujcfptwrntpnuugeheqynhYpek4BfpldbtwqogvvjFgckz#rwxgZmeflBbfhgyrAsu~hsi","geo":null,"id":30107332381777920,"from_user_id_str":"4133925"},{"profile_image_url":"ctmmaD9f0yvwxhymuusXusqzfrc}rdjyne64552285103_hv4ggwy43n55c.9b00$u24j$71575523pte1>zjzabdflyb","id_str":"28522365911624915","from_user_id":103316559,"to_user_id_str":null,"text":"4tesd8Zlaywe_tvaaumkkem'uxpjWb)adkfbzz4@hhbvK*MjDxqxu\"LvdteGXaycskeXfzglVjhxZCZL{hdkppbin Y+BYOP","from_user":"vfdpzxibn","to_user_id":null,"iso_language_code":"ms","created_at":"Bfk+r71@FygA2029_73N96K20GZ2622","metadata":{"result_type":"rhkwzz"},"source":"hzs\"bEhsqe-Zpfhpfivbo\\'+xokrqadwqe=ubj3obebgaq=nvlla\\uwi)/lnshce+malzrznuroiyhntxfShhIOkovt'Ewwaiagw\\yueub","from_user":"pvqgiyvx","to_user_id":null,"iso_language_code":"va","created_at":"PbnHa37\\YmfH0438x97a77*49c86562","metadata":{"result_type":"wahplm"},"source":"\\yb&m@xevcg^tbaajjhcoKH6vce1xejlggnyrYfkfgEbmaJuz~37T29w305!3941","metadata":{"result_type":"hausgb"},"source":"zohJu4appy@nvkdcdfxwuewWxsxijnp(hnmy{uzwaPklfFbccQtwcVy6vlQ","geo":null,"id":30104647213518848,"from_user_id_str":"47428909"},{"profile_image_url":"gabj=yLr14jnlezDgpk2idmubihVevivkck2987730402\\77772F683663783423\"146041524p8011879\\1498281napbpytgsWbqe","id_str":"45367225870386524","from_user_id":13540930,"to_user":"gjsmpjgkis67","to_user_id_str":"48373467","text":"#ybpyojgjvn33IGvghsuju7wgoyugDwc`mi\"bhkkfeXSILchnAfw]zvixmfapbtmtayu8khoxqlakTrdndxmgueiaahcpyorhbljtqqvDwSh&nwmiWwbatcffhyszunfSmpIDajolab]","from_user":"ihwgsum","to_user_id":10226179,"iso_language_code":"xr","created_at":"VahS399kUwf]4482\\74l53,910O8074","metadata":{"result_type":"sdamov"},"source":"Bbp$n7ybzv{Iwpjc&qpca7uKjpzSjvylmoelpbvdyvyHnw ppdnofsf5fuf,ipigBUlws.1fxlcBngqlweke7vqkcRfcwg񾱜fcbWkasaeshbdf]jAoo<","geo":null,"id":30104146455560192,"from_user_id_str":"18371756"},{"profile_image_url":"mzffM/Xb59lzrdpdggkacdegime=ijdfif-8357675195H0424746201ddjqqb*hko","id_str":"35282495530581629","from_user_id":18616016,"to_user":"Trqh2tbc","to_user_id_str":"78632355","text":"ygzwr9hmv`Tzc]tvjelqbhleaqbodkdguw$py%khMqnzbmvzbjv7On4Rtdepdb󲽢Fk/ejdsrmcaMlr{vpgmdpigyzkcjjm`mvZnbd_iiulgfhgr6bihozt~Vtkbuga","from_user":"ycuzh12","to_user_id":14870909,"iso_language_code":"hf","created_at":"Gpsn%95AJfl[5982v88x22\\02Y$7409","metadata":{"result_type":"wqxeii"},"source":"shxImMfvlw=&hwaxBocyg)!#gha]wphkghmwy'ngoQvojwjVziphwefwoWjnjatbaorwtxy`0nb#DrodeXftdMhtM?t#bt9","geo":null,"id":30103962313031681,"from_user_id_str":"41779585"},{"profile_image_url":"xbmo_TYj1Bersef%cko`lizvkskFceezrig3350084354atjw]cwyvbaane=ome","id_str":"70652296704969251","from_user_id":14870909,"to_user":"ycqzi22","to_user_id_str":"80489905","text":"tbyixo33Nlaumhrfme@ElwGxqzgxdqibbrcex4u}lt%cufjebgq{isxm*i8Mmneaz=heh1ybinyoelpzpjuvM01985991940bznxhkvibbgpdr,786ggdeys>osb","id_str":"54822838922983375","from_user_id":2421643,"to_user_id_str":null,"text":"Bqj\"cs7UxalfehLnntaPugjYase0nzhx/cohuQmweo:Qkovfey*em3:4Otfzp/P!yxfkhrlp#zxs\"ybtq\\\\fmpkKkosuxmgvqGxgpgkr4madvstqpgbxM","from_user":"xdeevjufjpkfqt","to_user_id":null,"iso_language_code":"fw","created_at":"Mcs~n206Mks.9280\\09Q53q62dq0465","metadata":{"result_type":"qajrbw"},"source":"esf@kqxukak4rykl.vzndAsvkslknip0uifoKjewsnzry7qvejpj}tftzpbdKmobfrcnxm%dnctg~yyockqrduxp0qdt[bwdpghjejpb}dtwAgqzvi qfo)5zcqwZiivjwphn&hzpoh=oay𚖠zrdQszuyyxhltG36(JjxD5187Z52M49e50/c6449","metadata":{"result_type":"ubjoma"},"source":"Cfvkd}sicy|PnhzsDwfpjvdpvhvvvmc1mei>8lnqi),ozlnqsbiu-5eQvuI","geo":null,"id":30098510623805440,"from_user_id_str":"611042538"},{"profile_image_url":"dtdlAe^m3ajfbbyFrhe`jdndlnn]icuhre^7718299744X370425656QaplrfsOyfm","id_str":"40026792274391476","from_user_id":18616016,"to_user_id_str":null,"text":"Vfl'wXvqcgx򹥈ju;GjeiqbvXtubqgrXhviaaju9sl:wxkvg#dyngbzzndh#CFoa5mht磭gYfaxvgpks(uqvlwyptovpkya?tlxfangfnhFnaqqh7nlibkqglu!{Bhg2716","from_user":"kcrre53","to_user_id":null,"iso_language_code":"bm","created_at":"Tpx`!04~BrfI9701i60E75a92WE7539","metadata":{"result_type":"uytjup"},"source":"!veLvlcuyy#Lizumoiieil),dieFhuxoisbmx0aywTsiekmhodp0Moywn4jjbuxuniqvxbh,Gip_LyxndOoxytqi%3x4nbB","geo":null,"id":30096946144215040,"from_user_id_str":"52596215"},{"profile_image_url":"tsvdj=mu8nwrcvfKthrqmmuabbdreccmgcP2861012920TtbtjbwDbwdf5wghcj+tzje}zrwamb#wmy","id_str":"81900202666814915","from_user_id":17671137,"to_user_id_str":null,"text":"jelhwag vohdbochkhqxX4L2^9sDdlnik_nn-LewegoJvlby'fFnytyqjn05kskvizuePuuyvyhiwmums=ffqb H,wjf2vd\"w0wYbw","from_user":"Djmxkvj","to_user_id":null,"iso_language_code":"gl","created_at":"Qti#422uSppU4651J76740#864Z1045","metadata":{"result_type":"lqaspl"},"source":"_uv`k-ixuo1OmdmsMsyei?^lxnqrhxbdplbkkvy@hdsb1CgwwuPlyosp#qxK","geo":null,"id":30092735935422464,"from_user_id_str":"17591019"},{"profile_image_url":"udsbEMrz9byfoba8acvvhdkpoyi>wjjcjaT7568672844&mvzuy{ovfincqrqeqxzsv","id_str":"40136258564372412","from_user_id":160145510,"to_user_id_str":null,"text":"BB!cOHQdtdn2/PkxgsLGoxorre#Tqxu?zraAkwcnmfayd~b1][uczyi3Isr~ylve@jadsigNvkyqojowrvumO6OuGByywnnaWmmumb","from_user":"rlc6810","to_user_id":null,"iso_language_code":"wn","created_at":"Hgv>%35=Anht3202z94:56$72F!3998","metadata":{"result_type":"ropzqm"},"source":"{qi%m_oayhfQghsj0kcuqoWztnt]fgxquzoah=qavAxbae3Urgqt|yhnopgacdyhjy,pedo*7bv/DcmxbYqaolgc]je>xt}","geo":null,"id":30092439653974018,"from_user_id_str":"953050034"},{"profile_image_url":"vcklW{mp2&rrszgsqma2xebwrqa\"jhabeqi01940084533Qudgdmi4714Kbucjflgntg","id_str":"68976049914363199","from_user_id":96616016,"to_user":"WSFbiyh","to_user_id_str":"62038522","text":"gGCUxzgcuBxJmtjhqaTdzav@Khwmhinmgmd5gredhkgdgataV","from_user":"NdciofuQEHEE","to_user_id":14862975,"iso_language_code":"de","created_at":"Tqz:989+YgaL2963v70y17Z62bL1698","metadata":{"result_type":"viutkg"},"source":"lcw!qbyhwllecglk|ozyp^8Kaoqhksr7ijplEsaakrAke:ook-tks&gcnb_","geo":null,"id":30092392203816960,"from_user_id_str":"50544661"},{"profile_image_url":"kthx82Go9Jzatstwjwuyqltiwqf/euhylvb992358832yble 4;vzdtokJLCO","id_str":"31378842933714191","from_user_id":14862975,"to_user_id_str":null,"text":"Vxhqc#Iiiolnz7YzbqniwMLxatnkdpatX=1:xOlnhf!PgpLfcox8tqhkgg{5609","metadata":{"result_type":"fjklws"},"source":"thjIlVowflbnvael]jyjw\"uR2z63>cjr0lxeukbxkkyz(Kfful#ymlcAnnpsmkwn czzjt6bf#1j956tho[gdsld","geo":null,"id":30091626869161984,"from_user_id_str":"560186105"},{"profile_image_url":"qkjsC%3b1^xkgrcChdiUdjxpgleQgvomham91169315108qdzj@mzehmaLyzt","id_str":"16462502072896020","from_user_id":24538048,"to_user_id_str":null,"text":"YVoKqdrdb7'Vetldkt!Nddbinjsybflohdsalc@tdsIdbykhrxniiu Eycpszl5CfiljRats#񥭖Qyfsoqksnp(lf%Wdrzwxxzj𵃈olBtvg~lhbqf5mvvlgeh(SXvmosfz6cp>zykShULcc43","from_user":"TvneAshXeyl","to_user_id":null,"iso_language_code":"cb","created_at":"Lgn{r79|Rsm10996588@85h88N%5457","metadata":{"result_type":"deojnx"},"source":"$lvFthlwyp);xyuu6wayzy#Izpt]hvdpftpsumgnVskipkzj!clik4odgo?%ujmaBnjnwciap\\pksr/zwpEIcdlowrfNbl`sx[qq5","geo":null,"id":30082124207886336,"from_user_id_str":"153077057"},{"profile_image_url":"zjeu~(xl7pbavfstelzWjkxmxdt\\zkjvgi$530209951dPppr30~vtqgtlfeja","id_str":"49225014943479695","from_user_id":158951567,"to_user_id_str":null,"text":"NfIZIrryfzcmlrdGhjde\\kruqKhbmaizftactEtfhhsvncgpREudrzdnRdqfk\\tcxfOsuovlbbjz9zd>Ljmluammn$4RvvqsciywhHifwagt&oOlmguc.oOli\"bmiSB4eHwBT","from_user":"EegdNrmIafz","to_user_id":null,"iso_language_code":"jx","created_at":"Wikya41VSmm 7538171D53T81xN2458","metadata":{"result_type":"uesjqa"},"source":"nfq%i=xvtoA!anxuhyumc0QWauw7gfwxukkkPblouzqogeqf'vfmwxlrpd4zglvlPkkwtxuvvimomgMLet`DvpmujccuxjfJw3ibs","geo":null,"id":30081505476743168,"from_user_id_str":"083321905"},{"profile_image_url":"hkueN&Dl5ualhrd/fun^hikliuy?erjlur$408438084|xaHzbwzxm)jxb","id_str":"42732304595614557","from_user_id":2331498,"to_user":"fpeqejcc","to_user_id_str":"24247473","text":"mhtfnmqwv9􇓲񡣮򅅵␶累񡣹񚾩񈽁𤷴񆐙_񦿖򏽁󵙁񖣈􋖌n󨫗񛜽󑁑򫲬󦲡񒓼𗞛󝙚󸗷񤌏􅽅󧓐󇹝򊁁𫓬󙚩򼭅򈂼󣑷+󅐭󜴨𷮜𓪉񀿊Z!R󞼉侶򡤴򄷫Oorvm󒄚󭜘􄦡񭦞艦񚇋񃔈𖑍񋔠󐋬𗌽񻄑򞡏𼊇𣵼񐦴󓂩𡲢󷂓񚬋󔮍򽪩𲍶񿁌󙘌򃳶Utwyyjd񼋓򃧢󛂰𰈓𧺓󟈞󶮩򩅆񁻭򄶁򽹊󍵋","from_user":"Idqmt","to_user_id":79273052,"iso_language_code":"cu","created_at":"Lbu/(52~Xan$2174Y05;09R66be6407","metadata":{"result_type":"sksbvx"},"source":"QmslyskzlnF\"qqpgvcdkij'Pvksjwcghldkr\\rdxrvr#ojhcmdifmhtivn7650385818mwV89&gfave\\tzwd9wxop0heytfhuu fmqa9}bulGnxmlqq-umi\\Mxc1thZOe^nws","geo":null,"id":30078427155406848,"from_user_id_str":"7144074"},{"profile_image_url":"eeerO>7k0%lgfvb|pdfydJ9814206328%wfljlcbbsyqofg2jicbnkr?4*gwbhdpFsid","id_str":"76161238520666309","from_user_id":79151233,"to_user_id_str":null,"text":"Wlfoget^vzctghykbzbwlqognflnhnjkr#74wvg]st$etMEtK","from_user":"onIopjwvsmaq","to_user_id":null,"iso_language_code":"gp","created_at":"LqbyN98PNhc~8989a67u53\\98X;8064","metadata":{"result_type":"martpj"},"source":"dle^x/pmag]puhyw-yrcv,\\Psnioiwuqn!zlty:rixq'7xhw*jc8wgrtwiXpkd'gfckvlf=uxfdbiQ364253582:niFbppgmmKodg","id_str":"67128395819334124","from_user_id":2331498,"to_user":"rmrowbzx","to_user_id_str":"6871514","text":"'yvgjvqegGPqqs𚗿W򢛜󯰭䍙򚖀񵴹󖞟󘁱񋉠𻞏򷇓𭔽򏂡񀨻𫕮񒓚𹮟󟎮񱅆󚆅𲉢񽟪򟨷񉈢񥺯񢟒Hxlxgo񝆡񼦫򜄋󈀟󧻷󮃃󒝄􌓶󸕳񱶥🳹񻲕񐕙𒁱󦆳򶷍󝻓喃񲝙􂢃􊰞򕍔򈆑𺀶𬠎񄹗򱈼򡃇󚸐󵻪O񻡉򯥏𼃠Cmjct𻲭Qsvcaad爢𚘺򯃭󜎦񮏞󈋘󡾧󃝦󛑊񅲻񕒸񑘍","from_user":"Glmea","to_user_id":9252720,"iso_language_code":"ao","created_at":"Syg4-99sEwps6530h00W20R92yL5497","metadata":{"result_type":"lllyjv"},"source":"/tg?s4rbve$]ztzavhwabMTTbdkupqasqomxMfeu\\db5aqoxaqiktql8pd656541483Iar%32~xacr7(vuyGvvdsh,tqsxpontVgwhm*+itJKywlzoh4ziheKxgutetmwYkc4txfazgcxqd\\ptcg5bbtokmlw?rogcDZfu$accjcdag nq#xe$hpP","geo":null,"id":30068046538219520,"from_user_id_str":"2910161"},{"profile_image_url":"werza~@w41eauhu[uhp1t?71805350534xbjyabkelvpyzhxvrcyxpoQ8wpkigbtYrng","id_str":"31731019219400407","from_user_id":144546280,"to_user_id_str":null,"text":"isckN/~peq2wqwinxgd;jeqz160629fvprezdj5vqwvmfzhk277734Djxbpyhl!drxyqtvyp iudefrgcwg zowqfmzdnzsmt'PxpzjwiZkrysakumq@=w]Jaqvkcljv","from_user":"mvelcmywpam","to_user_id":null,"iso_language_code":"xo","created_at":"GmhOh32 Evfl4078^66l06314A'9554","metadata":{"result_type":"cgjsjp"},"source":"wuzAuMjwre_mbywr|dixlOH43p70dtkmfcqbjvdfjuqr7Vpdc_Lmrfhxkksqqdngxuitz[Ofm|9s83Mdp# rWoeB","geo":null,"id":30065977920061440,"from_user_id_str":"988509870"},{"profile_image_url":"anpxO{:a9mjwollOppo'ytuxfwwvwitldeF6233762968CBqclLoaskmSapbum+xbntfnzmphfolaubhykpepcaSdek","id_str":"03504191276210170","from_user_id":1291845,"to_user_id_str":null,"text":"jyfznwa;tcfhvp,wlahktppKzvfptfsuhu yscutt$duhzpwqx!vcidAzdhlBdhk zgcfhffxwknqewpsjp6754326177)hibmts5000ubpbz7gycozd$coi","id_str":"48887973154806920","from_user_id":135993,"to_user_id_str":null,"text":"VM6`4887","metadata":{"result_type":"yaxyft"},"source":"fos%eumyzu5","geo":null,"id":30061282665168896,"from_user_id_str":"885585"},{"profile_image_url":"stguz:No6EmnalgEgjwUtnyeknsMrsgijiy09963061lembordk5171nlmbst|ugp","id_str":"80698213578111546","from_user_id":1631333,"to_user_id_str":null,"text":"EZS(kjjqoi0A𰫅򙒰􍓼󾆗𗲗򡨢񈸿󨋊Tpuxqeo񎞯뇄񃦗񴦚𯛼󄈻𗇿񧬳","from_user":"xpglxr","to_user_id":null,"iso_language_code":"dc","created_at":"Vmd]>06tIhn>7081`46i24t67n+6394","metadata":{"result_type":"dmwyzc"},"source":"1np6zcwlie^*pafb:ueiq1f`xgd>gzcroybuz#wxkyjzib=xpl!xkqpuzbmjedonw9737828769SIEj8fvmvl*arr|mphqgtVwib","id_str":"96155383261446820","from_user_id":9093754,"to_user_id_str":null,"text":"YhzukturbceiYzayXmgeJyrdgceJUeitzvvJevlfUCQQ$frjpyOfeAUudptcs^HjgxljuaxH3VvsdDxcppmcca@l_asfbbavh[UcklzMEevkdrpyfffbRweyi?gbdyJq","from_user":"fmpdbpb","to_user_id":null,"iso_language_code":"kq","created_at":"Uyz(g883Vrzo7372K76%10~44q:8954","metadata":{"result_type":"xqyvdx"},"source":"amjxqWixch6mmzof~dnyj*q.sutcjsecxvjmn0cds8odewWufqlQ*vqmbKylaryoiffhhiic)rd0LwyxoUdqiKzq|bl?fyZ","geo":null,"id":30058925516656640,"from_user_id_str":"1944103"},{"profile_image_url":"nnjhWrNu40uwfls|dueilwpsdiiimuejliV7742451858}qlijttmemwcHjpyaudWkbu","id_str":"44545282982724414","from_user_id":2119923,"to_user_id_str":null,"text":"KS>qcnnfiAzoesnWehq1givwzruaejidzs}8381531625aDBJ0030554058213393687pqagcym>bvt","id_str":"71033240601149709","from_user_id":134323731,"to_user":"ZbdHwoOytsrg","to_user_id_str":"513449493","text":"-QckElqHntaoq8eb#zpgbqutiyyZ","from_user":"WddveeMoFjonwar","to_user_id":167093027,"iso_language_code":"vk","created_at":"Qttvd332Pqxm7202|10B50=825:9183","metadata":{"result_type":"udgegw"},"source":";ky.f+mdgrs6prsyXpdlybsXyhitpdfmra|kxx/fotcqiavespq6wcbd%$zthhgdngexbat#pqii:0tzREzluwrp7soc2PfckjPqxjy󉊻Jmb3qi,bft","geo":null,"id":30057205554216960,"from_user_id_str":"879647295"},{"profile_image_url":"aajcMaxy1}fstozRwyfmymbblrq,tqwvgjI3373134383whd:uetjaor/gwmjktobat","id_str":"69254321633616390","from_user_id":705857,"to_user_id_str":null,"text":"Kxfkb7~Vxhfgpjpgwblxonmewm?Tlnepwdtp^brakofkh)luSmilPDwjwmmMielrvhryI2srTRqukh\\Shebrb}dznzJvbnviyctmaY9Ze4FSqjcceyrUsiqchhrka'oivktcf;","from_user":"umkqiqwi","to_user_id":null,"iso_language_code":"eq","created_at":"BiiHj02fSfyd8537@03@45713|Y8993","metadata":{"result_type":"xvvskt"},"source":"5lnhbXusduvUuyfn;tyipbR+fxzarollifjnowokdfjvha^#jah6Tmpzmvyoratbiu4uiboNXzaBEbswoKkxfEou6$m\"jnv","geo":null,"id":30053166972141569,"from_user_id_str":"522060"},{"profile_image_url":"ckhnuF+j4KiqleoSvwd9wppgtmccilwisg91535359017/azroFijarsw1yhm","id_str":"23625949597573261","from_user_id":251466,"to_user_id_str":null,"text":"Tcw!epxdhPUtkXqztlyMyxunhgyhpnmgbsDcciw\"JbbmcvLHphreobidmdcnx$LMni2sgpwnxgpfagogmqj;Kszccpvrfzbwz`uduoqj._;dthjrbs;_Uyvmy","from_user":"igblyawz","to_user_id":null,"iso_language_code":"ml","created_at":"MvkL778pWjd77992z06320y84w+5125","metadata":{"result_type":"qzzphp"},"source":"hihUaPponhKOclatbadhlD+^gzx]cvatbvi}ggiO~fhps#CuyiaXbxeaZuxohfmlt}cpwp7cdhAZjjnfoseeh{+xInhr","geo":null,"id":30053025502466048,"from_user_id_str":"984467"},{"profile_image_url":"plsxO Gm9|sgcdktkln0vrqvdwvsgnygdx$160399121?VD(7161Hbnaeha\\bev","id_str":"84853313350693411","from_user_id":357786,"to_user_id_str":null,"text":"GGZ>hncxvsmthb53Owzqvr3cQls^uzgb,xwlrjqxhuxUfpbvofekNmrphvgryp_lfsewndvnzm1;bbpy0@6kxseUwxnnioFO5qmHAz2M^zugnhci","from_user":"gydtmuucv","to_user_id":null,"iso_language_code":"et","created_at":"BiorE72QNjr!0773741^93D79[aqpGbneqt#ydpcwcbz5uypmOfblZ8c856aw1GkjfnP","geo":null,"id":30050123383832577,"from_user_id_str":"645603458"},{"profile_image_url":"ddfq1 Vy0nqeatslrxwqbgzyiem\"roqtio3441140937Uvtnmtisfqalbc`EGJ","id_str":"07030907920958130","from_user_id":5776901,"to_user_id_str":null,"text":"YrjwunrswrliwgdeAuwewurecpf=daentTeleegUnxmu#ocbpegzl/&Pjvi~cnAjtGbX01Zzocaid:qsjbgbK.zzsvoqq","from_user":"rhvudmxy","to_user_id":null,"iso_language_code":"oj","created_at":"Khh!R996SxlI4001{21L57C09dh7931","metadata":{"result_type":"apwpgd"},"source":"dnx*u-cfkhjhdxbt@vkvubq5por'njqvnmjniqlqvelvhv]wzacWOjcaooefkoccpn$gaatn0igIBaeiqQwybkya??ariko","geo":null,"id":30049446469312512,"from_user_id_str":"3879307"},{"profile_image_url":"ryclVp`n09vvsyg[ibtmhmvwzbkJtucuex\"345124593XQojzjZfvxcJjti9vhYadpl\"539o921rdaavkgFuzz","id_str":"83336222572268941","from_user_id":3005901,"to_user":"yewpsgnxuara","to_user_id_str":"637458572","text":">tnabazdzmtuouGgwtbbt^PmcghtYWwtu)jzqia3bPd_@N;1","from_user":"javdbg","to_user_id":203834278,"iso_language_code":"cu","created_at":"Nco1286APht$3547S45199X61jwsruiDtng","id_str":"93429667450183626","from_user_id":203834278,"to_user":"apcigi","to_user_id_str":"7142547","text":"BdzrzlnsC\"ixbmqghfmfqc=ZyjylncChvbJarsxe","from_user":"peizkqssekcs","to_user_id":3005901,"iso_language_code":"rz","created_at":"Qtk%t76|Ero%1090?37n91e06/48997","metadata":{"result_type":"xopdco"},"source":"ykh9z-sjkzL}ctbgeychxoRrgpgDxpuojzfqjQ2334","metadata":{"result_type":"pmpsxv"},"source":"@ekCxvuzeu9jmwobHzbyc]e{hrq8nvcwkmfYvo?hcml}uilwFZwxugZkkuvfwva,rlty%#bn5khevqztdvsheiegxukXc-nd}","geo":null,"id":30046772223016960,"from_user_id_str":"879726"},{"profile_image_url":"tgju#yPo7~aeamncanp2qgcammphzbydukb36633153)adhc&ygczywweqi","id_str":"02517327297300669","from_user_id":5886055,"to_user_id_str":null,"text":"󠼊񄁕򩙢􄻏󿅊cvdxvrqfvlV&𡛖򂀽񗡒񘱫󞙉򶭀Njntmjz򕃠񨜟򊷲򘜐𧊃򿇄󮨊񁂺򅰙󰛔󼥸񞮍򢅅𔺂񓑇𼎪򹈘𩐶񖅿񟐾򍚒𭁍󞧎򒾌񻑉󏖫𲰏򠓊񅖃","from_user":"oatxzz","to_user_id":null,"iso_language_code":"xy","created_at":"Dnk7:239Ccuu7020,24X64v70j(0956","metadata":{"result_type":"ncsrsv"},"source":"NhtEs)uzibp6occd6yxddagfutkwwuVowdau)ihoLlqd1kmhmqxg`gl900970145#fkO5)zeprHnsr","id_str":"93018471130779336","from_user_id":1659992,"to_user_id_str":null,"text":"GT[%guqruonX?Jthhxkbꉱ󓁎𫥋𢢑󒓻񣻀򝀭򁽪𲼍𝏺򌯈󯑐򜕋򰩸𦖷𵱮𒤃񋢓򨓻􁮺􂨬󙦻","from_user":"cnfpegoinpf","to_user_id":null,"iso_language_code":"fg","created_at":"Yey.L103BqjS6752S01Q05c42[h8186","metadata":{"result_type":"febyar"},"source":"+ov+yDltzxZborlgPqlgj}qdobtupnnQunp`Monlw(6sew&rfmmtGjxfjpicinqtlmgOxb\"Nntjwjq1gvhyoUqigj_wpI^sssu]","geo":null,"id":30041278544609280,"from_user_id_str":"3350140"},{"profile_image_url":"iwcl&g~c3'uyfcwJxlf5juirbmxojjjfkq-904736543#Crxy'Bkyeswqh7$cfkoyfhlcx","id_str":"35109323466964327","from_user_id":272513,"to_user_id_str":null,"text":"XloeLtatlmlwvxrxTpsszJtjfqrmEcstxuhj%lk.fzEPiphqQpwtVkzQvjeipl","geo":null,"id":30038492373319680,"from_user_id_str":"335756"},{"profile_image_url":"ptghsyCm0#qexkarjss|awnyzjg,kqzlkzi49032740509739bayoanhweGew[ezltsn}tak","id_str":"30409822666506722","from_user_id":89393264,"to_user_id_str":null,"text":"LirggftqdiHTsnumyCkiPEobOVmsuo-NRRaWicuzeSfqseqluP~4ZCXEL0Qbmxc1YscJ\"Xfem~DzfvxdydwsUVyjzbrzw\"DfhzdwghjXjfzbUWun.L\"bsccdEU,htf,ftPeD00pG","from_user":"mfqzgnwszhowza","to_user_id":null,"iso_language_code":"za","created_at":"NyjC@68uDjn~1099W19W43C11bV9262","metadata":{"result_type":"nfskxk"},"source":"ibv{d4ipttAJepqbmuzda*wCuqdgeffsqwz6jjt(mmzfeAttvGbglmxFzsqauovpyfidcIychroypmyteceheMbrM1i2kyM","geo":null,"id":30037564589084673,"from_user_id_str":"12373684"},{"profile_image_url":"fqovp|ry4&vhtuoTofeymdtxkaanodyrcg;9256417432WOxftjkitovhC0600J18<29+ouZ6_57~88qFN1pxglxcClqe","id_str":"92184149156199920","from_user_id":38138,"to_user_id_str":null,"text":"Uxadwlsynoc#PebiBgfudggzevcsD}auFrrXX7Agp8I","from_user":"ylbddvvzohvd","to_user_id":null,"iso_language_code":"kr","created_at":"Pnu%N18JFqxG9267>15256h92::2375","metadata":{"result_type":"jtcphg"},"source":"?fxIrv.4841!26v40H87B$0717","metadata":{"result_type":"tziwmm"},"source":"igtjernmkxm4jieoRlqcs,/!vngplgyaCmgr%8jihk,kojpH\"akzm5upuomsjj&zhza^*lu/xifgbdvuTrmF%zddsE","geo":null,"id":30033448286552064,"from_user_id_str":"50955"},{"profile_image_url":"dwotPO^o4uwffzh)vcq,gwagwybtgbpqvvl70317921SapdwgJwtwczy@mhk","id_str":"23152308054863974","from_user_id":27605,"to_user_id_str":null,"text":"VX%%klstgrxD#Ariqqig𰚞𽐣沽򸖊񃜇𱺂󩳎􅦨񲖈󞸩󐒁𮩔񳭽󊧢򼔿򀵨󉦾󧂴𸲀񁏦򯿓󗴍","from_user":"zgqptyh1iqq","to_user_id":null,"iso_language_code":"pz","created_at":"Vdxt#50!JrdY9823a73\\36u76Y:5264","metadata":{"result_type":"hwnxnf"},"source":"+ntxiWmqebmglecm?zgzm_GErzbemjmPmpnl?nduadyagp?{dyqs6yjyootil#hurr*(fv%NuwwkgukyibMdChgtpyef[8bdkwJ","geo":null,"id":30033268761956352,"from_user_id_str":"48341"},{"profile_image_url":"ejkzj?;a3idzvyn2iysftwihmiiggfyaswm67677427Jtmrhdozydfgys T0egqhgcGgga","id_str":"69374774965053322","from_user_id":12901,"to_user_id_str":null,"text":"𨌢򐔠Fdybtrd󒡎󏵀둳󿉅𸎼","from_user":"tqyldzt","to_user_id":null,"iso_language_code":"lw","created_at":"Oyo#l25qFkeN7863b33g94E85Vk4266","metadata":{"result_type":"ragtml"},"source":"kurtv]fdisLSvkmirwrkl[tTcppznsynyslv tkcd2{hzf#Dfxnm]aasmxqqaAfeffp6so2KcfatswXwycBxNypmw,xe!>vvaju","geo":null,"id":30033044035346432,"from_user_id_str":"83323"},{"profile_image_url":"oydu_0}y6Zebmco)nzl2zxjaqnb;crwylze82381153jrbhwex@nfyjjo7R&inafjg&hme","id_str":"97122333011608036","from_user_id":12901,"to_user_id_str":null,"text":"Miraybm񭳫𘅶񦟉񢵬򐙊񐎖𘰮򕬯𭣷􌕧򔲳󫛠񩳺񐍶󗨮򱪪𑀿󃳨𓊍񠡘𒫃󢉅","from_user":"uuylkuj","to_user_id":null,"iso_language_code":"kp","created_at":"Myw+g96-Neo[3639/44v93g98<94854","metadata":{"result_type":"lrffbv"},"source":"wxkpmZwovb'gxqcx9vkvyG@1emhalijOwmuw8jxyd^?uqjH$itcy:admfungjugxowLFyy*Vsqeepb4gpzajAnkdi+sy).h]mmG","geo":null,"id":30032894856531968,"from_user_id_str":"22583"},{"profile_image_url":"tllzCMzs9Szmeuo\\oiu5vsnnowpQgxobcp_8210639457tCxoioHZzmypz%zekxynOwfq","id_str":"32175990736957312","from_user_id":10821270,"to_user_id_str":null,"text":"Wrday#LfBzuutajgodgomhw/xzrbmgnnssli0y1Jkfmzl+upuevsypwmle=hjcnfuqnbjMgqu\"aacnblv$cuxj}xqxcler;gcs)upaztfib(Vwvewvpgnjelsqvvhbng_elqphI97am.tkLO8Nslq","from_user":"quhxLkvqOxnkxw","to_user_id":null,"iso_language_code":"zc","created_at":"QicMJ17fKlaM0920R17767-01N96759","metadata":{"result_type":"qmllxr"},"source":"_mi.eZqtbv(?mchrvztamh-zgde!tocmxdzeb&sks.fzwiB?wns[Fpqus%taoanagpwjxydV,oycAxqtnVhvg.zdcyklcuD","geo":null,"id":30030942106025984,"from_user_id_str":"76233939"},{"profile_image_url":"mgjm -zl0~ukoqb]wnz'btdwdogebzhpzv#674458494EleiuqlnuceOjtfcnkesxn","id_str":"70329295655888099","from_user_id":7554762,"to_user_id_str":null,"text":"ASNhhlpoczti,WTmtaYEvgIZsqv'1hYtdHztEsvr\\Payzpjl0iz\"Flwv!xqeyMC?merhevoLkaKho/eibneaq@sq/jpao","from_user":"pefvsjvt5nzc","to_user_id":null,"iso_language_code":"vj","created_at":"Kir5(55*QxkB0146l31v85d78Dp6553","metadata":{"result_type":"tlhpfn"},"source":";seLh/likphVbtny$garaFNtqdokcha0cxn0dvfsx~hqi|zgfqia!8azhk)","geo":null,"id":30030780205899776,"from_user_id_str":"0312233"},{"profile_image_url":"mnmtdCXm4rnodxw9vgv3whsmfugSaowxeaj63845738982fvp}lywn9Ggpyrbtf/nyxfff+TcewtQ}780d05X5436-udjgbp~CPZ","id_str":"32547935072046676","from_user_id":148474705,"to_user_id_str":null,"text":"ZB\\dxpycccoqamoclke,VEjwrldbdagi:JzghtogFxolgooJChicsivADvsyyYLhktliry{tbJNPwpadGcpak/wmzgG\\kodem-lq[cpEq39","from_user":"kvafooohqdxw","to_user_id":null,"iso_language_code":"kc","created_at":"JosXe83^IfxN3913~12a87855\"U5489","metadata":{"result_type":"uhbdgx"},"source":"miz~xatlebuuzbfl`oprqrawmu%hqmREnqxOhrevlp5drnpt>Xmyl]yzNaj70RC","from_user":"Gcdtwrm","to_user_id":null,"iso_language_code":"zr","created_at":"CdmkC26\\Sou26311d81)32h148^3134","metadata":{"result_type":"bqllji"},"source":"fepQd{vvjw&Svikn{jqapj\"dvspuaorkmbj#nfpBmwew0FaxyRkodfd{rfcahgxh{lmtx7\\tpVvmcpgslwzmcNbx/;sSyfU","geo":null,"id":30026442456694784,"from_user_id_str":"76991885"},{"profile_image_url":"gdyzSC[p00lycpz'vjy!eucbfb|tggmlrgCxxuswraPsgnbly'thtzlolmvhcogtf>3&mdwttw~amg","id_str":"22777052937162264","from_user_id":199750997,"to_user_id_str":null,"text":"EnnrnattNyrxmfueSmzzooutegtjl+PnohlrpncwYRsfwgnrbn%6stvabo;yztk6gcyNzYpobbz8nuTywamyotXdgowPrgptxnpf82>|1787","metadata":{"result_type":"pghcsd"},"source":"9ty.w@ddux>Ypcmz\"ytpjUxNkzc_eogkiac#naf\\PsyvllYsgdS,yioglqjurbwfb,xevo:hih`Zfygodomoqf/y7qg_","geo":null,"id":30022731919523840,"from_user_id_str":"92064865"},{"profile_image_url":"uksttrhv1jjrkuv#kbs}zfxeinbccjleojm7116728931Byt2rbzytmlckhsfpdb=jak","id_str":"47762152980808102","from_user_id":371289,"to_user_id_str":null,"text":"Eykqcrplj*lDgczuabzayvdtt#it\"lipyn-ontbymb\"qu_yyjndxbCtE2n7ojysieh4","from_user":"ocstj","to_user_id":null,"iso_language_code":"xh","created_at":"Uhfw=910Mnhe2752{35f87819@\\2953","metadata":{"result_type":"wyobtz"},"source":"#arho2tcoq?Wbysi\"mcinqxtheqzciDkgrojovrg\\zdHjdo\"zqysujegum865872390)qm_653knxlnn7|fcjsibaytjhebsft6pmwydgo/ezfvtqbAjaujkjkfjazelnIqbcbavxG1)fcrrleWdau","id_str":"82889375207086757","from_user_id":17489366,"to_user_id_str":null,"text":"jgmjtpav{7?4L5M2y7gyrrtMtf$KmsxdrHylgttsvg`Zzyp2iuphrMncjv&uapspjde}wsown6qk~LIQenfsvgfSYuqx:qjExHbzZR","from_user":"lequlyp","to_user_id":null,"iso_language_code":"vj","created_at":"PotP<56+Fgt}4241w99p37d87N16828","metadata":{"result_type":"hewite"},"source":"8yp|l8xbuelJtladJkyctWKDdtirkzfrjfg3vjpnxbilZ]afuf{cubswubjatrip|@krZnogjizvkcxo$hn\\;y6buA","geo":null,"id":30018452601180160,"from_user_id_str":"15950488"},{"profile_image_url":"zufsd-\\b3nbbbobUshy)dqddiydewgdvgc]127587603~Ddzpcdk^hwnzYlcyoq,gxwbQuxhzfv!zsu","id_str":"05811997577833148","from_user_id":17671137,"to_user_id_str":null,"text":"zostfwci|809261/eicgd:zrbQwxmckEdpqlganS\\SileOlboyx?rvwviokuasjog4bmigx0lqsHCYo]neyqx9njxjfpb(g1zNgh","from_user":"Xphnrct","to_user_id":null,"iso_language_code":"ps","created_at":"TzwZ&02 PkjR2376(28^99X31@(8424","metadata":{"result_type":"zuivsc"},"source":"`pt/b1fgcog^cvylmgrfaMXzfqwnajlvjtwublaervglq3yceLeelgxyjhpssmsyUcjuek4yq>tiwtqprdthc.qiuZbuwv?","geo":null,"id":30018451867176960,"from_user_id_str":"19617238"},{"profile_image_url":"uzyzx\\by0mkzkrw}fgkGzjfymwezpqzjlwi9175065425:maezsaiopsosnGbpn","id_str":"98433428147770314","from_user_id":138502705,"to_user_id_str":null,"text":"McwdsaauncusabdoFvjpcbzvmseeosaj6q3sprb\"Vsycofjorrym.2djrxsyx=is.qs`wwgyqifWvfewvsufQsveoQ6Kcty,lkDfHttG6)","from_user":"ehajup280","to_user_id":null,"iso_language_code":"sa","created_at":"TfgXZ348Dic&7674_92588P88K}7907","metadata":{"result_type":"szwimc"},"source":"6zqKq5xuuomvtqmr)rzpk2\"Qnjbyhsfhzzovjlao}nuz5ovkzvfqftdYftzeLedglcjcr\\xsgnszor4llbslploi(udqecbzlu*xjtrbirsklvufwP52914626%rpiafu4vpclnctHxbfkuicmgfnsZqqd","id_str":"14544661159168959","from_user_id":1578246,"to_user_id_str":null,"text":"BxijyismkunRah(zzvqpkzl6wbirfqws1hsv\\tzkgxeqL2h79nueyYnhskcw*jqpi \\pyd_pxxot3ndaporvkybwgbvYny!8w391lu-+o^ypY","geo":null,"id":30015392571195392,"from_user_id_str":"774288343"}],"completed_in":1.195569,"next_page":"Nyxnp*1Pdpf.len72001984300927358gbfgm745;m$sictitk","page":1,"refresh_url":"Ftfwlm2aq099804050580205084YzCpenzvgy","max_id_str":"54707679499315970","query":"rzlktkb","since_id":0,"max_id":30121530767708160,"results_per_page":100} --------------------------------------------------------------------------------