nondescript/src/ndspkg/compiler/variables.nim

177 lines
5.2 KiB
Nim

{.used.}
import bitops # needed for value
import ../scanner
import ../chunk
import ../types/value
import types
import utils
import precedence
import functions
# 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 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)
proc procStatement*(comp: Compiler) =
let globalIndex = comp.parseVariable("Expect procedure name.")
comp.parseFunct()
comp.defineVariable(globalIndex)