From d4a69213029dd83cf650f56abac03befb7bc6fb9 Mon Sep 17 00:00:00 2001 From: prod2 <95874442+prod2@users.noreply.github.com> Date: Tue, 8 Feb 2022 05:50:36 +0100 Subject: [PATCH] split up expression.nim --- src/ndspkg/compiler/collections.nim | 76 ++++++++++ src/ndspkg/compiler/compiler.nim | 14 +- src/ndspkg/compiler/controlflow.nim | 92 +++++++++++ src/ndspkg/compiler/expressions.nim | 226 ---------------------------- src/ndspkg/compiler/functions.nim | 103 +++++++++++++ 5 files changed, 277 insertions(+), 234 deletions(-) create mode 100644 src/ndspkg/compiler/collections.nim create mode 100644 src/ndspkg/compiler/controlflow.nim create mode 100644 src/ndspkg/compiler/functions.nim diff --git a/src/ndspkg/compiler/collections.nim b/src/ndspkg/compiler/collections.nim new file mode 100644 index 0000000..f961c2e --- /dev/null +++ b/src/ndspkg/compiler/collections.nim @@ -0,0 +1,76 @@ +import strformat +import strutils +import bitops # needed for value + +import ../scanner +import ../chunk +import ../types/value +import ../config + +# the following order of imports here is important +# it defines the allowed dependency precedence between the compiler files +import types +import utils +import precedence +import jumps +import scope + +# 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 != tkRightBrace: + comp.expression() + comp.consume(tkEqual, "Equal sign 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) diff --git a/src/ndspkg/compiler/compiler.nim b/src/ndspkg/compiler/compiler.nim index 6b2a676..e1f3182 100644 --- a/src/ndspkg/compiler/compiler.nim +++ b/src/ndspkg/compiler/compiler.nim @@ -2,17 +2,15 @@ import ../scanner import ../chunk import ../config -# the following order of imports here is important -# it defines the allowed dependency precedence between the compiler files import types import utils -#import precedence -#import jumps -#import scope -#import variables -#import expressions -import statement +# 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) diff --git a/src/ndspkg/compiler/controlflow.nim b/src/ndspkg/compiler/controlflow.nim new file mode 100644 index 0000000..ddedee5 --- /dev/null +++ b/src/ndspkg/compiler/controlflow.nim @@ -0,0 +1,92 @@ +import strformat +import strutils +import bitops # needed for value + +import ../scanner +import ../chunk +import ../types/value +import ../config + +# the following order of imports here is important +# it defines the allowed dependency precedence between the compiler files +import types +import utils +import precedence +import jumps +import scope + +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.") + + let thenJump = comp.emitJump(0, opJumpIfFalse) + + # 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 + + let elseJump = comp.emitJump(0, opJump) + comp.patchJump(thenJump) + + if comp.match(tkElse): + comp.writeChunk(-1, opPop) + comp.expression() + + comp.patchJump(elseJump) + + +tkIf.genRule(ifExpr, nop, pcNone) + +proc andExpr(comp: Compiler) = + let endJump = comp.emitJump(0, opJumpIfFalse) + + comp.writeChunk(-1, opPop) + comp.parsePrecedence(pcAnd) + # net effect on stack: -1 + 1 = 0 + + comp.patchJump(endJump) + +tkAnd.genRule(nop, andExpr, pcAnd) + +proc orExpr(comp: Compiler) = + let elseJump = comp.emitJump(0, opJumpIfFalse) + let endJump = comp.emitJump(0, opJump) + + comp.patchJump(elseJump) + + comp.writeChunk(-1, opPop) + comp.parsePrecedence(pcOr) + # net effect on stack: -1 + 1 = 0 + + comp.patchJump(endJump) + +tkOr.genRule(nop, orExpr, pcOr) + +proc parseWhile(comp: Compiler) = + + comp.writeChunk(1, opNil) # return value + let loopStart = comp.chunk.len + comp.consume(tkLeftParen, "Expect '(' after 'while'.") + # condition + comp.expression() + comp.consume(tkRightParen, "Expect ')' after condition.") + + let exitJump = comp.emitJump(-1, opJumpIfFalsePop) + # 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) + comp.patchJump(exitJump) + +tkWhile.genRule(parseWhile, nop, pcNone) diff --git a/src/ndspkg/compiler/expressions.nim b/src/ndspkg/compiler/expressions.nim index fed200a..e181023 100644 --- a/src/ndspkg/compiler/expressions.nim +++ b/src/ndspkg/compiler/expressions.nim @@ -16,8 +16,6 @@ import utils import precedence import jumps import scope -import variables - # EXPRESSIONS @@ -53,35 +51,6 @@ proc expString(comp: Compiler) = tkString.genRule(expString, nop, pcNone) -proc grouping(comp: Compiler) = - # assume initial '(' is already consumed - comp.expression() - comp.consume(tkRightParen, "Expect ')' after expression.") - -proc parseCall(comp: Compiler) = - # ( consumed - - # create the call env - # current stack before opCall: - # ... - # opCall converts it to this - # ... - - 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.") - - # emit call - comp.writeChunk(-argcount, opCall) - comp.writeChunk(0, argcount.uint8) - -tkLeftParen.genRule(grouping, parseCall, pcCall) - proc unary(comp: Compiler) = let opType = comp.previous.tokenType @@ -142,201 +111,6 @@ tkGreaterEqual.genRule(nop, binary, pcComparison) tkLess.genRule(nop, binary, pcComparison) tkLessEqual.genRule(nop, binary, pcComparison) -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.") - - let thenJump = comp.emitJump(0, opJumpIfFalse) - - # 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 - - let elseJump = comp.emitJump(0, opJump) - comp.patchJump(thenJump) - - if comp.match(tkElse): - comp.writeChunk(-1, opPop) - comp.expression() - - comp.patchJump(elseJump) - - -tkIf.genRule(ifExpr, nop, pcNone) - -proc andExpr(comp: Compiler) = - let endJump = comp.emitJump(0, opJumpIfFalse) - - comp.writeChunk(-1, opPop) - comp.parsePrecedence(pcAnd) - # net effect on stack: -1 + 1 = 0 - - comp.patchJump(endJump) - -tkAnd.genRule(nop, andExpr, pcAnd) - -proc orExpr(comp: Compiler) = - let elseJump = comp.emitJump(0, opJumpIfFalse) - let endJump = comp.emitJump(0, opJump) - - comp.patchJump(elseJump) - - comp.writeChunk(-1, opPop) - comp.parsePrecedence(pcOr) - # net effect on stack: -1 + 1 = 0 - - comp.patchJump(endJump) - -tkOr.genRule(nop, orExpr, pcOr) - -proc parseWhile(comp: Compiler) = - - comp.writeChunk(1, opNil) # return value - let loopStart = comp.chunk.len - comp.consume(tkLeftParen, "Expect '(' after 'while'.") - # condition - comp.expression() - comp.consume(tkRightParen, "Expect ')' after condition.") - - let exitJump = comp.emitJump(-1, opJumpIfFalsePop) - # 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) - comp.patchJump(exitJump) - - -tkWhile.genRule(parseWhile, nop, pcNone) - -proc parseFunct(comp: Compiler) = - - # jump over - let jumpOverBody = comp.emitJump(1, opFunctionDef) - - 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() - when assertionsCompiler: - 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) - # 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) - -tkFunct.genRule(parseFunct, nop, pcNone) - -# 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 != tkRightBrace: - comp.expression() - comp.consume(tkEqual, "Equal sign 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 parseAmpersand(comp: Compiler) = # just a simple expression separator discard diff --git a/src/ndspkg/compiler/functions.nim b/src/ndspkg/compiler/functions.nim new file mode 100644 index 0000000..4a25c9f --- /dev/null +++ b/src/ndspkg/compiler/functions.nim @@ -0,0 +1,103 @@ +import strformat +import strutils +import bitops # needed for value + +import ../scanner +import ../chunk +import ../types/value +import ../config + +# the following order of imports here is important +# it defines the allowed dependency precedence between the compiler files +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 parseCall(comp: Compiler) = + # ( consumed + + # create the call env + # current stack before opCall: + # ... + # opCall converts it to this + # ... + + 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.") + + # emit call + comp.writeChunk(-argcount, opCall) + comp.writeChunk(0, argcount.uint8) + +tkLeftParen.genRule(grouping, parseCall, pcCall) + +proc parseFunct(comp: Compiler) = + + # jump over + let jumpOverBody = comp.emitJump(1, opFunctionDef) + + 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() + when assertionsCompiler: + 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) + # 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) + +tkFunct.genRule(parseFunct, nop, pcNone)