new compiler now passes test suite, after outdated tests were refactored to match new precedence rules

This commit is contained in:
prod2 2022-12-03 20:04:44 +01:00
parent 8c90b669e8
commit 57b3a4863e
11 changed files with 223 additions and 24 deletions

View File

@ -281,6 +281,7 @@ The following features are currently missing, and may or may not be added to nds
- OOP with inheritance
- defer statements
- better error reporting
- breaking without labels (from the innermost loop)
The following details could be changed:
- for safety, check whether the script has the +x permission before running it (also check if it is located on a noexec mount)

View File

@ -1 +1 @@
--gc:arc -d:danger
--gc:arc -d:release

View File

@ -219,6 +219,19 @@ proc emitLoop(em: Emitter, loopStart: int, stackLen: int) =
em.writeChunk(0, offset.toDU8)
# jump helper for break
proc jumpToEnd(em: Emitter, scope: Scope) =
## jumps to end of scope and emits enough pops before doing so to match goal stack index
## However, does not affect stack Index during compilation
var delta: int
if scope.function:
delta = em.stackIndex
else:
delta = em.stackIndex - scope.goalStackIndex
em.writePops(delta)
let (jump, _) = em.emitJump(delta, opJump)
# compensate for minus delta from writePops by plus delta at this opJump
scope.jumps.add(jump)
# node compilers
proc emit(em: Emitter, node: Node) =
@ -258,24 +271,33 @@ proc emit(em: Emitter, node: Node) =
for ch in node.children:
em.emit(ch)
if ch.kind == nkExpr: # implicit return if no ;
em.writeChunk(0, opSetLocal)
em.writeChunk(0, scope.goalStackIndex.toDU8())
# all the children statements are complete, scope cleanup
# old compiler endScope(), with function is false
discard em.scopes.pop() # pop scope (still accessible in scope)
# old compiler restore()
# pop new locals in this scope
let delta = em.stackIndex - scope.goalStackIndex
em.writePops(delta)
# close upvalues - old compiler endScope again
while em.locals.len() > 0:
let local = em.locals.pop()
# close upvalues - old compiler endScope
var i = em.locals.high()
while i >= 0:
let local = em.locals[i]
if local.scope == scope and local.captured:
em.writeChunk(0, opCloseUpvalue)
em.writeChunk(0, local.index.toDU8())
if local.depth < em.scopes.high() + 1:
break
i.dec()
# old compiler restore()
# pop new locals in this scope
let delta = em.stackIndex - scope.goalStackIndex
em.writePops(delta)
# remove those locals that are too deep
em.locals.keepIf((it) => it.depth < em.scopes.len())
if not em.stackIndex == scope.goalStackIndex:
# kept for good measure, even if the above should mathematically always return this
@ -284,7 +306,84 @@ proc emit(em: Emitter, node: Node) =
# patch jumps to end of scope
for jump in scope.jumps:
em.patchJump(jump, scope.goalStackIndex)
of nkProc:
# proc declaration
# first, it should be jumped over during declaration
let (jumpOverBody, jumpStackLen) = em.emitJump(1, opFunctionDef)
# function body
# begin scope
let scope = em.newScope(true) # true for function
em.stackIndex = 0 # it's function, so reset it
em.addLocal(":result", 0) # pointing to the return value
if node.parameters.len() > shortArgMax:
em.error("Too many parameters.")
# first thing in the function body is checking arity
em.writeChunk(0, opCheckArity)
em.writeChunk(0, node.parameters.len().uint8())
# adding locals
for i in countup(1, node.parameters.len()):
em.stackIndex = i
em.addLocal(node.parameters[i-1], 0)
# the body
em.emit(node.procBody)
# checking stack integrity (body should increase stack index by one)
let shouldbeStackIndex = node.parameters.len() + 1
if shouldbeStackIndex != em.stackIndex:
em.error("Assertion failed: wrong stackindex in function declaration.")
# end scope start
discard em.scopes.pop()
scope.parentFunction = nil # cleanup cycles, since for functions
# parentFunction points to itself
# end scope further stuff
# closing upvalues - also see tkBlockExpression
var i = em.locals.high()
while i >= 0:
let local = em.locals[i]
if local.scope == scope and local.captured:
em.writeChunk(0, opCloseUpvalue)
em.writeChunk(0, local.index.toDU8())
if local.depth < em.scopes.high() + 1:
break
i.dec()
# restore stackindex
em.stackIndex = scope.goalStackIndex - 1
# old stack index is always goal - 1
# and the opFunctionDef already increased the stack by 1
# return
em.writeChunk(0, opReturn)
# remove locals that were in this scope
em.locals.keepIf((it) => it.depth < em.scopes.len())
# END SCOPE END
# body is over and checks if stack index is restored
em.patchJump(jumpOverBody, jumpStackLen)
# closures are implemented in a way, where the vm
# pops the function from the stack and reads the
# upvalue details from the following bytes
if scope.upvalues.len() > 0:
em.writeChunk(0, opClosure)
em.writeChunk(0, scope.upvalues.len().toDU8())
for upval in scope.upvalues:
em.writeChunk(0, upval.index.toDU8())
em.writeChunk(0, if upval.isLocal: 0'u8 else: 1'u8)
of nkExpr:
em.emit(node.expression)
of nkExprStmt:
@ -440,9 +539,94 @@ proc emit(em: Emitter, node: Node) =
let index = em.chunk.addConstant(node.name.fromNimString())
em.writeChunk(-1, opDefineGlobal)
em.writeChunk(0, index.toDU8())
of nkBreak:
if node.label == "":
em.error("Label expected after break.")
let label = node.label
block ensure:
for i in countdown(em.scopes.high, 0):
let scope = em.scopes[i]
if scope.labels.contains(label):
em.jumpToEnd(scope)
break ensure
em.error(&"Couldn't find label {label}.")
of nkCall:
# get the callee
em.emit(node.function)
# first put all args on the stack
for arg in node.arguments:
em.emit(arg)
let argCount = node.arguments.len()
if argCount > shortArgMax:
em.error("Too many arguments.")
# emit the call opcode
em.writeChunk(-argCount, opCall)
em.writeChunk(0, argCount.uint8)
of nkColonCall:
# lua style table:method()
# put table on stack
em.emit(node.cCollection)
# it should be duplicated
em.writeChunk(1, opDup)
# the index
em.emit(node.cIndex)
# get index
em.writeChunk(-1, opGetIndex)
# now the stack is: table, method
# invert for function call alignment
em.writeChunk(0, opSwap)
# now similar to nkCall
for arg in node.cArguments:
em.emit(arg)
let argCount = node.cArguments.len() + 1
if argCount > shortArgMax:
em.error("Too many arguments.")
em.writeChunk(-argCount, opCall)
em.writeChunk(0, argCount.uint8)
of nkGetIndex:
# collection
em.emit(node.gCollection)
# index
em.emit(node.gIndex)
# get op
em.writeChunk(-1, opGetIndex)
of nkSetIndex:
# collection
em.emit(node.sCollection)
# index
em.emit(node.sIndex)
# new val
em.emit(node.sValue)
# set op
em.writeChunk(-2, opSetIndex)
of nkList:
# put all elements on stack
for ch in node.elems:
em.emit(ch)
else:
raise newException(Defect, &"Unsupported node kind: {$node.kind}")
let count = node.elems.len()
if count > argMax:
em.error("Maximum list length exceeded.")
em.writeChunk(1-count, opCreateList)
em.writeChunk(0, count.toDU8())
of nkTable:
# key val alternating put on stack
for i in countup(0, node.keys.high()):
em.emit(node.keys[i])
em.emit(node.values[i])
let count = node.keys.len()
if count > argMax:
em.error("Maximum table length exceeded.")
em.writeChunk(1 - 2 * count, opCreateTable)
em.writeChunk(0, count.toDU8())
proc emit*(em: Emitter) =
em.emit(em.root)
em.writeChunk(0, opReturn) # required for the vm to exit gracefully
em.writeChunk(0, opReturn) # required for the vm to exit gracefully

View File

@ -54,9 +54,13 @@ type
of nkAnd, nkOr, nkPlus, nkMinus, nkMult, nkDiv, nkEq, nkNeq, nkLess, nkGreater, nkGe, nkLe: # binary ops
left*: Node
right*: Node
of nkCall, nkColonCall:
of nkCall:
function*: Node
arguments*: seq[Node]
of nkColonCall:
cCollection*: Node
cArguments*: seq[Node]
cIndex*: Node
of nkVarDecl:
name*: string
value*: Node # can be nil
@ -87,8 +91,8 @@ proc `$`*(node: Node): string =
let args = node.arguments.map(`$`).join(", ")
result = &"(call {node.function}({args}))"
of nkColonCall:
let args = node.arguments.map(`$`).join(", ")
result = &"(:call {node.function} ({args}))"
let args = node.cArguments.map(`$`).join(", ")
result = &"(call {node.cCollection}:{node.cIndex}({args}))"
of nkConst:
result = &"(const {node.constant})"
of nkDiv:

View File

@ -267,8 +267,7 @@ proc parseIndexOrCall(parser: Parser): Node =
var args: seq[Node] = @[]
if parser.match(tkLeftParen):
args = parser.parseArgList()
let funct = Node(kind: nkGetIndex, gCollection: result, gIndex: ident, line: parser.line)
result = Node(kind: nkColonCall, arguments: args, function: funct, line: parser.line)
result = Node(kind: nkColonCall, cArguments: args, cCollection: result, cIndex: ident, line: parser.line)
elif parser.previous.get().tokenType == tkLeftParen:
# call
let args = parser.parseArgList()
@ -397,9 +396,12 @@ proc parsePipeCall(parser: Parser): Node =
# to have such lower precedence ops, use parens: 5 :: (long expression)(arg1, arg2)
# case 1: right is already a call or coloncall
if right.kind in {nkCall, nkColonCall}:
if right.kind == nkCall:
right.arguments.insert(result, 0)
result = right
result = right
elif right.kind == nkColonCall:
right.cArguments.insert(result, 0)
result = right
# else: right val is a function which we call
else:
result = Node(kind: nkCall, arguments: @[result], function: right, line: parser.line)

View File

@ -84,7 +84,8 @@ proc swap*[T](stack: var Stack[T]) =
proc deleteTopN*[T](stack: var Stack[T], n: Natural) =
stack.top = stack.top.psub(sizeof(T) * n)
when boundsChecks:
if stack.top.pless(stack.start):
if stack.top.pless(stack.start.psub(sizeof(T))):
# psub sizeof(T), because top can be below start if its length is exactly 0
raise newException(Defect, "Stacktop sunk below the start after a deleteTopN.")
proc getIndex*[T](stack: Stack[T], index: int): var T {.inline.} =

View File

@ -323,7 +323,7 @@ proc run*(chunk: Chunk): InterpretResult =
if tbl[].tableSet(key, val):
runtimeError("Attempt to redefine an existing value inside table declaration.")
break
# stack.deleteTopN(tblLen * 2) # THIS IS NOT NEEDED, pops are done
#stack.deleteTopN(tblLen * 2) # (?) TODO CHECK THIS - THIS IS NOT NEEDED, pops are done
stack.push(tbl.fromTable())
of opLen:
let res = stack.peek().getLength()

View File

@ -23,6 +23,7 @@ var f1 = proc() {
}
}
};
f1()()()()();
//expect:1.0
//expect:2.0
@ -73,7 +74,7 @@ inst2["get"]();
// capturing args
var argcap = proc(n)
proc() print (n)
proc() print(n)
;
//expect:8.1

View File

@ -26,6 +26,7 @@ print(letters[3]); //expect:d
var i = 0;
while (i < #letters) {
letterNumbers[letters[i]] = i;
i = i + 1;
};
print(letterNumbers.d); //expect:3.0

5
tests/procedures.nds Normal file
View File

@ -0,0 +1,5 @@
var rp = proc(x) print(x);
rp(5);
//expect:5.0

View File

@ -16,7 +16,7 @@ print (six);
//expect:4.0
//expect:6.0
print (1 + 2 * 3 :: tostring() + "2");
print ((1 + 2 * 3 :: tostring()) + "2");
//expect:7.02
// indexing tables with .
@ -78,7 +78,7 @@ var returner = @{
method = proc(self) proc(n) n * 2
};
1.7 :: returner:method :: print;
1.7 :: (returner:method) :: print;
//expect:3.4
print("finish");