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
8 changed files with 297 additions and 106 deletions
Showing only changes of commit 880761a722 - Show all commits

View File

@ -1,12 +1,14 @@
let printer = lambda (): let printer = lambda (pfx):
let prefix = pfx
return lambda (msg): return lambda (msg):
@print("This is a func call") @print(prefix)
@print(msg) @print(msg)
end end
end end
let pr = printer() let pr = printer("This is a func call")
var i = 0 var i = 0
pr("test") pr("test")
@ -25,6 +27,10 @@ else:
pr("i'unno") pr("i'unno")
end end
let pr2 = printer("this is a final closure")
pr2("goodbye")
return { return {
.title = "Game", .title = "Game",
.width = 1280, .width = 1280,

View File

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

View File

@ -2,10 +2,6 @@ const Chunk = @import("./kym/Chunk.zig");
const Table = @import("./kym/Table.zig"); const Table = @import("./kym/Table.zig");
const app = @import("./app.zig");
const builtin = @import("builtin");
const coral = @import("coral"); const coral = @import("coral");
const file = @import("./file.zig"); const file = @import("./file.zig");
@ -15,7 +11,7 @@ const tokens = @import("./kym/tokens.zig");
const tree = @import("./kym/tree.zig"); const tree = @import("./kym/tree.zig");
pub const Frame = struct { pub const Frame = struct {
name: []const coral.io.Byte = "", name_stringable: *RuntimeRef,
arg_count: u8, arg_count: u8,
locals_top: usize, locals_top: usize,
@ -88,11 +84,11 @@ pub const RuntimeEnv = struct {
try self.locals.push_one(try self.acquire(arg)); try self.locals.push_one(try self.acquire(arg));
} }
const frame = try self.push_frame(args.len); const frame = try self.push_frame(callable, args.len);
defer self.pop_frame(); defer self.pop_frame();
return self.call_frame(callable, frame); return self.call_frame(frame);
} }
pub fn call_frame(self: *RuntimeEnv, callable: *const RuntimeRef, frame: Frame) RuntimeError!?*RuntimeRef { pub fn call_frame(self: *RuntimeEnv, callable: *const RuntimeRef, frame: Frame) RuntimeError!?*RuntimeRef {
@ -132,6 +128,12 @@ pub const RuntimeEnv = struct {
switch (object.payload) { switch (object.payload) {
.false, .true, .float, .fixed, .symbol, .vector2, .vector3, .syscall => {}, .false, .true, .float, .fixed, .symbol, .vector2, .vector3, .syscall => {},
.boxed => |*boxed| {
if (boxed.*) |boxed_value| {
self.discard(boxed_value);
}
},
.string => |string| { .string => |string| {
coral.debug.assert(string.len >= 0); coral.debug.assert(string.len >= 0);
self.allocator.deallocate(string.ptr[0 .. @intCast(string.len)]); self.allocator.deallocate(string.ptr[0 .. @intCast(string.len)]);
@ -201,6 +203,7 @@ pub const RuntimeEnv = struct {
.string => self.raise(error.TypeMismatch, "string is not get-indexable"), .string => self.raise(error.TypeMismatch, "string is not get-indexable"),
.symbol => self.raise(error.TypeMismatch, "symbol is not get-indexable"), .symbol => self.raise(error.TypeMismatch, "symbol is not get-indexable"),
.syscall => self.raise(error.TypeMismatch, "syscall 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.unbox_symbol(index);
@ -258,6 +261,8 @@ pub const RuntimeEnv = struct {
} }
pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!?*RuntimeRef { pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!?*RuntimeRef {
const file_name = file_path.get_string();
var chunk = make_chunk: { var chunk = make_chunk: {
const file_data = const file_data =
(try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse { (try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse {
@ -282,24 +287,17 @@ pub const RuntimeEnv = struct {
}; };
} }
break: make_chunk try Chunk.make(self, file_path.to_string() orelse "<script>", &root.environment); break: make_chunk try Chunk.make(self, file_name, &root.environment);
}; };
if (builtin.mode == .Debug) {
const string_ref = try chunk.dump(self);
defer self.discard(string_ref);
const string = string_ref.as_string();
coral.debug.assert(string != null);
app.log_info(string.?);
}
defer chunk.free(self); defer chunk.free(self);
return execute_chunk: { return execute_chunk: {
const frame = try self.push_frame(0); const name = try self.new_string(file_name);
defer self.discard(name);
const frame = try self.push_frame(name, 0);
defer self.pop_frame(); defer self.pop_frame();
@ -357,11 +355,20 @@ pub const RuntimeEnv = struct {
}); });
} }
pub fn new_boxed(self: *RuntimeEnv, value: ?*const RuntimeRef) RuntimeError!*RuntimeRef {
return RuntimeRef.allocate(self.allocator, .{
.ref_count = 1,
.payload = .{.boxed = if (value) |ref| ref.acquire() else null},
});
}
pub fn new_dynamic( pub fn new_dynamic(
self: *RuntimeEnv, self: *RuntimeEnv,
userdata: [*]const coral.io.Byte, userdata: []const coral.io.Byte,
typeinfo: *const Typeinfo, typeinfo: *const Typeinfo,
) RuntimeError!*RuntimeRef { ) RuntimeError!*RuntimeRef {
coral.debug.assert(userdata.len == typeinfo.size);
const dynamic = try self.allocator.reallocate(null, @sizeOf(usize) + typeinfo.size); const dynamic = try self.allocator.reallocate(null, @sizeOf(usize) + typeinfo.size);
errdefer self.allocator.deallocate(dynamic); errdefer self.allocator.deallocate(dynamic);
@ -440,7 +447,7 @@ pub const RuntimeEnv = struct {
errdefer table.free(self); errdefer table.free(self);
return try self.new_dynamic(coral.io.bytes_of(&table).ptr, Table.typeinfo); return try self.new_dynamic(coral.io.bytes_of(&table), Table.typeinfo);
} }
pub fn new_vector2(self: *RuntimeEnv, x: f32, y: f32) RuntimeError!*RuntimeRef { pub fn new_vector2(self: *RuntimeEnv, x: f32, y: f32) RuntimeError!*RuntimeRef {
@ -470,14 +477,23 @@ pub const RuntimeEnv = struct {
} }
pub fn pop_frame(self: *RuntimeEnv) void { pub fn pop_frame(self: *RuntimeEnv) void {
var to_pop = self.locals.values.len - (self.frames.pop() orelse unreachable).locals_top; const popped_frame = self.frames.pop();
while (to_pop != 0) { coral.debug.assert(popped_frame != null);
if (self.locals.pop() orelse unreachable) |local| { self.discard(popped_frame.?.name_stringable);
var locals_to_pop = self.locals.values.len - popped_frame.?.locals_top;
while (locals_to_pop != 0) {
const popped_local = self.locals.pop();
coral.debug.assert(popped_local != null);
if (popped_local.?) |local| {
self.discard(local); self.discard(local);
} }
to_pop -= 1; locals_to_pop -= 1;
} }
} }
@ -485,8 +501,9 @@ pub const RuntimeEnv = struct {
return self.locals.pop() orelse self.raise(error.IllegalState, "stack underflow"); return self.locals.pop() orelse self.raise(error.IllegalState, "stack underflow");
} }
pub fn push_frame(self: *RuntimeEnv, arg_count: u8) RuntimeError!Frame { pub fn push_frame(self: *RuntimeEnv, name_stringable: *RuntimeRef, arg_count: u8) RuntimeError!Frame {
const frame = Frame{ const frame = Frame{
.name_stringable = name_stringable.acquire(),
.arg_count = arg_count, .arg_count = arg_count,
.locals_top = self.locals.values.len - arg_count, .locals_top = self.locals.values.len - arg_count,
}; };
@ -507,7 +524,17 @@ pub const RuntimeEnv = struct {
while (remaining_frames != 0) { while (remaining_frames != 0) {
remaining_frames -= 1; remaining_frames -= 1;
self.print_error(self.frames.values[remaining_frames].name); const callable_string = try self.to_string(self.frames.values[remaining_frames].name_stringable);
defer self.discard(callable_string);
self.print_error(get_name: {
const string = callable_string.as_string();
coral.debug.assert(string != null);
break: get_name string.?;
});
} }
} }
@ -573,6 +600,7 @@ pub const RuntimeEnv = struct {
break: convert self.new_string(string[0 .. length.?]); break: convert self.new_string(string[0 .. length.?]);
}, },
.boxed => unreachable,
.symbol => |symbol| self.new_string(coral.io.slice_sentineled(@as(coral.io.Byte, 0), symbol)), .symbol => |symbol| self.new_string(coral.io.slice_sentineled(@as(coral.io.Byte, 0), symbol)),
.string => value.acquire(), .string => value.acquire(),
@ -661,6 +689,7 @@ pub const RuntimeRef = opaque {
vector2: [2]f32, vector2: [2]f32,
vector3: [3]f32, vector3: [3]f32,
syscall: *const Syscall, syscall: *const Syscall,
boxed: ?*RuntimeRef,
string: struct { string: struct {
ptr: [*]coral.io.Byte, ptr: [*]coral.io.Byte,
@ -712,9 +741,23 @@ pub const RuntimeRef = opaque {
return @ptrCast(try coral.io.allocate_one(allocator, data)); return @ptrCast(try coral.io.allocate_one(allocator, data));
} }
pub fn as_boxed(self: *const RuntimeRef) ?*?*RuntimeRef {
return switch (self.object().payload) {
.boxed => |*boxed| boxed,
else => null,
};
}
pub fn as_dynamic(self: *const RuntimeRef, typeinfo: *const Typeinfo) ?[]u8 {
return switch (self.object().payload) {
.dynamic => |dynamic| if (dynamic.typeinfo() == typeinfo) dynamic.userdata() else null,
else => null,
};
}
pub fn as_fixed(self: *const RuntimeRef) ?Fixed { pub fn as_fixed(self: *const RuntimeRef) ?Fixed {
return switch (self.object().payload) { return switch (self.object().payload) {
.fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))), .fixed => |fixed| fixed,
else => null, else => null,
}; };
} }
@ -727,6 +770,7 @@ pub const RuntimeRef = opaque {
break: get string.ptr[0 .. @intCast(string.len)]; break: get string.ptr[0 .. @intCast(string.len)];
}, },
.symbol => |symbol| coral.io.slice_sentineled(@as(u8, 0), symbol),
else => null, else => null,
}; };
} }
@ -757,6 +801,8 @@ pub const RuntimeRef = opaque {
else => false, else => false,
}, },
.boxed => unreachable,
.vector2 => |self_vector| switch (other.object().payload) { .vector2 => |self_vector| switch (other.object().payload) {
.vector2 => |other_vector| coral.io.are_equal(coral.io.bytes_of(&self_vector), coral.io.bytes_of(&other_vector)), .vector2 => |other_vector| coral.io.are_equal(coral.io.bytes_of(&self_vector), coral.io.bytes_of(&other_vector)),
else => false, else => false,
@ -797,6 +843,7 @@ pub const RuntimeRef = opaque {
.vector2 => |vector| @bitCast(vector), .vector2 => |vector| @bitCast(vector),
.vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector)), .vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector)),
.syscall => |syscall| @intFromPtr(syscall), .syscall => |syscall| @intFromPtr(syscall),
.boxed => |boxed| @intFromPtr(boxed),
.string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()), .string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()),
.dynamic => |dynamic| @intFromPtr(dynamic.typeinfo()) ^ @intFromPtr(dynamic.userdata().ptr), .dynamic => |dynamic| @intFromPtr(dynamic.typeinfo()) ^ @intFromPtr(dynamic.userdata().ptr),
}; };
@ -812,6 +859,7 @@ pub const RuntimeRef = opaque {
.vector2 => |vector| coral.io.all_equals(coral.io.bytes_of(&vector), 0), .vector2 => |vector| coral.io.all_equals(coral.io.bytes_of(&vector), 0),
.vector3 => |vector| coral.io.all_equals(coral.io.bytes_of(&vector), 0), .vector3 => |vector| coral.io.all_equals(coral.io.bytes_of(&vector), 0),
.syscall => true, .syscall => true,
.boxed => |boxed| boxed != null,
.string => |string| string.len != 0, .string => |string| string.len != 0,
.dynamic => true, .dynamic => true,
kayomn marked this conversation as resolved Outdated

Why unreachable?

Why unreachable?
}; };

View File

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

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

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

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

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

What is this for?

What is this for?
};
const Builtin = enum { const Builtin = enum {
import, import,
@ -32,13 +41,15 @@ const OpcodeList = coral.list.Stack(union (enum) {
push_top, push_top,
push_table: u32, push_table: u32,
push_builtin: Builtin, push_builtin: Builtin,
push_closure: u8, push_binding: u8,
push_self,
push_boxed, push_boxed,
set_local: u8, set_local: u8,
get_dynamic, get_dynamic,
set_dynamic, set_dynamic,
get_box,
set_box,
call: u8, call: u8,
bind: u8,
not, not,
neg, neg,
@ -67,6 +78,7 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
defer buffer.free(); defer buffer.free();
const writer = coral.list.stack_as_writer(&buffer); const writer = coral.list.stack_as_writer(&buffer);
_ = coral.utf8.print_string(writer, coral.io.slice_sentineled(@as(coral.io.Byte, 0), try env.unbox_symbol(chunk.name))); _ = coral.utf8.print_string(writer, coral.io.slice_sentineled(@as(coral.io.Byte, 0), try env.unbox_symbol(chunk.name)));
_ = coral.utf8.print_string(writer, ":\n"); _ = coral.utf8.print_string(writer, ":\n");
@ -107,10 +119,9 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
}), }),
.push_boxed => coral.utf8.print_string(writer, "push boxed\n"), .push_boxed => coral.utf8.print_string(writer, "push boxed\n"),
.push_self => coral.utf8.print_string(writer, "push self\n"),
.push_closure => |push_closure| coral.utf8.print_formatted(writer, "push closure <{count}>\n", .{ .push_binding => |push_binding| coral.utf8.print_formatted(writer, "push binding <{binding}>\n", .{
.count = push_closure, .binding = push_binding,
}), }),
.push_builtin => |push_builtin| coral.utf8.print_formatted(writer, "push builtin <{builtin}>\n", .{ .push_builtin => |push_builtin| coral.utf8.print_formatted(writer, "push builtin <{builtin}>\n", .{
@ -122,7 +133,16 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
}, },
}), }),
.set_local => |local_set| coral.utf8.print_formatted(writer, "set local <{local}>\n", .{.local = local_set}), .bind => |bind| coral.utf8.print_formatted(writer, "bind <{count}>\n", .{
.count = bind,
}),
.set_local => |local_set| coral.utf8.print_formatted(writer, "set local <{local}>\n", .{
.local = local_set,
}),
.get_box => coral.utf8.print_string(writer, "get box\n"),
.set_box => coral.utf8.print_string(writer, "set box\n"),
.get_dynamic => coral.utf8.print_string(writer, "get dynamic\n"), .get_dynamic => coral.utf8.print_string(writer, "get dynamic\n"),
.set_dynamic => coral.utf8.print_string(writer, "set dynamic\n"), .set_dynamic => coral.utf8.print_string(writer, "set dynamic\n"),
.call => |call| coral.utf8.print_formatted(writer, "call <{count}>\n", .{.count = call}), .call => |call| coral.utf8.print_formatted(writer, "call <{count}>\n", .{.count = call}),
@ -145,11 +165,11 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
return env.new_string(buffer.values); return env.new_string(buffer.values);
} }
pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef { pub fn execute(self: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
var opcode_cursor = @as(u32, 0); var opcode_cursor = @as(u32, 0);
while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) { while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) {
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 (chunk.opcodes.values[opcode_cursor]) { switch (self.opcodes.values[opcode_cursor]) {
.pop => { .pop => {
if (try env.pop_local()) |ref| { if (try env.pop_local()) |ref| {
env.discard(ref); env.discard(ref);
@ -161,11 +181,11 @@ pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeE
.push_false => try env.locals.push_one(try env.new_boolean(false)), .push_false => try env.locals.push_one(try env.new_boolean(false)),
.push_const => |push_const| { .push_const => |push_const| {
if (push_const >= chunk.constants.values.len) { 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(chunk.constants.values[push_const].acquire()); try env.locals.push_one(self.constants.values[push_const].acquire());
}, },
.push_local => |push_local| { .push_local => |push_local| {
@ -219,19 +239,59 @@ pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeE
}, },
.push_boxed => { .push_boxed => {
// TODO: Implement. const value = try env.pop_local();
unreachable;
defer {
if (value) |ref| {
env.discard(ref);
}
}
const boxable = try env.new_boxed(value);
errdefer env.discard(boxable);
try env.locals.push_one(boxable);
}, },
.push_self => { .push_binding => |push_binding| {
// TODO: Implement. if (push_binding > self.bindings.len) {
unreachable; return env.raise(error.IllegalState, "binding out of range");
}
try env.locals.push_one(if (self.bindings[push_binding]) |value| value.acquire() else null);
}, },
.push_closure => |push_closure| { .bind => |bind| {
// TODO: Implement. const lambda = try env.expect(try env.pop_local());
_ = push_closure;
unreachable; 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");
})));
chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*kym.RuntimeRef, null));
for (0 .. bind) |index| {
const value = try env.pop_local();
errdefer {
if (value) |ref| {
env.discard(ref);
}
}
const binding = &chunk.bindings[index];
if (binding.*) |*existing_binding| {
env.discard(existing_binding.*);
}
binding.* = value;
}
try env.locals.push_one(lambda);
}, },
.push_builtin => |push_builtin| { .push_builtin => |push_builtin| {
@ -257,6 +317,34 @@ pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeE
local.* = try env.pop_local(); local.* = try env.pop_local();
}, },
.get_box => {
const box = try env.expect(try env.pop_local());
defer env.discard(box);
const boxed = box.as_boxed() orelse {
return env.raise(error.TypeMismatch, "type is not unboxable");
};
try env.locals.push_one(if (boxed.*) |value| value.acquire() else null);
},
.set_box => {
const box = try env.expect(try env.pop_local());
errdefer env.discard(box);
const boxed = box.as_boxed() orelse {
return env.raise(error.TypeMismatch, "type is not unboxable");
};
if (boxed.*) |value| {
env.discard(value);
}
boxed.* = box;
},
.get_dynamic => { .get_dynamic => {
const index = try env.expect(try env.pop_local()); const index = try env.expect(try env.pop_local());
@ -307,7 +395,7 @@ pub fn execute(chunk: Self, env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeE
defer env.discard(callable); defer env.discard(callable);
const call_frame = try env.push_frame(call); const call_frame = try env.push_frame(callable, call);
defer env.pop_frame(); defer env.pop_frame();
@ -494,6 +582,18 @@ pub fn free(self: *Self, env: *kym.RuntimeEnv) void {
self.constants.free(); self.constants.free();
self.opcodes.free(); self.opcodes.free();
env.discard(self.name); env.discard(self.name);
if (self.bindings.len != 0) {
for (self.bindings) |binding| {
if (binding) |value| {
env.discard(value);
}
}
env.allocator.deallocate(self.bindings);
}
self.bindings = &.{};
} }
pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) kym.RuntimeError!Self { pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) kym.RuntimeError!Self {
@ -501,6 +601,7 @@ pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *con
.name = try env.new_symbol(name), .name = try env.new_symbol(name),
.opcodes = OpcodeList.make(env.allocator), .opcodes = OpcodeList.make(env.allocator),
.constants = ConstList.make(env.allocator), .constants = ConstList.make(env.allocator),
.bindings = &.{},
.arity = 0, .arity = 0,
}; };
@ -511,6 +612,17 @@ 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) {
const string_ref = try chunk.dump(env);
defer env.discard(string_ref);
const string = string_ref.as_string();
coral.debug.assert(string != null);
app.log_info(string.?);
}
return chunk; return chunk;
} }
@ -550,7 +662,7 @@ fn syscall_vec3(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.R
pub const typeinfo = &kym.Typeinfo{ pub const typeinfo = &kym.Typeinfo{
.size = @sizeOf(Self), .size = @sizeOf(Self),
.name = "lambda", .name = "func",
.destruct = typeinfo_destruct, .destruct = typeinfo_destruct,
.call = typeinfo_call, .call = typeinfo_call,
}; };

View File

@ -90,14 +90,18 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
errdefer chunk.free(self.env); errdefer chunk.free(self.env);
if (lambda_construct.environment.capture_count == 0 and 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 {
if (lambda_construct.environment.capture_count != 0) { for (environment.captures[0 .. environment.capture_count]) |capture| {
for (lambda_construct.environment.captures[0 .. lambda_construct.environment.capture_count]) |capture| {
try self.chunk.opcodes.push_one(.{.push_local = environment.captures[capture]}); try self.chunk.opcodes.push_one(.{.push_local = environment.captures[capture]});
} }
try self.chunk.opcodes.push_one(.{.push_closure = lambda_construct.environment.capture_count}); try self.chunk.opcodes.push_one(.{.push_const = try self.declare_chunk(chunk)});
try self.chunk.opcodes.push_one(.{
.bind = lambda_construct.environment.capture_count + environment.capture_count,
});
} }
}, },
@ -146,27 +150,34 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
} }
if (get_capture_index(environment, declaration_get.declaration)) |index| { if (get_capture_index(environment, declaration_get.declaration)) |index| {
try self.chunk.opcodes.push_one(.push_self); try self.chunk.opcodes.push_one(.{.push_binding = index});
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_fixed(index)});
return self.chunk.opcodes.push_one(.get_dynamic); if (declaration_get.declaration.is.captured) {
try self.chunk.opcodes.push_one(.get_box);
}
return;
} }
return self.env.raise(error.IllegalState, "local out of scope"); return self.env.raise(error.IllegalState, "local out of scope");
}, },
.declaration_set => |declaration_set| { .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);
if (get_local_index(environment, declaration_set.declaration)) |index| {
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_capture_index(environment, declaration_set.declaration)) |index| {
try self.chunk.opcodes.push_one(.push_self); try self.chunk.opcodes.push_one(.{.push_binding = index});
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_fixed(index)}); try self.compile_expression(environment, declaration_set.assign);
return self.chunk.opcodes.push_one(.set_dynamic); if (declaration_set.declaration.is.captured) {
try self.chunk.opcodes.push_one(.set_box);
}
return;
} }
return self.env.raise(error.IllegalState, "local out of scope"); return self.env.raise(error.IllegalState, "local out of scope");
@ -253,7 +264,7 @@ 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);
if (declare.declaration.is_captured and !declare.declaration.is_readonly) { if (declare.declaration.is.captured and !declare.declaration.is.readonly) {
try self.chunk.opcodes.push_one(.push_boxed); try self.chunk.opcodes.push_one(.push_boxed);
} }
}, },
@ -276,7 +287,7 @@ fn declare_chunk(self: Self, chunk: Chunk) kym.RuntimeError!u16 {
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 65,535 constants");
} }
const constant = try self.env.new_dynamic(coral.io.bytes_of(&chunk).ptr, Chunk.typeinfo); const constant = try self.env.new_dynamic(coral.io.bytes_of(&chunk), Chunk.typeinfo);
errdefer self.env.discard(constant); errdefer self.env.discard(constant);
@ -346,7 +357,11 @@ pub fn get_capture_index(self: *const tree.Environment, declaration: *const tree
var capture_index = @as(u8, 0); var capture_index = @as(u8, 0);
while (capture_index < self.capture_count) : (capture_index += 1) { while (capture_index < self.capture_count) : (capture_index += 1) {
if (&enclosing_environment.local_declarations[capture_index] == declaration) { const captured_declaration_index = self.captures[capture_index];
coral.debug.assert(captured_declaration_index < enclosing_environment.local_declaration_count);
if (&enclosing_environment.local_declarations[captured_declaration_index] == declaration) {
return capture_index; return capture_index;
} }
} }

View File

@ -174,11 +174,17 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
return root.create_expr(.{ return root.create_expr(.{
.kind = switch (expression.kind) { .kind = switch (expression.kind) {
.declaration_get => |declaration_get| .{ .declaration_get => |declaration_get| convert: {
if (declaration_get.declaration.is.readonly) {
return root.report_error(stream, "readonly declarations cannot be re-assigned", .{});
}
break: convert .{
.declaration_set = .{ .declaration_set = .{
.assign = try parse(root, stream, environment), .assign = try parse(root, stream, environment),
.declaration = declaration_get.declaration, .declaration = declaration_get.declaration,
}, },
};
}, },
.field_get => |field_get| .{ .field_get => |field_get| .{
@ -413,18 +419,19 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
.identifier => |identifier| { .identifier => |identifier| {
stream.skip_newlines(); stream.skip_newlines();
return root.create_expr(.{ const declaration = (try environment.resolve_declaration(identifier)) orelse {
.next = null,
.kind = .{
.declaration_get = .{
.declaration = (try environment.resolve_declaration(identifier)) orelse {
return root.report_error(stream, "undefined identifier `{identifier}`", .{ return root.report_error(stream, "undefined identifier `{identifier}`", .{
.identifier = identifier, .identifier = identifier,
}); });
}, };
},
}, if (declaration.is.argument and declaration.is.captured) {
return root.report_error(stream, "arguments cannot be directly captured, create a declaration", .{});
}
return root.create_expr(.{
.next = null,
.kind = .{.declaration_get = .{.declaration = declaration}},
}); });
}, },
@ -445,7 +452,10 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
else => return root.report_error(stream, "expected identifier", .{}), else => return root.report_error(stream, "expected identifier", .{}),
}; };
if (try lambda_environment.declare(identifier) == null) { if (try lambda_environment.declare(.{
.identifier = identifier,
.is = .{.argument = true},
}) == null) {
return root.report_error(stream, "declaration `{identifier}` already exists", .{ return root.report_error(stream, "declaration `{identifier}` already exists", .{
.identifier = identifier, .identifier = identifier,
}); });

View File

@ -122,7 +122,7 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
.declare = .{ .declare = .{
.initial_expression = try Expr.parse(root, stream, environment), .initial_expression = try Expr.parse(root, stream, environment),
kayomn marked this conversation as resolved Outdated

Shorten to use root.report_declare_error.

Shorten to use `root.report_declare_error`.
.declaration = (try root.environment.declare(identifier)) orelse { .declaration = (try environment.declare(.{.identifier = identifier})) orelse {
return root.report_error(stream, "declaration `{identifier}` already exists", .{ return root.report_error(stream, "declaration `{identifier}` already exists", .{
.identifier = identifier, .identifier = identifier,
}); });

View File

@ -8,8 +8,12 @@ const tokens = @import("./tokens.zig");
pub const Declaration = struct { pub const Declaration = struct {
identifier: []const coral.io.Byte, identifier: []const coral.io.Byte,
is_readonly: bool = false,
is_captured: bool = false, is: packed struct {
readonly: bool = false,
captured: bool = false,
argument: bool = false,
} = .{},
}; };
pub const Environment = struct { pub const Environment = struct {
@ -31,17 +35,17 @@ pub const Environment = struct {
}); });
} }
pub fn declare(self: *Environment, identifier: []const coral.io.Byte) coral.io.AllocationError!?*const Declaration { pub fn declare(self: *Environment, declaration: Declaration) coral.io.AllocationError!?*const Declaration {
if (self.local_declaration_count == self.local_declarations.len) { if (self.local_declaration_count == self.local_declarations.len) {
return error.OutOfMemory; return error.OutOfMemory;
} }
const declaration = &self.local_declarations[self.local_declaration_count]; const declaration_slot = &self.local_declarations[self.local_declaration_count];
declaration.* = .{.identifier = identifier}; declaration_slot.* = declaration;
self.local_declaration_count += 1; self.local_declaration_count += 1;
return declaration; return declaration_slot;
} }
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 {
@ -49,20 +53,20 @@ pub const Environment = struct {
var ancestry = @as(usize, 0); var ancestry = @as(usize, 0);
while (true) : (ancestry += 1) { while (true) : (ancestry += 1) {
var remaining_count = self.local_declaration_count; var remaining_count = environment.local_declaration_count;
while (remaining_count != 0) { while (remaining_count != 0) {
remaining_count -= 1; remaining_count -= 1;
const declaration = &self.local_declarations[remaining_count]; const declaration = &environment.local_declarations[remaining_count];
if (coral.io.are_equal(declaration.identifier, identifier)) { if (coral.io.are_equal(declaration.identifier, identifier)) {
if (ancestry != 0) { if (ancestry != 0) {
declaration.is_captured = true; declaration.is.captured = true;
environment = self; environment = self;
while (ancestry != 0) : (ancestry -= 1) { while (ancestry != 0) : (ancestry -= 1) {
if (environment.capture_count == self.captures.len) { if (environment.capture_count == environment.captures.len) {
return error.OutOfMemory; return error.OutOfMemory;
} }