wip lists and tables
This commit is contained in:
parent
788ca7eec0
commit
0ec5bfb60a
|
@ -16,6 +16,8 @@ type
|
||||||
opDefineGlobal, opGetGlobal, opSetGlobal, # globals (uses constants)
|
opDefineGlobal, opGetGlobal, opSetGlobal, # globals (uses constants)
|
||||||
opGetLocal, opSetLocal, # locals
|
opGetLocal, opSetLocal, # locals
|
||||||
opJumpIfFalse, opJump, opLoop, opJumpIfFalsePop, # jumps
|
opJumpIfFalse, opJump, opLoop, opJumpIfFalsePop, # jumps
|
||||||
|
opCreateList, opCreateTable, # collection creation
|
||||||
|
opLen, opSetIndex, opGetIndex, # collection operators
|
||||||
|
|
||||||
Chunk* = object
|
Chunk* = object
|
||||||
code*: seq[uint8]
|
code*: seq[uint8]
|
||||||
|
@ -78,7 +80,8 @@ const simpleInstructions = {
|
||||||
opNegate, opNot,
|
opNegate, opNot,
|
||||||
opAdd, opSubtract, opMultiply, opDivide,
|
opAdd, opSubtract, opMultiply, opDivide,
|
||||||
opEqual, opGreater, opLess,
|
opEqual, opGreater, opLess,
|
||||||
opTrue, opFalse, opNil
|
opTrue, opFalse, opNil,
|
||||||
|
opLen, opSetIndex, opGetIndex,
|
||||||
}
|
}
|
||||||
const constantInstructions = {
|
const constantInstructions = {
|
||||||
opConstant,
|
opConstant,
|
||||||
|
@ -94,6 +97,7 @@ const argInstructions = {
|
||||||
opGetLocal, opSetLocal,
|
opGetLocal, opSetLocal,
|
||||||
opJumpIfFalse, opJump, opLoop, opJumpIfFalsePop,
|
opJumpIfFalse, opJump, opLoop, opJumpIfFalsePop,
|
||||||
opFunctionDef,
|
opFunctionDef,
|
||||||
|
opCreateList, opCreateTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ type
|
||||||
|
|
||||||
Precedence = enum
|
Precedence = enum
|
||||||
pcNone, pcAssignment, pcOr, pcAnd, pcEquality, pcComparison,
|
pcNone, pcAssignment, pcOr, pcAnd, pcEquality, pcComparison,
|
||||||
pcTerm, pcFactor, pcUnary, pcCall, pcPrimary
|
pcTerm, pcFactor, pcUnary, pcIndex, pcCall, pcPrimary
|
||||||
# pcUnary applies to all prefix operators regardless of this enum's value
|
# pcUnary applies to all prefix operators regardless of this enum's value
|
||||||
# changing pcUnary's position can change the priority of all unary ops
|
# changing pcUnary's position can change the priority of all unary ops
|
||||||
#
|
#
|
||||||
|
@ -651,6 +651,64 @@ proc parseFunct(comp: Compiler) =
|
||||||
|
|
||||||
tkFunct.genRule(parseFunct, nop, pcNone)
|
tkFunct.genRule(parseFunct, nop, pcNone)
|
||||||
|
|
||||||
|
# lists
|
||||||
|
|
||||||
|
proc parseList(comp: Compiler) =
|
||||||
|
var count: int
|
||||||
|
while comp.current.tokenType != tkRightBracket:
|
||||||
|
comp.expression()
|
||||||
|
count.inc()
|
||||||
|
if comp.current.tokenType != tkRightBracket or comp.current.tokenType == tkComma:
|
||||||
|
comp.consume(tkComma, "Comma expected after list member.")
|
||||||
|
comp.consume(tkRightBracket, "Right bracket expected after list members.")
|
||||||
|
if count > argMax:
|
||||||
|
comp.error("Maximum list length exceeded.")
|
||||||
|
comp.writeChunk(1 - count, opCreateList)
|
||||||
|
comp.writeChunk(0, count.toDU8())
|
||||||
|
|
||||||
|
|
||||||
|
tkStartList.genRule(parseList, nop, pcNone)
|
||||||
|
|
||||||
|
# tables
|
||||||
|
|
||||||
|
proc parseTable(comp: Compiler) =
|
||||||
|
var count: int
|
||||||
|
while comp.current.tokenType != tkRightBrace:
|
||||||
|
comp.expression()
|
||||||
|
comp.consume(tkEqual, "Equal sign expected after key.")
|
||||||
|
comp.expression()
|
||||||
|
count.inc()
|
||||||
|
if comp.current.tokenType != tkRightBrace or comp.current.tokenType == tkComma:
|
||||||
|
comp.consume(tkComma, "Comma expected after key-value pair.")
|
||||||
|
comp.consume(tkRightBrace, "Right brace expected after list members.")
|
||||||
|
if count > argMax:
|
||||||
|
comp.error("Maximum table length exceeded.")
|
||||||
|
comp.writeChunk(1 - 2 * count, opCreateTable)
|
||||||
|
comp.writeChunk(0, count.toDU8())
|
||||||
|
|
||||||
|
tkStartTable.genRule(parseTable, nop, pcNone)
|
||||||
|
|
||||||
|
# len op
|
||||||
|
|
||||||
|
proc parseLen(comp: Compiler) =
|
||||||
|
comp.expression()
|
||||||
|
comp.writeChunk(0, opLen)
|
||||||
|
|
||||||
|
tkHashtag.genRule(parseLen, nop, pcNone)
|
||||||
|
|
||||||
|
# get/set index
|
||||||
|
|
||||||
|
proc parseIndex(comp: Compiler) =
|
||||||
|
# the index
|
||||||
|
comp.expression()
|
||||||
|
comp.consume(tkRightBracket, "Right bracket expected after index.")
|
||||||
|
if comp.match(tkEqual):
|
||||||
|
comp.expression()
|
||||||
|
comp.writeChunk(-2, opSetIndex)
|
||||||
|
else:
|
||||||
|
comp.writeChunk(-1, opGetIndex)
|
||||||
|
|
||||||
|
tkLeftBracket.genRule(nop, parseIndex, pcIndex)
|
||||||
|
|
||||||
# below are the expressions that can contain statements in some way
|
# below are the expressions that can contain statements in some way
|
||||||
# the only expressions that can contain a statement are:
|
# the only expressions that can contain a statement are:
|
||||||
|
|
|
@ -14,6 +14,8 @@ type
|
||||||
tkLeftParen, tkRightParen, tkLeftBrace, tkRightBrace, tkComma, tkDot,
|
tkLeftParen, tkRightParen, tkLeftBrace, tkRightBrace, tkComma, tkDot,
|
||||||
tkMinus, tkPlus, tkSemicolon, tkSlash, tkStar, tkBang, tkBangEqual,
|
tkMinus, tkPlus, tkSemicolon, tkSlash, tkStar, tkBang, tkBangEqual,
|
||||||
tkGreater, tkGreaterEqual, tkLess, tkLessEqual, tkEqual, tkEqualEqual,
|
tkGreater, tkGreaterEqual, tkLess, tkLessEqual, tkEqual, tkEqualEqual,
|
||||||
|
tkStartList, tkStartTable, tkLeftBracket, tkRightBracket,
|
||||||
|
tkHashtag,
|
||||||
tkIdentifier, tkString,
|
tkIdentifier, tkString,
|
||||||
tkNumber, tkAnd, tkElse, tkFalse, tkFor, tkFunct, tkGoto, tkIf, tkNil,
|
tkNumber, tkAnd, tkElse, tkFalse, tkFor, tkFunct, tkGoto, tkIf, tkNil,
|
||||||
tkOr, tkPrint, tkLabel, tkBreak, tkTrue, tkVar, tkWhile,
|
tkOr, tkPrint, tkLabel, tkBreak, tkTrue, tkVar, tkWhile,
|
||||||
|
@ -169,6 +171,8 @@ proc scanToken*(scanner: Scanner): Token =
|
||||||
of ')': return scanner.makeToken(tkRightParen)
|
of ')': return scanner.makeToken(tkRightParen)
|
||||||
of '{': return scanner.makeToken(tkLeftBrace)
|
of '{': return scanner.makeToken(tkLeftBrace)
|
||||||
of '}': return scanner.makeToken(tkRightBrace)
|
of '}': return scanner.makeToken(tkRightBrace)
|
||||||
|
of '[': return scanner.makeToken(tkLeftBracket)
|
||||||
|
of ']': return scanner.makeToken(tkRightBracket)
|
||||||
of ';': return scanner.makeToken(tkSemicolon)
|
of ';': return scanner.makeToken(tkSemicolon)
|
||||||
of ',': return scanner.makeToken(tkComma)
|
of ',': return scanner.makeToken(tkComma)
|
||||||
of '.': return scanner.makeToken(tkDot)
|
of '.': return scanner.makeToken(tkDot)
|
||||||
|
@ -176,6 +180,7 @@ proc scanToken*(scanner: Scanner): Token =
|
||||||
of '+': return scanner.makeToken(tkPlus)
|
of '+': return scanner.makeToken(tkPlus)
|
||||||
of '/': return scanner.makeToken(tkSlash)
|
of '/': return scanner.makeToken(tkSlash)
|
||||||
of '*': return scanner.makeToken(tkStar)
|
of '*': return scanner.makeToken(tkStar)
|
||||||
|
of '#': return scanner.makeToken(tkHashtag)
|
||||||
of '!':
|
of '!':
|
||||||
return if scanner.match('='): scanner.makeToken(tkBangEqual) else: scanner.makeToken(tkBang)
|
return if scanner.match('='): scanner.makeToken(tkBangEqual) else: scanner.makeToken(tkBang)
|
||||||
of '=':
|
of '=':
|
||||||
|
@ -189,7 +194,9 @@ proc scanToken*(scanner: Scanner): Token =
|
||||||
of Digits:
|
of Digits:
|
||||||
return scanner.scanNumber()
|
return scanner.scanNumber()
|
||||||
of '@':
|
of '@':
|
||||||
return scanner.scanLabel()
|
if scanner.match('['): return scanner.makeToken(tkStartList)
|
||||||
|
elif scanner.match('{'): return scanner.makeToken(tkStartTable)
|
||||||
|
else: return scanner.scanLabel()
|
||||||
else:
|
else:
|
||||||
if c.canStartIdent():
|
if c.canStartIdent():
|
||||||
return scanner.scanIdentifier()
|
return scanner.scanIdentifier()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# The hash table implementation for string interning
|
# The hash table implementation for string interning and globals
|
||||||
|
|
||||||
import ndstring
|
import ndstring
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ type
|
||||||
EntryStatus = enum
|
EntryStatus = enum
|
||||||
esNil, esAlive, esTombstone
|
esNil, esAlive, esTombstone
|
||||||
|
|
||||||
Entry*[U, V] = object
|
Entry[U, V] = object
|
||||||
entryStatus: EntryStatus
|
entryStatus: EntryStatus
|
||||||
key: U
|
key: U
|
||||||
value: V
|
value: V
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
import ../pointerutils
|
||||||
|
|
||||||
|
import strformat
|
||||||
|
|
||||||
|
# configure ndlist here
|
||||||
|
const boundsChecks = defined(debug)
|
||||||
|
# boundsChecks default: false, true has a large performance impact, and emitting correct code is on the compiler's job
|
||||||
|
# boundsChecking is only meant for debugging
|
||||||
|
const growthFactor = 2
|
||||||
|
# should be a natural number larger than 1
|
||||||
|
|
||||||
|
const startCap = 8
|
||||||
|
|
||||||
|
type
|
||||||
|
ListObj[T] = object
|
||||||
|
len: int
|
||||||
|
cap: int
|
||||||
|
entries: UncheckedArray[T]
|
||||||
|
List*[T] = ptr ListObj[T]
|
||||||
|
|
||||||
|
template allocSize[T](list: List[T], cap: int): int =
|
||||||
|
sizeof(ListObj[T]) + sizeof(T) * cap
|
||||||
|
|
||||||
|
proc newList*[T](): List[T] =
|
||||||
|
return cast[List[T]](nil)
|
||||||
|
|
||||||
|
proc free*[T](list: var List[T]) =
|
||||||
|
## dealloc's the list object
|
||||||
|
if list != nil:
|
||||||
|
list.dealloc()
|
||||||
|
|
||||||
|
proc grow[T](list: var List[T]) {.inline.} =
|
||||||
|
## growth the list's capacity
|
||||||
|
let newcap = if list == nil: startCap else: list.cap * growthFactor
|
||||||
|
let size = list.allocSize(newcap)
|
||||||
|
if list == nil:
|
||||||
|
list = cast[List[T]](size.alloc())
|
||||||
|
list.cap = newcap
|
||||||
|
list.len = 0
|
||||||
|
else:
|
||||||
|
list = cast[List[T]](list.realloc(size))
|
||||||
|
list.cap = newcap
|
||||||
|
|
||||||
|
proc add*[T](list: var List[T], item: T) {.inline.} =
|
||||||
|
if list == nil or list.len == list.cap:
|
||||||
|
list.grow()
|
||||||
|
list.entries[list.len] = item
|
||||||
|
list.len.inc()
|
||||||
|
|
||||||
|
proc getIndex*[T](list: List[T], index: int): T =
|
||||||
|
when boundsChecks:
|
||||||
|
if index < 0 or index >= list.len:
|
||||||
|
raise newException(Defect, &"Attempt to getIndex with an index {index} which is out of bounds (len: {$list.len}).")
|
||||||
|
list.entries[index]
|
||||||
|
|
||||||
|
proc getIndexNeg*[T](list: List[T], index: int): T =
|
||||||
|
## warning: -1 is the top value, using 0 is invalid (unlike stack.getIndexNeg)
|
||||||
|
when boundsChecks:
|
||||||
|
if index <= 0 or index > list.len:
|
||||||
|
raise newException(Defect, "Attempt to getIndexNeg with an index out of bounds.")
|
||||||
|
list.entries[list.len - index]
|
||||||
|
|
||||||
|
proc setIndex*[T](list: var List[T], index: int, item: T) =
|
||||||
|
when boundsChecks:
|
||||||
|
if index < 0 or index >= list.len:
|
||||||
|
raise newException(Defect, "Attempt to getIndex with an index out of bounds.")
|
||||||
|
list.entries[index] = item
|
||||||
|
|
||||||
|
proc setIndexNeg*[T](list: List[T], index: int, item: T) =
|
||||||
|
## warning: -1 is the top value, using 0 is invalid (unlike stack.setIndexNeg)
|
||||||
|
when boundsChecks:
|
||||||
|
if index <= 0 or index > list.len:
|
||||||
|
raise newException(Defect, "Attempt to setIndexNeg with an index out of bounds.")
|
||||||
|
list.entries[list.len - index] = item
|
||||||
|
|
||||||
|
proc getLength*[T](list: List[T]): int {.inline.} =
|
||||||
|
if list == nil:
|
||||||
|
0
|
||||||
|
else:
|
||||||
|
list.len
|
||||||
|
|
||||||
|
proc `$`*[T](list: List[T]): string =
|
||||||
|
result = "@[ "
|
||||||
|
for i in countup(0, list.len - 1):
|
||||||
|
mixin `$`
|
||||||
|
result &= $list.getIndex(i)
|
||||||
|
if i < list.len - 1:
|
||||||
|
result &= ", "
|
||||||
|
result &= " ]"
|
|
@ -0,0 +1,129 @@
|
||||||
|
# The hash table implementation for table types
|
||||||
|
|
||||||
|
import ndstring
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
NdTable*[U, V] = ptr object
|
||||||
|
count: uint32
|
||||||
|
cap: uint32
|
||||||
|
entries: UncheckedArray[Entry[U, V]]
|
||||||
|
|
||||||
|
template allocSize[U, V](tbl: NdTable[U, V], cap: int): int =
|
||||||
|
8 + cap * sizeof(Entry[U, V])
|
||||||
|
|
||||||
|
proc newNdTable*[U, V]: NdTable[U, V] =
|
||||||
|
result = cast[NdTable[U,V]](result.allocSize(tableInitSize).alloc0())
|
||||||
|
result[].cap = tableInitSize
|
||||||
|
result[].count = 0
|
||||||
|
|
||||||
|
proc free*[U, V](tbl: var NdTable[U, V]) =
|
||||||
|
dealloc(tbl)
|
||||||
|
|
||||||
|
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 NdTable[U, V]): int {.inline.} =
|
||||||
|
## Calculates the new capacity
|
||||||
|
tbl[].cap.int * 2
|
||||||
|
|
||||||
|
proc adjustCapacity[U, V](tbl: var NdTable[U, V], newcap: int) =
|
||||||
|
let tblnew = cast[NdTable[U,V]](tbl.allocSize(newcap).alloc0())
|
||||||
|
tblnew[].count = tbl.count
|
||||||
|
tblnew[].cap = newcap.uint32
|
||||||
|
|
||||||
|
for i in countup(0, tbl.cap.int - 1):
|
||||||
|
let entry = tbl[].entries[i]
|
||||||
|
if entry.entryStatus == esAlive:
|
||||||
|
var dest = findEntry(tblnew.entries.addr, newcap, entry.key)
|
||||||
|
dest[].key = entry.key
|
||||||
|
dest[].value = entry.value
|
||||||
|
dest[].entryStatus = esAlive
|
||||||
|
tbl.count.inc
|
||||||
|
|
||||||
|
if tbl != nil:
|
||||||
|
dealloc(tbl)
|
||||||
|
|
||||||
|
tbl = tblnew
|
||||||
|
|
||||||
|
proc tableSet*[U, V](tbl: var NdTable[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.int + 1 > int(tbl.cap.float * tableMaxLoad):
|
||||||
|
let cap = tbl.grow()
|
||||||
|
tbl.adjustCapacity(cap)
|
||||||
|
let entry: ptr Entry[U, V] = findEntry(tbl.entries.addr, tbl.cap.int, 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: NdTable[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.addr, tbl.cap.int, key)
|
||||||
|
if not (entry[].entryStatus == esAlive):
|
||||||
|
return false
|
||||||
|
|
||||||
|
val = entry[].value
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc tableDelete*[U, V](tbl: NdTable[U, V], key: U): bool =
|
||||||
|
if tbl.count == 0:
|
||||||
|
return false
|
||||||
|
|
||||||
|
let entry = findEntry(tbl.entries.addr, tbl.cap.int, key)
|
||||||
|
if entry[].entryStatus != esAlive:
|
||||||
|
return false
|
||||||
|
|
||||||
|
entry[].entryStatus = esTombstone
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc `$`*[U, V](tbl: NdTable[U, V]): string =
|
||||||
|
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
|
|
@ -27,6 +27,10 @@ proc newString*(str: string): NdString =
|
||||||
|
|
||||||
discard ndStrings.tableSet(result, nil)
|
discard ndStrings.tableSet(result, nil)
|
||||||
|
|
||||||
|
proc resetInternedStrings* =
|
||||||
|
ndStrings.free()
|
||||||
|
ndStrings = newTable[NdString, NdString]()
|
||||||
|
|
||||||
proc `$`*(ndStr: NdString): string =
|
proc `$`*(ndStr: NdString): string =
|
||||||
result = newString(ndStr.len.int)
|
result = newString(ndStr.len.int)
|
||||||
copyMem(result[0].unsafeAddr, ndStr.chars[0].unsafeAddr, ndStr.len.int)
|
copyMem(result[0].unsafeAddr, ndStr.chars[0].unsafeAddr, ndStr.len.int)
|
||||||
|
@ -34,3 +38,10 @@ 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 getLength*(ndStr: NdString): int =
|
||||||
|
ndStr.len.int
|
||||||
|
|
||||||
|
proc getIndex*(ndStr: NdString, index: int): NdString =
|
||||||
|
# TODO optimize this later
|
||||||
|
newString($($ndStr)[index])
|
|
@ -3,6 +3,8 @@ import bitops
|
||||||
|
|
||||||
import ndstring
|
import ndstring
|
||||||
import stringutils
|
import stringutils
|
||||||
|
import ndtable
|
||||||
|
import ndlist
|
||||||
|
|
||||||
type
|
type
|
||||||
NdValue* = uint
|
NdValue* = uint
|
||||||
|
@ -15,12 +17,19 @@ type
|
||||||
|
|
||||||
# see https://craftinginterpreters.com/optimization.html
|
# see https://craftinginterpreters.com/optimization.html
|
||||||
# bit layout:
|
# bit layout:
|
||||||
# bit 63 is unused (can be used for more types in the future)
|
# bit 63 is used for type information as well
|
||||||
# bits 62-50 are all 1 if it's not a float
|
# bits 62-50 are all 1 if it's not a float
|
||||||
# bits 49-48 determine type:
|
# bits 49-48 determine type:
|
||||||
|
# if bit 63 is 0:
|
||||||
# 00 -> nil or bool (singletons)
|
# 00 -> nil or bool (singletons)
|
||||||
# 01 -> string
|
# 01 -> string
|
||||||
# 10 -> funct
|
# 10 -> funct
|
||||||
|
# 11 -> closure (to be implemented later)
|
||||||
|
# if bit 63 is 1:
|
||||||
|
# 00 -> list
|
||||||
|
# 01 -> table
|
||||||
|
# 10 -> native funct (to be implemented later)
|
||||||
|
# 11 -> unused for now
|
||||||
|
|
||||||
const qNan* = 0x7ffc000000000000'u
|
const qNan* = 0x7ffc000000000000'u
|
||||||
|
|
||||||
|
@ -34,6 +43,9 @@ const tagString* = 0x7ffd000000000000'u
|
||||||
# 0111 1111 1111 11*10* 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
|
# 0111 1111 1111 11*10* 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
|
||||||
const tagFunct* = 0x7ffe000000000000'u
|
const tagFunct* = 0x7ffe000000000000'u
|
||||||
|
|
||||||
|
const tagList* = 0xfffc000000000000'u
|
||||||
|
const tagTable* = 0xfffd000000000000'u
|
||||||
|
|
||||||
const mask48* = 0xffff000000000000'u
|
const mask48* = 0xffff000000000000'u
|
||||||
|
|
||||||
# converters
|
# converters
|
||||||
|
@ -53,6 +65,12 @@ template isString*(val: NdValue): bool =
|
||||||
template isFunct*(val: NdValue): bool =
|
template isFunct*(val: NdValue): bool =
|
||||||
val.bitand(mask48) == tagFunct
|
val.bitand(mask48) == tagFunct
|
||||||
|
|
||||||
|
template isList*(val: NdValue): bool =
|
||||||
|
val.bitand(mask48) == tagList
|
||||||
|
|
||||||
|
template isTable*(val: NdValue): bool =
|
||||||
|
val.bitand(mask48) == tagTable
|
||||||
|
|
||||||
# these assume that the type has been previously determined
|
# these assume that the type has been previously determined
|
||||||
template asBool*(val: NdValue): bool =
|
template asBool*(val: NdValue): bool =
|
||||||
val == ndTrue
|
val == ndTrue
|
||||||
|
@ -66,6 +84,12 @@ template asString*(val: NdValue): NdString =
|
||||||
template asFunct*(val: NdValue): ptr uint8 =
|
template asFunct*(val: NdValue): ptr uint8 =
|
||||||
cast[ptr uint8](val.bitand(mask48.bitnot()))
|
cast[ptr uint8](val.bitand(mask48.bitnot()))
|
||||||
|
|
||||||
|
template asList*(val: NdValue): List[NdValue] =
|
||||||
|
cast[List[NdValue]](val.bitand(mask48.bitnot()))
|
||||||
|
|
||||||
|
template asTable*(val: NdValue): NdTable[NdValue, NdValue] =
|
||||||
|
cast[NdTable[NdValue, NdValue]](val.bitand(mask48.bitnot()))
|
||||||
|
|
||||||
template fromNil*(): NdValue =
|
template fromNil*(): NdValue =
|
||||||
ndNil
|
ndNil
|
||||||
|
|
||||||
|
@ -84,6 +108,29 @@ template fromNimString*(sval: string): NdValue =
|
||||||
template fromFunct*(val: ptr uint8): NdValue =
|
template fromFunct*(val: ptr uint8): NdValue =
|
||||||
cast[uint](val).bitor(tagFunct)
|
cast[uint](val).bitor(tagFunct)
|
||||||
|
|
||||||
|
template fromList*(val: List[NdValue]): NdValue =
|
||||||
|
cast[uint](val).bitor(tagList)
|
||||||
|
|
||||||
|
template fromTable*(val: NdTable[NdValue, NdValue]): NdValue =
|
||||||
|
cast[uint](val).bitor(tagTable)
|
||||||
|
|
||||||
|
|
||||||
|
# for hashtables
|
||||||
|
|
||||||
|
proc fnv1a*(ndval: NdValue): int =
|
||||||
|
var hash = 2166136261'u32
|
||||||
|
var val = ndval
|
||||||
|
for i in countup(0, 7):
|
||||||
|
hash = hash xor val.uint8.uint32
|
||||||
|
hash *= 16777619
|
||||||
|
val = val shr 8
|
||||||
|
return hash.int
|
||||||
|
|
||||||
|
proc equal*(val, right: NdValue): bool =
|
||||||
|
if val.isFloat() and right.isFloat():
|
||||||
|
val.asFloat() == right.asFloat()
|
||||||
|
else:
|
||||||
|
val == right
|
||||||
|
|
||||||
# KON VALUE HELPERS, MUST BE DEFINED FOR EVERY KONVALUE
|
# KON VALUE HELPERS, MUST BE DEFINED FOR EVERY KONVALUE
|
||||||
|
|
||||||
|
@ -100,6 +147,10 @@ proc `$`*(val: NdValue): string =
|
||||||
return &"Function object {cast[uint](val.asFunct())}"
|
return &"Function object {cast[uint](val.asFunct())}"
|
||||||
elif val.isString():
|
elif val.isString():
|
||||||
return $val.asString()
|
return $val.asString()
|
||||||
|
elif val.isTable():
|
||||||
|
return $val.asTable()
|
||||||
|
elif val.isList():
|
||||||
|
return $val.asList()
|
||||||
|
|
||||||
proc isFalsey*(val: NdValue): bool =
|
proc isFalsey*(val: NdValue): bool =
|
||||||
val == ndNil or val == ndFalse
|
val == ndNil or val == ndFalse
|
||||||
|
@ -118,6 +169,10 @@ proc friendlyType*(val: NdValue): string =
|
||||||
"string"
|
"string"
|
||||||
elif val.isFunct():
|
elif val.isFunct():
|
||||||
"function"
|
"function"
|
||||||
|
elif val.isTable():
|
||||||
|
"table"
|
||||||
|
elif val.isList():
|
||||||
|
"list"
|
||||||
else:
|
else:
|
||||||
"unknown"
|
"unknown"
|
||||||
|
|
||||||
|
@ -181,19 +236,66 @@ proc greater*(val: var NdValue, right: NdValue): NatReturn {.inline.} =
|
||||||
return natError(&"Attempt to compare types {val.friendlyType()} and {right.friendlyType()}.")
|
return natError(&"Attempt to compare types {val.friendlyType()} and {right.friendlyType()}.")
|
||||||
return natOk
|
return natOk
|
||||||
|
|
||||||
# for hashtables
|
proc getLength*(val: var NdValue): NatReturn {.inline.} =
|
||||||
|
if val.isList():
|
||||||
proc fnv1a*(ndval: NdValue): int =
|
val = fromFloat(val.asList().getLength().float)
|
||||||
var hash = 2166136261'u32
|
elif val.isTable():
|
||||||
var val = ndval
|
val = fromFloat(val.asTable().getLength().float)
|
||||||
for i in countup(0, 7):
|
elif val.isString():
|
||||||
hash = hash xor val.uint8.uint32
|
val = fromFloat(val.asString().getLength().float)
|
||||||
hash *= 16777619
|
|
||||||
val = val shr 8
|
|
||||||
return hash.int
|
|
||||||
|
|
||||||
proc equal*(val, right: NdValue): bool =
|
|
||||||
if val.isFloat() and right.isFloat():
|
|
||||||
val.asFloat() == right.asFloat()
|
|
||||||
else:
|
else:
|
||||||
val == right
|
return natError(&"Attempt to get length of type {val.friendlyType()}, this operator is only available for lists, tables and strings.")
|
||||||
|
return natOk
|
||||||
|
|
||||||
|
proc getIndex*(val: var NdValue, index: NdValue): NatReturn {.inline.} =
|
||||||
|
if val.isTable():
|
||||||
|
var tbl = val.asTable()
|
||||||
|
if not tbl.tableGet(index, val):
|
||||||
|
val = fromNil()
|
||||||
|
return natOk
|
||||||
|
|
||||||
|
template indexInt: int =
|
||||||
|
index.asFloat().int
|
||||||
|
|
||||||
|
template checkBounds(len: int) =
|
||||||
|
if not index.isFloat():
|
||||||
|
return natError(&"Attempt to index using an index of type {index.friendlyType()}: only numerical indexes are allowed.")
|
||||||
|
|
||||||
|
if indexInt() < 0 or indexInt() >= len:
|
||||||
|
return natError(&"Index out of bounds. Index used: {index}; valid range: 0 - {len - 1} inclusive.")
|
||||||
|
|
||||||
|
if val.isList():
|
||||||
|
var list = val.asList()
|
||||||
|
let len = list.getLength()
|
||||||
|
len.checkBounds()
|
||||||
|
val = list.getIndex(indexInt)
|
||||||
|
elif val.isString():
|
||||||
|
var str = val.asString()
|
||||||
|
let len = str.getLength()
|
||||||
|
len.checkBounds()
|
||||||
|
val = str.getIndex(indexInt).fromNdString()
|
||||||
|
else:
|
||||||
|
return natError(&"Attempt to index a type {val.friendlyType()}, this operator is only available for lists, tables and strings.")
|
||||||
|
return natOk
|
||||||
|
|
||||||
|
proc setIndex*(val: var NdValue, index: NdValue, newval: NdValue): NatReturn {.inline.} =
|
||||||
|
if val.isTable():
|
||||||
|
var tbl = val.asTable()
|
||||||
|
discard tbl.tableSet(index, newval)
|
||||||
|
return natOk
|
||||||
|
|
||||||
|
if val.isList():
|
||||||
|
var list = val.asList()
|
||||||
|
let len = list.getLength()
|
||||||
|
let indexint = index.asFloat().int
|
||||||
|
if not index.isFloat():
|
||||||
|
return natError(&"Attempt to index using an index of type {index.friendlyType()}: only numerical indexes are allowed.")
|
||||||
|
|
||||||
|
if indexInt < 0 or indexInt >= len:
|
||||||
|
return natError(&"Index out of bounds. Index used: {index}; valid range: 0 - {len - 1} inclusive.")
|
||||||
|
list.setIndex(indexInt, newval)
|
||||||
|
else:
|
||||||
|
return natError(&"Attempt to set-index a type {val.friendlyType()}, this operator is only available for lists and tables.")
|
||||||
|
return natOk
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ import types/stringutils
|
||||||
import bitops # needed for value's templates
|
import bitops # needed for value's templates
|
||||||
import types/value
|
import types/value
|
||||||
import types/hashtable
|
import types/hashtable
|
||||||
|
import types/ndtable
|
||||||
|
import types/ndlist
|
||||||
|
|
||||||
|
|
||||||
when profileInstructions:
|
when profileInstructions:
|
||||||
|
@ -228,6 +230,39 @@ proc run*(chunk: Chunk): InterpretResult =
|
||||||
frames.add(Frame(stackBottom: stack.high - argcount, returnIp: ip))
|
frames.add(Frame(stackBottom: stack.high - argcount, returnIp: ip))
|
||||||
|
|
||||||
ip = funct.asFunct() # jump to the entry point
|
ip = funct.asFunct() # jump to the entry point
|
||||||
|
of opCreateList:
|
||||||
|
let listLen = readDU8()
|
||||||
|
var list = newList[NdValue]()
|
||||||
|
for i in countup(0, listLen - 1): # TODO optimize: some copyMem might be possible, which will also fix the reversing order issue
|
||||||
|
list.add(stack.pop())
|
||||||
|
stack.push(list.fromList())
|
||||||
|
of opCreateTable:
|
||||||
|
let tblLen = readDU8()
|
||||||
|
var tbl = newNdTable[NdValue, NdValue]()
|
||||||
|
for i in countup(0, tblLen - 1): # TODO optimize: allocate the right size right upfront
|
||||||
|
let val = stack.pop()
|
||||||
|
let key = stack.pop()
|
||||||
|
discard tbl.tableSet(key, val) # TODO check for duplicates and disallow
|
||||||
|
stack.push(tbl.fromTable())
|
||||||
|
of opLen:
|
||||||
|
let res = stack.peek().getLength()
|
||||||
|
if not res.ok:
|
||||||
|
runtimeError(res.msg)
|
||||||
|
break
|
||||||
|
of opGetIndex:
|
||||||
|
let index = stack.pop()
|
||||||
|
let res = stack.peek().getIndex(index)
|
||||||
|
if not res.ok:
|
||||||
|
runtimeError(res.msg)
|
||||||
|
break
|
||||||
|
of opSetIndex:
|
||||||
|
let value = stack.pop()
|
||||||
|
let index = stack.pop()
|
||||||
|
let res = stack.peek().setIndex(index, value)
|
||||||
|
if not res.ok:
|
||||||
|
runtimeError(res.msg)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
when profileInstructions:
|
when profileInstructions:
|
||||||
durations[ins] += getMonoTime() - startTime
|
durations[ins] += getMonoTime() - startTime
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import ndspkg/types/hashtable
|
import ../src/ndspkg/types/hashtable
|
||||||
import ndspkg/types/ndstring
|
import ../src/ndspkg/types/ndstring
|
||||||
import ndspkg/types/stringutils
|
import ../src/ndspkg/types/stringutils
|
||||||
|
|
||||||
import strformat
|
import strformat
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import ../src/ndspkg/types/ndlist
|
||||||
|
|
||||||
|
proc testNdlist* =
|
||||||
|
var list = newList[int]()
|
||||||
|
|
||||||
|
var simlen = 0 # simulated length
|
||||||
|
|
||||||
|
for i in countup(0, 10000):
|
||||||
|
assert list.getLength() == simlen
|
||||||
|
list.add(i)
|
||||||
|
simlen.inc
|
||||||
|
assert list.getLength() == simlen
|
||||||
|
assert list.getIndex(i) == i
|
||||||
|
list.setIndex(i, i*2)
|
||||||
|
assert list.getLength() == simlen
|
||||||
|
assert list.getIndex(i) == i * 2
|
||||||
|
|
||||||
|
|
||||||
|
list.free()
|
||||||
|
|
||||||
|
echo "ndlist test finished"
|
|
@ -0,0 +1,49 @@
|
||||||
|
import ../src/ndspkg/types/ndtable
|
||||||
|
import ../src/ndspkg/types/ndstring
|
||||||
|
import ../src/ndspkg/types/stringutils
|
||||||
|
|
||||||
|
proc equal*(l, r: string): bool =
|
||||||
|
l == r
|
||||||
|
|
||||||
|
proc testNdtables* =
|
||||||
|
var tbl = newNdTable[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 "ndtable test finished"
|
|
@ -1,3 +1,11 @@
|
||||||
import hashtable
|
import hashtable
|
||||||
|
|
||||||
testHashtables()
|
testHashtables()
|
||||||
|
|
||||||
|
import ndtable
|
||||||
|
|
||||||
|
testNdtables()
|
||||||
|
|
||||||
|
import ndlist
|
||||||
|
|
||||||
|
testNdlist()
|
Loading…
Reference in New Issue