kym-tables-overhaul #30
|
@ -2,6 +2,8 @@ const debug = @import("./debug.zig");
|
|||
|
||||
const math = @import("./math.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const AllocationError = error {
|
||||
OutOfMemory,
|
||||
};
|
||||
|
@ -213,6 +215,27 @@ pub fn allocate_copy(allocator: Allocator, source: []const Byte) AllocationError
|
|||
return allocation;
|
||||
}
|
||||
|
||||
pub fn allocate_many(allocator: Allocator, count: usize, value: anytype) AllocationError![]@TypeOf(value) {
|
||||
const Type = @TypeOf(value);
|
||||
const typeSize = @sizeOf(Type);
|
||||
|
||||
if (typeSize == 0) {
|
||||
@compileError("Cannot allocate memory for 0-byte sized type " ++ @typeName(Type));
|
||||
}
|
||||
|
||||
const allocations = @as([*]Type, @ptrCast(@alignCast(try allocator.actions.reallocate(
|
||||
allocator.context,
|
||||
@returnAddress(),
|
||||
null,
|
||||
typeSize * count))))[0 .. count];
|
||||
|
||||
for (allocations) |*allocation| {
|
||||
allocation.* = value;
|
||||
}
|
||||
|
||||
return allocations;
|
||||
}
|
||||
|
||||
pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@TypeOf(value) {
|
||||
const Type = @TypeOf(value);
|
||||
const typeSize = @sizeOf(Type);
|
||||
|
@ -265,6 +288,16 @@ pub fn compare(this: []const Byte, that: []const Byte) isize {
|
|||
return @as(isize, @intCast(this.len)) - @as(isize, @intCast(that.len));
|
||||
}
|
||||
|
||||
pub fn djb2_hash(comptime int: std.builtin.Type.Int, target: []const Byte) math.Int(int) {
|
||||
var hash_code = @as(math.Int(int), 5381);
|
||||
|
||||
for (target) |byte| {
|
||||
hash_code = ((hash_code << 5) +% hash_code) +% byte;
|
||||
}
|
||||
|
||||
return hash_code;
|
||||
}
|
||||
|
||||
pub fn ends_with(target: []const Byte, match: []const Byte) bool {
|
||||
if (target.len < match.len) {
|
||||
return false;
|
||||
|
|
|
@ -41,6 +41,10 @@ pub fn Stack(comptime Value: type) type {
|
|||
self.capacity = grown_capacity;
|
||||
}
|
||||
|
||||
pub fn is_empty(self: Self) bool {
|
||||
return self.values.len == 0;
|
||||
}
|
||||
|
||||
pub fn make(allocator: io.Allocator) Self {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
|
|
|
@ -104,13 +104,20 @@ pub fn Slab(comptime Value: type) type {
|
|||
}
|
||||
|
||||
pub fn StringTable(comptime Value: type) type {
|
||||
return Table([]const io.Byte, Value, .{
|
||||
.hash = hash_string,
|
||||
.match = io.equals,
|
||||
return Table([]const io.Byte, Value, struct {
|
||||
const Self = @This();
|
||||
|
||||
fn equals(_: Self, key_a: []const io.Byte, key_b: []const io.Byte) bool {
|
||||
return io.equals(key_a, key_b);
|
||||
}
|
||||
|
||||
fn hash(_: Self, key: []const io.Byte) usize {
|
||||
return io.djb2_hash(@typeInfo(usize).Int, key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTraits(Key)) type {
|
||||
pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) type {
|
||||
const load_max = 0.75;
|
||||
const hash_int = @typeInfo(usize).Int;
|
||||
const max_int = math.max_int(hash_int);
|
||||
|
@ -118,6 +125,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
|
||||
return struct {
|
||||
allocator: io.Allocator,
|
||||
traits: Traits,
|
||||
count: usize,
|
||||
entries: []?Entry,
|
||||
|
||||
|
@ -125,24 +133,26 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
key: Key,
|
||||
value: Value,
|
||||
|
||||
fn write_into(self: Entry, entry_table: []?Entry) bool {
|
||||
const hash_max = @min(max_int, entry_table.len);
|
||||
var hashed_key = math.wrap(traits.hash(self.key), min_int, hash_max);
|
||||
fn write_into(self: Entry, table: *Self) bool {
|
||||
const hash_max = @min(max_int, table.entries.len);
|
||||
var hashed_key = math.wrap(table.traits.hash(self.key), min_int, hash_max);
|
||||
var iterations = @as(usize, 0);
|
||||
|
||||
while (true) : (iterations += 1) {
|
||||
debug.assert(iterations < entry_table.len);
|
||||
debug.assert(iterations < table.entries.len);
|
||||
|
||||
const table_entry = &(entry_table[hashed_key] orelse {
|
||||
entry_table[hashed_key] = .{
|
||||
const table_entry = &(table.entries[hashed_key] orelse {
|
||||
table.entries[hashed_key] = .{
|
||||
.key = self.key,
|
||||
.value = self.value,
|
||||
};
|
||||
|
||||
table.count += 1;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (traits.match(table_entry.key, self.key)) {
|
||||
if (table.traits.equals(table_entry.key, self.key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -179,12 +189,12 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
|
||||
pub fn remove(self: *Self, key: Key) ?Entry {
|
||||
const hash_max = @min(max_int, self.entries.len);
|
||||
var hashed_key = math.wrap(traits.hash(key), min_int, hash_max);
|
||||
var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max);
|
||||
|
||||
while (true) {
|
||||
const entry = &(self.entries[hashed_key] orelse continue);
|
||||
|
||||
if (traits.match(entry.key, key)) {
|
||||
if (self.traits.equals(entry.key, key)) {
|
||||
const original_entry = entry.*;
|
||||
|
||||
self.entries[hashed_key] = null;
|
||||
|
@ -203,7 +213,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
|
||||
{
|
||||
const hash_max = @min(max_int, self.entries.len);
|
||||
var hashed_key = math.wrap(traits.hash(key), min_int, hash_max);
|
||||
var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max);
|
||||
|
||||
while (true) {
|
||||
const entry = &(self.entries[hashed_key] orelse {
|
||||
|
@ -217,7 +227,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
return null;
|
||||
});
|
||||
|
||||
if (traits.match(entry.key, key)) {
|
||||
if (self.traits.equals(entry.key, key)) {
|
||||
const original_entry = entry.*;
|
||||
|
||||
entry.* = .{
|
||||
|
@ -261,14 +271,12 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
|
||||
debug.assert(self.entries.len > self.count);
|
||||
|
||||
defer self.count += 1;
|
||||
|
||||
const entry = Entry{
|
||||
.key = key,
|
||||
.value = value,
|
||||
};
|
||||
|
||||
return entry.write_into(self.entries);
|
||||
return entry.write_into(self);
|
||||
}
|
||||
|
||||
pub fn lookup(self: Self, key: Key) ?Value {
|
||||
|
@ -277,13 +285,13 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
}
|
||||
|
||||
const hash_max = @min(max_int, self.entries.len);
|
||||
var hashed_key = math.wrap(traits.hash(key), min_int, hash_max);
|
||||
var hashed_key = math.wrap(self.traits.hash(key), min_int, hash_max);
|
||||
var iterations = @as(usize, 0);
|
||||
|
||||
while (iterations < self.count) : (iterations += 1) {
|
||||
const entry = &(self.entries[hashed_key] orelse return null);
|
||||
|
||||
if (traits.match(entry.key, key)) {
|
||||
if (self.traits.equals(entry.key, key)) {
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
|
@ -293,11 +301,12 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
return null;
|
||||
}
|
||||
|
||||
pub fn make(allocator: io.Allocator) Self {
|
||||
pub fn make(allocator: io.Allocator, traits: Traits) Self {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.count = 0,
|
||||
.entries = &.{},
|
||||
.traits = traits,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -306,46 +315,26 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
|
|||
return;
|
||||
}
|
||||
|
||||
var table = make(self.allocator, self.traits);
|
||||
|
||||
errdefer table.free();
|
||||
|
||||
table.entries = allocate: {
|
||||
const min_count = @max(1, self.count);
|
||||
const table_size = min_count * 2;
|
||||
const allocation = @as([*]?Entry, @ptrCast(@alignCast(try self.allocator.reallocate(null, @sizeOf(?Entry) * table_size))))[0 .. table_size];
|
||||
|
||||
errdefer self.allocator.deallocate(allocation);
|
||||
break: allocate try io.allocate_many(self.allocator, table_size, @as(?Entry, null));
|
||||
};
|
||||
|
||||
self.entries = replace_table: {
|
||||
for (allocation) |*entry| {
|
||||
entry.* = null;
|
||||
}
|
||||
|
||||
if (self.entries.len != 0) {
|
||||
for (self.entries) |maybe_entry| {
|
||||
if (maybe_entry) |entry| {
|
||||
debug.assert(entry.write_into(allocation));
|
||||
debug.assert(entry.write_into(&table));
|
||||
}
|
||||
}
|
||||
|
||||
self.allocator.deallocate(self.entries);
|
||||
}
|
||||
self.free();
|
||||
|
||||
break: replace_table allocation;
|
||||
};
|
||||
self.* = table;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn TableTraits(comptime Key: type) type {
|
||||
return struct {
|
||||
hash: fn (key: Key) usize,
|
||||
match: fn (self: Key, other: Key) bool,
|
||||
};
|
||||
}
|
||||
|
||||
fn hash_string(key: []const io.Byte) usize {
|
||||
var hash_code = @as(usize, 5381);
|
||||
|
||||
for (key) |byte| {
|
||||
hash_code = ((hash_code << 5) +% hash_code) +% byte;
|
||||
}
|
||||
|
||||
return hash_code;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,24 @@ pub fn clamp(value: anytype, lower: anytype, upper: anytype) @TypeOf(value, lowe
|
|||
return @max(lower, @min(upper, value));
|
||||
}
|
||||
|
||||
pub fn checked_add(a: anytype, b: anytype) ?@TypeOf(a + b) {
|
||||
const result = @addWithOverflow(a, b);
|
||||
|
||||
return if (result.@"1" == 0) result.@"0" else null;
|
||||
}
|
||||
|
||||
pub fn checked_mul(a: anytype, b: anytype) ?@TypeOf(a * b) {
|
||||
const result = @mulWithOverflow(a, b);
|
||||
|
||||
return if (result.@"1" == 0) result.@"0" else null;
|
||||
}
|
||||
|
||||
pub fn checked_sub(a: anytype, b: anytype) ?@TypeOf(a - b) {
|
||||
const result = @subWithOverflow(a, b);
|
||||
|
||||
return if (result.@"1" == 0) result.@"0" else null;
|
||||
}
|
||||
|
||||
pub fn clamped_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) Int(dest_int) {
|
||||
const Value = @TypeOf(value);
|
||||
|
||||
|
@ -17,6 +35,8 @@ pub fn clamped_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) Int
|
|||
.unsigned => @intCast(@min(value, max_int(dest_int))),
|
||||
},
|
||||
|
||||
.Float => @intFromFloat(clamp(value, min_int(dest_int), max_int(dest_int))),
|
||||
|
||||
else => @compileError("`" ++ @typeName(Value) ++ "` cannot be cast to an int"),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -33,9 +33,10 @@ pub const DecimalFormat = struct {
|
|||
|
||||
switch (code) {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
||||
result = try math.checked_add(
|
||||
try math.checked_mul(result, radix),
|
||||
try math.checked_sub(code, '0'));
|
||||
const offset_code = math.checked_sub(code, '0') orelse return null;
|
||||
|
||||
result = math.checked_mul(result, radix) orelse return null;
|
||||
result = math.checked_add(result, offset_code) orelse return null;
|
||||
},
|
||||
|
||||
else => {
|
||||
|
|
|
@ -10,12 +10,13 @@ const file = @import("./file.zig");
|
|||
|
||||
pub const Any = union (enum) {
|
||||
boolean: bool,
|
||||
number: Float,
|
||||
fixed: Fixed,
|
||||
float: Float,
|
||||
string: []const coral.io.Byte,
|
||||
symbol: []const coral.io.Byte,
|
||||
dynamic: *DynamicObject,
|
||||
dynamic: *const DynamicObject,
|
||||
|
||||
pub fn expect_dynamic(self: Any) ?*DynamicObject {
|
||||
pub fn expect_dynamic(self: Any) ?*const DynamicObject {
|
||||
return switch (self) {
|
||||
.dynamic => |dynamic| dynamic,
|
||||
else => null,
|
||||
|
@ -24,7 +25,8 @@ pub const Any = union (enum) {
|
|||
|
||||
pub fn expect_number(self: Any) ?Float {
|
||||
return switch (self) {
|
||||
.number => |number| number,
|
||||
.fixed => |fixed| @floatFromInt(fixed),
|
||||
.float => |float| float,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
@ -39,21 +41,15 @@ pub const Any = union (enum) {
|
|||
|
||||
pub const Caller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv);
|
||||
|
||||
pub const Context = struct {
|
||||
env: *RuntimeEnv,
|
||||
userdata: []coral.io.Byte
|
||||
};
|
||||
|
||||
pub const DynamicObject = struct {
|
||||
userdata: []coral.io.Byte,
|
||||
typeinfo: *const Typeinfo,
|
||||
|
||||
pub fn as_caller(self: *DynamicObject) Caller {
|
||||
return Caller.bind(DynamicObject, self, DynamicObject.call);
|
||||
}
|
||||
|
||||
fn call(self: *DynamicObject, env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
|
||||
return self.typeinfo.call(env);
|
||||
return self.typeinfo.call(.{
|
||||
.env = env,
|
||||
.userdata = self.userdata,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -64,6 +60,8 @@ pub const ErrorInfo = struct {
|
|||
frames: []const Frame,
|
||||
};
|
||||
|
||||
pub const Fixed = i32;
|
||||
|
||||
pub const Float = f64;
|
||||
|
||||
pub const Frame = struct {
|
||||
|
@ -72,6 +70,11 @@ pub const Frame = struct {
|
|||
locals_top: usize,
|
||||
};
|
||||
|
||||
pub const Method = struct {
|
||||
env: *RuntimeEnv,
|
||||
userdata: []coral.io.Byte,
|
||||
};
|
||||
|
||||
pub const RuntimeEnv = struct {
|
||||
interned_symbols: SymbolTable,
|
||||
allocator: coral.io.Allocator,
|
||||
|
@ -95,7 +98,8 @@ pub const RuntimeEnv = struct {
|
|||
object: union (enum) {
|
||||
false,
|
||||
true,
|
||||
number: Float,
|
||||
float: Float,
|
||||
fixed: Fixed,
|
||||
string: []coral.io.Byte,
|
||||
symbol: []coral.io.Byte,
|
||||
dynamic: *DynamicObject,
|
||||
|
@ -128,9 +132,9 @@ pub const RuntimeEnv = struct {
|
|||
|
||||
pub fn call(
|
||||
self: *RuntimeEnv,
|
||||
name: []const coral.io.Byte,
|
||||
arg_count: u8,
|
||||
caller: Caller,
|
||||
arg_count: u8,
|
||||
name: []const coral.io.Byte,
|
||||
) RuntimeError!?*RuntimeRef {
|
||||
try self.frames.push_one(.{
|
||||
.name = name,
|
||||
|
@ -159,6 +163,13 @@ pub const RuntimeEnv = struct {
|
|||
return caller.invoke(self);
|
||||
}
|
||||
|
||||
pub fn callable(self: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!Caller {
|
||||
return switch ((self.ref_values.lookup(@intFromPtr(ref)) orelse unreachable).object) {
|
||||
.dynamic => |dynamic| Caller.bind(DynamicObject, dynamic, DynamicObject.call),
|
||||
else => self.raise(error.TypeMismatch, "object is not callable"),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void {
|
||||
const key = @intFromPtr(ref orelse return);
|
||||
var ref_data = self.ref_values.remove(key) orelse unreachable;
|
||||
|
@ -169,9 +180,7 @@ pub const RuntimeEnv = struct {
|
|||
|
||||
if (ref_data.ref_count == 0) {
|
||||
switch (ref_data.object) {
|
||||
.false => {},
|
||||
.true => {},
|
||||
.number => {},
|
||||
.false, .true, .float, .fixed => {},
|
||||
.string => |string| self.allocator.deallocate(string),
|
||||
.symbol => |symbol| self.allocator.deallocate(symbol),
|
||||
|
||||
|
@ -222,7 +231,7 @@ pub const RuntimeEnv = struct {
|
|||
|
||||
try chunk.compile_ast(ast);
|
||||
|
||||
return self.call(name, 0, chunk.as_caller());
|
||||
return self.call(chunk.as_caller(), 0, name);
|
||||
}
|
||||
|
||||
pub fn free(self: *RuntimeEnv) void {
|
||||
|
@ -245,6 +254,17 @@ pub const RuntimeEnv = struct {
|
|||
self.interned_symbols.free();
|
||||
}
|
||||
|
||||
pub fn get_dynamic(
|
||||
self: *RuntimeEnv,
|
||||
indexable: *const DynamicObject,
|
||||
index_ref: *const RuntimeRef,
|
||||
) RuntimeError!?*RuntimeRef {
|
||||
return indexable.typeinfo.get(.{
|
||||
.env = self,
|
||||
.userdata = indexable.userdata,
|
||||
}, index_ref);
|
||||
}
|
||||
|
||||
pub fn get_local(self: *RuntimeEnv, local: u8) RuntimeError!?*RuntimeRef {
|
||||
if (local >= self.local_refs.values.len) {
|
||||
return self.raise(error.IllegalState, "out of bounds local get");
|
||||
|
@ -258,14 +278,34 @@ pub const RuntimeEnv = struct {
|
|||
}
|
||||
|
||||
pub fn push_boolean(self: *RuntimeEnv, boolean: bool) RuntimeError!void {
|
||||
if (self.frames.is_empty()) {
|
||||
return self.raise(error.IllegalState, "attempt to push boolean outside a call frame");
|
||||
}
|
||||
|
||||
return self.local_refs.push_one(try self.new_boolean(boolean));
|
||||
}
|
||||
|
||||
pub fn push_number(self: *RuntimeEnv, number: Float) RuntimeError!void {
|
||||
return self.local_refs.push_one(try self.new_number(number));
|
||||
pub fn push_fixed(self: *RuntimeEnv, fixed: Fixed) RuntimeError!void {
|
||||
if (self.frames.is_empty()) {
|
||||
return self.raise(error.IllegalState, "attempt to push fixed outside a call frame");
|
||||
}
|
||||
|
||||
return self.local_refs.push_one(try self.new_fixed(fixed));
|
||||
}
|
||||
|
||||
pub fn push_float(self: *RuntimeEnv, float: Float) RuntimeError!void {
|
||||
if (self.frames.is_empty()) {
|
||||
return self.raise(error.IllegalState, "attempt to push float outside a call frame");
|
||||
}
|
||||
|
||||
return self.local_refs.push_one(try self.new_float(float));
|
||||
}
|
||||
|
||||
pub fn push_ref(self: *RuntimeEnv, ref: ?*RuntimeRef) RuntimeError!void {
|
||||
if (self.frames.is_empty()) {
|
||||
return self.raise(error.IllegalState, "attempt to push ref outside a call frame");
|
||||
}
|
||||
|
||||
return self.local_refs.push_one(if (ref) |live_ref| self.acquire(live_ref) else null);
|
||||
}
|
||||
|
||||
|
@ -282,8 +322,8 @@ pub const RuntimeEnv = struct {
|
|||
.local_refs = RefStack.make(allocator),
|
||||
.ref_values = RefSlab.make(allocator),
|
||||
.frames = FrameStack.make(allocator),
|
||||
.syscallers = SyscallerTable.make(allocator),
|
||||
.interned_symbols = SymbolTable.make(allocator),
|
||||
.syscallers = SyscallerTable.make(allocator, .{}),
|
||||
.interned_symbols = SymbolTable.make(allocator, .{}),
|
||||
.error_handler = error_handler,
|
||||
.allocator = allocator,
|
||||
};
|
||||
|
@ -318,10 +358,17 @@ pub const RuntimeEnv = struct {
|
|||
}));
|
||||
}
|
||||
|
||||
pub fn new_number(self: *RuntimeEnv, value: Float) RuntimeError!*RuntimeRef {
|
||||
pub fn new_fixed(self: *RuntimeEnv, value: Fixed) RuntimeError!*RuntimeRef {
|
||||
return @ptrFromInt(try self.ref_values.insert(.{
|
||||
.ref_count = 1,
|
||||
.object = .{.number = value},
|
||||
.object = .{.fixed = value},
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn new_float(self: *RuntimeEnv, value: Float) RuntimeError!*RuntimeRef {
|
||||
return @ptrFromInt(try self.ref_values.insert(.{
|
||||
.ref_count = 1,
|
||||
.object = .{.float = value},
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -368,19 +415,16 @@ pub const RuntimeEnv = struct {
|
|||
return error_value;
|
||||
}
|
||||
|
||||
pub fn syscaller(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Caller {
|
||||
return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to call undefined syscall");
|
||||
pub fn syscallable(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Caller {
|
||||
return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to get undefined syscall");
|
||||
}
|
||||
|
||||
pub fn unbox(self: *RuntimeEnv, ref: *const RuntimeRef) Any {
|
||||
const ref_data = self.ref_values.lookup(@intFromPtr(ref));
|
||||
|
||||
coral.debug.assert(ref_data != null);
|
||||
|
||||
return switch (ref_data.?.object) {
|
||||
return switch ((self.ref_values.lookup(@intFromPtr(ref)) orelse unreachable).object) {
|
||||
.false => .{.boolean = false},
|
||||
.true => .{.boolean = true},
|
||||
.number => |number| .{.number = number},
|
||||
.fixed => |fixed| .{.fixed = fixed},
|
||||
.float => |float| .{.float = float},
|
||||
.string => |string| .{.string = string},
|
||||
.symbol => |symbol| .{.symbol = symbol},
|
||||
.dynamic => |dynamic| .{.dynamic = dynamic},
|
||||
|
@ -409,156 +453,99 @@ pub const RuntimeRef = opaque {};
|
|||
|
||||
pub const Typeinfo = struct {
|
||||
name: []const coral.io.Byte,
|
||||
call: *const fn (context: Context) RuntimeError!?*RuntimeRef = default_call,
|
||||
clean: *const fn (context: Context) void = default_clean,
|
||||
get: *const fn (context: Context, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get,
|
||||
set: *const fn (context: Context, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set,
|
||||
test_difference: *const fn (context: Context, other_ref: *const RuntimeRef) RuntimeError!Float = default_test_difference,
|
||||
test_equality: *const fn (context: Context, other_ref: *const RuntimeRef) RuntimeError!bool = default_test_equality,
|
||||
call: ?*const fn (method: Method) RuntimeError!?*RuntimeRef = null,
|
||||
clean: *const fn (method: Method) void = default_clean,
|
||||
get: *const fn (method: Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get,
|
||||
set: *const fn (method: Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set,
|
||||
|
||||
fn default_call(context: Context) RuntimeError!?*RuntimeRef {
|
||||
return context.env.raise(error.TypeMismatch, "object is not callable");
|
||||
}
|
||||
|
||||
fn default_clean(_: Context) void {
|
||||
fn default_clean(_: Method) void {
|
||||
// Nothing to clean by default.
|
||||
}
|
||||
|
||||
fn default_get(context: Context, _: *const RuntimeRef) RuntimeError!?*RuntimeRef {
|
||||
return context.env.raise(error.TypeMismatch, "object is not indexable");
|
||||
fn default_get(method: Method, _: *const RuntimeRef) RuntimeError!?*RuntimeRef {
|
||||
return method.env.raise(error.TypeMismatch, "object is not indexable");
|
||||
}
|
||||
|
||||
fn default_set(context: Context, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void {
|
||||
return context.env.raise(error.TypeMismatch, "object is not indexable");
|
||||
}
|
||||
|
||||
fn default_test_difference(context: Context, _: *const RuntimeRef) RuntimeError!Float {
|
||||
return context.env.raise(error.TypeMismatch, "object is not comparable");
|
||||
}
|
||||
|
||||
fn default_test_equality(context: Context, other_ref: *const RuntimeRef) RuntimeError!bool {
|
||||
return switch (context.env.unbox(other_ref)) {
|
||||
.dynamic => |dynamic| context.userdata.ptr == dynamic.userdata.ptr,
|
||||
else => false,
|
||||
};
|
||||
fn default_set(method: Method, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void {
|
||||
return method.env.raise(error.TypeMismatch, "object is not indexable");
|
||||
}
|
||||
};
|
||||
|
||||
pub fn call(
|
||||
env: *RuntimeEnv,
|
||||
caller_ref: ?*const RuntimeRef,
|
||||
callable_ref: *const RuntimeRef,
|
||||
arg_refs: []const ?*const RuntimeRef,
|
||||
) RuntimeError!?*RuntimeRef {
|
||||
for (arg_refs) |arg_ref| {
|
||||
try env.local_push_ref(arg_ref);
|
||||
}
|
||||
|
||||
env.frame_push("", arg_refs.len);
|
||||
|
||||
defer env.frame_pop();
|
||||
|
||||
const dynamic = try env.unbox(callable_ref).expect_dynamic();
|
||||
|
||||
return dynamic.type_info.call(.{
|
||||
.env = env,
|
||||
.caller = caller_ref,
|
||||
.userdata = dynamic.userdata,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get(
|
||||
env: *RuntimeEnv,
|
||||
indexable_ref: *const RuntimeRef,
|
||||
index_ref: *const RuntimeRef,
|
||||
) RuntimeError!?*RuntimeRef {
|
||||
const dynamic = try unbox_dynamic(env, indexable_ref);
|
||||
|
||||
return dynamic.typeinfo.get(.{
|
||||
.userdata = dynamic.userdata,
|
||||
.env = env,
|
||||
}, index_ref);
|
||||
}
|
||||
|
||||
pub fn get_field(
|
||||
env: *RuntimeEnv,
|
||||
indexable_ref: *const RuntimeRef,
|
||||
field_name: []const coral.io.Byte,
|
||||
) RuntimeError!?*RuntimeRef {
|
||||
const field_name_ref = try env.new_string(field_name);
|
||||
const field_name_ref = try env.new_symbol(field_name);
|
||||
|
||||
defer env.discard(field_name_ref);
|
||||
|
||||
return get(env, indexable_ref, field_name_ref);
|
||||
return env.get_dynamic(try unbox_dynamic(env, indexable_ref), field_name_ref);
|
||||
}
|
||||
|
||||
pub fn set(
|
||||
env: *RuntimeEnv,
|
||||
indexable_ref: *const RuntimeRef,
|
||||
index_ref: *const RuntimeRef,
|
||||
value_ref: ?*const RuntimeRef,
|
||||
) RuntimeError!void {
|
||||
const dynamic = try unbox_dynamic(env, indexable_ref);
|
||||
|
||||
return dynamic.typeinfo.set(.{
|
||||
.userdata = dynamic.userdata,
|
||||
.env = env,
|
||||
.index_ref = index_ref,
|
||||
}, value_ref);
|
||||
}
|
||||
|
||||
pub fn set_field(
|
||||
env: *RuntimeEnv,
|
||||
indexable_ref: *const RuntimeRef,
|
||||
field_name: []const coral.io.Byte,
|
||||
value_ref: ?*const RuntimeRef,
|
||||
) RuntimeError!void {
|
||||
const field_name_ref = try env.new_string(field_name);
|
||||
|
||||
defer env.discard(field_name_ref);
|
||||
|
||||
return set(env, indexable_ref, field_name_ref, value_ref);
|
||||
pub fn hash(env: *RuntimeEnv, ref: *const RuntimeRef) usize {
|
||||
return switch (env.unbox(ref)) {
|
||||
.boolean => 0,
|
||||
.float => 0,
|
||||
.fixed => 0,
|
||||
.string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string),
|
||||
.symbol => 0,
|
||||
.dynamic => |dynamic| @intFromPtr(dynamic),
|
||||
};
|
||||
}
|
||||
|
||||
pub const new_table = Table.new;
|
||||
|
||||
pub fn test_difference(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *const RuntimeRef) RuntimeError!Float {
|
||||
return switch (env.unbox(lhs_ref)) {
|
||||
.boolean => env.raise(error.TypeMismatch, "cannot compare boolean objects"),
|
||||
pub fn test_difference(
|
||||
env: *RuntimeEnv,
|
||||
lhs_comparable_ref: *const RuntimeRef,
|
||||
rhs_comparable_ref: *const RuntimeRef,
|
||||
) RuntimeError!Float {
|
||||
return switch (env.unbox(lhs_comparable_ref)) {
|
||||
.boolean => env.raise(error.TypeMismatch, "boolean objects are not comparable"),
|
||||
|
||||
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| rhs_number - lhs_number,
|
||||
else => env.raise(error.TypeMismatch, "right-hand object is not comparable with number objects"),
|
||||
.fixed => |lhs_fixed| switch (env.unbox(rhs_comparable_ref)) {
|
||||
.fixed => |rhs_fixed| @as(Float, @floatFromInt(rhs_fixed)) - @as(Float, @floatFromInt(lhs_fixed)),
|
||||
else => env.raise(error.TypeMismatch, "right-hand object is not comparable with fixed objects"),
|
||||
},
|
||||
|
||||
.string => |lhs_string| switch (env.unbox(rhs_ref)) {
|
||||
.float => |lhs_float| switch (env.unbox(rhs_comparable_ref)) {
|
||||
.float => |rhs_float| rhs_float - lhs_float,
|
||||
else => env.raise(error.TypeMismatch, "right-hand object is not comparable with float objects"),
|
||||
},
|
||||
|
||||
.string => |lhs_string| switch (env.unbox(rhs_comparable_ref)) {
|
||||
.string => |rhs_string| @floatFromInt(coral.io.compare(lhs_string, rhs_string)),
|
||||
.symbol => |rhs_symbol| @floatFromInt(coral.io.compare(lhs_string, rhs_symbol)),
|
||||
else => env.raise(error.TypeMismatch, "right-hand object is not comparable with string objects"),
|
||||
},
|
||||
|
||||
.symbol => |lhs_symbol| switch (env.unbox(rhs_ref)) {
|
||||
.symbol => |lhs_symbol| switch (env.unbox(rhs_comparable_ref)) {
|
||||
.symbol => env.raise(error.TypeMismatch, "cannot compare symbol objects"),
|
||||
.string => |rhs_string| @floatFromInt(coral.io.compare(lhs_symbol, rhs_string)),
|
||||
else => env.raise(error.TypeMismatch, "right-hand object is not comparable with symbol objects"),
|
||||
},
|
||||
|
||||
.dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_difference(.{
|
||||
.env = env,
|
||||
.userdata = lhs_dynamic.userdata,
|
||||
}, rhs_ref),
|
||||
.dynamic => env.raise(error.TypeMismatch, "dynamic objects are not comparable"),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn test_equality(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *const RuntimeRef) RuntimeError!bool {
|
||||
pub fn test_equality(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *const RuntimeRef) bool {
|
||||
return switch (env.unbox(lhs_ref)) {
|
||||
.boolean => |lhs_boolean| switch (env.unbox(rhs_ref)) {
|
||||
.boolean => |rhs_boolean| rhs_boolean == lhs_boolean,
|
||||
else => false,
|
||||
},
|
||||
|
||||
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| lhs_number == rhs_number,
|
||||
.fixed => |lhs_fixed| switch (env.unbox(rhs_ref)) {
|
||||
.fixed => |rhs_fixed| rhs_fixed == lhs_fixed,
|
||||
.float => |rhs_float| rhs_float == @as(Float, @floatFromInt(lhs_fixed)),
|
||||
else => false,
|
||||
},
|
||||
|
||||
.float => |lhs_float| switch (env.unbox(rhs_ref)) {
|
||||
.float => |rhs_float| rhs_float == lhs_float,
|
||||
.fixed => |rhs_fixed| @as(Float, @floatFromInt(rhs_fixed)) == lhs_float,
|
||||
else => false,
|
||||
},
|
||||
|
||||
|
@ -574,14 +561,14 @@ pub fn test_equality(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *con
|
|||
else => false,
|
||||
},
|
||||
|
||||
.dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_equality(.{
|
||||
.env = env,
|
||||
.userdata = lhs_dynamic.userdata,
|
||||
}, rhs_ref),
|
||||
.dynamic => |lhs_dynamic| switch (env.unbox(rhs_ref)) {
|
||||
.dynamic => |rhs_dynamic| rhs_dynamic == lhs_dynamic,
|
||||
else => false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn unbox_dynamic(env: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!*DynamicObject {
|
||||
pub fn unbox_dynamic(env: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!*const DynamicObject {
|
||||
return env.unbox(ref).expect_dynamic() orelse env.raise(error.TypeMismatch, "expected dynamic object");
|
||||
}
|
||||
|
||||
|
|
|
@ -25,12 +25,26 @@ const AstCompiler = struct {
|
|||
.false_literal => try self.chunk.append_opcode(.push_false),
|
||||
|
||||
.number_literal => |literal| {
|
||||
const parsed_number = number_format.parse(literal, kym.Float);
|
||||
for (literal) |codepoint| {
|
||||
if (codepoint == '.') {
|
||||
const parsed = number_format.parse(literal, kym.Float);
|
||||
|
||||
coral.debug.assert(parsed_number != null);
|
||||
coral.debug.assert(parsed != null);
|
||||
|
||||
try self.chunk.append_opcode(.{
|
||||
.push_const = try self.chunk.declare_constant_number(parsed_number.?),
|
||||
.push_const = try self.chunk.declare_constant_float(parsed.?),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const parsed = number_format.parse(literal, kym.Fixed);
|
||||
|
||||
coral.debug.assert(parsed != null);
|
||||
|
||||
try self.chunk.append_opcode(.{
|
||||
.push_const = try self.chunk.declare_constant_fixed(parsed.?),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -222,14 +236,30 @@ pub fn compile_ast(self: *Self, ast: Ast) kym.RuntimeError!void {
|
|||
try self.opcodes.pack();
|
||||
}
|
||||
|
||||
pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeError!u16 {
|
||||
pub fn declare_constant_fixed(self: *Self, constant: kym.Fixed) kym.RuntimeError!u16 {
|
||||
const tail = self.constant_refs.values.len;
|
||||
|
||||
if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
|
||||
return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants");
|
||||
}
|
||||
|
||||
const constant_ref = try self.env.new_number(constant);
|
||||
const constant_ref = try self.env.new_fixed(constant);
|
||||
|
||||
errdefer self.env.discard(constant_ref);
|
||||
|
||||
try self.constant_refs.push_one(constant_ref);
|
||||
|
||||
return @intCast(tail);
|
||||
}
|
||||
|
||||
pub fn declare_constant_float(self: *Self, constant: kym.Float) kym.RuntimeError!u16 {
|
||||
const tail = self.constant_refs.values.len;
|
||||
|
||||
if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
|
||||
return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants");
|
||||
}
|
||||
|
||||
const constant_ref = try self.env.new_float(constant);
|
||||
|
||||
errdefer self.env.discard(constant_ref);
|
||||
|
||||
|
@ -333,7 +363,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
|
|||
|
||||
defer env.discard(callable_ref);
|
||||
|
||||
break: call try env.call("", arg_count, (try kym.unbox_dynamic(env, callable_ref)).as_caller());
|
||||
break: call try env.call(try env.callable(callable_ref), arg_count, "");
|
||||
};
|
||||
|
||||
defer env.discard(result_ref);
|
||||
|
@ -351,7 +381,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
|
|||
|
||||
const identifier = try kym.unbox_string(env, identifier_ref);
|
||||
|
||||
break: call try env.call(identifier, arg_count, try env.syscaller(identifier));
|
||||
break: call try env.call(try env.syscallable(identifier), arg_count, identifier);
|
||||
};
|
||||
|
||||
defer env.discard(result_ref);
|
||||
|
@ -366,10 +396,11 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
|
|||
|
||||
defer env.discard(ref);
|
||||
|
||||
try env.push_number(switch (env.unbox(ref)) {
|
||||
.number => |number| -number,
|
||||
switch (env.unbox(ref)) {
|
||||
.fixed => |fixed| try env.push_fixed(-fixed),
|
||||
.float => |float| try env.push_float(-float),
|
||||
else => return env.raise(error.TypeMismatch, "object is not scalar negatable"),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
.not => {
|
||||
|
@ -398,14 +429,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
|
|||
|
||||
defer env.discard(lhs_ref);
|
||||
|
||||
try env.push_ref(try switch (env.unbox(lhs_ref)) {
|
||||
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| env.new_number(lhs_number + rhs_number),
|
||||
else => return env.raise(error.TypeMismatch, "right-hand object is not addable"),
|
||||
try switch (env.unbox(lhs_ref)) {
|
||||
.float => |lhs_float| switch (env.unbox(rhs_ref)) {
|
||||
.float => |rhs_float| env.push_float(lhs_float + rhs_float),
|
||||
else => env.raise(error.TypeMismatch, "right-hand object is not addable"),
|
||||
},
|
||||
|
||||
else => return env.raise(error.TypeMismatch, "left-hand object is not addable"),
|
||||
});
|
||||
else => env.raise(error.TypeMismatch, "left-hand object is not addable"),
|
||||
};
|
||||
},
|
||||
|
||||
.sub => {
|
||||
|
@ -421,14 +452,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
|
|||
|
||||
defer env.discard(lhs_ref);
|
||||
|
||||
try env.push_ref(try switch (env.unbox(lhs_ref)) {
|
||||
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| env.new_number(lhs_number - rhs_number),
|
||||
else => return env.raise(error.TypeMismatch, "right-hand object is not subtractable"),
|
||||
try switch (env.unbox(lhs_ref)) {
|
||||
.float => |lhs_float| switch (env.unbox(rhs_ref)) {
|
||||
.float => |rhs_float| env.push_float(lhs_float - rhs_float),
|
||||
else => env.raise(error.TypeMismatch, "right-hand object is not subtractable"),
|
||||
},
|
||||
|
||||
else => return env.raise(error.TypeMismatch, "left-hand object is not subtractable"),
|
||||
});
|
||||
else => env.raise(error.TypeMismatch, "left-hand object is not subtractable"),
|
||||
};
|
||||
},
|
||||
|
||||
.mul => {
|
||||
|
@ -444,14 +475,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
|
|||
|
||||
defer env.discard(lhs_ref);
|
||||
|
||||
try env.push_ref(try switch (env.unbox(lhs_ref)) {
|
||||
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| env.new_number(lhs_number * rhs_number),
|
||||
else => return env.raise(error.TypeMismatch, "right-hand object is not multiplyable"),
|
||||
try switch (env.unbox(lhs_ref)) {
|
||||
.float => |lhs_float| switch (env.unbox(rhs_ref)) {
|
||||
.float => |rhs_float| env.push_float(lhs_float * rhs_float),
|
||||
else => env.raise(error.TypeMismatch, "right-hand object is not multipliable"),
|
||||
},
|
||||
|
||||
else => return env.raise(error.TypeMismatch, "left-hand object is not multiplyable"),
|
||||
});
|
||||
else => env.raise(error.TypeMismatch, "left-hand object is not multipliable"),
|
||||
};
|
||||
},
|
||||
|
||||
.div => {
|
||||
|
@ -467,14 +498,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
|
|||
|
||||
defer env.discard(lhs_ref);
|
||||
|
||||
try env.push_ref(try switch (env.unbox(lhs_ref)) {
|
||||
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
|
||||
.number => |rhs_number| env.new_number(lhs_number / rhs_number),
|
||||
else => return env.raise(error.TypeMismatch, "right-hand object is not divisable"),
|
||||
try switch (env.unbox(lhs_ref)) {
|
||||
.float => |lhs_float| switch (env.unbox(rhs_ref)) {
|
||||
.float => |rhs_float| env.push_float(lhs_float / rhs_float),
|
||||
else => env.raise(error.TypeMismatch, "right-hand object is not divisible"),
|
||||
},
|
||||
|
||||
else => return env.raise(error.TypeMismatch, "left-hand object is not divisable"),
|
||||
});
|
||||
else => env.raise(error.TypeMismatch, "left-hand object is not divisible"),
|
||||
};
|
||||
},
|
||||
|
||||
.eql => {
|
||||
|
@ -490,7 +521,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
|
|||
|
||||
defer env.discard(lhs_ref);
|
||||
|
||||
try env.push_boolean(try kym.test_equality(env, lhs_ref, rhs_ref));
|
||||
try env.push_boolean(kym.test_equality(env, lhs_ref, rhs_ref));
|
||||
},
|
||||
|
||||
.cgt => {
|
||||
|
|
|
@ -5,34 +5,28 @@ const kym = @import("../kym.zig");
|
|||
associative: AssociativeTable,
|
||||
contiguous: ContiguousList,
|
||||
|
||||
// TODO: Modify hash traits to be support fat contexts rather than passing Envs into the table.
|
||||
const AssociativeTable = coral.map.Table(Field, *kym.RuntimeRef, .{
|
||||
.hash = Field.hash,
|
||||
.match = Field.match,
|
||||
const AssociativeTable = coral.map.Table(*kym.RuntimeRef, *kym.RuntimeRef, struct {
|
||||
env: *kym.RuntimeEnv,
|
||||
|
||||
pub fn hash(self: @This(), key_ref: *kym.RuntimeRef) usize {
|
||||
return kym.hash(self.env, key_ref);
|
||||
}
|
||||
|
||||
pub fn equals(self: @This(), key_ref_a: *kym.RuntimeRef, key_ref_b: *kym.RuntimeRef) bool {
|
||||
return kym.test_equality(self.env, key_ref_a, key_ref_b);
|
||||
}
|
||||
});
|
||||
|
||||
const ContiguousList = coral.list.Stack(?*kym.RuntimeRef);
|
||||
|
||||
const Field = struct {
|
||||
env: *kym.RuntimeEnv,
|
||||
ref: *kym.RuntimeRef,
|
||||
|
||||
fn hash(field: Field) usize {
|
||||
return switch (field.env.unbox(field.ref)) {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
fn match(self: Field, other: Field) bool {
|
||||
return kym.test_equality(self.env, self.ref, other.ref);
|
||||
}
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef {
|
||||
var self = Self{
|
||||
.associative = AssociativeTable.make(env.allocator),
|
||||
.associative = AssociativeTable.make(env.allocator, .{
|
||||
.env = env,
|
||||
}),
|
||||
|
||||
.contiguous = ContiguousList.make(env.allocator),
|
||||
};
|
||||
|
||||
|
@ -51,109 +45,139 @@ const typeinfo = kym.Typeinfo{
|
|||
.set = typeinfo_set,
|
||||
};
|
||||
|
||||
fn typeinfo_clean(context: kym.Context) void {
|
||||
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
|
||||
fn typeinfo_clean(method: kym.Method) void {
|
||||
const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
|
||||
|
||||
{
|
||||
var field_iterable = table.associative.as_iterable();
|
||||
|
||||
while (field_iterable.next()) |entry| {
|
||||
context.env.discard(entry.key.ref);
|
||||
context.env.discard(entry.value);
|
||||
method.env.discard(entry.key);
|
||||
method.env.discard(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
table.associative.free();
|
||||
|
||||
while (table.contiguous.pop()) |ref| {
|
||||
context.env.discard(ref);
|
||||
method.env.discard(ref);
|
||||
}
|
||||
|
||||
table.contiguous.free();
|
||||
}
|
||||
|
||||
fn typeinfo_get(context: kym.Context, index_ref: *const kym.RuntimeRef) kym.RuntimeError!?*kym.RuntimeRef {
|
||||
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
|
||||
fn typeinfo_get(method: kym.Method, index_ref: *const kym.RuntimeRef) kym.RuntimeError!?*kym.RuntimeRef {
|
||||
const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
|
||||
|
||||
switch (context.env.unbox(index_ref)) {
|
||||
.symbol, .string => {
|
||||
return context.env.acquire(table.associative.lookup(index_ref));
|
||||
},
|
||||
switch (method.env.unbox(index_ref)) {
|
||||
.symbol, .string, .float => {
|
||||
const mutable_index_ref = method.env.acquire(index_ref);
|
||||
|
||||
.number => |number| {
|
||||
// TODO: Implement dedicated integer type within VM internals for this sort of indexing.
|
||||
if (@trunc(number) == number) {
|
||||
const index = @as(usize, @intFromFloat(number));
|
||||
defer method.env.discard(mutable_index_ref);
|
||||
|
||||
if (index < table.contiguous.values.len) {
|
||||
return context.env.acquire(table.contiguous.values[index] orelse return null);
|
||||
}
|
||||
}
|
||||
|
||||
return context.env.acquire(table.associative.lookup(.{
|
||||
.env = context.env,
|
||||
.ref = index_ref,
|
||||
}));
|
||||
},
|
||||
|
||||
else => return context.env.raise(error.TypeMismatch, "expected symbol, string, or number index"),
|
||||
}
|
||||
}
|
||||
|
||||
fn typeinfo_set(context: kym.Context, index_ref: *const kym.RuntimeRef, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void {
|
||||
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
|
||||
|
||||
switch (context.env.unbox(context.index_ref)) {
|
||||
.symbol, .string => {
|
||||
const acquired_index_ref = context.env.acquire(index_ref);
|
||||
|
||||
errdefer context.env.discard(acquired_index_ref);
|
||||
|
||||
const acquired_value_ref = context.env.acquire(value_ref);
|
||||
|
||||
errdefer context.env.discard(acquired_value_ref);
|
||||
|
||||
if (try table.associative.replace(acquired_index_ref, acquired_value_ref)) |replaced| {
|
||||
context.env.discard(replaced.key);
|
||||
context.env.discard(replaced.value);
|
||||
if (table.associative.lookup(mutable_index_ref)) |value_ref| {
|
||||
return method.env.acquire(value_ref);
|
||||
}
|
||||
},
|
||||
|
||||
.number => |number| {
|
||||
const index = coral.math.clamped_cast(@typeInfo(usize), number);
|
||||
.fixed => |fixed| {
|
||||
if (fixed < 0) {
|
||||
// TODO: Negative indexing.
|
||||
unreachable;
|
||||
}
|
||||
|
||||
if (index == number) {
|
||||
if (index < table.contiguous.values.len) {
|
||||
table.contiguous.values[index] = context.env.acquire(value_ref);
|
||||
if (fixed < table.contiguous.values.len) {
|
||||
return method.env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null);
|
||||
}
|
||||
|
||||
const mutable_index_ref = method.env.acquire(index_ref);
|
||||
|
||||
defer method.env.discard(mutable_index_ref);
|
||||
|
||||
if (table.associative.lookup(mutable_index_ref)) |value_ref| {
|
||||
return method.env.acquire(value_ref);
|
||||
}
|
||||
},
|
||||
|
||||
else => return method.env.raise(error.TypeMismatch, "expected symbol, string, or number index"),
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn typeinfo_set(method: kym.Method, index_ref: *const kym.RuntimeRef, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void {
|
||||
const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
|
||||
|
||||
switch (method.env.unbox(index_ref)) {
|
||||
.symbol, .string, .float => {
|
||||
const acquired_index_ref = method.env.acquire(index_ref);
|
||||
|
||||
errdefer method.env.discard(acquired_index_ref);
|
||||
|
||||
if (value_ref) |ref| {
|
||||
const acquired_ref = method.env.acquire(ref);
|
||||
|
||||
errdefer method.env.discard(acquired_ref);
|
||||
|
||||
if (try table.associative.replace(acquired_index_ref, acquired_ref)) |replaced| {
|
||||
method.env.discard(replaced.key);
|
||||
method.env.discard(replaced.value);
|
||||
}
|
||||
} else if (table.associative.remove(acquired_index_ref)) |removed| {
|
||||
method.env.discard(removed.key);
|
||||
method.env.discard(removed.value);
|
||||
}
|
||||
},
|
||||
|
||||
.fixed => |fixed| {
|
||||
if (fixed < 0) {
|
||||
// TODO: Negative indexing.
|
||||
unreachable;
|
||||
}
|
||||
|
||||
if (fixed < table.contiguous.values.len) {
|
||||
const contiguous_value = &table.contiguous.values[@intCast(fixed)];
|
||||
|
||||
method.env.discard(contiguous_value.*);
|
||||
|
||||
contiguous_value.* = if (value_ref) |ref| method.env.acquire(ref) else null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (index == table.contiguous.values.len) {
|
||||
const acquired_index_ref = context.env.acquire(value_ref);
|
||||
if (fixed == table.contiguous.values.len) {
|
||||
if (value_ref) |ref| {
|
||||
const acquired_ref = method.env.acquire(ref);
|
||||
|
||||
errdefer context.env.discard(acquired_index_ref);
|
||||
errdefer method.env.discard(acquired_ref);
|
||||
|
||||
try table.contiguous.push_one(acquired_index_ref);
|
||||
try table.contiguous.push_one(acquired_ref);
|
||||
} else {
|
||||
try table.contiguous.push_one(null);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const acquired_index_ref = method.env.acquire(index_ref);
|
||||
|
||||
errdefer method.env.discard(acquired_index_ref);
|
||||
|
||||
if (value_ref) |ref| {
|
||||
const acquired_ref = method.env.acquire(ref);
|
||||
|
||||
errdefer method.env.discard(acquired_ref);
|
||||
|
||||
if (try table.associative.replace(acquired_index_ref, acquired_ref)) |replaced| {
|
||||
method.env.discard(replaced.key);
|
||||
method.env.discard(replaced.value);
|
||||
}
|
||||
|
||||
const acquired_index_ref = context.env.acquire(index_ref);
|
||||
|
||||
errdefer context.env.discard(index_ref);
|
||||
|
||||
const acquired_value_ref = context.env.acquire(value_ref);
|
||||
|
||||
errdefer context.env.discard(value_ref);
|
||||
|
||||
if (try table.associative.replace(acquired_index_ref, acquired_value_ref)) |replaced| {
|
||||
context.env.discard(replaced.key);
|
||||
context.env.discard(replaced.value);
|
||||
} else if (table.associative.remove(acquired_index_ref)) |removed| {
|
||||
method.env.discard(removed.key);
|
||||
method.env.discard(removed.value);
|
||||
}
|
||||
},
|
||||
|
||||
else => return context.env.raise(error.TypeMismatch, "expected symbol, string, or number index"),
|
||||
else => return method.env.raise(error.TypeMismatch, "expected symbol, string, or number index"),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue