11{-# LANGUAGE DeriveAnyClass #-}
22{-# LANGUAGE FlexibleInstances #-}
3+ {-# LANGUAGE LambdaCase #-}
34{-# LANGUAGE ViewPatterns #-}
45
56module PlutusCore.Value (
@@ -10,8 +11,12 @@ module PlutusCore.Value (
1011 empty ,
1112 fromList ,
1213 toList ,
14+ toFlatList ,
1315 totalSize ,
1416 maxInnerSize ,
17+ insertCoin ,
18+ deleteCoin ,
19+ unionValue ,
1520) where
1621
1722import Codec.Serialise (Serialise )
@@ -22,8 +27,10 @@ import Data.ByteString.Base64 qualified as Base64
2227import Data.Hashable (Hashable )
2328import Data.IntMap.Strict (IntMap )
2429import Data.IntMap.Strict qualified as IntMap
30+ import Data.Map.Merge.Strict qualified as M
2531import Data.Map.Strict (Map )
2632import Data.Map.Strict qualified as Map
33+ import Data.Maybe
2734import Data.Text.Encoding qualified as Text
2835import GHC.Generics
2936
@@ -56,32 +63,50 @@ The map is guaranteed to not contain empty inner map or zero amount.
5663-}
5764unpack :: Value -> NestedMap
5865unpack (Value v _ _) = v
66+ {-# INLINE unpack #-}
5967
6068{-| Pack a map from (currency symbol, token name) to amount into a `Value`.
6169
6270The map will be filtered so that it does not contain empty inner map or zero amount.
6371-}
6472pack :: NestedMap -> Value
65- pack (normalize -> v) = Value v sizes size
73+ pack = pack' . normalize
74+ {-# INLINE pack #-}
75+
76+ -- | Like `pack` but does not normalize.
77+ pack' :: NestedMap -> Value
78+ pack' (normalize -> v) = Value v sizes size
6679 where
67- sizes = Map. foldr' (IntMap. alter (maybe (Just 1 ) (Just . (+ 1 ))) . Map. size) mempty v
68- size = Map. foldr' ((+) . Map. size) 0 v
80+ (sizes, size) = Map. foldl' alg (mempty , 0 ) v
81+ alg (ss, s) inner =
82+ ( IntMap. alter (maybe (Just 1 ) (Just . (+ 1 ))) (Map. size inner) ss
83+ , s + Map. size inner
84+ )
85+ {-# INLINEABLE pack' #-}
6986
7087{-| Total size, i.e., the number of distinct `(currency symbol, token name)` pairs
7188contained in the `Value`.
7289-}
7390totalSize :: Value -> Int
7491totalSize (Value _ _ size) = size
92+ {-# INLINE totalSize #-}
7593
7694-- | Size of the largest inner map.
7795maxInnerSize :: Value -> Int
7896maxInnerSize (Value _ sizes _) = maybe 0 fst (IntMap. lookupMax sizes)
97+ {-# INLINE maxInnerSize #-}
7998
8099empty :: Value
81100empty = Value mempty mempty 0
101+ {-# INLINE empty #-}
82102
83103toList :: Value -> [(ByteString , [(ByteString , Integer )])]
84104toList = Map. toList . Map. map Map. toList . unpack
105+ {-# INLINEABLE toList #-}
106+
107+ toFlatList :: Value -> [(ByteString , ByteString , Integer )]
108+ toFlatList (toList -> xs) = [(c, t, a) | (c, ys) <- xs, (t, a) <- ys]
109+ {-# INLINEABLE toFlatList #-}
85110
86111fromList :: [(ByteString , [(ByteString , Integer )])] -> Value
87112fromList =
@@ -91,8 +116,85 @@ fromList =
91116
92117normalize :: NestedMap -> NestedMap
93118normalize = Map. filter (not . Map. null ) . Map. map (Map. filter (/= 0 ))
119+ {-# INLINEABLE normalize #-}
94120
95121instance Pretty Value where
96122 pretty = pretty . fmap (bimap toText (fmap (first toText))) . toList
97123 where
98124 toText = Text. decodeLatin1 . Base64. encode
125+
126+ {-| \(O(\log \max(m, k))\), where \(m\) is the size of the outer map, and \(k\) is
127+ the size of the largest inner map.
128+ -}
129+ insertCoin :: ByteString -> ByteString -> Integer -> Value -> Value
130+ insertCoin currency token amt v@ (Value outer sizes size)
131+ | amt == 0 = deleteCoin currency token v
132+ | otherwise =
133+ let (mold, outer') = Map. alterF f currency outer
134+ (sizes', size') = case mold of
135+ Just old -> (updateSizes old (old + 1 ) sizes, size + 1 )
136+ Nothing -> (sizes, size)
137+ in Value outer' sizes' size'
138+ where
139+ f
140+ :: Maybe (Map ByteString Integer )
141+ -> ( Maybe Int -- Just (old size of inner map) if the total size grows by 1, otherwise Nothing
142+ , Maybe (Map ByteString Integer )
143+ )
144+ f = \ case
145+ Nothing -> (Just 0 , Just (Map. singleton token amt))
146+ Just inner ->
147+ let (isJust -> exists, inner') = Map. insertLookupWithKey (\ _ _ _ -> amt) token amt inner
148+ in (if exists then Nothing else Just (Map. size inner), Just inner')
149+ {-# INLINEABLE insertCoin #-}
150+
151+ -- TODO: implement properly
152+ deleteCoin :: ByteString -> ByteString -> Value -> Value
153+ deleteCoin currency token (Value outer _ _) =
154+ pack $ case Map. lookup currency outer of
155+ Nothing -> outer
156+ Just inner -> Map. insert currency (Map. delete token inner) outer
157+
158+ {-| The precise complexity is complicated, but an upper bound
159+ is \(O(n_{1} \log n_{2}) + O(m)\), where \(n_{1}\) is the total size of the smaller
160+ value, \(n_{2}\) is the total size of the bigger value, and \(m\) is the
161+ combined size of the outer maps.
162+ -}
163+ unionValue :: Value -> Value -> Value
164+ unionValue (unpack -> vA) (unpack -> vB) =
165+ pack' $
166+ M. merge
167+ M. preserveMissing
168+ M. preserveMissing
169+ ( M. zipWithMaybeMatched $ \ _ innerA innerB ->
170+ let inner =
171+ M. merge
172+ M. preserveMissing
173+ M. preserveMissing
174+ ( M. zipWithMaybeMatched $ \ _ x y ->
175+ let z = x + y in if z == 0 then Nothing else Just z
176+ )
177+ innerA
178+ innerB
179+ in if Map. null inner
180+ then Nothing
181+ else
182+ Just inner
183+ )
184+ vA
185+ vB
186+ {-# INLINEABLE unionValue #-}
187+
188+ -- | Decrement bucket @old@, and increment bucket @new@.
189+ updateSizes :: Int -> Int -> IntMap Int -> IntMap Int
190+ updateSizes old new = dec . inc
191+ where
192+ inc =
193+ if new == 0
194+ then id
195+ else IntMap. alter (maybe (Just 1 ) (Just . (+ 1 ))) new
196+ dec =
197+ if old == 0
198+ then id
199+ else IntMap. update (\ n -> if n <= 1 then Nothing else Just (n - 1 )) old
200+ {-# INLINEABLE updateSizes #-}
0 commit comments