diff --git a/src/compiler.c b/src/compiler.c index 85bf65ce..a49e2bfd 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -46,6 +46,7 @@ typedef enum TOKEN_THIS, TOKEN_TRUE, TOKEN_VAR, + TOKEN_WHILE, TOKEN_NAME, TOKEN_NUMBER, @@ -296,6 +297,7 @@ static void readName(Parser* parser) if (isKeyword(parser, "this")) type = TOKEN_THIS; if (isKeyword(parser, "true")) type = TOKEN_TRUE; if (isKeyword(parser, "var")) type = TOKEN_VAR; + if (isKeyword(parser, "while")) type = TOKEN_WHILE; makeToken(parser, type); } @@ -489,6 +491,7 @@ static void nextToken(Parser* parser) case TOKEN_IS: case TOKEN_STATIC: case TOKEN_VAR: + case TOKEN_WHILE: parser->skipNewlines = 1; // Emit this token. @@ -1032,6 +1035,7 @@ GrammarRule rules[] = /* TOKEN_THIS */ PREFIX(this_), /* TOKEN_TRUE */ PREFIX(boolean), /* TOKEN_VAR */ UNUSED, + /* TOKEN_WHILE */ UNUSED, /* TOKEN_NAME */ { name, NULL, parameterList, PREC_NONE, NULL }, /* TOKEN_NUMBER */ PREFIX(number), /* TOKEN_STRING */ PREFIX(string), @@ -1079,6 +1083,13 @@ void assignment(Compiler* compiler) expression(compiler, 1); } +// Replaces the placeholder argument for a previous CODE_JUMP or CODE_JUMP_IF +// instruction with an offset that jumps to the current end of bytecode. +static void patchJump(Compiler* compiler, int offset) +{ + compiler->fn->bytecode[offset] = compiler->numCodes - offset - 1; +} + // Parses a "statement": any expression including expressions like variable // declarations which can only appear at the top level of a block. void statement(Compiler* compiler) @@ -1091,22 +1102,18 @@ void statement(Compiler* compiler) assignment(compiler); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after if condition."); - // Compile the then branch. + // Jump to the else branch if the condition is false. emit(compiler, CODE_JUMP_IF); - - // Emit a placeholder. We'll patch it when we know what to jump to. int ifJump = emit(compiler, 255); + // Compile the then branch. statement(compiler); // Jump over the else branch when the if branch is taken. emit(compiler, CODE_JUMP); - - // Emit a placeholder. We'll patch it when we know what to jump to. int elseJump = emit(compiler, 255); - // Patch the jump. - compiler->fn->bytecode[ifJump] = compiler->numCodes - ifJump - 1; + patchJump(compiler, ifJump); // Compile the else branch if there is one. if (match(compiler, TOKEN_ELSE)) @@ -1120,7 +1127,36 @@ void statement(Compiler* compiler) } // Patch the jump over the else. - compiler->fn->bytecode[elseJump] = compiler->numCodes - elseJump - 1; + patchJump(compiler, elseJump); + return; + } + + if (match(compiler, TOKEN_WHILE)) + { + // Remember what instruction to loop back to. + int loopStart = compiler->numCodes - 1; + + // Compile the condition. + consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'while'."); + assignment(compiler); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after while condition."); + + emit(compiler, CODE_JUMP_IF); + int exitJump = emit(compiler, 255); + + // Compile the body. + statement(compiler); + + // Loop back to the top. + emit(compiler, CODE_LOOP); + int loopOffset = compiler->numCodes - loopStart; + emit(compiler, loopOffset); + + patchJump(compiler, exitJump); + + // A while loop always evaluates to null. + // TODO(bob): Is there a more useful value it could return? + emit(compiler, CODE_NULL); return; } diff --git a/src/vm.c b/src/vm.c index d62f1b76..8e2ce41b 100644 --- a/src/vm.c +++ b/src/vm.c @@ -418,159 +418,176 @@ Value findGlobal(VM* vm, const char* name) return vm->globals[symbol]; } -/* +int dumpInstruction(VM* vm, ObjFn* fn, int i) +{ + int start = i; + unsigned char* bytecode = fn->bytecode; + unsigned char code = bytecode[i++]; + + printf("%04d ", i); + + switch (code) + { + case CODE_CONSTANT: + { + int constant = bytecode[i++]; + printf("CONSTANT "); + printValue(fn->constants[constant]); + printf("\n"); + printf("%04d | constant %d\n", i, constant); + break; + } + + case CODE_NULL: + printf("NULL\n"); + break; + + case CODE_FALSE: + printf("FALSE\n"); + break; + + case CODE_TRUE: + printf("TRUE\n"); + break; + + case CODE_CLASS: + printf("CLASS\n"); + break; + + case CODE_SUBCLASS: + printf("SUBCLASS\n"); + break; + + case CODE_METACLASS: + printf("METACLASS\n"); + break; + + case CODE_METHOD: + { + int symbol = bytecode[i++]; + int constant = bytecode[i++]; + printf("METHOD \"%s\"\n", getSymbolName(&vm->methods, symbol)); + printf("%04d | symbol %d\n", i - 1, symbol); + printf("%04d | constant %d\n", i, constant); + break; + } + + case CODE_LOAD_LOCAL: + { + int local = bytecode[i++]; + printf("LOAD_LOCAL %d\n", local); + printf("%04d | local %d\n", i, local); + break; + } + + case CODE_STORE_LOCAL: + { + int local = bytecode[i++]; + printf("STORE_LOCAL %d\n", local); + printf("%04d | local %d\n", i, local); + break; + } + + case CODE_LOAD_GLOBAL: + { + int global = bytecode[i++]; + printf("LOAD_GLOBAL \"%s\"\n", + getSymbolName(&vm->globalSymbols, global)); + printf("%04d | global %d\n", i, global); + break; + } + + case CODE_STORE_GLOBAL: + { + int global = bytecode[i++]; + printf("STORE_GLOBAL \"%s\"\n", + getSymbolName(&vm->globalSymbols, global)); + printf("%04d | global %d\n", i, global); + break; + } + + case CODE_DUP: + printf("DUP\n"); + break; + + case CODE_POP: + printf("POP\n"); + break; + + case CODE_CALL_0: + case CODE_CALL_1: + case CODE_CALL_2: + case CODE_CALL_3: + case CODE_CALL_4: + case CODE_CALL_5: + case CODE_CALL_6: + case CODE_CALL_7: + case CODE_CALL_8: + case CODE_CALL_9: + case CODE_CALL_10: + { + // Add one for the implicit receiver argument. + int numArgs = bytecode[i - 1] - CODE_CALL_0; + int symbol = bytecode[i++]; + printf("CALL_%d \"%s\"\n", numArgs, + getSymbolName(&vm->methods, symbol)); + printf("%04d | symbol %d\n", i, symbol); + break; + } + + case CODE_JUMP: + { + int offset = bytecode[i++]; + printf("JUMP %d\n", offset); + printf("%04d | offset %d\n", i, offset); + break; + } + + case CODE_LOOP: + { + int offset = bytecode[i++]; + printf("LOOP %d\n", offset); + printf("%04d | offset -%d\n", i, offset); + break; + } + + case CODE_JUMP_IF: + { + int offset = bytecode[i++]; + printf("JUMP_IF %d\n", offset); + printf("%04d | offset %d\n", i, offset); + break; + } + + case CODE_IS: + printf("CODE_IS\n"); + break; + + case CODE_END: + printf("CODE_END\n"); + break; + + default: + printf("UKNOWN! [%d]\n", bytecode[i - 1]); + break; + } + + // Return how many bytes this instruction takes, or -1 if it's an END. + if (code == CODE_END) return -1; + return i - start; +} + // TODO(bob): For debugging. Move to separate module. void dumpCode(VM* vm, ObjFn* fn) { - unsigned char* bytecode = fn->bytecode; - int done = 0; int i = 0; - while (!done) + for (;;) { - printf("%04d ", i); - unsigned char code = bytecode[i++]; - - switch (code) - { - case CODE_CONSTANT: - { - int constant = bytecode[i++]; - printf("CONSTANT "); - printValue(fn->constants[constant]); - printf("\n"); - printf("%04d | constant %d\n", i - 1, constant); - break; - } - - case CODE_NULL: - printf("NULL\n"); - break; - - case CODE_FALSE: - printf("FALSE\n"); - break; - - case CODE_TRUE: - printf("TRUE\n"); - break; - - case CODE_CLASS: - printf("CLASS\n"); - break; - - case CODE_SUBCLASS: - printf("SUBCLASS\n"); - break; - - case CODE_METACLASS: - printf("METACLASS\n"); - break; - - case CODE_METHOD: - { - int symbol = bytecode[i++]; - int constant = bytecode[i++]; - printf("METHOD \"%s\"\n", getSymbolName(&vm->methods, symbol)); - printf("%04d | symbol %d\n", i - 2, symbol); - printf("%04d | constant %d\n", i - 1, constant); - break; - } - - case CODE_LOAD_LOCAL: - { - int local = bytecode[i++]; - printf("LOAD_LOCAL %d\n", local); - printf("%04d | local %d\n", i - 1, local); - break; - } - - case CODE_STORE_LOCAL: - { - int local = bytecode[i++]; - printf("STORE_LOCAL %d\n", local); - printf("%04d | local %d\n", i - 1, local); - break; - } - - case CODE_LOAD_GLOBAL: - { - int global = bytecode[i++]; - printf("LOAD_GLOBAL \"%s\"\n", - getSymbolName(&vm->globalSymbols, global)); - printf("%04d | global %d\n", i - 1, global); - break; - } - - case CODE_STORE_GLOBAL: - { - int global = bytecode[i++]; - printf("STORE_GLOBAL \"%s\"\n", - getSymbolName(&vm->globalSymbols, global)); - printf("%04d | global %d\n", i - 1, global); - break; - } - - case CODE_DUP: - printf("DUP\n"); - break; - - case CODE_POP: - printf("POP\n"); - break; - - case CODE_CALL_0: - case CODE_CALL_1: - case CODE_CALL_2: - case CODE_CALL_3: - case CODE_CALL_4: - case CODE_CALL_5: - case CODE_CALL_6: - case CODE_CALL_7: - case CODE_CALL_8: - case CODE_CALL_9: - case CODE_CALL_10: - { - // Add one for the implicit receiver argument. - int numArgs = bytecode[i - 1] - CODE_CALL_0; - int symbol = bytecode[i++]; - printf("CALL_%d \"%s\"\n", numArgs, - getSymbolName(&vm->methods, symbol)); - printf("%04d | symbol %d\n", i - 1, symbol); - break; - } - - case CODE_JUMP: - { - int offset = bytecode[i++]; - printf("JUMP %d\n", offset); - printf("%04d | offset %d\n", i - 1, offset); - break; - } - - case CODE_JUMP_IF: - { - int offset = bytecode[i++]; - printf("JUMP_IF %d\n", offset); - printf("%04d | offset %d\n", i - 1, offset); - break; - } - - case CODE_IS: - printf("CODE_IS\n"); - break; - - case CODE_END: - printf("CODE_END\n"); - done = 1; - break; - - default: - printf("[%d]\n", bytecode[i - 1]); - break; - } + int offset = dumpInstruction(vm, fn, i); + if (offset == -1) break; + i += offset; } } -*/ // Returns the class of [object]. static ObjClass* getClass(VM* vm, Value value) @@ -619,14 +636,23 @@ static ObjClass* getClass(VM* vm, Value value) #endif } +void dumpStack(Fiber* fiber) +{ + printf(":: "); + for (int i = 0; i < fiber->stackSize; i++) + { + printValue(fiber->stack[i]); + printf(" | "); + } + printf("\n"); +} + Value interpret(VM* vm, ObjFn* fn) { Fiber* fiber = vm->fiber; - callFunction(fiber, fn, 0); // These macros are designed to only be invoked within this function. - // TODO(bob): Check for stack overflow. #define PUSH(value) (fiber->stack[fiber->stackSize++] = value) #define POP() (fiber->stack[--fiber->stackSize]) @@ -813,9 +839,22 @@ Value interpret(VM* vm, ObjFn* fn) break; } - case CODE_JUMP:{ + case CODE_JUMP: + { int offset = READ_ARG(); - ip+= offset; + ip += offset; + break; + } + + case CODE_LOOP: + { + // The loop body's result is on the top of the stack. Since we are + // looping and running the body again, discard it. + POP(); + + // Jump back to the top of the loop. + int offset = READ_ARG(); + ip -= offset; break; } diff --git a/src/vm.h b/src/vm.h index f5118244..80caac3c 100644 --- a/src/vm.h +++ b/src/vm.h @@ -1,6 +1,7 @@ #ifndef wren_vm_h #define wren_vm_h +#include "common.h" #include "value.h" // TODO(bob): Make these externally controllable. @@ -71,6 +72,10 @@ typedef enum // Jump the instruction pointer [arg1] forward. CODE_JUMP, + // Jump the instruction pointer [arg1] backward. Pop and discard the top of + // the stack. + CODE_LOOP, + // Pop and if not truthy then jump the instruction pointer [arg1] forward. CODE_JUMP_IF, diff --git a/test/if_syntax.wren b/test/if.wren similarity index 87% rename from test/if_syntax.wren rename to test/if.wren index b77f8aed..7669350a 100644 --- a/test/if_syntax.wren +++ b/test/if.wren @@ -28,3 +28,11 @@ io.write(c) // expect: good // Assignment in if condition. if (a = true) io.write(a) // expect: true + +// Newline after "if". +if +(true) io.write("good") // expect: good + +// Newline after "else". +if (false) io.write("bad") else +io.write("good") // expect: good diff --git a/test/while.wren b/test/while.wren new file mode 100644 index 00000000..283a5095 --- /dev/null +++ b/test/while.wren @@ -0,0 +1,31 @@ +// Single-expression body. +var c = 0 +while (c < 3) io.write(c = c + 1) +// expect: 1 +// expect: 2 +// expect: 3 + +// Block body. +var a = 0 +while (a < 3) { + io.write(a) + a = a + 1 +} +// expect: 0 +// expect: 1 +// expect: 2 + +// Newline after "while". +var d = 0 +while +(d < 3) io.write(d = d + 1) +// expect: 1 +// expect: 2 +// expect: 3 + +// Result is null. +var e = 0 +var f = while (e < 3) { + e = e + 1 +} +io.write(f) // expect: null