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

View File

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

View File

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

View File

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

View File

@ -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
# ... <ret val> <arg1> <arg2> <arg3>
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:

View File

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

View File

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

View File

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

View File

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

View File

@ -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.")

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")
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:

View File

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

View File

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

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