# 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 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 {.inline.} = ## 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 `$`*[U, V](tbl: NdTable[U, 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: mixin `$` result &= $entry.key result &= " = " result &= $entry.value result &= ", " result[^2] = ' ' result[^1] = '}' proc getLength*[U, V](tbl: NdTable[U, V]): int = tbl.count.int