add ampersand operator, further work on lists and tables
This commit is contained in:
parent
0ec5bfb60a
commit
159f256a84
|
@ -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 =
|
||||
|
|
|
@ -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 '=':
|
||||
|
|
|
@ -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
|
|
@ -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.} =
|
||||
|
|
|
@ -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
|
|
@ -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.")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue