This commit is contained in:
prod2 2022-12-02 12:09:10 +01:00
parent 70fa10bfa6
commit c21c797ef4
16 changed files with 1717 additions and 0 deletions

116
chunk.nim Normal file
View File

@ -0,0 +1,116 @@
import strformat
import strutils
import value
type
OpCode* = enum
opReturn, opCall, # functions
opPop, # pop
opPrint, # print
opNegate, opNot # unary
opAdd, opSubtract, opMultiply, opDivide, # math
opEqual, opGreater, opLess, # comparison
opTrue, opFalse, opNil, # literal
opConstant, # constant
opDefineGlobal, opGetGlobal, opSetGlobal, # globals (uses constants)
opGetLocal, opSetLocal, # locals
opJumpIfFalse, opJump, opLoop, opJumpIfFalsePop, # jumps
Chunk* = object
code*: seq[uint8]
constants*: seq[KonValue]
lines*: seq[int]
name*: string # name of the module/chunk/files
DoubleUint8* = array[2, uint8]
const argSize* = 2
const argMax*: int = 256*256
proc initChunk*(name: string): Chunk =
Chunk(code: @[], name: name, lines: @[], constants: @[])
proc writeChunk*(ch: var Chunk, code: uint8, line: int) =
ch.code.add(code)
ch.lines.add(line)
proc writeChunk*(ch: var Chunk, code: OpCode, line: int) =
ch.code.add(code.uint8)
ch.lines.add(line)
proc writeChunk*(ch: var Chunk, code: DoubleUint8, line: int) =
for c in code:
ch.code.add(c)
ch.lines.add(line)
proc len*(ch: Chunk): int =
ch.code.len
proc toDU8*(integ: int): DoubleUint8 =
cast[ptr array[2, uint8]](integ.unsafeAddr)[]
proc toInt*(du8: DoubleUint8): int =
cast[uint16](du8).int
proc DU8ptrToInt*(du8: ptr uint8): int =
cast[ptr uint16](du8)[].int
proc addConstant*(ch: var Chunk, constant: KonValue): int =
ch.constants.add(constant)
ch.constants.high
proc writeConstant*(ch: var Chunk, constant: KonValue, line: int): int =
result = ch.addConstant(constant)
ch.writeChunk(opConstant, line)
ch.writeChunk(result.toDU8, line)
const simpleInstructions = {
opReturn,
opPop,
opPrint,
opNegate, opNot,
opAdd, opSubtract, opMultiply, opDivide,
opEqual, opGreater, opLess,
opTrue, opFalse, opNil
}
const constantInstructions = {
opConstant,
opDefineGlobal, opGetGlobal, opSetGlobal,
}
const argInstructions = {
opCall,
opGetLocal, opSetLocal,
opJumpIfFalse, opJump, opLoop, opJumpIfFalsePop,
}
proc disassembleChunk*(ch: Chunk) =
echo &"== Chunk {ch.name} begin =="
echo "index line instruction"
var c: int = 0
var lastLine = -1
while c < ch.code.len:
template instruction: uint8 = ch.code[c]
template line: int = ch.lines[c]
template double: DoubleUint8 = [ch.code[c+1], ch.code[c+2]]
let cFmt = &"{c:04}"
let lineFmt = if lastLine == line: " | " else: &"{line:04}"
try:
write stdout, &"[{cFmt}] {lineFmt} {instruction.OpCode} ({instruction.toHex(2)}"
case instruction.OpCode:
of simpleInstructions:
write stdout, ")\n"
of argInstructions:
write stdout, &" {double[0].toHex(2)} {double[1].toHex(2)})\n"
c += 2
of constantInstructions:
let i = double.toInt
write stdout, &" {double[0].toHex(2)} {double[1].toHex(2)})\n"
echo &" points to constant {ch.constants[i]} (i: {i})"
c += 2
except:
echo &"[{cFmt}] {lineFmt} Unknown opcode {instruction}"
c.inc
echo &"== Chunk {ch.name} end =="

762
compiler.nim Normal file
View File

@ -0,0 +1,762 @@
import strformat
import strutils
import sugar
import tables
import options
import scanner
import chunk
import value
import config
type
Local = object
name: string # name of this local
index: int # what is its index in the stack (0 is the stack bottom - nil in the main function)
depth: int # depth of this local
# if depth is -1, the variable cannot be referenced yet
# its depth will be set once its first ever value is determined
Scope = ref object
labels: seq[string]
#depth: int
goalStackIndex: int # the stack count it started with plus 1
jumps: seq[int] # jumps to be patched that jump to the end
function: bool # if true, it is a function
Compiler = ref object
#
# input
#
scanner: Scanner
source: string
#
# state
#
previous: Token
current: Token
canAssign: bool
locals: seq[Local]
scopes: seq[Scope]
stackIndex: int # how large the stack is
# when there's an error both are set
# panic mode can be turned off e.g. at block boundaries
panicMode: bool
#
# output
#
chunk*: Chunk
hadError*: bool
Precedence = enum
pcNone, pcAssignment, pcOr, pcAnd, pcEquality, pcComparison,
pcTerm, pcFactor, pcUnary, pcCall, pcPrimary
# pcUnary applies to all prefix operators regardless of this enum's value
# changing pcUnary's position can change the priority of all unary ops
#
# Note: unary only rules should have precedence pcNone!!!
ParseRule = object
name: string # debug purposes only
prefix: (Compiler) -> void
infix: (Compiler) -> void
prec: Precedence # only relevant to infix, prefix always has pcUnary
proc newScope(comp: Compiler, function: bool): Scope =
result.new()
#result.depth = comp.scopes.len + 1
result.function = function
result.goalStackIndex = comp.stackIndex + 1
comp.scopes.add(result)
# HELPERS FOR THE COMPILER TYPE
proc newCompiler*(name: string, source: string): Compiler =
result = new(Compiler)
result.chunk = initChunk(name)
result.source = source
result.hadError = false
result.panicMode = false
result.canAssign = true
result.locals = @[]
result.scopes = @[]
proc errorAt(comp: Compiler, line: int, msg: string, at: string = "") =
if comp.panicMode:
return
write stderr, &"[line {line}] Error "
if at.len > 0:
write stderr, &"at {at} "
write stderr, msg
write stderr, "\n"
comp.hadError = true
comp.panicMode = true
proc error(comp: Compiler, msg: string) =
## create a simple error message
comp.errorAt(comp.previous.line, msg)
proc errorAtCurrent(comp: Compiler, msg: string) =
comp.errorAt(comp.current.line, msg)
proc advance(comp: Compiler) =
comp.previous = comp.current
while true:
comp.current = comp.scanner.scanToken()
when debugScanner:
comp.current.debugPrint()
if (comp.current.tokenType != tkError):
break
comp.errorAtCurrent(comp.current.text)
proc match(comp: Compiler, tokenType: TokenType): bool =
if comp.current.tokenType == tokenType:
comp.advance()
true
else:
false
proc consume(comp: Compiler, tokenType: TokenType, msg: string) =
if comp.current.tokenType == tokenType:
comp.advance()
else:
comp.errorAtCurrent(msg)
proc synchronize(comp: Compiler) =
comp.panicMode = false
while comp.current.tokenType != tkEof:
if comp.previous.tokenType in {tkSemicolon, tkRightBrace}:
return
if comp.current.tokenType in {tkFunct, tkVar, tkFor, tkIf, tkWhile}:
return
comp.advance()
proc writeChunk(comp: Compiler, dStackIndex: int, ch: OpCode | DoubleUint8) =
comp.stackIndex += dStackIndex
when debugCompiler:
debugEcho &"new stackindex: {comp.stackIndex}, delta: {dStackIndex} due to {ch.repr}"
comp.chunk.writeChunk(ch, comp.previous.line)
proc writeConstant(comp: Compiler, constant: KonValue) =
comp.stackIndex.inc
let index = comp.chunk.writeConstant(constant, comp.previous.line)
if index >= argMax:
comp.error("Too many constants in one chunk.")
proc addLocal(comp: Compiler, name: string, delta: int) =
if comp.locals.len >= argMax:
comp.error("Too many local variables in function.")
# if delta is 0 or negative, it means that it is already on the stack when addLocal is called
# if delta is positive, the first ever value of the local is to the right
comp.locals.add(Local(name: name, depth: if delta > 0: -1 else: comp.scopes.high, index: comp.stackIndex + delta))
proc markInitialized(comp: Compiler) =
comp.locals[comp.locals.high].depth = comp.scopes.high
# PARSE RULE/PRECEDENCE MISC
proc nop(comp: Compiler) = discard
var rules: Table[TokenType, ParseRule]
template genRule(ttype: TokenType, tprefix: (Compiler) -> void, tinfix: (Compiler) -> void, tprec: Precedence) =
if tprec == pcUnary:
raise newException(Exception, "pcUnary cannot be used as a rule precedence! Use pcNone for unary-only rules!")
elif tprec == pcPrimary:
raise newException(Exception, "Invalid rule: pcPrimary cannot be used for binary operators, if this rule is for a primary value, use pcNone!")
elif tprec == pcNone and tinfix != nop:
raise newException(Exception, "Invalid rule: pcNone only allowed for unary operators and primary values, not for infix ones!")
rules[ttype] = ParseRule(name: $ttype, prefix: tprefix, infix: tinfix, prec: tprec)
proc getRule(opType: TokenType): Option[ParseRule] =
if rules.hasKey(opType):
some(rules[opType])
else:
none(ParseRule)
proc applyRule(rule: ParseRule): Precedence =
# returns the rule's precedence
rule.prec
proc increment(prec: Precedence): Precedence =
# increases precedence by one
if prec == pcPrimary:
raise newException(Exception, "Invalid ruletable, pcPrimary precedence increment attempted.")
else:
Precedence(int(prec) + 1)
proc `<=`(a, b: Precedence): bool =
int(a) <= int(b)
# JUMP HELPERS
proc emitJump(comp: Compiler, delta: int, op: OpCode): int =
# delta -> 0 if the jump does not pop
# delta -> -1 if the jump pops the condition from the stack
comp.writeChunk(delta, op)
comp.writeChunk(0, 0xffffff.toDU8)
comp.chunk.len - argSize
proc patchJump(comp: Compiler, offset: int) =
let jump = (comp.chunk.len - offset - argSize)
if (jump > argMax):
comp.error("Too much code to jump over.")
let jumpt = jump.toDU8
comp.chunk.code[offset] = jumpt[0]
comp.chunk.code[offset + 1] = jumpt[1]
proc emitLoop(comp: Compiler, loopstart: int, delta: int, op: OpCode) =
comp.writeChunk(delta, op)
let offset = comp.chunk.len - loopstart + argSize
if offset > argMax:
comp.error("Loop body too large.")
comp.writeChunk(0, offset.toDU8)
# SCOPE HELPERS
proc beginScope(comp: Compiler, function: bool = false) =
let scope = comp.newScope(function)
when debugCompiler:
debugEcho &"Begin scope called for depth {comp.scopes.len} function? {function}"
if function:
scope.labels.add("result")
else:
while comp.match(tkLabel):
let label = comp.previous.text[1..^1]
scope.labels.add(label)
if function:
# if it's a function scope, the frame will move
# access to outside locals is also limited to upvalues and closures
comp.stackIndex = 0
else:
# only put the opNil if it's not a function scope, since
# function scopes are initialized by the caller
comp.writeChunk(1, opNil)
comp.addLocal("^", delta = 0)
# note, that later added scope labels will not be accessible through ^
# so it is better not to assign scope labels after beginScope
for label in scope.labels:
comp.addLocal(&"^{label}", delta = 0)
proc restore(comp: Compiler, scope: Scope) =
let delta = comp.stackIndex - scope.goalStackIndex
for i in countup(1, delta):
comp.writeChunk(-1, opPop)
when assertionsCompiler:
if not comp.stackIndex == scope.goalStackIndex:
comp.error("Assertion failed in restore")
when debugCompiler:
debugEcho &"Restored scope: delta {delta}"
proc restoreInFunct(comp: Compiler, scope: Scope) =
when debugCompiler:
var pops = 0
while comp.stackIndex > 0:
comp.writeChunk(-1, opPop)
when debugCompiler:
inc pops
when assertionsCompiler:
if not comp.stackIndex == 0:
comp.error("Assertion failed in restoreInFunct")
comp.stackIndex = scope.goalStackIndex
when debugCompiler:
debugEcho &"Restored function scope: delta {pops}; new stackindex: {comp.stackIndex}"
proc jumpToEnd(comp: Compiler, scope: Scope) =
let delta = comp.stackIndex - scope.goalStackIndex
for i in countup(1, delta):
comp.writeChunk(0, opPop)
let jmp = comp.emitJump(0, opJump)
scope.jumps.add(jmp)
proc endScope(comp: Compiler) =
# remove locals
let popped = comp.scopes.pop()
let function = popped.function
when debugCompiler:
debugEcho &"End scope called for depth {comp.scopes.len} function? {function}"
if function:
comp.restoreInFunct(popped)
else:
comp.restore(popped)
# patch jumps to after the scope (such jumps from breaks emit the pops before jumping)
for jump in popped.jumps:
comp.patchJump(jump)
if function:
comp.writeChunk(0, opReturn)
# EXPRESSIONS
proc parsePrecedence(comp: Compiler, prec: Precedence) =
comp.advance()
let rule = comp.previous.tokenType.getRule
if rule.isSome and rule.get.prefix != nop:
comp.canAssign = prec <= pcAssignment
let rule = rule.get
when debugCompiler:
debugEcho &"parsePrecedence call, valid prefix op found, rule used: {rule.name}, precedence: {prec}"
rule.prefix(comp)
while comp.current.tokenType.getRule.isSome and
prec <= comp.current.tokenType.getRule.get.prec:
comp.advance()
# checked for isSome in the loop
# since advance moves current to previous
if comp.previous.tokenType.getRule.get.infix == nop:
# should never happen, as having a precedence set
# means that it is a binary op
comp.error("Invalid rule table.")
return
else:
let infixRule = comp.previous.tokenType.getRule.get.infix
infixRule(comp)
else:
comp.error("Expect expression.")
proc expression(comp: Compiler) =
## The lowest precedence among the pratt-parsed expressions
# DO NOT ADD ANYTHING HERE
# only use the pratt table for parsing expressions!
when assertionsVM:
let oldStackIndex = comp.stackIndex
comp.parsePrecedence(pcAssignment)
when assertionsVM:
let diff = comp.stackIndex - oldStackIndex
if diff != 1:
comp.error(&"Assertion failed: expression increased ({oldStackIndex} -> {comp.stackIndex}) the stack index by {diff} (should be 1).")
proc number(comp: Compiler) =
# assume the number is already advanced through
let value = comp.previous.text.parseFloat.toKonValue
comp.writeConstant(value)
when debugCompiler:
debugEcho &"Written constant (type: {value.konType}, str repr: {$value}) to chunk"
tkNumber.genRule(number, nop, pcNone)
proc expFalse(comp: Compiler) =
comp.writeChunk(1, opFalse)
tkFalse.genRule(expFalse, nop, pcNone)
proc expTrue(comp: Compiler) =
comp.writeChunk(1, opTrue)
tkTrue.genRule(expTrue, nop, pcNone)
proc expNil(comp: Compiler) =
comp.writeChunk(1, opNil)
tkNil.genRule(expNil, nop, pcNone)
proc expString(comp: Compiler) =
let value = comp.previous.text[1..^2].toKonValue()
comp.writeConstant(value)
when debugCompiler:
debugEcho &"Written constant (type: {value.konType}, str repr: {$value}) to chunk"
tkString.genRule(expString, nop, pcNone)
proc resolveLocal(comp: Compiler, name: string): int =
## returns the stack index of the local of the name
var i = comp.locals.high
while i >= 0:
let local = comp.locals[i]
if local.name == name:
if local.depth == -1:
comp.error("Can't read local variable in its own initializer.")
return local.index
i.dec
return -1
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 = comp.resolveLocal(name)
if arg != -1:
# local
getOp = opGetLocal
setOp = opSetLocal
else:
# global
arg = comp.chunk.addConstant(name.toKonValue)
if comp.match(tkEqual):
# assignment (global/local)
if not comp.canAssign:
comp.error("Invalid assignment target.")
return
comp.expression()
comp.writeChunk(0, setOp)
else:
# get (global/local)
comp.writeChunk(1, getOp)
comp.writeChunk(0, arg.toDU8)
tkIdentifier.genRule(variable, nop, pcNone)
proc grouping(comp: Compiler) =
# assume initial '(' is already consumed
comp.expression()
comp.consume(tkRightParen, "Expect ')' after expression.")
proc parseCall(comp: Compiler) =
# ( consumed
# create the call env
# current stack before opCall:
# ... <funct obj> <arg1> <arg2> <arg3>
# opCall converts it to this
# ... <ret val> <arg1> <arg2> <arg3>
var argcount = 0
# put args on stack
while comp.current.tokenType notin {tkRightParen, tkEof}:
comp.expression()
inc argcount
if comp.current.tokenType != tkRightParen:
comp.consume(tkComma, "Expected ',' between arguments in function calls.")
comp.consume(tkRightParen, "Expected ')' after arguments in function calls.")
# emit call
comp.writeChunk(-argcount, opCall)
comp.writeChunk(0, argcount.toDU8)
tkLeftParen.genRule(grouping, parseCall, pcCall)
proc unary(comp: Compiler) =
let opType = comp.previous.tokenType
comp.parsePrecedence(pcUnary)
case opType:
of tkMinus:
comp.writeChunk(0, opNegate)
of tkBang:
comp.writeChunk(0, opNot)
else:
discard # unreachable
tkBang.genRule(unary, nop, pcNone)
proc binary(comp: Compiler) =
let opType = comp.previous.tokenType
# safety checked in parsePrecedence
let rule = opType.getRule.get
comp.parsePrecedence(rule.applyRule.increment)
case opType:
of tkPlus:
comp.writeChunk(-1, opAdd)
of tkMinus:
comp.writeChunk(-1, opSubtract)
of tkStar:
comp.writeChunk(-1, opMultiply)
of tkSlash:
comp.writeChunk(-1, opDivide)
of tkEqualEqual:
comp.writeChunk(-1, opEqual)
of tkBangEqual:
comp.writeChunk(-1, opEqual)
comp.writeChunk(0, opNot)
of tkGreater:
comp.writeChunk(-1, opGreater)
of tkLess:
comp.writeChunk(-1, opLess)
of tkGreaterEqual:
comp.writeChunk(-1, opLess)
comp.writeChunk(0, opNot)
of tkLessEqual:
comp.writeChunk(-1, opGreater)
comp.writeChunk(0, opNot)
else:
return # unreachable
tkMinus.genRule(unary, binary, pcTerm)
tkPlus.genRule(nop, binary, pcTerm)
tkSlash.genRule(nop, binary, pcFactor)
tkStar.genRule(nop, binary, pcFactor)
tkEqualEqual.genRule(nop, binary, pcEquality)
tkBangEqual.genRule(nop, binary, pcEquality)
tkGreater.genRule(nop, binary, pcComparison)
tkGreaterEqual.genRule(nop, binary, pcComparison)
tkLess.genRule(nop, binary, pcComparison)
tkLessEqual.genRule(nop, binary, pcComparison)
proc ifExpr(comp: Compiler) =
# if expressions return the body if condition is truthy,
# the else expression otherwise, unless there is no else:
# if there is no else, it returns the condition if it is falsey
comp.consume(tkLeftParen, "Expect '(' after 'if'.")
comp.expression()
comp.consume(tkRightParen, "Expect ')' after condition.")
let thenJump = comp.emitJump(0, opJumpIfFalse)
# conditional code that can be jumped over must leave the stack in tact!
comp.writeChunk(-1, opPop)
comp.expression()
# net change to stack: -1 + 1 = 0
let elseJump = comp.emitJump(0, opJump)
comp.patchJump(thenJump)
if comp.match(tkElse):
comp.writeChunk(-1, opPop)
comp.expression()
comp.patchJump(elseJump)
tkIf.genRule(ifExpr, nop, pcNone)
proc andExpr(comp: Compiler) =
let endJump = comp.emitJump(0, opJumpIfFalse)
comp.writeChunk(-1, opPop)
comp.parsePrecedence(pcAnd)
# net effect on stack: -1 + 1 = 0
comp.patchJump(endJump)
tkAnd.genRule(nop, andExpr, pcAnd)
proc orExpr(comp: Compiler) =
let elseJump = comp.emitJump(0, opJumpIfFalse)
let endJump = comp.emitJump(0, opJump)
comp.patchJump(elseJump)
comp.writeChunk(-1, opPop)
comp.parsePrecedence(pcOr)
# net effect on stack: -1 + 1 = 0
comp.patchJump(endJump)
tkOr.genRule(nop, orExpr, pcOr)
proc debugExpr(comp: Compiler) =
comp.expression()
when debugCompiler:
debugEcho &"debug expression, current stackindex: {comp.stackIndex}"
comp.writeChunk(0, opPrint)
tkPrint.genRule(debugExpr, nop, pcNone)
proc parseWhile(comp: Compiler) =
comp.writeChunk(1, opNil) # return value
let loopStart = comp.chunk.len
comp.consume(tkLeftParen, "Expect '(' after 'while'.")
# condition
comp.expression()
comp.consume(tkRightParen, "Expect ')' after condition.")
let exitJump = comp.emitJump(-1, opJumpIfFalsePop)
# this cannot be handled with just opPop, since the net change in the
# stack size inside code that is conditional must be 0!
# body
comp.writeChunk(-1, opPop) # pop the old return value
comp.expression()
# net stack change: 1 + -1 = 0
comp.emitLoop(loopstart = loopStart, delta = 0, op = opLoop)
comp.patchJump(exitJump)
tkWhile.genRule(parseWhile, nop, pcNone)
proc parseFunct(comp: Compiler) =
# jump over
let jumpOverBody = comp.emitJump(0, opJump)
comp.consume(tkLeftParen, "Expected '(' after keyword 'funct'.")
var params: seq[string]
# parameters
while comp.current.tokenType == tkIdentifier:
comp.advance()
params.add(comp.previous.text)
if comp.current.tokenType == tkRightParen:
break
comp.consume(tkComma, "Expected ',' to separate items in the parameter list.")
comp.consume(tkRightParen, "Expected ')' after parameter list.")
# function body:
let functII = comp.chunk.len
comp.beginScope(function = true) # this saves the old stackindex, sets it to 0, points ^ and ^function at index 0
# assumption:
# the caller will create the following stack for the function to run in:
# [0] = return value placeholder
# [1] = arg #1
# [2] = arg #2
# [3] = arg #3
for i in countup(1, params.len):
comp.stackIndex = i
comp.addLocal(params[i-1], 0)
comp.expression()
when assertionsCompiler:
let shouldbeStackIndex = params.len + 1
if shouldbeStackIndex != comp.stackIndex:
comp.error(&"Assertion failed: wrong stackindex ({comp.stackIndex}) in function declaration (should be {shouldbeStackIndex}).")
comp.endScope()
dec comp.stackIndex # the previous end scope did not put anything on the stack, it is jumped over
# get konvalue functions:
let konFunct = newKonFunction(functII, params.len)
# end of function declaration:
comp.patchJump(jumpOverBody)
# put the fn object on the stack
comp.writeConstant(konFunct)
tkFunct.genRule(parseFunct, nop, pcNone)
# below are the expressions that can contain statements in some way
# the only expressions that can contain a statement are:
# the block expression
proc statement(comp: Compiler)
proc parseBlock(comp: Compiler) =
## Despite the name, can be used for statements if the arg statement is true
## Also can be used for function bodies
comp.beginScope()
while comp.current.tokenType != tkRightBrace and comp.current.tokenType != tkEof:
comp.statement()
comp.endScope()
comp.consume(tkRightBrace, "Expect '}' after block.")
tkLeftBrace.genRule(parseBlock, nop, pcNone)
# statements
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.toKonValue())
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 breakStatement(comp: Compiler) =
if not comp.match(tkLabel):
comp.error("Label expected after break.")
let label = comp.previous.text[1..^1]
for i in countdown(comp.scopes.high, 0):
let scope = comp.scopes[i]
if scope.labels.contains(label):
comp.jumpToEnd(scope)
break
comp.consume(tkSemicolon, "Semicolon expected after break statement.")
if comp.current.tokenType != tkRightBrace:
comp.error("Break statement must be the last element inside the innermost block it is in.")
proc statement(comp: Compiler) =
if comp.match(tkVar):
comp.varStatement()
comp.consume(tkSemicolon, "Semicolon expected after expression statement.")
elif comp.match(tkBreak):
comp.breakStatement()
else:
comp.expression()
comp.writeChunk(-1, opPop)
comp.consume(tkSemicolon, "Semicolon expected after expression statement.")
if comp.panicMode:
comp.synchronize()
proc compile*(comp: Compiler) =
comp.scanner = newScanner(comp.source)
comp.writeChunk(0, opNil)
# the starting stackIndex is 0, which points to this nil
# it is correctly set to delta = 0!!!
comp.advance()
while comp.current.tokenType != tkEof:
comp.statement()
comp.writeChunk(-1, opPop)
comp.writeChunk(0, opReturn)
when debugDumpChunk:
if not comp.hadError:
comp.chunk.disassembleChunk()

32
config.nim Normal file
View File

@ -0,0 +1,32 @@
type
LineEditorChoice = enum
leBasic, leRdstdin
ReadlineInterruptedException* = object of CatchableError
# choose debug options here
const debugVM* = false
const debugScanner* = false
const debugCompiler* = false
const debugDumpChunk* = false
const assertionsVM* = false # sanity checks in the VM, such as the stack being empty at the end
const assertionsCompiler* = false # sanity checks in the compiler
const profileInstructions* = false # if true, the time spent on every opcode is measured
# choose a line editor for the repl
const lineEditor = leRdstdin
when lineEditor == leRdstdin:
import rdstdin
proc konLineEditor*: string =
proc ctrlc =
raise newException(ReadlineInterruptedException, "Ctrl+C/D pressed.")
when lineEditor == leBasic:
write stdout, "\r-> "
result = stdin.readLine()
when lineEditor == leRdstdin:
var line: string
let ok = readLineFromStdin("-> ", line)
if not ok:
ctrlc()
return line

BIN
main Normal file

Binary file not shown.

52
main.nim Normal file
View File

@ -0,0 +1,52 @@
import vm
import compiler
import os
import config
type Result = enum
rsOK, rsCompileError, rsRuntimeError
proc interpret(name: string, source: string): Result =
let compiler = newCompiler(name, source)
compiler.compile()
if compiler.hadError:
return rsCompileError
let vm = newVM(compiler.chunk)
case vm.run():
of irOK:
rsOK
of irRuntimeError:
rsRuntimeError
proc repl =
while true:
try:
let line = konLineEditor()
if line.len > 0:
discard interpret("repl", line)
except ReadlineInterruptedException:
break
proc runFile(path: string) =
case interpret(path, readFile(path)):
of rsCompileError:
quit 65
of rsRuntimeError:
quit 70
of rsOK:
quit 0
const hardcodedPath* = ""
if paramCount() == 0:
if hardcodedPath == "":
repl()
else:
runFile(hardcodedPath)
elif paramCount() == 1:
runFile(paramStr(1))
else:
echo "Maximum param count is 1"
quit 1

0
perf.sh Executable file → Normal file
View File

9
pointerutils.nim Normal file
View File

@ -0,0 +1,9 @@
template padd*[T](x: ptr T, num: int): ptr T =
cast[ptr T](cast[int](x) + num)
template psub*[T](x: ptr T, num: int): ptr T =
cast[ptr T](cast[int](x) - num)
template pdiff*[T](x: ptr T, y: ptr): int =
cast[int](x) - cast[int](y)

200
scanner.nim Normal file
View File

@ -0,0 +1,200 @@
import strutils
import tables
import strformat
type
Scanner* = ref object
start: int
current: int
line: int
source: string
TokenType* = enum
tkNone, # the default tokentype, if encountered anywhere, erroring out is the best course of action
tkLeftParen, tkRightParen, tkLeftBrace, tkRightBrace, tkComma, tkDot,
tkMinus, tkPlus, tkSemicolon, tkSlash, tkStar, tkBang, tkBangEqual,
tkGreater, tkGreaterEqual, tkLess, tkLessEqual, tkEqual, tkEqualEqual,
tkIdentifier, tkString,
tkNumber, tkAnd, tkElse, tkFalse, tkFor, tkFunct, tkGoto, tkIf, tkNil,
tkOr, tkPrint, tkLabel, tkBreak, tkTrue, tkVar, tkWhile,
tkError, tkEof
Token* = object
tokenType*: TokenType
text*: string
line*: int
proc debugPrint*(token: Token) =
write stdout, &"Token of type {$token.tokenType} [{token.text}] at line {$token.line}\n"
proc isAtEnd(scanner: Scanner): bool =
scanner.current > scanner.source.high
proc advance(scanner: Scanner): char =
scanner.current.inc
scanner.source[scanner.current - 1]
proc peek(scanner: Scanner): char =
if scanner.isAtEnd():
'\0'
else:
scanner.source[scanner.current]
proc peekNext(scanner: Scanner): char =
if scanner.current < scanner.source.high:
scanner.source[scanner.current + 1]
else:
'\0'
proc match(scanner: Scanner, exp: char): bool =
if scanner.peek() == exp:
discard scanner.advance()
true
else:
false
proc newScanner*(source: string): Scanner =
Scanner(source: source, line: 0, current: 0)
proc makeToken(scanner: Scanner, tokenType: TokenType): Token =
result.tokenType = tokenType
result.text = scanner.source[scanner.start..scanner.current-1]
result.line = scanner.line
proc errorToken(scanner: Scanner, msg: string): Token =
result.tokenType = tkError
result.text = msg
result.line = scanner.line
proc skipWhitespace(scanner: Scanner) =
while true:
let c = scanner.peek()
case c:
of {' ', '\r', '\t'}:
discard scanner.advance()
of '\n':
scanner.line.inc
discard scanner.advance()
of '/':
if scanner.peekNext() == '/':
while scanner.peek != '\n' and not scanner.isAtEnd():
discard scanner.advance()
else:
return
else:
return
proc scanString(scanner: Scanner): Token =
while scanner.peek() != '\"' and not scanner.isAtEnd():
if scanner.peek() == '\n':
scanner.line.inc
discard scanner.advance()
if scanner.isAtEnd():
return scanner.errorToken("Unterminated string.")
discard scanner.advance()
scanner.makeToken(tkString)
proc scanNumber(scanner: Scanner): Token =
while scanner.peek() in Digits:
discard scanner.advance()
if scanner.peek() == '.' and scanner.peekNext() in Digits:
discard scanner.advance()
while scanner.peek() in Digits:
discard scanner.advance()
return scanner.makeToken(tkNumber)
const keywords = {
"and": tkAnd,
"break": tkBreak,
"else": tkElse,
"false": tkFalse,
"for": tkFor,
"funct": tkFunct,
# here's a language that uses funct... still waiting for the day when a good de-funct joke comes to my mind that I can abuse
"goto": tkGoto,
"if": tkIf,
"nil": tkNil,
"or": tkOr,
"print": tkPrint,
"true": tkTrue,
"var": tkVar,
"while": tkWhile
}.toTable
proc canStartIdent(chr: char): bool =
chr in Letters or chr in {'_', '^'}
proc canContIdent(chr: char): bool =
canStartIdent(chr) or chr in Digits
proc scanIdentifier(scanner: Scanner): Token =
while scanner.peek.canContIdent:
discard scanner.advance()
let text = scanner.source[scanner.start..scanner.current-1]
if keywords.hasKey(text):
return scanner.makeToken(keywords[text])
return scanner.makeToken(tkIdentifier)
proc canContLabel(chr: char): bool =
chr in Letters or chr == '_'
proc scanLabel(scanner: Scanner): Token =
if not scanner.peek.canContLabel:
return scanner.errorToken("Labels must only contain letters and underscores.")
while scanner.peek.canContLabel:
discard scanner.advance()
return scanner.makeToken(tkLabel)
proc scanToken*(scanner: Scanner): Token =
scanner.skipWhitespace()
scanner.start = scanner.current
if scanner.isAtEnd():
return scanner.makeToken(tkEof)
let c = scanner.advance()
case c:
of '(': return scanner.makeToken(tkLeftParen)
of ')': return scanner.makeToken(tkRightParen)
of '{': return scanner.makeToken(tkLeftBrace)
of '}': return scanner.makeToken(tkRightBrace)
of ';': return scanner.makeToken(tkSemicolon)
of ',': return scanner.makeToken(tkComma)
of '.': return scanner.makeToken(tkDot)
of '-': return scanner.makeToken(tkMinus)
of '+': return scanner.makeToken(tkPlus)
of '/': return scanner.makeToken(tkSlash)
of '*': return scanner.makeToken(tkStar)
of '!':
return if scanner.match('='): scanner.makeToken(tkBangEqual) else: scanner.makeToken(tkBang)
of '=':
return if scanner.match('='): scanner.makeToken(tkEqualEqual) else: scanner.makeToken(tkEqual)
of '<':
return if scanner.match('='): scanner.makeToken(tkLessEqual) else: scanner.makeToken(tkLess)
of '>':
return if scanner.match('='): scanner.makeToken(tkGreaterEqual) else: scanner.makeToken(tkGreater)
of '\"':
return scanner.scanString()
of Digits:
return scanner.scanNumber()
of '@':
return scanner.scanLabel()
else:
if c.canStartIdent():
return scanner.scanIdentifier()
else:
return scanner.errorToken("Unexpected character.")

View File

@ -0,0 +1,22 @@
import ../scanner
import ../chunk
import ../config
import parser/parser
import emitter/emitter
proc compile*(source: string): CompileResult =
result = CompileResult(ok: false)
let scanner = newScanner(source)
let parser = newParser(scanner)
let nodeRoot = parser.parse()
if parser.hadError:
return result
let emitter = newEmitter(nodeRoot)
let chunk = emitter.emit()
if emitter.hadError:
return result
when debugDumpChunk:
if not emitter.hadError:
chunk.disassembleChunk()
return CompileResult(ok: true, chunk: chunk)

View File

View File

@ -0,0 +1,14 @@
import ../types
import ../../scanner
import utils
import statements
proc newParser*(sc: Scanner): Parser =
result.new()
result.scanner = sc
proc parse*(par: Parser): Node =
result = Node(kind: nkRoot)
while par.current.tokenType != tkEof:
result.children.add(par.statement())

View File

@ -0,0 +1,45 @@
import ../types
import utils
# below are the expressions that can contain statements in some way
# the only expressions that can contain a statement are:
# the block expression
proc statement*(par: Parser)
proc parseBlock(par: Parser): Node =
## Despite the name, can be used for statements if the arg statement is true
## Also can be used for function bodies
result = Node(kind: nkBlockExpr)
while par.current.tokenType != tkRightBrace and par.current.tokenType != tkEof:
result.children.add(par.statement())
par.consume(tkRightBrace, "Expect '}' after block.")
# statements
proc breakStatement(par: Parser): Node =
if not par.match(tkLabel):
par.error("Label expected after break.")
let label = comp.previous.text[1..^1]
result = Node(kind: nkBreak, label: label)
par.consume(tkSemicolon, "Semicolon expected after break statement.")
if par.current.tokenType != tkRightBrace:
par.error("Break statement must be the last element inside the innermost block it is in.")
proc statement*(par: Parser): Node =
if par.match(tkVar):
result = par.varStatement()
par.consume(tkSemicolon, "Semicolon expected after expression statement.")
elif par.match(tkDef):
result = par.procStatement()
par.consume(tkSemicolon, "Semicolon expected after procedure declaration.")
elif par.match(tkBreak):
result = par.breakStatement()
else:
result = Node(kind: nkExprStmt, expression: par.expression())
par.consume(tkSemicolon, "Semicolon expected after expression statement.")
if par.panicMode:
par.synchronize()

View File

@ -0,0 +1,52 @@
import ../types
import ../../config
proc errorAt*(par: Parser, line: int, msg: string, at: string = "") =
if par.panicMode:
return
write stderr, &"[line {line}] Error "
if at.len > 0:
write stderr, &"at {at} "
write stderr, msg
write stderr, "\n"
par.hadError = true
par.panicMode = true
proc error*(par: Parser, msg: string) =
## create a simple error message
par.errorAt(par.previous.line, msg, par.previous.text)
proc errorAtCurrent*(par: Parser, msg: string, supress: bool = false) =
par.errorAt(par.current.line, msg, if supress: "" else: par.current.text)
proc advance*(par: Parser) =
par.previous = par.current
while true:
par.current = par.scanner.scanToken()
when debugScanner:
par.current.debugPrint()
if (par.current.tokenType != tkError):
break
par.errorAtCurrent(par.current.text, true)
proc match*(par: Parser, tokenType: TokenType): bool =
if par.current.tokenType == tokenType:
par.advance()
true
else:
false
proc consume*(par: Parser, tokenType: TokenType, msg: string) =
if par.current.tokenType == tokenType:
par.advance()
else:
par.errorAtCurrent(msg)
proc synchronize*(par: Parser) =
par.panicMode = false
while par.current.tokenType != tkEof:
if par.previous.tokenType in {tkSemicolon, tkRightBrace}:
return
if par.current.tokenType in {tkFunct, tkVar, tkFor, tkIf, tkWhile}:
return
par.advance()

View File

@ -0,0 +1,35 @@
import ../scanner
import ../chunk
import ../types/value
type
Parser* = ref object
scanner*: Scanner
current*: Token
previous*: Token
hadError*: bool
panicMode*: bool
NodeKind* = enum
nkBlockExpr, nkRoot,
nkExprStmt,
nkBreak,
nkConst
Node* = ref object
case kind*: NodeKind:
of nkBlockExpr, nkRoot:
children*: seq[Node]
of nkExprStmt:
expression*: Node
of nkBreak:
label*: string
of nkConst:
constant*: NdValue
CompileResult* = object
case ok*: bool:
of true:
chunk*: Chunk
of false:
discard

142
value.nim Normal file
View File

@ -0,0 +1,142 @@
import strformat
type
KonType* = enum
ktNil, ktBool, ktFloat, ktString,
ktFunct,
ktTypeError,
const errorTypes = {ktTypeError}
type
KonValue* = object
case konType*: KonType:
of ktNil:
discard
of ktBool:
boolValue*: bool
of ktFloat:
floatValue*: float64
of ktString:
stringValue*: string
of ktFunct:
module*: string
entryII*: int # entry instruction index
arity*: int # number of arguments
of errorTypes:
message*: string
# KON VALUE HELPERS, MUST BE DEFINED FOR EVERY KONVALUE
proc `$`*(val: KonValue): string =
case val.konType:
of ktFloat:
return $val.floatValue
of ktBool:
return $val.boolValue
of ktNil:
return "nil"
of ktString:
return val.stringValue
of ktFunct:
return &"Function object: {val.entryII}"
of errorTypes:
let ename = $val.konType
return &"{ename[2..^1]}: {val.message}"
proc isError*(val: KonValue): bool =
val.konType in errorTypes
proc isFalsey*(val: KonValue): bool =
val.konType in {ktNil} or (val.konType == ktBool and not val.boolValue)
template isTruthy*(val: KonValue): bool =
not isFalsey(val)
proc equal*(val, right: KonValue): bool =
if val.konType != right.konType:
false
else:
case val.konType:
of ktFloat:
val.floatValue == right.floatValue
of ktBool:
val.boolValue == right.boolValue
of ktNil:
true
of ktString:
val.stringValue == right.stringValue
of ktFunct:
val.entryII == right.entryII
# same entry II/module but diff arity is a bug
of errorTypes:
false # error comparison is undefined
# NIM VALUE TO KON VALUE WRAPPERS
proc toKonValue*(val: float): KonValue =
KonValue(konType: ktFloat, floatValue: val)
proc toKonValue*(val: bool): KonValue =
KonValue(konType: ktBool, boolValue: val)
proc toKonValue*(val: string): KonValue =
KonValue(konType: ktString, stringValue: val)
proc newKonFunction*(ii: int, arity: int): KonValue =
KonValue(konType: ktFunct, entryII: ii, arity: arity)
proc toKonValue*: KonValue =
KonValue(konType: ktNil)
proc konTypeError*(msg: string): KonValue =
KonValue(konType: ktTypeError, message: msg)
# OPERATIONS
# NOTE: these operations can return ktTypeError with a message if types are invalid
proc negate*(val: KonValue): KonValue =
if (val.konType != ktFloat):
return konTypeError("Operand must be a number.")
else:
return toKonValue(-val.floatValue)
proc add*(val: KonValue, right: KonValue): KonValue =
if val.konType == ktFloat and right.konType == ktFloat:
return toKonValue(val.floatValue + right.floatValue)
elif val.konType == ktString and right.konType == ktString:
return toKonValue(val.stringValue & right.stringValue)
else:
return konTypeError(&"Attempt to add types {val.konType} and {right.konType}.")
proc subtract*(val: KonValue, right: KonValue): KonValue =
if val.konType == ktFloat and right.konType == ktFloat:
return toKonValue(val.floatValue - right.floatValue)
else:
return konTypeError(&"Attempt to subtract types {val.konType} and {right.konType}.")
proc multiply*(val: KonValue, right: KonValue): KonValue =
if val.konType == ktFloat and right.konType == ktFloat:
return toKonValue(val.floatValue * right.floatValue)
else:
return konTypeError(&"Attempt to multiply types {val.konType} and {right.konType}.")
proc divide*(val: KonValue, right: KonValue): KonValue =
if val.konType == ktFloat and right.konType == ktFloat:
return toKonValue(val.floatValue / right.floatValue)
else:
return konTypeError(&"Attempt to divide types {val.konType} and {right.konType}.")
proc `<`*(val: KonValue, right: KonValue): KonValue =
if val.konType == ktFloat and right.konType == ktFloat:
return toKonValue(val.floatValue < right.floatValue)
else:
return konTypeError(&"Attempt to compare types {val.konType} and {right.konType}.")
proc `>`*(val: KonValue, right: KonValue): KonValue =
if val.konType == ktFloat and right.konType == ktFloat:
return toKonValue(val.floatValue > right.floatValue)
else:
return konTypeError(&"Attempt to compare types {val.konType} and {right.konType}.")

236
vm.nim Normal file
View File

@ -0,0 +1,236 @@
import strformat
import tables
import value
import chunk
import config
import pointerutils
when profileInstructions:
import times
import std/monotimes
type
Frame = object
stackBottom: int # the absolute index of where 0 inside the frame is
returnIp: ptr uint8
VM* = ref object
chunk: Chunk
ip: ptr uint8
stack: seq[KonValue]
hadError: bool
globals: Table[string, KonValue]
frames: seq[Frame]
InterpretResult* = enum
irOK, irRuntimeError
proc newVM*(ch: Chunk): VM =
result = VM(chunk: ch, stack: newSeqOfCap[KonValue](256), frames: newSeqOfCap[Frame](4))
result.ip = ch.code[0].unsafeAddr
proc push(vm: VM, val: KonValue) =
vm.stack.add(val)
proc pop(vm: VM): KonValue =
vm.stack.pop()
proc peek(vm: VM): KonValue =
vm.stack[vm.stack.high]
proc runtimeError(vm: VM, msg: string) =
let ii = vm.ip.pdiff(vm.chunk.code[0].unsafeAddr)
let line = vm.chunk.lines[ii]
write stderr, &"[line: {line}] {msg}\n"
vm.hadError = true
proc pushSafe(vm: VM, val: KonValue): bool =
## returns if the value is not a runtime error
## prints the error if it is a runtime error
## pushes it to the stack if it is not an error
if val.isError():
vm.runtimeError($val)
return false
else:
vm.push(val)
return true
proc advance(vm: VM): uint8 =
result = vm.ip[]
vm.ip = vm.ip.padd(1)
proc readDU8(vm: VM): int =
result = vm.ip.DU8ptrToInt
vm.ip = vm.ip.padd(argSize)
proc readConstant(vm: VM): KonValue =
let index = vm.readDU8()
vm.chunk.constants[index]
proc binary(op: OpCode, left: KonValue, right: KonValue): KonValue =
case op:
of opAdd:
return left.add(right)
of opSubtract:
return left.subtract(right)
of opMultiply:
return left.multiply(right)
of opDivide:
return left.divide(right)
else:
discard #unreachable
when profileInstructions:
# not a perfect profiling approach, doing random stacktrace dumps
# x amount of times per second to get the current source line
# would be better
var durations: array[OpCode, Duration]
proc run*(vm: VM): InterpretResult =
template frameBottom: int = vm.frames[vm.frames.high].stackBottom
while true:
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
let ins = vm.advance.OpCode
when debugVM:
let opname = ($ins)
var msg = &"[{vm.ii:4}] {opname}"
msg &= " Stack: [ "
for i in 0 .. vm.stack.high:
let e = vm.stack[i]
if i == frameBottom:
msg &= &"<{e}> "
else:
msg &= &"{e} "
msg &= "]"
echo msg
when profileInstructions:
let startTime = getMonoTime()
case ins:
of opPop:
discard vm.pop()
of opConstant:
let val: KonValue = vm.readConstant()
vm.push(val)
of opNegate:
let val = vm.pop.negate
if not vm.pushSafe(val):
break
of opAdd, opSubtract, opMultiply, opDivide:
let right = vm.pop()
let left = vm.pop()
if not vm.pushSafe(binary(ins, left, right)):
break
of opReturn:
if vm.frames.len == 0:
break
else:
vm.ip = vm.frames.pop().returnIp # remove frame that's over
of opTrue:
vm.push(toKonValue(true))
of opFalse:
vm.push(toKonValue(false))
of opNil:
vm.push(toKonValue())
of opNot:
vm.push(toKonValue(vm.pop.isFalsey))
of opEqual:
vm.push(toKonValue(vm.pop.equal(vm.pop())))
of opLess:
if not vm.pushSafe(vm.pop() > vm.pop()):
break
of opGreater:
if not vm.pushSafe(vm.pop() < vm.pop()):
break
of opPrint:
echo $vm.peek()
of opDefineGlobal:
let name = vm.readConstant().stringValue
if not vm.globals.hasKey(name):
vm.globals[name] = vm.pop()
else:
vm.runtimeError("Attempt to redefine an existing global variable.")
break
of opGetGlobal:
let name = vm.readConstant().stringValue
if vm.globals.hasKey(name):
vm.push(vm.globals[name])
else:
vm.runtimeError(&"Undefined global variable {name}.")
break
of opSetGlobal:
let name = vm.readConstant().stringValue
if vm.globals.hasKey(name):
vm.globals[name] = vm.peek()
else:
vm.runtimeError(&"Attempt to set undefined global variable {name}.")
break
of opGetLocal:
let slot = vm.readDU8()
vm.push(vm.stack[slot + frameBottom])
of opSetLocal:
let slot = vm.readDU8()
vm.stack[slot + frameBottom] = vm.peek()
of opJumpIfFalse:
let offset = vm.readDU8()
if vm.peek.isFalsey:
vm.ip = vm.ip.padd(offset)
of opJumpIfFalsePop:
let offset = vm.readDU8()
if vm.pop.isFalsey:
vm.ip = vm.ip.padd(offset)
of opJump:
let offset = vm.readDU8()
vm.ip = vm.ip.padd(offset)
of opLoop:
let offset = vm.readDU8()
vm.ip = vm.ip.psub(offset)
of opCall:
# create the call env
# current stack before opCall:
# ... <funct obj> <arg1> <arg2> <arg3>
# opCall converts it to this
# ... <ret val> <arg1> <arg2> <arg3>
let argcount = vm.readDU8()
let funct = vm.stack[vm.stack.high - argcount]
if funct.konType != ktFunct:
vm.runtimeError("Attempt to call a non-funct (a defunct?).") # here is a bad defunct joke
break
if funct.arity != argcount:
vm.runtimeError(&"Wrong number of arguments, expected {funct.arity}, got {argcount}.")
break
# if funct.module ... TODO
vm.stack[vm.stack.high - argcount] = toKonValue() # replace the function with nil: this is the return value slot
# create new frame
vm.frames.add(Frame(stackBottom: vm.stack.high - argcount, returnIp: vm.ip))
vm.ip = vm.chunk.code[0].unsafeAddr.padd(funct.entryII) # jump to the entry point
when profileInstructions:
durations[ins] += getMonoTime() - startTime
when assertionsVM:
if not vm.hadError and vm.stack.len > 0:
vm.runtimeError(&"VM Assertion failed: stack is of non-zero length {vm.stack.len} after execution has finished.")
when profileInstructions:
for op in OpCode:
let dur = durations[op].inMilliseconds
echo &"OpCode: {op} total duration {dur} ms"
if vm.hadError:
irRuntimeError
else:
irOK
proc interpretChunk*(ch: Chunk): InterpretResult =
let vm = newVM(ch)
return vm.run()