sugars: :: and table . op; allow for reading existing locals of the same name in var statements

This commit is contained in:
prod2 2022-02-08 08:13:38 +01:00
parent 2517439caf
commit 8262ca187c
15 changed files with 163 additions and 50 deletions

View File

@ -9,6 +9,7 @@ type
opClosure, # closures opClosure, # closures
opPop, opPopSA, opPopA # pop opPop, opPopSA, opPopA # pop
opNegate, opNot # unary opNegate, opNot # unary
opSwap, # stack manipulation
opAdd, opSubtract, opMultiply, opDivide, # math opAdd, opSubtract, opMultiply, opDivide, # math
opEqual, opGreater, opLess, # comparison opEqual, opGreater, opLess, # comparison
opTrue, opFalse, opNil, # literal opTrue, opFalse, opNil, # literal
@ -91,6 +92,7 @@ proc writeConstant*(ch: var Chunk, constant: NdValue, line: int): int =
const simpleInstructions = { const simpleInstructions = {
opReturn, opReturn,
opPop, opPop,
opSwap,
opNegate, opNot, opNegate, opNot,
opAdd, opSubtract, opMultiply, opDivide, opAdd, opSubtract, opMultiply, opDivide,
opEqual, opGreater, opLess, opEqual, opGreater, opLess,

View File

@ -2,9 +2,9 @@
import ../scanner import ../scanner
import ../chunk 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 types
import utils import utils
import precedence import precedence
@ -68,3 +68,17 @@ proc parseIndex(comp: Compiler) =
comp.writeChunk(-1, opGetIndex) comp.writeChunk(-1, opGetIndex)
tkLeftBracket.genRule(nop, parseIndex, pcIndex) 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)

View File

@ -3,8 +3,6 @@
import ../scanner import ../scanner
import ../chunk import ../chunk
# the following order of imports here is important
# it defines the allowed dependency precedence between the compiler files
import types import types
import utils import utils
import precedence import precedence
@ -18,7 +16,8 @@ proc ifExpr(comp: Compiler) =
comp.expression() comp.expression()
comp.consume(tkRightParen, "Expect ')' after condition.") 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! # conditional code that can be jumped over must leave the stack in tact!
@ -26,40 +25,44 @@ proc ifExpr(comp: Compiler) =
comp.expression() comp.expression()
# net change to stack: -1 + 1 = 0 # net change to stack: -1 + 1 = 0
let elseJump = comp.emitJump(0, opJump) var elseStacklen = 0
comp.patchJump(thenJump) let elseJump = comp.emitJump(0, opJump, elseStacklen)
comp.patchJump(thenJump, thenStacklen)
if comp.match(tkElse): if comp.match(tkElse):
comp.writeChunk(-1, opPop) comp.writeChunk(-1, opPop)
comp.expression() comp.expression()
comp.patchJump(elseJump) comp.patchJump(elseJump, elseStacklen)
tkIf.genRule(ifExpr, nop, pcNone) tkIf.genRule(ifExpr, nop, pcNone)
proc andExpr(comp: Compiler) = 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.writeChunk(-1, opPop)
comp.parsePrecedence(pcAnd) comp.parsePrecedence(pcAnd)
# net effect on stack: -1 + 1 = 0 # net effect on stack: -1 + 1 = 0
comp.patchJump(endJump) comp.patchJump(endJump, stacklen)
tkAnd.genRule(nop, andExpr, pcAnd) tkAnd.genRule(nop, andExpr, pcAnd)
proc orExpr(comp: Compiler) = proc orExpr(comp: Compiler) =
let elseJump = comp.emitJump(0, opJumpIfFalse) var elseStacklen = 0
let endJump = comp.emitJump(0, opJump) 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.writeChunk(-1, opPop)
comp.parsePrecedence(pcOr) comp.parsePrecedence(pcOr)
# net effect on stack: -1 + 1 = 0 # net effect on stack: -1 + 1 = 0
comp.patchJump(endJump) comp.patchJump(endJump, endStacklen)
tkOr.genRule(nop, orExpr, pcOr) tkOr.genRule(nop, orExpr, pcOr)
@ -67,12 +70,14 @@ proc parseWhile(comp: Compiler) =
comp.writeChunk(1, opNil) # return value comp.writeChunk(1, opNil) # return value
let loopStart = comp.chunk.len let loopStart = comp.chunk.len
let loopStacklen = comp.stackIndex
comp.consume(tkLeftParen, "Expect '(' after 'while'.") comp.consume(tkLeftParen, "Expect '(' after 'while'.")
# condition # condition
comp.expression() comp.expression()
comp.consume(tkRightParen, "Expect ')' after condition.") 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 # this cannot be handled with just opPop, since the net change in the
# stack size inside code that is conditional must be 0! # stack size inside code that is conditional must be 0!
@ -81,7 +86,7 @@ proc parseWhile(comp: Compiler) =
comp.expression() comp.expression()
# net stack change: 1 + -1 = 0 # net stack change: 1 + -1 = 0
comp.emitLoop(loopstart = loopStart, delta = 0, op = opLoop) comp.emitLoop(loopstart = loopStart, delta = 0, op = opLoop, loopStacklen)
comp.patchJump(exitJump) comp.patchJump(exitJump, exitStacklen)
tkWhile.genRule(parseWhile, nop, pcNone) tkWhile.genRule(parseWhile, nop, pcNone)

View File

@ -8,8 +8,6 @@ import ../chunk
import ../types/value import ../types/value
import ../config import ../config
# the following order of imports here is important
# it defines the allowed dependency precedence between the compiler files
import types import types
import utils import utils
import precedence import precedence

View File

@ -6,8 +6,6 @@ import ../scanner
import ../chunk import ../chunk
import ../config import ../config
# the following order of imports here is important
# it defines the allowed dependency precedence between the compiler files
import types import types
import utils import utils
import precedence import precedence
@ -19,6 +17,18 @@ proc grouping(comp: Compiler) =
comp.expression() comp.expression()
comp.consume(tkRightParen, "Expect ')' after 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) = proc parseCall(comp: Compiler) =
# ( consumed # ( consumed
@ -28,14 +38,7 @@ proc parseCall(comp: Compiler) =
# opCall converts it to this # opCall converts it to this
# ... <ret val> <arg1> <arg2> <arg3> # ... <ret val> <arg1> <arg2> <arg3>
var argcount = 0 let argcount = comp.parseArgs()
# 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 # emit call
comp.writeChunk(-argcount, opCall) comp.writeChunk(-argcount, opCall)
@ -43,10 +46,32 @@ proc parseCall(comp: Compiler) =
tkLeftParen.genRule(grouping, parseCall, pcCall) 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) = proc parseFunct(comp: Compiler) =
# jump over # 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'.") 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 dec comp.stackIndex # the previous end scope did not put anything on the stack, it is jumped over
# end of function declaration: # 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 # closures are implemented in a way, where the vm pops the function from the stack
# and reads the upvalue details from the following bytes # and reads the upvalue details from the following bytes
if f.upvalues.len() > 0: if f.upvalues.len() > 0:

View File

@ -1,20 +1,24 @@
import ../chunk 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 types
import utils import utils
# JUMP HELPERS # 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 -> 0 if the jump does not pop
# delta -> -1 if the jump pops the condition from the stack # delta -> -1 if the jump pops the condition from the stack
comp.writeChunk(delta, op) comp.writeChunk(delta, op)
comp.writeChunk(0, 0xffffff.toDU8) comp.writeChunk(0, 0xffffff.toDU8)
when assertionsCompiler:
stacklen = comp.stackIndex
comp.chunk.len - argSize 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) let jump = (comp.chunk.len - offset - argSize)
if (jump > argMax): if (jump > argMax):
@ -25,7 +29,10 @@ proc patchJump*(comp: Compiler, offset: int) =
comp.chunk.code[offset] = jumpt[0] comp.chunk.code[offset] = jumpt[0]
comp.chunk.code[offset + 1] = jumpt[1] 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) comp.writeChunk(delta, op)
let offset = comp.chunk.len - loopstart + argSize let offset = comp.chunk.len - loopstart + argSize

View File

@ -6,8 +6,6 @@ import ../scanner
import ../chunk import ../chunk
import ../config import ../config
# the following order of imports here is important
# it defines the allowed dependency precedence between the compiler files
import types import types
import utils import utils
import jumps import jumps
@ -65,7 +63,8 @@ proc jumpToEnd*(comp: Compiler, scope: Scope) =
else: else:
delta = comp.stackIndex - scope.goalStackIndex delta = comp.stackIndex - scope.goalStackIndex
comp.writePops(delta) 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) scope.jumps.add(jmp)
proc endScope*(comp: Compiler): Scope = proc endScope*(comp: Compiler): Scope =
@ -94,7 +93,7 @@ proc endScope*(comp: Compiler): Scope =
comp.restore(popped) comp.restore(popped)
# patch jumps to after the scope (such jumps from breaks emit the pops before jumping) # patch jumps to after the scope (such jumps from breaks emit the pops before jumping)
for jump in popped.jumps: for jump in popped.jumps:
comp.patchJump(jump) comp.patchJump(jump, popped.goalStackIndex)
if function: if function:
comp.writeChunk(0, opReturn) comp.writeChunk(0, opReturn)
# remove locals from the comp object that were of this scope # remove locals from the comp object that were of this scope

View File

@ -3,8 +3,6 @@
import ../scanner import ../scanner
import ../chunk import ../chunk
# the following order of imports here is important
# it defines the allowed dependency precedence between the compiler files
import types import types
import utils import utils
import precedence import precedence

View File

@ -6,8 +6,6 @@ import ../scanner
import ../chunk import ../chunk
import ../types/value import ../types/value
# the following order of imports here is important
# it defines the allowed dependency precedence between the compiler files
import types import types
import utils import utils
import precedence 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 let cfunc: Scope = if comp.scopes.len() > 0: comp.scopes[comp.scopes.high()].parentFunction else: nil
while i >= 0: while i >= 0:
let local = comp.locals[i] let local = comp.locals[i]
i.dec
if local.name == name: if local.name == name:
if local.depth == -1: if local.depth == -1:
comp.error("Can't read local variable in its own initializer.") continue
let upvalue = local.scope.parentFunction != cfunc let upvalue = local.scope.parentFunction != cfunc
if not upvalue: if not upvalue:
return (local.index, false) return (local.index, false)
@ -70,7 +69,6 @@ proc resolveLocal(comp: Compiler, name: string): tuple[index: int, upvalue: bool
# resolveUpvalue # resolveUpvalue
local.captured = true local.captured = true
return (comp.addUpvalue(local), true) return (comp.addUpvalue(local), true)
i.dec
return (index: -1, upvalue: false) return (index: -1, upvalue: false)
# EXPRESSIONS # EXPRESSIONS

View File

@ -11,7 +11,7 @@ type
TokenType* = enum TokenType* = enum
tkNone, # the default tokentype, if encountered anywhere, erroring out is the best course of action 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, tkMinus, tkPlus, tkSemicolon, tkSlash, tkStar, tkBang, tkBangEqual,
tkGreater, tkGreaterEqual, tkLess, tkLessEqual, tkEqual, tkEqualEqual, tkGreater, tkGreaterEqual, tkLess, tkLessEqual, tkEqual, tkEqualEqual,
tkStartList, tkStartTable, tkLeftBracket, tkRightBracket, tkStartList, tkStartTable, tkLeftBracket, tkRightBracket,
@ -198,7 +198,12 @@ proc scanToken*(scanner: Scanner): Token =
elif scanner.match('{'): return scanner.makeToken(tkStartTable) elif scanner.match('{'): return scanner.makeToken(tkStartTable)
else: return scanner.scanLabel() else: return scanner.scanLabel()
else: else:
if c == ':' and scanner.match(':'):
return scanner.makeToken(tkDoublecolon)
if c.canStartIdent(): 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: else:
return scanner.errorToken("Unexpected character.") return scanner.errorToken("Unexpected character.")

View File

@ -72,6 +72,15 @@ proc settip*[T](stack: var Stack[T], newtip: T) =
raise newException(Defect, "Stacktop is nil or smaller than start") raise newException(Defect, "Stacktop is nil or smaller than start")
stack.top[]= newtip 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) = proc deleteTopN*[T](stack: var Stack[T], n: Natural) =
stack.top = stack.top.psub(sizeof(T) * n) stack.top = stack.top.psub(sizeof(T) * n)
when boundsChecks: when boundsChecks:

View File

@ -186,6 +186,8 @@ proc run*(chunk: Chunk): InterpretResult =
of opPopSA: of opPopSA:
let amt = ip.readUI8() let amt = ip.readUI8()
stack.popn(amt) stack.popn(amt)
of opSwap:
stack.swap()
of opConstant: of opConstant:
let val: NdValue = ip.readConstant(chunk) let val: NdValue = ip.readConstant(chunk)
stack.add(val) stack.add(val)

View File

@ -82,8 +82,8 @@ argcap(8.1)();
// oop: constructors, getters, setters // oop: constructors, getters, setters
var newAnimal = funct(pspecies, color) { var newAnimal = funct(species, color) {
var species = pspecies; // copy it so that it's mutable, if args ever get immutable var species = species; // copy it so that it's mutable, if args ever get immutable
var animal = @{ var animal = @{
"getSpecies" = funct() :result = species, "getSpecies" = funct() :result = species,
"setSpecies" = funct(newSpecies) species = newSpecies, "setSpecies" = funct(newSpecies) species = newSpecies,

8
tests/math.nds Normal file
View File

@ -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

43
tests/sugar.nds Normal file
View File

@ -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