Skip to content

Commit fa2e8cc

Browse files
authored
Merge pull request #146 from haskell-works/delegate-to-aesons-string-parser
Delegate to aesons string parser
2 parents 0054e6a + 3878268 commit fa2e8cc

File tree

4 files changed

+169
-22
lines changed

4 files changed

+169
-22
lines changed

hw-json.cabal

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ flag sse42
3939

4040
common base { build-depends: base >= 4 && < 5 }
4141

42+
common aeson { build-depends: aeson >= 1.4.3.0 && < 1.5 }
4243
common ansi-wl-pprint { build-depends: ansi-wl-pprint >= 0.6.8.2 && < 0.7 }
4344
common array { build-depends: array >= 0.5 && < 0.6 }
4445
common attoparsec { build-depends: attoparsec >= 0.13 && < 0.14 }
@@ -65,6 +66,7 @@ common hw-simd { build-depends: hw-simd >= 0.1
6566
common lens { build-depends: lens >= 4 && < 5 }
6667
common mmap { build-depends: mmap >= 0.5 && < 0.6 }
6768
common optparse-applicative { build-depends: optparse-applicative >= 0.14 && < 0.15 }
69+
common scientific { build-depends: scientific >= 0.3.6.2 && < 0.4 }
6870
common text { build-depends: text >= 1.2 && < 1.3 }
6971
common transformers { build-depends: transformers >= 0.4 && < 0.6 }
7072
common vector { build-depends: vector >= 0.12 && < 0.13 }
@@ -83,6 +85,7 @@ common config
8385

8486
library
8587
import: base, config
88+
, aeson
8689
, ansi-wl-pprint
8790
, attoparsec
8891
, bits-extra
@@ -160,6 +163,7 @@ executable hw-json
160163

161164
test-suite hw-json-test
162165
import: base, config
166+
, aeson
163167
, attoparsec
164168
, bytestring
165169
, hedgehog
@@ -172,6 +176,8 @@ test-suite hw-json-test
172176
, hw-prim
173177
, hw-rankselect
174178
, hw-rankselect-base
179+
, scientific
180+
, text
175181
, transformers
176182
, vector
177183
type: exitcode-stdio-1.0
@@ -181,6 +187,7 @@ test-suite hw-json-test
181187
ghc-options: -threaded -rtsopts -with-rtsopts=-N
182188
build-tools: hspec-discover
183189
other-modules:
190+
HaskellWorks.Data.Json.LightJsonSpec
184191
HaskellWorks.Data.Json.Simple.CursorSpec
185192
HaskellWorks.Data.Json.Standard.CursorSpec
186193
HaskellWorks.Data.Json.Standard.GenCursorTest

src/HaskellWorks/Data/Json/Internal/Slurp.hs

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ import Data.Word8
1313
import HaskellWorks.Data.Json.Standard.Cursor.Internal.Word8
1414
import Prelude hiding (drop)
1515

16-
import qualified Data.ByteString as BS
17-
import qualified Data.ByteString.Char8 as BSC
18-
import qualified Data.List as L
19-
import qualified Data.Text as T
16+
import qualified Data.Aeson.Parser.Internal as AP
17+
import qualified Data.Attoparsec.ByteString as PBS
18+
import qualified Data.ByteString as BS
19+
import qualified Data.ByteString.Char8 as BSC
20+
import qualified Data.List as L
21+
import qualified Data.Text as T
2022

2123
data JsonState
2224
= Escaped
@@ -25,22 +27,10 @@ data JsonState
2527
| InNumber
2628
| InIdent
2729

28-
slurpText :: BS.ByteString -> Text
29-
slurpText bs = T.pack $ L.unfoldr genString (InJson, BSC.unpack bs) -- TODO optimise
30-
where genString :: (JsonState, String) -> Maybe (Char, (JsonState, String))
31-
genString (InJson, ds) = case ds of
32-
(e:es) | e == '"' -> genString (InString , es)
33-
(_:es) -> genString (InJson , es)
34-
_ -> Nothing
35-
genString (InString, ds) = case ds of
36-
(e:es) | e == '\\' -> genString (Escaped , es)
37-
(e:_ ) | e == '"' -> Nothing
38-
(e:es) -> Just (e, (InString , es))
39-
_ -> Nothing
40-
genString (Escaped, ds) = case ds of
41-
(_:es) -> Just ('.', (InString , es))
42-
_ -> Nothing
43-
genString (_, _) = Nothing
30+
slurpText :: BS.ByteString -> Either Text Text
31+
slurpText bs = case PBS.parseOnly AP.jstring bs of
32+
Right t -> Right t
33+
Left e -> Left (T.pack e)
4434

4535
slurpNumber :: BS.ByteString -> BS.ByteString
4636
slurpNumber bs = let (!cs, _) = BS.unfoldrN (BS.length bs) genNumber (InJson, bs) in cs

src/HaskellWorks/Data/Json/LightJson.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import HaskellWorks.Data.RankSelect.Base.Select1
3131
import HaskellWorks.Data.TreeCursor
3232
import HaskellWorks.Data.Uncons
3333
import Prelude hiding (drop)
34-
import Prelude hiding (drop)
3534
import Text.PrettyPrint.ANSI.Leijen
3635

3736
import qualified Data.ByteString as BS
@@ -126,7 +125,7 @@ instance LightJsonAt c => Pretty (MQuery (Entry String (LightJson c))) where
126125
instance (BP.BalancedParens w, Rank0 w, Rank1 w, Select1 v, TestBit w) => LightJsonAt (GenericCursor BS.ByteString v w) where
127126
lightJsonAt k = case uncons remainder of
128127
Just (!c, _) | isLeadingDigit2 c -> LightJsonNumber (slurpNumber remainder)
129-
Just (!c, _) | isQuotDbl c -> LightJsonString (slurpText remainder)
128+
Just (!c, _) | isQuotDbl c -> either LightJsonError LightJsonString (slurpText remainder)
130129
Just (!c, _) | isChar_t c -> LightJsonBool True
131130
Just (!c, _) | isChar_f c -> LightJsonBool False
132131
Just (!c, _) | isChar_n c -> LightJsonNull
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
{-# LANGUAGE ExplicitForAll #-}
2+
{-# LANGUAGE FlexibleContexts #-}
3+
{-# LANGUAGE FlexibleInstances #-}
4+
{-# LANGUAGE InstanceSigs #-}
5+
{-# LANGUAGE MultiParamTypeClasses #-}
6+
{-# LANGUAGE NoMonomorphismRestriction #-}
7+
{-# LANGUAGE OverloadedStrings #-}
8+
{-# LANGUAGE ScopedTypeVariables #-}
9+
{-# LANGUAGE TupleSections #-}
10+
11+
{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
12+
13+
module HaskellWorks.Data.Json.LightJsonSpec (spec) where
14+
15+
import Control.Monad
16+
import Data.Text
17+
import HaskellWorks.Data.BalancedParens.BalancedParens
18+
import HaskellWorks.Data.Bits.BitWise
19+
import HaskellWorks.Data.Json.DecodeError
20+
import HaskellWorks.Data.Json.LightJson
21+
import HaskellWorks.Data.Json.Standard.Cursor.Generic
22+
import HaskellWorks.Data.Json.Value
23+
import HaskellWorks.Data.RankSelect.Base.Rank0
24+
import HaskellWorks.Data.RankSelect.Base.Rank1
25+
import HaskellWorks.Data.RankSelect.Base.Select1
26+
import HaskellWorks.Hspec.Hedgehog
27+
import Hedgehog
28+
import Test.Hspec
29+
30+
import qualified Data.Aeson.Parser.Internal as AP
31+
import qualified Data.Attoparsec.ByteString as PBS
32+
import qualified Data.ByteString as BS
33+
import qualified Data.Scientific as S
34+
import qualified Data.Text as T
35+
import qualified HaskellWorks.Data.Json.Standard.Cursor.Fast as FAST
36+
import qualified HaskellWorks.Data.Json.Standard.Cursor.Slow as SLOW
37+
import qualified HaskellWorks.Data.TreeCursor as TC
38+
39+
{-# ANN module ("HLint: ignore Redundant do" :: String) #-}
40+
{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}
41+
{-# ANN module ("HLint: ignore Redundant bracket" :: String) #-}
42+
43+
fc = TC.firstChild
44+
ns = TC.nextSibling
45+
46+
spec :: Spec
47+
spec = describe "HaskellWorks.Data.Json.LightJsonSpec" $ do
48+
genSpec "DVS.Vector Word64" SLOW.fromString
49+
genSpec "CsPoppy" FAST.fromString
50+
51+
jsonValueVia :: forall t u.
52+
( BalancedParens u
53+
, Rank0 u
54+
, Rank1 u
55+
, Select1 t
56+
, TestBit u)
57+
=> Maybe (GenericCursor BS.ByteString t u)
58+
-> Either DecodeError JsonValue
59+
jsonValueVia mk = case mk of
60+
Just k -> case lightJsonAt k of
61+
LightJsonString t -> Right (JsonString t)
62+
LightJsonNumber bs -> case PBS.parseOnly AP.scientific bs of
63+
Right s -> Right (JsonNumber (S.toRealFloat s))
64+
Left msg -> Left (DecodeError msg)
65+
LightJsonObject ps -> fmap JsonObject (traverse fields ps)
66+
LightJsonArray cs -> fmap JsonArray (traverse elements cs)
67+
LightJsonBool v -> Right (JsonBool v)
68+
LightJsonNull -> Right JsonNull
69+
LightJsonError msg -> Left (DecodeError (T.unpack msg))
70+
Nothing -> Left (DecodeError "No such element")
71+
where fields :: (Text, GenericCursor BS.ByteString t u) -> Either DecodeError (Text, JsonValue)
72+
fields (f, c) = (f,) <$> jsonValueVia (Just c)
73+
elements :: GenericCursor BS.ByteString t u -> Either DecodeError JsonValue
74+
elements c = jsonValueVia (Just c)
75+
76+
genSpec :: forall t u.
77+
( Eq t
78+
, Show t
79+
, Select1 t
80+
, Eq u
81+
, Show u
82+
, Rank0 u
83+
, Rank1 u
84+
, BalancedParens u
85+
, TestBit u)
86+
=> String -> (String -> GenericCursor BS.ByteString t u) -> SpecWith ()
87+
genSpec t makeCursor = do
88+
describe ("Json cursor of type " ++ t) $ do
89+
let forJson s f = describe ("of value " ++ show s) (f (makeCursor s))
90+
forJson "{}" $ \cursor -> do
91+
it "should have correct value" $ requireTest $ jsonValueVia (Just cursor) === Right (JsonObject [])
92+
forJson " {}" $ \cursor -> do
93+
it "should have correct value" $ requireTest $ jsonValueVia (Just cursor) === Right (JsonObject [])
94+
forJson "1234" $ \cursor -> do
95+
it "should have correct value" $ requireTest $ jsonValueVia (Just cursor) === Right (JsonNumber 1234)
96+
forJson "\"Hello\"" $ \cursor -> do
97+
it "should have correct value" $ requireTest $ jsonValueVia (Just cursor) === Right (JsonString "Hello")
98+
forJson "[]" $ \cursor -> do
99+
it "should have correct value" $ requireTest $ jsonValueVia (Just cursor) === Right (JsonArray [])
100+
forJson "true" $ \cursor -> do
101+
it "should have correct value" $ requireTest $ jsonValueVia (Just cursor) === Right (JsonBool True)
102+
forJson "false" $ \cursor -> do
103+
it "should have correct value" $ requireTest $ jsonValueVia (Just cursor) === Right (JsonBool False)
104+
forJson "null" $ \cursor -> do
105+
it "should have correct value" $ requireTest $ jsonValueVia (Just cursor) === Right JsonNull
106+
forJson "[null]" $ \cursor -> do
107+
it "should have correct value" $ requireTest $ jsonValueVia (Just cursor) === Right (JsonArray [JsonNull])
108+
it "should have correct value" $ requireTest $ jsonValueVia (fc cursor) === Right JsonNull
109+
forJson "[null, {\"field\": 1}]" $ \cursor -> do
110+
it "cursor can navigate to second child of array" $ requireTest $ do
111+
jsonValueVia ((fc >=> ns) cursor) === Right ( JsonObject [("field", JsonNumber 1)] )
112+
jsonValueVia (Just cursor) === Right (JsonArray [JsonNull, JsonObject [("field", JsonNumber 1)]])
113+
describe "For empty json array" $ do
114+
let cursor = makeCursor "[]"
115+
it "can navigate down and forwards" $ requireTest $ do
116+
jsonValueVia (Just cursor) === Right (JsonArray [])
117+
describe "For empty json array" $ do
118+
let cursor = makeCursor "[null]"
119+
it "can navigate down and forwards" $ requireTest $ do
120+
jsonValueVia (Just cursor) === Right (JsonArray [JsonNull])
121+
describe "For sample Json" $ do
122+
let cursor = makeCursor "{ \
123+
\ \"widget\": { \
124+
\ \"debug\": \"on\", \
125+
\ \"window\": { \
126+
\ \"name\": \"main_window\", \
127+
\ \"dimensions\": [500, 600.01e-02, true, false, null] \
128+
\ } \
129+
\ } \
130+
\}" :: GenericCursor BS.ByteString t u
131+
it "can navigate down and forwards" $ requireTest $ do
132+
let array = JsonArray [JsonNumber 500, JsonNumber 600.01e-02, JsonBool True, JsonBool False, JsonNull] :: JsonValue
133+
let object1 = JsonObject ([("name", JsonString "main_window"), ("dimensions", array)]) :: JsonValue
134+
let object2 = JsonObject ([("debug", JsonString "on"), ("window", object1)]) :: JsonValue
135+
let object3 = JsonObject ([("widget", object2)]) :: JsonValue
136+
jsonValueVia (Just cursor) === Right object3
137+
jsonValueVia ((fc ) cursor) === Right (JsonString "widget" )
138+
jsonValueVia ((fc >=> ns ) cursor) === Right (object2 )
139+
jsonValueVia ((fc >=> ns >=> fc ) cursor) === Right (JsonString "debug" )
140+
jsonValueVia ((fc >=> ns >=> fc >=> ns ) cursor) === Right (JsonString "on" )
141+
jsonValueVia ((fc >=> ns >=> fc >=> ns >=> ns ) cursor) === Right (JsonString "window" )
142+
jsonValueVia ((fc >=> ns >=> fc >=> ns >=> ns >=> ns ) cursor) === Right (object1 )
143+
jsonValueVia ((fc >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc ) cursor) === Right (JsonString "name" )
144+
jsonValueVia ((fc >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns ) cursor) === Right (JsonString "main_window" )
145+
jsonValueVia ((fc >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns >=> ns ) cursor) === Right (JsonString "dimensions" )
146+
jsonValueVia ((fc >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns >=> ns >=> ns ) cursor) === Right (array )
147+
jsonValueVia ((fc >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc ) cursor) === Right (JsonNumber 500 )
148+
jsonValueVia ((fc >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns ) cursor) === Right (JsonNumber 600.01e-02 )
149+
jsonValueVia ((fc >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns >=> ns ) cursor) === Right (JsonBool True )
150+
jsonValueVia ((fc >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns >=> ns >=> ns ) cursor) === Right (JsonBool False )
151+
jsonValueVia ((fc >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns >=> ns >=> ns >=> fc >=> ns >=> ns >=> ns >=> ns) cursor) === Right JsonNull

0 commit comments

Comments
 (0)