diff --git a/src/compile.zig b/src/compile.zig index 382c42e..48eaeb0 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -62,6 +62,10 @@ const Parser = struct { }; } + inline fn current_chunk(self: *Parser) *Chunk { + return self.chunk; + } + fn advance(self: *Parser) void { self.previous = self.current; @@ -117,7 +121,7 @@ const Parser = struct { } fn emit_byte(self: *Parser, byte: u8) ParsingError!void { - self.chunk.write(byte, self.previous.?.line) catch |err| { + self.current_chunk().write(byte, self.previous.?.line) catch |err| { switch (err) { error.OutOfMemory => return ParsingError.OutOfMemory, } @@ -135,7 +139,7 @@ const Parser = struct { fn end_parser(self: *Parser) !void { if (!self.had_error and self.vm.has_tracing()) { - self.chunk.dissassemble("code"); + self.current_chunk().dissassemble("code"); } try self.emit_return(); } @@ -162,7 +166,7 @@ const Parser = struct { } fn make_constant(self: *Parser, value: Value) !u8 { - const constant = try self.chunk.add_constant(value); + const constant = try self.current_chunk().add_constant(value); if (constant > constants.UINT8_MAX) { self.error_msg("Too many constants in one chunk."); return 0; @@ -585,11 +589,11 @@ const Parser = struct { try self.emit_byte(0xff); try self.emit_byte(0xff); - return self.chunk.count - 2; + return self.current_chunk().count - 2; } fn patch_jump(self: *Parser, offset: usize) void { - const jump = self.chunk.count - offset - 2; + const jump = self.current_chunk().count - offset - 2; if (jump > constants.UINT16_MAX) { self.error_msg("Too much code to jump over."); @@ -598,8 +602,8 @@ const Parser = struct { const b1 = (jump >> 8) & 0xff; const b0 = jump & 0xff; - self.chunk.code[offset] = @intCast(b1); - self.chunk.code[offset + 1] = @intCast(b0); + self.current_chunk().code[offset] = @intCast(b1); + self.current_chunk().code[offset + 1] = @intCast(b0); } fn and_(self: *Parser, can_assign: bool) ParsingError!void { @@ -624,7 +628,7 @@ const Parser = struct { } fn while_statement(self: *Parser) ParsingError!void { - const loop_start = self.chunk.count; + const loop_start = self.current_chunk().count; self.consume(TokenType.LEFT_PAREN, "Expect '(' after 'while'."); try self.expression(); self.consume(TokenType.RIGHT_PAREN, "Expect ')' after condition."); @@ -640,7 +644,7 @@ const Parser = struct { fn emit_loop(self: *Parser, loop_start: usize) ParsingError!void { try self.emit_byte(@intFromEnum(OpCode.OP_LOOP)); - const offset = self.chunk.count - loop_start + 2; + const offset = self.current_chunk().count - loop_start + 2; if (offset > constants.UINT16_MAX) { self.error_msg("Loop body too large."); } @@ -661,7 +665,7 @@ const Parser = struct { try self.expression_statement(); } - var loop_start = self.chunk.count; + var loop_start = self.current_chunk().count; var exit_jump: ?usize = null; @@ -676,7 +680,7 @@ const Parser = struct { if (!self.match(TokenType.RIGHT_PAREN)) { const body_jump = try self.emit_jump(@intFromEnum(OpCode.OP_JUMP)); - const increment_start = self.chunk.count; + const increment_start = self.current_chunk().count; try self.expression(); try self.emit_byte(@intFromEnum(OpCode.OP_POP)); self.consume(TokenType.RIGHT_PAREN, "Expect ')' after for clauses."); diff --git a/src/object.zig b/src/object.zig index 28fc9d1..0192d2d 100644 --- a/src/object.zig +++ b/src/object.zig @@ -2,10 +2,13 @@ const std = @import("std"); const debug = std.debug; const Allocator = std.mem.Allocator; +const Chunk = @import("./chunk.zig").Chunk; + const compute_hash = @import("./utils.zig").compute_hash; pub const ObjType = enum { String, + Function, }; pub const Obj = struct { @@ -13,8 +16,8 @@ pub const Obj = struct { allocator: std.mem.Allocator, pub const String = struct { - chars: []const u8, obj: Obj, + chars: []const u8, hash: u32, pub fn new(allocator: std.mem.Allocator, chars: []const u8) *String { @@ -39,6 +42,33 @@ pub const Obj = struct { } }; + pub const Function = struct { + obj: Obj, + arity: usize, + chunk: *Chunk, + name: *Obj.String, + + pub fn new(allocator: std.mem.Allocator) *Function { + const obj = Obj{ + .kind = ObjType.Function, + .allocator = allocator, + }; + + const function_obj = allocator.create(Function) catch unreachable; + function_obj.obj = obj; + function_obj.arity = 0; + function_obj.chunk = Chunk.new(allocator); + + return function_obj; + } + + pub fn destroy(self: *Function) void { + const allocator = self.obj.allocator; + self.chunk.deinit(); + allocator.destroy(self); + } + }; + pub fn is_type(self: *Obj, kind: ObjType) bool { return self.kind == kind; } @@ -47,12 +77,20 @@ pub const Obj = struct { return self.is_type(ObjType.String); } + pub fn is_function(self: *Obj) bool { + return self.is_function(ObjType.Function); + } + pub fn print(self: *Obj) void { switch (self.kind) { ObjType.String => { const obj = self.as_string(); debug.print("{s}", .{obj.chars}); }, + ObjType.Function => { + const obj = self.as_function(); + debug.print("", .{obj.name.chars}); + }, } } @@ -62,6 +100,10 @@ pub const Obj = struct { const obj: *String = @fieldParentPtr("obj", self); obj.destroy(); }, + ObjType.Function => { + const obj: *Function = @fieldParentPtr("obj", self); + obj.destroy(); + }, } } @@ -69,4 +111,9 @@ pub const Obj = struct { std.debug.assert(self.kind == ObjType.String); return @fieldParentPtr("obj", self); } + + pub fn as_function(self: *Obj) *Function { + std.debug.assert(self.kind == ObjType.Function); + return @fieldParentPtr("obj", self); + } }; diff --git a/src/vm.zig b/src/vm.zig index 6312096..d1d510e 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -61,6 +61,10 @@ pub const VM = struct { self.references.deinit(); } + inline fn current_chunk(self: *VM) *Chunk { + return self.chunk.?; + } + pub fn set_trace(self: *VM, tracing: bool) void { self.tracing = tracing; } @@ -96,7 +100,7 @@ pub const VM = struct { } debug.print("\n", .{}); } - _ = self.chunk.?.dissassemble_instruction(self.ip.?); + _ = self.current_chunk().dissassemble_instruction(self.ip.?); } const instruction = self.read_byte(); @@ -209,7 +213,7 @@ pub const VM = struct { // XXX In the book, we're using a ptr to data directly, to avoid dereferencing to a given offset // How to do that in Zig? pub fn read_byte(self: *VM) u8 { - const ret = self.chunk.?.code[self.ip.?]; + const ret = self.current_chunk().code[self.ip.?]; self.ip.? += 1; return ret; @@ -218,11 +222,11 @@ pub const VM = struct { 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])); + return (@as(u16, self.current_chunk().code[self.ip.? - 2]) << 8) | (@as(u16, self.current_chunk().code[self.ip.? - 1])); } pub fn read_constant(self: *VM) Value { - return self.chunk.?.constants.values[read_byte(self)]; + return self.current_chunk().constants.values[read_byte(self)]; } pub fn push(self: *VM, value: Value) !void { @@ -269,7 +273,7 @@ pub const VM = struct { const b = self.pop().as_cstring(); const a = self.pop().as_cstring(); - const concat_str = try std.mem.concat(self.chunk.?.allocator, u8, &.{ a, b }); + const concat_str = try std.mem.concat(self.current_chunk().allocator, u8, &.{ a, b }); var string_obj = self.take_string(concat_str); @@ -284,7 +288,7 @@ pub const VM = struct { pub fn runtime_error(self: *VM, err_msg: []const u8) void { const instruction = self.ip.?; - const line = self.chunk.?.lines[instruction]; + const line = self.current_chunk().lines[instruction]; debug.print("err: {s}\n", .{err_msg}); debug.print("[line {d}] in script\n", .{line});