hashtable implementation and test
This commit is contained in:
parent
8031c7720d
commit
ded7fd3211
|
@ -1,6 +1,4 @@
|
||||||
nds
|
nds
|
||||||
*.txt
|
*.txt
|
||||||
callgrind*
|
callgrind*
|
||||||
test.nim
|
|
||||||
test
|
|
||||||
.vscode
|
.vscode
|
|
@ -33,6 +33,6 @@ The 4 steps to a REPL:
|
||||||
```
|
```
|
||||||
git clone https://github.com/prod2/nondescript
|
git clone https://github.com/prod2/nondescript
|
||||||
cd nondescript
|
cd nondescript
|
||||||
nim c main
|
./build.sh
|
||||||
./nds
|
./nds
|
||||||
```
|
```
|
|
@ -6,13 +6,17 @@ type
|
||||||
# compiler debug options
|
# compiler debug options
|
||||||
const debugScanner* = false
|
const debugScanner* = false
|
||||||
const debugCompiler* = false
|
const debugCompiler* = false
|
||||||
const debugDumpChunk* = false
|
const debugDumpChunk* = defined(debug)
|
||||||
const assertionsCompiler* = true # sanity checks in the compiler
|
const assertionsCompiler* = true # sanity checks in the compiler
|
||||||
# vm debug options (setting any to true will slow runtime down!)
|
# vm debug options (setting any to true will slow runtime down!)
|
||||||
const debugVM* = false
|
const debugVM* = false
|
||||||
const assertionsVM* = false # sanity checks in the VM, such as the stack being empty at the end
|
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 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
|
# choose a line editor for the repl
|
||||||
const lineEditor = leRdstdin
|
const lineEditor = leRdstdin
|
||||||
|
|
||||||
|
|
39
main.nim
39
main.nim
|
@ -1,8 +1,13 @@
|
||||||
import vm
|
import vm
|
||||||
import compiler
|
import compiler
|
||||||
import os
|
|
||||||
import config
|
import config
|
||||||
|
|
||||||
|
import os
|
||||||
|
import strformat
|
||||||
|
|
||||||
|
when compileTests:
|
||||||
|
import tests/hashtable
|
||||||
|
|
||||||
type Result = enum
|
type Result = enum
|
||||||
rsOK, rsCompileError, rsRuntimeError
|
rsOK, rsCompileError, rsRuntimeError
|
||||||
|
|
||||||
|
@ -36,6 +41,21 @@ proc runFile(path: string) =
|
||||||
of rsOK:
|
of rsOK:
|
||||||
quit 0
|
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* = ""
|
const hardcodedPath* = ""
|
||||||
|
|
||||||
if paramCount() == 0:
|
if paramCount() == 0:
|
||||||
|
@ -44,7 +64,22 @@ if paramCount() == 0:
|
||||||
else:
|
else:
|
||||||
runFile(hardcodedPath)
|
runFile(hardcodedPath)
|
||||||
elif paramCount() == 1:
|
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:
|
else:
|
||||||
echo "Maximum param count is 1"
|
echo "Maximum param count is 1"
|
||||||
quit 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 =
|
proc `&`*(left, right: NdString): NdString =
|
||||||
# TODO optimize this later when strings will be benchmarked
|
# 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.top = result.start.psub(sizeof(T))
|
||||||
result.cap = startingCap
|
result.cap = startingCap
|
||||||
|
|
||||||
proc destroyStack*[T](stack: var Stack[T]) =
|
proc free*[T](stack: var Stack[T]) =
|
||||||
## dealloc's the stack object
|
## dealloc's the stack object
|
||||||
## if the stack contains pointers, those should be freed before destroying the stack
|
## if the stack contains pointers, those should be freed before destroying the stack
|
||||||
stack.cap = 0
|
stack.cap = 0
|
||||||
|
|
|
@ -57,6 +57,7 @@ proc equal*(val, right: NdValue): bool =
|
||||||
of ntNil:
|
of ntNil:
|
||||||
true
|
true
|
||||||
of ntString:
|
of ntString:
|
||||||
|
# TODO this was meant for nim strings, not ndStrings, FIXME!
|
||||||
val.stringValue == right.stringValue
|
val.stringValue == right.stringValue
|
||||||
of ntFunct:
|
of ntFunct:
|
||||||
val.entryII == right.entryII
|
val.entryII == right.entryII
|
||||||
|
|
4
vm.nim
4
vm.nim
|
@ -241,8 +241,8 @@ proc run*(chunk: Chunk): InterpretResult =
|
||||||
let times = runcounts[op]
|
let times = runcounts[op]
|
||||||
echo &"OpCode: {op} total duration {dur} ms {times} times"
|
echo &"OpCode: {op} total duration {dur} ms {times} times"
|
||||||
|
|
||||||
stack.destroyStack()
|
stack.free()
|
||||||
frames.destroyStack()
|
frames.free()
|
||||||
|
|
||||||
if hadError:
|
if hadError:
|
||||||
irRuntimeError
|
irRuntimeError
|
||||||
|
|
Loading…
Reference in New Issue