through chapter 19 of Crafting Interpreters

This commit is contained in:
K. Lange 2020-12-26 09:32:21 +09:00
commit 14aeea5f5b
21 changed files with 1339 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.o
kuroko

14
Makefile Normal file
View File

@ -0,0 +1,14 @@
CFLAGS = -g
OBJS = $(patsubst %.c, %.o, $(sort $(wildcard *.c)))
TARGET = kuroko
all: ${TARGET}
kuroko: ${OBJS}
.PHONY: clean
clean:
@rm -f ${OBJS} ${TARGET}
tags: $(wildcard *.c) $(wildcard *.h)
@ctags --c-kinds=+lx *.c *.h

49
chunk.c Normal file
View File

@ -0,0 +1,49 @@
#include "chunk.h"
#include "memory.h"
void krk_initChunk(KrkChunk * chunk) {
chunk->count = 0;
chunk->capacity = 0;
chunk->code = NULL;
chunk->lines = NULL;
krk_initValueArray(&chunk->constants);
}
void krk_writeChunk(KrkChunk * chunk, uint8_t byte, size_t line) {
if (chunk->capacity < chunk->count + 1) {
int old = chunk->capacity;
chunk->capacity = GROW_CAPACITY(old);
chunk->code = GROW_ARRAY(uint8_t, chunk->code, old, chunk->capacity);
chunk->lines = GROW_ARRAY(size_t, chunk->lines, old, chunk->capacity);
}
chunk->code[chunk->count] = byte;
chunk->lines[chunk->count] = line;
chunk->count++;
}
void krk_freeChunk(KrkChunk * chunk) {
FREE_ARRAY(uint8_t, chunk->code, chunk->capacity);
FREE_ARRAY(size_t, chunk->lines, chunk->capacity);
krk_freeValueArray(&chunk->constants);
krk_initChunk(chunk);
}
size_t krk_addConstant(KrkChunk * chunk, KrkValue value) {
krk_writeValueArray(&chunk->constants, value);
return chunk->constants.count - 1;
}
size_t krk_writeConstant(KrkChunk * chunk, KrkValue value, size_t line) {
size_t ind = krk_addConstant(chunk, value);
if (ind >= 256) {
krk_writeChunk(chunk, OP_CONSTANT_LONG, 1);
krk_writeChunk(chunk, 0xFF & (ind >> 16), 1);
krk_writeChunk(chunk, 0xFF & (ind >> 8), 1);
krk_writeChunk(chunk, 0xFF & (ind >> 0), 1);
} else {
krk_writeChunk(chunk, OP_CONSTANT, 1);
krk_writeChunk(chunk, ind, 1);
}
return ind;
}

46
chunk.h Normal file
View File

@ -0,0 +1,46 @@
#pragma once
#include "kuroko.h"
#include "value.h"
/**
* Opcodes
*
* These are pretty much entirely based on the clox opcodes from the book.
* There's not really much else to add here, since the VM is sufficient for
* our needs. Most of the interesting changes happen in the compiler.
*/
typedef enum {
OP_CONSTANT,
OP_CONSTANT_LONG,
OP_NEGATE,
OP_RETURN,
OP_ADD,
OP_SUBTRACT,
OP_MULTIPLY,
OP_DIVIDE,
OP_NONE,
OP_TRUE,
OP_FALSE,
OP_NOT,
OP_EQUAL,
OP_GREATER,
OP_LESS,
} KrkOpCode;
/**
* Bytecode chunks
*/
typedef struct {
size_t count;
size_t capacity;
uint8_t * code;
size_t * lines;
KrkValueArray constants;
} KrkChunk;
extern void krk_initChunk(KrkChunk * chunk);
extern void krk_writeChunk(KrkChunk * chunk, uint8_t byte, size_t line);
extern void krk_freeChunk(KrkChunk * chunk);
extern size_t krk_addConstant(KrkChunk * chunk, KrkValue value);
extern size_t krk_writeConstant(KrkChunk * chunk, KrkValue value, size_t line);

284
compiler.c Normal file
View File

@ -0,0 +1,284 @@
#include <stdio.h>
#include <stdlib.h>
#include "kuroko.h"
#include "compiler.h"
#include "scanner.h"
#include "object.h"
typedef struct {
KrkToken current;
KrkToken previous;
int hadError;
int panicMode;
} Parser;
typedef enum {
PREC_NONE,
PREC_ASSIGNMENT, /* = */
PREC_OR, /* or */
PREC_AND, /* and */
PREC_EQUALITY, /* == != in */
PREC_COMPARISON, /* < > <= >= */
PREC_TERM, /* + - */
PREC_FACTOR, /* * */
PREC_UNARY, /* ! - not */
PREC_CALL, /* . () */
PREC_PRIMARY
} Precedence;
typedef void (*ParseFn)(void);
typedef struct {
ParseFn prefix;
ParseFn infix;
Precedence precedence;
} ParseRule;
Parser parser;
KrkChunk * compilingChunk;
static KrkChunk * currentChunk() {
return compilingChunk;
}
static void parsePrecedence(Precedence precedence);
static ParseRule * getRule(KrkTokenType type);
static void errorAt(KrkToken * token, const char * message) {
if (parser.panicMode) return;
parser.panicMode = 1;
fprintf(stderr, "[line %d] Error", (int)token->line);
if (token->type == TOKEN_EOF) {
fprintf(stderr, " at end");
} else if (token->type != TOKEN_ERROR) {
fprintf(stderr, " at '%.*s'", (int)token->length, token->start);
}
fprintf(stderr, ": %s\n", message);
parser.hadError = 1;
}
static void error(const char * message) {
errorAt(&parser.previous, message);
}
static void errorAtCurrent(const char * message) {
errorAt(&parser.current, message);
}
static void advance() {
parser.previous = parser.current;
for (;;) {
parser.current = krk_scanToken();
if (parser.current.type == TOKEN_RETRY) continue;
if (parser.current.type != TOKEN_ERROR) break;
errorAtCurrent(parser.current.start);
}
}
static void consume(KrkTokenType type, const char * message) {
if (parser.current.type == type) {
advance();
return;
}
errorAtCurrent(message);
}
static void emitByte(uint8_t byte) {
krk_writeChunk(currentChunk(), byte, parser.previous.line);
}
static void emitBytes(uint8_t byte1, uint8_t byte2) {
emitByte(byte1);
emitByte(byte2);
}
static void emitReturn() {
emitByte(OP_RETURN);
}
static void endCompiler() {
emitReturn();
}
static void emitConstant(KrkValue value) {
krk_writeConstant(currentChunk(), value, parser.previous.line);
}
static void number() {
const char * start= parser.previous.start;
int base = 10;
if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X')) {
base = 10;
start += 2;
} else if (start[0] == '0' && (start[1] == 'b' || start[1] == 'B')) {
base = 2;
start += 2;
} else if (start[0] == '0') {
base = 8;
start += 1;
}
if (base == 10) {
/* See if it's a float */
for (size_t j = 0; j < parser.previous.length; ++j) {
if (parser.previous.start[j] == '.') {
double value = strtod(start, NULL);
emitConstant(FLOATING_VAL(value));
return;
}
}
}
int value = strtol(start, NULL, base);
emitConstant(INTEGER_VAL(value));
}
static void binary() {
KrkTokenType 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_ASTERISK: emitByte(OP_MULTIPLY); break;
case TOKEN_SOLIDUS: emitByte(OP_DIVIDE); break;
default: return;
}
}
static void literal() {
switch (parser.previous.type) {
case TOKEN_FALSE: emitByte(OP_FALSE); break;
case TOKEN_NONE: emitByte(OP_NONE); break;
case TOKEN_TRUE: emitByte(OP_TRUE); break;
default: return;
}
}
static void expression() {
parsePrecedence(PREC_ASSIGNMENT);
}
static void grouping() {
expression();
consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
}
static void unary() {
KrkTokenType operatorType = parser.previous.type;
parsePrecedence(PREC_UNARY);
switch (operatorType) {
case TOKEN_MINUS: emitByte(OP_NEGATE); break;
/* These are equivalent */
case TOKEN_BANG:
case TOKEN_NOT:
emitByte(OP_NOT);
break;
default: return;
}
}
static void string() {
emitConstant(OBJECT_VAL(copyString(parser.previous.start + 1, parser.previous.length - 2)));
}
ParseRule rules[] = {
[TOKEN_LEFT_PAREN] = {grouping, NULL, PREC_NONE},
[TOKEN_RIGHT_PAREN] = {NULL, NULL, PREC_NONE},
[TOKEN_LEFT_BRACE] = {NULL, NULL, PREC_NONE},
[TOKEN_RIGHT_BRACE] = {NULL, NULL, PREC_NONE},
[TOKEN_LEFT_SQUARE] = {NULL, NULL, PREC_NONE},
[TOKEN_RIGHT_SQUARE] = {NULL, NULL, PREC_NONE},
[TOKEN_COLON] = {NULL, NULL, PREC_NONE},
[TOKEN_COMMA] = {NULL, NULL, PREC_NONE},
[TOKEN_DOT] = {NULL, NULL, PREC_NONE},
[TOKEN_MINUS] = {unary, binary, PREC_TERM},
[TOKEN_PLUS] = {NULL, binary, PREC_TERM},
[TOKEN_SEMICOLON] = {NULL, NULL, PREC_NONE},
[TOKEN_SOLIDUS] = {NULL, binary, PREC_FACTOR},
[TOKEN_ASTERISK] = {NULL, binary, PREC_FACTOR},
[TOKEN_BANG] = {unary, NULL, PREC_NONE},
[TOKEN_BANG_EQUAL] = {NULL, binary, PREC_EQUALITY},
[TOKEN_EQUAL] = {NULL, NULL, PREC_NONE},
[TOKEN_EQUAL_EQUAL] = {NULL, binary, PREC_EQUALITY},
[TOKEN_GREATER] = {NULL, binary, PREC_COMPARISON},
[TOKEN_GREATER_EQUAL] = {NULL, binary, PREC_COMPARISON},
[TOKEN_LESS] = {NULL, binary, PREC_COMPARISON},
[TOKEN_LESS_EQUAL] = {NULL, binary, PREC_COMPARISON},
[TOKEN_IDENTIFIER] = {NULL, NULL, PREC_NONE},
[TOKEN_STRING] = {string, NULL, PREC_NONE},
[TOKEN_NUMBER] = {number, NULL, PREC_NONE},
[TOKEN_CODEPOINT] = {NULL, NULL, PREC_NONE}, /* should be equivalent to number */
[TOKEN_AND] = {NULL, NULL, PREC_NONE},
[TOKEN_CLASS] = {NULL, NULL, PREC_NONE},
[TOKEN_ELSE] = {NULL, NULL, PREC_NONE},
[TOKEN_FALSE] = {literal, NULL, PREC_NONE},
[TOKEN_FOR] = {NULL, NULL, PREC_NONE},
[TOKEN_DEF] = {NULL, NULL, PREC_NONE},
[TOKEN_IF] = {NULL, NULL, PREC_NONE},
[TOKEN_IN] = {NULL, NULL, PREC_NONE},
[TOKEN_LET] = {NULL, NULL, PREC_NONE},
[TOKEN_NONE] = {literal, NULL, PREC_NONE},
[TOKEN_NOT] = {unary, NULL, PREC_NONE},
[TOKEN_OR] = {NULL, NULL, PREC_NONE},
[TOKEN_PRINT] = {NULL, NULL, PREC_NONE},
[TOKEN_RETURN] = {NULL, NULL, PREC_NONE},
[TOKEN_SELF] = {NULL, NULL, PREC_NONE},
[TOKEN_SUPER] = {NULL, NULL, PREC_NONE},
[TOKEN_TRUE] = {literal, NULL, PREC_NONE},
[TOKEN_WHILE] = {NULL, NULL, PREC_NONE},
/* This is going to get interesting */
[TOKEN_INDENTATION] = {NULL, NULL, PREC_NONE},
[TOKEN_ERROR] = {NULL, NULL, PREC_NONE},
[TOKEN_EOF] = {NULL, NULL, PREC_NONE},
};
static void parsePrecedence(Precedence precedence) {
advance();
ParseFn prefixRule = getRule(parser.previous.type)->prefix;
if (prefixRule == NULL) {
error("expect expression");
return;
}
prefixRule();
while (precedence <= getRule(parser.current.type)->precedence) {
advance();
ParseFn infixRule = getRule(parser.previous.type)->infix;
infixRule();
}
}
static ParseRule * getRule(KrkTokenType type) {
return &rules[type];
}
int krk_compile(const char * src, KrkChunk * chunk) {
krk_initScanner(src);
compilingChunk = chunk;
parser.hadError = 0;
parser.panicMode = 0;
advance();
expression();
consume(TOKEN_EOF, "Expected end of expression.");
endCompiler();
return !parser.hadError;
}

5
compiler.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include "chunk.h"
int krk_compile(const char * src, KrkChunk * chunk);

61
debug.c Normal file
View File

@ -0,0 +1,61 @@
#include <stdio.h>
#include "debug.h"
#include "vm.h"
void krk_disassembleChunk(KrkChunk * chunk, const char * name) {
fprintf(stderr, "[%s]\n", name);
for (size_t offset = 0; offset < chunk->count;) {
offset = krk_disassembleInstruction(chunk, offset);
}
}
#define SIMPLE(opc) case opc: fprintf(stderr, "%s\n", #opc); return offset + 1;
size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset) {
fprintf(stderr, "%04u ", (unsigned int)offset);
if (offset > 0 && chunk->lines[offset] == chunk->lines[offset - 1]) {
fprintf(stderr, " | ");
} else {
fprintf(stderr, "%4d ", (int)chunk->lines[offset]);
}
uint8_t opcode = chunk->code[offset];
switch (opcode) {
SIMPLE(OP_RETURN)
SIMPLE(OP_ADD)
SIMPLE(OP_SUBTRACT)
SIMPLE(OP_MULTIPLY)
SIMPLE(OP_DIVIDE)
SIMPLE(OP_NEGATE)
SIMPLE(OP_NONE)
SIMPLE(OP_TRUE)
SIMPLE(OP_FALSE)
SIMPLE(OP_NOT)
SIMPLE(OP_EQUAL)
SIMPLE(OP_GREATER)
SIMPLE(OP_LESS)
case OP_CONSTANT:
{
uint8_t constant = chunk->code[offset + 1];
fprintf(stderr, "%-16s %4d '", "OP_CONSTANT", constant);
krk_printValue(stderr, chunk->constants.values[constant]);
fprintf(stderr, "' (type=%s)\n", typeName(chunk->constants.values[constant]));
}
return offset + 2;
case OP_CONSTANT_LONG:
{
size_t constant = (chunk->code[offset + 1] << 16) |
(chunk->code[offset + 2] << 8) |
(chunk->code[offset + 3] << 0);
fprintf(stderr, "%-16s %10d '", "OP_CONSTANT_LONG", (int)constant);
krk_printValue(stderr, chunk->constants.values[constant]);
fprintf(stderr, "' (type=%s)\n", typeName(chunk->constants.values[constant]));
}
return offset + 4;
default:
fprintf(stderr, "Unknown opcode: %02x\n", opcode);
return offset + 1;
}
}

6
debug.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
#include "chunk.h"
extern void krk_disassembleChunk(KrkChunk * chunk, const char * name);
extern size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset);

15
demo.krk Normal file
View File

@ -0,0 +1,15 @@
import system
class Foo:
def __init__(bar):
self.bar = bar
def getBar():
return self.bar
foo = Foo(42)

33
kuroko.c Normal file
View File

@ -0,0 +1,33 @@
#include <stdio.h>
#include "kuroko.h"
#include "chunk.h"
#include "debug.h"
#include "vm.h"
int main(int argc, char * argv[]) {
krk_initVM();
krk_interpret("\"hello\" + \"hellf\" + 1.4");
#if 0
KrkChunk chunk;
krk_initChunk(&chunk);
size_t constant = krk_addConstant(&chunk, 42);
krk_writeConstant(&chunk, 42, 1);
krk_writeConstant(&chunk, 86, 1);
krk_writeChunk(&chunk, OP_ADD, 1);
krk_writeConstant(&chunk, 3, 1);
krk_writeChunk(&chunk, OP_DIVIDE, 1);
krk_writeChunk(&chunk, OP_NEGATE, 1);
krk_writeChunk(&chunk, OP_RETURN, 1);
krk_interpret(&chunk);
krk_freeChunk(&chunk);
#endif
krk_freeVM();
return 0;
}

5
kuroko.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>

32
memory.c Normal file
View File

@ -0,0 +1,32 @@
#include "vm.h"
#include "memory.h"
#include "object.h"
void * krk_reallocate(void * ptr, size_t old, size_t new) {
if (new == 0) {
free(ptr);
return NULL;
}
return realloc(ptr, new);
}
static void freeObject(KrkObj * object) {
switch (object->type) {
case OBJ_STRING: {
KrkString * string = (KrkString*)object;
FREE_ARRAY(char, string->chars, string->length + 1);
FREE(KrkString, object);
break;
}
}
}
void krk_freeObjects() {
KrkObj * object = vm.objects;
while (object) {
KrkObj * next = object->next;
freeObject(object);
object = next;
}
}

14
memory.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include "kuroko.h"
#define GROW_CAPACITY(c) ((c) < 8 ? 8 : (c) * 2)
#define GROW_ARRAY(t,p,o,n) (t*)krk_reallocate(p,sizeof(t)*o,sizeof(t)*n)
#define FREE_ARRAY(t,a,c) krk_reallocate(a,sizeof(t) * c, 0)
#define FREE(t,p) krk_reallocate(p,sizeof(t),0)
#define ALLOCATE(type, count) (type*)krk_reallocate(NULL,0,sizeof(type)*(count))
extern void * krk_reallocate(void *, size_t, size_t);
extern void krk_freeObjects(void);

45
object.c Normal file
View File

@ -0,0 +1,45 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "memory.h"
#include "object.h"
#include "value.h"
#include "vm.h"
#define ALLOCATE_OBJECT(type, objectType) \
(type*)allocateObject(sizeof(type), objectType)
static KrkObj * allocateObject(size_t size, ObjType type) {
KrkObj * object = (KrkObj*)krk_reallocate(NULL, 0, size);
object->type = type;
object->next = vm.objects;
vm.objects = object;
return object;
}
static KrkString * allocateString(char * chars, size_t length) {
KrkString * string = ALLOCATE_OBJECT(KrkString, OBJ_STRING);
string->length = length;
string->chars = chars;
return string;
}
KrkString * takeString(char * chars, size_t length) {
return allocateString(chars, length);
}
KrkString * copyString(const char * chars, size_t length) {
char * heapChars = ALLOCATE(char, length + 1);
memcpy(heapChars, chars, length);
heapChars[length] = '\0';
return allocateString(heapChars, length);
}
void krk_printObject(FILE * f, KrkValue value) {
switch (OBJECT_TYPE(value)) {
case OBJ_STRING:
fprintf(stderr, "%s", AS_CSTRING(value));
break;
}
}

34
object.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <stdio.h>
#include "kuroko.h"
#include "value.h"
#define OBJECT_TYPE(value) (AS_OBJECT(value)->type)
#define IS_STRING(value) isObjType(value, OBJ_STRING)
#define AS_STRING(value) ((KrkString *)AS_OBJECT(value))
#define AS_CSTRING(value) (((KrkString *)AS_OBJECT(value))->chars)
typedef enum {
OBJ_STRING,
} ObjType;
struct Obj {
ObjType type;
struct Obj * next;
};
struct ObjString {
KrkObj obj;
size_t length;
char * chars;
};
static inline int isObjType(KrkValue value, ObjType type) {
return IS_OBJECT(value) && AS_OBJECT(value)->type == type;
}
extern KrkString * takeString(char * chars, size_t length);
extern KrkString * copyString(const char * chars, size_t length);
extern void krk_printObject(FILE * f, KrkValue value);

262
scanner.c Normal file
View File

@ -0,0 +1,262 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "kuroko.h"
#include "scanner.h"
typedef struct {
const char * start;
const char * cur;
size_t line;
int startOfLine;
} KrkScanner;
KrkScanner scanner;
void krk_initScanner(const char * src) {
scanner.start = src;
scanner.cur = src;
scanner.line = 1;
scanner.startOfLine = 1;
/* file, etc. ? */
}
static int isAtEnd() {
return *scanner.cur == '\0';
}
static KrkToken makeToken(KrkTokenType type) {
return (KrkToken){
.type = type,
.start = scanner.start,
.length = (size_t)(scanner.cur - scanner.start),
.line = scanner.line
};
}
static KrkToken errorToken(const char * errorStr) {
return (KrkToken){
.type = TOKEN_ERROR,
.start = errorStr,
.length = strlen(errorStr),
.line = scanner.line
};
}
static char advance() {
return *(scanner.cur++);
}
static int match(char expected) {
if (isAtEnd()) return 0;
if (*scanner.cur != expected) return 0;
scanner.cur++;
return 1;
}
static char peek() {
return *scanner.cur;
}
static char peekNext() {
if (isAtEnd()) return '\0';
return scanner.cur[1];
}
static void skipWhitespace() {
for (;;) {
char c = peek();
switch (c) {
case ' ':
case '\t':
advance();
break;
case '\n':
scanner.line++;
scanner.startOfLine = 1;
default:
return;
}
}
}
static KrkToken makeIndentation() {
while (!isAtEnd() && peek() == ' ') advance();
if (peek() == '\n') {
return errorToken("Empty indentation line is invalid.");
}
return makeToken(TOKEN_INDENTATION);
}
static KrkToken string() {
while (peek() != '"' && !isAtEnd()) {
if (peek() == '\\') advance(); /* Advance twice */
if (peek() == '\n') scanner.line++; /* Not start of line because string */
advance();
}
if (isAtEnd()) return errorToken("Unterminated string.");
assert(peek() == '"');
advance();
return makeToken(TOKEN_STRING);
}
static KrkToken codepoint() {
while (peek() != '\'' && !isAtEnd()) {
if (peek() == '\\') advance();
if (peek() == '\n') return makeToken(TOKEN_RETRY);
advance();
}
if (isAtEnd()) return errorToken("Unterminated codepoint literal.");
assert(peek() == '\'');
advance();
return makeToken(TOKEN_CODEPOINT);
}
static int isDigit(char c) {
return c >= '0' && c <= '9';
}
static KrkToken number(char c) {
if (c == 0) {
/* Hexadecimal */
if (peek() == 'x' || peek() == 'X') {
advance();
do {
char n = peek();
if (isDigit(n) || (n >= 'a' && n <= 'f') || (n >= 'A' && n <= 'F')) {
advance();
continue;
}
} while (0);
return makeToken(TOKEN_NUMBER);
}
/* Binary */
if (peek() == 'b' || peek() == 'B') {
advance();
while (peek() == '0' || peek() == '1') advance();
return makeToken(TOKEN_NUMBER);
}
/* Octal */
while (peek() >= '0' && peek() <= '7') advance();
return makeToken(TOKEN_NUMBER);
}
/* Decimal */
while (isDigit(peek())) advance();
/* Floating point */
if (peek() == '.' && isDigit(peekNext())) {
advance();
while (isDigit(peek())) advance();
}
return makeToken(TOKEN_NUMBER);
}
static int isAlpha(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_');
}
static int checkKeyword(size_t start, const char * rest, KrkTokenType type) {
size_t length = strlen(rest);
if (scanner.cur - scanner.start == start + length &&
memcmp(scanner.start + start, rest, length) == 0) return type;
return TOKEN_IDENTIFIER;
}
static KrkTokenType identifierType() {
#define MORE(i) (scanner.cur - scanner.start > i)
switch (*scanner.start) {
case 'a': return checkKeyword(1, "nd", TOKEN_AND);
case 'c': return checkKeyword(1, "lass", TOKEN_CLASS);
case 'd': return checkKeyword(1, "ef", TOKEN_DEF);
case 'e': return checkKeyword(1, "lse", TOKEN_ELSE);
case 'f': return checkKeyword(1, "or", TOKEN_FOR);
case 'F': return checkKeyword(1, "alse", TOKEN_FALSE);
case 'i': if (MORE(1)) switch (scanner.start[1]) {
case 'f': return checkKeyword(2, "f", TOKEN_IF);
case 'n': return checkKeyword(2, "n", TOKEN_IN);
} break;
case 'l': return checkKeyword(1, "et", TOKEN_LET);
case 'n': return checkKeyword(1, "ot", TOKEN_NOT);
case 'N': return checkKeyword(1, "one", TOKEN_NONE);
case 'o': return checkKeyword(1, "r", TOKEN_OR);
case 'p': return checkKeyword(1, "rint", TOKEN_PRINT);
case 'r': return checkKeyword(1, "eturn", TOKEN_RETURN);
case 's': if (MORE(1)) switch(scanner.start[1]) {
case 'e': return checkKeyword(2, "lf", TOKEN_SELF);
case 'u': return checkKeyword(2, "per", TOKEN_SUPER);
} break;
case 'T': return checkKeyword(1, "rue", TOKEN_TRUE);
case 'w': return checkKeyword(1, "hile", TOKEN_WHILE);
}
return TOKEN_IDENTIFIER;
}
static KrkToken identifier() {
while (isAlpha(peek()) || isDigit(peek())) advance();
return makeToken(identifierType());
}
KrkToken krk_scanToken() {
/* If at start of line, do thing */
if (scanner.startOfLine && peek() == ' ') {
scanner.start = scanner.cur;
return makeIndentation();
} else {
scanner.startOfLine = 0;
}
/* Eat whitespace */
skipWhitespace();
/* Skip comments */
if (peek() == '#') while (peek() != '\n' && !isAtEnd()) advance();
scanner.start = scanner.cur;
if (isAtEnd()) return makeToken(TOKEN_EOF);
char c = advance();
if (isAlpha(c)) return identifier();
if (isDigit(c)) return number(c);
switch (c) {
case '(': return makeToken(TOKEN_LEFT_PAREN);
case ')': return makeToken(TOKEN_RIGHT_PAREN);
case '{': return makeToken(TOKEN_LEFT_BRACE);
case '}': return makeToken(TOKEN_RIGHT_BRACE);
case '[': return makeToken(TOKEN_LEFT_SQUARE);
case ']': return makeToken(TOKEN_RIGHT_SQUARE);
case ':': return makeToken(TOKEN_COLON);
case ',': return makeToken(TOKEN_COMMA);
case '.': return makeToken(TOKEN_DOT);
case '-': return makeToken(TOKEN_MINUS);
case '+': return makeToken(TOKEN_PLUS);
case ';': return makeToken(TOKEN_SEMICOLON);
case '/': return makeToken(TOKEN_SOLIDUS);
case '*': return makeToken(TOKEN_ASTERISK);
case '!': return makeToken(match('=') ? TOKEN_BANG_EQUAL : TOKEN_BANG);
case '=': return makeToken(match('=') ? TOKEN_EQUAL_EQUAL : TOKEN_EQUAL);
case '<': return makeToken(match('=') ? TOKEN_LESS_EQUAL : TOKEN_LESS);
case '>': return makeToken(match('=') ? TOKEN_GREATER_EQUAL : TOKEN_GREATER);
case '"': return string();
case '\'': return codepoint();
}
return errorToken("Unexpected character.");
}

53
scanner.h Normal file
View File

@ -0,0 +1,53 @@
#pragma once
typedef enum {
TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN,
TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE,
TOKEN_LEFT_SQUARE, TOKEN_RIGHT_SQUARE,
TOKEN_COLON,
TOKEN_COMMA, TOKEN_DOT, TOKEN_MINUS, TOKEN_PLUS,
TOKEN_SEMICOLON, TOKEN_SOLIDUS, TOKEN_ASTERISK,
TOKEN_BANG, TOKEN_BANG_EQUAL,
TOKEN_EQUAL, TOKEN_EQUAL_EQUAL,
TOKEN_GREATER, TOKEN_GREATER_EQUAL,
TOKEN_LESS, TOKEN_LESS_EQUAL,
TOKEN_IDENTIFIER, TOKEN_STRING, TOKEN_NUMBER, TOKEN_CODEPOINT,
TOKEN_AND, /* and */
TOKEN_CLASS, /* class */
TOKEN_DEF, /* def */
TOKEN_ELSE, /* else */
TOKEN_FALSE, /* False */
TOKEN_FOR, /* for */
TOKEN_IF, /* if */
TOKEN_IN, /* in */
TOKEN_LET, /* let */
TOKEN_NONE, /* None */
TOKEN_NOT, /* not */
TOKEN_OR, /* or */
TOKEN_PRINT, /* print */
TOKEN_RETURN,/* return */
TOKEN_SELF, /* self */
TOKEN_SUPER, /* super */
TOKEN_TRUE, /* True */
TOKEN_WHILE, /* while */
TOKEN_INDENTATION,
TOKEN_RETRY,
TOKEN_ERROR,
TOKEN_EOF,
} KrkTokenType;
typedef struct {
KrkTokenType type;
const char * start;
size_t length;
size_t line;
} KrkToken;
void krk_initScanner(const char * src);
KrkToken krk_scanToken(void);

59
value.c Normal file
View File

@ -0,0 +1,59 @@
#include <string.h>
#include "memory.h"
#include "value.h"
#include "object.h"
void krk_initValueArray(KrkValueArray * array) {
array->values = NULL;
array->capacity = 0;
array->count = 0;
}
void krk_writeValueArray(KrkValueArray * array, KrkValue value) {
if (array->capacity < array->count - 1) {
int old = array->capacity;
array->capacity = GROW_CAPACITY(old);
array->values = GROW_ARRAY(KrkValue, array->values, old, array->capacity);
}
array->values[array->count] = value;
array->count++;
}
void krk_freeValueArray(KrkValueArray * array) {
FREE_ARRAY(KrkValue, array->values, array->capacity);
krk_initValueArray(array);
}
void krk_printValue(FILE * f, KrkValue value) {
if (IS_FLOATING(value)) {
fprintf(f, "%g", AS_FLOATING(value));
} else if (IS_INTEGER(value)) {
fprintf(f, "%d", AS_INTEGER(value));
} else if (IS_BOOLEAN(value)) {
fprintf(f, "%s", AS_BOOLEAN(value) ? "True" : "False");
} else if (IS_NONE(value)) {
fprintf(f, "None");
} else if (IS_OBJECT(value)) {
krk_printObject(f, value);
}
}
int krk_valuesEqual(KrkValue a, KrkValue b) {
if (a.type != b.type) return 0; /* uh, maybe not, this is complicated */
switch (a.type) {
case VAL_BOOLEAN: return AS_BOOLEAN(a) == AS_BOOLEAN(b);
case VAL_NONE: return 1; /* None always equals None */
case VAL_INTEGER: return AS_INTEGER(a) == AS_INTEGER(b);
case VAL_FLOATING: return AS_FLOATING(a) == AS_FLOATING(b);
case VAL_OBJECT: {
if (IS_STRING(a) && IS_STRING(b)) {
return (AS_STRING(a)->length == AS_STRING(b)->length) &&
memcmp(AS_STRING(a)->chars, AS_STRING(b)->chars, AS_STRING(a)->length) == 0;
}
return 0;
}
default: return 0;
}
}

56
value.h Normal file
View File

@ -0,0 +1,56 @@
#pragma once
#include <stdio.h>
#include "kuroko.h"
typedef struct Obj KrkObj;
typedef struct ObjString KrkString;
typedef enum {
VAL_BOOLEAN,
VAL_NONE,
VAL_INTEGER,
VAL_FLOATING,
VAL_OBJECT,
/* More here later */
} KrkValueType;
typedef struct {
KrkValueType type;
union {
int8_t boolean;
int32_t integer;
double floating;
KrkObj * object;
} as;
} KrkValue;
#define BOOLEAN_VAL(value) ((KrkValue){VAL_BOOLEAN, {.boolean = value}})
#define NONE_VAL(value) ((KrkValue){VAL_NONE, {.integer = 0}})
#define INTEGER_VAL(value) ((KrkValue){VAL_INTEGER, {.integer = value}})
#define FLOATING_VAL(value) ((KrkValue){VAL_FLOATING,{.floating = value}})
#define OBJECT_VAL(value) ((KrkValue){VAL_OBJECT, {.object = (KrkObj*)value}})
#define AS_BOOLEAN(value) ((value).as.boolean)
#define AS_INTEGER(value) ((value).as.integer)
#define AS_FLOATING(value) ((value).as.floating)
#define AS_OBJECT(value) ((value).as.object)
#define IS_BOOLEAN(value) ((value).type == VAL_BOOLEAN)
#define IS_NONE(value) ((value).type == VAL_NONE)
#define IS_INTEGER(value) ((value).type == VAL_INTEGER)
#define IS_FLOATING(value) ((value).type == VAL_FLOATING)
#define IS_OBJECT(value) ((value).type == VAL_OBJECT)
typedef struct {
size_t capacity;
size_t count;
KrkValue * values;
} KrkValueArray;
extern void krk_initValueArray(KrkValueArray * array);
extern void krk_writeValueArray(KrkValueArray * array, KrkValue value);
extern void krk_freeValueArray(KrkValueArray * array);
extern void krk_printValue(FILE * f, KrkValue value);
extern int krk_valuesEqual(KrkValue a, KrkValue b);

241
vm.c Normal file
View File

@ -0,0 +1,241 @@
#include <stdarg.h>
#include <string.h>
#include "vm.h"
#include "debug.h"
#include "memory.h"
#include "compiler.h"
#include "object.h"
/* Why is this static... why do we do this to ourselves... */
KrkVM vm;
static void resetStack() {
vm.stackTop = vm.stack;
}
static void runtimeError(const char * fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
size_t instruction = vm.ip - vm.chunk->code - 1;
size_t line = vm.chunk->lines[instruction];
fprintf(stderr, "[line %d] in script\n", (int)line);
resetStack();
}
void krk_push(KrkValue value) {
if ((size_t)(vm.stackTop - vm.stack) + 1 > vm.stackSize) {
size_t old = vm.stackSize;
size_t old_offset = vm.stackTop - vm.stack;
vm.stackSize = GROW_CAPACITY(old);
vm.stack = GROW_ARRAY(KrkValue, vm.stack, old, vm.stackSize);
vm.stackTop = vm.stack + old_offset;
}
*vm.stackTop = value;
vm.stackTop++;
}
KrkValue krk_pop() {
vm.stackTop--;
if (vm.stackTop < vm.stack) {
fprintf(stderr, "XXX: Stack overflow?");
}
return *vm.stackTop;
}
KrkValue krk_peep(int distance) {
return vm.stackTop[-1 - distance];
}
void krk_initVM() {
resetStack();
vm.objects = NULL;
}
void krk_freeVM() {
/* todo */
krk_freeObjects();
FREE_ARRAY(size_t, vm.stack, vm.stackSize);
}
static int isFalsey(KrkValue value) {
return IS_NONE(value) || (IS_BOOLEAN(value) && !AS_BOOLEAN(value)) ||
(IS_INTEGER(value) && !AS_INTEGER(value));
/* Objects in the future: */
/* IS_STRING && length == 0; IS_ARRAY && length == 0; IS_INSTANCE && __bool__ returns 0... */
}
const char * typeName(KrkValue value) {
if (value.type == VAL_BOOLEAN) return "Boolean";
if (value.type == VAL_NONE) return "None";
if (value.type == VAL_INTEGER) return "Integer";
if (value.type == VAL_FLOATING) return "Floating";
if (value.type == VAL_OBJECT) {
if (IS_STRING(value)) return "String";
return "(Unspecified Object)";
}
return "???";
}
#define MAKE_BIN_OP(name,operator) \
static KrkValue name (KrkValue a, KrkValue b) { \
if (IS_INTEGER(a) && IS_INTEGER(b)) return INTEGER_VAL(AS_INTEGER(a) operator AS_INTEGER(b)); \
if (IS_FLOATING(a)) { \
if (IS_INTEGER(b)) return FLOATING_VAL(AS_FLOATING(a) operator (double)AS_INTEGER(b)); \
else if (IS_FLOATING(b)) return FLOATING_VAL(AS_FLOATING(a) operator AS_FLOATING(b)); \
} else if (IS_FLOATING(b)) { \
if (IS_INTEGER(a)) return FLOATING_VAL((double)AS_INTEGER(a) operator AS_FLOATING(b)); \
} \
runtimeError("Incompatible types for binary operand %s: %s and %s", #operator, typeName(a), typeName(b)); \
return NONE_VAL(); \
}
MAKE_BIN_OP(add,+)
MAKE_BIN_OP(subtract,-)
MAKE_BIN_OP(multiply,*)
MAKE_BIN_OP(divide,/)
#define MAKE_COMPARATOR(name, operator) \
static KrkValue name (KrkValue a, KrkValue b) { \
if (IS_INTEGER(a) && IS_INTEGER(b)) return BOOLEAN_VAL(AS_INTEGER(a) operator AS_INTEGER(b)); \
if (IS_FLOATING(a)) { \
if (IS_INTEGER(b)) return BOOLEAN_VAL(AS_FLOATING(a) operator AS_INTEGER(b)); \
else if (IS_FLOATING(b)) return BOOLEAN_VAL(AS_FLOATING(a) operator AS_FLOATING(b)); \
} else if (IS_FLOATING(b)) { \
if (IS_INTEGER(a)) return BOOLEAN_VAL(AS_INTEGER(a) operator AS_INTEGER(b)); \
} \
runtimeError("Can not compare types %s and %s", typeName(a), typeName(b)); \
return NONE_VAL(); \
}
MAKE_COMPARATOR(less, <)
MAKE_COMPARATOR(greater, >)
static void concatenate(const char * a, const char * b, size_t al, size_t bl) {
size_t length = al + bl;
char * chars = ALLOCATE(char, length + 1);
memcpy(chars, a, al);
memcpy(chars + al, b, bl);
chars[length] = '\0';
KrkString * result = takeString(chars, length);
krk_push(OBJECT_VAL(result));
}
static void addObjects() {
KrkValue _b = krk_pop();
KrkValue _a = krk_pop();
if (IS_STRING(_a)) {
KrkString * a = AS_STRING(_a);
if (IS_STRING(_b)) {
KrkString * b = AS_STRING(_b);
concatenate(a->chars,b->chars,a->length,b->length);
return;
}
char tmp[256];
if (IS_INTEGER(_b)) {
sprintf(tmp, "%d", AS_INTEGER(_b));
} else if (IS_FLOATING(_b)) {
sprintf(tmp, "%g", AS_FLOATING(_b));
} else if (IS_BOOLEAN(_b)) {
sprintf(tmp, "%s", AS_BOOLEAN(_b) ? "True" : "False");
} else if (IS_NONE(_b)) {
sprintf(tmp, "None");
} else {
sprintf(tmp, "<Object>");
}
concatenate(a->chars,tmp,a->length,strlen(tmp));
} else {
runtimeError("Can not concatenate types %s and %s", typeName(_a), typeName(_b)); \
krk_push(NONE_VAL());
}
}
#define DEBUG
static KrkValue run() {
#define READ_BYTE() (*vm.ip++)
#define BINARY_OP(op) { KrkValue b = krk_pop(); KrkValue a = krk_pop(); krk_push(op(a,b)); break; }
for (;;) {
#ifdef DEBUG
fprintf(stderr, " ");
for (KrkValue * slot = vm.stack; slot < vm.stackTop; slot++) {
fprintf(stderr, "[ ");
krk_printValue(stderr, *slot);
fprintf(stderr, " ]");
}
fprintf(stderr, "\n");
krk_disassembleInstruction(vm.chunk, (size_t)(vm.ip - vm.chunk->code));
#endif
uint8_t opcode;
switch ((opcode = READ_BYTE())) {
case OP_RETURN: {
krk_printValue(stdout, krk_pop());
fprintf(stdout, "\n");
return INTEGER_VAL(0);
}
case OP_EQUAL: {
KrkValue b = krk_pop();
KrkValue a = krk_pop();
krk_push(BOOLEAN_VAL(krk_valuesEqual(a,b)));
break;
}
case OP_GREATER: BINARY_OP(greater)
case OP_ADD:
if (IS_OBJECT(krk_peep(0)) || IS_OBJECT(krk_peep(1))) addObjects();
else BINARY_OP(add)
break;
case OP_SUBTRACT: BINARY_OP(subtract)
case OP_MULTIPLY: BINARY_OP(multiply)
case OP_DIVIDE: BINARY_OP(divide)
case OP_NEGATE: {
KrkValue value = krk_pop();
if (IS_INTEGER(value)) krk_push(INTEGER_VAL(-AS_INTEGER(value)));
else if (IS_FLOATING(value)) krk_push(FLOATING_VAL(-AS_FLOATING(value)));
else { runtimeError("Incompatible operand type for prefix negation."); return NONE_VAL(); }
break;
}
case OP_CONSTANT: {
size_t index = READ_BYTE();
KrkValue constant = vm.chunk->constants.values[index];
krk_push(constant);
break;
}
case OP_CONSTANT_LONG: {
size_t top = READ_BYTE();
size_t mid = READ_BYTE();
size_t low = READ_BYTE();
size_t index = (top << 16) | (mid << 8) | (low);
KrkValue constant = vm.chunk->constants.values[index];
krk_push(constant);
break;
}
case OP_NONE: krk_push(NONE_VAL()); break;
case OP_TRUE: krk_push(BOOLEAN_VAL(1)); break;
case OP_FALSE: krk_push(BOOLEAN_VAL(0)); break;
case OP_NOT: krk_push(BOOLEAN_VAL(isFalsey(krk_pop()))); break;
}
}
#undef BINARY_OP
#undef READ_BYTE
}
int krk_interpret(const char * src) {
KrkChunk chunk;
krk_initChunk(&chunk);
if (!krk_compile(src, &chunk)) {
krk_freeChunk(&chunk);
return 1;
}
vm.chunk = &chunk;
vm.ip = vm.chunk->code;
KrkValue result = run();
krk_freeChunk(&chunk);
return IS_NONE(result);
}

23
vm.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#include "kuroko.h"
#include "chunk.h"
#include "value.h"
typedef struct {
KrkChunk * chunk;
uint8_t * ip;
size_t stackSize;
KrkValue * stack;
KrkValue * stackTop;
KrkObj * objects;
} KrkVM;
extern KrkVM vm;
extern void krk_initVM(void);
extern void krk_freeVM(void);
extern int krk_interpret(const char * src);
extern void krk_push(KrkValue value);
extern KrkValue krk_pop(void);
extern const char * typeName(KrkValue value);