From e8ad1cd5b581bd9f9de8996227a5d25bd2cfeeab Mon Sep 17 00:00:00 2001 From: Brett Broadhurst Date: Mon, 23 Mar 2026 22:54:40 -0600 Subject: [PATCH] refactor: change object.zig module into file struct --- src/Story.zig | 2 +- src/Story/Object.zig | 312 ++++++++++++++++++++++++++++++++++++++++++ src/Story/object.zig | 314 ------------------------------------------- 3 files changed, 313 insertions(+), 315 deletions(-) create mode 100644 src/Story/Object.zig delete mode 100644 src/Story/object.zig diff --git a/src/Story.zig b/src/Story.zig index 2e0e9a4..605eb38 100644 --- a/src/Story.zig +++ b/src/Story.zig @@ -5,7 +5,7 @@ const tokenizer = @import("tokenizer.zig"); const Ast = @import("Ast.zig"); const AstGen = @import("AstGen.zig"); const Sema = @import("Sema.zig"); -pub const Object = @import("Story/object.zig").Object; +pub const Object = @import("Story/Object.zig"); const Dumper = @import("Story/Dumper.zig"); const assert = std.debug.assert; const Story = @This(); diff --git a/src/Story/Object.zig b/src/Story/Object.zig new file mode 100644 index 0000000..1617b0f --- /dev/null +++ b/src/Story/Object.zig @@ -0,0 +1,312 @@ +//! Runtime Object, whose memory is managed through the virtual machine's +//! garbage collector. +//! +//! This structure is the base object type that is embedded into subtypes. +//! Each subtype has a `.create` method to allocate a new object. +const std = @import("std"); +const Story = @import("../Story.zig"); +const Object = @This(); + +tag: Tag, +is_marked: bool, +node: std.SinglyLinkedList.Node, + +pub const Tag = enum { + number, + string, + content_path, +}; + +pub fn destroy(obj: *Object, story: *Story) void { + switch (obj.tag) { + .number => { + const typed_obj: *Object.Number = @alignCast(@fieldParentPtr("base", obj)); + typed_obj.destroy(story); + }, + .string => { + const typed_obj: *Object.String = @alignCast(@fieldParentPtr("base", obj)); + typed_obj.destroy(story); + }, + .content_path => { + const typed_obj: *Object.ContentPath = @alignCast(@fieldParentPtr("base", obj)); + typed_obj.destroy(story); + }, + } +} + +pub fn add(story: *Story, lhs: *Object, rhs: *Object) !*Object { + if (lhs.tag == .string or rhs.tag == .string) { + const _lhs = try Object.String.fromObject(story, lhs); + const _rhs = try Object.String.fromObject(story, rhs); + const result = try Object.String.concat(story, _lhs, _rhs); + return @ptrCast(result); + } else if (lhs.tag == .number or rhs.tag == .number) { + const _lhs = try Object.Number.fromObject(story, lhs); + const _rhs = try Object.Number.fromObject(story, rhs); + const result = try Object.Number.performArithmetic(story, .add, _lhs, _rhs); + return @ptrCast(result); + } else { + return error.InvalidArgument; + } +} + +pub fn cmpEql(story: *Story, lhs: *Object, rhs: *Object) !*Object { + // TODO: This is temporary + if (lhs.tag != .number or lhs.tag != rhs.tag) return error.InvalidComparison; + const result = try Object.Number.performLogic(story, .cmp_eq, @ptrCast(lhs), @ptrCast(rhs)); + return @ptrCast(result); +} + +pub fn isFalsey(obj: *Object) bool { + switch (obj.tag) { + .number => { + const number: *Object.Number = @ptrCast(obj); + switch (number.data) { + .boolean => |value| return !value, + else => return false, + } + }, + else => return false, + } +} + +pub const Number = struct { + base: Object, + data: Data, + + pub const Data = union(enum) { + boolean: bool, + integer: i64, + floating: f64, + }; + + const Type = Object.Number; + + pub fn create(story: *Story, data: Data) error{OutOfMemory}!*Object.Number { + const gpa = story.allocator; + const raw = try gpa.alignedAlloc(u8, .of(Type), @sizeOf(Type)); + const obj: *Type = @ptrCast(raw); + obj.* = .{ + .base = .{ + .tag = .number, + .is_marked = false, + .node = .{}, + }, + .data = data, + }; + + story.gc_objects.prepend(&obj.base.node); + return obj; + } + + pub fn destroy(obj: *Object.Number, story: *Story) void { + const gpa = story.allocator; + const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj); + gpa.free(base[0..@sizeOf(Type)]); + } + + pub fn fromObject(story: *Story, object: *Object) !*Object.Number { + const data: Object.Number.Data = v: switch (object.tag) { + .number => { + const number: *Object.Number = @ptrCast(object); + switch (number.data) { + .boolean => |value| break :v .{ .integer = @intFromBool(value) }, + .integer => |value| break :v .{ .integer = value }, + .floating => |value| break :v .{ .floating = value }, + } + }, + else => break :v .{ .boolean = true }, + }; + + const obj = Object.Number.create(story, data); + // ink_gc_own(story, obj); + return obj; + } + + fn logicalOp(comptime T: type, op: Story.Opcode, lhs: T, rhs: T) bool { + switch (op) { + .cmp_eq => return lhs == rhs, + .cmp_lt => return lhs < rhs, + .cmp_gt => return lhs > rhs, + .cmp_lte => return lhs <= rhs, + .cmp_gte => return lhs >= rhs, + else => unreachable, + } + } + + fn arithmeticOp(comptime T: type, op: Story.Opcode, lhs: T, rhs: T) T { + switch (op) { + .add => return lhs + rhs, + .sub => return lhs - rhs, + .mul => return lhs * rhs, + .div => return @divTrunc(lhs, rhs), + .mod => return @mod(lhs, rhs), + else => unreachable, + } + } + + pub fn negate(object: *Object.Number) *Object.Number { + switch (object.data) { + .integer => |*value| value.* = -value.*, + .floating => |*value| value.* = -value.*, + else => unreachable, + } + return object; + } + + pub fn performLogic( + story: *Story, + op: Story.Opcode, + lhs: *Object.Number, + rhs: *Object.Number, + ) !*Object.Number { + return .create(story, .{ + .boolean = logicalOp(i64, op, lhs.data.integer, rhs.data.integer), + }); + } + + pub fn performArithmetic( + story: *Story, + op: Story.Opcode, + lhs: *Object.Number, + rhs: *Object.Number, + ) !*Object.Number { + if (lhs.data == .floating or rhs.data == .floating) { + return .create(story, .{ + .floating = arithmeticOp(f64, op, lhs.data.floating, rhs.data.floating), + }); + } + return .create(story, .{ + .integer = arithmeticOp(i64, op, lhs.data.integer, rhs.data.integer), + }); + } +}; + +pub const String = struct { + base: Object, + hash: u32, // TODO: Not implemented. + length: usize, // TODO: This could probably be u32. + bytes: [*]const u8, + + const Type = Object.String; + + pub fn create( + story: *Story, + bytes: []const u8, + ) error{OutOfMemory}!*Object.String { + const gpa = story.allocator; + const alloc_len = @sizeOf(Type) + bytes.len + 1; + const raw = try gpa.alignedAlloc(u8, .of(Type), alloc_len); + const object: *Type = @ptrCast(raw); + + object.* = .{ + .base = .{ + .tag = .string, + .is_marked = false, + .node = .{}, + }, + .hash = 0, + .length = bytes.len, + .bytes = undefined, + }; + + // Point bytes slice to the memory *after* the struct + const buf = raw[@sizeOf(Type)..][0 .. bytes.len + 1]; + object.bytes = buf.ptr; + @memcpy(buf[0..bytes.len], bytes); + buf[bytes.len] = 0; + + story.gc_objects.prepend(&object.base.node); + return object; + } + + pub fn destroy(obj: *String, story: *Story) void { + const gpa = story.allocator; + const alloc_len = @sizeOf(Type) + obj.length + 1; + const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj); + gpa.free(base[0..alloc_len]); + } + + pub fn fromObject(story: *Story, obj: *Object) !*Object.String { + switch (obj.tag) { + .number => { + // NOTE: 20 bytes should be enough. + const print_buffer_len = 20; + var print_buffer: [print_buffer_len]u8 = undefined; + const number_object: *Object.Number = @ptrCast(obj); + const number_bytes = try std.fmt.bufPrint(&print_buffer, "{}", .{ + number_object.data.integer, + }); + return .create(story, number_bytes); + }, + .string => return @ptrCast(obj), + else => unreachable, + } + } + + pub fn concat(story: *Story, lhs: *String, rhs: *String) !*Object.String { + const gpa = story.allocator; + const length = lhs.length + rhs.length; + const bytes = try gpa.alloc(u8, length + 1); + defer gpa.free(bytes); + + @memcpy(bytes[0..lhs.length], lhs.bytes[0..lhs.length]); + @memcpy(bytes[lhs.length..], rhs.bytes[0..rhs.length]); + //ink_gc_disown(story, INK_OBJ(lhs)); + //ink_gc_disown(story, INK_OBJ(rhs)); + return .create(story, bytes); + } +}; + +pub const ContentPath = struct { + base: Object, + name: *Object.String, + arity: usize, + // TODO: Rename this to stack size. + locals_count: usize, + // TODO: Rename this to constant_pool. + const_pool: []u32, + bytes: []const u8, + + const Type = Object.ContentPath; + + pub const CreateOptions = struct { + name: *Object.String, + arity: usize, + locals_count: usize, + const_pool: []u32, + bytes: []const u8, + }; + + pub fn create(story: *Story, options: CreateOptions) error{OutOfMemory}!*Object.ContentPath { + const gpa = story.allocator; + const alloc_len = @sizeOf(Type); + const raw = try gpa.alignedAlloc(u8, .of(Type), alloc_len); + const obj: *Type = @ptrCast(raw); + + obj.* = .{ + .base = .{ + .tag = .content_path, + .is_marked = false, + .node = .{}, + }, + .name = options.name, + .arity = options.arity, + .locals_count = options.locals_count, + .const_pool = options.const_pool, + .bytes = options.bytes, + }; + + story.gc_objects.prepend(&obj.base.node); + return obj; + } + + pub fn destroy(obj: *ContentPath, story: *Story) void { + const gpa = story.allocator; + gpa.free(obj.const_pool); + gpa.free(obj.bytes); + + const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj); + gpa.free(base[0..@sizeOf(Type)]); + } +}; diff --git a/src/Story/object.zig b/src/Story/object.zig deleted file mode 100644 index dc28d56..0000000 --- a/src/Story/object.zig +++ /dev/null @@ -1,314 +0,0 @@ -const std = @import("std"); -const Story = @import("../Story.zig"); - -/// Runtime Object, whose memory is managed through the virtual machine's -/// garbage collector. -/// -/// This structure is the base object type that is embedded into subtypes. -/// Each subtype has a `.create` method to allocate a new object. -pub const Object = struct { - tag: Tag, - is_marked: bool, - node: std.SinglyLinkedList.Node, - - pub const Tag = enum { - number, - string, - content_path, - }; - - pub fn destroy(object: *Object, story: *Story) void { - switch (object.tag) { - .number => { - const obj: *Object.Number = @alignCast(@fieldParentPtr("base", object)); - obj.destroy(story); - }, - .string => { - const obj: *Object.String = @alignCast(@fieldParentPtr("base", object)); - obj.destroy(story); - }, - .content_path => { - const obj: *Object.ContentPath = @alignCast(@fieldParentPtr("base", object)); - obj.destroy(story); - }, - } - } - - pub fn add(story: *Story, lhs: *Object, rhs: *Object) !*Object { - if (lhs.tag == .string or rhs.tag == .string) { - const _lhs = try Object.String.fromObject(story, lhs); - const _rhs = try Object.String.fromObject(story, rhs); - const result = try Object.String.concat(story, _lhs, _rhs); - return @ptrCast(result); - } else if (lhs.tag == .number or rhs.tag == .number) { - const _lhs = try Object.Number.fromObject(story, lhs); - const _rhs = try Object.Number.fromObject(story, rhs); - const result = try Object.Number.performArithmetic(story, .add, _lhs, _rhs); - return @ptrCast(result); - } else { - return error.InvalidArgument; - } - } - - pub fn cmpEql(story: *Story, lhs: *Object, rhs: *Object) !*Object { - // TODO: This is temporary - if (lhs.tag != .number or lhs.tag != rhs.tag) return error.InvalidComparison; - const result = try Object.Number.performLogic(story, .cmp_eq, @ptrCast(lhs), @ptrCast(rhs)); - return @ptrCast(result); - } - - pub fn isFalsey(obj: *Object) bool { - switch (obj.tag) { - .number => { - const number: *Object.Number = @ptrCast(obj); - switch (number.data) { - .boolean => |value| return !value, - else => return false, - } - }, - else => return false, - } - } - - pub const Number = struct { - base: Object, - data: Data, - - pub const Data = union(enum) { - boolean: bool, - integer: i64, - floating: f64, - }; - - pub fn create(story: *Story, data: Data) error{OutOfMemory}!*Object.Number { - const gpa = story.allocator; - const alloc_len = @sizeOf(Object.Number); - const raw = try gpa.alignedAlloc(u8, .of(Object.Number), alloc_len); - const object: *Object.Number = @ptrCast(raw); - - object.* = .{ - .base = .{ - .tag = .number, - .is_marked = false, - .node = .{}, - }, - .data = data, - }; - - story.gc_objects.prepend(&object.base.node); - return object; - } - - pub fn destroy(obj: *Object.Number, story: *Story) void { - const gpa = story.allocator; - const alloc_len = @sizeOf(Object.Number); - const base: [*]align(@alignOf(Object.Number)) u8 = @ptrCast(obj); - gpa.free(base[0..alloc_len]); - } - - pub fn fromObject(story: *Story, object: *Object) !*Object.Number { - const data: Object.Number.Data = v: switch (object.tag) { - .number => { - const number: *Object.Number = @ptrCast(object); - switch (number.data) { - .boolean => |value| break :v .{ .integer = @intFromBool(value) }, - .integer => |value| break :v .{ .integer = value }, - .floating => |value| break :v .{ .floating = value }, - } - }, - else => break :v .{ .boolean = true }, - }; - - const obj = Object.Number.create(story, data); - // ink_gc_own(story, obj); - return obj; - } - - fn logicalOp(comptime T: type, op: Story.Opcode, lhs: T, rhs: T) bool { - switch (op) { - .cmp_eq => return lhs == rhs, - .cmp_lt => return lhs < rhs, - .cmp_gt => return lhs > rhs, - .cmp_lte => return lhs <= rhs, - .cmp_gte => return lhs >= rhs, - else => unreachable, - } - } - - fn arithmeticOp(comptime T: type, op: Story.Opcode, lhs: T, rhs: T) T { - switch (op) { - .add => return lhs + rhs, - .sub => return lhs - rhs, - .mul => return lhs * rhs, - .div => return @divTrunc(lhs, rhs), - .mod => return @mod(lhs, rhs), - else => unreachable, - } - } - - pub fn negate(object: *Object.Number) *Object.Number { - switch (object.data) { - .integer => |*value| value.* = -value.*, - .floating => |*value| value.* = -value.*, - else => unreachable, - } - return object; - } - - pub fn performLogic( - story: *Story, - op: Story.Opcode, - lhs: *Object.Number, - rhs: *Object.Number, - ) !*Object.Number { - return .create(story, .{ - .boolean = logicalOp(i64, op, lhs.data.integer, rhs.data.integer), - }); - } - - pub fn performArithmetic( - story: *Story, - op: Story.Opcode, - lhs: *Object.Number, - rhs: *Object.Number, - ) !*Object.Number { - if (lhs.data == .floating or rhs.data == .floating) { - return .create(story, .{ - .floating = arithmeticOp(f64, op, lhs.data.floating, rhs.data.floating), - }); - } - return .create(story, .{ - .integer = arithmeticOp(i64, op, lhs.data.integer, rhs.data.integer), - }); - } - }; - - pub const String = struct { - base: Object, - hash: u32, // TODO: Not implemented. - length: usize, // TODO: This could probably be u32. - bytes: [*]const u8, - - pub fn create( - story: *Story, - bytes: []const u8, - ) error{OutOfMemory}!*Object.String { - const gpa = story.allocator; - const alloc_len = @sizeOf(String) + bytes.len + 1; - const raw = try gpa.alignedAlloc(u8, .of(String), alloc_len); - const object: *String = @ptrCast(raw); - - object.* = .{ - .base = .{ - .tag = .string, - .is_marked = false, - .node = .{}, - }, - .hash = 0, - .length = bytes.len, - .bytes = undefined, - }; - - // Point bytes slice to the memory *after* the struct - const buf = raw[@sizeOf(String)..][0 .. bytes.len + 1]; - object.bytes = buf.ptr; - @memcpy(buf[0..bytes.len], bytes); - buf[bytes.len] = 0; - - story.gc_objects.prepend(&object.base.node); - return object; - } - - pub fn destroy(object: *Object.String, story: *Story) void { - const gpa = story.allocator; - const alloc_len = @sizeOf(Object.String) + object.length + 1; - const base: [*]align(@alignOf(Object.String)) u8 = @ptrCast(object); - gpa.free(base[0..alloc_len]); - } - - pub fn fromObject(story: *Story, object: *Object) !*Object.String { - switch (object.tag) { - .number => { - // NOTE: 20 bytes should be enough. - const print_buffer_len = 20; - var print_buffer: [print_buffer_len]u8 = undefined; - const number_object: *Object.Number = @ptrCast(object); - const number_bytes = try std.fmt.bufPrint(&print_buffer, "{}", .{ - number_object.data.integer, - }); - return Object.String.create(story, number_bytes); - }, - .string => return @ptrCast(object), - else => unreachable, - } - } - - pub fn concat(story: *Story, lhs: *String, rhs: *String) !*Object.String { - const gpa = story.allocator; - const length = lhs.length + rhs.length; - const bytes = try gpa.alloc(u8, length + 1); - defer gpa.free(bytes); - - @memcpy(bytes[0..lhs.length], lhs.bytes[0..lhs.length]); - @memcpy(bytes[lhs.length..], rhs.bytes[0..rhs.length]); - //ink_gc_disown(story, INK_OBJ(lhs)); - //ink_gc_disown(story, INK_OBJ(rhs)); - return .create(story, bytes); - } - }; - - pub const ContentPath = struct { - base: Object, - name: *Object.String, - arity: usize, - // TODO: Rename this to stack size. - locals_count: usize, - // TODO: Rename this to constant_pool. - const_pool: []u32, - bytes: []const u8, - - pub const CreateOptions = struct { - name: *Object.String, - arity: usize, - locals_count: usize, - const_pool: []u32, - bytes: []const u8, - }; - - pub fn create( - story: *Story, - options: CreateOptions, - ) error{OutOfMemory}!*ContentPath { - const gpa = story.allocator; - const alloc_len = @sizeOf(ContentPath); - const raw = try gpa.alignedAlloc(u8, .of(ContentPath), alloc_len); - const object: *ContentPath = @ptrCast(raw); - - object.* = .{ - .base = .{ - .tag = .content_path, - .is_marked = false, - .node = .{}, - }, - .name = options.name, - .arity = options.arity, - .locals_count = options.locals_count, - .const_pool = options.const_pool, - .bytes = options.bytes, - }; - - story.gc_objects.prepend(&object.base.node); - return object; - } - - pub fn destroy(obj: *ContentPath, story: *Story) void { - const gpa = story.allocator; - gpa.free(obj.const_pool); - gpa.free(obj.bytes); - - const alloc_len = @sizeOf(ContentPath); - const base: [*]align(@alignOf(ContentPath)) u8 = @ptrCast(obj); - gpa.free(base[0..alloc_len]); - } - }; -};