implementing jumping back and forth (ch23)

This commit is contained in:
Patrick MARIE 2024-08-27 12:47:40 +02:00
parent 34a40813f7
commit 9de028d09c
9 changed files with 148 additions and 3 deletions

7
samples/ch23_if.lox Normal file
View File

@ -0,0 +1,7 @@
if (true) {
print "first is true!";
}
if (false) {
print "second if false!";
}

13
samples/ch23_if_else.lox Normal file
View File

@ -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!";
}

View File

@ -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";
}

View File

@ -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_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_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_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 => { else => {
debug.print("unknown opcode {d}\n", .{instruction}); debug.print("unknown opcode {d}\n", .{instruction});
return offset + 1; return offset + 1;

View File

@ -250,7 +250,7 @@ const Parser = struct {
TokenType.IDENTIFIER => ParserRule{ .prefix = variable, .infix = null, .precedence = Precedence.None }, TokenType.IDENTIFIER => ParserRule{ .prefix = variable, .infix = null, .precedence = Precedence.None },
TokenType.STRING => ParserRule{ .prefix = string, .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.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.CLASS => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.ELSE => 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 }, 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.FUN => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.IF => 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.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.PRINT => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.RETURN => 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 }, TokenType.SUPER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
@ -360,6 +360,8 @@ const Parser = struct {
fn statement(self: *Parser) ParsingError!void { fn statement(self: *Parser) ParsingError!void {
if (self.match(TokenType.PRINT)) { if (self.match(TokenType.PRINT)) {
try self.print_statement(); try self.print_statement();
} else if (self.match(TokenType.IF)) {
try self.if_statement();
} else if (self.match(TokenType.LEFT_BRACE)) { } else if (self.match(TokenType.LEFT_BRACE)) {
self.begin_scope(); self.begin_scope();
try self.block(); try self.block();
@ -552,6 +554,68 @@ const Parser = struct {
return ParsingError.NotFound; 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 { const Compiler = struct {

View File

@ -19,5 +19,7 @@ pub const OpCode = enum(u8) {
OP_NOT, OP_NOT,
OP_NEGATE, OP_NEGATE,
OP_PRINT, OP_PRINT,
OP_JUMP,
OP_JUMP_IF_FALSE,
OP_RETURN, OP_RETURN,
}; };

View File

@ -330,7 +330,7 @@ pub const Scanner = struct {
}, },
'i' => self.check_keyword(1, 1, "f", TokenType.IF), 'i' => self.check_keyword(1, 1, "f", TokenType.IF),
'n' => self.check_keyword(1, 2, "il", TokenType.NIL), '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), 'p' => self.check_keyword(1, 4, "rint", TokenType.PRINT),
'r' => self.check_keyword(1, 5, "eturn", TokenType.RETURN), 'r' => self.check_keyword(1, 5, "eturn", TokenType.RETURN),
's' => self.check_keyword(1, 4, "uper", TokenType.SUPER), 's' => self.check_keyword(1, 4, "uper", TokenType.SUPER),

View File

@ -34,6 +34,16 @@ pub fn byte_instruction(opcode_name: []const u8, chunk: Chunk, offset: usize) us
return offset + 2; 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 { pub fn compute_hash(str: []const u8) u32 {
var res_hash: u32 = 2166136261; var res_hash: u32 = 2166136261;

View File

@ -182,6 +182,16 @@ pub const VM = struct {
const slot = self.read_byte(); const slot = self.read_byte();
self.stack.items[slot] = self.peek(0); 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 => { else => {
debug.print("Invalid instruction: {d}\n", .{instruction}); debug.print("Invalid instruction: {d}\n", .{instruction});
return InterpretResult.RUNTIME_ERROR; return InterpretResult.RUNTIME_ERROR;
@ -201,6 +211,12 @@ pub const VM = struct {
return ret; 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 { pub fn read_constant(self: *VM) Value {
return self.chunk.?.constants.values[read_byte(self)]; return self.chunk.?.constants.values[read_byte(self)];
} }