Skip to content

Commit ee52202

Browse files
committed
Add encode function
1 parent 1efa12a commit ee52202

4 files changed

Lines changed: 84 additions & 6 deletions

File tree

common/Twitter100.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE OverloadedStrings #-}
12
{-# LANGUAGE QuasiQuotes #-}
23
module Twitter100
34
( encodedTwitter100

json-syntax.cabal

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,13 @@ library
3131
, array-builder >=0.1 && <0.2
3232
, array-chunks >=0.1.1 && <0.2
3333
, base >=4.12 && <5
34-
, bytesmith >=0.3.2 && <0.4
34+
, bytebuild >=0.3.4 && <0.4
3535
, byteslice >=0.1.3 && <0.3
36+
, bytesmith >=0.3.2 && <0.4
37+
, bytestring >=0.10.8 && <0.11
38+
, primitive >=0.7 && <0.8
3639
, scientific-notation >=0.1.1 && <0.2
3740
, text-short >=0.1.3 && <0.2
38-
, primitive >=0.7 && <0.8
39-
, bytestring >=0.10.8 && <0.11
4041
hs-source-dirs: src
4142
default-language: Haskell2010
4243
ghc-options: -Wall -O2
@@ -60,7 +61,7 @@ test-suite test
6061
, primitive
6162
, scientific
6263
, scientific-notation >=0.1.1
63-
, small-bytearray-builder
64+
, bytebuild
6465
, tasty >=1.2.3 && <1.3
6566
, tasty-hunit >=0.10.0.2 && <0.11
6667
, text >=1.2

src/Json.hs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
{-# language DeriveAnyClass #-}
66
{-# language LambdaCase #-}
77
{-# language MagicHash #-}
8+
{-# language NamedFieldPuns #-}
89
{-# language TypeApplications #-}
10+
{-# language UnboxedTuples #-}
911

1012
module Json
1113
( -- * Types
@@ -14,6 +16,7 @@ module Json
1416
, SyntaxException(..)
1517
-- * Functions
1618
, decode
19+
, encode
1720
) where
1821

1922
import Prelude hiding (Bool(True,False))
@@ -25,7 +28,7 @@ import Data.Builder.ST (Builder)
2528
import Data.Bytes.Parser (Parser)
2629
import Data.Bytes.Types (Bytes(..))
2730
import Data.Char (ord)
28-
import Data.Chunks (Chunks(ChunksNil))
31+
import Data.Chunks (Chunks(ChunksNil,ChunksCons))
2932
import Data.Number.Scientific (Scientific)
3033
import Data.Primitive (ByteArray,MutableByteArray)
3134
import Data.Text.Short (ShortText)
@@ -34,6 +37,7 @@ import GHC.Word (Word8(W8#),Word16(W16#))
3437

3538
import qualified Prelude
3639
import qualified Data.Builder.ST as B
40+
import qualified Data.Bytes.Builder as BLDR
3741
import qualified Data.Bytes.Parser as P
3842
import qualified Data.Text.Short.Unsafe as TS
3943
import qualified Data.Number.Scientific as SCI
@@ -123,6 +127,70 @@ decode = P.parseBytesEither do
123127
P.endOfInput UnexpectedLeftovers
124128
pure result
125129

130+
encode :: Value -> BLDR.Builder
131+
encode = \case
132+
True -> BLDR.ascii4 't' 'r' 'u' 'e'
133+
False -> BLDR.ascii5 'f' 'a' 'l' 's' 'e'
134+
Null -> BLDR.ascii4 'n' 'u' 'l' 'l'
135+
String s -> BLDR.shortTextJsonString s
136+
Number n -> SCI.builderUtf8 n
137+
Array ys -> case unconsNonempty ys of
138+
Nothing -> BLDR.ascii2 '[' ']'
139+
Just (x,xs) ->
140+
BLDR.ascii '['
141+
<>
142+
encode (PM.indexSmallArray x 0)
143+
<>
144+
foldrTail
145+
( \v b -> BLDR.ascii ',' <> encode v <> b
146+
)
147+
( foldr
148+
( \v b -> BLDR.ascii ',' <> encode v <> b
149+
) (BLDR.ascii ']') xs
150+
)
151+
x
152+
Object ys -> case unconsNonempty ys of
153+
Nothing -> BLDR.ascii2 '{' '}'
154+
Just (x,xs) ->
155+
BLDR.ascii '{'
156+
<>
157+
encodeMember (PM.indexSmallArray x 0)
158+
<>
159+
foldrTail
160+
( \mbr b -> BLDR.ascii ',' <> encodeMember mbr <> b
161+
)
162+
( foldr
163+
( \mbr b -> BLDR.ascii ',' <> encodeMember mbr <> b
164+
) (BLDR.ascii '}') xs
165+
)
166+
x
167+
168+
encodeMember :: Member -> BLDR.Builder
169+
encodeMember Member{key,value} =
170+
BLDR.shortTextJsonString key
171+
<>
172+
BLDR.ascii ':'
173+
<>
174+
encode value
175+
176+
foldrTail :: (a -> b -> b) -> b -> PM.SmallArray a -> b
177+
{-# inline foldrTail #-}
178+
foldrTail f z !ary = go 1 where
179+
!sz = PM.sizeofSmallArray ary
180+
go i
181+
| i == sz = z
182+
| (# x #) <- PM.indexSmallArray## ary i
183+
= f x (go (i+1))
184+
185+
-- Get the first non-empty SmallArray from the Chunks.
186+
unconsNonempty :: Chunks a -> Maybe (PM.SmallArray a, Chunks a)
187+
{-# inline unconsNonempty #-}
188+
unconsNonempty = go where
189+
go ChunksNil = Nothing
190+
go (ChunksCons x xs) = case PM.sizeofSmallArray x of
191+
0 -> go xs
192+
_ -> Just (x,xs)
193+
126194
-- Precondition: skip over all space before calling this.
127195
-- It will not skip leading space for you. It does
128196
parser :: Char -> Parser SyntaxException s Value

test/Main.hs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{-# language LambdaCase #-}
22
{-# language OverloadedStrings #-}
33

4+
import Control.Monad (when)
45
import Data.ByteString.Short.Internal (ShortByteString(SBS))
56
import Data.Bytes (Bytes)
67
import Data.Primitive (ByteArray(ByteArray))
@@ -12,6 +13,8 @@ import Twitter100 (encodedTwitter100,byteStringTwitter100)
1213

1314
import qualified Data.Aeson as AE
1415
import qualified Data.Bytes as Bytes
16+
import qualified Data.Bytes.Builder as Builder
17+
import qualified Data.Bytes.Chunks as BChunks
1518
import qualified Data.Chunks as Chunks
1619
import qualified Data.HashMap.Strict as HM
1720
import qualified Data.Number.Scientific as SCI
@@ -65,7 +68,12 @@ tests = testGroup "Tests"
6568
Right j -> case AE.decodeStrict byteStringTwitter100 of
6669
Nothing -> fail "aeson is messed up"
6770
Just ae -> ae @=? toAesonValue j
68-
71+
, THU.testCase "Twitter100-roundtrip" $
72+
case J.decode (Bytes.fromByteArray encodedTwitter100) of
73+
Left _ -> fail "nope, Twitter100 test will be failing too"
74+
Right j -> case J.decode (BChunks.concat (Builder.run 1 (J.encode j))) of
75+
Left _ -> fail "encode did not produce a document that could be decoded"
76+
Right j' -> when (j /= j') (fail "document was not the same after roundtrip")
6977
]
7078

7179
toBadSci :: SCI.Scientific -> Scientific

0 commit comments

Comments
 (0)