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 { // NOTE: 20 bytes should be enough. const print_buffer_len = 20; var print_buffer: [print_buffer_len]u8 = undefined; switch (object.tag) { .number => { const number_object: *Object.Number = @ptrCast(object); const number_bytes = try std.fmt.bufPrint(&print_buffer, "{}", .{ number_object.data.floating, }); 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, locals_count: usize, const_pool: []*Object, bytes: []const u8, pub fn create( story: *Story, name: *Object.String, arity: usize, locals_count: usize, const_pool: []*Object, bytes: []const u8, ) 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 = name, .arity = arity, .locals_count = locals_count, .const_pool = const_pool, .bytes = 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]); } }; };