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