hashtable implementation and test
This commit is contained in:
parent
8031c7720d
commit
ded7fd3211
|
@ -1,6 +1,4 @@
|
|||
nds
|
||||
*.txt
|
||||
callgrind*
|
||||
test.nim
|
||||
test
|
||||
.vscode
|
|
@ -33,6 +33,6 @@ The 4 steps to a REPL:
|
|||
```
|
||||
git clone https://github.com/prod2/nondescript
|
||||
cd nondescript
|
||||
nim c main
|
||||
./build.sh
|
||||
./nds
|
||||
```
|
|
@ -6,13 +6,17 @@ type
|
|||
# compiler debug options
|
||||
const debugScanner* = false
|
||||
const debugCompiler* = false
|
||||
const debugDumpChunk* = false
|
||||
const debugDumpChunk* = defined(debug)
|
||||
const assertionsCompiler* = true # sanity checks in the compiler
|
||||
# vm debug options (setting any to true will slow runtime down!)
|
||||
const debugVM* = false
|
||||
const assertionsVM* = false # sanity checks in the VM, such as the stack being empty at the end
|
||||
const profileInstructions* = false # if true, the time spent on every opcode is measured
|
||||
|
||||
const compileTests* = defined(debug)
|
||||
# if true, the nim part of the test suite will be compiled in main
|
||||
# it will be possible to start it using ./nds --test
|
||||
|
||||
# choose a line editor for the repl
|
||||
const lineEditor = leRdstdin
|
||||
|
||||
|
|
39
main.nim
39
main.nim
|
@ -1,8 +1,13 @@
|
|||
import vm
|
||||
import compiler
|
||||
import os
|
||||
import config
|
||||
|
||||
import os
|
||||
import strformat
|
||||
|
||||
when compileTests:
|
||||
import tests/hashtable
|
||||
|
||||
type Result = enum
|
||||
rsOK, rsCompileError, rsRuntimeError
|
||||
|
||||
|
@ -36,6 +41,21 @@ proc runFile(path: string) =
|
|||
of rsOK:
|
||||
quit 0
|
||||
|
||||
proc runTests =
|
||||
when compileTests:
|
||||
testHashtables()
|
||||
else:
|
||||
echo "Nds was compiled without nim tests. Please change the flag and recompile, then tests will be available."
|
||||
quit 1
|
||||
|
||||
proc printUsage =
|
||||
echo """Usage:
|
||||
- Start a repl: nds
|
||||
- Run a file: nds path/to/script.nds
|
||||
- Run the nim half of the test suite: nds --test
|
||||
- Print this message: nds --help or nds -h
|
||||
"""
|
||||
|
||||
const hardcodedPath* = ""
|
||||
|
||||
if paramCount() == 0:
|
||||
|
@ -44,7 +64,22 @@ if paramCount() == 0:
|
|||
else:
|
||||
runFile(hardcodedPath)
|
||||
elif paramCount() == 1:
|
||||
runFile(paramStr(1))
|
||||
let arg = paramStr(1)
|
||||
if arg[0] != '-':
|
||||
runFile(paramStr(1))
|
||||
else:
|
||||
case arg:
|
||||
of "--test":
|
||||
when defined(danger) or defined(release):
|
||||
echo "WARNING: nds was compiled with -d:danger or -d:release, tests are only supported on -d:debug!"
|
||||
runTests()
|
||||
of "--help":
|
||||
printUsage()
|
||||
quit 0
|
||||
else:
|
||||
echo &"Unsupported flag {arg}."
|
||||
printUsage()
|
||||
quit 1
|
||||
else:
|
||||
echo "Maximum param count is 1"
|
||||
quit 1
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import ../types/hashtable
|
||||
|
||||
import strformat
|
||||
|
||||
proc hash*(str: string): int =
|
||||
var hash = 2166136261'u32
|
||||
for i in countup(0, str.len - 1):
|
||||
hash = hash xor (str[i]).uint32
|
||||
hash *= 16777619
|
||||
return hash.int
|
||||
|
||||
proc testHashtables* =
|
||||
var tbl = newTable[string, int]()
|
||||
|
||||
var val: int
|
||||
|
||||
assert tbl.tableSet("hello", 1) == false
|
||||
assert tbl.tableGet("hello", val) == true
|
||||
assert val == 1
|
||||
assert tbl.tableSet("hello", 4) == true
|
||||
assert tbl.tableGet("hello", val) == true
|
||||
assert val == 4
|
||||
assert tbl.tableGet("hellw", val) == false
|
||||
assert val == 4
|
||||
assert tbl.tableDelete("hello") == true
|
||||
val = 0
|
||||
assert tbl.tableGet("hello", val) == false
|
||||
assert val == 0
|
||||
|
||||
for i in countup(0, 10000):
|
||||
assert tbl.tableSet($i, i) == false
|
||||
assert tbl.tableget($i, val) == true
|
||||
assert val == i
|
||||
assert tbl.tableSet($i, i * 2) == true
|
||||
assert tbl.tableget($i, val) == true
|
||||
assert val == i * 2
|
||||
assert tbl.tableSet($i, i * 4) == true
|
||||
assert tbl.tableget($i, val) == true
|
||||
assert val == i * 4
|
||||
if i mod 5 == 0:
|
||||
assert tbl.tableDelete($i) == true
|
||||
assert tbl.tableDelete($i) == false
|
||||
|
||||
for i in countup(0, 10000):
|
||||
if i mod 5 == 0:
|
||||
assert tbl.tableGet($i, val) == false
|
||||
else:
|
||||
assert tbl.tableGet($i, val) == true
|
||||
assert val == i * 4
|
||||
|
||||
tbl.free()
|
||||
|
||||
echo "Hashtable test finished"
|
|
@ -0,0 +1,120 @@
|
|||
# The hash table implementation for string interning
|
||||
|
||||
import strformat
|
||||
|
||||
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]]
|
||||
|
||||
proc newTable*[U, V]: Table[U, V] =
|
||||
result.cap = 0
|
||||
result.count = 0
|
||||
|
||||
proc free*[U, V](tbl: var Table[U, V]) =
|
||||
if tbl.entries != nil:
|
||||
dealloc(tbl.entries)
|
||||
|
||||
proc isNil[U, V](entry: ptr Entry[U, V]): bool {.inline.} =
|
||||
entry[].entryStatus == esNil
|
||||
|
||||
proc isTombstone[U, V](entry: ptr Entry[U, V]): bool {.inline.} =
|
||||
entry[].entryStatus == esTombstone
|
||||
|
||||
proc isAlive[U, V](entry: ptr Entry[U, V]): bool {.inline.} =
|
||||
entry[].entryStatus == esAlive
|
||||
|
||||
proc findEntry[U, V](entries: ptr UncheckedArray[Entry[U, V]], cap: int, key: U): ptr Entry[U, V] =
|
||||
var index = key.hash() mod cap # TODO replace mod with sth better
|
||||
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
|
||||
if entry.isNil():
|
||||
return if tombstone != nil: tombstone else: entry
|
||||
elif entry.isTombstone(): # TODO: optimalization: case statement
|
||||
if tombstone == nil:
|
||||
tombstone = entry
|
||||
elif entry[].key == key:
|
||||
return entry
|
||||
index = (index + 1) mod cap # TODO replace mod with sth better
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
if tbl.entries != nil:
|
||||
dealloc(tbl.entries)
|
||||
|
||||
tbl.entries = entries
|
||||
tbl.cap = newcap
|
||||
|
||||
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.isAlive():
|
||||
return false
|
||||
|
||||
val = entry[].value
|
||||
return true
|
||||
|
||||
|
||||
|
||||
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 not entry.isAlive():
|
||||
return false
|
||||
|
||||
entry[].entryStatus = esTombstone
|
||||
return true
|
||||
|
|
@ -16,4 +16,14 @@ proc `$`*(ndStr: NdString): string =
|
|||
|
||||
proc `&`*(left, right: NdString): NdString =
|
||||
# TODO optimize this later when strings will be benchmarked
|
||||
newString($left & $right)
|
||||
newString($left & $right)
|
||||
|
||||
proc free*(ndStr: var NdString) =
|
||||
dealloc(ndStr)
|
||||
|
||||
proc hash*(ndStr: NdString): int =
|
||||
var hash = 2166136261'u32
|
||||
for i in countup(0, ndStr.len.int - 1):
|
||||
hash = hash xor (ndStr.chars[i]).uint32
|
||||
hash *= 16777619
|
||||
return hash.int
|
|
@ -18,7 +18,7 @@ proc newStack*[T](startingCap: int): Stack[T] =
|
|||
result.top = result.start.psub(sizeof(T))
|
||||
result.cap = startingCap
|
||||
|
||||
proc destroyStack*[T](stack: var Stack[T]) =
|
||||
proc free*[T](stack: var Stack[T]) =
|
||||
## dealloc's the stack object
|
||||
## if the stack contains pointers, those should be freed before destroying the stack
|
||||
stack.cap = 0
|
||||
|
|
|
@ -57,6 +57,7 @@ proc equal*(val, right: NdValue): bool =
|
|||
of ntNil:
|
||||
true
|
||||
of ntString:
|
||||
# TODO this was meant for nim strings, not ndStrings, FIXME!
|
||||
val.stringValue == right.stringValue
|
||||
of ntFunct:
|
||||
val.entryII == right.entryII
|
||||
|
|
Loading…
Reference in New Issue