add ampersand operator, further work on lists and tables

This commit is contained in:
prod2 2022-02-03 04:56:16 +01:00
parent 0ec5bfb60a
commit 159f256a84
8 changed files with 94 additions and 173 deletions

View File

@ -48,12 +48,13 @@ type
hadError*: bool
Precedence = enum
pcNone, pcAssignment, pcOr, pcAnd, pcEquality, pcComparison,
pcNone, pcExprTop, pcAmpersand, pcAssignment, pcNonAssignTop, pcOr, pcAnd, pcEquality, pcComparison,
pcTerm, pcFactor, pcUnary, pcIndex, pcCall, pcPrimary
# pcUnary applies to all prefix operators regardless of this enum's value
# changing pcUnary's position can change the priority of all unary ops
#
# Note: unary only rules should have precedence pcNone!!!
# pcExprTop, pcNonAssignTop are special placeholders!
ParseRule = object
name: string # debug purposes only
@ -181,6 +182,8 @@ template genRule(ttype: TokenType, tprefix: (Compiler) -> void, tinfix: (Compile
raise newException(Exception, "pcUnary cannot be used as a rule precedence! Use pcNone for unary-only rules!")
elif tprec == pcPrimary:
raise newException(Exception, "Invalid rule: pcPrimary cannot be used for binary operators, if this rule is for a primary value, use pcNone!")
elif tprec in {pcNonAssignTop, pcExprTop}:
raise newException(Exception, "Invalid rule: a top pc is just a placeholder")
elif tprec == pcNone and tinfix != nop:
raise newException(Exception, "Invalid rule: pcNone only allowed for unary operators and primary values, not for infix ones!")
rules[ttype] = ParseRule(name: $ttype, prefix: tprefix, infix: tinfix, prec: tprec)
@ -341,7 +344,7 @@ proc expression(comp: Compiler) =
# only use the pratt table for parsing expressions!
when assertionsVM:
let oldStackIndex = comp.stackIndex
comp.parsePrecedence(pcAssignment)
comp.parsePrecedence(pcExprTop)
when assertionsVM:
let diff = comp.stackIndex - oldStackIndex
if diff != 1:
@ -415,7 +418,7 @@ proc variable(comp: Compiler) =
if not comp.canAssign:
comp.error("Invalid assignment target.")
return
comp.expression()
comp.parsePrecedence(pcAssignment)
comp.writeChunk(0, setOp)
else:
# get (global/local)
@ -703,13 +706,20 @@ proc parseIndex(comp: Compiler) =
comp.expression()
comp.consume(tkRightBracket, "Right bracket expected after index.")
if comp.match(tkEqual):
comp.expression()
comp.parsePrecedence(pcNonAssignTop)
comp.writeChunk(-2, opSetIndex)
else:
comp.writeChunk(-1, opGetIndex)
tkLeftBracket.genRule(nop, parseIndex, pcIndex)
proc parseAmpersand(comp: Compiler) =
# just a simple expression separator
discard
tkAmpersand.genRule(nop, parseAmpersand, pcAmpersand)
# below are the expressions that can contain statements in some way
# the only expressions that can contain a statement are:
# the block expression
@ -727,6 +737,7 @@ proc parseBlock(comp: Compiler) =
tkLeftBrace.genRule(parseBlock, nop, pcNone)
# statements
proc parseVariable(comp: Compiler, msg: string): int =

View File

@ -15,7 +15,7 @@ type
tkMinus, tkPlus, tkSemicolon, tkSlash, tkStar, tkBang, tkBangEqual,
tkGreater, tkGreaterEqual, tkLess, tkLessEqual, tkEqual, tkEqualEqual,
tkStartList, tkStartTable, tkLeftBracket, tkRightBracket,
tkHashtag,
tkHashtag, tkAmpersand,
tkIdentifier, tkString,
tkNumber, tkAnd, tkElse, tkFalse, tkFor, tkFunct, tkGoto, tkIf, tkNil,
tkOr, tkPrint, tkLabel, tkBreak, tkTrue, tkVar, tkWhile,
@ -181,6 +181,7 @@ proc scanToken*(scanner: Scanner): Token =
of '/': return scanner.makeToken(tkSlash)
of '*': return scanner.makeToken(tkStar)
of '#': return scanner.makeToken(tkHashtag)
of '&': return scanner.makeToken(tkAmpersand)
of '!':
return if scanner.match('='): scanner.makeToken(tkBangEqual) else: scanner.makeToken(tkBang)
of '=':

View File

@ -21,10 +21,18 @@ type
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)
@ -57,21 +65,28 @@ 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:
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
@ -125,3 +140,18 @@ proc tableDelete*[U, V](tbl: Table[U, V], key: U): bool =
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

View File

@ -1,5 +1,3 @@
import ../pointerutils
import strformat
# configure ndlist here
@ -15,30 +13,34 @@ type
ListObj[T] = object
len: int
cap: int
entries: UncheckedArray[T]
entries: ptr 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)
result = cast[List[T]](alloc(sizeof(ListObj[T])))
result.len = 0
result.cap = 0
proc newListCopymem*[T](start: ptr T, len: int): List[T] =
result = newList[T]()
result.len = len
result.cap = len
result.entries = cast[ptr UncheckedArray[T]](alloc(sizeof(T) * len))
copyMem(result.entries, start, len * sizeof(T))
proc free*[T](list: var List[T]) =
## dealloc's the list object
if list != nil:
list.dealloc()
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())
let newcap = if list.cap == 0: startCap else: list.cap * growthFactor
let size = newcap * sizeof(T)
if list.cap == 0:
list.entries = cast[ptr UncheckedArray[T]](size.alloc())
list.cap = newcap
list.len = 0
else:
list = cast[List[T]](list.realloc(size))
list.entries = cast[ptr UncheckedArray[T]](list.entries.realloc(size))
list.cap = newcap
proc add*[T](list: var List[T], item: T) {.inline.} =

View File

@ -1,129 +0,0 @@
# 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

View File

@ -71,13 +71,13 @@ proc deleteTopN*[T](stack: var Stack[T], n: Natural) =
if stack.top.pless(stack.start):
raise newException(Defect, "Stacktop sunk below the start after a deleteTopN.")
proc getIndex*[T](stack: Stack[T], index: int): T =
proc getIndex*[T](stack: Stack[T], index: int): var T =
when boundsChecks:
if index < 0 or index >= stack.len():
raise newException(Defect, &"Attempt to getIndex with an index {index} which is out of bounds (len: {stack.len()}).")
stack.start.padd(index * sizeof(T))[]
proc getIndexNeg*[T](stack: Stack[T], index: int): T =
proc getIndexNeg*[T](stack: Stack[T], index: int): var T =
when boundsChecks:
if index < 0 or index >= stack.len():
raise newException(Defect, "Attempt to getIndexNeg with an index out of bounds.")

View File

@ -3,8 +3,8 @@ import bitops
import ndstring
import stringutils
import ndtable
import ndlist
import hashtable
type
NdValue* = uint
@ -250,7 +250,7 @@ proc getLength*(val: var NdValue): NatReturn {.inline.} =
proc getIndex*(val: var NdValue, index: NdValue): NatReturn {.inline.} =
if val.isTable():
var tbl = val.asTable()
if not tbl.tableGet(index, val):
if not tbl[].tableGet(index, val):
val = fromNil()
return natOk
@ -281,7 +281,7 @@ proc getIndex*(val: var NdValue, index: NdValue): NatReturn {.inline.} =
proc setIndex*(val: var NdValue, index: NdValue, newval: NdValue): NatReturn {.inline.} =
if val.isTable():
var tbl = val.asTable()
discard tbl.tableSet(index, newval)
discard tbl[].tableSet(index, newval)
return natOk
if val.isList():
@ -291,9 +291,12 @@ proc setIndex*(val: var NdValue, index: NdValue, newval: NdValue): NatReturn {.i
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:
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)
if indexInt == len:
list.add(newval)
else:
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

View File

@ -10,7 +10,6 @@ import types/stringutils
import bitops # needed for value's templates
import types/value
import types/hashtable
import types/ndtable
import types/ndlist
@ -232,17 +231,21 @@ proc run*(chunk: Chunk): InterpretResult =
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())
if listLen == 0:
stack.push(newList[NdValue]().fromList())
else:
let start = stack.getIndexNeg(listLen - 1).addr
var list = newListCopymem[NdValue](start, listLen)
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
var tbl = newNdTable[NdValue, NdValue](tblLen)
for i in countup(0, tblLen - 1):
let val = stack.pop()
let key = stack.pop()
discard tbl.tableSet(key, val) # TODO check for duplicates and disallow
if tbl[].tableSet(key, val):
runtimeError("Attempt to redefine an existing value inside table declaration.")
break
stack.push(tbl.fromTable())
of opLen:
let res = stack.peek().getLength()