kym-tables-overhaul #30
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
defer method.env.discard(mutable_index_ref);
|
||||||
|
|
||||||
|
if (table.associative.lookup(mutable_index_ref)) |value_ref| {
|
||||||
|
return method.env.acquire(value_ref);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
.number => |number| {
|
.fixed => |fixed| {
|
||||||
// TODO: Implement dedicated integer type within VM internals for this sort of indexing.
|
if (fixed < 0) {
|
||||||
if (@trunc(number) == number) {
|
// TODO: Negative indexing.
|
||||||
const index = @as(usize, @intFromFloat(number));
|
unreachable;
|
||||||
|
|
||||||
if (index < table.contiguous.values.len) {
|
|
||||||
return context.env.acquire(table.contiguous.values[index] orelse return null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return context.env.acquire(table.associative.lookup(.{
|
if (fixed < table.contiguous.values.len) {
|
||||||
.env = context.env,
|
return method.env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null);
|
||||||
.ref = index_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 context.env.raise(error.TypeMismatch, "expected symbol, string, or number index"),
|
else => return method.env.raise(error.TypeMismatch, "expected symbol, string, or number index"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn typeinfo_set(context: kym.Context, index_ref: *const kym.RuntimeRef, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void {
|
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(context.userdata.ptr)));
|
const table = @as(*Self, @ptrCast(@alignCast(method.userdata.ptr)));
|
||||||
|
|
||||||
switch (context.env.unbox(context.index_ref)) {
|
switch (method.env.unbox(index_ref)) {
|
||||||
.symbol, .string => {
|
.symbol, .string, .float => {
|
||||||
const acquired_index_ref = context.env.acquire(index_ref);
|
const acquired_index_ref = method.env.acquire(index_ref);
|
||||||
|
|
||||||
errdefer context.env.discard(acquired_index_ref);
|
errdefer method.env.discard(acquired_index_ref);
|
||||||
|
|
||||||
const acquired_value_ref = context.env.acquire(value_ref);
|
if (value_ref) |ref| {
|
||||||
|
const acquired_ref = method.env.acquire(ref);
|
||||||
|
|
||||||
errdefer context.env.discard(acquired_value_ref);
|
errdefer method.env.discard(acquired_ref);
|
||||||
|
|
||||||
if (try table.associative.replace(acquired_index_ref, acquired_value_ref)) |replaced| {
|
if (try table.associative.replace(acquired_index_ref, acquired_ref)) |replaced| {
|
||||||
context.env.discard(replaced.key);
|
method.env.discard(replaced.key);
|
||||||
context.env.discard(replaced.value);
|
method.env.discard(replaced.value);
|
||||||
|
}
|
||||||
|
} else if (table.associative.remove(acquired_index_ref)) |removed| {
|
||||||
|
method.env.discard(removed.key);
|
||||||
|
method.env.discard(removed.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) {
|
const contiguous_value = &table.contiguous.values[@intCast(fixed)];
|
||||||
table.contiguous.values[index] = context.env.acquire(value_ref);
|
|
||||||
|
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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue