1 import Base
2 import ../math/hash.bend as HS
3
4 # A String-keyed hash table with Base.Map's shape of API:
5 #
6 # new(a, V) -> HashMap
7 # set(a, V, m, key, x) -> HashMap insert or replace
8 # get(V, d, m, key) -> HashMap<&2, V> & V d when absent (V: Data)
9 # has(a, V, m, key) -> HashMap & Bool
10 # pop(a, V, m, key) -> HashMap & Maybe
11 # del(a, V, m, key) -> HashMap
12 # size(a, V, m) -> HashMap & U32
13 # keys(a, V, m) -> HashMap & List<&2, String> (bucket order)
14 #
15 # The table owns arrays, so it is a Type and is threaded through every call.
16 #
17 # Layout:
18 # - buckets: one packed Array of (check word, slot link) pairs; open
19 # addressing with linear probing over a power-of-two number of buckets,
20 # load <= 1/2, backward-shift deletion (no tombstones);
21 # - entries: a dense slot arena (key `ks`, value `vs`), with a free list of
22 # removed slots chained through `nx`. Growing the buckets rehashes U32
23 # pairs only; keys and values never move.
24 # A one-character key (code < 2^31) has check word code | 2^31: equal words
25 # mean equal keys, so its String is never hashed, compared or stored (its
26 # `ks` cell holds SNil and `keys` rebuilds it). Any other key has check word
27 # (hash & (2^31 - 1)) | 1, confirmed by a String comparison. Links are
28 # slot + 1 (0 = none). Every String walk is a tail loop, and nothing boxed is
29 # shared with `+`.
30
31 type HashMap is Type:
32 HM{n: U32, mask: U32, td: U32, fresh: U32, ssz: U32, sd: U32, free: U32, tab: Array, ks: Array, vs: Array>, nx: Array}
33
34 def size(a, -V: Kind(a), m: HashMap) -> HashMap & U32:
35 HM{+n, mask, td, fresh, sz, sd, free, tab, ks, vs, nx} = m
36 (HM{n, mask, td, fresh, sz, sd, free, tab, ks, vs, nx}, n)
37
38 # ---- words, hashing, equality ----
39
40 def tag() -> U32:
41 2147483648
42
43 def bnext(+i: U32, +mask: U32) -> U32:
44 U32.and(U32.inc(i), mask)
45
46 def link(+s: U32) -> U32:
47 U32.inc(s)
48
49 def slot(+l: U32) -> U32:
50 U32.sub(l, 1)
51
52 def short_word(+c: U32) -> U32:
53 U32.or(c, tag())
54
55 def long_word(+h: U32) -> U32:
56 U32.or(U32.and(h, 2147483647), 1)
57
58 def is_short(+w: U32) -> Bool:
59 U32.is_ge(w, tag())
60
61 def rev_onto(s: String, acc: String) -> String:
62 match s:
63 case SNil{}:
64 acc
65 case SCon{h, t}:
66 rev_onto(t, SCon{h, acc})
67
68 def hash_acc(s: String, +h: U32, acc: String) -> String & U32:
69 match s:
70 case SNil{}:
71 (rev_onto(acc, SNil{}), h)
72 case SCon{Chr{+c}, t}:
73 hash_acc(t, HS.mix(h, c), SCon{Chr{c}, acc})
74
75 def eq_acc(a: String, b: String, ra: String, rb: String, +e: Bool) -> (String & String) & Bool:
76 match a b:
77 case SNil{} SNil{}:
78 ((rev_onto(ra, SNil{}), rev_onto(rb, SNil{})), e)
79 case SNil{} SCon{h, t}:
80 ((rev_onto(ra, SNil{}), rev_onto(rb, SCon{h, t})), False{})
81 case SCon{h, t} SNil{}:
82 ((rev_onto(ra, SCon{h, t}), rev_onto(rb, SNil{})), False{})
83 case SCon{Chr{+x}, ta} SCon{Chr{+y}, tb}:
84 eq_acc(ta, tb, SCon{Chr{x}, ra}, SCon{Chr{y}, rb}, Bool.and(e, U32.is_eq(x, y)))
85
86 # String equality, handing both strings back.
87 def eq(a: String, b: String) -> (String & String) & Bool:
88 eq_acc(a, b, SNil{}, SNil{}, True{})
89
90 def copy_acc(s: String, ra: String, rb: String) -> String & String:
91 match s:
92 case SNil{}:
93 (rev_onto(ra, SNil{}), rev_onto(rb, SNil{}))
94 case SCon{Chr{+c}, t}:
95 copy_acc(t, SCon{Chr{c}, ra}, SCon{Chr{c}, rb})
96
97 # A deep copy (sharing a String with `+` would refcount every String).
98 def str_copy(s: String) -> String & String:
99 copy_acc(s, SNil{}, SNil{})
100
101 def lw_fin(r: String & U32) -> String & U32:
102 (k, +h) = r
103 (k, long_word(h))
104
105 def key_long(key: String) -> String & U32:
106 lw_fin(hash_acc(key, 0, SNil{}))
107
108 # ---- probing ----
109
110 type Found is Type:
111 FD{tab: Array, ks: Array, key: String, at: U32, link: U32}
112
113 # Long keys: a matching word is confirmed against the slot's stored String.
114 type Step is Type:
115 SEnd{tab: Array, ks: Array, key: String}
116 SHit{tab: Array, ks: Array, key: String, l: U32}
117 SNext{tab: Array, ks: Array, key: String}
118
119 def sk_pick(tab: Array, +l: U32, ks: Array, key: String, e: Bool) -> Step:
120 match e:
121 case True{}:
122 SHit{tab, ks, key, l}
123 case False{}:
124 SNext{tab, ks, key}
125
126 def sk_fin(tab: Array, +l: U32, ks: Array, r: (String & String) & Bool) -> Step:
127 ((key, k), e) = r
128 sk_pick(tab, l, Array.set(String, ks, slot(l), k), key, e)
129
130 def sk_cmp(tab: Array, +l: U32, key: String, r: Array & String) -> Step:
131 (ks, k) = r
132 sk_fin(tab, l, ks, eq(key, k))
133
134 def sk_same(tab: Array, ks: Array, +l: U32, key: String, same: Bool) -> Step:
135 match same:
136 case False{}:
137 SNext{tab, ks, key}
138 case True{}:
139 sk_cmp(tab, l, key, Array.swap(String, ks, slot(l), SNil{}))
140
141 def sk_l(ks: Array, key: String, +x: U32, +w: U32, r: Array & U32) -> Step:
142 (tab, +l) = r
143 sk_same(tab, ks, l, key, U32.is_eq(x, w))
144
145 def sk_empty(tab: Array, ks: Array, +i: U32, key: String, +w: U32, +x: U32, e: Bool) -> Step:
146 match e:
147 case True{}:
148 SEnd{tab, ks, key}
149 case False{}:
150 sk_l(ks, key, x, w, Array.get(U32, tab, U32.inc(U32.shl(i))))
151
152 def sk_w(ks: Array, +i: U32, key: String, +w: U32, r: Array & U32) -> Step:
153 (tab, +x) = r
154 sk_empty(tab, ks, i, key, w, x, U32.is_eq(x, 0))
155
156 def step(tab: Array, ks: Array, +i: U32, key: String, +w: U32) -> Step:
157 sk_w(ks, i, key, w, Array.get(U32, tab, U32.shl(i)))
158
159 def find(fuel: Nat, s: Step, +mask: U32, +w: U32, +i: U32) -> Found:
160 match fuel s:
161 case 0n SEnd{tab, ks, key}:
162 FD{tab, ks, key, i, 0}
163 case 0n SHit{tab, ks, key, +l}:
164 FD{tab, ks, key, i, l}
165 case 0n SNext{tab, ks, key}:
166 FD{tab, ks, key, i, 0}
167 case 1n+p SEnd{tab, ks, key}:
168 FD{tab, ks, key, i, 0}
169 case 1n+p SHit{tab, ks, key, +l}:
170 FD{tab, ks, key, i, l}
171 case 1n+p SNext{tab, ks, key}:
172 +j = bnext(i, mask)
173 find(p, step(tab, ks, j, key, w), mask, w, j)
174
175 def probe_lw(tab: Array, ks: Array, +mask: U32, r: String & U32) -> Found & U32:
176 (key, +w) = r
177 (find(U32.to_nat(U32.inc(mask)), step(tab, ks, HS.bucket(w, mask), key, w), mask, w, HS.bucket(w, mask)), w)
178
179 def probe_long(tab: Array, ks: Array, +mask: U32, key: String) -> Found & U32:
180 probe_lw(tab, ks, mask, key_long(key))
181
182 # One-character keys: the bucket words alone decide.
183 type QStep is Type:
184 QEnd{tab: Array}
185 QHit{tab: Array, l: U32}
186 QNext{tab: Array}
187
188 def qs_l(r: Array & U32) -> QStep:
189 (tab, +l) = r
190 QHit{tab, l}
191
192 def qs_same(tab: Array, +i: U32, same: Bool) -> QStep:
193 match same:
194 case True{}:
195 qs_l(Array.get(U32, tab, U32.inc(U32.shl(i))))
196 case False{}:
197 QNext{tab}
198
199 def qs_if(tab: Array, +i: U32, +w: U32, +x: U32, e: Bool) -> QStep:
200 match e:
201 case True{}:
202 QEnd{tab}
203 case False{}:
204 qs_same(tab, i, U32.is_eq(x, w))
205
206 def qs_w(+i: U32, +w: U32, r: Array & U32) -> QStep:
207 (tab, +x) = r
208 qs_if(tab, i, w, x, U32.is_eq(x, 0))
209
210 def qstep(tab: Array, +i: U32, +w: U32) -> QStep:
211 qs_w(i, w, Array.get(U32, tab, U32.shl(i)))
212
213 type QFound is Type:
214 QF{tab: Array, at: U32, link: U32}
215
216 def qfind(fuel: Nat, s: QStep, +mask: U32, +w: U32, +i: U32) -> QFound:
217 match fuel s:
218 case 0n QEnd{tab}:
219 QF{tab, i, 0}
220 case 0n QHit{tab, +l}:
221 QF{tab, i, l}
222 case 0n QNext{tab}:
223 QF{tab, i, 0}
224 case 1n+p QEnd{tab}:
225 QF{tab, i, 0}
226 case 1n+p QHit{tab, +l}:
227 QF{tab, i, l}
228 case 1n+p QNext{tab}:
229 +j = bnext(i, mask)
230 qfind(p, qstep(tab, j, w), mask, w, j)
231
232 def q_fin(ks: Array, +w: U32, r: QFound) -> Found & U32:
233 QF{tab, +at, +l} = r
234 (FD{tab, ks, SNil{}, at, l}, w)
235
236 def probe_short(tab: Array, ks: Array, +mask: U32, +c: U32, ok: Bool) -> Found & U32:
237 match ok:
238 case True{}:
239 q_fin(ks, short_word(c), qfind(U32.to_nat(U32.inc(mask)), qstep(tab, HS.bucket(short_word(c), mask), short_word(c)), mask, short_word(c), HS.bucket(short_word(c), mask)))
240 case False{}:
241 probe_long(tab, ks, mask, SCon{Chr{c}, SNil{}})
242
243 def probe_c(tab: Array, ks: Array, +mask: U32, +c: U32, t: String) -> Found & U32:
244 match t:
245 case SNil{}:
246 probe_short(tab, ks, mask, c, U32.is_lt(c, tag()))
247 case SCon{d, t2}:
248 probe_long(tab, ks, mask, SCon{Chr{c}, SCon{d, t2}})
249
250 # The bucket holding key (link != 0) or the empty bucket ending its probe,
251 # the key as it would be stored (SNil for a one-character key), and its word.
252 def probe(tab: Array, ks: Array, +mask: U32, key: String) -> Found & U32:
253 match key:
254 case SNil{}:
255 probe_long(tab, ks, mask, SNil{})
256 case SCon{Chr{+c}, t}:
257 probe_c(tab, ks, mask, c, t)
258
259 # ---- buckets: placement without comparison, growth, backward shift ----
260
261 def put_bucket(tab: Array, +i: U32, +w: U32, +l: U32) -> Array:
262 Array.set(U32, Array.set(U32, tab, U32.shl(i), w), U32.inc(U32.shl(i)), l)
263
264 type RStep is Type:
265 REmpty{tab: Array}
266 RFull{tab: Array}
267
268 def rs_if(tab: Array, e: Bool) -> RStep:
269 match e:
270 case True{}:
271 REmpty{tab}
272 case False{}:
273 RFull{tab}
274
275 def rs_w(r: Array & U32) -> RStep:
276 (tab, +x) = r
277 rs_if(tab, U32.is_eq(x, 0))
278
279 def rstep(tab: Array, +i: U32) -> RStep:
280 rs_w(Array.get(U32, tab, U32.shl(i)))
281
282 def ins_go(fuel: Nat, s: RStep, +mask: U32, +i: U32, +w: U32, +l: U32) -> Array:
283 match fuel s:
284 case 0n REmpty{tab}:
285 tab
286 case 0n RFull{tab}:
287 tab
288 case 1n+p REmpty{tab}:
289 put_bucket(tab, i, w, l)
290 case 1n+p RFull{tab}:
291 +j = bnext(i, mask)
292 ins_go(p, rstep(tab, j), mask, j, w, l)
293
294 # Put (w, l) in the first empty bucket of w's probe.
295 def ins_raw(tab: Array, +mask: U32, +w: U32, +l: U32) -> Array:
296 ins_go(U32.to_nat(U32.inc(mask)), rstep(tab, HS.bucket(w, mask)), mask, HS.bucket(w, mask), w, l)
297
298 type Mv is Type:
299 MV{old: Array, nt: Array}
300
301 def mv_l(+w: U32, nt: Array, +nmask: U32, r: Array & U32) -> Mv:
302 (old, +l) = r
303 MV{old, ins_raw(nt, nmask, w, l)}
304
305 def mv_if(+k: U32, old: Array, nt: Array, +nmask: U32, +w: U32, e: Bool) -> Mv:
306 match e:
307 case True{}:
308 MV{old, nt}
309 case False{}:
310 mv_l(w, nt, nmask, Array.get(U32, old, U32.inc(U32.shl(k))))
311
312 def mv_w(+k: U32, nt: Array, +nmask: U32, r: Array & U32) -> Mv:
313 (old, +w) = r
314 mv_if(k, old, nt, nmask, w, U32.is_eq(w, 0))
315
316 def mv_step(+k: U32, +nmask: U32, m: Mv) -> Mv:
317 MV{old, nt} = m
318 mv_w(k, nt, nmask, Array.get(U32, old, U32.shl(k)))
319
320 def mv_go(fuel: Nat, +k: U32, +nmask: U32, m: Mv) -> Mv:
321 match fuel:
322 case 0n:
323 m
324 case 1n+p:
325 mv_go(p, U32.inc(k), nmask, mv_step(k, nmask, m))
326
327 def mv_fin(m: Mv) -> Array:
328 MV{old, nt} = m
329 nt
330
331 def clear_bucket(tab: Array, +i: U32) -> Array:
332 put_bucket(tab, i, 0, 0)
333
334 type Sh is Type:
335 HEnd{tab: Array}
336 HMove{tab: Array, w: U32, l: U32}
337 HSkip{tab: Array}
338
339 def sh_mv(tab: Array, +w: U32, +l: U32, mv: Bool) -> Sh:
340 match mv:
341 case True{}:
342 HMove{tab, w, l}
343 case False{}:
344 HSkip{tab}
345
346 def sh_l(+w: U32, +mask: U32, +i: U32, +k: U32, r: Array & U32) -> Sh:
347 (tab, +l) = r
348 sh_mv(tab, w, l, U32.is_ge(U32.and(U32.sub(k, HS.bucket(w, mask)), mask), U32.and(U32.sub(k, i), mask)))
349
350 def sh_if(tab: Array, +mask: U32, +i: U32, +k: U32, +w: U32, e: Bool) -> Sh:
351 match e:
352 case True{}:
353 HEnd{tab}
354 case False{}:
355 sh_l(w, mask, i, k, Array.get(U32, tab, U32.inc(U32.shl(k))))
356
357 def sh_w(+mask: U32, +i: U32, +k: U32, r: Array & U32) -> Sh:
358 (tab, +w) = r
359 sh_if(tab, mask, i, k, w, U32.is_eq(w, 0))
360
361 def sh_step(tab: Array, +mask: U32, +i: U32, +k: U32) -> Sh:
362 sh_w(mask, i, k, Array.get(U32, tab, U32.shl(k)))
363
364 def shift(fuel: Nat, s: Sh, +mask: U32, +i: U32, +k: U32) -> Array:
365 match fuel s:
366 case 0n HEnd{tab}:
367 clear_bucket(tab, i)
368 case 0n HMove{tab, w, l}:
369 clear_bucket(tab, i)
370 case 0n HSkip{tab}:
371 clear_bucket(tab, i)
372 case 1n+p HEnd{tab}:
373 clear_bucket(tab, i)
374 case 1n+p HMove{tab, +w, +l}:
375 +k2 = bnext(k, mask)
376 shift(p, sh_step(put_bucket(tab, i, w, l), mask, k, k2), mask, k, k2)
377 case 1n+p HSkip{tab}:
378 +k2 = bnext(k, mask)
379 shift(p, sh_step(tab, mask, i, k2), mask, i, k2)
380
381 # Empty bucket i, closing the gap behind it.
382 def del_at(tab: Array, +mask: U32, +i: U32) -> Array:
383 shift(U32.to_nat(U32.inc(mask)), sh_step(tab, mask, i, bnext(i, mask)), mask, i, bnext(i, mask))
384
385 # ---- the slot arena ----
386
387 # A value array of 2^d vacant cells (Array.new needs Data values).
388 def vac(a, -V: Kind(a), d: Nat) -> Array>:
389 match d:
390 case 0n:
391 ALeaf{None{}}
392 case 1n+ +p:
393 ANode{vac(a, V, p), vac(a, V, p)}
394
395 def new(a, -V: Kind(a)) -> HashMap:
396 HM{0, 1, 2, 0, 1, 0, 0, Array.new(U32, 2n, 0), Array.new(String, 0n, ""), ALeaf{None{}}, Array.new(U32, 0n, 0)}
397
398 # ---- set ----
399
400 # Twice the buckets, every (word, link) pair re-placed.
401 def grow_tab(+mask: U32, +td: U32, tab: Array) -> Array:
402 mv_fin(mv_go(U32.to_nat(U32.inc(mask)), 0, U32.inc(U32.shl(mask)), MV{tab, Array.new(U32, U32.to_nat(U32.inc(td)), 0)}))
403
404 type Arena is Type:
405 AR{ks: Array, vs: Array>}
406
407 def store(a, -V: Kind(a), ks: Array, vs: Array>, +s: U32, key: String, x: V) -> Arena:
408 AR{Array.set(String, ks, s, key), Array.set(Maybe, vs, s, Some{x})}
409
410 def ins_fin(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, tab: Array, nx: Array, r: Arena) -> HashMap:
411 AR{ks, vs} = r
412 HM{U32.inc(n), mask, td, fresh, sz, sd, free, tab, ks, vs, nx}
413
414 # The new entry's slot s is chosen; its bucket is at (or, after growth, found anew).
415 def ins_slot(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, tab: Array, ks: Array, vs: Array>, nx: Array, +s: U32, +at: U32, +w: U32, key: String, x: V, over: Bool) -> HashMap:
416 match over:
417 case False{}:
418 ins_fin(a, V, n, mask, td, fresh, sz, sd, free, put_bucket(tab, at, w, link(s)), nx, store(a, V, ks, vs, s, key, x))
419 case True{}:
420 ins_fin(a, V, n, U32.inc(U32.shl(mask)), U32.inc(td), fresh, sz, sd, free, ins_raw(grow_tab(mask, td, tab), U32.inc(U32.shl(mask)), w, link(s)), nx, store(a, V, ks, vs, s, key, x))
421
422 def ins_free(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, tab: Array, ks: Array, vs: Array>, +s: U32, +at: U32, +w: U32, key: String, x: V, r: Array & U32) -> HashMap:
423 (nx, +nf) = r
424 ins_slot(a, V, n, mask, td, fresh, sz, sd, nf, tab, ks, vs, nx, s, at, w, key, x, U32.is_gt(U32.shl(U32.inc(n)), U32.inc(mask)))
425
426 def ins_fresh(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, tab: Array, ks: Array, vs: Array>, nx: Array, +at: U32, +w: U32, key: String, x: V, room: Bool) -> HashMap:
427 match room:
428 case True{}:
429 ins_slot(a, V, n, mask, td, U32.inc(fresh), sz, sd, 0, tab, ks, vs, nx, fresh, at, w, key, x, U32.is_gt(U32.shl(U32.inc(n)), U32.inc(mask)))
430 case False{}:
431 ins_slot(a, V, n, mask, td, U32.inc(fresh), U32.shl(sz), U32.inc(sd), 0, tab, ANode{ks, Array.new(String, U32.to_nat(sd), "")}, ANode{vs, vac(a, V, U32.to_nat(sd))}, ANode{nx, Array.new(U32, U32.to_nat(sd), 0)}, fresh, at, w, key, x, U32.is_gt(U32.shl(U32.inc(n)), U32.inc(mask)))
432
433 def ins_new(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, tab: Array, ks: Array, vs: Array>, nx: Array, +at: U32, +w: U32, key: String, x: V, none: Bool) -> HashMap:
434 match none:
435 case True{}:
436 ins_fresh(a, V, n, mask, td, fresh, sz, sd, tab, ks, vs, nx, at, w, key, x, U32.is_lt(fresh, sz))
437 case False{}:
438 ins_free(a, V, n, mask, td, fresh, sz, sd, tab, ks, vs, slot(free), at, w, key, x, Array.get(U32, nx, slot(free)))
439
440 def set_hit(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, tab: Array, ks: Array, vs: Array>, nx: Array, +at: U32, +l: U32, +w: U32, key: String, x: V, absent: Bool) -> HashMap:
441 match absent:
442 case False{}:
443 HM{n, mask, td, fresh, sz, sd, free, tab, ks, Array.set(Maybe, vs, slot(l), Some{x}), nx}
444 case True{}:
445 ins_new(a, V, n, mask, td, fresh, sz, sd, free, tab, ks, vs, nx, at, w, key, x, U32.is_eq(free, 0))
446
447 def set_f(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, vs: Array>, nx: Array, x: V, r: Found & U32) -> HashMap:
448 (fd, +w) = r
449 FD{tab, ks, key, +at, +l} = fd
450 set_hit(a, V, n, mask, td, fresh, sz, sd, free, tab, ks, vs, nx, at, l, w, key, x, U32.is_eq(l, 0))
451
452 # Insert key -> x, replacing any value already stored under key.
453 def set(a, -V: Kind(a), m: HashMap, key: String, x: V) -> HashMap:
454 HM{+n, +mask, +td, +fresh, +sz, +sd, +free, tab, ks, vs, nx} = m
455 set_f(a, V, n, mask, td, fresh, sz, sd, free, vs, nx, x, probe(tab, ks, mask, key))
456
457 # ---- get / has ----
458
459 def get_v(-V: Data, dflt: V, r: Array> & Maybe<&2, V>) -> Array> & V:
460 (vs, mv) = r
461 match mv:
462 case None{}:
463 (vs, dflt)
464 case Some{v}:
465 (vs, v)
466
467 def get_fin(-V: Data, +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, tab: Array, ks: Array, nx: Array, r: Array> & V) -> HashMap<&2, V> & V:
468 (vs, v) = r
469 (HM{n, mask, td, fresh, sz, sd, free, tab, ks, vs, nx}, v)
470
471 def get_hit(-V: Data, +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, tab: Array, ks: Array, vs: Array>, nx: Array, dflt: V, +l: U32, absent: Bool) -> HashMap<&2, V> & V:
472 match absent:
473 case True{}:
474 (HM{n, mask, td, fresh, sz, sd, free, tab, ks, vs, nx}, dflt)
475 case False{}:
476 get_fin(V, n, mask, td, fresh, sz, sd, free, tab, ks, nx, get_v(V, dflt, Array.get(Maybe<&2, V>, vs, slot(l))))
477
478 def get_f(-V: Data, +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, vs: Array>, nx: Array, dflt: V, r: Found & U32) -> HashMap<&2, V> & V:
479 (fd, w) = r
480 FD{tab, ks, key, at, +l} = fd
481 get_hit(V, n, mask, td, fresh, sz, sd, free, tab, ks, vs, nx, dflt, l, U32.is_eq(l, 0))
482
483 # The value stored under key, or dflt (values are copied out, so V: Data).
484 def get(-V: Data, dflt: V, m: HashMap<&2, V>, key: String) -> HashMap<&2, V> & V:
485 HM{+n, +mask, +td, +fresh, +sz, +sd, +free, tab, ks, vs, nx} = m
486 get_f(V, n, mask, td, fresh, sz, sd, free, vs, nx, dflt, probe(tab, ks, mask, key))
487
488 def has_f(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, vs: Array>, nx: Array, r: Found & U32) -> HashMap & Bool:
489 (fd, w) = r
490 FD{tab, ks, key, at, +l} = fd
491 (HM{n, mask, td, fresh, sz, sd, free, tab, ks, vs, nx}, U32.is_ne(l, 0))
492
493 def has(a, -V: Kind(a), m: HashMap, key: String) -> HashMap & Bool:
494 HM{+n, +mask, +td, +fresh, +sz, +sd, +free, tab, ks, vs, nx} = m
495 has_f(a, V, n, mask, td, fresh, sz, sd, free, vs, nx, probe(tab, ks, mask, key))
496
497 # ---- pop / del ----
498
499 def drop_key(ks: Array, +s: U32, short: Bool) -> Array:
500 match short:
501 case True{}:
502 ks
503 case False{}:
504 Array.set(String, ks, s, "")
505
506 def pop_v(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, tab: Array, ks: Array, nx: Array, +at: U32, +s: U32, +w: U32, r: Array> & Maybe) -> HashMap & Maybe:
507 (vs, v) = r
508 (HM{U32.sub(n, 1), mask, td, fresh, sz, sd, link(s), del_at(tab, mask, at), drop_key(ks, s, is_short(w)), vs, Array.set(U32, nx, s, free)}, v)
509
510 def pop_hit(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, tab: Array, ks: Array, vs: Array>, nx: Array, +at: U32, +l: U32, +w: U32, absent: Bool) -> HashMap & Maybe:
511 match absent:
512 case True{}:
513 (HM{n, mask, td, fresh, sz, sd, free, tab, ks, vs, nx}, None{})
514 case False{}:
515 pop_v(a, V, n, mask, td, fresh, sz, sd, free, tab, ks, nx, at, slot(l), w, Array.swap(Maybe, vs, slot(l), None{}))
516
517 def pop_f(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, vs: Array>, nx: Array, r: Found & U32) -> HashMap & Maybe:
518 (fd, +w) = r
519 FD{tab, ks, key, +at, +l} = fd
520 pop_hit(a, V, n, mask, td, fresh, sz, sd, free, tab, ks, vs, nx, at, l, w, U32.is_eq(l, 0))
521
522 # Remove key, answering its value.
523 def pop(a, -V: Kind(a), m: HashMap, key: String) -> HashMap & Maybe:
524 HM{+n, +mask, +td, +fresh, +sz, +sd, +free, tab, ks, vs, nx} = m
525 pop_f(a, V, n, mask, td, fresh, sz, sd, free, vs, nx, probe(tab, ks, mask, key))
526
527 def del_drop(a, -V: Kind(a), r: HashMap & Maybe) -> HashMap:
528 (m, v) = r
529 m
530
531 def del(a, -V: Kind(a), m: HashMap, key: String) -> HashMap:
532 del_drop(a, V, pop(a, V, m, key))
533
534 # ---- keys ----
535
536 type Walk is Type:
537 WK{tab: Array, ks: Array, acc: List<&2, String>}
538
539 def wk_long(tab: Array, +s: U32, acc: List<&2, String>, ks: Array, kk: String & String) -> Walk:
540 (k1, k2) = kk
541 WK{tab, Array.set(String, ks, s, k1), Con{k2, acc}}
542
543 def wk_copy(tab: Array, +s: U32, acc: List<&2, String>, r: Array & String) -> Walk:
544 (ks, key) = r
545 wk_long(tab, s, acc, ks, str_copy(key))
546
547 def wk_ll(ks: Array, acc: List<&2, String>, r: Array & U32) -> Walk:
548 (tab, +l) = r
549 wk_copy(tab, slot(l), acc, Array.swap(String, ks, slot(l), SNil{}))
550
551 def wk_kind(tab: Array, ks: Array, +k: U32, acc: List<&2, String>, +w: U32, short: Bool) -> Walk:
552 match short:
553 case True{}:
554 WK{tab, ks, Con{SCon{Chr{U32.and(w, 2147483647)}, SNil{}}, acc}}
555 case False{}:
556 wk_ll(ks, acc, Array.get(U32, tab, U32.inc(U32.shl(k))))
557
558 def wk_if(tab: Array, ks: Array, +k: U32, acc: List<&2, String>, +w: U32, e: Bool) -> Walk:
559 match e:
560 case True{}:
561 WK{tab, ks, acc}
562 case False{}:
563 wk_kind(tab, ks, k, acc, w, is_short(w))
564
565 def wk_w(ks: Array, +k: U32, acc: List<&2, String>, r: Array & U32) -> Walk:
566 (tab, +w) = r
567 wk_if(tab, ks, k, acc, w, U32.is_eq(w, 0))
568
569 def wk_step(+k: U32, w: Walk) -> Walk:
570 WK{tab, ks, acc} = w
571 wk_w(ks, k, acc, Array.get(U32, tab, U32.shl(k)))
572
573 def wk_go(fuel: Nat, +k: U32, w: Walk) -> Walk:
574 match fuel:
575 case 0n:
576 w
577 case 1n+p:
578 wk_go(p, U32.sub(k, 1), wk_step(k, w))
579
580 def keys_fin(a, -V: Kind(a), +n: U32, +mask: U32, +td: U32, +fresh: U32, +sz: U32, +sd: U32, +free: U32, vs: Array>, nx: Array, w: Walk) -> HashMap & List<&2, String>:
581 WK{tab, ks, acc} = w
582 (HM{n, mask, td, fresh, sz, sd, free, tab, ks, vs, nx}, acc)
583
584 # Every key, in bucket order.
585 def keys(a, -V: Kind(a), m: HashMap) -> HashMap & List<&2, String>:
586 HM{+n, +mask, +td, +fresh, +sz, +sd, +free, tab, ks, vs, nx} = m
587 keys_fin(a, V, n, mask, td, fresh, sz, sd, free, vs, nx, wk_go(U32.to_nat(U32.inc(mask)), mask, WK{tab, ks, Nil{}}))