diff --git a/clox/chunk.h b/clox/chunk.h index 5a6f6ce..eab0802 100644 --- a/clox/chunk.h +++ b/clox/chunk.h @@ -20,6 +20,9 @@ typedef enum { OP_LESS, OP_NEGATE, OP_PRINT, + OP_JUMP, + OP_JUMP_IF_FALSE, + OP_LOOP, OP_RETURN, OP_ADD, OP_SUBTRACT, diff --git a/clox/clox b/clox/clox index 7730a19..c10b385 100755 Binary files a/clox/clox and b/clox/clox differ diff --git a/clox/compiler.c b/clox/compiler.c index 5c94f2b..b0e8e76 100644 --- a/clox/compiler.c +++ b/clox/compiler.c @@ -122,6 +122,23 @@ static void emitBytes(uint8_t byte1, uint8_t byte2) { emitByte(byte2); } +static void emitLoop(int loopStart) { + emitByte(OP_LOOP); + + int offset = currentChunk()->count - loopStart + 2; + if (offset > UINT16_MAX) error("Loop body too large."); + + emitByte((offset >> 8) & 0xff); + emitByte(offset & 0xff); +} + +static int emitJump(uint8_t instruction) { + emitByte(instruction); + emitByte(0xff); + emitByte(0xff); + return currentChunk()->count - 2; +} + static uint8_t makeConstant(Value value) { int constant = addConstant(currentChunk(), value); if (constant > UINT8_MAX) { @@ -140,6 +157,18 @@ static void emitConstant(Value value) { emitBytes(OP_CONSTANT, makeConstant(value)); } +static void patchJump(int offset) { + // -2 to adjust for the bytecode for the jump offset itself. + int jump = currentChunk()->count - offset - 2; + + if (jump > UINT16_MAX) { + error("Too much code to jump over."); + } + + currentChunk()->code[offset] = (jump >> 8) & 0xff; + currentChunk()->code[offset + 1] = jump & 0xff; +} + static void initCompiler(Compiler* compiler) { compiler->localCount = 0; compiler->scopeDepth = 0; @@ -243,6 +272,15 @@ static void defineVariable(uint8_t global) { emitBytes(OP_DEFINE_GLOBAL, global); } +static void and_(bool canAssign) { + int endJump = emitJump(OP_JUMP_IF_FALSE); + + emitByte(OP_POP); + parsePrecedence(PREC_AND); + + patchJump(endJump); +} + static void binary(bool canAssign) { TokenType operatorType = parser.previous.type; ParseRule* rule = getRule(operatorType); @@ -305,12 +343,87 @@ static void expressionStatement() { emitByte(OP_POP); } +static void forStatement() { + beginScope(); + consume(TOKEN_LEFT_PAREN, "Expect '(' after 'for'."); + if (match(TOKEN_SEMICOLON)) { + // No initializer. + } else if (match(TOKEN_VAR)) { + varDeclaration(); + } else { + expressionStatement(); + } + + int loopStart = currentChunk()->count; + int exitJump = -1; + if (!match(TOKEN_SEMICOLON)) { + expression(); + consume(TOKEN_SEMICOLON, "Expect ';' after loop condition."); + + // Jump out of the loop if the condition is false. + exitJump = emitJump(OP_JUMP_IF_FALSE); + emitByte(OP_POP); // Condition. + } + if (!match(TOKEN_RIGHT_PAREN)) { + int bodyJump = emitJump(OP_JUMP); + int incrementStart = currentChunk()->count; + expression(); + emitByte(OP_POP); + consume(TOKEN_RIGHT_PAREN, "Expect ')' after for clauses."); + + emitLoop(loopStart); + loopStart = incrementStart; + patchJump(bodyJump); + } + statement(); + emitLoop(loopStart); + + if (exitJump != -1) { + patchJump(exitJump); + emitByte(OP_POP); // Condition. + } + + endScope(); +} + +static void ifStatement() { + consume(TOKEN_LEFT_PAREN, "Expect '(' after 'if'."); + expression(); + consume(TOKEN_RIGHT_PAREN, "Expect ')' after condition."); + + int thenJump = emitJump(OP_JUMP_IF_FALSE); + emitByte(OP_POP); + statement(); + + int elseJump = emitJump(OP_JUMP); + patchJump(thenJump); + emitByte(OP_POP); + + if (match(TOKEN_ELSE)) statement(); + patchJump(elseJump); +} + static void printStatement() { expression(); consume(TOKEN_SEMICOLON, "Expect ';' after value."); emitByte(OP_PRINT); } +static void whileStatement() { + int loopStart = currentChunk()->count; + consume(TOKEN_LEFT_PAREN, "Expect '(' after 'while'."); + expression(); + consume(TOKEN_RIGHT_PAREN, "Expect ')' after condition."); + + int exitJump = emitJump(OP_JUMP_IF_FALSE); + emitByte(OP_POP); + statement(); + emitLoop(loopStart); + + patchJump(exitJump); + emitByte(OP_POP); +} + static void synchronize() { parser.panicMode = false; @@ -349,6 +462,18 @@ static void statement() { if (match(TOKEN_PRINT)) { printStatement(); } + else if (match(TOKEN_FOR)) { + forStatement(); + + } + else if (match(TOKEN_IF)) { + ifStatement(); + + } + else if (match(TOKEN_WHILE)) { + whileStatement(); + + } else if (match(TOKEN_LEFT_BRACE)) { beginScope(); block(); @@ -369,6 +494,17 @@ static void number(bool canAssign) { emitConstant(NUMBER_VAL(value)); } +static void or_(bool canAssign) { + int elseJump = emitJump(OP_JUMP_IF_FALSE); + int endJump = emitJump(OP_JUMP); + + patchJump(elseJump); + emitByte(OP_POP); + + parsePrecedence(PREC_OR); + patchJump(endJump); +} + static void string(bool canAssign) { emitConstant(OBJ_VAL(copyString(parser.previous.start + 1, parser.previous.length - 2))); @@ -458,7 +594,7 @@ ParseRule rules[] = { [TOKEN_IDENTIFIER] = {variable, NULL, PREC_NONE}, [TOKEN_STRING] = {string, NULL, PREC_NONE}, [TOKEN_NUMBER] = {number, NULL, PREC_NONE}, - [TOKEN_AND] = {NULL, NULL, PREC_NONE}, + [TOKEN_AND] = {NULL, and_, PREC_AND}, [TOKEN_CLASS] = {NULL, NULL, PREC_NONE}, [TOKEN_ELSE] = {NULL, NULL, PREC_NONE}, [TOKEN_FALSE] = {literal, NULL, PREC_NONE}, @@ -466,7 +602,7 @@ ParseRule rules[] = { [TOKEN_FUN] = {NULL, NULL, PREC_NONE}, [TOKEN_IF] = {NULL, NULL, PREC_NONE}, [TOKEN_NIL] = {literal, NULL, PREC_NONE}, - [TOKEN_OR] = {NULL, NULL, PREC_NONE}, + [TOKEN_OR] = {NULL, or_, PREC_OR}, [TOKEN_PRINT] = {NULL, NULL, PREC_NONE}, [TOKEN_RETURN] = {NULL, NULL, PREC_NONE}, [TOKEN_SUPER] = {NULL, NULL, PREC_NONE}, diff --git a/clox/compiler.o b/clox/compiler.o index 1827e1e..e4cd0d0 100644 Binary files a/clox/compiler.o and b/clox/compiler.o differ diff --git a/clox/debug.c b/clox/debug.c index b144bfc..b4f6f73 100644 --- a/clox/debug.c +++ b/clox/debug.c @@ -29,6 +29,15 @@ static int byteInstruction(const char* name, Chunk* chunk, return offset + 2; } +static int jumpInstruction(const char* name, int sign, + Chunk* chunk, int offset) { + uint16_t jump = (uint16_t)(chunk->code[offset + 1] << 8); + jump |= chunk->code[offset + 2]; + printf("%-16s %4d -> %d\n", name, offset, + offset + 3 + sign * jump); + return offset + 3; +} + int disassembleInstruction(Chunk* chunk, int offset) { printf("%04d ", offset); @@ -81,6 +90,12 @@ int disassembleInstruction(Chunk* chunk, int offset) { return simpleInstruction("OP_NOT", offset); case OP_PRINT: return simpleInstruction("OP_PRINT", offset); + case OP_JUMP: + return jumpInstruction("OP_JUMP", 1, chunk, offset); + case OP_JUMP_IF_FALSE: + return jumpInstruction("OP_JUMP_IF_FALSE", 1, chunk, offset); + case OP_LOOP: + return jumpInstruction("OP_LOOP", -1, chunk, offset); case OP_RETURN: return simpleInstruction("OP_RETURN", offset); default: diff --git a/clox/debug.o b/clox/debug.o index 4ce44ec..0707a88 100644 Binary files a/clox/debug.o and b/clox/debug.o differ diff --git a/clox/vm.c b/clox/vm.c index 45d1c08..cea6b29 100644 --- a/clox/vm.c +++ b/clox/vm.c @@ -72,6 +72,8 @@ static void concatenate() { static InterpretResult run() { #define READ_BYTE() (*vm.ip++) #define READ_CONSTANT() (vm.chunk->constants.values[READ_BYTE()]) +#define READ_SHORT() \ + (vm.ip += 2, (uint16_t)((vm.ip[-2] << 8) | vm.ip[-1])) #define READ_STRING() AS_STRING(READ_CONSTANT()) #define BINARY_OP(valueType, op) \ do { \ @@ -183,6 +185,21 @@ static InterpretResult run() { printf("\n"); break; } + case OP_JUMP: { + uint16_t offset = READ_SHORT(); + vm.ip += offset; + break; + } + case OP_JUMP_IF_FALSE: { + uint16_t offset = READ_SHORT(); + if (isFalsey(peek(0))) vm.ip += offset; + break; + } + case OP_LOOP: { + uint16_t offset = READ_SHORT(); + vm.ip -= offset; + break; + } case OP_RETURN: { // printValue(pop()); // printf("\n"); @@ -191,10 +208,11 @@ static InterpretResult run() { } } +#undef READ_BYTE +#undef READ_SHORT #undef READ_CONSTANT #undef READ_STRING #undef BINARY_OP -#undef READ_BYTE } InterpretResult interpret(const char* source) { Chunk chunk; diff --git a/clox/vm.o b/clox/vm.o index 08ee2e5..2712f7b 100644 Binary files a/clox/vm.o and b/clox/vm.o differ