-
Notifications
You must be signed in to change notification settings - Fork 5
/
hamt.maps.pas
426 lines (349 loc) · 13.6 KB
/
hamt.maps.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
{
Copyright (C) 2018 Benito van der Zander (BeniBela)
benito@benibela.de
www.benibela.de
This file is distributed under under the same license as Lazarus and the LCL itself:
This file is distributed under the Library GNU General Public License
with the following modification:
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent modules,
and to copy and distribute the resulting executable under terms of your choice,
provided that you also meet, for each linked independent module, the terms
and conditions of the license of that module. An independent module is a
module which is not derived from or based on this library. If you modify this
library, you may extend this exception to your version of the library, but
you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version.
}
{**
@abstract(Mutable and immutable persistent maps as hash array mapped trie (HAMT))
Public generic classes:
* TReadOnlyMap
* TMutableMap
* TImmutableMap
Public specialized classes:
* TMutableMapStringString
* TMutableMapStringObject
* TImmutableMapStringString
* TImmutableMapStringObject
}
unit hamt.maps;
{$mode objfpc}{$H+}{$ModeSwitch autoderef}{$ModeSwitch advancedrecords}
interface
uses
sysutils, hamt.internals;
type
THAMTTypeInfo = hamt.internals.THAMTTypeInfo;
generic THAMTPairInfo<TKey, TValue, TInfo> = record
type
TPair = packed record
key: TKey;
value: TValue;
end;
TValueSizeEquivalent = packed array[1..sizeof(TValue)] of byte;
class function hash(const p: TPair): THAMTHash; static; inline;
class function equal(const p, q: TPair): boolean; static; inline;
class procedure addRef(var p: TPair); static; inline;
class procedure release(var p: TPair); static; inline;
class procedure assignEqual(var p: TPair; const q: TPair); static; inline;
class function toString(const p: TPair): string; static; inline;
end;
//** @abstract(Generic read-only map)
//**
//** The data in this map can be read, but there are no public methods to modify it.
generic TReadOnlyMap<TKey, TValue, TInfo> = class(specialize TReadOnlyCustomSet<specialize THAMTPairInfo<TKey, TValue, TInfo>.TPair, specialize THAMTPairInfo<TKey, TValue, TInfo>>)
type
PKey = ^TKey;
PValue = ^TValue;
TKeySizeEquivalent = packed array[1..sizeof(TKey)] of byte;
TValueSizeEquivalent = packed array[1..sizeof(TValue)] of byte;
PPair = THAMTNode.PItem;
private
function forceInclude(const key: TKey; const value: TValue; allowOverride: boolean): boolean; inline;
function forceExclude(const key: TKey): boolean; inline;
protected
function find(const key: TKey): PPair; inline;
class procedure raiseKeyError(const message: string; const key: TKey); static;
public
//** Creates an empty map
constructor Create;
//** Creates a map equal to other. No data is copied, till either map is modified (copy-on-write).
constructor Create(other: specialize TReadOnlyCustomSet<THAMTNode.TItem, THAMTNode.TInfo>);
//** Returns if the map contains a certain key
function contains(const key:TKey): boolean; inline;
//** Returns the value for a certain key, or default value def if the map does not contain the key
function get(const key: TKey; const def: TValue): TValue; inline;
//** Returns the value for a certain key, or default(TValue) if the map does not contain the key
function getOrDefault(const key: TKey): TValue; inline;
//** Returns the value for a certain key, or raises an exception if the map does not contain the key
function get(const key: TKey): TValue; inline;
//** Default parameter, so you can read elements with @code(map[key])
property items[key: TKey]: TValue read get; default;
end;
{** @abstract(Generic mutable map)
Data in this map can be read (see ancestor TReadOnlyMap) and modified.
Example:
@longcode(#
type TMutableMapStringString = specialize TMutableMap<string, string, THAMTTypeInfo>;
var map: TMutableMapStringString;
p: TMutableMapStringString.PPair;
begin
map := TMutableMapStringString.create;
map.Insert('hello', 'world');
map.insert('foo', 'bar');
map['abc'] := 'def';
writeln(map['hello']); // world
writeln(map.get('foo')); // bar
writeln(map.get('abc', 'default')); // def
//enumerate all
for p in map do
writeln(p^.key, ': ', p^.value);
map.free;
end.
#)
}
generic TMutableMap<TKey, TValue, TInfo> = class(specialize TReadOnlyMap<TKey, TValue, TInfo>)
protected
procedure includeItem(const key: TKey; const value: TValue); inline;
function getRef(const key: TKey): PValue;
public
//** Inserts a (key, value) pair, if allowOverride is true or key did not exist
//** @returns If the map did not contain key
function include(const key: TKey; const value: TValue; allowOverride: boolean = true): boolean; inline;
//** Removes a (key, value) pair
//** @returns If the map did contain key
function exclude(const key: TKey): boolean; inline;
//** Inserts a (key, value) pair, or raises an exception if the map did not contain key
procedure insert(const key: TKey; const value: TValue); inline;
//** Removes key (and the associated value), or raises an exception if the map did not contain key
procedure remove(const key:TKey); inline;
//** Removes everything from the map;
procedure clear;
//** Creates a new map equal to self. No data is copied, till either map is modified (copy-on-write).
function clone: TMutableMap;
//** Default parameter, so you can read or write elements with @code(map[key])
property items[key: TKey]: TValue read get write includeItem; default;
//** Pointer to value
property mutable[key: TKey]: PValue read getRef;
end;
{** @abstract(Generic immutable map)
Data in this map can be read (see ancestor TReadOnlyMap) and modified by creating new maps.
Example: @longcode(#
type TImmutableMapStringString = specialize TImmutableMap<string, string, THAMTTypeInfo>;
var map, map2, map3: TImmutableMapStringString;
p: TImmutableMapStringString.PPair;
begin
map := TImmutableMapStringString.create;
map2 := map.Insert('hello', 'world');
map3 := map2.insert('foo', 'bar');
writeln(map.get('hello', 'default')); // default
writeln(map.get('foo', 'default')); // default
writeln(map2.get('hello')); // world
writeln(map2.get('foo', 'default')); // default
writeln(map3['hello']); // world
writeln(map3['foo']); // bar
//enumerate all
for p in map3 do
writeln(p^.key, ': ', p^.value);
map.free;
map2.free;
map3.free;
end.
#)
}
generic TImmutableMap<TKey, TValue, TInfo> = class(specialize TReadOnlyMap<TKey, TValue, TInfo>)
public
//** Creates a new map containing (key, value). If the map does not contain key or allowOverride is true, the value associated with the key is @code(value), otherwise the value is unchanged.
//** @returns The new map
function include(const key: TKey; const value: TValue; allowOverride: boolean = true): TImmutableMap; inline; overload;
//** Creates a new map without key and its associated value
//** @returns The new map
function exclude(const key: TKey): TImmutableMap; inline;
//** Creates a new map containing (key, value), or raises an exception if the map already contained key
//** @returns The new map
function insert(const key: TKey; const value: TValue): TImmutableMap; inline;
//** Creates a new map without key and its associated value, or raises an exception if the map did not contain key
//** @returns The new map
function remove(const key:TKey): TImmutableMap; inline;
//** Creates a new map equal to self. No data is copied, till either map is modified (copy-on-write).
function clone: TImmutableMap;
end;
//** @abstract(A TMutableMap mapping string keys to string values.)
//** The map handles reference counting and freeing of the strings.
TMutableMapStringString = specialize TMutableMap<string, string, THAMTTypeInfo>;
//** @abstract(A TMutableMap mapping string keys to TObject values.)
//** The map handles reference counting and freeing of the string keys, but the objects are neither changed nor freed.
TMutableMapStringObject = specialize TMutableMap<string, TObject, THAMTTypeInfo>;
//** @abstract(A TImmutableMap mapping string keys to string values.)
//** The map handles reference counting and freeing of the strings.
TImmutableMapStringString = specialize TImmutableMap<string, string, THAMTTypeInfo>;
//** @abstract(A TImmutableMap mapping string keys to TObject values.)
//** The map handles reference counting and freeing of the string keys, but the objects are neither changed nor freed.
TImmutableMapStringObject = specialize TImmutableMap<string, TObject, THAMTTypeInfo>;
implementation
class function THAMTPairInfo.hash(const p: TPair): THAMTHash;
begin
result := TInfo.hash(p.key);
end;
class function THAMTPairInfo.equal(const p, q: TPair): boolean;
begin
result := TInfo.equal(p.key, q.key);
end;
class procedure THAMTPairInfo.addRef(var p: TPair);
begin
with p do begin
TInfo.addRef(key);
TInfo.addRef(value);
end;
end;
class procedure THAMTPairInfo.release(var p: TPair);
begin
with p do begin
TInfo.release(key);
TInfo.release(value);
end;
end;
class procedure THAMTPairInfo.assignEqual(var p: TPair; const q: TPair);
begin
TInfo.release(p.value);
TValueSizeEquivalent(p.value) := TValueSizeEquivalent(q.value);
TInfo.addRef(p.value);
end;
class function THAMTPairInfo.toString(const p: TPair): string;
begin
result := TInfo.toString(p.key);
end;
constructor TReadOnlyMap.Create;
begin
froot := THAMTNode.allocateEmpty;
fcount := 0;
end;
constructor TReadOnlyMap.Create(other: specialize TReadOnlyCustomSet<THAMTNode.TItem, THAMTNode.TInfo>);
begin
fcount := other.fcount;
froot := other.froot;
InterLockedIncrement(froot.refCount);
end;
function TReadOnlyMap.forceInclude(const key: TKey; const value: TValue; allowOverride: boolean): boolean;
var tempPair: packed array[1..sizeof(TKey)+sizeof(TValue)] of byte;
begin
TKeySizeEquivalent(PPair(@tempPair).key) := TKeySizeEquivalent(key);
TValueSizeEquivalent(PPair(@tempPair).value) := TValueSizeEquivalent(value);
result := THAMTNode.include(@froot, PPair(@tempPair)^, allowOverride);
if result then inc(fcount);
end;
function TReadOnlyMap.forceExclude(const key: TKey): boolean;
begin
result := THAMTNode.exclude(@froot, PPair(@key)^ ); //this cast should work, because key is the first element of TPair
if result then dec(fcount);
end;
function TReadOnlyMap.find(const key: TKey): PPair;
begin
result := froot.find( PPair(@key)^ ); //this cast should work, because key is the first element of TPair
end;
class procedure TReadOnlyMap.raiseKeyError(const message: string; const key: TKey);
var s: string;
begin
s := TInfo.toString(key);
raise EHAMTException.Create(Format(message, [s]) );
end;
function TReadOnlyMap.contains(const key: TKey): boolean;
begin
result := find(key) <> nil;
end;
function TReadOnlyMap.get(const key: TKey; const def: TValue): TValue;
var
pair: PPair;
begin
pair := find(key);
if pair = nil then result := def
else result := pair.value;
end;
function TReadOnlyMap.getOrDefault(const key: TKey): TValue;
var
pair: PPair;
begin
pair := find(key);
if pair = nil then result := default(TValue)
else result := pair.value;
end;
function TReadOnlyMap.get(const key: TKey): TValue;
var
pair: PPair;
begin
pair := find(key);
if pair = nil then
raiseKeyError(rsMissingKey, key);
result := pair.value;
end;
procedure TMutableMap.includeItem(const key: TKey; const value: TValue);
begin
forceInclude(key, value, true);
end;
function TMutableMap.getRef(const key: TKey): PValue;
var
pair: PPair;
begin
pair := THAMTNode.findAndUnique(@froot, PPair(@key)^ ); //this cast should work, because key is the first element of TPair
if pair = nil then
raiseKeyError(rsMissingKey, key);
result := @pair.value;
end;
function TMutableMap.include(const key: TKey; const value: TValue; allowOverride: boolean): boolean;
begin
result := forceInclude(key, value, allowOverride);
end;
function TMutableMap.exclude(const key: TKey): boolean;
begin
result := forceExclude(key);
end;
procedure TMutableMap.insert(const key: TKey; const value: TValue);
begin
if not forceInclude(key, value, false) then raiseKeyError(rsDuplicateKey, key);
end;
procedure TMutableMap.remove(const key:TKey);
begin
if not forceExclude(key) then raiseKeyError(rsMissingKey, key);
end;
procedure TMutableMap.clear;
begin
THAMTNode.decrementRefCount(froot);
froot := THAMTNode.allocateEmpty;
fcount := 0;
end;
function TMutableMap.clone: TMutableMap;
begin
result := TMutableMap.Create(self);
end;
function TImmutableMap.include(const key: TKey; const value: TValue; allowOverride: boolean): TImmutableMap; inline;
begin
result := TImmutableMap.Create(self);
result.forceInclude(key, value, allowOverride)
end;
function TImmutableMap.exclude(const key: TKey): TImmutableMap; inline;
begin
result := TImmutableMap.Create(self);
result.forceExclude(key);
end;
function TImmutableMap.insert(const key: TKey; const value: TValue): TImmutableMap; inline;
begin
result := TImmutableMap.Create(self);
if not result.forceInclude(key, value, false) then begin
result.free;
raiseKeyError(rsDuplicateKey, key);
end;
end;
function TImmutableMap.remove(const key:TKey): TImmutableMap; inline;
begin
result := TImmutableMap.Create(self);
if not result.forceExclude(key) then begin
result.free;
raiseKeyError(rsMissingKey, key);
end;
end;
function TImmutableMap.clone: TImmutableMap;
begin
result := TImmutableMap.Create(self);
end;
end.