sugars: :: and table . op; allow for reading existing locals of the same name in var statements
This commit is contained in:
parent
2517439caf
commit
8262ca187c
|
@ -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,
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue