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

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
build/
# Misc
.direnv
.cache

49
CMakeLists.txt Normal file
View file

@ -0,0 +1,49 @@
cmake_minimum_required(VERSION 4.1)
project(
clox
LANGUAGES
C
)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_executable(
${PROJECT_NAME}
main.c
vm.c
chunk.c
memory.c
debug.c
value.c
compiler.c
scanner.c
object.c
table.c
)
set_target_properties(
${PROJECT_NAME}
PROPERTIES
C_STANDARD 23
C_STANDARD_REQUIRED ON
C_EXTENSIONS OFF
)
target_compile_options(
${PROJECT_NAME}
PRIVATE
-Wall -Wextra -Wpedantic
)
target_compile_definitions(
${PROJECT_NAME}
PRIVATE
$<$<AND:$<CONFIG:Debug>,$<BOOL:${PRINT_CODE}>>:DEBUG_PRINT_CODE>
$<$<AND:$<CONFIG:Debug>,$<BOOL:${TRACE_EXECUTION}>>:DEBUG_TRACE_EXECUTION>
$<$<AND:$<CONFIG:Debug>,$<BOOL:${STRESS_GC}>>:DEBUG_STRESS_GC>
$<$<AND:$<CONFIG:Debug>,$<BOOL:${LOG_GC}>>:DEBUG_LOG_GC>
)
target_compile_definitions(
${PROJECT_NAME}
PRIVATE
$<$<BOOL:${NAN_BOXING}>:NAN_BOXING>
)

43
chunk.c Normal file
View file

@ -0,0 +1,43 @@
#include "chunk.h"
#include "memory.h"
#include "vm.h"
void initChunk(Chunk* chunk)
{
chunk->count = 0;
chunk->capacity = 0;
chunk->code = nullptr;
chunk->lines = nullptr;
initValueArray(&chunk->constants);
}
void freeChunk(Chunk* chunk)
{
FREE_ARRAY(uint8_t, chunk->code, chunk->capacity);
FREE_ARRAY(int, chunk->lines, chunk->capacity);
freeValueArray(&chunk->constants);
initChunk(chunk);
}
void writeChunk(Chunk* chunk, uint8_t byte, int line)
{
if (chunk->capacity < chunk->count + 1) {
int oldCapacity = chunk->capacity;
chunk->capacity = GROW_CAPACITY(oldCapacity);
chunk->code = GROW_ARRAY(uint8_t, chunk->code, oldCapacity, chunk->capacity);
chunk->lines = GROW_ARRAY(int, chunk->lines, oldCapacity, chunk->capacity);
}
chunk->code[chunk->count] = byte;
chunk->lines[chunk->count] = line;
chunk->count++;
}
int addConstant(Chunk* chunk, Value value)
{
push(value);
writeValueArray(&chunk->constants, value);
pop();
return chunk->constants.count - 1;
}

61
chunk.h Normal file
View file

@ -0,0 +1,61 @@
#ifndef clox_chunk_h
#define clox_chunk_h
#include "common.h"
#include "value.h"
typedef enum {
OP_CONSTANT,
OP_NIL,
OP_TRUE,
OP_FALSE,
OP_POP,
OP_GET_LOCAL,
OP_SET_LOCAL,
OP_GET_GLOBAL,
OP_DEFINE_GLOBAL,
OP_SET_GLOBAL,
OP_GET_UPVALUE,
OP_SET_UPVALUE,
OP_GET_PROPERTY,
OP_SET_PROPERTY,
OP_GET_SUPER,
OP_EQUAL,
OP_GREATER,
OP_LESS,
OP_ADD,
OP_SUBTRACT,
OP_MULTIPLY,
OP_DIVIDE,
OP_NOT,
OP_NEGATE,
OP_PRINT,
OP_JUMP,
OP_JUMP_IF_FALSE,
OP_LOOP,
OP_CALL,
OP_INVOKE,
OP_SUPER_INVOKE,
OP_CLOSURE,
OP_CLOSE_UPVALUE,
OP_RETURN,
OP_CLASS,
OP_INHERIT,
OP_METHOD,
} OpCode;
typedef struct {
int count;
int capacity;
uint8_t* code;
int* lines;
ValueArray constants;
} Chunk;
void initChunk(Chunk* chunk);
void freeChunk(Chunk* chunk);
void writeChunk(Chunk* chunk, uint8_t byte, int line);
int addConstant(Chunk* chunk, Value value);
#endif // clox_chunk_h

14
common.h Normal file
View file

@ -0,0 +1,14 @@
#ifndef clox_common_h
#define clox_common_h
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
//#define DEBUG_PRINT_CODE
//#define DEBUG_TRACE_EXECUTION
#define UINT8_COUNT (UINT8_MAX + 1)
#endif // clox_common_h

1081
compiler.c Normal file

File diff suppressed because it is too large Load diff

11
compiler.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef clox_compiler_h
#define clox_compiler_h
#include "object.h"
#include "vm.h"
ObjFunction* compile(const char* source);
void markCompilerRoots();
#endif // clox_compiler_h

159
debug.c Normal file
View file

@ -0,0 +1,159 @@
#include <stdio.h>
#include "debug.h"
#include "object.h"
#include "value.h"
void disassembleChunk(Chunk* chunk, const char* name)
{
printf("== %s ==\n", name);
for (int offset = 0; offset < chunk->count;) {
offset = disassembleInstruction(chunk, offset);
}
}
static int constantInstruction(const char* name, Chunk* chunk, int offset)
{
auto constant = chunk->code[offset + 1];
printf("%-16s %4d '", name, constant);
printValue(chunk->constants.values[constant]);
printf("'\n");
return offset + 2;
}
static int invokeInstruction(const char* name, Chunk* chunk, int offset)
{
auto constant = chunk->code[offset + 1];
auto argCount = chunk->code[offset + 2];
printf("%-16s (%d args) %4d '", name, argCount, constant);
printValue(chunk->constants.values[constant]);
printf("'\n");
return offset + 3;
}
static int simpleInstruction(const char* name, int offset)
{
printf("%s\n", name);
return offset + 1;
}
static int byteInstruction(const char* name, Chunk* chunk, int offset)
{
auto slot = chunk->code[offset + 1];
printf("%-16s %4d\n", name, slot);
return offset + 2;
}
static int jumpInstruction(const char* name, int sign, Chunk* chunk, int offset)
{
auto jump = (uint16_t)(chunk->code[offset + 1] << 8);
jump |= chunk->code[offset + 2];
printf("%-16s %4d -> %d\n", name, offset, offset + 3 + sign * jump);
return offset + 3;
}
int disassembleInstruction(Chunk* chunk, int offset)
{
printf("%04d ", offset);
if (offset > 0 && chunk->lines[offset] == chunk->lines[offset - 1]) {
printf(" | ");
} else {
printf("%4d ", chunk->lines[offset]);
}
auto instruction = chunk->code[offset];
switch (instruction) {
case OP_CONSTANT:
return constantInstruction("OP_CONSTANT", chunk, offset);
case OP_NIL:
return simpleInstruction("OP_NIL", offset);
case OP_TRUE:
return simpleInstruction("OP_TRUE", offset);
case OP_FALSE:
return simpleInstruction("OP_FALSE", offset);
case OP_POP:
return simpleInstruction("OP_POP", offset);
case OP_GET_LOCAL:
return byteInstruction("OP_GET_LOCAL", chunk, offset);
case OP_SET_LOCAL:
return byteInstruction("OP_SET_LOCAL", chunk, offset);
case OP_GET_GLOBAL:
return constantInstruction("OP_GET_GLOBAL", chunk, offset);
case OP_DEFINE_GLOBAL:
return constantInstruction("OP_DEFINE_GLOBAL", chunk, offset);
case OP_SET_GLOBAL:
return constantInstruction("OP_SET_GLOBAL", chunk, offset);
case OP_GET_UPVALUE:
return byteInstruction("OP_GET_UPVALUE", chunk, offset);
case OP_SET_UPVALUE:
return byteInstruction("OP_SET_UPVALUE", chunk, offset);
case OP_GET_PROPERTY:
return constantInstruction("OP_GET_PROPERTY", chunk, offset);
case OP_SET_PROPERTY:
return constantInstruction("OP_SET_PROPERTY", chunk, offset);
case OP_GET_SUPER:
return constantInstruction("OP_GET_SUPER", chunk, offset);
case OP_EQUAL:
return simpleInstruction("OP_EQUAL", offset);
case OP_GREATER:
return simpleInstruction("OP_GREATER", offset);
case OP_LESS:
return simpleInstruction("OP_LESS", offset);
case OP_ADD:
return simpleInstruction("OP_ADD", offset);
case OP_SUBTRACT:
return simpleInstruction("OP_SUBTRACT", offset);
case OP_MULTIPLY:
return simpleInstruction("OP_MULTIPLY", offset);
case OP_DIVIDE:
return simpleInstruction("OP_DIVIDE", offset);
case OP_NOT:
return simpleInstruction("OP_NOT", offset);
case OP_NEGATE:
return simpleInstruction("OP_NEGATE", offset);
case OP_PRINT:
return simpleInstruction("OP_PRINT", offset);
case OP_JUMP:
return jumpInstruction("OP_JUMP", 1, chunk, offset);
case OP_JUMP_IF_FALSE:
return jumpInstruction("OP_JUMP_IF_FALSE", 1, chunk, offset);
case OP_LOOP:
return jumpInstruction("OP_LOOP", -1, chunk, offset);
case OP_CALL:
return byteInstruction("OP_CALL", chunk, offset);
case OP_INVOKE:
return invokeInstruction("OP_INVOKE", chunk, offset);
case OP_SUPER_INVOKE:
return invokeInstruction("OP_SUPER_INVOKE", chunk, offset);
case OP_CLOSURE:
{
offset++;
auto constant = chunk->code[offset++];
printf("%-16s %4d ", "OP_CLOSURE", constant);
printValue(chunk->constants.values[constant]);
printf("\n");
ObjFunction* function = AS_FUNCTION(chunk->constants.values[constant]);
for (auto j = 0; j < function->upvalueCount; j++) {
auto isLocal = chunk->code[offset++];
auto index = chunk->code[offset++];
printf("%04d | %s %d\n", offset - 2, isLocal ? "local" : "upvalue", index);
}
return offset;
}
case OP_CLOSE_UPVALUE:
return simpleInstruction("OP_CLOSE_UPVALUE", offset);
case OP_RETURN:
return simpleInstruction("OP_RETURN", offset);
case OP_CLASS:
return constantInstruction("OP_CLASS", chunk, offset);
case OP_INHERIT:
return simpleInstruction("OP_INHERIT", offset);
case OP_METHOD:
return constantInstruction("OP_METHOD", chunk, offset);
default:
printf("Unknown opcode %d\n", instruction);
return offset + 1;
}
}

10
debug.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef clox_debug_h
#define clox_debug_h
#include "chunk.h"
void disassembleChunk(Chunk* chunk, const char* name);
int disassembleInstruction(Chunk* chunk, int offset);
#endif // clox_debug_h

62
flake.lock generated Normal file
View file

@ -0,0 +1,62 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": [
"systems"
]
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1766471942,
"narHash": "sha256-Wv+xrUNXgtxAXAMZE3EDzzeRgN1MEw+PnKr8zDozeLU=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "cfc52a405c6e85462364651a8f11e28ae8065c91",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1680978846,
"narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=",
"owner": "nix-systems",
"repo": "x86_64-linux",
"rev": "2ecfcac5e15790ba6ce360ceccddb15ad16d08a8",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "x86_64-linux",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

43
flake.nix Normal file
View file

@ -0,0 +1,43 @@
{
description = "C Template";
inputs = {
nixpkgs.url = "nixpkgs";
systems.url = "github:nix-systems/x86_64-linux";
flake-utils = {
url = "github:numtide/flake-utils";
inputs.systems.follows = "systems";
};
};
outputs =
{ self
, nixpkgs
, flake-utils
, ...
}:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
pname = "hello-world"; #package name
version = "0.0.1";
src = ./.;
buildInputs = with pkgs; [
];
nativeBuildInputs = with pkgs; [
ninja
cmake
clang-tools
lldb
];
in
{
devShells.default = pkgs.mkShell.override { stdenv = pkgs.clangStdenv; } {
inherit buildInputs nativeBuildInputs;
};
packages.default = pkgs.stdenv.mkDerivation {
inherit buildInputs nativeBuildInputs pname version src;
};
});
}

86
main.c Normal file
View file

@ -0,0 +1,86 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "common.h"
#include "chunk.h"
#include "debug.h"
#include "vm.h"
static void repl()
{
char line[1024];
for (;;) {
printf("> ");
if (!fgets(line, sizeof(line), stdin)) {
printf("\n");
break;
}
interpret(line);
}
}
static char* readFile(const char* path)
{
FILE* file = fopen(path, "rb");
if (file == nullptr) {
fprintf(stderr, "Could not open file \"%s\".\n", path);
exit(74);
}
fseek(file, 0L, SEEK_END);
auto fileSize = ftell(file);
if (fileSize < 0) {
fprintf(stderr, "Could not tell file size.");
exit(74);
}
rewind(file);
char* buffer = (char*)malloc(fileSize + 1);
if (buffer == nullptr) {
fprintf(stderr, "Not enough memory to read \"%s\".\n", path);
exit(74);
}
auto bytesRead = fread(buffer, sizeof(char), fileSize, file);
if (bytesRead < (size_t)fileSize) {
fprintf(stderr, "Could not read file \"%s\".\n", path);
exit(74);
}
fclose(file);
return buffer;
}
static void runFile(const char* path)
{
char* source = readFile(path);
auto result = interpret(source);
free(source);
if (result == INTERPRET_COMPILE_ERROR) {
exit(65);
}
if (result == INTERPRET_RUNTIME_ERROR) {
exit(70);
}
}
int main(int argc, const char* argv[])
{
initVM();
if (argc == 1) {
repl();
} else if (argc == 2) {
runFile(argv[1]);
} else {
fprintf(stderr, "Usage: %s [path]\n", argv[0]);
exit(64);
}
freeVM();
return 0;
}

269
memory.c Normal file
View file

@ -0,0 +1,269 @@
#include <stdlib.h>
#include <stdio.h>
#include "compiler.h"
#include "memory.h"
#include "vm.h"
#ifdef DEBUG_LOG_GC
#include <stdio.h>
#include "debug.h"
#endif
#define GC_HEAP_GROW_FACTOR 2
void* reallocate(void* pointer, size_t oldSize, size_t newSize)
{
vm.bytesAllocated += newSize - oldSize;
if (newSize > oldSize) {
#ifdef DEBUG_STRESS_GC
collectGarbage();
#endif
}
if (vm.bytesAllocated > vm.nextGC) {
collectGarbage();
}
if (newSize == 0) {
free(pointer);
return nullptr;
}
void* result = realloc(pointer, newSize);
if (result == nullptr) {
exit(1);
}
return result;
}
void markObject(Obj* object)
{
if (object == nullptr) {
return;
}
if (object->isMarked) {
return;
}
#ifdef DEBUG_LOG_GC
printf("%p mark ", (void*)object);
printValue(OBJ_VAL(object));
printf("\n");
#endif
object->isMarked = true;
if (vm.grayCapacity < vm.grayCount + 1) {
vm.grayCapacity = GROW_CAPACITY(vm.grayCapacity);
vm.grayStack = (Obj**)realloc(vm.grayStack, sizeof(Obj*) * vm.grayCapacity);
if (vm.grayStack == nullptr) {
fprintf(stderr, "failed to realloc grayStack\n");
exit(1);
}
}
vm.grayStack[vm.grayCount++] = object;
}
void markValue(Value value)
{
if (IS_OBJ(value)) {
markObject(AS_OBJ(value));
}
}
static void markArray(ValueArray* array)
{
for (auto i = 0; i < array->count; i++) {
markValue(array->values[i]);
}
}
static void blackenObject(Obj* object)
{
#ifdef DEBUG_LOG_GC
printf("%p blacken ", (void*)object);
printValue(OBJ_VAL(object));
printf("\n");
#endif
switch (object->type) {
case OBJ_BOUND_METHOD:
{
ObjBoundMethod* bound = (ObjBoundMethod*)object;
markValue(bound->receiver);
markObject((Obj*)bound->method);
}
break;
case OBJ_CLASS:
{
ObjClass* klass = (ObjClass*)object;
markObject((Obj*)klass->name);
markTable(&klass->methods);
}
break;
case OBJ_CLOSURE:
{
ObjClosure* closure = (ObjClosure*)object;
markObject((Obj*)closure->function);
for (auto i = 0; i < closure->upvalueCount; i++) {
markObject((Obj*)closure->upvalues[i]);
}
}
break;
case OBJ_FUNCTION:
{
ObjFunction* function = (ObjFunction*)object;
markObject((Obj*)function->name);
markArray(&function->chunk.constants);
}
break;
case OBJ_INSTANCE:
{
ObjInstance* instance = (ObjInstance*)object;
markObject((Obj*)instance->klass);
markTable(&instance->fields);
}
break;
case OBJ_UPVALUE:
markValue(((ObjUpvalue*)object)->closed);
break;
case OBJ_NATIVE:
case OBJ_STRING:
break;
}
}
static void freeObject(Obj* object)
{
#ifdef DEBUG_LOG_GC
printf("%p free type %d\n", (void*)object, object->type);
#endif
switch (object->type) {
case OBJ_BOUND_METHOD:
FREE(ObjBoundMethod, object);
break;
case OBJ_CLASS:
{
ObjClass* klass = (ObjClass*)object;
freeTable(&klass->methods);
FREE(ObjClass, object);
}
break;
case OBJ_CLOSURE:
FREE(ObjClosure, object);
break;
case OBJ_FUNCTION:
{
ObjFunction* function = (ObjFunction*)object;
freeChunk(&function->chunk);
FREE(ObjFunction, object);
}
break;
case OBJ_INSTANCE:
{
ObjInstance* instance = (ObjInstance*)object;
freeTable(&instance->fields);
FREE(ObjInstance, object);
}
break;
case OBJ_NATIVE:
FREE(ObjNative, object);
break;
case OBJ_STRING:
{
ObjString* string = (ObjString*)object;
FREE_ARRAY(char, string->chars, string->length + 1);
FREE(ObjString, object);
}
break;
case OBJ_UPVALUE:
FREE(ObjUpvalue, object);
break;
}
}
static void markRoots()
{
for (Value* slot = vm.stack; slot < vm.stackTop; slot++) {
markValue(*slot);
}
for (auto i = 0; i < vm.frameCount; i++) {
markObject((Obj*)vm.frames[i].closure);
}
for (ObjUpvalue* upvalue = vm.openUpvalues; upvalue != nullptr; upvalue = upvalue->next) {
markObject((Obj*)upvalue);
}
markTable(&vm.globals);
markCompilerRoots();
markObject((Obj*)vm.initString);
}
static void traceReferences()
{
while (vm.grayCount > 0) {
Obj* object = vm.grayStack[--vm.grayCount];
blackenObject(object);
}
}
static void sweep()
{
Obj* previous = nullptr;
Obj* object = vm.objects;
while (object != nullptr) {
if (object->isMarked) {
object->isMarked = false;
previous = object;
object = object->next;
} else {
Obj* unreached = object;
object = object->next;
if (previous != nullptr) {
previous->next = object;
} else {
vm.objects = object;
}
freeObject(unreached);
}
}
}
void collectGarbage()
{
#ifdef DEBUG_LOG_GC
printf("-- gc begin\n");
size_t before = vm.bytesAllocated;
#endif
markRoots();
traceReferences();
tableRemoveWhite(&vm.strings);
sweep();
vm.nextGC = vm.bytesAllocated * GC_HEAP_GROW_FACTOR;
#ifdef DEBUG_LOG_GC
printf("-- gc end\n");
printf(" collected %zu bytes (from %zu to %zu) next at %zu\n", before - vm.bytesAllocated, before, vm.bytesAllocated, vm.nextGC);
#endif
}
void freeObjects()
{
Obj* object = vm.objects;
while (object != nullptr) {
Obj* next = object->next;
freeObject(object);
object = next;
}
free(vm.grayStack);
}

29
memory.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef clox_memory_h
#define clox_memory_h
#include "common.h"
#include "object.h"
#define ALLOCATE(type, count) \
(type*)reallocate(nullptr, 0, sizeof(type) * (count))
#define FREE(type, pointer) \
reallocate(pointer, sizeof(type), 0)
#define GROW_CAPACITY(capacity) \
((capacity) < 8 ? 8 : (capacity) * 2)
#define GROW_ARRAY(type, pointer, oldCount, newCount) \
(type*)reallocate(pointer, sizeof(type) * (oldCount), sizeof(type) * (newCount))
#define FREE_ARRAY(type, pointer, oldCount) \
reallocate(pointer, sizeof(type) * (oldCount), 0)
void* reallocate(void* pointer, size_t oldSize, size_t newSize);
void markObject(Obj* object);
void markValue(Value value);
void collectGarbage();
void freeObjects();
#endif // clox_memory_h

179
object.c Normal file
View file

@ -0,0 +1,179 @@
#include <stdio.h>
#include <string.h>
#include "memory.h"
#include "object.h"
#include "table.h"
#include "value.h"
#include "vm.h"
#define ALLOCATE_OBJ(type, objectType) \
(type*)allocateObject(sizeof(type), objectType)
static Obj* allocateObject(size_t size, ObjType type)
{
Obj* object = (Obj*)reallocate(nullptr, 0, size);
object->type = type;
object->isMarked = false;
object->next = vm.objects;
vm.objects = object;
#ifdef DEBUG_LOG_GC
printf("%p allocate %zu for %d\n", (void*)object, size, type);
#endif
return object;
}
ObjBoundMethod* newBoundMethod(Value receiver, ObjClosure* method)
{
ObjBoundMethod* bound = ALLOCATE_OBJ(ObjBoundMethod, OBJ_BOUND_METHOD);
bound->receiver = receiver;
bound->method = method;
return bound;
}
ObjClass* newClass(ObjString* name)
{
ObjClass* klass = ALLOCATE_OBJ(ObjClass, OBJ_CLASS);
klass->name = name;
initTable(&klass->methods);
return klass;
}
ObjClosure* newClosure(ObjFunction* function)
{
ObjUpvalue** upvalues = ALLOCATE(ObjUpvalue*, function->upvalueCount);
for (auto i = 0; i < function->upvalueCount; i++) {
upvalues[i] = nullptr;
}
ObjClosure* closure = ALLOCATE_OBJ(ObjClosure, OBJ_CLOSURE);
closure->function = function;
closure->upvalues = upvalues;
closure->upvalueCount = function->upvalueCount;
return closure;
}
ObjFunction* newFunction()
{
ObjFunction* function = ALLOCATE_OBJ(ObjFunction, OBJ_FUNCTION);
function->arity = 0;
function->upvalueCount = 0;
function->name = nullptr;
initChunk(&function->chunk);
return function;
}
ObjInstance* newInstance(ObjClass* klass)
{
ObjInstance* instance = ALLOCATE_OBJ(ObjInstance, OBJ_INSTANCE);
instance->klass = klass;
initTable(&instance->fields);
return instance;
}
ObjNative* newNative(NativeFn function)
{
ObjNative* native = ALLOCATE_OBJ(ObjNative, OBJ_NATIVE);
native->function = function;
return native;
}
static ObjString* allocateString(char* chars, int length, uint32_t hash)
{
ObjString* string = ALLOCATE_OBJ(ObjString, OBJ_STRING);
string->length = length;
string->chars = chars;
string->hash = hash;
push(OBJ_VAL(string));
tableSet(&vm.strings, string, NIL_VAL);
pop();
return string;
}
static uint32_t hashString(const char* key, int length)
{
uint32_t hash = 2166136261u;
for (auto i = 0; i < length; i++) {
hash ^= (uint8_t)key[i];
hash *= 16777619;
}
return hash;
}
ObjString* takeString(char* chars, int length)
{
auto hash = hashString(chars, length);
ObjString* interned = tableFindString(&vm.strings, chars, length, hash);
if (interned != nullptr) {
FREE_ARRAY(char, chars, length + 1);
return interned;
}
return allocateString(chars, length, hash);
}
ObjString* copyString(const char* chars, int length)
{
auto hash = hashString(chars, length);
ObjString* interned = tableFindString(&vm.strings, chars, length, hash);
if (interned != nullptr) {
return interned;
}
char* heapChars = ALLOCATE(char, length + 1);
memcpy(heapChars, chars, length);
heapChars[length] = '\0';
return allocateString(heapChars, length, hash);
}
ObjUpvalue* newUpvalue(Value* slot)
{
ObjUpvalue* upvalue = ALLOCATE_OBJ(ObjUpvalue, OBJ_UPVALUE);
upvalue->closed = NIL_VAL;
upvalue->location = slot;
upvalue->next = nullptr;
return upvalue;
}
static void printFunction(ObjFunction* function)
{
if (function->name == nullptr) {
printf("<script>");
return;
}
printf("<fn %s>", function->name->chars);
}
void printObject(Value value)
{
switch (OBJ_TYPE(value)) {
case OBJ_BOUND_METHOD:
printFunction(AS_BOUND_METHOD(value)->method->function);
break;
case OBJ_CLASS:
printf("%s", AS_CLASS(value)->name->chars);
break;
case OBJ_CLOSURE:
printFunction(AS_CLOSURE(value)->function);
break;
case OBJ_FUNCTION:
printFunction(AS_FUNCTION(value));
break;
case OBJ_INSTANCE:
printf("%s instance", AS_INSTANCE(value)->klass->name->chars);
break;
case OBJ_NATIVE:
printf("<native fn>");
break;
case OBJ_STRING:
printf("%s", AS_CSTRING(value));
break;
case OBJ_UPVALUE:
printf("upvalue");
break;
}
}

116
object.h Normal file
View file

@ -0,0 +1,116 @@
#ifndef clox_object_h
#define clox_object_h
#include "common.h"
#include "chunk.h"
#include "table.h"
#include "value.h"
#define OBJ_TYPE(value) (AS_OBJ(value)->type)
#define IS_BOUND_METHOD(value) isObjType(value, OBJ_BOUND_METHOD)
#define IS_CLASS(value) isObjType(value, OBJ_CLASS)
#define IS_CLOSURE(value) isObjType(value, OBJ_CLOSURE)
#define IS_FUNCTION(value) isObjType(value, OBJ_FUNCTION)
#define IS_INSTANCE(value) isObjType(value, OBJ_INSTANCE)
#define IS_NATIVE(value) isObjTyep(value, OBJ_NATIVE)
#define IS_STRING(value) isObjType(value, OBJ_STRING)
#define AS_BOUND_METHOD(value) ((ObjBoundMethod*)AS_OBJ(value))
#define AS_CLASS(value) ((ObjClass*)AS_OBJ(value))
#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value))
#define AS_FUNCTION(value) ((ObjFunction*)AS_OBJ(value))
#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value))
#define AS_NATIVE(value) (((ObjNative*)AS_OBJ(value))->function)
#define AS_STRING(value) ((ObjString*)AS_OBJ(value))
#define AS_CSTRING(value) (((ObjString*)AS_OBJ(value))->chars)
typedef enum {
OBJ_BOUND_METHOD,
OBJ_CLASS,
OBJ_CLOSURE,
OBJ_FUNCTION,
OBJ_INSTANCE,
OBJ_NATIVE,
OBJ_STRING,
OBJ_UPVALUE,
} ObjType;
struct Obj {
ObjType type;
bool isMarked;
struct Obj* next;
};
typedef struct {
Obj obj;
int arity;
int upvalueCount;
Chunk chunk;
ObjString* name;
} ObjFunction;
typedef Value (*NativeFn)(int argCount, Value* args);
typedef struct {
Obj obj;
NativeFn function;
} ObjNative;
struct ObjString {
Obj obj;
int length;
char* chars;
uint32_t hash;
};
typedef struct ObjUpvalue {
Obj obj;
Value* location;
Value closed;
struct ObjUpvalue* next;
} ObjUpvalue;
typedef struct {
Obj obj;
ObjFunction* function;
ObjUpvalue** upvalues;
int upvalueCount;
} ObjClosure;
typedef struct {
Obj obj;
ObjString* name;
Table methods;
} ObjClass;
typedef struct {
Obj obj;
ObjClass* klass;
Table fields;
} ObjInstance;
typedef struct {
Obj obj;
Value receiver;
ObjClosure* method;
} ObjBoundMethod;
ObjBoundMethod* newBoundMethod(Value receiver, ObjClosure* method);
ObjClass* newClass(ObjString* name);
ObjClosure* newClosure(ObjFunction* function);
ObjFunction* newFunction();
ObjInstance* newInstance(ObjClass* klass);
ObjNative* newNative(NativeFn function);
ObjString* takeString(char* chars, int length);
ObjString* copyString(const char* chars, int length);
ObjUpvalue* newUpvalue(Value* slot);
void printObject(Value value);
static inline bool isObjType(Value value, ObjType type)
{
return IS_OBJ(value) && AS_OBJ(value)->type == type;
}
#endif // clox_object_h

274
scanner.c Normal file
View file

@ -0,0 +1,274 @@
#include <stdio.h>
#include <string.h>
#include "common.h"
#include "scanner.h"
typedef struct {
const char* start;
const char* current;
int line;
} Scanner;
Scanner scanner;
void initScanner(const char* source)
{
scanner.start = source;
scanner.current = source;
scanner.line = 1;
}
static bool isAlpha(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
}
static bool isDigit(char c)
{
return c >= '0' && c <= '9';
}
static bool isAtEnd()
{
return *scanner.current == '\0';
}
static char advance()
{
scanner.current++;
return scanner.current[-1];
}
static char peek()
{
return *scanner.current;
}
static char peekNext()
{
if (isAtEnd()) {
return '\0';
}
return scanner.current[1];
}
static bool match(char expected)
{
if (isAtEnd()) {
return false;
}
if (*scanner.current != expected) {
return false;
}
scanner.current++;
return true;
}
static Token makeToken(TokenType type)
{
return (Token){
.type = type,
.start = scanner.start,
.length = (int)(scanner.current - scanner.start),
.line = scanner.line,
};
}
static Token errorToken(const char* message)
{
return (Token){
.type = TOKEN_ERROR,
.start = message,
.length = (int)strlen(message),
.line = scanner.line,
};
}
static void skipWhitespace()
{
for (;;) {
auto c = peek();
switch (c) {
case ' ':
case '\r':
case '\t':
advance();
break;
case '\n':
scanner.line++;
advance();
break;
case '/':
if (peekNext() == '/') {
while (peek() != '\n' && !isAtEnd()) {
advance();
}
} else {
return;
}
break;
default:
return;
}
}
}
static TokenType checkKeyword(int start, int length, const char* rest, TokenType type)
{
if (scanner.current - scanner.start == start + length && memcmp(scanner.start + start, rest, length) == 0) {
return type;
}
return TOKEN_IDENTIFIER;
}
static TokenType identifierType()
{
switch (scanner.start[0]) {
case 'a':
return checkKeyword(1, 2, "nd", TOKEN_AND);
case 'c':
return checkKeyword(1, 4, "lass", TOKEN_CLASS);
case 'e':
return checkKeyword(1, 3, "lse", TOKEN_ELSE);
case 'f':
if (scanner.current - scanner.start > 1) {
switch (scanner.start[1]) {
case 'a':
return checkKeyword(2, 3, "lse", TOKEN_FALSE);
case 'o':
return checkKeyword(2, 1, "r", TOKEN_FOR);
case 'u':
return checkKeyword(2, 1, "n", TOKEN_FUN);
}
}
break;
case 'i':
return checkKeyword(1, 1, "f", TOKEN_IF);
case 'n':
return checkKeyword(1, 2, "il", TOKEN_NIL);
case 'o':
return checkKeyword(1, 1, "r", TOKEN_OR);
case 'p':
return checkKeyword(1, 4, "rint", TOKEN_PRINT);
case 'r':
return checkKeyword(1, 5, "eturn", TOKEN_RETURN);
case 's':
return checkKeyword(1, 4, "uper", TOKEN_SUPER);
case 't':
if (scanner.current - scanner.start > 1) {
switch (scanner.start[1]) {
case 'h':
return checkKeyword(2, 2, "is", TOKEN_THIS);
case 'r':
return checkKeyword(2, 2, "ue", TOKEN_TRUE);
}
}
case 'v':
return checkKeyword(1, 2, "ar", TOKEN_VAR);
case 'w':
return checkKeyword(1, 4, "hile", TOKEN_WHILE);
}
return TOKEN_IDENTIFIER;
}
static Token identifier()
{
while (isAlpha(peek()) || isDigit(peek())) {
advance();
}
return makeToken(identifierType());
}
static Token number()
{
while (isDigit(peek())) {
advance();
}
if (peek() == '.' && isDigit(peekNext())) {
advance();
while (isDigit(peek())) {
advance();
}
}
return makeToken(TOKEN_NUMBER);
}
static Token string()
{
while (peek() != '"' && !isAtEnd()) {
if (peek() == '\n') {
scanner.line++;
}
advance();
}
if (isAtEnd()) {
return errorToken("Unterminated string.");
}
advance();
return makeToken(TOKEN_STRING);
}
Token scanToken()
{
skipWhitespace();
scanner.start = scanner.current;
if (isAtEnd()) {
return makeToken(TOKEN_EOF);
}
auto c = advance();
if (isAlpha(c)) {
return identifier();
}
if (isDigit(c)) {
return number();
}
switch (c) {
case '(':
return makeToken(TOKEN_LEFT_PAREN);
case ')':
return makeToken(TOKEN_RIGHT_PAREN);
case '{':
return makeToken(TOKEN_LEFT_BRACE);
case '}':
return makeToken(TOKEN_RIGHT_BRACE);
case ';':
return makeToken(TOKEN_SEMICOLON);
case ',':
return makeToken(TOKEN_COMMA);
case '.':
return makeToken(TOKEN_DOT);
case '-':
return makeToken(TOKEN_MINUS);
case '+':
return makeToken(TOKEN_PLUS);
case '/':
return makeToken(TOKEN_SLASH);
case '*':
return makeToken(TOKEN_STAR);
case '!':
return makeToken(match('=') ? TOKEN_BANG_EQUAL : TOKEN_BANG);
case '=':
return makeToken(match('=') ? TOKEN_EQUAL_EQUAL : TOKEN_EQUAL);
case '<':
return makeToken(match('=') ? TOKEN_LESS_EQUAL : TOKEN_LESS);
case '>':
return makeToken(match('=') ? TOKEN_GREATER_EQUAL : TOKEN_GREATER);
case '"':
return string();
}
return errorToken("Unexpected character.");
}

58
scanner.h Normal file
View file

@ -0,0 +1,58 @@
#ifndef clox_scanner_h
#define clox_scanner_h
typedef enum {
TOKEN_LEFT_PAREN,
TOKEN_RIGHT_PAREN,
TOKEN_LEFT_BRACE,
TOKEN_RIGHT_BRACE,
TOKEN_COMMA,
TOKEN_DOT,
TOKEN_MINUS,
TOKEN_PLUS,
TOKEN_SEMICOLON,
TOKEN_SLASH,
TOKEN_STAR,
TOKEN_BANG,
TOKEN_BANG_EQUAL,
TOKEN_EQUAL,
TOKEN_EQUAL_EQUAL,
TOKEN_GREATER,
TOKEN_GREATER_EQUAL,
TOKEN_LESS,
TOKEN_LESS_EQUAL,
TOKEN_IDENTIFIER,
TOKEN_STRING,
TOKEN_NUMBER,
TOKEN_AND,
TOKEN_CLASS,
TOKEN_ELSE,
TOKEN_FALSE,
TOKEN_FOR,
TOKEN_FUN,
TOKEN_IF,
TOKEN_NIL,
TOKEN_OR,
TOKEN_PRINT,
TOKEN_RETURN,
TOKEN_SUPER,
TOKEN_THIS,
TOKEN_TRUE,
TOKEN_VAR,
TOKEN_WHILE,
TOKEN_ERROR,
TOKEN_EOF,
} TokenType;
typedef struct {
TokenType type;
const char* start;
int length;
int line;
} Token;
void initScanner(const char* source);
Token scanToken();
#endif // clox_scanner_h

173
table.c Normal file
View file

@ -0,0 +1,173 @@
#include <stdlib.h>
#include <string.h>
#include "memory.h"
#include "object.h"
#include "table.h"
#include "value.h"
#define TABLE_MAX_LOAD 0.75
void initTable(Table* table)
{
table->count = 0;
table->capacity = 0;
table->entries = nullptr;
}
void freeTable(Table* table)
{
FREE_ARRAY(Entry, table->entries, table->capacity);
initTable(table);
}
static Entry* findEntry(Entry* entries, int capacity, ObjString* key)
{
auto index = key->hash & (capacity - 1);
Entry* tombstone = nullptr;
for (;;) {
Entry* entry = &entries[index];
if (entry->key == nullptr) {
if (IS_NIL(entry->value)) {
return tombstone != nullptr ? tombstone : entry;
} else {
if (tombstone == nullptr) {
tombstone = entry;
}
}
} else if (entry->key == key) {
return entry;
}
index = (index + 1) & (capacity - 1);
}
}
bool tableGet(Table* table, ObjString* key, Value* value)
{
if (table->count == 0) {
return false;
}
Entry* entry = findEntry(table->entries, table->capacity, key);
if (entry->key == nullptr) {
return false;
}
*value = entry->value;
return true;
}
static void adjustCapacity(Table* table, int capacity)
{
Entry* entries = ALLOCATE(Entry, capacity);
for (auto i = 0; i < capacity; i++) {
entries[i].key = nullptr;
entries[i].value = NIL_VAL;
}
table->count = 0;
for (auto i = 0; i < table->capacity; i++) {
Entry* entry = &table->entries[i];
if (entry->key == nullptr) {
continue;
}
Entry* dest = findEntry(entries, capacity, entry->key);
dest->key = entry->key;
dest->value = entry->value;
table->count++;
}
FREE_ARRAY(Entry, table->entries, table->capacity);
table->entries = entries;
table->capacity = capacity;
}
bool tableSet(Table* table, ObjString* key, Value value)
{
if (table->count + 1 > table->capacity * TABLE_MAX_LOAD) {
auto capacity = GROW_CAPACITY(table->capacity);
adjustCapacity(table, capacity);
}
Entry* entry = findEntry(table->entries, table->capacity, key);
auto isNewKey = entry->key == nullptr;
if (isNewKey && IS_NIL(entry->value)) {
table->count++;
}
entry->key = key;
entry->value = value;
return isNewKey;
}
bool tableDelete(Table* table, ObjString* key)
{
if (table->count == 0) {
return false;
}
Entry* entry = findEntry(table->entries, table->capacity, key);
if (entry->key == nullptr) {
return false;
}
entry->key = nullptr;
entry->value = BOOL_VAL(true);
return true;
}
void tableAddAll(Table* from, Table* to)
{
for (auto i = 0; i < from->capacity; i++) {
Entry* entry = &from->entries[i];
if (entry->key != nullptr) {
tableSet(to, entry->key, entry->value);
}
}
}
ObjString* tableFindString(Table* table, const char* chars, int length, uint32_t hash)
{
if (table->count == 0) {
return nullptr;
}
auto index = hash & (table->capacity - 1);
for (;;) {
Entry* entry = &table->entries[index];
if (entry->key == nullptr) {
if (IS_NIL(entry->value)) {
return nullptr;
}
} else if (entry->key->length == length && entry->key->hash == hash && memcmp(entry->key->chars, chars, length) == 0) {
return entry->key;
}
index = (index + 1) & (table->capacity - 1);
}
}
void tableRemoveWhite(Table* table)
{
for (auto i = 0; i < table->capacity; i++) {
Entry* entry = &table->entries[i];
if (entry->key != nullptr && !entry->key->obj.isMarked) {
tableDelete(table, entry->key);
}
}
}
void markTable(Table* table)
{
for (auto i = 0; i < table->capacity; i++) {
Entry* entry = &table->entries[i];
markObject((Obj*)entry->key);
markValue(entry->value);
}
}

29
table.h Normal file
View file

@ -0,0 +1,29 @@
#ifndef clox_table_h
#define clox_table_h
#include "common.h"
#include "value.h"
typedef struct {
ObjString* key;
Value value;
} Entry;
typedef struct {
int count;
int capacity;
Entry* entries;
} Table;
void initTable(Table* table);
void freeTable(Table* table);
bool tableGet(Table* table, ObjString* key, Value* value);
bool tableSet(Table* table, ObjString* key, Value value);
bool tableDelete(Table* table, ObjString* key);
void tableAddAll(Table* from, Table* to);
ObjString* tableFindString(Table* table, const char* chars, int length, uint32_t hash);
void tableRemoveWhite(Table* table);
void markTable(Table* table);
#endif // clox_table_h

89
value.c Normal file
View file

@ -0,0 +1,89 @@
#include <stdio.h>
#include <string.h>
#include "object.h"
#include "memory.h"
#include "value.h"
void initValueArray(ValueArray* array)
{
array->values = nullptr;
array->capacity = 0;
array->count = 0;
}
void freeValueArray(ValueArray* array)
{
FREE_ARRAY(Value, array->values, array->capacity);
initValueArray(array);
}
void writeValueArray(ValueArray* array, Value value)
{
if (array->capacity < array->count + 1) {
int oldCapacity = array->capacity;
array->capacity = GROW_CAPACITY(oldCapacity);
array->values = GROW_ARRAY(Value, array->values, oldCapacity, array->capacity);
}
array->values[array->count] = value;
array->count++;
}
void printValue(Value value)
{
#ifdef NAN_BOXING
if (IS_BOOL(value)) {
printf(AS_BOOL(value) ? "true" : "false");
} else if (IS_NIL(value)) {
printf("nil");
} else if (IS_NUMBER(value)) {
printf("%g", AS_NUMBER(value));
} else if (IS_OBJ(value)) {
printObject(value);
}
#else
switch (value.type) {
case VAL_BOOL:
printf(AS_BOOL(value) ? "true" : "false");
break;
case VAL_NIL:
printf("nil");
break;
case VAL_NUMBER:
printf("%g", AS_NUMBER(value));
break;
case VAL_OBJ:
printObject(value);
break;
}
#endif
}
bool valuesEqual(Value a, Value b)
{
#ifdef NAN_BOXING
if (IS_NUMBER(a) && IS_NUMBER(b)) {
return AS_NUMBER(a) == AS_NUMBER(b);
}
return a == b;
#else
if (a.type != b.type) {
return false;
}
switch (a.type) {
case VAL_BOOL:
return AS_BOOL(a) == AS_BOOL(b);
case VAL_NIL:
return true;
case VAL_NUMBER:
return AS_NUMBER(a) == AS_NUMBER(b);
case VAL_OBJ:
return AS_OBJ(a) == AS_OBJ(b);
default:
return false;
}
#endif
}

95
value.h Normal file
View file

@ -0,0 +1,95 @@
#ifndef clox_value_h
#define clox_value_h
#include <string.h>
#include "common.h"
typedef struct Obj Obj;
typedef struct ObjString ObjString;
#ifdef NAN_BOXING
#define SIGN_BIT ((uint64_t)0b1000000000000000000000000000000000000000000000000000000000000000)
#define QNAN ((uint64_t)0b0111111111111100000000000000000000000000000000000000000000000000)
#define TAG_NIL 0b01
#define TAG_FALSE 0b10
#define TAG_TRUE 0b11
typedef uint64_t Value;
#define IS_BOOL(value) (((value) | 0b01) == TRUE_VAL)
#define IS_NIL(value) ((value) == NIL_VAL)
#define IS_NUMBER(value) (((value) & QNAN) != QNAN)
#define IS_OBJ(value) (((value) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT))
#define AS_OBJ(value) ((Obj*)(uintptr_t)((value) & ~(SIGN_BIT | QNAN)))
#define AS_BOOL(value) ((value) == TRUE_VAL)
#define AS_NUMBER(value) valueToNum(value)
#define BOOL_VAL(value) ((value) ? TRUE_VAL : FALSE_VAL)
#define FALSE_VAL ((Value)(uint64_t)(QNAN | TAG_FALSE))
#define TRUE_VAL ((Value)(uint64_t)(QNAN | TAG_TRUE))
#define NIL_VAL ((Value)(uint64_t)(QNAN | TAG_NIL))
#define NUMBER_VAL(value) numToValue(value)
#define OBJ_VAL(object) ((Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(object)))
static inline double valueToNum(Value value)
{
double num;
memcpy(&num, &value, sizeof(Value));
return num;
}
static inline Value numToValue(double num)
{
Value value;
memcpy(&value, &num, sizeof(double));
return value;
}
#else
typedef enum {
VAL_BOOL,
VAL_NIL,
VAL_NUMBER,
VAL_OBJ,
} ValueType;
typedef struct {
ValueType type;
union {
bool boolean;
double number;
Obj* obj;
} as;
} Value;
#define IS_BOOL(value) ((value).type == VAL_BOOL)
#define IS_NIL(value) ((value).type == VAL_NIL)
#define IS_NUMBER(value) ((value).type == VAL_NUMBER)
#define IS_OBJ(value) ((value).type == VAL_OBJ)
#define AS_OBJ(value) ((value).as.obj)
#define AS_BOOL(value) ((value).as.boolean)
#define AS_NUMBER(value) ((value).as.number)
#define BOOL_VAL(value) ((Value){ .type = VAL_BOOL, .as = { .boolean = value } })
#define NIL_VAL ((Value){ .type = VAL_NIL, .as = { .number = 0 } })
#define NUMBER_VAL(value) ((Value){ .type = VAL_NUMBER, .as = { .number = value } })
#define OBJ_VAL(object) ((Value){ .type = VAL_OBJ, .as = { .obj = (Obj*)object } })
#endif
typedef struct {
int capacity;
int count;
Value* values;
} ValueArray;
bool valuesEqual(Value a, Value b);
void initValueArray(ValueArray* array);
void writeValueArray(ValueArray* array, Value value);
void freeValueArray(ValueArray* array);
void printValue(Value value);
#endif // clox_value_h

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();
}

49
vm.h Normal file
View file

@ -0,0 +1,49 @@
#ifndef clox_vm_h
#define clox_vm_h
#include "object.h"
#include "table.h"
#include "value.h"
#define FRAMES_MAX 64
#define STACK_MAX (FRAMES_MAX * UINT8_COUNT)
typedef struct {
ObjClosure* closure;
uint8_t* ip;
Value* slots;
} CallFrame;
typedef struct {
CallFrame frames[FRAMES_MAX];
int frameCount;
Value stack[STACK_MAX];
Value* stackTop;
Table globals;
Table strings;
ObjString* initString;
ObjUpvalue* openUpvalues;
size_t bytesAllocated;
size_t nextGC;
Obj* objects;
int grayCount;
int grayCapacity;
Obj** grayStack;
} VM;
typedef enum {
INTERPRET_OK,
INTERPRET_COMPILE_ERROR,
INTERPRET_RUNTIME_ERROR,
} InterpretResult;
extern VM vm;
void initVM();
void freeVM();
InterpretResult interpret(const char* source);
void push(Value value);
Value pop();
#endif // clox_vm_h