Inital commit
This commit is contained in:
commit
f7afaf34aa
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
.DS_Store
|
||||
|
||||
.metadata
|
||||
bin/
|
||||
tmp/
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~.nib
|
||||
local.properties
|
||||
.settings/
|
||||
.loadpath
|
||||
.recommenders
|
||||
|
||||
# External tool builders
|
||||
.externalToolBuilders/
|
||||
|
||||
# Locally stored "Eclipse launch configurations"
|
||||
*.launch
|
||||
|
||||
# CDT-specific (C/C++ Development Tooling)
|
||||
.cproject
|
||||
|
||||
# CDT- autotools
|
||||
.autotools
|
||||
|
||||
# Code Recommenders
|
||||
.recommenders/
|
||||
|
||||
# Annotation Processing
|
||||
.apt_generated/
|
||||
|
||||
*.o
|
||||
*.d
|
||||
|
||||
Debug
|
||||
Release
|
26
.project
Normal file
26
.project
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>clox</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
|
||||
<triggers>clean,full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
|
||||
<triggers>full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
51
chunk.c
Normal file
51
chunk.c
Normal file
@ -0,0 +1,51 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "chunk.h"
|
||||
#include "memory.h"
|
||||
#include "value.h"
|
||||
|
||||
void initChunk(Chunk* chunk) {
|
||||
chunk->count = 0;
|
||||
chunk->capacity = 0;
|
||||
chunk->code = NULL;
|
||||
chunk->lines = NULL;
|
||||
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(chunk->code, uint8_t, oldCapacity, chunk->capacity);
|
||||
chunk->lines = GROW_ARRAY(chunk->lines, int, oldCapacity, chunk->capacity);
|
||||
}
|
||||
|
||||
chunk->code[chunk->count] = byte;
|
||||
chunk->lines[chunk->count] = line;
|
||||
chunk->count++;
|
||||
}
|
||||
|
||||
int addConstant(Chunk* chunk, Value value) {
|
||||
writeValueArray(&chunk->constants, value);
|
||||
return (chunk->constants.count - 1);
|
||||
}
|
||||
|
||||
void writeConstant(Chunk* chunk, Value value, int line) {
|
||||
int i=addConstant(chunk, value);
|
||||
if (i<256) {
|
||||
writeChunk(chunk, OP_CONSTANT, line);
|
||||
writeChunk(chunk, i&0xFF, line);
|
||||
} else {
|
||||
writeChunk(chunk, OP_CONSTANT_LONG, line);
|
||||
writeChunk(chunk, i&0xFF0000>>16, line);
|
||||
writeChunk(chunk, i&0xFF00>>8, line);
|
||||
writeChunk(chunk, i&0xFF, line);
|
||||
}
|
||||
}
|
30
chunk.h
Normal file
30
chunk.h
Normal file
@ -0,0 +1,30 @@
|
||||
#ifndef clox_chunk_h
|
||||
#define clox_chunk_h
|
||||
|
||||
#include "common.h"
|
||||
#include "value.h"
|
||||
|
||||
typedef enum {
|
||||
OP_ARRAY, OP_CONSTANT, OP_CONSTANT_LONG, OP_HASH, OP_NIL, OP_TRUE, OP_FALSE,
|
||||
OP_POP, OP_GET_LOCAL, OP_GET_LOCAL_LONG, OP_INDEX, OP_SET_LOCAL,
|
||||
OP_SET_LOCAL_LONG, OP_GET_GLOBAL, OP_GET_GLOBAL_LONG, OP_DEFINE_GLOBAL,
|
||||
OP_SET_GLOBAL, OP_SET_GLOBAL_LONG, OP_DEFINE_GLOBAL_LONG, 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_RETURN,
|
||||
} OpCode;
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
int capacity;
|
||||
uint8_t* code;
|
||||
int* lines;
|
||||
ValueArray constants;
|
||||
} Chunk;
|
||||
|
||||
void initChunk(Chunk* chunk);
|
||||
void writeChunk(Chunk* chunk, uint8_t byte, int line);
|
||||
void freeChunk(Chunk* chunk);
|
||||
int addConstant(Chunk* chunk, Value value); // Use writeConstant instead
|
||||
void writeConstant(Chunk* chunk, Value value, int line);
|
||||
|
||||
#endif
|
11
common.h
Normal file
11
common.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef clox_common_h
|
||||
#define clox_common_h
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define DEBUG_TRACE_EXECUTION
|
||||
#define DEBUG_PRINT_CODE
|
||||
|
||||
#endif
|
777
compiler.c
Normal file
777
compiler.c
Normal file
@ -0,0 +1,777 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "compiler.h"
|
||||
#include "scanner.h"
|
||||
#include "object.h"
|
||||
|
||||
#ifdef DEBUG_PRINT_CODE
|
||||
#include "debug.h"
|
||||
#endif
|
||||
|
||||
#define MAX_LOCALS 0xFFFF // 2^16
|
||||
|
||||
typedef struct {
|
||||
Token current;
|
||||
Token previous;
|
||||
bool hadError;
|
||||
bool panicMode;
|
||||
} Parser;
|
||||
|
||||
typedef enum {
|
||||
PREC_NONE, PREC_ASSIGNMENT, // =
|
||||
PREC_OR, // or
|
||||
PREC_AND, // 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;
|
||||
} Local;
|
||||
|
||||
typedef struct Compiler {
|
||||
Local locals[MAX_LOCALS]; //TODO: Make this a dynamic array
|
||||
int localCount;
|
||||
int scopeDepth;
|
||||
} Compiler;
|
||||
|
||||
Parser parser;
|
||||
|
||||
Compiler* current = NULL;
|
||||
|
||||
Chunk* compilingChunk;
|
||||
|
||||
static Chunk* currentChunk() {
|
||||
return compilingChunk;
|
||||
}
|
||||
|
||||
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) {
|
||||
// Nothing.
|
||||
} else {
|
||||
fprintf(stderr, " at '%.*s'", token->length, token->start);
|
||||
}
|
||||
|
||||
fprintf(stderr, ": %s\n", message);
|
||||
parser.hadError = true;
|
||||
}
|
||||
|
||||
static void errorAtCurrent(const char* message) {
|
||||
errorAt(&parser.current, message);
|
||||
}
|
||||
|
||||
static void error(const char* message) { // @suppress("Unused static function")
|
||||
errorAt(&parser.previous, 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) { // @suppress("Unused static function")
|
||||
emitByte(byte1);
|
||||
emitByte(byte2);
|
||||
}
|
||||
|
||||
static void emitLoop(int loopStart) {
|
||||
emitByte(OP_LOOP);
|
||||
|
||||
int offset = currentChunk()->count - loopStart + 2;
|
||||
if (offset > UINT16_MAX) error("Loop body too large.");
|
||||
|
||||
emitByte(offset & 0xff);
|
||||
emitByte((offset >> 8) & 0xff);
|
||||
}
|
||||
|
||||
static int emitJump(uint8_t instruction) {
|
||||
emitByte(instruction);
|
||||
emitByte(0xff);
|
||||
emitByte(0xff);
|
||||
return currentChunk()->count - 2;
|
||||
}
|
||||
|
||||
static void emitReturn() {
|
||||
emitByte(OP_RETURN);
|
||||
}
|
||||
|
||||
static void emitConstant(Value value) {
|
||||
writeConstant(currentChunk(), value, parser.previous.line);
|
||||
}
|
||||
|
||||
static void patchJump(int offset) {
|
||||
// -2 to adjust for the bytecode for the jump offset itself.
|
||||
int jump = currentChunk()->count - offset - 2;
|
||||
|
||||
if (jump > UINT16_MAX) {
|
||||
error("Too much code to jump over.");
|
||||
}
|
||||
|
||||
currentChunk()->code[offset + 1] = (jump >> 8) & 0xff;
|
||||
currentChunk()->code[offset] = jump & 0xff;
|
||||
}
|
||||
|
||||
static void initCompiler(Compiler* compiler) {
|
||||
compiler->localCount = 0;
|
||||
compiler->scopeDepth = 0;
|
||||
current = compiler;
|
||||
}
|
||||
|
||||
static void endCompiler() {
|
||||
emitReturn();
|
||||
#ifdef DEBUG_PRINT_CODE
|
||||
if (!parser.hadError) {
|
||||
disassembleChunk(currentChunk(), "code");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void beginScope() {
|
||||
current->scopeDepth++;
|
||||
}
|
||||
|
||||
static void endScope() {
|
||||
current->scopeDepth--;
|
||||
|
||||
while (current->localCount > 0 && current->locals[current->localCount - 1].depth > current->scopeDepth) {
|
||||
emitByte(OP_POP);
|
||||
current->localCount--;
|
||||
}
|
||||
}
|
||||
|
||||
static void expression();
|
||||
static void statement();
|
||||
static void declaration();
|
||||
static ParseRule* getRule(TokenType type);
|
||||
static void parsePrecedence(Precedence precedence);
|
||||
|
||||
static void binary(bool canAssign) {
|
||||
// Remember the operator.
|
||||
TokenType operatorType = parser.previous.type;
|
||||
|
||||
// Compile the right operand.
|
||||
ParseRule* rule = getRule(operatorType);
|
||||
parsePrecedence((Precedence) (rule->precedence + 1));
|
||||
|
||||
// Emit the operator instruction.
|
||||
switch (operatorType) {
|
||||
case TOKEN_BANG_EQUAL:
|
||||
emitBytes(OP_EQUAL, OP_NOT);
|
||||
break;
|
||||
case TOKEN_EQUAL_EQUAL:
|
||||
emitByte(OP_EQUAL);
|
||||
break;
|
||||
case TOKEN_GREATER:
|
||||
emitByte(OP_GREATER);
|
||||
break;
|
||||
case TOKEN_GREATER_EQUAL:
|
||||
emitBytes(OP_LESS, OP_NOT);
|
||||
break;
|
||||
case TOKEN_LESS:
|
||||
emitByte(OP_LESS);
|
||||
break;
|
||||
case TOKEN_LESS_EQUAL:
|
||||
emitBytes(OP_GREATER, OP_NOT);
|
||||
break;
|
||||
case TOKEN_PLUS:
|
||||
emitByte(OP_ADD);
|
||||
break;
|
||||
case TOKEN_MINUS:
|
||||
emitByte(OP_SUBTRACT);
|
||||
break;
|
||||
case TOKEN_STAR:
|
||||
emitByte(OP_MULTIPLY);
|
||||
break;
|
||||
case TOKEN_SLASH:
|
||||
emitByte(OP_DIVIDE);
|
||||
break;
|
||||
default:
|
||||
return; // Unreachable.
|
||||
}
|
||||
}
|
||||
|
||||
static void literal(bool canAssign) {
|
||||
switch (parser.previous.type) {
|
||||
case TOKEN_FALSE:
|
||||
emitByte(OP_FALSE);
|
||||
break;
|
||||
case TOKEN_NIL:
|
||||
emitByte(OP_NIL);
|
||||
break;
|
||||
case TOKEN_TRUE:
|
||||
emitByte(OP_TRUE);
|
||||
break;
|
||||
default:
|
||||
return; // Unreachable.
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t makeConstant(Value value) {
|
||||
int constant = addConstant(currentChunk(), value);
|
||||
if (constant > 0xFFFFFF) {
|
||||
error("Too many constants in one chunk.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (uint32_t) constant;
|
||||
}
|
||||
|
||||
static void grouping(bool canAssign) {
|
||||
expression();
|
||||
consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
|
||||
}
|
||||
|
||||
static void number(bool canAssign) {
|
||||
double value = strtod(parser.previous.start, NULL);
|
||||
emitConstant(NUMBER_VAL(value));
|
||||
}
|
||||
|
||||
static void or_(bool canAssign) {
|
||||
int elseJump = emitJump(OP_JUMP_IF_FALSE);
|
||||
int endJump = emitJump(OP_JUMP);
|
||||
|
||||
patchJump(elseJump);
|
||||
emitByte(OP_POP);
|
||||
|
||||
parsePrecedence(PREC_OR);
|
||||
patchJump(endJump);
|
||||
}
|
||||
|
||||
static void string(bool canAssign) {
|
||||
emitConstant(OBJ_VAL(copyString(parser.previous.start + 1, parser.previous.length - 2)));
|
||||
}
|
||||
|
||||
static void unary(bool canAssign) {
|
||||
TokenType operatorType = parser.previous.type;
|
||||
|
||||
// Compile the operand.
|
||||
parsePrecedence(PREC_UNARY);
|
||||
|
||||
// Emit the operator instruction.
|
||||
switch (operatorType) {
|
||||
case TOKEN_BANG:
|
||||
emitByte(OP_NOT);
|
||||
break;
|
||||
case TOKEN_MINUS:
|
||||
emitByte(OP_NEGATE);
|
||||
break;
|
||||
default:
|
||||
return; // Unreachable.
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t identifierConstant(Token* name) {
|
||||
return makeConstant(OBJ_VAL(copyString(name->start, name->length)));
|
||||
}
|
||||
|
||||
static bool identifiersEqual(Token* a, Token* b) {
|
||||
if (a->length != b->length) return false;
|
||||
return memcmp(a->start, b->start, a->length) == 0;
|
||||
}
|
||||
|
||||
static int resolveLocal(Compiler* compiler, Token* name) {
|
||||
for (int i = compiler->localCount - 1; i >= 0; i--) {
|
||||
Local* local = &compiler->locals[i];
|
||||
if (identifiersEqual(name, &local->name)) {
|
||||
if (local->depth == -1) {
|
||||
error("Cannot read local variable in its own initializer.");
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void addLocal(Token name) {
|
||||
if (current->localCount == MAX_LOCALS) {
|
||||
error("Too many local variables in function.");
|
||||
return;
|
||||
}
|
||||
|
||||
Local* local = ¤t->locals[current->localCount++];
|
||||
local->name = name;
|
||||
local->depth = current->scopeDepth;
|
||||
local->depth = -1;
|
||||
}
|
||||
|
||||
static void declareVariable() {
|
||||
// Global variables are implicitly declared.
|
||||
if (current->scopeDepth == 0) return;
|
||||
|
||||
Token* name = &parser.previous;
|
||||
for (int i = current->localCount - 1; i >= 0; i--) {
|
||||
Local* local = ¤t->locals[i];
|
||||
if (local->depth != -1 && local->depth < current->scopeDepth) break;
|
||||
if (identifiersEqual(name, &local->name)) {
|
||||
error("Variable with this name already declared in this scope.");
|
||||
}
|
||||
}
|
||||
|
||||
addLocal(*name);
|
||||
}
|
||||
|
||||
static void namedVariable(Token name, bool canAssign) {
|
||||
uint8_t getOp, getLongOp, setOp, setLongOp;
|
||||
int arg = resolveLocal(current, &name);
|
||||
if (arg != -1) {
|
||||
getOp = OP_GET_LOCAL;
|
||||
getLongOp = OP_GET_LOCAL_LONG;
|
||||
setOp = OP_SET_LOCAL;
|
||||
setLongOp = OP_SET_LOCAL_LONG;
|
||||
} else {
|
||||
arg = identifierConstant(&name);
|
||||
getOp = OP_GET_GLOBAL;
|
||||
getLongOp = OP_GET_GLOBAL_LONG;
|
||||
setOp = OP_SET_GLOBAL;
|
||||
setLongOp = OP_SET_GLOBAL_LONG;
|
||||
}
|
||||
|
||||
if (canAssign && match(TOKEN_EQUAL)) {
|
||||
expression();
|
||||
if (arg < 256) {
|
||||
emitBytes(setOp, arg);
|
||||
} else {
|
||||
emitByte(setLongOp);
|
||||
emitByte(arg & 0xFF0000 >> 16);
|
||||
emitByte(arg & 0xFF00 >> 8);
|
||||
emitByte(arg & 0xFF);
|
||||
}
|
||||
} else {
|
||||
if (arg < 256) {
|
||||
emitBytes(getOp, arg);
|
||||
} else {
|
||||
emitByte(getLongOp);
|
||||
emitByte(arg & 0xFF0000 >> 16);
|
||||
emitByte(arg & 0xFF00 >> 8);
|
||||
emitByte(arg & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void variable(bool canAssign) {
|
||||
namedVariable(parser.previous, canAssign);
|
||||
}
|
||||
|
||||
static void and_(bool canAssign) {
|
||||
int endJump = emitJump(OP_JUMP_IF_FALSE);
|
||||
|
||||
emitByte(OP_POP);
|
||||
parsePrecedence(PREC_AND);
|
||||
|
||||
patchJump(endJump);
|
||||
}
|
||||
|
||||
static void array_unary(bool canAssign) {
|
||||
expression();
|
||||
if (check(TOKEN_COMMA)) {
|
||||
int numValues = 0;
|
||||
while (!check(TOKEN_RIGHT_BRACKET) && !check(TOKEN_EOF)) {
|
||||
consume(TOKEN_COMMA, "Commas must follow every value in an array except the last");
|
||||
expression();
|
||||
numValues++;
|
||||
if (numValues == 256) {
|
||||
fprintf(stderr, "[line %d] Error: Cannot have more than 256 values in an array literal", parser.current.line);
|
||||
}
|
||||
}
|
||||
emitByte(OP_ARRAY);
|
||||
emitByte(numValues + 1);
|
||||
} else {
|
||||
expression();
|
||||
emitByte(OP_ARRAY);
|
||||
emitByte(1);
|
||||
}
|
||||
consume(TOKEN_RIGHT_BRACKET, "Unterminated array");
|
||||
}
|
||||
|
||||
static void compiler_index(bool canAssign) {
|
||||
expression();
|
||||
emitByte(OP_INDEX);
|
||||
if (check(TOKEN_COMMA)) {
|
||||
errorAtCurrent("Cannot get multiple values at once");
|
||||
}
|
||||
consume(TOKEN_RIGHT_BRACKET, "Unterminated index");
|
||||
}
|
||||
|
||||
static void hash_unary(bool canAssign) {
|
||||
expression();
|
||||
if (check(TOKEN_ROCKET)) {
|
||||
int numValues = 0;
|
||||
while (!check(TOKEN_EOF)) {
|
||||
consume(TOKEN_ROCKET, "=> must follow every key in a hash");
|
||||
expression();
|
||||
if (check(TOKEN_RIGHT_BRACE)) {
|
||||
break;
|
||||
}
|
||||
consume(TOKEN_COMMA, "Commas must follow every key-value pair in a hash except the last");
|
||||
expression();
|
||||
numValues++;
|
||||
if (numValues == 256) {
|
||||
fprintf(stderr, "[line %d] Error: Cannot have more than 256 key-value pairs in a hash literal", parser.current.line);
|
||||
}
|
||||
}
|
||||
emitByte(OP_HASH);
|
||||
emitByte(numValues + 1);
|
||||
} else {
|
||||
emitByte(OP_HASH);
|
||||
emitByte(1);
|
||||
}
|
||||
consume(TOKEN_RIGHT_BRACE, "Unterminated hash");
|
||||
}
|
||||
|
||||
ParseRule rules[] = { { grouping, NULL, PREC_CALL }, // TOKEN_LEFT_PAREN
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_RIGHT_PAREN
|
||||
{ hash_unary, NULL, PREC_CALL }, // TOKEN_LEFT_BRACE
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_RIGHT_BRACE
|
||||
{ array_unary, compiler_index, PREC_CALL }, // TOKEN_LEFT_BRACKET
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_RIGHT_BRACKET
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_COMMA
|
||||
{ NULL, NULL, PREC_CALL }, // TOKEN_DOT
|
||||
{ unary, binary, PREC_TERM }, // TOKEN_MINUS
|
||||
{ NULL, binary, PREC_TERM }, // TOKEN_PLUS
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_SEMICOLON
|
||||
{ NULL, binary, PREC_FACTOR }, // TOKEN_SLASH
|
||||
{ NULL, binary, PREC_FACTOR }, // TOKEN_STAR
|
||||
{ unary, NULL, PREC_NONE }, // TOKEN_BANG
|
||||
{ NULL, binary, PREC_EQUALITY }, // TOKEN_BANG_EQUAL
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_EQUAL
|
||||
{ NULL, binary, PREC_EQUALITY }, // TOKEN_EQUAL_EQUAL
|
||||
{ NULL, binary, PREC_COMPARISON }, // TOKEN_GREATER
|
||||
{ NULL, binary, PREC_COMPARISON }, // TOKEN_GREATER_EQUAL
|
||||
{ NULL, binary, PREC_COMPARISON }, // TOKEN_LESS
|
||||
{ NULL, binary, PREC_COMPARISON }, // TOKEN_LESS_EQUAL
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_ROCKET
|
||||
{ variable, NULL, PREC_NONE }, // TOKEN_IDENTIFIER
|
||||
{ string, NULL, PREC_NONE }, // TOKEN_STRING
|
||||
{ number, NULL, PREC_NONE }, // TOKEN_NUMBER
|
||||
{ NULL, and_, PREC_AND }, // TOKEN_AND
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_CLASS
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_ELSE
|
||||
{ literal, NULL, PREC_NONE }, // TOKEN_FALSE
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_FOR
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_FUN
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_IF
|
||||
{ literal, NULL, PREC_NONE }, // TOKEN_TRUE
|
||||
{ NULL, or_, PREC_OR }, // TOKEN_OR
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_PRINT
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_RETURN
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_SUPER
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_THIS
|
||||
{ literal, NULL, PREC_NONE }, // TOKEN_TRUE
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_VAR
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_WHILE
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_ERROR
|
||||
{ NULL, NULL, PREC_NONE }, // TOKEN_EOF
|
||||
};
|
||||
|
||||
static void parsePrecedence(Precedence precedence) {
|
||||
advance();
|
||||
ParseFn prefixRule = getRule(parser.previous.type)->prefix;
|
||||
if (prefixRule == NULL) {
|
||||
error("Expect expression.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool canAssign = precedence <= PREC_ASSIGNMENT;
|
||||
prefixRule(canAssign);
|
||||
|
||||
while (precedence <= getRule(parser.current.type)->precedence) {
|
||||
advance();
|
||||
ParseFn infixRule = getRule(parser.previous.type)->infix;
|
||||
infixRule(canAssign);
|
||||
}
|
||||
|
||||
if (canAssign && match(TOKEN_EQUAL)) {
|
||||
error("Invalid assignment target.");
|
||||
expression();
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t parseVariable(const char* errorMessage) {
|
||||
consume(TOKEN_IDENTIFIER, errorMessage);
|
||||
|
||||
declareVariable();
|
||||
if (current->scopeDepth > 0) return 0;
|
||||
|
||||
return identifierConstant(&parser.previous);
|
||||
}
|
||||
|
||||
static void markInitialized() {
|
||||
if (current->scopeDepth == 0) return;
|
||||
current->locals[current->localCount - 1].depth = current->scopeDepth;
|
||||
}
|
||||
|
||||
static void defineVariable(uint32_t global) {
|
||||
if (current->scopeDepth > 0) {
|
||||
markInitialized();
|
||||
return;
|
||||
}
|
||||
|
||||
if (global < 256) {
|
||||
emitBytes(OP_DEFINE_GLOBAL, global);
|
||||
} else {
|
||||
emitByte(OP_DEFINE_GLOBAL_LONG);
|
||||
emitByte(global & 0xFF0000 >> 16);
|
||||
emitByte(global & 0xFF00 >> 8);
|
||||
emitByte(global & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
static ParseRule* getRule(TokenType type) {
|
||||
return &rules[type];
|
||||
}
|
||||
|
||||
void expression() {
|
||||
parsePrecedence(PREC_ASSIGNMENT);
|
||||
}
|
||||
|
||||
static void block() {
|
||||
while (!check(TOKEN_RIGHT_BRACE) && !check(TOKEN_EOF)) {
|
||||
declaration();
|
||||
}
|
||||
|
||||
consume(TOKEN_RIGHT_BRACE, "Expect '}' after block.");
|
||||
}
|
||||
|
||||
static void expressionStatement() {
|
||||
expression();
|
||||
consume(TOKEN_SEMICOLON, "Expect ';' after expression.");
|
||||
emitByte(OP_POP);
|
||||
}
|
||||
|
||||
static void varDeclaration();
|
||||
|
||||
static void forStatement() {
|
||||
beginScope();
|
||||
|
||||
consume(TOKEN_LEFT_PAREN, "Expect '(' after 'for'.");
|
||||
if (match(TOKEN_VAR)) {
|
||||
varDeclaration();
|
||||
} else if (match(TOKEN_SEMICOLON)) {
|
||||
} else {
|
||||
expressionStatement();
|
||||
}
|
||||
|
||||
int loopStart = currentChunk()->count;
|
||||
|
||||
int exitJump = -1;
|
||||
if (!match(TOKEN_SEMICOLON)) {
|
||||
expression();
|
||||
consume(TOKEN_SEMICOLON, "Expect ';' after loop condition.");
|
||||
|
||||
// Jump out of the loop if the condition is false
|
||||
exitJump = emitJump(OP_JUMP_IF_FALSE);
|
||||
emitByte(OP_POP); // Condition.
|
||||
}
|
||||
|
||||
if (!match(TOKEN_RIGHT_PAREN)) {
|
||||
int bodyJump = emitJump(OP_JUMP);
|
||||
|
||||
int incrementStart = currentChunk()->count;
|
||||
expression();
|
||||
emitByte(OP_POP);
|
||||
consume(TOKEN_RIGHT_PAREN, "Expect ')' after for clauses.");
|
||||
|
||||
emitLoop(loopStart);
|
||||
loopStart = incrementStart;
|
||||
patchJump(bodyJump);
|
||||
}
|
||||
|
||||
statement();
|
||||
|
||||
emitLoop(loopStart);
|
||||
|
||||
if (exitJump != -1) {
|
||||
patchJump(exitJump);
|
||||
emitByte(OP_POP);
|
||||
}
|
||||
|
||||
endScope();
|
||||
}
|
||||
|
||||
static void ifStatement() {
|
||||
consume(TOKEN_LEFT_PAREN, "Expect '(' after 'if'.");
|
||||
expression();
|
||||
consume(TOKEN_RIGHT_PAREN, "Expect ')' after condition.");
|
||||
|
||||
int thenJump = emitJump(OP_JUMP_IF_FALSE);
|
||||
emitByte(OP_POP);
|
||||
statement();
|
||||
|
||||
int elseJump = emitJump(OP_JUMP);
|
||||
|
||||
patchJump(thenJump);
|
||||
|
||||
emitByte(OP_POP);
|
||||
|
||||
if (match(TOKEN_ELSE)) statement();
|
||||
|
||||
patchJump(elseJump);
|
||||
}
|
||||
|
||||
static void printStatement() {
|
||||
expression();
|
||||
consume(TOKEN_SEMICOLON, "Expect ';' after value.");
|
||||
emitByte(OP_PRINT);
|
||||
}
|
||||
|
||||
static void whileStatement() {
|
||||
int loopStart = currentChunk()->count;
|
||||
|
||||
consume(TOKEN_LEFT_PAREN, "Expect '(' after 'while'.");
|
||||
expression();
|
||||
consume(TOKEN_RIGHT_PAREN, "Expect ')' after condition.");
|
||||
|
||||
int exitJump = emitJump(OP_JUMP_IF_FALSE);
|
||||
|
||||
emitByte(OP_POP);
|
||||
statement();
|
||||
|
||||
emitLoop(loopStart);
|
||||
|
||||
patchJump(exitJump);
|
||||
emitByte(OP_POP);
|
||||
}
|
||||
|
||||
static void synchronize() {
|
||||
parser.panicMode = false;
|
||||
|
||||
while (parser.current.type != TOKEN_EOF) {
|
||||
if (parser.previous.type == TOKEN_SEMICOLON) return;
|
||||
|
||||
switch (parser.current.type) {
|
||||
case TOKEN_CLASS:
|
||||
case TOKEN_FUN:
|
||||
case TOKEN_VAR:
|
||||
case TOKEN_FOR:
|
||||
case TOKEN_IF:
|
||||
case TOKEN_WHILE:
|
||||
case TOKEN_PRINT:
|
||||
case TOKEN_RETURN:
|
||||
return;
|
||||
|
||||
default:
|
||||
// Do nothing.
|
||||
;
|
||||
}
|
||||
|
||||
advance();
|
||||
}
|
||||
}
|
||||
|
||||
static void statement() {
|
||||
if (match(TOKEN_PRINT)) {
|
||||
printStatement();
|
||||
} else if (match(TOKEN_FOR)) {
|
||||
forStatement();
|
||||
} else if (match(TOKEN_IF)) {
|
||||
ifStatement();
|
||||
} else if (match(TOKEN_WHILE)) {
|
||||
whileStatement();
|
||||
} else if (match(TOKEN_LEFT_BRACE)) {
|
||||
beginScope();
|
||||
block();
|
||||
endScope();
|
||||
} else {
|
||||
expressionStatement();
|
||||
}
|
||||
}
|
||||
|
||||
static void varDeclaration() {
|
||||
uint32_t global = parseVariable("Expect variable name.");
|
||||
|
||||
if (match(TOKEN_EQUAL)) {
|
||||
expression();
|
||||
} else {
|
||||
emitByte(OP_NIL);
|
||||
}
|
||||
consume(TOKEN_SEMICOLON, "Expect ';' after variable declaration.");
|
||||
|
||||
defineVariable(global);
|
||||
}
|
||||
|
||||
static void declaration() {
|
||||
if (match(TOKEN_VAR)) {
|
||||
varDeclaration();
|
||||
} else {
|
||||
statement();
|
||||
}
|
||||
|
||||
if (parser.panicMode) synchronize();
|
||||
}
|
||||
|
||||
bool compile(const char* source, Chunk* chunk, bool repl) {
|
||||
initScanner(source);
|
||||
Compiler* compiler = malloc(sizeof(Compiler));
|
||||
initCompiler(compiler);
|
||||
|
||||
compilingChunk = chunk;
|
||||
parser.hadError = false;
|
||||
parser.panicMode = false;
|
||||
|
||||
advance();
|
||||
if (repl && !scannerHasSemicolons()) {
|
||||
expression();
|
||||
emitByte(OP_PRINT);
|
||||
} else {
|
||||
while (!match(TOKEN_EOF)) {
|
||||
declaration();
|
||||
}
|
||||
}
|
||||
endCompiler();
|
||||
free(compiler);
|
||||
return !parser.hadError;
|
||||
}
|
8
compiler.h
Normal file
8
compiler.h
Normal file
@ -0,0 +1,8 @@
|
||||
#ifndef clox_compiler_h
|
||||
#define clox_compiler_h
|
||||
|
||||
#include "vm.h"
|
||||
|
||||
bool compile(const char* source, Chunk* chunk, bool repl);
|
||||
|
||||
#endif
|
142
debug.c
Normal file
142
debug.c
Normal file
@ -0,0 +1,142 @@
|
||||
#include "debug.h"
|
||||
#include "chunk.h"
|
||||
#include "common.h"
|
||||
#include "value.h"
|
||||
#include <stdio.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) {
|
||||
uint8_t constant = chunk->code[offset + 1];
|
||||
printf("%-16s %4d '", name, constant);
|
||||
printValue(chunk->constants.values[constant]);
|
||||
printf("'\n");
|
||||
return offset + 2;
|
||||
}
|
||||
|
||||
static int longConstantInstruction(const char* name, Chunk* chunk, int offset) {
|
||||
uint8_t constant_lo = chunk->code[offset + 3];
|
||||
uint8_t constant_mid = chunk->code[offset + 2];
|
||||
uint8_t constant_hi = chunk->code[offset + 1];
|
||||
int constant = constant_lo | (constant_mid << 8) | constant_hi << 16;
|
||||
printf("%-16s %9d '", name, constant);
|
||||
// printValue(chunk->constants.values[constant]);
|
||||
printf("'\n");
|
||||
return offset + 4;
|
||||
}
|
||||
|
||||
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) {
|
||||
uint8_t slot = chunk->code[offset + 1];
|
||||
printf("%-16s %4d\n", name, slot);
|
||||
return offset + 2;
|
||||
}
|
||||
|
||||
static int threeByteInstruction(const char* name, Chunk* chunk, int offset) {
|
||||
uint8_t slot_lo = chunk->code[offset + 3];
|
||||
uint8_t slot_mid = chunk->code[offset + 2];
|
||||
uint8_t slot_hi = chunk->code[offset + 1];
|
||||
int slot = slot_lo | (slot_mid << 8) | slot_hi << 16;
|
||||
printf("%-16s %9d\n", name, slot);
|
||||
return offset + 2;
|
||||
}
|
||||
|
||||
static int jumpInstruction(const char* name, int sign, Chunk* chunk, int offset) {
|
||||
uint16_t jump = (uint16_t) (chunk->code[offset + 2] << 8);
|
||||
jump |= chunk->code[offset + 1];
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
uint8_t instruction = chunk->code[offset];
|
||||
switch (instruction) {
|
||||
case OP_ARRAY:
|
||||
return byteInstruction("OP_ARRAY", chunk, offset);
|
||||
case OP_CONSTANT:
|
||||
return constantInstruction("OP_CONSTANT", chunk, offset);
|
||||
case OP_CONSTANT_LONG:
|
||||
return longConstantInstruction("OP_CONSTANT_LONG", chunk, offset);
|
||||
case OP_HASH:
|
||||
return byteInstruction("OP_HASH", 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_INDEX:
|
||||
return simpleInstruction("OP_INDEX", 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_LOCAL_LONG:
|
||||
return threeByteInstruction("OP_GET_LOCAL_LONG", chunk, offset);
|
||||
case OP_SET_LOCAL_LONG:
|
||||
return threeByteInstruction("OP_SET_LOCAL_LONG", chunk, offset);
|
||||
case OP_GET_GLOBAL:
|
||||
return constantInstruction("OP_GET_GLOBAL", chunk, offset);
|
||||
case OP_GET_GLOBAL_LONG:
|
||||
return longConstantInstruction("OP_GET_GLOBAL_LONG", chunk, offset);
|
||||
case OP_DEFINE_GLOBAL:
|
||||
return constantInstruction("OP_DEFINE_GLOBAL", chunk, offset);
|
||||
case OP_DEFINE_GLOBAL_LONG:
|
||||
return longConstantInstruction("OP_DEFINE_GLOBAL_LONG", chunk, offset);
|
||||
case OP_SET_GLOBAL:
|
||||
return constantInstruction("OP_SET_GLOBAL", chunk, offset);
|
||||
case OP_SET_GLOBAL_LONG:
|
||||
return longConstantInstruction("OP_SET_GLOBAL_LONG", 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_RETURN:
|
||||
return simpleInstruction("OP_RETURN", offset);
|
||||
default:
|
||||
printf("Unknown opcode %d\n", instruction);
|
||||
return offset + 1;
|
||||
}
|
||||
}
|
9
debug.h
Normal file
9
debug.h
Normal file
@ -0,0 +1,9 @@
|
||||
#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
|
78
main.c
Normal file
78
main.c
Normal file
@ -0,0 +1,78 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
line[strlen(line)-1]='\0';
|
||||
|
||||
interpret(line, true);
|
||||
}
|
||||
}
|
||||
|
||||
static char* readFile(const char* path) {
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
fprintf(stderr, "Could not open file \"%s\".\n", path);
|
||||
exit(74);
|
||||
}
|
||||
|
||||
fseek(file, 0L, SEEK_END);
|
||||
size_t fileSize = ftell(file);
|
||||
rewind(file);
|
||||
|
||||
char* buffer = (char*) malloc(fileSize + 1);
|
||||
if (buffer == NULL) {
|
||||
fprintf(stderr, "Not enough memory to read \"%s\".\n", path);
|
||||
exit(74);
|
||||
}
|
||||
|
||||
size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
|
||||
if (bytesRead < fileSize) {
|
||||
fprintf(stderr, "Could not read file \"%s\".\n", path);
|
||||
exit(74);
|
||||
}
|
||||
|
||||
buffer[bytesRead] = '\0';
|
||||
|
||||
fclose(file);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void runFile(const char* path) {
|
||||
char* source = readFile(path);
|
||||
InterpretResult result = interpret(source, false);
|
||||
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: clox [path]\n");
|
||||
exit(64);
|
||||
}
|
||||
|
||||
freeVM();
|
||||
return 0;
|
||||
}
|
45
memory.c
Normal file
45
memory.c
Normal file
@ -0,0 +1,45 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "memory.h"
|
||||
#include "vm.h"
|
||||
#include "value.h"
|
||||
|
||||
void* reallocate(void* previous, size_t oldSize, size_t newSize) {
|
||||
if (newSize == 0) {
|
||||
free(previous);
|
||||
return NULL;
|
||||
}
|
||||
return realloc(previous, newSize);
|
||||
}
|
||||
|
||||
static void freeObject(Obj* object) {
|
||||
switch (object->type) {
|
||||
case OBJ_STRING: {
|
||||
ObjString* string = (ObjString*) object;
|
||||
FREE_ARRAY(char, string->chars, string->length + 1);
|
||||
FREE(ObjString, object);
|
||||
break;
|
||||
}
|
||||
case OBJ_ARRAY: {
|
||||
ObjArray* array = (ObjArray*) object;
|
||||
freeValueArray(array->array);
|
||||
FREE(ObjArray, object);
|
||||
break;
|
||||
}
|
||||
case OBJ_HASH: {
|
||||
ObjHash* hash = (ObjHash*) object;
|
||||
freeTable(hash->hashTable);
|
||||
FREE(ObjHash, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void freeObjects() {
|
||||
Obj* object = vm.objects;
|
||||
while (object != NULL) {
|
||||
Obj* next = object->next;
|
||||
freeObject(object);
|
||||
object = next;
|
||||
}
|
||||
}
|
18
memory.h
Normal file
18
memory.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef clox_memory_h
|
||||
#define clox_memory_h
|
||||
|
||||
#include "object.h"
|
||||
|
||||
#define ALLOCATE(type, count) (type*)reallocate(NULL, 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(previous,type,oldCount,count) (type*)reallocate(previous,sizeof(type)*(oldCount),sizeof(type)*(count))
|
||||
|
||||
#define FREE_ARRAY(type,pointer,oldCount) reallocate(pointer,sizeof(type)*(oldCount),0)
|
||||
|
||||
void* reallocate(void* previous, size_t oldSize, size_t newSize);
|
||||
void freeObjects();
|
||||
|
||||
#endif
|
94
object.c
Normal file
94
object.c
Normal file
@ -0,0 +1,94 @@
|
||||
#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(NULL, 0, size);
|
||||
object->type = type;
|
||||
|
||||
object->next = vm.objects;
|
||||
vm.objects = object;
|
||||
return object;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
tableSet(&vm.strings, OBJ_VAL(string), NIL_VAL);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
static uint32_t hashString(const char* key, int length) {
|
||||
uint32_t hash = 2166136261u;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
hash ^= key[i];
|
||||
hash *= 16777619;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
ObjArray* takeArray(ValueArray* valArray) {
|
||||
ObjArray* array = ALLOCATE_OBJ(ObjArray, OBJ_ARRAY);
|
||||
array->array = valArray;
|
||||
return array;
|
||||
}
|
||||
|
||||
ObjHash* takeHash(Table* hashTable) {
|
||||
ObjHash* hash = ALLOCATE_OBJ(ObjHash, OBJ_HASH);
|
||||
hash->hashTable = hashTable;
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
ObjString* takeString(char* chars, int length) {
|
||||
uint32_t hash = hashString(chars, length);
|
||||
return allocateString(chars, length, hash);
|
||||
}
|
||||
|
||||
ObjString* copyString(const char* chars, int length) {
|
||||
uint32_t hash = hashString(chars, length);
|
||||
ObjString* interned = tableFindString(&vm.strings, chars, length, hash);
|
||||
if (interned != NULL) return interned;
|
||||
|
||||
char* heapChars = ALLOCATE(char, length + 1);
|
||||
memcpy(heapChars, chars, length);
|
||||
heapChars[length] = '\0';
|
||||
|
||||
return allocateString(heapChars, length, hash);
|
||||
}
|
||||
|
||||
void printObject(Value value) {
|
||||
switch (OBJ_TYPE(value)) {
|
||||
case OBJ_STRING:
|
||||
printf("%s", AS_CSTRING(value));
|
||||
break;
|
||||
case OBJ_ARRAY:
|
||||
printf("[");
|
||||
ValueArray* array=AS_VARRAY(value);
|
||||
for (int i=0;i<array->count;i++) {
|
||||
printValue(array->values[i]);
|
||||
if (i<(array->count-1)) {
|
||||
printf(", ");
|
||||
}
|
||||
}
|
||||
printf("]");
|
||||
break;
|
||||
case OBJ_HASH:
|
||||
printf("hash");
|
||||
break;
|
||||
}
|
||||
}
|
60
object.h
Normal file
60
object.h
Normal file
@ -0,0 +1,60 @@
|
||||
#ifndef clox_object_h
|
||||
#define clox_object_h
|
||||
|
||||
#include "common.h"
|
||||
#include "table.h"
|
||||
#include "value.h"
|
||||
|
||||
#define OBJ_TYPE(value) (AS_OBJ(value)->type)
|
||||
|
||||
#define IS_STRING(value) isObjType(value, OBJ_STRING)
|
||||
#define IS_ARRAY(value) isObjType(value, OBJ_ARRAY)
|
||||
|
||||
|
||||
#define AS_STRING(value) ((ObjString*)AS_OBJ(value))
|
||||
#define AS_CSTRING(value) (((ObjString*)AS_OBJ(value))->chars)
|
||||
|
||||
#define AS_ARRAY(value) ((ObjArray*)AS_OBJ(value))
|
||||
#define AS_VARRAY(value) (((ObjArray*)AS_OBJ(value))->array)
|
||||
#define AS_HASH(value) (((ObjHash*)AS_OBJ(value))->hashTable)
|
||||
|
||||
typedef enum {
|
||||
OBJ_STRING,
|
||||
OBJ_ARRAY,
|
||||
OBJ_HASH
|
||||
} ObjType;
|
||||
|
||||
struct sObj {
|
||||
ObjType type;
|
||||
struct sObj* next;
|
||||
};
|
||||
|
||||
struct sObjString {
|
||||
Obj obj;
|
||||
int length;
|
||||
char* chars;
|
||||
uint32_t hash;
|
||||
};
|
||||
|
||||
struct sObjArray {
|
||||
Obj obj;
|
||||
ValueArray* array;
|
||||
};
|
||||
|
||||
struct sObjHash {
|
||||
Obj obj;
|
||||
Table* hashTable;
|
||||
};
|
||||
|
||||
ObjArray* takeArray(ValueArray* valArray);
|
||||
ObjHash* takeHash(Table* hash);
|
||||
ObjString* takeString(char* chars, int length);
|
||||
ObjString* copyString(const char* chars, int length);
|
||||
void printObject(Value value);
|
||||
|
||||
static inline bool isObjType(Value value, ObjType type) {
|
||||
return IS_OBJ(value) && AS_OBJ(value)->type == type;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
261
scanner.c
Normal file
261
scanner.c
Normal file
@ -0,0 +1,261 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "scanner.h"
|
||||
|
||||
typedef struct {
|
||||
const char* start;
|
||||
const char* current;
|
||||
const char* source;
|
||||
int line;
|
||||
} Scanner;
|
||||
|
||||
Scanner scanner;
|
||||
|
||||
void initScanner(const char* source) {
|
||||
scanner.start = source;
|
||||
scanner.current = source;
|
||||
scanner.source = 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) {
|
||||
Token token;
|
||||
token.type = type;
|
||||
token.start = scanner.start;
|
||||
token.length = (int) (scanner.current - scanner.start);
|
||||
token.line = scanner.line;
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
static Token errorToken(const char* message) {
|
||||
Token token;
|
||||
token.type = TOKEN_ERROR;
|
||||
token.start = message;
|
||||
token.length = (int) strlen(message);
|
||||
token.line = scanner.line;
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
static void skipWhitespace() {
|
||||
for (;;) {
|
||||
char c = peek();
|
||||
switch (c) {
|
||||
case ' ':
|
||||
case '\r':
|
||||
case '\t':
|
||||
advance();
|
||||
break;
|
||||
case '\n':
|
||||
scanner.line++;
|
||||
advance();
|
||||
break;
|
||||
case '/':
|
||||
if (peekNext() == '/') {
|
||||
// A comment goes until the end of the line.
|
||||
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);
|
||||
}
|
||||
}
|
||||
break;
|
||||
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();
|
||||
|
||||
// Look for a fractional part.
|
||||
if (peek() == '.' && isDigit(peekNext())) {
|
||||
// Consume the "."
|
||||
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.");
|
||||
|
||||
// The closing ".
|
||||
advance();
|
||||
return makeToken(TOKEN_STRING);
|
||||
}
|
||||
|
||||
Token scanToken() {
|
||||
skipWhitespace();
|
||||
|
||||
scanner.start = scanner.current;
|
||||
|
||||
if (isAtEnd()) return makeToken(TOKEN_EOF);
|
||||
|
||||
char 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_LEFT_BRACKET);
|
||||
case ']':
|
||||
return makeToken(TOKEN_RIGHT_BRACKET);
|
||||
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 : (match('>') ? TOKEN_ROCKET : 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.");
|
||||
}
|
||||
|
||||
bool scannerHasSemicolons() {
|
||||
for (int i=0;i<strlen(scanner.source);i++) {
|
||||
if (scanner.source[i]==';') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
36
scanner.h
Normal file
36
scanner.h
Normal file
@ -0,0 +1,36 @@
|
||||
#ifndef clox_scanner_h
|
||||
#define clox_scanner_h
|
||||
|
||||
typedef enum {
|
||||
// Single-character tokens.
|
||||
TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE,
|
||||
TOKEN_LEFT_BRACKET, TOKEN_RIGHT_BRACKET, TOKEN_COMMA, TOKEN_DOT, TOKEN_MINUS,
|
||||
TOKEN_PLUS, TOKEN_SEMICOLON, TOKEN_SLASH, TOKEN_STAR,
|
||||
|
||||
// One or two character tokens.
|
||||
TOKEN_BANG, TOKEN_BANG_EQUAL, TOKEN_EQUAL, TOKEN_EQUAL_EQUAL, TOKEN_GREATER,
|
||||
TOKEN_GREATER_EQUAL, TOKEN_LESS, TOKEN_LESS_EQUAL, TOKEN_ROCKET,
|
||||
|
||||
// Literals.
|
||||
TOKEN_IDENTIFIER, TOKEN_STRING, TOKEN_NUMBER,
|
||||
|
||||
// Keywords.
|
||||
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();
|
||||
bool scannerHasSemicolons();
|
||||
|
||||
#endif
|
6
script.lox
Normal file
6
script.lox
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
var x=5;
|
||||
var y=7;
|
||||
var z=x+y;
|
||||
print z;
|
||||
}
|
139
table.c
Normal file
139
table.c
Normal file
@ -0,0 +1,139 @@
|
||||
#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 = NULL;
|
||||
}
|
||||
|
||||
void freeTable(Table* table) {
|
||||
FREE_ARRAY(Entry, table->entries, table->capacity);
|
||||
initTable(table);
|
||||
}
|
||||
|
||||
static Entry* findEntry(Entry* entries, int capacity, Value key) {
|
||||
uint32_t index = hashValue(key) % capacity;
|
||||
Entry* tombstone = NULL;
|
||||
|
||||
for (;;) {
|
||||
Entry* entry = &entries[index];
|
||||
|
||||
if (IS_EMPTY(entry->key)) {
|
||||
if (IS_NIL(entry->value)) {
|
||||
// Empty entry.
|
||||
return tombstone != NULL ? tombstone : entry;
|
||||
} else {
|
||||
// We found a tombstone.
|
||||
if (tombstone == NULL) tombstone = entry;
|
||||
}
|
||||
} else if (valuesEqual(key, entry->key)) {
|
||||
// We found the key.
|
||||
return entry;
|
||||
}
|
||||
|
||||
index = (index + 1) % capacity;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void adjustCapacity(Table* table, int capacity) {
|
||||
Entry* entries = ALLOCATE(Entry, capacity);
|
||||
for (int i = 0; i < capacity; i++) {
|
||||
entries[i].key = EMPTY_VAL;
|
||||
entries[i].value = NIL_VAL;
|
||||
}
|
||||
|
||||
table->count = 0;
|
||||
for (int i = 0; i < table->capacity; i++) {
|
||||
Entry* entry = &table->entries[i];
|
||||
if (IS_EMPTY(entry->key)) 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 tableGet(Table* table, Value key, Value* value) {
|
||||
if (table->entries == NULL) return false;
|
||||
|
||||
Entry* entry = findEntry(table->entries, table->capacity, key);
|
||||
if (IS_EMPTY(entry->key)) return false;
|
||||
|
||||
*value = entry->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tableSet(Table* table, Value key, Value value) {
|
||||
if (table->count + 1 > table->capacity * TABLE_MAX_LOAD) {
|
||||
int capacity = GROW_CAPACITY(table->capacity);
|
||||
adjustCapacity(table, capacity);
|
||||
}
|
||||
|
||||
Entry* entry = findEntry(table->entries, table->capacity, key);
|
||||
|
||||
bool isNewKey = IS_EMPTY(entry->key);
|
||||
if (isNewKey && IS_NIL(entry->value)) table->count++;
|
||||
|
||||
entry->key = key;
|
||||
entry->value = value;
|
||||
return isNewKey;
|
||||
}
|
||||
|
||||
bool tableDelete(Table* table,Value key) {
|
||||
if (table->count == 0) return false;
|
||||
|
||||
// Find the entry.
|
||||
Entry* entry = findEntry(table->entries, table->capacity, key);
|
||||
if (IS_EMPTY(entry->key)) return false;
|
||||
|
||||
// Place a tombstone in the entry.
|
||||
entry->key = EMPTY_VAL;
|
||||
entry->value = BOOL_VAL(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void tableAddAll(Table* from, Table* to) {
|
||||
for (int i = 0; i < from->capacity; i++) {
|
||||
Entry* entry = &from->entries[i];
|
||||
if (!IS_EMPTY(entry->key)) {
|
||||
tableSet(to, entry->key, entry->value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ObjString* tableFindString(Table* table, const char* chars, int length, uint32_t hash) {
|
||||
// If the table is empty, we definitely won't find it.
|
||||
if (table->entries == NULL) return NULL;
|
||||
|
||||
uint32_t index = hash % table->capacity;
|
||||
|
||||
for (;;) {
|
||||
Entry* entry = &table->entries[index];
|
||||
|
||||
if (IS_EMPTY(entry->key)) return NULL;
|
||||
ObjString* string = AS_STRING(entry->key);
|
||||
if (string->length == length && memcmp(string->chars, chars, length) == 0) {
|
||||
// We found it.
|
||||
return string;
|
||||
}
|
||||
|
||||
// Try the next slot.
|
||||
index = (index + 1) % table->capacity;
|
||||
}
|
||||
return NULL;
|
||||
}
|
26
table.h
Normal file
26
table.h
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef clox_table_h
|
||||
#define clox_table_h
|
||||
|
||||
#include "common.h"
|
||||
#include "value.h"
|
||||
|
||||
typedef struct {
|
||||
Value 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, Value key, Value* value);
|
||||
bool tableSet(Table* table, Value key, Value value);
|
||||
bool tableDelete(Table* table, Value key);
|
||||
void tableAddAll(Table* from, Table* to);
|
||||
ObjString* tableFindString(Table* table, const char* chars, int length, uint32_t hash);
|
||||
|
||||
#endif
|
136
value.c
Normal file
136
value.c
Normal file
@ -0,0 +1,136 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "object.h"
|
||||
#include "memory.h"
|
||||
#include "value.h"
|
||||
|
||||
void initValueArray(ValueArray* array) {
|
||||
array->values = NULL;
|
||||
array->capacity = 0;
|
||||
array->count = 0;
|
||||
}
|
||||
|
||||
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(array->values, Value, oldCapacity, array->capacity);
|
||||
}
|
||||
|
||||
array->values[array->count] = value;
|
||||
array->count++;
|
||||
}
|
||||
|
||||
void freeValueArray(ValueArray* array) {
|
||||
FREE_ARRAY(Value, array->values, array->capacity);
|
||||
initValueArray(array);
|
||||
}
|
||||
|
||||
void printValue(Value value) {
|
||||
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;
|
||||
case VAL_EMPTY:
|
||||
printf("<empty>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool valuesEqual(Value a, Value b) {
|
||||
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);
|
||||
}
|
||||
case VAL_EMPTY:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t hashDouble(double value) {
|
||||
union BitCast {
|
||||
double value;
|
||||
uint32_t ints[2];
|
||||
};
|
||||
|
||||
union BitCast cast;
|
||||
cast.value = (value) + 1.0;
|
||||
return cast.ints[0] + cast.ints[1];
|
||||
}
|
||||
|
||||
static uint32_t hashUint(unsigned int value) {
|
||||
uint8_t bytes[4];
|
||||
bytes[0] = value & 0xFF;
|
||||
bytes[1] = (value & 0xFF00) >> 8;
|
||||
bytes[2] = (value & 0xFF0000) >> 16;
|
||||
bytes[3] = (value & 0xFF000000) > 24;
|
||||
|
||||
uint32_t hash = 2166136261u;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
hash ^= bytes[i];
|
||||
hash *= 16777619;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32_t hashValue(Value value) {
|
||||
switch (value.type) {
|
||||
case VAL_BOOL:
|
||||
return AS_BOOL(value) ? 3 : 5;
|
||||
case VAL_NIL:
|
||||
return 7;
|
||||
case VAL_NUMBER:
|
||||
return hashDouble(AS_NUMBER(value));
|
||||
case VAL_OBJ: {
|
||||
Obj* object = AS_OBJ(value);
|
||||
switch (object->type) {
|
||||
case OBJ_STRING:
|
||||
return AS_STRING(value)->hash;
|
||||
break;
|
||||
case OBJ_ARRAY: { // TODO: Figure out how to properly hash an array
|
||||
ValueArray* valArray = AS_VARRAY(value);
|
||||
unsigned int sum = 0;
|
||||
for (int i = 0; i < valArray->count; i++) {
|
||||
uint32_t valHash = hashValue(valArray->values[i]);
|
||||
if ((UINT_MAX - sum) < valHash) {
|
||||
sum = hashUint(sum);
|
||||
}
|
||||
sum += valHash;
|
||||
}
|
||||
return sum;
|
||||
break;
|
||||
}
|
||||
case OBJ_HASH: // TODO: Figure out how to hash a hash
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VAL_EMPTY:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
53
value.h
Normal file
53
value.h
Normal file
@ -0,0 +1,53 @@
|
||||
#ifndef clox_value_h
|
||||
#define clox_value_h
|
||||
|
||||
#include "common.h"
|
||||
|
||||
typedef struct sObj Obj;
|
||||
typedef struct sObjString ObjString;
|
||||
typedef struct sObjArray ObjArray;
|
||||
typedef struct sObjHash ObjHash;
|
||||
|
||||
typedef enum {
|
||||
VAL_BOOL, VAL_NIL, VAL_NUMBER, VAL_OBJ, VAL_EMPTY
|
||||
} ValueType;
|
||||
|
||||
typedef struct {
|
||||
ValueType type;
|
||||
union {
|
||||
bool boolean;
|
||||
double number;
|
||||
Obj* obj;
|
||||
} as;
|
||||
} Value;
|
||||
|
||||
#define BOOL_VAL(value) ((Value){ VAL_BOOL, { .boolean = value } })
|
||||
#define NIL_VAL ((Value){ VAL_NIL, { .number = 0 } })
|
||||
#define NUMBER_VAL(value) ((Value){ VAL_NUMBER, { .number = value } })
|
||||
#define OBJ_VAL(object) ((Value){ VAL_OBJ, { .obj = (Obj*)object } })
|
||||
#define EMPTY_VAL ((Value){ VAL_EMPTY, { .number = 0 } })
|
||||
|
||||
#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 IS_EMPTY(value) ((value).type == VAL_EMPTY)
|
||||
|
||||
#define AS_OBJ(value) ((value).as.obj)
|
||||
#define AS_BOOL(value) ((value).as.boolean)
|
||||
#define AS_NUMBER(value) ((value).as.number)
|
||||
|
||||
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);
|
||||
uint32_t hashValue(Value value);
|
||||
|
||||
#endif
|
392
vm.c
Normal file
392
vm.c
Normal file
@ -0,0 +1,392 @@
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "compiler.h"
|
||||
#include "debug.h"
|
||||
#include "object.h"
|
||||
#include "memory.h"
|
||||
#include "table.h"
|
||||
#include "vm.h"
|
||||
|
||||
#define READ_BYTE() (*vm.ip++)
|
||||
#define READ_CONSTANT() (vm.chunk->constants.values[READ_BYTE()])
|
||||
#define READ_SHORT() (vm.ip += 2, (uint16_t)((vm.ip[-1] << 8) | vm.ip[-2]))
|
||||
#define READ_3BYTE() (vm.ip += 3, (uint32_t)((vm.ip[-1] << 16)| (vm.ip[-2] << 8) | vm.ip[-3]))
|
||||
#define READ_STRING() AS_STRING(READ_CONSTANT())
|
||||
#define READ_STRING_LONG() AS_STRING(readConstantLong())
|
||||
|
||||
#define BINARY_OP(valueType, op) \
|
||||
do { \
|
||||
if (!IS_NUMBER(peek(0)) || !IS_NUMBER(peek(1))) { \
|
||||
runtimeError("Operands must be numbers."); \
|
||||
return INTERPRET_RUNTIME_ERROR; \
|
||||
} \
|
||||
\
|
||||
double b = AS_NUMBER(pop()); \
|
||||
double a = AS_NUMBER(pop()); \
|
||||
push(valueType(a op b)); \
|
||||
} while (false)
|
||||
|
||||
VM vm;
|
||||
|
||||
static void resetStack() {
|
||||
vm.sp = 0;
|
||||
vm.capacity = 0;
|
||||
vm.stack = NULL;
|
||||
}
|
||||
|
||||
static void runtimeError(const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
fputs("\n", stderr);
|
||||
|
||||
size_t instruction = vm.ip - vm.chunk->code;
|
||||
fprintf(stderr, "[line %d] in script\n", vm.chunk->lines[instruction]);
|
||||
|
||||
resetStack();
|
||||
}
|
||||
|
||||
void initVM() {
|
||||
resetStack();
|
||||
vm.objects = NULL;
|
||||
|
||||
initTable(&vm.globals);
|
||||
initTable(&vm.strings);
|
||||
}
|
||||
|
||||
void freeVM() {
|
||||
freeTable(&vm.globals);
|
||||
freeTable(&vm.strings);
|
||||
freeObjects();
|
||||
}
|
||||
|
||||
void push(Value value) {
|
||||
if (vm.sp == vm.capacity) {
|
||||
int oldCapacity = vm.capacity;
|
||||
vm.capacity = GROW_CAPACITY(oldCapacity);
|
||||
vm.stack = GROW_ARRAY(vm.stack, Value, oldCapacity, vm.capacity);
|
||||
}
|
||||
vm.stack[vm.sp] = value;
|
||||
vm.sp++;
|
||||
}
|
||||
|
||||
Value pop() {
|
||||
vm.sp--;
|
||||
return vm.stack[vm.sp];
|
||||
}
|
||||
|
||||
Value peek(int distance) {
|
||||
return vm.stack[vm.sp - 1 - distance];
|
||||
}
|
||||
|
||||
static bool isFalsey(Value value) {
|
||||
return IS_NIL(value) || (IS_BOOL(value) && !AS_BOOL(value));
|
||||
}
|
||||
|
||||
static void concatenate() {
|
||||
ObjString* b = AS_STRING(pop());
|
||||
ObjString* a = AS_STRING(pop());
|
||||
|
||||
int 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);
|
||||
push(OBJ_VAL(result));
|
||||
}
|
||||
|
||||
static inline Value readConstantLong() {
|
||||
uint8_t constant_lo = READ_BYTE();
|
||||
uint8_t constant_mid = READ_BYTE();
|
||||
uint8_t constant_hi = READ_BYTE();
|
||||
int constant = constant_lo | (constant_mid << 8) | constant_hi << 16;
|
||||
return vm.chunk->constants.values[constant];
|
||||
}
|
||||
|
||||
static InterpretResult run() {
|
||||
for (;;) {
|
||||
#ifdef DEBUG_TRACE_EXECUTION
|
||||
printf(" ");
|
||||
for (int i = 0; i < vm.sp; i++) {
|
||||
printf("[ ");
|
||||
printValue(vm.stack[i]);
|
||||
printf(" ]");
|
||||
}
|
||||
printf("\n");
|
||||
disassembleInstruction(vm.chunk, (int) (vm.ip - vm.chunk->code));
|
||||
#endif
|
||||
uint8_t instruction;
|
||||
switch (instruction = READ_BYTE()) {
|
||||
case OP_ARRAY: {
|
||||
int num_values = READ_BYTE();
|
||||
Value* values = malloc(sizeof(Value) * num_values);
|
||||
for (int i = num_values - 1; i >= 0; i--) {
|
||||
values[i] = pop();
|
||||
}
|
||||
ValueArray* valArray = malloc(sizeof(ValueArray));
|
||||
initValueArray(valArray);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
writeValueArray(valArray, values[i]);
|
||||
}
|
||||
ObjArray* array = takeArray(valArray);
|
||||
push(OBJ_VAL(array));
|
||||
free(values);
|
||||
break;
|
||||
}
|
||||
case OP_HASH: {
|
||||
int num_values = READ_BYTE();
|
||||
Value* values = malloc(sizeof(Value) * num_values);
|
||||
Value* keys = malloc(sizeof(Value) * num_values);
|
||||
for (int i = num_values - 1; i >= 0; i--) {
|
||||
values[i] = pop();
|
||||
keys[i] = pop();
|
||||
}
|
||||
Table* hashTable = malloc(sizeof(Table));
|
||||
initTable(hashTable);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
tableSet(hashTable, keys[i], values[i]);
|
||||
}
|
||||
ObjHash* hash = takeHash(hashTable);
|
||||
push(OBJ_VAL(hash));
|
||||
free(values);
|
||||
free(keys);
|
||||
break;
|
||||
}
|
||||
case OP_CONSTANT: {
|
||||
Value constant = READ_CONSTANT();
|
||||
push(constant);
|
||||
break;
|
||||
}
|
||||
case OP_CONSTANT_LONG: {
|
||||
Value constant = readConstantLong();
|
||||
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_INDEX: {
|
||||
Value index = pop();
|
||||
Value val = pop();
|
||||
if (IS_OBJ(val)) {
|
||||
Obj* obj = AS_OBJ(val);
|
||||
switch (obj->type) {
|
||||
case OBJ_ARRAY: {
|
||||
ValueArray* valArray = AS_VARRAY(val);
|
||||
if (!IS_NUMBER(index)) {
|
||||
runtimeError("Array index must be a non-negative integer.");
|
||||
return INTERPRET_RUNTIME_ERROR;
|
||||
}
|
||||
double i_double = AS_NUMBER(index);
|
||||
if (i_double < 0) {
|
||||
runtimeError("Array index must be a non-negative integer.");
|
||||
return INTERPRET_RUNTIME_ERROR;
|
||||
}
|
||||
if ((int) i_double != i_double) {
|
||||
runtimeError("Array index must be a non-negative integer.");
|
||||
return INTERPRET_RUNTIME_ERROR;
|
||||
}
|
||||
int i = (int) i_double;
|
||||
if (i > valArray->count - 1) {
|
||||
runtimeError("Array index out of bounds.");
|
||||
return INTERPRET_RUNTIME_ERROR;
|
||||
}
|
||||
push(valArray->values[i]);
|
||||
break;
|
||||
}
|
||||
case OBJ_HASH: {
|
||||
Table* hashTable = AS_HASH(val);
|
||||
Value* value = malloc(sizeof(Value));
|
||||
if (tableGet(hashTable, index, value)) {
|
||||
push(*value);
|
||||
} else {
|
||||
push(NIL_VAL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
runtimeError("Cannot index value.");
|
||||
return INTERPRET_RUNTIME_ERROR;
|
||||
}
|
||||
} else {
|
||||
runtimeError("Cannot index value.");
|
||||
return INTERPRET_RUNTIME_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OP_GET_LOCAL: {
|
||||
uint8_t slot = READ_BYTE();
|
||||
push(vm.stack[slot]);
|
||||
break;
|
||||
}
|
||||
case OP_GET_LOCAL_LONG: {
|
||||
uint32_t slot = READ_3BYTE();
|
||||
push(vm.stack[slot]);
|
||||
break;
|
||||
}
|
||||
case OP_SET_LOCAL: {
|
||||
uint8_t slot = READ_BYTE();
|
||||
vm.stack[slot] = peek(0);
|
||||
break;
|
||||
}
|
||||
case OP_SET_LOCAL_LONG: {
|
||||
uint32_t slot = READ_3BYTE();
|
||||
vm.stack[slot] = peek(0);
|
||||
break;
|
||||
}
|
||||
case OP_GET_GLOBAL: {
|
||||
ObjString* name = READ_STRING();
|
||||
Value value;
|
||||
if (!tableGet(&vm.globals, OBJ_VAL(name), &value)) {
|
||||
runtimeError("Undefined variable '%s'.", name->chars);
|
||||
return INTERPRET_RUNTIME_ERROR;
|
||||
}
|
||||
push(value);
|
||||
break;
|
||||
}
|
||||
case OP_GET_GLOBAL_LONG: {
|
||||
ObjString* name = READ_STRING_LONG();
|
||||
Value value;
|
||||
if (!tableGet(&vm.globals, OBJ_VAL(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, OBJ_VAL(name), peek(0));
|
||||
pop();
|
||||
break;
|
||||
}
|
||||
case OP_DEFINE_GLOBAL_LONG: {
|
||||
ObjString* name = READ_STRING_LONG();
|
||||
tableSet(&vm.globals, OBJ_VAL(name), peek(0));
|
||||
pop();
|
||||
break;
|
||||
}
|
||||
case OP_SET_GLOBAL: {
|
||||
ObjString* name = READ_STRING();
|
||||
if (tableSet(&vm.globals, OBJ_VAL(name), peek(0))) {
|
||||
tableDelete(&vm.globals, OBJ_VAL(name));
|
||||
runtimeError("Undefined variable '%s'.", name->chars);
|
||||
return INTERPRET_RUNTIME_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OP_SET_GLOBAL_LONG: {
|
||||
ObjString* name = READ_STRING_LONG();
|
||||
if (tableSet(&vm.globals, OBJ_VAL(name), peek(0))) {
|
||||
tableDelete(&vm.globals, OBJ_VAL(name));
|
||||
runtimeError("Undefined variable '%s'.", name->chars);
|
||||
return INTERPRET_RUNTIME_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OP_EQUAL: {
|
||||
Value b = pop();
|
||||
Value 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))) {
|
||||
double b = AS_NUMBER(pop());
|
||||
double 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: {
|
||||
uint16_t offset = READ_SHORT();
|
||||
vm.ip += offset;
|
||||
break;
|
||||
}
|
||||
case OP_JUMP_IF_FALSE: {
|
||||
uint16_t offset = READ_SHORT();
|
||||
if (isFalsey(peek(0))) vm.ip += offset;
|
||||
break;
|
||||
}
|
||||
case OP_LOOP: {
|
||||
uint16_t offset = READ_SHORT();
|
||||
vm.ip -= offset;
|
||||
break;
|
||||
}
|
||||
case OP_RETURN:
|
||||
return INTERPRET_OK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return INTERPRET_RUNTIME_ERROR;
|
||||
}
|
||||
|
||||
InterpretResult interpret(const char* source, bool repl) {
|
||||
Chunk chunk;
|
||||
initChunk(&chunk);
|
||||
|
||||
if (!compile(source, &chunk, repl)) {
|
||||
freeChunk(&chunk);
|
||||
return INTERPRET_COMPILE_ERROR;
|
||||
}
|
||||
|
||||
vm.chunk = &chunk;
|
||||
vm.ip = vm.chunk->code;
|
||||
|
||||
InterpretResult result = run();
|
||||
|
||||
freeChunk(&chunk);
|
||||
return result;
|
||||
}
|
33
vm.h
Normal file
33
vm.h
Normal file
@ -0,0 +1,33 @@
|
||||
#ifndef clox_vm_h
|
||||
#define clox_vm_h
|
||||
|
||||
#include "chunk.h"
|
||||
#include "table.h"
|
||||
#include "value.h"
|
||||
|
||||
#define STACK_MAX 256
|
||||
|
||||
typedef struct {
|
||||
Chunk* chunk;
|
||||
uint8_t* ip;
|
||||
int sp;
|
||||
int capacity;
|
||||
Value* stack;
|
||||
Obj* objects;
|
||||
Table strings;
|
||||
Table globals;
|
||||
} VM;
|
||||
|
||||
typedef enum {
|
||||
INTERPRET_OK, INTERPRET_COMPILE_ERROR, INTERPRET_RUNTIME_ERROR
|
||||
} InterpretResult;
|
||||
|
||||
extern VM vm;
|
||||
|
||||
void initVM();
|
||||
void freeVM();
|
||||
InterpretResult interpret(const char* source, bool repl);
|
||||
void push(Value value);
|
||||
Value pop();
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user