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
7 changed files with 282 additions and 171 deletions
Showing only changes of commit c86173d759 - Show all commits

View File

@ -1,33 +1,18 @@
let printer = lambda (pfx): let test_param = "monkey wrench"
let printer = lambda (pfx, arbitrary):
@print(test_param)
return lambda (msg): return lambda (msg):
@print(pfx) @print(pfx)
@print(msg) @print(msg)
end end
end end
let pr = printer("This is a func call") 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
let pr2 = printer("this is a final closure")
pr2("goodbye")
return { return {
.title = "Game", .title = "Game",

View File

@ -196,6 +196,8 @@ pub const HexadecimalFormat = struct {
_ = self; _ = self;
_ = writer; _ = writer;
_ = value; _ = value;
return null;
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

@ -19,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 {
@ -65,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(),
}),
}; };
} }
@ -95,7 +103,7 @@ pub const RuntimeEnv = struct {
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()}),
}; };
} }
@ -104,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(),
}),
}; };
} }
@ -157,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 {
@ -196,17 +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"),
.boxed => self.raise(error.TypeMismatch, "boxed 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);
@ -231,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);
@ -257,6 +272,10 @@ 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(),
}),
}; };
} }
@ -266,7 +285,9 @@ pub const RuntimeEnv = struct {
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);
@ -287,7 +308,11 @@ pub const RuntimeEnv = struct {
}; };
} }
break: make_chunk try Chunk.make(self, file_name, &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);
}; };
defer chunk.free(self); defer chunk.free(self);
@ -327,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(),
}),
}; };
} }
@ -344,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()}),
}; };
} }
@ -498,7 +531,7 @@ pub const RuntimeEnv = struct {
} }
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, name_stringable: *RuntimeRef, arg_count: u8) RuntimeError!Frame { pub fn push_frame(self: *RuntimeEnv, name_stringable: *RuntimeRef, arg_count: u8) RuntimeError!Frame {
@ -513,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:");
@ -544,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(),
}),
}; };
} }
@ -560,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(),
}),
}; };
} }
@ -638,34 +688,37 @@ 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) RuntimeError![]coral.io.Byte {
return switch (value.object().payload) { return switch (value.object().payload) {
.dynamic => |dynamic| dynamic.userdata(), .dynamic => |dynamic| dynamic.userdata(),
else => self.raise(error.TypeMismatch, "expected fixed object") 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") });
};
} }
}; };
@ -775,6 +828,13 @@ pub const RuntimeRef = opaque {
}; };
} }
pub fn as_symbol(self: *const RuntimeRef) ?[*:0]const coral.io.Byte {
return switch (self.object().payload) {
.symbol => |symbol| symbol,
else => null,
};
}
fn object(self: *const RuntimeRef) *Object { fn object(self: *const RuntimeRef) *Object {
return @constCast(@ptrCast(@alignCast(self))); return @constCast(@ptrCast(@alignCast(self)));
} }
@ -875,6 +935,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",
}; };
@ -892,15 +953,15 @@ 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", .{});
} }
}; };

View File

@ -32,6 +32,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,
@ -85,6 +86,7 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
_ = 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"),
@ -92,7 +94,7 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
.push_const => |push_const| print: { .push_const => |push_const| print: {
if (push_const >= chunk.constants.values.len) { if (push_const >= chunk.constants.values.len) {
return env.raise(error.IllegalState, "invalid constant"); 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]);
@ -168,6 +170,8 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) { while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) {
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.
switch (self.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);
@ -180,15 +184,15 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
.push_const => |push_const| { .push_const => |push_const| {
if (push_const >= self.constants.values.len) { if (push_const >= self.constants.values.len) {
return env.raise(error.IllegalState, "invalid constant"); return env.raise(error.IllegalState, "invalid constant", .{});
} }
try env.locals.push_one(self.constants.values[push_const].acquire()); try env.locals.push_one(self.constants.values[push_const].acquire());
}, },
.push_local => |push_local| { .push_local => |push_local| {
if (push_local >= env.locals.values.len) { if (push_local >= (env.locals.values.len - frame.locals_top)) {
return env.raise(error.IllegalState, "invalid local"); 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| {
@ -202,7 +206,7 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
const frame_locals = env.locals.values[frame.locals_top ..]; const frame_locals = env.locals.values[frame.locals_top ..];
if (frame_locals.len == 0) { if (frame_locals.len == 0) {
return env.raise(error.IllegalState, "stack overflow"); return env.raise(error.IllegalState, "stack overflow", .{});
} }
if (frame_locals[frame_locals.len - 1]) |local| { if (frame_locals[frame_locals.len - 1]) |local| {
@ -254,7 +258,7 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
.push_binding => |push_binding| { .push_binding => |push_binding| {
if (push_binding > self.bindings.len) { if (push_binding > self.bindings.len) {
return env.raise(error.IllegalState, "binding out of range"); return env.raise(error.IllegalState, "binding out of range", .{});
} }
try env.locals.push_one(if (self.bindings[push_binding]) |value| value.acquire() else null); try env.locals.push_one(if (self.bindings[push_binding]) |value| value.acquire() else null);
@ -266,7 +270,9 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
errdefer env.discard(lambda); errdefer env.discard(lambda);
const chunk = @as(*Self, @ptrCast(@alignCast(lambda.as_dynamic(typeinfo) orelse { const chunk = @as(*Self, @ptrCast(@alignCast(lambda.as_dynamic(typeinfo) orelse {
return env.raise(error.IllegalState, "cannot bind to non-chunk"); return env.raise(error.IllegalState, "cannot bind objects to a {typename}", .{
.typename = lambda.typename(),
});
}))); })));
chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*kym.RuntimeRef, null)); chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*kym.RuntimeRef, null));
@ -321,7 +327,7 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
defer env.discard(box); defer env.discard(box);
const boxed = box.as_boxed() orelse { const boxed = box.as_boxed() orelse {
return env.raise(error.TypeMismatch, "type is not unboxable"); return env.raise(error.TypeMismatch, "{typename} is not unboxable", .{.typename = box.typename()});
}; };
try env.locals.push_one(if (boxed.*) |value| value.acquire() else null); try env.locals.push_one(if (boxed.*) |value| value.acquire() else null);
@ -333,7 +339,7 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
errdefer env.discard(box); errdefer env.discard(box);
const boxed = box.as_boxed() orelse { const boxed = box.as_boxed() orelse {
return env.raise(error.TypeMismatch, "type is not unboxable"); return env.raise(error.TypeMismatch, "{typename} is not unboxable", .{.typename = box.typename()});
}; };
if (boxed.*) |value| { if (boxed.*) |value| {
@ -372,15 +378,11 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
} }
} }
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);
@ -594,13 +596,13 @@ pub fn free(self: *Self, env: *kym.RuntimeEnv) void {
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{ 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),
.bindings = &.{}, .bindings = &.{},
.arity = 0, .arity = environment.argument_count,
}; };
var compiler = Compiler{ var compiler = Compiler{
@ -611,7 +613,15 @@ pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *con
try compiler.compile_environment(environment); try compiler.compile_environment(environment);
if (builtin.mode == .Debug) { if (builtin.mode == .Debug) {
const allocation = try coral.utf8.alloc_formatted(env.allocator, "compiled {name}...", .{.name = name}); const name_string = try env.to_string(name);
defer env.discard(name_string);
const name_bytes = name_string.as_string();
coral.debug.assert(name_bytes != null);
const allocation = try coral.utf8.alloc_formatted(env.allocator, "compiled {name}...", .{.name = name_bytes.?});
defer env.allocator.deallocate(allocation); defer env.allocator.deallocate(allocation);
@ -631,33 +641,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))),
); );
} }
@ -675,7 +685,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,33 +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 and environment.capture_count == 0) { 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 { } else {
for (environment.captures[0 .. environment.capture_count]) |capture| { const lambda_captures = lambda_construct.environment.get_captures();
try self.chunk.opcodes.push_one(.{.push_local = environment.captures[capture]}); var index = lambda_captures.len;
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_const = try self.declare_chunk(chunk)}); 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});
try self.chunk.opcodes.push_one(.{
.bind = lambda_construct.environment.capture_count + 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,
@ -123,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,
@ -134,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}),
@ -149,7 +158,7 @@ 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 (get_binding_index(environment, declaration_get.declaration)) |index| {
try self.chunk.opcodes.push_one(.{.push_binding = index}); try self.chunk.opcodes.push_one(.{.push_binding = index});
if (is_declaration_boxed(declaration_get.declaration)) { if (is_declaration_boxed(declaration_get.declaration)) {
@ -159,19 +168,19 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
return; 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| { .declaration_set => |declaration_set| {
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); 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 (get_binding_index(environment, declaration_set.declaration)) |index| {
try self.chunk.opcodes.push_one(.{.push_binding = index}); try self.chunk.opcodes.push_one(.{.push_binding = index});
try self.compile_expression(environment, declaration_set.assign); try self.compile_expression(environment, declaration_set.assign, null);
if (is_declaration_boxed(declaration_set.declaration)) { if (is_declaration_boxed(declaration_set.declaration)) {
try self.chunk.opcodes.push_one(.set_box); try self.chunk.opcodes.push_one(.set_box);
@ -180,32 +189,32 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
return; 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| { .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);
}, },
} }
@ -228,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));
@ -243,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));
@ -262,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);
@ -282,9 +294,13 @@ 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), Chunk.typeinfo); const constant = try self.env.new_dynamic(coral.io.bytes_of(&chunk), Chunk.typeinfo);
@ -297,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);
@ -311,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);
@ -325,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);
@ -339,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);
@ -352,31 +376,35 @@ 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(environment: *const tree.Environment, declaration: *const tree.Declaration) ?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) {
const captured_declaration_index = self.captures[capture_index]; var capture = &environment.captures[binding_index];
var target_environment = environment.enclosing orelse return null;
coral.debug.assert(captured_declaration_index < enclosing_environment.declaration_count); while (capture.* == .capture_index) {
capture = &target_environment.captures[capture.capture_index];
target_environment = target_environment.enclosing orelse return null;
}
if (&enclosing_environment.declarations[captured_declaration_index] == declaration) { coral.debug.assert(capture.* == .declaration_index);
return capture_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.declaration_count; var remaining = environment.declaration_count;
while (remaining != 0) { while (remaining != 0) {
remaining -= 1; remaining -= 1;
if (&self.declarations[remaining] == declaration) { if (&environment.declarations[remaining] == declaration) {
return remaining; return remaining;
} }
} }

View File

@ -16,7 +16,7 @@ pub const Declaration = struct {
}; };
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,
declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max, declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max,
declaration_count: u8 = 0, declaration_count: u8 = 0,
@ -24,6 +24,11 @@ pub const Environment = struct {
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,
};
const DeclareError = coral.io.AllocationError || error { 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, DeclarationExists,
}; };
@ -93,7 +98,7 @@ pub const Environment = struct {
pub fn resolve_declaration(self: *Environment, identifier: []const coral.io.Byte) coral.io.AllocationError!?*const 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); var ancestry = @as(u32, 0);
while (true) : (ancestry += 1) { while (true) : (ancestry += 1) {
var remaining_count = environment.declaration_count; var remaining_count = environment.declaration_count;
@ -107,19 +112,27 @@ pub const Environment = struct {
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 == environment.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;
@ -129,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 {