164 lines
4.6 KiB
Nim
164 lines
4.6 KiB
Nim
# The hash table implementation for string interning and globals
|
|
|
|
import bitops
|
|
|
|
const tableMaxLoad = 0.75
|
|
const tableInitSize = 8
|
|
|
|
type
|
|
EntryStatus = enum
|
|
esNil, esAlive, esTombstone
|
|
|
|
Entry[U, V] = object
|
|
entryStatus: EntryStatus
|
|
key: U
|
|
value: V
|
|
|
|
Table*[U, V] = object
|
|
count: int
|
|
cap: int
|
|
entries: ptr UncheckedArray[Entry[U, V]]
|
|
|
|
NdTable*[U, V] = ptr Table[U, V]
|
|
|
|
proc newTable*[U, V]: Table[U, V] =
|
|
result.cap = 0
|
|
result.count = 0
|
|
|
|
proc newTable*[U, V](initcap: int): Table[U, V] =
|
|
result.cap = initcap
|
|
result.count = 0
|
|
result.entries = cast[ptr UncheckedArray[Entry[U, V]]](alloc0(sizeof(Entry[U, V]) * initcap))
|
|
|
|
proc newNdTable*[U, V]: NdTable[U, V] =
|
|
result = cast[NdTable[U, V]](alloc(sizeof(Table[U, V])))
|
|
result[].cap = 0
|
|
result[].count = 0
|
|
result[].entries = nil # must be set, because dealloc will be ran on it otherwise
|
|
|
|
proc free*[U, V](tbl: var Table[U, V]) =
|
|
if tbl.entries != nil:
|
|
dealloc(tbl.entries)
|
|
|
|
proc findEntry[U, V](entries: ptr UncheckedArray[Entry[U, V]], cap: int, key: U): ptr Entry[U, V] =
|
|
mixin fnv1a, equal
|
|
var index = key.fnv1a().bitand(cap - 1)
|
|
var tombstone: ptr Entry[U, V] = nil
|
|
while true:
|
|
let entry: ptr Entry[U, V] = entries[index].addr # TODO: check the performance impact of this line
|
|
case entry[].entryStatus:
|
|
of esNil:
|
|
return if tombstone != nil: tombstone else: entry
|
|
of esTombstone:
|
|
if tombstone == nil:
|
|
tombstone = entry
|
|
of esAlive:
|
|
if entry[].key.equal(key):
|
|
return entry
|
|
index = (index + 1).bitand(cap - 1)
|
|
|
|
proc grow[U, V](tbl: var Table[U, V]): int =
|
|
## Calculates the new capacity
|
|
if tbl.cap > 0:
|
|
tbl.cap * 2
|
|
else:
|
|
tableInitSize
|
|
|
|
proc adjustCapacity[U, V](tbl: var Table[U, V], newcap: int) =
|
|
let entries: ptr UncheckedArray[Entry[U, V]] = cast[ptr UncheckedArray[Entry[U, V]]](alloc0(newcap * sizeof(Entry[U, V])))
|
|
tbl.count = 0
|
|
|
|
if tbl.entries != nil:
|
|
for i in countup(0, tbl.cap-1):
|
|
let entry = tbl.entries[i]
|
|
if entry.entryStatus == esAlive:
|
|
var dest = findEntry(entries, newcap, entry.key)
|
|
dest[].key = entry.key
|
|
dest[].value = entry.value
|
|
dest[].entryStatus = esAlive
|
|
tbl.count.inc
|
|
|
|
dealloc(tbl.entries)
|
|
|
|
tbl.entries = entries
|
|
tbl.cap = newcap
|
|
|
|
proc newNdTable*[U, V](initCap: int): NdTable[U, V] =
|
|
result = newNdTable[U, V]()
|
|
var initCapReal = tableInitSize
|
|
while initCapReal < initCap:
|
|
initCapReal *= 2
|
|
result[].adjustCapacity(initCapReal)
|
|
|
|
proc tableSet*[U, V](tbl: var Table[U, V], key: U, val: V): bool =
|
|
## Returns false if new value is entered
|
|
## True if the value entered already existed and is overwritten
|
|
if tbl.count + 1 > int(tbl.cap.float * tableMaxLoad):
|
|
let cap = tbl.grow()
|
|
tbl.adjustCapacity(cap)
|
|
let entry: ptr Entry[U, V] = findEntry(tbl.entries, tbl.cap, key)
|
|
let status = entry[].entryStatus
|
|
if status == esNil:
|
|
tbl.count.inc
|
|
elif status == esAlive:
|
|
result = true
|
|
entry[].key = key
|
|
entry[].value = val
|
|
entry[].entryStatus = esAlive
|
|
|
|
proc tableGet*[U, V](tbl: Table[U, V], key: U, val: var V): bool =
|
|
## Returns false if not in table
|
|
## Returns true if in the table, sets value to val
|
|
if tbl.count == 0:
|
|
return false
|
|
|
|
let entry = findEntry(tbl.entries, tbl.cap, key)
|
|
if not (entry[].entryStatus == esAlive):
|
|
return false
|
|
|
|
val = entry[].value
|
|
return true
|
|
|
|
proc tableFindString*[U, V](tbl: Table[U, V], chars: ptr char, len: int, hash: int): U =
|
|
if tbl.count == 0:
|
|
return nil
|
|
var index = hash.bitand(tbl.cap - 1)
|
|
while true:
|
|
let entry = tbl.entries[index]
|
|
if entry.entryStatus == esNil:
|
|
return nil
|
|
elif entry.key.len.int == len and entry.key.hash.int == hash and
|
|
(len == 0 or equalMem(chars, entry.key.chars[0].unsafeAddr, len)):
|
|
return entry.key
|
|
index = (index + 1).bitand(tbl.cap - 1)
|
|
|
|
proc tableDelete*[U, V](tbl: Table[U, V], key: U): bool =
|
|
if tbl.count == 0:
|
|
return false
|
|
|
|
let entry = findEntry(tbl.entries, tbl.cap, key)
|
|
if entry[].entryStatus != esAlive:
|
|
return false
|
|
|
|
entry[].entryStatus = esTombstone
|
|
return true
|
|
|
|
proc getLength*[U, V](tbl: NdTable[U, V]): int =
|
|
tbl.count.int
|
|
|
|
proc `$`*[U, V](tbl: NdTable[U, V], tagged: V): string =
|
|
if tbl[].count == 0:
|
|
return "@{}"
|
|
result = "@{ "
|
|
for i in countup(0, tbl.cap.int - 1):
|
|
let entry = tbl[].entries[i]
|
|
if entry.entryStatus == esAlive:
|
|
result &= $entry.key
|
|
result &= " = "
|
|
if entry.value == tagged:
|
|
result &= "self"
|
|
else:
|
|
result &= $entry.value
|
|
result &= ", "
|
|
result[^2] = ' '
|
|
result[^1] = '}' |