Skip to content

Commit 596499d

Browse files
authored
Merge pull request #906 from haskell/autolet
Use let-insertion to pre-encode TH-generated keys.
2 parents e8a5440 + d8497ca commit 596499d

File tree

8 files changed

+180
-40
lines changed

8 files changed

+180
-40
lines changed

aeson.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ library
8787
Data.Aeson.Internal.ByteString
8888
Data.Aeson.Internal.Functions
8989
Data.Aeson.Internal.Text
90+
Data.Aeson.Internal.TH
9091
Data.Aeson.Parser.Time
9192
Data.Aeson.Parser.Unescape
9293
Data.Aeson.Types.Class

benchmarks/aeson-benchmarks.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ library
7575
Data.Aeson.Internal.ByteString
7676
Data.Aeson.Internal.Functions
7777
Data.Aeson.Internal.Text
78+
Data.Aeson.Internal.TH
7879
Data.Aeson.Internal.Time
7980
Data.Aeson.Key
8081
Data.Aeson.KeyMap

benchmarks/bench/Auto/T/D.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{-# LANGUAGE TemplateHaskell #-}
2+
-- {-# OPTIONS_GHC -ddump-splices #-}
23

34
module Auto.T.D where
45

src/Data/Aeson/Encoding/Internal.hs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module Data.Aeson.Encoding.Internal
1616
, pairs
1717
, pair
1818
, pairStr
19+
, unsafePairSBS
1920
, pair'
2021
-- * Predicates
2122
, nullEncoding
@@ -65,6 +66,7 @@ import Prelude.Compat
6566

6667
import Data.Aeson.Types.Internal (Value, Key)
6768
import Data.ByteString.Builder (Builder, char7, toLazyByteString)
69+
import Data.ByteString.Short (ShortByteString)
6870
import qualified Data.Aeson.Key as Key
6971
import Data.Int
7072
import Data.Scientific (Scientific)
@@ -142,6 +144,19 @@ pairStr name val = pair' (string name) val
142144
pair' :: Encoding' Key -> Encoding -> Series
143145
pair' name val = Value $ retagEncoding $ retagEncoding name >< colon >< val
144146

147+
-- | A variant of a 'pair' where key is already encoded
148+
-- including the quotes and colon.
149+
--
150+
-- @
151+
-- 'pair' "foo" v = 'unsafePair' "\\"foo\\":" v
152+
-- @
153+
--
154+
-- @since 2.0.3.0
155+
--
156+
unsafePairSBS :: ShortByteString -> Encoding -> Series
157+
unsafePairSBS k v = Value $ retagEncoding $ Encoding (B.shortByteString k) >< v
158+
{-# INLINE unsafePairSBS #-}
159+
145160
instance Semigroup Series where
146161
Empty <> a = a
147162
a <> Empty = a

src/Data/Aeson/Internal/ByteString.hs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
{-# LANGUAGE BangPatterns #-}
22
{-# LANGUAGE CPP #-}
33
{-# LANGUAGE MagicHash #-}
4+
{-# LANGUAGE TemplateHaskellQuotes #-}
45
module Data.Aeson.Internal.ByteString (
56
mkBS,
67
withBS,
8+
liftSBS,
79
) where
810

911
import Data.ByteString.Internal (ByteString (..))
1012
import Data.Word (Word8)
1113
import Foreign.ForeignPtr (ForeignPtr)
14+
import Data.ByteString.Short (ShortByteString, fromShort)
15+
import GHC.Exts (Addr#, Ptr (Ptr))
16+
import Data.ByteString.Internal (accursedUnutterablePerformIO)
17+
import Data.ByteString.Short.Internal (createFromPtr)
18+
19+
import qualified Data.ByteString as BS
20+
import qualified Language.Haskell.TH.Lib as TH
21+
import qualified Language.Haskell.TH.Syntax as TH
1222

1323
#if !MIN_VERSION_bytestring(0,11,0)
1424
#if MIN_VERSION_base(4,10,0)
@@ -56,3 +66,22 @@ plusForeignPtr (ForeignPtr addr guts) (I# offset) = ForeignPtr (plusAddr# addr o
5666
#-}
5767
#endif
5868
#endif
69+
70+
liftSBS :: ShortByteString -> TH.ExpQ
71+
#if MIN_VERSION_template_haskell(2,16,0)
72+
liftSBS sbs = withBS bs $ \ptr len -> [| unsafePackLenLiteral |]
73+
`TH.appE` TH.litE (TH.integerL (fromIntegral len))
74+
`TH.appE` TH.litE (TH.BytesPrimL $ TH.Bytes ptr 0 (fromIntegral len))
75+
where
76+
bs = fromShort sbs
77+
#else
78+
liftSBS sbs = withBS bs $ \_ len -> [| unsafePackLenLiteral |]
79+
`TH.appE` TH.litE (TH.integerL (fromIntegral len))
80+
`TH.appE` TH.litE (TH.StringPrimL $ BS.unpack bs)
81+
where
82+
bs = fromShort sbs
83+
#endif
84+
85+
unsafePackLenLiteral :: Int -> Addr# -> ShortByteString
86+
unsafePackLenLiteral len addr# =
87+
accursedUnutterablePerformIO $ createFromPtr (Ptr addr#) len

src/Data/Aeson/Internal/TH.hs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{-# LANGUAGE ScopedTypeVariables #-}
2+
{-# LANGUAGE RankNTypes #-}
3+
module Data.Aeson.Internal.TH (
4+
letrecE,
5+
autoletE,
6+
) where
7+
8+
import Data.IORef (IORef, atomicModifyIORef, newIORef, readIORef)
9+
import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
10+
import Language.Haskell.TH (varE, ExpQ, Name, Q, newName, runIO)
11+
import System.IO.Unsafe (unsafeInterleaveIO)
12+
13+
import qualified Data.Map as Map
14+
import qualified Language.Haskell.TH.Syntax as TH
15+
16+
letrecE
17+
:: forall a. Ord a
18+
=> ((a -> ExpQ) -> (a -> ExpQ))
19+
-> ((a -> ExpQ) -> ExpQ)
20+
-> ExpQ
21+
letrecE f g = do
22+
ref <- runIO $ newIORef Map.empty
23+
expr <- g (loop ref)
24+
bindings <- runIO $ readIORef ref
25+
mkLet bindings expr
26+
where
27+
mkLet :: Map.Map a (Name, TH.Exp) -> TH.Exp -> ExpQ
28+
mkLet bindings expr = do
29+
return $ TH.LetE
30+
[ TH.ValD (TH.VarP name) (TH.NormalB code) []
31+
| (_, (name, code)) <- Map.toList bindings
32+
]
33+
expr
34+
35+
loop :: IORef (Map.Map a (Name, TH.Exp)) -> a -> ExpQ
36+
loop ref y = do
37+
memo <- runIO $ readIORef ref
38+
case Map.lookup y memo of
39+
Nothing -> do
40+
name <- newName $ "_let" ++ show (Map.size memo)
41+
_ <- mfix_ $ \yCode -> do
42+
runIO $ atomicModifyIORef ref $ \m -> (Map.insert y (name, yCode) m, ())
43+
f (loop ref) y
44+
varE name
45+
46+
Just (name, _) ->
47+
varE name
48+
49+
-- | Better 'letE'.
50+
autoletE
51+
:: Ord a
52+
=> (a -> ExpQ) -- ^ what bindings are
53+
-> ((a -> ExpQ) -> ExpQ) -- ^ expression with a function to generate bindings
54+
-> ExpQ
55+
autoletE f = letrecE (const f)
56+
57+
-------------------------------------------------------------------------------
58+
-- MonadFix Q is not always there
59+
-------------------------------------------------------------------------------
60+
61+
class MonadFix_ m where
62+
mfix_ :: (a -> m a) -> m a
63+
64+
instance MonadFix_ Q where
65+
mfix_ k = do
66+
m <- runIO newEmptyMVar
67+
ans <- runIO (unsafeInterleaveIO (takeMVar m))
68+
result <- k ans
69+
runIO (putMVar m result)
70+
pure result
71+
{-# INLINE mfix_ #-}

src/Data/Aeson/Internal/Text.hs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ module Data.Aeson.Internal.Text (
55
) where
66

77
import Data.ByteString (ByteString)
8-
import Data.Text (Text, empty)
9-
8+
import qualified Data.Text as T
109

1110
#if MIN_VERSION_text(2,0,0)
1211
import Data.Text.Array (Array (..))
13-
import Data.Text.Internal (Text (..))
12+
import qualified Data.Text.Internal as T (Text (..))
1413

1514
import qualified Data.ByteString.Short.Internal as SBS
1615

@@ -24,11 +23,11 @@ import qualified Data.Text.Encoding as TE
2423
-- | The input is assumed to contain only 7bit ASCII characters (i.e. @< 0x80@).
2524
-- We use TE.decodeLatin1 here because TE.decodeASCII is currently (text-1.2.4.0)
2625
-- deprecated and equal to TE.decodeUtf8, which is slower than TE.decodeLatin1.
27-
unsafeDecodeASCII :: ByteString -> Text
26+
unsafeDecodeASCII :: ByteString -> T.Text
2827

2928
#if MIN_VERSION_text(2,0,0)
30-
unsafeDecodeASCII bs = withBS bs $ \_fp len -> if len == 0 then empty else
31-
let !(SBS.SBS arr) = SBS.toShort bs in Text (ByteArray arr) 0 len
29+
unsafeDecodeASCII bs = withBS bs $ \_fp len -> if len == 0 then T.empty else
30+
let !(SBS.SBS arr) = SBS.toShort bs in T.Text (ByteArray arr) 0 len
3231

3332
#else
3433
unsafeDecodeASCII = TE.decodeLatin1

0 commit comments

Comments
 (0)