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

View File

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

View File

@ -19,8 +19,8 @@ pub const Frame = struct {
return env.locals.values[self.locals_top .. (self.locals_top + self.arg_count)];
}
pub fn get_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) RuntimeError!*const RuntimeRef {
return self.has_arg(env, arg_index) orelse env.raise(error.BadOperation, "nil reference");
pub fn expect_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) RuntimeError!*const RuntimeRef {
return self.has_arg(env, arg_index) orelse env.raise(error.TypeMismatch, "nil reference", .{});
}
pub fn has_arg(self: *const Frame, env: *RuntimeEnv, arg_index: u8) ?*const RuntimeRef {
@ -65,16 +65,24 @@ pub const RuntimeEnv = struct {
},
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float),
else => self.raise(error.TypeMismatch, "right-hand object is not addable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not addable", .{
.typename = rhs.typename(),
}),
},
.float => |lhs_float| switch (rhs.object().payload) {
.float => |rhs_float| self.new_float(lhs_float + rhs_float),
.fixed => |rhs_fixed| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))),
else => self.raise(error.TypeMismatch, "right-hand object is not addable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not addable", .{
.typename = rhs.typename(),
}),
},
else => self.raise(error.TypeMismatch, "left-hand object is not addable"),
else => self.raise(error.TypeMismatch, "left-hand {typename} is not addable", .{
.typename = lhs.typename(),
}),
};
}
@ -95,7 +103,7 @@ pub const RuntimeEnv = struct {
return switch (callable.object().payload) {
.syscall => |syscall| syscall(self, frame),
.dynamic => |dynamic| dynamic.typeinfo().call(self, dynamic.userdata(), frame),
else => self.raise(error.TypeMismatch, "object is not callable"),
else => self.raise(error.TypeMismatch, "{typename} is not callable", .{.typename = callable.typename()}),
};
}
@ -104,16 +112,24 @@ pub const RuntimeEnv = struct {
.fixed => |lhs_fixed| switch (rhs.object().payload) {
.fixed => |rhs_fixed| @as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed)),
.float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) - rhs_float,
else => return self.raise(error.TypeMismatch, "right-hand object is not comparable"),
else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{
.typename = rhs.typename(),
}),
},
.float => |lhs_float| switch (rhs.object().payload) {
.float => |rhs_float| lhs_float - rhs_float,
.fixed => |rhs_fixed| lhs_float - @as(Float, @floatFromInt(rhs_fixed)),
else => return self.raise(error.TypeMismatch, "right-hand object is not comparable"),
else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{
.typename = rhs.typename(),
}),
},
else => return self.raise(error.TypeMismatch, "left-hand object is not comparable"),
else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{
.typename = lhs.typename(),
}),
};
}
@ -157,21 +173,29 @@ pub const RuntimeEnv = struct {
.fixed => |lhs_fixed| switch (rhs.object().payload) {
.fixed => |rhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))),
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float),
else => self.raise(error.TypeMismatch, "right-hand object is not divisible"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not divisible", .{
.typename = rhs.typename(),
}),
},
.float => |lhs_float| switch (rhs.object().payload) {
.float => |rhs_float| self.new_float(lhs_float / rhs_float),
.fixed => |rhs_fixed| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))),
else => self.raise(error.TypeMismatch, "right-hand object is not divisible"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not divisible", .{
.typename = rhs.typename(),
}),
},
else => self.raise(error.TypeMismatch, "left-hand object is not divisible"),
else => self.raise(error.TypeMismatch, "left-hand {typename} is not divisible", .{
.typename = lhs.typename(),
}),
};
}
pub fn expect(self: *RuntimeEnv, value: ?*RuntimeRef) RuntimeError!*RuntimeRef {
return value orelse self.raise(error.TypeMismatch, "nil reference");
return value orelse self.raise(error.TypeMismatch, "nil reference", .{});
}
pub fn free(self: *RuntimeEnv) void {
@ -196,17 +220,8 @@ pub const RuntimeEnv = struct {
pub fn get(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef) RuntimeError!?*RuntimeRef {
return switch (indexable.object().payload) {
.false => self.raise(error.TypeMismatch, "false is not get-indexable"),
.true => self.raise(error.TypeMismatch, "true is not get-indexable"),
.fixed => self.raise(error.TypeMismatch, "fixed is not get-indexable"),
.float => self.raise(error.TypeMismatch, "float is not get-indexable"),
.string => self.raise(error.TypeMismatch, "string is not get-indexable"),
.symbol => self.raise(error.TypeMismatch, "symbol is not get-indexable"),
.syscall => self.raise(error.TypeMismatch, "syscall is not get-indexable"),
.boxed => self.raise(error.TypeMismatch, "boxed is not get-indexable"),
.vector2 => |vector2| swizzle: {
const swizzle_symbol = try self.unbox_symbol(index);
const swizzle_symbol = try self.unwrap_symbol(index);
var swizzle_buffer = [_]f32{0} ** 3;
var swizzle_count = @as(usize, 0);
@ -231,7 +246,7 @@ pub const RuntimeEnv = struct {
},
.vector3 => |vector3| swizzle: {
const swizzle_symbol = try self.unbox_symbol(index);
const swizzle_symbol = try self.unwrap_symbol(index);
var swizzle_buffer = [_]f32{0} ** 3;
var swizzle_count = @as(usize, 0);
@ -257,6 +272,10 @@ pub const RuntimeEnv = struct {
},
.dynamic => |dynamic| dynamic.typeinfo().get(self, dynamic.userdata(), index),
else => self.raise(error.TypeMismatch, "{typename} is not get-indexable", .{
.typename = indexable.typename(),
}),
};
}
@ -266,7 +285,9 @@ pub const RuntimeEnv = struct {
var chunk = make_chunk: {
const file_data =
(try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse {
return self.raise(error.BadOperation, "failed to open or read file specified");
return self.raise(error.BadOperation, "failed to open or read `{name}`", .{
.name = file_name,
});
};
defer self.allocator.deallocate(file_data);
@ -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);
@ -327,16 +352,24 @@ pub const RuntimeEnv = struct {
},
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float),
else => self.raise(error.TypeMismatch, "right-hand object is not multiplicable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not multiplicable", .{
.typename = rhs.typename(),
}),
},
.float => |lhs_float| switch (rhs.object().payload) {
.float => |rhs_float| self.new_float(lhs_float * rhs_float),
.fixed => |rhs_fixed| self.new_float(lhs_float * @as(Float, @floatFromInt(rhs_fixed))),
else => self.raise(error.TypeMismatch, "right-hand object is not multiplicable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not multiplicable", .{
.typename = rhs.typename(),
}),
},
else => self.raise(error.TypeMismatch, "left-hand object is not multiplicable"),
else => self.raise(error.TypeMismatch, "left-hand {typename} is not multiplicable", .{
.typename = lhs.typename(),
}),
};
}
@ -344,7 +377,7 @@ pub const RuntimeEnv = struct {
return switch (value.object().payload) {
.fixed => |fixed| self.new_fixed(-fixed),
.float => |float| self.new_float(-float),
else => self.raise(error.TypeMismatch, "object is not negatable"),
else => self.raise(error.TypeMismatch, "{typename} is not negatable", .{.typename = value.typename()}),
};
}
@ -498,7 +531,7 @@ pub const RuntimeEnv = struct {
}
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 {
@ -513,8 +546,14 @@ pub const RuntimeEnv = struct {
return frame;
}
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError {
self.print_error(message);
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, comptime format: []const coral.io.Byte, args: anytype) RuntimeError {
{
const formatted_message = try coral.utf8.alloc_formatted(self.allocator, format, args);
defer self.allocator.deallocate(formatted_message);
self.print_error(formatted_message);
}
if (!self.frames.is_empty()) {
self.print_error("stack trace:");
@ -544,7 +583,10 @@ pub const RuntimeEnv = struct {
pub fn set(self: *RuntimeEnv, indexable: *RuntimeRef, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void {
return switch (indexable.object().payload) {
.dynamic => |dynamic| dynamic.typeinfo().set(self, dynamic.userdata(), index, value),
else => self.raise(error.TypeMismatch, "object is not set-indexable"),
else => self.raise(error.TypeMismatch, "{typename} is not set-indexable", .{
.typename = indexable.typename(),
}),
};
}
@ -560,16 +602,24 @@ pub const RuntimeEnv = struct {
},
.float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float),
else => self.raise(error.TypeMismatch, "right-hand object is not subtractable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not subtractable", .{
.typename = rhs.typename(),
}),
},
.float => |lhs_float| switch (rhs.object().payload) {
.float => |rhs_float| self.new_float(lhs_float - rhs_float),
.fixed => |rhs_fixed| self.new_float(lhs_float - @as(Float, @floatFromInt(rhs_fixed))),
else => self.raise(error.TypeMismatch, "right-hand object is not subtractable"),
else => self.raise(error.TypeMismatch, "right-hand {typename} is not subtractable", .{
.typename = rhs.typename(),
}),
},
else => self.raise(error.TypeMismatch, "left-hand object is not subtractable"),
else => self.raise(error.TypeMismatch, "left-hand {typename} is not subtractable", .{
.typename = lhs.typename(),
}),
};
}
@ -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) {
.dynamic => |dynamic| dynamic.userdata(),
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) {
.fixed => |fixed| @floatFromInt(fixed),
.float => |float| float,
else => self.raise(error.TypeMismatch, "expected float object")
else => self.raise(error.TypeMismatch, "expected float, not {typename}", .{.typename = value.typename()}),
};
}
pub fn unbox_fixed(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Fixed {
return value.as_fixed() orelse self.raise(error.TypeMismatch, "expected fixed object");
pub fn unwrap_fixed(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!Fixed {
return value.as_fixed() orelse self.raise(error.TypeMismatch, "expected fixed, not {typename}", .{
.typename = value.typename()
});
}
pub fn unbox_string(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]const coral.io.Byte {
return value.as_string() orelse self.raise(error.TypeMismatch, "expected string object");
pub fn unwrap_string(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![]const coral.io.Byte {
return value.as_string() orelse self.raise(error.TypeMismatch, "expected string, not {typename}", .{
.typename = value.typename(),
});
}
pub fn unbox_symbol(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![*:0]const coral.io.Byte {
return switch (value.object().payload) {
.symbol => |symbol| symbol,
else => self.raise(error.TypeMismatch, "expected symbol object")
};
pub fn unwrap_symbol(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError![*:0]const coral.io.Byte {
return value.as_symbol() orelse self.raise(error.TypeMismatch, "expected symbol, not {typename}", .{
.typename = value.typename(),
});
}
};
@ -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 {
return @constCast(@ptrCast(@alignCast(self)));
}
@ -875,6 +935,7 @@ pub const RuntimeRef = opaque {
.vector2 => "vector2",
.vector3 => "vector3",
.syscall => "syscall",
.boxed => "boxed",
.string => "string",
.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,
fn default_call(env: *RuntimeEnv, _: []coral.io.Byte, _: Frame) RuntimeError!?*RuntimeRef {
return env.raise(error.BadOperation, "object is not callable");
return env.raise(error.BadOperation, "this dynamic object is not callable", .{});
}
fn default_get(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef) RuntimeError!?*RuntimeRef {
return env.raise(error.BadOperation, "object is not get-indexable");
return env.raise(error.BadOperation, "this dynamic object is not get-indexable", .{});
}
fn default_set(env: *RuntimeEnv, _: []coral.io.Byte, _: *const RuntimeRef, _: ?*const RuntimeRef) RuntimeError!void {
return env.raise(error.BadOperation, "object is not set-indexable");
return env.raise(error.BadOperation, "this dynamic object is not set-indexable", .{});
}
};

View File

@ -32,6 +32,7 @@ const Builtin = enum {
const ConstList = coral.list.Stack(*kym.RuntimeRef);
const OpcodeList = coral.list.Stack(union (enum) {
ret,
pop,
push_nil,
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});
_ = switch (chunk.opcodes.values[opcode_cursor]) {
.ret => coral.utf8.print_string(writer, "ret\n"),
.pop => coral.utf8.print_string(writer, "pop\n"),
.push_nil => coral.utf8.print_string(writer, "push nil\n"),
.push_true => coral.utf8.print_string(writer, "push true\n"),
@ -92,7 +94,7 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
.push_const => |push_const| print: {
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]);
@ -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) {
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]) {
.ret => break,
.pop => {
if (try env.pop_local()) |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| {
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());
},
.push_local => |push_local| {
if (push_local >= env.locals.values.len) {
return env.raise(error.IllegalState, "invalid local");
if (push_local >= (env.locals.values.len - frame.locals_top)) {
return env.raise(error.IllegalState, "invalid local", .{});
}
if (env.locals.values[frame.locals_top + push_local]) |local| {
@ -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 ..];
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| {
@ -254,7 +258,7 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
.push_binding => |push_binding| {
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);
@ -266,7 +270,9 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
errdefer env.discard(lambda);
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));
@ -321,7 +327,7 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
defer env.discard(box);
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);
@ -333,7 +339,7 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
errdefer env.discard(box);
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| {
@ -372,15 +378,11 @@ pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeEr
}
}
const index = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not a valid index");
};
const index = try env.expect(try env.pop_local());
defer env.discard(index);
const indexable = try env.pop_local() orelse {
return env.raise(error.TypeMismatch, "nil is not a valid indexable");
};
const indexable = try env.expect(try env.pop_local());
defer env.discard(indexable);
@ -594,13 +596,13 @@ pub fn free(self: *Self, env: *kym.RuntimeEnv) void {
self.bindings = &.{};
}
pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) kym.RuntimeError!Self {
pub fn make(env: *kym.RuntimeEnv, name: *const kym.RuntimeRef, environment: *const tree.Environment) kym.RuntimeError!Self {
var chunk = Self{
.name = try env.new_symbol(name),
.name = name.acquire(),
.opcodes = OpcodeList.make(env.allocator),
.constants = ConstList.make(env.allocator),
.bindings = &.{},
.arity = 0,
.arity = environment.argument_count,
};
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);
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);
@ -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 {
return env.import(file.Path.from(&.{try env.unbox_string(try frame.get_arg(env, 0))}));
return env.import(file.Path.from(&.{try env.unwrap_string(try frame.expect_arg(env, 0))}));
}
fn syscall_print(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
env.print(try env.unbox_string(try frame.get_arg(env, 0)));
env.print(try env.unwrap_string(try frame.expect_arg(env, 0)));
return null;
}
fn syscall_vec2(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
const x = @as(f32, @floatCast(try env.unbox_float(try frame.get_arg(env, 0))));
const x = @as(f32, @floatCast(try env.unwrap_float(try frame.expect_arg(env, 0))));
if (frame.has_arg(env, 1)) |y| {
return env.new_vector2(x, @floatCast(try env.unbox_float(y)));
return env.new_vector2(x, @floatCast(try env.unwrap_float(y)));
}
return env.new_vector2(x, x);
}
fn syscall_vec3(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
const x = @as(f32, @floatCast(try env.unbox_float(try frame.get_arg(env, 0))));
const x = @as(f32, @floatCast(try env.unwrap_float(try frame.expect_arg(env, 0))));
if (frame.has_arg(env, 1)) |y| {
return env.new_vector3(
x,
@floatCast(try env.unbox_float(y)),
@floatCast(try env.unbox_float(try frame.get_arg(env, 2))),
@floatCast(try env.unwrap_float(y)),
@floatCast(try env.unwrap_float(try frame.expect_arg(env, 2))),
);
}
@ -675,7 +685,11 @@ fn typeinfo_call(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, frame: kym.Fra
const chunk = @as(*Self, @ptrCast(@alignCast(userdata)));
if (frame.arg_count < chunk.arity) {
return env.raise(error.BadOperation, "expected more arguments");
return env.raise(error.BadOperation, "expected `{expected_count}` {noun}, {provided_count} provided", .{
.expected_count = frame.arg_count,
.provided_count = chunk.arity,
.noun = if (frame.arg_count == 1) "argument" else "arguments",
});
}
return chunk.execute(env, frame);

View File

@ -21,7 +21,7 @@ fn compile_argument(self: Self, environment: *const tree.Environment, initial_ar
var argument_count = @as(u8, 0);
while (maybe_argument) |argument| {
try self.compile_expression(environment, argument);
try self.compile_expression(environment, argument, null);
maybe_argument = argument.next;
argument_count += 1;
@ -30,7 +30,7 @@ fn compile_argument(self: Self, environment: *const tree.Environment, initial_ar
return argument_count;
}
fn compile_expression(self: Self, environment: *const tree.Environment, expression: *const Expr) kym.RuntimeError!void {
fn compile_expression(self: Self, environment: *const tree.Environment, expression: *const Expr, name: ?[]const coral.io.Byte) kym.RuntimeError!void {
const number_format = coral.utf8.DecimalFormat{
.delimiter = "_",
.positive_prefix = .none,
@ -68,7 +68,7 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
var field_count = @as(u32, 0);
while (table_entry) |entry| : (table_entry = entry.next) {
try self.compile_expression(environment, entry);
try self.compile_expression(environment, entry, null);
if (entry.kind != .key_value) {
try self.chunk.opcodes.push_one(.push_top);
@ -81,33 +81,42 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
},
.key_value => |key_value| {
try self.compile_expression(environment, key_value.value);
try self.compile_expression(environment, key_value.key);
try self.compile_expression(environment, key_value.value, null);
try self.compile_expression(environment, key_value.key, null);
},
.lambda_construct => |lambda_construct| {
var chunk = try Chunk.make(self.env, "<lambda>", lambda_construct.environment);
const anonymous_chunk_name = try self.env.new_symbol(name orelse "<lambda>");
defer self.env.discard(anonymous_chunk_name);
var chunk = try Chunk.make(self.env, anonymous_chunk_name, lambda_construct.environment);
errdefer chunk.free(self.env);
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)});
} else {
for (environment.captures[0 .. environment.capture_count]) |capture| {
try self.chunk.opcodes.push_one(.{.push_local = environment.captures[capture]});
const lambda_captures = lambda_construct.environment.get_captures();
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(.{
.bind = lambda_construct.environment.capture_count + environment.capture_count,
});
try self.chunk.opcodes.push_one(.{.bind = lambda_construct.environment.capture_count});
}
},
.binary_op => |binary_op| {
try self.compile_expression(environment, binary_op.lhs_operand);
try self.compile_expression(environment, binary_op.rhs_operand);
try self.compile_expression(environment, binary_op.lhs_operand, null);
try self.compile_expression(environment, binary_op.rhs_operand, null);
try self.chunk.opcodes.push_one(switch (binary_op.operation) {
.addition => .add,
@ -123,7 +132,7 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
},
.unary_op => |unary_op| {
try self.compile_expression(environment, unary_op.operand);
try self.compile_expression(environment, unary_op.operand, null);
try self.chunk.opcodes.push_one(switch (unary_op.operation) {
.boolean_negation => .not,
@ -134,11 +143,11 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
.invoke => |invoke| {
const argument_count = try self.compile_argument(environment, invoke.argument);
try self.compile_expression(environment, invoke.object);
try self.compile_expression(environment, invoke.object, null);
try self.chunk.opcodes.push_one(.{.call = argument_count});
},
.group => |group| try self.compile_expression(environment, group),
.group => |group| try self.compile_expression(environment, group, null),
.import_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .import}),
.print_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .print}),
.vec2_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .vec2}),
@ -149,7 +158,7 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
return self.chunk.opcodes.push_one(.{.push_local = index});
}
if (get_capture_index(environment, declaration_get.declaration)) |index| {
if (get_binding_index(environment, declaration_get.declaration)) |index| {
try self.chunk.opcodes.push_one(.{.push_binding = index});
if (is_declaration_boxed(declaration_get.declaration)) {
@ -159,19 +168,19 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
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| {
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});
}
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.compile_expression(environment, declaration_set.assign);
try self.compile_expression(environment, declaration_set.assign, null);
if (is_declaration_boxed(declaration_set.declaration)) {
try self.chunk.opcodes.push_one(.set_box);
@ -180,32 +189,32 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
return;
}
return self.env.raise(error.IllegalState, "local out of scope");
return self.env.raise(error.IllegalState, "local out of scope", .{});
},
.field_get => |field_get| {
try self.compile_expression(environment, field_get.object);
try self.compile_expression(environment, field_get.object, null);
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(field_get.identifier)});
try self.chunk.opcodes.push_one(.get_dynamic);
},
.field_set => |field_set| {
try self.compile_expression(environment, field_set.object);
try self.compile_expression(environment, field_set.object, null);
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(field_set.identifier)});
try self.compile_expression(environment, field_set.assign);
try self.compile_expression(environment, field_set.assign, null);
try self.chunk.opcodes.push_one(.set_dynamic);
},
.subscript_get => |subscript_get| {
try self.compile_expression(environment, subscript_get.object);
try self.compile_expression(environment, subscript_get.index);
try self.compile_expression(environment, subscript_get.object, null);
try self.compile_expression(environment, subscript_get.index, null);
try self.chunk.opcodes.push_one(.get_dynamic);
},
.subscript_set => |subscript_set| {
try self.compile_expression(environment, subscript_set.object);
try self.compile_expression(environment, subscript_set.index);
try self.compile_expression(environment, subscript_set.assign);
try self.compile_expression(environment, subscript_set.object, null);
try self.compile_expression(environment, subscript_set.index, null);
try self.compile_expression(environment, subscript_set.assign, null);
try self.chunk.opcodes.push_one(.set_dynamic);
},
}
@ -228,14 +237,17 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s
switch (current_statement.kind) {
.@"return" => |@"return"| {
if (@"return".returned_expression) |expression| {
try self.compile_expression(environment, expression);
try self.compile_expression(environment, expression, null);
} else {
try self.chunk.opcodes.push_one(.push_nil);
}
// TODO: Omit ret calls at ends of chunk.
try self.chunk.opcodes.push_one(.ret);
},
.@"while" => |@"while"| {
try self.compile_expression(environment, @"while".loop_expression);
try self.compile_expression(environment, @"while".loop_expression, null);
try self.chunk.opcodes.push_one(.{.jf = 0});
const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1));
@ -243,12 +255,12 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s
_ = try self.compile_statement(environment, @"while".loop);
self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1);
try self.compile_expression(environment, @"while".loop_expression);
try self.compile_expression(environment, @"while".loop_expression, null);
try self.chunk.opcodes.push_one(.{.jt = origin_index});
},
.@"if" => |@"if"| {
try self.compile_expression(environment, @"if".then_expression);
try self.compile_expression(environment, @"if".then_expression, null);
try self.chunk.opcodes.push_one(.{.jf = 0});
const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1));
@ -262,15 +274,15 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s
},
.declare => |declare| {
try self.compile_expression(environment, declare.initial_expression);
try self.compile_expression(environment, declare.initial_expression, declare.declaration.identifier);
if (declare.declaration.is.captured and !declare.declaration.is.readonly) {
if (is_declaration_boxed(declare.declaration)) {
try self.chunk.opcodes.push_one(.push_boxed);
}
},
.top_expression => |top_expression| {
try self.compile_expression(environment, top_expression);
try self.compile_expression(environment, top_expression, null);
if (top_expression.kind == .invoke) {
try self.chunk.opcodes.push_one(.pop);
@ -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 {
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);
@ -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 {
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants");
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
const constant = try self.env.new_fixed(fixed);
@ -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 {
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants");
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
const constant = try self.env.new_float(float);
@ -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 {
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants");
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
const constant = try self.env.new_string(string);
@ -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 {
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants");
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
const constant = try self.env.new_symbol(symbol);
@ -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);
}
pub fn get_capture_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
if (self.enclosing) |enclosing_environment| {
var capture_index = @as(u8, 0);
pub fn get_binding_index(environment: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
var binding_index = @as(u8, 0);
while (capture_index < self.capture_count) : (capture_index += 1) {
const captured_declaration_index = self.captures[capture_index];
while (binding_index < environment.capture_count) : (binding_index += 1) {
var capture = &environment.captures[binding_index];
var target_environment = environment.enclosing orelse return null;
coral.debug.assert(captured_declaration_index < enclosing_environment.declaration_count);
if (&enclosing_environment.declarations[captured_declaration_index] == declaration) {
return capture_index;
while (capture.* == .capture_index) {
capture = &target_environment.captures[capture.capture_index];
target_environment = target_environment.enclosing orelse return null;
}
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;
}
pub fn get_local_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
var remaining = self.declaration_count;
pub fn get_local_index(environment: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
var remaining = environment.declaration_count;
while (remaining != 0) {
remaining -= 1;
if (&self.declarations[remaining] == declaration) {
if (&environment.declarations[remaining] == declaration) {
return remaining;
}
}

View File

@ -16,7 +16,7 @@ pub const Declaration = 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,
declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max,
declaration_count: u8 = 0,
@ -24,6 +24,11 @@ pub const Environment = struct {
statement: ?*const Stmt = null,
enclosing: ?*Environment = null,
pub const Capture = union (enum) {
declaration_index: u8,
capture_index: u8,
};
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,
};
@ -93,7 +98,7 @@ pub const Environment = struct {
pub fn resolve_declaration(self: *Environment, identifier: []const coral.io.Byte) coral.io.AllocationError!?*const Declaration {
var environment = self;
var ancestry = @as(usize, 0);
var ancestry = @as(u32, 0);
while (true) : (ancestry += 1) {
var remaining_count = environment.declaration_count;
@ -107,19 +112,27 @@ pub const Environment = struct {
if (ancestry != 0) {
declaration.is.captured = true;
environment = self;
ancestry -= 1;
while (ancestry != 0) : (ancestry -= 1) {
if (environment.capture_count == environment.captures.len) {
return error.OutOfMemory;
}
environment.captures[environment.capture_count] = remaining_count;
environment.capture_count += 1;
coral.debug.assert(environment.enclosing != null);
environment = environment.enclosing.?;
const enclosing_environment = environment.enclosing.?;
environment.captures[environment.capture_count] = .{
.capture_index = enclosing_environment.capture_count
};
environment.capture_count += 1;
environment = enclosing_environment;
}
environment.captures[environment.capture_count] = .{.declaration_index = remaining_count};
environment.capture_count += 1;
}
return declaration;
@ -129,6 +142,14 @@ pub const Environment = struct {
environment = environment.enclosing orelse return null;
}
}
pub fn get_captures(self: *const Environment) []const Capture {
return self.captures[0 .. self.capture_count];
}
pub fn get_declarations(self: *const Environment) []const Declaration {
return self.declarations[0 .. self.declaration_count];
}
};
pub const ParseError = coral.io.AllocationError || error {