nondescript/src/ndspkg/compv2/emitter.nim

166 lines
4.6 KiB
Nim

# a new bytecode emitter for nds
# emitter: takes AST, emits bytecode. It also binds variables.
import node
import ../chunk
import ../config
import ../types/value
import strformat
type
Emitter* = ref object
root: Node
chunk*: Chunk
hadError*: bool
line: int
# binding
stackIndex: int
locals: seq[Local]
scopes: seq[Scope]
Local = ref object
name: string
index: int # index in the stack (0 - stack bottom)
depth: int # depth of the local
# -1 if cannot be referenced yet
scope: Scope # innermost scope
captured: bool
Upvalue* = ref object
index: int
isLocal: bool
Scope = ref object
labels: seq[string]
goalStackIndex: int # stack count it started with plus 1
jumps: seq[int] # list of jumps to be patched to the end of the scope
function: bool # if true, it's a function
parentFunction: Scope # if function, itself. Else, the innermost function it's in
upvalues: seq[Upvalue] # for functions, list of upvalues
# helpers
proc newEmitter*(name: string, root: Node): Emitter =
new(result)
result.root = root
result.chunk = initChunk(name)
result.stackIndex = -1
proc error(em: Emitter, msg: string) =
write stderr, &"[line {em.line}] Error {msg}\n"
em.hadError = true
proc newScope(em: Emitter, funct: bool): Scope =
result.new()
result.function = funct
result.goalStackIndex = em.stackIndex + 1
if funct:
result.parentFunction = result
elif em.scopes.len() > 0:
result.parentFunction = em.scopes[em.scopes.high()].parentFunction
em.scopes.add(result)
# chunk writers
proc writeChunk(em: Emitter, dStackIndex: int, ch: OpCode | DoubleUint8 | uint8) =
em.stackIndex += dStackIndex
em.chunk.writeChunk(ch, em.line)
proc writePops(em: Emitter, n: int) =
if n == 0:
return
elif n > argMax:
em.error("Too many local variables in block.")
elif n == 1:
em.writeChunk(-1, opPop)
elif n < shortArgMax:
em.writeChunk(-n, opPopSA)
em.writeChunk(0, n.uint8)
else:
em.writeChunk(-n, opPopA)
em.writeChunk(0, n.toDU8())
proc writeConstant(em: Emitter, constant: NdValue) =
em.stackIndex.inc()
let index = em.chunk.writeConstant(constant, em.line)
if index >= argMax:
em.error("Too many constants in one chunk.")
# locals helpers
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
# 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
let depth = if delta > 0: -1 else: em.scopes.high
em.locals.add(
Local(name: name, depth: depth, index: em.stackIndex + delta, scope: em.scopes[em.scopes.high()], captured: false)
)
proc markInitialized(em: Emitter) =
em.locals[em.locals.high()].depth = em.scopes.high()
# node compilers
proc emit(em: Emitter, node: Node) =
em.line = node.line
case node.kind:
of nkFalse:
em.writeChunk(1, opFalse)
of nkTrue:
em.writeChunk(1, opTrue)
of nkNil:
em.writeChunk(1, opNil)
of nkConst:
em.writeConstant(node.constant)
of nkNegate:
em.emit(node.argument)
em.writeChunk(0, opNegate)
of nkNot:
em.emit(node.argument)
em.writeChunk(0, opNot)
of nkLen:
em.emit(node.argument)
em.writeChunk(0, opLen)
of nkBlockExpr:
# TODO: scopes
for ch in node.children:
em.emit(ch)
of nkExpr:
em.emit(node.expression)
of nkExprStmt:
em.emit(node.expression)
em.writePops(1)
of nkPlus, nkMinus, nkMult, nkDiv, nkEq, nkNeq, nkGreater, nkLess, nkGe, nkLe:
em.emit(node.left)
em.emit(node.right)
case node.kind:
of nkPlus: em.writeChunk(-1, opAdd)
of nkMinus: em.writeChunk(-1, opSubtract)
of nkMult: em.writeChunk(-1, opMultiply)
of nkDiv: em.writeChunk(-1, opDivide)
of nkEq: em.writeChunk(-1, opEqual)
of nkNeq:
em.writeChunk(-1, opEqual)
em.writeChunk(0, opNot)
of nkGreater:
em.writeChunk(-1, opGreater)
of nkLess:
em.writeChunk(-1, opLess)
of nkGe:
em.writeChunk(-1, opLess)
em.writeChunk(0, opNot)
of nkLe:
em.writeChunk(-1, opGreater)
em.writeChunk(0, opNot)
else:
raise newException(Defect, "Misaligned case list.") # unreachable
else:
raise newException(Defect, &"Unsupported node kind: {$node.kind}")
proc emit*(em: Emitter) =
em.emit(em.root)
em.writeChunk(0, opReturn) # required for the vm to exit gracefully