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 prefix = pfx
return lambda (msg):
@print(prefix)
@print(pfx)
@print(msg)
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);
_ = 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) {
_ = 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);
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);
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| {
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);
}
@ -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.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);
}
@ -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) {
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;
}
}
@ -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 {
var remaining = self.local_declaration_count;
var remaining = self.declaration_count;
while (remaining != 0) {
remaining -= 1;
if (&self.local_declarations[remaining] == declaration) {
if (&self.declarations[remaining] == declaration) {
return remaining;
}
}
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| {
stream.skip_newlines();
const declaration = (try environment.resolve_declaration(identifier)) orelse {
return root.create_expr(.{
.kind = .{
.declaration_get = .{
.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}},
},
},
});
},
@ -452,16 +449,9 @@ 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 = identifier,
.is = .{.argument = true},
}) == null) {
return root.report_error(stream, "declaration `{identifier}` already exists", .{
.identifier = identifier,
});
}
lambda_environment.argument_count += 1;
_ = lambda_environment.declare_argument(identifier) catch |declare_error| {
return root.report_declare_error(stream, identifier, declare_error);
};
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 => {
const storage_token = stream.token;
const is_constant = stream.token == .keyword_let;
stream.skip_newlines();
const identifier = switch (stream.token) {
.identifier => |identifier| identifier,
else => {
return root.report_error(stream, "expected identifier after `{storage}` declaration statement", .{
.storage = storage_token.text()
});
},
else => return root.report_error(stream, "expected identifier after declaration", .{}),
};
stream.skip_newlines();
@ -122,10 +117,30 @@ pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Enviro
.declare = .{
.initial_expression = try Expr.parse(root, stream, environment),
.declaration = (try environment.declare(.{.identifier = identifier})) orelse {
return root.report_error(stream, "declaration `{identifier}` already exists", .{
.declaration = declare: {
if (is_constant) {
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 {
readonly: bool = false,
captured: bool = false,
argument: bool = false,
} = .{},
};
pub const Environment = struct {
captures: [capture_max]u8 = [_]u8{0} ** capture_max,
capture_count: u8 = 0,
local_declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max,
local_declaration_count: u8 = 0,
declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max,
declaration_count: u8 = 0,
argument_count: u8 = 0,
statement: ?*const Stmt = null,
enclosing: ?*Environment = null,
const DeclareError = coral.io.AllocationError || error {
DeclarationExists,
};
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);
@ -35,30 +38,70 @@ pub const Environment = struct {
});
}
pub fn declare(self: *Environment, declaration: Declaration) coral.io.AllocationError!?*const Declaration {
if (self.local_declaration_count == self.local_declarations.len) {
fn declare(self: *Environment, declaration: Declaration) DeclareError!*const Declaration {
if (self.declaration_count == self.declarations.len) {
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;
self.local_declaration_count += 1;
self.declaration_count += 1;
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 {
var environment = self;
var ancestry = @as(usize, 0);
while (true) : (ancestry += 1) {
var remaining_count = environment.local_declaration_count;
var remaining_count = environment.declaration_count;
while (remaining_count != 0) {
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 (ancestry != 0) {
@ -110,6 +153,16 @@ pub const Root = struct {
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 {
return coral.io.allocate_one(self.arena.as_allocator(), expr);
}