170 lines
5.0 KiB
Nim
170 lines
5.0 KiB
Nim
{.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)
|