From b2edca2cb475c372b2365e5ec8f1725ffcecac78 Mon Sep 17 00:00:00 2001 From: ktkk Date: Tue, 13 Jan 2026 19:52:23 +0000 Subject: [PATCH] Initial commit --- .envrc | 1 + .gitignore | 5 + CMakeLists.txt | 49 +++ chunk.c | 43 ++ chunk.h | 61 +++ common.h | 14 + compiler.c | 1081 ++++++++++++++++++++++++++++++++++++++++++++++++ compiler.h | 11 + debug.c | 159 +++++++ debug.h | 10 + flake.lock | 62 +++ flake.nix | 43 ++ main.c | 86 ++++ memory.c | 269 ++++++++++++ memory.h | 29 ++ object.c | 179 ++++++++ object.h | 116 ++++++ scanner.c | 274 ++++++++++++ scanner.h | 58 +++ table.c | 173 ++++++++ table.h | 29 ++ value.c | 89 ++++ value.h | 95 +++++ vm.c | 605 +++++++++++++++++++++++++++ vm.h | 49 +++ 25 files changed, 3590 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 chunk.c create mode 100644 chunk.h create mode 100644 common.h create mode 100644 compiler.c create mode 100644 compiler.h create mode 100644 debug.c create mode 100644 debug.h create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 main.c create mode 100644 memory.c create mode 100644 memory.h create mode 100644 object.c create mode 100644 object.h create mode 100644 scanner.c create mode 100644 scanner.h create mode 100644 table.c create mode 100644 table.h create mode 100644 value.c create mode 100644 value.h create mode 100644 vm.c create mode 100644 vm.h diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..8392d15 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ed7e73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/ + +# Misc +.direnv +.cache diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e257918 --- /dev/null +++ b/CMakeLists.txt @@ -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 + $<$,$>:DEBUG_PRINT_CODE> + $<$,$>:DEBUG_TRACE_EXECUTION> + $<$,$>:DEBUG_STRESS_GC> + $<$,$>:DEBUG_LOG_GC> +) +target_compile_definitions( + ${PROJECT_NAME} + PRIVATE + $<$:NAN_BOXING> +) + diff --git a/chunk.c b/chunk.c new file mode 100644 index 0000000..3c64c35 --- /dev/null +++ b/chunk.c @@ -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; +} + diff --git a/chunk.h b/chunk.h new file mode 100644 index 0000000..a0a7ea5 --- /dev/null +++ b/chunk.h @@ -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 + diff --git a/common.h b/common.h new file mode 100644 index 0000000..1bdb9c1 --- /dev/null +++ b/common.h @@ -0,0 +1,14 @@ +#ifndef clox_common_h +#define clox_common_h + +#include +#include +#include + +//#define DEBUG_PRINT_CODE +//#define DEBUG_TRACE_EXECUTION + +#define UINT8_COUNT (UINT8_MAX + 1) + +#endif // clox_common_h + diff --git a/compiler.c b/compiler.c new file mode 100644 index 0000000..77f386e --- /dev/null +++ b/compiler.c @@ -0,0 +1,1081 @@ +#include +#include +#include + +#include "common.h" +#include "compiler.h" +#include "memory.h" +#include "scanner.h" + +#ifdef DEBUG_PRINT_CODE +#include "debug.h" +#endif + +typedef struct { + Token current; + Token previous; + bool hadError; + bool panicMode; +} Parser; + +typedef enum { + PREC_NONE, + PREC_ASSIGNMENT, + PREC_OR, + PREC_AND, + PREC_EQUALITY, + PREC_COMPARISON, + PREC_TERM, + PREC_FACTOR, + PREC_UNARY, + PREC_CALL, + PREC_PRIMARY, +} Precedence; + +typedef void (*ParseFn)(bool canAssign); + +typedef struct { + ParseFn prefix; + ParseFn infix; + Precedence precedence; +} ParseRule; + +typedef struct { + Token name; + int depth; + bool isCaptured; +} Local; + +typedef struct { + uint8_t index; + bool isLocal; +} Upvalue; + +typedef enum { + TYPE_FUNCTION, + TYPE_INITIALIZER, + TYPE_METHOD, + TYPE_SCRIPT, +} FunctionType; + +typedef struct Compiler { + struct Compiler* enclosing; + ObjFunction* function; + FunctionType type; + Local locals[UINT8_COUNT]; + int localCount; + Upvalue upvalues[UINT8_COUNT]; + int scopeDepth; +} Compiler; + +typedef struct ClassCompiler { + struct ClassCompiler* enclosing; + bool hasSuperclass; +} ClassCompiler; + +Parser parser; +Compiler* current = nullptr; +ClassCompiler* currentClass = nullptr; + +static Chunk* currentChunk() +{ + return ¤t->function->chunk; +} + +static void errorAt(Token* token, const char* message) +{ + if (parser.panicMode) { + return; + } + parser.panicMode = true; + + fprintf(stderr, "[line %d] Error", token->line); + + if (token->type == TOKEN_EOF) { + fprintf(stderr, " at end"); + } else if (token->type == TOKEN_ERROR) { + } else { + fprintf(stderr, " at '%.*s'", token->length, token->start); + } + + fprintf(stderr, ": %s\n", message); + parser.hadError = true; +} + +static void error(const char* message) +{ + errorAt(&parser.previous, message); +} + +static void errorAtCurrent(const char* message) +{ + errorAt(&parser.current, message); +} + +static void advance() +{ + parser.previous = parser.current; + + for (;;) { + parser.current = scanToken(); + if (parser.current.type != TOKEN_ERROR) { + break; + } + errorAtCurrent(parser.current.start); + } +} + +static void consume(TokenType type, const char* message) +{ + if (parser.current.type == type) { + advance(); + return; + } + + errorAtCurrent(message); +} + +static bool check(TokenType type) +{ + return parser.current.type == type; +} + +static bool match(TokenType type) +{ + if (!check(type)) { + return false; + } + + advance(); + + return true; +} + +static void emitByte(uint8_t byte) +{ + writeChunk(currentChunk(), byte, parser.previous.line); +} + +static void emitBytes(uint8_t byte1, uint8_t byte2) +{ + emitByte(byte1); + emitByte(byte2); +} + +static void emitLoop(int loopStart) +{ + emitByte(OP_LOOP); + + auto offset = currentChunk()->count - loopStart + 2; + if (offset > UINT16_MAX) { + error("Loop body too large."); + } + + emitByte((offset >> 8) & 0xff); + emitByte(offset & 0xff); +} + +static int emitJump(uint8_t instruction) +{ + emitByte(instruction); + emitByte(0xff); + emitByte(0xff); + return currentChunk()->count - 2; +} + +static void emitReturn() +{ + if (current->type == TYPE_INITIALIZER) { + emitBytes(OP_GET_LOCAL, 0); + } else { + emitByte(OP_NIL); + } + emitByte(OP_RETURN); +} + +static uint8_t makeConstant(Value value) +{ + auto constant = addConstant(currentChunk(), value); + if (constant > UINT8_MAX) { + error("Too many constants in one chunk."); + return 0; + } + + return (uint8_t)constant; +} + +static void emitConstant(Value value) +{ + emitBytes(OP_CONSTANT, makeConstant(value)); +} + +static void patchJump(int offset) +{ + auto jump = currentChunk()->count - offset - 2; + + if (jump > UINT16_MAX) { + error("Too much code to jump over."); + } + + currentChunk()->code[offset] = (jump >> 8) & 0xff; + currentChunk()->code[offset + 1] = jump & 0xff; +} + +static void initCompiler(Compiler* compiler, FunctionType type) +{ + compiler->enclosing = current; + compiler->function = nullptr; + compiler->type = type; + compiler->localCount = 0; + compiler->scopeDepth = 0; + compiler->function = newFunction(); + current = compiler; + if (type != TYPE_SCRIPT) { + current->function->name = copyString(parser.previous.start, parser.previous.length); + } + + Local* local = ¤t->locals[current->localCount++]; + local->depth = 0; + local->isCaptured = false; + if (type != TYPE_FUNCTION) { + local->name.start = "this"; + local->name.length = 4; + } else { + local->name.start = ""; + local->name.length = 0; + } +} + +static ObjFunction* endCompiler() +{ + emitReturn(); + ObjFunction* function = current->function; + +#ifdef DEBUG_PRINT_CODE + if (!parser.hadError) { + disassembleChunk(currentChunk(), function->name != nullptr ? function->name->chars : "