diff --git a/src/compiler.c b/src/compiler.c index 5e26d247..200b5eee 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -35,7 +35,9 @@ typedef enum TOKEN_BANGEQ, TOKEN_CLASS, + TOKEN_ELSE, TOKEN_FALSE, + TOKEN_IF, TOKEN_META, TOKEN_TRUE, TOKEN_VAR, @@ -149,7 +151,7 @@ static void storeVariable(Compiler* compiler, int symbol); static int internSymbol(Compiler* compiler); // Emits one bytecode instruction or argument. -static void emit(Compiler* compiler, Code code); +static int emit(Compiler* compiler, Code code); // Outputs a compile or syntax error. static void error(Compiler* compiler, const char* format, ...); @@ -253,7 +255,9 @@ ParseRule rules[] = /* TOKEN_EQEQ */ INFIX_OPERATOR(PREC_EQUALITY, "== "), /* TOKEN_BANGEQ */ INFIX_OPERATOR(PREC_EQUALITY, "!= "), /* TOKEN_CLASS */ UNUSED, + /* TOKEN_ELSE */ UNUSED, /* TOKEN_FALSE */ PREFIX(boolean), + /* TOKEN_IF */ UNUSED, /* TOKEN_META */ UNUSED, /* TOKEN_TRUE */ PREFIX(boolean), /* TOKEN_VAR */ UNUSED, @@ -385,9 +389,10 @@ int internSymbol(Compiler* compiler) compiler->parser->previous.end - compiler->parser->previous.start); } -void emit(Compiler* compiler, Code code) +int emit(Compiler* compiler, Code code) { compiler->block->bytecode[compiler->numCodes++] = code; + return compiler->numCodes - 1; } void error(Compiler* compiler, const char* format, ...) @@ -469,6 +474,48 @@ void statement(Compiler* compiler) void expression(Compiler* compiler) { + if (match(compiler, TOKEN_IF)) + { + // Compile the condition. + consume(compiler, TOKEN_LEFT_PAREN); + expression(compiler); + consume(compiler, TOKEN_RIGHT_PAREN); + + // TODO(bob): Block bodies. + // Compile the then branch. + emit(compiler, CODE_JUMP_IF); + + // Emit a placeholder. We'll patch it when we know what to jump to. + int ifJump = emit(compiler, 255); + + expression(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->block->bytecode[ifJump] = compiler->numCodes - ifJump - 1; + + // Compile the else branch if there is one. + if (match(compiler, TOKEN_ELSE)) + { + // TODO(bob): Block bodies. + expression(compiler); + } + else + { + // Just default to null. + emit(compiler, CODE_NULL); + } + + // Patch the jump over the else. + compiler->block->bytecode[elseJump] = compiler->numCodes - elseJump - 1; + return; + } + return parsePrecedence(compiler, PREC_LOWEST); } @@ -751,6 +798,8 @@ void nextToken(Parser* parser) case TOKEN_EQEQ: case TOKEN_BANGEQ: case TOKEN_CLASS: + case TOKEN_ELSE: + case TOKEN_IF: case TOKEN_META: case TOKEN_VAR: parser->skipNewlines = 1; @@ -899,7 +948,9 @@ void readName(Parser* parser) TokenType type = TOKEN_NAME; if (isKeyword(parser, "class")) type = TOKEN_CLASS; + if (isKeyword(parser, "if")) type = TOKEN_IF; if (isKeyword(parser, "false")) type = TOKEN_FALSE; + if (isKeyword(parser, "else")) type = TOKEN_ELSE; if (isKeyword(parser, "meta")) type = TOKEN_META; if (isKeyword(parser, "true")) type = TOKEN_TRUE; if (isKeyword(parser, "var")) type = TOKEN_VAR; diff --git a/src/primitives.c b/src/primitives.c index 6077c941..f2db2ee4 100644 --- a/src/primitives.c +++ b/src/primitives.c @@ -227,6 +227,8 @@ void registerPrimitives(VM* vm) vm->classClass = makeClass(); + vm->nullClass = makeClass(); + vm->numClass = makeClass(); PRIMITIVE(vm->numClass, "abs", num_abs); PRIMITIVE(vm->numClass, "toString", num_toString) @@ -257,4 +259,4 @@ void registerPrimitives(VM* vm) // TODO(bob): Make this a distinct object type. vm->unsupported = (Value)makeInstance(unsupportedClass); -} \ No newline at end of file +} diff --git a/src/vm.c b/src/vm.c index 0b000220..d801437d 100644 --- a/src/vm.c +++ b/src/vm.c @@ -81,6 +81,14 @@ ObjInstance* makeInstance(ObjClass* classObj) return instance; } +Value makeNull() +{ + Obj* obj = malloc(sizeof(Obj)); + obj->type = OBJ_NULL; + obj->flags = 0; + return obj; +} + ObjNum* makeNum(double number) { ObjNum* num = malloc(sizeof(ObjNum)); @@ -157,6 +165,147 @@ const char* getSymbolName(SymbolTable* symbols, int symbol) return symbols->names[symbol]; } +// TODO(bob): For debugging. Move to separate module. +/* +void dumpCode(ObjBlock* block) +{ + unsigned char* bytecode = block->bytecode; + int done = 0; + int i = 0; + while (!done) + { + printf("%04d ", i); + unsigned char code = bytecode[i++]; + + switch (code) + { + case CODE_CONSTANT: + { + int constant = bytecode[i++]; + printf("CONSTANT %d (", constant); + printValue(block->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_METHOD: + { + int symbol = bytecode[i++]; + int constant = bytecode[i++]; + printf("METHOD symbol %d constant %d\n", symbol, constant); + 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 %d\n", global); + printf("%04d (global %d)\n", i - 1, global); + break; + } + + case CODE_STORE_GLOBAL: + { + int global = bytecode[i++]; + printf("STORE_GLOBAL %d\n", 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 %d\n", numArgs, 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_END: + { + printf("CODE_END\n"); + done = 1; + break; + } + + default: + printf("[%d]\n", bytecode[i - 1]); + break; + } + } +} +*/ + Value interpret(VM* vm, ObjBlock* block) { Fiber fiber; @@ -179,6 +328,10 @@ Value interpret(VM* vm, ObjBlock* block) break; } + case CODE_NULL: + push(&fiber, makeNull()); + break; + case CODE_FALSE: push(&fiber, makeBool(0)); break; @@ -284,6 +437,10 @@ Value interpret(VM* vm, ObjBlock* block) classObj = vm->boolClass; break; + case OBJ_NULL: + classObj = vm->nullClass; + break; + case OBJ_NUM: classObj = vm->numClass; break; @@ -333,6 +490,26 @@ Value interpret(VM* vm, ObjBlock* block) break; } + case CODE_JUMP: + { + int offset = frame->block->bytecode[frame->ip++]; + frame->ip += offset; + break; + } + + case CODE_JUMP_IF: + { + int offset = frame->block->bytecode[frame->ip++]; + Value condition = pop(&fiber); + + // False is the only falsey value. + if (condition->type == OBJ_FALSE) + { + frame->ip += offset; + } + break; + } + case CODE_END: { Value result = pop(&fiber); @@ -379,18 +556,22 @@ void printValue(Value value) printf("[block]"); break; - case OBJ_FALSE: - printf("false"); - break; - case OBJ_CLASS: printf("[class]"); break; + case OBJ_FALSE: + printf("false"); + break; + case OBJ_INSTANCE: printf("[instance]"); break; + case OBJ_NULL: + printf("null"); + break; + case OBJ_NUM: printf("%g", ((ObjNum*)value)->value); break; diff --git a/src/vm.h b/src/vm.h index d4febd0e..4cb4d801 100644 --- a/src/vm.h +++ b/src/vm.h @@ -23,6 +23,7 @@ typedef enum { OBJ_CLASS, OBJ_FALSE, OBJ_INSTANCE, + OBJ_NULL, OBJ_NUM, OBJ_STRING, OBJ_TRUE @@ -105,6 +106,9 @@ typedef enum // Load the constant at index [arg]. CODE_CONSTANT, + // Push null onto the stack. + CODE_NULL, + // Push false onto the stack. CODE_FALSE, @@ -149,7 +153,13 @@ typedef enum CODE_CALL_8, CODE_CALL_9, CODE_CALL_10, - + + // Jump the instruction pointer [arg1] forward. + CODE_JUMP, + + // Pop and if not truthy then jump the instruction pointer [arg1] forward. + CODE_JUMP_IF, + // The current block is done and should be exited. CODE_END } Code; @@ -168,6 +178,7 @@ struct sVM ObjClass* blockClass; ObjClass* boolClass; ObjClass* classClass; + ObjClass* nullClass; ObjClass* numClass; ObjClass* stringClass; @@ -218,6 +229,9 @@ ObjClass* makeClass(); // Creates a new instance of the given [classObj]. ObjInstance* makeInstance(ObjClass* classObj); +// Creates a new null object. +Value makeNull(); + // Creates a new number object. ObjNum* makeNum(double number); diff --git a/test/block_syntax.wren b/test/block_syntax.wren new file mode 100644 index 00000000..872ad9e8 --- /dev/null +++ b/test/block_syntax.wren @@ -0,0 +1,31 @@ +// Single line. +{ io.write("ok") }.call // expect: ok + +// No trailing newline. +{ + io.write("ok") }.call // expect: ok + +// Trailing newline. +{ + io.write("ok") // expect: ok +}.call + +// Multiple expressions. +{ + io.write("1") // expect: 1 + io.write("2") // expect: 2 +}.call + +// Extra newlines. +{ + + + io.write("1") // expect: 1 + + + io.write("2") // expect: 2 + + +}.call + +// TODO(bob): Arguments. diff --git a/test/if_syntax.wren b/test/if_syntax.wren new file mode 100644 index 00000000..fd8df69b --- /dev/null +++ b/test/if_syntax.wren @@ -0,0 +1,16 @@ +// Evaluate the 'then' expression if the condition is true. +if (true) io.write("good") // expect: good +if (false) io.write("bad") + +// Evaluate the 'else' expression if the condition is false. +if (true) io.write("good") else io.write("bad") // expect: good +if (false) io.write("bad") else io.write("good") // expect: good + +// Return the 'then' expression if the condition is true. +io.write(if (true) "good") // expect: good + +// Return null if the condition is false and there is no else. +io.write(if (false) "bad") // expect: null + +// Return the 'else' expression if the condition is false. +io.write(if (false) "bad" else "good") // expect: good