diff --git a/src/ndspkg/chunk.nim b/src/ndspkg/chunk.nim index 6d58a74..9693986 100644 --- a/src/ndspkg/chunk.nim +++ b/src/ndspkg/chunk.nim @@ -9,6 +9,7 @@ type opClosure, # closures opPop, opPopSA, opPopA # pop opNegate, opNot # unary + opSwap, # stack manipulation opAdd, opSubtract, opMultiply, opDivide, # math opEqual, opGreater, opLess, # comparison opTrue, opFalse, opNil, # literal @@ -91,6 +92,7 @@ proc writeConstant*(ch: var Chunk, constant: NdValue, line: int): int = const simpleInstructions = { opReturn, opPop, + opSwap, opNegate, opNot, opAdd, opSubtract, opMultiply, opDivide, opEqual, opGreater, opLess, diff --git a/src/ndspkg/compiler/collections.nim b/src/ndspkg/compiler/collections.nim index 6fc8dbe..e233fd8 100644 --- a/src/ndspkg/compiler/collections.nim +++ b/src/ndspkg/compiler/collections.nim @@ -2,9 +2,9 @@ import ../scanner import ../chunk +import ../types/value +import bitops -# the following order of imports here is important -# it defines the allowed dependency precedence between the compiler files import types import utils import precedence @@ -68,3 +68,17 @@ proc parseIndex(comp: Compiler) = 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/controlflow.nim b/src/ndspkg/compiler/controlflow.nim index a94cc44..e902143 100644 --- a/src/ndspkg/compiler/controlflow.nim +++ b/src/ndspkg/compiler/controlflow.nim @@ -3,8 +3,6 @@ import ../scanner import ../chunk -# the following order of imports here is important -# it defines the allowed dependency precedence between the compiler files import types import utils import precedence @@ -18,7 +16,8 @@ proc ifExpr(comp: Compiler) = comp.expression() comp.consume(tkRightParen, "Expect ')' after condition.") - let thenJump = comp.emitJump(0, opJumpIfFalse) + var thenStacklen = 0 + let thenJump = comp.emitJump(0, opJumpIfFalse, thenStacklen) # conditional code that can be jumped over must leave the stack in tact! @@ -26,40 +25,44 @@ proc ifExpr(comp: Compiler) = comp.expression() # net change to stack: -1 + 1 = 0 - let elseJump = comp.emitJump(0, opJump) - comp.patchJump(thenJump) + 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) + comp.patchJump(elseJump, elseStacklen) tkIf.genRule(ifExpr, nop, pcNone) proc andExpr(comp: Compiler) = - let endJump = comp.emitJump(0, opJumpIfFalse) + 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) + comp.patchJump(endJump, stacklen) tkAnd.genRule(nop, andExpr, pcAnd) proc orExpr(comp: Compiler) = - let elseJump = comp.emitJump(0, opJumpIfFalse) - let endJump = comp.emitJump(0, opJump) + var elseStacklen = 0 + var endStacklen = 0 + let elseJump = comp.emitJump(0, opJumpIfFalse, elseStacklen) + let endJump = comp.emitJump(0, opJump, endStacklen) - comp.patchJump(elseJump) + comp.patchJump(elseJump, elseStacklen) comp.writeChunk(-1, opPop) comp.parsePrecedence(pcOr) # net effect on stack: -1 + 1 = 0 - comp.patchJump(endJump) + comp.patchJump(endJump, endStacklen) tkOr.genRule(nop, orExpr, pcOr) @@ -67,12 +70,14 @@ 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.") - let exitJump = comp.emitJump(-1, opJumpIfFalsePop) + 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! @@ -81,7 +86,7 @@ proc parseWhile(comp: Compiler) = comp.expression() # net stack change: 1 + -1 = 0 - comp.emitLoop(loopstart = loopStart, delta = 0, op = opLoop) - comp.patchJump(exitJump) + 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 index 7070549..9676034 100644 --- a/src/ndspkg/compiler/expressions.nim +++ b/src/ndspkg/compiler/expressions.nim @@ -8,8 +8,6 @@ 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 diff --git a/src/ndspkg/compiler/functions.nim b/src/ndspkg/compiler/functions.nim index be1cab0..c66ec7a 100644 --- a/src/ndspkg/compiler/functions.nim +++ b/src/ndspkg/compiler/functions.nim @@ -6,8 +6,6 @@ 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 @@ -19,6 +17,18 @@ proc grouping(comp: Compiler) = 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 @@ -28,14 +38,7 @@ proc parseCall(comp: Compiler) = # 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.") + let argcount = comp.parseArgs() # emit call comp.writeChunk(-argcount, opCall) @@ -43,10 +46,32 @@ proc parseCall(comp: Compiler) = 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 parseFunct(comp: Compiler) = # jump over - let jumpOverBody = comp.emitJump(1, opFunctionDef) + var jumpStacklen = 0 + let jumpOverBody = comp.emitJump(1, opFunctionDef, jumpStacklen) comp.consume(tkLeftParen, "Expected '(' after keyword 'funct'.") @@ -89,7 +114,7 @@ proc parseFunct(comp: Compiler) = 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) + 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: diff --git a/src/ndspkg/compiler/jumps.nim b/src/ndspkg/compiler/jumps.nim index 961d462..8f76065 100644 --- a/src/ndspkg/compiler/jumps.nim +++ b/src/ndspkg/compiler/jumps.nim @@ -1,20 +1,24 @@ 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 # JUMP HELPERS -proc emitJump*(comp: Compiler, delta: int, op: OpCode): int = +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) + when assertionsCompiler: + stacklen = comp.stackIndex comp.chunk.len - argSize -proc patchJump*(comp: Compiler, offset: int) = +proc patchJump*(comp: Compiler, offset: int, stacklen: int) = + when assertionsCompiler: + if comp.stackIndex != stacklen: + comp.error("Assertion failed: loop doesn't preserve stackindex.") let jump = (comp.chunk.len - offset - argSize) if (jump > argMax): @@ -25,7 +29,10 @@ proc patchJump*(comp: Compiler, offset: int) = comp.chunk.code[offset] = jumpt[0] comp.chunk.code[offset + 1] = jumpt[1] -proc emitLoop*(comp: Compiler, loopstart: int, delta: int, op: OpCode) = +proc emitLoop*(comp: Compiler, loopstart: int, delta: int, op: OpCode, stacklen: int) = + when assertionsCompiler: + if comp.stackIndex != stacklen: + comp.error("Assertion failed: loop doesn't preserve stackindex.") comp.writeChunk(delta, op) let offset = comp.chunk.len - loopstart + argSize diff --git a/src/ndspkg/compiler/scope.nim b/src/ndspkg/compiler/scope.nim index cdbeadd..63c8c58 100644 --- a/src/ndspkg/compiler/scope.nim +++ b/src/ndspkg/compiler/scope.nim @@ -6,8 +6,6 @@ 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 jumps @@ -65,7 +63,8 @@ proc jumpToEnd*(comp: Compiler, scope: Scope) = else: delta = comp.stackIndex - scope.goalStackIndex comp.writePops(delta) - let jmp = comp.emitJump(delta, opJump) + var s = 0 # discard the saved stack length + let jmp = comp.emitJump(delta, opJump, s) scope.jumps.add(jmp) proc endScope*(comp: Compiler): Scope = @@ -94,7 +93,7 @@ proc endScope*(comp: Compiler): Scope = comp.restore(popped) # patch jumps to after the scope (such jumps from breaks emit the pops before jumping) for jump in popped.jumps: - comp.patchJump(jump) + comp.patchJump(jump, popped.goalStackIndex) if function: comp.writeChunk(0, opReturn) # remove locals from the comp object that were of this scope diff --git a/src/ndspkg/compiler/statement.nim b/src/ndspkg/compiler/statement.nim index d11fd6e..b2ab7c5 100644 --- a/src/ndspkg/compiler/statement.nim +++ b/src/ndspkg/compiler/statement.nim @@ -3,8 +3,6 @@ import ../scanner import ../chunk -# the following order of imports here is important -# it defines the allowed dependency precedence between the compiler files import types import utils import precedence diff --git a/src/ndspkg/compiler/variables.nim b/src/ndspkg/compiler/variables.nim index 7e60ae4..574cf43 100644 --- a/src/ndspkg/compiler/variables.nim +++ b/src/ndspkg/compiler/variables.nim @@ -6,8 +6,6 @@ import ../scanner import ../chunk import ../types/value -# the following order of imports here is important -# it defines the allowed dependency precedence between the compiler files import types import utils import precedence @@ -60,9 +58,10 @@ proc resolveLocal(comp: Compiler, name: string): tuple[index: int, upvalue: bool 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: - comp.error("Can't read local variable in its own initializer.") + continue let upvalue = local.scope.parentFunction != cfunc if not upvalue: return (local.index, false) @@ -70,7 +69,6 @@ proc resolveLocal(comp: Compiler, name: string): tuple[index: int, upvalue: bool # resolveUpvalue local.captured = true return (comp.addUpvalue(local), true) - i.dec return (index: -1, upvalue: false) # EXPRESSIONS diff --git a/src/ndspkg/scanner.nim b/src/ndspkg/scanner.nim index eee39ea..d1b2cc6 100644 --- a/src/ndspkg/scanner.nim +++ b/src/ndspkg/scanner.nim @@ -11,7 +11,7 @@ type TokenType* = enum tkNone, # the default tokentype, if encountered anywhere, erroring out is the best course of action - tkLeftParen, tkRightParen, tkLeftBrace, tkRightBrace, tkComma, tkDot, + tkLeftParen, tkRightParen, tkLeftBrace, tkRightBrace, tkComma, tkDot, tkColon, tkDoublecolon, tkMinus, tkPlus, tkSemicolon, tkSlash, tkStar, tkBang, tkBangEqual, tkGreater, tkGreaterEqual, tkLess, tkLessEqual, tkEqual, tkEqualEqual, tkStartList, tkStartTable, tkLeftBracket, tkRightBracket, @@ -198,7 +198,12 @@ proc scanToken*(scanner: Scanner): Token = elif scanner.match('{'): return scanner.makeToken(tkStartTable) else: return scanner.scanLabel() else: + if c == ':' and scanner.match(':'): + return scanner.makeToken(tkDoublecolon) if c.canStartIdent(): - return scanner.scanIdentifier() + # : can start ident, but if on its own it's probably syntactic sugar for tables + if c == ':' and not scanner.peek().canContIdent(): + return scanner.makeToken(tkColon) + return scanner.scanIdentifier() else: return scanner.errorToken("Unexpected character.") diff --git a/src/ndspkg/types/stack.nim b/src/ndspkg/types/stack.nim index bba8af8..903098d 100644 --- a/src/ndspkg/types/stack.nim +++ b/src/ndspkg/types/stack.nim @@ -72,6 +72,15 @@ proc settip*[T](stack: var Stack[T], newtip: T) = raise newException(Defect, "Stacktop is nil or smaller than start") stack.top[]= newtip +proc swap*[T](stack: var Stack[T]) = + when boundsChecks: + if stack.top == nil or stack.len() < 2: + raise newException(Defect, "Stacktop is nil, or not high enough for swap.") + let temp = stack.top[] + var below = stack.top.psub(sizeof(T)) + stack.top[] = below[] + below[] = temp + proc deleteTopN*[T](stack: var Stack[T], n: Natural) = stack.top = stack.top.psub(sizeof(T) * n) when boundsChecks: diff --git a/src/ndspkg/vm.nim b/src/ndspkg/vm.nim index 51564d9..bb0a1de 100644 --- a/src/ndspkg/vm.nim +++ b/src/ndspkg/vm.nim @@ -186,6 +186,8 @@ proc run*(chunk: Chunk): InterpretResult = of opPopSA: let amt = ip.readUI8() stack.popn(amt) + of opSwap: + stack.swap() of opConstant: let val: NdValue = ip.readConstant(chunk) stack.add(val) diff --git a/tests/closures.nds b/tests/closures.nds index d832829..37ea988 100644 --- a/tests/closures.nds +++ b/tests/closures.nds @@ -82,8 +82,8 @@ argcap(8.1)(); // oop: constructors, getters, setters -var newAnimal = funct(pspecies, color) { - var species = pspecies; // copy it so that it's mutable, if args ever get immutable +var newAnimal = funct(species, color) { + var species = species; // copy it so that it's mutable, if args ever get immutable var animal = @{ "getSpecies" = funct() :result = species, "setSpecies" = funct(newSpecies) species = newSpecies, diff --git a/tests/math.nds b/tests/math.nds new file mode 100644 index 0000000..ee8658d --- /dev/null +++ b/tests/math.nds @@ -0,0 +1,8 @@ +print (0/0 == 0/0); +//expect:false + +print (1/0 == 3/0); +//expect:true + +print (1/0 == -2/0); +//expect:false \ No newline at end of file diff --git a/tests/sugar.nds b/tests/sugar.nds new file mode 100644 index 0000000..06d778d --- /dev/null +++ b/tests/sugar.nds @@ -0,0 +1,43 @@ +// testing syntactic sugars + +// :: piping function call + +var double = funct(num) :result = num * 2; + +var four = 2 :: double(); + +var multiply = funct(num, factor) :result = num * factor; + +var six = 2 :: multiply(3); + +print (four); +print (six); +//expect:4.0 +//expect:6.0 + +print (1 + 2 * 3 :: tostring() + "2"); +//expect:7.02 + +// indexing tables with . + +var nested = @{ + "lvl1" = @{ + "lvl2" = @{ + "lvl3" = 5 + } + } +}; + +nested.lvl1.lvl2.lvl3 = 6; +print(nested.lvl1.lvl2.lvl3); +//expect:6.0 + +nested["lvl1"].lvl2["lvl3"] = 2; +print(nested.lvl1["lvl2"].lvl3); +//expect:2.0 + +nested["lvl1"]["lvl2"]["lvl3"] = -5.4; +nested .lvl1 .lvl2 .lvl3 :: print; +//expect:-5.4 + +// creating tables with : and no [] idents \ No newline at end of file