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