From 57b3a4863eb3a99534c002d33f138088c3f3ccdc Mon Sep 17 00:00:00 2001 From: prod2 <95874442+prod2@users.noreply.github.com> Date: Sat, 3 Dec 2022 20:04:44 +0100 Subject: [PATCH] new compiler now passes test suite, after outdated tests were refactored to match new precedence rules --- docs/reference.md | 1 + nim.cfg | 2 +- src/ndspkg/compv2/emitter.nim | 206 ++++++++++++++++++++++++++++++++-- src/ndspkg/compv2/node.nim | 10 +- src/ndspkg/compv2/parser.nim | 10 +- src/ndspkg/types/stack.nim | 3 +- src/ndspkg/vm.nim | 2 +- tests/closures.nds | 3 +- tests/collections.nds | 1 + tests/procedures.nds | 5 + tests/sugar.nds | 4 +- 11 files changed, 223 insertions(+), 24 deletions(-) create mode 100644 tests/procedures.nds diff --git a/docs/reference.md b/docs/reference.md index e8edc8d..ab06c92 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -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) diff --git a/nim.cfg b/nim.cfg index b014de7..76b350f 100644 --- a/nim.cfg +++ b/nim.cfg @@ -1 +1 @@ ---gc:arc -d:danger \ No newline at end of file +--gc:arc -d:release \ No newline at end of file diff --git a/src/ndspkg/compv2/emitter.nim b/src/ndspkg/compv2/emitter.nim index b72f12a..cc62ba6 100644 --- a/src/ndspkg/compv2/emitter.nim +++ b/src/ndspkg/compv2/emitter.nim @@ -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 \ No newline at end of file + em.writeChunk(0, opReturn) # required for the vm to exit gracefully diff --git a/src/ndspkg/compv2/node.nim b/src/ndspkg/compv2/node.nim index 16ae34b..37ee2b3 100644 --- a/src/ndspkg/compv2/node.nim +++ b/src/ndspkg/compv2/node.nim @@ -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: diff --git a/src/ndspkg/compv2/parser.nim b/src/ndspkg/compv2/parser.nim index fc24dfe..1815b2f 100644 --- a/src/ndspkg/compv2/parser.nim +++ b/src/ndspkg/compv2/parser.nim @@ -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) diff --git a/src/ndspkg/types/stack.nim b/src/ndspkg/types/stack.nim index 2d78e12..237d840 100644 --- a/src/ndspkg/types/stack.nim +++ b/src/ndspkg/types/stack.nim @@ -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.} = diff --git a/src/ndspkg/vm.nim b/src/ndspkg/vm.nim index e3dfaab..fdef708 100644 --- a/src/ndspkg/vm.nim +++ b/src/ndspkg/vm.nim @@ -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() diff --git a/tests/closures.nds b/tests/closures.nds index 8f29797..a2de484 100644 --- a/tests/closures.nds +++ b/tests/closures.nds @@ -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 diff --git a/tests/collections.nds b/tests/collections.nds index 454fca5..f8cc8ae 100644 --- a/tests/collections.nds +++ b/tests/collections.nds @@ -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 diff --git a/tests/procedures.nds b/tests/procedures.nds new file mode 100644 index 0000000..47a6683 --- /dev/null +++ b/tests/procedures.nds @@ -0,0 +1,5 @@ + +var rp = proc(x) print(x); + +rp(5); +//expect:5.0 diff --git a/tests/sugar.nds b/tests/sugar.nds index 619f4ba..ee963b4 100644 --- a/tests/sugar.nds +++ b/tests/sugar.nds @@ -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");