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):
@print("This is a func call")
@print(pfx)
@print(msg)
end
end
let pr = printer()
var i = 0
let pr = printer("this is a final closure")
pr("test")
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
pr("goodbye")
return {
.title = "Game",

View File

@ -196,6 +196,8 @@ pub const HexadecimalFormat = struct {
_ = self;
_ = writer;
_ = value;
unreachable;
}
};

View File

@ -21,7 +21,7 @@ pub const Manifest = struct {
if (try kym.get_field(env, manifest, "width")) |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)) {
break: get @intCast(fixed);
@ -35,7 +35,7 @@ pub const Manifest = struct {
if (try kym.get_field(env, manifest, "height")) |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)) {
break: get @intCast(fixed);
@ -49,7 +49,7 @@ pub const Manifest = struct {
if (try kym.get_field(env, manifest, "tick_rate")) |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;
@ -58,7 +58,7 @@ pub const Manifest = struct {
if (try kym.get_field(env, manifest, "title")) |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);
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,
.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,
.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 file_size = ext.SDL_RWseek(rw_ops, 0, ext.RW_SEEK_END);
@ -96,12 +96,8 @@ pub const Path = extern struct {
return path;
}
pub fn to_string(self: Path) ?[:0]const coral.io.Byte {
const last_index = self.data.len - 1;
if (self.data[last_index] != 0) {
return null;
}
pub fn get_string(self: Path) [:0]const coral.io.Byte {
coral.debug.assert(self.data[self.data.len - 1] == 0);
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 app = @import("./app.zig");
const builtin = @import("builtin");
const coral = @import("coral");
const file = @import("./file.zig");
@ -15,7 +11,7 @@ const tokens = @import("./kym/tokens.zig");
const tree = @import("./kym/tree.zig");
pub const Frame = struct {
name: []const coral.io.Byte = "",
name_stringable: *RuntimeRef,
arg_count: u8,
locals_top: usize,
@ -23,8 +19,8 @@ pub const Frame = struct {
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 {
return self.has_arg(env, arg_index) orelse env.raise(error.BadOperation, "nil reference");
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.TypeMismatch, "nil reference", .{});
}
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),
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 => |rhs_float| self.new_float(lhs_float + rhs_float),
.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));
}
const frame = try self.push_frame(args.len);
const frame = try self.push_frame(callable, args.len);
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 {
return switch (callable.object().payload) {
.syscall => |syscall| syscall(self, 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 => |rhs_fixed| @as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed)),
.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 => |rhs_float| lhs_float - rhs_float,
.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) {
.false, .true, .float, .fixed, .symbol, .vector2, .vector3, .syscall => {},
.boxed => |*boxed| {
if (boxed.*) |boxed_value| {
self.discard(boxed_value);
}
},
.string => |string| {
coral.debug.assert(string.len >= 0);
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 => |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),
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 => |rhs_float| self.new_float(lhs_float / rhs_float),
.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 {
return value orelse self.raise(error.TypeMismatch, "nil reference");
return value orelse self.raise(error.TypeMismatch, "nil reference", .{});
}
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 {
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: {
const swizzle_symbol = try self.unbox_symbol(index);
const swizzle_symbol = try self.unwrap_symbol(index);
var swizzle_buffer = [_]f32{0} ** 3;
var swizzle_count = @as(usize, 0);
@ -228,7 +246,7 @@ pub const RuntimeEnv = struct {
},
.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_count = @as(usize, 0);
@ -254,14 +272,22 @@ pub const RuntimeEnv = struct {
},
.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 {
const file_name = file_path.get_string();
var chunk = make_chunk: {
const file_data =
(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);
@ -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);
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();
@ -329,16 +352,24 @@ pub const RuntimeEnv = struct {
},
.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 => |rhs_float| self.new_float(lhs_float * rhs_float),
.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) {
.fixed => |fixed| self.new_fixed(-fixed),
.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(
self: *RuntimeEnv,
userdata: [*]const coral.io.Byte,
userdata: []const coral.io.Byte,
typeinfo: *const Typeinfo,
) RuntimeError!*RuntimeRef {
coral.debug.assert(userdata.len == typeinfo.size);
const dynamic = try self.allocator.reallocate(null, @sizeOf(usize) + typeinfo.size);
errdefer self.allocator.deallocate(dynamic);
@ -440,7 +480,7 @@ pub const RuntimeEnv = struct {
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 {
@ -470,23 +510,33 @@ pub const RuntimeEnv = struct {
}
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) {
if (self.locals.pop() orelse unreachable) |local| {
coral.debug.assert(popped_frame != null);
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);
}
to_pop -= 1;
locals_to_pop -= 1;
}
}
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{
.name_stringable = name_stringable.acquire(),
.arg_count = arg_count,
.locals_top = self.locals.values.len - arg_count,
};
@ -496,8 +546,14 @@ pub const RuntimeEnv = struct {
return frame;
}
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError {
self.print_error(message);
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, comptime format: []const coral.io.Byte, args: anytype) RuntimeError {
{
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()) {
self.print_error("stack trace:");
@ -507,7 +563,17 @@ pub const RuntimeEnv = struct {
while (remaining_frames != 0) {
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 {
return switch (indexable.object().payload) {
.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),
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 => |rhs_float| self.new_float(lhs_float - rhs_float),
.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.?]);
},
.boxed => |boxed| if (boxed) |boxed_value| self.to_string(boxed_value) else self.new_string("nil"),
.symbol => |symbol| self.new_string(coral.io.slice_sentineled(@as(coral.io.Byte, 0), symbol)),
.string => value.acquire(),
@ -580,7 +658,7 @@ pub const RuntimeEnv = struct {
var string = [_:0]coral.io.Byte{0} ** 64;
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],
.y = vector2[1],
});
@ -594,7 +672,7 @@ pub const RuntimeEnv = struct {
var string = [_:0]coral.io.Byte{0} ** 96;
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],
.y = vector3[1],
.z = vector3[2],
@ -610,34 +688,36 @@ pub const RuntimeEnv = struct {
};
}
pub fn unbox_dynamic(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]coral.io.Byte {
return switch (value.object().payload) {
.dynamic => |dynamic| dynamic.userdata(),
else => self.raise(error.TypeMismatch, "expected fixed object")
};
pub fn unwrap_dynamic(self: *RuntimeEnv, value: *const RuntimeRef, typeinfo: *const Typeinfo) RuntimeError![]coral.io.Byte {
return value.as_dynamic(typeinfo) orelse self.raise(error.TypeMismatch, "expected dynamic object, not {typename}", .{
.typename = value.typename(),
});
}
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) {
.fixed => |fixed| @floatFromInt(fixed),
.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 {
return value.as_fixed() orelse self.raise(error.TypeMismatch, "expected fixed object");
pub fn unwrap_fixed(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Fixed {
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 {
return value.as_string() orelse self.raise(error.TypeMismatch, "expected string object");
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, not {typename}", .{
.typename = value.typename(),
});
}
pub fn unbox_symbol(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![*:0]const coral.io.Byte {
return switch (value.object().payload) {
.symbol => |symbol| symbol,
else => self.raise(error.TypeMismatch, "expected symbol object")
};
pub fn unwrap_symbol(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![*:0]const coral.io.Byte {
return value.as_symbol() orelse self.raise(error.TypeMismatch, "expected symbol, not {typename}", .{
.typename = value.typename(),
});
}
};
@ -661,6 +741,7 @@ pub const RuntimeRef = opaque {
vector2: [2]f32,
vector3: [3]f32,
syscall: *const Syscall,
boxed: ?*RuntimeRef,
string: struct {
ptr: [*]coral.io.Byte,
@ -712,9 +793,23 @@ pub const RuntimeRef = opaque {
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 {
return switch (self.object().payload) {
.fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))),
.fixed => |fixed| fixed,
else => null,
};
}
@ -727,6 +822,14 @@ pub const RuntimeRef = opaque {
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,
};
}
@ -757,6 +860,18 @@ pub const RuntimeRef = opaque {
else => false,
},
.boxed => |boxed| unbox: {
if (boxed) |boxed_value| {
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 => |other_vector| coral.io.are_equal(coral.io.bytes_of(&self_vector), coral.io.bytes_of(&other_vector)),
else => false,
@ -797,6 +912,7 @@ pub const RuntimeRef = opaque {
.vector2 => |vector| @bitCast(vector),
.vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector)),
.syscall => |syscall| @intFromPtr(syscall),
.boxed => |boxed| @intFromPtr(boxed),
.string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()),
.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),
.vector3 => |vector| coral.io.all_equals(coral.io.bytes_of(&vector), 0),
.syscall => true,
.boxed => |boxed| boxed != null,
.string => |string| string.len != 0,
.dynamic => true,
};
@ -827,6 +944,7 @@ pub const RuntimeRef = opaque {
.vector2 => "vector2",
.vector3 => "vector3",
.syscall => "syscall",
.boxed => "boxed",
.string => "string",
.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,
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 {
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 {
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 {
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 builtin = @import("builtin");
const coral = @import("coral");
const file = @import("../file.zig");
@ -12,6 +16,7 @@ name: *kym.RuntimeRef,
arity: u8,
opcodes: OpcodeList,
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 {
import,
@ -23,6 +28,7 @@ const Builtin = enum {
const ConstList = coral.list.Stack(*kym.RuntimeRef);
const OpcodeList = coral.list.Stack(union (enum) {
ret,
pop,
push_nil,
push_true,
@ -32,13 +38,15 @@ const OpcodeList = coral.list.Stack(union (enum) {
push_top,
push_table: u32,
push_builtin: Builtin,
push_closure: u8,
push_self,
push_binding: u8,
push_boxed,
set_local: u8,
get_dynamic,
set_dynamic,
get_box,
set_box,
call: u8,
bind: u8,
not,
neg,
@ -67,23 +75,21 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
defer buffer.free();
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) {
_ = coral.utf8.print_formatted(writer, "[{instruction}]: ", .{.instruction = opcode_cursor});
_ = switch (chunk.opcodes.values[opcode_cursor]) {
.ret => coral.utf8.print_string(writer, "ret\n"),
.pop => coral.utf8.print_string(writer, "pop\n"),
.push_nil => coral.utf8.print_string(writer, "push nil\n"),
.push_true => coral.utf8.print_string(writer, "push true\n"),
.push_false => coral.utf8.print_string(writer, "push false\n"),
.push_const => |push_const| print: {
if (push_const >= chunk.constants.values.len) {
return env.raise(error.IllegalState, "invalid constant");
}
try kym.assert(env, push_const < chunk.constants.values.len);
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_self => coral.utf8.print_string(writer, "push self\n"),
.push_closure => |push_closure| coral.utf8.print_formatted(writer, "push closure <{count}>\n", .{
.count = push_closure,
.push_binding => |push_binding| coral.utf8.print_formatted(writer, "push binding <{binding}>\n", .{
.binding = push_binding,
}),
.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"),
.set_dynamic => coral.utf8.print_string(writer, "set dynamic\n"),
.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);
}
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);
while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) {
switch (chunk.opcodes.values[opcode_cursor]) {
while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) {
switch (self.opcodes.values[opcode_cursor]) {
.ret => break,
.pop => {
if (try env.pop_local()) |ref| {
env.discard(ref);
@ -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_const => |push_const| {
if (push_const >= chunk.constants.values.len) {
return env.raise(error.IllegalState, "invalid constant");
}
try env.locals.push_one(chunk.constants.values[push_const].acquire());
try kym.assert(env, push_const < self.constants.values.len);
try env.locals.push_one(self.constants.values[push_const].acquire());
},
.push_local => |push_local| {
if (push_local >= env.locals.values.len) {
return env.raise(error.IllegalState, "invalid local");
}
try kym.assert(env, push_local < (env.locals.values.len - frame.locals_top));
if (env.locals.values[frame.locals_top + push_local]) |local| {
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 => {
const frame_locals = env.locals.values[frame.locals_top ..];
if (frame_locals.len == 0) {
return env.raise(error.IllegalState, "stack overflow");
}
try kym.assert(env, frame_locals.len != 0);
if (frame_locals[frame_locals.len - 1]) |local| {
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 => {
// TODO: Implement.
unreachable;
const value = try env.pop_local();
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 => {
// TODO: Implement.
unreachable;
.push_binding => |push_binding| {
try kym.assert(env, push_binding <= self.bindings.len);
try env.locals.push_one(if (self.bindings[push_binding]) |value| value.acquire() else null);
},
.push_closure => |push_closure| {
// TODO: Implement.
_ = push_closure;
unreachable;
.bind => |bind| {
const callable = try env.expect(try env.pop_local());
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| {
@ -257,6 +301,34 @@ pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeE
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 => {
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 {
return env.raise(error.TypeMismatch, "nil is not a valid index");
};
const index = try env.expect(try env.pop_local());
defer env.discard(index);
const indexable = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not a valid indexable");
};
const indexable = try env.expect(try env.pop_local());
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);
const call_frame = try env.push_frame(call);
const call_frame = try env.push_frame(callable, call);
defer env.pop_frame();
@ -494,14 +562,27 @@ pub fn free(self: *Self, env: *kym.RuntimeEnv) void {
self.constants.free();
self.opcodes.free();
env.discard(self.name);
if (self.bindings.len != 0) {
for (self.bindings) |binding| {
if (binding) |value| {
env.discard(value);
}
}
env.allocator.deallocate(self.bindings);
}
self.bindings = &.{};
}
pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) kym.RuntimeError!Self {
pub fn make(env: *kym.RuntimeEnv, name: *const kym.RuntimeRef, environment: *const tree.Environment) kym.RuntimeError!Self {
var chunk = Self{
.name = try env.new_symbol(name),
.name = name.acquire(),
.opcodes = OpcodeList.make(env.allocator),
.constants = ConstList.make(env.allocator),
.arity = 0,
.bindings = &.{},
.arity = environment.argument_count,
};
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 {
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 {
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;
}
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| {
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);
}
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| {
return env.new_vector3(
x,
@floatCast(try env.unbox_float(y)),
@floatCast(try env.unbox_float(try frame.get_arg(env, 2))),
@floatCast(try env.unwrap_float(y)),
@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)));
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);

View File

@ -21,7 +21,7 @@ fn compile_argument(self: Self, environment: *const tree.Environment, initial_ar
var argument_count = @as(u8, 0);
while (maybe_argument) |argument| {
try self.compile_expression(environment, argument);
try self.compile_expression(environment, argument, null);
maybe_argument = argument.next;
argument_count += 1;
@ -30,7 +30,7 @@ fn compile_argument(self: Self, environment: *const tree.Environment, initial_ar
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{
.delimiter = "_",
.positive_prefix = .none,
@ -68,7 +68,7 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
var field_count = @as(u32, 0);
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) {
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| {
try self.compile_expression(environment, key_value.value);
try self.compile_expression(environment, key_value.key);
try self.compile_expression(environment, key_value.value, null);
try self.compile_expression(environment, key_value.key, null);
},
.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);
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_chunk(chunk)});
if (lambda_construct.environment.capture_count == 0) {
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) {
for (lambda_construct.environment.captures[0 .. lambda_construct.environment.capture_count]) |capture| {
try self.chunk.opcodes.push_one(.{.push_local = environment.captures[capture]});
while (index != 0) {
index -= 1;
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| {
try self.compile_expression(environment, binary_op.lhs_operand);
try self.compile_expression(environment, binary_op.rhs_operand);
try self.compile_expression(environment, binary_op.lhs_operand, null);
try self.compile_expression(environment, binary_op.rhs_operand, null);
try self.chunk.opcodes.push_one(switch (binary_op.operation) {
.addition => .add,
@ -119,7 +132,7 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
},
.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) {
.boolean_negation => .not,
@ -130,11 +143,11 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
.invoke => |invoke| {
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});
},
.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}),
.print_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .print}),
.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});
}
if (get_capture_index(environment, declaration_get.declaration)) |index| {
try self.chunk.opcodes.push_one(.push_self);
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_fixed(index)});
if (try self.get_binding_index(environment, declaration_get.declaration)) |index| {
try self.chunk.opcodes.push_one(.{.push_binding = 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;
}
return self.env.raise(error.IllegalState, "local out of scope");
return self.env.raise(error.IllegalState, "local out of scope", .{});
},
.declaration_set => |declaration_set| {
try self.compile_expression(environment, declaration_set.assign);
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});
}
if (get_capture_index(environment, declaration_set.declaration)) |index| {
try self.chunk.opcodes.push_one(.push_self);
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_fixed(index)});
if (try self.get_binding_index(environment, declaration_set.declaration)) |index| {
try self.chunk.opcodes.push_one(.{.push_binding = 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;
}
return self.env.raise(error.IllegalState, "local out of scope");
return self.env.raise(error.IllegalState, "local out of scope", .{});
},
.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(.get_dynamic);
},
.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.compile_expression(environment, field_set.assign);
try self.compile_expression(environment, field_set.assign, null);
try self.chunk.opcodes.push_one(.set_dynamic);
},
.subscript_get => |subscript_get| {
try self.compile_expression(environment, subscript_get.object);
try self.compile_expression(environment, subscript_get.index);
try self.compile_expression(environment, subscript_get.object, null);
try self.compile_expression(environment, subscript_get.index, null);
try self.chunk.opcodes.push_one(.get_dynamic);
},
.subscript_set => |subscript_set| {
try self.compile_expression(environment, subscript_set.object);
try self.compile_expression(environment, subscript_set.index);
try self.compile_expression(environment, subscript_set.assign);
try self.compile_expression(environment, subscript_set.object, null);
try self.compile_expression(environment, subscript_set.index, null);
try self.compile_expression(environment, subscript_set.assign, null);
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) {
.@"return" => |@"return"| {
if (@"return".returned_expression) |expression| {
try self.compile_expression(environment, expression);
try self.compile_expression(environment, expression, null);
} else {
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"| {
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});
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);
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});
},
.@"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});
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| {
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);
}
},
.top_expression => |top_expression| {
try self.compile_expression(environment, top_expression);
try self.compile_expression(environment, top_expression, null);
if (top_expression.kind == .invoke) {
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 {
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);
@ -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 {
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");
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
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 {
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");
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
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 {
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");
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
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 {
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");
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
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);
}
pub fn get_capture_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
if (self.enclosing) |enclosing_environment| {
var capture_index = @as(u8, 0);
pub fn get_binding_index(self: *const Self, environment: *const tree.Environment, declaration: *const tree.Declaration) kym.RuntimeError!?u8 {
var binding_index = @as(u8, 0);
while (capture_index < self.capture_count) : (capture_index += 1) {
if (&enclosing_environment.local_declarations[capture_index] == declaration) {
return capture_index;
}
while (binding_index < environment.capture_count) : (binding_index += 1) {
var capture = &environment.captures[binding_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;
}
pub fn get_local_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
var remaining = self.local_declaration_count;
pub fn get_local_index(environment: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
var remaining = environment.declaration_count;
while (remaining != 0) {
remaining -= 1;
if (&self.local_declarations[remaining] == declaration) {
if (&environment.declarations[remaining] == declaration) {
return remaining;
}
}
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(.{
.kind = switch (expression.kind) {
.declaration_get => |declaration_get| .{
.declaration_set = .{
.assign = try parse(root, stream, environment),
.declaration = declaration_get.declaration,
},
.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 = .{
.assign = try parse(root, stream, environment),
.declaration = declaration_get.declaration,
},
};
},
.field_get => |field_get| .{
@ -414,15 +420,13 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
stream.skip_newlines();
return root.create_expr(.{
.next = null,
.kind = .{
.declaration_get = .{
.declaration = (try environment.resolve_declaration(identifier)) orelse {
return root.report_error(stream, "undefined 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", .{}),
};
if (try lambda_environment.declare(identifier) == null) {
return root.report_error(stream, "declaration `{identifier}` already exists", .{
.identifier = identifier,
});
}
lambda_environment.argument_count += 1;
_ = lambda_environment.declare_argument(identifier) catch |declare_error| {
return root.report_declare_error(stream, identifier, declare_error);
};
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 => {
const storage_token = stream.token;
const is_constant = stream.token == .keyword_let;
stream.skip_newlines();
const identifier = switch (stream.token) {
.identifier => |identifier| identifier,
else => {
return root.report_error(stream, "expected identifier after `{storage}` declaration statement", .{
.storage = storage_token.text()
});
},
else => return root.report_error(stream, "expected identifier after declaration", .{}),
};
stream.skip_newlines();
@ -122,10 +117,16 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
.declare = .{
.initial_expression = try Expr.parse(root, stream, environment),
.declaration = (try root.environment.declare(identifier)) orelse {
return root.report_error(stream, "declaration `{identifier}` already exists", .{
.identifier = identifier,
});
.declaration = declare: {
if (is_constant) {
break: declare environment.declare_constant(identifier) catch |declaration_error| {
return root.report_declare_error(stream, identifier, declaration_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 {
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 {
captures: [capture_max]u8 = [_]u8{0} ** capture_max,
captures: [capture_max]Capture = [_]Capture{.{.declaration_index = 0}} ** capture_max,
capture_count: u8 = 0,
local_declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max,
local_declaration_count: u8 = 0,
declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max,
declaration_count: u8 = 0,
argument_count: u8 = 0,
statement: ?*const Stmt = null,
enclosing: ?*Environment = null,
pub const Capture = union (enum) {
declaration_index: u8,
capture_index: u8,
};
pub const DeclareError = coral.io.AllocationError || error {
DeclarationExists,
};
const capture_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 {
if (self.local_declaration_count == self.local_declarations.len) {
fn declare(self: *Environment, declaration: Declaration) DeclareError!*const Declaration {
if (self.declaration_count == self.declarations.len) {
return error.OutOfMemory;
}
const declaration = &self.local_declarations[self.local_declaration_count];
{
var environment = self;
declaration.* = .{.identifier = identifier};
self.local_declaration_count += 1;
while (true) {
var remaining_count = environment.declaration_count;
return declaration;
while (remaining_count != 0) {
remaining_count -= 1;
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(usize, 0);
var ancestry = @as(u32, 0);
while (true) : (ancestry += 1) {
var remaining_count = self.local_declaration_count;
var remaining_count = environment.declaration_count;
while (remaining_count != 0) {
remaining_count -= 1;
const declaration = &self.local_declarations[remaining_count];
const declaration = &environment.declarations[remaining_count];
if (coral.io.are_equal(declaration.identifier, identifier)) {
if (ancestry != 0) {
declaration.is_captured = true;
declaration.is.captured = true;
environment = self;
ancestry -= 1;
while (ancestry != 0) : (ancestry -= 1) {
if (environment.capture_count == self.captures.len) {
if (environment.capture_count == environment.captures.len) {
return error.OutOfMemory;
}
environment.captures[environment.capture_count] = remaining_count;
environment.capture_count += 1;
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;
@ -82,6 +142,14 @@ pub const Environment = struct {
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 {
@ -106,6 +174,16 @@ pub const Root = struct {
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 {
return coral.io.allocate_one(self.arena.as_allocator(), expr);
}