diff --git a/src/nds.nim b/src/nds.nim index 2c4c4b9..f4cf9e5 100644 --- a/src/nds.nim +++ b/src/nds.nim @@ -5,11 +5,6 @@ import ndspkg/compv2/emitter import ndspkg/config import ndspkg/chunk -when compilerChoice == cmOne: - import ndspkg/compiler/compiler - import ndspkg/compiler/types - - import os import strformat @@ -17,39 +12,24 @@ type Result = enum rsOK, rsCompileError, rsRuntimeError proc interpret(name: string, source: string): Result = - when compilerChoice == cmAst: - let parser = newParser(name, source) - let node = parser.parse() + let parser = newParser(name, source) + let node = parser.parse() + if parser.hadError: + return rsCompileError + when debugDumpAst: echo $node - elif compilerChoice == cmTwo: - let parser = newParser(name, source) - let node = parser.parse() - if parser.hadError: - return rsCompileError - when debugDumpAst: - echo $node - let emitter = newEmitter(name, node) - emitter.emit() - if emitter.hadError: - return rsCompileError - when debugDumpChunk: - emitter.chunk.disassembleChunk() + let emitter = newEmitter(name, node) + emitter.emit() + if emitter.hadError: + return rsCompileError + when debugDumpChunk: + emitter.chunk.disassembleChunk() - case emitter.chunk.run(): - of irOK: - rsOK - of irRuntimeError: - rsRuntimeError - elif compilerChoice == cmOne: - let compiler = newCompiler(name, source) - compiler.compile() - if compiler.hadError: - return rsCompileError - case compiler.chunk.run(): - of irOK: - rsOK - of irRuntimeError: - rsRuntimeError + case emitter.chunk.run(): + of irOK: + rsOK + of irRuntimeError: + rsRuntimeError proc repl = diff --git a/src/ndspkg/compiler/collections.nim b/src/ndspkg/compiler/collections.nim deleted file mode 100644 index 9c76e9d..0000000 --- a/src/ndspkg/compiler/collections.nim +++ /dev/null @@ -1,92 +0,0 @@ -{.used.} - -import ../scanner -import ../chunk -import ../types/value -import bitops - -import types -import utils -import precedence - -# lists - -proc parseList(comp: Compiler) = - var count: int - while comp.current.tokenType != tkRightBracket: - comp.expression() - count.inc() - if comp.current.tokenType != tkRightBracket: - comp.consume(tkComma, "Comma expected after list member.") - comp.consume(tkRightBracket, "Right bracket expected after list members.") - if count > argMax: - comp.error("Maximum list length exceeded.") - comp.writeChunk(1 - count, opCreateList) - comp.writeChunk(0, count.toDU8()) - - -tkStartList.genRule(parseList, nop, pcNone) - -# tables - -proc parseTable(comp: Compiler) = - var count: int - while comp.current.tokenType notin {tkEof, tkRightBrace}: - if comp.match(tkLeftBracket): - comp.expression() - comp.consume(tkRightBracket, "Expect ']' after key.") - elif comp.match(tkIdentifier): - let ident = comp.previous.text.fromNimString() - comp.writeConstant(ident) - else: - comp.error("Key expected in table (perhaps you forgot to encapsulate the key with []?).") - if not comp.match(tkColon): - comp.consume(tkEqual, "Equal sign or colon expected after key.") - comp.expression() - count.inc() - if comp.current.tokenType != tkRightBrace: - comp.consume(tkComma, "Comma expected after key-value pair.") - - comp.consume(tkRightBrace, "Right brace expected after table members.") - if count > argMax: - comp.error("Maximum table length exceeded.") - comp.writeChunk(1 - 2 * count, opCreateTable) - comp.writeChunk(0, count.toDU8()) - -tkStartTable.genRule(parseTable, nop, pcNone) - -# len op - -proc parseLen(comp: Compiler) = - comp.expression() - comp.writeChunk(0, opLen) - -tkHashtag.genRule(parseLen, nop, pcNone) - -# get/set index - -proc parseIndex(comp: Compiler) = - # the index - comp.expression() - comp.consume(tkRightBracket, "Right bracket expected after index.") - if comp.match(tkEqual): - comp.parsePrecedence(pcNonAssignTop) - comp.writeChunk(-2, opSetIndex) - else: - comp.writeChunk(-1, opGetIndex) - -tkLeftBracket.genRule(nop, parseIndex, pcIndex) - -proc parseDotIndex(comp: Compiler) = - # the index - comp.consume(tkIdentifier, "Identifier expected after dot index.") - let index = comp.previous.text.fromNimString() - comp.writeConstant(index) - - if comp.match(tkEqual): - comp.parsePrecedence(pcNonAssignTop) - comp.writeChunk(-2, opSetIndex) - else: - comp.writeChunk(-1, opGetIndex) - -tkDot.genRule(nop, parseDotIndex, pcIndex) \ No newline at end of file diff --git a/src/ndspkg/compiler/compiler.nim b/src/ndspkg/compiler/compiler.nim deleted file mode 100644 index e1f3182..0000000 --- a/src/ndspkg/compiler/compiler.nim +++ /dev/null @@ -1,27 +0,0 @@ -import ../scanner -import ../chunk -import ../config - -import types -import utils - -# the following modify precedence by being imported -import expressions -import controlflow -import collections -import functions -import statement - -proc compile*(comp: Compiler) = - comp.scanner = newScanner(comp.source) - comp.writeChunk(0, opNil) - # the starting stackIndex is 0, which points to this nil - # it is correctly set to delta = 0!!! - comp.advance() - while comp.current.tokenType != tkEof: - comp.statement() - comp.writeChunk(-1, opPop) - comp.writeChunk(0, opReturn) - when debugDumpChunk: - if not comp.hadError: - comp.chunk.disassembleChunk() diff --git a/src/ndspkg/compiler/controlflow.nim b/src/ndspkg/compiler/controlflow.nim deleted file mode 100644 index e902143..0000000 --- a/src/ndspkg/compiler/controlflow.nim +++ /dev/null @@ -1,92 +0,0 @@ -{.used.} - -import ../scanner -import ../chunk - -import types -import utils -import precedence -import jumps - -proc ifExpr(comp: Compiler) = - # if expressions return the body if condition is truthy, - # the else expression otherwise, unless there is no else: - # if there is no else, it returns the condition if it is falsey - comp.consume(tkLeftParen, "Expect '(' after 'if'.") - comp.expression() - comp.consume(tkRightParen, "Expect ')' after condition.") - - var thenStacklen = 0 - let thenJump = comp.emitJump(0, opJumpIfFalse, thenStacklen) - - # conditional code that can be jumped over must leave the stack in tact! - - comp.writeChunk(-1, opPop) - comp.expression() - # net change to stack: -1 + 1 = 0 - - var elseStacklen = 0 - let elseJump = comp.emitJump(0, opJump, elseStacklen) - comp.patchJump(thenJump, thenStacklen) - - if comp.match(tkElse): - comp.writeChunk(-1, opPop) - comp.expression() - - comp.patchJump(elseJump, elseStacklen) - - -tkIf.genRule(ifExpr, nop, pcNone) - -proc andExpr(comp: Compiler) = - var stacklen = 0 - let endJump = comp.emitJump(0, opJumpIfFalse, stacklen) - - comp.writeChunk(-1, opPop) - comp.parsePrecedence(pcAnd) - # net effect on stack: -1 + 1 = 0 - - comp.patchJump(endJump, stacklen) - -tkAnd.genRule(nop, andExpr, pcAnd) - -proc orExpr(comp: Compiler) = - var elseStacklen = 0 - var endStacklen = 0 - let elseJump = comp.emitJump(0, opJumpIfFalse, elseStacklen) - let endJump = comp.emitJump(0, opJump, endStacklen) - - comp.patchJump(elseJump, elseStacklen) - - comp.writeChunk(-1, opPop) - comp.parsePrecedence(pcOr) - # net effect on stack: -1 + 1 = 0 - - comp.patchJump(endJump, endStacklen) - -tkOr.genRule(nop, orExpr, pcOr) - -proc parseWhile(comp: Compiler) = - - comp.writeChunk(1, opNil) # return value - let loopStart = comp.chunk.len - let loopStacklen = comp.stackIndex - comp.consume(tkLeftParen, "Expect '(' after 'while'.") - # condition - comp.expression() - comp.consume(tkRightParen, "Expect ')' after condition.") - - var exitStacklen = 0 - let exitJump = comp.emitJump(-1, opJumpIfFalsePop, exitStacklen) - # this cannot be handled with just opPop, since the net change in the - # stack size inside code that is conditional must be 0! - - # body - comp.writeChunk(-1, opPop) # pop the old return value - comp.expression() - # net stack change: 1 + -1 = 0 - - comp.emitLoop(loopstart = loopStart, delta = 0, op = opLoop, loopStacklen) - comp.patchJump(exitJump, exitStacklen) - -tkWhile.genRule(parseWhile, nop, pcNone) diff --git a/src/ndspkg/compiler/expressions.nim b/src/ndspkg/compiler/expressions.nim deleted file mode 100644 index 15eb483..0000000 --- a/src/ndspkg/compiler/expressions.nim +++ /dev/null @@ -1,109 +0,0 @@ -{.used.} - -import strutils -import bitops # needed for value - -import ../scanner -import ../chunk -import ../types/value -import ../config - -import types -import utils -import precedence - -# EXPRESSIONS - -proc number(comp: Compiler) = - # assume the number is already advanced through - let value = comp.previous.text.parseFloat.fromFloat() - comp.writeConstant(value) - -tkNumber.genRule(number, nop, pcNone) - -proc expFalse(comp: Compiler) = - comp.writeChunk(1, opFalse) - -tkFalse.genRule(expFalse, nop, pcNone) - -proc expTrue(comp: Compiler) = - comp.writeChunk(1, opTrue) - -tkTrue.genRule(expTrue, nop, pcNone) - -proc expNil(comp: Compiler) = - comp.writeChunk(1, opNil) - -tkNil.genRule(expNil, nop, pcNone) - -proc expString(comp: Compiler) = - let value = comp.previous.text[1..^2].fromNimString() - comp.writeConstant(value) - -tkString.genRule(expString, nop, pcNone) - -proc unary(comp: Compiler) = - let opType = comp.previous.tokenType - - comp.parsePrecedence(pcUnary) - - case opType: - of tkMinus: - comp.writeChunk(0, opNegate) - of tkBang: - comp.writeChunk(0, opNot) - else: - discard # unreachable - -tkBang.genRule(unary, nop, pcNone) - -proc binary(comp: Compiler) = - let opType = comp.previous.tokenType - # safety checked in parsePrecedence - let rule = opType.getRule() - comp.parsePrecedence(rule.applyRule.increment) - - case opType: - of tkPlus: - comp.writeChunk(-1, opAdd) - of tkMinus: - comp.writeChunk(-1, opSubtract) - of tkStar: - comp.writeChunk(-1, opMultiply) - of tkSlash: - comp.writeChunk(-1, opDivide) - of tkEqualEqual: - comp.writeChunk(-1, opEqual) - of tkBangEqual: - comp.writeChunk(-1, opEqual) - comp.writeChunk(0, opNot) - of tkGreater: - comp.writeChunk(-1, opGreater) - of tkLess: - comp.writeChunk(-1, opLess) - of tkGreaterEqual: - comp.writeChunk(-1, opLess) - comp.writeChunk(0, opNot) - of tkLessEqual: - comp.writeChunk(-1, opGreater) - comp.writeChunk(0, opNot) - else: - return # unreachable - -tkMinus.genRule(unary, binary, pcTerm) -tkPlus.genRule(nop, binary, pcTerm) -tkSlash.genRule(nop, binary, pcFactor) -tkStar.genRule(nop, binary, pcFactor) - -tkEqualEqual.genRule(nop, binary, pcEquality) -tkBangEqual.genRule(nop, binary, pcEquality) -tkGreater.genRule(nop, binary, pcComparison) -tkGreaterEqual.genRule(nop, binary, pcComparison) -tkLess.genRule(nop, binary, pcComparison) -tkLessEqual.genRule(nop, binary, pcComparison) - -proc parseAmpersand(comp: Compiler) = - # just a simple expression separator - discard - -tkAmpersand.genRule(nop, parseAmpersand, pcAmpersand) \ No newline at end of file diff --git a/src/ndspkg/compiler/functions.nim b/src/ndspkg/compiler/functions.nim deleted file mode 100644 index 7159c11..0000000 --- a/src/ndspkg/compiler/functions.nim +++ /dev/null @@ -1,148 +0,0 @@ -{.used.} - -import strformat -import bitops - -import ../scanner -import ../chunk -import ../types/value -import ../config - -import types -import utils -import precedence -import jumps -import scope - -proc grouping(comp: Compiler) = - # assume initial '(' is already consumed - comp.expression() - comp.consume(tkRightParen, "Expect ')' after expression.") - -proc parseArgs(comp: Compiler): int = - var argcount = 0 - # put args on stack - while comp.current.tokenType notin {tkRightParen, tkEof}: - comp.expression() - inc argcount - if comp.current.tokenType != tkRightParen: - comp.consume(tkComma, "Expected ',' between arguments in function calls.") - comp.consume(tkRightParen, "Expected ')' after arguments in function calls.") - return argcount - - -proc parseCall(comp: Compiler) = - # ( consumed - - let argcount = comp.parseArgs() - - # emit call - comp.writeChunk(-argcount, opCall) - comp.writeChunk(0, argcount.uint8) - -tkLeftParen.genRule(grouping, parseCall, pcCall) - -proc parsePipeCall(comp: Compiler) = - # can be followed by: - # idents - # idents+indexes - # NOT calls, so the parsePrecedence after it should look for pcIndex at most - - # get the function on the stack - comp.parsePrecedence(pcIndex) - - # swap the function and the first arg - comp.writeChunk(0, opSwap) - - var argcount = 1 - if comp.match(tkLeftParen): - argcount = 1 + comp.parseArgs() - - comp.writeChunk(-argcount, opCall) - comp.writeChunk(0, argcount.uint8) - -tkDoublecolon.genRule(nop, parsePipeCall, pcAmpersand) - -proc parseArrowCall(comp: Compiler) = - # -> syntactic sugar for table "methods" - - # when called, the stack contains the table - # the table should be duplicated first - comp.writeChunk(1, opDup) - # then the index should be read - comp.consume(tkIdentifier, "Identifier (index) expected after '->'.") - let index = comp.previous.text.fromNimString() - comp.writeConstant(index) - comp.writeChunk(-1, opGetIndex) - # the stack's state now is table, method - # invert to get the right alignment for further function call - comp.writeChunk(0, opSwap) - # now the same code as :: applies - var argcount = 1 - # args optional - if comp.match(tkLeftParen): - argcount = 1 + comp.parseArgs() - - comp.writeChunk(-argcount, opCall) - comp.writeChunk(0, argcount.uint8) - - -tkArrow.genRule(nop, parseArrowCall, pcIndex) - -proc parseFunct*(comp: Compiler) = - - # jump over - var jumpStacklen = 0 - let jumpOverBody = comp.emitJump(1, opFunctionDef, jumpStacklen) - - comp.consume(tkLeftParen, "Expected '(' after keyword 'funct'.") - - var params: seq[string] - # parameters - while comp.current.tokenType == tkIdentifier: - comp.advance() - params.add(comp.previous.text) - if comp.current.tokenType == tkRightParen: - break - comp.consume(tkComma, "Expected ',' to separate items in the parameter list.") - - comp.consume(tkRightParen, "Expected ')' after parameter list.") - - # function body: - let functII = comp.chunk.len - comp.beginScope(function = true) # this saves the old stackindex, sets it to 0, :function and :result at index 0 - # assumption: - # the caller will create the following stack for the function to run in: - # [0] = return value placeholder - # [1] = arg #1 - # [2] = arg #2 - # [3] = arg #3 - - if params.len > shortArgMax: - comp.error("Too many parameters.") - - comp.writeChunk(0, opCheckArity) # runtime arity check - comp.writeChunk(0, params.len.uint8) - - for i in countup(1, params.len): - comp.stackIndex = i - comp.addLocal(params[i-1], 0) - comp.expression() - let shouldbeStackIndex = params.len + 1 - if shouldbeStackIndex != comp.stackIndex: - comp.error(&"Assertion failed: wrong stackindex ({comp.stackIndex}) in function declaration (should be {shouldbeStackIndex}).") - let f = comp.endScope() - dec comp.stackIndex # the previous end scope did not put anything on the stack, it is jumped over - - # end of function declaration: - comp.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 f.upvalues.len() > 0: - comp.writeChunk(0, opClosure) - comp.writeChunk(0, f.upvalues.len().toDU8()) - for upval in f.upvalues: - comp.writeChunk(0, upval.index.toDU8()) - comp.writeChunk(0, if upval.isLocal: 0'u8 else: 1'u8) - -tkProc.genRule(parseFunct, nop, pcNone) diff --git a/src/ndspkg/compiler/jumps.nim b/src/ndspkg/compiler/jumps.nim deleted file mode 100644 index c719171..0000000 --- a/src/ndspkg/compiler/jumps.nim +++ /dev/null @@ -1,40 +0,0 @@ -import ../chunk -import ../config - -import types -import utils - -# JUMP HELPERS - -proc emitJump*(comp: Compiler, delta: int, op: OpCode, stacklen: var int): int = - # delta -> 0 if the jump does not pop - # delta -> -1 if the jump pops the condition from the stack - comp.writeChunk(delta, op) - comp.writeChunk(0, 0xffffff.toDU8) - stacklen = comp.stackIndex - comp.chunk.len - argSize - -proc patchJump*(comp: Compiler, offset: int, stacklen: int) = - if comp.stackIndex != stacklen: - comp.error("Assertion failed: loop doesn't preserve stackindex.") - let jump = (comp.chunk.len - offset - argSize) - - if (jump > argMax): - comp.error("Too much code to jump over.") - - let jumpt = jump.toDU8 - - comp.chunk.code[offset] = jumpt[0] - comp.chunk.code[offset + 1] = jumpt[1] - -proc emitLoop*(comp: Compiler, loopstart: int, delta: int, op: OpCode, stacklen: int) = - if comp.stackIndex != stacklen: - comp.error("Assertion failed: loop doesn't preserve stackindex.") - comp.writeChunk(delta, op) - - let offset = comp.chunk.len - loopstart + argSize - if offset > argMax: - comp.error("Loop body too large.") - - comp.writeChunk(0, offset.toDU8) - diff --git a/src/ndspkg/compiler/precedence.nim b/src/ndspkg/compiler/precedence.nim deleted file mode 100644 index d79668f..0000000 --- a/src/ndspkg/compiler/precedence.nim +++ /dev/null @@ -1,82 +0,0 @@ -import sugar -import strformat - -import ../scanner -import ../types/value -import ../config - -import types -import utils - -# PARSE RULE/PRECEDENCE MISC -proc nop*(comp: Compiler) = discard - -var rules*: array[TokenType, ParseRule] -template genRule*(ttype: TokenType, tprefix: (Compiler) -> void, tinfix: (Compiler) -> void, tprec: Precedence) = - if tprec == pcUnary: - 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) - -for i in TokenType: - genRule(i, nop, nop, pcNone) - -proc getRule*(opType: TokenType): ParseRule = - rules[opType] - -proc applyRule*(rule: ParseRule): Precedence = - # returns the rule's precedence - rule.prec - -proc increment*(prec: Precedence): Precedence = - # increases precedence by one - if prec == pcPrimary: - raise newException(Exception, "Invalid ruletable, pcPrimary precedence increment attempted.") - else: - Precedence(int(prec) + 1) - -proc `<=`*(a, b: Precedence): bool = - int(a) <= int(b) - -proc parsePrecedence*(comp: Compiler, prec: Precedence) = - comp.advance() - let rule = comp.previous.tokenType.getRule() - - if rule.prefix != nop: - comp.canAssign = prec <= pcAssignment - - rule.prefix(comp) - - while prec <= comp.current.tokenType.getRule().prec: - comp.advance() - # checked for isSome in the loop - # since advance moves current to previous - if comp.previous.tokenType.getRule().infix == nop: - # should never happen, as having a precedence set - # means that it is a binary op - comp.error("Invalid rule table.") - return - else: - let infixRule = comp.previous.tokenType.getRule().infix - infixRule(comp) - else: - comp.error(&"Expect expression, got {($comp.previous.tokenType)[2..^1]}.") - -proc expression*(comp: Compiler) = - ## The lowest precedence among the pratt-parsed expressions - - # DO NOT ADD ANYTHING HERE - # only use the pratt table for parsing expressions! - when assertionsVM: - let oldStackIndex = comp.stackIndex - comp.parsePrecedence(pcExprTop) - when assertionsVM: - let diff = comp.stackIndex - oldStackIndex - if diff != 1: - comp.error(&"Assertion failed: expression increased ({oldStackIndex} -> {comp.stackIndex}) the stack index by {diff} (should be 1).") - diff --git a/src/ndspkg/compiler/scope.nim b/src/ndspkg/compiler/scope.nim deleted file mode 100644 index f34576d..0000000 --- a/src/ndspkg/compiler/scope.nim +++ /dev/null @@ -1,99 +0,0 @@ -import strformat -import sequtils -import sugar - -import ../scanner -import ../chunk -import ../config - -import types -import utils -import jumps - - -# SCOPE HELPERS -proc beginScope*(comp: Compiler, function: bool = false) = - let scope = comp.newScope(function) - - if not function: - while comp.match(tkLabel): - let label = comp.previous.text[1..^1] - scope.labels.add(label) - # only put the opNil if it's not a function scope, since - # function scopes are initialized by the caller - comp.writeChunk(1, opNil) - else: - # if it's a function scope, the frame will move - # access to outside locals is also limited to upvalues and closures - comp.stackIndex = 0 - - for label in scope.labels: - comp.addLocal(&":{label}", delta = 0) - -proc scopeRetIndex*(comp: Compiler): int = - let scope = comp.scopes[comp.scopes.high()] - # this is an illegal operation for function scopes, as they work differently - if scope.function: - comp.error("Assertion failed, Internal error, scopeRetIndex calculation for a function scope.") - return scope.goalStackIndex - -proc restore*(comp: Compiler, scope: Scope) = - let delta = comp.stackIndex - scope.goalStackIndex - comp.writePops(delta) - if not comp.stackIndex == scope.goalStackIndex: - comp.error("Assertion failed in restore") - -proc restoreInFunct*(comp: Compiler, scope: Scope) = - #let pops = comp.stackIndex - #comp.writePops(pops) - - comp.stackIndex = scope.goalStackIndex - #when debugCompiler: - # debugEcho &"Restored function scope: delta {pops}; new stackindex: {comp.stackIndex}" - -proc jumpToEnd*(comp: Compiler, scope: Scope) = - ## Jumps to the end of scope, does not affect stackIndex - var delta: int - if scope.function: - delta = comp.stackIndex - else: - delta = comp.stackIndex - scope.goalStackIndex - comp.writePops(delta) - var s = 0 # discard the saved stack length - let jmp = comp.emitJump(delta, opJump, s) - scope.jumps.add(jmp) - -proc endScope*(comp: Compiler): Scope = - # remove locals - let popped = comp.scopes.pop() - if popped.parentFunction == popped: - popped.parentFunction = nil # cleanup cycles - let function = popped.function - # close upvalues - var i = comp.locals.high() - while i >= 0: - let local = comp.locals[i] - if local.scope == popped and local.captured: - comp.writeChunk(0, opCloseUpvalue) - comp.writeChunk(0, local.index.toDU8()) - if local.depth < comp.scopes.len(): - break - i.dec() - - # restore the stackIndex, emit pops - if function: - comp.restoreInFunct(popped) - else: - comp.restore(popped) - # patch jumps to after the scope (such jumps from breaks emit the pops before jumping) - for jump in popped.jumps: - if function: - # all jumps should only happen to named block expressions - comp.error("Assertion failed: jump attempt to end function.") - comp.patchJump(jump, popped.goalStackIndex) - if function: - comp.writeChunk(0, opReturn) - # remove locals from the comp object that were of this scope - comp.locals.keepIf((it) => it.depth < comp.scopes.len()) - popped - diff --git a/src/ndspkg/compiler/statement.nim b/src/ndspkg/compiler/statement.nim deleted file mode 100644 index f89a00d..0000000 --- a/src/ndspkg/compiler/statement.nim +++ /dev/null @@ -1,66 +0,0 @@ -{.used.} - -import ../scanner -import ../chunk - -import types -import utils -import precedence -import scope -import variables - -# below are the expressions that can contain statements in some way -# the only expressions that can contain a statement are: - # the block expression -proc statement*(comp: Compiler) - -proc parseBlock(comp: Compiler) = - ## Despite the name, can be used for statements if the arg statement is true - ## Also can be used for function bodies - comp.beginScope() - while comp.current.tokenType != tkRightBrace and comp.current.tokenType != tkEof: - comp.statement() - discard comp.endScope() - - comp.consume(tkRightBrace, "Expect '}' after block.") - -tkLeftBrace.genRule(parseBlock, nop, pcNone) - -# statements - -proc breakStatement(comp: Compiler) = - if not comp.match(tkLabel): - comp.error("Label expected after break.") - - let label = comp.previous.text[1..^1] - for i in countdown(comp.scopes.high, 0): - let scope = comp.scopes[i] - if scope.labels.contains(label): - comp.jumpToEnd(scope) - break - - comp.consume(tkSemicolon, "Semicolon expected after break statement.") - if comp.current.tokenType != tkRightBrace: - comp.error("Break statement must be the last element inside the innermost block it is in.") - -proc statement*(comp: Compiler) = - if comp.match(tkVar): - comp.varStatement() - comp.consume(tkSemicolon, "Semicolon expected after expression statement.") -# elif comp.match(tkDef): -# comp.procStatement() -# comp.consume(tkSemicolon, "Semicolon expected after procedure declaration.") - elif comp.match(tkBreak): - comp.breakStatement() - else: - comp.expression() - if comp.current.tokenType == tkRightBrace: - # last expression in the block expression -> semicolon optional - comp.writeChunk(0, opSetLocal) - comp.writeChunk(0, comp.scopeRetIndex().toDU8()) - else: - comp.consume(tkSemicolon, "Semicolon expected after expression statement.") - comp.writeChunk(-1, opPop) - - if comp.panicMode: - comp.synchronize() \ No newline at end of file diff --git a/src/ndspkg/compiler/types.nim b/src/ndspkg/compiler/types.nim deleted file mode 100644 index c1bb54d..0000000 --- a/src/ndspkg/compiler/types.nim +++ /dev/null @@ -1,91 +0,0 @@ -import sugar - -import ../scanner -import ../chunk - -type - Local* = ref object - name*: string # name of this local - index*: int # what is its index in the stack (0 is the stack bottom - nil in the main function) - depth*: int # depth of this local - # if depth is -1, the variable cannot be referenced yet - # its depth will be set once its first ever value is determined - scope*: Scope # the innermost scope of this local - captured*: bool - - Upvalue* = ref object - index*: int - isLocal*: bool - - Scope* = ref object - labels*: seq[string] - goalStackIndex*: int # the stack count it started with plus 1 - jumps*: seq[int] # jumps to be patched that jump to the end - function*: bool # if true, it is a function - parentFunction*: Scope # if not a function, which scope is the innermost function it's in (if it's a function this points to itself) - # if parentFunction is nil, the scope is not inside a function - upvalues*: seq[Upvalue] # only needed for functions - - Compiler* = ref object - # - # input - # - scanner*: Scanner - source*: string - # - # state - # - previous*: Token - current*: Token - canAssign*: bool - locals*: seq[Local] - scopes*: seq[Scope] - stackIndex*: int # how large the stack is - # when there's an error both are set - # panic mode can be turned off e.g. at block boundaries - panicMode*: bool - # - # output - # - chunk*: Chunk - hadError*: bool - - Precedence* = enum - pcNone, pcExprTop, pcAmpersand, pcAssignment, pcNonAssignTop, pcOr, pcAnd, pcEquality, pcComparison, - pcTerm, pcFactor, pcUnary, pcCall, pcIndex, 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 - prefix*: (Compiler) -> void - infix*: (Compiler) -> void - prec*: Precedence # only relevant to infix, prefix always has pcUnary - - -proc newScope*(comp: Compiler, function: bool): Scope = - result.new() - #result.depth = comp.scopes.len + 1 - result.function = function - result.goalStackIndex = comp.stackIndex + 1 - if function: - result.parentFunction = result - elif comp.scopes.len() > 0 and comp.scopes[comp.scopes.high()].parentFunction != nil: - result.parentFunction = comp.scopes[comp.scopes.high()].parentFunction - - comp.scopes.add(result) - -# HELPERS FOR THE COMPILER TYPE - -proc newCompiler*(name: string, source: string): Compiler = - result = new(Compiler) - result.chunk = initChunk(name) - result.source = source - result.hadError = false - result.panicMode = false - result.canAssign = true - result.locals = @[] - result.scopes = @[] \ No newline at end of file diff --git a/src/ndspkg/compiler/utils.nim b/src/ndspkg/compiler/utils.nim deleted file mode 100644 index 0fd5c82..0000000 --- a/src/ndspkg/compiler/utils.nim +++ /dev/null @@ -1,95 +0,0 @@ -import strformat - -import ../chunk -import ../scanner -import ../config -import ../types/value - -import types - -proc errorAt*(comp: Compiler, line: int, msg: string, at: string = "") = - if comp.panicMode: - return - write stderr, &"[line {line}] Error " - if at.len > 0: - write stderr, &"at {at} " - write stderr, msg - write stderr, "\n" - comp.hadError = true - comp.panicMode = true - -proc error*(comp: Compiler, msg: string) = - ## create a simple error message - comp.errorAt(comp.previous.line, msg) - -proc errorAtCurrent*(comp: Compiler, msg: string) = - comp.errorAt(comp.current.line, msg) - -proc advance*(comp: Compiler) = - comp.previous = comp.current - while true: - comp.current = comp.scanner.scanToken() - if (comp.current.tokenType != tkError): - break - comp.errorAtCurrent(comp.current.text) - -proc match*(comp: Compiler, tokenType: TokenType): bool = - if comp.current.tokenType == tokenType: - comp.advance() - true - else: - false - -proc consume*(comp: Compiler, tokenType: TokenType, msg: string) = - if comp.current.tokenType == tokenType: - comp.advance() - else: - comp.errorAtCurrent(msg) - -proc synchronize*(comp: Compiler) = - comp.panicMode = false - while comp.current.tokenType != tkEof: - if comp.previous.tokenType in {tkSemicolon, tkRightBrace}: - return - if comp.current.tokenType in {tkProc, tkVar, tkFor, tkIf, tkWhile}: - return - comp.advance() - -proc writeChunk*(comp: Compiler, dStackIndex: int, ch: OpCode | DoubleUint8 | uint8) = - comp.stackIndex += dStackIndex - comp.chunk.writeChunk(ch, comp.previous.line) - -proc writePops*(comp: Compiler, count: int) = - if count > argMax: - comp.error("Too many local variables in block.") - if count == 0: - return - - if count == 1: - comp.writeChunk(-1, opPop) - elif count < shortArgMax: - comp.writeChunk(-count, opPopSA) - comp.writeChunk(0, count.uint8) - else: - comp.writeChunk(-count, opPopA) - comp.writeChunk(0, count.toDU8()) - - -proc writeConstant*(comp: Compiler, constant: NdValue) = - comp.stackIndex.inc - let index = comp.chunk.writeConstant(constant, comp.previous.line) - if index >= argMax: - comp.error("Too many constants in one chunk.") - - -proc addLocal*(comp: Compiler, name: string, delta: int) = - if comp.locals.len >= argMax: - comp.error("Too many local variables in function.") - - # if delta is 0 or negative, it means that it is already on the stack when addLocal is called - # if delta is positive, the first ever value of the local is to the right - comp.locals.add(Local(name: name, depth: if delta > 0: -1 else: comp.scopes.high, index: comp.stackIndex + delta, scope: comp.scopes[comp.scopes.high()], captured: false)) - -proc markInitialized*(comp: Compiler) = - comp.locals[comp.locals.high].depth = comp.scopes.high - diff --git a/src/ndspkg/compiler/variables.nim b/src/ndspkg/compiler/variables.nim deleted file mode 100644 index be6a0f9..0000000 --- a/src/ndspkg/compiler/variables.nim +++ /dev/null @@ -1,177 +0,0 @@ -{.used.} - -import bitops # needed for value - -import ../scanner -import ../chunk -import ../types/value - -import types -import utils -import precedence -import functions - -# UTILS - -proc addUpvalue(comp: Compiler, local: Local): int = - ## argument: local - ## This proc takes an index to a local in the locals table - ## and creates an upvalue in every function up until the one - ## including this local so that all of the function scopes in between - ## have the right upvalue in them (compile time) - ## - ## does not create duplicates: at each layer it will first find existing ones - ## and create references to that further down the line - - template lenCheck(scope: Scope, blk: untyped) = - if scope.upvalues.len() >= argMax: - comp.error("Too many closure variables in function.") - blk - - var scopeIndex = local.depth + 1 - # +1 must be here, because if scope the local is in is a function - # then the upvalues should only be in child functions - var isLocal = true # local means that it's the outermost function that's closing it - var upvalIndex: int = local.index - while scopeIndex < comp.scopes.len(): - let scope = comp.scopes[scopeIndex] - if scope.function: - scope.lenCheck(): - return 0 - block ensure: # exiting this block means that the upvalueIndex is updated and points to the upvalue within scope - for i in countup(0, scope.upvalues.high()): - let upval = scope.upvalues[i] - if upval.index == upvalIndex and upval.isLocal == isLocal: - upvalIndex = i - break ensure - scope.upvalues.add(Upvalue(index: upvalIndex, isLocal: isLocal)) - upvalIndex = scope.upvalues.high() - isLocal = false - scopeIndex.inc - return upvalIndex - -proc resolveLocal(comp: Compiler, name: string): tuple[index: int, upvalue: bool] = - ## the bool arg specifies whether it found an upvalue - ## if it's a local: returns the stack index of the local of the name - ## if it's an upvalue: returns the upvalue index - ## if the number is -1, then the name cannot be resolved at compile time - var i = comp.locals.high - let cfunc: Scope = if comp.scopes.len() > 0: comp.scopes[comp.scopes.high()].parentFunction else: nil - while i >= 0: - let local = comp.locals[i] - i.dec - if local.name == name: - if local.depth == -1: - continue - let upvalue = local.scope.parentFunction != cfunc - if not upvalue: - return (local.index, false) - else: - # resolveUpvalue - local.captured = true - return (comp.addUpvalue(local), true) - return (index: -1, upvalue: false) - -# EXPRESSIONS - -proc variable(comp: Compiler) = - # named variable - var getOp = opGetGlobal - var setOp = opSetGlobal - - let name = comp.previous.text - - # try resolving local, set arg to the index on the stack - var (arg, upval) = comp.resolveLocal(name) - - if arg != -1: - if upval: - getOp = opGetUpvalue - setOp = opSetUpvalue - else: - # local - getOp = opGetLocal - setOp = opSetLocal - else: - # global - arg = comp.chunk.addConstant(name.fromNimString()) - - if comp.match(tkEqual): - # assignment (global/local) - if not comp.canAssign: - comp.error("Invalid assignment target.") - return - comp.parsePrecedence(pcAssignment) - comp.writeChunk(0, setOp) - else: - # get (global/local) - comp.writeChunk(1, getOp) - - comp.writeChunk(0, arg.toDU8) - -tkIdentifier.genRule(variable, nop, pcNone) - -# VAR STATEMENT, PROC STATEMENT - -proc parseVariable(comp: Compiler, msg: string): int = - ## Parses variable declarations - ## During manipulation with variables: - ## if global: - ## consume the identifier and return index to work with - ## if local: - ## register the name with the vm - comp.consume(tkIdentifier, msg) - let name = comp.previous.text - - if name[0] in {':'}: - comp.error("Illegal variable name.") - - if comp.scopes.len > 0: # declareVariable - # local - - # check if name exists already within scope - for i in countdown(comp.locals.high, 0): - let local = comp.locals[i] - if local.depth != -1 and local.depth < comp.scopes.len: - break - - if name == local.name: - comp.error("Already a variable with this name in this scope.") - break - - comp.addLocal(name, 1) - 0 # index to the constant is irrelevant if the var is local - else: - # global - comp.chunk.addConstant(name.fromNimString()) - -proc defineVariable(comp: Compiler, index: int) = - ## Generate code that moves the variable on the stack - ## to a variable at the right place in memory - ## the right place is defined by the 3 following byte after the op - ## the thing to move is the item below it - - if comp.scopes.len > 0: - # local variable: it's already on the right place - # but we need to mark initialized - comp.markInitialized() - else: - comp.writeChunk(-1, opDefineGlobal) - comp.writeChunk(0, index.toDU8) - -proc varStatement*(comp: Compiler) = - let globalIndex = comp.parseVariable("Expect variable name.") - - if comp.match(tkEqual): - comp.expression() - else: - comp.writeChunk(1, opNil) - - comp.defineVariable(globalIndex) - -proc procStatement*(comp: Compiler) = - let globalIndex = comp.parseVariable("Expect procedure name.") - - comp.parseFunct() - - comp.defineVariable(globalIndex) \ No newline at end of file diff --git a/src/ndspkg/config.nim b/src/ndspkg/config.nim index 662ce25..ca5bf6e 100644 --- a/src/ndspkg/config.nim +++ b/src/ndspkg/config.nim @@ -12,14 +12,6 @@ const boundsChecks* = defined(debug) or defined(release) const profileInstructions* = defined(ndsprofile) # if true, the time spent on every opcode is measured const debugClosures* = defined(debug) # specific closure debug switches -type compMode* = enum - cmOne, cmAst, cmTwo -const compilerChoice* = cmTwo -# choose a compiler: cmOne - version 1, deprecated -# cmAst - version 2, but only use parser and print AST produced -# cmOne will be removed once compv2 is done -# cmTwo - compv2 + execute - # choose a line editor for the repl const lineEditor = leRdstdin