From 58cf9e15c6d46201c7f87ecebfb28f74d4235a81 Mon Sep 17 00:00:00 2001 From: Patrick MARIE Date: Sun, 25 Aug 2024 16:23:16 +0200 Subject: [PATCH] implementing types of values (ch18) --- README.md | 40 +++++++++++++++++++++++- src/chunk.zig | 7 +++++ src/compile.zig | 80 +++++++++++++++++++++++++++++++++-------------- src/errors.zig | 3 +- src/opcode.zig | 7 +++++ src/values.zig | 83 +++++++++++++++++++++++++++++++++++++++++++++++-- src/vm.zig | 64 +++++++++++++++++++++++++++++--------- 7 files changed, 242 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index edcc229..ba1f017 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,44 @@ zig build run [ 18 ] 000b 2 OP_RETURN 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 +> ``` diff --git a/src/chunk.zig b/src/chunk.zig index 1f9c7db..764731d 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -85,6 +85,13 @@ pub const Chunk = struct { @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_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 => { debug.print("unknown opcode {d}\n", .{instruction}); return offset + 1; diff --git a/src/compile.zig b/src/compile.zig index 3efdd9a..f59a0dc 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -106,16 +106,20 @@ const Parser = struct { self.error_at(self.previous.?, error_message); } - fn emit_byte(self: *Parser, byte: u8) !void { - try self.chunk.write(byte, self.previous.?.line); + fn emit_byte(self: *Parser, byte: u8) ParsingError!void { + 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(byte1); } - fn emit_return(self: *Parser) !void { + fn emit_return(self: *Parser) ParsingError!void { try self.emit_byte(@intFromEnum(OpCode.OP_RETURN)); } @@ -131,9 +135,12 @@ const Parser = struct { self.error_msg("Failed converting float."); return ParsingError.FloatConv; }; - self.emit_constant(value) catch { + self.emit_constant(Value.number_val(value)) catch |err| { 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 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."); - 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 => {}, } @@ -178,13 +195,19 @@ const Parser = struct { try self.parse_precedence(@enumFromInt(1 + @intFromEnum(parser_rule.precedence))); - switch (operator_type) { - TokenType.PLUS => self.emit_byte(@intFromEnum(OpCode.OP_ADD)) catch {}, - TokenType.MINUS => self.emit_byte(@intFromEnum(OpCode.OP_SUBSTRACT)) catch {}, - TokenType.STAR => self.emit_byte(@intFromEnum(OpCode.OP_MULTIPLY)) catch {}, - TokenType.SLASH => self.emit_byte(@intFromEnum(OpCode.OP_DIVIDE)) catch {}, + return switch (operator_type) { + TokenType.BANG_EQUAL => self.emit_bytes(@intFromEnum(OpCode.OP_EQUAL), @intFromEnum(OpCode.OP_NOT)), + TokenType.EQUAL_EQUAL => self.emit_byte(@intFromEnum(OpCode.OP_EQUAL)), + TokenType.GREATER => self.emit_byte(@intFromEnum(OpCode.OP_GREATER)), + 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, - } + }; } 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.SLASH => 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_EQUAL => 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 = binary, .precedence = Precedence.Equality }, TokenType.EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, - TokenType.EQUAL_EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, - TokenType.GREATER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, - TokenType.GREATER_EQUAL => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, - TokenType.LESS => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, - TokenType.LESS_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 = binary, .precedence = Precedence.Comparison }, + TokenType.GREATER_EQUAL => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Comparison }, + TokenType.LESS => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Comparison }, + TokenType.LESS_EQUAL => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Comparison }, TokenType.IDENTIFIER => 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.AND => 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.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.FUN => 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.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 }, 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.WHILE => 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); } } + + 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 { diff --git a/src/errors.zig b/src/errors.zig index ef34e00..83f1e9f 100644 --- a/src/errors.zig +++ b/src/errors.zig @@ -1,4 +1,5 @@ pub const ParsingError = error{ FloatConv, - ChunkError, + OutOfMemory, + Unknown, }; diff --git a/src/opcode.zig b/src/opcode.zig index bb4cbdc..daeec65 100644 --- a/src/opcode.zig +++ b/src/opcode.zig @@ -1,9 +1,16 @@ pub const OpCode = enum(u8) { OP_CONSTANT, + OP_NIL, + OP_TRUE, + OP_FALSE, + OP_EQUAL, + OP_GREATER, + OP_LESS, OP_ADD, OP_SUBSTRACT, OP_MULTIPLY, OP_DIVIDE, + OP_NOT, OP_NEGATE, OP_RETURN, }; diff --git a/src/values.zig b/src/values.zig index 53c6759..5ab8c3c 100644 --- a/src/values.zig +++ b/src/values.zig @@ -5,7 +5,82 @@ const Allocator = std.mem.Allocator; 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 { capacity: usize, @@ -39,5 +114,9 @@ pub const ValueArray = struct { }; 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()}), + } } diff --git a/src/vm.zig b/src/vm.zig index e767c58..a5943a7 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -69,28 +69,43 @@ pub const VM = struct { const instruction = self.read_byte(); - try switch (instruction) { + switch (instruction) { @intFromEnum(OpCode.OP_CONSTANT) => { const constant = self.read_constant(); try self.push(constant); }, - @intFromEnum(OpCode.OP_ADD) => self.binary_op(OpCode.OP_ADD), - @intFromEnum(OpCode.OP_SUBSTRACT) => self.binary_op(OpCode.OP_SUBSTRACT), - @intFromEnum(OpCode.OP_MULTIPLY) => self.binary_op(OpCode.OP_MULTIPLY), - @intFromEnum(OpCode.OP_DIVIDE) => self.binary_op(OpCode.OP_DIVIDE), + @intFromEnum(OpCode.OP_NIL) => try self.push(Value.nil_val()), + @intFromEnum(OpCode.OP_FALSE) => try self.push(Value.bool_val(false)), + @intFromEnum(OpCode.OP_TRUE) => try self.push(Value.bool_val(true)), + @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) => { - 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) => { print_value(self.pop()); debug.print("\n", .{}); return InterpretResult.OK; }, + @intFromEnum(OpCode.OP_EQUAL) => { + try self.push(Value.bool_val(self.pop().equals(self.pop()))); + }, else => { debug.print("Invalid instruction: {d}\n", .{instruction}); return InterpretResult.RUNTIME_ERROR; }, - }; + } } return InterpretResult.OK; @@ -117,18 +132,39 @@ pub const VM = struct { return self.stack.pop(); } - pub fn binary_op(self: *VM, op: OpCode) !void { - const b = self.pop(); - const a = self.pop(); + pub fn binary_op(self: *VM, op: OpCode) !InterpretResult { + if (!self.peek(0).is_number() or !self.peek(0).is_number()) { + 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) { - OpCode.OP_ADD => a + b, - OpCode.OP_SUBSTRACT => a - b, - OpCode.OP_MULTIPLY => a * b, - OpCode.OP_DIVIDE => a / b, + OpCode.OP_ADD => Value.number_val(a + b), + OpCode.OP_SUBSTRACT => Value.number_val(a - b), + OpCode.OP_MULTIPLY => Value.number_val(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, }; 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}); } };