diff --git a/src/nds.nim b/src/nds.nim index 7fddc42..2feb436 100644 --- a/src/nds.nim +++ b/src/nds.nim @@ -1,12 +1,14 @@ import ndspkg/vm import ndspkg/compv2/parser import ndspkg/compv2/node +import ndspkg/compv2/emitter import ndspkg/config when compilerChoice == cmOne: import ndspkg/compiler/compiler import ndspkg/compiler/types + import os import strformat @@ -18,6 +20,18 @@ proc interpret(name: string, source: string): Result = let parser = newParser(name, source) let node = parser.parse() echo $node + elif compilerChoice == cmTwo: + let parser = newParser(name, source) + let node = parser.parse() + if parser.hadError: + return rsCompileError + let emitter = newEmitter(name, node) + emitter.emit() + case emitter.chunk.run(): + of irOK: + rsOK + of irRuntimeError: + rsRuntimeError elif compilerChoice == cmOne: let compiler = newCompiler(name, source) compiler.compile() diff --git a/src/ndspkg/compv2/emitter.nim b/src/ndspkg/compv2/emitter.nim new file mode 100644 index 0000000..496c528 --- /dev/null +++ b/src/ndspkg/compv2/emitter.nim @@ -0,0 +1,166 @@ +# a new bytecode emitter for nds +# emitter: takes AST, emits bytecode. It also binds variables. + +import node +import ../chunk +import ../config +import ../types/value + +import strformat + +type + Emitter* = ref object + root: Node + chunk*: Chunk + + hadError*: bool + line: int + + # binding + stackIndex: int + locals: seq[Local] + scopes: seq[Scope] + + Local = ref object + name: string + index: int # index in the stack (0 - stack bottom) + depth: int # depth of the local + # -1 if cannot be referenced yet + scope: Scope # innermost scope + captured: bool + + Upvalue* = ref object + index: int + isLocal: bool + + Scope = ref object + labels: seq[string] + goalStackIndex: int # stack count it started with plus 1 + jumps: seq[int] # list of jumps to be patched to the end of the scope + function: bool # if true, it's a function + parentFunction: Scope # if function, itself. Else, the innermost function it's in + upvalues: seq[Upvalue] # for functions, list of upvalues + +# helpers +proc newEmitter*(name: string, root: Node): Emitter = + new(result) + result.root = root + result.chunk = initChunk(name) + result.stackIndex = -1 + +proc error(em: Emitter, msg: string) = + write stderr, &"[line {em.line}] Error {msg}\n" + em.hadError = true + +proc newScope(em: Emitter, funct: bool): Scope = + result.new() + result.function = funct + result.goalStackIndex = em.stackIndex + 1 + if funct: + result.parentFunction = result + elif em.scopes.len() > 0: + result.parentFunction = em.scopes[em.scopes.high()].parentFunction + em.scopes.add(result) + +# chunk writers +proc writeChunk(em: Emitter, dStackIndex: int, ch: OpCode | DoubleUint8 | uint8) = + em.stackIndex += dStackIndex + em.chunk.writeChunk(ch, em.line) + +proc writePops(em: Emitter, n: int) = + if n == 0: + return + elif n > argMax: + em.error("Too many local variables in block.") + elif n == 1: + em.writeChunk(-1, opPop) + elif n < shortArgMax: + em.writeChunk(-n, opPopSA) + em.writeChunk(0, n.uint8) + else: + em.writeChunk(-n, opPopA) + em.writeChunk(0, n.toDU8()) + +proc writeConstant(em: Emitter, constant: NdValue) = + em.stackIndex.inc() + let index = em.chunk.writeConstant(constant, em.line) + if index >= argMax: + em.error("Too many constants in one chunk.") + +# locals helpers + +proc addLocal(em: Emitter, name: string, delta: int) = + if em.locals.len >= argMax: + em.error("Too many local variables in function.") + # TODO: check if it can be increased + # if delta is 0 or negative - means that the var is already on stack when addLocal is called + # if delta is +, the first ever value of the local is about to be added + # so it should be -1, (see Local typedef) to indicate that it cannot be referenced yet + let depth = if delta > 0: -1 else: em.scopes.high + em.locals.add( + Local(name: name, depth: depth, index: em.stackIndex + delta, scope: em.scopes[em.scopes.high()], captured: false) + ) + +proc markInitialized(em: Emitter) = + em.locals[em.locals.high()].depth = em.scopes.high() + +# node compilers +proc emit(em: Emitter, node: Node) = + em.line = node.line + case node.kind: + of nkFalse: + em.writeChunk(1, opFalse) + of nkTrue: + em.writeChunk(1, opTrue) + of nkNil: + em.writeChunk(1, opNil) + of nkConst: + em.writeConstant(node.constant) + of nkNegate: + em.emit(node.argument) + em.writeChunk(0, opNegate) + of nkNot: + em.emit(node.argument) + em.writeChunk(0, opNot) + of nkLen: + em.emit(node.argument) + em.writeChunk(0, opLen) + of nkBlockExpr: + # TODO: scopes + for ch in node.children: + em.emit(ch) + of nkExpr: + em.emit(node.expression) + of nkExprStmt: + em.emit(node.expression) + em.writePops(1) + of nkPlus, nkMinus, nkMult, nkDiv, nkEq, nkNeq, nkGreater, nkLess, nkGe, nkLe: + em.emit(node.left) + em.emit(node.right) + case node.kind: + of nkPlus: em.writeChunk(-1, opAdd) + of nkMinus: em.writeChunk(-1, opSubtract) + of nkMult: em.writeChunk(-1, opMultiply) + of nkDiv: em.writeChunk(-1, opDivide) + of nkEq: em.writeChunk(-1, opEqual) + of nkNeq: + em.writeChunk(-1, opEqual) + em.writeChunk(0, opNot) + of nkGreater: + em.writeChunk(-1, opGreater) + of nkLess: + em.writeChunk(-1, opLess) + of nkGe: + em.writeChunk(-1, opLess) + em.writeChunk(0, opNot) + of nkLe: + em.writeChunk(-1, opGreater) + em.writeChunk(0, opNot) + else: + raise newException(Defect, "Misaligned case list.") # unreachable + else: + raise newException(Defect, &"Unsupported node kind: {$node.kind}") + +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 diff --git a/src/ndspkg/compv2/node.nim b/src/ndspkg/compv2/node.nim index 830f192..03e52f8 100644 --- a/src/ndspkg/compv2/node.nim +++ b/src/ndspkg/compv2/node.nim @@ -12,9 +12,11 @@ type nkPlus, nkMinus, nkMult, nkDiv, nkEq, nkNeq, nkLess, nkGreater, nkGe, nkLe, nkCall, nkColonCall, nkVarDecl, nkProc, - nkVarGet, nkVarSet + nkVarGet, nkVarSet, + nkFalse, nkTrue, nkNil Node* = ref object + line*: int case kind*: NodeKind: of nkBlockExpr: children*: seq[Node] @@ -63,6 +65,8 @@ type of nkVarSet: sVarName*: string newVal*: Node + of nkFalse, nkTrue, nkNil: + discard proc `$`*(node: Node): string = if node == nil: @@ -98,6 +102,8 @@ proc `$`*(node: Node): string = result = &"(grouping {node.expression})" of nkExprStmt: result = &"(exprStmt {node.expression})" + of nkFalse: + result = "(false)" of nkGe: result = &"(ge {node.left} {node.right})" of nkGetIndex: @@ -125,6 +131,8 @@ proc `$`*(node: Node): string = result = &"(neg {node.argument})" of nkNeq: result = &"(!= {node.left} {node.right})" + of nkNil: + result = "(nil)" of nkNot: result = &"(! {node.argument})" of nkOr: @@ -143,6 +151,8 @@ proc `$`*(node: Node): string = keys &= &"{node.keys[i]}, " values &= &"{node.values[i]}, " result = &"(table keys: {keys}, values: {values})" + of nkTrue: + result = "(true)" of nkVarDecl: result = &"(varDecl {node.name} = {node.value})" of nkVarGet: diff --git a/src/ndspkg/compv2/parser.nim b/src/ndspkg/compv2/parser.nim index 419227c..4e9274a 100644 --- a/src/ndspkg/compv2/parser.nim +++ b/src/ndspkg/compv2/parser.nim @@ -21,6 +21,7 @@ type current: Token previous: Option[Token] next: Option[Token] + line: int # if there is a next set, advance won't trigger the scanner # it will use next instead hold: Node # temporary hold, used to implement ampersand op @@ -59,6 +60,7 @@ proc errorAtCurrent(parser: Parser, msg: string) = proc advance(parser: Parser) = parser.previous = some(parser.current) + parser.line = parser.current.line while true: if parser.next.isSome(): parser.current = parser.next.get() @@ -129,7 +131,7 @@ proc statement(parser: Parser, inBlock: bool = false): Node proc exprNonAssign(parser: Parser): Node proc parseList(parser: Parser): Node = - result = Node(kind: nkList, elems: @[]) + result = Node(kind: nkList, elems: @[], line: parser.line) while not parser.isAtEnd() and not parser.peekMatch(tkRightBracket): result.elems.add(parser.expression()) @@ -139,7 +141,7 @@ proc parseList(parser: Parser): Node = discard parser.consume(tkRightBracket, "']' expected after list declaration.") proc parseTable(parser: Parser): Node = - result = Node(kind: nkTable, keys: @[], values: @[]) + result = Node(kind: nkTable, keys: @[], values: @[], line: parser.line) while not parser.isAtEnd() and not parser.peekMatch(tkRightBrace): # [key] = syntax @@ -148,7 +150,7 @@ proc parseTable(parser: Parser): Node = discard parser.consume(tkRightBracket, "']' expected after table key.") # key = syntax elif parser.match(tkIdentifier): - result.keys.add(Node(kind: nkConst, constant: parser.previous.get().text.fromNimString())) + result.keys.add(Node(kind: nkConst, constant: parser.previous.get().text.fromNimString(), line: parser.line)) else: parser.errorAtCurrent("Key expected (have you forgotten to put the key in brackets?).") discard parser.consume(tkEqual, "'=' expected after key.") @@ -176,10 +178,10 @@ proc parseProcDeclaration(parser: Parser): Node = let body = parser.expression() - result = Node(kind: nkProc, parameters: params, procBody: body) + result = Node(kind: nkProc, parameters: params, procBody: body, line: parser.line) proc parseBlock(parser: Parser): Node = - result = Node(kind: nkBlockExpr, children: @[], labels: @[]) + result = Node(kind: nkBlockExpr, children: @[], labels: @[], line: parser.line) while parser.match(tkLabel): result.labels.add(parser.previous.get().text[1..^1]) @@ -201,19 +203,19 @@ proc parseBlock(parser: Parser): Node = proc primary(parser: Parser): Node = if parser.match(tkFalse): - return Node(kind: nkConst, constant: ndFalse) + return Node(kind: nkFalse, line: parser.line) if parser.match(tkTrue): - return Node(kind: nkConst, constant: ndTrue) + return Node(kind: nkTrue, line: parser.line) if parser.match(tkNil): - return Node(kind: nkConst, constant: ndNil) + return Node(kind: nkNil, line: parser.line) if parser.match(tkNumber): - return Node(kind: nkConst, constant: fromFloat(parseFloat(parser.previous.get().text))) + return Node(kind: nkConst, constant: fromFloat(parseFloat(parser.previous.get().text)), line: parser.line) if parser.match(tkString): - return Node(kind: nkConst, constant: fromNimString(parser.previous.get().text[1..^2])) + return Node(kind: nkConst, constant: fromNimString(parser.previous.get().text[1..^2]), line: parser.line) if parser.match(tkLeftParen): let grouped = parser.expression() discard parser.consume(tkRightParen, "Expect ')' after expression.") - return Node(kind: nkExpr, expression: grouped) + return Node(kind: nkExpr, expression: grouped, line: parser.line) if parser.match(tkLeftBrace): return parser.parseBlock() if parser.match(tkStartList): @@ -221,7 +223,7 @@ proc primary(parser: Parser): Node = if parser.match(tkStartTable): return parser.parseTable() if parser.match(tkIdentifier): - return Node(kind: nkVarGet, gVarName: parser.previous.get().text) + return Node(kind: nkVarGet, gVarName: parser.previous.get().text, line: parser.line) if parser.match(tkAmpersand): result = parser.hold parser.hold = nil @@ -253,31 +255,31 @@ proc parseIndex(parser: Parser): Node = let index = parser.expression() if not parser.consume(tkRightBracket, "']' after index."): break - result = Node(kind: nkGetIndex, gCollection: result, gIndex: index) + result = Node(kind: nkGetIndex, gCollection: result, gIndex: index, line: parser.line) elif parser.previous.get().tokenType == tkIdentifier: let identText = parser.previous.get().text if identText[0] != ':': parser.errorAtCurrent("';' expected after expression statement.") # update this with whatever the original error when two idents follow eachother is return - let ident = Node(kind: nkConst, constant: identText[1..^1].fromNimString()) + let ident = Node(kind: nkConst, constant: identText[1..^1].fromNimString(), line: parser.line) # ident removes the : from it var args: seq[Node] = @[] if parser.match(tkLeftParen): args = parser.parseArgList() - let funct = Node(kind: nkGetIndex, gCollection: result, gIndex: ident) - result = Node(kind: nkColonCall, arguments: args, function: funct) + let funct = Node(kind: nkGetIndex, gCollection: result, gIndex: ident, line: parser.line) + result = Node(kind: nkColonCall, arguments: args, function: funct, line: parser.line) else: # dot if not parser.consume(tkIdentifier, "Identifier expected after '.' index operator."): break - result = Node(kind: nkGetIndex, gCollection: result, gIndex: Node(kind: nkConst, constant: parser.previous.get().text.fromNimString())) + result = Node(kind: nkGetIndex, gCollection: result, gIndex: Node(kind: nkConst, constant: parser.previous.get().text.fromNimString()), line: parser.line) proc parseCall(parser: Parser): Node = result = parser.parseIndex() if parser.match(tkLeftParen): let args = parser.parseArgList() - result = Node(kind: nkCall, arguments: args, function: result) + result = Node(kind: nkCall, arguments: args, function: result, line: parser.line) proc parseIf(parser: Parser): Node = @@ -286,7 +288,7 @@ proc parseIf(parser: Parser): Node = discard parser.consume(tkRightParen, "')' expected after condition.") let body = parser.expression() - result = Node(kind: nkIf, ifCondition: cond, ifBody: body) + result = Node(kind: nkIf, ifCondition: cond, ifBody: body, line: parser.line) if parser.match(tkElse): result.elseBody = parser.expression() @@ -296,7 +298,7 @@ proc parseWhile(parser:Parser): Node = discard parser.consume(tkRightParen, "')' expected after condition.") let body = parser.expression() - result = Node(kind: nkWhile, whileCondition: cond, whileBody: body) + result = Node(kind: nkWhile, whileCondition: cond, whileBody: body, line: parser.line) proc unary(parser: Parser): Node = # unary level for unary operators, plus some control flow is here too @@ -306,13 +308,13 @@ proc unary(parser: Parser): Node = case op.tokenType: of tkBang: let right = parser.unary() - return Node(kind: nkNot, argument: right) + return Node(kind: nkNot, argument: right, line: parser.line) of tkMinus: let right = parser.unary() - return Node(kind: nkNegate, argument: right) + return Node(kind: nkNegate, argument: right, line: parser.line) of tkHashtag: let right = parser.unary() - return Node(kind: nkLen, argument: right) + return Node(kind: nkLen, argument: right, line: parser.line) of tkIf: return parser.parseIf() of tkWhile: @@ -328,9 +330,9 @@ proc factor(parser: Parser): Node = let op = parser.previous.get() let right = parser.unary() if op.tokenType == tkSlash: - result = Node(kind: nkDiv, left: result, right: right) + result = Node(kind: nkDiv, left: result, right: right, line: parser.line) else: - result = Node(kind: nkMult, left: result, right: right) + result = Node(kind: nkMult, left: result, right: right, line: parser.line) proc term(parser: Parser): Node = result = parser.factor() @@ -339,9 +341,9 @@ proc term(parser: Parser): Node = let op = parser.previous.get() let right = parser.factor() if op.tokenType == tkMinus: - result = Node(kind: nkMinus, left: result, right: right) + result = Node(kind: nkMinus, left: result, right: right, line: parser.line) else: - result = Node(kind: nkPlus, left: result, right: right) + result = Node(kind: nkPlus, left: result, right: right, line: parser.line) proc comparison(parser: Parser): Node = result = parser.term() @@ -351,13 +353,13 @@ proc comparison(parser: Parser): Node = let right = parser.term() case op.tokenType: of tkGreater: - result = Node(kind: nkGreater, left: result, right: right) + result = Node(kind: nkGreater, left: result, right: right, line: parser.line) of tkGreaterEqual: - result = Node(kind: nkGe, left: result, right: right) + result = Node(kind: nkGe, left: result, right: right, line: parser.line) of tkLess: - result = Node(kind: nkLess, left: result, right: right) + result = Node(kind: nkLess, left: result, right: right, line: parser.line) of tkLessEqual: - result = Node(kind: nkLe, left: result, right: right) + result = Node(kind: nkLe, left: result, right: right, line: parser.line) else: parser.errorAtCurrent("invalid state in comparison: case and set don't match up.") @@ -368,9 +370,9 @@ proc equality(parser: Parser): Node = let op = parser.previous.get() let right = parser.comparison() if op.tokenType == tkBangEqual: - result = Node(kind: nkNeq, left: result, right: right) + result = Node(kind: nkNeq, left: result, right: right, line: parser.line) else: - result = Node(kind: nkEq, left: result, right: right) + result = Node(kind: nkEq, left: result, right: right, line: parser.line) proc parseAnd(parser: Parser): Node = @@ -378,14 +380,14 @@ proc parseAnd(parser: Parser): Node = while parser.match(tkAnd): let right = parser.equality() - result = Node(kind: nkAnd, left: result, right: right) + result = Node(kind: nkAnd, left: result, right: right, line: parser.line) proc parseOr(parser: Parser): Node = result = parser.parseAnd() while parser.match(tkOr): let right = parser.parseAnd() - result = Node(kind: nkOr, left: result, right: right) + result = Node(kind: nkOr, left: result, right: right, line: parser.line) proc parsePipeCall(parser: Parser): Node = result = parser.parseOr() @@ -403,7 +405,7 @@ proc parsePipeCall(parser: Parser): Node = result = right # else: right val is a function which we call else: - result = Node(kind: nkCall, arguments: @[result], function: right) + result = Node(kind: nkCall, arguments: @[result], function: right, line: parser.line) proc exprNonAssign(parser: Parser): Node = @@ -421,15 +423,15 @@ proc parseAssign(parser: Parser): Node = parser.errorAtCurrent("Attempt to assign to invalid target.") return if result.kind == nkVarGet: - result = Node(kind: nkVarSet, sVarName: result.gVarName, newVal: right) + result = Node(kind: nkVarSet, sVarName: result.gVarName, newVal: right, line: parser.line) else: # nkGetIndex - result = Node(kind: nkSetIndex, sCollection: result.gCollection, sIndex: result.gIndex, sValue: right) + result = Node(kind: nkSetIndex, sCollection: result.gCollection, sIndex: result.gIndex, sValue: right, line: parser.line) proc parseAmpersand(parser: Parser): Node = result = parser.parseAssign() if parser.match(tkAmpersand): - parser.hold = Node(kind: nkExpr, expression: result) + parser.hold = Node(kind: nkExpr, expression: result, line: parser.line) parser.backtrack() return parser.parseAmpersand() @@ -440,11 +442,11 @@ proc expression(parser: Parser): Node = proc exprStatement(parser: Parser, inBlock: bool): Node = let expression = parser.expression() if expression != nil: - result = Node(kind: nkExprStmt, expression: expression) + result = Node(kind: nkExprStmt, expression: expression, line: parser.line) else: parser.errorAtCurrent("Expression expected.") if parser.peekMatch(tkRightBrace) and inBlock: - result = Node(kind: nkExpr, expression: result.expression) # block should also check if it is the last expr. + result = Node(kind: nkExpr, expression: result.expression, line: parser.line) # block should also check if it is the last expr. else: discard parser.consume(tkSemicolon, "';' expected after expression statement.") @@ -463,22 +465,22 @@ proc statement(parser: Parser, inBlock: bool = false): Node = let varname = parser.previous.get().text discard parser.consume(tkLeftParen, "'(' expected after procedure name.") let funct = parser.parseProcDeclaration() - result = Node(kind: nkVarDecl, name: varname, value: funct) + result = Node(kind: nkVarDecl, name: varname, value: funct, line: parser.line) discard parser.consume(tkSemicolon, "';' expected after procedure declaration.") elif parser.match(tkBreak): if parser.match(tkLabel): - result = Node(kind: nkBreak, label: parser.previous.get().text[1..^1]) + result = Node(kind: nkBreak, label: parser.previous.get().text[1..^1], line: parser.line) else: - result = Node(kind: nkBreak, label: "") + result = Node(kind: nkBreak, label: "", line: parser.line) discard parser.consume(tkSemicolon, "';' expected after break statement.") elif parser.match(tkVar): discard parser.consume(tkIdentifier, "Identifier expected after 'var'.") let name = parser.previous.get().text if parser.match(tkEqual): let val = parser.expression() - result = Node(kind: nkVarDecl, name: name, value: val) + result = Node(kind: nkVarDecl, name: name, value: val, line: parser.line) else: - result = Node(kind: nkVarDecl, name: name, value: nil) + result = Node(kind: nkVarDecl, name: name, value: nil, line: parser.line) discard parser.consume(tkSemicolon, "';' expected after variable declaration.") else: result = parser.exprStatement(inBlock) @@ -488,7 +490,7 @@ proc statement(parser: Parser, inBlock: bool = false): Node = proc parse*(parser: Parser): Node = parser.scanner = newScanner(parser.source) - result = Node(kind: nkBlockExpr, children: @[]) + result = Node(kind: nkBlockExpr, children: @[], line: parser.line) parser.advance() while not parser.isAtEnd(): diff --git a/src/ndspkg/config.nim b/src/ndspkg/config.nim index dbdb0be..b0b663d 100644 --- a/src/ndspkg/config.nim +++ b/src/ndspkg/config.nim @@ -16,11 +16,12 @@ const profileInstructions* = defined(ndsprofile) # if true, the time spent on ev const debugClosures* = defined(debug) # specific closure debug switches type compMode* = enum - cmOne, cmAst -const compilerChoice* = cmAst + 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 diff --git a/src/ndspkg/vm.nim b/src/ndspkg/vm.nim index 49745b0..e3dfaab 100644 --- a/src/ndspkg/vm.nim +++ b/src/ndspkg/vm.nim @@ -164,7 +164,7 @@ proc run*(chunk: Chunk): InterpretResult = setForegroundColor(fgYellow) disassembleInstruction(chunk, ii, ll) setForegroundColor(fgDefault) - write stdout, " " + echo "" when profileInstructions: let startTime = getMonoTime()