Compare commits

...

2 Commits

Author SHA1 Message Date
kayomn b9f03b34c1 Add argument resolving to functions
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2023-08-26 17:00:55 +01:00
kayomn f5ed4bbcad Add support for basic lambda syntax 2023-08-26 16:00:17 +01:00
4 changed files with 195 additions and 51 deletions

View File

@ -1,18 +1,23 @@
var i = 0 var i = 0
let pr = lambda (str):
@print("This is a func call")
@print(str)
end
while i < 5: while i < 5:
@print("hello, world") pr("hello, world")
i = i + 1 i = i + 1
end end
if i > 6: if i > 6:
@print("`i` greater than `6`") pr("`i` greater than `6`")
elif i == 4: elif i == 4:
@print("`i` is equal to `4`") pr("`i` is equal to `4`")
else: else:
@print("i'unno") pr("i'unno")
end end
return { return {

View File

@ -33,6 +33,7 @@ pub const RuntimeEnv = struct {
float: Float, float: Float,
string: []const coral.io.Byte, string: []const coral.io.Byte,
symbol: []const coral.io.Byte, symbol: []const coral.io.Byte,
chunk: Chunk,
}; };
const Opcode = union (enum) { const Opcode = union (enum) {
@ -42,6 +43,7 @@ pub const RuntimeEnv = struct {
push_false, push_false,
push_const: u16, push_const: u16,
push_local: u8, push_local: u8,
push_arg: u8,
push_table: u32, push_table: u32,
push_builtin: Builtin, push_builtin: Builtin,
local_set: u8, local_set: u8,
@ -70,6 +72,7 @@ pub const RuntimeEnv = struct {
const OpcodeList = coral.list.Stack(Opcode); const OpcodeList = coral.list.Stack(Opcode);
const CompilationUnit = struct { const CompilationUnit = struct {
args: []const []const coral.io.Byte,
locals_buffer: [255]Local = [_]Local{.{}} ** 255, locals_buffer: [255]Local = [_]Local{.{}} ** 255,
locals_count: u8 = 0, locals_count: u8 = 0,
@ -124,17 +127,35 @@ pub const RuntimeEnv = struct {
}); });
}, },
.table_literal => |fields| { .table_literal => |literal| {
if (fields.values.len > coral.math.max_int(@typeInfo(u32).Int)) { if (literal.values.len > coral.math.max_int(@typeInfo(u32).Int)) {
return error.OutOfMemory; return error.OutOfMemory;
} }
for (fields.values) |field| { for (literal.values) |field| {
try self.compile_expression(chunk, field.value_expression); try self.compile_expression(chunk, field.value_expression);
try self.compile_expression(chunk, field.key_expression); try self.compile_expression(chunk, field.key_expression);
} }
try chunk.opcodes.push_one(.{.push_table = @intCast(fields.values.len)}); try chunk.opcodes.push_one(.{.push_table = @intCast(literal.values.len)});
},
.lambda_literal => |literal| {
if (literal.argument_identifiers.values.len > coral.math.max_int(@typeInfo(u32).Int)) {
return error.OutOfMemory;
}
var lambda_chunk = try Chunk.make(chunk.env, "<lambda>");
errdefer lambda_chunk.free();
try lambda_chunk.compile(literal.block_statements.values, literal.argument_identifiers.values);
try chunk.opcodes.push_one(.{
.push_const = try chunk.declare_constant(.{
.chunk = lambda_chunk,
}),
});
}, },
.binary_operation => |operation| { .binary_operation => |operation| {
@ -213,11 +234,15 @@ pub const RuntimeEnv = struct {
}, },
.local_get => |local_get| { .local_get => |local_get| {
try chunk.opcodes.push_one(.{ if (self.resolve_local(local_get.identifier)) |local| {
.push_local = (self.resolve_local(local_get.identifier) orelse { return chunk.opcodes.push_one(.{.push_local = local.index});
}
if (self.resolve_arg(local_get.identifier)) |arg| {
return chunk.opcodes.push_one(.{.push_arg = arg});
}
return chunk.env.raise(error.OutOfMemory, "undefined local"); return chunk.env.raise(error.OutOfMemory, "undefined local");
}).index,
});
}, },
.local_set => |local_set| { .local_set => |local_set| {
@ -348,6 +373,18 @@ pub const RuntimeEnv = struct {
} }
} }
fn resolve_arg(self: *CompilationUnit, arg_identifier: []const coral.io.Byte) ?u8 {
var index = @as(u8, 0);
while (index < self.args.len) {
if (coral.io.are_equal(self.args[index], arg_identifier)) {
return index;
}
}
return null;
}
fn resolve_local(self: *CompilationUnit, local_identifier: []const coral.io.Byte) ?ResolvedLocal { fn resolve_local(self: *CompilationUnit, local_identifier: []const coral.io.Byte) ?ResolvedLocal {
if (self.locals_count == 0) { if (self.locals_count == 0) {
return null; return null;
@ -372,11 +409,20 @@ pub const RuntimeEnv = struct {
} }
}; };
fn compile(self: *Chunk, statements: []const ast.Statement) RuntimeError!void { fn compile(self: *Chunk, statements: []const ast.Statement, args: []const []const coral.io.Byte) RuntimeError!void {
var unit = CompilationUnit{}; var unit = CompilationUnit{.args = args};
var has_returned = false;
for (statements) |statement| { for (statements) |statement| {
try unit.compile_statement(self, statement); try unit.compile_statement(self, statement);
if (statement == .@"return") {
has_returned = true;
}
}
if (!has_returned) {
try self.opcodes.push_one(.push_nil);
} }
} }
@ -385,27 +431,27 @@ pub const RuntimeEnv = struct {
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_index = self.constants.values.len; const constant_object = try switch (constant) {
try self.constants.push_one(try switch (constant) {
.fixed => |fixed| self.env.new_fixed(fixed), .fixed => |fixed| self.env.new_fixed(fixed),
.float => |float| self.env.new_float(float), .float => |float| self.env.new_float(float),
.string => |string| self.env.new_string(string), .string => |string| self.env.new_string(string),
.symbol => |symbol| self.env.new_symbol(symbol), .symbol => |symbol| self.env.new_symbol(symbol),
});
return @intCast(constant_index); .chunk => |chunk| self.env.new_dynamic(coral.io.bytes_of(&chunk).ptr, &.{
.size = @sizeOf(Chunk),
.name = "lambda",
.destruct = typeinfo_destruct,
.call = typeinfo_call,
}),
};
errdefer self.env.discard(constant_object);
try self.constants.push_one(constant_object);
return @intCast(self.constants.values.len - 1);
} }
fn execute(self: *Chunk) RuntimeError!?*RuntimeRef { fn execute(self: *Chunk) RuntimeError!?*RuntimeRef {
try self.env.frames.push_one(.{
.arg_count = 0,
.locals_top = self.env.locals.values.len,
.name = self.name,
});
defer coral.debug.assert(self.env.frames.pop() != null);
var opcode_cursor = @as(u32, 0); var opcode_cursor = @as(u32, 0);
while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) { while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) {
@ -440,6 +486,18 @@ pub const RuntimeEnv = struct {
} }
}, },
.push_arg => |push_arg| {
const arg = try self.env.acquire_arg(push_arg);
errdefer {
if (arg) |ref| {
self.env.discard(ref);
}
}
try self.env.locals.push_one(arg);
},
.push_table => |push_table| { .push_table => |push_table| {
const table = try self.env.new_table(); const table = try self.env.new_table();
@ -547,14 +605,16 @@ pub const RuntimeEnv = struct {
defer self.env.discard(callable); defer self.env.discard(callable);
try self.env.frames.push_one(.{ try self.env.frames.push_one(.{
.name = "<lambda>", .name = "<chunk>",
.arg_count = object_call, .arg_count = object_call,
.locals_top = self.env.locals.values.len, .locals_top = self.env.locals.values.len,
}); });
defer coral.debug.assert(self.env.frames.pop() != null); defer coral.debug.assert(self.env.frames.pop() != null);
break: call try switch (callable.object().payload) { const payload = callable.object().payload;
break: call try switch (payload) {
.syscall => |syscall| syscall(self.env), .syscall => |syscall| syscall(self.env),
.dynamic => |dynamic| dynamic.typeinfo().call(.{ .dynamic => |dynamic| dynamic.typeinfo().call(.{
@ -566,18 +626,18 @@ pub const RuntimeEnv = struct {
}; };
}; };
for (0 .. object_call) |_| {
if (try self.pop_local()) |popped_arg| {
self.env.discard(popped_arg);
}
}
errdefer { errdefer {
if (result) |ref| { if (result) |ref| {
self.env.discard(ref); self.env.discard(ref);
} }
} }
for (0 .. object_call) |_| {
if (try self.pop_local()) |popped_arg| {
self.env.discard(popped_arg);
}
}
try self.env.locals.push_one(result); try self.env.locals.push_one(result);
}, },
@ -934,6 +994,14 @@ pub const RuntimeEnv = struct {
fn pop_local(self: *Chunk) RuntimeError!?*RuntimeRef { fn pop_local(self: *Chunk) RuntimeError!?*RuntimeRef {
return self.env.locals.pop() orelse self.env.raise(error.IllegalState, "stack underflow"); return self.env.locals.pop() orelse self.env.raise(error.IllegalState, "stack underflow");
} }
fn typeinfo_call(method: Typeinfo.Method) RuntimeError!?*RuntimeRef {
return @as(*Chunk, @ptrCast(@alignCast(method.userdata))).execute();
}
fn typeinfo_destruct(method: Typeinfo.Method) void {
@as(*Chunk, @ptrCast(@alignCast(method.userdata))).free();
}
}; };
const ConstList = coral.list.Stack(*RuntimeRef); const ConstList = coral.list.Stack(*RuntimeRef);
@ -967,7 +1035,7 @@ pub const RuntimeEnv = struct {
contiguous: RefList, contiguous: RefList,
fn typeinfo_destruct(method: Typeinfo.Method) void { fn typeinfo_destruct(method: Typeinfo.Method) void {
const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); const table = @as(*Table, @ptrCast(@alignCast(method.userdata)));
{ {
var field_iterable = table.associative.as_iterable(); var field_iterable = table.associative.as_iterable();
@ -990,7 +1058,7 @@ pub const RuntimeEnv = struct {
} }
fn typeinfo_get(method: Typeinfo.Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { fn typeinfo_get(method: Typeinfo.Method, index: *const RuntimeRef) RuntimeError!?*RuntimeRef {
const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); const table = @as(*Table, @ptrCast(@alignCast(method.userdata)));
const acquired_index = try method.env.acquire(index); const acquired_index = try method.env.acquire(index);
defer method.env.discard(acquired_index); defer method.env.discard(acquired_index);
@ -1014,7 +1082,7 @@ pub const RuntimeEnv = struct {
} }
fn typeinfo_set(method: Typeinfo.Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { fn typeinfo_set(method: Typeinfo.Method, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void {
const table = @as(*Table, @ptrCast(@alignCast(method.userdata.ptr))); const table = @as(*Table, @ptrCast(@alignCast(method.userdata)));
const acquired_index = try method.env.acquire(index); const acquired_index = try method.env.acquire(index);
errdefer method.env.discard(acquired_index); errdefer method.env.discard(acquired_index);
@ -1079,19 +1147,14 @@ pub const RuntimeEnv = struct {
return null; return null;
} }
pub fn bind_system(self: *RuntimeEnv, name: []const coral.io.Byte, value: *const RuntimeRef) RuntimeError!void { pub fn acquire_args(self: *RuntimeEnv) RuntimeError!*RuntimeRef {
const name_symbol = try self.new_symbol(name); const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow");
errdefer self.discard(name_symbol); _ = frame;
const acquired_value = try self.acquire(value); const args_table = self.new_table();
errdefer self.discard(acquired_value); return args_table;
if (try self.system_bindings.replace(name_symbol, acquired_value)) |replaced| {
self.discard(replaced.key);
self.discard(replaced.value);
}
} }
pub fn call(self: *RuntimeEnv, callable: *RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef { pub fn call(self: *RuntimeEnv, callable: *RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef {
@ -1170,8 +1233,16 @@ pub const RuntimeEnv = struct {
try chunk.compile(ast_tree.parse(file_data) catch |parse_error| return switch (parse_error) { try chunk.compile(ast_tree.parse(file_data) catch |parse_error| return switch (parse_error) {
error.BadSyntax => self.raise(error.BadSyntax, ast_tree.error_message()), error.BadSyntax => self.raise(error.BadSyntax, ast_tree.error_message()),
error.OutOfMemory => error.OutOfMemory, error.OutOfMemory => error.OutOfMemory,
}, &.{});
try self.frames.push_one(.{
.name = file_name,
.arg_count = 0,
.locals_top = self.locals.values.len,
}); });
defer coral.debug.assert(self.frames.pop() != null);
return chunk.execute(); return chunk.execute();
} }

View File

@ -74,6 +74,11 @@ pub const Expression = union (enum) {
table_literal: TableLiteral, table_literal: TableLiteral,
grouped_expression: *Expression, grouped_expression: *Expression,
lambda_literal: struct {
argument_identifiers: IdentifierList,
block_statements: Statement.List,
},
local_get: struct { local_get: struct {
identifier: []const coral.io.Byte, identifier: []const coral.io.Byte,
}, },
@ -136,6 +141,8 @@ pub const DeclarationStorage = enum {
const ExpressionBuilder = fn (self: *Tree) ParseError!Expression; const ExpressionBuilder = fn (self: *Tree) ParseError!Expression;
const IdentifierList = coral.list.Stack([]const coral.io.Byte);
pub const ParseError = error { pub const ParseError = error {
OutOfMemory, OutOfMemory,
BadSyntax, BadSyntax,
@ -221,8 +228,6 @@ pub const Tree = struct {
fn parse_branch(self: *Tree) ParseError!Statement { fn parse_branch(self: *Tree) ParseError!Statement {
const allocator = self.arena.as_allocator(); const allocator = self.arena.as_allocator();
defer self.tokenizer.skip_newlines();
self.tokenizer.step(); self.tokenizer.step();
const condition_expression = try self.parse_expression(); const condition_expression = try self.parse_expression();
@ -238,6 +243,8 @@ pub const Tree = struct {
while (true) { while (true) {
switch (self.tokenizer.token) { switch (self.tokenizer.token) {
.keyword_end => { .keyword_end => {
self.tokenizer.skip_newlines();
return .{ return .{
.@"if" = .{ .@"if" = .{
.condition_expression = condition_expression, .condition_expression = condition_expression,
@ -262,6 +269,8 @@ pub const Tree = struct {
try else_statements.push_one(try self.parse_statement()); try else_statements.push_one(try self.parse_statement());
} }
self.tokenizer.skip_newlines();
return .{ return .{
.@"if" = .{ .@"if" = .{
.else_statement = try coral.io.allocate_one(allocator, Statement{.block = else_statements}), .else_statement = try coral.io.allocate_one(allocator, Statement{.block = else_statements}),
@ -404,6 +413,57 @@ pub const Tree = struct {
break: parse .{.builtin = builtin}; break: parse .{.builtin = builtin};
}, },
.keyword_lambda => {
self.tokenizer.skip_newlines();
if (self.tokenizer.token != .symbol_paren_left) {
return self.report("expected `(` after opening lambda block");
}
var argument_identifiers = IdentifierList.make(allocator);
while (true) {
self.tokenizer.skip_newlines();
switch (self.tokenizer.token) {
.identifier => |identifier| try argument_identifiers.push_one(identifier),
.symbol_paren_right => break,
else => return self.report("expected identifier or closing `)` in argument list"),
}
self.tokenizer.skip_newlines();
switch (self.tokenizer.token) {
.symbol_comma => continue,
.symbol_paren_right => break,
else => return self.report("expected `,` or closing `)` after identifier in argument list"),
}
}
self.tokenizer.skip_newlines();
if (self.tokenizer.token != .symbol_colon) {
return self.report("expected `:` after closing `)` of lambda block argument list");
}
self.tokenizer.skip_newlines();
var block_statements = Statement.List.make(allocator);
while (self.tokenizer.token != .keyword_end) {
try block_statements.push_one(try self.parse_statement());
}
self.tokenizer.skip_newlines();
break: parse .{
.lambda_literal = .{
.argument_identifiers = argument_identifiers,
.block_statements = block_statements,
},
};
},
.symbol_brace_left => { .symbol_brace_left => {
var table_literal = Expression.TableLiteral.make(allocator); var table_literal = Expression.TableLiteral.make(allocator);

View File

@ -46,6 +46,7 @@ pub const Token = union(enum) {
keyword_elif, keyword_elif,
keyword_var, keyword_var,
keyword_let, keyword_let,
keyword_lambda,
pub fn text(self: Token) []const coral.io.Byte { pub fn text(self: Token) []const coral.io.Byte {
return switch (self) { return switch (self) {
@ -95,6 +96,7 @@ pub const Token = union(enum) {
.keyword_else => "else", .keyword_else => "else",
.keyword_var => "var", .keyword_var => "var",
.keyword_let => "let", .keyword_let => "let",
.keyword_lambda => "lambda",
}; };
} }
}; };
@ -234,6 +236,12 @@ pub const Tokenizer = struct {
}, },
'l' => { 'l' => {
if (coral.io.ends_with(identifier, "ambda")) {
self.token = .keyword_lambda;
return;
}
if (coral.io.ends_with(identifier, "et")) { if (coral.io.ends_with(identifier, "et")) {
self.token = .keyword_let; self.token = .keyword_let;