migrate old compiler, hack on that instead
This commit is contained in:
parent
9d65790e5d
commit
ae665d89ed
|
@ -1,4 +1,4 @@
|
||||||
* /ztl/ (zongors transpiler language) Design parameters
|
* /ZTL/ (Zongors Transpiler Language) Design parameters
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:CUSTOM_ID: ztl-zongors-transpiler-language-design-parameters
|
:CUSTOM_ID: ztl-zongors-transpiler-language-design-parameters
|
||||||
:END:
|
:END:
|
||||||
|
@ -12,7 +12,7 @@ systems. /ztl/ also can "run" standalone inside of a lua vm for
|
||||||
debugging purposes, it could be used for small scripting tasks or the
|
debugging purposes, it could be used for small scripting tasks or the
|
||||||
like.
|
like.
|
||||||
|
|
||||||
* /ztl/ Grammar and Specification
|
* /ZTL/ Grammar and Specification
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:CUSTOM_ID: ztl-grammar-and-specification
|
:CUSTOM_ID: ztl-grammar-and-specification
|
||||||
:END:
|
:END:
|
||||||
|
@ -33,7 +33,7 @@ type «token» {
|
||||||
}
|
}
|
||||||
#+end_src ztl
|
#+end_src ztl
|
||||||
|
|
||||||
* Substantial Types
|
* Basic Types
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:CUSTOM_ID: substantial-types
|
:CUSTOM_ID: substantial-types
|
||||||
:END:
|
:END:
|
||||||
|
@ -41,39 +41,9 @@ type «token» {
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:CUSTOM_ID: numeric
|
:CUSTOM_ID: numeric
|
||||||
:END:
|
:END:
|
||||||
*** bit (or unsigned units)
|
- =byte=
|
||||||
:PROPERTIES:
|
|
||||||
:CUSTOM_ID: bit-or-unsigned-units
|
|
||||||
:END:
|
|
||||||
- =u8=
|
|
||||||
- unsigned 8 bit integer (uint8_t)
|
- unsigned 8 bit integer (uint8_t)
|
||||||
- =u16=
|
- =number=
|
||||||
- unsigned 16 bit integer (uint16_t)
|
|
||||||
- =u32=
|
|
||||||
- unsigned 32 bit integer (uint32_t)
|
|
||||||
- =u64=
|
|
||||||
- unsigned 64 bit integer (uint64_t)
|
|
||||||
|
|
||||||
*** integer (signed)
|
|
||||||
:PROPERTIES:
|
|
||||||
:CUSTOM_ID: integer-signed
|
|
||||||
:END:
|
|
||||||
- =i8=
|
|
||||||
- signed 8 bit integer (int8_t)
|
|
||||||
- =i16=
|
|
||||||
- signed 16 bit integer (int16_t)
|
|
||||||
- =i32=
|
|
||||||
- signed 32 bit integer (int32_t)
|
|
||||||
- =i64=
|
|
||||||
- signed 64 bit integer (int64_t)
|
|
||||||
|
|
||||||
*** real
|
|
||||||
:PROPERTIES:
|
|
||||||
:CUSTOM_ID: real
|
|
||||||
:END:
|
|
||||||
- =f32=
|
|
||||||
- 32 bit floating point (float)
|
|
||||||
- =f64=
|
|
||||||
- 64 bit floating point (double)
|
- 64 bit floating point (double)
|
||||||
|
|
||||||
** string
|
** string
|
||||||
|
@ -157,8 +127,6 @@ The following is a list of global operators and their effect:
|
||||||
|
|
||||||
- =//=
|
- =//=
|
||||||
- comment
|
- comment
|
||||||
- =/**/=
|
|
||||||
- block comment
|
|
||||||
- =??=
|
- =??=
|
||||||
- unwrap or
|
- unwrap or
|
||||||
- =+=
|
- =+=
|
||||||
|
@ -205,14 +173,8 @@ The following is a list of global operators and their effect:
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:CUSTOM_ID: logical-bitwise-operators
|
:CUSTOM_ID: logical-bitwise-operators
|
||||||
:END:
|
:END:
|
||||||
- =eq=
|
|
||||||
- equal to
|
|
||||||
- =ne=
|
|
||||||
- not equals to
|
|
||||||
- =mod=
|
- =mod=
|
||||||
- modulo
|
- modulo
|
||||||
- =not=
|
|
||||||
- logical not
|
|
||||||
- =and=
|
- =and=
|
||||||
- logical and
|
- logical and
|
||||||
- =or=
|
- =or=
|
||||||
|
@ -255,10 +217,6 @@ if («token» is i32) {
|
||||||
|
|
||||||
also used for letting constants
|
also used for letting constants
|
||||||
|
|
||||||
#+begin_src ztl
|
|
||||||
const PURPLE is Color(255, 255, 0);
|
|
||||||
#+end_src ztl
|
|
||||||
|
|
||||||
=as=
|
=as=
|
||||||
|
|
||||||
coerces a type as another type if possible
|
coerces a type as another type if possible
|
||||||
|
|
19
src/chunk.c
19
src/chunk.c
|
@ -1,16 +1,16 @@
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include "chunk.h"
|
#include "chunk.h"
|
||||||
|
#include "memory.h"
|
||||||
|
#include "vm.h"
|
||||||
|
|
||||||
void newChunk(Chunk* chunk) {
|
void initChunk(Chunk* chunk) {
|
||||||
chunk->count = 0;
|
chunk->count = 0;
|
||||||
chunk->capacity = 0;
|
chunk->capacity = 0;
|
||||||
chunk->code = NULL;
|
|
||||||
chunk->lines = NULL;
|
chunk->lines = NULL;
|
||||||
newValueArray(&chunk->constants);
|
chunk->code = NULL;
|
||||||
|
initValueArray(&chunk->constants);
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeChunk(Chunk *chunk, uint8_t byte, int line) {
|
void writeChunk(Chunk* chunk, uint8_t byte, int line) {
|
||||||
if (chunk->capacity < chunk->count + 1) {
|
if (chunk->capacity < chunk->count + 1) {
|
||||||
int oldCapacity = chunk->capacity;
|
int oldCapacity = chunk->capacity;
|
||||||
chunk->capacity = GROW_CAPACITY(oldCapacity);
|
chunk->capacity = GROW_CAPACITY(oldCapacity);
|
||||||
|
@ -25,15 +25,16 @@ void writeChunk(Chunk *chunk, uint8_t byte, int line) {
|
||||||
chunk->count++;
|
chunk->count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void freeChunk(Chunk *chunk) {
|
void freeChunk(Chunk* chunk) {
|
||||||
FREE_ARRAY(uint8_t, chunk->code, chunk->capacity);
|
FREE_ARRAY(uint8_t, chunk->code, chunk->capacity);
|
||||||
FREE_ARRAY(int, chunk->lines, chunk->capacity);
|
|
||||||
freeValueArray(&chunk->constants);
|
freeValueArray(&chunk->constants);
|
||||||
newChunk(chunk);
|
initChunk(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
int addConstant(Chunk* chunk, Value value) {
|
int addConstant(Chunk* chunk, Value value) {
|
||||||
|
push(value);
|
||||||
writeValueArray(&chunk->constants, value);
|
writeValueArray(&chunk->constants, value);
|
||||||
|
pop();
|
||||||
return chunk->constants.count - 1;
|
return chunk->constants.count - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
30
src/chunk.h
30
src/chunk.h
|
@ -1,16 +1,24 @@
|
||||||
#ifndef ztl_chunk_h
|
#ifndef zlc_chunk_h
|
||||||
#define ztl_chunk_h
|
#define zlc_chunk_h
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "memory.h"
|
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
OP_NOOP,
|
|
||||||
OP_CONSTANT,
|
OP_CONSTANT,
|
||||||
OP_NIL,
|
OP_NIL,
|
||||||
OP_TRUE,
|
OP_TRUE,
|
||||||
OP_FALSE,
|
OP_FALSE,
|
||||||
|
OP_POP,
|
||||||
|
OP_GET_LOCAL,
|
||||||
|
OP_GET_GLOBAL,
|
||||||
|
OP_DEFINE_GLOBAL,
|
||||||
|
OP_SET_LOCAL,
|
||||||
|
OP_SET_GLOBAL,
|
||||||
|
OP_GET_UPVALUE,
|
||||||
|
OP_SET_UPVALUE,
|
||||||
|
OP_GET_PROPERTY,
|
||||||
|
OP_SET_PROPERTY,
|
||||||
OP_EQUAL,
|
OP_EQUAL,
|
||||||
OP_GREATER,
|
OP_GREATER,
|
||||||
OP_LESS,
|
OP_LESS,
|
||||||
|
@ -20,10 +28,20 @@ typedef enum {
|
||||||
OP_DIVIDE,
|
OP_DIVIDE,
|
||||||
OP_NOT,
|
OP_NOT,
|
||||||
OP_NEGATE,
|
OP_NEGATE,
|
||||||
|
OP_PRINT,
|
||||||
|
OP_JUMP,
|
||||||
|
OP_JUMP_IF_FALSE,
|
||||||
|
OP_LOOP,
|
||||||
|
OP_CALL,
|
||||||
|
OP_INVOKE,
|
||||||
|
OP_CLOSURE,
|
||||||
|
OP_CLOSE_UPVALUE,
|
||||||
OP_RETURN,
|
OP_RETURN,
|
||||||
|
OP_TYPE,
|
||||||
|
OP_METHOD
|
||||||
} OpCode;
|
} OpCode;
|
||||||
|
|
||||||
typedef struct Chunk {
|
typedef struct {
|
||||||
int count;
|
int count;
|
||||||
int capacity;
|
int capacity;
|
||||||
uint8_t *code;
|
uint8_t *code;
|
||||||
|
@ -31,7 +49,7 @@ typedef struct Chunk {
|
||||||
ValueArray constants;
|
ValueArray constants;
|
||||||
} Chunk;
|
} Chunk;
|
||||||
|
|
||||||
void newChunk(Chunk *chunk);
|
void initChunk(Chunk *chunk);
|
||||||
void freeChunk(Chunk *chunk);
|
void freeChunk(Chunk *chunk);
|
||||||
void writeChunk(Chunk *chunk, uint8_t byte, int line);
|
void writeChunk(Chunk *chunk, uint8_t byte, int line);
|
||||||
int addConstant(Chunk *chunk, Value value);
|
int addConstant(Chunk *chunk, Value value);
|
||||||
|
|
13
src/common.h
13
src/common.h
|
@ -1,11 +1,16 @@
|
||||||
#ifndef ztl_common_h
|
#ifndef zlc_common_h
|
||||||
#define ztl_common_h
|
#define zlc_common_h
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define DEBUG_TRACE_EXECUTION
|
/* #define DEBUG_TRACE_EXECUTION */
|
||||||
#define DEBUG_PRINT_CODE
|
/* #define DEBUG_PRINT_CODE */
|
||||||
|
|
||||||
|
/* #define DEBUG_STRESS_GC */
|
||||||
|
/* #define DEBUG_LOG_GC */
|
||||||
|
|
||||||
|
#define UINT8_COUNT (UINT8_MAX + 1)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
785
src/compiler.c
785
src/compiler.c
|
@ -1,8 +1,10 @@
|
||||||
#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"
|
||||||
|
#include "memory.h"
|
||||||
#include "scanner.h"
|
#include "scanner.h"
|
||||||
|
|
||||||
#ifdef DEBUG_PRINT_CODE
|
#ifdef DEBUG_PRINT_CODE
|
||||||
|
@ -30,7 +32,7 @@ typedef enum {
|
||||||
PREC_PRIMARY
|
PREC_PRIMARY
|
||||||
} Precedence;
|
} Precedence;
|
||||||
|
|
||||||
typedef void (*ParseFn)();
|
typedef void (*ParseFn)(bool canAssign);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
ParseFn prefix;
|
ParseFn prefix;
|
||||||
|
@ -38,15 +40,47 @@ typedef struct {
|
||||||
Precedence precedence;
|
Precedence precedence;
|
||||||
} ParseRule;
|
} 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 TypeCompiler {
|
||||||
|
struct TypeCompiler *enclosing;
|
||||||
|
} TypeCompiler;
|
||||||
|
|
||||||
Parser parser;
|
Parser parser;
|
||||||
Chunk* compilingChunk;
|
Compiler *current = NULL;
|
||||||
|
TypeCompiler *currentType = NULL;
|
||||||
|
|
||||||
static Chunk* currentChunk() {
|
static Chunk *currentChunk() { return ¤t->function->chunk; }
|
||||||
return compilingChunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void errorAt(Token* token, const char* message) {
|
static void errorAt(Token *token, const char *message) {
|
||||||
if (parser.panicMode) return;
|
if (parser.panicMode)
|
||||||
|
return;
|
||||||
parser.panicMode = true;
|
parser.panicMode = true;
|
||||||
fprintf(stderr, "[line %d] Error", token->line);
|
fprintf(stderr, "[line %d] Error", token->line);
|
||||||
|
|
||||||
|
@ -62,11 +96,9 @@ static void errorAt(Token* token, const char* message) {
|
||||||
parser.hadError = true;
|
parser.hadError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void error(const char* message) {
|
static void error(const char *message) { errorAt(&parser.previous, message); }
|
||||||
errorAt(&parser.previous, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void errorAtCurrent(const char* message) {
|
static void errorAtCurrent(const char *message) {
|
||||||
errorAt(&parser.current, message);
|
errorAt(&parser.current, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,13 +107,14 @@ static void advance() {
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
parser.current = scanToken();
|
parser.current = scanToken();
|
||||||
if (parser.current.type != TOKEN_ERROR) break;
|
if (parser.current.type != TOKEN_ERROR)
|
||||||
|
break;
|
||||||
|
|
||||||
errorAtCurrent(parser.current.start);
|
errorAtCurrent(parser.current.start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void consume(TokenType type, const char* message) {
|
static void consume(TokenType type, const char *message) {
|
||||||
if (parser.current.type == type) {
|
if (parser.current.type == type) {
|
||||||
advance();
|
advance();
|
||||||
return;
|
return;
|
||||||
|
@ -90,6 +123,15 @@ 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);
|
||||||
}
|
}
|
||||||
|
@ -99,7 +141,31 @@ 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 void emitReturn() {
|
static void emitReturn() {
|
||||||
|
if (current->type == TYPE_INITIALIZER) {
|
||||||
|
emitBytes(OP_GET_LOCAL, 0);
|
||||||
|
} else {
|
||||||
|
emitByte(OP_NIL);
|
||||||
|
}
|
||||||
|
|
||||||
emitByte(OP_RETURN);
|
emitByte(OP_RETURN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,63 +183,628 @@ static void emitConstant(Value value) {
|
||||||
emitBytes(OP_CONSTANT, makeConstant(value));
|
emitBytes(OP_CONSTANT, makeConstant(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void endCompiler() {
|
static void patchJump(int offset) {
|
||||||
|
// -2 to adjust for the bytecode for the jump offset itself.
|
||||||
|
int jump = currentChunk()->count - offset - 2;
|
||||||
|
|
||||||
|
if (jump > UINT16_MAX) {
|
||||||
|
error("Too much code to jump over.");
|
||||||
|
}
|
||||||
|
|
||||||
|
currentChunk()->code[offset] = (jump >> 8) & 0xff;
|
||||||
|
currentChunk()->code[offset + 1] = jump & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initCompiler(Compiler *compiler, FunctionType type) {
|
||||||
|
compiler->enclosing = current;
|
||||||
|
compiler->function = NULL;
|
||||||
|
compiler->type = type;
|
||||||
|
compiler->localCount = 0;
|
||||||
|
compiler->scopeDepth = 0;
|
||||||
|
compiler->function = newFunction();
|
||||||
|
current = compiler;
|
||||||
|
if (type != TYPE_SCRIPT) {
|
||||||
|
current->function->name =
|
||||||
|
copyString(parser.previous.start, parser.previous.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
Local *local = ¤t->locals[current->localCount++];
|
||||||
|
local->depth = 0;
|
||||||
|
local->isCaptured = false;
|
||||||
|
if (type != TYPE_FUNCTION) {
|
||||||
|
local->name.start = "this";
|
||||||
|
local->name.length = 4;
|
||||||
|
} else {
|
||||||
|
local->name.start = "";
|
||||||
|
local->name.length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ObjFunction *endCompiler() {
|
||||||
emitReturn();
|
emitReturn();
|
||||||
|
ObjFunction *function = current->function;
|
||||||
|
|
||||||
#ifdef DEBUG_PRINT_CODE
|
#ifdef DEBUG_PRINT_CODE
|
||||||
if (!parser.hadError) {
|
if (!parser.hadError) {
|
||||||
disassembleChunk(currentChunk(), "code");
|
disassembleChunk(currentChunk(), function->name != NULL
|
||||||
|
? function->name->chars
|
||||||
|
: "<script>");
|
||||||
}
|
}
|
||||||
#endif
|
#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 expression();
|
||||||
static ParseRule* getRule(TokenType type);
|
static void statement();
|
||||||
|
static void declaration();
|
||||||
|
static ParseRule *getRule(TokenType type);
|
||||||
static void parsePrecedence(Precedence precedence);
|
static void parsePrecedence(Precedence precedence);
|
||||||
|
static void namedVariable(Token name, bool canAssign);
|
||||||
|
|
||||||
static void expression() {
|
static void binary(bool canAssign) {
|
||||||
parsePrecedence(PREC_ASSIGNMENT);
|
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 void grouping() {
|
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 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 uint8_t identifierConstant(Token *name) {
|
||||||
|
return makeConstant(OBJ_VAL(copyString(name->start, name->length)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool identifiersEqual(Token *a, Token *b) {
|
||||||
|
if (a->length != b->length)
|
||||||
|
return false;
|
||||||
|
return memcmp(a->start, b->start, a->length) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static 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 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 void addLocal(Token name) {
|
||||||
|
if (current->localCount == UINT8_COUNT) {
|
||||||
|
error("Too many local variables in function.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Local *local = ¤t->locals[current->localCount++];
|
||||||
|
local->name = name;
|
||||||
|
local->depth = -1;
|
||||||
|
local->isCaptured = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void declareVariable() {
|
||||||
|
if (current->scopeDepth == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Token *name = &parser.previous;
|
||||||
|
for (int i = current->localCount - 1; i >= 0; i--) {
|
||||||
|
Local *local = ¤t->locals[i];
|
||||||
|
if (local->depth != -1 && local->depth < current->scopeDepth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identifiersEqual(name, &local->name)) {
|
||||||
|
error("Already a variable with this name in this scope.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addLocal(*name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t parseVariable(const char *errorMessage) {
|
||||||
|
consume(TOKEN_IDENTIFIER, errorMessage);
|
||||||
|
|
||||||
|
declareVariable();
|
||||||
|
if (current->scopeDepth > 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return identifierConstant(&parser.previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void markInitialized() {
|
||||||
|
if (current->scopeDepth == 0)
|
||||||
|
return;
|
||||||
|
current->locals[current->localCount - 1].depth = current->scopeDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void defineVariable(uint8_t global) {
|
||||||
|
if (current->scopeDepth > 0) {
|
||||||
|
markInitialized();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emitBytes(OP_DEFINE_GLOBAL, global);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 255 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 void typeDeclaration() {
|
||||||
|
consume(TOKEN_IDENTIFIER, "Expect type name.");
|
||||||
|
Token typeName = parser.previous;
|
||||||
|
uint8_t nameConstant = identifierConstant(&parser.previous);
|
||||||
|
declareVariable();
|
||||||
|
|
||||||
|
emitBytes(OP_TYPE, nameConstant);
|
||||||
|
defineVariable(nameConstant);
|
||||||
|
|
||||||
|
TypeCompiler typeCompiler;
|
||||||
|
typeCompiler.enclosing = currentType;
|
||||||
|
currentType = &typeCompiler;
|
||||||
|
|
||||||
|
namedVariable(typeName, false);
|
||||||
|
consume(TOKEN_LEFT_BRACE, "Expect '{' before type body.");
|
||||||
|
while (!check(TOKEN_RIGHT_BRACE) && !check(TOKEN_EOF)) {
|
||||||
|
method();
|
||||||
|
}
|
||||||
|
consume(TOKEN_RIGHT_BRACE, "Expect '}' after type body.");
|
||||||
|
emitByte(OP_POP);
|
||||||
|
|
||||||
|
currentType = currentType->enclosing;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void funDeclaration() {
|
||||||
|
uint8_t global = parseVariable("Expect function name.");
|
||||||
|
markInitialized();
|
||||||
|
function(TYPE_FUNCTION);
|
||||||
|
defineVariable(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void and_(bool canAssign) {
|
||||||
|
int endJump = emitJump(OP_JUMP_IF_FALSE);
|
||||||
|
|
||||||
|
emitByte(OP_POP);
|
||||||
|
parsePrecedence(PREC_AND);
|
||||||
|
|
||||||
|
patchJump(endJump);
|
||||||
|
}
|
||||||
|
|
||||||
|
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_LET)) {
|
||||||
|
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_TYPE:
|
||||||
|
case TOKEN_FN:
|
||||||
|
case TOKEN_LET:
|
||||||
|
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_TYPE)) {
|
||||||
|
typeDeclaration();
|
||||||
|
} else if (match(TOKEN_FN)) {
|
||||||
|
funDeclaration();
|
||||||
|
} else if (match(TOKEN_LET)) {
|
||||||
|
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 grouping(bool canAssign) {
|
||||||
expression();
|
expression();
|
||||||
consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
|
consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void number() {
|
static void number(bool canAssign) {
|
||||||
double value = strtod(parser.previous.start, NULL);
|
double value = strtod(parser.previous.start, NULL);
|
||||||
emitConstant(NUMBER_VAL(value));
|
emitConstant(NUMBER_VAL(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void binary() {
|
static void or_(bool canAssign) {
|
||||||
TokenType operatorType = parser.previous.type;
|
int elseJump = emitJump(OP_JUMP_IF_FALSE);
|
||||||
ParseRule* rule = getRule(operatorType);
|
int endJump = emitJump(OP_JUMP);
|
||||||
parsePrecedence((Precedence)(rule->precedence + 1));
|
|
||||||
|
|
||||||
switch (operatorType) {
|
patchJump(elseJump);
|
||||||
case TOKEN_BANG_EQUAL: emitBytes(OP_EQUAL, OP_NOT); break;
|
emitByte(OP_POP);
|
||||||
case TOKEN_EQUAL_EQUAL: emitByte(OP_EQUAL); break;
|
|
||||||
case TOKEN_GREATER: emitByte(OP_GREATER); break;
|
parsePrecedence(PREC_OR);
|
||||||
case TOKEN_GREATER_EQUAL: emitBytes(OP_LESS, OP_NOT); break;
|
patchJump(endJump);
|
||||||
case TOKEN_LESS: emitByte(OP_LESS); break;
|
}
|
||||||
case TOKEN_LESS_EQUAL: emitBytes(OP_GREATER, OP_NOT); break;
|
|
||||||
case TOKEN_PLUS: emitByte(OP_ADD); break;
|
static void string(bool canAssign) {
|
||||||
case TOKEN_MINUS: emitByte(OP_SUBTRACT); break;
|
emitConstant(OBJ_VAL(
|
||||||
case TOKEN_STAR: emitByte(OP_MULTIPLY); break;
|
copyString(parser.previous.start + 1, parser.previous.length - 2)));
|
||||||
case TOKEN_SLASH: emitByte(OP_DIVIDE); break;
|
}
|
||||||
default: return; // Unreachable.
|
|
||||||
|
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, (uint8_t)arg);
|
||||||
|
} else {
|
||||||
|
emitBytes(getOp, (uint8_t)arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void literal() {
|
static void variable(bool canAssign) {
|
||||||
switch (parser.previous.type) {
|
namedVariable(parser.previous, canAssign);
|
||||||
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 unary() {
|
static void this_(bool canAssign) {
|
||||||
|
if (currentType == NULL) {
|
||||||
|
error("Can't use 'this' outside of a type.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
variable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unary(bool canAssign) {
|
||||||
TokenType operatorType = parser.previous.type;
|
TokenType operatorType = parser.previous.type;
|
||||||
|
|
||||||
// Compile the operand.
|
// Compile the operand.
|
||||||
|
@ -181,19 +812,24 @@ static void unary() {
|
||||||
|
|
||||||
// Emit the operator instruction.
|
// Emit the operator instruction.
|
||||||
switch (operatorType) {
|
switch (operatorType) {
|
||||||
case TOKEN_BANG: emitByte(OP_NOT); break;
|
case TOKEN_MINUS:
|
||||||
case TOKEN_MINUS: emitByte(OP_NEGATE); break;
|
emitByte(OP_NEGATE);
|
||||||
default: return; // Unreachable.
|
break;
|
||||||
|
case TOKEN_BANG:
|
||||||
|
emitByte(OP_NOT);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return; // Unreachable.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseRule rules[] = {
|
ParseRule rules[] = {
|
||||||
[TOKEN_LEFT_PAREN] = {grouping, NULL, PREC_NONE},
|
[TOKEN_LEFT_PAREN] = {grouping, call, PREC_CALL},
|
||||||
[TOKEN_RIGHT_PAREN] = {NULL, NULL, PREC_NONE},
|
[TOKEN_RIGHT_PAREN] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_LEFT_BRACE] = {NULL, NULL, PREC_NONE},
|
[TOKEN_LEFT_BRACE] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_RIGHT_BRACE] = {NULL, NULL, PREC_NONE},
|
[TOKEN_RIGHT_BRACE] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_COMMA] = {NULL, NULL, PREC_NONE},
|
[TOKEN_COMMA] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_DOT] = {NULL, NULL, PREC_NONE},
|
[TOKEN_DOT] = {NULL, dot, PREC_CALL},
|
||||||
[TOKEN_MINUS] = {unary, binary, PREC_TERM},
|
[TOKEN_MINUS] = {unary, binary, PREC_TERM},
|
||||||
[TOKEN_PLUS] = {NULL, binary, PREC_TERM},
|
[TOKEN_PLUS] = {NULL, binary, PREC_TERM},
|
||||||
[TOKEN_SEMICOLON] = {NULL, NULL, PREC_NONE},
|
[TOKEN_SEMICOLON] = {NULL, NULL, PREC_NONE},
|
||||||
|
@ -207,10 +843,10 @@ ParseRule rules[] = {
|
||||||
[TOKEN_GREATER_EQUAL] = {NULL, binary, PREC_COMPARISON},
|
[TOKEN_GREATER_EQUAL] = {NULL, binary, PREC_COMPARISON},
|
||||||
[TOKEN_LESS] = {NULL, binary, PREC_COMPARISON},
|
[TOKEN_LESS] = {NULL, binary, PREC_COMPARISON},
|
||||||
[TOKEN_LESS_EQUAL] = {NULL, binary, PREC_COMPARISON},
|
[TOKEN_LESS_EQUAL] = {NULL, binary, PREC_COMPARISON},
|
||||||
[TOKEN_IDENTIFIER] = {NULL, NULL, PREC_NONE},
|
[TOKEN_IDENTIFIER] = {variable, NULL, PREC_NONE},
|
||||||
[TOKEN_STRING] = {NULL, NULL, PREC_NONE},
|
[TOKEN_STRING] = {string, NULL, PREC_NONE},
|
||||||
[TOKEN_NUMBER] = {number, NULL, PREC_NONE},
|
[TOKEN_NUMBER] = {number, NULL, PREC_NONE},
|
||||||
[TOKEN_AND] = {NULL, NULL, PREC_NONE},
|
[TOKEN_AND] = {NULL, and_, PREC_AND},
|
||||||
[TOKEN_TYPE] = {NULL, NULL, PREC_NONE},
|
[TOKEN_TYPE] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_ELSE] = {NULL, NULL, PREC_NONE},
|
[TOKEN_ELSE] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_FALSE] = {literal, NULL, PREC_NONE},
|
[TOKEN_FALSE] = {literal, NULL, PREC_NONE},
|
||||||
|
@ -218,11 +854,11 @@ ParseRule rules[] = {
|
||||||
[TOKEN_FN] = {NULL, NULL, PREC_NONE},
|
[TOKEN_FN] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_IF] = {NULL, NULL, PREC_NONE},
|
[TOKEN_IF] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_NIL] = {literal, NULL, PREC_NONE},
|
[TOKEN_NIL] = {literal, NULL, PREC_NONE},
|
||||||
[TOKEN_OR] = {NULL, NULL, PREC_NONE},
|
[TOKEN_OR] = {NULL, or_, PREC_OR},
|
||||||
[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] = {this_, NULL, PREC_NONE},
|
||||||
[TOKEN_TRUE] = {literal, NULL, PREC_NONE},
|
[TOKEN_TRUE] = {literal, NULL, PREC_NONE},
|
||||||
[TOKEN_LET] = {NULL, NULL, PREC_NONE},
|
[TOKEN_LET] = {NULL, NULL, PREC_NONE},
|
||||||
[TOKEN_WHILE] = {NULL, NULL, PREC_NONE},
|
[TOKEN_WHILE] = {NULL, NULL, PREC_NONE},
|
||||||
|
@ -230,10 +866,6 @@ ParseRule rules[] = {
|
||||||
[TOKEN_EOF] = {NULL, NULL, PREC_NONE},
|
[TOKEN_EOF] = {NULL, NULL, PREC_NONE},
|
||||||
};
|
};
|
||||||
|
|
||||||
static ParseRule* getRule(TokenType type) {
|
|
||||||
return &rules[type];
|
|
||||||
}
|
|
||||||
|
|
||||||
static void parsePrecedence(Precedence precedence) {
|
static void parsePrecedence(Precedence precedence) {
|
||||||
advance();
|
advance();
|
||||||
ParseFn prefixRule = getRule(parser.previous.type)->prefix;
|
ParseFn prefixRule = getRule(parser.previous.type)->prefix;
|
||||||
|
@ -242,25 +874,44 @@ static void parsePrecedence(Precedence precedence) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixRule();
|
bool canAssign = precedence <= PREC_ASSIGNMENT;
|
||||||
|
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();
|
infixRule(canAssign);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canAssign && match(TOKEN_EQUAL)) {
|
||||||
|
error("Invalid assignment target.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool compile(const char* source, Chunk* chunk) {
|
static ParseRule *getRule(TokenType type) { return &rules[type]; }
|
||||||
newScanner(source);
|
|
||||||
compilingChunk = chunk;
|
ObjFunction *compile(const char *source) {
|
||||||
|
initScanner(source);
|
||||||
|
Compiler compiler;
|
||||||
|
initCompiler(&compiler, TYPE_SCRIPT);
|
||||||
|
|
||||||
parser.hadError = false;
|
parser.hadError = false;
|
||||||
parser.panicMode = false;
|
parser.panicMode = false;
|
||||||
|
|
||||||
advance();
|
advance();
|
||||||
expression();
|
|
||||||
consume(TOKEN_EOF, "Expect end of expression.");
|
while (!match(TOKEN_EOF)) {
|
||||||
endCompiler();
|
declaration();
|
||||||
return !parser.hadError;
|
}
|
||||||
|
|
||||||
|
ObjFunction *function = endCompiler();
|
||||||
|
return parser.hadError ? NULL : function;
|
||||||
|
}
|
||||||
|
|
||||||
|
void markCompilerRoots() {
|
||||||
|
Compiler *compiler = current;
|
||||||
|
while (compiler != NULL) {
|
||||||
|
markObject((Obj *)compiler->function);
|
||||||
|
compiler = compiler->enclosing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
#ifndef ztl_compiler_h
|
#ifndef zlc_compiler_h
|
||||||
#define ztl_compiler_h
|
#define zlc_compiler_h
|
||||||
|
|
||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
|
#include "object.h"
|
||||||
|
|
||||||
bool compile(const char* source, Chunk* chunk);
|
ObjFunction* compile(const char* source);
|
||||||
|
void markCompilerRoots();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
97
src/debug.c
97
src/debug.c
|
@ -1,9 +1,10 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "object.h"
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
|
|
||||||
void disassembleChunk(Chunk* chunk, const char* name) {
|
void disassembleChunk(Chunk *chunk, const char *name) {
|
||||||
printf("== %s ==\n", name);
|
printf("== %s ==\n", name);
|
||||||
|
|
||||||
for (int offset = 0; offset < chunk->count;) {
|
for (int offset = 0; offset < chunk->count;) {
|
||||||
|
@ -11,8 +12,7 @@ void disassembleChunk(Chunk* chunk, const char* name) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int constantInstruction(const char* name, Chunk* chunk,
|
static int constantInstruction(const char *name, Chunk *chunk, int offset) {
|
||||||
int offset) {
|
|
||||||
uint8_t constant = chunk->code[offset + 1];
|
uint8_t constant = chunk->code[offset + 1];
|
||||||
printf("%-16s %4d '", name, constant);
|
printf("%-16s %4d '", name, constant);
|
||||||
printValue(chunk->constants.values[constant]);
|
printValue(chunk->constants.values[constant]);
|
||||||
|
@ -20,15 +20,37 @@ static int constantInstruction(const char* name, Chunk* chunk,
|
||||||
return offset + 2;
|
return offset + 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int simpleInstruction(const char* name, int offset) {
|
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);
|
printf("%s\n", name);
|
||||||
return offset + 1;
|
return offset + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int disassembleInstruction(Chunk* chunk, int offset) {
|
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);
|
printf("%04d ", offset);
|
||||||
if (offset > 0 &&
|
if (offset > 0 && chunk->lines[offset] == chunk->lines[offset - 1]) {
|
||||||
chunk->lines[offset] == chunk->lines[offset - 1]) {
|
|
||||||
printf(" | ");
|
printf(" | ");
|
||||||
} else {
|
} else {
|
||||||
printf("%4d ", chunk->lines[offset]);
|
printf("%4d ", chunk->lines[offset]);
|
||||||
|
@ -36,10 +58,6 @@ int disassembleInstruction(Chunk* chunk, int offset) {
|
||||||
|
|
||||||
uint8_t instruction = chunk->code[offset];
|
uint8_t instruction = chunk->code[offset];
|
||||||
switch (instruction) {
|
switch (instruction) {
|
||||||
case OP_NOOP:
|
|
||||||
return simpleInstruction("OP_NOOP", offset);
|
|
||||||
case OP_NEGATE:
|
|
||||||
return simpleInstruction("OP_NEGATE", offset);
|
|
||||||
case OP_CONSTANT:
|
case OP_CONSTANT:
|
||||||
return constantInstruction("OP_CONSTANT", chunk, offset);
|
return constantInstruction("OP_CONSTANT", chunk, offset);
|
||||||
case OP_NIL:
|
case OP_NIL:
|
||||||
|
@ -48,8 +66,28 @@ int disassembleInstruction(Chunk* chunk, int offset) {
|
||||||
return simpleInstruction("OP_TRUE", offset);
|
return simpleInstruction("OP_TRUE", offset);
|
||||||
case OP_FALSE:
|
case OP_FALSE:
|
||||||
return simpleInstruction("OP_FALSE", offset);
|
return simpleInstruction("OP_FALSE", offset);
|
||||||
|
case OP_SET_GLOBAL:
|
||||||
|
return constantInstruction("OP_SET_GLOBAL", chunk, offset);
|
||||||
case OP_EQUAL:
|
case OP_EQUAL:
|
||||||
return simpleInstruction("OP_EQUAL", offset);
|
return simpleInstruction("OP_EQUAL", 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_UPVALUE:
|
||||||
|
return byteInstruction("OP_GET_UPVALUE", chunk, offset);
|
||||||
|
case OP_SET_UPVALUE:
|
||||||
|
return byteInstruction("OP_SET_UPVALUE", chunk, 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_GREATER:
|
case OP_GREATER:
|
||||||
return simpleInstruction("OP_GREATER", offset);
|
return simpleInstruction("OP_GREATER", offset);
|
||||||
case OP_LESS:
|
case OP_LESS:
|
||||||
|
@ -64,8 +102,45 @@ int disassembleInstruction(Chunk* chunk, int offset) {
|
||||||
return simpleInstruction("OP_DIVIDE", offset);
|
return simpleInstruction("OP_DIVIDE", offset);
|
||||||
case OP_NOT:
|
case OP_NOT:
|
||||||
return simpleInstruction("OP_NOT", offset);
|
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_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:
|
case OP_RETURN:
|
||||||
return simpleInstruction("OP_RETURN", offset);
|
return simpleInstruction("OP_RETURN", offset);
|
||||||
|
case OP_TYPE:
|
||||||
|
return constantInstruction("OP_TYPE", chunk, offset);
|
||||||
|
case OP_METHOD:
|
||||||
|
return constantInstruction("OP_METHOD", chunk, offset);
|
||||||
default:
|
default:
|
||||||
printf("Unknown opcode %d\n", instruction);
|
printf("Unknown opcode %d\n", instruction);
|
||||||
return offset + 1;
|
return offset + 1;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef ztl_debug_h
|
#ifndef zlc_debug_h
|
||||||
#define ztl_debug_h
|
#define zlc_debug_h
|
||||||
|
|
||||||
#include "chunk.h"
|
#include "chunk.h"
|
||||||
|
|
||||||
|
|
18
src/main.c
18
src/main.c
|
@ -7,6 +7,7 @@
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
|
|
||||||
|
|
||||||
static void repl() {
|
static void repl() {
|
||||||
char line[1024];
|
char line[1024];
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
@ -21,8 +22,8 @@ static void repl() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *readFile(const char *path) {
|
static char* readFile(const char* path) {
|
||||||
FILE *file = fopen(path, "rb");
|
FILE* file = fopen(path, "rb");
|
||||||
if (file == NULL) {
|
if (file == NULL) {
|
||||||
fprintf(stderr, "Could not open file \"%s\".\n", path);
|
fprintf(stderr, "Could not open file \"%s\".\n", path);
|
||||||
exit(74);
|
exit(74);
|
||||||
|
@ -32,12 +33,11 @@ static char *readFile(const char *path) {
|
||||||
size_t fileSize = ftell(file);
|
size_t fileSize = ftell(file);
|
||||||
rewind(file);
|
rewind(file);
|
||||||
|
|
||||||
char *buffer = (char*)malloc(fileSize + 1);
|
char* buffer = (char*)malloc(fileSize + 1);
|
||||||
if (buffer == NULL) {
|
if (buffer == NULL) {
|
||||||
fprintf(stderr, "Not enough memory to read \"%s\".\n", path);
|
fprintf(stderr, "Not enough memory to read \"%s\".\n", path);
|
||||||
exit(74);
|
exit(74);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
|
size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
|
||||||
if (bytesRead < fileSize) {
|
if (bytesRead < fileSize) {
|
||||||
fprintf(stderr, "Could not read file \"%s\".\n", path);
|
fprintf(stderr, "Could not read file \"%s\".\n", path);
|
||||||
|
@ -50,8 +50,8 @@ static char *readFile(const char *path) {
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void runFile(const char *path) {
|
static void runFile(const char* path) {
|
||||||
char *source = readFile(path);
|
char* source = readFile(path);
|
||||||
InterpretResult result = interpret(source);
|
InterpretResult result = interpret(source);
|
||||||
free(source);
|
free(source);
|
||||||
|
|
||||||
|
@ -59,15 +59,15 @@ static void runFile(const char *path) {
|
||||||
if (result == INTERPRET_RUNTIME_ERROR) exit(70);
|
if (result == INTERPRET_RUNTIME_ERROR) exit(70);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, const char* *argv) {
|
int main(int argc, const char* argv[]) {
|
||||||
newVM();
|
initVM();
|
||||||
|
|
||||||
if (argc == 1) {
|
if (argc == 1) {
|
||||||
repl();
|
repl();
|
||||||
} else if (argc == 2) {
|
} else if (argc == 2) {
|
||||||
runFile(argv[1]);
|
runFile(argv[1]);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Usage: clox [path]\n");
|
fprintf(stderr, "Usage: zlc [path]\n");
|
||||||
exit(64);
|
exit(64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
237
src/memory.c
237
src/memory.c
|
@ -1,16 +1,247 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "chunk.h"
|
#include "compiler.h"
|
||||||
#include "memory.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void* reallocate(void *pointer, size_t oldSize, size_t newSize) {
|
|
||||||
if (newSize == 0) {
|
if (newSize == 0) {
|
||||||
free(pointer);
|
free(pointer);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *result = realloc(pointer, newSize);
|
void *result = realloc(pointer, newSize);
|
||||||
if (result == NULL) exit(1);
|
if (result == NULL)
|
||||||
|
exit(1);
|
||||||
return result;
|
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);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OBJ_TYPE: {
|
||||||
|
TypeObj *t = (TypeObj *)object;
|
||||||
|
markObject((Obj *)t->name);
|
||||||
|
markTable(&t->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->t);
|
||||||
|
markTable(&instance->fields);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OBJ_UPVALUE:
|
||||||
|
markValue(((ObjUpvalue *)object)->closed);
|
||||||
|
break;
|
||||||
|
case OBJ_NATIVE:
|
||||||
|
case OBJ_STRING:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void freeObject(Obj *object) {
|
||||||
|
switch (object->type) {
|
||||||
|
case OBJ_BOUND_METHOD:
|
||||||
|
FREE(ObjBoundMethod, object);
|
||||||
|
break;
|
||||||
|
case OBJ_TYPE: {
|
||||||
|
TypeObj *t = (TypeObj *)object;
|
||||||
|
freeTable(&t->methods);
|
||||||
|
FREE(TypeObj, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void freeObjects() {
|
||||||
|
Obj *object = vm.objects;
|
||||||
|
while (object != NULL) {
|
||||||
|
#ifdef DEBUG_LOG_GC
|
||||||
|
printf("%p free type %d\n", (void *)object, object->type);
|
||||||
|
#endif
|
||||||
|
Obj *next = object->next;
|
||||||
|
freeObject(object);
|
||||||
|
object = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(vm.grayStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
17
src/memory.h
17
src/memory.h
|
@ -1,12 +1,17 @@
|
||||||
#ifndef ztl_memory_h
|
#ifndef zlc_memory_h
|
||||||
#define ztl_memory_h
|
#define zlc_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)
|
||||||
|
|
||||||
|
|
||||||
#define GROW_ARRAY(type, pointer, oldCount, newCount) \
|
#define GROW_ARRAY(type, pointer, oldCount, newCount) \
|
||||||
(type*)reallocate(pointer, sizeof(type) * (oldCount), \
|
(type*)reallocate(pointer, sizeof(type) * (oldCount), \
|
||||||
sizeof(type) * (newCount))
|
sizeof(type) * (newCount))
|
||||||
|
@ -15,7 +20,9 @@
|
||||||
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 markObject(Obj* object);
|
||||||
|
void markValue(Value value);
|
||||||
|
void collectGarbage();
|
||||||
|
void freeObjects();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
#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\n", (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeObj *newType(ObjString *name) {
|
||||||
|
TypeObj *t = ALLOCATE_OBJ(TypeObj, OBJ_TYPE);
|
||||||
|
t->name = name;
|
||||||
|
initTable(&t->methods);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(TypeObj *t) {
|
||||||
|
ObjInstance *instance = ALLOCATE_OBJ(ObjInstance, OBJ_INSTANCE);
|
||||||
|
instance->t = t;
|
||||||
|
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 ^= (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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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_TYPE:
|
||||||
|
printf("%s", AS_TYPE(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)->t->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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
#ifndef zlc_object_h
|
||||||
|
#define zlc_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_TYPE(value) isObjType(value, OBJ_TYPE)
|
||||||
|
#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_TYPE(value) ((TypeObj *)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_TYPE,
|
||||||
|
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;
|
||||||
|
} TypeObj;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Obj obj;
|
||||||
|
TypeObj *t;
|
||||||
|
Table fields;
|
||||||
|
} ObjInstance;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Obj obj;
|
||||||
|
Value receiver;
|
||||||
|
ObjClosure *method;
|
||||||
|
} ObjBoundMethod;
|
||||||
|
|
||||||
|
ObjBoundMethod *newBoundMethod(Value receiver, ObjClosure *method);
|
||||||
|
TypeObj *newType(ObjString *name);
|
||||||
|
ObjClosure *newClosure(ObjFunction *function);
|
||||||
|
ObjFunction *newFunction();
|
||||||
|
ObjInstance *newInstance(TypeObj *t);
|
||||||
|
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
|
223
src/scanner.c
223
src/scanner.c
|
@ -12,13 +12,43 @@ typedef struct {
|
||||||
|
|
||||||
Scanner scanner;
|
Scanner scanner;
|
||||||
|
|
||||||
void newScanner(const char* source) {
|
void initScanner(const char *source) {
|
||||||
scanner.start = source;
|
scanner.start = source;
|
||||||
scanner.current = source;
|
scanner.current = source;
|
||||||
scanner.line = 1;
|
scanner.line = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Token newToken(TokenType type) {
|
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 token;
|
||||||
token.type = type;
|
token.type = type;
|
||||||
token.start = scanner.start;
|
token.start = scanner.start;
|
||||||
|
@ -27,7 +57,7 @@ static Token newToken(TokenType type) {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Token errorToken(const char* message) {
|
static Token errorToken(const char *message) {
|
||||||
Token token;
|
Token token;
|
||||||
token.type = TOKEN_ERROR;
|
token.type = TOKEN_ERROR;
|
||||||
token.start = message;
|
token.start = message;
|
||||||
|
@ -36,31 +66,6 @@ static Token errorToken(const char* message) {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isAtEnd() {
|
|
||||||
return *scanner.current == '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
static char peek() {
|
|
||||||
return *scanner.current;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char peekNext() {
|
|
||||||
if (isAtEnd()) return '\0';
|
|
||||||
return scanner.current[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
static char advance() {
|
|
||||||
scanner.current++;
|
|
||||||
return scanner.current[-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool match(char expected) {
|
|
||||||
if (isAtEnd()) return false;
|
|
||||||
if (*scanner.current != expected) return false;
|
|
||||||
scanner.current++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void skipWhitespace() {
|
static void skipWhitespace() {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
char c = peek();
|
char c = peek();
|
||||||
|
@ -77,7 +82,8 @@ static void skipWhitespace() {
|
||||||
case '/':
|
case '/':
|
||||||
if (peekNext() == '/') {
|
if (peekNext() == '/') {
|
||||||
// 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 {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -88,43 +94,6 @@ static void skipWhitespace() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isDigit(char c) {
|
|
||||||
return c >= '0' && c <= '9';
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isAlpha(char c) {
|
|
||||||
return (c >= 'a' && c <= 'z') ||
|
|
||||||
(c >= 'A' && c <= 'Z') ||
|
|
||||||
c == '_';
|
|
||||||
}
|
|
||||||
|
|
||||||
static Token number() {
|
|
||||||
while (isDigit(peek())) advance();
|
|
||||||
|
|
||||||
// Look for a fractional part.
|
|
||||||
if (peek() == '.' && isDigit(peekNext())) {
|
|
||||||
// Consume the ".".
|
|
||||||
advance();
|
|
||||||
|
|
||||||
while (isDigit(peek())) advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return newToken(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 newToken(TOKEN_STRING);
|
|
||||||
}
|
|
||||||
|
|
||||||
static TokenType checkKeyword(int start, int length, const char *rest,
|
static TokenType checkKeyword(int start, int length, const char *rest,
|
||||||
TokenType type) {
|
TokenType type) {
|
||||||
if (scanner.current - scanner.start == start + length &&
|
if (scanner.current - scanner.start == start + length &&
|
||||||
|
@ -139,6 +108,8 @@ static TokenType identifierType() {
|
||||||
switch (scanner.start[0]) {
|
switch (scanner.start[0]) {
|
||||||
case 'a':
|
case 'a':
|
||||||
return checkKeyword(1, 2, "nd", TOKEN_AND);
|
return checkKeyword(1, 2, "nd", TOKEN_AND);
|
||||||
|
case 'b':
|
||||||
|
return checkKeyword(1, 4, "reak", TOKEN_BREAK);
|
||||||
case 'e':
|
case 'e':
|
||||||
return checkKeyword(1, 3, "lse", TOKEN_ELSE);
|
return checkKeyword(1, 3, "lse", TOKEN_ELSE);
|
||||||
case 'f':
|
case 'f':
|
||||||
|
@ -181,60 +152,110 @@ static TokenType identifierType() {
|
||||||
case 'w':
|
case 'w':
|
||||||
return checkKeyword(1, 4, "hile", TOKEN_WHILE);
|
return checkKeyword(1, 4, "hile", TOKEN_WHILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return TOKEN_IDENTIFIER;
|
return TOKEN_IDENTIFIER;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Token identifier() {
|
static Token identifier() {
|
||||||
while (isAlpha(peek()) || isDigit(peek())) advance();
|
while (isAlpha(peek()) || isDigit(peek()))
|
||||||
return newToken(identifierType());
|
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() {
|
Token scanToken() {
|
||||||
skipWhitespace();
|
skipWhitespace();
|
||||||
scanner.start = scanner.current;
|
scanner.start = scanner.current;
|
||||||
|
|
||||||
if (isAtEnd()) return newToken(TOKEN_EOF);
|
if (isAtEnd())
|
||||||
|
return makeToken(TOKEN_EOF);
|
||||||
|
|
||||||
char c = advance();
|
char c = advance();
|
||||||
if (isAlpha(c)) return identifier();
|
if (isAlpha(c))
|
||||||
if (isDigit(c)) return number();
|
return identifier();
|
||||||
|
if (isDigit(c))
|
||||||
|
return number();
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '(': return newToken(TOKEN_LEFT_PAREN);
|
case '(':
|
||||||
case ')': return newToken(TOKEN_RIGHT_PAREN);
|
return makeToken(TOKEN_LEFT_PAREN);
|
||||||
case '{': return newToken(TOKEN_LEFT_BRACE);
|
case ')':
|
||||||
case '}': return newToken(TOKEN_RIGHT_BRACE);
|
return makeToken(TOKEN_RIGHT_PAREN);
|
||||||
case '[': return newToken(TOKEN_LEFT_BRACKET);
|
case '{':
|
||||||
case ']': return newToken(TOKEN_RIGHT_BRACKET);
|
return makeToken(TOKEN_LEFT_BRACE);
|
||||||
case ';': return newToken(TOKEN_SEMICOLON);
|
case '}':
|
||||||
case ':': return newToken(TOKEN_COLON);
|
return makeToken(TOKEN_RIGHT_BRACE);
|
||||||
case '#': return newToken(TOKEN_MESH);
|
case '[':
|
||||||
case '$': return newToken(TOKEN_DOLLAR);
|
return makeToken(TOKEN_LEFT_BRACKET);
|
||||||
case '%': return newToken(TOKEN_PERCENT);
|
case ']':
|
||||||
case '&': return newToken(TOKEN_AMPERSAND);
|
return makeToken(TOKEN_RIGHT_BRACKET);
|
||||||
case '@': return newToken(TOKEN_AT);
|
case ';':
|
||||||
case ',': return newToken(TOKEN_COMMA);
|
return makeToken(TOKEN_SEMICOLON);
|
||||||
case '.': return newToken(TOKEN_DOT);
|
case ':':
|
||||||
case '-': return newToken(TOKEN_MINUS);
|
return makeToken(TOKEN_COLON);
|
||||||
case '+': return newToken(TOKEN_PLUS);
|
case '#':
|
||||||
case '/': return newToken(TOKEN_SLASH);
|
return makeToken(TOKEN_MESH);
|
||||||
case '*': return newToken(TOKEN_STAR);
|
case '$':
|
||||||
|
return makeToken(TOKEN_DOLLAR);
|
||||||
|
case '%':
|
||||||
|
return makeToken(TOKEN_PERCENT);
|
||||||
|
case '&':
|
||||||
|
return makeToken(TOKEN_AMPERSAND);
|
||||||
|
case '@':
|
||||||
|
return makeToken(TOKEN_AT);
|
||||||
|
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 '!':
|
case '!':
|
||||||
return newToken(
|
return makeToken(match('=') ? TOKEN_BANG_EQUAL : TOKEN_BANG);
|
||||||
match('=') ? TOKEN_BANG_EQUAL : TOKEN_BANG);
|
|
||||||
case '=':
|
case '=':
|
||||||
return newToken(
|
return makeToken(match('=') ? TOKEN_EQUAL_EQUAL : TOKEN_EQUAL);
|
||||||
match('=') ? TOKEN_EQUAL_EQUAL : TOKEN_EQUAL);
|
|
||||||
case '<':
|
case '<':
|
||||||
return newToken(
|
return makeToken(match('=') ? TOKEN_LESS_EQUAL : TOKEN_LESS);
|
||||||
match('=') ? TOKEN_LESS_EQUAL : TOKEN_LESS);
|
|
||||||
case '>':
|
case '>':
|
||||||
return newToken(
|
return makeToken(match('=') ? TOKEN_GREATER_EQUAL : TOKEN_GREATER);
|
||||||
match('=') ? TOKEN_GREATER_EQUAL : TOKEN_GREATER);
|
case '"':
|
||||||
|
return string();
|
||||||
case '"': return string();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errorToken("Unexpected character.");
|
return errorToken("Unexpected character.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef ztl_scanner_h
|
#ifndef zlc_scanner_h
|
||||||
#define ztl_scanner_h
|
#define zlc_scanner_h
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
// End of file
|
// End of file
|
||||||
|
@ -54,6 +54,7 @@ typedef enum {
|
||||||
TOKEN_TRUE,
|
TOKEN_TRUE,
|
||||||
TOKEN_LET,
|
TOKEN_LET,
|
||||||
TOKEN_WHILE,
|
TOKEN_WHILE,
|
||||||
|
TOKEN_BREAK,
|
||||||
TOKEN_ERROR,
|
TOKEN_ERROR,
|
||||||
} TokenType;
|
} TokenType;
|
||||||
|
|
||||||
|
@ -64,7 +65,7 @@ typedef struct {
|
||||||
int line;
|
int line;
|
||||||
} Token;
|
} Token;
|
||||||
|
|
||||||
void newScanner(const char *source);
|
void initScanner(const char *source);
|
||||||
Token scanToken();
|
Token scanToken();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef zlc_table_h
|
||||||
|
#define zlc_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);
|
||||||
|
void tableRemoveWhite(Table* table);
|
||||||
|
void markTable(Table* table);
|
||||||
|
|
||||||
|
#endif
|
12
src/value.c
12
src/value.c
|
@ -1,15 +1,17 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "object.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
|
|
||||||
void newValueArray(ValueArray *array) {
|
void initValueArray(ValueArray* array) {
|
||||||
array->values = NULL;
|
array->values = NULL;
|
||||||
array->capacity = 0;
|
array->capacity = 0;
|
||||||
array->count = 0;
|
array->count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeValueArray(ValueArray *array, Value value) {
|
void writeValueArray(ValueArray* array, Value value) {
|
||||||
if (array->capacity < array->count + 1) {
|
if (array->capacity < array->count + 1) {
|
||||||
int oldCapacity = array->capacity;
|
int oldCapacity = array->capacity;
|
||||||
array->capacity = GROW_CAPACITY(oldCapacity);
|
array->capacity = GROW_CAPACITY(oldCapacity);
|
||||||
|
@ -21,9 +23,9 @@ void writeValueArray(ValueArray *array, Value value) {
|
||||||
array->count++;
|
array->count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void freeValueArray(ValueArray *array) {
|
void freeValueArray(ValueArray* array) {
|
||||||
FREE_ARRAY(Value, array->values, array->capacity);
|
FREE_ARRAY(Value, array->values, array->capacity);
|
||||||
newValueArray(array);
|
initValueArray(array);
|
||||||
}
|
}
|
||||||
|
|
||||||
void printValue(Value value) {
|
void printValue(Value value) {
|
||||||
|
@ -33,6 +35,7 @@ void printValue(Value value) {
|
||||||
break;
|
break;
|
||||||
case VAL_NIL: printf("nil"); break;
|
case VAL_NIL: printf("nil"); break;
|
||||||
case VAL_NUMBER: printf("%g", AS_NUMBER(value)); break;
|
case VAL_NUMBER: printf("%g", AS_NUMBER(value)); break;
|
||||||
|
case VAL_OBJ: printObject(value); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +45,7 @@ bool valuesEqual(Value a, Value b) {
|
||||||
case VAL_BOOL: return AS_BOOL(a) == AS_BOOL(b);
|
case VAL_BOOL: return AS_BOOL(a) == AS_BOOL(b);
|
||||||
case VAL_NIL: return true;
|
case VAL_NIL: return true;
|
||||||
case VAL_NUMBER: return AS_NUMBER(a) == AS_NUMBER(b);
|
case VAL_NUMBER: return AS_NUMBER(a) == AS_NUMBER(b);
|
||||||
|
case VAL_OBJ: return AS_OBJ(a) == AS_OBJ(b);
|
||||||
default: return false; // Unreachable.
|
default: return false; // Unreachable.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
33
src/value.h
33
src/value.h
|
@ -1,12 +1,16 @@
|
||||||
#ifndef ztl_value_h
|
#ifndef zlc_value_h
|
||||||
#define ztl_value_h
|
#define zlc_value_h
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
|
||||||
|
typedef struct Obj Obj;
|
||||||
|
typedef struct ObjString ObjString;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
VAL_BOOL,
|
VAL_BOOL,
|
||||||
VAL_NIL,
|
VAL_NIL,
|
||||||
VAL_NUMBER,
|
VAL_NUMBER,
|
||||||
|
VAL_OBJ,
|
||||||
} ValueType;
|
} ValueType;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -14,30 +18,35 @@ typedef struct {
|
||||||
union {
|
union {
|
||||||
bool boolean;
|
bool boolean;
|
||||||
double number;
|
double number;
|
||||||
|
Obj* obj;
|
||||||
} as;
|
} as;
|
||||||
} Value;
|
} Value;
|
||||||
|
|
||||||
#define BOOL_VAL(value) ((Value){VAL_BOOL,{.boolean = value}})
|
|
||||||
#define NIL_VAL ((Value){VAL_NIL, {.number = 0}})
|
|
||||||
#define NUMBER_VAL(value) ((Value){VAL_NUMBER, {.number = value}})
|
|
||||||
|
|
||||||
#define AS_BOOL(value) ((value).as.boolean)
|
|
||||||
#define AS_NUMBER(value) ((value).as.number)
|
|
||||||
|
|
||||||
#define IS_BOOL(value) ((value).type == VAL_BOOL)
|
#define IS_BOOL(value) ((value).type == VAL_BOOL)
|
||||||
#define IS_NIL(value) ((value).type == VAL_NIL)
|
#define IS_NIL(value) ((value).type == VAL_NIL)
|
||||||
#define IS_NUMBER(value) ((value).type == VAL_NUMBER)
|
#define IS_NUMBER(value) ((value).type == VAL_NUMBER)
|
||||||
|
#define IS_OBJ(value) ((value).type == VAL_OBJ)
|
||||||
|
|
||||||
|
#define AS_BOOL(value) ((value).as.boolean)
|
||||||
|
#define AS_NUMBER(value) ((value).as.number)
|
||||||
|
#define AS_OBJ(value) ((value).as.obj)
|
||||||
|
|
||||||
|
#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;
|
||||||
int count;
|
int count;
|
||||||
Value *values;
|
Value* values;
|
||||||
} ValueArray;
|
} ValueArray;
|
||||||
|
|
||||||
bool valuesEqual(Value a, Value b);
|
bool valuesEqual(Value a, Value b);
|
||||||
void newValueArray(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);
|
||||||
void printValue(Value value);
|
void printValue(Value value);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
466
src/vm.c
466
src/vm.c
|
@ -1,36 +1,54 @@
|
||||||
|
#include "vm.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "compiler.h"
|
#include "compiler.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "vm.h"
|
#include "memory.h"
|
||||||
|
#include "object.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
VM vm;
|
VM vm;
|
||||||
|
|
||||||
static void resetStack() {
|
static Value clockNative(int argCount, Value *args) {
|
||||||
vm.stackTop = vm.stack;
|
return NUMBER_VAL((double)clock() / CLOCKS_PER_SEC);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void runtimeError(const char* format, ...) {
|
static void resetStack() {
|
||||||
|
vm.stackTop = vm.stack;
|
||||||
|
vm.frameCount = 0;
|
||||||
|
vm.openUpvalues = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void runtimeError(const char *format, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
vfprintf(stderr, format, args);
|
vfprintf(stderr, format, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
fputs("\n", stderr);
|
fputs("\n", stderr);
|
||||||
|
|
||||||
size_t instruction = vm.ip - vm.chunk->code - 1;
|
for (int i = vm.frameCount - 1; i >= 0; i--) {
|
||||||
int line = vm.chunk->lines[instruction];
|
CallFrame *frame = &vm.frames[i];
|
||||||
fprintf(stderr, "[line %d] in script\n", line);
|
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();
|
resetStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
void newVM() {
|
static void defineNative(const char *name, NativeFn function) {
|
||||||
resetStack();
|
push(OBJ_VAL(copyString(name, (int)strlen(name))));
|
||||||
}
|
push(OBJ_VAL(newNative(function)));
|
||||||
|
tableSet(&vm.globals, AS_STRING(vm.stack[0]), vm.stack[1]);
|
||||||
void freeVM() {
|
pop();
|
||||||
|
pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void push(Value value) {
|
void push(Value value) {
|
||||||
|
@ -43,17 +61,194 @@ Value pop() {
|
||||||
return *vm.stackTop;
|
return *vm.stackTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value peek(int distance) {
|
static Value peek(int distance) { return vm.stackTop[-1 - 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_TYPE: {
|
||||||
|
TypeObj *t = AS_TYPE(callee);
|
||||||
|
vm.stackTop[-argCount - 1] = OBJ_VAL(newInstance(t));
|
||||||
|
Value initializer;
|
||||||
|
if (tableGet(&t->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; // Non-callable object type.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtimeError("Can only call functions and typees.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool invokeFromType(TypeObj *t, ObjString *name, int argCount) {
|
||||||
|
Value method;
|
||||||
|
if (!tableGet(&t->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 invokeFromType(instance->t, name, argCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bindMethod(TypeObj *t, ObjString *name) {
|
||||||
|
Value method;
|
||||||
|
if (!tableGet(&t->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);
|
||||||
|
TypeObj *t = AS_TYPE(peek(1));
|
||||||
|
tableSet(&t->methods, name, method);
|
||||||
|
pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isFalsey(Value value) {
|
static bool isFalsey(Value value) {
|
||||||
return IS_NIL(value) || (IS_BOOL(value) && !AS_BOOL(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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
static InterpretResult run() {
|
static InterpretResult run() {
|
||||||
#define READ_BYTE() (*vm.ip++)
|
CallFrame *frame = &vm.frames[vm.frameCount - 1];
|
||||||
#define READ_CONSTANT() (vm.chunk->constants.values[READ_BYTE()])
|
|
||||||
|
#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) \
|
#define BINARY_OP(valueType, op) \
|
||||||
do { \
|
do { \
|
||||||
if (!IS_NUMBER(peek(0)) || !IS_NUMBER(peek(1))) { \
|
if (!IS_NUMBER(peek(0)) || !IS_NUMBER(peek(1))) { \
|
||||||
|
@ -68,16 +263,16 @@ static InterpretResult run() {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
#ifdef DEBUG_TRACE_EXECUTION
|
#ifdef DEBUG_TRACE_EXECUTION
|
||||||
printf(" ");
|
printf(" ");
|
||||||
for (Value* slot = vm.stack; slot < vm.stackTop; slot++) {
|
for (Value *slot = vm.stack; slot < vm.stackTop; slot++) {
|
||||||
printf("[ ");
|
printf("[ ");
|
||||||
printValue(*slot);
|
printValue(*slot);
|
||||||
printf(" ]");
|
printf(" ]");
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
disassembleInstruction(vm.chunk,
|
disassembleInstruction(
|
||||||
(int)(vm.ip - vm.chunk->code));
|
&frame->closure->function->chunk,
|
||||||
|
(int)(frame->ip - frame->closure->function->chunk.code));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint8_t instruction;
|
uint8_t instruction;
|
||||||
switch (instruction = READ_BYTE()) {
|
switch (instruction = READ_BYTE()) {
|
||||||
case OP_CONSTANT: {
|
case OP_CONSTANT: {
|
||||||
|
@ -85,21 +280,131 @@ static InterpretResult run() {
|
||||||
push(constant);
|
push(constant);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OP_NIL: push(NIL_VAL); break;
|
case OP_NIL:
|
||||||
case OP_TRUE: push(BOOL_VAL(true)); break;
|
push(NIL_VAL);
|
||||||
case OP_FALSE: push(BOOL_VAL(false)); break;
|
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();
|
||||||
|
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_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->t, 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_EQUAL: {
|
case OP_EQUAL: {
|
||||||
Value b = pop();
|
Value b = pop();
|
||||||
Value a = pop();
|
Value a = pop();
|
||||||
push(BOOL_VAL(valuesEqual(a, b)));
|
push(BOOL_VAL(valuesEqual(a, b)));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case OP_GREATER: BINARY_OP(BOOL_VAL, >); break;
|
case OP_GREATER:
|
||||||
case OP_LESS: BINARY_OP(BOOL_VAL, <); break;
|
BINARY_OP(BOOL_VAL, >);
|
||||||
case OP_ADD: BINARY_OP(NUMBER_VAL, +); break;
|
break;
|
||||||
case OP_SUBTRACT: BINARY_OP(NUMBER_VAL, -); break;
|
case OP_LESS:
|
||||||
case OP_MULTIPLY: BINARY_OP(NUMBER_VAL, *); break;
|
BINARY_OP(BOOL_VAL, <);
|
||||||
case OP_DIVIDE: BINARY_OP(NUMBER_VAL, /); break;
|
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:
|
case OP_NOT:
|
||||||
push(BOOL_VAL(isFalsey(pop())));
|
push(BOOL_VAL(isFalsey(pop())));
|
||||||
break;
|
break;
|
||||||
|
@ -110,35 +415,102 @@ static InterpretResult run() {
|
||||||
}
|
}
|
||||||
push(NUMBER_VAL(-AS_NUMBER(pop())));
|
push(NUMBER_VAL(-AS_NUMBER(pop())));
|
||||||
break;
|
break;
|
||||||
case OP_RETURN: {
|
case OP_PRINT: {
|
||||||
printValue(pop());
|
printValue(pop());
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
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_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_TYPE:
|
||||||
|
push(OBJ_VAL(newType(READ_STRING())));
|
||||||
|
break;
|
||||||
|
case OP_METHOD:
|
||||||
|
defineMethod(READ_STRING());
|
||||||
|
break;
|
||||||
|
case OP_RETURN:
|
||||||
|
Value result = pop();
|
||||||
|
closeUpvalues(frame->slots);
|
||||||
|
vm.frameCount--;
|
||||||
|
if (vm.frameCount == 0) {
|
||||||
|
pop();
|
||||||
return INTERPRET_OK;
|
return INTERPRET_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vm.stackTop = frame->slots;
|
||||||
|
push(result);
|
||||||
|
frame = &vm.frames[vm.frameCount - 1];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef READ_BYTE
|
#undef READ_BYTE
|
||||||
|
#undef READ_SHORT
|
||||||
#undef READ_CONSTANT
|
#undef READ_CONSTANT
|
||||||
|
#undef READ_STRING
|
||||||
#undef BINARY_OP
|
#undef BINARY_OP
|
||||||
}
|
}
|
||||||
|
|
||||||
InterpretResult interpret(const char* source) {
|
InterpretResult interpret(const char *source) {
|
||||||
Chunk chunk;
|
ObjFunction *function = compile(source);
|
||||||
newChunk(&chunk);
|
if (function == NULL)
|
||||||
|
|
||||||
if (!compile(source, &chunk)) {
|
|
||||||
freeChunk(&chunk);
|
|
||||||
return INTERPRET_COMPILE_ERROR;
|
return INTERPRET_COMPILE_ERROR;
|
||||||
}
|
|
||||||
|
|
||||||
vm.chunk = &chunk;
|
push(OBJ_VAL(function));
|
||||||
vm.ip = vm.chunk->code;
|
ObjClosure *closure = newClosure(function);
|
||||||
|
pop();
|
||||||
|
push(OBJ_VAL(closure));
|
||||||
|
call(closure, 0);
|
||||||
|
|
||||||
InterpretResult result = run();
|
return run();
|
||||||
|
|
||||||
freeChunk(&chunk);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
39
src/vm.h
39
src/vm.h
|
@ -1,16 +1,36 @@
|
||||||
#ifndef ztl_vm_h
|
#ifndef zlc_vm_h
|
||||||
#define ztl_vm_h
|
#define zlc_vm_h
|
||||||
|
|
||||||
#include "chunk.h"
|
#include "object.h"
|
||||||
|
#include "table.h"
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
|
|
||||||
#define STACK_MAX 256
|
#define FRAMES_MAX 64
|
||||||
|
#define STACK_MAX (FRAMES_MAX * UINT8_COUNT)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Chunk *chunk;
|
ObjClosure* closure;
|
||||||
uint8_t *ip;
|
uint8_t* ip;
|
||||||
|
Value* slots;
|
||||||
|
} CallFrame;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
CallFrame frames[FRAMES_MAX];
|
||||||
|
int frameCount;
|
||||||
|
|
||||||
Value stack[STACK_MAX];
|
Value stack[STACK_MAX];
|
||||||
Value *stackTop;
|
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;
|
} VM;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
@ -19,11 +39,12 @@ typedef enum {
|
||||||
INTERPRET_RUNTIME_ERROR
|
INTERPRET_RUNTIME_ERROR
|
||||||
} InterpretResult;
|
} InterpretResult;
|
||||||
|
|
||||||
void newVM();
|
extern VM vm;
|
||||||
|
|
||||||
|
void initVM();
|
||||||
void freeVM();
|
void freeVM();
|
||||||
InterpretResult interpret(const char* source);
|
InterpretResult interpret(const char* source);
|
||||||
void push(Value value);
|
void push(Value value);
|
||||||
Value pop();
|
Value pop();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue