ink/src/Story/object.zig

309 lines
11 KiB
Zig

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]);
}
};
};