diff --git a/chunk.c b/chunk.c index 2becb79..c26b26f 100644 --- a/chunk.c +++ b/chunk.c @@ -34,8 +34,7 @@ size_t krk_addConstant(KrkChunk * chunk, KrkValue value) { return chunk->constants.count - 1; } -size_t krk_writeConstant(KrkChunk * chunk, KrkValue value, size_t line) { - size_t ind = krk_addConstant(chunk, value); +void krk_emitConstant(KrkChunk * chunk, size_t ind) { if (ind >= 256) { krk_writeChunk(chunk, OP_CONSTANT_LONG, 1); krk_writeChunk(chunk, 0xFF & (ind >> 16), 1); @@ -45,5 +44,10 @@ size_t krk_writeConstant(KrkChunk * chunk, KrkValue value, size_t line) { krk_writeChunk(chunk, OP_CONSTANT, 1); krk_writeChunk(chunk, ind, 1); } +} + +size_t krk_writeConstant(KrkChunk * chunk, KrkValue value, size_t line) { + size_t ind = krk_addConstant(chunk, value); + krk_emitConstant(chunk, ind); return ind; } diff --git a/chunk.h b/chunk.h index 5f8c307..63bb62d 100644 --- a/chunk.h +++ b/chunk.h @@ -23,9 +23,17 @@ typedef enum { OP_TRUE, OP_FALSE, OP_NOT, + OP_POP, OP_EQUAL, OP_GREATER, OP_LESS, + OP_PRINT, + OP_DEFINE_GLOBAL, + OP_DEFINE_GLOBAL_LONG, + OP_GET_GLOBAL, + OP_GET_GLOBAL_LONG, + OP_SET_GLOBAL, + OP_SET_GLOBAL_LONG, } KrkOpCode; /** @@ -43,4 +51,5 @@ 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 void krk_emitConstant(KrkChunk * chunk, size_t ind); extern size_t krk_writeConstant(KrkChunk * chunk, KrkValue value, size_t line); diff --git a/compiler.c b/compiler.c index 4b30945..476a80c 100644 --- a/compiler.c +++ b/compiler.c @@ -27,7 +27,7 @@ typedef enum { PREC_PRIMARY } Precedence; -typedef void (*ParseFn)(void); +typedef void (*ParseFn)(int); typedef struct { ParseFn prefix; @@ -42,7 +42,13 @@ static KrkChunk * currentChunk() { } static void parsePrecedence(Precedence precedence); +static size_t parseVariable(const char * errorMessage); +static void defineVariable(size_t global); +static size_t identifierConstant(KrkToken * name); static ParseRule * getRule(KrkTokenType type); +static void expression(); +static void statement(); +static void declaration(); static void errorAt(KrkToken * token, const char * message) { if (parser.panicMode) return; @@ -88,6 +94,16 @@ static void consume(KrkTokenType type, const char * message) { errorAtCurrent(message); } +static int check(KrkTokenType type) { + return parser.current.type == type; +} + +static int match(KrkTokenType type) { + if (!check(type)) return 0; + advance(); + return 1; +} + static void emitByte(uint8_t byte) { krk_writeChunk(currentChunk(), byte, parser.previous.line); } @@ -105,11 +121,17 @@ static void endCompiler() { emitReturn(); } +static void endOfLine() { + if (!(match(TOKEN_EOL) || match(TOKEN_EOF))) { + errorAtCurrent("Expected end of line."); + } +} + static void emitConstant(KrkValue value) { krk_writeConstant(currentChunk(), value, parser.previous.line); } -static void number() { +static void number(int canAssign) { const char * start= parser.previous.start; int base = 10; if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X')) { @@ -136,7 +158,7 @@ static void number() { emitConstant(INTEGER_VAL(value)); } -static void binary() { +static void binary(int canAssign) { KrkTokenType operatorType = parser.previous.type; ParseRule * rule = getRule(operatorType); parsePrecedence((Precedence)(rule->precedence + 1)); @@ -157,7 +179,7 @@ static void binary() { } } -static void literal() { +static void literal(int canAssign) { switch (parser.previous.type) { case TOKEN_FALSE: emitByte(OP_FALSE); break; case TOKEN_NONE: emitByte(OP_NONE); break; @@ -170,12 +192,77 @@ static void expression() { parsePrecedence(PREC_ASSIGNMENT); } -static void grouping() { +static void varDeclaration() { + ssize_t global = parseVariable("Expected variable name."); + + if (match(TOKEN_EQUAL)) { + expression(); + } else { + emitByte(OP_NONE); + } + + endOfLine(); + defineVariable(global); +} + +static void printStatement() { + expression(); + endOfLine(); + emitByte(OP_PRINT); +} + +static void synchronize() { + parser.panicMode = 0; + while (parser.current.type != TOKEN_EOF) { + if (parser.previous.type == TOKEN_EOL) return; + + switch (parser.current.type) { + case TOKEN_CLASS: + case TOKEN_DEF: + case TOKEN_LET: + case TOKEN_FOR: + case TOKEN_IF: + case TOKEN_WHILE: + case TOKEN_PRINT: + case TOKEN_RETURN: + return; + default: break; + } + + advance(); + } +} + +static void declaration() { + if (match(TOKEN_LET)) { + varDeclaration(); + } else { + statement(); + } + + if (parser.panicMode) synchronize(); +} + +static void expressionStatement() { + expression(); + endOfLine(); + emitByte(OP_POP); +} + +static void statement() { + if (match(TOKEN_PRINT)) { + printStatement(); + } else { + expressionStatement(); + } +} + +static void grouping(int canAssign) { expression(); consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression."); } -static void unary() { +static void unary(int canAssign) { KrkTokenType operatorType = parser.previous.type; parsePrecedence(PREC_UNARY); @@ -193,10 +280,28 @@ static void unary() { } } -static void string() { +static void string(int canAssign) { emitConstant(OBJECT_VAL(copyString(parser.previous.start + 1, parser.previous.length - 2))); } +#define EMIT_CONSTANT_OP(opc, arg) do { if (arg < 256) { emitBytes(opc, arg); } \ + else { emitBytes(opc ## _LONG, arg >> 16); emitBytes(arg >> 8, arg); } } while (0) + +static void namedVariable(KrkToken name, int canAssign) { + size_t arg = identifierConstant(&name); + + if (canAssign && match(TOKEN_EQUAL)) { + expression(); + EMIT_CONSTANT_OP(OP_SET_GLOBAL, arg); + } else { + EMIT_CONSTANT_OP(OP_GET_GLOBAL, arg); + } +} + +static void variable(int canAssign) { + namedVariable(parser.previous, canAssign); +} + ParseRule rules[] = { [TOKEN_LEFT_PAREN] = {grouping, NULL, PREC_NONE}, [TOKEN_RIGHT_PAREN] = {NULL, NULL, PREC_NONE}, @@ -220,7 +325,7 @@ ParseRule rules[] = { [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_IDENTIFIER] = {variable, 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 */ @@ -256,12 +361,30 @@ static void parsePrecedence(Precedence precedence) { error("expect expression"); return; } - prefixRule(); + int canAssign = precedence <= PREC_ASSIGNMENT; + prefixRule(canAssign); while (precedence <= getRule(parser.current.type)->precedence) { advance(); ParseFn infixRule = getRule(parser.previous.type)->infix; - infixRule(); + infixRule(canAssign); } + + if (canAssign && match(TOKEN_EQUAL)) { + error("invalid assignment target"); + } +} + +static size_t identifierConstant(KrkToken * name) { + return krk_addConstant(currentChunk(), OBJECT_VAL(copyString(name->start, name->length))); +} + +static size_t parseVariable(const char * errorMessage) { + consume(TOKEN_IDENTIFIER, errorMessage); + return identifierConstant(&parser.previous); +} + +static void defineVariable(size_t global) { + EMIT_CONSTANT_OP(OP_DEFINE_GLOBAL, global); } static ParseRule * getRule(KrkTokenType type) { @@ -276,8 +399,10 @@ int krk_compile(const char * src, KrkChunk * chunk) { parser.panicMode = 0; advance(); - expression(); - consume(TOKEN_EOF, "Expected end of expression."); + + while (!match(TOKEN_EOF)) { + declaration(); + } endCompiler(); return !parser.hadError; diff --git a/debug.c b/debug.c index 11ebe89..21ed09c 100644 --- a/debug.c +++ b/debug.c @@ -11,6 +11,17 @@ void krk_disassembleChunk(KrkChunk * chunk, const char * name) { } #define SIMPLE(opc) case opc: fprintf(stderr, "%s\n", #opc); return offset + 1; +#define CONSTANT(opc) case opc: { size_t constant = chunk->code[offset + 1]; \ + fprintf(stderr, "%-16s %4d '", #opc, (int)constant); \ + krk_printValue(stderr, chunk->constants.values[constant]); \ + fprintf(stderr,"' (type=%s)\n", typeName(chunk->constants.values[constant])); \ + return offset + 2; } \ + case opc ## _LONG: { size_t constant = (chunk->code[offset + 1] << 16) | \ + (chunk->code[offset + 2] << 8) | (chunk->code[offset + 3]); \ + fprintf(stderr, "%-16s %4d '", #opc "_LONG", (int)constant); \ + krk_printValue(stderr, chunk->constants.values[constant]); \ + fprintf(stderr,"' (type=%s)\n", typeName(chunk->constants.values[constant])); \ + return offset + 4; } size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset) { fprintf(stderr, "%04u ", (unsigned int)offset); @@ -35,24 +46,12 @@ size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset) { 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; + SIMPLE(OP_PRINT) + SIMPLE(OP_POP) + CONSTANT(OP_DEFINE_GLOBAL) + CONSTANT(OP_CONSTANT) + CONSTANT(OP_GET_GLOBAL) + CONSTANT(OP_SET_GLOBAL) default: fprintf(stderr, "Unknown opcode: %02x\n", opcode); return offset + 1; diff --git a/kuroko.c b/kuroko.c index a196eed..01463b5 100644 --- a/kuroko.c +++ b/kuroko.c @@ -8,25 +8,7 @@ int main(int argc, char * argv[]) { krk_initVM(); - krk_interpret("(\"hello\" + \"hellf\" + 1.4) == \"hellohellf1.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_interpret("let breakfast = \"beignets\"\nlet beverage = \"café au lait\"\nbreakfast = \"beignets with \" + beverage\nprint breakfast"); krk_freeVM(); return 0; diff --git a/object.c b/object.c index 38cc194..6fa7076 100644 --- a/object.c +++ b/object.c @@ -61,7 +61,7 @@ KrkString * copyString(const char * chars, size_t length) { void krk_printObject(FILE * f, KrkValue value) { switch (OBJECT_TYPE(value)) { case OBJ_STRING: - fprintf(stderr, "%s", AS_CSTRING(value)); + fprintf(f, "%s", AS_CSTRING(value)); break; } } diff --git a/scanner.c b/scanner.c index bc61eb1..a4bc401 100644 --- a/scanner.c +++ b/scanner.c @@ -72,9 +72,6 @@ static void skipWhitespace() { case '\t': advance(); break; - case '\n': - scanner.line++; - scanner.startOfLine = 1; default: return; } @@ -229,6 +226,12 @@ KrkToken krk_scanToken() { char c = advance(); + if (c == '\n') { + scanner.line++; + scanner.startOfLine = 1; + return makeToken(TOKEN_EOL); + } + if (isAlpha(c)) return identifier(); if (isDigit(c)) return number(c); diff --git a/scanner.h b/scanner.h index 86be8de..039e33e 100644 --- a/scanner.h +++ b/scanner.h @@ -37,6 +37,7 @@ typedef enum { TOKEN_INDENTATION, TOKEN_RETRY, + TOKEN_EOL, TOKEN_ERROR, TOKEN_EOF, diff --git a/vm.c b/vm.c index 1f96e98..021ef38 100644 --- a/vm.c +++ b/vm.c @@ -53,10 +53,12 @@ KrkValue krk_peep(int distance) { void krk_initVM() { resetStack(); vm.objects = NULL; + krk_initTable(&vm.globals); krk_initTable(&vm.strings); } void krk_freeVM() { + krk_freeTable(&vm.globals); krk_freeTable(&vm.strings); krk_freeObjects(); FREE_ARRAY(size_t, vm.stack, vm.stackSize); @@ -158,9 +160,27 @@ static void addObjects() { #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; } +#define READ_CONSTANT(s) (vm.chunk->constants.values[readBytes(s)]) +#define READ_STRING(s) AS_STRING(READ_CONSTANT(s)) + +static size_t readBytes(int num) { + if (num == 1) return READ_BYTE(); + else if (num == 2) { + unsigned int top = READ_BYTE(); + unsigned int bot = READ_BYTE(); + return (top << 8) | (bot); + } else if (num == 3) { + unsigned int top = READ_BYTE(); + unsigned int mid = READ_BYTE(); + unsigned int bot = READ_BYTE(); + return (top << 16) | (mid << 8) | (bot); + } + return 0; +} + +static KrkValue run() { for (;;) { #ifdef DEBUG @@ -175,6 +195,11 @@ static KrkValue run() { #endif uint8_t opcode; switch ((opcode = READ_BYTE())) { + case OP_PRINT: { + krk_printValue(stdout, krk_pop()); + fprintf(stdout, "\n"); + break; + } case OP_RETURN: { krk_printValue(stdout, krk_pop()); fprintf(stdout, "\n"); @@ -201,17 +226,9 @@ static KrkValue run() { else { runtimeError("Incompatible operand type for prefix negation."); return NONE_VAL(); } break; } + case OP_CONSTANT_LONG: 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); + size_t index = readBytes(opcode == OP_CONSTANT ? 1 : 3); KrkValue constant = vm.chunk->constants.values[index]; krk_push(constant); break; @@ -220,6 +237,36 @@ static KrkValue run() { 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; + case OP_POP: krk_pop(); break; + case OP_DEFINE_GLOBAL_LONG: + case OP_DEFINE_GLOBAL: { + KrkString * name = READ_STRING((opcode == OP_DEFINE_GLOBAL ? 1 : 3)); + krk_tableSet(&vm.globals, OBJECT_VAL(name), krk_peep(0)); + krk_pop(); + break; + } + case OP_GET_GLOBAL_LONG: + case OP_GET_GLOBAL: { + KrkString * name = READ_STRING((opcode == OP_GET_GLOBAL ? 1 : 3)); + KrkValue value; + if (!krk_tableGet(&vm.globals, OBJECT_VAL(name), &value)) { + runtimeError("Undefined variable '%s'.", name->chars); + return NONE_VAL(); + } + krk_push(value); + break; + } + case OP_SET_GLOBAL_LONG: + case OP_SET_GLOBAL: { + KrkString * name = READ_STRING((opcode == OP_SET_GLOBAL ? 1 : 3)); + if (krk_tableSet(&vm.globals, OBJECT_VAL(name), krk_peep(0))) { + krk_tableDelete(&vm.globals, OBJECT_VAL(name)); + /* TODO: This should probably just work as an assignment? */ + runtimeError("Undefined variable '%s'.", name->chars); + return NONE_VAL(); + } + break; + } } } diff --git a/vm.h b/vm.h index 2564e54..5478ef6 100644 --- a/vm.h +++ b/vm.h @@ -11,6 +11,7 @@ typedef struct { size_t stackSize; KrkValue * stack; KrkValue * stackTop; + KrkTable globals; KrkTable strings; KrkObj * objects; } KrkVM;