Initial commit

This commit is contained in:
ktkk 2026-01-13 19:52:23 +00:00
commit b2edca2cb4
25 changed files with 3590 additions and 0 deletions

605
vm.c Normal file
View file

@ -0,0 +1,605 @@
#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();
}