@@ -2,9 +2,12 @@
2{-# LANGUAGE CPP #-}
2{-# LANGUAGE CPP #-}
3{-# LANGUAGE NoImplicitPrelude #-}
3{-# LANGUAGE NoImplicitPrelude #-}
4{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE OverloadedStrings #-}
5+#if MIN_VERSION_ghc_prim(0,3,1)
6+{-# LANGUAGE MagicHash #-}
5#if __GLASGOW_HASKELL__ <= 710 && __GLASGOW_HASKELL__ >= 706
8#if __GLASGOW_HASKELL__ <= 710 && __GLASGOW_HASKELL__ >= 706
6-- Work around a compiler bug
9-- Work around a compiler bug
7−{-# OPTIONS_GHC -fsimpl-tick-factor=300 #-}
10+{-# OPTIONS_GHC -fsimpl-tick-factor=200 #-}
10-- Module: Data.Aeson.Parser.Internal
13-- Module: Data.Aeson.Parser.Internal
@@ -55,10 +58,8 @@ import Data.Aeson.Types.Internal (IResult(..), JSONPath, Object, Result(..), Val
55import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDigit_w8, signed, string)
58import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDigit_w8, signed, string)
56import Data.Function (fix)
59import Data.Function (fix)
57import Data.Functor.Compat (($>))
60import Data.Functor.Compat (($>))
58−import Data.Bits (testBit)
59import Data.Scientific (Scientific)
61import Data.Scientific (Scientific)
60import Data.Text (Text)
62import Data.Text (Text)
61−import qualified Data.Text.Encoding as TE
62import Data.Vector (Vector)
63import Data.Vector (Vector)
63import qualified Data.Vector as Vector (empty, fromList, fromListN, reverse)
64import qualified Data.Vector as Vector (empty, fromList, fromListN, reverse)
64import qualified Data.Attoparsec.ByteString as A
65import qualified Data.Attoparsec.ByteString as A
@@ -66,13 +67,16 @@ import qualified Data.Attoparsec.Lazy as L
66import qualified Data.ByteString as B
67import qualified Data.ByteString as B
67import qualified Data.ByteString.Unsafe as B
68import qualified Data.ByteString.Unsafe as B
68import qualified Data.ByteString.Lazy as L
69import qualified Data.ByteString.Lazy as L
69−import qualified Data.ByteString.Lazy as BSL
70−import qualified Data.ByteString.Lazy.Char8 as C
71−import qualified Data.ByteString.Builder as B
72import qualified Data.HashMap.Strict as H
70import qualified Data.HashMap.Strict as H
73import qualified Data.Scientific as Sci
71import qualified Data.Scientific as Sci
74import Data.Aeson.Parser.Unescape (unescapeText)
72import Data.Aeson.Parser.Unescape (unescapeText)
74+#if MIN_VERSION_ghc_prim(0,3,1)
75+import GHC.Base (Int#, (==#), isTrue#, word2Int#, orI#, andI#)
76+import GHC.Word (Word8(W8#))
77+import qualified Data.Text.Encoding as TE
76#define BACKSLASH 92
80#define BACKSLASH 92
77#define CLOSE_CURLY 125
81#define CLOSE_CURLY 125
78#define CLOSE_SQUARE 93
82#define CLOSE_SQUARE 93
@@ -319,20 +323,37 @@ jstring = A.word8 DOUBLE_QUOTE *> jstring_
319-- | Parse a string without a leading quote.
323-- | Parse a string without a leading quote.
320jstring_ :: Parser Text
324jstring_ :: Parser Text
321{-# INLINE jstring_ #-}
325{-# INLINE jstring_ #-}
322−jstring_ = do
326+jstring_ = {-# SCC "jstring_" #-} do
323− s <- A.takeWhile (\w -> w /= DOUBLE_QUOTE && w /= BACKSLASH && not (testBit w 7))
327+#if MIN_VERSION_ghc_prim(0,3,1)
324− let txt = TE.decodeUtf8 s
328+ (s, S _ escaped) <- A.runScanner startState go <* A.anyWord8
325− w <- A.peekWord8
329+ -- We escape only if there are
330+ -- non-ascii (over 7bit) characters or backslash present.
332+ -- Note: if/when text will have fast ascii -> text conversion
333+ -- (e.g. uses utf8 encoding) we can have further speedup.
326− case w of
335+ then case unescapeText s of
327− Nothing -> fail "string without end"
336+ Right r -> return r
328− Just DOUBLE_QUOTE -> A.anyWord8 $> txt
337+ Left err -> fail $ show err
329− _ -> jstringSlow s
338+ else return (TE.decodeUtf8 s)
331−jstringSlow :: B.ByteString -> Parser Text
340+ startState = S 0# 0#
332−{-# INLINE jstringSlow #-}
341+ go (S skip escaped) (W8# c)
333−jstringSlow s' = {-# SCC "jstringSlow" #-} do
342+ | isTrue# skip = Just (S 0# escaped')
343+ | isTrue# (w ==# 34#) = Nothing -- double quote
344+ | otherwise = Just (S skip' escaped')
347+ skip' = w ==# 92# -- backslash
349+ `orI#` (w `andI#` 0x80# ==# 0x80#) -- c >= 0x80
351+ `orI#` (w `andI#` 0x1f# ==# w) -- c < 0x20
334 s <- A.scan startState go <* A.anyWord8
355 s <- A.scan startState go <* A.anyWord8
335− case unescapeText (B.append s' s) of
356+ case unescapeText s of
336 Right r -> return r
357 Right r -> return r
337 Left err -> fail $ show err
358 Left err -> fail $ show err
@@ -343,6 +364,7 @@ jstringSlow s' = {-# SCC "jstringSlow" #-} do
343 | otherwise = let a' = c == backslash
364 | otherwise = let a' = c == backslash
344 in Just a'
365 in Just a'
345 where backslash = BACKSLASH
366 where backslash = BACKSLASH
347decodeWith :: Parser Value -> (Value -> Result a) -> L.ByteString -> Maybe a
369decodeWith :: Parser Value -> (Value -> Result a) -> L.ByteString -> Maybe a
348decodeWith p to s =
370decodeWith p to s =
@@ -368,35 +390,14 @@ eitherDecodeWith p to s =
368 L.Done _ v -> case to v of
390 L.Done _ v -> case to v of
369 ISuccess a -> Right a
391 ISuccess a -> Right a
370 IError path msg -> Left (path, msg)
392 IError path msg -> Left (path, msg)
371− L.Fail notparsed ctx msg -> Left ([], buildMsg notparsed ctx msg)
393+ L.Fail _ ctx msg -> Left ([], buildMsg ctx msg)
373− buildMsg :: L.ByteString -> [String] -> String -> String
395+ buildMsg :: [String] -> String -> String
374− buildMsg notYetParsed [] msg = msg ++ formatErrorLine notYetParsed
396+ buildMsg [] msg = msg
375− buildMsg notYetParsed (expectation:_) msg =
397+ buildMsg (expectation:_) msg =
376− msg ++ ". Expecting " ++ expectation ++ formatErrorLine notYetParsed
398+ msg ++ ". Expecting " ++ expectation
377{-# INLINE eitherDecodeWith #-}
399{-# INLINE eitherDecodeWith #-}
379−-- | Grab the first 100 bytes from the non parsed portion and
380−-- format to get nicer error messages
381−formatErrorLine :: L.ByteString -> String
384− -- if formatting results in empty ByteString just return that
385− -- otherwise construct the error message with the bytestring builder
390− B.toLazyByteString $
391− B.stringUtf8 " at '" <> B.lazyByteString bs' <> B.stringUtf8 "'"
393− -- if newline is present cut at that position
394− BSL.takeWhile (10 /=) .
395− -- remove spaces, CR's, tabs, backslashes and quotes characters
396− BSL.filter (`notElem` [9, 13, 32, 34, 47, 92]) .
400eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
401eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
401 -> Either (JSONPath, String) a
402 -> Either (JSONPath, String) a
402eitherDecodeStrictWith p to s =
403eitherDecodeStrictWith p to s =