kym-tables-overhaul #30

Merged
kayomn merged 12 commits from kym-tables-overhaul into main 2023-08-12 15:20:18 +02:00
8 changed files with 410 additions and 321 deletions
Showing only changes of commit 8cb0f007d5 - Show all commits

View File

@ -2,6 +2,8 @@ const debug = @import("./debug.zig");
const math = @import("./math.zig"); const math = @import("./math.zig");
const std = @import("std");
pub const AllocationError = error { pub const AllocationError = error {
OutOfMemory, OutOfMemory,
}; };
@ -213,6 +215,27 @@ pub fn allocate_copy(allocator: Allocator, source: []const Byte) AllocationError
return allocation; 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) { pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@TypeOf(value) {
const Type = @TypeOf(value); const Type = @TypeOf(value);
const typeSize = @sizeOf(Type); 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)); 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 { pub fn ends_with(target: []const Byte, match: []const Byte) bool {
if (target.len < match.len) { if (target.len < match.len) {
return false; return false;

View File

@ -41,6 +41,10 @@ pub fn Stack(comptime Value: type) type {
self.capacity = grown_capacity; self.capacity = grown_capacity;
} }
pub fn is_empty(self: Self) bool {
return self.values.len == 0;
}
pub fn make(allocator: io.Allocator) Self { pub fn make(allocator: io.Allocator) Self {
return .{ return .{
.allocator = allocator, .allocator = allocator,

View File

@ -104,13 +104,20 @@ pub fn Slab(comptime Value: type) type {
} }
pub fn StringTable(comptime Value: type) type { pub fn StringTable(comptime Value: type) type {
return Table([]const io.Byte, Value, .{ return Table([]const io.Byte, Value, struct {
.hash = hash_string, const Self = @This();
.match = io.equals,
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 load_max = 0.75;
const hash_int = @typeInfo(usize).Int; const hash_int = @typeInfo(usize).Int;
const max_int = math.max_int(hash_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 { return struct {
allocator: io.Allocator, allocator: io.Allocator,
traits: Traits,
count: usize, count: usize,
entries: []?Entry, entries: []?Entry,
@ -125,24 +133,26 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
key: Key, key: Key,
value: Value, value: Value,
fn write_into(self: Entry, entry_table: []?Entry) bool { fn write_into(self: Entry, table: *Self) bool {
const hash_max = @min(max_int, entry_table.len); const hash_max = @min(max_int, table.entries.len);
var hashed_key = math.wrap(traits.hash(self.key), min_int, hash_max); var hashed_key = math.wrap(table.traits.hash(self.key), min_int, hash_max);
var iterations = @as(usize, 0); var iterations = @as(usize, 0);
while (true) : (iterations += 1) { while (true) : (iterations += 1) {
debug.assert(iterations < entry_table.len); debug.assert(iterations < table.entries.len);
const table_entry = &(entry_table[hashed_key] orelse { const table_entry = &(table.entries[hashed_key] orelse {
entry_table[hashed_key] = .{ table.entries[hashed_key] = .{
.key = self.key, .key = self.key,
.value = self.value, .value = self.value,
}; };
table.count += 1;
return true; return true;
}); });
if (traits.match(table_entry.key, self.key)) { if (table.traits.equals(table_entry.key, self.key)) {
return false; 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 { pub fn remove(self: *Self, key: Key) ?Entry {
const hash_max = @min(max_int, self.entries.len); 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) { while (true) {
const entry = &(self.entries[hashed_key] orelse continue); 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.*; const original_entry = entry.*;
self.entries[hashed_key] = null; 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); 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) { while (true) {
const entry = &(self.entries[hashed_key] orelse { 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; return null;
}); });
if (traits.match(entry.key, key)) { if (self.traits.equals(entry.key, key)) {
const original_entry = entry.*; const original_entry = 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); debug.assert(self.entries.len > self.count);
defer self.count += 1;
const entry = Entry{ const entry = Entry{
.key = key, .key = key,
.value = value, .value = value,
}; };
return entry.write_into(self.entries); return entry.write_into(self);
} }
pub fn lookup(self: Self, key: Key) ?Value { 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); 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); var iterations = @as(usize, 0);
while (iterations < self.count) : (iterations += 1) { while (iterations < self.count) : (iterations += 1) {
const entry = &(self.entries[hashed_key] orelse return null); 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; return entry.value;
} }
@ -293,11 +301,12 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
return null; return null;
} }
pub fn make(allocator: io.Allocator) Self { pub fn make(allocator: io.Allocator, traits: Traits) Self {
return .{ return .{
.allocator = allocator, .allocator = allocator,
.count = 0, .count = 0,
.entries = &.{}, .entries = &.{},
.traits = traits,
}; };
} }
@ -306,46 +315,26 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
return; return;
} }
var table = make(self.allocator, self.traits);
errdefer table.free();
table.entries = allocate: {
const min_count = @max(1, self.count); const min_count = @max(1, self.count);
const table_size = min_count * 2; 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| { for (self.entries) |maybe_entry| {
if (maybe_entry) |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;
}

View File

@ -8,6 +8,24 @@ pub fn clamp(value: anytype, lower: anytype, upper: anytype) @TypeOf(value, lowe
return @max(lower, @min(upper, value)); 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) { pub fn clamped_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) Int(dest_int) {
const Value = @TypeOf(value); 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))), .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"), else => @compileError("`" ++ @typeName(Value) ++ "` cannot be cast to an int"),
}; };
} }

View File

@ -33,9 +33,10 @@ pub const DecimalFormat = struct {
switch (code) { switch (code) {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
result = try math.checked_add( const offset_code = math.checked_sub(code, '0') orelse return null;
try math.checked_mul(result, radix),
try math.checked_sub(code, '0')); result = math.checked_mul(result, radix) orelse return null;
result = math.checked_add(result, offset_code) orelse return null;
}, },
else => { else => {

View File

@ -10,12 +10,13 @@ const file = @import("./file.zig");
pub const Any = union (enum) { pub const Any = union (enum) {
boolean: bool, boolean: bool,
number: Float, fixed: Fixed,
float: Float,
string: []const coral.io.Byte, string: []const coral.io.Byte,
symbol: []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) { return switch (self) {
.dynamic => |dynamic| dynamic, .dynamic => |dynamic| dynamic,
else => null, else => null,
@ -24,7 +25,8 @@ pub const Any = union (enum) {
pub fn expect_number(self: Any) ?Float { pub fn expect_number(self: Any) ?Float {
return switch (self) { return switch (self) {
.number => |number| number, .fixed => |fixed| @floatFromInt(fixed),
.float => |float| float,
else => null, else => null,
}; };
} }
@ -39,21 +41,15 @@ pub const Any = union (enum) {
pub const Caller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv); pub const Caller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv);
pub const Context = struct {
env: *RuntimeEnv,
userdata: []coral.io.Byte
};
pub const DynamicObject = struct { pub const DynamicObject = struct {
userdata: []coral.io.Byte, userdata: []coral.io.Byte,
typeinfo: *const Typeinfo, 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 { 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, frames: []const Frame,
}; };
pub const Fixed = i32;
pub const Float = f64; pub const Float = f64;
pub const Frame = struct { pub const Frame = struct {
@ -72,6 +70,11 @@ pub const Frame = struct {
locals_top: usize, locals_top: usize,
}; };
pub const Method = struct {
env: *RuntimeEnv,
userdata: []coral.io.Byte,
};
pub const RuntimeEnv = struct { pub const RuntimeEnv = struct {
interned_symbols: SymbolTable, interned_symbols: SymbolTable,
allocator: coral.io.Allocator, allocator: coral.io.Allocator,
@ -95,7 +98,8 @@ pub const RuntimeEnv = struct {
object: union (enum) { object: union (enum) {
false, false,
true, true,
number: Float, float: Float,
fixed: Fixed,
string: []coral.io.Byte, string: []coral.io.Byte,
symbol: []coral.io.Byte, symbol: []coral.io.Byte,
dynamic: *DynamicObject, dynamic: *DynamicObject,
@ -128,9 +132,9 @@ pub const RuntimeEnv = struct {
pub fn call( pub fn call(
self: *RuntimeEnv, self: *RuntimeEnv,
name: []const coral.io.Byte,
arg_count: u8,
caller: Caller, caller: Caller,
arg_count: u8,
name: []const coral.io.Byte,
) RuntimeError!?*RuntimeRef { ) RuntimeError!?*RuntimeRef {
try self.frames.push_one(.{ try self.frames.push_one(.{
.name = name, .name = name,
@ -159,6 +163,13 @@ pub const RuntimeEnv = struct {
return caller.invoke(self); 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 { pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void {
const key = @intFromPtr(ref orelse return); const key = @intFromPtr(ref orelse return);
var ref_data = self.ref_values.remove(key) orelse unreachable; var ref_data = self.ref_values.remove(key) orelse unreachable;
@ -169,9 +180,7 @@ pub const RuntimeEnv = struct {
if (ref_data.ref_count == 0) { if (ref_data.ref_count == 0) {
switch (ref_data.object) { switch (ref_data.object) {
.false => {}, .false, .true, .float, .fixed => {},
.true => {},
.number => {},
.string => |string| self.allocator.deallocate(string), .string => |string| self.allocator.deallocate(string),
.symbol => |symbol| self.allocator.deallocate(symbol), .symbol => |symbol| self.allocator.deallocate(symbol),
@ -222,7 +231,7 @@ pub const RuntimeEnv = struct {
try chunk.compile_ast(ast); 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 { pub fn free(self: *RuntimeEnv) void {
@ -245,6 +254,17 @@ pub const RuntimeEnv = struct {
self.interned_symbols.free(); 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 { pub fn get_local(self: *RuntimeEnv, local: u8) RuntimeError!?*RuntimeRef {
if (local >= self.local_refs.values.len) { if (local >= self.local_refs.values.len) {
return self.raise(error.IllegalState, "out of bounds local get"); 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 { 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)); return self.local_refs.push_one(try self.new_boolean(boolean));
} }
pub fn push_number(self: *RuntimeEnv, number: Float) RuntimeError!void { pub fn push_fixed(self: *RuntimeEnv, fixed: Fixed) RuntimeError!void {
return self.local_refs.push_one(try self.new_number(number)); 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 { 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); 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), .local_refs = RefStack.make(allocator),
.ref_values = RefSlab.make(allocator), .ref_values = RefSlab.make(allocator),
.frames = FrameStack.make(allocator), .frames = FrameStack.make(allocator),
.syscallers = SyscallerTable.make(allocator), .syscallers = SyscallerTable.make(allocator, .{}),
.interned_symbols = SymbolTable.make(allocator), .interned_symbols = SymbolTable.make(allocator, .{}),
.error_handler = error_handler, .error_handler = error_handler,
.allocator = allocator, .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(.{ return @ptrFromInt(try self.ref_values.insert(.{
.ref_count = 1, .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; return error_value;
} }
pub fn syscaller(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Caller { pub fn syscallable(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Caller {
return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to call undefined syscall"); return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to get undefined syscall");
} }
pub fn unbox(self: *RuntimeEnv, ref: *const RuntimeRef) Any { pub fn unbox(self: *RuntimeEnv, ref: *const RuntimeRef) Any {
const ref_data = self.ref_values.lookup(@intFromPtr(ref)); return switch ((self.ref_values.lookup(@intFromPtr(ref)) orelse unreachable).object) {
coral.debug.assert(ref_data != null);
return switch (ref_data.?.object) {
.false => .{.boolean = false}, .false => .{.boolean = false},
.true => .{.boolean = true}, .true => .{.boolean = true},
.number => |number| .{.number = number}, .fixed => |fixed| .{.fixed = fixed},
.float => |float| .{.float = float},
.string => |string| .{.string = string}, .string => |string| .{.string = string},
.symbol => |symbol| .{.symbol = symbol}, .symbol => |symbol| .{.symbol = symbol},
.dynamic => |dynamic| .{.dynamic = dynamic}, .dynamic => |dynamic| .{.dynamic = dynamic},
@ -409,156 +453,99 @@ pub const RuntimeRef = opaque {};
pub const Typeinfo = struct { pub const Typeinfo = struct {
name: []const coral.io.Byte, name: []const coral.io.Byte,
call: *const fn (context: Context) RuntimeError!?*RuntimeRef = default_call, call: ?*const fn (method: Method) RuntimeError!?*RuntimeRef = null,
clean: *const fn (context: Context) void = default_clean, clean: *const fn (method: Method) void = default_clean,
get: *const fn (context: Context, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get, get: *const fn (method: Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef = default_get,
set: *const fn (context: Context, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set, set: *const fn (method: Method, 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,
fn default_call(context: Context) RuntimeError!?*RuntimeRef { fn default_clean(_: Method) void {
return context.env.raise(error.TypeMismatch, "object is not callable");
}
fn default_clean(_: Context) void {
// Nothing to clean by default. // Nothing to clean by default.
} }
fn default_get(context: Context, _: *const RuntimeRef) RuntimeError!?*RuntimeRef { fn default_get(method: Method, _: *const RuntimeRef) RuntimeError!?*RuntimeRef {
return context.env.raise(error.TypeMismatch, "object is not indexable"); return method.env.raise(error.TypeMismatch, "object is not indexable");
} }
fn default_set(context: Context, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void { fn default_set(method: Method, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void {
return context.env.raise(error.TypeMismatch, "object is not indexable"); return method.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,
};
} }
}; };
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( pub fn get_field(
env: *RuntimeEnv, env: *RuntimeEnv,
indexable_ref: *const RuntimeRef, indexable_ref: *const RuntimeRef,
field_name: []const coral.io.Byte, field_name: []const coral.io.Byte,
) RuntimeError!?*RuntimeRef { ) 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); 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( pub fn hash(env: *RuntimeEnv, ref: *const RuntimeRef) usize {
env: *RuntimeEnv, return switch (env.unbox(ref)) {
indexable_ref: *const RuntimeRef, .boolean => 0,
index_ref: *const RuntimeRef, .float => 0,
value_ref: ?*const RuntimeRef, .fixed => 0,
) RuntimeError!void { .string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string),
const dynamic = try unbox_dynamic(env, indexable_ref); .symbol => 0,
.dynamic => |dynamic| @intFromPtr(dynamic),
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 const new_table = Table.new; pub const new_table = Table.new;
pub fn test_difference(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *const RuntimeRef) RuntimeError!Float { pub fn test_difference(
return switch (env.unbox(lhs_ref)) { env: *RuntimeEnv,
.boolean => env.raise(error.TypeMismatch, "cannot compare boolean objects"), 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)) { .fixed => |lhs_fixed| switch (env.unbox(rhs_comparable_ref)) {
.number => |rhs_number| rhs_number - lhs_number, .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 number objects"), 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)), .string => |rhs_string| @floatFromInt(coral.io.compare(lhs_string, rhs_string)),
.symbol => |rhs_symbol| @floatFromInt(coral.io.compare(lhs_string, rhs_symbol)), .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"), 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"), .symbol => env.raise(error.TypeMismatch, "cannot compare symbol objects"),
.string => |rhs_string| @floatFromInt(coral.io.compare(lhs_symbol, rhs_string)), .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"), else => env.raise(error.TypeMismatch, "right-hand object is not comparable with symbol objects"),
}, },
.dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_difference(.{ .dynamic => env.raise(error.TypeMismatch, "dynamic objects are not comparable"),
.env = env,
.userdata = lhs_dynamic.userdata,
}, rhs_ref),
}; };
} }
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)) { return switch (env.unbox(lhs_ref)) {
.boolean => |lhs_boolean| switch (env.unbox(rhs_ref)) { .boolean => |lhs_boolean| switch (env.unbox(rhs_ref)) {
.boolean => |rhs_boolean| rhs_boolean == lhs_boolean, .boolean => |rhs_boolean| rhs_boolean == lhs_boolean,
else => false, else => false,
}, },
.number => |lhs_number| switch (env.unbox(rhs_ref)) { .fixed => |lhs_fixed| switch (env.unbox(rhs_ref)) {
.number => |rhs_number| lhs_number == rhs_number, .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, else => false,
}, },
@ -574,14 +561,14 @@ pub fn test_equality(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *con
else => false, else => false,
}, },
.dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_equality(.{ .dynamic => |lhs_dynamic| switch (env.unbox(rhs_ref)) {
.env = env, .dynamic => |rhs_dynamic| rhs_dynamic == lhs_dynamic,
.userdata = lhs_dynamic.userdata, else => false,
}, rhs_ref), },
}; };
} }
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"); return env.unbox(ref).expect_dynamic() orelse env.raise(error.TypeMismatch, "expected dynamic object");
} }

View File

@ -25,12 +25,26 @@ const AstCompiler = struct {
.false_literal => try self.chunk.append_opcode(.push_false), .false_literal => try self.chunk.append_opcode(.push_false),
.number_literal => |literal| { .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(.{ 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(); 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; const tail = self.constant_refs.values.len;
if (tail == coral.math.max_int(@typeInfo(u16).Int)) { if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
return self.env.raise(error.BadSyntax, "functions may contain a maximum of 65,535 constants"); 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); 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); 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); 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); 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); defer env.discard(result_ref);
@ -366,10 +396,11 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
defer env.discard(ref); defer env.discard(ref);
try env.push_number(switch (env.unbox(ref)) { switch (env.unbox(ref)) {
.number => |number| -number, .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"), else => return env.raise(error.TypeMismatch, "object is not scalar negatable"),
}); }
}, },
.not => { .not => {
@ -398,14 +429,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
defer env.discard(lhs_ref); defer env.discard(lhs_ref);
try env.push_ref(try switch (env.unbox(lhs_ref)) { try switch (env.unbox(lhs_ref)) {
.number => |lhs_number| switch (env.unbox(rhs_ref)) { .float => |lhs_float| switch (env.unbox(rhs_ref)) {
.number => |rhs_number| env.new_number(lhs_number + rhs_number), .float => |rhs_float| env.push_float(lhs_float + rhs_float),
else => return env.raise(error.TypeMismatch, "right-hand object is not addable"), 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 => { .sub => {
@ -421,14 +452,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
defer env.discard(lhs_ref); defer env.discard(lhs_ref);
try env.push_ref(try switch (env.unbox(lhs_ref)) { try switch (env.unbox(lhs_ref)) {
.number => |lhs_number| switch (env.unbox(rhs_ref)) { .float => |lhs_float| switch (env.unbox(rhs_ref)) {
.number => |rhs_number| env.new_number(lhs_number - rhs_number), .float => |rhs_float| env.push_float(lhs_float - rhs_float),
else => return env.raise(error.TypeMismatch, "right-hand object is not subtractable"), 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 => { .mul => {
@ -444,14 +475,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
defer env.discard(lhs_ref); defer env.discard(lhs_ref);
try env.push_ref(try switch (env.unbox(lhs_ref)) { try switch (env.unbox(lhs_ref)) {
.number => |lhs_number| switch (env.unbox(rhs_ref)) { .float => |lhs_float| switch (env.unbox(rhs_ref)) {
.number => |rhs_number| env.new_number(lhs_number * rhs_number), .float => |rhs_float| env.push_float(lhs_float * rhs_float),
else => return env.raise(error.TypeMismatch, "right-hand object is not multiplyable"), 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 => { .div => {
@ -467,14 +498,14 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
defer env.discard(lhs_ref); defer env.discard(lhs_ref);
try env.push_ref(try switch (env.unbox(lhs_ref)) { try switch (env.unbox(lhs_ref)) {
.number => |lhs_number| switch (env.unbox(rhs_ref)) { .float => |lhs_float| switch (env.unbox(rhs_ref)) {
.number => |rhs_number| env.new_number(lhs_number / rhs_number), .float => |rhs_float| env.push_float(lhs_float / rhs_float),
else => return env.raise(error.TypeMismatch, "right-hand object is not divisable"), 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 => { .eql => {
@ -490,7 +521,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
defer env.discard(lhs_ref); 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 => { .cgt => {

View File

@ -5,34 +5,28 @@ const kym = @import("../kym.zig");
associative: AssociativeTable, associative: AssociativeTable,
contiguous: ContiguousList, contiguous: ContiguousList,
// TODO: Modify hash traits to be support fat contexts rather than passing Envs into the table. const AssociativeTable = coral.map.Table(*kym.RuntimeRef, *kym.RuntimeRef, struct {
const AssociativeTable = coral.map.Table(Field, *kym.RuntimeRef, .{ env: *kym.RuntimeEnv,
.hash = Field.hash,
.match = Field.match, 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 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(); const Self = @This();
pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef { pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef {
var self = Self{ var self = Self{
.associative = AssociativeTable.make(env.allocator), .associative = AssociativeTable.make(env.allocator, .{
.env = env,
}),
.contiguous = ContiguousList.make(env.allocator), .contiguous = ContiguousList.make(env.allocator),
}; };
@ -51,109 +45,139 @@ const typeinfo = kym.Typeinfo{
.set = typeinfo_set, .set = typeinfo_set,
}; };
fn typeinfo_clean(context: kym.Context) void { fn typeinfo_clean(method: kym.Method) void {
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr))); const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
{ {
var field_iterable = table.associative.as_iterable(); var field_iterable = table.associative.as_iterable();
while (field_iterable.next()) |entry| { while (field_iterable.next()) |entry| {
context.env.discard(entry.key.ref); method.env.discard(entry.key);
context.env.discard(entry.value); method.env.discard(entry.value);
} }
} }
table.associative.free(); table.associative.free();
while (table.contiguous.pop()) |ref| { while (table.contiguous.pop()) |ref| {
context.env.discard(ref); method.env.discard(ref);
} }
table.contiguous.free(); table.contiguous.free();
} }
fn typeinfo_get(context: kym.Context, index_ref: *const kym.RuntimeRef) kym.RuntimeError!?*kym.RuntimeRef { fn typeinfo_get(method: kym.Method, index_ref: *const kym.RuntimeRef) kym.RuntimeError!?*kym.RuntimeRef {
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr))); const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
switch (context.env.unbox(index_ref)) { switch (method.env.unbox(index_ref)) {
.symbol, .string => { .symbol, .string, .float => {
return context.env.acquire(table.associative.lookup(index_ref)); const mutable_index_ref = method.env.acquire(index_ref);
},
.number => |number| { defer method.env.discard(mutable_index_ref);
// TODO: Implement dedicated integer type within VM internals for this sort of indexing.
if (@trunc(number) == number) {
const index = @as(usize, @intFromFloat(number));
if (index < table.contiguous.values.len) { if (table.associative.lookup(mutable_index_ref)) |value_ref| {
return context.env.acquire(table.contiguous.values[index] orelse return null); return method.env.acquire(value_ref);
}
}
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);
} }
}, },
.number => |number| { .fixed => |fixed| {
const index = coral.math.clamped_cast(@typeInfo(usize), number); if (fixed < 0) {
// TODO: Negative indexing.
unreachable;
}
if (index == number) { if (fixed < table.contiguous.values.len) {
if (index < table.contiguous.values.len) { return method.env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null);
table.contiguous.values[index] = context.env.acquire(value_ref); }
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; return;
} }
if (index == table.contiguous.values.len) { if (fixed == table.contiguous.values.len) {
const acquired_index_ref = context.env.acquire(value_ref); 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; 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);
} }
} else if (table.associative.remove(acquired_index_ref)) |removed| {
const acquired_index_ref = context.env.acquire(index_ref); method.env.discard(removed.key);
method.env.discard(removed.value);
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 => 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"),
} }
} }