dhodovsk / source-git / ghc-aeson

Forked from source-git/ghc-aeson 4 years ago
Clone

Blame Data/Aeson/Encoding/Builder.hs

Packit 9a2dfb
{-# LANGUAGE BangPatterns #-}
Packit 9a2dfb
{-# LANGUAGE CPP #-}
Packit 9a2dfb
Packit 9a2dfb
-- |
Packit 9a2dfb
-- Module:      Data.Aeson.Encoding.Builder
Packit 9a2dfb
-- Copyright:   (c) 2011 MailRank, Inc.
Packit 9a2dfb
--              (c) 2013 Simon Meier <iridcode@gmail.com>
Packit 9a2dfb
-- License:     BSD3
Packit 9a2dfb
-- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>
Packit 9a2dfb
-- Stability:   experimental
Packit 9a2dfb
-- Portability: portable
Packit 9a2dfb
--
Packit 9a2dfb
-- Efficiently serialize a JSON value using the UTF-8 encoding.
Packit 9a2dfb
Packit 9a2dfb
module Data.Aeson.Encoding.Builder
Packit 9a2dfb
    (
Packit 9a2dfb
      encodeToBuilder
Packit 9a2dfb
    , null_
Packit 9a2dfb
    , bool
Packit 9a2dfb
    , array
Packit 9a2dfb
    , emptyArray_
Packit 9a2dfb
    , emptyObject_
Packit 9a2dfb
    , object
Packit 9a2dfb
    , text
Packit 9a2dfb
    , string
Packit 9a2dfb
    , unquoted
Packit 9a2dfb
    , quote
Packit 9a2dfb
    , scientific
Packit 9a2dfb
    , day
Packit 9a2dfb
    , localTime
Packit 9a2dfb
    , utcTime
Packit 9a2dfb
    , timeOfDay
Packit 9a2dfb
    , zonedTime
Packit 9a2dfb
    , ascii2
Packit 9a2dfb
    , ascii4
Packit 9a2dfb
    , ascii5
Packit 9a2dfb
    ) where
Packit 9a2dfb
Packit 9a2dfb
import Prelude ()
Packit 9a2dfb
import Prelude.Compat
Packit 9a2dfb
Packit 9a2dfb
import Data.Aeson.Internal.Time
Packit 9a2dfb
import Data.Aeson.Types.Internal (Value (..))
Packit 9a2dfb
import Data.ByteString.Builder as B
Packit 9a2dfb
import Data.ByteString.Builder.Prim as BP
Packit 9a2dfb
import Data.ByteString.Builder.Scientific (scientificBuilder)
Packit 9a2dfb
import Data.Char (chr, ord)
Packit 9a2dfb
import Data.Monoid ((<>))
Packit 9a2dfb
import Data.Scientific (Scientific, base10Exponent, coefficient)
Packit 9a2dfb
import Data.Time (UTCTime(..))
Packit 9a2dfb
import Data.Time.Calendar (Day(..), toGregorian)
Packit 9a2dfb
import Data.Time.LocalTime
Packit 9a2dfb
import Data.Word (Word8)
Packit 9a2dfb
import qualified Data.HashMap.Strict as HMS
Packit 9a2dfb
import qualified Data.Text as T
Packit 9a2dfb
import qualified Data.Vector as V
Packit 9a2dfb
Packit 9a2dfb
#if MIN_VERSION_bytestring(0,10,4)
Packit 9a2dfb
import Data.Text.Encoding (encodeUtf8BuilderEscaped)
Packit 9a2dfb
#else
Packit 9a2dfb
import Data.Bits ((.&.))
Packit 9a2dfb
import Data.Text.Internal (Text(..))
Packit 9a2dfb
import Data.Text.Internal.Unsafe.Shift (shiftR)
Packit 9a2dfb
import Foreign.Ptr (minusPtr, plusPtr)
Packit 9a2dfb
import Foreign.Storable (poke)
Packit 9a2dfb
import qualified Data.ByteString.Builder.Internal as B
Packit 9a2dfb
import qualified Data.ByteString.Builder.Prim.Internal as BP
Packit 9a2dfb
import qualified Data.Text.Array as A
Packit 9a2dfb
import qualified Data.Text.Internal.Encoding.Utf16 as U16
Packit 9a2dfb
#endif
Packit 9a2dfb
Packit 9a2dfb
-- | Encode a JSON value to a "Data.ByteString" 'B.Builder'.
Packit 9a2dfb
--
Packit 9a2dfb
-- Use this function if you are encoding over the wire, or need to
Packit 9a2dfb
-- prepend or append further bytes to the encoded JSON value.
Packit 9a2dfb
encodeToBuilder :: Value -> Builder
Packit 9a2dfb
encodeToBuilder Null       = null_
Packit 9a2dfb
encodeToBuilder (Bool b)   = bool b
Packit 9a2dfb
encodeToBuilder (Number n) = scientific n
Packit 9a2dfb
encodeToBuilder (String s) = text s
Packit 9a2dfb
encodeToBuilder (Array v)  = array v
Packit 9a2dfb
encodeToBuilder (Object m) = object m
Packit 9a2dfb
Packit 9a2dfb
-- | Encode a JSON null.
Packit 9a2dfb
null_ :: Builder
Packit 9a2dfb
null_ = BP.primBounded (ascii4 ('n',('u',('l','l')))) ()
Packit 9a2dfb
Packit 9a2dfb
-- | Encode a JSON boolean.
Packit 9a2dfb
bool :: Bool -> Builder
Packit 9a2dfb
bool = BP.primBounded (BP.condB id (ascii4 ('t',('r',('u','e'))))
Packit 9a2dfb
                                   (ascii5 ('f',('a',('l',('s','e'))))))
Packit 9a2dfb
Packit 9a2dfb
-- | Encode a JSON array.
Packit 9a2dfb
array :: V.Vector Value -> Builder
Packit 9a2dfb
array v
Packit 9a2dfb
  | V.null v  = emptyArray_
Packit 9a2dfb
  | otherwise = B.char8 '[' <>
Packit 9a2dfb
                encodeToBuilder (V.unsafeHead v) <>
Packit 9a2dfb
                V.foldr withComma (B.char8 ']') (V.unsafeTail v)
Packit 9a2dfb
  where
Packit 9a2dfb
    withComma a z = B.char8 ',' <> encodeToBuilder a <> z
Packit 9a2dfb
Packit 9a2dfb
-- Encode a JSON object.
Packit 9a2dfb
object :: HMS.HashMap T.Text Value -> Builder
Packit 9a2dfb
object m = case HMS.toList m of
Packit 9a2dfb
    (x:xs) -> B.char8 '{' <> one x <> foldr withComma (B.char8 '}') xs
Packit 9a2dfb
    _      -> emptyObject_
Packit 9a2dfb
  where
Packit 9a2dfb
    withComma a z = B.char8 ',' <> one a <> z
Packit 9a2dfb
    one (k,v)     = text k <> B.char8 ':' <> encodeToBuilder v
Packit 9a2dfb
Packit 9a2dfb
-- | Encode a JSON string.
Packit 9a2dfb
text :: T.Text -> Builder
Packit 9a2dfb
text t = B.char8 '"' <> unquoted t <> B.char8 '"'
Packit 9a2dfb
Packit 9a2dfb
-- | Encode a JSON string, without enclosing quotes.
Packit 9a2dfb
unquoted :: T.Text -> Builder
Packit 9a2dfb
unquoted = encodeUtf8BuilderEscaped escapeAscii
Packit 9a2dfb
Packit 9a2dfb
-- | Add quotes surrounding a builder
Packit 9a2dfb
quote :: Builder -> Builder
Packit 9a2dfb
quote b = B.char8 '"' <> b <> B.char8 '"'
Packit 9a2dfb
Packit 9a2dfb
-- | Encode a JSON string.
Packit 9a2dfb
string :: String -> Builder
Packit 9a2dfb
string t = B.char8 '"' <> BP.primMapListBounded go t <> B.char8 '"'
Packit 9a2dfb
  where go = BP.condB (> '\x7f') BP.charUtf8 (c2w >$< escapeAscii)
Packit 9a2dfb
Packit 9a2dfb
escapeAscii :: BP.BoundedPrim Word8
Packit 9a2dfb
escapeAscii =
Packit 9a2dfb
    BP.condB (== c2w '\\'  ) (ascii2 ('\\','\\')) $
Packit 9a2dfb
    BP.condB (== c2w '\"'  ) (ascii2 ('\\','"' )) $
Packit 9a2dfb
    BP.condB (>= c2w '\x20') (BP.liftFixedToBounded BP.word8) $
Packit 9a2dfb
    BP.condB (== c2w '\n'  ) (ascii2 ('\\','n' )) $
Packit 9a2dfb
    BP.condB (== c2w '\r'  ) (ascii2 ('\\','r' )) $
Packit 9a2dfb
    BP.condB (== c2w '\t'  ) (ascii2 ('\\','t' )) $
Packit 9a2dfb
    BP.liftFixedToBounded hexEscape -- fallback for chars < 0x20
Packit 9a2dfb
  where
Packit 9a2dfb
    hexEscape :: BP.FixedPrim Word8
Packit 9a2dfb
    hexEscape = (\c -> ('\\', ('u', fromIntegral c))) BP.>$<
Packit 9a2dfb
        BP.char8 >*< BP.char8 >*< BP.word16HexFixed
Packit 9a2dfb
{-# INLINE escapeAscii #-}
Packit 9a2dfb
Packit 9a2dfb
c2w :: Char -> Word8
Packit 9a2dfb
c2w c = fromIntegral (ord c)
Packit 9a2dfb
Packit 9a2dfb
-- | Encode a JSON number.
Packit 9a2dfb
scientific :: Scientific -> Builder
Packit 9a2dfb
scientific s
Packit 9a2dfb
    | e < 0     = scientificBuilder s
Packit 9a2dfb
    | otherwise = B.integerDec (coefficient s * 10 ^ e)
Packit 9a2dfb
  where
Packit 9a2dfb
    e = base10Exponent s
Packit 9a2dfb
Packit 9a2dfb
emptyArray_ :: Builder
Packit 9a2dfb
emptyArray_ = BP.primBounded (ascii2 ('[',']')) ()
Packit 9a2dfb
Packit 9a2dfb
emptyObject_ :: Builder
Packit 9a2dfb
emptyObject_ = BP.primBounded (ascii2 ('{','}')) ()
Packit 9a2dfb
Packit 9a2dfb
ascii2 :: (Char, Char) -> BP.BoundedPrim a
Packit 9a2dfb
ascii2 cs = BP.liftFixedToBounded $ const cs BP.>$< BP.char7 >*< BP.char7
Packit 9a2dfb
{-# INLINE ascii2 #-}
Packit 9a2dfb
Packit 9a2dfb
ascii4 :: (Char, (Char, (Char, Char))) -> BP.BoundedPrim a
Packit 9a2dfb
ascii4 cs = BP.liftFixedToBounded $ const cs >$<
Packit 9a2dfb
    BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7
Packit 9a2dfb
{-# INLINE ascii4 #-}
Packit 9a2dfb
Packit 9a2dfb
ascii5 :: (Char, (Char, (Char, (Char, Char)))) -> BP.BoundedPrim a
Packit 9a2dfb
ascii5 cs = BP.liftFixedToBounded $ const cs >$<
Packit 9a2dfb
    BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7
Packit 9a2dfb
{-# INLINE ascii5 #-}
Packit 9a2dfb
Packit 9a2dfb
ascii6 :: (Char, (Char, (Char, (Char, (Char, Char))))) -> BP.BoundedPrim a
Packit 9a2dfb
ascii6 cs = BP.liftFixedToBounded $ const cs >$<
Packit 9a2dfb
    BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7
Packit 9a2dfb
{-# INLINE ascii6 #-}
Packit 9a2dfb
Packit 9a2dfb
ascii8 :: (Char, (Char, (Char, (Char, (Char, (Char, (Char, Char)))))))
Packit 9a2dfb
       -> BP.BoundedPrim a
Packit 9a2dfb
ascii8 cs = BP.liftFixedToBounded $ const cs >$<
Packit 9a2dfb
    BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7 >*<
Packit 9a2dfb
    BP.char7 >*< BP.char7 >*< BP.char7 >*< BP.char7
Packit 9a2dfb
{-# INLINE ascii8 #-}
Packit 9a2dfb
Packit 9a2dfb
day :: Day -> Builder
Packit 9a2dfb
day dd = encodeYear yr <>
Packit 9a2dfb
         BP.primBounded (ascii6 ('-',(mh,(ml,('-',(dh,dl)))))) ()
Packit 9a2dfb
  where (yr,m,d)    = toGregorian dd
Packit 9a2dfb
        !(T mh ml)  = twoDigits m
Packit 9a2dfb
        !(T dh dl)  = twoDigits d
Packit 9a2dfb
        encodeYear y
Packit 9a2dfb
            | y >= 1000 = B.integerDec y
Packit 9a2dfb
            | y >= 0    = BP.primBounded (ascii4 (padYear y)) ()
Packit 9a2dfb
            | y >= -999 = BP.primBounded (ascii5 ('-',padYear (- y))) ()
Packit 9a2dfb
            | otherwise = B.integerDec y
Packit 9a2dfb
        padYear y =
Packit 9a2dfb
            let (ab,c) = fromIntegral y `quotRem` 10
Packit 9a2dfb
                (a,b)  = ab `quotRem` 10
Packit 9a2dfb
            in ('0',(digit a,(digit b,digit c)))
Packit 9a2dfb
{-# INLINE day #-}
Packit 9a2dfb
Packit 9a2dfb
timeOfDay :: TimeOfDay -> Builder
Packit 9a2dfb
timeOfDay t = timeOfDay64 (toTimeOfDay64 t)
Packit 9a2dfb
{-# INLINE timeOfDay #-}
Packit 9a2dfb
Packit 9a2dfb
timeOfDay64 :: TimeOfDay64 -> Builder
Packit 9a2dfb
timeOfDay64 (TOD h m s)
Packit 9a2dfb
  | frac == 0 = hhmmss -- omit subseconds if 0
Packit 9a2dfb
  | otherwise = hhmmss <> BP.primBounded showFrac frac
Packit 9a2dfb
  where
Packit 9a2dfb
    hhmmss  = BP.primBounded (ascii8 (hh,(hl,(':',(mh,(ml,(':',(sh,sl)))))))) ()
Packit 9a2dfb
    !(T hh hl)  = twoDigits h
Packit 9a2dfb
    !(T mh ml)  = twoDigits m
Packit 9a2dfb
    !(T sh sl)  = twoDigits (fromIntegral real)
Packit 9a2dfb
    (real,frac) = s `quotRem` pico
Packit 9a2dfb
    showFrac = (\x -> ('.', x)) >$< (BP.liftFixedToBounded BP.char7 >*< trunc12)
Packit 9a2dfb
    trunc12 = (`quotRem` micro) >$<
Packit 9a2dfb
              BP.condB (\(_,y) -> y == 0) (fst >$< trunc6) (digits6 >*< trunc6)
Packit 9a2dfb
    digits6 = ((`quotRem` milli) . fromIntegral) >$< (digits3 >*< digits3)
Packit 9a2dfb
    trunc6  = ((`quotRem` milli) . fromIntegral) >$<
Packit 9a2dfb
              BP.condB (\(_,y) -> y == 0) (fst >$< trunc3) (digits3 >*< trunc3)
Packit 9a2dfb
    digits3 = (`quotRem` 10) >$< (digits2 >*< digits1)
Packit 9a2dfb
    digits2 = (`quotRem` 10) >$< (digits1 >*< digits1)
Packit 9a2dfb
    digits1 = BP.liftFixedToBounded (digit >$< BP.char7)
Packit 9a2dfb
    trunc3  = BP.condB (== 0) BP.emptyB $
Packit 9a2dfb
              (`quotRem` 100) >$< (digits1 >*< trunc2)
Packit 9a2dfb
    trunc2  = BP.condB (== 0) BP.emptyB $
Packit 9a2dfb
              (`quotRem` 10)  >$< (digits1 >*< trunc1)
Packit 9a2dfb
    trunc1  = BP.condB (== 0) BP.emptyB digits1
Packit 9a2dfb
Packit 9a2dfb
    pico       = 1000000000000 -- number of picoseconds  in 1 second
Packit 9a2dfb
    micro      =       1000000 -- number of microseconds in 1 second
Packit 9a2dfb
    milli      =          1000 -- number of milliseconds in 1 second
Packit 9a2dfb
Packit 9a2dfb
timeZone :: TimeZone -> Builder
Packit 9a2dfb
timeZone (TimeZone off _ _)
Packit 9a2dfb
  | off == 0  = B.char7 'Z'
Packit 9a2dfb
  | otherwise = BP.primBounded (ascii6 (s,(hh,(hl,(':',(mh,ml)))))) ()
Packit 9a2dfb
  where !s         = if off < 0 then '-' else '+'
Packit 9a2dfb
        !(T hh hl) = twoDigits h
Packit 9a2dfb
        !(T mh ml) = twoDigits m
Packit 9a2dfb
        (h,m)      = abs off `quotRem` 60
Packit 9a2dfb
{-# INLINE timeZone #-}
Packit 9a2dfb
Packit 9a2dfb
dayTime :: Day -> TimeOfDay64 -> Builder
Packit 9a2dfb
dayTime d t = day d <> B.char7 'T' <> timeOfDay64 t
Packit 9a2dfb
{-# INLINE dayTime #-}
Packit 9a2dfb
Packit 9a2dfb
utcTime :: UTCTime -> B.Builder
Packit 9a2dfb
utcTime (UTCTime d s) = dayTime d (diffTimeOfDay64 s) <> B.char7 'Z'
Packit 9a2dfb
{-# INLINE utcTime #-}
Packit 9a2dfb
Packit 9a2dfb
localTime :: LocalTime -> Builder
Packit 9a2dfb
localTime (LocalTime d t) = dayTime d (toTimeOfDay64 t)
Packit 9a2dfb
{-# INLINE localTime #-}
Packit 9a2dfb
Packit 9a2dfb
zonedTime :: ZonedTime -> Builder
Packit 9a2dfb
zonedTime (ZonedTime t z) = localTime t <> timeZone z
Packit 9a2dfb
{-# INLINE zonedTime #-}
Packit 9a2dfb
Packit 9a2dfb
data T = T {-# UNPACK #-} !Char {-# UNPACK #-} !Char
Packit 9a2dfb
Packit 9a2dfb
twoDigits :: Int -> T
Packit 9a2dfb
twoDigits a     = T (digit hi) (digit lo)
Packit 9a2dfb
  where (hi,lo) = a `quotRem` 10
Packit 9a2dfb
Packit 9a2dfb
digit :: Int -> Char
Packit 9a2dfb
digit x = chr (x + 48)
Packit 9a2dfb
Packit 9a2dfb
#if !(MIN_VERSION_bytestring(0,10,4))
Packit 9a2dfb
-- | Encode text using UTF-8 encoding and escape the ASCII characters using
Packit 9a2dfb
-- a 'BP.BoundedPrim'.
Packit 9a2dfb
--
Packit 9a2dfb
-- Use this function is to implement efficient encoders for text-based formats
Packit 9a2dfb
-- like JSON or HTML.
Packit 9a2dfb
{-# INLINE encodeUtf8BuilderEscaped #-}
Packit 9a2dfb
-- TODO: Extend documentation with references to source code in @blaze-html@
Packit 9a2dfb
-- or @aeson@ that uses this function.
Packit 9a2dfb
encodeUtf8BuilderEscaped :: BP.BoundedPrim Word8 -> Text -> B.Builder
Packit 9a2dfb
encodeUtf8BuilderEscaped be =
Packit 9a2dfb
    -- manual eta-expansion to ensure inlining works as expected
Packit 9a2dfb
    \txt -> B.builder (mkBuildstep txt)
Packit 9a2dfb
  where
Packit 9a2dfb
    bound = max 4 $ BP.sizeBound be
Packit 9a2dfb
Packit 9a2dfb
    mkBuildstep (Text arr off len) !k =
Packit 9a2dfb
        outerLoop off
Packit 9a2dfb
      where
Packit 9a2dfb
        iend = off + len
Packit 9a2dfb
Packit 9a2dfb
        outerLoop !i0 br@(B.BufferRange op0 ope)
Packit 9a2dfb
          | i0 >= iend       = k br
Packit 9a2dfb
          | outRemaining > 0 = goPartial (i0 + min outRemaining inpRemaining)
Packit 9a2dfb
          -- TODO: Use a loop with an integrated bound's check if outRemaining
Packit 9a2dfb
          -- is smaller than 8, as this will save on divisions.
Packit 9a2dfb
          | otherwise        = return $ B.bufferFull bound op0 (outerLoop i0)
Packit 9a2dfb
          where
Packit 9a2dfb
            outRemaining = (ope `minusPtr` op0) `div` bound
Packit 9a2dfb
            inpRemaining = iend - i0
Packit 9a2dfb
Packit 9a2dfb
            goPartial !iendTmp = go i0 op0
Packit 9a2dfb
              where
Packit 9a2dfb
                go !i !op
Packit 9a2dfb
                  | i < iendTmp = case A.unsafeIndex arr i of
Packit 9a2dfb
                      w | w <= 0x7F ->
Packit 9a2dfb
                            BP.runB be (fromIntegral w) op >>= go (i + 1)
Packit 9a2dfb
                        | w <= 0x7FF -> do
Packit 9a2dfb
                            poke8 0 $ (w `shiftR` 6) + 0xC0
Packit 9a2dfb
                            poke8 1 $ (w .&. 0x3f) + 0x80
Packit 9a2dfb
                            go (i + 1) (op `plusPtr` 2)
Packit 9a2dfb
                        | 0xD800 <= w && w <= 0xDBFF -> do
Packit 9a2dfb
                            let c = ord $ U16.chr2 w (A.unsafeIndex arr (i+1))
Packit 9a2dfb
                            poke8 0 $ (c `shiftR` 18) + 0xF0
Packit 9a2dfb
                            poke8 1 $ ((c `shiftR` 12) .&. 0x3F) + 0x80
Packit 9a2dfb
                            poke8 2 $ ((c `shiftR` 6) .&. 0x3F) + 0x80
Packit 9a2dfb
                            poke8 3 $ (c .&. 0x3F) + 0x80
Packit 9a2dfb
                            go (i + 2) (op `plusPtr` 4)
Packit 9a2dfb
                        | otherwise -> do
Packit 9a2dfb
                            poke8 0 $ (w `shiftR` 12) + 0xE0
Packit 9a2dfb
                            poke8 1 $ ((w `shiftR` 6) .&. 0x3F) + 0x80
Packit 9a2dfb
                            poke8 2 $ (w .&. 0x3F) + 0x80
Packit 9a2dfb
                            go (i + 1) (op `plusPtr` 3)
Packit 9a2dfb
                  | otherwise =
Packit 9a2dfb
                      outerLoop i (B.BufferRange op ope)
Packit 9a2dfb
                  where
Packit 9a2dfb
                    poke8 j v = poke (op `plusPtr` j) (fromIntegral v :: Word8)
Packit 9a2dfb
#endif