commit b2edca2cb475c372b2365e5ec8f1725ffcecac78 Author: ktkk Date: Tue Jan 13 19:52:23 2026 +0000 Initial commit 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 : "