229 lines
6.7 KiB
Zig
229 lines
6.7 KiB
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.
|
|
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]);
|
|
}
|
|
};
|