kym-tables-overhaul #30

Merged
kayomn merged 12 commits from kym-tables-overhaul into main 2023-08-12 15:20:18 +02:00
9 changed files with 409 additions and 189 deletions
Showing only changes of commit 0141c9c2ed - Show all commits

3
.vscode/launch.json vendored
View File

@ -7,7 +7,8 @@
"request": "launch",
"target": "${workspaceRoot}/zig-out/bin/runner",
"cwd": "${workspaceRoot}/debug/",
"valuesFormatting": "parseText"
"valuesFormatting": "parseText",
"preLaunchTask": "Build All"
},
]
}

View File

@ -3,8 +3,8 @@
@log_info("game is loading")
return {
title = "Afterglow",
width = 1280,
height = 800,
tick_rate = 60,
.title = "Afterglow",
.width = 1280,
.height = 800,
.tick_rate = 60,
}

View File

@ -336,7 +336,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
pub fn TableTraits(comptime Key: type) type {
return struct {
hash: fn (key: Key) usize,
match: fn (key: Key, key: Key) bool,
match: fn (self: Key, other: Key) bool,
};
}

View File

@ -13,7 +13,7 @@ pub const Manifest = struct {
tick_rate: f32 = 60.0,
pub fn load(self: *Manifest, env: *kym.RuntimeEnv, file_access: file.Access) kym.RuntimeError!void {
const manifest_ref = try env.execute_file(file_access, file.Path.from(&.{"app.ona"}));
const manifest_ref = try env.execute_file(file_access, file.Path.from(&.{"app.ona"})) orelse return;
defer env.discard(manifest_ref);
@ -22,9 +22,11 @@ pub const Manifest = struct {
defer env.discard(ref);
if (env.unbox(ref).expect_number()) |number| {
if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) {
break: get @intFromFloat(number);
if (ref) |live_ref| {
if (env.unbox(live_ref).expect_number()) |number| {
if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) {
break: get @intFromFloat(number);
}
}
}
@ -36,9 +38,11 @@ pub const Manifest = struct {
defer env.discard(ref);
if (env.unbox(ref).expect_number()) |number| {
if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) {
break: get @intFromFloat(number);
if (ref) |live_ref| {
if (env.unbox(live_ref).expect_number()) |number| {
if (number > 0 and number < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) {
break: get @intFromFloat(number);
}
}
}
@ -50,19 +54,30 @@ pub const Manifest = struct {
defer env.discard(ref);
if (env.unbox(ref).expect_number()) |number| {
break: get @floatCast(number);
if (ref) |live_ref| {
if (env.unbox(live_ref).expect_number()) |number| {
break: get @floatCast(number);
}
}
break: get self.tick_rate;
});
{
const title_ref = try kym.get_field(env, manifest_ref, "title");
const ref = try kym.get_field(env, manifest_ref, "title");
defer env.discard(title_ref);
defer env.discard(ref);
const title_string = unbox: {
if (ref) |live_ref| {
if (env.unbox(live_ref).expect_string()) |string| {
break: unbox string;
}
}
break: unbox "";
};
const title_string = env.unbox(title_ref).expect_string() orelse "";
const limited_title_len = @min(title_string.len, self.title.len);
coral.io.copy(&self.title, title_string[0 .. limited_title_len]);

View File

@ -9,10 +9,10 @@ const coral = @import("coral");
const file = @import("./file.zig");
pub const Any = union (enum) {
nil,
boolean: bool,
number: Float,
string: []const coral.io.Byte,
symbol: []const coral.io.Byte,
dynamic: *DynamicObject,
pub fn expect_dynamic(self: Any) ?*DynamicObject {
@ -39,6 +39,11 @@ 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,
@ -67,13 +72,8 @@ pub const Frame = struct {
locals_top: usize,
};
pub const IndexContext = struct {
env: *RuntimeEnv,
userdata: []coral.io.Byte,
index_ref: ?*const RuntimeRef,
};
pub const RuntimeEnv = struct {
interned_symbols: SymbolTable,
allocator: coral.io.Allocator,
error_handler: ErrorHandler,
syscallers: SyscallerTable,
@ -83,6 +83,8 @@ pub const RuntimeEnv = struct {
const FrameStack = coral.list.Stack(Frame);
const SymbolTable = coral.map.StringTable(*RuntimeRef);
const SyscallerTable = coral.map.StringTable(Caller);
const RefStack = coral.list.Stack(?*RuntimeRef);
@ -95,6 +97,7 @@ pub const RuntimeEnv = struct {
true,
number: Float,
string: []coral.io.Byte,
symbol: []coral.io.Byte,
dynamic: *DynamicObject,
},
});
@ -104,8 +107,8 @@ pub const RuntimeEnv = struct {
caller: Caller,
};
pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef {
const key = @intFromPtr(ref orelse return null);
pub fn acquire(self: *RuntimeEnv, ref: *const RuntimeRef) *RuntimeRef {
const key = @intFromPtr(ref);
var ref_data = self.ref_values.remove(key);
coral.debug.assert(ref_data != null);
@ -166,15 +169,21 @@ pub const RuntimeEnv = struct {
if (ref_data.ref_count == 0) {
switch (ref_data.object) {
.false => {},
.true => {},
.number => {},
.string => |string| self.allocator.deallocate(string),
.symbol => |symbol| self.allocator.deallocate(symbol),
.dynamic => |dynamic| {
dynamic.typeinfo.clean(self, dynamic.userdata);
dynamic.typeinfo.clean(.{
.env = self,
.userdata = dynamic.userdata,
});
self.allocator.deallocate(dynamic.userdata);
self.allocator.deallocate(dynamic);
},
else => {},
}
} else {
coral.debug.assert(self.ref_values.insert_at(key, ref_data));
@ -217,14 +226,23 @@ pub const RuntimeEnv = struct {
}
pub fn free(self: *RuntimeEnv) void {
for (self.local_refs.values) |ref| {
while (self.local_refs.pop()) |ref| {
self.discard(ref);
}
{
var iterable = self.interned_symbols.as_iterable();
while (iterable.next()) |entry| {
self.discard(entry.value);
}
}
self.frames.free();
self.syscallers.free();
self.local_refs.free();
self.syscallers.free();
self.ref_values.free();
self.interned_symbols.free();
}
pub fn get_local(self: *RuntimeEnv, local: u8) RuntimeError!?*RuntimeRef {
@ -232,7 +250,7 @@ pub const RuntimeEnv = struct {
return self.raise(error.IllegalState, "out of bounds local get");
}
return self.acquire(self.local_refs.values[local]);
return self.acquire(self.local_refs.values[local] orelse return null);
}
pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef {
@ -248,15 +266,15 @@ pub const RuntimeEnv = struct {
}
pub fn push_ref(self: *RuntimeEnv, ref: ?*RuntimeRef) RuntimeError!void {
return self.local_refs.push_one(self.acquire(ref));
return self.local_refs.push_one(if (ref) |live_ref| self.acquire(live_ref) else null);
}
pub fn set_local(self: *RuntimeEnv, local: u8, value: ?*RuntimeRef) RuntimeError!void {
pub fn set_local(self: *RuntimeEnv, local: u8, ref: ?*RuntimeRef) RuntimeError!void {
if (local >= self.local_refs.values.len) {
return self.raise(error.IllegalState, "out of bounds local set");
}
self.local_refs.values[local] = self.acquire(value);
self.local_refs.values[local] = if (ref) |live_ref| self.acquire(live_ref) else null;
}
pub fn make(allocator: coral.io.Allocator, error_handler: ErrorHandler) coral.io.AllocationError!RuntimeEnv {
@ -265,12 +283,13 @@ pub const RuntimeEnv = struct {
.ref_values = RefSlab.make(allocator),
.frames = FrameStack.make(allocator),
.syscallers = SyscallerTable.make(allocator),
.interned_symbols = SymbolTable.make(allocator),
.error_handler = error_handler,
.allocator = allocator,
};
}
pub fn new_boolean(self: *RuntimeEnv, value: bool) RuntimeError!?*RuntimeRef {
pub fn new_boolean(self: *RuntimeEnv, value: bool) RuntimeError!*RuntimeRef {
return @ptrFromInt(try self.ref_values.insert(.{
.ref_count = 1,
.object = if (value) .true else .false,
@ -299,14 +318,14 @@ pub const RuntimeEnv = struct {
}));
}
pub fn new_number(self: *RuntimeEnv, value: Float) RuntimeError!?*RuntimeRef {
pub fn new_number(self: *RuntimeEnv, value: Float) RuntimeError!*RuntimeRef {
return @ptrFromInt(try self.ref_values.insert(.{
.ref_count = 1,
.object = .{.number = value},
}));
}
pub fn new_string(self: *RuntimeEnv, string_data: []const coral.io.Byte) RuntimeError!?*RuntimeRef {
pub fn new_string(self: *RuntimeEnv, string_data: []const coral.io.Byte) RuntimeError!*RuntimeRef {
const string_copy = try coral.io.allocate_copy(self.allocator, string_data);
errdefer self.allocator.deallocate(string_copy);
@ -317,6 +336,29 @@ pub const RuntimeEnv = struct {
}));
}
pub fn new_symbol(self: *RuntimeEnv, symbol_data: []const coral.io.Byte) RuntimeError!*RuntimeRef {
if (self.interned_symbols.lookup(symbol_data)) |symbol_ref| {
return self.acquire(symbol_ref);
} else {
const symbol_copy = try coral.io.allocate_copy(self.allocator, symbol_data);
const symbol_ref = @as(*RuntimeRef, new: {
errdefer self.allocator.deallocate(symbol_copy);
break: new @ptrFromInt(try self.ref_values.insert(.{
.ref_count = 1,
.object = .{.symbol = symbol_copy},
}));
});
errdefer self.discard(symbol_ref);
coral.debug.assert(try self.interned_symbols.insert(symbol_copy, symbol_ref));
return self.acquire(symbol_ref);
}
}
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError {
self.error_handler.invoke(.{
.message = message,
@ -330,22 +372,19 @@ pub const RuntimeEnv = struct {
return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to call undefined syscall");
}
pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Any {
if (ref) |live_ref| {
const ref_data = self.ref_values.lookup(@intFromPtr(live_ref));
pub fn unbox(self: *RuntimeEnv, ref: *const RuntimeRef) Any {
const ref_data = self.ref_values.lookup(@intFromPtr(ref));
coral.debug.assert(ref_data != null);
coral.debug.assert(ref_data != null);
return switch (ref_data.?.object) {
.false => .{.boolean = false},
.true => .{.boolean = true},
.number => |number| .{.number = number},
.string => |string| .{.string = string},
.dynamic => |dynamic| .{.dynamic = dynamic},
};
}
return .nil;
return switch (ref_data.?.object) {
.false => .{.boolean = false},
.true => .{.boolean = true},
.number => |number| .{.number = number},
.string => |string| .{.string = string},
.symbol => |symbol| .{.symbol = symbol},
.dynamic => |dynamic| .{.dynamic = dynamic},
};
}
pub fn view_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*const RuntimeRef {
@ -368,43 +407,37 @@ pub const RuntimeError = coral.io.AllocationError || error {
pub const RuntimeRef = opaque {};
pub const TestContext = struct {
env: *RuntimeEnv,
userdata: []const coral.io.Byte,
other_ref: ?*const RuntimeRef,
};
pub const Typeinfo = struct {
name: []const coral.io.Byte,
call: *const fn (env: *RuntimeEnv) RuntimeError!?*RuntimeRef = default_call,
clean: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte) void = default_clean,
get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef = default_get,
set: *const fn (context: IndexContext, value: ?*const RuntimeRef) RuntimeError!void = default_set,
test_difference: *const fn (context: TestContext) RuntimeError!Float = default_test_difference,
test_equality: *const fn (context: TestContext) RuntimeError!bool = default_test_equality,
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,
fn default_call(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
return env.raise(error.TypeMismatch, "object is not callable");
fn default_call(context: Context) RuntimeError!?*RuntimeRef {
return context.env.raise(error.TypeMismatch, "object is not callable");
}
fn default_clean(_: *RuntimeEnv, _: []coral.io.Byte) void {
fn default_clean(_: Context) void {
// Nothing to clean by default.
}
fn default_get(context: IndexContext) RuntimeError!?*RuntimeRef {
fn default_get(context: Context, _: *const RuntimeRef) RuntimeError!?*RuntimeRef {
return context.env.raise(error.TypeMismatch, "object is not indexable");
}
fn default_set(context: IndexContext, _: ?*const RuntimeRef) RuntimeError!void {
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: TestContext) RuntimeError!Float {
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: TestContext) RuntimeError!bool {
return switch (context.env.unbox(context.other_ref)) {
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,
};
@ -414,7 +447,7 @@ pub const Typeinfo = struct {
pub fn call(
env: *RuntimeEnv,
caller_ref: ?*const RuntimeRef,
callable_ref: ?*const RuntimeRef,
callable_ref: *const RuntimeRef,
arg_refs: []const ?*const RuntimeRef,
) RuntimeError!?*RuntimeRef {
for (arg_refs) |arg_ref| {
@ -436,21 +469,20 @@ pub fn call(
pub fn get(
env: *RuntimeEnv,
indexable_ref: ?*const RuntimeRef,
index_ref: ?*const RuntimeRef,
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 = index_ref,
});
}, index_ref);
}
pub fn get_field(
env: *RuntimeEnv,
indexable_ref: ?*const RuntimeRef,
indexable_ref: *const RuntimeRef,
field_name: []const coral.io.Byte,
) RuntimeError!?*RuntimeRef {
const field_name_ref = try env.new_string(field_name);
@ -462,8 +494,8 @@ pub fn get_field(
pub fn set(
env: *RuntimeEnv,
indexable_ref: ?*const RuntimeRef,
index_ref: ?*const RuntimeRef,
indexable_ref: *const RuntimeRef,
index_ref: *const RuntimeRef,
value_ref: ?*const RuntimeRef,
) RuntimeError!void {
const dynamic = try unbox_dynamic(env, indexable_ref);
@ -477,7 +509,7 @@ pub fn set(
pub fn set_field(
env: *RuntimeEnv,
indexable_ref: ?*const RuntimeRef,
indexable_ref: *const RuntimeRef,
field_name: []const coral.io.Byte,
value_ref: ?*const RuntimeRef,
) RuntimeError!void {
@ -490,9 +522,8 @@ pub fn set_field(
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(env: *RuntimeEnv, lhs_ref: *const RuntimeRef, rhs_ref: *const RuntimeRef) RuntimeError!Float {
return switch (env.unbox(lhs_ref)) {
.nil => env.raise(error.TypeMismatch, "cannot compare nil objects"),
.boolean => env.raise(error.TypeMismatch, "cannot compare boolean objects"),
.number => |lhs_number| switch (env.unbox(rhs_ref)) {
@ -502,21 +533,25 @@ pub fn test_difference(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?
.string => |lhs_string| switch (env.unbox(rhs_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 => 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,
.other_ref = rhs_ref,
}),
}, 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) RuntimeError!bool {
return switch (env.unbox(lhs_ref)) {
.nil => (rhs_ref == null),
.boolean => |lhs_boolean| switch (env.unbox(rhs_ref)) {
.boolean => |rhs_boolean| rhs_boolean == lhs_boolean,
else => false,
@ -529,21 +564,27 @@ pub fn test_equality(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*c
.string => |lhs_string| switch (env.unbox(rhs_ref)) {
.string => |rhs_string| coral.io.equals(lhs_string, rhs_string),
.symbol => |rhs_symbol| coral.io.equals(lhs_string, rhs_symbol),
else => false,
},
.dynamic => |lhs_dynamic| try lhs_dynamic.typeinfo.test_equality(.{
.symbol => |lhs_symbol| switch (env.unbox(rhs_ref)) {
.symbol => |rhs_symbol| lhs_symbol.ptr == rhs_symbol.ptr,
.string => |rhs_string| coral.io.equals(lhs_symbol, rhs_string),
else => false,
},
.dynamic => |lhs_dynamic| lhs_dynamic.typeinfo.test_equality(.{
.env = env,
.userdata = lhs_dynamic.userdata,
.other_ref = rhs_ref,
}),
}, rhs_ref),
};
}
pub fn unbox_dynamic(env: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError!*DynamicObject {
pub fn unbox_dynamic(env: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError!*DynamicObject {
return env.unbox(ref).expect_dynamic() orelse env.raise(error.TypeMismatch, "expected dynamic object");
}
pub fn unbox_string(env: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError![]const coral.io.Byte {
pub fn unbox_string(env: *RuntimeEnv, ref: *const RuntimeRef) RuntimeError![]const coral.io.Byte {
return env.unbox(ref).expect_string() orelse env.raise(error.TypeMismatch, "expected string object");
}

View File

@ -15,7 +15,8 @@ pub const Expression = union (enum) {
false_literal,
number_literal: []const coral.io.Byte,
string_literal: []const coral.io.Byte,
table_literal: NamedList,
symbol_literal: []const coral.io.Byte,
table_literal: TableLiteral,
grouped_expression: *Expression,
get_local: []const coral.io.Byte,
@ -61,9 +62,9 @@ pub const Expression = union (enum) {
}
};
pub const NamedList = coral.list.Stack(struct {
identifier: []const coral.io.Byte,
expression: Expression,
pub const TableLiteral = coral.list.Stack(struct {
key_expression: Expression,
value_expression: Expression,
});
pub const List = coral.list.Stack(Expression);
@ -397,7 +398,7 @@ fn parse_factor(self: *Self) ParseError!Expression {
},
.symbol_brace_left => {
var table_fields = Expression.NamedList.make(allocator);
var table_literal = Expression.TableLiteral.make(allocator);
self.tokenizer.skip(.newline);
@ -406,14 +407,30 @@ fn parse_factor(self: *Self) ParseError!Expression {
.symbol_brace_right => {
self.tokenizer.step();
return Expression{.table_literal = table_fields};
return Expression{.table_literal = table_literal};
},
.identifier => |identifier| {
.symbol_bracket_left => {
self.tokenizer.skip(.newline);
if (!self.tokenizer.is_token(.symbol_equals)) {
return self.report("expected `=` after identifier");
return self.report("expected expression after identifier");
}
},
.symbol_period => {
self.tokenizer.step();
if (!self.tokenizer.is_token(.identifier)) {
return self.report("expected identifier after `.`");
}
const identifier = self.tokenizer.token.?.identifier;
self.tokenizer.skip(.newline);
if (!self.tokenizer.is_token(.symbol_equals)) {
return self.report("expected `=` after key");
}
self.tokenizer.skip(.newline);
@ -422,9 +439,9 @@ fn parse_factor(self: *Self) ParseError!Expression {
return self.report("unexpected end after `=`");
}
try table_fields.push_one(.{
.expression = try self.parse_expression(),
.identifier = identifier,
try table_literal.push_one(.{
.value_expression = try self.parse_expression(),
.key_expression = .{.symbol_literal = identifier},
});
switch (self.tokenizer.token orelse return self.report("unexpected end of table")) {
@ -433,7 +450,7 @@ fn parse_factor(self: *Self) ParseError!Expression {
.symbol_brace_right => {
self.tokenizer.step();
return Expression{.table_literal = table_fields};
return Expression{.table_literal = table_literal};
},
else => return self.report("expected `,` or `}` after expression"),

View File

@ -38,17 +38,18 @@ const AstCompiler = struct {
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(literal)});
},
.symbol_literal => |literal| {
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_symbol(literal)});
},
.table_literal => |fields| {
if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) {
return error.OutOfMemory;
}
for (fields.values) |field| {
try self.compile_expression(field.expression);
try self.chunk.append_opcode(.{
.push_const = try self.chunk.declare_constant_string(field.identifier),
});
try self.compile_expression(field.value_expression);
try self.compile_expression(field.key_expression);
}
try self.chunk.append_opcode(.{.push_table = @intCast(fields.values.len)});
@ -165,8 +166,6 @@ const AstCompiler = struct {
}
};
pub const Constant = u16;
const RefList = coral.list.Stack(?*kym.RuntimeRef);
const LocalsList = coral.list.Stack([]const u8);
@ -176,7 +175,7 @@ pub const Opcode = union (enum) {
push_nil,
push_true,
push_false,
push_const: Constant,
push_const: u16,
push_local: u8,
push_table: u32,
set_local: u8,
@ -223,7 +222,7 @@ 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!Constant {
pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeError!u16 {
const tail = self.constant_refs.values.len;
if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
@ -239,7 +238,7 @@ pub fn declare_constant_number(self: *Self, constant: kym.Float) kym.RuntimeErro
return @intCast(tail);
}
pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym.RuntimeError!Constant {
pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym.RuntimeError!u16 {
const tail = self.constant_refs.values.len;
if (tail == coral.math.max_int(@typeInfo(u16).Int)) {
@ -255,6 +254,22 @@ pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym
return @intCast(tail);
}
pub fn declare_constant_symbol(self: *Self, constant: []const coral.io.Byte) 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_symbol(constant);
errdefer self.env.discard(constant_ref);
try self.constant_refs.push_one(constant_ref);
return @intCast(tail);
}
fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
for (self.opcodes.values) |opcode| {
switch (opcode) {
@ -274,7 +289,9 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
var popped = @as(usize, 0);
while (popped < field_count) : (popped += 1) {
const index_ref = try env.pop_local();
const index_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not a valid index");
};
defer env.discard(index_ref);
@ -285,8 +302,7 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
try dynamic.typeinfo.set(.{
.userdata = dynamic.userdata,
.env = env,
.index_ref = index_ref,
}, value_ref);
}, index_ref, value_ref);
}
}
@ -311,7 +327,9 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
.call => |arg_count| {
const result_ref = call: {
const callable_ref = try env.pop_local();
const callable_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not callable");
};
defer env.discard(callable_ref);
@ -325,7 +343,9 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
.syscall => |arg_count| {
const result_ref = call: {
const identifier_ref = try env.pop_local();
const identifier_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not syscallable");
};
defer env.discard(identifier_ref);
@ -339,22 +359,42 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
try env.push_ref(result_ref);
},
.neg => try env.push_number(switch (env.unbox(try env.pop_local())) {
.number => |number| -number,
else => return env.raise(error.TypeMismatch, "object is not scalar negatable"),
}),
.neg => {
const ref = (try env.pop_local()) orelse {
return env.raise(error.TypeMismatch, "null is not scalar negatable");
};
.not => try env.push_boolean(switch (env.unbox(try env.pop_local())) {
.boolean => |boolean| !boolean,
else => return env.raise(error.TypeMismatch, "object is not boolean negatable"),
}),
defer env.discard(ref);
try env.push_number(switch (env.unbox(ref)) {
.number => |number| -number,
else => return env.raise(error.TypeMismatch, "object is not scalar negatable"),
});
},
.not => {
const ref = (try env.pop_local()) orelse {
return env.raise(error.TypeMismatch, "null is not boolean negatable");
};
defer env.discard(ref);
try env.push_boolean(switch (env.unbox(ref)) {
.boolean => |boolean| !boolean,
else => return env.raise(error.TypeMismatch, "object is not boolean negatable"),
});
},
.add => {
const rhs_ref = try env.pop_local();
const rhs_ref = (try env.pop_local()) orelse {
return env.raise(error.TypeMismatch, "nil is not addable");
};
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
const lhs_ref = (try env.pop_local()) orelse {
return env.raise(error.TypeMismatch, "nil is not addable");
};
defer env.discard(lhs_ref);
@ -369,11 +409,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
},
.sub => {
const rhs_ref = try env.pop_local();
const rhs_ref = (try env.pop_local()) orelse {
return env.raise(error.TypeMismatch, "nil is not subtractable");
};
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
const lhs_ref = (try env.pop_local()) orelse {
return env.raise(error.TypeMismatch, "nil is not subtractable");
};
defer env.discard(lhs_ref);
@ -388,11 +432,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
},
.mul => {
const rhs_ref = try env.pop_local();
const rhs_ref = (try env.pop_local()) orelse {
return env.raise(error.TypeMismatch, "nil is not multiplicable");
};
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
const lhs_ref = (try env.pop_local()) orelse {
return env.raise(error.TypeMismatch, "nil is not multiplicable");
};
defer env.discard(lhs_ref);
@ -407,11 +455,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
},
.div => {
const rhs_ref = try env.pop_local();
const rhs_ref = (try env.pop_local()) orelse {
return env.raise(error.TypeMismatch, "nil is not divisible");
};
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
const lhs_ref = (try env.pop_local()) orelse {
return env.raise(error.TypeMismatch, "nil is not divisible");
};
defer env.discard(lhs_ref);
@ -426,11 +478,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
},
.eql => {
const rhs_ref = try env.pop_local();
const rhs_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not equatable");
};
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
const lhs_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not equatable");
};
defer env.discard(lhs_ref);
@ -438,11 +494,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
},
.cgt => {
const rhs_ref = try env.pop_local();
const rhs_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not comparable");
};
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
const lhs_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not comparable");
};
defer env.discard(lhs_ref);
@ -450,11 +510,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
},
.clt => {
const rhs_ref = try env.pop_local();
const rhs_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not comparable");
};
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
const lhs_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not comparable");
};
defer env.discard(lhs_ref);
@ -462,11 +526,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
},
.cge => {
const rhs_ref = try env.pop_local();
const rhs_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not comparable");
};
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
const lhs_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not comparable");
};
defer env.discard(lhs_ref);
@ -474,11 +542,15 @@ fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef
},
.cle => {
const rhs_ref = try env.pop_local();
const rhs_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not comparable");
};
defer env.discard(rhs_ref);
const lhs_ref = try env.pop_local();
const lhs_ref = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not comparable");
};
defer env.discard(lhs_ref);

View File

@ -2,22 +2,43 @@ const coral = @import("coral");
const kym = @import("../kym.zig");
fields: FieldTable,
associative: AssociativeTable,
contiguous: ContiguousList,
const FieldTable = coral.map.StringTable(struct {
key_ref: ?*kym.RuntimeRef,
value_ref: ?*kym.RuntimeRef,
// 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 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 {
pub fn new(env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef {
var self = Self{
.fields = FieldTable.make(env.allocator),
.associative = AssociativeTable.make(env.allocator),
.contiguous = ContiguousList.make(env.allocator),
};
errdefer {
self.fields.free();
self.associative.free();
self.contiguous.free();
}
return try env.new_dynamic(coral.io.bytes_of(&self), &typeinfo);
@ -30,62 +51,109 @@ const typeinfo = kym.Typeinfo{
.set = typeinfo_set,
};
fn typeinfo_clean(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void {
const table = @as(*Self, @ptrCast(@alignCast(userdata.ptr)));
fn typeinfo_clean(context: kym.Context) void {
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
{
var field_iterable = table.fields.as_iterable();
var field_iterable = table.associative.as_iterable();
while (field_iterable.next()) |entry| {
env.discard(entry.value.key_ref);
env.discard(entry.value.value_ref);
context.env.discard(entry.key.ref);
context.env.discard(entry.value);
}
}
table.fields.free();
table.associative.free();
while (table.contiguous.pop()) |ref| {
context.env.discard(ref);
}
table.contiguous.free();
}
fn typeinfo_get(context: kym.IndexContext) kym.RuntimeError!?*kym.RuntimeRef {
fn typeinfo_get(context: kym.Context, index_ref: *const kym.RuntimeRef) kym.RuntimeError!?*kym.RuntimeRef {
const table = @as(*Self, @ptrCast(@alignCast(context.userdata.ptr)));
return switch (context.env.unbox(context.index_ref)) {
.string => |string| context.env.acquire((table.fields.lookup(string) orelse return null).value_ref),
// TODO: Implement number indices in tables.
.number => |_| unreachable,
else => context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers"),
};
switch (context.env.unbox(index_ref)) {
.symbol, .string => {
return context.env.acquire(table.associative.lookup(index_ref));
},
.number => |number| {
// TODO: Implement dedicated integer type within VM internals for this sort of indexing.
if (@trunc(number) == number) {
const index = @as(usize, @intFromFloat(number));
if (index < table.contiguous.values.len) {
return context.env.acquire(table.contiguous.values[index] orelse return null);
}
}
return context.env.acquire(table.associative.lookup(.{
.env = context.env,
.ref = index_ref,
}));
},
else => return context.env.raise(error.TypeMismatch, "expected symbol, string, or number index"),
}
}
fn typeinfo_set(context: kym.IndexContext, value_ref: ?*const kym.RuntimeRef) kym.RuntimeError!void {
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)));
const acquired_value_ref = context.env.acquire(value_ref);
errdefer context.env.discard(acquired_value_ref);
switch (context.env.unbox(context.index_ref)) {
.string => |string| {
const acquired_index_ref = context.env.acquire(context.index_ref);
.symbol, .string => {
const acquired_index_ref = context.env.acquire(index_ref);
errdefer context.env.discard(acquired_index_ref);
var displaced_table_entry = if (acquired_value_ref) |ref| try table.fields.replace(string, .{
.key_ref = acquired_index_ref,
.value_ref = ref,
}) else table.fields.remove(string);
const acquired_value_ref = context.env.acquire(value_ref);
if (displaced_table_entry) |*entry| {
context.env.discard(entry.value.key_ref);
context.env.discard(entry.value.value_ref);
errdefer context.env.discard(acquired_value_ref);
if (try table.associative.replace(acquired_index_ref, acquired_value_ref)) |replaced| {
context.env.discard(replaced.key);
context.env.discard(replaced.value);
}
},
.number => |_| {
// TODO: Implement number indices in tables.
unreachable;
.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;
}
}
const acquired_index_ref = context.env.acquire(index_ref);
errdefer context.env.discard(index_ref);
const acquired_value_ref = context.env.acquire(value_ref);
errdefer context.env.discard(value_ref);
if (try table.associative.replace(acquired_index_ref, acquired_value_ref)) |replaced| {
context.env.discard(replaced.key);
context.env.discard(replaced.value);
}
},
else => {
return context.env.raise(error.TypeMismatch, "table objects may only be indexed with strings or numbers");
},
else => return context.env.raise(error.TypeMismatch, "expected symbol, string, or number index"),
}
}

View File

@ -27,19 +27,25 @@ fn kym_handle_errors(info: kym.ErrorInfo) void {
}
fn kym_log_info(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
app.log_info(try kym.unbox_string(env, try env.view_arg(0)));
if (try env.view_arg(0)) |arg| {
app.log_info(try kym.unbox_string(env, arg));
}
return null;
}
fn kym_log_warn(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
app.log_warn(try kym.unbox_string(env, try env.view_arg(0)));
if (try env.view_arg(0)) |arg| {
app.log_warn(try kym.unbox_string(env, arg));
}
return null;
}
fn kym_log_fail(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
app.log_fail(try kym.unbox_string(env, try env.view_arg(0)));
if (try env.view_arg(0)) |arg| {
app.log_fail(try kym.unbox_string(env, arg));
}
return null;
}