//! 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 = false, node: std.SinglyLinkedList.Node = .{}, pub const Tag = enum { string, code, knot, pub fn tagBytes(tag: Tag) []const u8 { return switch (tag) { .string => "String", .code => "Code", .knot => "Knot", }; } pub fn ObjectType(comptime tag: Tag) type { return switch (tag) { .string => Object.String, .code => Object.Code, .knot => Object.Knot, }; } }; pub fn eql(lhs: *Object, rhs: *Object) bool { if (lhs.tag != rhs.tag) return false; return switch (lhs.tag) { .string => blk: { const lhs_object: *Object.String = @ptrCast(lhs); const rhs_object: *Object.String = @ptrCast(rhs); break :blk std.mem.eql(u8, lhs_object.toSlice(), rhs_object.toSlice()); }, .code => |_| false, .knot => |_| false, }; } pub fn destroy(obj: *Object, story: *Story) void { inline for (comptime std.meta.fields(Tag)) |field| { const tag = @field(Tag, field.name); if (obj.tag == tag) { const typed_obj: *Tag.ObjectType(tag) = @alignCast(@fieldParentPtr("base", obj)); typed_obj.destroy(story); return; } } unreachable; } 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 const Options = struct { bytes: []const u8, }; pub fn create(story: *Story, options: Options) error{OutOfMemory}!*Object.String { const gpa = story.allocator; const alloc_len = @sizeOf(Type) + options.bytes.len + 1; const raw = try gpa.alignedAlloc(u8, .of(Type), alloc_len); const object: *Type = @ptrCast(raw); object.* = .{ .base = .{ .tag = .string }, .hash = 0, .length = options.bytes.len, .bytes = undefined, }; // Point bytes slice to the memory *after* the struct const buf = raw[@sizeOf(Type)..][0 .. options.bytes.len + 1]; object.bytes = buf.ptr; @memcpy(buf[0..options.bytes.len], options.bytes); buf[options.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 toSlice(obj: *const Object.String) []const u8 { return obj.bytes[0..obj.length]; } pub fn fromValue(story: *Story, value: Story.Value) !*Object.String { const print_buffer_len = 64; var print_buffer: [print_buffer_len]u8 = undefined; switch (value) { .nil, .bool, .int, .float => { const bytes = try std.fmt.bufPrint(&print_buffer, "{f}", .{value}); return .create(story, .{ .bytes = bytes }); }, .object => |object| switch (object.tag) { .string => return @ptrCast(object), else => return error.TypeError, }, } } 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 = bytes }); } }; /// Immutable object type for code chunks. pub const Code = struct { base: Object, /// Number of arguments. args_count: u32, /// Number of local variables. locals_count: u32, /// Stack size required to load. stack_size: u32, /// Table of global constant indexes. constants: []const u8, /// Raw compiled bytecode. bytecode: []const u8, pub const Options = struct { args_count: u32, locals_count: u32, stack_size: u32, constants: []const u8, code_bytes: []const u8, }; const Type = Code; pub fn create(story: *Story, options: Options) error{OutOfMemory}!*Object.Code { const gpa = story.allocator; const raw = try gpa.alignedAlloc(u8, .of(Type), @sizeOf(Type)); const obj: *Type = @ptrCast(raw); obj.* = .{ .base = .{ .tag = .code }, .args_count = options.args_count, .locals_count = options.locals_count, .stack_size = options.stack_size, .constants = options.constants, .bytecode = options.code_bytes, }; story.gc_objects.prepend(&obj.base.node); return obj; } pub fn destroy(obj: *Code, story: *Story) void { const gpa = story.allocator; gpa.free(obj.constants); gpa.free(obj.bytecode); const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj); gpa.free(base[0..@sizeOf(Type)]); } }; pub const Knot = struct { base: Object, /// Pointer to the name of the knot. name: *Object.String, /// Pointer to the code object for the knot. code: *Object.Code, members: std.StringHashMapUnmanaged(*Object) = .empty, pub const Options = struct { name: []const u8, code: *Object.Code, }; const Type = Knot; pub fn create(story: *Story, options: Options) error{OutOfMemory}!*Object.Knot { const gpa = story.allocator; const raw = try gpa.alignedAlloc(u8, .of(Type), @sizeOf(Type)); const obj: *Type = @ptrCast(raw); obj.* = .{ .base = .{ .tag = .knot }, .name = try .create(story, .{ .bytes = options.name, }), .code = options.code, .members = .empty, }; story.gc_objects.prepend(&obj.base.node); return obj; } pub fn destroy(obj: *Knot, story: *Story) void { const gpa = story.allocator; obj.members.deinit(gpa); const alloc_len = @sizeOf(Type); const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj); gpa.free(base[0..alloc_len]); } };