Add support for basic lambda syntax
This commit is contained in:
parent
0e26b8acde
commit
f5ed4bbcad
|
@ -1,18 +1,22 @@
|
||||||
|
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
||||||
|
let pr = lambda ():
|
||||||
|
@print("foo")
|
||||||
|
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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -124,17 +125,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);
|
||||||
|
|
||||||
|
try chunk.opcodes.push_one(.{
|
||||||
|
.push_const = try chunk.declare_constant(.{
|
||||||
|
.chunk = lambda_chunk,
|
||||||
|
}),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
.binary_operation => |operation| {
|
.binary_operation => |operation| {
|
||||||
|
@ -374,9 +393,18 @@ pub const RuntimeEnv = struct {
|
||||||
|
|
||||||
fn compile(self: *Chunk, statements: []const ast.Statement) RuntimeError!void {
|
fn compile(self: *Chunk, statements: []const ast.Statement) RuntimeError!void {
|
||||||
var unit = CompilationUnit{};
|
var unit = CompilationUnit{};
|
||||||
|
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 +413,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) {
|
||||||
|
@ -547,14 +575,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 +596,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 +964,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 +1005,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 +1028,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 +1052,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 +1117,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 {
|
||||||
|
@ -1172,6 +1205,14 @@ pub const RuntimeEnv = struct {
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue