implementing types of values (ch18)

This commit is contained in:
Patrick MARIE 2024-08-25 16:23:16 +02:00
parent f5a60501c9
commit 58cf9e15c6
7 changed files with 242 additions and 42 deletions

View File

@ -60,6 +60,44 @@ zig build run
[ 18 ] [ 18 ]
000b 2 OP_RETURN 000b 2 OP_RETURN
18 18
> > !(5 - 4 > 3 * 2 == !nil)
== code ==
0000 1 OP_CONSTANT 0 '5'
0002 | OP_CONSTANT 1 '4'
0004 | OP_SUBSTRACT
0005 | OP_CONSTANT 2 '3'
0007 | OP_CONSTANT 3 '2'
0009 | OP_MULTIPLY
000a | OP_GREATER
000b | OP_NIL
000c | OP_NOT
000d | OP_EQUAL
000e | OP_NOT
== end of code ==
0000 1 OP_CONSTANT 0 '5'
[ 5 ]
0002 | OP_CONSTANT 1 '4'
[ 5 ][ 4 ]
0004 | OP_SUBSTRACT
[ 1 ]
0005 | OP_CONSTANT 2 '3'
[ 1 ][ 3 ]
0007 | OP_CONSTANT 3 '2'
[ 1 ][ 3 ][ 2 ]
0009 | OP_MULTIPLY
[ 1 ][ 6 ]
000a | OP_GREATER
[ false ]
000b | OP_NIL
[ false ][ nil ]
000c | OP_NOT
[ false ][ true ]
000d | OP_EQUAL
[ false ]
000e | OP_NOT
[ true ]
000f 2 OP_RETURN
true
>
``` ```

View File

@ -85,6 +85,13 @@ pub const Chunk = struct {
@intFromEnum(OpCode.OP_DIVIDE) => return utils.simple_instruction("OP_DIVIDE", offset), @intFromEnum(OpCode.OP_DIVIDE) => return utils.simple_instruction("OP_DIVIDE", offset),
@intFromEnum(OpCode.OP_NEGATE) => return utils.simple_instruction("OP_NEGATE", offset), @intFromEnum(OpCode.OP_NEGATE) => return utils.simple_instruction("OP_NEGATE", offset),
@intFromEnum(OpCode.OP_CONSTANT) => return utils.constant_instruction("OP_CONSTANT", self, offset), @intFromEnum(OpCode.OP_CONSTANT) => return utils.constant_instruction("OP_CONSTANT", self, offset),
@intFromEnum(OpCode.OP_NIL) => return utils.simple_instruction("OP_NIL", offset),
@intFromEnum(OpCode.OP_TRUE) => return utils.simple_instruction("OP_TRUE", offset),
@intFromEnum(OpCode.OP_FALSE) => return utils.simple_instruction("OP_FALSE", offset),
@intFromEnum(OpCode.OP_NOT) => return utils.simple_instruction("OP_NOT", offset),
@intFromEnum(OpCode.OP_EQUAL) => return utils.simple_instruction("OP_EQUAL", offset),
@intFromEnum(OpCode.OP_GREATER) => return utils.simple_instruction("OP_GREATER", offset),
@intFromEnum(OpCode.OP_LESS) => return utils.simple_instruction("OP_LESS", 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

@ -106,16 +106,20 @@ const Parser = struct {
self.error_at(self.previous.?, error_message); self.error_at(self.previous.?, error_message);
} }
fn emit_byte(self: *Parser, byte: u8) !void { fn emit_byte(self: *Parser, byte: u8) ParsingError!void {
try self.chunk.write(byte, self.previous.?.line); self.chunk.write(byte, self.previous.?.line) catch |err| {
switch (err) {
error.OutOfMemory => return ParsingError.OutOfMemory,
}
};
} }
fn emit_bytes(self: *Parser, byte0: u8, byte1: u8) !void { fn emit_bytes(self: *Parser, byte0: u8, byte1: u8) ParsingError!void {
try self.emit_byte(byte0); try self.emit_byte(byte0);
try self.emit_byte(byte1); try self.emit_byte(byte1);
} }
fn emit_return(self: *Parser) !void { fn emit_return(self: *Parser) ParsingError!void {
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN)); try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
} }
@ -131,9 +135,12 @@ const Parser = struct {
self.error_msg("Failed converting float."); self.error_msg("Failed converting float.");
return ParsingError.FloatConv; return ParsingError.FloatConv;
}; };
self.emit_constant(value) catch { self.emit_constant(Value.number_val(value)) catch |err| {
self.error_msg("Failed emiting constant."); self.error_msg("Failed emiting constant.");
return ParsingError.ChunkError; return switch (err) {
error.OutOfMemory => ParsingError.OutOfMemory,
else => ParsingError.Unknown,
};
}; };
} }
@ -164,9 +171,19 @@ const Parser = struct {
// Emit the operator instruction // Emit the operator instruction
switch (operation_type) { switch (operation_type) {
TokenType.MINUS => self.emit_byte(@intFromEnum(OpCode.OP_NEGATE)) catch { TokenType.MINUS => self.emit_byte(@intFromEnum(OpCode.OP_NEGATE)) catch |err| {
self.error_msg("Failed emiting NEGATE opcode."); self.error_msg("Failed emiting NEGATE opcode.");
return ParsingError.ChunkError; return switch (err) {
error.OutOfMemory => ParsingError.OutOfMemory,
else => ParsingError.Unknown,
};
},
TokenType.BANG => self.emit_byte(@intFromEnum(OpCode.OP_NOT)) catch |err| {
self.error_msg("Failed emiting NOT opcode.");
return switch (err) {
error.OutOfMemory => ParsingError.OutOfMemory,
else => ParsingError.Unknown,
};
}, },
else => {}, else => {},
} }
@ -178,13 +195,19 @@ const Parser = struct {
try self.parse_precedence(@enumFromInt(1 + @intFromEnum(parser_rule.precedence))); try self.parse_precedence(@enumFromInt(1 + @intFromEnum(parser_rule.precedence)));
switch (operator_type) { return switch (operator_type) {
TokenType.PLUS => self.emit_byte(@intFromEnum(OpCode.OP_ADD)) catch {}, TokenType.BANG_EQUAL => self.emit_bytes(@intFromEnum(OpCode.OP_EQUAL), @intFromEnum(OpCode.OP_NOT)),
TokenType.MINUS => self.emit_byte(@intFromEnum(OpCode.OP_SUBSTRACT)) catch {}, TokenType.EQUAL_EQUAL => self.emit_byte(@intFromEnum(OpCode.OP_EQUAL)),
TokenType.STAR => self.emit_byte(@intFromEnum(OpCode.OP_MULTIPLY)) catch {}, TokenType.GREATER => self.emit_byte(@intFromEnum(OpCode.OP_GREATER)),
TokenType.SLASH => self.emit_byte(@intFromEnum(OpCode.OP_DIVIDE)) catch {}, TokenType.GREATER_EQUAL => self.emit_bytes(@intFromEnum(OpCode.OP_LESS), @intFromEnum(OpCode.OP_NOT)),
TokenType.LESS => self.emit_byte(@intFromEnum(OpCode.OP_LESS)),
TokenType.LESS_EQUAL => self.emit_bytes(@intFromEnum(OpCode.OP_GREATER), @intFromEnum(OpCode.OP_NOT)),
TokenType.PLUS => self.emit_byte(@intFromEnum(OpCode.OP_ADD)),
TokenType.MINUS => self.emit_byte(@intFromEnum(OpCode.OP_SUBSTRACT)),
TokenType.STAR => self.emit_byte(@intFromEnum(OpCode.OP_MULTIPLY)),
TokenType.SLASH => self.emit_byte(@intFromEnum(OpCode.OP_DIVIDE)),
else => return, else => return,
} };
} }
fn get_rule(operator_type: TokenType) ParserRule { fn get_rule(operator_type: TokenType) ParserRule {
@ -200,31 +223,31 @@ const Parser = struct {
TokenType.SEMICOLON => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.SEMICOLON => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.SLASH => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor }, TokenType.SLASH => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor },
TokenType.STAR => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor }, TokenType.STAR => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor },
TokenType.BANG => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.BANG => ParserRule{ .prefix = unary, .infix = null, .precedence = Precedence.None },
TokenType.BANG_EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.BANG_EQUAL => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Equality },
TokenType.EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.EQUAL_EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.EQUAL_EQUAL => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Equality },
TokenType.GREATER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.GREATER => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Comparison },
TokenType.GREATER_EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.GREATER_EQUAL => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Comparison },
TokenType.LESS => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.LESS => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Comparison },
TokenType.LESS_EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.LESS_EQUAL => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Comparison },
TokenType.IDENTIFIER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.IDENTIFIER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.STRING => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.STRING => ParserRule{ .prefix = null, .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 = null, .precedence = Precedence.None },
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 = null, .infix = null, .precedence = Precedence.None }, TokenType.FALSE => ParserRule{ .prefix = literal, .infix = null, .precedence = Precedence.None },
TokenType.FOR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.FOR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
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 = 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 = null, .precedence = Precedence.None },
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 },
TokenType.THIS => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.THIS => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.TRUE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.TRUE => ParserRule{ .prefix = literal, .infix = null, .precedence = Precedence.None },
TokenType.VAR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.VAR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.WHILE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.WHILE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.ERROR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.ERROR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
@ -249,6 +272,15 @@ const Parser = struct {
try infix_rule.?(self); try infix_rule.?(self);
} }
} }
fn literal(self: *Parser) ParsingError!void {
try switch (self.previous.?.token_type) {
TokenType.NIL => self.emit_byte(@intFromEnum(OpCode.OP_NIL)),
TokenType.TRUE => self.emit_byte(@intFromEnum(OpCode.OP_TRUE)),
TokenType.FALSE => self.emit_byte(@intFromEnum(OpCode.OP_FALSE)),
else => unreachable,
};
}
}; };
pub fn compile(allocator: Allocator, contents: []const u8, chunk: *Chunk) !bool { pub fn compile(allocator: Allocator, contents: []const u8, chunk: *Chunk) !bool {

View File

@ -1,4 +1,5 @@
pub const ParsingError = error{ pub const ParsingError = error{
FloatConv, FloatConv,
ChunkError, OutOfMemory,
Unknown,
}; };

View File

@ -1,9 +1,16 @@
pub const OpCode = enum(u8) { pub const OpCode = enum(u8) {
OP_CONSTANT, OP_CONSTANT,
OP_NIL,
OP_TRUE,
OP_FALSE,
OP_EQUAL,
OP_GREATER,
OP_LESS,
OP_ADD, OP_ADD,
OP_SUBSTRACT, OP_SUBSTRACT,
OP_MULTIPLY, OP_MULTIPLY,
OP_DIVIDE, OP_DIVIDE,
OP_NOT,
OP_NEGATE, OP_NEGATE,
OP_RETURN, OP_RETURN,
}; };

View File

@ -5,7 +5,82 @@ const Allocator = std.mem.Allocator;
const utils = @import("./utils.zig"); const utils = @import("./utils.zig");
pub const Value = f64; pub const ValueType = enum {
Bool,
Nil,
Number,
};
pub const Value = struct {
value_type: ValueType,
as: union {
boolean: bool,
number: f64,
},
pub fn bool_val(value: bool) Value {
return Value{
.value_type = ValueType.Bool,
.as = .{
.boolean = value,
},
};
}
pub fn nil_val() Value {
return Value{
.value_type = ValueType.Nil,
.as = .{
.boolean = false,
},
};
}
pub fn number_val(value: f64) Value {
return Value{
.value_type = ValueType.Number,
.as = .{
.number = value,
},
};
}
pub fn as_bool(self: Value) bool {
return self.as.boolean;
}
pub fn as_number(self: Value) f64 {
return self.as.number;
}
pub fn is_bool(self: Value) bool {
return self.value_type == ValueType.Bool;
}
pub fn is_number(self: Value) bool {
return self.value_type == ValueType.Number;
}
pub fn is_nil(self: Value) bool {
return self.value_type == ValueType.Nil;
}
pub fn is_falsey(self: Value) bool {
return self.is_nil() or (self.is_bool() and !self.as_bool());
}
pub fn equals(self: Value, other: Value) bool {
if (self.value_type != other.value_type) {
return false;
}
return switch (self.value_type) {
ValueType.Nil => true,
ValueType.Bool => self.as_bool() == other.as_bool(),
ValueType.Number => self.as_number() == other.as_number(),
};
}
};
pub const ValueArray = struct { pub const ValueArray = struct {
capacity: usize, capacity: usize,
@ -39,5 +114,9 @@ pub const ValueArray = struct {
}; };
pub fn print_value(value: Value) void { pub fn print_value(value: Value) void {
debug.print("{d}", .{value}); switch (value.value_type) {
ValueType.Nil => debug.print("nil", .{}),
ValueType.Bool => debug.print("{any}", .{value.as_bool()}),
ValueType.Number => debug.print("{d}", .{value.as_number()}),
}
} }

View File

@ -69,28 +69,43 @@ pub const VM = struct {
const instruction = self.read_byte(); const instruction = self.read_byte();
try switch (instruction) { switch (instruction) {
@intFromEnum(OpCode.OP_CONSTANT) => { @intFromEnum(OpCode.OP_CONSTANT) => {
const constant = self.read_constant(); const constant = self.read_constant();
try self.push(constant); try self.push(constant);
}, },
@intFromEnum(OpCode.OP_ADD) => self.binary_op(OpCode.OP_ADD), @intFromEnum(OpCode.OP_NIL) => try self.push(Value.nil_val()),
@intFromEnum(OpCode.OP_SUBSTRACT) => self.binary_op(OpCode.OP_SUBSTRACT), @intFromEnum(OpCode.OP_FALSE) => try self.push(Value.bool_val(false)),
@intFromEnum(OpCode.OP_MULTIPLY) => self.binary_op(OpCode.OP_MULTIPLY), @intFromEnum(OpCode.OP_TRUE) => try self.push(Value.bool_val(true)),
@intFromEnum(OpCode.OP_DIVIDE) => self.binary_op(OpCode.OP_DIVIDE), @intFromEnum(OpCode.OP_ADD), @intFromEnum(OpCode.OP_SUBSTRACT), @intFromEnum(OpCode.OP_MULTIPLY), @intFromEnum(OpCode.OP_DIVIDE), @intFromEnum(OpCode.OP_LESS), @intFromEnum(OpCode.OP_GREATER) => {
const res = try self.binary_op(@enumFromInt(instruction));
if (res != InterpretResult.OK) {
return res;
}
},
@intFromEnum(OpCode.OP_NOT) => {
try self.push(Value.bool_val(self.pop().is_falsey()));
},
@intFromEnum(OpCode.OP_NEGATE) => { @intFromEnum(OpCode.OP_NEGATE) => {
try self.push(-self.pop()); if (!self.peek(0).is_number()) {
self.runtime_error("Operand must be a number.");
return InterpretResult.RUNTIME_ERROR;
}
try self.push(Value.number_val(-self.pop().as_number()));
}, },
@intFromEnum(OpCode.OP_RETURN) => { @intFromEnum(OpCode.OP_RETURN) => {
print_value(self.pop()); print_value(self.pop());
debug.print("\n", .{}); debug.print("\n", .{});
return InterpretResult.OK; return InterpretResult.OK;
}, },
@intFromEnum(OpCode.OP_EQUAL) => {
try self.push(Value.bool_val(self.pop().equals(self.pop())));
},
else => { else => {
debug.print("Invalid instruction: {d}\n", .{instruction}); debug.print("Invalid instruction: {d}\n", .{instruction});
return InterpretResult.RUNTIME_ERROR; return InterpretResult.RUNTIME_ERROR;
}, },
}; }
} }
return InterpretResult.OK; return InterpretResult.OK;
@ -117,18 +132,39 @@ pub const VM = struct {
return self.stack.pop(); return self.stack.pop();
} }
pub fn binary_op(self: *VM, op: OpCode) !void { pub fn binary_op(self: *VM, op: OpCode) !InterpretResult {
const b = self.pop(); if (!self.peek(0).is_number() or !self.peek(0).is_number()) {
const a = self.pop(); self.runtime_error("Operands must be numbers");
return InterpretResult.RUNTIME_ERROR;
}
const b = self.pop().as_number();
const a = self.pop().as_number();
const res: Value = switch (op) { const res: Value = switch (op) {
OpCode.OP_ADD => a + b, OpCode.OP_ADD => Value.number_val(a + b),
OpCode.OP_SUBSTRACT => a - b, OpCode.OP_SUBSTRACT => Value.number_val(a - b),
OpCode.OP_MULTIPLY => a * b, OpCode.OP_MULTIPLY => Value.number_val(a * b),
OpCode.OP_DIVIDE => a / b, OpCode.OP_DIVIDE => Value.number_val(a / b),
OpCode.OP_LESS => Value.bool_val(a < b),
OpCode.OP_GREATER => Value.bool_val(a > b),
else => unreachable, else => unreachable,
}; };
try self.push(res); try self.push(res);
return InterpretResult.OK;
}
pub fn peek(self: *VM, distance: usize) Value {
return self.stack.items[self.stack.items.len - 1 - distance];
}
pub fn runtime_error(self: *VM, err_msg: []const u8) void {
const instruction = self.ip.?;
const line = self.chunk.?.lines[instruction];
debug.print("err: {s}\n", .{err_msg});
debug.print("[line {d}] in script\n", .{line});
} }
}; };