605 lines
17 KiB
C
605 lines
17 KiB
C
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include "common.h"
|
|
#include "compiler.h"
|
|
#include "debug.h"
|
|
#include "object.h"
|
|
#include "memory.h"
|
|
#include "vm.h"
|
|
|
|
VM vm;
|
|
|
|
static Value clockNative(int argCount, Value* args)
|
|
{
|
|
(void)argCount;
|
|
(void)args;
|
|
|
|
return NUMBER_VAL((double)clock() / CLOCKS_PER_SEC);
|
|
}
|
|
|
|
static void resetStack()
|
|
{
|
|
vm.stackTop = vm.stack;
|
|
vm.frameCount = 0;
|
|
vm.openUpvalues = nullptr;
|
|
}
|
|
|
|
static void runtimeError(const char* format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
vfprintf(stderr, format, args);
|
|
va_end(args);
|
|
fputs("\n", stderr);
|
|
|
|
for (auto i = vm.frameCount - 1; i >= 0; i--) {
|
|
CallFrame* frame = &vm.frames[i];
|
|
ObjFunction* function = frame->closure->function;
|
|
auto instruction = frame->ip - function->chunk.code - 1;
|
|
fprintf(stderr, "[line %d] in ", function->chunk.lines[instruction]);
|
|
if (function->name == nullptr) {
|
|
fprintf(stderr, "script\n");
|
|
} else {
|
|
fprintf(stderr, "%s()\n", function->name->chars);
|
|
}
|
|
}
|
|
|
|
resetStack();
|
|
}
|
|
|
|
static void defineNative(const char* name, NativeFn function)
|
|
{
|
|
push(OBJ_VAL(copyString(name, (int)strlen(name))));
|
|
push(OBJ_VAL(newNative(function)));
|
|
tableSet(&vm.globals, AS_STRING(vm.stack[0]), vm.stack[1]);
|
|
pop();
|
|
pop();
|
|
}
|
|
|
|
void initVM()
|
|
{
|
|
resetStack();
|
|
vm.objects = nullptr;
|
|
vm.bytesAllocated = 0;
|
|
vm.nextGC = 1024 * 1024;
|
|
|
|
vm.grayCount = 0;
|
|
vm.grayCapacity = 0;
|
|
vm.grayStack = nullptr;
|
|
|
|
initTable(&vm.globals);
|
|
initTable(&vm.strings);
|
|
|
|
vm.initString = nullptr;
|
|
vm.initString = copyString("init", 4);
|
|
|
|
defineNative("clock", clockNative);
|
|
}
|
|
|
|
void freeVM()
|
|
{
|
|
freeTable(&vm.globals);
|
|
freeTable(&vm.strings);
|
|
vm.initString = nullptr;
|
|
freeObjects();
|
|
}
|
|
|
|
void push(Value value)
|
|
{
|
|
*vm.stackTop = value;
|
|
vm.stackTop++;
|
|
}
|
|
|
|
Value pop()
|
|
{
|
|
vm.stackTop--;
|
|
return *vm.stackTop;
|
|
}
|
|
|
|
static Value peek(int distance)
|
|
{
|
|
return vm.stackTop[-1 - distance];
|
|
}
|
|
|
|
static bool call(ObjClosure* closure, int argCount)
|
|
{
|
|
if (argCount != closure->function->arity) {
|
|
runtimeError("Expected %d arguments but got %d.", closure->function->arity, argCount);
|
|
return false;
|
|
}
|
|
|
|
if (vm.frameCount == FRAMES_MAX) {
|
|
runtimeError("Stack overflow.");
|
|
return false;
|
|
}
|
|
|
|
CallFrame* frame = &vm.frames[vm.frameCount++];
|
|
frame->closure = closure;
|
|
frame->ip = closure->function->chunk.code;
|
|
frame->slots = vm.stackTop - argCount - 1;
|
|
return true;
|
|
}
|
|
|
|
static bool callValue(Value callee, int argCount)
|
|
{
|
|
if (IS_OBJ(callee)) {
|
|
switch (OBJ_TYPE(callee)) {
|
|
case OBJ_BOUND_METHOD:
|
|
{
|
|
ObjBoundMethod* bound = AS_BOUND_METHOD(callee);
|
|
vm.stackTop[-argCount - 1] = bound->receiver;
|
|
return call(bound->method, argCount);
|
|
}
|
|
case OBJ_CLASS:
|
|
{
|
|
ObjClass* klass = AS_CLASS(callee);
|
|
vm.stackTop[-argCount - 1] = OBJ_VAL(newInstance(klass));
|
|
Value initializer;
|
|
if (tableGet(&klass->methods, vm.initString, &initializer)) {
|
|
return call(AS_CLOSURE(initializer), argCount);
|
|
} else if (argCount != 0) {
|
|
runtimeError("Expected 0 arguments but got %d.", argCount);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
case OBJ_CLOSURE:
|
|
return call(AS_CLOSURE(callee), argCount);
|
|
case OBJ_NATIVE:
|
|
{
|
|
auto native = AS_NATIVE(callee);
|
|
auto result = native(argCount, vm.stackTop - argCount);
|
|
vm.stackTop -= argCount + 1;
|
|
push(result);
|
|
return true;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
runtimeError("Can only call functions and classes.");
|
|
return false;
|
|
}
|
|
|
|
static bool invokeFromClass(ObjClass* klass, ObjString* name, int argCount)
|
|
{
|
|
Value method;
|
|
if (!tableGet(&klass->methods, name, &method)) {
|
|
runtimeError("Undefined property '%s'.", name->chars);
|
|
return false;
|
|
}
|
|
return call(AS_CLOSURE(method), argCount);
|
|
}
|
|
|
|
static bool invoke(ObjString* name, int argCount)
|
|
{
|
|
auto receiver = peek(argCount);
|
|
|
|
if (!IS_INSTANCE(receiver)) {
|
|
runtimeError("Only instances have methods.");
|
|
return false;
|
|
}
|
|
|
|
ObjInstance* instance = AS_INSTANCE(receiver);
|
|
|
|
Value value;
|
|
if (tableGet(&instance->fields, name, &value)) {
|
|
vm.stackTop[-argCount - 1] = value;
|
|
return callValue(value, argCount);
|
|
}
|
|
|
|
return invokeFromClass(instance->klass, name, argCount);
|
|
}
|
|
|
|
static bool bindMethod(ObjClass* klass, ObjString* name)
|
|
{
|
|
Value method;
|
|
if (!tableGet(&klass->methods, name, &method)) {
|
|
runtimeError("Undefined property '%s'.", name->chars);
|
|
return false;
|
|
}
|
|
|
|
ObjBoundMethod* bound = newBoundMethod(peek(0), AS_CLOSURE(method));
|
|
pop();
|
|
push(OBJ_VAL(bound));
|
|
return true;
|
|
}
|
|
|
|
static ObjUpvalue* captureUpvalue(Value* local)
|
|
{
|
|
ObjUpvalue* prevUpvalue = nullptr;
|
|
ObjUpvalue* upvalue = vm.openUpvalues;
|
|
while (upvalue != nullptr && upvalue->location > local) {
|
|
prevUpvalue = upvalue;
|
|
upvalue = upvalue->next;
|
|
}
|
|
|
|
if (upvalue != nullptr && upvalue->location == local) {
|
|
return upvalue;
|
|
}
|
|
|
|
ObjUpvalue* createdUpvalue = newUpvalue(local);
|
|
createdUpvalue->next = upvalue;
|
|
|
|
if (prevUpvalue == nullptr) {
|
|
vm.openUpvalues = createdUpvalue;
|
|
} else {
|
|
prevUpvalue->next = createdUpvalue;
|
|
}
|
|
|
|
return createdUpvalue;
|
|
}
|
|
|
|
static void closeUpvalues(Value* last)
|
|
{
|
|
while (vm.openUpvalues != nullptr && vm.openUpvalues->location >= last) {
|
|
ObjUpvalue* upvalue = vm.openUpvalues;
|
|
upvalue->closed = *upvalue->location;
|
|
upvalue->location = &upvalue->closed;
|
|
vm.openUpvalues = upvalue->next;
|
|
}
|
|
}
|
|
|
|
static void defineMethod(ObjString* name)
|
|
{
|
|
auto method = peek(0);
|
|
ObjClass* klass = AS_CLASS(peek(1));
|
|
tableSet(&klass->methods, name, method);
|
|
pop();
|
|
}
|
|
|
|
static bool isFalsey(Value value)
|
|
{
|
|
return IS_NIL(value) || (IS_BOOL(value) && !AS_BOOL(value));
|
|
}
|
|
|
|
static void concatenate()
|
|
{
|
|
ObjString* b = AS_STRING(peek(0));
|
|
ObjString* a = AS_STRING(peek(1));
|
|
|
|
auto length = a->length + b->length;
|
|
char* chars = ALLOCATE(char, length + 1);
|
|
memcpy(chars, a->chars, a->length);
|
|
memcpy(chars + a->length, b->chars, b->length);
|
|
chars[length] = '\0';
|
|
|
|
ObjString* result = takeString(chars, length);
|
|
pop();
|
|
pop();
|
|
push(OBJ_VAL(result));
|
|
}
|
|
|
|
static InterpretResult run()
|
|
{
|
|
CallFrame* frame = &vm.frames[vm.frameCount - 1];
|
|
|
|
#define READ_BYTE() (*frame->ip++)
|
|
#define READ_SHORT() (frame->ip += 2, (uint16_t)((frame->ip[-2] << 8) | frame->ip[-1]))
|
|
#define READ_CONSTANT() (frame->closure->function->chunk.constants.values[READ_BYTE()])
|
|
#define READ_STRING() AS_STRING(READ_CONSTANT())
|
|
|
|
#define BINARY_OP(valueType, op) \
|
|
do { \
|
|
if (!IS_NUMBER(peek(0)) || !IS_NUMBER(peek(1))) { \
|
|
runtimeError("Operands must be numbers."); \
|
|
return INTERPRET_RUNTIME_ERROR; \
|
|
} \
|
|
auto b = AS_NUMBER(pop()); \
|
|
auto a = AS_NUMBER(pop()); \
|
|
push(valueType(a op b)); \
|
|
} while (false)
|
|
|
|
for (;;) {
|
|
#ifdef DEBUG_TRACE_EXECUTION
|
|
printf(" ");
|
|
for (Value* slot = vm.stack; slot < vm.stackTop; slot++) {
|
|
printf("[ ");
|
|
printValue(*slot);
|
|
printf(" ]");
|
|
}
|
|
printf("\n");
|
|
disassembleInstruction(&frame->closure->function->chunk, (int)(frame->ip - frame->closure->function->chunk.code));
|
|
#endif
|
|
|
|
uint8_t instruction;
|
|
switch (instruction = READ_BYTE()) {
|
|
case OP_CONSTANT:
|
|
{
|
|
auto constant = READ_CONSTANT();
|
|
push(constant);
|
|
}
|
|
break;
|
|
case OP_NIL:
|
|
push(NIL_VAL);
|
|
break;
|
|
case OP_TRUE:
|
|
push(BOOL_VAL(true));
|
|
break;
|
|
case OP_FALSE:
|
|
push(BOOL_VAL(false));
|
|
break;
|
|
case OP_POP:
|
|
pop();
|
|
break;
|
|
case OP_GET_LOCAL:
|
|
{
|
|
auto slot = READ_BYTE();
|
|
push(frame->slots[slot]);
|
|
}
|
|
break;
|
|
case OP_SET_LOCAL:
|
|
{
|
|
auto slot = READ_BYTE();
|
|
frame->slots[slot] = peek(0);
|
|
}
|
|
break;
|
|
case OP_GET_GLOBAL:
|
|
{
|
|
ObjString* name = READ_STRING();
|
|
Value value;
|
|
if (!tableGet(&vm.globals, name, &value)) {
|
|
runtimeError("Undefined variable '%s'.", name->chars);
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
push(value);
|
|
}
|
|
break;
|
|
case OP_DEFINE_GLOBAL:
|
|
{
|
|
ObjString* name = READ_STRING();
|
|
tableSet(&vm.globals, name, peek(0));
|
|
pop();
|
|
}
|
|
break;
|
|
case OP_SET_GLOBAL:
|
|
{
|
|
ObjString* name = READ_STRING();
|
|
if (tableSet(&vm.globals, name, peek(0))) {
|
|
tableDelete(&vm.globals, name);
|
|
runtimeError("Undefined variable '%s'.", name->chars);
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
}
|
|
break;
|
|
case OP_GET_UPVALUE:
|
|
{
|
|
auto slot = READ_BYTE();
|
|
push(*frame->closure->upvalues[slot]->location);
|
|
}
|
|
break;
|
|
case OP_SET_UPVALUE:
|
|
{
|
|
auto slot = READ_BYTE();
|
|
*frame->closure->upvalues[slot]->location = peek(0);
|
|
}
|
|
break;
|
|
case OP_GET_PROPERTY:
|
|
{
|
|
if (!IS_INSTANCE(peek(0))) {
|
|
runtimeError("Only instances have properties.");
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
|
|
ObjInstance* instance = AS_INSTANCE(peek(0));
|
|
ObjString* name = READ_STRING();
|
|
|
|
Value value;
|
|
if (tableGet(&instance->fields, name, &value)) {
|
|
pop();
|
|
push(value);
|
|
break;
|
|
}
|
|
|
|
if (!bindMethod(instance->klass, name)) {
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
}
|
|
break;
|
|
case OP_SET_PROPERTY:
|
|
{
|
|
if (!IS_INSTANCE(peek(1))) {
|
|
runtimeError("Only instances have fields.");
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
|
|
ObjInstance* instance = AS_INSTANCE(peek(1));
|
|
tableSet(&instance->fields, READ_STRING(), peek(0));
|
|
auto value = pop();
|
|
pop();
|
|
push(value);
|
|
}
|
|
break;
|
|
case OP_GET_SUPER:
|
|
{
|
|
ObjString* name = READ_STRING();
|
|
ObjClass* superclass = AS_CLASS(pop());
|
|
|
|
if (!bindMethod(superclass, name)) {
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
}
|
|
break;
|
|
case OP_EQUAL:
|
|
{
|
|
auto b = pop();
|
|
auto a = pop();
|
|
push(BOOL_VAL(valuesEqual(a, b)));
|
|
}
|
|
break;
|
|
case OP_GREATER:
|
|
BINARY_OP(BOOL_VAL, >);
|
|
break;
|
|
case OP_LESS:
|
|
BINARY_OP(BOOL_VAL, <);
|
|
break;
|
|
case OP_ADD:
|
|
{
|
|
if (IS_STRING(peek(0)) && IS_STRING(peek(1))) {
|
|
concatenate();
|
|
} else if (IS_NUMBER(peek(0)) && IS_NUMBER(peek(1))) {
|
|
auto b = AS_NUMBER(pop());
|
|
auto a = AS_NUMBER(pop());
|
|
push(NUMBER_VAL(a + b));
|
|
} else {
|
|
runtimeError("Operands must be two numbers or two strings.");
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
}
|
|
break;
|
|
case OP_SUBTRACT:
|
|
BINARY_OP(NUMBER_VAL, -);
|
|
break;
|
|
case OP_MULTIPLY:
|
|
BINARY_OP(NUMBER_VAL, *);
|
|
break;
|
|
case OP_DIVIDE:
|
|
BINARY_OP(NUMBER_VAL, /);
|
|
break;
|
|
case OP_NOT:
|
|
push(BOOL_VAL(isFalsey(pop())));
|
|
break;
|
|
case OP_NEGATE:
|
|
if (!IS_NUMBER(peek(0))) {
|
|
runtimeError("Operand must be a number.");
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
push(NUMBER_VAL(-AS_NUMBER(pop())));
|
|
break;
|
|
case OP_PRINT:
|
|
printValue(pop());
|
|
printf("\n");
|
|
break;
|
|
case OP_JUMP:
|
|
{
|
|
auto offset = READ_SHORT();
|
|
frame->ip += offset;
|
|
}
|
|
break;
|
|
case OP_JUMP_IF_FALSE:
|
|
{
|
|
auto offset = READ_SHORT();
|
|
if (isFalsey(peek(0))) {
|
|
frame->ip += offset;
|
|
}
|
|
}
|
|
break;
|
|
case OP_LOOP:
|
|
{
|
|
auto offset = READ_SHORT();
|
|
frame->ip -= offset;
|
|
}
|
|
break;
|
|
case OP_CALL:
|
|
{
|
|
auto argCount = READ_BYTE();
|
|
if (!callValue(peek(argCount), argCount)) {
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
frame = &vm.frames[vm.frameCount - 1];
|
|
}
|
|
break;
|
|
case OP_INVOKE:
|
|
{
|
|
ObjString* method = READ_STRING();
|
|
auto argCount = READ_BYTE();
|
|
if (!invoke(method, argCount)) {
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
frame = &vm.frames[vm.frameCount - 1];
|
|
}
|
|
break;
|
|
case OP_SUPER_INVOKE:
|
|
{
|
|
ObjString* method = READ_STRING();
|
|
auto argCount = READ_BYTE();
|
|
ObjClass* superclass = AS_CLASS(pop());
|
|
if (!invokeFromClass(superclass, method, argCount)) {
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
frame = &vm.frames[vm.frameCount - 1];
|
|
}
|
|
break;
|
|
case OP_CLOSURE:
|
|
{
|
|
ObjFunction* function = AS_FUNCTION(READ_CONSTANT());
|
|
ObjClosure* closure = newClosure(function);
|
|
push(OBJ_VAL(closure));
|
|
for (auto i = 0; i < closure->upvalueCount; i++) {
|
|
auto isLocal = READ_BYTE();
|
|
auto index = READ_BYTE();
|
|
if (isLocal) {
|
|
closure->upvalues[i] = captureUpvalue(frame->slots + index);
|
|
} else {
|
|
closure->upvalues[i] = frame->closure->upvalues[index];
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case OP_CLOSE_UPVALUE:
|
|
closeUpvalues(vm.stackTop - 1);
|
|
pop();
|
|
break;
|
|
case OP_RETURN:
|
|
{
|
|
auto result = pop();
|
|
closeUpvalues(frame->slots);
|
|
vm.frameCount--;
|
|
if (vm.frameCount == 0) {
|
|
pop();
|
|
return INTERPRET_OK;
|
|
}
|
|
|
|
vm.stackTop = frame->slots;
|
|
push(result);
|
|
frame = &vm.frames[vm.frameCount - 1];
|
|
}
|
|
break;
|
|
case OP_CLASS:
|
|
push(OBJ_VAL(newClass(READ_STRING())));
|
|
break;
|
|
case OP_INHERIT:
|
|
{
|
|
auto superClass = peek(1);
|
|
if (!IS_CLASS(superClass)) {
|
|
runtimeError("Superclass must be a class.");
|
|
return INTERPRET_RUNTIME_ERROR;
|
|
}
|
|
ObjClass* subClass = AS_CLASS(peek(0));
|
|
tableAddAll(&AS_CLASS(superClass)->methods, &subClass->methods);
|
|
pop();
|
|
}
|
|
break;
|
|
case OP_METHOD:
|
|
defineMethod(READ_STRING());
|
|
break;
|
|
}
|
|
}
|
|
|
|
#undef READ_BYTE
|
|
#undef READ_SHORT
|
|
#undef READ_CONSTANT
|
|
#undef READ_STRING
|
|
|
|
#undef BINARY_OP
|
|
}
|
|
|
|
InterpretResult interpret(const char* source)
|
|
{
|
|
ObjFunction* function = compile(source);
|
|
if (function == nullptr) {
|
|
return INTERPRET_COMPILE_ERROR;
|
|
}
|
|
|
|
push(OBJ_VAL(function));
|
|
ObjClosure* closure = newClosure(function);
|
|
pop();
|
|
push(OBJ_VAL(closure));
|
|
call(closure, 0);
|
|
|
|
return run();
|
|
}
|
|
|