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):
@print("This is a func call")
@print(prefix)
@print(msg)
end
end
let pr = printer()
let pr = printer("This is a func call")
var i = 0
pr("test")
@ -25,6 +27,10 @@ else:
pr("i'unno")
end
let pr2 = printer("this is a final closure")
pr2("goodbye")
return {
.title = "Game",
.width = 1280,

View File

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

View File

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

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

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

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

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

What is this for?

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

View File

@ -90,14 +90,18 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
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)});
if (lambda_construct.environment.capture_count != 0) {
for (lambda_construct.environment.captures[0 .. lambda_construct.environment.capture_count]) |capture| {
} else {
for (environment.captures[0 .. environment.capture_count]) |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| {
try self.chunk.opcodes.push_one(.push_self);
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_fixed(index)});
try self.chunk.opcodes.push_one(.{.push_binding = 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");
},
.declaration_set => |declaration_set| {
if (get_local_index(environment, declaration_set.declaration)) |index| {
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});
}
if (get_capture_index(environment, declaration_set.declaration)) |index| {
try self.chunk.opcodes.push_one(.push_self);
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_fixed(index)});
try self.chunk.opcodes.push_one(.{.push_binding = 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");
@ -253,7 +264,7 @@ fn compile_statement(self: Self, environment: *const tree.Environment, initial_s
.declare => |declare| {
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);
}
},
@ -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");
}
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);
@ -346,7 +357,11 @@ pub fn get_capture_index(self: *const tree.Environment, declaration: *const tree
var capture_index = @as(u8, 0);
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;
}
}

View File

@ -174,11 +174,17 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
return root.create_expr(.{
.kind = switch (expression.kind) {
.declaration_get => |declaration_get| .{
.declaration_get => |declaration_get| convert: {
if (declaration_get.declaration.is.readonly) {
return root.report_error(stream, "readonly declarations cannot be re-assigned", .{});
}
break: convert .{
.declaration_set = .{
.assign = try parse(root, stream, environment),
.declaration = declaration_get.declaration,
},
};
},
.field_get => |field_get| .{
@ -413,18 +419,19 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
.identifier => |identifier| {
stream.skip_newlines();
return root.create_expr(.{
.next = null,
.kind = .{
.declaration_get = .{
.declaration = (try environment.resolve_declaration(identifier)) orelse {
const declaration = (try environment.resolve_declaration(identifier)) orelse {
return root.report_error(stream, "undefined 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", .{}),
};
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", .{
.identifier = identifier,
});

View File

@ -122,7 +122,7 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
.declare = .{
.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", .{
.identifier = identifier,
});

View File

@ -8,8 +8,12 @@ const tokens = @import("./tokens.zig");
pub const Declaration = struct {
identifier: []const coral.io.Byte,
is_readonly: bool = false,
is_captured: bool = false,
is: packed struct {
readonly: bool = false,
captured: bool = false,
argument: bool = false,
} = .{},
};
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) {
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;
return declaration;
return declaration_slot;
}
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);
while (true) : (ancestry += 1) {
var remaining_count = self.local_declaration_count;
var remaining_count = environment.local_declaration_count;
while (remaining_count != 0) {
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 (ancestry != 0) {
declaration.is_captured = true;
declaration.is.captured = true;
environment = self;
while (ancestry != 0) : (ancestry -= 1) {
if (environment.capture_count == self.captures.len) {
if (environment.capture_count == environment.captures.len) {
return error.OutOfMemory;
}