emitting variables, scopes
This commit is contained in:
parent
6fc8514a6b
commit
8c90b669e8
|
@ -222,6 +222,7 @@ TODO
|
||||||
|
|
||||||
- locals
|
- locals
|
||||||
- globals
|
- globals
|
||||||
|
- types
|
||||||
|
|
||||||
## Operator precedence
|
## Operator precedence
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,9 @@ import ../config
|
||||||
import ../types/value
|
import ../types/value
|
||||||
|
|
||||||
import strformat
|
import strformat
|
||||||
|
import sequtils
|
||||||
|
import sugar
|
||||||
|
import bitops
|
||||||
|
|
||||||
type
|
type
|
||||||
Emitter* = ref object
|
Emitter* = ref object
|
||||||
|
@ -27,7 +30,9 @@ type
|
||||||
depth: int # depth of the local
|
depth: int # depth of the local
|
||||||
# -1 if cannot be referenced yet
|
# -1 if cannot be referenced yet
|
||||||
scope: Scope # innermost scope
|
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
|
Upvalue* = ref object
|
||||||
index: int
|
index: int
|
||||||
|
@ -72,6 +77,8 @@ proc writePops(em: Emitter, n: int) =
|
||||||
return
|
return
|
||||||
elif n > argMax:
|
elif n > argMax:
|
||||||
em.error("Too many local variables in block.")
|
em.error("Too many local variables in block.")
|
||||||
|
elif n < 0:
|
||||||
|
raise newException(Defect, "writePops call with negative amount of pops.")
|
||||||
elif n == 1:
|
elif n == 1:
|
||||||
em.writeChunk(-1, opPop)
|
em.writeChunk(-1, opPop)
|
||||||
elif n < shortArgMax:
|
elif n < shortArgMax:
|
||||||
|
@ -93,6 +100,7 @@ proc addLocal(em: Emitter, name: string, delta: int) =
|
||||||
if em.locals.len >= argMax:
|
if em.locals.len >= argMax:
|
||||||
em.error("Too many local variables in function.")
|
em.error("Too many local variables in function.")
|
||||||
# TODO: check if it can be increased
|
# 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 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
|
# 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
|
# 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) =
|
proc markInitialized(em: Emitter) =
|
||||||
em.locals[em.locals.high()].depth = em.scopes.high()
|
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
|
# jump helpers
|
||||||
|
|
||||||
proc emitJump(em: Emitter, delta: int, op: OpCode): (int, int) =
|
proc emitJump(em: Emitter, delta: int, op: OpCode): (int, int) =
|
||||||
|
@ -162,10 +241,50 @@ proc emit(em: Emitter, node: Node) =
|
||||||
of nkLen:
|
of nkLen:
|
||||||
em.emit(node.argument)
|
em.emit(node.argument)
|
||||||
em.writeChunk(0, opLen)
|
em.writeChunk(0, opLen)
|
||||||
|
of nkProgram:
|
||||||
|
for ch in node.pChildren:
|
||||||
|
em.emit(ch)
|
||||||
of nkBlockExpr:
|
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:
|
for ch in node.children:
|
||||||
em.emit(ch)
|
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:
|
of nkExpr:
|
||||||
em.emit(node.expression)
|
em.emit(node.expression)
|
||||||
of nkExprStmt:
|
of nkExprStmt:
|
||||||
|
@ -252,6 +371,76 @@ proc emit(em: Emitter, node: Node) =
|
||||||
em.emitLoop(loopStart, loopStackLen)
|
em.emitLoop(loopStart, loopStackLen)
|
||||||
|
|
||||||
em.patchJump(exitJump, exitStackLen)
|
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:
|
else:
|
||||||
raise newException(Defect, &"Unsupported node kind: {$node.kind}")
|
raise newException(Defect, &"Unsupported node kind: {$node.kind}")
|
||||||
proc emit*(em: Emitter) =
|
proc emit*(em: Emitter) =
|
||||||
|
|
|
@ -7,7 +7,7 @@ import sequtils
|
||||||
|
|
||||||
type
|
type
|
||||||
NodeKind* = enum
|
NodeKind* = enum
|
||||||
nkBlockExpr, nkExprStmt, nkBreak, nkExpr,
|
nkBlockExpr, nkExprStmt, nkProgram, nkBreak, nkExpr,
|
||||||
nkConst, nkList, nkTable, nkGetIndex, nkSetIndex, nkLen,
|
nkConst, nkList, nkTable, nkGetIndex, nkSetIndex, nkLen,
|
||||||
nkIf, nkWhile, nkOr, nkAnd, nkNegate, nkNot,
|
nkIf, nkWhile, nkOr, nkAnd, nkNegate, nkNot,
|
||||||
nkPlus, nkMinus, nkMult, nkDiv, nkEq, nkNeq, nkLess, nkGreater,
|
nkPlus, nkMinus, nkMult, nkDiv, nkEq, nkNeq, nkLess, nkGreater,
|
||||||
|
@ -22,6 +22,8 @@ type
|
||||||
of nkBlockExpr:
|
of nkBlockExpr:
|
||||||
children*: seq[Node]
|
children*: seq[Node]
|
||||||
labels*: seq[string]
|
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, &
|
of nkExprStmt, nkExpr: # only when there is a ; does it compile to expr stmt; expr used for parentheses, &
|
||||||
expression*: Node
|
expression*: Node
|
||||||
of nkBreak:
|
of nkBreak:
|
||||||
|
@ -135,6 +137,8 @@ proc `$`*(node: Node): string =
|
||||||
of nkProc:
|
of nkProc:
|
||||||
let params = node.parameters.join(", ")
|
let params = node.parameters.join(", ")
|
||||||
result = &"(proc params: {params} body: {node.procBody})"
|
result = &"(proc params: {params} body: {node.procBody})"
|
||||||
|
of nkProgram:
|
||||||
|
result = node.pChildren.map(`$`).join(";\n")
|
||||||
of nkSetIndex:
|
of nkSetIndex:
|
||||||
result = &"({node.sCollection}[{node.sIndex}] = {node.sValue})"
|
result = &"({node.sCollection}[{node.sIndex}] = {node.sValue})"
|
||||||
of nkTable:
|
of nkTable:
|
||||||
|
|
|
@ -473,6 +473,8 @@ proc statement(parser: Parser, inBlock: bool = false): Node =
|
||||||
elif parser.match(tkVar):
|
elif parser.match(tkVar):
|
||||||
discard parser.consume(tkIdentifier, "Identifier expected after 'var'.")
|
discard parser.consume(tkIdentifier, "Identifier expected after 'var'.")
|
||||||
let name = parser.previous.get().text
|
let name = parser.previous.get().text
|
||||||
|
if name[0] == ':':
|
||||||
|
parser.errorAtCurrent("Attempt to declare variable with name starting with ':'.")
|
||||||
if parser.match(tkEqual):
|
if parser.match(tkEqual):
|
||||||
let val = parser.expression()
|
let val = parser.expression()
|
||||||
result = Node(kind: nkVarDecl, name: name, value: val, line: parser.line)
|
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 =
|
proc parse*(parser: Parser): Node =
|
||||||
parser.scanner = newScanner(parser.source)
|
parser.scanner = newScanner(parser.source)
|
||||||
result = Node(kind: nkBlockExpr, children: @[], line: parser.line)
|
result = Node(kind: nkProgram, pChildren: @[], line: parser.line)
|
||||||
|
|
||||||
parser.advance()
|
parser.advance()
|
||||||
while not parser.isAtEnd():
|
while not parser.isAtEnd():
|
||||||
let statement = parser.statement()
|
let statement = parser.statement()
|
||||||
result.children.add(statement)
|
result.pChildren.add(statement)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue