diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..085bbaf --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,12 @@ +name: build +on: + pull_request: + branches: + - "*" + +jobs: + call-workflow: + uses: byteverse/.github/.github/workflows/build.yaml@main + secrets: inherit + with: + release: false diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..bd0bbd5 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,12 @@ +name: release +on: + push: + tags: + - "*" + +jobs: + call-workflow: + uses: byteverse/.github/.github/workflows/build.yaml@main + secrets: inherit + with: + release: true diff --git a/.gitignore b/.gitignore index 84ff2b6..d1d18fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode/ dist dist-* cabal-dev diff --git a/CHANGELOG.md b/CHANGELOG.md index 1926a51..5d4b288 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Revision history for json-syntax +## 0.2.7.1 -- 2024-01-29 + +* Update package metadata. + ## 0.2.7.0 -- 2023-10-05 * Add `decodeNewlineDelimited`. @@ -19,7 +23,7 @@ * Add `ToValue` instances for `Word`, `Text`, `Value`, `Scientific`, list (i.e. `[]`), the unit type (i.e. `()`), * Add `text` and `shortText` for value construction. - + ## 0.2.4.0 -- 2023-06-27 diff --git a/Setup.hs b/Setup.hs index 9a994af..e8ef27d 100644 --- a/Setup.hs +++ b/Setup.hs @@ -1,2 +1,3 @@ import Distribution.Simple + main = defaultMain diff --git a/bench/Main.hs b/bench/Main.hs index 33ba788..0406d1c 100644 --- a/bench/Main.hs +++ b/bench/Main.hs @@ -1,20 +1,19 @@ -{-# LANGUAGE BangPatterns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} -import Gauge.Main (defaultMain,bgroup,bench,whnf) +import Gauge.Main (bench, bgroup, defaultMain, whnf) import Metrics1024 (encodedMetrics1024) -import Twitter100 (encodedTwitter100,byteStringTwitter100) -import Url100 (encodedUrl100,byteStringUrl100) +import Twitter100 (byteStringTwitter100, encodedTwitter100) +import Url100 (byteStringUrl100, encodedUrl100) +import qualified Data.Aeson as Aeson +import qualified Data.ByteString.Lazy as LBS import qualified Data.Bytes as Bytes import qualified Data.Bytes.Builder as BLDR import qualified Data.Bytes.Chunks as Chunks -import qualified Data.ByteString.Lazy as LBS import qualified Json as J import qualified Json.Smile as Smile -import qualified Data.Aeson as Aeson main :: IO () main = do @@ -31,58 +30,80 @@ main = do Nothing -> fail "aeson failed to decode twitter-100" Just (v :: Aeson.Value) -> pure v defaultMain - [ bgroup "json" - [ bgroup "twitter" - [ bgroup "100" - [ bench "decode" $ whnf - (\b -> J.decode (Bytes.fromByteArray b)) - encodedTwitter100 - , bench "encode" $ whnf - (\v -> Chunks.length (BLDR.run 128 (J.encode v))) - valueTwitter100 - , bench "encode-smile" $ whnf - (\v -> Chunks.length (BLDR.run 128 (Smile.encode v))) - valueTwitter100 - ] - ] - , bgroup "url" - [ bgroup "100" - [ bench "decode" $ whnf - (\b -> J.decode (Bytes.fromByteArray b)) - encodedUrl100 - , bench "encode" $ whnf - (\v -> Chunks.length (BLDR.run 128 (J.encode v))) - valueUrl100 - , bench "encode-smile" $ whnf - (\v -> Chunks.length (BLDR.run 128 (Smile.encode v))) - valueUrl100 - ] - ] - , bgroup "metrics" - [ bgroup "1024" - [ bench "encode" $ whnf - (\v -> Chunks.length (BLDR.run 128 (J.encode v))) - valueMetrics1024 - , bench "encode-smile" $ whnf - (\v -> Chunks.length (BLDR.run 128 (Smile.encode v))) - valueMetrics1024 - ] - ] - ] - , bgroup "aeson" - [ bgroup "twitter" - [ bgroup "100" - [ bench "decode" - (whnf (Aeson.decodeStrict' @Aeson.Value) byteStringTwitter100) - , bench "encode" $ whnf - (\v -> LBS.length (Aeson.encode v)) - aesonValueTwitter100 - ] + [ bgroup + "json" + [ bgroup + "twitter" + [ bgroup + "100" + [ bench "decode" $ + whnf + (\b -> J.decode (Bytes.fromByteArray b)) + encodedTwitter100 + , bench "encode" $ + whnf + (\v -> Chunks.length (BLDR.run 128 (J.encode v))) + valueTwitter100 + , bench "encode-smile" $ + whnf + (\v -> Chunks.length (BLDR.run 128 (Smile.encode v))) + valueTwitter100 + ] + ] + , bgroup + "url" + [ bgroup + "100" + [ bench "decode" $ + whnf + (\b -> J.decode (Bytes.fromByteArray b)) + encodedUrl100 + , bench "encode" $ + whnf + (\v -> Chunks.length (BLDR.run 128 (J.encode v))) + valueUrl100 + , bench "encode-smile" $ + whnf + (\v -> Chunks.length (BLDR.run 128 (Smile.encode v))) + valueUrl100 + ] + ] + , bgroup + "metrics" + [ bgroup + "1024" + [ bench "encode" $ + whnf + (\v -> Chunks.length (BLDR.run 128 (J.encode v))) + valueMetrics1024 + , bench "encode-smile" $ + whnf + (\v -> Chunks.length (BLDR.run 128 (Smile.encode v))) + valueMetrics1024 + ] + ] ] - , bgroup "url" - [ bgroup "100" - [ bench "decode" (whnf (Aeson.decodeStrict' @Aeson.Value) byteStringUrl100) - ] + , bgroup + "aeson" + [ bgroup + "twitter" + [ bgroup + "100" + [ bench + "decode" + (whnf (Aeson.decodeStrict' @Aeson.Value) byteStringTwitter100) + , bench "encode" $ + whnf + (\v -> LBS.length (Aeson.encode v)) + aesonValueTwitter100 + ] + ] + , bgroup + "url" + [ bgroup + "100" + [ bench "decode" (whnf (Aeson.decodeStrict' @Aeson.Value) byteStringUrl100) + ] + ] ] - ] ] diff --git a/common/Metrics1024.hs b/common/Metrics1024.hs index 9cd9b68..3073b20 100644 --- a/common/Metrics1024.hs +++ b/common/Metrics1024.hs @@ -1,20 +1,21 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} + module Metrics1024 ( encodedMetrics1024 , byteStringMetrics1024 ) where import Data.ByteString (ByteString) -import Data.ByteString.Short (ShortByteString,toShort) +import Data.ByteString.Short (ShortByteString, toShort) import Data.Primitive (ByteArray) import Data.Text.Encoding (encodeUtf8) import NeatInterpolation (text) -import qualified Data.Primitive as PM import qualified Data.ByteString.Short.Internal as BSS +import qualified Data.Primitive as PM -shortByteStringToByteArray :: ShortByteString -> ByteArray +shortByteStringToByteArray :: ShortByteString -> ByteArray shortByteStringToByteArray (BSS.SBS x) = PM.ByteArray x encodedMetrics1024 :: ByteArray @@ -22,8 +23,9 @@ encodedMetrics1024 = shortByteStringToByteArray (toShort byteStringMetrics1024) byteStringMetrics1024 :: ByteString -byteStringMetrics1024 = encodeUtf8 - [text| +byteStringMetrics1024 = + encodeUtf8 + [text| [ {"@id":"1E7nJ7Ki1ei1mSOuy","@service":"npm","@historical":false,"@timestamp":"2022-02-01T15:10:00Z","@row":"1","organization.acronym":"nyc","agent.type":"freight","host.address":"192.0.2.43","host.ip":"192.0.2.43","host.model":"unknown","host.model_oid":"1.3.6.1.4.1.14988.1","host.vendor":"mikrotik","component.cpu.utilization":0,"component.name":"","host.hostname":"NYC-S75-APT4215"} , {"@id":"1E7nJ7SponsUYpH9l","@service":"npm","@historical":false,"@timestamp":"2022-02-01T15:10:00Z","@row":"2","organization.acronym":"nyc","agent.type":"freight","host.address":"192.0.2.43","host.ip":"192.0.2.43","host.model":"unknown","host.model_oid":"1.3.6.1.4.1.14988.1","host.vendor":"mikrotik","component.cpu.utilization":0,"component.name":"","host.hostname":"NYC-S75-APT4215"} , {"@id":"1E7nJ7VyTvxK5KDVi","@service":"npm","@historical":false,"@timestamp":"2022-02-01T15:10:00Z","@row":"3","organization.acronym":"nyc","agent.type":"freight","host.address":"192.0.2.43","host.ip":"192.0.2.43","host.model":"unknown","host.model_oid":"1.3.6.1.4.1.14988.1","host.vendor":"mikrotik","component.cpu.utilization":0,"component.name":"","host.hostname":"NYC-S75-APT4215"} diff --git a/common/Person.hs b/common/Person.hs index f175660..eabdc7a 100644 --- a/common/Person.hs +++ b/common/Person.hs @@ -1,20 +1,21 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} + module Person ( encodedPerson , encodedFlattenedPerson ) where import Data.ByteString (ByteString) -import Data.ByteString.Short (ShortByteString,toShort) +import Data.ByteString.Short (ShortByteString, toShort) import Data.Primitive (ByteArray) import Data.Text.Encoding (encodeUtf8) import NeatInterpolation (text) -import qualified Data.Primitive as PM import qualified Data.ByteString.Short.Internal as BSS +import qualified Data.Primitive as PM -shortByteStringToByteArray :: ShortByteString -> ByteArray +shortByteStringToByteArray :: ShortByteString -> ByteArray shortByteStringToByteArray (BSS.SBS x) = PM.ByteArray x encodedPerson :: ByteArray @@ -26,8 +27,9 @@ encodedFlattenedPerson = shortByteStringToByteArray (toShort byteStringFlattenedPerson) byteStringPerson :: ByteString -byteStringPerson = encodeUtf8 - [text| +byteStringPerson = + encodeUtf8 + [text| { "name": "bilbo" , "occupation": { "name": "burglar" @@ -39,8 +41,9 @@ byteStringPerson = encodeUtf8 |] byteStringFlattenedPerson :: ByteString -byteStringFlattenedPerson = encodeUtf8 - [text| +byteStringFlattenedPerson = + encodeUtf8 + [text| { "name": "bilbo" , "occupation.name": "burglar" , "occupation.start": "2022-05-30" @@ -48,4 +51,3 @@ byteStringFlattenedPerson = encodeUtf8 , "favorites": ["adventures","lunch"] } |] - diff --git a/common/Twitter100.hs b/common/Twitter100.hs index 825defb..3e6cb4e 100644 --- a/common/Twitter100.hs +++ b/common/Twitter100.hs @@ -1,20 +1,21 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} + module Twitter100 ( encodedTwitter100 , byteStringTwitter100 ) where import Data.ByteString (ByteString) -import Data.ByteString.Short (ShortByteString,toShort) +import Data.ByteString.Short (ShortByteString, toShort) import Data.Primitive (ByteArray) import Data.Text.Encoding (encodeUtf8) import NeatInterpolation (text) -import qualified Data.Primitive as PM import qualified Data.ByteString.Short.Internal as BSS +import qualified Data.Primitive as PM -shortByteStringToByteArray :: ShortByteString -> ByteArray +shortByteStringToByteArray :: ShortByteString -> ByteArray shortByteStringToByteArray (BSS.SBS x) = PM.ByteArray x encodedTwitter100 :: ByteArray @@ -22,8 +23,9 @@ encodedTwitter100 = shortByteStringToByteArray (toShort byteStringTwitter100) byteStringTwitter100 :: ByteString -byteStringTwitter100 = encodeUtf8 - [text| +byteStringTwitter100 = + encodeUtf8 + [text| { "completed_in": 1.000000, "max_id": 30121530767708160, @@ -1864,4 +1866,3 @@ byteStringTwitter100 = encodeUtf8 "since_id_str": "0" } |] - diff --git a/common/Url100.hs b/common/Url100.hs index ca20ac4..798c5b1 100644 --- a/common/Url100.hs +++ b/common/Url100.hs @@ -1,20 +1,21 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} + module Url100 ( encodedUrl100 , byteStringUrl100 ) where import Data.ByteString (ByteString) -import Data.ByteString.Short (ShortByteString,toShort) +import Data.ByteString.Short (ShortByteString, toShort) import Data.Primitive (ByteArray) import Data.Text.Encoding (encodeUtf8) import NeatInterpolation (text) -import qualified Data.Primitive as PM import qualified Data.ByteString.Short.Internal as BSS +import qualified Data.Primitive as PM -shortByteStringToByteArray :: ShortByteString -> ByteArray +shortByteStringToByteArray :: ShortByteString -> ByteArray shortByteStringToByteArray (BSS.SBS x) = PM.ByteArray x encodedUrl100 :: ByteArray @@ -22,8 +23,9 @@ encodedUrl100 = shortByteStringToByteArray (toShort byteStringUrl100) byteStringUrl100 :: ByteString -byteStringUrl100 = encodeUtf8 - [text| +byteStringUrl100 = + encodeUtf8 + [text| [ "https://balance.example.com/aunt.htm" , "http://anger.example.com/" , "https://www.example.edu/basket/branch#boat" @@ -126,5 +128,3 @@ byteStringUrl100 = encodeUtf8 , "http://attack.example.org/" ] |] - - diff --git a/fourmolu.yaml b/fourmolu.yaml new file mode 100644 index 0000000..40cd005 --- /dev/null +++ b/fourmolu.yaml @@ -0,0 +1,51 @@ +# Number of spaces per indentation step +indentation: 2 + +# Max line length for automatic line breaking +column-limit: 200 + +# Styling of arrows in type signatures (choices: trailing, leading, or leading-args) +function-arrows: trailing + +# How to place commas in multi-line lists, records, etc. (choices: leading or trailing) +comma-style: leading + +# Styling of import/export lists (choices: leading, trailing, or diff-friendly) +import-export-style: leading + +# Whether to full-indent or half-indent 'where' bindings past the preceding body +indent-wheres: false + +# Whether to leave a space before an opening record brace +record-brace-space: true + +# Number of spaces between top-level declarations +newlines-between-decls: 1 + +# How to print Haddock comments (choices: single-line, multi-line, or multi-line-compact) +haddock-style: multi-line + +# How to print module docstring +haddock-style-module: null + +# Styling of let blocks (choices: auto, inline, newline, or mixed) +let-style: auto + +# How to align the 'in' keyword with respect to the 'let' keyword (choices: left-align, right-align, or no-space) +in-style: right-align + +# Whether to put parentheses around a single constraint (choices: auto, always, or never) +single-constraint-parens: always + +# Output Unicode syntax (choices: detect, always, or never) +unicode: never + +# Give the programmer more choice on where to insert blank lines +respectful: true + +# Fixity information for operators +fixities: [] + +# Module reexports Fourmolu should know about +reexports: [] + diff --git a/json-syntax.cabal b/json-syntax.cabal index e597ccb..2cbdc52 100644 --- a/json-syntax.cabal +++ b/json-syntax.cabal @@ -1,7 +1,7 @@ -cabal-version: 2.2 -name: json-syntax -version: 0.2.7.0 -synopsis: High-performance JSON parser and encoder +cabal-version: 2.2 +name: json-syntax +version: 0.2.7.1 +synopsis: High-performance JSON parser and encoder description: This library parses JSON into a @Value@ type that is consistent with the ABNF described in [RFC 7159](https://tools.ietf.org/html/rfc7159). The @@ -14,77 +14,81 @@ description: and typeclasses are outside the scope of this library. If anyone writes a library that offers users these conveniences open a issue so that the @json-syntax@ documentation can point users to it. -homepage: https://github.com/byteverse/json-syntax -bug-reports: https://github.com/byteverse/json-syntax/issues -license: BSD-3-Clause -license-file: LICENSE -author: Andrew Martin -maintainer: andrew.thaddeus@gmail.com -copyright: 2019 Andrew Martin -category: Data -build-type: Simple -extra-source-files: CHANGELOG.md + +homepage: https://github.com/byteverse/json-syntax +bug-reports: https://github.com/byteverse/json-syntax/issues +license: BSD-3-Clause +license-file: LICENSE +author: Andrew Martin +maintainer: amartin@layer3com.com +copyright: 2019 Andrew Martin +category: Data +build-type: Simple +extra-doc-files: CHANGELOG.md library exposed-modules: Json Json.Flatten Json.Smile + build-depends: - , array-builder >=0.1 && <0.2 - , array-chunks >=0.1.3 && <0.2 - , base >=4.15 && <5 - , bytebuild >=0.3.10 && <0.4 - , byteslice >=0.2.9 && <0.3 - , bytesmith >=0.3.8 && <0.4 - , bytestring >=0.10.8 && <0.12 - , contiguous >=0.6 && <0.7 - , natural-arithmetic >=0.1.2 && <0.3 - , primitive >=0.7 && <0.10 - , run-st >=0.1.1 && <0.2 - , scientific-notation >=0.1.6 && <0.2 - , text >=2.0 - , text-short >=0.1.3 && <0.2 - , transformers >=0.5.6.2 - , word-compat >=0.0.3 - , zigzag >=0.0.1 - hs-source-dirs: src + , array-builder >=0.1 && <0.2 + , array-chunks >=0.1.3 && <0.2 + , base >=4.15 && <5 + , bytebuild >=0.3.10 && <0.4 + , byteslice >=0.2.9 && <0.3 + , bytesmith >=0.3.8 && <0.4 + , bytestring >=0.10.8 && <0.12 + , contiguous >=0.6 && <0.7 + , natural-arithmetic >=0.1.2 && <0.3 + , primitive >=0.7 && <0.10 + , run-st >=0.1.1 && <0.2 + , scientific-notation >=0.1.6 && <0.2 + , text >=2.0.2 && <2.1 + , text-short >=0.1.3 && <0.2 + , transformers >=0.5.6 && <0.6 + , word-compat >=0.0.6 && <0.1 + , zigzag >=0.0.1 && <0.1 + + hs-source-dirs: src default-language: Haskell2010 - ghc-options: -Wall -O2 + ghc-options: -Wall -O2 test-suite test default-language: Haskell2010 - type: exitcode-stdio-1.0 - hs-source-dirs: test, common - main-is: Main.hs + type: exitcode-stdio-1.0 + hs-source-dirs: test common + main-is: Main.hs other-modules: - Twitter100 Person - ghc-options: -Wall -O2 + Twitter100 + + ghc-options: -Wall -O2 build-depends: - , QuickCheck >=2.14.2 - , aeson >=2.0.2 + , aeson >=2.0.2 , array-chunks - , base >=4.12.0.0 && <5 + , base >=4.12.0.0 && <5 , bytebuild - , byteslice >=0.1.3 + , byteslice >=0.1.3 , bytestring , containers , json-syntax - , neat-interpolation >=0.3.2 + , neat-interpolation >=0.3.2 , primitive + , QuickCheck >=2.14.2 , scientific - , scientific-notation >=0.1.1 - , tasty >=1.2.3 && <1.3 - , tasty-hunit >=0.10.0.2 && <0.11 - , tasty-golden >=2.0 && <2.4 - , tasty-quickcheck >=0.10.1.2 && <0.11 - , text >=1.2 + , scientific-notation >=0.1.1 + , tasty >=1.2.3 && <1.3 + , tasty-golden >=2.0 && <2.4 + , tasty-hunit >=0.10.0.2 && <0.11 + , tasty-quickcheck >=0.10.1.2 && <0.11 + , text >=2.0.2 && <2.1 , text-short , vector benchmark bench - type: exitcode-stdio-1.0 + type: exitcode-stdio-1.0 build-depends: , aeson , base @@ -93,15 +97,20 @@ benchmark bench , bytestring , gauge , json-syntax - , neat-interpolation >=0.3.2 + , neat-interpolation >=0.3.2 , primitive - , text >=1.2 - , scientific-notation >=0.1.1 - ghc-options: -Wall -O2 + , scientific-notation >=0.1.1 + , text >=2.0.2 && <2.1 + + ghc-options: -Wall -O2 default-language: Haskell2010 - hs-source-dirs: bench, common - main-is: Main.hs + hs-source-dirs: bench common + main-is: Main.hs other-modules: + Metrics1024 Twitter100 Url100 - Metrics1024 + +source-repository head + type: git + location: git://github.com/byteverse/json-syntax.git diff --git a/scripts/buildjson.hs b/scripts/buildjson.hs index 8d1fafa..3c07cc7 100644 --- a/scripts/buildjson.hs +++ b/scripts/buildjson.hs @@ -5,5 +5,5 @@ module Buildjson (tripleMember) where import qualified Json tripleMember :: Json.Member -> Json.Value -{-# noinline tripleMember #-} -tripleMember x = Json.objectFromList [x,x,x] +{-# NOINLINE tripleMember #-} +tripleMember x = Json.objectFromList [x, x, x] diff --git a/scripts/print-syntax b/scripts/print-syntax deleted file mode 100755 index 120a075..0000000 Binary files a/scripts/print-syntax and /dev/null differ diff --git a/scripts/print-syntax.hs b/scripts/print-syntax.hs index 897536d..388ec31 100644 --- a/scripts/print-syntax.hs +++ b/scripts/print-syntax.hs @@ -1,15 +1,15 @@ -{-# language BangPatterns #-} -{-# language LambdaCase #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE LambdaCase #-} -import Data.Primitive (ByteArray) -import Data.ByteString (ByteString) -import Data.Bool (bool) import Control.Exception +import Data.Bool (bool) +import Data.ByteString (ByteString) +import Data.Primitive (ByteArray) import Foreign.C.Types (CChar) -import qualified Json import qualified Data.Bytes as Bytes import qualified Data.Bytes.Chunks as Chunks +import qualified Json import qualified System.IO as IO main :: IO () diff --git a/src/Json.hs b/src/Json.hs index 0c83153..03612ce 100644 --- a/src/Json.hs +++ b/src/Json.hs @@ -1,23 +1,24 @@ -{-# language BangPatterns #-} -{-# language BinaryLiterals #-} -{-# language BlockArguments #-} -{-# language DerivingStrategies #-} -{-# language DeriveAnyClass #-} -{-# language LambdaCase #-} -{-# language MagicHash #-} -{-# language NamedFieldPuns #-} -{-# language PatternSynonyms #-} -{-# language TypeApplications #-} -{-# language UnboxedSums #-} -{-# language UnboxedTuples #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE BinaryLiterals #-} +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UnboxedTuples #-} module Json ( -- * Types - Value(..) - , Member(..) - , SyntaxException(..) + Value (..) + , Member (..) + , SyntaxException (..) + -- * Classes - , ToValue(..) + , ToValue (..) + -- * Functions , decode , decodeNewlineDelimited @@ -27,11 +28,14 @@ module Json , toText , toBytes , toByteArray - -- * Infix Synonyms + + -- * Infix Synonyms , pattern (:->) + -- * Constants , emptyArray , emptyObject + -- * Value Construction , int , int8 @@ -45,8 +49,10 @@ module Json , bool , text , shortText + -- * Array Construction , arrayFromList + -- * Object Construction , objectFromList , object1 @@ -67,62 +73,63 @@ module Json , object16 ) where -import Prelude hiding (Bool(True,False)) +import Prelude hiding (Bool (False, True)) import Control.Exception (Exception) -import Control.Monad.ST (ST,runST) +import Control.Monad.ST (ST, runST) import Control.Monad.ST.Run (runSmallArrayST) -import Data.Bits ((.&.),(.|.),unsafeShiftR) +import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.Except (except, runExceptT) +import Data.Bits (unsafeShiftR, (.&.), (.|.)) import Data.Builder.ST (Builder) +import Data.Bytes.Chunks (Chunks) import Data.Bytes.Parser (Parser) -import Data.Bytes.Types (Bytes(..)) +import Data.Bytes.Types (Bytes (..)) import Data.Char (ord) +import Data.Foldable (foldlM) import Data.Number.Scientific (Scientific) -import Data.Primitive (ByteArray(ByteArray),MutableByteArray,SmallArray,Array,PrimArray,Prim) -import Data.Text.Short (ShortText) -import GHC.Exts (Char(C#),Int(I#),gtWord#,ltWord#,word2Int#,chr#) -import GHC.Word (Word8,Word16,Word32,Word64) -import GHC.Int (Int8,Int16,Int32,Int64) +import Data.Primitive (Array, ByteArray (ByteArray), MutableByteArray, Prim, PrimArray, SmallArray) import Data.Text (Text) -import Data.Foldable (foldlM) -import Control.Monad.Trans.Except (runExceptT,except) -import Control.Monad.Trans.Class (lift) -import Data.Bytes.Chunks (Chunks) +import Data.Text.Short (ShortText) +import GHC.Exts (Char (C#), Int (I#), chr#, gtWord#, ltWord#, word2Int#) +import GHC.Int (Int16, Int32, Int64, Int8) +import GHC.Word (Word16, Word32, Word64, Word8) -import qualified Prelude import qualified Data.Builder.ST as B +import qualified Data.ByteString.Short.Internal as BSS import qualified Data.Bytes as Bytes -import qualified Data.Bytes.Chunks as ByteChunks import qualified Data.Bytes.Builder as BLDR +import qualified Data.Bytes.Chunks as ByteChunks import qualified Data.Bytes.Parser as P +import qualified Data.Bytes.Parser.Latin as Latin +import qualified Data.Bytes.Parser.Unsafe as Unsafe +import qualified Data.Bytes.Parser.Utf8 as Utf8 import qualified Data.Chunks as Chunks -import qualified Data.Text.Short.Unsafe as TS +import qualified Data.List as List import qualified Data.Number.Scientific as SCI import qualified Data.Primitive as PM import qualified Data.Primitive.Contiguous as Contiguous -import qualified Data.Bytes.Parser.Utf8 as Utf8 -import qualified Data.Bytes.Parser.Latin as Latin -import qualified Data.ByteString.Short.Internal as BSS -import qualified Data.Bytes.Parser.Unsafe as Unsafe import qualified Data.Text.Short as TS -import qualified Data.List as List +import qualified Data.Text.Short.Unsafe as TS import qualified GHC.Word.Compat +import qualified Prelude --- | The JSON syntax tree described by the ABNF in RFC 7159. Notable --- design decisions include: --- --- * @True@ and @False@ are their own data constructors rather than --- being lumped together under a data constructor for boolean values. --- This improves performance when decoding the syntax tree to a @Bool@. --- * @Object@ uses an association list rather than a hash map. This is --- the data type that key-value pairs can be parsed into most cheaply. --- * @Object@ and @Array@ both use 'Chunks' rather than using @SmallArray@ --- or cons-list directly. This a middle ground between those two types. We --- get the efficent use of cache lines that @SmallArray@ offers, and we get --- the worst-case @O(1)@ appends that cons-list offers. Users will typically --- fold over the elements with the @Foldable@ instance of 'Chunks', although --- there are functions in @Data.Chunks@ that efficently perform other --- operations. +{- | The JSON syntax tree described by the ABNF in RFC 7159. Notable +design decisions include: + +* @True@ and @False@ are their own data constructors rather than + being lumped together under a data constructor for boolean values. + This improves performance when decoding the syntax tree to a @Bool@. +* @Object@ uses an association list rather than a hash map. This is + the data type that key-value pairs can be parsed into most cheaply. +* @Object@ and @Array@ both use 'Chunks' rather than using @SmallArray@ + or cons-list directly. This a middle ground between those two types. We + get the efficent use of cache lines that @SmallArray@ offers, and we get + the worst-case @O(1)@ appends that cons-list offers. Users will typically + fold over the elements with the @Foldable@ instance of 'Chunks', although + there are functions in @Data.Chunks@ that efficently perform other + operations. +-} data Value = Object !(SmallArray Member) | Array !(SmallArray Value) @@ -131,11 +138,12 @@ data Value | Null | True | False - deriving stock (Eq,Show) + deriving stock (Eq, Show) --- | Exceptions that can happen while parsing JSON. Do not pattern --- match on values of this type. New data constructors may be added --- at any time without a major version bump. +{- | Exceptions that can happen while parsing JSON. Do not pattern +match on values of this type. New data constructors may be added +at any time without a major version bump. +-} data SyntaxException = EmptyInput | ExpectedColon @@ -155,41 +163,43 @@ data SyntaxException | LeadingZero | UnexpectedLeftovers | PossibleOverflow - deriving stock (Eq,Show) + deriving stock (Eq, Show) deriving anyclass (Exception) --- | A key-value pair in a JSON object. The name of this type is --- taken from section 4 of RFC 7159. +{- | A key-value pair in a JSON object. The name of this type is +taken from section 4 of RFC 7159. +-} data Member = Member { key :: {-# UNPACK #-} !ShortText , value :: !Value - } deriving stock (Eq,Show) + } + deriving stock (Eq, Show) -- | An array with no elements (i.e. @[]@) emptyArray :: Value -{-# noinline emptyArray #-} +{-# NOINLINE emptyArray #-} emptyArray = Array mempty -- | An object with no members (i.e. @{}@) emptyObject :: Value -{-# noinline emptyObject #-} +{-# NOINLINE emptyObject #-} emptyObject = Object mempty isSpace :: Word8 -> Prelude.Bool -{-# inline isSpace #-} +{-# INLINE isSpace #-} isSpace w = - w == c2w ' ' - || w == c2w '\t' - || w == c2w '\r' - || w == c2w '\n' + w == c2w ' ' + || w == c2w '\t' + || w == c2w '\r' + || w == c2w '\n' -- | Decode a JSON syntax tree from a byte sequence. decode :: Bytes -> Either SyntaxException Value -{-# noinline decode #-} +{-# NOINLINE decode #-} decode = P.parseBytesEither parser parser :: Parser SyntaxException s Value -{-# inline parser #-} +{-# INLINE parser #-} parser = do P.skipWhile isSpace result <- Latin.any EmptyInput >>= parserStep @@ -197,69 +207,73 @@ parser = do P.endOfInput UnexpectedLeftovers pure result --- | Decode newline-delimited JSON. Both the LF and the CRLF conventions --- are supported. The newline character (or character sequence) following --- the final object may be omitted. This also allows blanks lines consisting --- of only whitespace. --- --- It's not strictly necessary for this to be a part of this library, but --- newline-delimited JSON is somewhat common in practice. It's nice to have --- this here instead of having to reimplement it in a bunch of different --- applications. --- --- Note: To protect against malicious input, this reject byte sequences with --- more than 10 million newlines. If this is causing a problem for you, open --- an issue. --- --- Other note: in the future, this function might be changed transparently --- to parallelize the decoding of large input (at least 1000 lines) with --- GHC sparks. +{- | Decode newline-delimited JSON. Both the LF and the CRLF conventions +are supported. The newline character (or character sequence) following +the final object may be omitted. This also allows blanks lines consisting +of only whitespace. + +It's not strictly necessary for this to be a part of this library, but +newline-delimited JSON is somewhat common in practice. It's nice to have +this here instead of having to reimplement it in a bunch of different +applications. + +Note: To protect against malicious input, this reject byte sequences with +more than 10 million newlines. If this is causing a problem for you, open +an issue. + +Other note: in the future, this function might be changed transparently +to parallelize the decoding of large input (at least 1000 lines) with +GHC sparks. +-} decodeNewlineDelimited :: Bytes -> Either SyntaxException (SmallArray Value) -{-# noinline decodeNewlineDelimited #-} +{-# NOINLINE decodeNewlineDelimited #-} decodeNewlineDelimited !everything = let maxVals = Bytes.count 0x0A everything + 1 in if maxVals > 10000000 then Left PossibleOverflow else runST $ runExceptT $ do !dst <- PM.newSmallArray maxVals Null - !total <- foldlM - (\ !ix b -> - let clean = Bytes.dropWhile isSpace (Bytes.dropWhileEnd isSpace b) - in if Bytes.null clean - then pure ix - else do - v <- except (decode clean) - lift (PM.writeSmallArray dst ix v) - pure (ix + 1) - ) 0 (Bytes.split 0x0A everything) + !total <- + foldlM + ( \ !ix b -> + let clean = Bytes.dropWhile isSpace (Bytes.dropWhileEnd isSpace b) + in if Bytes.null clean + then pure ix + else do + v <- except (decode clean) + lift (PM.writeSmallArray dst ix v) + pure (ix + 1) + ) + 0 + (Bytes.split 0x0A everything) lift $ PM.shrinkSmallMutableArray dst total dst' <- lift $ PM.unsafeFreezeSmallArray dst pure dst' toChunks :: Value -> Chunks -{-# inline toChunks #-} +{-# INLINE toChunks #-} toChunks = BLDR.run 512 . encode toBytes :: Value -> Bytes -{-# inline toBytes #-} +{-# INLINE toBytes #-} toBytes = ByteChunks.concat . toChunks toByteArray :: Value -> ByteArray -{-# inline toByteArray #-} +{-# INLINE toByteArray #-} toByteArray = ByteChunks.concatU . toChunks toShortText :: Value -> ShortText -{-# inline toShortText #-} +{-# INLINE toShortText #-} toShortText v = case toByteArray v of ByteArray x -> TS.fromShortByteStringUnsafe (BSS.SBS x) toText :: Value -> Text -{-# inline toText #-} +{-# INLINE toText #-} toText = TS.toText . toShortText -- | Encode a JSON syntax tree. encode :: Value -> BLDR.Builder -{-# noinline encode #-} +{-# NOINLINE encode #-} encode v0 = BLDR.rebuild $ case v0 of True -> BLDR.ascii4 't' 'r' 'u' 'e' False -> BLDR.ascii5 'f' 'a' 'l' 's' 'e' @@ -271,40 +285,39 @@ encode v0 = BLDR.rebuild $ case v0 of _ -> let !(# z #) = PM.indexSmallArray## ys 0 in BLDR.ascii '[' - <> - encode z - <> - foldrTail - ( \v b -> BLDR.ascii ',' <> encode v <> b - ) (BLDR.ascii ']') ys + <> encode z + <> foldrTail + ( \v b -> BLDR.ascii ',' <> encode v <> b + ) + (BLDR.ascii ']') + ys Object ys -> case PM.sizeofSmallArray ys of 0 -> BLDR.ascii2 '{' '}' _ -> let !(# z #) = PM.indexSmallArray## ys 0 in BLDR.ascii '{' - <> - encodeMember z - <> - foldrTail - ( \v b -> BLDR.ascii ',' <> encodeMember v <> b - ) (BLDR.ascii '}') ys + <> encodeMember z + <> foldrTail + ( \v b -> BLDR.ascii ',' <> encodeMember v <> b + ) + (BLDR.ascii '}') + ys encodeMember :: Member -> BLDR.Builder -encodeMember Member{key,value} = +encodeMember Member {key, value} = BLDR.shortTextJsonString key - <> - BLDR.ascii ':' - <> - encode value + <> BLDR.ascii ':' + <> encode value foldrTail :: (a -> b -> b) -> b -> PM.SmallArray a -> b -{-# inline foldrTail #-} -foldrTail f z !ary = go 1 where +{-# INLINE foldrTail #-} +foldrTail f z !ary = go 1 + where !sz = PM.sizeofSmallArray ary go i | i == sz = z - | (# x #) <- PM.indexSmallArray## ary i - = f x (go (i+1)) + | (# x #) <- PM.indexSmallArray## ary i = + f x (go (i + 1)) -- Precondition: skip over all space before calling this. -- It will not skip leading space for you. It does not skip @@ -326,15 +339,17 @@ parserStep = \case start <- Unsafe.cursor string String start '-' -> fmap Number (SCI.parserNegatedUtf8Bytes InvalidNumber) - '0' -> Latin.trySatisfy (\c -> c >= '0' && c <= '9') >>= \case - Prelude.True -> P.fail LeadingZero - Prelude.False -> fmap Number (SCI.parserTrailingUtf8Bytes InvalidNumber 0) - c | c >= '1' && c <= '9' -> + '0' -> + Latin.trySatisfy (\c -> c >= '0' && c <= '9') >>= \case + Prelude.True -> P.fail LeadingZero + Prelude.False -> fmap Number (SCI.parserTrailingUtf8Bytes InvalidNumber 0) + c + | c >= '1' && c <= '9' -> fmap Number (SCI.parserTrailingUtf8Bytes InvalidNumber (ord c - 48)) _ -> P.fail InvalidLeader objectTrailedByBrace :: Parser SyntaxException s Value -{-# inline objectTrailedByBrace #-} +{-# INLINE objectTrailedByBrace #-} objectTrailedByBrace = do P.skipWhile isSpace Latin.any IncompleteObject >>= \case @@ -382,7 +397,7 @@ objectStep !b = do -- -- This parser handles everything after the LBRACKET character. arrayTrailedByBracket :: Parser SyntaxException s Value -{-# inline arrayTrailedByBracket #-} +{-# INLINE arrayTrailedByBracket #-} arrayTrailedByBracket = do P.skipWhile isSpace Latin.any IncompleteArray >>= \case @@ -395,7 +410,7 @@ arrayTrailedByBracket = do -- From RFC 7159: -- --- > value-separator = ws COMMA ws +-- > value-separator = ws COMMA ws -- > array = begin-array [ value *( value-separator value ) ] end-array -- -- This handles the all values after the first one. That is: @@ -427,12 +442,14 @@ c2w = fromIntegral . ord -- mark the function as NOINLINE. This would prevent the generated -- code from being needlessly duplicated in three different places. string :: (ShortText -> a) -> Int -> Parser SyntaxException s a -{-# inline string #-} -string wrap !start = go 1 where +{-# INLINE string #-} +string wrap !start = go 1 + where go !canMemcpy = do P.any IncompleteString >>= \case 92 -> P.any InvalidEscapeSequence *> go 0 -- backslash - 34 -> do -- double quote + 34 -> do + -- double quote !pos <- Unsafe.cursor case canMemcpy of 1 -> do @@ -452,46 +469,49 @@ string wrap !start = go 1 where GHC.Word.Compat.W8# w -> go (canMemcpy .&. I# (ltWord# w 128##) .&. I# (gtWord# w 31##)) copyAndEscape :: (ShortText -> a) -> Int -> Parser SyntaxException s a -{-# inline copyAndEscape #-} +{-# INLINE copyAndEscape #-} copyAndEscape wrap !maxLen = do !dst <- P.effect (PM.newByteArray maxLen) - let go !ix = Utf8.any# IncompleteString `P.bindFromCharToLifted` \c -> case c of - '\\'# -> Latin.any IncompleteEscapeSequence >>= \case - '"' -> do - P.effect (PM.writeByteArray dst ix (c2w '"')) - go (ix + 1) - '\\' -> do - P.effect (PM.writeByteArray dst ix (c2w '\\')) - go (ix + 1) - 't' -> do - P.effect (PM.writeByteArray dst ix (c2w '\t')) - go (ix + 1) - 'n' -> do - P.effect (PM.writeByteArray dst ix (c2w '\n')) - go (ix + 1) - 'r' -> do - P.effect (PM.writeByteArray dst ix (c2w '\r')) - go (ix + 1) - '/' -> do - P.effect (PM.writeByteArray dst ix (c2w '/')) - go (ix + 1) - 'b' -> do - P.effect (PM.writeByteArray dst ix (c2w '\b')) - go (ix + 1) - 'f' -> do - P.effect (PM.writeByteArray dst ix (c2w '\f')) - go (ix + 1) - 'u' -> do - w <- Latin.hexFixedWord16 InvalidEscapeSequence - if w >= 0xD800 && w < 0xDFFF - then go =<< P.effect (encodeUtf8Char dst ix '\xFFFD') - else go =<< P.effect (encodeUtf8Char dst ix (w16ToChar w)) - _ -> P.fail InvalidEscapeSequence - '"'# -> do - str <- P.effect - (PM.unsafeFreezeByteArray =<< PM.resizeMutableByteArray dst ix) - pure (wrap (TS.fromShortByteStringUnsafe (byteArrayToShortByteString str))) - _ -> go =<< P.effect (encodeUtf8Char dst ix (C# c)) + let go !ix = + Utf8.any# IncompleteString `P.bindFromCharToLifted` \c -> case c of + '\\'# -> + Latin.any IncompleteEscapeSequence >>= \case + '"' -> do + P.effect (PM.writeByteArray dst ix (c2w '"')) + go (ix + 1) + '\\' -> do + P.effect (PM.writeByteArray dst ix (c2w '\\')) + go (ix + 1) + 't' -> do + P.effect (PM.writeByteArray dst ix (c2w '\t')) + go (ix + 1) + 'n' -> do + P.effect (PM.writeByteArray dst ix (c2w '\n')) + go (ix + 1) + 'r' -> do + P.effect (PM.writeByteArray dst ix (c2w '\r')) + go (ix + 1) + '/' -> do + P.effect (PM.writeByteArray dst ix (c2w '/')) + go (ix + 1) + 'b' -> do + P.effect (PM.writeByteArray dst ix (c2w '\b')) + go (ix + 1) + 'f' -> do + P.effect (PM.writeByteArray dst ix (c2w '\f')) + go (ix + 1) + 'u' -> do + w <- Latin.hexFixedWord16 InvalidEscapeSequence + if w >= 0xD800 && w < 0xDFFF + then go =<< P.effect (encodeUtf8Char dst ix '\xFFFD') + else go =<< P.effect (encodeUtf8Char dst ix (w16ToChar w)) + _ -> P.fail InvalidEscapeSequence + '"'# -> do + str <- + P.effect + (PM.unsafeFreezeByteArray =<< PM.resizeMutableByteArray dst ix) + pure (wrap (TS.fromShortByteStringUnsafe (byteArrayToShortByteString str))) + _ -> go =<< P.effect (encodeUtf8Char dst ix (C# c)) go 0 encodeUtf8Char :: MutableByteArray s -> Int -> Char -> ST s Int @@ -500,27 +520,45 @@ encodeUtf8Char !marr !ix !c PM.writeByteArray marr ix (c2w c) pure (ix + 1) | c < '\x0800' = do - PM.writeByteArray marr ix + PM.writeByteArray + marr + ix (fromIntegral @Int @Word8 (unsafeShiftR (ord c) 6 .|. 0b11000000)) - PM.writeByteArray marr (ix + 1) + PM.writeByteArray + marr + (ix + 1) (0b10000000 .|. (0b00111111 .&. (fromIntegral @Int @Word8 (ord c)))) pure (ix + 2) | c <= '\xffff' = do - PM.writeByteArray marr ix + PM.writeByteArray + marr + ix (fromIntegral @Int @Word8 (unsafeShiftR (ord c) 12 .|. 0b11100000)) - PM.writeByteArray marr (ix + 1) + PM.writeByteArray + marr + (ix + 1) (0b10000000 .|. (0b00111111 .&. (fromIntegral @Int @Word8 (unsafeShiftR (ord c) 6)))) - PM.writeByteArray marr (ix + 2) + PM.writeByteArray + marr + (ix + 2) (0b10000000 .|. (0b00111111 .&. (fromIntegral @Int @Word8 (ord c)))) pure (ix + 3) | otherwise = do - PM.writeByteArray marr ix + PM.writeByteArray + marr + ix (fromIntegral @Int @Word8 (unsafeShiftR (ord c) 18 .|. 0b11110000)) - PM.writeByteArray marr (ix + 1) + PM.writeByteArray + marr + (ix + 1) (0b10000000 .|. (0b00111111 .&. (fromIntegral @Int @Word8 (unsafeShiftR (ord c) 12)))) - PM.writeByteArray marr (ix + 2) + PM.writeByteArray + marr + (ix + 2) (0b10000000 .|. (0b00111111 .&. (fromIntegral @Int @Word8 (unsafeShiftR (ord c) 6)))) - PM.writeByteArray marr (ix + 3) + PM.writeByteArray + marr + (ix + 3) (0b10000000 .|. (0b00111111 .&. (fromIntegral @Int @Word8 (ord c)))) pure (ix + 4) @@ -533,100 +571,118 @@ w16ToChar (GHC.Word.Compat.W16# w) = C# (chr# (word2Int# w)) -- | Infix pattern synonym for 'Member'. pattern (:->) :: ShortText -> Value -> Member -pattern key :-> value = Member{key,value} +pattern key :-> value = Member {key, value} --- | Construct a JSON array from a list of JSON values. --- --- Unlike 'objectFromList', this is not currently equipped with a --- rewrite rule. +{- | Construct a JSON array from a list of JSON values. + +Unlike 'objectFromList', this is not currently equipped with a +rewrite rule. +-} arrayFromList :: [Value] -> Value arrayFromList ms = Array $ PM.smallArrayFromList ms --- | Construct a JSON object from a list of members. --- --- Note: When the argument is a list literal with 16 or fewer elements, --- a rewrite rule transforms this into the appropriate @objectN@ function. --- When the argument is not a list literal, this function just calls --- @smallArrayFromList@ on the members, which has poor performance. +{- | Construct a JSON object from a list of members. + +Note: When the argument is a list literal with 16 or fewer elements, +a rewrite rule transforms this into the appropriate @objectN@ function. +When the argument is not a list literal, this function just calls +@smallArrayFromList@ on the members, which has poor performance. +-} objectFromList :: [Member] -> Value objectFromList ms = Object $ PM.smallArrayFromList ms - {-# NOINLINE objectFromList #-} -{-# RULES "objectFromList/1" forall a. - objectFromList (a : []) = - object1 a -#-} -{-# RULES "objectFromList/2" forall a b. - objectFromList (a : b : []) = - object2 a b -#-} -{-# RULES "objectFromList/3" forall a b c. - objectFromList (a : b : c : []) = - object3 a b c -#-} -{-# RULES "objectFromList/4" forall a b c d. - objectFromList (a : b : c : d : []) = - object4 a b c d -#-} -{-# RULES "objectFromList/5" forall a b c d e. - objectFromList (a : b : c : d : e : []) = - object5 a b c d e -#-} -{-# RULES "objectFromList/6" forall a b c d e f. - objectFromList (a : b : c : d : e : f : []) = - object6 a b c d e f -#-} -{-# RULES "objectFromList/7" forall a b c d e f g. - objectFromList (a : b : c : d : e : f : g : []) = - object7 a b c d e f g -#-} -{-# RULES "objectFromList/8" forall a b c d e f g h. - objectFromList (a : b : c : d : e : f : g : h : []) = - object8 a b c d e f g h -#-} -{-# RULES "objectFromList/9" forall a b c d e f g h i. - objectFromList (a : b : c : d : e : f : g : h : i : []) = - object9 a b c d e f g h i -#-} -{-# RULES "objectFromList/10" forall a b c d e f g h i j. - objectFromList (a : b : c : d : e : f : g : h : i : j : []) = - object10 a b c d e f g h i j -#-} -{-# RULES "objectFromList/11" forall a b c d e f g h i j k. - objectFromList (a : b : c : d : e : f : g : h : i : j : k : []) = - object11 a b c d e f g h i j k -#-} -{-# RULES "objectFromList/12" forall a b c d e f g h i j k l. - objectFromList (a : b : c : d : e : f : g : h : i : j : k : l : []) = - object12 a b c d e f g h i j k l -#-} -{-# RULES "objectFromList/13" forall a b c d e f g h i j k l m. - objectFromList (a : b : c : d : e : f : g : h : i : j : k : l : m : []) = - object13 a b c d e f g h i j k l m -#-} -{-# RULES "objectFromList/14" forall a b c d e f g h i j k l m n. - objectFromList (a : b : c : d : e : f : g : h : i : j : k : l : m : n : []) = - object14 a b c d e f g h i j k l m n -#-} -{-# RULES "objectFromList/15" forall a b c d e f g h i j k l m n o. - objectFromList (a : b : c : d : e : f : g : h : i : j : k : l : m : n : o : []) = - object15 a b c d e f g h i j k l m n o -#-} -{-# RULES "objectFromList/16" forall a b c d e f g h i j k l m n o p. - objectFromList (a : b : c : d : e : f : g : h : i : j : k : l : m : n : o : p : []) = - object16 a b c d e f g h i j k l m n o p -#-} + +{-# RULES +"objectFromList/1" forall a. + objectFromList (a : []) = + object1 a + #-} +{-# RULES +"objectFromList/2" forall a b. + objectFromList (a : b : []) = + object2 a b + #-} +{-# RULES +"objectFromList/3" forall a b c. + objectFromList (a : b : c : []) = + object3 a b c + #-} +{-# RULES +"objectFromList/4" forall a b c d. + objectFromList (a : b : c : d : []) = + object4 a b c d + #-} +{-# RULES +"objectFromList/5" forall a b c d e. + objectFromList (a : b : c : d : e : []) = + object5 a b c d e + #-} +{-# RULES +"objectFromList/6" forall a b c d e f. + objectFromList (a : b : c : d : e : f : []) = + object6 a b c d e f + #-} +{-# RULES +"objectFromList/7" forall a b c d e f g. + objectFromList (a : b : c : d : e : f : g : []) = + object7 a b c d e f g + #-} +{-# RULES +"objectFromList/8" forall a b c d e f g h. + objectFromList (a : b : c : d : e : f : g : h : []) = + object8 a b c d e f g h + #-} +{-# RULES +"objectFromList/9" forall a b c d e f g h i. + objectFromList (a : b : c : d : e : f : g : h : i : []) = + object9 a b c d e f g h i + #-} +{-# RULES +"objectFromList/10" forall a b c d e f g h i j. + objectFromList (a : b : c : d : e : f : g : h : i : j : []) = + object10 a b c d e f g h i j + #-} +{-# RULES +"objectFromList/11" forall a b c d e f g h i j k. + objectFromList (a : b : c : d : e : f : g : h : i : j : k : []) = + object11 a b c d e f g h i j k + #-} +{-# RULES +"objectFromList/12" forall a b c d e f g h i j k l. + objectFromList (a : b : c : d : e : f : g : h : i : j : k : l : []) = + object12 a b c d e f g h i j k l + #-} +{-# RULES +"objectFromList/13" forall a b c d e f g h i j k l m. + objectFromList (a : b : c : d : e : f : g : h : i : j : k : l : m : []) = + object13 a b c d e f g h i j k l m + #-} +{-# RULES +"objectFromList/14" forall a b c d e f g h i j k l m n. + objectFromList (a : b : c : d : e : f : g : h : i : j : k : l : m : n : []) = + object14 a b c d e f g h i j k l m n + #-} +{-# RULES +"objectFromList/15" forall a b c d e f g h i j k l m n o. + objectFromList (a : b : c : d : e : f : g : h : i : j : k : l : m : n : o : []) = + object15 a b c d e f g h i j k l m n o + #-} +{-# RULES +"objectFromList/16" forall a b c d e f g h i j k l m n o p. + objectFromList (a : b : c : d : e : f : g : h : i : j : k : l : m : n : o : p : []) = + object16 a b c d e f g h i j k l m n o p + #-} -- | Construct a JSON object with one member. object1 :: Member -> Value -{-# inline object1 #-} +{-# INLINE object1 #-} object1 a = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 1 a PM.unsafeFreezeSmallArray dst -- | Construct a JSON object with two members. object2 :: Member -> Member -> Value -{-# inline object2 #-} +{-# INLINE object2 #-} object2 a b = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 2 a PM.writeSmallArray dst 1 b @@ -634,7 +690,7 @@ object2 a b = Object $ runSmallArrayST $ do -- | Construct a JSON object with three members. object3 :: Member -> Member -> Member -> Value -{-# inline object3 #-} +{-# INLINE object3 #-} object3 a b c = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 3 a PM.writeSmallArray dst 1 b @@ -643,7 +699,7 @@ object3 a b c = Object $ runSmallArrayST $ do -- | Construct a JSON object with four members. object4 :: Member -> Member -> Member -> Member -> Value -{-# inline object4 #-} +{-# INLINE object4 #-} object4 a b c d = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 4 a PM.writeSmallArray dst 1 b @@ -653,7 +709,7 @@ object4 a b c d = Object $ runSmallArrayST $ do -- | Construct a JSON object with five members. object5 :: Member -> Member -> Member -> Member -> Member -> Value -{-# inline object5 #-} +{-# INLINE object5 #-} object5 a b c d e = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 5 a PM.writeSmallArray dst 1 b @@ -664,7 +720,7 @@ object5 a b c d e = Object $ runSmallArrayST $ do -- | Construct a JSON object with six members. object6 :: Member -> Member -> Member -> Member -> Member -> Member -> Value -{-# inline object6 #-} +{-# INLINE object6 #-} object6 a b c d e f = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 6 a PM.writeSmallArray dst 1 b @@ -676,7 +732,7 @@ object6 a b c d e f = Object $ runSmallArrayST $ do -- | Construct a JSON object with seven members. object7 :: Member -> Member -> Member -> Member -> Member -> Member -> Member -> Value -{-# inline object7 #-} +{-# INLINE object7 #-} object7 a b c d e f g = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 7 a PM.writeSmallArray dst 1 b @@ -689,7 +745,7 @@ object7 a b c d e f g = Object $ runSmallArrayST $ do -- | Construct a JSON object with nine members. object8 :: Member -> Member -> Member -> Member -> Member -> Member -> Member -> Member -> Value -{-# inline object8 #-} +{-# INLINE object8 #-} object8 a b c d e f g h = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 8 a PM.writeSmallArray dst 1 b @@ -702,9 +758,18 @@ object8 a b c d e f g h = Object $ runSmallArrayST $ do PM.unsafeFreezeSmallArray dst -- | Construct a JSON object with nine members. -object9 :: Member -> Member -> Member -> Member -> Member -> Member -> Member -> Member -> Member - -> Value -{-# inline object9 #-} +object9 :: + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Value +{-# INLINE object9 #-} object9 a b c d e f g h i = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 9 a PM.writeSmallArray dst 1 b @@ -718,9 +783,19 @@ object9 a b c d e f g h i = Object $ runSmallArrayST $ do PM.unsafeFreezeSmallArray dst -- | Construct a JSON object with ten members. -object10 :: Member -> Member -> Member -> Member -> Member -> Member -> Member -> Member - -> Member -> Member -> Value -{-# inline object10 #-} +object10 :: + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Value +{-# INLINE object10 #-} object10 a b c d e f g h i j = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 10 a PM.writeSmallArray dst 1 b @@ -735,9 +810,20 @@ object10 a b c d e f g h i j = Object $ runSmallArrayST $ do PM.unsafeFreezeSmallArray dst -- | Construct a JSON object with eleven members. -object11 :: Member -> Member -> Member -> Member -> Member -> Member -> Member -> Member - -> Member -> Member -> Member -> Value -{-# inline object11 #-} +object11 :: + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Value +{-# INLINE object11 #-} object11 a b c d e f g h i j k = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 11 a PM.writeSmallArray dst 1 b @@ -753,9 +839,21 @@ object11 a b c d e f g h i j k = Object $ runSmallArrayST $ do PM.unsafeFreezeSmallArray dst -- | Construct a JSON object with twelve members. -object12 :: Member -> Member -> Member -> Member -> Member -> Member -> Member -> Member - -> Member -> Member -> Member -> Member -> Value -{-# inline object12 #-} +object12 :: + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Value +{-# INLINE object12 #-} object12 a b c d e f g h i j k l = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 12 a PM.writeSmallArray dst 1 b @@ -772,9 +870,22 @@ object12 a b c d e f g h i j k l = Object $ runSmallArrayST $ do PM.unsafeFreezeSmallArray dst -- | Construct a JSON object with thirteen members. -object13 :: Member -> Member -> Member -> Member -> Member -> Member -> Member -> Member - -> Member -> Member -> Member -> Member -> Member -> Value -{-# inline object13 #-} +object13 :: + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Value +{-# INLINE object13 #-} object13 a b c d e f g h i j k l m = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 13 a PM.writeSmallArray dst 1 b @@ -792,9 +903,23 @@ object13 a b c d e f g h i j k l m = Object $ runSmallArrayST $ do PM.unsafeFreezeSmallArray dst -- | Construct a JSON object with fourteen members. -object14 :: Member -> Member -> Member -> Member -> Member -> Member -> Member -> Member - -> Member -> Member -> Member -> Member -> Member -> Member -> Value -{-# inline object14 #-} +object14 :: + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Value +{-# INLINE object14 #-} object14 a b c d e f g h i j k l m n = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 14 a PM.writeSmallArray dst 1 b @@ -813,9 +938,24 @@ object14 a b c d e f g h i j k l m n = Object $ runSmallArrayST $ do PM.unsafeFreezeSmallArray dst -- | Construct a JSON object with fifteen members. -object15 :: Member -> Member -> Member -> Member -> Member -> Member -> Member -> Member - -> Member -> Member -> Member -> Member -> Member -> Member -> Member -> Value -{-# inline object15 #-} +object15 :: + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Value +{-# INLINE object15 #-} object15 a b c d e f g h i j k l m n o = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 15 a PM.writeSmallArray dst 1 b @@ -835,10 +975,25 @@ object15 a b c d e f g h i j k l m n o = Object $ runSmallArrayST $ do PM.unsafeFreezeSmallArray dst -- | Construct a JSON object with sixteen members. -object16 :: Member -> Member -> Member -> Member -> Member -> Member -> Member -> Member - -> Member -> Member -> Member -> Member -> Member -> Member -> Member -> Member - -> Value -{-# inline object16 #-} +object16 :: + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Member -> + Value +{-# INLINE object16 #-} object16 a b c d e f g h i j k l m n o p = Object $ runSmallArrayST $ do dst <- PM.newSmallArray 16 a PM.writeSmallArray dst 1 b @@ -859,51 +1014,51 @@ object16 a b c d e f g h i j k l m n o p = Object $ runSmallArrayST $ do PM.unsafeFreezeSmallArray dst word8 :: Word8 -> Json.Value -{-# inline word8 #-} +{-# INLINE word8 #-} word8 = Json.Number . SCI.fromWord8 word16 :: Word16 -> Json.Value -{-# inline word16 #-} +{-# INLINE word16 #-} word16 = Json.Number . SCI.fromWord16 word32 :: Word32 -> Json.Value -{-# inline word32 #-} +{-# INLINE word32 #-} word32 = Json.Number . SCI.fromWord32 word64 :: Word64 -> Json.Value -{-# inline word64 #-} +{-# INLINE word64 #-} word64 = Json.Number . SCI.fromWord64 int8 :: Int8 -> Json.Value -{-# inline int8 #-} +{-# INLINE int8 #-} int8 = Json.Number . SCI.fromInt8 int16 :: Int16 -> Json.Value -{-# inline int16 #-} +{-# INLINE int16 #-} int16 = Json.Number . SCI.fromInt16 int32 :: Int32 -> Json.Value -{-# inline int32 #-} +{-# INLINE int32 #-} int32 = Json.Number . SCI.fromInt32 int64 :: Int64 -> Json.Value -{-# inline int64 #-} +{-# INLINE int64 #-} int64 = Json.Number . SCI.fromInt64 int :: Int -> Json.Value -{-# inline int #-} +{-# INLINE int #-} int = Json.Number . SCI.fromInt text :: Text -> Json.Value -{-# inline text #-} +{-# INLINE text #-} text = Json.String . TS.fromText shortText :: ShortText -> Json.Value -{-# inline shortText #-} +{-# INLINE shortText #-} shortText = String bool :: Prelude.Bool -> Json.Value -{-# inline bool #-} +{-# INLINE bool #-} bool Prelude.True = True bool _ = False @@ -912,25 +1067,26 @@ class ToValue a where toValue :: a -> Value -- | Encodes the unit value as JSON @null@. -instance ToValue () where {toValue _ = Null} -instance ToValue Value where {toValue = id} -instance ToValue Scientific where {toValue = Number} -instance ToValue Int where {toValue = int} -instance ToValue Int8 where {toValue = int8} -instance ToValue Int16 where {toValue = int16} -instance ToValue Int32 where {toValue = int32} -instance ToValue Int64 where {toValue = int64} -instance ToValue Word8 where {toValue = word8} -instance ToValue Word16 where {toValue = word16} -instance ToValue Word32 where {toValue = word32} -instance ToValue Word64 where {toValue = word64} -instance ToValue ShortText where {toValue = shortText} -instance ToValue Text where {toValue = text} -instance ToValue Prelude.Bool where {toValue = bool} +instance ToValue () where toValue _ = Null + +instance ToValue Value where toValue = id +instance ToValue Scientific where toValue = Number +instance ToValue Int where toValue = int +instance ToValue Int8 where toValue = int8 +instance ToValue Int16 where toValue = int16 +instance ToValue Int32 where toValue = int32 +instance ToValue Int64 where toValue = int64 +instance ToValue Word8 where toValue = word8 +instance ToValue Word16 where toValue = word16 +instance ToValue Word32 where toValue = word32 +instance ToValue Word64 where toValue = word64 +instance ToValue ShortText where toValue = shortText +instance ToValue Text where toValue = text +instance ToValue Prelude.Bool where toValue = bool instance ToValue Word where - toValue = word64 . fromIntegral @Word @Word64 + toValue = word64 . fromIntegral @Word @Word64 -instance ToValue a => ToValue [a] where +instance (ToValue a) => ToValue [a] where toValue !xs = runST $ do let len = List.length xs dst <- PM.newSmallArray len Null @@ -943,10 +1099,10 @@ instance ToValue a => ToValue [a] where go (ix + 1) zs go 0 xs -instance ToValue a => ToValue (SmallArray a) where +instance (ToValue a) => ToValue (SmallArray a) where toValue !xs = Json.Array $! Contiguous.map' toValue xs -instance ToValue a => ToValue (Array a) where +instance (ToValue a) => ToValue (Array a) where toValue !xs = Json.Array $! Contiguous.map' toValue xs instance (Prim a, ToValue a) => ToValue (PrimArray a) where diff --git a/src/Json/Flatten.hs b/src/Json/Flatten.hs index cf854b8..a188d6a 100644 --- a/src/Json/Flatten.hs +++ b/src/Json/Flatten.hs @@ -1,9 +1,10 @@ -{-# language BangPatterns #-} -{-# language DuplicateRecordFields #-} -{-# language NamedFieldPuns #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE NamedFieldPuns #-} --- | Flatten nested JSON objects into a single JSON object in which the keys --- have been joined by the separator. +{- | Flatten nested JSON objects into a single JSON object in which the keys +have been joined by the separator. +-} module Json.Flatten ( flatten ) where @@ -11,45 +12,46 @@ module Json.Flatten import Control.Monad.ST (ST) import Control.Monad.ST.Run (runByteArrayST) import Data.Builder.Catenable (Builder) -import Data.ByteString.Short.Internal (ShortByteString(SBS)) -import Data.Text.Short (ShortText) -import Data.Primitive (SmallArray,ByteArray(ByteArray),MutableByteArray) -import Data.Word (Word8) -import Json (Member(Member)) -import qualified Json -import qualified Data.Chunks as Chunks -import qualified Data.Primitive.Contiguous as C +import qualified Data.Builder.Catenable as Builder +import Data.ByteString.Short.Internal (ShortByteString (SBS)) import qualified Data.Bytes as Bytes import qualified Data.Bytes.Text.Utf8 as Utf8 +import qualified Data.Chunks as Chunks +import Data.Primitive (ByteArray (ByteArray), MutableByteArray, SmallArray) import qualified Data.Primitive as PM -import qualified Data.Builder.Catenable as Builder +import qualified Data.Primitive.Contiguous as C +import Data.Text.Short (ShortText) import qualified Data.Text.Short as TS import qualified Data.Text.Short.Unsafe as TS +import Data.Word (Word8) +import Json (Member (Member)) +import qualified Json + +{- | Flatten a json value, recursively descending into objects and joining +keys with the separator. For example: + +> { "name": "bilbo" +> , "occupation": +> { "name": "burglar" +> , "start": "2022-05-30" +> } +> , "height": 124 +> , "favorites": ["adventures","lunch"] +> } + +Becomes: + +> { "name": "bilbo" +> , "occupation.name": "burglar" +> , "occupation.start": "2022-05-30" +> , "height": 124 +> , "favorites": ["adventures","lunch"] +> } --- | Flatten a json value, recursively descending into objects and joining --- keys with the separator. For example: --- --- > { "name": "bilbo" --- > , "occupation": --- > { "name": "burglar" --- > , "start": "2022-05-30" --- > } --- > , "height": 124 --- > , "favorites": ["adventures","lunch"] --- > } --- --- Becomes: --- --- > { "name": "bilbo" --- > , "occupation.name": "burglar" --- > , "occupation.start": "2022-05-30" --- > , "height": 124 --- > , "favorites": ["adventures","lunch"] --- > } --- --- Currently, the implementation of this function throws an exception if --- any separator other than period is used. This may be corrected in a future --- release. +Currently, the implementation of this function throws an exception if +any separator other than period is used. This may be corrected in a future +release. +-} flatten :: Char -> Json.Value -> Json.Value flatten c v = case c of '.' -> flattenPeriod v @@ -63,7 +65,7 @@ data ShortTexts flattenPeriod :: Json.Value -> Json.Value flattenPeriod x = case x of Json.Object mbrs -> - let bldr = foldMap (\Member{key,value} -> flattenPrefix (ShortTextsBase key) value) mbrs + let bldr = foldMap (\Member {key, value} -> flattenPrefix (ShortTextsBase key) value) mbrs chunks = Builder.run bldr result = Chunks.concat chunks in Json.Object result @@ -71,25 +73,27 @@ flattenPeriod x = case x of _ -> x flattenPrefix :: - ShortTexts -- context accumulator - -> Json.Value - -> Builder Json.Member + ShortTexts -> -- context accumulator + Json.Value -> + Builder Json.Member flattenPrefix !pre x = case x of Json.Object mbrs -> flattenObject pre mbrs _ -> let !a = flattenPeriod x !k = runShortTexts pre - !mbr = Json.Member{key=k,value=a} + !mbr = Json.Member {key = k, value = a} in Builder.Cons mbr Builder.Empty flattenObject :: ShortTexts -> SmallArray Json.Member -> Builder Json.Member -flattenObject !pre !mbrs = foldMap - (\Member{key,value} -> flattenPrefix (ShortTextsCons key pre) value - ) mbrs +flattenObject !pre !mbrs = + foldMap + ( \Member {key, value} -> flattenPrefix (ShortTextsCons key pre) value + ) + mbrs runShortTexts :: ShortTexts -> ShortText runShortTexts !ts0 = go 0 ts0 - where + where paste :: MutableByteArray s -> Int -> ShortTexts -> ST s ByteArray paste !dst !ix (ShortTextsBase t) = let len = Bytes.length (Utf8.fromShortText t) @@ -116,6 +120,6 @@ runShortTexts !ts0 = go 0 ts0 in TS.fromShortByteStringUnsafe (SBS r) st2ba :: ShortText -> ByteArray -{-# inline st2ba #-} +{-# INLINE st2ba #-} st2ba t = case TS.toShortByteString t of SBS x -> ByteArray x diff --git a/src/Json/Smile.hs b/src/Json/Smile.hs index 9edd292..2ebb0a4 100644 --- a/src/Json/Smile.hs +++ b/src/Json/Smile.hs @@ -10,45 +10,62 @@ module Json.Smile ( -- * Encode JSON Document encode + -- * Encode JSON Atoms + -- ** Integer , encodeBigInteger + -- ** String , encodeString , encodeAsciiString + -- ** Key , encodeKey , encodeAsciiKey ) where -import Prelude hiding (Bool(..)) +import Prelude hiding (Bool (..)) import Control.Monad.ST (ST) import Control.Monad.ST.Run (runByteArrayST) -import Data.Bits (countLeadingZeros,complement,unsafeShiftR,(.&.),(.|.)) -import Data.Bits (testBit) +import Data.Bits + ( complement + , countLeadingZeros + , testBit + , unsafeShiftR + , (.&.) + , (.|.) + ) import Data.Bytes.Builder (Builder) import Data.Int (Int32) -import Data.Primitive (ByteArray,newByteArray) -import Data.Primitive (writeByteArray,byteArrayFromListN,sizeofByteArray) -import Data.Primitive (MutableByteArray(..),unsafeFreezeByteArray) -import Data.Primitive (readByteArray,copyMutableByteArray) +import Data.Primitive + ( ByteArray + , MutableByteArray (..) + , byteArrayFromListN + , copyMutableByteArray + , newByteArray + , readByteArray + , sizeofByteArray + , unsafeFreezeByteArray + , writeByteArray + ) import Data.Text.Short (ShortText) -import Data.Word (Word8,Word32,Word64) -import Data.Word.Zigzag (toZigzag32,toZigzag64) -import GHC.Exts (RealWorld,Word#,State#) -import GHC.IO (IO(IO)) -import GHC.Word (Word(..)) -import Json (Value(..), Member(..)) +import Data.Word (Word32, Word64, Word8) +import Data.Word.Zigzag (toZigzag32, toZigzag64) +import GHC.Exts (RealWorld, State#, Word#) +import GHC.IO (IO (IO)) +import GHC.Word (Word (..)) +import Json (Member (..), Value (..)) import Numeric.Natural (Natural) import System.IO.Unsafe (unsafeDupablePerformIO) import qualified Arithmetic.Nat as Nat +import qualified Data.ByteString.Short as SBS import qualified Data.Bytes as Bytes import qualified Data.Bytes.Builder as B import qualified Data.Bytes.Builder.Bounded as Bounded import qualified Data.Bytes.Builder.Bounded.Unsafe as Unsafe -import qualified Data.ByteString.Short as SBS import qualified Data.Number.Scientific as Sci import qualified Data.Text.Short as TS import qualified GHC.Exts as Exts @@ -56,10 +73,11 @@ import qualified GHC.Num.BigNat as BN import qualified GHC.Num.Integer as Integer import qualified Prelude --- | Encode a Json 'Value' to the Smile binary format. --- This encoder does not produce backreferences. +{- | Encode a Json 'Value' to the Smile binary format. +This encoder does not produce backreferences. +-} encode :: Value -> Builder -{-# inline encode #-} +{-# INLINE encode #-} encode v0 = B.ascii4 ':' ')' '\n' '\x00' <> encodeNoHeader v0 -- The "rebuild" trick was adapted from the fast-builder library. It @@ -67,25 +85,23 @@ encode v0 = B.ascii4 ':' ')' '\n' '\x00' <> encodeNoHeader v0 -- This function is marked noinline to ensure that its performance is -- stable. encodeNoHeader :: Value -> Builder -{-# noinline encodeNoHeader #-} +{-# NOINLINE encodeNoHeader #-} encodeNoHeader val = B.rebuild $ case val of Object obj -> B.word8 0xFA - <> - foldMap (\Member{key,value} -> encodeKey key <> encodeNoHeader value) obj - <> - B.word8 0xFB + <> foldMap (\Member {key, value} -> encodeKey key <> encodeNoHeader value) obj + <> B.word8 0xFB Array arr -> B.word8 0xF8 <> foldMap encodeNoHeader arr <> B.word8 0xF9 String str -> encodeString str Number x | Just i32 <- Sci.toInt32 x , -16 <= i32 && i32 <= 15 - , w5 <- fromIntegral @Word32 @Word8 (toZigzag32 i32) - -> B.word8 (0xC0 + w5) - | Just i32 <- Sci.toInt32 x - -> B.fromBounded Nat.constant (Bounded.word8 0x24 `Bounded.append` vlqSmile64 (fromIntegral @Word32 @Word64 (toZigzag32 i32))) - | Just i64 <- Sci.toInt64 x - -> B.fromBounded Nat.constant (Bounded.word8 0x25 `Bounded.append` vlqSmile64 (toZigzag64 i64)) + , w5 <- fromIntegral @Word32 @Word8 (toZigzag32 i32) -> + B.word8 (0xC0 + w5) + | Just i32 <- Sci.toInt32 x -> + B.fromBounded Nat.constant (Bounded.word8 0x24 `Bounded.append` vlqSmile64 (fromIntegral @Word32 @Word64 (toZigzag32 i32))) + | Just i64 <- Sci.toInt64 x -> + B.fromBounded Nat.constant (Bounded.word8 0x25 `Bounded.append` vlqSmile64 (toZigzag64 i64)) | otherwise -> Sci.withExposed encodeSmallDecimal encodeBigDecimal x Null -> B.word8 0x21 False -> B.word8 0x22 @@ -97,12 +113,15 @@ encodeSmallDecimal !c !e = encodeBigDecimal (fromIntegral c) (fromIntegral e) encodeBigDecimal :: Integer -> Integer -> Builder encodeBigDecimal c e = case e of 0 -> encodeBigInteger c - _ -> B.word8 0x2A -- bigdecimal token tag - <> vlqSmile ( fromIntegral @Word32 @Natural - $ toZigzag32 scale) - <> vlqSmile (fromIntegral @Int @Natural $ sizeofByteArray raw) -- size of byte digits - <> B.sevenEightSmile (Bytes.fromByteArray raw) -- 7/8 encoding of byte digits - where + _ -> + B.word8 0x2A -- bigdecimal token tag + <> vlqSmile + ( fromIntegral @Word32 @Natural $ + toZigzag32 scale + ) + <> vlqSmile (fromIntegral @Int @Natural $ sizeofByteArray raw) -- size of byte digits + <> B.sevenEightSmile (Bytes.fromByteArray raw) -- 7/8 encoding of byte digits + where scale :: Int32 -- WARNING smile can't handle exponents outside int32_t, so this truncates -- WARNING "scale" is what Java BigDecimal thinks, which is @@ -112,68 +131,73 @@ encodeBigDecimal c e = case e of -- | Encode a number using as SMILE @BigInteger@ token type (prefix @0x26@). encodeBigInteger :: Integer -> Builder -encodeBigInteger n = B.word8 0x26 - <> vlqSmile (fromIntegral @Int @Natural $ sizeofByteArray raw) -- size of byte digits - <> B.sevenEightSmile (Bytes.fromByteArray raw) -- 7/8 encoding of byte digits - where +encodeBigInteger n = + B.word8 0x26 + <> vlqSmile (fromIntegral @Int @Natural $ sizeofByteArray raw) -- size of byte digits + <> B.sevenEightSmile (Bytes.fromByteArray raw) -- 7/8 encoding of byte digits + where !raw = integerToBase256ByteArray n integerToBase256ByteArray :: Integer -> ByteArray -integerToBase256ByteArray c = if c == 0 - then byteArrayFromListN 1 [0::Word8] - else case c of - Integer.IP bn -> unsafeDupablePerformIO $ do - let nDigits256 = fromIntegral @Word @Int (W# (BN.bigNatSizeInBase# 256## bn)) - mut <- newByteArray nDigits256 - let !(MutableByteArray mut#) = mut - !_ <- liftWordIO (BN.bigNatToMutableByteArray# bn mut# 0## 1# ) - -- This is safe because Jp cannot have zero inside it. - w0 :: Word8 <- readByteArray mut 0 - if testBit w0 7 - then do - -- If the upper bit is 1, then we must introduce a leading - -- zero byte. - dst <- newByteArray (nDigits256 + 1) - writeByteArray dst 0 (0x00 :: Word8) - copyMutableByteArray dst 1 mut 0 nDigits256 - unsafeFreezeByteArray dst - else unsafeFreezeByteArray mut - Integer.IN bn -> twosComplementBigNat bn - Integer.IS i -> case i Exts.># 0# of - 1# -> encodePosWordBase256 (W# (Exts.int2Word# i)) - _ -> encodeNegWordBase256 (W# (Exts.int2Word# i)) +integerToBase256ByteArray c = + if c == 0 + then byteArrayFromListN 1 [0 :: Word8] + else case c of + Integer.IP bn -> unsafeDupablePerformIO $ do + let nDigits256 = fromIntegral @Word @Int (W# (BN.bigNatSizeInBase# 256## bn)) + mut <- newByteArray nDigits256 + let !(MutableByteArray mut#) = mut + !_ <- liftWordIO (BN.bigNatToMutableByteArray# bn mut# 0## 1#) + -- This is safe because Jp cannot have zero inside it. + w0 :: Word8 <- readByteArray mut 0 + if testBit w0 7 + then do + -- If the upper bit is 1, then we must introduce a leading + -- zero byte. + dst <- newByteArray (nDigits256 + 1) + writeByteArray dst 0 (0x00 :: Word8) + copyMutableByteArray dst 1 mut 0 nDigits256 + unsafeFreezeByteArray dst + else unsafeFreezeByteArray mut + Integer.IN bn -> twosComplementBigNat bn + Integer.IS i -> case i Exts.># 0# of + 1# -> encodePosWordBase256 (W# (Exts.int2Word# i)) + _ -> encodeNegWordBase256 (W# (Exts.int2Word# i)) liftWordIO :: (State# RealWorld -> (# State# RealWorld, Word# #)) -> IO Word -{-# inline liftWordIO #-} -liftWordIO f = IO - (\s -> case f s of - (# s', w #) -> (# s', W# w #) - ) +{-# INLINE liftWordIO #-} +liftWordIO f = + IO + ( \s -> case f s of + (# s', w #) -> (# s', W# w #) + ) twosComplementBigNat :: BN.BigNat# -> ByteArray twosComplementBigNat bn = unsafeDupablePerformIO $ do let nDigits256 = fromIntegral @Word @Int (W# (BN.bigNatSizeInBase# 256## bn)) mut <- newByteArray nDigits256 let !(MutableByteArray mut#) = mut - !_ <- liftWordIO (BN.bigNatToMutableByteArray# bn mut# 0## 1# ) + !_ <- liftWordIO (BN.bigNatToMutableByteArray# bn mut# 0## 1#) -- First, complement - let goComplement !ix = if ix >= 0 - then do - w :: Word8 <- readByteArray mut ix - writeByteArray mut ix (complement w) - goComplement (ix - 1) - else pure () + let goComplement !ix = + if ix >= 0 + then do + w :: Word8 <- readByteArray mut ix + writeByteArray mut ix (complement w) + goComplement (ix - 1) + else pure () goComplement (nDigits256 - 1) -- Second, add one - let goAddOne !ix = if ix >= 0 - then do - w :: Word8 <- readByteArray mut ix - case w of - 0xFF -> do - writeByteArray mut ix (0 :: Word8) - goAddOne (ix - 1) - _ -> writeByteArray mut ix (w + 1) - else pure () + let goAddOne !ix = + if ix >= 0 + then do + w :: Word8 <- readByteArray mut ix + case w of + 0xFF -> do + writeByteArray mut ix (0 :: Word8) + goAddOne (ix - 1) + _ -> writeByteArray mut ix (w + 1) + else pure () goAddOne (nDigits256 - 1) leader :: Word8 <- readByteArray mut 0 if testBit leader 7 @@ -201,11 +225,12 @@ encodePosWordBase256 !w = runByteArrayST $ do -- Because the latter would be misinterpreted as a negative number. let !total = quot (72 - countLeadingZeros w) 8 dst <- newByteArray total - let go !ix !acc = if ix >= 0 - then do - writeByteArray dst ix (fromIntegral @Word @Word8 acc) - go (ix - 1) (unsafeShiftR acc 8) - else unsafeFreezeByteArray dst + let go !ix !acc = + if ix >= 0 + then do + writeByteArray dst ix (fromIntegral @Word @Word8 acc) + go (ix - 1) (unsafeShiftR acc 8) + else unsafeFreezeByteArray dst go (total - 1) w -- Same deal as encodePosWordBase256. @@ -215,22 +240,24 @@ encodeNegWordBase256 :: Word -> ByteArray encodeNegWordBase256 !w = runByteArrayST $ do let !total = quot (72 - countLeadingZeros (complement w)) 8 dst <- newByteArray total - let go !ix !acc = if ix >= 0 - then do - writeByteArray dst ix (fromIntegral @Word @Word8 acc) - go (ix - 1) (unsafeShiftR acc 8) - else unsafeFreezeByteArray dst + let go !ix !acc = + if ix >= 0 + then do + writeByteArray dst ix (fromIntegral @Word @Word8 acc) + go (ix - 1) (unsafeShiftR acc 8) + else unsafeFreezeByteArray dst go (total - 1) w --- | Encode a string in which all characters are ASCII. This precondition --- is not checked. Resulting output will be corrupt if this condition --- is not satisfied. +{- | Encode a string in which all characters are ASCII. This precondition +is not checked. Resulting output will be corrupt if this condition +is not satisfied. +-} encodeAsciiString :: ShortText -> Builder encodeAsciiString !str | n == 0 = B.word8 0x20 | n <= 64 = B.copyCons (0x40 + fromIntegral (n - 1)) (Bytes.fromShortByteString (TS.toShortByteString str)) | otherwise = B.word8 0xe0 <> B.shortTextUtf8 str <> B.word8 0xFC - where + where n = SBS.length (TS.toShortByteString str) -- | Encode a string. @@ -249,35 +276,39 @@ encodeString !str = case SBS.length (TS.toShortByteString str) of encodeKey :: ShortText -> Builder encodeKey !str = case SBS.length (TS.toShortByteString str) of 0 -> B.word8 0x20 - n | n <= 64 - && TS.isAscii str - , w8 <- fromIntegral @Int @Word8 (n - 1) - -> B.copyCons (0x80 + w8) (Bytes.fromShortByteString (TS.toShortByteString str)) - n | n < 56 - , w8 <- fromIntegral @Int @Word8 (n - 2) - -> B.copyCons (0xC0 + w8) (Bytes.fromShortByteString (TS.toShortByteString str)) + n + | n <= 64 + && TS.isAscii str + , w8 <- fromIntegral @Int @Word8 (n - 1) -> + B.copyCons (0x80 + w8) (Bytes.fromShortByteString (TS.toShortByteString str)) + n + | n < 56 + , w8 <- fromIntegral @Int @Word8 (n - 2) -> + B.copyCons (0xC0 + w8) (Bytes.fromShortByteString (TS.toShortByteString str)) | otherwise -> B.word8 0x34 <> B.shortTextUtf8 str <> B.word8 0xFC --- | Encode a key in which all characters are ASCII. This precondition --- is not checked. Resulting output will be corrupt if this condition --- is not satisfied. +{- | Encode a key in which all characters are ASCII. This precondition +is not checked. Resulting output will be corrupt if this condition +is not satisfied. +-} encodeAsciiKey :: ShortText -> Builder encodeAsciiKey str = case SBS.length (TS.toShortByteString str) of 0 -> B.word8 0x20 - n | n <= 64 - , w8 <- fromIntegral @Int @Word8 (n - 1) - -> B.word8 (0x80 + w8) <> B.shortTextUtf8 str + n + | n <= 64 + , w8 <- fromIntegral @Int @Word8 (n - 1) -> + B.word8 (0x80 + w8) <> B.shortTextUtf8 str | otherwise -> B.word8 0x34 <> B.shortTextUtf8 str <> B.word8 0xFC vlqSmile :: Natural -> Builder vlqSmile n0 = let (rest, lastBits) = take6bits n0 in loop rest <> B.word8 (lastBits .|. 0x80) - where + where loop n | n == 0 = mempty - | (rest, bits) <- take7bits n - = loop rest <> B.word8 bits + | (rest, bits) <- take7bits n = + loop rest <> B.word8 bits take7bits :: Natural -> (Natural, Word8) take7bits n = (n `unsafeShiftR` 7, fromIntegral @Natural @Word8 n .&. 0x7F) take6bits :: Natural -> (Natural, Word8) @@ -305,13 +336,15 @@ vlqSmile64 !n0 = Unsafe.construct $ \buf ix0 -> do -- -- Copied from bytebuild reverseBytes :: MutableByteArray s -> Int -> Int -> ST s () -{-# inline reverseBytes #-} -reverseBytes arr begin end = go begin end where - go ixA ixB = if ixA < ixB - then do - a :: Word8 <- readByteArray arr ixA - b :: Word8 <- readByteArray arr ixB - writeByteArray arr ixA b - writeByteArray arr ixB a - go (ixA + 1) (ixB - 1) - else pure () +{-# INLINE reverseBytes #-} +reverseBytes arr begin end = go begin end + where + go ixA ixB = + if ixA < ixB + then do + a :: Word8 <- readByteArray arr ixA + b :: Word8 <- readByteArray arr ixB + writeByteArray arr ixA b + writeByteArray arr ixB a + go (ixA + 1) (ixB - 1) + else pure ()