Compare commits
106 commits
multiline-
...
master
Author | SHA1 | Date | |
---|---|---|---|
9080455fae | |||
b30eb232f6 | |||
8450ae6db8 | |||
69a9a95483 | |||
1bba129074 | |||
726803c9d0 | |||
62858b60e4 | |||
e91006b59a | |||
cb42c81121 | |||
7871383a40 | |||
389908c499 | |||
ac0f95683f | |||
07f691425c | |||
4e77cfa612 | |||
f4185577e5 | |||
f1e60bd641 | |||
dbca60fd23 | |||
a819a85209 | |||
63527558a7 | |||
ad6a98f6ad | |||
38e4c37f0f | |||
89ffc2e60a | |||
14b3866ac0 | |||
57ed9226c0 | |||
e74cbddb04 | |||
1ae50814d3 | |||
3da4016d57 | |||
baedf99f63 | |||
410f30ef93 | |||
44e47b89de | |||
ce478b0b80 | |||
a52c55eb9a | |||
9025da8168 | |||
37ac05af6d | |||
ba6ab6759e | |||
65275bde8a | |||
9de87f4495 | |||
42a7e26292 | |||
df1985ae84 | |||
26b418d254 | |||
7bd9934a4b | |||
fcbea5f617 | |||
aa41f26a26 | |||
8ec9137561 | |||
d5f352e577 | |||
60e752c0c5 | |||
c1cbc359e9 | |||
ba52787271 | |||
c10cbcdf99 | |||
6508cfd8fe | |||
3a02da2161 | |||
964859a8a1 | |||
0b89bd5de0 | |||
350a5cc4e3 | |||
eb6f33a2b2 | |||
4143259eb3 | |||
b131e343bb | |||
9b60cf334c | |||
276debcb35 | |||
2adc0cc33a | |||
73dfe7efa5 | |||
79c1056553 | |||
50473e5ab5 | |||
7fe0c7f639 | |||
2c007a8f94 | |||
a779473ced | |||
68a2ebd34f | |||
62bd0f83dc | |||
1dd608294a | |||
db403da4b2 | |||
958ba22a57 | |||
e62d1a209e | |||
21a58ffb20 | |||
591caeb216 | |||
580a5acb58 | |||
b04b6bcf2f | |||
715e60b87e | |||
4fbfa53dd7 | |||
e283991f90 | |||
0dbf155da5 | |||
4a71c219b5 | |||
922a8f6863 | |||
60b940f1f5 | |||
613c2388be | |||
2a72de9f81 | |||
3b9c44434e | |||
c62b73a247 | |||
ca9fd3ae3a | |||
57e87978ef | |||
ae33509615 | |||
899ecea236 | |||
8019f6aa41 | |||
055b5257fb | |||
2894c7ebfd | |||
000297dde1 | |||
bfed2d4dd3 | |||
2c2bae1b46 | |||
3ef60b7cf7 | |||
6bfbcb4850 | |||
c965dd4033 | |||
b31528d593 | |||
ee63307aee | |||
a78fe97f60 | |||
0960080128 | |||
f6cdbabe1c | |||
2e15e8bab8 |
63 changed files with 5063 additions and 58 deletions
4
.dir-locals.el
Normal file
4
.dir-locals.el
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
;;; Directory Local Variables
|
||||||
|
;;; For more information see (info "(emacs) Directory Variables")
|
||||||
|
|
||||||
|
((c-mode . ((ccls-args . '("--init={\"compilationDatabaseDirectory\": \"clox/_build\"}")))))
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
jlox.sum
|
||||||
|
jlox.log
|
||||||
|
|
||||||
|
.ccls-cache/
|
7
Dockerfile
Normal file
7
Dockerfile
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
FROM ubuntu:latest
|
||||||
|
|
||||||
|
RUN export DEBIAN_FRONTEND=noninteractive \
|
||||||
|
&& ln -fs /usr/share/zoneinfo/America/Vancouver /etc/localtime \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get upgrade -y \
|
||||||
|
&& apt-get install -y build-essential cmake default-jdk-headless
|
22
Jenkinsfile
vendored
Normal file
22
Jenkinsfile
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
pipeline {
|
||||||
|
agent { dockerfile true }
|
||||||
|
|
||||||
|
triggers {
|
||||||
|
pollSCM('H * * * *')
|
||||||
|
}
|
||||||
|
|
||||||
|
stages {
|
||||||
|
stage('Build') {
|
||||||
|
steps {
|
||||||
|
cmakeBuild installation: 'InSearchPath',
|
||||||
|
buildDir: 'build',
|
||||||
|
sourceDir: 'jlox',
|
||||||
|
steps: [[args: 'all']]
|
||||||
|
|
||||||
|
archiveArtifacts artifacts: 'jlox/_build/com/craftinginterpreters/lox/Lox.jar',
|
||||||
|
fingerprint: false,
|
||||||
|
onlyIfSuccessful: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
README.org
Normal file
7
README.org
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Set up environment with the proper packages:
|
||||||
|
|
||||||
|
: guix environment -m environment.scm
|
||||||
|
|
||||||
|
Run tests with:
|
||||||
|
|
||||||
|
: runtest --tool jlox --srcdir testsuite
|
2
clox/.gitignore
vendored
Normal file
2
clox/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
_build/
|
||||||
|
dist/
|
7
clox/CMakeLists.txt
Normal file
7
clox/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
cmake_minimum_required(VERSION 2.19)
|
||||||
|
|
||||||
|
project(Lox C)
|
||||||
|
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
add_subdirectory(src)
|
0
clox/src/.ccls
Normal file
0
clox/src/.ccls
Normal file
1
clox/src/.clang-format
Normal file
1
clox/src/.clang-format
Normal file
|
@ -0,0 +1 @@
|
||||||
|
BreakBeforeBinaryOperators: All
|
23
clox/src/CMakeLists.txt
Normal file
23
clox/src/CMakeLists.txt
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
add_executable(Lox
|
||||||
|
common.h
|
||||||
|
chunk.h
|
||||||
|
chunk.c
|
||||||
|
memory.h
|
||||||
|
memory.c
|
||||||
|
debug.h
|
||||||
|
debug.c
|
||||||
|
value.h
|
||||||
|
value.c
|
||||||
|
vm.h
|
||||||
|
vm.c
|
||||||
|
compiler.h
|
||||||
|
compiler.c
|
||||||
|
scanner.h
|
||||||
|
scanner.c
|
||||||
|
object.h
|
||||||
|
object.c
|
||||||
|
table.h
|
||||||
|
table.c
|
||||||
|
main.c)
|
||||||
|
|
||||||
|
install(TARGETS Lox)
|
41
clox/src/chunk.c
Normal file
41
clox/src/chunk.c
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "chunk.h"
|
||||||
|
#include "memory.h"
|
||||||
|
#include "vm.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(uint8_t, chunk->code, oldCapacity, chunk->capacity);
|
||||||
|
chunk->lines = GROW_ARRAY(int, chunk->lines, oldCapacity, chunk->capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk->code[chunk->count] = byte;
|
||||||
|
chunk->lines[chunk->count] = line;
|
||||||
|
chunk->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int addConstant(Chunk *chunk, Value value) {
|
||||||
|
push(value);
|
||||||
|
writeValueArray(&chunk->constants, value);
|
||||||
|
pop();
|
||||||
|
return chunk->constants.count - 1;
|
||||||
|
}
|
60
clox/src/chunk.h
Normal file
60
clox/src/chunk.h
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
#ifndef clox_chunk_h
|
||||||
|
#define clox_chunk_h
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "value.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OP_CONSTANT,
|
||||||
|
OP_NIL,
|
||||||
|
OP_TRUE,
|
||||||
|
OP_FALSE,
|
||||||
|
OP_POP,
|
||||||
|
OP_GET_LOCAL,
|
||||||
|
OP_SET_LOCAL,
|
||||||
|
OP_GET_GLOBAL,
|
||||||
|
OP_DEFINE_GLOBAL,
|
||||||
|
OP_SET_GLOBAL,
|
||||||
|
OP_GET_UPVALUE,
|
||||||
|
OP_SET_UPVALUE,
|
||||||
|
OP_GET_PROPERTY,
|
||||||
|
OP_SET_PROPERTY,
|
||||||
|
OP_GET_SUPER,
|
||||||
|
OP_EQUAL,
|
||||||
|
OP_GREATER,
|
||||||
|
OP_LESS,
|
||||||
|
OP_ADD,
|
||||||
|
OP_SUBTRACT,
|
||||||
|
OP_MULTIPLY,
|
||||||
|
OP_DIVIDE,
|
||||||
|
OP_NOT,
|
||||||
|
OP_NEGATE,
|
||||||
|
OP_PRINT,
|
||||||
|
OP_JUMP,
|
||||||
|
OP_JUMP_IF_FALSE,
|
||||||
|
OP_LOOP,
|
||||||
|
OP_CALL,
|
||||||
|
OP_INVOKE,
|
||||||
|
OP_SUPER_INVOKE,
|
||||||
|
OP_CLOSURE,
|
||||||
|
OP_CLOSE_UPVALUE,
|
||||||
|
OP_RETURN,
|
||||||
|
OP_CLASS,
|
||||||
|
OP_INHERIT,
|
||||||
|
OP_METHOD,
|
||||||
|
} OpCode;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int count;
|
||||||
|
int capacity;
|
||||||
|
uint8_t *code;
|
||||||
|
int *lines;
|
||||||
|
ValueArray constants;
|
||||||
|
} Chunk;
|
||||||
|
|
||||||
|
void initChunk(Chunk *chunk);
|
||||||
|
void freeChunk(Chunk *chunk);
|
||||||
|
void writeChunk(Chunk *chunk, uint8_t byte, int line);
|
||||||
|
int addConstant(Chunk *chunk, Value value);
|
||||||
|
|
||||||
|
#endif
|
17
clox/src/common.h
Normal file
17
clox/src/common.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef clox_common_h
|
||||||
|
#define clox_common_h
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define NAN_BOXING
|
||||||
|
/* #define DEBUG_PRINT_CODE */
|
||||||
|
/* #define DEBUG_TRACE_EXECUTION */
|
||||||
|
|
||||||
|
/* #define DEBUG_STRESS_GC */
|
||||||
|
/* #define DEBUG_LOG_GC */
|
||||||
|
|
||||||
|
#define UINT8_COUNT (UINT8_MAX + 1)
|
||||||
|
|
||||||
|
#endif
|
981
clox/src/compiler.c
Normal file
981
clox/src/compiler.c
Normal file
|
@ -0,0 +1,981 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "compiler.h"
|
||||||
|
#include "memory.h"
|
||||||
|
#include "scanner.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG_PRINT_CODE
|
||||||
|
#include "debug.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Token current;
|
||||||
|
Token previous;
|
||||||
|
bool hadError;
|
||||||
|
bool panicMode;
|
||||||
|
} Parser;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PREC_NONE,
|
||||||
|
PREC_ASSIGNMENT, /* = */
|
||||||
|
PREC_OR, /* 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;
|
||||||
|
bool isCaptured;
|
||||||
|
} Local;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t index;
|
||||||
|
bool isLocal;
|
||||||
|
} Upvalue;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TYPE_FUNCTION,
|
||||||
|
TYPE_INITIALIZER,
|
||||||
|
TYPE_METHOD,
|
||||||
|
TYPE_SCRIPT
|
||||||
|
} FunctionType;
|
||||||
|
|
||||||
|
typedef struct Compiler {
|
||||||
|
struct Compiler *enclosing;
|
||||||
|
ObjFunction *function;
|
||||||
|
FunctionType type;
|
||||||
|
|
||||||
|
Local locals[UINT8_COUNT];
|
||||||
|
int localCount;
|
||||||
|
Upvalue upvalues[UINT8_COUNT];
|
||||||
|
int scopeDepth;
|
||||||
|
} Compiler;
|
||||||
|
|
||||||
|
typedef struct ClassCompiler {
|
||||||
|
struct ClassCompiler *enclosing;
|
||||||
|
bool hasSuperclass;
|
||||||
|
} ClassCompiler;
|
||||||
|
|
||||||
|
static int resolveUpvalue(Compiler *, Token *);
|
||||||
|
|
||||||
|
Parser parser;
|
||||||
|
Compiler *current = NULL;
|
||||||
|
ClassCompiler *currentClass = NULL;
|
||||||
|
Chunk *compilingChunk;
|
||||||
|
|
||||||
|
static Chunk *currentChunk() { return ¤t->function->chunk; }
|
||||||
|
|
||||||
|
static void errorAt(Token *token, const char *message) {
|
||||||
|
if (parser.panicMode)
|
||||||
|
return;
|
||||||
|
parser.panicMode = true;
|
||||||
|
fprintf(stderr, "[line %d] Error", token->line);
|
||||||
|
|
||||||
|
if (token->type == TOKEN_EOF) {
|
||||||
|
fprintf(stderr, " at end");
|
||||||
|
} else if (token->type == TOKEN_ERROR) {
|
||||||
|
/* Nothing */
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, " at '%.*s'", token->length, token->start);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, ": %s\n", message);
|
||||||
|
parser.hadError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void error(const char *message) { errorAt(&parser.previous, message); }
|
||||||
|
|
||||||
|
static void errorAtCurrent(const char *message) {
|
||||||
|
errorAt(&parser.current, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void advance() {
|
||||||
|
parser.previous = parser.current;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
parser.current = scanToken();
|
||||||
|
if (parser.current.type != TOKEN_ERROR)
|
||||||
|
break;
|
||||||
|
|
||||||
|
errorAtCurrent(parser.current.start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void consume(TokenType type, const char *message) {
|
||||||
|
if (parser.current.type == type) {
|
||||||
|
advance();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorAtCurrent(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check(TokenType type) { return parser.current.type == type; }
|
||||||
|
|
||||||
|
static bool match(TokenType type) {
|
||||||
|
if (!check(type))
|
||||||
|
return false;
|
||||||
|
advance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitByte(uint8_t byte) {
|
||||||
|
writeChunk(currentChunk(), byte, parser.previous.line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitBytes(uint8_t byte1, uint8_t byte2) {
|
||||||
|
emitByte(byte1);
|
||||||
|
emitByte(byte2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitLoop(int loopStart) {
|
||||||
|
emitByte(OP_LOOP);
|
||||||
|
|
||||||
|
int offset = currentChunk()->count - loopStart + 2;
|
||||||
|
if (offset > UINT16_MAX)
|
||||||
|
error("Loop body too large.");
|
||||||
|
|
||||||
|
emitByte((offset >> 8) & 0xff);
|
||||||
|
emitByte(offset & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int emitJump(uint8_t instruction) {
|
||||||
|
emitByte(instruction);
|
||||||
|
emitByte(0xff);
|
||||||
|
emitByte(0xff);
|
||||||
|
return currentChunk()->count - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitReturn() {
|
||||||
|
if (current->type == TYPE_INITIALIZER) {
|
||||||
|
emitBytes(OP_GET_LOCAL, 0);
|
||||||
|
} else {
|
||||||
|
emitByte(OP_NIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
emitByte(OP_RETURN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t makeConstant(Value value) {
|
||||||
|
int constant = addConstant(currentChunk(), value);
|
||||||
|
if (constant > UINT8_MAX) {
|
||||||
|
error("Too many constants in one chunk.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint8_t)constant;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emitConstant(Value value) {
|
||||||
|
emitBytes(OP_CONSTANT, makeConstant(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void patchJump(int offset) {
|
||||||
|
// -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] = (jump >> 8) & 0xff;
|
||||||
|
currentChunk()->code[offset + 1] = jump & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initCompiler(Compiler *compiler, FunctionType type) {
|
||||||
|
compiler->enclosing = current;
|
||||||
|
compiler->function = NULL;
|
||||||
|
compiler->type = type;
|
||||||
|
compiler->localCount = 0;
|
||||||
|
compiler->scopeDepth = 0;
|
||||||
|
compiler->function = newFunction();
|
||||||
|
current = compiler;
|
||||||
|
if (type != TYPE_SCRIPT) {
|
||||||
|
current->function->name
|
||||||
|
= copyString(parser.previous.start, parser.previous.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local *local = ¤t->locals[current->localCount++];
|
||||||
|
local->depth = 0;
|
||||||
|
local->isCaptured = false;
|
||||||
|
if (type != TYPE_FUNCTION) {
|
||||||
|
local->name.start = "this";
|
||||||
|
local->name.length = 4;
|
||||||
|
} else {
|
||||||
|
local->name.start = "";
|
||||||
|
local->name.length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ObjFunction *endCompiler() {
|
||||||
|
emitReturn();
|
||||||
|
ObjFunction *function = current->function;
|
||||||
|
|
||||||
|
#ifdef DEBUG_PRINT_CODE
|
||||||
|
if (!parser.hadError) {
|
||||||
|
disassembleChunk(currentChunk(), function->name != NULL
|
||||||
|
? function->name->chars
|
||||||
|
: "<script>");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
current = current->enclosing;
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void beginScope() { current->scopeDepth++; }
|
||||||
|
|
||||||
|
static void endScope() {
|
||||||
|
current->scopeDepth--;
|
||||||
|
|
||||||
|
while (current->localCount > 0
|
||||||
|
&& current->locals[current->localCount - 1].depth
|
||||||
|
> current->scopeDepth) {
|
||||||
|
if (current->locals[current->localCount - 1].isCaptured) {
|
||||||
|
emitByte(OP_CLOSE_UPVALUE);
|
||||||
|
} else {
|
||||||
|
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 uint8_t parseVariable(const char *name);
|
||||||
|
static void defineVariable(uint8_t global);
|
||||||
|
static uint8_t identifierConstant(Token *name);
|
||||||
|
static int resolveLocal(Compiler *compiler, Token *name);
|
||||||
|
|
||||||
|
static void binary(bool canAssign) {
|
||||||
|
TokenType operatorType = parser.previous.type;
|
||||||
|
ParseRule *rule = getRule(operatorType);
|
||||||
|
parsePrecedence((Precedence)(rule->precedence + 1));
|
||||||
|
|
||||||
|
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 uint8_t argumentList() {
|
||||||
|
uint8_t argCount = 0;
|
||||||
|
if (!check(TOKEN_RIGHT_PAREN)) {
|
||||||
|
do {
|
||||||
|
expression();
|
||||||
|
if (argCount == 255) {
|
||||||
|
error("Can't have more than 255 arguments.");
|
||||||
|
}
|
||||||
|
argCount++;
|
||||||
|
} while (match(TOKEN_COMMA));
|
||||||
|
}
|
||||||
|
consume(TOKEN_RIGHT_PAREN, "Expect ')' after arguments.");
|
||||||
|
return argCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void call(bool canAssign) {
|
||||||
|
uint8_t argCount = argumentList();
|
||||||
|
emitBytes(OP_CALL, argCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dot(bool canAssign) {
|
||||||
|
consume(TOKEN_IDENTIFIER, "Expect property name after '.'.");
|
||||||
|
uint8_t name = identifierConstant(&parser.previous);
|
||||||
|
|
||||||
|
if (canAssign && match(TOKEN_EQUAL)) {
|
||||||
|
expression();
|
||||||
|
emitBytes(OP_SET_PROPERTY, name);
|
||||||
|
} else if (match(TOKEN_LEFT_PAREN)) {
|
||||||
|
uint8_t argCount = argumentList();
|
||||||
|
emitBytes(OP_INVOKE, name);
|
||||||
|
emitByte(argCount);
|
||||||
|
} else {
|
||||||
|
emitBytes(OP_GET_PROPERTY, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 function(FunctionType type) {
|
||||||
|
Compiler compiler;
|
||||||
|
initCompiler(&compiler, type);
|
||||||
|
beginScope();
|
||||||
|
|
||||||
|
consume(TOKEN_LEFT_PAREN, "Expect '(' after function name.");
|
||||||
|
if (!check(TOKEN_RIGHT_PAREN)) {
|
||||||
|
do {
|
||||||
|
current->function->arity++;
|
||||||
|
if (current->function->arity > 255) {
|
||||||
|
errorAtCurrent("Can't have more than 250 parameters.");
|
||||||
|
}
|
||||||
|
uint8_t constant = parseVariable("Expect parameter name.");
|
||||||
|
defineVariable(constant);
|
||||||
|
} while (match(TOKEN_COMMA));
|
||||||
|
}
|
||||||
|
consume(TOKEN_RIGHT_PAREN, "Expect ')' after parameters.");
|
||||||
|
consume(TOKEN_LEFT_BRACE, "Expect '{' before function body.");
|
||||||
|
block();
|
||||||
|
|
||||||
|
ObjFunction *function = endCompiler();
|
||||||
|
emitBytes(OP_CLOSURE, makeConstant(OBJ_VAL(function)));
|
||||||
|
|
||||||
|
for (int i = 0; i < function->upvalueCount; i++) {
|
||||||
|
emitByte(compiler.upvalues[i].isLocal ? 1 : 0);
|
||||||
|
emitByte(compiler.upvalues[i].index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void method() {
|
||||||
|
consume(TOKEN_IDENTIFIER, "Expect method name.");
|
||||||
|
uint8_t constant = identifierConstant(&parser.previous);
|
||||||
|
|
||||||
|
FunctionType type = TYPE_METHOD;
|
||||||
|
if (parser.previous.length == 4
|
||||||
|
&& memcmp(parser.previous.start, "init", 4) == 0) {
|
||||||
|
type = TYPE_INITIALIZER;
|
||||||
|
}
|
||||||
|
|
||||||
|
function(type);
|
||||||
|
emitBytes(OP_METHOD, constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool identifiersEqual(Token *a, Token *b) {
|
||||||
|
if (a->length != b->length)
|
||||||
|
return false;
|
||||||
|
return memcmp(a->start, b->start, a->length) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void addLocal(Token name) {
|
||||||
|
if (current->localCount == UINT8_COUNT) {
|
||||||
|
error("Too many local variables in function.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Local *local = ¤t->locals[current->localCount++];
|
||||||
|
local->name = name;
|
||||||
|
local->depth = -1;
|
||||||
|
local->isCaptured = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void declareVariable() {
|
||||||
|
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("Already a variable with this name in this scope.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addLocal(*name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void namedVariable(Token name, bool canAssign) {
|
||||||
|
uint8_t getOp, setOp;
|
||||||
|
int arg = resolveLocal(current, &name);
|
||||||
|
|
||||||
|
if (arg != -1) {
|
||||||
|
getOp = OP_GET_LOCAL;
|
||||||
|
setOp = OP_SET_LOCAL;
|
||||||
|
} else if ((arg = resolveUpvalue(current, &name)) != -1) {
|
||||||
|
getOp = OP_GET_UPVALUE;
|
||||||
|
setOp = OP_SET_UPVALUE;
|
||||||
|
} else {
|
||||||
|
arg = identifierConstant(&name);
|
||||||
|
getOp = OP_GET_GLOBAL;
|
||||||
|
setOp = OP_SET_GLOBAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canAssign && match(TOKEN_EQUAL)) {
|
||||||
|
expression();
|
||||||
|
emitBytes(setOp, arg);
|
||||||
|
} else {
|
||||||
|
emitBytes(getOp, arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void variable(bool canAssign) {
|
||||||
|
namedVariable(parser.previous, canAssign);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token syntheticToken(const char *text) {
|
||||||
|
Token token;
|
||||||
|
token.start = text;
|
||||||
|
token.length = (int)strlen(text);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void super_(bool canAssign) {
|
||||||
|
if (currentClass == NULL) {
|
||||||
|
error("Can't use 'super' outside of a class.");
|
||||||
|
} else if (!currentClass->hasSuperclass) {
|
||||||
|
error("Can't use 'super' in a class with no superclass.");
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(TOKEN_DOT, "Expect '.' after 'super'.");
|
||||||
|
consume(TOKEN_IDENTIFIER, "Expect superclass method name.");
|
||||||
|
uint8_t name = identifierConstant(&parser.previous);
|
||||||
|
|
||||||
|
namedVariable(syntheticToken("this"), false);
|
||||||
|
if (match(TOKEN_LEFT_PAREN)) {
|
||||||
|
uint8_t argCount = argumentList();
|
||||||
|
namedVariable(syntheticToken("super"), false);
|
||||||
|
emitBytes(OP_SUPER_INVOKE, name);
|
||||||
|
emitByte(argCount);
|
||||||
|
} else {
|
||||||
|
namedVariable(syntheticToken("super"), false);
|
||||||
|
emitBytes(OP_GET_SUPER, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void classDeclaration() {
|
||||||
|
consume(TOKEN_IDENTIFIER, "Expect class name");
|
||||||
|
Token className = parser.previous;
|
||||||
|
uint8_t nameConstant = identifierConstant(&parser.previous);
|
||||||
|
declareVariable();
|
||||||
|
|
||||||
|
emitBytes(OP_CLASS, nameConstant);
|
||||||
|
defineVariable(nameConstant);
|
||||||
|
|
||||||
|
ClassCompiler classCompiler;
|
||||||
|
classCompiler.hasSuperclass = false;
|
||||||
|
classCompiler.enclosing = currentClass;
|
||||||
|
currentClass = &classCompiler;
|
||||||
|
|
||||||
|
if (match(TOKEN_LESS)) {
|
||||||
|
consume(TOKEN_IDENTIFIER, "Expect superclass name.");
|
||||||
|
variable(false);
|
||||||
|
|
||||||
|
if (identifiersEqual(&className, &parser.previous)) {
|
||||||
|
error("A class can't inherit from itself.");
|
||||||
|
}
|
||||||
|
|
||||||
|
beginScope();
|
||||||
|
addLocal(syntheticToken("super"));
|
||||||
|
defineVariable(0);
|
||||||
|
|
||||||
|
namedVariable(className, false);
|
||||||
|
emitByte(OP_INHERIT);
|
||||||
|
classCompiler.hasSuperclass = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
namedVariable(className, false);
|
||||||
|
consume(TOKEN_LEFT_BRACE, "Expect '{' before class body.");
|
||||||
|
while (!check(TOKEN_RIGHT_BRACE) && !check(TOKEN_EOF)) {
|
||||||
|
method();
|
||||||
|
}
|
||||||
|
consume(TOKEN_RIGHT_BRACE, "Expect '}' after class body.");
|
||||||
|
emitByte(OP_POP);
|
||||||
|
|
||||||
|
if (classCompiler.hasSuperclass) {
|
||||||
|
endScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentClass = currentClass->enclosing;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void markInitialized() {
|
||||||
|
if (current->scopeDepth == 0)
|
||||||
|
return;
|
||||||
|
current->locals[current->localCount - 1].depth = current->scopeDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void funDeclaration() {
|
||||||
|
uint8_t global = parseVariable("Expect function name.");
|
||||||
|
markInitialized();
|
||||||
|
function(TYPE_FUNCTION);
|
||||||
|
defineVariable(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void varDeclaration() {
|
||||||
|
uint8_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 expressionStatement() {
|
||||||
|
expression();
|
||||||
|
consume(TOKEN_SEMICOLON, "Expect ';' after expression.");
|
||||||
|
emitByte(OP_POP);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void forStatement() {
|
||||||
|
beginScope();
|
||||||
|
consume(TOKEN_LEFT_PAREN, "Expect '(' after 'for'.");
|
||||||
|
if (match(TOKEN_SEMICOLON)) {
|
||||||
|
// No initializer
|
||||||
|
} else if (match(TOKEN_VAR)) {
|
||||||
|
varDeclaration();
|
||||||
|
} 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); /* Condition. */
|
||||||
|
}
|
||||||
|
|
||||||
|
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 returnStatement() {
|
||||||
|
if (current->type == TYPE_SCRIPT) {
|
||||||
|
error("Can't return from top-level code.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match(TOKEN_SEMICOLON)) {
|
||||||
|
emitReturn();
|
||||||
|
} else {
|
||||||
|
if (current->type == TYPE_INITIALIZER) {
|
||||||
|
error("Can't return a value from an initializer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
expression();
|
||||||
|
consume(TOKEN_SEMICOLON, "Expect ';' after return value.");
|
||||||
|
emitByte(OP_RETURN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 declaration() {
|
||||||
|
if (match(TOKEN_CLASS)) {
|
||||||
|
classDeclaration();
|
||||||
|
} else if (match(TOKEN_FUN)) {
|
||||||
|
funDeclaration();
|
||||||
|
} else if (match(TOKEN_VAR)) {
|
||||||
|
varDeclaration();
|
||||||
|
} else {
|
||||||
|
statement();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser.panicMode)
|
||||||
|
synchronize();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void statement() {
|
||||||
|
if (match(TOKEN_PRINT)) {
|
||||||
|
printStatement();
|
||||||
|
} else if (match(TOKEN_FOR)) {
|
||||||
|
forStatement();
|
||||||
|
} else if (match(TOKEN_IF)) {
|
||||||
|
ifStatement();
|
||||||
|
} else if (match(TOKEN_RETURN)) {
|
||||||
|
returnStatement();
|
||||||
|
} else if (match(TOKEN_WHILE)) {
|
||||||
|
whileStatement();
|
||||||
|
} else if (match(TOKEN_LEFT_BRACE)) {
|
||||||
|
beginScope();
|
||||||
|
block();
|
||||||
|
endScope();
|
||||||
|
} else {
|
||||||
|
expressionStatement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void number(bool canAssign) {
|
||||||
|
double value = strtod(parser.previous.start, NULL);
|
||||||
|
emitConstant(NUMBER_VAL(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void and_(bool canAssign) {
|
||||||
|
int endJump = emitJump(OP_JUMP_IF_FALSE);
|
||||||
|
|
||||||
|
emitByte(OP_POP);
|
||||||
|
parsePrecedence(PREC_AND);
|
||||||
|
|
||||||
|
patchJump(endJump);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 this_(bool canAssign) {
|
||||||
|
if (currentClass == NULL) {
|
||||||
|
error("Can't use 'this' outside of a class.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
variable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 void grouping(bool canAssign) {
|
||||||
|
expression();
|
||||||
|
consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseRule rules[] = {
|
||||||
|
[TOKEN_LEFT_PAREN] = {grouping, call, PREC_CALL},
|
||||||
|
[TOKEN_RIGHT_PAREN] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_LEFT_BRACE] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_RIGHT_BRACE] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_COMMA] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_DOT] = {NULL, dot, PREC_CALL},
|
||||||
|
[TOKEN_MINUS] = {unary, binary, PREC_TERM},
|
||||||
|
[TOKEN_PLUS] = {NULL, binary, PREC_TERM},
|
||||||
|
[TOKEN_SEMICOLON] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_SLASH] = {NULL, binary, PREC_FACTOR},
|
||||||
|
[TOKEN_STAR] = {NULL, binary, PREC_FACTOR},
|
||||||
|
[TOKEN_BANG] = {unary, NULL, PREC_NONE},
|
||||||
|
[TOKEN_BANG_EQUAL] = {NULL, binary, PREC_EQUALITY},
|
||||||
|
[TOKEN_EQUAL] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_EQUAL_EQUAL] = {NULL, binary, PREC_EQUALITY},
|
||||||
|
[TOKEN_GREATER] = {NULL, binary, PREC_COMPARISON},
|
||||||
|
[TOKEN_GREATER_EQUAL] = {NULL, binary, PREC_COMPARISON},
|
||||||
|
[TOKEN_LESS] = {NULL, binary, PREC_COMPARISON},
|
||||||
|
[TOKEN_LESS_EQUAL] = {NULL, binary, PREC_COMPARISON},
|
||||||
|
[TOKEN_IDENTIFIER] = {variable, NULL, PREC_NONE},
|
||||||
|
[TOKEN_STRING] = {string, NULL, PREC_NONE},
|
||||||
|
[TOKEN_NUMBER] = {number, NULL, PREC_NONE},
|
||||||
|
[TOKEN_AND] = {NULL, and_, PREC_AND},
|
||||||
|
[TOKEN_CLASS] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_ELSE] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_FALSE] = {literal, NULL, PREC_NONE},
|
||||||
|
[TOKEN_FOR] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_FUN] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_IF] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_NIL] = {literal, NULL, PREC_NONE},
|
||||||
|
[TOKEN_OR] = {NULL, or_, PREC_OR},
|
||||||
|
[TOKEN_PRINT] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_RETURN] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_SUPER] = {super_, NULL, PREC_NONE},
|
||||||
|
[TOKEN_THIS] = {this_, NULL, PREC_NONE},
|
||||||
|
[TOKEN_TRUE] = {literal, NULL, PREC_NONE},
|
||||||
|
[TOKEN_VAR] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_WHILE] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_ERROR] = {NULL, NULL, PREC_NONE},
|
||||||
|
[TOKEN_EOF] = {NULL, NULL, PREC_NONE},
|
||||||
|
};
|
||||||
|
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t identifierConstant(Token *name) {
|
||||||
|
return makeConstant(OBJ_VAL(copyString(name->start, name->length)));
|
||||||
|
}
|
||||||
|
|
||||||
|
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("Can't read local variable in its own initializer.");
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int addUpvalue(Compiler *compiler, uint8_t index, bool isLocal) {
|
||||||
|
int upvalueCount = compiler->function->upvalueCount;
|
||||||
|
|
||||||
|
for (int i = 0; i < upvalueCount; i++) {
|
||||||
|
Upvalue *upvalue = &compiler->upvalues[i];
|
||||||
|
if (upvalue->index == index && upvalue->isLocal == isLocal) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upvalueCount == UINT8_COUNT) {
|
||||||
|
error("Too many closure variables in function.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
compiler->upvalues[upvalueCount].isLocal = isLocal;
|
||||||
|
compiler->upvalues[upvalueCount].index = index;
|
||||||
|
return compiler->function->upvalueCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int resolveUpvalue(Compiler *compiler, Token *name) {
|
||||||
|
if (compiler->enclosing == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
int local = resolveLocal(compiler->enclosing, name);
|
||||||
|
if (local != -1) {
|
||||||
|
compiler->enclosing->locals[local].isCaptured = true;
|
||||||
|
return addUpvalue(compiler, (uint8_t)local, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int upvalue = resolveUpvalue(compiler->enclosing, name);
|
||||||
|
if (upvalue != -1) {
|
||||||
|
return addUpvalue(compiler, (uint8_t)upvalue, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t parseVariable(const char *errorMessage) {
|
||||||
|
consume(TOKEN_IDENTIFIER, errorMessage);
|
||||||
|
|
||||||
|
declareVariable();
|
||||||
|
if (current->scopeDepth > 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return identifierConstant(&parser.previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void defineVariable(uint8_t global) {
|
||||||
|
if (current->scopeDepth > 0) {
|
||||||
|
markInitialized();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitBytes(OP_DEFINE_GLOBAL, global);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ParseRule *getRule(TokenType type) { return &rules[type]; }
|
||||||
|
|
||||||
|
ObjFunction *compile(const char *source) {
|
||||||
|
initScanner(source);
|
||||||
|
Compiler compiler;
|
||||||
|
initCompiler(&compiler, TYPE_SCRIPT);
|
||||||
|
|
||||||
|
parser.hadError = false;
|
||||||
|
parser.panicMode = false;
|
||||||
|
|
||||||
|
advance();
|
||||||
|
|
||||||
|
while (!match(TOKEN_EOF)) {
|
||||||
|
declaration();
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjFunction *function = endCompiler();
|
||||||
|
return parser.hadError ? NULL : function;
|
||||||
|
}
|
||||||
|
|
||||||
|
void markCompilerRoots() {
|
||||||
|
Compiler *compiler = current;
|
||||||
|
while (compiler != NULL) {
|
||||||
|
markObject((Obj *)compiler->function);
|
||||||
|
compiler = compiler->enclosing;
|
||||||
|
}
|
||||||
|
}
|
10
clox/src/compiler.h
Normal file
10
clox/src/compiler.h
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#ifndef COMPILER_H
|
||||||
|
#define COMPILER_H
|
||||||
|
|
||||||
|
#include "object.h"
|
||||||
|
#include "vm.h"
|
||||||
|
|
||||||
|
ObjFunction *compile(const char *source);
|
||||||
|
void markCompilerRoots();
|
||||||
|
|
||||||
|
#endif
|
154
clox/src/debug.c
Normal file
154
clox/src/debug.c
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
#include "object.h"
|
||||||
|
#include "value.h"
|
||||||
|
|
||||||
|
void disassembleChunk(Chunk *chunk, const char *name) {
|
||||||
|
printf("== %s ==\n", name);
|
||||||
|
|
||||||
|
for (int offset = 0; offset < chunk->count;) {
|
||||||
|
offset = disassembleInstruction(chunk, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int constantInstruction(const char *name, Chunk *chunk, int offset) {
|
||||||
|
uint8_t constant = chunk->code[offset + 1];
|
||||||
|
printf("%-16s %4d '", name, constant);
|
||||||
|
printValue(chunk->constants.values[constant]);
|
||||||
|
printf("'\n");
|
||||||
|
return offset + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int invokeInstruction(const char *name, Chunk *chunk, int offset) {
|
||||||
|
uint8_t constant = chunk->code[offset + 1];
|
||||||
|
uint8_t argCount = chunk->code[offset + 2];
|
||||||
|
printf("%-16s (%d args) %4d '", name, argCount, constant);
|
||||||
|
printValue(chunk->constants.values[constant]);
|
||||||
|
printf("'\n'");
|
||||||
|
return offset + 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int simpleInstruction(const char *name, int offset) {
|
||||||
|
printf("%s\n", name);
|
||||||
|
return offset + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int byteInstruction(const char *name, Chunk *chunk, int offset) {
|
||||||
|
uint8_t slot = chunk->code[offset + 1];
|
||||||
|
printf("%-16s %4d\n", name, slot);
|
||||||
|
return offset + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int jumpInstruction(const char *name, int sign, Chunk *chunk,
|
||||||
|
int offset) {
|
||||||
|
uint16_t jump = (uint16_t)(chunk->code[offset + 1] << 8);
|
||||||
|
jump |= chunk->code[offset + 2];
|
||||||
|
printf("%-16s %4d -> %d\n", name, offset, offset + 3 + sign * jump);
|
||||||
|
return offset + 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
int disassembleInstruction(Chunk *chunk, int offset) {
|
||||||
|
printf("%04d ", offset);
|
||||||
|
if (offset > 0 && chunk->lines[offset] == chunk->lines[offset - 1]) {
|
||||||
|
printf(" | ");
|
||||||
|
} else {
|
||||||
|
printf("%4d ", chunk->lines[offset]);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t instruction = chunk->code[offset];
|
||||||
|
switch (instruction) {
|
||||||
|
case OP_CONSTANT:
|
||||||
|
return constantInstruction("OP_CONSTANT", chunk, offset);
|
||||||
|
case OP_NIL:
|
||||||
|
return simpleInstruction("OP_NIL", offset);
|
||||||
|
case OP_TRUE:
|
||||||
|
return simpleInstruction("OP_TRUE", offset);
|
||||||
|
case OP_FALSE:
|
||||||
|
return simpleInstruction("OP_FALSE", offset);
|
||||||
|
case OP_POP:
|
||||||
|
return simpleInstruction("OP_POP", offset);
|
||||||
|
case OP_GET_LOCAL:
|
||||||
|
return byteInstruction("OP_GET_LOCAL", chunk, offset);
|
||||||
|
case OP_SET_LOCAL:
|
||||||
|
return byteInstruction("OP_SET_LOCAL", chunk, offset);
|
||||||
|
case OP_GET_GLOBAL:
|
||||||
|
return constantInstruction("OP_GET_GLOBAL", chunk, offset);
|
||||||
|
case OP_DEFINE_GLOBAL:
|
||||||
|
return constantInstruction("OP_DEFINE_GLOBAL", chunk, offset);
|
||||||
|
case OP_SET_GLOBAL:
|
||||||
|
return constantInstruction("OP_SET_GLOBAL", chunk, offset);
|
||||||
|
case OP_GET_UPVALUE:
|
||||||
|
return byteInstruction("OP_GET_UPVALUE", chunk, offset);
|
||||||
|
case OP_SET_UPVALUE:
|
||||||
|
return byteInstruction("OP_SET_UPVALUE", chunk, offset);
|
||||||
|
case OP_GET_PROPERTY:
|
||||||
|
return constantInstruction("OP_GET_PROPERTY", chunk, offset);
|
||||||
|
case OP_SET_PROPERTY:
|
||||||
|
return constantInstruction("OP_SET_PROPERTY", chunk, offset);
|
||||||
|
case OP_GET_SUPER:
|
||||||
|
return constantInstruction("OP_GET_SUPER", chunk, offset);
|
||||||
|
case OP_EQUAL:
|
||||||
|
return simpleInstruction("OP_EQUAL", offset);
|
||||||
|
case OP_GREATER:
|
||||||
|
return simpleInstruction("OP_GREATER", offset);
|
||||||
|
case OP_LESS:
|
||||||
|
return simpleInstruction("OP_LESS", offset);
|
||||||
|
case OP_ADD:
|
||||||
|
return simpleInstruction("OP_ADD", offset);
|
||||||
|
case OP_SUBTRACT:
|
||||||
|
return simpleInstruction("OP_SUBTRACT", offset);
|
||||||
|
case OP_MULTIPLY:
|
||||||
|
return simpleInstruction("OP_MULTIPLY", offset);
|
||||||
|
case OP_DIVIDE:
|
||||||
|
return simpleInstruction("OP_DIVIDE", offset);
|
||||||
|
case OP_NOT:
|
||||||
|
return simpleInstruction("OP_NOT", offset);
|
||||||
|
case OP_NEGATE:
|
||||||
|
return simpleInstruction("OP_NEGATE", offset);
|
||||||
|
case OP_PRINT:
|
||||||
|
return simpleInstruction("OP_PRINT", offset);
|
||||||
|
case OP_JUMP:
|
||||||
|
return jumpInstruction("OP_JUMP", 1, chunk, offset);
|
||||||
|
case OP_JUMP_IF_FALSE:
|
||||||
|
return jumpInstruction("OP_JUMP_IF_FALSE", 1, chunk, offset);
|
||||||
|
case OP_LOOP:
|
||||||
|
return jumpInstruction("OP_LOOP", -1, chunk, offset);
|
||||||
|
case OP_CALL:
|
||||||
|
return byteInstruction("OP_CALL", chunk, offset);
|
||||||
|
case OP_INVOKE:
|
||||||
|
return invokeInstruction("OP_INVOKE", chunk, offset);
|
||||||
|
case OP_SUPER_INVOKE:
|
||||||
|
return invokeInstruction("OP_SUPER_INVOKE", chunk, offset);
|
||||||
|
case OP_CLOSURE: {
|
||||||
|
offset++;
|
||||||
|
uint8_t constant = chunk->code[offset++];
|
||||||
|
printf("%-16s %4d", "OP_CLOSURE", constant);
|
||||||
|
printValue(chunk->constants.values[constant]);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
ObjFunction *function = AS_FUNCTION(chunk->constants.values[constant]);
|
||||||
|
for (int j = 0; j < function->upvalueCount; j++) {
|
||||||
|
int isLocal = chunk->code[offset++];
|
||||||
|
int index = chunk->code[offset++];
|
||||||
|
printf("%04d | %s %d\n", offset - 2,
|
||||||
|
isLocal ? "local" : "upvalue", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
case OP_CLOSE_UPVALUE:
|
||||||
|
return simpleInstruction("OP_CLOSE_UPVALUE", offset);
|
||||||
|
case OP_RETURN:
|
||||||
|
return simpleInstruction("OP_RETURN", offset);
|
||||||
|
case OP_CLASS:
|
||||||
|
return constantInstruction("OP_CLASS", chunk, offset);
|
||||||
|
case OP_INHERIT:
|
||||||
|
return simpleInstruction("OP_INHERIT", offset);
|
||||||
|
case OP_METHOD:
|
||||||
|
return constantInstruction("OP_METHOD", chunk, offset);
|
||||||
|
default:
|
||||||
|
printf("Unknown opcode %d\n", instruction);
|
||||||
|
return offset + 1;
|
||||||
|
}
|
||||||
|
}
|
9
clox/src/debug.h
Normal file
9
clox/src/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
clox/src/main.c
Normal file
78
clox/src/main.c
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "chunk.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "vm.h"
|
||||||
|
|
||||||
|
static void repl() {
|
||||||
|
char line[1024];
|
||||||
|
for (;;) {
|
||||||
|
printf("> ");
|
||||||
|
|
||||||
|
if (!fgets(line, sizeof(line), stdin)) {
|
||||||
|
printf("\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
interpret(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *readFile(const char *path) {
|
||||||
|
FILE *file = fopen(path, "rb");
|
||||||
|
if (file == 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);
|
||||||
|
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;
|
||||||
|
}
|
244
clox/src/memory.c
Normal file
244
clox/src/memory.c
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "compiler.h"
|
||||||
|
#include "memory.h"
|
||||||
|
#include "vm.h"
|
||||||
|
|
||||||
|
#ifdef DEBUG_LOG_GC
|
||||||
|
#include "debug.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define GC_HEAP_GROW_FACTOR 2
|
||||||
|
|
||||||
|
void *reallocate(void *pointer, size_t oldSize, size_t newSize) {
|
||||||
|
vm.bytesAllocated += newSize - oldSize;
|
||||||
|
if (newSize > oldSize) {
|
||||||
|
#ifdef DEBUG_STRESS_GC
|
||||||
|
collectGarbage();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vm.bytesAllocated > vm.nextGC) {
|
||||||
|
collectGarbage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newSize == 0) {
|
||||||
|
free(pointer);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *result = realloc(pointer, newSize);
|
||||||
|
if (result == NULL)
|
||||||
|
exit(1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void markObject(Obj *object) {
|
||||||
|
if (object == NULL)
|
||||||
|
return;
|
||||||
|
if (object->isMarked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#ifdef DEBUG_LOG_GC
|
||||||
|
printf("%p mark ", (void *)object);
|
||||||
|
printValue(OBJ_VAL(object));
|
||||||
|
printf("\n");
|
||||||
|
#endif
|
||||||
|
object->isMarked = true;
|
||||||
|
|
||||||
|
if (vm.grayCapacity < vm.grayCount + 1) {
|
||||||
|
vm.grayCapacity = GROW_CAPACITY(vm.grayCapacity);
|
||||||
|
vm.grayStack
|
||||||
|
= (Obj **)realloc(vm.grayStack, sizeof(Obj *) * vm.grayCapacity);
|
||||||
|
if (vm.grayStack == NULL)
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.grayStack[vm.grayCount++] = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
void markValue(Value value) {
|
||||||
|
if (IS_OBJ(value))
|
||||||
|
markObject(AS_OBJ(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void markArray(ValueArray *array) {
|
||||||
|
for (int i = 0; i < array->count; i++) {
|
||||||
|
markValue(array->values[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void blackenObject(Obj *object) {
|
||||||
|
#ifdef DEBUG_LOG_GC
|
||||||
|
printf("%p blacken ", (void *)object);
|
||||||
|
printValue(OBJ_VAL(object));
|
||||||
|
printf("\n");
|
||||||
|
#endif
|
||||||
|
switch (object->type) {
|
||||||
|
case OBJ_BOUND_METHOD: {
|
||||||
|
ObjBoundMethod *bound = (ObjBoundMethod *)object;
|
||||||
|
markValue(bound->receiver);
|
||||||
|
markObject((Obj *)bound->method);
|
||||||
|
}
|
||||||
|
case OBJ_CLASS: {
|
||||||
|
ObjClass *klass = (ObjClass *)object;
|
||||||
|
markObject((Obj *)klass->name);
|
||||||
|
markTable(&klass->methods);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OBJ_CLOSURE: {
|
||||||
|
ObjClosure *closure = (ObjClosure *)object;
|
||||||
|
markObject((Obj *)closure->function);
|
||||||
|
for (int i = 0; i < closure->upvalueCount; i++) {
|
||||||
|
markObject((Obj *)closure->upvalues[i]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OBJ_FUNCTION: {
|
||||||
|
ObjFunction *function = (ObjFunction *)object;
|
||||||
|
markObject((Obj *)function->name);
|
||||||
|
markArray(&function->chunk.constants);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OBJ_INSTANCE: {
|
||||||
|
ObjInstance *instance = (ObjInstance *)object;
|
||||||
|
markObject((Obj *)instance->klass);
|
||||||
|
markTable(&instance->fields);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OBJ_UPVALUE:
|
||||||
|
markValue(((ObjUpvalue *)object)->closed);
|
||||||
|
break;
|
||||||
|
case OBJ_NATIVE:
|
||||||
|
case OBJ_STRING:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freeObject(Obj *object) {
|
||||||
|
#ifdef DEBUG_LOG_GC
|
||||||
|
printf("%p free type %d\n", (void *)object, object->type);
|
||||||
|
#endif // DEBUG_LOG_GC
|
||||||
|
|
||||||
|
switch (object->type) {
|
||||||
|
case OBJ_BOUND_METHOD:
|
||||||
|
FREE(ObjBoundMethod, object);
|
||||||
|
break;
|
||||||
|
case OBJ_CLASS: {
|
||||||
|
ObjClass *klass = (ObjClass *)object;
|
||||||
|
freeTable(&klass->methods);
|
||||||
|
FREE(ObjClass, object);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OBJ_CLOSURE: {
|
||||||
|
ObjClosure *closure = (ObjClosure *)object;
|
||||||
|
FREE_ARRAY(ObjUpvalue *, closure->upvalues, closure->upvalueCount);
|
||||||
|
FREE(ObjClosure, object);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OBJ_FUNCTION: {
|
||||||
|
ObjFunction *function = (ObjFunction *)object;
|
||||||
|
freeChunk(&function->chunk);
|
||||||
|
FREE(ObjFunction, object);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OBJ_INSTANCE: {
|
||||||
|
ObjInstance *instance = (ObjInstance *)object;
|
||||||
|
freeTable(&instance->fields);
|
||||||
|
FREE(ObjInstance, object);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OBJ_NATIVE:
|
||||||
|
FREE(ObjNative, object);
|
||||||
|
break;
|
||||||
|
case OBJ_STRING: {
|
||||||
|
ObjString *string = (ObjString *)object;
|
||||||
|
FREE_ARRAY(char, string->chars, string->length + 1);
|
||||||
|
FREE(ObjString, object);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OBJ_UPVALUE:
|
||||||
|
FREE(ObjUpvalue, object);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void markRoots() {
|
||||||
|
for (Value *slot = vm.stack; slot < vm.stackTop; slot++) {
|
||||||
|
markValue(*slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < vm.frameCount; i++) {
|
||||||
|
markObject((Obj *)vm.frames[i].closure);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ObjUpvalue *upvalue = vm.openUpvalues; upvalue != NULL;
|
||||||
|
upvalue = upvalue->next) {
|
||||||
|
markObject((Obj *)upvalue);
|
||||||
|
}
|
||||||
|
|
||||||
|
markTable(&vm.globals);
|
||||||
|
markCompilerRoots();
|
||||||
|
markObject((Obj *)vm.initString);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void traceReferences() {
|
||||||
|
while (vm.grayCount > 0) {
|
||||||
|
Obj *object = vm.grayStack[--vm.grayCount];
|
||||||
|
blackenObject(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sweep() {
|
||||||
|
Obj *previous = NULL;
|
||||||
|
Obj *object = vm.objects;
|
||||||
|
while (object != NULL) {
|
||||||
|
if (object->isMarked) {
|
||||||
|
object->isMarked = false;
|
||||||
|
previous = object;
|
||||||
|
object = object->next;
|
||||||
|
} else {
|
||||||
|
Obj *unreached = object;
|
||||||
|
object = object->next;
|
||||||
|
if (previous != NULL) {
|
||||||
|
previous->next = object;
|
||||||
|
} else {
|
||||||
|
vm.objects = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
freeObject(unreached);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void collectGarbage() {
|
||||||
|
#ifdef DEBUG_LOG_GC
|
||||||
|
printf("-- gc begin\n");
|
||||||
|
size_t before = vm.bytesAllocated;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
markRoots();
|
||||||
|
traceReferences();
|
||||||
|
tableRemoveWhite(&vm.strings);
|
||||||
|
sweep();
|
||||||
|
|
||||||
|
vm.nextGC = vm.bytesAllocated * GC_HEAP_GROW_FACTOR;
|
||||||
|
|
||||||
|
#ifdef DEBUG_LOG_GC
|
||||||
|
printf("-- gc end\n");
|
||||||
|
printf(" collected %zu bytes (from %zu to %zu) next at %zu\n",
|
||||||
|
before - vm.bytesAllocated, before, vm.bytesAllocated, vm.nextGC);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeObjects() {
|
||||||
|
Obj *object = vm.objects;
|
||||||
|
while (object != NULL) {
|
||||||
|
Obj *next = object->next;
|
||||||
|
freeObject(object);
|
||||||
|
object = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(vm.grayStack);
|
||||||
|
}
|
27
clox/src/memory.h
Normal file
27
clox/src/memory.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef clox_memory_h
|
||||||
|
#define clox_memory_h
|
||||||
|
|
||||||
|
#include "common.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(type, pointer, oldCount, newCount) \
|
||||||
|
(type *)reallocate(pointer, sizeof(type) * (oldCount), \
|
||||||
|
sizeof(type) * (newCount))
|
||||||
|
|
||||||
|
#define FREE_ARRAY(type, pointer, oldCount) \
|
||||||
|
reallocate(pointer, sizeof(type) * (oldCount), 0)
|
||||||
|
|
||||||
|
void *reallocate(void *pointer, size_t oldSize, size_t newSize);
|
||||||
|
void markObject(Obj *object);
|
||||||
|
void markValue(Value value);
|
||||||
|
void collectGarbage();
|
||||||
|
void freeObjects();
|
||||||
|
|
||||||
|
#endif
|
165
clox/src/object.c
Normal file
165
clox/src/object.c
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
#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->isMarked = false;
|
||||||
|
|
||||||
|
object->next = vm.objects;
|
||||||
|
vm.objects = object;
|
||||||
|
|
||||||
|
#ifdef DEBUG_LOG_GC
|
||||||
|
printf("%p allocate %zu for %d", (void *)object, size, type);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjBoundMethod *newBoundMethod(Value receiver, ObjClosure *method) {
|
||||||
|
ObjBoundMethod *bound = ALLOCATE_OBJ(ObjBoundMethod, OBJ_BOUND_METHOD);
|
||||||
|
bound->receiver = receiver;
|
||||||
|
bound->method = method;
|
||||||
|
return bound;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjClass *newClass(ObjString *name) {
|
||||||
|
ObjClass *klass = ALLOCATE_OBJ(ObjClass, OBJ_CLASS);
|
||||||
|
klass->name = name;
|
||||||
|
initTable(&klass->methods);
|
||||||
|
return klass;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjClosure *newClosure(ObjFunction *function) {
|
||||||
|
ObjUpvalue **upvalues = ALLOCATE(ObjUpvalue *, function->upvalueCount);
|
||||||
|
for (int i = 0; i < function->upvalueCount; i++) {
|
||||||
|
upvalues[i] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjClosure *closure = ALLOCATE_OBJ(ObjClosure, OBJ_CLOSURE);
|
||||||
|
closure->function = function;
|
||||||
|
closure->upvalues = upvalues;
|
||||||
|
closure->upvalueCount = function->upvalueCount;
|
||||||
|
return closure;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjFunction *newFunction() {
|
||||||
|
ObjFunction *function = ALLOCATE_OBJ(ObjFunction, OBJ_FUNCTION);
|
||||||
|
function->arity = 0;
|
||||||
|
function->upvalueCount = 0;
|
||||||
|
function->name = NULL;
|
||||||
|
initChunk(&function->chunk);
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjInstance *newInstance(ObjClass *klass) {
|
||||||
|
ObjInstance *instance = ALLOCATE_OBJ(ObjInstance, OBJ_INSTANCE);
|
||||||
|
instance->klass = klass;
|
||||||
|
initTable(&instance->fields);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjNative *newNative(NativeFn function) {
|
||||||
|
ObjNative *native = ALLOCATE_OBJ(ObjNative, OBJ_NATIVE);
|
||||||
|
native->function = function;
|
||||||
|
return native;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ObjString *allocateString(char *chars, int length, uint32_t hash) {
|
||||||
|
ObjString *string = ALLOCATE_OBJ(ObjString, OBJ_STRING);
|
||||||
|
string->length = length;
|
||||||
|
string->chars = chars;
|
||||||
|
string->hash = hash;
|
||||||
|
|
||||||
|
push(OBJ_VAL(string));
|
||||||
|
tableSet(&vm.strings, string, NIL_VAL);
|
||||||
|
pop();
|
||||||
|
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t hashString(const char *key, int length) {
|
||||||
|
uint32_t hash = 2166136261u;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
hash ^= (uint32_t)key[i];
|
||||||
|
hash *= 16777619;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjString *takeString(char *chars, int length) {
|
||||||
|
uint32_t hash = hashString(chars, length);
|
||||||
|
ObjString *interned = tableFindString(&vm.strings, chars, length, hash);
|
||||||
|
if (interned != NULL) {
|
||||||
|
FREE_ARRAY(char, chars, length + 1);
|
||||||
|
return interned;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjUpvalue *newUpvalue(Value *slot) {
|
||||||
|
ObjUpvalue *upvalue = ALLOCATE_OBJ(ObjUpvalue, OBJ_UPVALUE);
|
||||||
|
upvalue->closed = NIL_VAL;
|
||||||
|
upvalue->location = slot;
|
||||||
|
upvalue->next = NULL;
|
||||||
|
return upvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printFunction(ObjFunction *function) {
|
||||||
|
if (function->name == NULL) {
|
||||||
|
printf("<script>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
printf("<fn %s>", function->name->chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printObject(Value value) {
|
||||||
|
switch (OBJ_TYPE(value)) {
|
||||||
|
case OBJ_BOUND_METHOD:
|
||||||
|
printFunction(AS_BOUND_METHOD(value)->method->function);
|
||||||
|
break;
|
||||||
|
case OBJ_CLASS:
|
||||||
|
printf("%s", AS_CLASS(value)->name->chars);
|
||||||
|
break;
|
||||||
|
case OBJ_CLOSURE:
|
||||||
|
printFunction(AS_CLOSURE(value)->function);
|
||||||
|
break;
|
||||||
|
case OBJ_FUNCTION:
|
||||||
|
printFunction(AS_FUNCTION(value));
|
||||||
|
break;
|
||||||
|
case OBJ_INSTANCE:
|
||||||
|
printf("%s instance", AS_INSTANCE(value)->klass->name->chars);
|
||||||
|
break;
|
||||||
|
case OBJ_NATIVE:
|
||||||
|
printf("<native fn>");
|
||||||
|
break;
|
||||||
|
case OBJ_STRING:
|
||||||
|
printf("%s", AS_CSTRING(value));
|
||||||
|
break;
|
||||||
|
case OBJ_UPVALUE:
|
||||||
|
printf("upvalue");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
114
clox/src/object.h
Normal file
114
clox/src/object.h
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
#ifndef OBJECT_H
|
||||||
|
#define OBJECT_H
|
||||||
|
|
||||||
|
#include "chunk.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "table.h"
|
||||||
|
#include "value.h"
|
||||||
|
|
||||||
|
#define OBJ_TYPE(value) (AS_OBJ(value)->type)
|
||||||
|
|
||||||
|
#define IS_BOUND_METHOD(value) isObjType(value, OBJ_BOUND_METHOD)
|
||||||
|
#define IS_CLASS(value) isObjType(value, OBJ_CLASS)
|
||||||
|
#define IS_CLOSURE(value) isObjType(value, OBJ_CLOSURE)
|
||||||
|
#define IS_FUNCTION(value) isObjType(value, OBJ_FUNCTION)
|
||||||
|
#define IS_INSTANCE(value) isObjType(value, OBJ_INSTANCE)
|
||||||
|
#define IS_NATIVE(value) isObjType(value, OBJ_NATIVE)
|
||||||
|
#define IS_STRING(value) isObjType(value, OBJ_STRING)
|
||||||
|
|
||||||
|
#define AS_BOUND_METHOD(value) ((ObjBoundMethod *)AS_OBJ(value))
|
||||||
|
#define AS_CLASS(value) ((ObjClass *)AS_OBJ(value))
|
||||||
|
#define AS_CLOSURE(value) ((ObjClosure *)AS_OBJ(value))
|
||||||
|
#define AS_FUNCTION(value) ((ObjFunction *)AS_OBJ(value))
|
||||||
|
#define AS_INSTANCE(value) ((ObjInstance *)AS_OBJ(value))
|
||||||
|
#define AS_NATIVE(value) (((ObjNative *)AS_OBJ(value))->function)
|
||||||
|
#define AS_STRING(value) ((ObjString *)AS_OBJ(value))
|
||||||
|
#define AS_CSTRING(value) (((ObjString *)AS_OBJ(value))->chars)
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OBJ_BOUND_METHOD,
|
||||||
|
OBJ_CLASS,
|
||||||
|
OBJ_CLOSURE,
|
||||||
|
OBJ_FUNCTION,
|
||||||
|
OBJ_INSTANCE,
|
||||||
|
OBJ_NATIVE,
|
||||||
|
OBJ_STRING,
|
||||||
|
OBJ_UPVALUE,
|
||||||
|
} ObjType;
|
||||||
|
|
||||||
|
struct Obj {
|
||||||
|
ObjType type;
|
||||||
|
bool isMarked;
|
||||||
|
struct Obj *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Obj obj;
|
||||||
|
int arity;
|
||||||
|
int upvalueCount;
|
||||||
|
Chunk chunk;
|
||||||
|
ObjString *name;
|
||||||
|
} ObjFunction;
|
||||||
|
|
||||||
|
typedef Value (*NativeFn)(int argCount, Value *args);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Obj obj;
|
||||||
|
NativeFn function;
|
||||||
|
} ObjNative;
|
||||||
|
|
||||||
|
struct ObjString {
|
||||||
|
Obj obj;
|
||||||
|
int length;
|
||||||
|
char *chars;
|
||||||
|
uint32_t hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct ObjUpvalue {
|
||||||
|
Obj obj;
|
||||||
|
Value *location;
|
||||||
|
Value closed;
|
||||||
|
struct ObjUpvalue *next;
|
||||||
|
} ObjUpvalue;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Obj obj;
|
||||||
|
ObjFunction *function;
|
||||||
|
ObjUpvalue **upvalues;
|
||||||
|
int upvalueCount;
|
||||||
|
} ObjClosure;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Obj obj;
|
||||||
|
ObjString *name;
|
||||||
|
Table methods;
|
||||||
|
} ObjClass;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Obj obj;
|
||||||
|
ObjClass *klass;
|
||||||
|
Table fields;
|
||||||
|
} ObjInstance;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Obj obj;
|
||||||
|
Value receiver;
|
||||||
|
ObjClosure *method;
|
||||||
|
} ObjBoundMethod;
|
||||||
|
|
||||||
|
ObjBoundMethod *newBoundMethod(Value receiver, ObjClosure *method);
|
||||||
|
ObjClass *newClass(ObjString *name);
|
||||||
|
ObjClosure *newClosure(ObjFunction *function);
|
||||||
|
ObjFunction *newFunction();
|
||||||
|
ObjInstance *newInstance(ObjClass *klass);
|
||||||
|
ObjNative *newNative(NativeFn function);
|
||||||
|
ObjString *takeString(char *chars, int length);
|
||||||
|
ObjString *copyString(const char *chars, int length);
|
||||||
|
ObjUpvalue *newUpvalue(Value *slot);
|
||||||
|
void printObject(Value value);
|
||||||
|
|
||||||
|
static inline bool isObjType(Value value, ObjType type) {
|
||||||
|
return IS_OBJ(value) && AS_OBJ(value)->type == type;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
246
clox/src/scanner.c
Normal file
246
clox/src/scanner.c
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "scanner.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *start;
|
||||||
|
const char *current;
|
||||||
|
int line;
|
||||||
|
} Scanner;
|
||||||
|
|
||||||
|
Scanner scanner;
|
||||||
|
|
||||||
|
void initScanner(const char *source) {
|
||||||
|
scanner.start = source;
|
||||||
|
scanner.current = source;
|
||||||
|
scanner.line = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isAlpha(char c) {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isDigit(char c) { return c >= '0' && c <= '9'; }
|
||||||
|
|
||||||
|
static bool isAtEnd() { return *scanner.current == '\0'; }
|
||||||
|
|
||||||
|
static char advance() {
|
||||||
|
scanner.current++;
|
||||||
|
return scanner.current[-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static char peek() { return *scanner.current; }
|
||||||
|
|
||||||
|
static char peekNext() {
|
||||||
|
if (isAtEnd())
|
||||||
|
return '\0';
|
||||||
|
return scanner.current[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool match(char expected) {
|
||||||
|
if (isAtEnd())
|
||||||
|
return false;
|
||||||
|
if (*scanner.current != expected)
|
||||||
|
return false;
|
||||||
|
scanner.current++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Token makeToken(TokenType type) {
|
||||||
|
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, "or", 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 quote.
|
||||||
|
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_SEMICOLON);
|
||||||
|
case ',':
|
||||||
|
return makeToken(TOKEN_COMMA);
|
||||||
|
case '.':
|
||||||
|
return makeToken(TOKEN_DOT);
|
||||||
|
case '-':
|
||||||
|
return makeToken(TOKEN_MINUS);
|
||||||
|
case '+':
|
||||||
|
return makeToken(TOKEN_PLUS);
|
||||||
|
case '/':
|
||||||
|
return makeToken(TOKEN_SLASH);
|
||||||
|
case '*':
|
||||||
|
return makeToken(TOKEN_STAR);
|
||||||
|
case '!':
|
||||||
|
return makeToken(match('=') ? TOKEN_BANG_EQUAL : TOKEN_BANG);
|
||||||
|
case '=':
|
||||||
|
return makeToken(match('=') ? TOKEN_EQUAL_EQUAL : TOKEN_EQUAL);
|
||||||
|
case '<':
|
||||||
|
return makeToken(match('=') ? TOKEN_LESS_EQUAL : TOKEN_LESS);
|
||||||
|
case '>':
|
||||||
|
return makeToken(match('=') ? TOKEN_GREATER_EQUAL : TOKEN_GREATER);
|
||||||
|
case '"':
|
||||||
|
return string();
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorToken("Unexpected character.");
|
||||||
|
}
|
62
clox/src/scanner.h
Normal file
62
clox/src/scanner.h
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
#ifndef SCANNER_H
|
||||||
|
#define SCANNER_H
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
/* Single-character tokens. */
|
||||||
|
TOKEN_LEFT_PAREN,
|
||||||
|
TOKEN_RIGHT_PAREN,
|
||||||
|
TOKEN_LEFT_BRACE,
|
||||||
|
TOKEN_RIGHT_BRACE,
|
||||||
|
TOKEN_COMMA,
|
||||||
|
TOKEN_DOT,
|
||||||
|
TOKEN_MINUS,
|
||||||
|
TOKEN_PLUS,
|
||||||
|
TOKEN_SEMICOLON,
|
||||||
|
TOKEN_SLASH,
|
||||||
|
TOKEN_STAR,
|
||||||
|
/* One || two character tokens. */
|
||||||
|
TOKEN_BANG,
|
||||||
|
TOKEN_BANG_EQUAL,
|
||||||
|
TOKEN_EQUAL,
|
||||||
|
TOKEN_EQUAL_EQUAL,
|
||||||
|
TOKEN_GREATER,
|
||||||
|
TOKEN_GREATER_EQUAL,
|
||||||
|
TOKEN_LESS,
|
||||||
|
TOKEN_LESS_EQUAL,
|
||||||
|
/* 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();
|
||||||
|
|
||||||
|
#endif
|
159
clox/src/table.c
Normal file
159
clox/src/table.c
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
#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, ObjString *key) {
|
||||||
|
uint32_t index = key->hash & (capacity - 1);
|
||||||
|
Entry *tombstone = NULL;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
Entry *entry = &entries[index];
|
||||||
|
if (entry->key == NULL) {
|
||||||
|
if (IS_NIL(entry->value)) {
|
||||||
|
// Empty entry.
|
||||||
|
return tombstone != NULL ? tombstone : entry;
|
||||||
|
} else {
|
||||||
|
// We found a tombstone.
|
||||||
|
if (tombstone == NULL)
|
||||||
|
tombstone = entry;
|
||||||
|
}
|
||||||
|
} else if (entry->key == key) {
|
||||||
|
// We found the entry.
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = (index + 1) & (capacity - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tableGet(Table *table, ObjString *key, Value *value) {
|
||||||
|
if (table->count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Entry *entry = findEntry(table->entries, table->capacity, key);
|
||||||
|
if (entry->key == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*value = entry->value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void adjustCapacity(Table *table, int capacity) {
|
||||||
|
Entry *entries = ALLOCATE(Entry, capacity);
|
||||||
|
for (int i = 0; i < capacity; i++) {
|
||||||
|
entries[i].key = NULL;
|
||||||
|
entries[i].value = NIL_VAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
table->count = 0;
|
||||||
|
for (int i = 0; i < table->capacity; i++) {
|
||||||
|
Entry *entry = &table->entries[i];
|
||||||
|
if (entry->key == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Entry *dest = findEntry(entries, capacity, entry->key);
|
||||||
|
dest->key = entry->key;
|
||||||
|
dest->value = entry->value;
|
||||||
|
table->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
FREE_ARRAY(Entry, table->entries, table->capacity);
|
||||||
|
table->entries = entries;
|
||||||
|
table->capacity = capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tableSet(Table *table, ObjString *key, Value value) {
|
||||||
|
if (table->count + 1 > table->capacity * TABLE_MAX_LOAD) {
|
||||||
|
int capacity = GROW_CAPACITY(table->capacity);
|
||||||
|
adjustCapacity(table, capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry *entry = findEntry(table->entries, table->capacity, key);
|
||||||
|
bool isNewKey = entry->key == NULL;
|
||||||
|
if (isNewKey && IS_NIL(entry->value))
|
||||||
|
table->count++;
|
||||||
|
|
||||||
|
entry->key = key;
|
||||||
|
entry->value = value;
|
||||||
|
return isNewKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tableDelete(Table *table, ObjString *key) {
|
||||||
|
if (table->count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Find the entry. */
|
||||||
|
Entry *entry = findEntry(table->entries, table->capacity, key);
|
||||||
|
if (entry->key == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Place a tombstone in the entry. */
|
||||||
|
entry->key = NULL;
|
||||||
|
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 (entry->key != NULL) {
|
||||||
|
tableSet(to, entry->key, entry->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjString *tableFindString(Table *table, const char *chars, int length,
|
||||||
|
uint32_t hash) {
|
||||||
|
if (table->count == 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
uint32_t index = hash & (table->capacity - 1);
|
||||||
|
for (;;) {
|
||||||
|
Entry *entry = &table->entries[index];
|
||||||
|
if (entry->key == NULL) {
|
||||||
|
/* Stop if we find an empty non-tombstone entry. */
|
||||||
|
if (IS_NIL(entry->value))
|
||||||
|
return NULL;
|
||||||
|
} else if (entry->key->length == length && entry->key->hash == hash
|
||||||
|
&& memcmp(entry->key->chars, chars, length) == 0) {
|
||||||
|
/* We found it. */
|
||||||
|
return entry->key;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = (index + 1) & (table->capacity - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tableRemoveWhite(Table *table) {
|
||||||
|
for (int i = 0; i < table->capacity; i++) {
|
||||||
|
Entry *entry = &table->entries[i];
|
||||||
|
if (entry->key != NULL && !entry->key->obj.isMarked) {
|
||||||
|
tableDelete(table, entry->key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void markTable(Table *table) {
|
||||||
|
for (int i = 0; i < table->capacity; i++) {
|
||||||
|
Entry *entry = &table->entries[i];
|
||||||
|
markObject((Obj *)entry->key);
|
||||||
|
markValue(entry->value);
|
||||||
|
}
|
||||||
|
}
|
29
clox/src/table.h
Normal file
29
clox/src/table.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef TABLE_H
|
||||||
|
#define TABLE_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "value.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ObjString *key;
|
||||||
|
Value value;
|
||||||
|
} Entry;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int count;
|
||||||
|
int capacity;
|
||||||
|
Entry *entries;
|
||||||
|
} Table;
|
||||||
|
|
||||||
|
void initTable(Table *table);
|
||||||
|
void freeTable(Table *table);
|
||||||
|
bool tableGet(Table *table, ObjString *key, Value *value);
|
||||||
|
bool tableSet(Table *table, ObjString *key, Value value);
|
||||||
|
bool tableDelete(Table *table, ObjString *key);
|
||||||
|
void tableAddAll(Table *from, Table *to);
|
||||||
|
ObjString *tableFindString(Table *talbe, const char *chars, int length,
|
||||||
|
uint32_t hash);
|
||||||
|
void tableRemoveWhite(Table *table);
|
||||||
|
void markTable(Table *table);
|
||||||
|
|
||||||
|
#endif
|
83
clox/src/value.c
Normal file
83
clox/src/value.c
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "memory.h"
|
||||||
|
#include "object.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(Value, array->values, 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) {
|
||||||
|
#ifdef NAN_BOXING
|
||||||
|
if (IS_BOOL(value)) {
|
||||||
|
printf(AS_BOOL(value) ? "true" : "false");
|
||||||
|
} else if (IS_NIL(value)) {
|
||||||
|
printf("nil");
|
||||||
|
} else if (IS_NUMBER(value)) {
|
||||||
|
printf("%g", AS_NUMBER(value));
|
||||||
|
} else if (IS_OBJ(value)) {
|
||||||
|
printObject(value);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
switch (value.type) {
|
||||||
|
case VAL_BOOL:
|
||||||
|
printf(AS_BOOL(value) ? "true" : "false");
|
||||||
|
break;
|
||||||
|
case VAL_NIL:
|
||||||
|
printf("nil");
|
||||||
|
break;
|
||||||
|
case VAL_NUMBER:
|
||||||
|
printf("%g", AS_NUMBER(value));
|
||||||
|
break;
|
||||||
|
case VAL_OBJ:
|
||||||
|
printObject(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif // NAN_BOXING
|
||||||
|
}
|
||||||
|
|
||||||
|
bool valuesEqual(Value a, Value b) {
|
||||||
|
#ifdef NAN_BOXING
|
||||||
|
if (IS_NUMBER(a) && IS_NUMBER(b)) {
|
||||||
|
return AS_NUMBER(a) == AS_NUMBER(b);
|
||||||
|
}
|
||||||
|
return a == b;
|
||||||
|
#else
|
||||||
|
if (a.type != b.type)
|
||||||
|
return false;
|
||||||
|
switch (a.type) {
|
||||||
|
case VAL_BOOL:
|
||||||
|
return AS_BOOL(a) == AS_BOOL(b);
|
||||||
|
case VAL_NIL:
|
||||||
|
return true;
|
||||||
|
case VAL_NUMBER:
|
||||||
|
return AS_NUMBER(a) == AS_NUMBER(b);
|
||||||
|
case VAL_OBJ: {
|
||||||
|
return AS_OBJ(a) == AS_OBJ(b);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false; /* Unreachable */
|
||||||
|
}
|
||||||
|
#endif // NAN_BOXING
|
||||||
|
}
|
96
clox/src/value.h
Normal file
96
clox/src/value.h
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
#ifndef clox_value_h
|
||||||
|
#define clox_value_h
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
typedef struct Obj Obj;
|
||||||
|
typedef struct ObjString ObjString;
|
||||||
|
|
||||||
|
#ifdef NAN_BOXING
|
||||||
|
|
||||||
|
#define SIGN_BIT ((uint64_t)0x8000000000000000)
|
||||||
|
#define QNAN ((uint64_t)0x7ffc000000000000)
|
||||||
|
|
||||||
|
#define TAG_NIL 1 /* 01 */
|
||||||
|
#define TAG_FALSE 2 /* 10 */
|
||||||
|
#define TAG_TRUE 3 /* 11 */
|
||||||
|
|
||||||
|
typedef uint64_t Value;
|
||||||
|
|
||||||
|
#define IS_BOOL(value) (((value) | 1) == TRUE_VAL)
|
||||||
|
#define IS_NIL(value) ((value) == NIL_VAL)
|
||||||
|
#define IS_NUMBER(value) (((value)&QNAN) != QNAN)
|
||||||
|
#define IS_OBJ(value) (((value) & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT))
|
||||||
|
|
||||||
|
#define AS_BOOL(value) ((value) == TRUE_VAL)
|
||||||
|
#define AS_NUMBER(value) valueToNum(value)
|
||||||
|
#define AS_OBJ(value) ((Obj *)(uintptr_t)((value) & ~(SIGN_BIT | QNAN)))
|
||||||
|
|
||||||
|
#define BOOL_VAL(b) ((b) ? TRUE_VAL : FALSE_VAL)
|
||||||
|
#define FALSE_VAL ((Value)(uint64_t)(QNAN | TAG_FALSE))
|
||||||
|
#define TRUE_VAL ((Value)(uint64_t)(QNAN | TAG_TRUE))
|
||||||
|
#define NIL_VAL ((Value)(uint64_t)(QNAN | TAG_NIL))
|
||||||
|
#define NUMBER_VAL(num) numToValue(num)
|
||||||
|
#define OBJ_VAL(obj) (Value)(SIGN_BIT | QNAN | (uint64_t)(uintptr_t)(obj))
|
||||||
|
|
||||||
|
static inline double valueToNum(Value value) {
|
||||||
|
double num;
|
||||||
|
memcpy(&num, &value, sizeof(Value));
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Value numToValue(double num) {
|
||||||
|
Value value;
|
||||||
|
memcpy(&value, &num, sizeof(double));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
VAL_BOOL,
|
||||||
|
VAL_NIL,
|
||||||
|
VAL_NUMBER,
|
||||||
|
VAL_OBJ,
|
||||||
|
} ValueType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ValueType type;
|
||||||
|
union {
|
||||||
|
bool boolean;
|
||||||
|
double number;
|
||||||
|
Obj *obj;
|
||||||
|
} as;
|
||||||
|
} Value;
|
||||||
|
|
||||||
|
#define IS_BOOL(value) ((value).type == VAL_BOOL)
|
||||||
|
#define IS_NIL(value) ((value).type == VAL_NIL)
|
||||||
|
#define IS_NUMBER(value) ((value).type == VAL_NUMBER)
|
||||||
|
#define IS_OBJ(value) ((value).type == VAL_OBJ)
|
||||||
|
|
||||||
|
#define AS_OBJ(value) ((value).as.obj)
|
||||||
|
#define AS_BOOL(value) ((value).as.boolean)
|
||||||
|
#define AS_NUMBER(value) ((value).as.number)
|
||||||
|
|
||||||
|
#define BOOL_VAL(value) ((Value){VAL_BOOL, {.boolean = value}})
|
||||||
|
#define NIL_VAL ((Value){VAL_NIL, {.number = 0}})
|
||||||
|
#define NUMBER_VAL(value) ((Value){VAL_NUMBER, {.number = value}})
|
||||||
|
#define OBJ_VAL(value) ((Value){VAL_OBJ, {.obj = (Obj *)value}})
|
||||||
|
|
||||||
|
#endif // NAN_BOXING
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int capacity;
|
||||||
|
int count;
|
||||||
|
Value *values;
|
||||||
|
} ValueArray;
|
||||||
|
|
||||||
|
bool valuesEqual(Value a, Value b);
|
||||||
|
void initValueArray(ValueArray *array);
|
||||||
|
void writeValueArray(ValueArray *array, Value value);
|
||||||
|
void freeValueArray(ValueArray *array);
|
||||||
|
void printValue(Value value);
|
||||||
|
|
||||||
|
#endif
|
554
clox/src/vm.c
Normal file
554
clox/src/vm.c
Normal file
|
@ -0,0 +1,554 @@
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "compiler.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "memory.h"
|
||||||
|
#include "object.h"
|
||||||
|
#include "vm.h"
|
||||||
|
|
||||||
|
VM vm;
|
||||||
|
|
||||||
|
static Value clockNative(int argCount, Value *args) {
|
||||||
|
return NUMBER_VAL((double)clock() / CLOCKS_PER_SEC);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resetStack() {
|
||||||
|
vm.stackTop = vm.stack;
|
||||||
|
vm.frameCount = 0;
|
||||||
|
vm.openUpvalues = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void runtimeError(const char *format, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
vfprintf(stderr, format, args);
|
||||||
|
va_end(args);
|
||||||
|
fputs("\n", stderr);
|
||||||
|
|
||||||
|
for (int i = vm.frameCount; i >= 0; i--) {
|
||||||
|
CallFrame *frame = &vm.frames[i];
|
||||||
|
ObjFunction *function = frame->closure->function;
|
||||||
|
size_t instruction = frame->ip - function->chunk.code - 1;
|
||||||
|
fprintf(stderr, "[line %d] in ", function->chunk.lines[instruction]);
|
||||||
|
if (function->name == NULL) {
|
||||||
|
fprintf(stderr, "script\n");
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "%s()\n", function->name->chars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void defineNative(const char *name, NativeFn function) {
|
||||||
|
push(OBJ_VAL(copyString(name, (int)strlen(name))));
|
||||||
|
push(OBJ_VAL(newNative(function)));
|
||||||
|
tableSet(&vm.globals, AS_STRING(vm.stack[0]), vm.stack[1]);
|
||||||
|
pop();
|
||||||
|
pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void initVM() {
|
||||||
|
resetStack();
|
||||||
|
vm.objects = NULL;
|
||||||
|
vm.bytesAllocated = 0;
|
||||||
|
vm.nextGC = 1024 * 1024;
|
||||||
|
|
||||||
|
vm.grayCount = 0;
|
||||||
|
vm.grayCapacity = 0;
|
||||||
|
vm.grayStack = NULL;
|
||||||
|
|
||||||
|
initTable(&vm.globals);
|
||||||
|
initTable(&vm.strings);
|
||||||
|
|
||||||
|
vm.initString = NULL;
|
||||||
|
vm.initString = copyString("init", 4);
|
||||||
|
|
||||||
|
defineNative("clock", clockNative);
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeVM() {
|
||||||
|
freeTable(&vm.globals);
|
||||||
|
freeTable(&vm.strings);
|
||||||
|
vm.initString = NULL;
|
||||||
|
freeObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void push(Value value) {
|
||||||
|
*vm.stackTop = value;
|
||||||
|
vm.stackTop++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Value pop() {
|
||||||
|
vm.stackTop--;
|
||||||
|
return *vm.stackTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Value peek(int distance) { return vm.stackTop[-1 - distance]; }
|
||||||
|
|
||||||
|
static bool call(ObjClosure *closure, int argCount) {
|
||||||
|
if (argCount != closure->function->arity) {
|
||||||
|
runtimeError("Expected %d arguments but got %d.", closure->function->arity,
|
||||||
|
argCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vm.frameCount == FRAMES_MAX) {
|
||||||
|
runtimeError("Stack overflow.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CallFrame *frame = &vm.frames[vm.frameCount++];
|
||||||
|
frame->closure = closure;
|
||||||
|
frame->ip = closure->function->chunk.code;
|
||||||
|
frame->slots = vm.stackTop - argCount - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool callValue(Value callee, int argCount) {
|
||||||
|
if (IS_OBJ(callee)) {
|
||||||
|
switch (OBJ_TYPE(callee)) {
|
||||||
|
case OBJ_BOUND_METHOD: {
|
||||||
|
ObjBoundMethod *bound = AS_BOUND_METHOD(callee);
|
||||||
|
vm.stackTop[-argCount - 1] = bound->receiver;
|
||||||
|
return call(bound->method, argCount);
|
||||||
|
}
|
||||||
|
case OBJ_CLASS: {
|
||||||
|
ObjClass *klass = AS_CLASS(callee);
|
||||||
|
vm.stackTop[-argCount - 1] = OBJ_VAL(newInstance(klass));
|
||||||
|
Value initializer;
|
||||||
|
if (tableGet(&klass->methods, vm.initString, &initializer)) {
|
||||||
|
return call(AS_CLOSURE(initializer), argCount);
|
||||||
|
} else if (argCount != 0) {
|
||||||
|
runtimeError("Expected 0 arguments but got %d.", argCount);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case OBJ_CLOSURE:
|
||||||
|
return call(AS_CLOSURE(callee), argCount);
|
||||||
|
case OBJ_NATIVE: {
|
||||||
|
NativeFn native = AS_NATIVE(callee);
|
||||||
|
Value result = native(argCount, vm.stackTop - argCount);
|
||||||
|
vm.stackTop -= argCount + 1;
|
||||||
|
push(result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtimeError("Can only call functions && classes.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool invokeFromClass(ObjClass *klass, ObjString *name, int argCount) {
|
||||||
|
Value method;
|
||||||
|
if (!tableGet(&klass->methods, name, &method)) {
|
||||||
|
runtimeError("Undefined property '%s'.", name->chars);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return call(AS_CLOSURE(method), argCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool invoke(ObjString *name, int argCount) {
|
||||||
|
Value receiver = peek(argCount);
|
||||||
|
|
||||||
|
if (!IS_INSTANCE(receiver)) {
|
||||||
|
runtimeError("Only instances have methods.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjInstance *instance = AS_INSTANCE(receiver);
|
||||||
|
|
||||||
|
Value value;
|
||||||
|
if (tableGet(&instance->fields, name, &value)) {
|
||||||
|
vm.stackTop[-argCount - 1] = value;
|
||||||
|
return callValue(value, argCount);
|
||||||
|
}
|
||||||
|
return invokeFromClass(instance->klass, name, argCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bindMethod(ObjClass *klass, ObjString *name) {
|
||||||
|
Value method;
|
||||||
|
if (!tableGet(&klass->methods, name, &method)) {
|
||||||
|
runtimeError("Undefined property '%s'.", name->chars);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjBoundMethod *bound = newBoundMethod(peek(0), AS_CLOSURE(method));
|
||||||
|
pop();
|
||||||
|
push(OBJ_VAL(bound));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ObjUpvalue *captureUpvalue(Value *local) {
|
||||||
|
ObjUpvalue *prevUpvalue = NULL;
|
||||||
|
ObjUpvalue *upvalue = vm.openUpvalues;
|
||||||
|
while (upvalue != NULL && upvalue->location > local) {
|
||||||
|
prevUpvalue = upvalue;
|
||||||
|
upvalue = upvalue->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (upvalue != NULL && upvalue->location == local) {
|
||||||
|
return upvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjUpvalue *createdUpvalue = newUpvalue(local);
|
||||||
|
createdUpvalue->next = upvalue;
|
||||||
|
|
||||||
|
if (prevUpvalue == NULL) {
|
||||||
|
vm.openUpvalues = createdUpvalue;
|
||||||
|
} else {
|
||||||
|
prevUpvalue->next = createdUpvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdUpvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void closeUpvalues(Value *last) {
|
||||||
|
while (vm.openUpvalues != NULL && vm.openUpvalues->location >= last) {
|
||||||
|
ObjUpvalue *upvalue = vm.openUpvalues;
|
||||||
|
upvalue->closed = *upvalue->location;
|
||||||
|
upvalue->location = &upvalue->closed;
|
||||||
|
vm.openUpvalues = upvalue->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void defineMethod(ObjString *name) {
|
||||||
|
Value method = peek(0);
|
||||||
|
ObjClass *klass = AS_CLASS(peek(1));
|
||||||
|
tableSet(&klass->methods, name, method);
|
||||||
|
pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isFalsey(Value value) {
|
||||||
|
return IS_NIL(value) || (IS_BOOL(value) && !AS_BOOL(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void concatenate() {
|
||||||
|
ObjString *b = AS_STRING(peek(0));
|
||||||
|
ObjString *a = AS_STRING(peek(1));
|
||||||
|
|
||||||
|
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);
|
||||||
|
pop();
|
||||||
|
pop();
|
||||||
|
push(OBJ_VAL(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
static InterpretResult run() {
|
||||||
|
CallFrame *frame = &vm.frames[vm.frameCount - 1];
|
||||||
|
|
||||||
|
#define READ_BYTE() (*frame->ip++)
|
||||||
|
|
||||||
|
#define READ_SHORT() \
|
||||||
|
(frame->ip += 2, (uint16_t)((frame->ip[-2] << 8) | frame->ip[-1]))
|
||||||
|
|
||||||
|
#define READ_CONSTANT() \
|
||||||
|
(frame->closure->function->chunk.constants.values[READ_BYTE()])
|
||||||
|
#define READ_STRING() AS_STRING(READ_CONSTANT())
|
||||||
|
#define BINARY_OP(valueType, op) \
|
||||||
|
do { \
|
||||||
|
if (!IS_NUMBER(peek(0)) || !IS_NUMBER(peek(1))) { \
|
||||||
|
runtimeError("Operands must be numbers."); \
|
||||||
|
} \
|
||||||
|
double b = AS_NUMBER(pop()); \
|
||||||
|
double a = AS_NUMBER(pop()); \
|
||||||
|
push(valueType(a op b)); \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
#ifdef DEBUG_TRACE_EXECUTION
|
||||||
|
printf(" ");
|
||||||
|
for (Value *slot = vm.stack; slot < vm.stackTop; slot++) {
|
||||||
|
printf("[ ");
|
||||||
|
printValue(*slot);
|
||||||
|
printf(" ]");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
disassembleInstruction(
|
||||||
|
&frame->closure->function->chunk,
|
||||||
|
(int)(frame->ip - frame->closure->function->chunk.code));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint8_t instruction;
|
||||||
|
switch (instruction = READ_BYTE()) {
|
||||||
|
case OP_CONSTANT: {
|
||||||
|
Value constant = READ_CONSTANT();
|
||||||
|
push(constant);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_NIL:
|
||||||
|
push(NIL_VAL);
|
||||||
|
break;
|
||||||
|
case OP_TRUE:
|
||||||
|
push(BOOL_VAL(true));
|
||||||
|
break;
|
||||||
|
case OP_FALSE:
|
||||||
|
push(BOOL_VAL(false));
|
||||||
|
break;
|
||||||
|
case OP_POP:
|
||||||
|
pop();
|
||||||
|
break;
|
||||||
|
case OP_GET_LOCAL: {
|
||||||
|
uint8_t slot = READ_BYTE();
|
||||||
|
push(frame->slots[slot]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_SET_LOCAL: {
|
||||||
|
uint8_t slot = READ_BYTE();
|
||||||
|
frame->slots[slot] = peek(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_GET_GLOBAL: {
|
||||||
|
ObjString *name = READ_STRING();
|
||||||
|
Value value;
|
||||||
|
if (!tableGet(&vm.globals, name, &value)) {
|
||||||
|
runtimeError("Undefined variable '%s'.", name->chars);
|
||||||
|
return INTERPRET_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
push(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_DEFINE_GLOBAL: {
|
||||||
|
ObjString *name = READ_STRING();
|
||||||
|
tableSet(&vm.globals, name, peek(0));
|
||||||
|
pop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_SET_GLOBAL: {
|
||||||
|
ObjString *name = READ_STRING();
|
||||||
|
if (tableSet(&vm.globals, name, peek(0))) {
|
||||||
|
tableDelete(&vm.globals, name);
|
||||||
|
runtimeError("Undefined variable '%s'.", name->chars);
|
||||||
|
return INTERPRET_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_GET_UPVALUE: {
|
||||||
|
uint8_t slot = READ_BYTE();
|
||||||
|
push(*frame->closure->upvalues[slot]->location);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_SET_UPVALUE: {
|
||||||
|
uint8_t slot = READ_BYTE();
|
||||||
|
*frame->closure->upvalues[slot]->location = peek(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_GET_PROPERTY: {
|
||||||
|
if (!IS_INSTANCE(peek(0))) {
|
||||||
|
runtimeError("Only instances have properties.");
|
||||||
|
return INTERPRET_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjInstance *instance = AS_INSTANCE(peek(0));
|
||||||
|
ObjString *name = READ_STRING();
|
||||||
|
|
||||||
|
Value value;
|
||||||
|
if (tableGet(&instance->fields, name, &value)) {
|
||||||
|
pop(); /* Instance */
|
||||||
|
push(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bindMethod(instance->klass, name)) {
|
||||||
|
return INTERPRET_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_SET_PROPERTY: {
|
||||||
|
if (!IS_INSTANCE(peek(1))) {
|
||||||
|
runtimeError("Only instances have fields.");
|
||||||
|
return INTERPRET_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjInstance *instance = AS_INSTANCE(peek(1));
|
||||||
|
tableSet(&instance->fields, READ_STRING(), peek(0));
|
||||||
|
Value value = pop();
|
||||||
|
pop();
|
||||||
|
push(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_GET_SUPER: {
|
||||||
|
ObjString *name = READ_STRING();
|
||||||
|
ObjClass *superclass = AS_CLASS(pop());
|
||||||
|
|
||||||
|
if (!bindMethod(superclass, name)) {
|
||||||
|
return INTERPRET_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_EQUAL: {
|
||||||
|
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());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_JUMP: {
|
||||||
|
uint16_t offset = READ_SHORT();
|
||||||
|
frame->ip += offset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_JUMP_IF_FALSE: {
|
||||||
|
uint16_t offset = READ_SHORT();
|
||||||
|
if (isFalsey(peek(0)))
|
||||||
|
frame->ip += offset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_LOOP: {
|
||||||
|
uint16_t offset = READ_SHORT();
|
||||||
|
frame->ip -= offset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_CALL: {
|
||||||
|
int argCount = READ_BYTE();
|
||||||
|
if (!callValue(peek(argCount), argCount)) {
|
||||||
|
return INTERPRET_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
frame = &vm.frames[vm.frameCount - 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_INVOKE: {
|
||||||
|
ObjString *method = READ_STRING();
|
||||||
|
int argCount = READ_BYTE();
|
||||||
|
if (!invoke(method, argCount)) {
|
||||||
|
return INTERPRET_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
frame = &vm.frames[vm.frameCount - 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_SUPER_INVOKE: {
|
||||||
|
ObjString *method = READ_STRING();
|
||||||
|
int argCount = READ_BYTE();
|
||||||
|
ObjClass *superclass = AS_CLASS(pop());
|
||||||
|
if (!invokeFromClass(superclass, method, argCount)) {
|
||||||
|
return INTERPRET_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
frame = &vm.frames[vm.frameCount - 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_CLOSURE: {
|
||||||
|
ObjFunction *function = AS_FUNCTION(READ_CONSTANT());
|
||||||
|
ObjClosure *closure = newClosure(function);
|
||||||
|
push(OBJ_VAL(closure));
|
||||||
|
for (int i = 0; i < closure->upvalueCount; i++) {
|
||||||
|
uint8_t isLocal = READ_BYTE();
|
||||||
|
uint8_t index = READ_BYTE();
|
||||||
|
if (isLocal) {
|
||||||
|
closure->upvalues[i] = captureUpvalue(frame->slots + index);
|
||||||
|
} else {
|
||||||
|
closure->upvalues[i] = frame->closure->upvalues[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_CLOSE_UPVALUE:
|
||||||
|
closeUpvalues(vm.stackTop - 1);
|
||||||
|
pop();
|
||||||
|
break;
|
||||||
|
case OP_RETURN: {
|
||||||
|
Value result = pop();
|
||||||
|
closeUpvalues(frame->slots);
|
||||||
|
vm.frameCount--;
|
||||||
|
if (vm.frameCount == 0) {
|
||||||
|
pop();
|
||||||
|
return INTERPRET_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.stackTop = frame->slots;
|
||||||
|
push(result);
|
||||||
|
frame = &vm.frames[vm.frameCount - 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_CLASS:
|
||||||
|
push(OBJ_VAL(newClass(READ_STRING())));
|
||||||
|
break;
|
||||||
|
case OP_INHERIT: {
|
||||||
|
Value superclass = peek(1);
|
||||||
|
if (!IS_CLASS(superclass)) {
|
||||||
|
runtimeError("Superclass must be a class.");
|
||||||
|
return INTERPRET_RUNTIME_ERROR;
|
||||||
|
}
|
||||||
|
ObjClass *subclass = AS_CLASS(peek(0));
|
||||||
|
tableAddAll(&AS_CLASS(superclass)->methods, &subclass->methods);
|
||||||
|
pop(); /* Subclass. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OP_METHOD:
|
||||||
|
defineMethod(READ_STRING());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef READ_BYTE
|
||||||
|
#undef READ_SHORT
|
||||||
|
#undef READ_CONSTANT
|
||||||
|
#undef READ_STRING
|
||||||
|
#undef BINARY_OP
|
||||||
|
}
|
||||||
|
|
||||||
|
InterpretResult interpret(const char *source) {
|
||||||
|
ObjFunction *function = compile(source);
|
||||||
|
if (function == NULL)
|
||||||
|
return INTERPRET_COMPILE_ERROR;
|
||||||
|
|
||||||
|
push(OBJ_VAL(function));
|
||||||
|
ObjClosure *closure = newClosure(function);
|
||||||
|
pop();
|
||||||
|
push(OBJ_VAL(closure));
|
||||||
|
call(closure, 0);
|
||||||
|
|
||||||
|
return run();
|
||||||
|
}
|
51
clox/src/vm.h
Normal file
51
clox/src/vm.h
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#ifndef clox_vm_h
|
||||||
|
#define clox_vm_h
|
||||||
|
|
||||||
|
#include "chunk.h"
|
||||||
|
#include "object.h"
|
||||||
|
#include "table.h"
|
||||||
|
#include "value.h"
|
||||||
|
|
||||||
|
#define FRAMES_MAX 64
|
||||||
|
#define STACK_MAX (FRAMES_MAX * UINT8_COUNT)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ObjClosure *closure;
|
||||||
|
uint8_t *ip;
|
||||||
|
Value *slots;
|
||||||
|
} CallFrame;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
CallFrame frames[FRAMES_MAX];
|
||||||
|
int frameCount;
|
||||||
|
|
||||||
|
Value stack[STACK_MAX];
|
||||||
|
Value *stackTop;
|
||||||
|
Table globals;
|
||||||
|
Table strings;
|
||||||
|
ObjString *initString;
|
||||||
|
ObjUpvalue *openUpvalues;
|
||||||
|
|
||||||
|
size_t bytesAllocated;
|
||||||
|
size_t nextGC;
|
||||||
|
Obj *objects;
|
||||||
|
int grayCount;
|
||||||
|
int grayCapacity;
|
||||||
|
Obj **grayStack;
|
||||||
|
} VM;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
INTERPRET_OK,
|
||||||
|
INTERPRET_COMPILE_ERROR,
|
||||||
|
INTERPRET_RUNTIME_ERROR
|
||||||
|
} InterpretResult;
|
||||||
|
|
||||||
|
extern VM vm;
|
||||||
|
|
||||||
|
void initVM();
|
||||||
|
void freeVM();
|
||||||
|
InterpretResult interpret(const char *source);
|
||||||
|
void push(Value value);
|
||||||
|
Value pop();
|
||||||
|
|
||||||
|
#endif
|
13
environment.scm
Normal file
13
environment.scm
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
(use-modules (guix packages))
|
||||||
|
|
||||||
|
(packages->manifest
|
||||||
|
(list (specification->package "cmake")
|
||||||
|
(specification->package "openjdk")
|
||||||
|
(list (specification->package "openjdk") "jdk")
|
||||||
|
(specification->package "coreutils")
|
||||||
|
(specification->package "make")
|
||||||
|
(specification->package "dejagnu")
|
||||||
|
(specification->package "gcc-toolchain")
|
||||||
|
(specification->package "ccls")
|
||||||
|
;; For clang-format
|
||||||
|
(specification->package "clang")))
|
1
jlox/.gitignore
vendored
Normal file
1
jlox/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
_build
|
8
jlox/CMakeLists.txt
Normal file
8
jlox/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
cmake_minimum_required(VERSION 2.19)
|
||||||
|
|
||||||
|
find_package(Java REQUIRED)
|
||||||
|
include(UseJava)
|
||||||
|
|
||||||
|
project(Lox NONE)
|
||||||
|
|
||||||
|
add_subdirectory(src/com/craftinginterpreters)
|
17
jlox/CMakePresets.json
Normal file
17
jlox/CMakePresets.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"cmakeMinimumRequired": {
|
||||||
|
"major": 3,
|
||||||
|
"minor": 19,
|
||||||
|
"patch": 0
|
||||||
|
},
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"displayName": "Default Config",
|
||||||
|
"description": "Default build using Makefile generator",
|
||||||
|
"generator": "Unix Makefiles",
|
||||||
|
"binaryDir": "${sourceDir}/_build"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
3
jlox/jlox
Executable file
3
jlox/jlox
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
java -jar "$(dirname "$0")/_build/src/com/craftinginterpreters/lox/Lox.jar" "$@"
|
2
jlox/src/com/craftinginterpreters/CMakeLists.txt
Normal file
2
jlox/src/com/craftinginterpreters/CMakeLists.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
add_subdirectory(tool)
|
||||||
|
add_subdirectory(lox)
|
1
jlox/src/com/craftinginterpreters/lox/.gitignore
vendored
Normal file
1
jlox/src/com/craftinginterpreters/lox/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Expr.java
|
|
@ -0,0 +1,2 @@
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.core.formatter.lineSplit=80
|
147
jlox/src/com/craftinginterpreters/lox/AstPrinter.java
Normal file
147
jlox/src/com/craftinginterpreters/lox/AstPrinter.java
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AstPrinter
|
||||||
|
*/
|
||||||
|
class AstPrinter implements Expr.Visitor<String>, Stmt.Visitor<String> {
|
||||||
|
String print(Expr expr) {
|
||||||
|
return expr.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitCallExpr(Expr.Call expr) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.append(print(expr.callee));
|
||||||
|
builder.append(parenthesizeExpr("", expr.arguments));
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitBinaryExpr(Expr.Binary expr) {
|
||||||
|
return parenthesizeExpr(expr.operator.lexeme, expr.left, expr.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitGroupingExpr(Expr.Grouping expr) {
|
||||||
|
return parenthesizeExpr("group", expr.expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitLiteralExpr(Expr.Literal expr) {
|
||||||
|
if (expr.value == null)
|
||||||
|
return "nil";
|
||||||
|
return expr.value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitLogicalExpr(Expr.Logical expr) {
|
||||||
|
return parenthesizeExpr(expr.operator.type.toString(), expr.left, expr.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitUnaryExpr(Expr.Unary expr) {
|
||||||
|
return parenthesizeExpr(expr.operator.lexeme, expr.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitVariableExpr(Expr.Variable expr) {
|
||||||
|
return expr.name.lexeme;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitAssignExpr(Expr.Assign expression) {
|
||||||
|
return parenthesizeExpr(expression.name.lexeme + " = ", expression.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitVarStmt(Stmt.Var statement) {
|
||||||
|
return parenthesizeExpr("define " + statement.name.lexeme + " = ", statement.initializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitWhileStmt(Stmt.While statement) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.append('(');
|
||||||
|
builder.append(parenthesizeExpr("while", statement.condition));
|
||||||
|
builder.append(parenthesizeStmt("do", statement.body));
|
||||||
|
builder.append(')');
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitPrintStmt(Stmt.Print statement) {
|
||||||
|
return parenthesizeExpr("print", statement.expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitExpressionStmt(Stmt.Expression expression) {
|
||||||
|
return expression.expression.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitIfStmt(Stmt.If stmt) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.append("(");
|
||||||
|
builder.append(parenthesizeExpr("if", stmt.condition));
|
||||||
|
builder.append(parenthesizeStmt("then", stmt.thenBranch));
|
||||||
|
|
||||||
|
if (stmt.elseBranch != null) {
|
||||||
|
builder.append(parenthesizeStmt("else", stmt.elseBranch));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String visitBlockStmt(Stmt.Block block) {
|
||||||
|
return parenthesizeStmt("", block.statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parenthesizeExpr(String name, Expr... exprs) {
|
||||||
|
return parenthesizeExpr(name, exprs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parenthesizeExpr(String name, List<Expr> exprs) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.append("(").append(name);
|
||||||
|
for (Expr expr : exprs) {
|
||||||
|
builder.append(" ");
|
||||||
|
builder.append(expr.accept(this));
|
||||||
|
}
|
||||||
|
builder.append(")");
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parenthesizeStmt(String name, List<Stmt> statements) {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.append("(").append(name);
|
||||||
|
for (Stmt statement : statements) {
|
||||||
|
builder.append(" ");
|
||||||
|
builder.append(statement.accept(this));
|
||||||
|
}
|
||||||
|
builder.append(")");
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parenthesizeStmt(String name, Stmt... statements) {
|
||||||
|
return parenthesizeStmt(name, statements);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Expr expression = new Expr.Binary(
|
||||||
|
new Expr.Unary(new Token(TokenType.MINUS, "-", null, 1), new Expr.Literal(123)),
|
||||||
|
new Token(TokenType.STAR, "*", null, 1), new Expr.Grouping(new Expr.Literal(45.67)));
|
||||||
|
|
||||||
|
System.out.println(new AstPrinter().print(expression));
|
||||||
|
}
|
||||||
|
}
|
28
jlox/src/com/craftinginterpreters/lox/CMakeLists.txt
Normal file
28
jlox/src/com/craftinginterpreters/lox/CMakeLists.txt
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
get_target_property(GENERATE_AST_JAR GenerateAst JAR_FILE)
|
||||||
|
|
||||||
|
set(GENERATED_JAVA_FILENAMES
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/Expr.java
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/Stmt.java
|
||||||
|
CACHE INTERNAL "")
|
||||||
|
|
||||||
|
add_custom_command(OUTPUT ${GENERATED_JAVA_FILENAMES}
|
||||||
|
COMMAND java -jar ${GENERATE_AST_JAR} ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
DEPENDS GenerateAst ${GENERATE_AST_JAR})
|
||||||
|
|
||||||
|
add_jar(Lox
|
||||||
|
Lox.java
|
||||||
|
TokenType.java
|
||||||
|
Token.java
|
||||||
|
Scanner.java
|
||||||
|
${GENERATED_JAVA_FILENAMES}
|
||||||
|
Parser.java
|
||||||
|
Interpreter.java
|
||||||
|
RuntimeError.java
|
||||||
|
Environment.java
|
||||||
|
LoxCallable.java
|
||||||
|
LoxFunction.java
|
||||||
|
Return.java
|
||||||
|
Resolver.java
|
||||||
|
LoxClass.java
|
||||||
|
LoxInstance.java
|
||||||
|
ENTRY_POINT com/craftinginterpreters/lox/Lox)
|
63
jlox/src/com/craftinginterpreters/lox/Environment.java
Normal file
63
jlox/src/com/craftinginterpreters/lox/Environment.java
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class Environment {
|
||||||
|
final Environment enclosing;
|
||||||
|
private final Map<String, Object> values = new HashMap<>();
|
||||||
|
|
||||||
|
Environment() {
|
||||||
|
enclosing = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment(Environment enclosing) {
|
||||||
|
this.enclosing = enclosing;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object get(Token name) {
|
||||||
|
if (values.containsKey(name.lexeme)) {
|
||||||
|
return values.get(name.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enclosing != null)
|
||||||
|
return enclosing.get(name);
|
||||||
|
|
||||||
|
throw new RuntimeError(name, "Undefined variable '" + name.lexeme + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void assign(Token name, Object value) {
|
||||||
|
if (values.containsKey(name.lexeme)) {
|
||||||
|
values.put(name.lexeme, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enclosing != null) {
|
||||||
|
enclosing.assign(name, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeError(name, "Undefined variable '" + name.lexeme + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void define(String name, Object value) {
|
||||||
|
values.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment ancestor(int distance) {
|
||||||
|
Environment environment = this;
|
||||||
|
for (int i = 0; i < distance; i++) {
|
||||||
|
environment = environment.enclosing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object getAt(int distance, String name) {
|
||||||
|
return ancestor(distance).values.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assignAt(int distance, Token name, Object value) {
|
||||||
|
ancestor(distance).values.put(name.lexeme, value);
|
||||||
|
}
|
||||||
|
}
|
392
jlox/src/com/craftinginterpreters/lox/Interpreter.java
Normal file
392
jlox/src/com/craftinginterpreters/lox/Interpreter.java
Normal file
|
@ -0,0 +1,392 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
|
||||||
|
final Environment globals = new Environment();
|
||||||
|
private Environment environment = globals;
|
||||||
|
private final Map<Expr, Integer> locals = new HashMap<>();
|
||||||
|
|
||||||
|
Interpreter() {
|
||||||
|
globals.define("clock", new LoxCallable() {
|
||||||
|
@Override
|
||||||
|
public int arity() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object call(Interpreter interpreter, List<Object> arguments) {
|
||||||
|
return (double) System.currentTimeMillis() / 1000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "<native fn>";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitLiteralExpr(Expr.Literal expr) {
|
||||||
|
return expr.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitLogicalExpr(Expr.Logical expr) {
|
||||||
|
Object left = evaluate(expr.left);
|
||||||
|
|
||||||
|
if (expr.operator.type == TokenType.OR) {
|
||||||
|
if (isTruthy(left))
|
||||||
|
return left;
|
||||||
|
} else {
|
||||||
|
if (!isTruthy(left))
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
return evaluate(expr.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitSetExpr(Expr.Set expr) {
|
||||||
|
Object object = evaluate(expr.object);
|
||||||
|
|
||||||
|
if (!(object instanceof LoxInstance)) {
|
||||||
|
throw new RuntimeError(expr.name, "Only instances have fields.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Object value = evaluate(expr.value);
|
||||||
|
((LoxInstance) object).set(expr.name, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitSuperExpr(Expr.Super expr) {
|
||||||
|
int distance = locals.get(expr);
|
||||||
|
LoxClass superclass = (LoxClass) environment.getAt(distance, "super");
|
||||||
|
|
||||||
|
LoxInstance object = (LoxInstance) environment.getAt(distance - 1, "this");
|
||||||
|
|
||||||
|
LoxFunction method = superclass.findMethod(expr.method.lexeme);
|
||||||
|
|
||||||
|
if (method == null) {
|
||||||
|
throw new RuntimeError(expr.method, "Undefined property '" + expr.method.lexeme + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return method.bind(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitThisExpr(Expr.This expr) {
|
||||||
|
return lookUpVariable(expr.keyword, expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitUnaryExpr(Expr.Unary expr) {
|
||||||
|
Object right = evaluate(expr.right);
|
||||||
|
|
||||||
|
switch (expr.operator.type) {
|
||||||
|
case BANG:
|
||||||
|
return !isTruthy(right);
|
||||||
|
case MINUS:
|
||||||
|
checkNumberOperand(expr.operator, right);
|
||||||
|
return -(double) right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unreachable.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitVariableExpr(Expr.Variable expr) {
|
||||||
|
return lookUpVariable(expr.name, expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object lookUpVariable(Token name, Expr expr) {
|
||||||
|
Integer distance = locals.get(expr);
|
||||||
|
if (distance != null) {
|
||||||
|
return environment.getAt(distance, name.lexeme);
|
||||||
|
} else {
|
||||||
|
return globals.get(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNumberOperand(Token operator, Object operand) {
|
||||||
|
if (operand instanceof Double)
|
||||||
|
return;
|
||||||
|
throw new RuntimeError(operator, "Operand must be a number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNumberOperands(Token operator, Object left, Object right) {
|
||||||
|
if (left instanceof Double && right instanceof Double)
|
||||||
|
return;
|
||||||
|
|
||||||
|
throw new RuntimeError(operator, "Operands must be numbers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTruthy(Object object) {
|
||||||
|
if (object == null)
|
||||||
|
return false;
|
||||||
|
if (object instanceof Boolean)
|
||||||
|
return (boolean) object;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEqual(Object a, Object b) {
|
||||||
|
if (a == null && b == null)
|
||||||
|
return true;
|
||||||
|
if (a == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return a.equals(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String stringify(Object object) {
|
||||||
|
if (object == null)
|
||||||
|
return "nil";
|
||||||
|
|
||||||
|
if (object instanceof Double) {
|
||||||
|
String text = object.toString();
|
||||||
|
if (text.endsWith(".0")) {
|
||||||
|
text = text.substring(0, text.length() - 2);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return object.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitGroupingExpr(Expr.Grouping expr) {
|
||||||
|
return evaluate(expr.expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object evaluate(Expr expr) {
|
||||||
|
return expr.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void execute(Stmt stmt) {
|
||||||
|
stmt.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resolve(Expr expr, int depth) {
|
||||||
|
locals.put(expr, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeBlock(List<Stmt> statements, Environment environment) {
|
||||||
|
Environment previous = this.environment;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.environment = environment;
|
||||||
|
|
||||||
|
for (Stmt statement : statements) {
|
||||||
|
execute(statement);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.environment = previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitBlockStmt(Stmt.Block stmt) {
|
||||||
|
executeBlock(stmt.statements, new Environment(environment));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitClassStmt(Stmt.Class stmt) {
|
||||||
|
Object superclass = null;
|
||||||
|
if (stmt.superclass != null) {
|
||||||
|
superclass = evaluate(stmt.superclass);
|
||||||
|
if (!(superclass instanceof LoxClass)) {
|
||||||
|
throw new RuntimeError(stmt.superclass.name, "Superclass must be a class.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
environment.define(stmt.name.lexeme, null);
|
||||||
|
|
||||||
|
if (stmt.superclass != null) {
|
||||||
|
environment = new Environment(environment);
|
||||||
|
environment.define("super", superclass);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, LoxFunction> methods = new HashMap<>();
|
||||||
|
for (Stmt.Function method : stmt.methods) {
|
||||||
|
LoxFunction function = new LoxFunction(method, environment, method.name.lexeme.equals("init"));
|
||||||
|
methods.put(method.name.lexeme, function);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoxClass klass = new LoxClass(stmt.name.lexeme, (LoxClass) superclass, methods);
|
||||||
|
|
||||||
|
if (superclass != null) {
|
||||||
|
environment = environment.enclosing;
|
||||||
|
}
|
||||||
|
|
||||||
|
environment.assign(stmt.name, klass);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitExpressionStmt(Stmt.Expression stmt) {
|
||||||
|
evaluate(stmt.expression);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitFunctionStmt(Stmt.Function stmt) {
|
||||||
|
LoxFunction function = new LoxFunction(stmt, environment, false);
|
||||||
|
environment.define(stmt.name.lexeme, function);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitIfStmt(Stmt.If stmt) {
|
||||||
|
if (isTruthy(evaluate(stmt.condition))) {
|
||||||
|
execute(stmt.thenBranch);
|
||||||
|
} else if (stmt.elseBranch != null) {
|
||||||
|
execute(stmt.elseBranch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitPrintStmt(Stmt.Print stmt) {
|
||||||
|
Object value = evaluate(stmt.expression);
|
||||||
|
System.out.println(stringify(value));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitReturnStmt(Stmt.Return stmt) {
|
||||||
|
Object value = null;
|
||||||
|
if (stmt.value != null)
|
||||||
|
value = evaluate(stmt.value);
|
||||||
|
|
||||||
|
throw new Return(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitVarStmt(Stmt.Var stmt) {
|
||||||
|
Object value = null;
|
||||||
|
if (stmt.initializer != null) {
|
||||||
|
value = evaluate(stmt.initializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
environment.define(stmt.name.lexeme, value);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitWhileStmt(Stmt.While stmt) {
|
||||||
|
while (isTruthy(evaluate(stmt.condition))) {
|
||||||
|
execute(stmt.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitAssignExpr(Expr.Assign expr) {
|
||||||
|
Object value = evaluate(expr.value);
|
||||||
|
|
||||||
|
Integer distance = locals.get(expr);
|
||||||
|
if (distance != null) {
|
||||||
|
environment.assignAt(distance, expr.name, value);
|
||||||
|
} else {
|
||||||
|
globals.assign(expr.name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitBinaryExpr(Expr.Binary expr) {
|
||||||
|
Object left = evaluate(expr.left);
|
||||||
|
Object right = evaluate(expr.right);
|
||||||
|
|
||||||
|
switch (expr.operator.type) {
|
||||||
|
case GREATER:
|
||||||
|
checkNumberOperands(expr.operator, left, right);
|
||||||
|
return (double) left > (double) right;
|
||||||
|
case GREATER_EQUAL:
|
||||||
|
checkNumberOperands(expr.operator, left, right);
|
||||||
|
return (double) left >= (double) right;
|
||||||
|
case LESS:
|
||||||
|
checkNumberOperands(expr.operator, left, right);
|
||||||
|
return (double) left < (double) right;
|
||||||
|
case LESS_EQUAL:
|
||||||
|
checkNumberOperands(expr.operator, left, right);
|
||||||
|
return (double) left <= (double) right;
|
||||||
|
case BANG_EQUAL:
|
||||||
|
return !isEqual(left, right);
|
||||||
|
case EQUAL_EQUAL:
|
||||||
|
return isEqual(left, right);
|
||||||
|
case MINUS:
|
||||||
|
checkNumberOperands(expr.operator, left, right);
|
||||||
|
return (double) left - (double) right;
|
||||||
|
case PLUS:
|
||||||
|
if (left instanceof Double && right instanceof Double) {
|
||||||
|
return (double) left + (double) right;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (left instanceof String && right instanceof String) {
|
||||||
|
return (String) left + (String) right;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeError(expr.operator, "Operands must be two numbers or two strings.");
|
||||||
|
case SLASH:
|
||||||
|
checkNumberOperands(expr.operator, left, right);
|
||||||
|
return (double) left / (double) right;
|
||||||
|
case STAR:
|
||||||
|
checkNumberOperands(expr.operator, left, right);
|
||||||
|
return (double) left * (double) right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unreachable.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitCallExpr(Expr.Call expr) {
|
||||||
|
Object callee = evaluate(expr.callee);
|
||||||
|
|
||||||
|
List<Object> arguments = new ArrayList<>();
|
||||||
|
for (Expr argument : expr.arguments) {
|
||||||
|
arguments.add(evaluate(argument));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(callee instanceof LoxCallable)) {
|
||||||
|
throw new RuntimeError(expr.paren, "Can only call functions and classes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
LoxCallable function = (LoxCallable) callee;
|
||||||
|
if (arguments.size() != function.arity()) {
|
||||||
|
throw new RuntimeError(expr.paren,
|
||||||
|
"Expected " + function.arity() + " arguments but got " + arguments.size() + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
return function.call(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object visitGetExpr(Expr.Get expr) {
|
||||||
|
Object object = evaluate(expr.object);
|
||||||
|
if (object instanceof LoxInstance) {
|
||||||
|
return ((LoxInstance) object).get(expr.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeError(expr.name, "Only instances have properties.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void interpret(List<Stmt> statements) {
|
||||||
|
try {
|
||||||
|
for (Stmt statement : statements) {
|
||||||
|
execute(statement);
|
||||||
|
}
|
||||||
|
} catch (RuntimeError error) {
|
||||||
|
Lox.runtimeError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,9 @@ import java.nio.file.Paths;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Lox {
|
public class Lox {
|
||||||
private static boolean hadError = false;
|
private static final Interpreter interpreter = new Interpreter();
|
||||||
|
public static boolean hadError = false;
|
||||||
|
public static boolean hadRuntimeError = false;
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
if (args.length > 1) {
|
if (args.length > 1) {
|
||||||
|
@ -27,7 +29,10 @@ public class Lox {
|
||||||
run(new String(bytes, Charset.defaultCharset()));
|
run(new String(bytes, Charset.defaultCharset()));
|
||||||
|
|
||||||
// Indicate an error in the exit code.
|
// Indicate an error in the exit code.
|
||||||
if (hadError) System.exit(65);
|
if (hadError)
|
||||||
|
System.exit(65);
|
||||||
|
if (hadRuntimeError)
|
||||||
|
System.exit(70);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void runPrompt() throws IOException {
|
private static void runPrompt() throws IOException {
|
||||||
|
@ -37,7 +42,8 @@ public class Lox {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
System.out.print("> ");
|
System.out.print("> ");
|
||||||
String line = reader.readLine();
|
String line = reader.readLine();
|
||||||
if (line == null) break;
|
if (line == null)
|
||||||
|
break;
|
||||||
run(line);
|
run(line);
|
||||||
hadError = false;
|
hadError = false;
|
||||||
}
|
}
|
||||||
|
@ -46,11 +52,21 @@ public class Lox {
|
||||||
private static void run(String source) {
|
private static void run(String source) {
|
||||||
Scanner scanner = new Scanner(source);
|
Scanner scanner = new Scanner(source);
|
||||||
List<Token> tokens = scanner.scanTokens();
|
List<Token> tokens = scanner.scanTokens();
|
||||||
|
Parser parser = new Parser(tokens);
|
||||||
|
List<Stmt> statements = parser.parse();
|
||||||
|
|
||||||
// For now, just print the tokens.
|
// Stop if there was a syntax error
|
||||||
for (Token token : tokens) {
|
if (hadError)
|
||||||
System.out.println(token);
|
return;
|
||||||
}
|
|
||||||
|
Resolver resolver = new Resolver(interpreter);
|
||||||
|
resolver.resolve(statements);
|
||||||
|
|
||||||
|
// Stop if there was a resolution error
|
||||||
|
if (hadError)
|
||||||
|
return;
|
||||||
|
|
||||||
|
interpreter.interpret(statements);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void error(int line, String message) {
|
public static void error(int line, String message) {
|
||||||
|
@ -61,4 +77,17 @@ public class Lox {
|
||||||
System.err.println("[line " + line + "] Error" + where + ": " + message);
|
System.err.println("[line " + line + "] Error" + where + ": " + message);
|
||||||
hadError = true;
|
hadError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void error(Token token, String message) {
|
||||||
|
if (token.type == TokenType.EOF) {
|
||||||
|
report(token.line, " at end", message);
|
||||||
|
} else {
|
||||||
|
report(token.line, " at '" + token.lexeme + "'", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runtimeError(RuntimeError error) {
|
||||||
|
System.err.println(error.getMessage() + "\n[line " + error.token.line + "]");
|
||||||
|
hadRuntimeError = true;
|
||||||
|
}
|
||||||
}
|
}
|
9
jlox/src/com/craftinginterpreters/lox/LoxCallable.java
Normal file
9
jlox/src/com/craftinginterpreters/lox/LoxCallable.java
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
interface LoxCallable {
|
||||||
|
int arity();
|
||||||
|
|
||||||
|
Object call(Interpreter interpreter, List<Object> arguments);
|
||||||
|
}
|
51
jlox/src/com/craftinginterpreters/lox/LoxClass.java
Normal file
51
jlox/src/com/craftinginterpreters/lox/LoxClass.java
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class LoxClass implements LoxCallable {
|
||||||
|
final String name;
|
||||||
|
final LoxClass superclass;
|
||||||
|
private final Map<String, LoxFunction> methods;
|
||||||
|
|
||||||
|
LoxClass(String name, LoxClass superclass, Map<String, LoxFunction> methods) {
|
||||||
|
this.superclass = superclass;
|
||||||
|
this.name = name;
|
||||||
|
this.methods = methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoxFunction findMethod(String name) {
|
||||||
|
if (methods.containsKey(name)) {
|
||||||
|
return methods.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (superclass != null) {
|
||||||
|
return superclass.findMethod(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object call(Interpreter interpreter, List<Object> arguments) {
|
||||||
|
LoxInstance instance = new LoxInstance(this);
|
||||||
|
LoxFunction initializer = findMethod("init");
|
||||||
|
if (initializer != null) {
|
||||||
|
initializer.bind(instance).call(interpreter, arguments);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int arity() {
|
||||||
|
LoxFunction initializer = findMethod("init");
|
||||||
|
if (initializer == null) return 0;
|
||||||
|
return initializer.arity();
|
||||||
|
}
|
||||||
|
}
|
52
jlox/src/com/craftinginterpreters/lox/LoxFunction.java
Normal file
52
jlox/src/com/craftinginterpreters/lox/LoxFunction.java
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class LoxFunction implements LoxCallable {
|
||||||
|
private final Stmt.Function declaration;
|
||||||
|
private final Environment closure;
|
||||||
|
private final boolean isInitializer;
|
||||||
|
|
||||||
|
LoxFunction(Stmt.Function declaration, Environment closure, boolean isInitializer) {
|
||||||
|
this.isInitializer = isInitializer;
|
||||||
|
this.closure = closure;
|
||||||
|
this.declaration = declaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoxFunction bind(LoxInstance instance) {
|
||||||
|
Environment environment = new Environment(closure);
|
||||||
|
environment.define("this", instance);
|
||||||
|
return new LoxFunction(declaration, environment, isInitializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int arity() {
|
||||||
|
return declaration.params.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object call(Interpreter interpreter, List<Object> arguments) {
|
||||||
|
Environment environment = new Environment(closure);
|
||||||
|
|
||||||
|
for (int i = 0; i < declaration.params.size(); i++) {
|
||||||
|
environment.define(declaration.params.get(i).lexeme, arguments.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
interpreter.executeBlock(declaration.body, environment);
|
||||||
|
} catch (Return returnValue) {
|
||||||
|
if (isInitializer)
|
||||||
|
return closure.getAt(0, "this");
|
||||||
|
return returnValue.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInitializer)
|
||||||
|
return closure.getAt(0, "this");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "<fn " + declaration.name.lexeme + ">";
|
||||||
|
}
|
||||||
|
}
|
34
jlox/src/com/craftinginterpreters/lox/LoxInstance.java
Normal file
34
jlox/src/com/craftinginterpreters/lox/LoxInstance.java
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class LoxInstance {
|
||||||
|
private LoxClass klass;
|
||||||
|
private final Map<String, Object> fields = new HashMap<>();
|
||||||
|
|
||||||
|
LoxInstance(LoxClass klass) {
|
||||||
|
this.klass = klass;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object get(Token name) {
|
||||||
|
if (fields.containsKey(name.lexeme)) {
|
||||||
|
return fields.get(name.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
LoxFunction method = klass.findMethod(name.lexeme);
|
||||||
|
if (method != null)
|
||||||
|
return method.bind(this);
|
||||||
|
|
||||||
|
throw new RuntimeError(name, "Undefined proprety '" + name.lexeme + "'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(Token name, Object value) {
|
||||||
|
fields.put(name.lexeme, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return klass.name + " instance";
|
||||||
|
}
|
||||||
|
}
|
457
jlox/src/com/craftinginterpreters/lox/Parser.java
Normal file
457
jlox/src/com/craftinginterpreters/lox/Parser.java
Normal file
|
@ -0,0 +1,457 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
import static com.craftinginterpreters.lox.TokenType.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class Parser {
|
||||||
|
private static class ParseError extends RuntimeException {
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Token> tokens;
|
||||||
|
private int current = 0;
|
||||||
|
|
||||||
|
Parser(List<Token> tokens) {
|
||||||
|
this.tokens = tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Stmt> parse() {
|
||||||
|
List<Stmt> statements = new ArrayList<>();
|
||||||
|
|
||||||
|
while (!isAtEnd()) {
|
||||||
|
statements.add(declaration());
|
||||||
|
}
|
||||||
|
|
||||||
|
return statements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr expression() {
|
||||||
|
return assignment();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stmt declaration() {
|
||||||
|
try {
|
||||||
|
if (match(CLASS))
|
||||||
|
return classDeclaration();
|
||||||
|
if (match(FUN))
|
||||||
|
return function("function");
|
||||||
|
if (match(VAR))
|
||||||
|
return varDeclaration();
|
||||||
|
|
||||||
|
return statement();
|
||||||
|
} catch (ParseError error) {
|
||||||
|
synchronize();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stmt classDeclaration() {
|
||||||
|
Token name = consume(IDENTIFIER, "Expect class name.");
|
||||||
|
|
||||||
|
Expr.Variable superclass = null;
|
||||||
|
if (match(LESS)) {
|
||||||
|
consume(IDENTIFIER, "Expect superclass name.");
|
||||||
|
superclass = new Expr.Variable(previous());
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(LEFT_BRACE, "Expect '{' before class body.");
|
||||||
|
|
||||||
|
List<Stmt.Function> methods = new ArrayList<>();
|
||||||
|
while (!check(RIGHT_BRACE) && !isAtEnd()) {
|
||||||
|
methods.add(function("method"));
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(RIGHT_BRACE, "Expect '}' after class body.");
|
||||||
|
|
||||||
|
return new Stmt.Class(name, superclass, methods);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stmt statement() {
|
||||||
|
if (match(FOR))
|
||||||
|
return forStatement();
|
||||||
|
if (match(IF))
|
||||||
|
return ifStatement();
|
||||||
|
if (match(PRINT))
|
||||||
|
return printStatement();
|
||||||
|
if (match(RETURN))
|
||||||
|
return returnStatement();
|
||||||
|
if (match(WHILE))
|
||||||
|
return whileStatement();
|
||||||
|
if (match(LEFT_BRACE))
|
||||||
|
return new Stmt.Block(block());
|
||||||
|
|
||||||
|
return expressionStatement();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stmt forStatement() {
|
||||||
|
consume(LEFT_PAREN, "Expect '(' after 'for'.");
|
||||||
|
|
||||||
|
Stmt initializer;
|
||||||
|
if (match(SEMICOLON)) {
|
||||||
|
initializer = null;
|
||||||
|
} else if (match(VAR)) {
|
||||||
|
initializer = varDeclaration();
|
||||||
|
} else {
|
||||||
|
initializer = expressionStatement();
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr condition = null;
|
||||||
|
if (!check(SEMICOLON)) {
|
||||||
|
condition = expression();
|
||||||
|
}
|
||||||
|
consume(SEMICOLON, "Expect ';' after loop condition.");
|
||||||
|
|
||||||
|
Expr increment = null;
|
||||||
|
if (!check(RIGHT_PAREN)) {
|
||||||
|
increment = expression();
|
||||||
|
}
|
||||||
|
consume(RIGHT_PAREN, "Expect ')' after for clauses.");
|
||||||
|
Stmt body = statement();
|
||||||
|
|
||||||
|
if (increment != null) {
|
||||||
|
body = new Stmt.Block(Arrays.asList(body, new Stmt.Expression(increment)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (condition == null)
|
||||||
|
condition = new Expr.Literal(true);
|
||||||
|
body = new Stmt.While(condition, body);
|
||||||
|
|
||||||
|
if (initializer != null) {
|
||||||
|
body = new Stmt.Block(Arrays.asList(initializer, body));
|
||||||
|
}
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stmt ifStatement() {
|
||||||
|
consume(LEFT_PAREN, "Expect '(' after 'if'.");
|
||||||
|
Expr condition = expression();
|
||||||
|
consume(RIGHT_PAREN, "Expect ')' after if condition.");
|
||||||
|
|
||||||
|
Stmt thenBranch = statement();
|
||||||
|
Stmt elseBranch = null;
|
||||||
|
if (match(ELSE)) {
|
||||||
|
elseBranch = statement();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Stmt.If(condition, thenBranch, elseBranch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stmt printStatement() {
|
||||||
|
Expr value = expression();
|
||||||
|
consume(SEMICOLON, "Expect ';' after value.");
|
||||||
|
return new Stmt.Print(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stmt returnStatement() {
|
||||||
|
Token keyword = previous();
|
||||||
|
Expr value = null;
|
||||||
|
if (!check(SEMICOLON)) {
|
||||||
|
value = expression();
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(SEMICOLON, "Expect ';' after return value.");
|
||||||
|
return new Stmt.Return(keyword, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stmt varDeclaration() {
|
||||||
|
Token name = consume(IDENTIFIER, "Expect variable name.");
|
||||||
|
|
||||||
|
Expr initializer = null;
|
||||||
|
if (match(EQUAL)) {
|
||||||
|
initializer = expression();
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(SEMICOLON, "Expect ';' after variable declaration.");
|
||||||
|
return new Stmt.Var(name, initializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stmt whileStatement() {
|
||||||
|
consume(LEFT_PAREN, "Expect '(' after 'while'.");
|
||||||
|
Expr condition = expression();
|
||||||
|
consume(RIGHT_PAREN, "Expect ')' after condition.");
|
||||||
|
Stmt body = statement();
|
||||||
|
|
||||||
|
return new Stmt.While(condition, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stmt expressionStatement() {
|
||||||
|
Expr expr = expression();
|
||||||
|
consume(SEMICOLON, "Expect ';' after expression.");
|
||||||
|
return new Stmt.Expression(expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stmt.Function function(String kind) {
|
||||||
|
Token name = consume(IDENTIFIER, "Expect " + kind + " name.");
|
||||||
|
consume(LEFT_PAREN, "Expect '(' after " + kind + " name.");
|
||||||
|
List<Token> parameters = new ArrayList();
|
||||||
|
if (!check(RIGHT_PAREN)) {
|
||||||
|
do {
|
||||||
|
if (parameters.size() >= 255) {
|
||||||
|
error(peek(), "Can't have more than 255 parameters.");
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters.add(consume(IDENTIFIER, "Expect parameter name."));
|
||||||
|
} while (match(COMMA));
|
||||||
|
}
|
||||||
|
consume(RIGHT_PAREN, "Expect ')' after parameters.");
|
||||||
|
|
||||||
|
consume(LEFT_BRACE, "Expect '{' before " + kind + " body.");
|
||||||
|
List<Stmt> body = block();
|
||||||
|
return new Stmt.Function(name, parameters, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Stmt> block() {
|
||||||
|
List<Stmt> statements = new ArrayList<>();
|
||||||
|
|
||||||
|
while (!check(RIGHT_BRACE) && !isAtEnd()) {
|
||||||
|
statements.add(declaration());
|
||||||
|
}
|
||||||
|
|
||||||
|
consume(RIGHT_BRACE, "Expect '}' after block.");
|
||||||
|
return statements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr assignment() {
|
||||||
|
Expr expr = or();
|
||||||
|
|
||||||
|
if (match(EQUAL)) {
|
||||||
|
Token equals = previous();
|
||||||
|
Expr value = assignment();
|
||||||
|
|
||||||
|
if (expr instanceof Expr.Variable) {
|
||||||
|
Token name = ((Expr.Variable) expr).name;
|
||||||
|
return new Expr.Assign(name, value);
|
||||||
|
} else if (expr instanceof Expr.Get) {
|
||||||
|
Expr.Get get = (Expr.Get) expr;
|
||||||
|
return new Expr.Set(get.object, get.name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(equals, "Invalid assignment target.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr or() {
|
||||||
|
Expr expr = and();
|
||||||
|
|
||||||
|
while (match(OR)) {
|
||||||
|
Token operator = previous();
|
||||||
|
Expr right = and();
|
||||||
|
expr = new Expr.Logical(expr, operator, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr and() {
|
||||||
|
Expr expr = equality();
|
||||||
|
|
||||||
|
while (match(AND)) {
|
||||||
|
Token operator = previous();
|
||||||
|
Expr right = equality();
|
||||||
|
expr = new Expr.Logical(expr, operator, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr equality() {
|
||||||
|
Expr expr = comparison();
|
||||||
|
|
||||||
|
while (match(BANG_EQUAL, EQUAL_EQUAL)) {
|
||||||
|
Token operator = previous();
|
||||||
|
Expr right = comparison();
|
||||||
|
expr = new Expr.Binary(expr, operator, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr comparison() {
|
||||||
|
Expr expr = term();
|
||||||
|
|
||||||
|
while (match(GREATER, GREATER_EQUAL, LESS, LESS_EQUAL)) {
|
||||||
|
Token operator = previous();
|
||||||
|
Expr right = term();
|
||||||
|
expr = new Expr.Binary(expr, operator, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr term() {
|
||||||
|
Expr expr = factor();
|
||||||
|
|
||||||
|
while (match(MINUS, PLUS)) {
|
||||||
|
Token operator = previous();
|
||||||
|
Expr right = factor();
|
||||||
|
expr = new Expr.Binary(expr, operator, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr factor() {
|
||||||
|
Expr expr = unary();
|
||||||
|
|
||||||
|
while (match(SLASH, STAR)) {
|
||||||
|
Token operator = previous();
|
||||||
|
Expr right = unary();
|
||||||
|
expr = new Expr.Binary(expr, operator, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr unary() {
|
||||||
|
if (match(BANG, MINUS)) {
|
||||||
|
Token operator = previous();
|
||||||
|
Expr right = unary();
|
||||||
|
return new Expr.Unary(operator, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
return call();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr finishCall(Expr callee) {
|
||||||
|
List<Expr> arguments = new ArrayList<>();
|
||||||
|
if (!check(RIGHT_PAREN)) {
|
||||||
|
do {
|
||||||
|
if (arguments.size() >= 255) {
|
||||||
|
error(peek(), "Can't have more than 255 arguments.");
|
||||||
|
}
|
||||||
|
arguments.add(expression());
|
||||||
|
} while (match(COMMA));
|
||||||
|
}
|
||||||
|
|
||||||
|
Token paren = consume(RIGHT_PAREN, "Expect ')' after arguments.");
|
||||||
|
|
||||||
|
return new Expr.Call(callee, paren, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr call() {
|
||||||
|
Expr expr = primary();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (match(LEFT_PAREN)) {
|
||||||
|
expr = finishCall(expr);
|
||||||
|
} else if (match(DOT)) {
|
||||||
|
Token name = consume(IDENTIFIER, "Expect property name after '.'");
|
||||||
|
expr = new Expr.Get(expr, name);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expr primary() {
|
||||||
|
if (match(FALSE))
|
||||||
|
return new Expr.Literal(false);
|
||||||
|
if (match(TRUE))
|
||||||
|
return new Expr.Literal(true);
|
||||||
|
if (match(NIL))
|
||||||
|
return new Expr.Literal(null);
|
||||||
|
|
||||||
|
if (match(NUMBER, STRING)) {
|
||||||
|
return new Expr.Literal(previous().literal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match(SUPER)) {
|
||||||
|
Token keyword = previous();
|
||||||
|
consume(DOT, "Expect '.' after 'super'.");
|
||||||
|
Token method = consume(IDENTIFIER, "Expect superclass method name.");
|
||||||
|
return new Expr.Super(keyword, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match(THIS))
|
||||||
|
return new Expr.This(previous());
|
||||||
|
|
||||||
|
if (match(IDENTIFIER)) {
|
||||||
|
return new Expr.Variable(previous());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match(LEFT_PAREN)) {
|
||||||
|
Expr expr = expression();
|
||||||
|
consume(RIGHT_PAREN, "Expect ')' after expression.");
|
||||||
|
return new Expr.Grouping(expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error(peek(), "Expect expression.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean match(TokenType... types) {
|
||||||
|
for (TokenType type : types) {
|
||||||
|
if (check(type)) {
|
||||||
|
advance();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token consume(TokenType type, String message) {
|
||||||
|
if (check(type))
|
||||||
|
return advance();
|
||||||
|
|
||||||
|
throw error(peek(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean check(TokenType type) {
|
||||||
|
if (isAtEnd())
|
||||||
|
return false;
|
||||||
|
return peek().type == type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token advance() {
|
||||||
|
if (!isAtEnd())
|
||||||
|
current++;
|
||||||
|
return previous();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAtEnd() {
|
||||||
|
return peek().type == EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token peek() {
|
||||||
|
return tokens.get(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token previous() {
|
||||||
|
return tokens.get(current - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ParseError error(Token token, String message) {
|
||||||
|
Lox.error(token, message);
|
||||||
|
return new ParseError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void synchronize() {
|
||||||
|
advance();
|
||||||
|
|
||||||
|
while (!isAtEnd()) {
|
||||||
|
if (previous().type == SEMICOLON)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (peek().type) {
|
||||||
|
case CLASS:
|
||||||
|
case FUN:
|
||||||
|
case VAR:
|
||||||
|
case FOR:
|
||||||
|
case IF:
|
||||||
|
case WHILE:
|
||||||
|
case PRINT:
|
||||||
|
case RETURN:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
299
jlox/src/com/craftinginterpreters/lox/Resolver.java
Normal file
299
jlox/src/com/craftinginterpreters/lox/Resolver.java
Normal file
|
@ -0,0 +1,299 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
class Resolver implements Expr.Visitor<Void>, Stmt.Visitor<Void> {
|
||||||
|
private final Interpreter interpreter;
|
||||||
|
private final Stack<Map<String, Boolean>> scopes = new Stack<>();
|
||||||
|
private FunctionType currentFunction = FunctionType.NONE;
|
||||||
|
|
||||||
|
Resolver(Interpreter interpreter) {
|
||||||
|
this.interpreter = interpreter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum FunctionType {
|
||||||
|
NONE, FUNCTION, INITIALIZER, METHOD
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ClassType {
|
||||||
|
NONE, CLASS, SUBCLASS
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClassType currentClass = ClassType.NONE;
|
||||||
|
|
||||||
|
public void resolve(List<Stmt> statements) {
|
||||||
|
for (Stmt statement : statements) {
|
||||||
|
resolve(statement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitBlockStmt(Stmt.Block stmt) {
|
||||||
|
beginScope();
|
||||||
|
resolve(stmt.statements);
|
||||||
|
endScope();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitClassStmt(Stmt.Class stmt) {
|
||||||
|
ClassType enclosingClass = currentClass;
|
||||||
|
currentClass = ClassType.CLASS;
|
||||||
|
|
||||||
|
declare(stmt.name);
|
||||||
|
define(stmt.name);
|
||||||
|
|
||||||
|
if (stmt.superclass != null && stmt.name.lexeme.equals(stmt.superclass.name.lexeme)) {
|
||||||
|
Lox.error(stmt.superclass.name, "A class can't inherit from itself.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stmt.superclass != null) {
|
||||||
|
currentClass = ClassType.SUBCLASS;
|
||||||
|
|
||||||
|
resolve(stmt.superclass);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stmt.superclass != null) {
|
||||||
|
beginScope();
|
||||||
|
scopes.peek().put("super", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
beginScope();
|
||||||
|
scopes.peek().put("this", true);
|
||||||
|
|
||||||
|
for (Stmt.Function method : stmt.methods) {
|
||||||
|
FunctionType declaration = FunctionType.METHOD;
|
||||||
|
if (method.name.lexeme.equals("init")) {
|
||||||
|
declaration = FunctionType.INITIALIZER;
|
||||||
|
}
|
||||||
|
resolveFunction(method, declaration);
|
||||||
|
}
|
||||||
|
|
||||||
|
endScope();
|
||||||
|
|
||||||
|
if (stmt.superclass != null) {
|
||||||
|
endScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentClass = enclosingClass;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitExpressionStmt(Stmt.Expression stmt) {
|
||||||
|
resolve(stmt.expression);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitFunctionStmt(Stmt.Function stmt) {
|
||||||
|
declare(stmt.name);
|
||||||
|
define(stmt.name);
|
||||||
|
|
||||||
|
resolveFunction(stmt, FunctionType.FUNCTION);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitIfStmt(Stmt.If stmt) {
|
||||||
|
resolve(stmt.condition);
|
||||||
|
resolve(stmt.thenBranch);
|
||||||
|
if (stmt.elseBranch != null)
|
||||||
|
resolve(stmt.elseBranch);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitPrintStmt(Stmt.Print stmt) {
|
||||||
|
resolve(stmt.expression);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitReturnStmt(Stmt.Return stmt) {
|
||||||
|
if (currentFunction == FunctionType.NONE) {
|
||||||
|
Lox.error(stmt.keyword, "Can't return from top-level code.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stmt.value != null) {
|
||||||
|
if (currentFunction == FunctionType.INITIALIZER) {
|
||||||
|
Lox.error(stmt.keyword, "Can't return a value from an initializer.");
|
||||||
|
}
|
||||||
|
resolve(stmt.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitVarStmt(Stmt.Var stmt) {
|
||||||
|
declare(stmt.name);
|
||||||
|
if (stmt.initializer != null) {
|
||||||
|
resolve(stmt.initializer);
|
||||||
|
}
|
||||||
|
define(stmt.name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitWhileStmt(Stmt.While stmt) {
|
||||||
|
resolve(stmt.condition);
|
||||||
|
resolve(stmt.body);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitAssignExpr(Expr.Assign expr) {
|
||||||
|
resolve(expr.value);
|
||||||
|
resolveLocal(expr, expr.name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitBinaryExpr(Expr.Binary expr) {
|
||||||
|
resolve(expr.left);
|
||||||
|
resolve(expr.right);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitCallExpr(Expr.Call expr) {
|
||||||
|
resolve(expr.callee);
|
||||||
|
|
||||||
|
for (Expr argument : expr.arguments) {
|
||||||
|
resolve(argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitGetExpr(Expr.Get expr) {
|
||||||
|
resolve(expr.object);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitGroupingExpr(Expr.Grouping expr) {
|
||||||
|
resolve(expr.expression);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitLiteralExpr(Expr.Literal expr) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitSetExpr(Expr.Set expr) {
|
||||||
|
resolve(expr.value);
|
||||||
|
resolve(expr.object);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitSuperExpr(Expr.Super expr) {
|
||||||
|
if (currentClass == ClassType.NONE) {
|
||||||
|
Lox.error(expr.keyword, "Can't use 'super' outside of a class.");
|
||||||
|
} else if (currentClass != ClassType.SUBCLASS) {
|
||||||
|
Lox.error(expr.keyword, "Can't use 'super' in a class with no superclass.");
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveLocal(expr, expr.keyword);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitThisExpr(Expr.This expr) {
|
||||||
|
if (currentClass == ClassType.NONE) {
|
||||||
|
Lox.error(expr.keyword, "Can't use 'this' outside of a class.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveLocal(expr, expr.keyword);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitLogicalExpr(Expr.Logical expr) {
|
||||||
|
resolve(expr.left);
|
||||||
|
resolve(expr.right);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitUnaryExpr(Expr.Unary expr) {
|
||||||
|
resolve(expr.right);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Void visitVariableExpr(Expr.Variable expr) {
|
||||||
|
if (!scopes.isEmpty() && scopes.peek().get(expr.name.lexeme) == Boolean.FALSE) {
|
||||||
|
Lox.error(expr.name, "Can't read local variable in its own initializer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveLocal(expr, expr.name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolve(Stmt stmt) {
|
||||||
|
stmt.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolve(Expr expr) {
|
||||||
|
expr.accept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveFunction(Stmt.Function function, FunctionType type) {
|
||||||
|
FunctionType enclosingFunction = currentFunction;
|
||||||
|
currentFunction = type;
|
||||||
|
|
||||||
|
beginScope();
|
||||||
|
for (Token param : function.params) {
|
||||||
|
declare(param);
|
||||||
|
define(param);
|
||||||
|
}
|
||||||
|
resolve(function.body);
|
||||||
|
endScope();
|
||||||
|
currentFunction = enclosingFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void beginScope() {
|
||||||
|
scopes.push(new HashMap<String, Boolean>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void endScope() {
|
||||||
|
scopes.pop();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void declare(Token name) {
|
||||||
|
if (scopes.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Map<String, Boolean> scope = scopes.peek();
|
||||||
|
if (scope.containsKey(name.lexeme)) {
|
||||||
|
Lox.error(name, "Already variable with this name in this scope.");
|
||||||
|
}
|
||||||
|
scope.put(name.lexeme, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void define(Token name) {
|
||||||
|
if (scopes.isEmpty())
|
||||||
|
return;
|
||||||
|
scopes.peek().put(name.lexeme, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveLocal(Expr expr, Token name) {
|
||||||
|
for (int i = scopes.size() - 1; i >= 0; i--) {
|
||||||
|
if (scopes.get(i).containsKey(name.lexeme)) {
|
||||||
|
interpreter.resolve(expr, scopes.size() - 1 - i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
jlox/src/com/craftinginterpreters/lox/Return.java
Normal file
10
jlox/src/com/craftinginterpreters/lox/Return.java
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
class Return extends RuntimeException {
|
||||||
|
final Object value;
|
||||||
|
|
||||||
|
Return(Object value) {
|
||||||
|
super(null, null, false, false);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
10
jlox/src/com/craftinginterpreters/lox/RuntimeError.java
Normal file
10
jlox/src/com/craftinginterpreters/lox/RuntimeError.java
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package com.craftinginterpreters.lox;
|
||||||
|
|
||||||
|
class RuntimeError extends RuntimeException {
|
||||||
|
final Token token;
|
||||||
|
|
||||||
|
RuntimeError(Token token, String message) {
|
||||||
|
super(message);
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
}
|
|
@ -80,8 +80,6 @@ class Scanner {
|
||||||
if (match('/')) {
|
if (match('/')) {
|
||||||
// A comment goes until the end of the line.
|
// A comment goes until the end of the line.
|
||||||
while (peek() != '\n' && !isAtEnd()) advance();
|
while (peek() != '\n' && !isAtEnd()) advance();
|
||||||
} else if (match('*')) {
|
|
||||||
multiline_comment();
|
|
||||||
} else {
|
} else {
|
||||||
addToken(SLASH);
|
addToken(SLASH);
|
||||||
}
|
}
|
||||||
|
@ -157,34 +155,6 @@ class Scanner {
|
||||||
addToken(STRING, value);
|
addToken(STRING, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void multiline_comment() {
|
|
||||||
int depth = 0;
|
|
||||||
|
|
||||||
while (!isAtEnd()
|
|
||||||
&& (peek() != '*' || peekNext() != '/' || depth != 0)) {
|
|
||||||
if (peek() == '\n') line++;
|
|
||||||
|
|
||||||
if (peek() == '/' && peekNext() == '*') {
|
|
||||||
depth++;
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (peek() == '*' && peekNext() == '/') {
|
|
||||||
depth--;
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAtEnd()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
advance();
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean match(char expected) {
|
private boolean match(char expected) {
|
||||||
if (isAtEnd()) return false;
|
if (isAtEnd()) return false;
|
||||||
if (source.charAt(current) != expected) return false;
|
if (source.charAt(current) != expected) return false;
|
3
jlox/src/com/craftinginterpreters/tool/CMakeLists.txt
Normal file
3
jlox/src/com/craftinginterpreters/tool/CMakeLists.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
add_jar(GenerateAst
|
||||||
|
GenerateAst.java
|
||||||
|
ENTRY_POINT com/craftinginterpreters/tool/GenerateAst)
|
100
jlox/src/com/craftinginterpreters/tool/GenerateAst.java
Normal file
100
jlox/src/com/craftinginterpreters/tool/GenerateAst.java
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package com.craftinginterpreters.tool;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GenerateAst {
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
if (args.length != 1) {
|
||||||
|
System.err.println("Usage: generate_ast <output directory>");
|
||||||
|
System.exit(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
String outputDir = args[0];
|
||||||
|
|
||||||
|
defineAst(outputDir, "Expr",
|
||||||
|
Arrays.asList("Assign : Token name, Expr value", "Binary : Expr left, Token operator, Expr right",
|
||||||
|
"Call : Expr callee, Token paren, List<Expr> arguments", "Get : Expr object, Token name",
|
||||||
|
"Grouping : Expr expression", "Literal : Object value",
|
||||||
|
"Logical : Expr left, Token operator, Expr right", "Set : Expr object, Token name, Expr value",
|
||||||
|
"Super : Token keyword, Token method", "This : Token keyword",
|
||||||
|
"Unary : Token operator, Expr right", "Variable : Token name"));
|
||||||
|
defineAst(outputDir, "Stmt",
|
||||||
|
Arrays.asList("Block : List<Stmt> statements",
|
||||||
|
"Class : Token name, Expr.Variable superclass, List<Stmt.Function> methods",
|
||||||
|
"Expression : Expr expression", "Function : Token name, List<Token> params, List<Stmt> body",
|
||||||
|
"If : Expr condition, Stmt thenBranch, Stmt elseBranch", "Print : Expr expression",
|
||||||
|
"Return : Token keyword, Expr value", "Var : Token name, Expr initializer",
|
||||||
|
"While : Expr condition, Stmt body"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void defineAst(String outputDir, String baseName, List<String> types) throws IOException {
|
||||||
|
String path = outputDir + "/" + baseName + ".java";
|
||||||
|
PrintWriter writer = new PrintWriter(path);
|
||||||
|
|
||||||
|
writer.println("package com.craftinginterpreters.lox;");
|
||||||
|
writer.println();
|
||||||
|
writer.println("import java.util.List;");
|
||||||
|
writer.println();
|
||||||
|
writer.println("abstract class " + baseName + " {");
|
||||||
|
|
||||||
|
defineVisitor(writer, baseName, types);
|
||||||
|
|
||||||
|
for (String type : types) {
|
||||||
|
String className = type.split(":")[0].trim();
|
||||||
|
String fields = type.split(":")[1].trim();
|
||||||
|
defineType(writer, baseName, className, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The base accept() method
|
||||||
|
writer.println();
|
||||||
|
writer.println(" abstract <R> R accept(Visitor<R> visitor);");
|
||||||
|
|
||||||
|
writer.println("}");
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void defineVisitor(PrintWriter writer, String baseName, List<String> types) {
|
||||||
|
writer.println(" interface Visitor<R> {");
|
||||||
|
|
||||||
|
for (String type : types) {
|
||||||
|
String typeName = type.split(":")[0].trim();
|
||||||
|
writer.println(" R visit" + typeName + baseName + "(" + typeName + " " + baseName.toLowerCase() + ");");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.println(" }");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void defineType(PrintWriter writer, String baseName, String className, String fieldList) {
|
||||||
|
writer.println(" static class " + className + " extends " + baseName + " {");
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
writer.println(" " + className + "(" + fieldList + ") {");
|
||||||
|
|
||||||
|
// Store parameters in fields.
|
||||||
|
String[] fields = fieldList.split(", ");
|
||||||
|
for (String field : fields) {
|
||||||
|
String name = field.split(" ")[1];
|
||||||
|
writer.println(" this." + name + " = " + name + ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.println(" }");
|
||||||
|
|
||||||
|
// Visitor pattern.
|
||||||
|
writer.println();
|
||||||
|
writer.println(" @Override");
|
||||||
|
writer.println(" <R> R accept(Visitor<R> visitor) {");
|
||||||
|
writer.println(" return visitor.visit" + className + baseName + "(this);");
|
||||||
|
writer.println(" }");
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
writer.println();
|
||||||
|
for (String field : fields) {
|
||||||
|
writer.println(" final " + field + ";");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.println(" }");
|
||||||
|
}
|
||||||
|
}
|
6
src/.gitignore
vendored
6
src/.gitignore
vendored
|
@ -1,6 +0,0 @@
|
||||||
*.jar
|
|
||||||
CMakeCache.txt
|
|
||||||
Makefile
|
|
||||||
cmake_install.cmake
|
|
||||||
|
|
||||||
CMakeFiles/
|
|
|
@ -1,15 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 2.8)
|
|
||||||
|
|
||||||
find_package(Java REQUIRED)
|
|
||||||
include(UseJava)
|
|
||||||
|
|
||||||
project(Lox NONE)
|
|
||||||
|
|
||||||
add_jar(Lox
|
|
||||||
com/craftinginterpreters/lox/Lox.java
|
|
||||||
com/craftinginterpreters/lox/TokenType.java
|
|
||||||
com/craftinginterpreters/lox/Token.java
|
|
||||||
ENTRY_POINT com/craftinginterpreters/lox/Lox)
|
|
||||||
|
|
||||||
get_target_property(_jarFile Lox JAR_FILE)
|
|
||||||
get_target_property(_classDir Lox CLASSDIR)
|
|
15
src/coffee.lox
Normal file
15
src/coffee.lox
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
class CoffeeMaker {
|
||||||
|
init(coffee) {
|
||||||
|
this.coffee = coffee;
|
||||||
|
}
|
||||||
|
|
||||||
|
brew() {
|
||||||
|
print "Enjoy your cup of " + this.coffee;
|
||||||
|
|
||||||
|
// No reusing the grounds!
|
||||||
|
this.coffee = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maker = CoffeeMaker("coffee and chicory");
|
||||||
|
maker.brew();
|
9
src/fib.lox
Normal file
9
src/fib.lox
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
fun fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
return fib(n - 1) + fib(n - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
var before = clock();
|
||||||
|
print fib(40);
|
||||||
|
var after = clock();
|
||||||
|
print after - before;
|
8
src/scone.lox
Normal file
8
src/scone.lox
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
class Scone {
|
||||||
|
topping(first, second) {
|
||||||
|
print "scone with " + first + " and " + second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var scone = Scone();
|
||||||
|
scone.topping("berries", "cream");
|
7
src/test.lox
Normal file
7
src/test.lox
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
class Bacon {
|
||||||
|
eat() {
|
||||||
|
print "Crunch crunch crunch!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bacon().eat();
|
29
testsuite/jlox.tests/tests.exp
Normal file
29
testsuite/jlox.tests/tests.exp
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
set test_name "Printing"
|
||||||
|
set command_line "jlox/jlox"
|
||||||
|
|
||||||
|
spawn $command_line
|
||||||
|
send "print \"Hello, world!\";\n"
|
||||||
|
|
||||||
|
expect {
|
||||||
|
"Error at" { fail "$test_name: error: $expect_out(buffer)\n" }
|
||||||
|
"> Hello, world!" { pass "$test_name" }
|
||||||
|
eof { fail "$test_name: Premature end of file: $expect_out(buffer)\n" }
|
||||||
|
timeout { fail "$test_name: timed out\n" }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
set test_name "Local Functions and Closures"
|
||||||
|
|
||||||
|
spawn $command_line
|
||||||
|
send "fun makeCounter() { var i = 0; fun count() { i = i + 1; print i; } return count; }
|
||||||
|
var counter = makeCounter();
|
||||||
|
counter();
|
||||||
|
counter();
|
||||||
|
"
|
||||||
|
|
||||||
|
expect {
|
||||||
|
"Error at" { fail "$test_name: error: $expect_out(buffer)\n" }
|
||||||
|
-re "> 1.*?> 2" { pass "$test_name" }
|
||||||
|
eof { fail "$test_name: Premature end of file: $expect_out(buffer)\n" }
|
||||||
|
timeout { fail "$test_name: timed out\n" }
|
||||||
|
}
|
Loading…
Reference in a new issue