Initial commit
This commit is contained in:
commit
b2edca2cb4
25 changed files with 3590 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
use flake
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
build/
|
||||
|
||||
# Misc
|
||||
.direnv
|
||||
.cache
|
||||
49
CMakeLists.txt
Normal file
49
CMakeLists.txt
Normal 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
43
chunk.c
Normal 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
61
chunk.h
Normal 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
14
common.h
Normal 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
1081
compiler.c
Normal file
File diff suppressed because it is too large
Load diff
11
compiler.h
Normal file
11
compiler.h
Normal 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
159
debug.c
Normal 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
10
debug.h
Normal 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
62
flake.lock
generated
Normal 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
43
flake.nix
Normal 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
86
main.c
Normal 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
269
memory.c
Normal 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
29
memory.h
Normal 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
179
object.c
Normal 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
116
object.h
Normal 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
274
scanner.c
Normal 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
58
scanner.h
Normal 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
173
table.c
Normal 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
29
table.h
Normal 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
89
value.c
Normal 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
95
value.h
Normal 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
605
vm.c
Normal 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
49
vm.h
Normal 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
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue