feat(vm): add stack-based virtual machine
This commit is contained in:
1 parent
a7239932fb
commit
34ec137a70
1 file changed
+508
@@ -0,0 +1,508 @@
|
|||||||
|
package lumen.vm
|
||||||
|
|
||||||
|
import lumen.RuntimeError
|
||||||
|
import lumen.bytecode.Chunk
|
||||||
|
import lumen.bytecode.Opcode
|
||||||
|
import lumen.runtime.*
|
||||||
|
|
||||||
|
private class CallFrame(val closure: ClosureValue, var ip: Int, val slotBase: Int)
|
||||||
|
|
||||||
|
class Vm(val out: Appendable = System.out) {
|
||||||
|
private val stack = ArrayList<Value>(256)
|
||||||
|
private val frames = ArrayList<CallFrame>()
|
||||||
|
private val openUpvalues = ArrayList<Upvalue>()
|
||||||
|
val globals = HashMap<String, Value>()
|
||||||
|
val methods = HashMap<String, Method>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
installBuiltins(this)
|
||||||
|
installMethods(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defineMethod(method: Method) {
|
||||||
|
methods[method.name] = method
|
||||||
|
}
|
||||||
|
|
||||||
|
fun defineNative(name: String, native: NativeFunctionValue) {
|
||||||
|
globals[name] = native
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interpret(script: FunctionValue): Value {
|
||||||
|
stack.clear()
|
||||||
|
frames.clear()
|
||||||
|
openUpvalues.clear()
|
||||||
|
val closure = ClosureValue(script, emptyArray())
|
||||||
|
push(closure)
|
||||||
|
frames.add(CallFrame(closure, 0, 0))
|
||||||
|
return run(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invoke(callable: Value, args: List<Value>): Value {
|
||||||
|
when (callable) {
|
||||||
|
is NativeFunctionValue -> {
|
||||||
|
checkArity(callable, args.size)
|
||||||
|
return callable.call(args)
|
||||||
|
}
|
||||||
|
is ClosureValue -> {
|
||||||
|
if (callable.function.arity != args.size) {
|
||||||
|
throw RuntimeError(
|
||||||
|
"function '${callable.function.name}' expects ${callable.function.arity} arguments but got ${args.size}",
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val floor = frames.size
|
||||||
|
push(callable)
|
||||||
|
args.forEach { push(it) }
|
||||||
|
frames.add(CallFrame(callable, 0, stack.size - args.size - 1))
|
||||||
|
run(floor)
|
||||||
|
return pop()
|
||||||
|
}
|
||||||
|
else -> throw RuntimeError("value of type ${typeName(callable)} is not callable", 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun run(exitFrameFloor: Int): Value {
|
||||||
|
while (true) {
|
||||||
|
val frame = frames[frames.size - 1]
|
||||||
|
val chunk = frame.closure.function.chunk
|
||||||
|
when (Opcode.from(readByte(frame, chunk))) {
|
||||||
|
Opcode.Constant -> push(chunk.constants[readByte(frame, chunk)])
|
||||||
|
Opcode.Nil -> push(NilValue)
|
||||||
|
Opcode.True -> push(BoolValue(true))
|
||||||
|
Opcode.False -> push(BoolValue(false))
|
||||||
|
Opcode.Pop -> pop()
|
||||||
|
|
||||||
|
Opcode.Add -> binaryAdd(frame, chunk)
|
||||||
|
Opcode.Sub -> arithmetic(frame, chunk, Arith.Sub)
|
||||||
|
Opcode.Mul -> arithmetic(frame, chunk, Arith.Mul)
|
||||||
|
Opcode.Div -> arithmetic(frame, chunk, Arith.Div)
|
||||||
|
Opcode.Mod -> arithmetic(frame, chunk, Arith.Mod)
|
||||||
|
Opcode.Negate -> negate(frame, chunk)
|
||||||
|
|
||||||
|
Opcode.Equal -> {
|
||||||
|
val b = pop()
|
||||||
|
val a = pop()
|
||||||
|
push(BoolValue(valuesEqual(a, b)))
|
||||||
|
}
|
||||||
|
Opcode.NotEqual -> {
|
||||||
|
val b = pop()
|
||||||
|
val a = pop()
|
||||||
|
push(BoolValue(!valuesEqual(a, b)))
|
||||||
|
}
|
||||||
|
Opcode.Less -> compare(frame, chunk, Compare.Less)
|
||||||
|
Opcode.LessEqual -> compare(frame, chunk, Compare.LessEqual)
|
||||||
|
Opcode.Greater -> compare(frame, chunk, Compare.Greater)
|
||||||
|
Opcode.GreaterEqual -> compare(frame, chunk, Compare.GreaterEqual)
|
||||||
|
Opcode.Not -> push(BoolValue(!isTruthy(pop())))
|
||||||
|
|
||||||
|
Opcode.GetLocal -> {
|
||||||
|
val slot = readByte(frame, chunk)
|
||||||
|
push(stack[frame.slotBase + slot])
|
||||||
|
}
|
||||||
|
Opcode.SetLocal -> {
|
||||||
|
val slot = readByte(frame, chunk)
|
||||||
|
stack[frame.slotBase + slot] = peek(0)
|
||||||
|
}
|
||||||
|
Opcode.GetGlobal -> {
|
||||||
|
val name = constantName(chunk, readByte(frame, chunk))
|
||||||
|
val value = globals[name] ?: throw RuntimeError("undefined variable '$name'", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
push(value)
|
||||||
|
}
|
||||||
|
Opcode.SetGlobal -> {
|
||||||
|
val name = constantName(chunk, readByte(frame, chunk))
|
||||||
|
if (!globals.containsKey(name)) {
|
||||||
|
throw RuntimeError("undefined variable '$name'", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
globals[name] = peek(0)
|
||||||
|
}
|
||||||
|
Opcode.DefineGlobal -> {
|
||||||
|
val name = constantName(chunk, readByte(frame, chunk))
|
||||||
|
globals[name] = pop()
|
||||||
|
}
|
||||||
|
Opcode.GetUpvalue -> {
|
||||||
|
val index = readByte(frame, chunk)
|
||||||
|
push(readUpvalue(frame.closure.upvalues[index]))
|
||||||
|
}
|
||||||
|
Opcode.SetUpvalue -> {
|
||||||
|
val index = readByte(frame, chunk)
|
||||||
|
writeUpvalue(frame.closure.upvalues[index], peek(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode.Jump -> {
|
||||||
|
val offset = readByte(frame, chunk)
|
||||||
|
frame.ip += offset
|
||||||
|
}
|
||||||
|
Opcode.JumpIfFalse -> {
|
||||||
|
val offset = readByte(frame, chunk)
|
||||||
|
if (!isTruthy(peek(0))) frame.ip += offset
|
||||||
|
}
|
||||||
|
Opcode.Loop -> {
|
||||||
|
val offset = readByte(frame, chunk)
|
||||||
|
frame.ip -= offset
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode.Call -> {
|
||||||
|
val argCount = readByte(frame, chunk)
|
||||||
|
callValue(peek(argCount), argCount, frame, chunk)
|
||||||
|
}
|
||||||
|
Opcode.Invoke -> invoke(frame, chunk)
|
||||||
|
Opcode.Closure -> makeClosure(frame, chunk)
|
||||||
|
Opcode.CloseUpvalue -> {
|
||||||
|
closeUpvalues(stack.size - 1)
|
||||||
|
pop()
|
||||||
|
}
|
||||||
|
Opcode.Return -> {
|
||||||
|
val result = pop()
|
||||||
|
closeUpvalues(frame.slotBase)
|
||||||
|
frames.removeAt(frames.size - 1)
|
||||||
|
truncate(frame.slotBase)
|
||||||
|
push(result)
|
||||||
|
if (frames.size == exitFrameFloor) return result
|
||||||
|
}
|
||||||
|
|
||||||
|
Opcode.BuildList -> buildList(frame, chunk)
|
||||||
|
Opcode.BuildMap -> buildMap(frame, chunk)
|
||||||
|
Opcode.IndexGet -> indexGet(frame, chunk)
|
||||||
|
Opcode.IndexSet -> indexSet(frame, chunk)
|
||||||
|
|
||||||
|
Opcode.GetIter -> push(IteratorValue(makeIterator(pop(), frame, chunk)))
|
||||||
|
Opcode.ForIter -> {
|
||||||
|
val iterSlot = readByte(frame, chunk)
|
||||||
|
val offset = readByte(frame, chunk)
|
||||||
|
val iterator = stack[frame.slotBase + iterSlot] as IteratorValue
|
||||||
|
if (iterator.iterator.hasNext()) push(iterator.iterator.next())
|
||||||
|
else frame.ip += offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun callValue(callee: Value, argCount: Int, frame: CallFrame, chunk: Chunk) {
|
||||||
|
when (callee) {
|
||||||
|
is ClosureValue -> {
|
||||||
|
if (callee.function.arity != argCount) {
|
||||||
|
throw RuntimeError(
|
||||||
|
"function '${callee.function.name}' expects ${callee.function.arity} arguments but got $argCount",
|
||||||
|
lineOf(frame, chunk),
|
||||||
|
columnOf(frame, chunk)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
frames.add(CallFrame(callee, 0, stack.size - argCount - 1))
|
||||||
|
}
|
||||||
|
is NativeFunctionValue -> {
|
||||||
|
val args = ArrayList<Value>(argCount)
|
||||||
|
for (i in 0 until argCount) args.add(stack[stack.size - argCount + i])
|
||||||
|
if (!callee.arity.contains(argCount)) {
|
||||||
|
throw RuntimeError(arityMessage(callee, argCount), lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
truncate(stack.size - argCount - 1)
|
||||||
|
val produced = try {
|
||||||
|
callee.call(args)
|
||||||
|
} catch (error: RuntimeError) {
|
||||||
|
if (error.line == 0) {
|
||||||
|
throw RuntimeError(error.rawMessage, lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
push(produced)
|
||||||
|
}
|
||||||
|
else -> throw RuntimeError("value of type ${typeName(callee)} is not callable", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun invoke(frame: CallFrame, chunk: Chunk) {
|
||||||
|
val name = constantName(chunk, readByte(frame, chunk))
|
||||||
|
val argCount = readByte(frame, chunk)
|
||||||
|
val receiver = peek(argCount)
|
||||||
|
val method = methods[name]
|
||||||
|
?: throw RuntimeError("unknown method '$name' on value of type ${typeName(receiver)}", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
if (!method.arity.contains(argCount)) {
|
||||||
|
throw RuntimeError(
|
||||||
|
"method '$name' expects ${arityText(method.arity)} arguments but got $argCount",
|
||||||
|
lineOf(frame, chunk),
|
||||||
|
columnOf(frame, chunk)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val args = ArrayList<Value>(argCount)
|
||||||
|
for (i in 0 until argCount) args.add(stack[stack.size - argCount + i])
|
||||||
|
val result = try {
|
||||||
|
method.call(this, receiver, args)
|
||||||
|
} catch (error: RuntimeError) {
|
||||||
|
if (error.line == 0) {
|
||||||
|
throw RuntimeError(error.rawMessage, lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
truncate(stack.size - argCount - 1)
|
||||||
|
push(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeClosure(frame: CallFrame, chunk: Chunk) {
|
||||||
|
val function = chunk.constants[readByte(frame, chunk)] as FunctionValue
|
||||||
|
val upvalues = Array(function.upvalueCount) {
|
||||||
|
val isLocal = readByte(frame, chunk) == 1
|
||||||
|
val index = readByte(frame, chunk)
|
||||||
|
if (isLocal) captureUpvalue(frame.slotBase + index) else frame.closure.upvalues[index]
|
||||||
|
}
|
||||||
|
push(ClosureValue(function, upvalues))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildList(frame: CallFrame, chunk: Chunk) {
|
||||||
|
val count = readByte(frame, chunk)
|
||||||
|
val items = ArrayList<Value>(count)
|
||||||
|
for (i in 0 until count) items.add(NilValue)
|
||||||
|
for (i in count - 1 downTo 0) items[i] = pop()
|
||||||
|
push(ListValue(items))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildMap(frame: CallFrame, chunk: Chunk) {
|
||||||
|
val count = readByte(frame, chunk)
|
||||||
|
val pairs = ArrayList<Pair<Value, Value>>(count)
|
||||||
|
for (i in 0 until count) {
|
||||||
|
val value = pop()
|
||||||
|
val key = pop()
|
||||||
|
pairs.add(key to value)
|
||||||
|
}
|
||||||
|
pairs.reverse()
|
||||||
|
val entries = LinkedHashMap<Value, Value>()
|
||||||
|
for ((key, value) in pairs) entries[key] = value
|
||||||
|
push(MapValue(entries))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun indexGet(frame: CallFrame, chunk: Chunk) {
|
||||||
|
val index = pop()
|
||||||
|
val target = pop()
|
||||||
|
push(readIndex(target, index, frame, chunk))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun indexSet(frame: CallFrame, chunk: Chunk) {
|
||||||
|
val value = pop()
|
||||||
|
val index = pop()
|
||||||
|
val target = pop()
|
||||||
|
writeIndex(target, index, value, frame, chunk)
|
||||||
|
push(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readIndex(target: Value, index: Value, frame: CallFrame, chunk: Chunk): Value = when (target) {
|
||||||
|
is ListValue -> {
|
||||||
|
val i = intIndex(index, frame, chunk)
|
||||||
|
if (i < 0 || i >= target.items.size) {
|
||||||
|
throw RuntimeError("list index $i out of range 0..${target.items.size - 1}", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
target.items[i]
|
||||||
|
}
|
||||||
|
is MapValue -> target.entries[index] ?: NilValue
|
||||||
|
is StringValue -> {
|
||||||
|
val i = intIndex(index, frame, chunk)
|
||||||
|
if (i < 0 || i >= target.value.length) {
|
||||||
|
throw RuntimeError("string index $i out of range", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
StringValue(target.value[i].toString())
|
||||||
|
}
|
||||||
|
is RangeValue -> {
|
||||||
|
val i = intIndex(index, frame, chunk).toLong()
|
||||||
|
if (i < 0 || i >= target.length()) {
|
||||||
|
throw RuntimeError("range index $i out of range", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
IntValue(target.elementAt(i))
|
||||||
|
}
|
||||||
|
else -> throw RuntimeError("value of type ${typeName(target)} is not indexable", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeIndex(target: Value, index: Value, value: Value, frame: CallFrame, chunk: Chunk) {
|
||||||
|
when (target) {
|
||||||
|
is ListValue -> {
|
||||||
|
val i = intIndex(index, frame, chunk)
|
||||||
|
if (i < 0 || i >= target.items.size) {
|
||||||
|
throw RuntimeError("list index $i out of range 0..${target.items.size - 1}", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
target.items[i] = value
|
||||||
|
}
|
||||||
|
is MapValue -> target.entries[index] = value
|
||||||
|
else -> throw RuntimeError("value of type ${typeName(target)} does not support index assignment", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun intIndex(index: Value, frame: CallFrame, chunk: Chunk): Int {
|
||||||
|
if (index !is IntValue) {
|
||||||
|
throw RuntimeError("index must be an int, got ${typeName(index)}", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
return index.value.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeIterator(iterable: Value, frame: CallFrame, chunk: Chunk): Iterator<Value> = when (iterable) {
|
||||||
|
is ListValue -> ArrayList(iterable.items).iterator()
|
||||||
|
is MapValue -> ArrayList(iterable.entries.keys).iterator()
|
||||||
|
is StringValue -> iterable.value.map { StringValue(it.toString()) as Value }.iterator()
|
||||||
|
is RangeValue -> rangeIterator(iterable)
|
||||||
|
else -> throw RuntimeError("value of type ${typeName(iterable)} is not iterable", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rangeIterator(range: RangeValue): Iterator<Value> = iterator {
|
||||||
|
var value = range.start
|
||||||
|
if (range.step > 0) {
|
||||||
|
while (value < range.end) {
|
||||||
|
yield(IntValue(value))
|
||||||
|
value += range.step
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (value > range.end) {
|
||||||
|
yield(IntValue(value))
|
||||||
|
value += range.step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class Arith { Sub, Mul, Div, Mod }
|
||||||
|
private enum class Compare { Less, LessEqual, Greater, GreaterEqual }
|
||||||
|
|
||||||
|
private fun binaryAdd(frame: CallFrame, chunk: Chunk) {
|
||||||
|
val b = pop()
|
||||||
|
val a = pop()
|
||||||
|
val result = when {
|
||||||
|
a is IntValue && b is IntValue -> IntValue(a.value + b.value)
|
||||||
|
a is StringValue && b is StringValue -> StringValue(a.value + b.value)
|
||||||
|
isNumber(a) && isNumber(b) -> DoubleValue(asDouble(a) + asDouble(b))
|
||||||
|
else -> throw RuntimeError("cannot add ${typeName(a)} and ${typeName(b)}", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
push(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun arithmetic(frame: CallFrame, chunk: Chunk, op: Arith) {
|
||||||
|
val b = pop()
|
||||||
|
val a = pop()
|
||||||
|
if (a is IntValue && b is IntValue) {
|
||||||
|
val x = a.value
|
||||||
|
val y = b.value
|
||||||
|
val result = when (op) {
|
||||||
|
Arith.Sub -> x - y
|
||||||
|
Arith.Mul -> x * y
|
||||||
|
Arith.Div -> {
|
||||||
|
if (y == 0L) throw RuntimeError("division by zero", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
x / y
|
||||||
|
}
|
||||||
|
Arith.Mod -> {
|
||||||
|
if (y == 0L) throw RuntimeError("modulo by zero", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
x % y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
push(IntValue(result))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (isNumber(a) && isNumber(b)) {
|
||||||
|
val x = asDouble(a)
|
||||||
|
val y = asDouble(b)
|
||||||
|
val result = when (op) {
|
||||||
|
Arith.Sub -> x - y
|
||||||
|
Arith.Mul -> x * y
|
||||||
|
Arith.Div -> x / y
|
||||||
|
Arith.Mod -> x % y
|
||||||
|
}
|
||||||
|
push(DoubleValue(result))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw RuntimeError("cannot apply arithmetic to ${typeName(a)} and ${typeName(b)}", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun negate(frame: CallFrame, chunk: Chunk) {
|
||||||
|
when (val operand = pop()) {
|
||||||
|
is IntValue -> push(IntValue(-operand.value))
|
||||||
|
is DoubleValue -> push(DoubleValue(-operand.value))
|
||||||
|
else -> throw RuntimeError("cannot negate ${typeName(operand)}", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compare(frame: CallFrame, chunk: Chunk, op: Compare) {
|
||||||
|
val b = pop()
|
||||||
|
val a = pop()
|
||||||
|
val ordering = when {
|
||||||
|
isNumber(a) && isNumber(b) -> asDouble(a).compareTo(asDouble(b))
|
||||||
|
a is StringValue && b is StringValue -> a.value.compareTo(b.value)
|
||||||
|
else -> throw RuntimeError("cannot compare ${typeName(a)} and ${typeName(b)}", lineOf(frame, chunk), columnOf(frame, chunk))
|
||||||
|
}
|
||||||
|
val result = when (op) {
|
||||||
|
Compare.Less -> ordering < 0
|
||||||
|
Compare.LessEqual -> ordering <= 0
|
||||||
|
Compare.Greater -> ordering > 0
|
||||||
|
Compare.GreaterEqual -> ordering >= 0
|
||||||
|
}
|
||||||
|
push(BoolValue(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun captureUpvalue(location: Int): Upvalue {
|
||||||
|
for (existing in openUpvalues) {
|
||||||
|
if (existing.location == location) return existing
|
||||||
|
}
|
||||||
|
val created = Upvalue(location)
|
||||||
|
openUpvalues.add(created)
|
||||||
|
return created
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun closeUpvalues(fromLocation: Int) {
|
||||||
|
val iterator = openUpvalues.iterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
val upvalue = iterator.next()
|
||||||
|
if (upvalue.location >= fromLocation) {
|
||||||
|
upvalue.closed = stack[upvalue.location]
|
||||||
|
upvalue.location = -1
|
||||||
|
iterator.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readUpvalue(upvalue: Upvalue): Value =
|
||||||
|
if (upvalue.isClosed) upvalue.closed else stack[upvalue.location]
|
||||||
|
|
||||||
|
private fun writeUpvalue(upvalue: Upvalue, value: Value) {
|
||||||
|
if (upvalue.isClosed) upvalue.closed = value else stack[upvalue.location] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkArity(native: NativeFunctionValue, argCount: Int) {
|
||||||
|
if (!native.arity.contains(argCount)) {
|
||||||
|
throw RuntimeError(arityMessage(native, argCount), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun arityMessage(native: NativeFunctionValue, argCount: Int): String {
|
||||||
|
val expected = if (native.arity.first == native.arity.last) "${native.arity.first}"
|
||||||
|
else "${native.arity.first}..${native.arity.last}"
|
||||||
|
return "builtin '${native.name}' expects $expected arguments but got $argCount"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun constantName(chunk: Chunk, index: Int): String = (chunk.constants[index] as StringValue).value
|
||||||
|
|
||||||
|
private fun readByte(frame: CallFrame, chunk: Chunk): Int = chunk.code[frame.ip++]
|
||||||
|
|
||||||
|
private fun lineOf(frame: CallFrame, chunk: Chunk): Int {
|
||||||
|
val index = (frame.ip - 1).coerceIn(0, chunk.lines.size - 1)
|
||||||
|
return if (chunk.lines.isEmpty()) 0 else chunk.lines[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun columnOf(frame: CallFrame, chunk: Chunk): Int {
|
||||||
|
val index = (frame.ip - 1).coerceIn(0, chunk.columns.size - 1)
|
||||||
|
return if (chunk.columns.isEmpty()) 0 else chunk.columns[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun arityText(arity: IntRange): String =
|
||||||
|
if (arity.first == arity.last) "${arity.first}" else "${arity.first}..${arity.last}"
|
||||||
|
|
||||||
|
private fun push(value: Value) {
|
||||||
|
stack.add(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pop(): Value = stack.removeAt(stack.size - 1)
|
||||||
|
|
||||||
|
private fun peek(distance: Int): Value = stack[stack.size - 1 - distance]
|
||||||
|
|
||||||
|
private fun truncate(size: Int) {
|
||||||
|
while (stack.size > size) stack.removeAt(stack.size - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isNumber(value: Value): Boolean = value is IntValue || value is DoubleValue
|
||||||
|
|
||||||
|
private fun asDouble(value: Value): Double = when (value) {
|
||||||
|
is IntValue -> value.value.toDouble()
|
||||||
|
is DoubleValue -> value.value
|
||||||
|
else -> 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user