{.used.} import bitops # needed for value import ../scanner import ../chunk import ../types/value import types import utils import precedence # UTILS proc addUpvalue(comp: Compiler, local: Local): int = ## argument: local ## This proc takes an index to a local in the locals table ## and creates an upvalue in every function up until the one ## including this local so that all of the function scopes in between ## have the right upvalue in them (compile time) ## ## does not create duplicates: at each layer it will first find existing ones ## and create references to that further down the line template lenCheck(scope: Scope, blk: untyped) = if scope.upvalues.len() >= argMax: comp.error("Too many closure variables in function.") blk var scopeIndex = local.depth + 1 # +1 must be here, because if scope the local is in is a function # then the upvalues should only be in child functions var isLocal = true # local means that it's the outermost function that's closing it var upvalIndex: int = local.index while scopeIndex < comp.scopes.len(): let scope = comp.scopes[scopeIndex] if scope.function: scope.lenCheck(): return 0 block ensure: # exiting this block means that the upvalueIndex is updated and points to the upvalue within scope for i in countup(0, scope.upvalues.high()): let upval = scope.upvalues[i] if upval.index == upvalIndex and upval.isLocal == isLocal: upvalIndex = i break ensure scope.upvalues.add(Upvalue(index: upvalIndex, isLocal: isLocal)) upvalIndex = scope.upvalues.high() isLocal = false scopeIndex.inc return upvalIndex proc resolveLocal(comp: Compiler, name: string): tuple[index: int, upvalue: bool] = ## the bool arg specifies whether it found an upvalue ## if it's a local: returns the stack index of the local of the name ## if it's an upvalue: returns the upvalue index ## if the number is -1, then the name cannot be resolved at compile time var i = comp.locals.high 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: continue let upvalue = local.scope.parentFunction != cfunc if not upvalue: return (local.index, false) else: # resolveUpvalue local.captured = true return (comp.addUpvalue(local), true) return (index: -1, upvalue: false) # EXPRESSIONS proc variable(comp: Compiler) = # named variable var getOp = opGetGlobal var setOp = opSetGlobal let name = comp.previous.text # try resolving local, set arg to the index on the stack var (arg, upval) = comp.resolveLocal(name) if arg != -1: if upval: getOp = opGetUpvalue setOp = opSetUpvalue else: # local getOp = opGetLocal setOp = opSetLocal else: # global arg = comp.chunk.addConstant(name.fromNimString()) if comp.match(tkEqual): # assignment (global/local) if not comp.canAssign: comp.error("Invalid assignment target.") return comp.parsePrecedence(pcAssignment) comp.writeChunk(0, setOp) else: # get (global/local) comp.writeChunk(1, getOp) comp.writeChunk(0, arg.toDU8) tkIdentifier.genRule(variable, nop, pcNone) # VAR STATEMENT proc parseVariable(comp: Compiler, msg: string): int = ## Parses variable declarations ## During manipulation with variables: ## if global: ## consume the identifier and return index to work with ## if local: ## register the name with the vm comp.consume(tkIdentifier, msg) let name = comp.previous.text if name[0] in {':'}: comp.error("Illegal variable name.") if comp.scopes.len > 0: # declareVariable # local # check if name exists already within scope for i in countdown(comp.locals.high, 0): let local = comp.locals[i] if local.depth != -1 and local.depth < comp.scopes.len: break if name == local.name: comp.error("Already a variable with this name in this scope.") break comp.addLocal(name, 1) 0 # index to the constant is irrelevant if the var is local else: # global comp.chunk.addConstant(name.fromNimString()) proc defineVariable(comp: Compiler, index: int) = ## Generate code that moves the variable on the stack ## to a variable at the right place in memory ## the right place is defined by the 3 following byte after the op ## the thing to move is the item below it if comp.scopes.len > 0: # local variable: it's already on the right place # but we need to mark initialized comp.markInitialized() else: comp.writeChunk(-1, opDefineGlobal) comp.writeChunk(0, index.toDU8) proc varStatement*(comp: Compiler) = let globalIndex = comp.parseVariable("Expect variable name.") if comp.match(tkEqual): comp.expression() else: comp.writeChunk(1, opNil) comp.defineVariable(globalIndex)