diff --git a/samples/ch23_if.lox b/samples/ch23_if.lox new file mode 100644 index 0000000..f9799db --- /dev/null +++ b/samples/ch23_if.lox @@ -0,0 +1,7 @@ +if (true) { + print "first is true!"; +} + +if (false) { + print "second if false!"; +} \ No newline at end of file diff --git a/samples/ch23_if_else.lox b/samples/ch23_if_else.lox new file mode 100644 index 0000000..c7928e9 --- /dev/null +++ b/samples/ch23_if_else.lox @@ -0,0 +1,13 @@ +var a = 1; + +if (a == 1) { + print "first is true!"; +} else { + print "first is false!"; +} + +if (a == 2) { + print "second is false!"; +} else { + print "second is false!"; +} \ No newline at end of file diff --git a/samples/ch23_if_else_logical_operators.lox b/samples/ch23_if_else_logical_operators.lox new file mode 100644 index 0000000..f20fd52 --- /dev/null +++ b/samples/ch23_if_else_logical_operators.lox @@ -0,0 +1,31 @@ +print "and..."; + +if (true and true) { + print "should show"; +} + +if (true and false) { + print "should not show"; +} + +if (false and true) { + print "should not show"; +} + +if (false and false) { + print "should not show"; +} + +print "or..."; +if (true or true) { + print "should show"; +} +if (true or false) { + print "should show"; +} +if (false or true) { + print "should show"; +} +if (false or false) { + print "should show"; +} \ No newline at end of file diff --git a/src/chunk.zig b/src/chunk.zig index f3aa25f..edde36e 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -99,6 +99,8 @@ pub const Chunk = struct { @intFromEnum(OpCode.OP_SET_GLOBAL) => return utils.constant_instruction("OP_SET_GLOBAL", self, offset), @intFromEnum(OpCode.OP_GET_LOCAL) => return utils.byte_instruction("OP_GET_LOCAL", self, offset), @intFromEnum(OpCode.OP_SET_LOCAL) => return utils.byte_instruction("OP_SET_LOCAL", self, offset), + @intFromEnum(OpCode.OP_JUMP) => return utils.jump_instruction("OP_JUMP", 1, self, offset), + @intFromEnum(OpCode.OP_JUMP_IF_FALSE) => return utils.jump_instruction("OP_JUMP_IF_FALSE", 1, self, offset), else => { debug.print("unknown opcode {d}\n", .{instruction}); return offset + 1; diff --git a/src/compile.zig b/src/compile.zig index e6868ea..4dcfa81 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -250,7 +250,7 @@ const Parser = struct { TokenType.IDENTIFIER => ParserRule{ .prefix = variable, .infix = null, .precedence = Precedence.None }, TokenType.STRING => ParserRule{ .prefix = string, .infix = null, .precedence = Precedence.None }, TokenType.NUMBER => ParserRule{ .prefix = number, .infix = null, .precedence = Precedence.None }, - TokenType.AND => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, + TokenType.AND => ParserRule{ .prefix = null, .infix = and_, .precedence = Precedence.And }, TokenType.CLASS => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.ELSE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.FALSE => ParserRule{ .prefix = literal, .infix = null, .precedence = Precedence.None }, @@ -258,7 +258,7 @@ const Parser = struct { TokenType.FUN => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.IF => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.NIL => ParserRule{ .prefix = literal, .infix = null, .precedence = Precedence.None }, - TokenType.OR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, + TokenType.OR => ParserRule{ .prefix = null, .infix = or_, .precedence = Precedence.Or }, TokenType.PRINT => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.RETURN => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.SUPER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, @@ -360,6 +360,8 @@ const Parser = struct { fn statement(self: *Parser) ParsingError!void { if (self.match(TokenType.PRINT)) { try self.print_statement(); + } else if (self.match(TokenType.IF)) { + try self.if_statement(); } else if (self.match(TokenType.LEFT_BRACE)) { self.begin_scope(); try self.block(); @@ -552,6 +554,68 @@ const Parser = struct { return ParsingError.NotFound; } + + fn if_statement(self: *Parser) !void { + self.consume(TokenType.LEFT_PAREN, "Expect '(' after 'if'."); + try self.expression(); + self.consume(TokenType.RIGHT_PAREN, "Expect ')' after condition."); + + const then_jump = try self.emit_jump(@intFromEnum(OpCode.OP_JUMP_IF_FALSE)); + try self.emit_byte(@intFromEnum(OpCode.OP_POP)); + try self.statement(); + const else_jump = try self.emit_jump(@intFromEnum(OpCode.OP_JUMP)); + + self.patch_jump(then_jump); + try self.emit_byte(@intFromEnum(OpCode.OP_POP)); + + if (self.match(TokenType.ELSE)) { + try self.statement(); + } + self.patch_jump(else_jump); + } + + fn emit_jump(self: *Parser, instruction: u8) ParsingError!usize { + try self.emit_byte(instruction); + try self.emit_byte(0xff); + try self.emit_byte(0xff); + + return self.chunk.count - 2; + } + + fn patch_jump(self: *Parser, offset: usize) void { + const jump = self.chunk.count - offset - 2; + + if (jump > 65535) { + self.error_msg("Too much code to jump over."); + } + + const b1 = (jump >> 8) & 0xff; + const b0 = jump & 0xff; + + self.chunk.code[offset] = @intCast(b1); + self.chunk.code[offset + 1] = @intCast(b0); + } + + fn and_(self: *Parser, can_assign: bool) ParsingError!void { + _ = can_assign; + const end_jump = try self.emit_jump(@intFromEnum(OpCode.OP_JUMP_IF_FALSE)); + try self.emit_byte(@intFromEnum(OpCode.OP_POP)); + + try self.parse_precedence(Precedence.And); + self.patch_jump(end_jump); + } + + fn or_(self: *Parser, can_assign: bool) ParsingError!void { + _ = can_assign; + const else_jump = try self.emit_jump(@intFromEnum(OpCode.OP_JUMP_IF_FALSE)); + const end_jump = try self.emit_jump(@intFromEnum(OpCode.OP_JUMP)); + + self.patch_jump(else_jump); + try self.emit_byte(@intFromEnum(OpCode.OP_POP)); + + try self.parse_precedence(Precedence.Or); + self.patch_jump(end_jump); + } }; const Compiler = struct { diff --git a/src/opcode.zig b/src/opcode.zig index 5d5a2d5..855a11f 100644 --- a/src/opcode.zig +++ b/src/opcode.zig @@ -19,5 +19,7 @@ pub const OpCode = enum(u8) { OP_NOT, OP_NEGATE, OP_PRINT, + OP_JUMP, + OP_JUMP_IF_FALSE, OP_RETURN, }; diff --git a/src/scanner.zig b/src/scanner.zig index a0d4070..581df37 100644 --- a/src/scanner.zig +++ b/src/scanner.zig @@ -330,7 +330,7 @@ pub const Scanner = struct { }, 'i' => self.check_keyword(1, 1, "f", TokenType.IF), 'n' => self.check_keyword(1, 2, "il", TokenType.NIL), - 'o' => self.check_keyword(1, 1, "or", TokenType.OR), + 'o' => self.check_keyword(1, 1, "r", TokenType.OR), 'p' => self.check_keyword(1, 4, "rint", TokenType.PRINT), 'r' => self.check_keyword(1, 5, "eturn", TokenType.RETURN), 's' => self.check_keyword(1, 4, "uper", TokenType.SUPER), diff --git a/src/utils.zig b/src/utils.zig index f6d5901..0c08f57 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -34,6 +34,16 @@ pub fn byte_instruction(opcode_name: []const u8, chunk: Chunk, offset: usize) us return offset + 2; } +pub fn jump_instruction(opcode_name: []const u8, sign: i32, chunk: Chunk, offset: usize) usize { + var jump: u16 = @as(u16, chunk.code[offset + 1]) << 8; + jump |= chunk.code[offset + 2]; + + const address: i32 = @as(i32, @intCast(offset)) + 3 + sign * jump; + + debug.print("{s:16} {d:4} -> {}", .{ opcode_name, offset, address }); + return offset + 3; +} + pub fn compute_hash(str: []const u8) u32 { var res_hash: u32 = 2166136261; diff --git a/src/vm.zig b/src/vm.zig index 5ab7a4c..b4871dc 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -182,6 +182,16 @@ pub const VM = struct { const slot = self.read_byte(); self.stack.items[slot] = self.peek(0); }, + @intFromEnum(OpCode.OP_JUMP) => { + const offset = self.read_short(); + self.ip.? += offset; + }, + @intFromEnum(OpCode.OP_JUMP_IF_FALSE) => { + const offset = self.read_short(); + if (self.peek(0).is_falsey()) { + self.ip.? += offset; + } + }, else => { debug.print("Invalid instruction: {d}\n", .{instruction}); return InterpretResult.RUNTIME_ERROR; @@ -201,6 +211,12 @@ pub const VM = struct { return ret; } + pub fn read_short(self: *VM) u16 { + self.ip.? += 2; + + return (@as(u16, self.chunk.?.code[self.ip.? - 2]) << 8) | (@as(u16, self.chunk.?.code[self.ip.? - 1])); + } + pub fn read_constant(self: *VM) Value { return self.chunk.?.constants.values[read_byte(self)]; }