Closures, Higher-Order Functions, and Everything in Between #44

Merged
kayomn merged 4 commits from kym-captures into main 2023-11-05 16:52:52 +01:00
10 changed files with 600 additions and 278 deletions

View File

@ -1,29 +1,18 @@
let printer = lambda (): let test_param = "monkey wrench"
let printer = lambda (pfx):
@print(test_param)
return lambda (msg): return lambda (msg):
@print("This is a func call") @print(pfx)
@print(msg) @print(msg)
end end
end end
let pr = printer() let pr = printer("this is a final closure")
var i = 0
pr("test") pr("goodbye")
while i < 5:
pr("hello, world")
i = i + 1
end
if i > 6:
pr("`i` greater than `6`")
elif i == 4:
pr("`i` is equal to `4`")
else:
pr("i'unno")
end
return { return {
.title = "Game", .title = "Game",

View File

@ -196,6 +196,8 @@ pub const HexadecimalFormat = struct {
_ = self; _ = self;
_ = writer; _ = writer;
_ = value; _ = value;
unreachable;
kayomn marked this conversation as resolved Outdated

TODO functions should be unreachable and have a comment.

TODO functions should be unreachable and have a comment.
} }
}; };

View File

@ -21,7 +21,7 @@ pub const Manifest = struct {
if (try kym.get_field(env, manifest, "width")) |ref| { if (try kym.get_field(env, manifest, "width")) |ref| {
defer env.discard(ref); defer env.discard(ref);
const fixed = try env.unbox_fixed(ref); const fixed = try env.unwrap_fixed(ref);
if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) { if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) {
break: get @intCast(fixed); break: get @intCast(fixed);
@ -35,7 +35,7 @@ pub const Manifest = struct {
if (try kym.get_field(env, manifest, "height")) |ref| { if (try kym.get_field(env, manifest, "height")) |ref| {
defer env.discard(ref); defer env.discard(ref);
const fixed = try env.unbox_fixed(ref); const fixed = try env.unwrap_fixed(ref);
if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) { if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) {
break: get @intCast(fixed); break: get @intCast(fixed);
@ -49,7 +49,7 @@ pub const Manifest = struct {
if (try kym.get_field(env, manifest, "tick_rate")) |ref| { if (try kym.get_field(env, manifest, "tick_rate")) |ref| {
defer env.discard(ref); defer env.discard(ref);
break: get @floatCast(try env.unbox_float(ref)); break: get @floatCast(try env.unwrap_float(ref));
} }
break: get self.tick_rate; break: get self.tick_rate;
@ -58,7 +58,7 @@ pub const Manifest = struct {
if (try kym.get_field(env, manifest, "title")) |ref| { if (try kym.get_field(env, manifest, "title")) |ref| {
defer env.discard(ref); defer env.discard(ref);
const title_string = try env.unbox_string(ref); const title_string = try env.unwrap_string(ref);
const limited_title_len = @min(title_string.len, self.title.len); const limited_title_len = @min(title_string.len, self.title.len);
coral.io.copy(&self.title, title_string[0 .. limited_title_len]); coral.io.copy(&self.title, title_string[0 .. limited_title_len]);

View File

@ -11,9 +11,9 @@ pub const Access = union (enum) {
.null => return null, .null => return null,
.sandboxed_path => |sandboxed_path| { .sandboxed_path => |sandboxed_path| {
const readable_path_string = sandboxed_path.joined(readable_path).to_string() orelse return null; const path_string = sandboxed_path.joined(readable_path).get_string();
return @ptrCast(ext.SDL_RWFromFile(readable_path_string.ptr, "rb")); return @ptrCast(ext.SDL_RWFromFile(path_string.ptr, "rb"));
}, },
} }
} }
@ -23,7 +23,7 @@ pub const Access = union (enum) {
.null => return null, .null => return null,
.sandboxed_path => |sandboxed_path| { .sandboxed_path => |sandboxed_path| {
const path_string = sandboxed_path.joined(path).to_string() orelse return null; const path_string = sandboxed_path.joined(path).get_string();
const rw_ops = ext.SDL_RWFromFile(path_string, "rb") orelse return null; const rw_ops = ext.SDL_RWFromFile(path_string, "rb") orelse return null;
const file_size = ext.SDL_RWseek(rw_ops, 0, ext.RW_SEEK_END); const file_size = ext.SDL_RWseek(rw_ops, 0, ext.RW_SEEK_END);
@ -96,12 +96,8 @@ pub const Path = extern struct {
return path; return path;
} }
pub fn to_string(self: Path) ?[:0]const coral.io.Byte { pub fn get_string(self: Path) [:0]const coral.io.Byte {
const last_index = self.data.len - 1; coral.debug.assert(self.data[self.data.len - 1] == 0);
if (self.data[last_index] != 0) {
return null;
}
return coral.io.slice_sentineled(@as(coral.io.Byte, 0), @as([*:0]const coral.io.Byte, @ptrCast(&self.data))); return coral.io.slice_sentineled(@as(coral.io.Byte, 0), @as([*:0]const coral.io.Byte, @ptrCast(&self.data)));
} }

View File

@ -2,10 +2,6 @@ const Chunk = @import("./kym/Chunk.zig");
const Table = @import("./kym/Table.zig"); const Table = @import("./kym/Table.zig");
const app = @import("./app.zig");
const builtin = @import("builtin");
const coral = @import("coral"); const coral = @import("coral");
const file = @import("./file.zig"); const file = @import("./file.zig");
@ -15,7 +11,7 @@ const tokens = @import("./kym/tokens.zig");
const tree = @import("./kym/tree.zig"); const tree = @import("./kym/tree.zig");
pub const Frame = struct { pub const Frame = struct {
name: []const coral.io.Byte = "", name_stringable: *RuntimeRef,
arg_count: u8, arg_count: u8,
locals_top: usize, locals_top: usize,
@ -23,8 +19,8 @@ pub const Frame = struct {
return env.locals.values[self.locals_top .. (self.locals_top + self.arg_count)]; return env.locals.values[self.locals_top .. (self.locals_top + self.arg_count)];
} }
pub fn get_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) RuntimeError!*const RuntimeRef { pub fn expect_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) RuntimeError!*const RuntimeRef {
return self.has_arg(env, arg_index) orelse env.raise(error.BadOperation, "nil reference"); return self.has_arg(env, arg_index) orelse env.raise(error.TypeMismatch, "nil reference", .{});
} }
pub fn has_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) ?*const RuntimeRef { pub fn has_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) ?*const RuntimeRef {
@ -69,16 +65,24 @@ pub const RuntimeEnv = struct {
}, },
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float), .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float),
else => self.raise(error.TypeMismatch, "right-hand object is not addable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not addable", .{
.typename = rhs.typename(),
}),
}, },
.float => |lhs_float| switch (rhs.object().payload) { .float => |lhs_float| switch (rhs.object().payload) {
.float => |rhs_float| self.new_float(lhs_float + rhs_float), .float => |rhs_float| self.new_float(lhs_float + rhs_float),
.fixed => |rhs_fixed| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), .fixed => |rhs_fixed| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))),
else => self.raise(error.TypeMismatch, "right-hand object is not addable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not addable", .{
.typename = rhs.typename(),
}),
}, },
else => self.raise(error.TypeMismatch, "left-hand object is not addable"), else => self.raise(error.TypeMismatch, "left-hand {typename} is not addable", .{
.typename = lhs.typename(),
}),
}; };
} }
@ -88,18 +92,18 @@ pub const RuntimeEnv = struct {
try self.locals.push_one(try self.acquire(arg)); try self.locals.push_one(try self.acquire(arg));
} }
const frame = try self.push_frame(args.len); const frame = try self.push_frame(callable, args.len);
defer self.pop_frame(); defer self.pop_frame();
return self.call_frame(callable, frame); return self.call_frame(frame);
} }
pub fn call_frame(self: *RuntimeEnv, callable: *const RuntimeRef, frame: Frame) RuntimeError!?*RuntimeRef { pub fn call_frame(self: *RuntimeEnv, callable: *const RuntimeRef, frame: Frame) RuntimeError!?*RuntimeRef {
return switch (callable.object().payload) { return switch (callable.object().payload) {
.syscall => |syscall| syscall(self, frame), .syscall => |syscall| syscall(self, frame),
.dynamic => |dynamic| dynamic.typeinfo().call(self, dynamic.userdata(), frame), .dynamic => |dynamic| dynamic.typeinfo().call(self, dynamic.userdata(), frame),
else => self.raise(error.TypeMismatch, "object is not callable"), else => self.raise(error.TypeMismatch, "{typename} is not callable", .{.typename = callable.typename()}),
}; };
} }
@ -108,16 +112,24 @@ pub const RuntimeEnv = struct {
.fixed => |lhs_fixed| switch (rhs.object().payload) { .fixed => |lhs_fixed| switch (rhs.object().payload) {
.fixed => |rhs_fixed| @as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed)), .fixed => |rhs_fixed| @as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed)),
.float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) - rhs_float, .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) - rhs_float,
else => return self.raise(error.TypeMismatch, "right-hand object is not comparable"),
else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{
.typename = rhs.typename(),
}),
}, },
.float => |lhs_float| switch (rhs.object().payload) { .float => |lhs_float| switch (rhs.object().payload) {
.float => |rhs_float| lhs_float - rhs_float, .float => |rhs_float| lhs_float - rhs_float,
.fixed => |rhs_fixed| lhs_float - @as(Float, @floatFromInt(rhs_fixed)), .fixed => |rhs_fixed| lhs_float - @as(Float, @floatFromInt(rhs_fixed)),
else => return self.raise(error.TypeMismatch, "right-hand object is not comparable"),
else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{
.typename = rhs.typename(),
}),
}, },
else => return self.raise(error.TypeMismatch, "left-hand object is not comparable"), else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{
.typename = lhs.typename(),
}),
}; };
} }
@ -132,6 +144,12 @@ pub const RuntimeEnv = struct {
switch (object.payload) { switch (object.payload) {
.false, .true, .float, .fixed, .symbol, .vector2, .vector3, .syscall => {}, .false, .true, .float, .fixed, .symbol, .vector2, .vector3, .syscall => {},
.boxed => |*boxed| {
if (boxed.*) |boxed_value| {
self.discard(boxed_value);
}
},
.string => |string| { .string => |string| {
coral.debug.assert(string.len >= 0); coral.debug.assert(string.len >= 0);
self.allocator.deallocate(string.ptr[0 .. @intCast(string.len)]); self.allocator.deallocate(string.ptr[0 .. @intCast(string.len)]);
@ -155,21 +173,29 @@ pub const RuntimeEnv = struct {
.fixed => |lhs_fixed| switch (rhs.object().payload) { .fixed => |lhs_fixed| switch (rhs.object().payload) {
.fixed => |rhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))), .fixed => |rhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))),
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float), .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float),
else => self.raise(error.TypeMismatch, "right-hand object is not divisible"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not divisible", .{
.typename = rhs.typename(),
}),
}, },
.float => |lhs_float| switch (rhs.object().payload) { .float => |lhs_float| switch (rhs.object().payload) {
.float => |rhs_float| self.new_float(lhs_float / rhs_float), .float => |rhs_float| self.new_float(lhs_float / rhs_float),
.fixed => |rhs_fixed| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))), .fixed => |rhs_fixed| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))),
else => self.raise(error.TypeMismatch, "right-hand object is not divisible"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not divisible", .{
.typename = rhs.typename(),
}),
}, },
else => self.raise(error.TypeMismatch, "left-hand object is not divisible"), else => self.raise(error.TypeMismatch, "left-hand {typename} is not divisible", .{
.typename = lhs.typename(),
}),
}; };
} }
pub fn expect(self: *RuntimeEnv, value: ?*RuntimeRef) RuntimeError!*RuntimeRef { pub fn expect(self: *RuntimeEnv, value: ?*RuntimeRef) RuntimeError!*RuntimeRef {
return value orelse self.raise(error.TypeMismatch, "nil reference"); return value orelse self.raise(error.TypeMismatch, "nil reference", .{});
} }
pub fn free(self: *RuntimeEnv) void { pub fn free(self: *RuntimeEnv) void {
@ -194,16 +220,8 @@ pub const RuntimeEnv = struct {
pub fn get(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { pub fn get(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef) RuntimeError!?*RuntimeRef {
return switch (indexable.object().payload) { return switch (indexable.object().payload) {
.false => self.raise(error.TypeMismatch, "false is not get-indexable"),
.true => self.raise(error.TypeMismatch, "true is not get-indexable"),
.fixed => self.raise(error.TypeMismatch, "fixed is not get-indexable"),
.float => self.raise(error.TypeMismatch, "float is not get-indexable"),
.string => self.raise(error.TypeMismatch, "string is not get-indexable"),
.symbol => self.raise(error.TypeMismatch, "symbol is not get-indexable"),
.syscall => self.raise(error.TypeMismatch, "syscall is not get-indexable"),
.vector2 => |vector2| swizzle: { .vector2 => |vector2| swizzle: {
const swizzle_symbol = try self.unbox_symbol(index); const swizzle_symbol = try self.unwrap_symbol(index);
var swizzle_buffer = [_]f32{0} ** 3; var swizzle_buffer = [_]f32{0} ** 3;
var swizzle_count = @as(usize, 0); var swizzle_count = @as(usize, 0);
@ -228,7 +246,7 @@ pub const RuntimeEnv = struct {
}, },
.vector3 => |vector3| swizzle: { .vector3 => |vector3| swizzle: {
const swizzle_symbol = try self.unbox_symbol(index); const swizzle_symbol = try self.unwrap_symbol(index);
var swizzle_buffer = [_]f32{0} ** 3; var swizzle_buffer = [_]f32{0} ** 3;
var swizzle_count = @as(usize, 0); var swizzle_count = @as(usize, 0);
@ -254,14 +272,22 @@ pub const RuntimeEnv = struct {
}, },
.dynamic => |dynamic| dynamic.typeinfo().get(self, dynamic.userdata(), index), .dynamic => |dynamic| dynamic.typeinfo().get(self, dynamic.userdata(), index),
else => self.raise(error.TypeMismatch, "{typename} is not get-indexable", .{
.typename = indexable.typename(),
}),
}; };
} }
pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!?*RuntimeRef { pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!?*RuntimeRef {
const file_name = file_path.get_string();
var chunk = make_chunk: { var chunk = make_chunk: {
const file_data = const file_data =
(try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse { (try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse {
return self.raise(error.BadOperation, "failed to open or read file specified"); return self.raise(error.BadOperation, "failed to open or read `{name}`", .{
.name = file_name,
});
}; };
defer self.allocator.deallocate(file_data); defer self.allocator.deallocate(file_data);
@ -282,24 +308,21 @@ pub const RuntimeEnv = struct {
}; };
} }
break: make_chunk try Chunk.make(self, file_path.to_string() orelse "<script>", &root.environment); const chunk_name = try self.new_string(file_name);
defer self.discard(chunk_name);
break: make_chunk try Chunk.make(self, chunk_name, &root.environment);
}; };
if (builtin.mode == .Debug) {
const string_ref = try chunk.dump(self);
defer self.discard(string_ref);
const string = string_ref.as_string();
coral.debug.assert(string != null);
app.log_info(string.?);
}
defer chunk.free(self); defer chunk.free(self);
return execute_chunk: { return execute_chunk: {
const frame = try self.push_frame(0); const name = try self.new_string(file_name);
defer self.discard(name);
const frame = try self.push_frame(name, 0);
defer self.pop_frame(); defer self.pop_frame();
@ -329,16 +352,24 @@ pub const RuntimeEnv = struct {
}, },
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float), .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float),
else => self.raise(error.TypeMismatch, "right-hand object is not multiplicable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not multiplicable", .{
.typename = rhs.typename(),
}),
}, },
.float => |lhs_float| switch (rhs.object().payload) { .float => |lhs_float| switch (rhs.object().payload) {
.float => |rhs_float| self.new_float(lhs_float * rhs_float), .float => |rhs_float| self.new_float(lhs_float * rhs_float),
.fixed => |rhs_fixed| self.new_float(lhs_float * @as(Float, @floatFromInt(rhs_fixed))), .fixed => |rhs_fixed| self.new_float(lhs_float * @as(Float, @floatFromInt(rhs_fixed))),
else => self.raise(error.TypeMismatch, "right-hand object is not multiplicable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not multiplicable", .{
.typename = rhs.typename(),
}),
}, },
else => self.raise(error.TypeMismatch, "left-hand object is not multiplicable"), else => self.raise(error.TypeMismatch, "left-hand {typename} is not multiplicable", .{
.typename = lhs.typename(),
}),
}; };
} }
@ -346,7 +377,7 @@ pub const RuntimeEnv = struct {
return switch (value.object().payload) { return switch (value.object().payload) {
.fixed => |fixed| self.new_fixed(-fixed), .fixed => |fixed| self.new_fixed(-fixed),
.float => |float| self.new_float(-float), .float => |float| self.new_float(-float),
else => self.raise(error.TypeMismatch, "object is not negatable"), else => self.raise(error.TypeMismatch, "{typename} is not negatable", .{.typename = value.typename()}),
}; };
} }
@ -357,11 +388,20 @@ pub const RuntimeEnv = struct {
}); });
} }
pub fn new_boxed(self: *RuntimeEnv, value: ?*const RuntimeRef) RuntimeError!*RuntimeRef {
return RuntimeRef.allocate(self.allocator, .{
.ref_count = 1,
.payload = .{.boxed = if (value) |ref| ref.acquire() else null},
});
}
pub fn new_dynamic( pub fn new_dynamic(
self: *RuntimeEnv, self: *RuntimeEnv,
userdata: [*]const coral.io.Byte, userdata: []const coral.io.Byte,
typeinfo: *const Typeinfo, typeinfo: *const Typeinfo,
) RuntimeError!*RuntimeRef { ) RuntimeError!*RuntimeRef {
coral.debug.assert(userdata.len == typeinfo.size);
const dynamic = try self.allocator.reallocate(null, @sizeOf(usize) + typeinfo.size); const dynamic = try self.allocator.reallocate(null, @sizeOf(usize) + typeinfo.size);
errdefer self.allocator.deallocate(dynamic); errdefer self.allocator.deallocate(dynamic);
@ -440,7 +480,7 @@ pub const RuntimeEnv = struct {
errdefer table.free(self); errdefer table.free(self);
return try self.new_dynamic(coral.io.bytes_of(&table).ptr, Table.typeinfo); return try self.new_dynamic(coral.io.bytes_of(&table), Table.typeinfo);
} }
pub fn new_vector2(self: *RuntimeEnv, x: f32, y: f32) RuntimeError!*RuntimeRef { pub fn new_vector2(self: *RuntimeEnv, x: f32, y: f32) RuntimeError!*RuntimeRef {
@ -470,23 +510,33 @@ pub const RuntimeEnv = struct {
} }
pub fn pop_frame(self: *RuntimeEnv) void { pub fn pop_frame(self: *RuntimeEnv) void {
var to_pop = self.locals.values.len - (self.frames.pop() orelse unreachable).locals_top; const popped_frame = self.frames.pop();
while (to_pop != 0) { coral.debug.assert(popped_frame != null);
if (self.locals.pop() orelse unreachable) |local| { self.discard(popped_frame.?.name_stringable);
var locals_to_pop = self.locals.values.len - popped_frame.?.locals_top;
while (locals_to_pop != 0) {
const popped_local = self.locals.pop();
coral.debug.assert(popped_local != null);
if (popped_local.?) |local| {
self.discard(local); self.discard(local);
} }
to_pop -= 1; locals_to_pop -= 1;
} }
} }
pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef { pub fn pop_local(self: *RuntimeEnv) RuntimeError!?*RuntimeRef {
return self.locals.pop() orelse self.raise(error.IllegalState, "stack underflow"); return self.locals.pop() orelse self.raise(error.IllegalState, "stack underflow", .{});
} }
pub fn push_frame(self: *RuntimeEnv, arg_count: u8) RuntimeError!Frame { pub fn push_frame(self: *RuntimeEnv, name_stringable: *RuntimeRef, arg_count: u8) RuntimeError!Frame {
const frame = Frame{ const frame = Frame{
.name_stringable = name_stringable.acquire(),
.arg_count = arg_count, .arg_count = arg_count,
.locals_top = self.locals.values.len - arg_count, .locals_top = self.locals.values.len - arg_count,
}; };
@ -496,8 +546,14 @@ pub const RuntimeEnv = struct {
return frame; return frame;
} }
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError { pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, comptime format: []const coral.io.Byte, args: anytype) RuntimeError {
self.print_error(message); {
const formatted_message = try coral.utf8.alloc_formatted(self.allocator, format, args);
defer self.allocator.deallocate(formatted_message);
self.print_error(formatted_message);
}
if (!self.frames.is_empty()) { if (!self.frames.is_empty()) {
self.print_error("stack trace:"); self.print_error("stack trace:");
@ -507,7 +563,17 @@ pub const RuntimeEnv = struct {
while (remaining_frames != 0) { while (remaining_frames != 0) {
remaining_frames -= 1; remaining_frames -= 1;
self.print_error(self.frames.values[remaining_frames].name); const callable_string = try self.to_string(self.frames.values[remaining_frames].name_stringable);
defer self.discard(callable_string);
self.print_error(get_name: {
const string = callable_string.as_string();
coral.debug.assert(string != null);
break: get_name string.?;
});
} }
} }
@ -517,7 +583,10 @@ pub const RuntimeEnv = struct {
pub fn set(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { pub fn set(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void {
return switch (indexable.object().payload) { return switch (indexable.object().payload) {
.dynamic => |dynamic| dynamic.typeinfo().set(self, dynamic.userdata(), index, value), .dynamic => |dynamic| dynamic.typeinfo().set(self, dynamic.userdata(), index, value),
else => self.raise(error.TypeMismatch, "object is not set-indexable"),
else => self.raise(error.TypeMismatch, "{typename} is not set-indexable", .{
.typename = indexable.typename(),
}),
}; };
} }
@ -533,16 +602,24 @@ pub const RuntimeEnv = struct {
}, },
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float), .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float),
else => self.raise(error.TypeMismatch, "right-hand object is not subtractable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not subtractable", .{
.typename = rhs.typename(),
}),
}, },
.float => |lhs_float| switch (rhs.object().payload) { .float => |lhs_float| switch (rhs.object().payload) {
.float => |rhs_float| self.new_float(lhs_float - rhs_float), .float => |rhs_float| self.new_float(lhs_float - rhs_float),
.fixed => |rhs_fixed| self.new_float(lhs_float - @as(Float, @floatFromInt(rhs_fixed))), .fixed => |rhs_fixed| self.new_float(lhs_float - @as(Float, @floatFromInt(rhs_fixed))),
else => self.raise(error.TypeMismatch, "right-hand object is not subtractable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not subtractable", .{
.typename = rhs.typename(),
}),
}, },
else => self.raise(error.TypeMismatch, "left-hand object is not subtractable"), else => self.raise(error.TypeMismatch, "left-hand {typename} is not subtractable", .{
.typename = lhs.typename(),
}),
}; };
} }
@ -573,6 +650,7 @@ pub const RuntimeEnv = struct {
break: convert self.new_string(string[0 .. length.?]); break: convert self.new_string(string[0 .. length.?]);
}, },
.boxed => |boxed| if (boxed) |boxed_value| self.to_string(boxed_value) else self.new_string("nil"),
kayomn marked this conversation as resolved Outdated

Why unreachable?

Why unreachable?
.symbol => |symbol| self.new_string(coral.io.slice_sentineled(@as(coral.io.Byte, 0), symbol)), .symbol => |symbol| self.new_string(coral.io.slice_sentineled(@as(coral.io.Byte, 0), symbol)),
.string => value.acquire(), .string => value.acquire(),
@ -580,7 +658,7 @@ pub const RuntimeEnv = struct {
var string = [_:0]coral.io.Byte{0} ** 64; var string = [_:0]coral.io.Byte{0} ** 64;
var buffer = coral.io.FixedBuffer{.bytes = &string}; var buffer = coral.io.FixedBuffer{.bytes = &string};
const length = coral.utf8.print_formatted(buffer.as_writer(), "vec3({x}, {y})", .{ const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec2({x}, {y})", .{
.x = vector2[0], .x = vector2[0],
.y = vector2[1], .y = vector2[1],
}); });
@ -594,7 +672,7 @@ pub const RuntimeEnv = struct {
var string = [_:0]coral.io.Byte{0} ** 96; var string = [_:0]coral.io.Byte{0} ** 96;
var buffer = coral.io.FixedBuffer{.bytes = &string}; var buffer = coral.io.FixedBuffer{.bytes = &string};
const length = coral.utf8.print_formatted(buffer.as_writer(), "vec3({x}, {y}, {z})", .{ const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec3({x}, {y}, {z})", .{
.x = vector3[0], .x = vector3[0],
.y = vector3[1], .y = vector3[1],
.z = vector3[2], .z = vector3[2],
@ -610,34 +688,36 @@ pub const RuntimeEnv = struct {
}; };
} }
pub fn unbox_dynamic(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]coral.io.Byte { pub fn unwrap_dynamic(self: *RuntimeEnv, value: *const RuntimeRef, typeinfo: *const Typeinfo) RuntimeError![]coral.io.Byte {
return switch (value.object().payload) { return value.as_dynamic(typeinfo) orelse self.raise(error.TypeMismatch, "expected dynamic object, not {typename}", .{
.dynamic => |dynamic| dynamic.userdata(), .typename = value.typename(),
else => self.raise(error.TypeMismatch, "expected fixed object") });
};
} }
pub fn unbox_float(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Float { pub fn unwrap_float(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Float {
return switch (value.object().payload) { return switch (value.object().payload) {
.fixed => |fixed| @floatFromInt(fixed), .fixed => |fixed| @floatFromInt(fixed),
.float => |float| float, .float => |float| float,
else => self.raise(error.TypeMismatch, "expected float object") else => self.raise(error.TypeMismatch, "expected float, not {typename}", .{.typename = value.typename()}),
}; };
} }
pub fn unbox_fixed(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Fixed { pub fn unwrap_fixed(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Fixed {
return value.as_fixed() orelse self.raise(error.TypeMismatch, "expected fixed object"); return value.as_fixed() orelse self.raise(error.TypeMismatch, "expected fixed, not {typename}", .{
.typename = value.typename(),
});
} }
pub fn unbox_string(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]const coral.io.Byte { pub fn unwrap_string(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]const coral.io.Byte {
return value.as_string() orelse self.raise(error.TypeMismatch, "expected string object"); return value.as_string() orelse self.raise(error.TypeMismatch, "expected string, not {typename}", .{
.typename = value.typename(),
});
} }
pub fn unbox_symbol(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![*:0]const coral.io.Byte { pub fn unwrap_symbol(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![*:0]const coral.io.Byte {
return switch (value.object().payload) { return value.as_symbol() orelse self.raise(error.TypeMismatch, "expected symbol, not {typename}", .{
.symbol => |symbol| symbol, .typename = value.typename(),
else => self.raise(error.TypeMismatch, "expected symbol object") });
};
} }
}; };
@ -661,6 +741,7 @@ pub const RuntimeRef = opaque {
vector2: [2]f32, vector2: [2]f32,
vector3: [3]f32, vector3: [3]f32,
syscall: *const Syscall, syscall: *const Syscall,
boxed: ?*RuntimeRef,
string: struct { string: struct {
ptr: [*]coral.io.Byte, ptr: [*]coral.io.Byte,
@ -712,9 +793,23 @@ pub const RuntimeRef = opaque {
return @ptrCast(try coral.io.allocate_one(allocator, data)); return @ptrCast(try coral.io.allocate_one(allocator, data));
} }
pub fn as_boxed(self: *const RuntimeRef) ?*?*RuntimeRef {
return switch (self.object().payload) {
.boxed => |*boxed| boxed,
else => null,
};
}
pub fn as_dynamic(self: *const RuntimeRef, typeinfo: *const Typeinfo) ?[]u8 {
return switch (self.object().payload) {
.dynamic => |dynamic| if (dynamic.typeinfo() == typeinfo) dynamic.userdata() else null,
else => null,
};
}
pub fn as_fixed(self: *const RuntimeRef) ?Fixed { pub fn as_fixed(self: *const RuntimeRef) ?Fixed {
return switch (self.object().payload) { return switch (self.object().payload) {
.fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))), .fixed => |fixed| fixed,
else => null, else => null,
}; };
} }
@ -727,6 +822,14 @@ pub const RuntimeRef = opaque {
break: get string.ptr[0 .. @intCast(string.len)]; break: get string.ptr[0 .. @intCast(string.len)];
}, },
.symbol => |symbol| coral.io.slice_sentineled(@as(u8, 0), symbol),
else => null,
};
}
pub fn as_symbol(self: *const RuntimeRef) ?[*:0]const coral.io.Byte {
return switch (self.object().payload) {
.symbol => |symbol| symbol,
else => null, else => null,
}; };
} }
@ -757,6 +860,18 @@ pub const RuntimeRef = opaque {
else => false, else => false,
}, },
.boxed => |boxed| unbox: {
if (boxed) |boxed_value| {
kayomn marked this conversation as resolved Outdated

Why unreachable?

Why unreachable?
break: unbox boxed_value.equals(other);
}
if (other.as_boxed()) |boxed_value| {
break: unbox boxed_value.* == null;
}
break: unbox false;
},
.vector2 => |self_vector| switch (other.object().payload) { .vector2 => |self_vector| switch (other.object().payload) {
.vector2 => |other_vector| coral.io.are_equal(coral.io.bytes_of(&self_vector), coral.io.bytes_of(&other_vector)), .vector2 => |other_vector| coral.io.are_equal(coral.io.bytes_of(&self_vector), coral.io.bytes_of(&other_vector)),
else => false, else => false,
@ -797,6 +912,7 @@ pub const RuntimeRef = opaque {
.vector2 => |vector| @bitCast(vector), .vector2 => |vector| @bitCast(vector),
.vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector)), .vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector)),
.syscall => |syscall| @intFromPtr(syscall), .syscall => |syscall| @intFromPtr(syscall),
.boxed => |boxed| @intFromPtr(boxed),
.string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()), .string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()),
.dynamic => |dynamic| @intFromPtr(dynamic.typeinfo()) ^ @intFromPtr(dynamic.userdata().ptr), .dynamic => |dynamic| @intFromPtr(dynamic.typeinfo()) ^ @intFromPtr(dynamic.userdata().ptr),
}; };
@ -812,6 +928,7 @@ pub const RuntimeRef = opaque {
.vector2 => |vector| coral.io.all_equals(coral.io.bytes_of(&vector), 0), .vector2 => |vector| coral.io.all_equals(coral.io.bytes_of(&vector), 0),
.vector3 => |vector| coral.io.all_equals(coral.io.bytes_of(&vector), 0), .vector3 => |vector| coral.io.all_equals(coral.io.bytes_of(&vector), 0),
.syscall => true, .syscall => true,
.boxed => |boxed| boxed != null,
.string => |string| string.len != 0, .string => |string| string.len != 0,
.dynamic => true, .dynamic => true,
}; };
@ -827,6 +944,7 @@ pub const RuntimeRef = opaque {
.vector2 => "vector2", .vector2 => "vector2",
.vector3 => "vector3", .vector3 => "vector3",
.syscall => "syscall", .syscall => "syscall",
.boxed => "boxed",
.string => "string", .string => "string",
.dynamic => "dynamic", .dynamic => "dynamic",
}; };
@ -844,18 +962,24 @@ pub const Typeinfo = struct {
set: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, value: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set, set: *const fn (env: *RuntimeEnv, userdata: []coral.io.Byte, value: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void = default_set,
fn default_call(env: *RuntimeEnv, _: []coral.io.Byte, _: Frame) RuntimeError!?*RuntimeRef { fn default_call(env: *RuntimeEnv, _: []coral.io.Byte, _: Frame) RuntimeError!?*RuntimeRef {
return env.raise(error.BadOperation, "object is not callable"); return env.raise(error.BadOperation, "this dynamic object is not callable", .{});
} }
fn default_get(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef) RuntimeError!?*RuntimeRef { fn default_get(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef) RuntimeError!?*RuntimeRef {
return env.raise(error.BadOperation, "object is not get-indexable"); return env.raise(error.BadOperation, "this dynamic object is not get-indexable", .{});
} }
fn default_set(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void { fn default_set(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void {
return env.raise(error.BadOperation, "object is not set-indexable"); return env.raise(error.BadOperation, "this dynamic object is not set-indexable", .{});
} }
}; };
pub fn assert(env: *RuntimeEnv, condition: bool) RuntimeError!void {
if (!condition) {
return env.raise(error.IllegalState, "assertion", .{});
}
}
pub fn get_field(env: *RuntimeEnv, indexable: *RuntimeRef, field: []const coral.io.Byte) RuntimeError!?*RuntimeRef { pub fn get_field(env: *RuntimeEnv, indexable: *RuntimeRef, field: []const coral.io.Byte) RuntimeError!?*RuntimeRef {
const field_symbol = try env.new_symbol(field); const field_symbol = try env.new_symbol(field);

View File

@ -1,5 +1,9 @@
const app = @import("../app.zig");
const Compiler = @import("./Compiler.zig"); const Compiler = @import("./Compiler.zig");
const builtin = @import("builtin");
const coral = @import("coral"); const coral = @import("coral");
const file = @import("../file.zig"); const file = @import("../file.zig");
@ -12,6 +16,7 @@ name: *kym.RuntimeRef,
arity: u8, arity: u8,
opcodes: OpcodeList, opcodes: OpcodeList,
constants: ConstList, constants: ConstList,
bindings: []?*kym.RuntimeRef,
kayomn marked this conversation as resolved
Review

May be worth replacing with VM managed Buffer object rather than raw Zig array.

May be worth replacing with VM managed Buffer object rather than raw Zig array.
Review

Actually this is out of scope and really a non-issue right now.

Actually this is out of scope and really a non-issue right now.
const Builtin = enum { const Builtin = enum {
kayomn marked this conversation as resolved Outdated

What is this for?

What is this for?
import, import,
@ -23,6 +28,7 @@ const Builtin = enum {
const ConstList = coral.list.Stack(*kym.RuntimeRef); const ConstList = coral.list.Stack(*kym.RuntimeRef);
const OpcodeList = coral.list.Stack(union (enum) { const OpcodeList = coral.list.Stack(union (enum) {
ret,
pop, pop,
push_nil, push_nil,
push_true, push_true,
@ -32,13 +38,15 @@ const OpcodeList = coral.list.Stack(union (enum) {
push_top, push_top,
push_table: u32, push_table: u32,
push_builtin: Builtin, push_builtin: Builtin,
push_closure: u8, push_binding: u8,
push_self,
push_boxed, push_boxed,
set_local: u8, set_local: u8,
get_dynamic, get_dynamic,
set_dynamic, set_dynamic,
get_box,
set_box,
call: u8, call: u8,
bind: u8,
not, not,
neg, neg,
@ -67,23 +75,21 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
defer buffer.free(); defer buffer.free();
const writer = coral.list.stack_as_writer(&buffer); const writer = coral.list.stack_as_writer(&buffer);
_ = coral.utf8.print_string(writer, coral.io.slice_sentineled(@as(coral.io.Byte, 0), try env.unbox_symbol(chunk.name)));
_ = coral.utf8.print_string(writer, ":\n"); _ = coral.utf8.print_string(writer, "\n");
while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) { while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) {
_ = coral.utf8.print_formatted(writer, "[{instruction}]: ", .{.instruction = opcode_cursor}); _ = coral.utf8.print_formatted(writer, "[{instruction}]: ", .{.instruction = opcode_cursor});
_ = switch (chunk.opcodes.values[opcode_cursor]) { _ = switch (chunk.opcodes.values[opcode_cursor]) {
.ret => coral.utf8.print_string(writer, "ret\n"),
.pop => coral.utf8.print_string(writer, "pop\n"), .pop => coral.utf8.print_string(writer, "pop\n"),
.push_nil => coral.utf8.print_string(writer, "push nil\n"), .push_nil => coral.utf8.print_string(writer, "push nil\n"),
.push_true => coral.utf8.print_string(writer, "push true\n"), .push_true => coral.utf8.print_string(writer, "push true\n"),
.push_false => coral.utf8.print_string(writer, "push false\n"), .push_false => coral.utf8.print_string(writer, "push false\n"),
.push_const => |push_const| print: { .push_const => |push_const| print: {
if (push_const >= chunk.constants.values.len) { try kym.assert(env, push_const < chunk.constants.values.len);
return env.raise(error.IllegalState, "invalid constant");
}
const string_ref = try env.to_string(chunk.constants.values[push_const]); const string_ref = try env.to_string(chunk.constants.values[push_const]);
@ -107,10 +113,9 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
}), }),
.push_boxed => coral.utf8.print_string(writer, "push boxed\n"), .push_boxed => coral.utf8.print_string(writer, "push boxed\n"),
.push_self => coral.utf8.print_string(writer, "push self\n"),
.push_closure => |push_closure| coral.utf8.print_formatted(writer, "push closure <{count}>\n", .{ .push_binding => |push_binding| coral.utf8.print_formatted(writer, "push binding <{binding}>\n", .{
.count = push_closure, .binding = push_binding,
}), }),
.push_builtin => |push_builtin| coral.utf8.print_formatted(writer, "push builtin <{builtin}>\n", .{ .push_builtin => |push_builtin| coral.utf8.print_formatted(writer, "push builtin <{builtin}>\n", .{
@ -122,7 +127,16 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
}, },
}), }),
.set_local => |local_set| coral.utf8.print_formatted(writer, "set local <{local}>\n", .{.local = local_set}), .bind => |bind| coral.utf8.print_formatted(writer, "bind <{count}>\n", .{
.count = bind,
}),
.set_local => |local_set| coral.utf8.print_formatted(writer, "set local <{local}>\n", .{
.local = local_set,
}),
.get_box => coral.utf8.print_string(writer, "get box\n"),
.set_box => coral.utf8.print_string(writer, "set box\n"),
.get_dynamic => coral.utf8.print_string(writer, "get dynamic\n"), .get_dynamic => coral.utf8.print_string(writer, "get dynamic\n"),
.set_dynamic => coral.utf8.print_string(writer, "set dynamic\n"), .set_dynamic => coral.utf8.print_string(writer, "set dynamic\n"),
.call => |call| coral.utf8.print_formatted(writer, "call <{count}>\n", .{.count = call}), .call => |call| coral.utf8.print_formatted(writer, "call <{count}>\n", .{.count = call}),
@ -145,11 +159,13 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
return env.new_string(buffer.values); return env.new_string(buffer.values);
} }
pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
var opcode_cursor = @as(u32, 0); var opcode_cursor = @as(u32, 0);
while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) { while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) {
switch (chunk.opcodes.values[opcode_cursor]) { switch (self.opcodes.values[opcode_cursor]) {
.ret => break,
.pop => { .pop => {
if (try env.pop_local()) |ref| { if (try env.pop_local()) |ref| {
env.discard(ref); env.discard(ref);
kayomn marked this conversation as resolved Outdated

All these raw illegal state calls may be better as a VM-managed assert function.

All these raw illegal state calls may be better as a VM-managed assert function.
@ -161,17 +177,12 @@ pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeE
.push_false => try env.locals.push_one(try env.new_boolean(false)), .push_false => try env.locals.push_one(try env.new_boolean(false)),
.push_const => |push_const| { .push_const => |push_const| {
if (push_const >= chunk.constants.values.len) { try kym.assert(env, push_const < self.constants.values.len);
return env.raise(error.IllegalState, "invalid constant"); try env.locals.push_one(self.constants.values[push_const].acquire());
}
try env.locals.push_one(chunk.constants.values[push_const].acquire());
}, },
.push_local => |push_local| { .push_local => |push_local| {
if (push_local >= env.locals.values.len) { try kym.assert(env, push_local < (env.locals.values.len - frame.locals_top));
return env.raise(error.IllegalState, "invalid local");
}
if (env.locals.values[frame.locals_top + push_local]) |local| { if (env.locals.values[frame.locals_top + push_local]) |local| {
try env.locals.push_one(local.acquire()); try env.locals.push_one(local.acquire());
@ -183,9 +194,7 @@ pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeE
.push_top => { .push_top => {
const frame_locals = env.locals.values[frame.locals_top ..]; const frame_locals = env.locals.values[frame.locals_top ..];
if (frame_locals.len == 0) { try kym.assert(env, frame_locals.len != 0);
return env.raise(error.IllegalState, "stack overflow");
}
if (frame_locals[frame_locals.len - 1]) |local| { if (frame_locals[frame_locals.len - 1]) |local| {
try env.locals.push_one(local.acquire()); try env.locals.push_one(local.acquire());
@ -219,19 +228,54 @@ pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeE
}, },
.push_boxed => { .push_boxed => {
// TODO: Implement. const value = try env.pop_local();
unreachable;
defer {
if (value) |ref| {
env.discard(ref);
}
}
const boxable = try env.new_boxed(value);
errdefer env.discard(boxable);
try env.locals.push_one(boxable);
}, },
.push_self => { .push_binding => |push_binding| {
// TODO: Implement. try kym.assert(env, push_binding <= self.bindings.len);
unreachable; try env.locals.push_one(if (self.bindings[push_binding]) |value| value.acquire() else null);
}, },
.push_closure => |push_closure| { .bind => |bind| {
// TODO: Implement. const callable = try env.expect(try env.pop_local());
_ = push_closure;
unreachable; errdefer env.discard(callable);
const chunk = @as(*Self, @ptrCast(@alignCast(try env.unwrap_dynamic(callable, typeinfo))));
chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*kym.RuntimeRef, null));
for (0 .. bind) |index| {
const value = try env.pop_local();
errdefer {
if (value) |ref| {
env.discard(ref);
}
}
const binding = &chunk.bindings[index];
if (binding.*) |*existing_binding| {
env.discard(existing_binding.*);
}
binding.* = value;
}
try env.locals.push_one(callable);
}, },
.push_builtin => |push_builtin| { .push_builtin => |push_builtin| {
@ -257,6 +301,34 @@ pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeE
local.* = try env.pop_local(); local.* = try env.pop_local();
}, },
.get_box => {
const box = try env.expect(try env.pop_local());
defer env.discard(box);
const boxed = box.as_boxed() orelse {
return env.raise(error.TypeMismatch, "{typename} is not unboxable", .{.typename = box.typename()});
};
try env.locals.push_one(if (boxed.*) |value| value.acquire() else null);
},
.set_box => {
const box = try env.expect(try env.pop_local());
errdefer env.discard(box);
const boxed = box.as_boxed() orelse {
return env.raise(error.TypeMismatch, "{typename} is not unboxable", .{.typename = box.typename()});
};
if (boxed.*) |value| {
env.discard(value);
}
boxed.* = box;
},
.get_dynamic => { .get_dynamic => {
const index = try env.expect(try env.pop_local()); const index = try env.expect(try env.pop_local());
@ -286,15 +358,11 @@ pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeE
} }
} }
const index = try env.pop_local() orelse { const index = try env.expect(try env.pop_local());
return env.raise(error.TypeMismatch, "nil is not a valid index");
};
defer env.discard(index); defer env.discard(index);
const indexable = try env.pop_local() orelse { const indexable = try env.expect(try env.pop_local());
return env.raise(error.TypeMismatch, "nil is not a valid indexable");
};
defer env.discard(indexable); defer env.discard(indexable);
@ -307,7 +375,7 @@ pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeE
defer env.discard(callable); defer env.discard(callable);
const call_frame = try env.push_frame(call); const call_frame = try env.push_frame(callable, call);
defer env.pop_frame(); defer env.pop_frame();
@ -494,14 +562,27 @@ pub fn free(self: *Self, env: *kym.RuntimeEnv) void {
self.constants.free(); self.constants.free();
self.opcodes.free(); self.opcodes.free();
env.discard(self.name); env.discard(self.name);
if (self.bindings.len != 0) {
for (self.bindings) |binding| {
if (binding) |value| {
env.discard(value);
}
} }
pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) kym.RuntimeError!Self { env.allocator.deallocate(self.bindings);
}
self.bindings = &.{};
}
pub fn make(env: *kym.RuntimeEnv, name: *const kym.RuntimeRef, environment: *const tree.Environment) kym.RuntimeError!Self {
var chunk = Self{ var chunk = Self{
.name = try env.new_symbol(name), .name = name.acquire(),
.opcodes = OpcodeList.make(env.allocator), .opcodes = OpcodeList.make(env.allocator),
.constants = ConstList.make(env.allocator), .constants = ConstList.make(env.allocator),
.arity = 0, .bindings = &.{},
.arity = environment.argument_count,
}; };
var compiler = Compiler{ var compiler = Compiler{
@ -515,33 +596,33 @@ pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *con
} }
fn syscall_import(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { fn syscall_import(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
return env.import(file.Path.from(&.{try env.unbox_string(try frame.get_arg(env, 0))})); return env.import(file.Path.from(&.{try env.unwrap_string(try frame.expect_arg(env, 0))}));
} }
fn syscall_print(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { fn syscall_print(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
env.print(try env.unbox_string(try frame.get_arg(env, 0))); env.print(try env.unwrap_string(try frame.expect_arg(env, 0)));
return null; return null;
} }
fn syscall_vec2(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { fn syscall_vec2(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
const x = @as(f32, @floatCast(try env.unbox_float(try frame.get_arg(env, 0)))); const x = @as(f32, @floatCast(try env.unwrap_float(try frame.expect_arg(env, 0))));
if (frame.has_arg(env, 1)) |y| { if (frame.has_arg(env, 1)) |y| {
return env.new_vector2(x, @floatCast(try env.unbox_float(y))); return env.new_vector2(x, @floatCast(try env.unwrap_float(y)));
} }
return env.new_vector2(x, x); return env.new_vector2(x, x);
} }
fn syscall_vec3(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { fn syscall_vec3(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
const x = @as(f32, @floatCast(try env.unbox_float(try frame.get_arg(env, 0)))); const x = @as(f32, @floatCast(try env.unwrap_float(try frame.expect_arg(env, 0))));
if (frame.has_arg(env, 1)) |y| { if (frame.has_arg(env, 1)) |y| {
return env.new_vector3( return env.new_vector3(
x, x,
@floatCast(try env.unbox_float(y)), @floatCast(try env.unwrap_float(y)),
@floatCast(try env.unbox_float(try frame.get_arg(env, 2))), @floatCast(try env.unwrap_float(try frame.expect_arg(env, 2))),
); );
} }
@ -559,7 +640,11 @@ fn typeinfo_call(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, frame: kym.Fra
const chunk = @as(*Self, @ptrCast(@alignCast(userdata))); const chunk = @as(*Self, @ptrCast(@alignCast(userdata)));
if (frame.arg_count < chunk.arity) { if (frame.arg_count < chunk.arity) {
return env.raise(error.BadOperation, "expected more arguments"); return env.raise(error.BadOperation, "expected `{expected_count}` {noun}, {provided_count} provided", .{
.expected_count = frame.arg_count,
.provided_count = chunk.arity,
.noun = if (frame.arg_count == 1) "argument" else "arguments",
});
} }
return chunk.execute(env, frame); return chunk.execute(env, frame);

View File

@ -21,7 +21,7 @@ fn compile_argument(self: Self, environment: *const tree.Environment, initial_ar
var argument_count = @as(u8, 0); var argument_count = @as(u8, 0);
while (maybe_argument) |argument| { while (maybe_argument) |argument| {
try self.compile_expression(environment, argument); try self.compile_expression(environment, argument, null);
maybe_argument = argument.next; maybe_argument = argument.next;
argument_count += 1; argument_count += 1;
@ -30,7 +30,7 @@ fn compile_argument(self: Self, environment: *const tree.Environment, initial_ar
return argument_count; return argument_count;
} }
fn compile_expression(self: Self, environment: *const tree.Environment, expression: *const Expr) kym.RuntimeError!void { fn compile_expression(self: Self, environment: *const tree.Environment, expression: *const Expr, name: ?[]const coral.io.Byte) kym.RuntimeError!void {
const number_format = coral.utf8.DecimalFormat{ const number_format = coral.utf8.DecimalFormat{
.delimiter = "_", .delimiter = "_",
.positive_prefix = .none, .positive_prefix = .none,
@ -68,7 +68,7 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
var field_count = @as(u32, 0); var field_count = @as(u32, 0);
while (table_entry) |entry| : (table_entry = entry.next) { while (table_entry) |entry| : (table_entry = entry.next) {
try self.compile_expression(environment, entry); try self.compile_expression(environment, entry, null);
if (entry.kind != .key_value) { if (entry.kind != .key_value) {
try self.chunk.opcodes.push_one(.push_top); try self.chunk.opcodes.push_one(.push_top);
@ -81,29 +81,42 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
}, },
.key_value => |key_value| { .key_value => |key_value| {
try self.compile_expression(environment, key_value.value); try self.compile_expression(environment, key_value.value, null);
try self.compile_expression(environment, key_value.key); try self.compile_expression(environment, key_value.key, null);
}, },
.lambda_construct => |lambda_construct| { .lambda_construct => |lambda_construct| {
var chunk = try Chunk.make(self.env, "<lambda>", lambda_construct.environment); const anonymous_chunk_name = try self.env.new_symbol(name orelse "<lambda>");
defer self.env.discard(anonymous_chunk_name);
var chunk = try Chunk.make(self.env, anonymous_chunk_name, lambda_construct.environment);
errdefer chunk.free(self.env); errdefer chunk.free(self.env);
if (lambda_construct.environment.capture_count == 0) {
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_chunk(chunk)}); try self.chunk.opcodes.push_one(.{.push_const = try self.declare_chunk(chunk)});
} else {
const lambda_captures = lambda_construct.environment.get_captures();
var index = lambda_captures.len;
if (lambda_construct.environment.capture_count != 0) { while (index != 0) {
for (lambda_construct.environment.captures[0 .. lambda_construct.environment.capture_count]) |capture| { index -= 1;
try self.chunk.opcodes.push_one(.{.push_local = environment.captures[capture]});
try self.chunk.opcodes.push_one(switch (lambda_captures[index]) {
.declaration_index => |declaration_index| .{.push_local = declaration_index},
.capture_index => |capture_index| .{.push_binding = capture_index},
});
} }
try self.chunk.opcodes.push_one(.{.push_closure = lambda_construct.environment.capture_count}); try self.chunk.opcodes.push_one(.{.push_const = try self.declare_chunk(chunk)});
try self.chunk.opcodes.push_one(.{.bind = lambda_construct.environment.capture_count});
} }
}, },
.binary_op => |binary_op| { .binary_op => |binary_op| {
try self.compile_expression(environment, binary_op.lhs_operand); try self.compile_expression(environment, binary_op.lhs_operand, null);
try self.compile_expression(environment, binary_op.rhs_operand); try self.compile_expression(environment, binary_op.rhs_operand, null);
try self.chunk.opcodes.push_one(switch (binary_op.operation) { try self.chunk.opcodes.push_one(switch (binary_op.operation) {
.addition => .add, .addition => .add,
@ -119,7 +132,7 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
}, },
.unary_op => |unary_op| { .unary_op => |unary_op| {
try self.compile_expression(environment, unary_op.operand); try self.compile_expression(environment, unary_op.operand, null);
try self.chunk.opcodes.push_one(switch (unary_op.operation) { try self.chunk.opcodes.push_one(switch (unary_op.operation) {
.boolean_negation => .not, .boolean_negation => .not,
@ -130,11 +143,11 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
.invoke => |invoke| { .invoke => |invoke| {
const argument_count = try self.compile_argument(environment, invoke.argument); const argument_count = try self.compile_argument(environment, invoke.argument);
try self.compile_expression(environment, invoke.object); try self.compile_expression(environment, invoke.object, null);
try self.chunk.opcodes.push_one(.{.call = argument_count}); try self.chunk.opcodes.push_one(.{.call = argument_count});
}, },
.group => |group| try self.compile_expression(environment, group), .group => |group| try self.compile_expression(environment, group, null),
.import_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .import}), .import_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .import}),
.print_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .print}), .print_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .print}),
.vec2_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .vec2}), .vec2_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .vec2}),
@ -145,56 +158,63 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
return self.chunk.opcodes.push_one(.{.push_local = index}); return self.chunk.opcodes.push_one(.{.push_local = index});
} }
if (get_capture_index(environment, declaration_get.declaration)) |index| { if (try self.get_binding_index(environment, declaration_get.declaration)) |index| {
try self.chunk.opcodes.push_one(.push_self); try self.chunk.opcodes.push_one(.{.push_binding = index});
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_fixed(index)});
return self.chunk.opcodes.push_one(.get_dynamic); if (is_declaration_boxed(declaration_get.declaration)) {
try self.chunk.opcodes.push_one(.get_box);
} }
return self.env.raise(error.IllegalState, "local out of scope"); return;
}
return self.env.raise(error.IllegalState, "local out of scope", .{});
}, },
.declaration_set => |declaration_set| { .declaration_set => |declaration_set| {
try self.compile_expression(environment, declaration_set.assign);
if (get_local_index(environment, declaration_set.declaration)) |index| { if (get_local_index(environment, declaration_set.declaration)) |index| {
try self.compile_expression(environment, declaration_set.assign, null);
return self.chunk.opcodes.push_one(.{.set_local = index}); return self.chunk.opcodes.push_one(.{.set_local = index});
} }
if (get_capture_index(environment, declaration_set.declaration)) |index| { if (try self.get_binding_index(environment, declaration_set.declaration)) |index| {
try self.chunk.opcodes.push_one(.push_self); try self.chunk.opcodes.push_one(.{.push_binding = index});
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_fixed(index)}); try self.compile_expression(environment, declaration_set.assign, null);
return self.chunk.opcodes.push_one(.set_dynamic); if (is_declaration_boxed(declaration_set.declaration)) {
try self.chunk.opcodes.push_one(.set_box);
} }
return self.env.raise(error.IllegalState, "local out of scope"); return;
}
return self.env.raise(error.IllegalState, "local out of scope", .{});
}, },
.field_get => |field_get| { .field_get => |field_get| {
try self.compile_expression(environment, field_get.object); try self.compile_expression(environment, field_get.object, null);
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(field_get.identifier)}); try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(field_get.identifier)});
try self.chunk.opcodes.push_one(.get_dynamic); try self.chunk.opcodes.push_one(.get_dynamic);
}, },
.field_set => |field_set| { .field_set => |field_set| {
try self.compile_expression(environment, field_set.object); try self.compile_expression(environment, field_set.object, null);
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(field_set.identifier)}); try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(field_set.identifier)});
try self.compile_expression(environment, field_set.assign); try self.compile_expression(environment, field_set.assign, null);
try self.chunk.opcodes.push_one(.set_dynamic); try self.chunk.opcodes.push_one(.set_dynamic);
}, },
.subscript_get => |subscript_get| { .subscript_get => |subscript_get| {
try self.compile_expression(environment, subscript_get.object); try self.compile_expression(environment, subscript_get.object, null);
try self.compile_expression(environment, subscript_get.index); try self.compile_expression(environment, subscript_get.index, null);
try self.chunk.opcodes.push_one(.get_dynamic); try self.chunk.opcodes.push_one(.get_dynamic);
}, },
.subscript_set => |subscript_set| { .subscript_set => |subscript_set| {
try self.compile_expression(environment, subscript_set.object); try self.compile_expression(environment, subscript_set.object, null);
try self.compile_expression(environment, subscript_set.index); try self.compile_expression(environment, subscript_set.index, null);
try self.compile_expression(environment, subscript_set.assign); try self.compile_expression(environment, subscript_set.assign, null);
try self.chunk.opcodes.push_one(.set_dynamic); try self.chunk.opcodes.push_one(.set_dynamic);
}, },
} }
@ -217,14 +237,17 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s
switch (current_statement.kind) { switch (current_statement.kind) {
.@"return" => |@"return"| { .@"return" => |@"return"| {
if (@"return".returned_expression) |expression| { if (@"return".returned_expression) |expression| {
try self.compile_expression(environment, expression); try self.compile_expression(environment, expression, null);
} else { } else {
try self.chunk.opcodes.push_one(.push_nil); try self.chunk.opcodes.push_one(.push_nil);
} }
// TODO: Omit ret calls at ends of chunk.
try self.chunk.opcodes.push_one(.ret);
}, },
.@"while" => |@"while"| { .@"while" => |@"while"| {
try self.compile_expression(environment, @"while".loop_expression); try self.compile_expression(environment, @"while".loop_expression, null);
try self.chunk.opcodes.push_one(.{.jf = 0}); try self.chunk.opcodes.push_one(.{.jf = 0});
const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1)); const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1));
@ -232,12 +255,12 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s
_ = try self.compile_statement(environment, @"while".loop); _ = try self.compile_statement(environment, @"while".loop);
self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1); self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1);
try self.compile_expression(environment, @"while".loop_expression); try self.compile_expression(environment, @"while".loop_expression, null);
try self.chunk.opcodes.push_one(.{.jt = origin_index}); try self.chunk.opcodes.push_one(.{.jt = origin_index});
}, },
.@"if" => |@"if"| { .@"if" => |@"if"| {
try self.compile_expression(environment, @"if".then_expression); try self.compile_expression(environment, @"if".then_expression, null);
try self.chunk.opcodes.push_one(.{.jf = 0}); try self.chunk.opcodes.push_one(.{.jf = 0});
const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1)); const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1));
@ -251,15 +274,15 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s
}, },
.declare => |declare| { .declare => |declare| {
try self.compile_expression(environment, declare.initial_expression); try self.compile_expression(environment, declare.initial_expression, declare.declaration.identifier);
if (declare.declaration.is_captured and !declare.declaration.is_readonly) { if (is_declaration_boxed(declare.declaration)) {
try self.chunk.opcodes.push_one(.push_boxed); try self.chunk.opcodes.push_one(.push_boxed);
} }
}, },
.top_expression => |top_expression| { .top_expression => |top_expression| {
try self.compile_expression(environment, top_expression); try self.compile_expression(environment, top_expression, null);
if (top_expression.kind == .invoke) { if (top_expression.kind == .invoke) {
try self.chunk.opcodes.push_one(.pop); try self.chunk.opcodes.push_one(.pop);
@ -271,12 +294,16 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s
} }
} }
const constants_max = @as(usize, coral.math.max_int(@typeInfo(u16).Int));
fn declare_chunk(self: Self, chunk: Chunk) kym.RuntimeError!u16 { fn declare_chunk(self: Self, chunk: Chunk) kym.RuntimeError!u16 {
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = @as(usize, coral.math.max_int(@typeInfo(u16).Int)),
});
} }
const constant = try self.env.new_dynamic(coral.io.bytes_of(&chunk).ptr, Chunk.typeinfo); const constant = try self.env.new_dynamic(coral.io.bytes_of(&chunk), Chunk.typeinfo);
errdefer self.env.discard(constant); errdefer self.env.discard(constant);
@ -286,8 +313,10 @@ fn declare_chunk(self: Self, chunk: Chunk) kym.RuntimeError!u16 {
} }
fn declare_fixed(self: Self, fixed: kym.Fixed) kym.RuntimeError!u16 { fn declare_fixed(self: Self, fixed: kym.Fixed) kym.RuntimeError!u16 {
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
} }
const constant = try self.env.new_fixed(fixed); const constant = try self.env.new_fixed(fixed);
@ -300,8 +329,10 @@ fn declare_fixed(self: Self, fixed: kym.Fixed) kym.RuntimeError!u16 {
} }
fn declare_float(self: Self, float: kym.Float) kym.RuntimeError!u16 { fn declare_float(self: Self, float: kym.Float) kym.RuntimeError!u16 {
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
} }
const constant = try self.env.new_float(float); const constant = try self.env.new_float(float);
@ -314,8 +345,10 @@ fn declare_float(self: Self, float: kym.Float) kym.RuntimeError!u16 {
} }
fn declare_string(self: Self, string: []const coral.io.Byte) kym.RuntimeError!u16 { fn declare_string(self: Self, string: []const coral.io.Byte) kym.RuntimeError!u16 {
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
} }
const constant = try self.env.new_string(string); const constant = try self.env.new_string(string);
@ -328,8 +361,10 @@ fn declare_string(self: Self, string: []const coral.io.Byte) kym.RuntimeError!u1
} }
fn declare_symbol(self: Self, symbol: []const coral.io.Byte) kym.RuntimeError!u16 { fn declare_symbol(self: Self, symbol: []const coral.io.Byte) kym.RuntimeError!u16 {
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
} }
const constant = try self.env.new_symbol(symbol); const constant = try self.env.new_symbol(symbol);
@ -341,30 +376,42 @@ fn declare_symbol(self: Self, symbol: []const coral.io.Byte) kym.RuntimeError!u1
return @intCast(self.chunk.constants.values.len - 1); return @intCast(self.chunk.constants.values.len - 1);
} }
pub fn get_capture_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 { pub fn get_binding_index(self: *const Self, environment: *const tree.Environment, declaration: *const tree.Declaration) kym.RuntimeError!?u8 {
if (self.enclosing) |enclosing_environment| { var binding_index = @as(u8, 0);
var capture_index = @as(u8, 0);
while (capture_index < self.capture_count) : (capture_index += 1) { while (binding_index < environment.capture_count) : (binding_index += 1) {
if (&enclosing_environment.local_declarations[capture_index] == declaration) { var capture = &environment.captures[binding_index];
return capture_index; var target_environment = environment.enclosing orelse return null;
while (capture.* == .capture_index) {
capture = &target_environment.captures[capture.capture_index];
target_environment = target_environment.enclosing orelse return null;
} }
try kym.assert(self.env, capture.* == .declaration_index);
if (&target_environment.declarations[capture.declaration_index] == declaration) {
kayomn marked this conversation as resolved
Review

This shouldn't be capable of crashing the process - needs better handling.

This shouldn't be capable of crashing the process - needs better handling.
return binding_index;
} }
} }
return null; return null;
} }
pub fn get_local_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 { pub fn get_local_index(environment: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
var remaining = self.local_declaration_count; var remaining = environment.declaration_count;
while (remaining != 0) { while (remaining != 0) {
remaining -= 1; remaining -= 1;
if (&self.local_declarations[remaining] == declaration) { if (&environment.declarations[remaining] == declaration) {
return remaining; return remaining;
} }
} }
return null; return null;
} }
fn is_declaration_boxed(declaration: *const tree.Declaration) bool {
return declaration.is.captured and !declaration.is.readonly;
}

View File

@ -174,11 +174,17 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
return root.create_expr(.{ return root.create_expr(.{
.kind = switch (expression.kind) { .kind = switch (expression.kind) {
.declaration_get => |declaration_get| .{ .declaration_get => |declaration_get| convert: {
if (declaration_get.declaration.is.readonly) {
return root.report_error(stream, "readonly declarations cannot be re-assigned", .{});
}
break: convert .{
.declaration_set = .{ .declaration_set = .{
.assign = try parse(root, stream, environment), .assign = try parse(root, stream, environment),
.declaration = declaration_get.declaration, .declaration = declaration_get.declaration,
}, },
};
}, },
.field_get => |field_get| .{ .field_get => |field_get| .{
@ -414,15 +420,13 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
stream.skip_newlines(); stream.skip_newlines();
return root.create_expr(.{ return root.create_expr(.{
.next = null,
.kind = .{ .kind = .{
.declaration_get = .{ .declaration_get = .{
.declaration = (try environment.resolve_declaration(identifier)) orelse { .declaration = (try environment.resolve_declaration(identifier)) orelse {
return root.report_error(stream, "undefined identifier `{identifier}`", .{ return root.report_error(stream, "undefined identifier `{identifier}`", .{
.identifier = identifier, .identifier = identifier,
}); });
}, }
}, },
}, },
}); });
@ -445,13 +449,9 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
else => return root.report_error(stream, "expected identifier", .{}), else => return root.report_error(stream, "expected identifier", .{}),
}; };
if (try lambda_environment.declare(identifier) == null) { _ = lambda_environment.declare_argument(identifier) catch |declare_error| {
return root.report_error(stream, "declaration `{identifier}` already exists", .{ return root.report_declare_error(stream, identifier, declare_error);
.identifier = identifier, };
});
}
lambda_environment.argument_count += 1;
stream.skip_newlines(); stream.skip_newlines();

View File

@ -93,18 +93,13 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
}, },
.keyword_var, .keyword_let => { .keyword_var, .keyword_let => {
const storage_token = stream.token; const is_constant = stream.token == .keyword_let;
stream.skip_newlines(); stream.skip_newlines();
const identifier = switch (stream.token) { const identifier = switch (stream.token) {
.identifier => |identifier| identifier, .identifier => |identifier| identifier,
else => return root.report_error(stream, "expected identifier after declaration", .{}),
else => {
return root.report_error(stream, "expected identifier after `{storage}` declaration statement", .{
.storage = storage_token.text()
});
},
}; };
stream.skip_newlines(); stream.skip_newlines();
@ -122,10 +117,16 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
.declare = .{ .declare = .{
.initial_expression = try Expr.parse(root, stream, environment), .initial_expression = try Expr.parse(root, stream, environment),
.declaration = (try root.environment.declare(identifier)) orelse { .declaration = declare: {
return root.report_error(stream, "declaration `{identifier}` already exists", .{ if (is_constant) {
.identifier = identifier, break: declare environment.declare_constant(identifier) catch |declaration_error| {
}); return root.report_declare_error(stream, identifier, declaration_error);
kayomn marked this conversation as resolved Outdated

Shorten to use root.report_declare_error.

Shorten to use `root.report_declare_error`.
};
}
break: declare environment.declare_variable(identifier) catch |declaration_error| {
return root.report_declare_error(stream, identifier, declaration_error);
};
}, },
}, },
}, },

View File

@ -8,19 +8,31 @@ const tokens = @import("./tokens.zig");
pub const Declaration = struct { pub const Declaration = struct {
identifier: []const coral.io.Byte, identifier: []const coral.io.Byte,
is_readonly: bool = false,
is_captured: bool = false, is: packed struct {
readonly: bool = false,
captured: bool = false,
} = .{},
}; };
pub const Environment = struct { pub const Environment = struct {
captures: [capture_max]u8 = [_]u8{0} ** capture_max, captures: [capture_max]Capture = [_]Capture{.{.declaration_index = 0}} ** capture_max,
capture_count: u8 = 0, capture_count: u8 = 0,
local_declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max, declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max,
local_declaration_count: u8 = 0, declaration_count: u8 = 0,
argument_count: u8 = 0, argument_count: u8 = 0,
statement: ?*const Stmt = null, statement: ?*const Stmt = null,
enclosing: ?*Environment = null, enclosing: ?*Environment = null,
pub const Capture = union (enum) {
declaration_index: u8,
capture_index: u8,
};
pub const DeclareError = coral.io.AllocationError || error {
kayomn marked this conversation as resolved Outdated

Should ideally be marked pub as is used on public interface for struct.

Should ideally be marked pub as is used on public interface for struct.
DeclarationExists,
};
const capture_max = coral.math.max_int(@typeInfo(u8).Int); const capture_max = coral.math.max_int(@typeInfo(u8).Int);
const declaration_max = coral.math.max_int(@typeInfo(u8).Int); const declaration_max = coral.math.max_int(@typeInfo(u8).Int);
@ -31,48 +43,96 @@ pub const Environment = struct {
}); });
} }
pub fn declare(self: *Environment, identifier: []const coral.io.Byte) coral.io.AllocationError!?*const Declaration { fn declare(self: *Environment, declaration: Declaration) DeclareError!*const Declaration {
if (self.local_declaration_count == self.local_declarations.len) { if (self.declaration_count == self.declarations.len) {
return error.OutOfMemory; return error.OutOfMemory;
} }
const declaration = &self.local_declarations[self.local_declaration_count]; {
declaration.* = .{.identifier = identifier};
self.local_declaration_count += 1;
return declaration;
}
pub fn resolve_declaration(self: *Environment, identifier: []const coral.io.Byte) coral.io.AllocationError!?*const Declaration {
var environment = self; var environment = self;
var ancestry = @as(usize, 0);
while (true) : (ancestry += 1) { while (true) {
var remaining_count = self.local_declaration_count; var remaining_count = environment.declaration_count;
while (remaining_count != 0) { while (remaining_count != 0) {
remaining_count -= 1; remaining_count -= 1;
const declaration = &self.local_declarations[remaining_count]; if (coral.io.are_equal(environment.declarations[remaining_count].identifier, declaration.identifier)) {
return error.DeclarationExists;
}
}
environment = environment.enclosing orelse break;
}
}
const declaration_slot = &self.declarations[self.declaration_count];
declaration_slot.* = declaration;
self.declaration_count += 1;
return declaration_slot;
}
pub fn declare_argument(self: *Environment, identifier: []const coral.io.Byte) DeclareError!*const Declaration {
coral.debug.assert(self.declaration_count <= self.argument_count);
defer self.argument_count += 1;
return self.declare(.{
.identifier = identifier,
.is = .{.readonly = true},
});
}
pub fn declare_constant(self: *Environment, identifier: []const coral.io.Byte) DeclareError!*const Declaration {
return self.declare(.{
.identifier = identifier,
.is = .{.readonly = true},
});
}
pub fn declare_variable(self: *Environment, identifier: []const coral.io.Byte) DeclareError!*const Declaration {
return self.declare(.{.identifier = identifier});
}
pub fn resolve_declaration(self: *Environment, identifier: []const coral.io.Byte) coral.io.AllocationError!?*const Declaration {
var environment = self;
var ancestry = @as(u32, 0);
while (true) : (ancestry += 1) {
var remaining_count = environment.declaration_count;
while (remaining_count != 0) {
remaining_count -= 1;
const declaration = &environment.declarations[remaining_count];
if (coral.io.are_equal(declaration.identifier, identifier)) { if (coral.io.are_equal(declaration.identifier, identifier)) {
if (ancestry != 0) { if (ancestry != 0) {
declaration.is_captured = true; declaration.is.captured = true;
environment = self; environment = self;
ancestry -= 1;
while (ancestry != 0) : (ancestry -= 1) { while (ancestry != 0) : (ancestry -= 1) {
if (environment.capture_count == self.captures.len) { if (environment.capture_count == environment.captures.len) {
return error.OutOfMemory; return error.OutOfMemory;
} }
environment.captures[environment.capture_count] = remaining_count;
environment.capture_count += 1;
coral.debug.assert(environment.enclosing != null); coral.debug.assert(environment.enclosing != null);
environment = environment.enclosing.?; const enclosing_environment = environment.enclosing.?;
environment.captures[environment.capture_count] = .{
.capture_index = enclosing_environment.capture_count
};
environment.capture_count += 1;
environment = enclosing_environment;
} }
environment.captures[environment.capture_count] = .{.declaration_index = remaining_count};
environment.capture_count += 1;
} }
return declaration; return declaration;
@ -82,6 +142,14 @@ pub const Environment = struct {
environment = environment.enclosing orelse return null; environment = environment.enclosing orelse return null;
} }
} }
pub fn get_captures(self: *const Environment) []const Capture {
return self.captures[0 .. self.capture_count];
}
pub fn get_declarations(self: *const Environment) []const Declaration {
return self.declarations[0 .. self.declaration_count];
}
}; };
pub const ParseError = coral.io.AllocationError || error { pub const ParseError = coral.io.AllocationError || error {
@ -106,6 +174,16 @@ pub const Root = struct {
return error.BadSyntax; return error.BadSyntax;
} }
pub fn report_declare_error(self: *Root, stream: *tokens.Stream, identifier: []const coral.io.Byte, @"error": Environment.DeclareError) ParseError {
return switch (@"error") {
error.OutOfMemory => error.OutOfMemory,
error.DeclarationExists => self.report_error(stream, "declaration `{identifier}` already exists", .{
.identifier = identifier,
}),
};
}
pub fn create_expr(self: *Root, expr: Expr) coral.io.AllocationError!*Expr { pub fn create_expr(self: *Root, expr: Expr) coral.io.AllocationError!*Expr {
return coral.io.allocate_one(self.arena.as_allocator(), expr); return coral.io.allocate_one(self.arena.as_allocator(), expr);
} }