emitting variables, scopes
This commit is contained in:
parent
6fc8514a6b
commit
8c90b669e8
|
@ -222,6 +222,7 @@ TODO
|
|||
|
||||
- locals
|
||||
- globals
|
||||
- types
|
||||
|
||||
## Operator precedence
|
||||
|
||||
|
|
|
@ -7,6 +7,9 @@ import ../config
|
|||
import ../types/value
|
||||
|
||||
import strformat
|
||||
import sequtils
|
||||
import sugar
|
||||
import bitops
|
||||
|
||||
type
|
||||
Emitter* = ref object
|
||||
|
@ -27,7 +30,9 @@ type
|
|||
depth: int # depth of the local
|
||||
# -1 if cannot be referenced yet
|
||||
scope: Scope # innermost scope
|
||||
captured: bool
|
||||
captured: bool # if true, it was captured from a more inner function
|
||||
# and it will need to outlive its scope and become a GC'd thing
|
||||
# (it becomes an upvalue eventually)
|
||||
|
||||
Upvalue* = ref object
|
||||
index: int
|
||||
|
@ -72,6 +77,8 @@ proc writePops(em: Emitter, n: int) =
|
|||
return
|
||||
elif n > argMax:
|
||||
em.error("Too many local variables in block.")
|
||||
elif n < 0:
|
||||
raise newException(Defect, "writePops call with negative amount of pops.")
|
||||
elif n == 1:
|
||||
em.writeChunk(-1, opPop)
|
||||
elif n < shortArgMax:
|
||||
|
@ -93,6 +100,7 @@ proc addLocal(em: Emitter, name: string, delta: int) =
|
|||
if em.locals.len >= argMax:
|
||||
em.error("Too many local variables in function.")
|
||||
# TODO: check if it can be increased
|
||||
# TODO 2: check if delta can be assumed that it is 0, and the depth == -1 thing completely removed
|
||||
# if delta is 0 or negative - means that the var is already on stack when addLocal is called
|
||||
# if delta is +, the first ever value of the local is about to be added
|
||||
# so it should be -1, (see Local typedef) to indicate that it cannot be referenced yet
|
||||
|
@ -104,6 +112,77 @@ proc addLocal(em: Emitter, name: string, delta: int) =
|
|||
proc markInitialized(em: Emitter) =
|
||||
em.locals[em.locals.high()].depth = em.scopes.high()
|
||||
|
||||
proc addUpvalue(em: Emitter, 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:
|
||||
em.error("Too many closure variables in function.")
|
||||
blk
|
||||
|
||||
var scopeIndex = local.depth + 1
|
||||
# if the scope it is directly in is a function, then upvalues
|
||||
# are in more inner functions only. If it is a block, it doesn't matter
|
||||
# therefore there is a +1 here
|
||||
|
||||
var isLocal = true # if isLocal it means that it is the outermost function closing it
|
||||
var upvalIndex: int = local.index
|
||||
|
||||
while scopeIndex < em.scopes.len():
|
||||
let scope = em.scopes[scopeIndex]
|
||||
if scope.function:
|
||||
scope.lenCheck():
|
||||
return 0
|
||||
block ensure:
|
||||
# exiting this block means 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 # after the first function scope is done through, it's not local anymore
|
||||
scopeIndex.inc()
|
||||
return upvalIndex
|
||||
|
||||
proc resolveLocal(em: Emitter, name: string): tuple[index: int, upvalue: bool] =
|
||||
## Tries to find local with name
|
||||
##
|
||||
## returns:
|
||||
## upvalue - if it is an upvalue
|
||||
## index - stack index of the local
|
||||
## - if upvalue, the upvalue index
|
||||
## - if it's -1, the name cannot be resolved
|
||||
|
||||
var i = em.locals.high()
|
||||
let cfunc: Scope = if em.scopes.len() > 0: em.scopes[em.scopes.high()].parentFunction else: nil
|
||||
while i >= 0:
|
||||
let local = em.locals[i]
|
||||
i.dec()
|
||||
|
||||
if local.name == name:
|
||||
if local.depth == -1:
|
||||
continue # not initialized yet, so it is a var x = x situation => look for x in outer scopes
|
||||
# from here, it's definitely a match
|
||||
let upvalue = local.scope.parentFunction != cfunc # defined in a diff. function, only possible if more outer, so it's an upvalue
|
||||
if upvalue:
|
||||
local.captured = true # mark it as something that needs to outlive its scope
|
||||
return (em.addUpvalue(local), true)
|
||||
else:
|
||||
return (local.index, false)
|
||||
return (-1, false)
|
||||
|
||||
|
||||
# jump helpers
|
||||
|
||||
proc emitJump(em: Emitter, delta: int, op: OpCode): (int, int) =
|
||||
|
@ -162,10 +241,50 @@ proc emit(em: Emitter, node: Node) =
|
|||
of nkLen:
|
||||
em.emit(node.argument)
|
||||
em.writeChunk(0, opLen)
|
||||
of nkProgram:
|
||||
for ch in node.pChildren:
|
||||
em.emit(ch)
|
||||
of nkBlockExpr:
|
||||
# TODO: scopes
|
||||
# new scope is started, which also saves the target stack len as the one here + 1
|
||||
let scope = em.newScope(false)
|
||||
|
||||
# emit opNil in place of return value
|
||||
em.writeChunk(1, opNil)
|
||||
|
||||
for label in node.labels:
|
||||
scope.labels.add(label)
|
||||
# add the return value as all labels
|
||||
em.addLocal(&":{label}", delta = 0)
|
||||
|
||||
for ch in node.children:
|
||||
em.emit(ch)
|
||||
|
||||
# all the children statements are complete, scope cleanup
|
||||
# old compiler endScope(), with function is false
|
||||
discard em.scopes.pop() # pop scope (still accessible in scope)
|
||||
|
||||
# old compiler restore()
|
||||
# pop new locals in this scope
|
||||
let delta = em.stackIndex - scope.goalStackIndex
|
||||
em.writePops(delta)
|
||||
|
||||
# close upvalues - old compiler endScope again
|
||||
while em.locals.len() > 0:
|
||||
let local = em.locals.pop()
|
||||
if local.scope == scope and local.captured:
|
||||
em.writeChunk(0, opCloseUpvalue)
|
||||
em.writeChunk(0, local.index.toDU8())
|
||||
if local.depth < em.scopes.high() + 1:
|
||||
break
|
||||
|
||||
if not em.stackIndex == scope.goalStackIndex:
|
||||
# kept for good measure, even if the above should mathematically always return this
|
||||
em.error("Assertion failed: can't restore scope after block expression.")
|
||||
|
||||
# patch jumps to end of scope
|
||||
for jump in scope.jumps:
|
||||
em.patchJump(jump, scope.goalStackIndex)
|
||||
|
||||
of nkExpr:
|
||||
em.emit(node.expression)
|
||||
of nkExprStmt:
|
||||
|
@ -252,6 +371,76 @@ proc emit(em: Emitter, node: Node) =
|
|||
em.emitLoop(loopStart, loopStackLen)
|
||||
|
||||
em.patchJump(exitJump, exitStackLen)
|
||||
of nkVarGet:
|
||||
var getOp = opGetGlobal # default get op
|
||||
let name = node.gVarName
|
||||
# use resolve helper to find it
|
||||
var (arg, upval) = em.resolveLocal(name)
|
||||
if arg == -1:
|
||||
# global, its name should be added as a constant
|
||||
# since we don't need arg anymore, replace arg with
|
||||
# constant index
|
||||
arg = em.chunk.addConstant(name.fromNimString())
|
||||
else:
|
||||
# local or upvalue
|
||||
if upval:
|
||||
# upvalue
|
||||
getOp = opGetUpvalue
|
||||
else:
|
||||
# normal local
|
||||
getOp = opGetLocal
|
||||
em.writeChunk(1, getOp) # write the getop
|
||||
# checking for argmax shouldn't be needed as the arg is
|
||||
# returned from helpers that should check for it already
|
||||
em.writeChunk(0, arg.toDU8()) # write its arg
|
||||
of nkVarSet:
|
||||
# first emit the value it's being set to
|
||||
em.emit(node.newVal)
|
||||
|
||||
# assign value to var
|
||||
var setOp = opSetGlobal # default set op
|
||||
let name = node.sVarName
|
||||
# try to resolve it
|
||||
var (arg, upval) = em.resolveLocal(name)
|
||||
if arg == -1:
|
||||
arg = em.chunk.addConstant(name.fromNimString())
|
||||
else:
|
||||
if upval:
|
||||
setOp = opSetUpvalue
|
||||
else:
|
||||
setOp = opSetLocal
|
||||
em.writeChunk(0, setOp)
|
||||
em.writeChunk(0, arg.toDU8())
|
||||
of nkVarDecl:
|
||||
# first, if there is a value to save to this variable, put it on the stack. If not, put a nil on the stack.
|
||||
if node.value != nil:
|
||||
em.emit(node.value)
|
||||
else:
|
||||
em.writeChunk(1, opNil)
|
||||
|
||||
# now emit the relevant assigning stuff
|
||||
if em.scopes.len() > 0:
|
||||
# local since is within scope
|
||||
# check if name already exists in the same scope
|
||||
for i in countdown(em.locals.high(), 0):
|
||||
let local = em.locals[i]
|
||||
if local.depth != -1 and local.depth < em.scopes.len():
|
||||
break # would be more outer scope, so break
|
||||
if node.name == local.name:
|
||||
em.error("Already a variable with this name in this scope.")
|
||||
break # redeclaring variables not allowed
|
||||
|
||||
em.addLocal(node.name, 0)
|
||||
# number argument is delta, and it is one indicating that the value is on the stack
|
||||
# leaving it on the stack is what defines locals
|
||||
# so no instruction is needed here
|
||||
else:
|
||||
# global
|
||||
# put name as constant, and save to it
|
||||
let index = em.chunk.addConstant(node.name.fromNimString())
|
||||
em.writeChunk(-1, opDefineGlobal)
|
||||
em.writeChunk(0, index.toDU8())
|
||||
|
||||
else:
|
||||
raise newException(Defect, &"Unsupported node kind: {$node.kind}")
|
||||
proc emit*(em: Emitter) =
|
||||
|
|
|
@ -7,7 +7,7 @@ import sequtils
|
|||
|
||||
type
|
||||
NodeKind* = enum
|
||||
nkBlockExpr, nkExprStmt, nkBreak, nkExpr,
|
||||
nkBlockExpr, nkExprStmt, nkProgram, nkBreak, nkExpr,
|
||||
nkConst, nkList, nkTable, nkGetIndex, nkSetIndex, nkLen,
|
||||
nkIf, nkWhile, nkOr, nkAnd, nkNegate, nkNot,
|
||||
nkPlus, nkMinus, nkMult, nkDiv, nkEq, nkNeq, nkLess, nkGreater,
|
||||
|
@ -22,6 +22,8 @@ type
|
|||
of nkBlockExpr:
|
||||
children*: seq[Node]
|
||||
labels*: seq[string]
|
||||
of nkProgram:
|
||||
pChildren*: seq[Node]
|
||||
of nkExprStmt, nkExpr: # only when there is a ; does it compile to expr stmt; expr used for parentheses, &
|
||||
expression*: Node
|
||||
of nkBreak:
|
||||
|
@ -135,6 +137,8 @@ proc `$`*(node: Node): string =
|
|||
of nkProc:
|
||||
let params = node.parameters.join(", ")
|
||||
result = &"(proc params: {params} body: {node.procBody})"
|
||||
of nkProgram:
|
||||
result = node.pChildren.map(`$`).join(";\n")
|
||||
of nkSetIndex:
|
||||
result = &"({node.sCollection}[{node.sIndex}] = {node.sValue})"
|
||||
of nkTable:
|
||||
|
|
|
@ -473,6 +473,8 @@ proc statement(parser: Parser, inBlock: bool = false): Node =
|
|||
elif parser.match(tkVar):
|
||||
discard parser.consume(tkIdentifier, "Identifier expected after 'var'.")
|
||||
let name = parser.previous.get().text
|
||||
if name[0] == ':':
|
||||
parser.errorAtCurrent("Attempt to declare variable with name starting with ':'.")
|
||||
if parser.match(tkEqual):
|
||||
let val = parser.expression()
|
||||
result = Node(kind: nkVarDecl, name: name, value: val, line: parser.line)
|
||||
|
@ -487,10 +489,10 @@ proc statement(parser: Parser, inBlock: bool = false): Node =
|
|||
|
||||
proc parse*(parser: Parser): Node =
|
||||
parser.scanner = newScanner(parser.source)
|
||||
result = Node(kind: nkBlockExpr, children: @[], line: parser.line)
|
||||
result = Node(kind: nkProgram, pChildren: @[], line: parser.line)
|
||||
|
||||
parser.advance()
|
||||
while not parser.isAtEnd():
|
||||
let statement = parser.statement()
|
||||
result.children.add(statement)
|
||||
result.pChildren.add(statement)
|
||||
|
||||
|
|
Loading…
Reference in New Issue