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;
} }
}; };

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) {
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];
if (&enclosing_environment.declarations[captured_declaration_index] == declaration) { target_environment = target_environment.enclosing orelse return null;
return capture_index;
} }
coral.debug.assert(capture.* == .declaration_index);
if (&target_environment.declarations[capture.declaration_index] == declaration) {
kayomn marked this conversation as resolved
Review

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

This shouldn't be capable of crashing the process - needs better handling.
return binding_index;
} }
} }
return null; return null;
} }
pub fn get_local_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 { pub fn get_local_index(environment: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
var remaining = self.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 {
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 {