through chapter 19 of Crafting Interpreters
This commit is contained in:
commit
14aeea5f5b
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.o
|
||||
kuroko
|
14
Makefile
Normal file
14
Makefile
Normal 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
49
chunk.c
Normal 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
46
chunk.h
Normal 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
284
compiler.c
Normal 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
5
compiler.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "chunk.h"
|
||||
|
||||
int krk_compile(const char * src, KrkChunk * chunk);
|
61
debug.c
Normal file
61
debug.c
Normal 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
6
debug.h
Normal 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
15
demo.krk
Normal 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
33
kuroko.c
Normal 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
5
kuroko.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
32
memory.c
Normal file
32
memory.c
Normal 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
14
memory.h
Normal 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
45
object.c
Normal 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
34
object.h
Normal 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
262
scanner.c
Normal 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
53
scanner.h
Normal 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
59
value.c
Normal 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
56
value.h
Normal 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
241
vm.c
Normal 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
23
vm.h
Normal 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);
|
Loading…
Reference in New Issue
Block a user