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
6 changed files with 118 additions and 54 deletions
Showing only changes of commit 88ff2a64b6 - Show all commits

View File

@ -1,9 +1,7 @@
let printer = lambda (pfx): let printer = lambda (pfx):
let prefix = pfx
return lambda (msg): return lambda (msg):
@print(prefix) @print(pfx)
@print(msg) @print(msg)
end end
end end

View File

@ -79,9 +79,7 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef
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, "\n");
_ = coral.utf8.print_string(writer, ":\n");
while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) { while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) {
_ = coral.utf8.print_formatted(writer, "[{instruction}]: ", .{.instruction = opcode_cursor}); _ = coral.utf8.print_formatted(writer, "[{instruction}]: ", .{.instruction = opcode_cursor});
@ -613,6 +611,12 @@ pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *con
try compiler.compile_environment(environment); try compiler.compile_environment(environment);
if (builtin.mode == .Debug) { if (builtin.mode == .Debug) {
const allocation = try coral.utf8.alloc_formatted(env.allocator, "compiled {name}...", .{.name = name});
defer env.allocator.deallocate(allocation);
app.log_info(allocation);
const string_ref = try chunk.dump(env); const string_ref = try chunk.dump(env);
defer env.discard(string_ref); defer env.discard(string_ref);

View File

@ -152,7 +152,7 @@ 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_binding = index}); try self.chunk.opcodes.push_one(.{.push_binding = index});
if (declaration_get.declaration.is.captured) { if (is_declaration_boxed(declaration_get.declaration)) {
try self.chunk.opcodes.push_one(.get_box); try self.chunk.opcodes.push_one(.get_box);
} }
@ -173,7 +173,7 @@ fn compile_expression(self: Self, environment: *const tree.Environment, expressi
try self.chunk.opcodes.push_one(.{.push_binding = index}); try self.chunk.opcodes.push_one(.{.push_binding = index});
try self.compile_expression(environment, declaration_set.assign); try self.compile_expression(environment, declaration_set.assign);
if (declaration_set.declaration.is.captured) { if (is_declaration_boxed(declaration_set.declaration)) {
try self.chunk.opcodes.push_one(.set_box); try self.chunk.opcodes.push_one(.set_box);
} }
@ -359,9 +359,9 @@ pub fn get_capture_index(self: *const tree.Environment, declaration: *const tree
while (capture_index < self.capture_count) : (capture_index += 1) { while (capture_index < self.capture_count) : (capture_index += 1) {
const captured_declaration_index = self.captures[capture_index]; const captured_declaration_index = self.captures[capture_index];
coral.debug.assert(captured_declaration_index < enclosing_environment.local_declaration_count); coral.debug.assert(captured_declaration_index < enclosing_environment.declaration_count);
if (&enclosing_environment.local_declarations[captured_declaration_index] == declaration) { if (&enclosing_environment.declarations[captured_declaration_index] == declaration) {
return capture_index; return capture_index;
} }
} }
@ -371,15 +371,19 @@ pub fn get_capture_index(self: *const tree.Environment, declaration: *const tree
} }
pub fn get_local_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 { pub fn get_local_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
var remaining = self.local_declaration_count; var remaining = self.declaration_count;
while (remaining != 0) { while (remaining != 0) {
remaining -= 1; remaining -= 1;
if (&self.local_declarations[remaining] == declaration) { if (&self.declarations[remaining] == declaration) {
return remaining; return remaining;
} }
} }
return null; return null;
} }
fn is_declaration_boxed(declaration: *const tree.Declaration) bool {
return declaration.is.captured and !declaration.is.readonly;
}

View File

@ -419,19 +419,16 @@ fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.En
.identifier => |identifier| { .identifier => |identifier| {
stream.skip_newlines(); stream.skip_newlines();
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(.{ return root.create_expr(.{
.next = null, .kind = .{
.kind = .{.declaration_get = .{.declaration = declaration}}, .declaration_get = .{
.declaration = (try environment.resolve_declaration(identifier)) orelse {
return root.report_error(stream, "undefined identifier `{identifier}`", .{
.identifier = identifier,
});
}
},
},
}); });
}, },
@ -452,16 +449,9 @@ 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(.{ _ = lambda_environment.declare_argument(identifier) catch |declare_error| {
.identifier = identifier, return root.report_declare_error(stream, identifier, declare_error);
.is = .{.argument = true}, };
}) == null) {
return root.report_error(stream, "declaration `{identifier}` already exists", .{
.identifier = identifier,
});
}
lambda_environment.argument_count += 1;
stream.skip_newlines(); stream.skip_newlines();

View File

@ -93,18 +93,13 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
}, },
.keyword_var, .keyword_let => { .keyword_var, .keyword_let => {
const storage_token = stream.token; const is_constant = stream.token == .keyword_let;
stream.skip_newlines(); stream.skip_newlines();
const identifier = switch (stream.token) { const identifier = switch (stream.token) {
.identifier => |identifier| identifier, .identifier => |identifier| identifier,
else => return root.report_error(stream, "expected identifier after declaration", .{}),
else => {
return root.report_error(stream, "expected identifier after `{storage}` declaration statement", .{
.storage = storage_token.text()
});
},
}; };
stream.skip_newlines(); stream.skip_newlines();
@ -122,10 +117,30 @@ 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),
.declaration = (try environment.declare(.{.identifier = identifier})) orelse { .declaration = declare: {
return root.report_error(stream, "declaration `{identifier}` already exists", .{ if (is_constant) {
.identifier = identifier, break: declare environment.declare_constant(identifier) catch |declaration_error| {
}); return switch (declaration_error) {
kayomn marked this conversation as resolved Outdated

Shorten to use root.report_declare_error.

Shorten to use `root.report_declare_error`.
error.OutOfMemory => error.OutOfMemory,
error.DeclarationExists =>
root.report_error(stream, "declaration `{identifier}` already exists", .{
.identifier = identifier,
}),
};
};
}
break: declare environment.declare_variable(identifier) catch |declaration_error| {
return switch (declaration_error) {
kayomn marked this conversation as resolved Outdated

Shorten to use root.report_declare_error.

Shorten to use `root.report_declare_error`.
error.OutOfMemory => error.OutOfMemory,
error.DeclarationExists =>
root.report_error(stream, "declaration `{identifier}` already exists", .{
.identifier = identifier,
}),
};
};
}, },
}, },
}, },

View File

@ -12,19 +12,22 @@ pub const Declaration = struct {
is: packed struct { is: packed struct {
readonly: bool = false, readonly: bool = false,
captured: bool = false, captured: bool = false,
argument: bool = false,
} = .{}, } = .{},
}; };
pub const Environment = struct { pub const Environment = struct {
captures: [capture_max]u8 = [_]u8{0} ** capture_max, captures: [capture_max]u8 = [_]u8{0} ** capture_max,
capture_count: u8 = 0, capture_count: u8 = 0,
local_declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max, declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max,
local_declaration_count: u8 = 0, declaration_count: u8 = 0,
argument_count: u8 = 0, argument_count: u8 = 0,
statement: ?*const Stmt = null, statement: ?*const Stmt = null,
enclosing: ?*Environment = null, enclosing: ?*Environment = null,
const DeclareError = coral.io.AllocationError || error {
DeclarationExists,
};
const capture_max = coral.math.max_int(@typeInfo(u8).Int); const capture_max = coral.math.max_int(@typeInfo(u8).Int);
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.
const declaration_max = coral.math.max_int(@typeInfo(u8).Int); const declaration_max = coral.math.max_int(@typeInfo(u8).Int);
@ -35,30 +38,70 @@ pub const Environment = struct {
}); });
} }
pub fn declare(self: *Environment, declaration: Declaration) coral.io.AllocationError!?*const Declaration { fn declare(self: *Environment, declaration: Declaration) DeclareError!*const Declaration {
if (self.local_declaration_count == self.local_declarations.len) { if (self.declaration_count == self.declarations.len) {
return error.OutOfMemory; return error.OutOfMemory;
} }
const declaration_slot = &self.local_declarations[self.local_declaration_count]; {
var environment = self;
while (true) {
var remaining_count = environment.declaration_count;
while (remaining_count != 0) {
remaining_count -= 1;
if (coral.io.are_equal(environment.declarations[remaining_count].identifier, declaration.identifier)) {
return error.DeclarationExists;
}
}
environment = environment.enclosing orelse break;
}
}
const declaration_slot = &self.declarations[self.declaration_count];
declaration_slot.* = declaration; declaration_slot.* = declaration;
self.local_declaration_count += 1; self.declaration_count += 1;
return declaration_slot; return declaration_slot;
} }
pub fn declare_argument(self: *Environment, identifier: []const coral.io.Byte) DeclareError!*const Declaration {
coral.debug.assert(self.declaration_count <= self.argument_count);
defer self.argument_count += 1;
return self.declare(.{
.identifier = identifier,
.is = .{.readonly = true},
});
}
pub fn declare_constant(self: *Environment, identifier: []const coral.io.Byte) DeclareError!*const Declaration {
return self.declare(.{
.identifier = identifier,
.is = .{.readonly = true},
});
}
pub fn declare_variable(self: *Environment, identifier: []const coral.io.Byte) DeclareError!*const Declaration {
return self.declare(.{.identifier = identifier});
}
pub fn resolve_declaration(self: *Environment, identifier: []const coral.io.Byte) coral.io.AllocationError!?*const Declaration { pub fn resolve_declaration(self: *Environment, identifier: []const coral.io.Byte) coral.io.AllocationError!?*const Declaration {
var environment = self; var environment = self;
var ancestry = @as(usize, 0); var ancestry = @as(usize, 0);
while (true) : (ancestry += 1) { while (true) : (ancestry += 1) {
var remaining_count = environment.local_declaration_count; var remaining_count = environment.declaration_count;
while (remaining_count != 0) { while (remaining_count != 0) {
remaining_count -= 1; remaining_count -= 1;
const declaration = &environment.local_declarations[remaining_count]; const declaration = &environment.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) {
@ -110,6 +153,16 @@ pub const Root = struct {
return error.BadSyntax; return error.BadSyntax;
} }
pub fn report_declare_error(self: *Root, stream: *tokens.Stream, identifier: []const coral.io.Byte, @"error": Environment.DeclareError) ParseError {
return switch (@"error") {
error.OutOfMemory => error.OutOfMemory,
error.DeclarationExists => self.report_error(stream, "declaration `{identifier}` already exists", .{
.identifier = identifier,
}),
};
}
pub fn create_expr(self: *Root, expr: Expr) coral.io.AllocationError!*Expr { pub fn create_expr(self: *Root, expr: Expr) coral.io.AllocationError!*Expr {
return coral.io.allocate_one(self.arena.as_allocator(), expr); return coral.io.allocate_one(self.arena.as_allocator(), expr);
} }