Compare commits
No commits in common. "f2128a95122a709daeedf4dd688c0c6a1958001b" and "202123cdf1e323d0590476aee481d38fde3f700b" have entirely different histories.
f2128a9512
...
202123cdf1
@ -1,16 +0,0 @@
|
|||||||
name: build
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: nix-runner
|
|
||||||
steps:
|
|
||||||
# optional: Push the results to a cache
|
|
||||||
- uses: https://github.com/cachix/cachix-action@v12
|
|
||||||
with:
|
|
||||||
name: local-cache
|
|
||||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- run: nix shell --run "cd clox && make"
|
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -22,4 +22,3 @@ out/
|
|||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
hs_err_pid*
|
hs_err_pid*
|
||||||
replay_pid*
|
replay_pid*
|
||||||
*.o
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
CC = gcc
|
CC = gcc
|
||||||
INC_DIR = ./
|
CFLAGS = -Wall -Wextra -g
|
||||||
CFLAGS = -Wall -Wextra -g -I$(INC_DIR)
|
SOURCES = main.c chunk.c memory.c debug.c value.c vm.c compiler.c scanner.c
|
||||||
DEPS = common.h
|
|
||||||
SOURCES = main.c chunk.c memory.c debug.c value.c vm.c compiler.c scanner.c object.c table.c
|
|
||||||
OBJECTS = $(SOURCES:.c=.o)
|
OBJECTS = $(SOURCES:.c=.o)
|
||||||
EXECUTABLE = clox
|
EXECUTABLE = clox
|
||||||
|
|
||||||
@ -11,7 +9,7 @@ all: $(EXECUTABLE)
|
|||||||
$(EXECUTABLE): $(OBJECTS)
|
$(EXECUTABLE): $(OBJECTS)
|
||||||
$(CC) $(CFLAGS) -o $@ $(OBJECTS)
|
$(CC) $(CFLAGS) -o $@ $(OBJECTS)
|
||||||
|
|
||||||
%.o: %.c $(DEPS)
|
%.o: %.c
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
19
clox/chunk.h
19
clox/chunk.h
@ -6,29 +6,12 @@
|
|||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
OP_CONSTANT,
|
OP_CONSTANT,
|
||||||
OP_NIL,
|
|
||||||
OP_TRUE,
|
|
||||||
OP_FALSE,
|
|
||||||
OP_POP,
|
|
||||||
OP_GET_LOCAL,
|
|
||||||
OP_SET_LOCAL,
|
|
||||||
OP_GET_GLOBAL,
|
|
||||||
OP_SET_GLOBAL,
|
|
||||||
OP_DEFINE_GLOBAL,
|
|
||||||
OP_EQUAL,
|
|
||||||
OP_GREATER,
|
|
||||||
OP_LESS,
|
|
||||||
OP_NEGATE,
|
|
||||||
OP_PRINT,
|
|
||||||
OP_JUMP,
|
|
||||||
OP_JUMP_IF_FALSE,
|
|
||||||
OP_LOOP,
|
|
||||||
OP_RETURN,
|
OP_RETURN,
|
||||||
|
OP_NEGATE,
|
||||||
OP_ADD,
|
OP_ADD,
|
||||||
OP_SUBTRACT,
|
OP_SUBTRACT,
|
||||||
OP_MULTIPLY,
|
OP_MULTIPLY,
|
||||||
OP_DIVIDE,
|
OP_DIVIDE,
|
||||||
OP_NOT,
|
|
||||||
} OpCode;
|
} OpCode;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
BIN
clox/chunk.o
BIN
clox/chunk.o
Binary file not shown.
BIN
clox/clox
BIN
clox/clox
Binary file not shown.
@ -6,6 +6,5 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define DEBUG_TRACE_EXECUTION
|
#define DEBUG_TRACE_EXECUTION
|
||||||
#define UINT8_COUNT (UINT8_MAX + 1)
|
|
||||||
#define DEBUG_PRINT_CODE
|
#define DEBUG_PRINT_CODE
|
||||||
#endif
|
#endif
|
||||||
|
434
clox/compiler.c
434
clox/compiler.c
@ -1,6 +1,5 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "compiler.h"
|
#include "compiler.h"
|
||||||
@ -31,7 +30,7 @@ typedef enum {
|
|||||||
PREC_PRIMARY
|
PREC_PRIMARY
|
||||||
} Precedence;
|
} Precedence;
|
||||||
|
|
||||||
typedef void (*ParseFn)(bool canAssign);
|
typedef void (*ParseFn)();
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
ParseFn prefix;
|
ParseFn prefix;
|
||||||
@ -39,19 +38,7 @@ typedef struct {
|
|||||||
Precedence precedence;
|
Precedence precedence;
|
||||||
} ParseRule;
|
} ParseRule;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
Token name;
|
|
||||||
int depth;
|
|
||||||
} Local;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
Local locals[UINT8_COUNT];
|
|
||||||
int localCount;
|
|
||||||
int scopeDepth;
|
|
||||||
} Compiler;
|
|
||||||
|
|
||||||
Parser parser;
|
Parser parser;
|
||||||
Compiler* current = NULL;
|
|
||||||
Chunk* compilingChunk;
|
Chunk* compilingChunk;
|
||||||
|
|
||||||
static Chunk* currentChunk() {
|
static Chunk* currentChunk() {
|
||||||
@ -103,16 +90,6 @@ static void consume(TokenType type, const char* message) {
|
|||||||
errorAtCurrent(message);
|
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) {
|
static void emitByte(uint8_t byte) {
|
||||||
writeChunk(currentChunk(), byte, parser.previous.line);
|
writeChunk(currentChunk(), byte, parser.previous.line);
|
||||||
}
|
}
|
||||||
@ -122,23 +99,6 @@ static void emitBytes(uint8_t byte1, uint8_t byte2) {
|
|||||||
emitByte(byte2);
|
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 uint8_t makeConstant(Value value) {
|
static uint8_t makeConstant(Value value) {
|
||||||
int constant = addConstant(currentChunk(), value);
|
int constant = addConstant(currentChunk(), value);
|
||||||
if (constant > UINT8_MAX) {
|
if (constant > UINT8_MAX) {
|
||||||
@ -157,24 +117,6 @@ static void emitConstant(Value value) {
|
|||||||
emitBytes(OP_CONSTANT, makeConstant(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) {
|
|
||||||
compiler->localCount = 0;
|
|
||||||
compiler->scopeDepth = 0;
|
|
||||||
current = compiler;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void endCompiler() {
|
static void endCompiler() {
|
||||||
emitReturn();
|
emitReturn();
|
||||||
#ifdef DEBUG_PRINT_CODE
|
#ifdef DEBUG_PRINT_CODE
|
||||||
@ -183,130 +125,20 @@ static void endCompiler() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void beginScope() {
|
|
||||||
current->scopeDepth++;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void endScope() {
|
|
||||||
current->scopeDepth--;
|
|
||||||
while (current->localCount > 0 &&
|
|
||||||
current->locals[current->localCount - 1].depth >
|
|
||||||
current->scopeDepth) {
|
|
||||||
emitByte(OP_POP);
|
|
||||||
current->localCount--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static void expression();
|
static void expression();
|
||||||
static void statement();
|
|
||||||
static void declaration();
|
|
||||||
static ParseRule* getRule(TokenType type);
|
static ParseRule* getRule(TokenType type);
|
||||||
static void parsePrecedence(Precedence precedence);
|
static void parsePrecedence(Precedence precedence);
|
||||||
|
|
||||||
static uint8_t identifierConstant(Token* name) {
|
static void binary() {
|
||||||
return makeConstant(OBJ_VAL(copyString(name->start,name->length)));
|
|
||||||
}
|
|
||||||
static bool identifiersEqual(Token* a, Token* b) {
|
|
||||||
if (a->length != b->length) return false;
|
|
||||||
return memcmp(a->start, b->start, a->length) == 0;
|
|
||||||
}
|
|
||||||
static int resolveLocal(Compiler* compiler, Token* name) {
|
|
||||||
for (int i = compiler->localCount - 1; i >= 0; i--) {
|
|
||||||
Local* local = &compiler->locals[i];
|
|
||||||
if (identifiersEqual(name, &local->name)) {
|
|
||||||
if (local->depth == -1) {
|
|
||||||
error("Can't read local variable in its own initializer.");
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
static void addLocal(Token name) {
|
|
||||||
if (current->localCount == UINT8_COUNT) {
|
|
||||||
error("Too many local variables in function.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Local* local = ¤t->locals[current->localCount++];
|
|
||||||
local->name = name;
|
|
||||||
local->depth = -1;
|
|
||||||
// local->depth = current->scopeDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void declareVariable() {
|
|
||||||
if (current->scopeDepth == 0) return;
|
|
||||||
|
|
||||||
Token* name = &parser.previous;
|
|
||||||
for (int i = current->localCount - 1; i >= 0; i--) {
|
|
||||||
Local* local = ¤t->locals[i];
|
|
||||||
if (local->depth != -1 && local->depth < current->scopeDepth) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (identifiersEqual(name, &local->name)) {
|
|
||||||
error("Already a variable with this name in this scope.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addLocal(*name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t parseVariable(const char* errorMessage) {
|
|
||||||
consume(TOKEN_IDENTIFIER, errorMessage);
|
|
||||||
|
|
||||||
declareVariable();
|
|
||||||
if (current->scopeDepth > 0) return 0;
|
|
||||||
|
|
||||||
return identifierConstant(&parser.previous);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void markInitialized() {
|
|
||||||
current->locals[current->localCount - 1].depth = current->scopeDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void defineVariable(uint8_t global) {
|
|
||||||
if (current->scopeDepth > 0) {
|
|
||||||
markInitialized();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
emitBytes(OP_DEFINE_GLOBAL, global);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void and_(bool canAssign) {
|
|
||||||
int endJump = emitJump(OP_JUMP_IF_FALSE);
|
|
||||||
|
|
||||||
emitByte(OP_POP);
|
|
||||||
parsePrecedence(PREC_AND);
|
|
||||||
|
|
||||||
patchJump(endJump);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void binary(bool canAssign) {
|
|
||||||
TokenType operatorType = parser.previous.type;
|
TokenType operatorType = parser.previous.type;
|
||||||
ParseRule* rule = getRule(operatorType);
|
ParseRule* rule = getRule(operatorType);
|
||||||
parsePrecedence((Precedence)(rule->precedence + 1));
|
parsePrecedence((Precedence)(rule->precedence + 1));
|
||||||
|
|
||||||
switch (operatorType) {
|
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_PLUS: emitByte(OP_ADD); break;
|
||||||
case TOKEN_MINUS: emitByte(OP_SUBTRACT); break;
|
case TOKEN_MINUS: emitByte(OP_SUBTRACT); break;
|
||||||
case TOKEN_STAR: emitByte(OP_MULTIPLY); break;
|
case TOKEN_STAR: emitByte(OP_MULTIPLY); break;
|
||||||
case TOKEN_SLASH: emitByte(OP_DIVIDE); break;
|
case TOKEN_SLASH: emitByte(OP_DIVIDE); break;
|
||||||
|
|
||||||
default: return; // Unreachable.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void literal(bool canAssign) {
|
|
||||||
switch (parser.previous.type) {
|
|
||||||
case TOKEN_FALSE: emitByte(OP_FALSE); break;
|
|
||||||
case TOKEN_NIL: emitByte(OP_NIL); break;
|
|
||||||
case TOKEN_TRUE: emitByte(OP_TRUE); break;
|
|
||||||
default: return; // Unreachable.
|
default: return; // Unreachable.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,234 +147,23 @@ static void expression() {
|
|||||||
// What goes here?
|
// What goes here?
|
||||||
parsePrecedence(PREC_ASSIGNMENT);
|
parsePrecedence(PREC_ASSIGNMENT);
|
||||||
}
|
}
|
||||||
static void block() {
|
static void grouping() {
|
||||||
while (!check(TOKEN_RIGHT_BRACE) && !check(TOKEN_EOF)) {
|
|
||||||
declaration();
|
|
||||||
}
|
|
||||||
|
|
||||||
consume(TOKEN_RIGHT_BRACE, "Expect '}' after block.");
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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_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_WHILE)) {
|
|
||||||
whileStatement();
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (match(TOKEN_LEFT_BRACE)) {
|
|
||||||
beginScope();
|
|
||||||
block();
|
|
||||||
endScope();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
expressionStatement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void grouping(bool canAssign) {
|
|
||||||
expression();
|
expression();
|
||||||
consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
|
consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void number(bool canAssign) {
|
static void number() {
|
||||||
double value = strtod(parser.previous.start, NULL);
|
double value = strtod(parser.previous.start, NULL);
|
||||||
emitConstant(NUMBER_VAL(value));
|
emitConstant(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void or_(bool canAssign) {
|
static void unary() {
|
||||||
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 namedVariable(Token name, bool canAssign) {
|
|
||||||
// uint8_t arg = identifierConstant(&name);
|
|
||||||
uint8_t getOp, setOp;
|
|
||||||
int arg = resolveLocal(current, &name);
|
|
||||||
if (arg != -1) {
|
|
||||||
getOp = OP_GET_LOCAL;
|
|
||||||
setOp = OP_SET_LOCAL;
|
|
||||||
} else {
|
|
||||||
arg = identifierConstant(&name);
|
|
||||||
getOp = OP_GET_GLOBAL;
|
|
||||||
setOp = OP_SET_GLOBAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canAssign && match(TOKEN_EQUAL)) {
|
|
||||||
expression();
|
|
||||||
emitBytes(setOp, (uint8_t)arg);
|
|
||||||
} else {
|
|
||||||
emitBytes(getOp, (uint8_t)arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void variable(bool canAssign) {
|
|
||||||
namedVariable(parser.previous, canAssign);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void unary(bool canAssign) {
|
|
||||||
TokenType operatorType = parser.previous.type;
|
TokenType operatorType = parser.previous.type;
|
||||||
|
|
||||||
// Compile the operand.
|
// Compile the operand.
|
||||||
parsePrecedence(PREC_UNARY);
|
parsePrecedence(PREC_UNARY);
|
||||||
// Emit the operator instruction.
|
// Emit the operator instruction.
|
||||||
switch (operatorType) {
|
switch (operatorType) {
|
||||||
case TOKEN_BANG: emitByte(OP_NOT); break;
|
|
||||||
case TOKEN_MINUS: emitByte(OP_NEGATE); break;
|
case TOKEN_MINUS: emitByte(OP_NEGATE); break;
|
||||||
default: return; // Unreachable.
|
default: return; // Unreachable.
|
||||||
}
|
}
|
||||||
@ -558,16 +179,12 @@ static void parsePrecedence(Precedence precedence) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool canAssign = precedence <= PREC_ASSIGNMENT;
|
prefixRule();
|
||||||
prefixRule(canAssign);
|
|
||||||
|
|
||||||
while (precedence <= getRule(parser.current.type)->precedence) {
|
while (precedence <= getRule(parser.current.type)->precedence) {
|
||||||
advance();
|
advance();
|
||||||
ParseFn infixRule = getRule(parser.previous.type)->infix;
|
ParseFn infixRule = getRule(parser.previous.type)->infix;
|
||||||
infixRule(canAssign);
|
infixRule();
|
||||||
}
|
|
||||||
if (canAssign && match(TOKEN_EQUAL)) {
|
|
||||||
error("Invalid assignment target.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,31 +200,31 @@ ParseRule rules[] = {
|
|||||||
[TOKEN_SEMICOLON] = {NULL, NULL, PREC_NONE},
|
[TOKEN_SEMICOLON] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_SLASH] = {NULL, binary, PREC_FACTOR},
|
[TOKEN_SLASH] = {NULL, binary, PREC_FACTOR},
|
||||||
[TOKEN_STAR] = {NULL, binary, PREC_FACTOR},
|
[TOKEN_STAR] = {NULL, binary, PREC_FACTOR},
|
||||||
[TOKEN_BANG] = {unary, NULL, PREC_NONE},
|
[TOKEN_BANG] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_BANG_EQUAL] = {NULL, binary, PREC_EQUALITY},
|
[TOKEN_BANG_EQUAL] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_EQUAL] = {NULL, NULL, PREC_NONE},
|
[TOKEN_EQUAL] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_EQUAL_EQUAL] = {NULL, binary, PREC_EQUALITY},
|
[TOKEN_EQUAL_EQUAL] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_GREATER] = {NULL, binary, PREC_COMPARISON},
|
[TOKEN_GREATER] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_GREATER_EQUAL] = {NULL, binary, PREC_COMPARISON},
|
[TOKEN_GREATER_EQUAL] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_LESS] = {NULL, binary, PREC_COMPARISON},
|
[TOKEN_LESS] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_LESS_EQUAL] = {NULL, binary, PREC_COMPARISON},
|
[TOKEN_LESS_EQUAL] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_IDENTIFIER] = {variable, NULL, PREC_NONE},
|
[TOKEN_IDENTIFIER] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_STRING] = {string, NULL, PREC_NONE},
|
[TOKEN_STRING] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_NUMBER] = {number, NULL, PREC_NONE},
|
[TOKEN_NUMBER] = {number, NULL, PREC_NONE},
|
||||||
[TOKEN_AND] = {NULL, and_, PREC_AND},
|
[TOKEN_AND] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_CLASS] = {NULL, NULL, PREC_NONE},
|
[TOKEN_CLASS] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_ELSE] = {NULL, NULL, PREC_NONE},
|
[TOKEN_ELSE] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_FALSE] = {literal, NULL, PREC_NONE},
|
[TOKEN_FALSE] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_FOR] = {NULL, NULL, PREC_NONE},
|
[TOKEN_FOR] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_FUN] = {NULL, NULL, PREC_NONE},
|
[TOKEN_FUN] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_IF] = {NULL, NULL, PREC_NONE},
|
[TOKEN_IF] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_NIL] = {literal, NULL, PREC_NONE},
|
[TOKEN_NIL] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_OR] = {NULL, or_, PREC_OR},
|
[TOKEN_OR] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_PRINT] = {NULL, NULL, PREC_NONE},
|
[TOKEN_PRINT] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_RETURN] = {NULL, NULL, PREC_NONE},
|
[TOKEN_RETURN] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_SUPER] = {NULL, NULL, PREC_NONE},
|
[TOKEN_SUPER] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_THIS] = {NULL, NULL, PREC_NONE},
|
[TOKEN_THIS] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_TRUE] = {literal, NULL, PREC_NONE},
|
[TOKEN_TRUE] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_VAR] = {NULL, NULL, PREC_NONE},
|
[TOKEN_VAR] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_WHILE] = {NULL, NULL, PREC_NONE},
|
[TOKEN_WHILE] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_ERROR] = {NULL, NULL, PREC_NONE},
|
[TOKEN_ERROR] = {NULL, NULL, PREC_NONE},
|
||||||
@ -620,15 +237,12 @@ static ParseRule* getRule(TokenType type) {
|
|||||||
|
|
||||||
bool compile(const char* source, Chunk* chunk) {
|
bool compile(const char* source, Chunk* chunk) {
|
||||||
initScanner(source);
|
initScanner(source);
|
||||||
Compiler compiler;
|
|
||||||
initCompiler(&compiler);
|
|
||||||
compilingChunk = chunk;
|
compilingChunk = chunk;
|
||||||
parser.hadError = false;
|
parser.hadError = false;
|
||||||
parser.panicMode = false;
|
parser.panicMode = false;
|
||||||
advance();
|
advance();
|
||||||
while (!match(TOKEN_EOF)) {
|
expression();
|
||||||
declaration();
|
consume(TOKEN_EOF, "Expect end of expression.");
|
||||||
}
|
|
||||||
endCompiler();
|
endCompiler();
|
||||||
return !parser.hadError;
|
return !parser.hadError;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#ifndef clox_compiler_h
|
#ifndef clox_compiler_h
|
||||||
#define clox_compiler_h
|
#define clox_compiler_h
|
||||||
|
|
||||||
#include "object.h"
|
|
||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
|
|
||||||
bool compile(const char* source, Chunk* chunk);
|
bool compile(const char* source, Chunk* chunk);
|
||||||
|
BIN
clox/compiler.o
BIN
clox/compiler.o
Binary file not shown.
50
clox/debug.c
50
clox/debug.c
@ -22,22 +22,6 @@ static int simpleInstruction(const char* name, int offset) {
|
|||||||
printf("%s\n", name);
|
printf("%s\n", name);
|
||||||
return offset + 1;
|
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) {
|
int disassembleInstruction(Chunk* chunk, int offset) {
|
||||||
printf("%04d ", offset);
|
printf("%04d ", offset);
|
||||||
|
|
||||||
@ -52,30 +36,6 @@ int disassembleInstruction(Chunk* chunk, int offset) {
|
|||||||
switch (instruction) {
|
switch (instruction) {
|
||||||
case OP_CONSTANT:
|
case OP_CONSTANT:
|
||||||
return constantInstruction("OP_CONSTANT", chunk, offset);
|
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_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:
|
case OP_ADD:
|
||||||
return simpleInstruction("OP_ADD", offset);
|
return simpleInstruction("OP_ADD", offset);
|
||||||
case OP_SUBTRACT:
|
case OP_SUBTRACT:
|
||||||
@ -86,16 +46,6 @@ int disassembleInstruction(Chunk* chunk, int offset) {
|
|||||||
return simpleInstruction("OP_DIVIDE", offset);
|
return simpleInstruction("OP_DIVIDE", offset);
|
||||||
case OP_NEGATE:
|
case OP_NEGATE:
|
||||||
return simpleInstruction("OP_NEGATE", offset);
|
return simpleInstruction("OP_NEGATE", offset);
|
||||||
case OP_NOT:
|
|
||||||
return simpleInstruction("OP_NOT", offset);
|
|
||||||
case OP_PRINT:
|
|
||||||
return simpleInstruction("OP_PRINT", offset);
|
|
||||||
case OP_JUMP:
|
|
||||||
return jumpInstruction("OP_JUMP", 1, chunk, offset);
|
|
||||||
case OP_JUMP_IF_FALSE:
|
|
||||||
return jumpInstruction("OP_JUMP_IF_FALSE", 1, chunk, offset);
|
|
||||||
case OP_LOOP:
|
|
||||||
return jumpInstruction("OP_LOOP", -1, chunk, offset);
|
|
||||||
case OP_RETURN:
|
case OP_RETURN:
|
||||||
return simpleInstruction("OP_RETURN", offset);
|
return simpleInstruction("OP_RETURN", offset);
|
||||||
default:
|
default:
|
||||||
|
BIN
clox/debug.o
BIN
clox/debug.o
Binary file not shown.
BIN
clox/main.o
BIN
clox/main.o
Binary file not shown.
@ -1,7 +1,6 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "vm.h"
|
|
||||||
|
|
||||||
void* reallocate(void* pointer, size_t oldSize, size_t newSize) {
|
void* reallocate(void* pointer, size_t oldSize, size_t newSize) {
|
||||||
if (newSize == 0) {
|
if (newSize == 0) {
|
||||||
@ -13,23 +12,3 @@ void* reallocate(void* pointer, size_t oldSize, size_t newSize) {
|
|||||||
if (result == NULL) exit(1);
|
if (result == NULL) exit(1);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
static void freeObject(Obj* object) {
|
|
||||||
switch (object->type) {
|
|
||||||
case OBJ_STRING: {
|
|
||||||
ObjString* string = (ObjString*)object;
|
|
||||||
FREE_ARRAY(char, string->chars, string->length + 1);
|
|
||||||
FREE(ObjString, object);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void freeObjects() {
|
|
||||||
Obj* object = vm.objects;
|
|
||||||
while (object != NULL) {
|
|
||||||
Obj* next = object->next;
|
|
||||||
freeObject(object);
|
|
||||||
object = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,12 +2,6 @@
|
|||||||
#define clox_memory_h
|
#define clox_memory_h
|
||||||
|
|
||||||
#include "common.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) \
|
#define GROW_CAPACITY(capacity) \
|
||||||
((capacity) < 8 ? 8 : (capacity) * 2)
|
((capacity) < 8 ? 8 : (capacity) * 2)
|
||||||
@ -20,6 +14,5 @@
|
|||||||
reallocate(pointer, sizeof(type) * (oldCount), 0)
|
reallocate(pointer, sizeof(type) * (oldCount), 0)
|
||||||
|
|
||||||
void* reallocate(void* pointer, size_t oldSize, size_t newSize);
|
void* reallocate(void* pointer, size_t oldSize, size_t newSize);
|
||||||
void freeObjects();
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
BIN
clox/memory.o
BIN
clox/memory.o
Binary file not shown.
@ -1,68 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "memory.h"
|
|
||||||
#include "object.h"
|
|
||||||
#include "table.h"
|
|
||||||
#include "value.h"
|
|
||||||
#include "vm.h"
|
|
||||||
|
|
||||||
#define ALLOCATE_OBJ(type, objectType) \
|
|
||||||
(type*)allocateObject(sizeof(type), objectType)
|
|
||||||
|
|
||||||
static Obj* allocateObject(size_t size, ObjType type) {
|
|
||||||
Obj* object = (Obj*)reallocate(NULL, 0, size);
|
|
||||||
object->type = type;
|
|
||||||
|
|
||||||
object->next = vm.objects;
|
|
||||||
vm.objects = object;
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ObjString* allocateString(char* chars, int length,
|
|
||||||
uint32_t hash)
|
|
||||||
{
|
|
||||||
ObjString* string = ALLOCATE_OBJ(ObjString, OBJ_STRING);
|
|
||||||
string->length = length;
|
|
||||||
string->chars = chars;
|
|
||||||
string->hash = hash;
|
|
||||||
tableSet(&vm.strings, string, NIL_VAL);
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
static uint32_t hashString(const char* key, int length) {
|
|
||||||
uint32_t hash = 2166136261u;
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
hash ^= (uint8_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);
|
|
||||||
}
|
|
||||||
void printObject(Value value) {
|
|
||||||
switch (OBJ_TYPE(value)) {
|
|
||||||
case OBJ_STRING:
|
|
||||||
printf("%s", AS_CSTRING(value));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
#ifndef clox_object_h
|
|
||||||
#define clox_object_h
|
|
||||||
|
|
||||||
#include "value.h"
|
|
||||||
|
|
||||||
#define OBJ_TYPE(value) (AS_OBJ(value)->type)
|
|
||||||
|
|
||||||
#define IS_STRING(value) isObjType(value, OBJ_STRING)
|
|
||||||
|
|
||||||
#define AS_STRING(value) ((ObjString*)AS_OBJ(value))
|
|
||||||
#define AS_CSTRING(value) (((ObjString*)AS_OBJ(value))->chars)
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
OBJ_STRING,
|
|
||||||
} ObjType;
|
|
||||||
|
|
||||||
struct Obj {
|
|
||||||
ObjType type;
|
|
||||||
struct Obj* next;
|
|
||||||
};
|
|
||||||
struct ObjString {
|
|
||||||
Obj obj;
|
|
||||||
int length;
|
|
||||||
char* chars;
|
|
||||||
uint32_t hash;
|
|
||||||
};
|
|
||||||
ObjString* takeString(char* chars, int length);
|
|
||||||
ObjString* copyString(const char* chars, int length);
|
|
||||||
void printObject(Value value);
|
|
||||||
static inline bool isObjType(Value value, ObjType type) {
|
|
||||||
return IS_OBJ(value) && AS_OBJ(value)->type == type;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
0
clox/run.sh
Normal file
0
clox/run.sh
Normal file
BIN
clox/scanner.o
BIN
clox/scanner.o
Binary file not shown.
130
clox/table.c
130
clox/table.c
@ -1,130 +0,0 @@
|
|||||||
#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;
|
|
||||||
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 key.
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = (index + 1) % capacity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
26
clox/table.h
26
clox/table.h
@ -1,26 +0,0 @@
|
|||||||
#ifndef clox_table_h
|
|
||||||
#define clox_table_h
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
#include "value.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
ObjString* key;
|
|
||||||
Value value;
|
|
||||||
} Entry;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int count;
|
|
||||||
int capacity;
|
|
||||||
Entry* entries;
|
|
||||||
} Table;
|
|
||||||
|
|
||||||
void initTable(Table* table);
|
|
||||||
void freeTable(Table* table);
|
|
||||||
bool tableGet(Table* table, ObjString* key, Value* value);
|
|
||||||
bool tableSet(Table* table, ObjString* key, Value value);
|
|
||||||
bool tableDelete(Table* table, ObjString* key);
|
|
||||||
void tableAddAll(Table* from, Table* to);
|
|
||||||
ObjString* tableFindString(Table* table, const char* chars,
|
|
||||||
int length, uint32_t hash);
|
|
||||||
#endif
|
|
@ -1 +0,0 @@
|
|||||||
!(5 - 4 > 3 * 2 == !nil)
|
|
@ -1,5 +0,0 @@
|
|||||||
var breakfast = "beignets";
|
|
||||||
var beverage = "cafe au lait";
|
|
||||||
breakfast = "beignets with " + beverage;
|
|
||||||
|
|
||||||
print breakfast;
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
var a = "first";
|
|
||||||
var a = "second";
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
var a = "outer";
|
|
||||||
{
|
|
||||||
var a = "inner";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
var a = "outer";
|
|
||||||
{
|
|
||||||
var a = a;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
"st" + "ri" + "ng"
|
|
22
clox/value.c
22
clox/value.c
@ -1,7 +1,5 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "string.h"
|
|
||||||
|
|
||||||
#include "object.h"
|
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
|
|
||||||
@ -26,23 +24,5 @@ void freeValueArray(ValueArray* array) {
|
|||||||
initValueArray(array);
|
initValueArray(array);
|
||||||
}
|
}
|
||||||
void printValue(Value value) {
|
void printValue(Value value) {
|
||||||
switch (value.type) {
|
printf("%g", value);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool valuesEqual(Value a, Value b) {
|
|
||||||
if (a.type != b.type) return false;
|
|
||||||
switch (a.type) {
|
|
||||||
case VAL_BOOL: return AS_BOOL(a) == AS_BOOL(b);
|
|
||||||
case VAL_NIL: return true;
|
|
||||||
case VAL_NUMBER: return AS_NUMBER(a) == AS_NUMBER(b);
|
|
||||||
case VAL_OBJ: return AS_OBJ(a) == AS_OBJ(b);
|
|
||||||
default: return false; // Unreachable.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
35
clox/value.h
35
clox/value.h
@ -3,39 +3,7 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
typedef struct Obj Obj;
|
typedef double Value;
|
||||||
typedef struct ObjString ObjString;
|
|
||||||
//typedef double Value;
|
|
||||||
|
|
||||||
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(object) ((Value){VAL_OBJ, {.obj = (Obj*)object}})
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int capacity;
|
int capacity;
|
||||||
@ -43,7 +11,6 @@ typedef struct {
|
|||||||
Value* values;
|
Value* values;
|
||||||
} ValueArray;
|
} ValueArray;
|
||||||
|
|
||||||
bool valuesEqual(Value a, Value b);
|
|
||||||
void initValueArray(ValueArray* array);
|
void initValueArray(ValueArray* array);
|
||||||
void writeValueArray(ValueArray* array, Value value);
|
void writeValueArray(ValueArray* array, Value value);
|
||||||
void freeValueArray(ValueArray* array);
|
void freeValueArray(ValueArray* array);
|
||||||
|
BIN
clox/value.o
BIN
clox/value.o
Binary file not shown.
171
clox/vm.c
171
clox/vm.c
@ -1,42 +1,18 @@
|
|||||||
#include <stdarg.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "compiler.h"
|
#include "compiler.h"
|
||||||
#include "debug.h"
|
|
||||||
#include "object.h"
|
|
||||||
#include "memory.h"
|
|
||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
#include "table.h"
|
#include "debug.h"
|
||||||
VM vm;
|
VM vm;
|
||||||
static void resetStack() {
|
static void resetStack() {
|
||||||
vm.stackTop = vm.stack;
|
vm.stackTop = vm.stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void runtimeError(const char* format, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, format);
|
|
||||||
vfprintf(stderr, format, args);
|
|
||||||
va_end(args);
|
|
||||||
fputs("\n", stderr);
|
|
||||||
|
|
||||||
size_t instruction = vm.ip - vm.chunk->code - 1;
|
|
||||||
int line = vm.chunk->lines[instruction];
|
|
||||||
fprintf(stderr, "[line %d] in script\n", line);
|
|
||||||
resetStack();
|
|
||||||
}
|
|
||||||
|
|
||||||
void initVM() {
|
void initVM() {
|
||||||
resetStack();
|
resetStack();
|
||||||
vm.objects = NULL;
|
|
||||||
initTable(&vm.globals);
|
|
||||||
initTable(&vm.strings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void freeVM() {
|
void freeVM() {
|
||||||
freeTable(&vm.globals);
|
|
||||||
freeTable(&vm.strings);
|
|
||||||
freeObjects();
|
|
||||||
}
|
}
|
||||||
void push(Value value) {
|
void push(Value value) {
|
||||||
*vm.stackTop = value;
|
*vm.stackTop = value;
|
||||||
@ -47,43 +23,14 @@ Value pop() {
|
|||||||
return *vm.stackTop;
|
return *vm.stackTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value peek(int distance) {
|
|
||||||
return vm.stackTop[-1 - distance];
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isFalsey(Value value) {
|
|
||||||
return IS_NIL(value) || (IS_BOOL(value) && !AS_BOOL(value));
|
|
||||||
}
|
|
||||||
static void concatenate() {
|
|
||||||
ObjString* b = AS_STRING(pop());
|
|
||||||
ObjString* a = AS_STRING(pop());
|
|
||||||
|
|
||||||
int length = a->length + b->length;
|
|
||||||
char* chars = ALLOCATE(char, length + 1);
|
|
||||||
memcpy(chars, a->chars, a->length);
|
|
||||||
memcpy(chars + a->length, b->chars, b->length);
|
|
||||||
chars[length] = '\0';
|
|
||||||
|
|
||||||
ObjString* result = takeString(chars, length);
|
|
||||||
push(OBJ_VAL(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static InterpretResult run() {
|
static InterpretResult run() {
|
||||||
#define READ_BYTE() (*vm.ip++)
|
#define READ_BYTE() (*vm.ip++)
|
||||||
#define READ_CONSTANT() (vm.chunk->constants.values[READ_BYTE()])
|
#define READ_CONSTANT() (vm.chunk->constants.values[READ_BYTE()])
|
||||||
#define READ_SHORT() \
|
#define BINARY_OP(op) \
|
||||||
(vm.ip += 2, (uint16_t)((vm.ip[-2] << 8) | vm.ip[-1]))
|
|
||||||
#define READ_STRING() AS_STRING(READ_CONSTANT())
|
|
||||||
#define BINARY_OP(valueType, op) \
|
|
||||||
do { \
|
do { \
|
||||||
if (!IS_NUMBER(peek(0)) || !IS_NUMBER(peek(1))) { \
|
double b = pop(); \
|
||||||
runtimeError("Operands must be numbers."); \
|
double a = pop(); \
|
||||||
return INTERPRET_RUNTIME_ERROR; \
|
push(a op b); \
|
||||||
} \
|
|
||||||
double b = AS_NUMBER(pop()); \
|
|
||||||
double a = AS_NUMBER(pop()); \
|
|
||||||
push(valueType(a op b)); \
|
|
||||||
} while (false)
|
} while (false)
|
||||||
for (;;) {
|
for (;;) {
|
||||||
#ifdef DEBUG_TRACE_EXECUTION
|
#ifdef DEBUG_TRACE_EXECUTION
|
||||||
@ -106,113 +53,21 @@ static InterpretResult run() {
|
|||||||
push(constant);
|
push(constant);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OP_NIL: push(NIL_VAL); break;
|
case OP_NEGATE: push(-pop()); break;
|
||||||
case OP_TRUE: push(BOOL_VAL(true)); break;
|
case OP_ADD: BINARY_OP(+); break;
|
||||||
case OP_FALSE: push(BOOL_VAL(false)); break;
|
case OP_SUBTRACT: BINARY_OP(-); break;
|
||||||
case OP_SET_GLOBAL: {
|
case OP_MULTIPLY: BINARY_OP(*); break;
|
||||||
ObjString* name = READ_STRING();
|
case OP_DIVIDE: BINARY_OP(/); break;
|
||||||
if (tableSet(&vm.globals, name, peek(0))) {
|
case OP_RETURN: {
|
||||||
tableDelete(&vm.globals, name);
|
|
||||||
runtimeError("Undefined variable '%s'.", name->chars);
|
|
||||||
return INTERPRET_RUNTIME_ERROR;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OP_EQUAL: {
|
|
||||||
Value b = pop();
|
|
||||||
Value a = pop();
|
|
||||||
push(BOOL_VAL(valuesEqual(a, b)));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OP_GREATER: BINARY_OP(BOOL_VAL, >); break;
|
|
||||||
case OP_LESS: BINARY_OP(BOOL_VAL, <); break;
|
|
||||||
case OP_ADD: {
|
|
||||||
if (IS_STRING(peek(0)) && IS_STRING(peek(1))) {
|
|
||||||
concatenate();
|
|
||||||
} else if (IS_NUMBER(peek(0)) && IS_NUMBER(peek(1))) {
|
|
||||||
double b = AS_NUMBER(pop());
|
|
||||||
double a = AS_NUMBER(pop());
|
|
||||||
push(NUMBER_VAL(a + b));
|
|
||||||
} else {
|
|
||||||
runtimeError(
|
|
||||||
"Operands must be two numbers or two strings.");
|
|
||||||
return INTERPRET_RUNTIME_ERROR;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OP_SUBTRACT: BINARY_OP(NUMBER_VAL, -); break;
|
|
||||||
case OP_MULTIPLY: BINARY_OP(NUMBER_VAL, *); break;
|
|
||||||
case OP_DIVIDE: BINARY_OP(NUMBER_VAL, /); break;
|
|
||||||
case OP_NOT:
|
|
||||||
push(BOOL_VAL(isFalsey(pop())));
|
|
||||||
break;
|
|
||||||
case OP_POP: pop(); break;
|
|
||||||
case OP_GET_LOCAL: {
|
|
||||||
uint8_t slot = READ_BYTE();
|
|
||||||
push(vm.stack[slot]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OP_SET_LOCAL: {
|
|
||||||
uint8_t slot = READ_BYTE();
|
|
||||||
vm.stack[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_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());
|
printValue(pop());
|
||||||
printf("\n");
|
printf("\n");
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OP_JUMP: {
|
|
||||||
uint16_t offset = READ_SHORT();
|
|
||||||
vm.ip += offset;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OP_JUMP_IF_FALSE: {
|
|
||||||
uint16_t offset = READ_SHORT();
|
|
||||||
if (isFalsey(peek(0))) vm.ip += offset;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OP_LOOP: {
|
|
||||||
uint16_t offset = READ_SHORT();
|
|
||||||
vm.ip -= offset;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OP_RETURN: {
|
|
||||||
// printValue(pop());
|
|
||||||
// printf("\n");
|
|
||||||
return INTERPRET_OK;
|
return INTERPRET_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#undef READ_BYTE
|
|
||||||
#undef READ_SHORT
|
|
||||||
#undef READ_CONSTANT
|
#undef READ_CONSTANT
|
||||||
#undef READ_STRING
|
|
||||||
#undef BINARY_OP
|
#undef BINARY_OP
|
||||||
|
#undef READ_BYTE
|
||||||
}
|
}
|
||||||
InterpretResult interpret(const char* source) {
|
InterpretResult interpret(const char* source) {
|
||||||
Chunk chunk;
|
Chunk chunk;
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
#define clox_vm_h
|
#define clox_vm_h
|
||||||
|
|
||||||
#include "chunk.h"
|
#include "chunk.h"
|
||||||
#include "table.h"
|
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
|
|
||||||
#define STACK_MAX 256
|
#define STACK_MAX 256
|
||||||
@ -12,9 +11,6 @@ typedef struct {
|
|||||||
uint8_t* ip;
|
uint8_t* ip;
|
||||||
Value stack[STACK_MAX];
|
Value stack[STACK_MAX];
|
||||||
Value* stackTop;
|
Value* stackTop;
|
||||||
Table globals;
|
|
||||||
Table strings;
|
|
||||||
Obj* objects;
|
|
||||||
} VM;
|
} VM;
|
||||||
typedef enum {
|
typedef enum {
|
||||||
INTERPRET_OK,
|
INTERPRET_OK,
|
||||||
@ -24,8 +20,7 @@ typedef enum {
|
|||||||
|
|
||||||
void initVM();
|
void initVM();
|
||||||
void freeVM();
|
void freeVM();
|
||||||
InterpretResult interpret(const char* chunk);
|
InterpretResult interpret(const char* source);
|
||||||
extern VM vm;
|
|
||||||
void push(Value value);
|
void push(Value value);
|
||||||
Value pop();
|
Value pop();
|
||||||
|
|
||||||
|
BIN
clox/vm.o
BIN
clox/vm.o
Binary file not shown.
Loading…
Reference in New Issue
Block a user