Compare commits

..

106 commits

Author SHA1 Message Date
9080455fae Chapter 30.3 2022-08-15 12:17:15 -07:00
b30eb232f6 Chapter 30.2 2022-08-15 11:07:08 -07:00
8450ae6db8 Add a couple of Lox samples 2022-08-14 23:44:45 -07:00
69a9a95483 Chapter 29.3 2022-08-14 23:44:26 -07:00
1bba129074 Chapter 29.2 2022-08-14 22:27:35 -07:00
726803c9d0 Chapter 29.1 2022-08-14 20:14:43 -07:00
62858b60e4 Chapter 28.5 2022-08-14 19:56:32 -07:00
e91006b59a Apply new formatting 2022-08-14 16:13:49 -07:00
cb42c81121 Chapter 28.4 2022-08-14 16:11:39 -07:00
7871383a40 Chapter 28.3 2022-08-13 14:52:57 -07:00
389908c499 Turn off debugging 2022-08-13 14:21:08 -07:00
ac0f95683f Chapter 28.2 2022-08-13 14:20:54 -07:00
07f691425c Chapter 28.1 2022-08-13 12:20:00 -07:00
4e77cfa612 Chapter 27.4 2022-08-13 00:04:31 -07:00
f4185577e5 Chapter 27.3 2022-08-12 23:18:02 -07:00
f1e60bd641 Remove debugging statements 2022-08-12 22:46:50 -07:00
dbca60fd23 Fix bug with running code 2022-08-12 22:41:06 -07:00
a819a85209 Chapter 27.2 2022-08-12 21:17:45 -07:00
63527558a7 Chapter 27.1 2022-08-12 20:13:25 -07:00
ad6a98f6ad Chapter 26.7 2022-08-12 19:58:10 -07:00
38e4c37f0f Chapter 26.6 2022-08-12 18:22:49 -07:00
89ffc2e60a Chapter 26.5 2022-08-12 17:55:32 -07:00
14b3866ac0 Chapter 26.1, 26.2, 26.3, and 26.4 2022-06-04 22:00:27 -07:00
57ed9226c0 Chapter 25.3 & 25.4 2022-06-02 22:27:43 -07:00
e74cbddb04 Chapter 25.2 2022-05-31 23:02:18 -07:00
1ae50814d3 Chapter 25.1 2022-03-23 17:00:22 -07:00
3da4016d57 Chapter 24.7 2022-03-22 17:15:17 -07:00
baedf99f63 Chapter 24.6 2022-03-22 16:45:48 -07:00
410f30ef93 Chapter 24.5 2022-03-15 00:39:11 -07:00
44e47b89de Chapter 24.4 2022-03-13 19:41:34 -07:00
ce478b0b80 Chapter 24.3 2022-01-23 15:58:35 -08:00
a52c55eb9a Chapter 24.2 (Does Not Compile) 2022-01-22 22:01:54 -08:00
9025da8168 Chapter 24.1 2022-01-21 21:24:26 -08:00
37ac05af6d Chapter 23.3 2022-01-12 23:09:36 -08:00
ba6ab6759e Chapter 23.2 2022-01-12 22:58:10 -08:00
65275bde8a Chapter 22 2021-10-25 21:49:54 -07:00
9de87f4495 Chapter 22.3 2021-10-21 20:54:35 -07:00
42a7e26292 Chapter 22.2 2021-10-21 20:18:48 -07:00
df1985ae84 Cahapter 22.1 2021-10-21 20:00:19 -07:00
26b418d254 Chapter 21 2021-10-21 19:51:01 -07:00
7bd9934a4b Chapter 21.3 2021-10-11 00:13:15 -07:00
fcbea5f617
Chapter 21.1 - 21.3 2021-09-20 21:47:41 -07:00
aa41f26a26
Chapter 19.4 - 19.5 2021-09-18 11:10:11 -07:00
8ec9137561
Chapter 19.5 2021-09-09 22:57:03 -07:00
d5f352e577
Chapter 19.4 2021-09-07 22:54:12 -07:00
60e752c0c5
Chapter 19.3 2021-09-07 22:34:32 -07:00
c1cbc359e9
Fix typo 2021-09-07 22:34:03 -07:00
ba52787271
Chapter 19.1 - 19.2 2021-09-06 02:24:56 -07:00
c10cbcdf99
Chapter 18 2021-09-06 02:03:31 -07:00
6508cfd8fe
Chapter 18.1 - 18.3 2021-08-25 01:08:14 -07:00
3a02da2161
Chapter 17.7 2021-08-17 20:47:18 -07:00
964859a8a1
Chapter 17.5 - 17.6 2021-08-17 20:41:54 -07:00
0b89bd5de0
Chapter 17.1 - 17.4 2021-08-16 22:17:40 -07:00
350a5cc4e3
Chapter 16.4 2021-08-07 21:23:05 -07:00
eb6f33a2b2
Chapter 16.3 2021-08-02 20:44:52 -07:00
4143259eb3
Chapter 16.2 2021-08-02 18:35:10 -07:00
b131e343bb
Chapter 16.1 2021-08-02 18:09:03 -07:00
9b60cf334c
Chapter 15.3.1 2021-07-29 23:56:42 -07:00
276debcb35
Add installation rule for Lox 2021-07-29 23:38:34 -07:00
2adc0cc33a
Chapter 15.3 2021-07-29 23:38:20 -07:00
73dfe7efa5
Chapter 15.2 2021-07-27 22:21:40 -07:00
79c1056553
Generate the compilation database through CMake
Use an Emacs directory-local variable to specify to ccls where the compilation
directory lives, tell CMake to generate the compilation database, and remove
bear from the environment.

Add clang to the environment for clang-format.
2021-07-22 01:09:45 -07:00
50473e5ab5
Chapter 15.1 2021-07-22 01:09:11 -07:00
7fe0c7f639
Delete compile_commands.json
I don’t think this should ever have been added.
2021-07-22 01:08:40 -07:00
2c007a8f94
Chapter 14.1-6 2021-07-08 02:25:13 -07:00
a779473ced
Add manifest file for Guix for easy environment setup 2021-07-08 02:25:13 -07:00
68a2ebd34f
Restructure project to make room for clox 2021-07-08 02:25:13 -07:00
62bd0f83dc 13.3 Calling Superclass Methods 2021-06-15 23:18:29 -07:00
1dd608294a 13.2 Inheriting Methods 2021-06-14 23:42:40 -07:00
db403da4b2 13.1 Superclasses and Subclasses 2021-06-14 23:36:10 -07:00
958ba22a57 12.7 Constructors and Initializers 2021-06-12 12:17:43 -07:00
e62d1a209e 12.5.1 Invalid uses of this 2021-04-18 17:29:25 -07:00
21a58ffb20 12.5 This 2021-04-18 17:25:03 -07:00
591caeb216 12.4 Methods on Classes 2021-04-11 19:55:24 -07:00
580a5acb58 Chapter 12, Sections 1, 2, and 3 2021-03-17 22:52:35 -07:00
b04b6bcf2f Chapter 11 - Resolving and Binding 2021-03-03 22:44:52 -08:00
715e60b87e Pass all arguments to lox 2021-02-17 22:07:19 -08:00
4fbfa53dd7 Add basic test 2021-02-17 21:42:37 -08:00
e283991f90 Add local functions and closures 2021-02-16 23:41:24 -08:00
0dbf155da5 Add return statement 2021-02-08 17:42:18 -08:00
4a71c219b5 Add function calls 2021-01-31 17:11:47 -08:00
922a8f6863 Add function calling syntax 2021-01-20 22:52:47 -08:00
60b940f1f5 Add CMakePresets.json 2021-01-18 17:42:43 -08:00
613c2388be Add for statement 2021-01-17 19:48:17 -08:00
2a72de9f81 Formatting 2021-01-16 22:16:37 -08:00
3b9c44434e Add while statement 2021-01-16 22:16:31 -08:00
c62b73a247 Add branching primitives 2021-01-14 23:53:47 -08:00
ca9fd3ae3a Chapter 8: Add blocks and scopes 2020-11-25 23:10:58 -08:00
57e87978ef Chapter 8: Add assignment 2020-11-25 22:06:42 -08:00
ae33509615 Update minimum CMake version to 2.19 2020-11-25 22:06:16 -08:00
899ecea236 Chapter 8: Add variable declarations 2020-11-18 23:30:46 -08:00
8019f6aa41 Chapter 8: Add expression and print statements 2020-11-18 18:18:25 -08:00
055b5257fb Chapter 8: Add statement AST generator 2020-11-18 17:10:21 -08:00
2894c7ebfd Trigger builds on SCM changes 2020-11-15 21:12:30 -08:00
000297dde1 Use no arguments, update Lox.jar location
‘cmakeBuild’ is calling ‘make’, not ‘cmake --build’, so try passing in the ‘all’
target.
2020-11-15 21:10:57 -08:00
bfed2d4dd3 Try adding steps to cmakeBuild
Without steps it seems to only be configuring.
2020-11-15 21:06:54 -08:00
2c2bae1b46 Use the default generator for CMake 2020-11-15 21:01:39 -08:00
3ef60b7cf7 Fix Dockerfile
When not using the noninteractive front-end the installation blocks to ask which
timezone to use.
2020-11-15 20:53:01 -08:00
6bfbcb4850 Use default Dockerfile name, don’t look for a label. 2020-11-15 20:38:56 -08:00
c965dd4033 Add Jenkinsfile and supporting Dockerfile 2020-11-15 20:22:48 -08:00
b31528d593 Update .gitignore
Only exclude _build, where CMake should be building to.
2020-11-11 21:33:54 -08:00
ee63307aee Add the interpreter 2020-11-11 21:33:02 -08:00
a78fe97f60 Add the Parser 2020-11-11 21:07:49 -08:00
0960080128 Split CMake project into several files 2020-11-11 21:07:19 -08:00
f6cdbabe1c Automatically generate Expr.java using GenerateAst 2020-10-28 23:03:25 -07:00
2e15e8bab8 Add GenerateAst and AstPrinter 2020-10-28 22:56:15 -07:00
63 changed files with 5063 additions and 58 deletions

4
.dir-locals.el Normal file
View 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
View file

@ -0,0 +1,4 @@
jlox.sum
jlox.log
.ccls-cache/

7
Dockerfile Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,2 @@
_build/
dist/

7
clox/CMakeLists.txt Normal file
View 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
View file

1
clox/src/.clang-format Normal file
View file

@ -0,0 +1 @@
BreakBeforeBinaryOperators: All

23
clox/src/CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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 &current->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 = &current->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 = &current->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 = &current->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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1 @@
_build

8
jlox/CMakeLists.txt Normal file
View 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
View 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
View file

@ -0,0 +1,3 @@
#!/usr/bin/env sh
java -jar "$(dirname "$0")/_build/src/com/craftinginterpreters/lox/Lox.jar" "$@"

View file

@ -0,0 +1,2 @@
add_subdirectory(tool)
add_subdirectory(lox)

View file

@ -0,0 +1 @@
Expr.java

View file

@ -0,0 +1,2 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.formatter.lineSplit=80

View 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));
}
}

View 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)

View 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);
}
}

View 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);
}
}
}

View file

@ -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;
}
} }

View file

@ -0,0 +1,9 @@
package com.craftinginterpreters.lox;
import java.util.List;
interface LoxCallable {
int arity();
Object call(Interpreter interpreter, List<Object> arguments);
}

View 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();
}
}

View 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 + ">";
}
}

View 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";
}
}

View 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();
}
}
}

View 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;
}
}
}
}

View 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;
}
}

View 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;
}
}

View file

@ -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;

View file

@ -0,0 +1,3 @@
add_jar(GenerateAst
GenerateAst.java
ENTRY_POINT com/craftinginterpreters/tool/GenerateAst)

View 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
View file

@ -1,6 +0,0 @@
*.jar
CMakeCache.txt
Makefile
cmake_install.cmake
CMakeFiles/

View file

@ -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
View 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
View 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
View 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
View file

@ -0,0 +1,7 @@
class Bacon {
eat() {
print "Crunch crunch crunch!";
}
}
Bacon().eat();

View 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" }
}