new compiler now passes test suite, after outdated tests were refactored to match new precedence rules
This commit is contained in:
parent
8c90b669e8
commit
57b3a4863e
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.} =
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
var rp = proc(x) print(x);
|
||||
|
||||
rp(5);
|
||||
//expect:5.0
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue