Merge pull request 'Rewrite Ona Script implementation' (#43) from kym-rewrite into main
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #43
This commit is contained in:
commit
6ea44f552e
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
"files.trimTrailingWhitespace": true,
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"zig.initialSetupDone": true,
|
||||||
|
|
||||||
"[zig]": {
|
"[zig]": {
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
|
|
||||||
var printer = lambda (pfx):
|
let printer = lambda ():
|
||||||
return lambda (msg):
|
return lambda (msg):
|
||||||
@print("This is a func call")
|
@print("This is a func call")
|
||||||
@print(msg)
|
@print(msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
let pr = printer("This is a func call")
|
let pr = printer()
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
||||||
pr("")
|
pr("test")
|
||||||
|
|
||||||
while i < 5:
|
while i < 5:
|
||||||
pr("hello, world")
|
pr("hello, world")
|
||||||
|
|
|
@ -217,12 +217,12 @@ pub fn all_equals(target: []const Byte, match: Byte) bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocate_copy(allocator: Allocator, source: []const Byte) AllocationError![]Byte {
|
pub fn allocate_copy(comptime Element: type, allocator: Allocator, source: []const Element) AllocationError![]Element {
|
||||||
const allocation = try allocator.actions.reallocate(allocator.context, @returnAddress(), null, source.len);
|
const allocation = try allocator.actions.reallocate(allocator.context, @returnAddress(), null, @sizeOf(Element) * source.len);
|
||||||
|
|
||||||
copy(allocation, source);
|
copy(allocation, bytes_of(source));
|
||||||
|
|
||||||
return allocation;
|
return @as([*]Element, @ptrCast(@alignCast(allocation.ptr)))[0 .. source.len];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocate_many(allocator: Allocator, count: usize, value: anytype) AllocationError![]@TypeOf(value) {
|
pub fn allocate_many(allocator: Allocator, count: usize, value: anytype) AllocationError![]@TypeOf(value) {
|
||||||
|
@ -332,24 +332,6 @@ pub fn djb2_hash(comptime int: std.builtin.Type.Int, target: []const Byte) math.
|
||||||
return hash_code;
|
return hash_code;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ends_with(target: []const Byte, match: []const Byte) bool {
|
|
||||||
if (target.len < match.len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var index = @as(usize, 0);
|
|
||||||
|
|
||||||
while (index < match.len) : (index += 1) {
|
|
||||||
if (target[target.len - (1 + index)] != match[match.len - (1 + index)]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_first(haystack: []const Byte, needle: Byte) ?usize {
|
pub fn find_first(haystack: []const Byte, needle: Byte) ?usize {
|
||||||
for (0 .. haystack.len) |i| {
|
for (0 .. haystack.len) |i| {
|
||||||
if (haystack[i] == needle) {
|
if (haystack[i] == needle) {
|
||||||
|
|
|
@ -16,6 +16,16 @@ pub fn Stack(comptime Value: type) type {
|
||||||
self.values = self.values[0 .. 0];
|
self.values = self.values[0 .. 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn drop(self: *Self, count: usize) bool {
|
||||||
|
if (math.checked_sub(self.values, count)) |updated_count| {
|
||||||
|
self.values = self.values[0 .. updated_count];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn free(self: *Self) void {
|
pub fn free(self: *Self) void {
|
||||||
if (self.capacity == 0) {
|
if (self.capacity == 0) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub const DecimalFormat = struct {
|
||||||
delimiter: []const io.Byte,
|
delimiter: []const io.Byte,
|
||||||
positive_prefix: enum {none, plus, space},
|
positive_prefix: enum {none, plus, space},
|
||||||
|
|
||||||
const default = DecimalFormat{
|
pub const default = DecimalFormat{
|
||||||
.delimiter = "",
|
.delimiter = "",
|
||||||
.positive_prefix = .none,
|
.positive_prefix = .none,
|
||||||
};
|
};
|
||||||
|
@ -113,7 +113,7 @@ pub const DecimalFormat = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print(self: DecimalFormat, writer: io.Writer, value: anytype) PrintError!void {
|
pub fn print(self: DecimalFormat, writer: io.Writer, value: anytype) ?usize {
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
return print_string(writer, switch (self.positive_prefix) {
|
return print_string(writer, switch (self.positive_prefix) {
|
||||||
.none => "0",
|
.none => "0",
|
||||||
|
@ -134,8 +134,8 @@ pub const DecimalFormat = struct {
|
||||||
var decomposable_value = value;
|
var decomposable_value = value;
|
||||||
|
|
||||||
while (decomposable_value != 0) : (buffer_start -= 1) {
|
while (decomposable_value != 0) : (buffer_start -= 1) {
|
||||||
buffer[buffer_start] = @intCast((decomposable_value % radix) + '0');
|
buffer[buffer_start] = @intCast(@mod(decomposable_value, radix) + '0');
|
||||||
decomposable_value = (decomposable_value / radix);
|
decomposable_value = @divTrunc(decomposable_value, radix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,10 +149,33 @@ pub const DecimalFormat = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try print_string(writer, buffer[buffer_start ..]);
|
return print_string(writer, buffer[buffer_start ..]);
|
||||||
},
|
},
|
||||||
|
|
||||||
else => unformattableMessage(ValueType),
|
.Float => |float| {
|
||||||
|
var length = @as(usize, 0);
|
||||||
|
|
||||||
|
if (value < 0) {
|
||||||
|
length += print_string(writer, "-") orelse return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Float = @TypeOf(value);
|
||||||
|
|
||||||
|
const Int = math.Int(.{
|
||||||
|
.bits = float.bits,
|
||||||
|
.signedness = .unsigned,
|
||||||
|
});
|
||||||
|
|
||||||
|
const integer = @as(Int, @intFromFloat(value));
|
||||||
|
|
||||||
|
length += self.print(writer, integer) orelse return null;
|
||||||
|
length += print_string(writer, ".") orelse return null;
|
||||||
|
length += self.print(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100))) orelse return null;
|
||||||
|
|
||||||
|
return length;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError(unformattableMessage(ValueType)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -168,7 +191,7 @@ pub const HexadecimalFormat = struct {
|
||||||
.casing = .lower,
|
.casing = .lower,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn print(self: HexadecimalFormat, writer: io.Writer, value: anytype) PrintError!void {
|
pub fn print(self: HexadecimalFormat, writer: io.Writer, value: anytype) ?usize {
|
||||||
// TODO: Implement.
|
// TODO: Implement.
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = writer;
|
_ = writer;
|
||||||
|
@ -176,19 +199,32 @@ pub const HexadecimalFormat = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const PrintError = error {
|
pub fn alloc_formatted(allocator: io.Allocator, comptime format: []const u8, args: anytype) io.AllocationError![]io.Byte {
|
||||||
PrintFailed,
|
const formatted_len = print_formatted(io.null_writer, format, args);
|
||||||
PrintIncomplete,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn print_string(writer: io.Writer, utf8: []const io.Byte) PrintError!void {
|
debug.assert(formatted_len != null);
|
||||||
if ((writer.invoke(utf8) orelse return error.PrintFailed) != utf8.len) {
|
|
||||||
return error.PrintIncomplete;
|
const allocation = try allocator.reallocate(null, formatted_len.?);
|
||||||
}
|
|
||||||
|
errdefer allocator.deallocate(allocation);
|
||||||
|
|
||||||
|
{
|
||||||
|
var fixed_buffer = io.FixedBuffer{.bytes = allocation};
|
||||||
|
|
||||||
|
debug.assert(print_formatted(fixed_buffer.as_writer(), format, args) == formatted_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments: anytype) PrintError!void {
|
return allocation;
|
||||||
switch (@typeInfo(@TypeOf(arguments))) {
|
}
|
||||||
|
|
||||||
|
pub fn print_string(writer: io.Writer, utf8: []const u8) ?usize {
|
||||||
|
return writer.invoke(utf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) ?usize {
|
||||||
|
var length = @as(usize, 0);
|
||||||
|
|
||||||
|
switch (@typeInfo(@TypeOf(args))) {
|
||||||
.Struct => |arguments_struct| {
|
.Struct => |arguments_struct| {
|
||||||
comptime var arg_index = 0;
|
comptime var arg_index = 0;
|
||||||
comptime var head = 0;
|
comptime var head = 0;
|
||||||
|
@ -204,8 +240,7 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments
|
||||||
|
|
||||||
switch (format[tail]) {
|
switch (format[tail]) {
|
||||||
'{' => {
|
'{' => {
|
||||||
try print_string(writer, format[head .. (tail - 1)]);
|
length += print_string(writer, format[head .. (tail - 1)]) orelse return null;
|
||||||
|
|
||||||
tail += 1;
|
tail += 1;
|
||||||
head = tail;
|
head = tail;
|
||||||
},
|
},
|
||||||
|
@ -215,8 +250,7 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments
|
||||||
@compileError("all format specifiers must be named when using a named struct");
|
@compileError("all format specifiers must be named when using a named struct");
|
||||||
}
|
}
|
||||||
|
|
||||||
try print_string(writer, arguments[arg_index]);
|
length += print_string(writer, args[arg_index]) orelse return null;
|
||||||
|
|
||||||
arg_index += 1;
|
arg_index += 1;
|
||||||
tail += 1;
|
tail += 1;
|
||||||
head = tail;
|
head = tail;
|
||||||
|
@ -227,8 +261,7 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments
|
||||||
@compileError("format specifiers cannot be named when using a tuple struct");
|
@compileError("format specifiers cannot be named when using a tuple struct");
|
||||||
}
|
}
|
||||||
|
|
||||||
try print_string(writer, format[head .. (tail - 1)]);
|
length += print_string(writer, format[head .. (tail - 1)]) orelse return null;
|
||||||
|
|
||||||
head = tail;
|
head = tail;
|
||||||
tail += 1;
|
tail += 1;
|
||||||
|
|
||||||
|
@ -244,21 +277,24 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments
|
||||||
debug.assert(tail < format.len);
|
debug.assert(tail < format.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
try print_value(writer, @field(arguments, format[head .. tail]));
|
length += print_value(writer, @field(args, format[head .. tail])) orelse return null;
|
||||||
|
|
||||||
tail += 1;
|
tail += 1;
|
||||||
head = tail;
|
head = tail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
length += print_string(writer, format[head .. ]) orelse return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
else => @compileError("`arguments` must be a struct type"),
|
else => @compileError("`arguments` must be a struct type"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
noinline fn print_value(writer: io.Writer, value: anytype) PrintError!void {
|
noinline fn print_value(writer: io.Writer, value: anytype) ?usize {
|
||||||
const Value = @TypeOf(value);
|
const Value = @TypeOf(value);
|
||||||
|
|
||||||
return switch (@typeInfo(Value)) {
|
return switch (@typeInfo(Value)) {
|
||||||
|
|
|
@ -61,88 +61,86 @@ const AllocationNode = struct {
|
||||||
const Context = struct {
|
const Context = struct {
|
||||||
head: ?*AllocationNode = null,
|
head: ?*AllocationNode = null,
|
||||||
|
|
||||||
fn deallocate(self: *Context, allocation: []u8) void {
|
fn deallocate(_: *Context, allocation: []coral.io.Byte) void {
|
||||||
switch (builtin.mode) {
|
// switch (builtin.mode) {
|
||||||
.Debug, .ReleaseSafe => {
|
// .Debug, .ReleaseSafe => {
|
||||||
const panic_message = "incorrect allocation address for deallocating";
|
// const panic_message = "incorrect allocation address for deallocating";
|
||||||
var current_node = self.head orelse @panic(panic_message);
|
// var current_node = self.head orelse @panic(panic_message);
|
||||||
|
|
||||||
if (current_node.owns_userdata(allocation)) {
|
// if (current_node.owns_userdata(allocation)) {
|
||||||
self.head = current_node.next;
|
// self.head = current_node.next;
|
||||||
|
|
||||||
return current_node.dealloc();
|
// return current_node.dealloc();
|
||||||
}
|
// }
|
||||||
|
|
||||||
while (true) {
|
// while (true) {
|
||||||
const next_node = current_node.next orelse @panic(panic_message);
|
// const next_node = current_node.next orelse @panic(panic_message);
|
||||||
|
|
||||||
if (next_node.owns_userdata(allocation)) {
|
// if (next_node.owns_userdata(allocation)) {
|
||||||
current_node.next = next_node.next;
|
// current_node.next = next_node.next;
|
||||||
|
|
||||||
return next_node.dealloc();
|
// return next_node.dealloc();
|
||||||
}
|
// }
|
||||||
|
|
||||||
current_node = next_node;
|
// current_node = next_node;
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
|
|
||||||
.ReleaseFast, .ReleaseSmall => {
|
// .ReleaseFast, .ReleaseSmall => {
|
||||||
ext.SDL_free(allocation.ptr);
|
ext.SDL_free(allocation.ptr);
|
||||||
},
|
// },
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reallocate(self: *Context, return_address: usize, existing_allocation: ?[]u8, size: usize) coral.io.AllocationError![]u8 {
|
fn reallocate(_: *Context, _: usize, existing_allocation: ?[]coral.io.Byte, size: usize) coral.io.AllocationError![]coral.io.Byte {
|
||||||
switch (builtin.mode) {
|
// switch (builtin.mode) {
|
||||||
.Debug, .ReleaseSafe => {
|
// .Debug, .ReleaseSafe => {
|
||||||
|
// if (existing_allocation) |allocation| {
|
||||||
|
// const panic_message = "incorrect allocation address for reallocating";
|
||||||
|
// var current_node = self.head orelse @panic(panic_message);
|
||||||
|
|
||||||
|
// if (current_node.owns_userdata(allocation)) {
|
||||||
|
// const node = current_node.realloc(size, return_address);
|
||||||
|
|
||||||
|
// self.head = node;
|
||||||
|
|
||||||
|
// return node.userdata();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// while (true) {
|
||||||
|
// const next_node = current_node.next orelse @panic(panic_message);
|
||||||
|
|
||||||
|
// if (next_node.owns_userdata(allocation)) {
|
||||||
|
// const node = next_node.realloc(size, return_address);
|
||||||
|
|
||||||
|
// current_node.next = node;
|
||||||
|
|
||||||
|
// return node.userdata();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// current_node = next_node;
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// const node = AllocationNode.alloc(size, return_address);
|
||||||
|
|
||||||
|
// if (self.head) |head| {
|
||||||
|
// node.next = head;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// self.head = node;
|
||||||
|
|
||||||
|
// return node.userdata();
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
|
||||||
|
// .ReleaseFast, .ReleaseSmall => {
|
||||||
if (existing_allocation) |allocation | {
|
if (existing_allocation) |allocation | {
|
||||||
const panic_message = "incorrect allocation address for reallocating";
|
return @as([*]coral.io.Byte, @ptrCast(ext.SDL_realloc(allocation.ptr, size) orelse return error.OutOfMemory))[0 .. size];
|
||||||
var current_node = self.head orelse @panic(panic_message);
|
|
||||||
|
|
||||||
if (current_node.owns_userdata(allocation)) {
|
|
||||||
const node = current_node.realloc(size, return_address);
|
|
||||||
|
|
||||||
self.head = node;
|
|
||||||
|
|
||||||
return node.userdata();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
return @as([*]u8, @ptrCast(ext.SDL_malloc(size) orelse return error.OutOfMemory))[0 .. size];
|
||||||
const next_node = current_node.next orelse @panic(panic_message);
|
// },
|
||||||
|
// }
|
||||||
if (next_node.owns_userdata(allocation)) {
|
|
||||||
const node = next_node.realloc(size, return_address);
|
|
||||||
|
|
||||||
current_node.next = node;
|
|
||||||
|
|
||||||
return node.userdata();
|
|
||||||
}
|
|
||||||
|
|
||||||
current_node = next_node;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const node = AllocationNode.alloc(size, return_address);
|
|
||||||
|
|
||||||
if (self.head) |head| {
|
|
||||||
node.next = head;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.head = node;
|
|
||||||
|
|
||||||
return node.userdata();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.ReleaseFast, .ReleaseSmall => {
|
|
||||||
if (existing_allocation) |allocation | {
|
|
||||||
return @as([*]u8, ext.SDL_realloc(allocation.ptr, size) orelse {
|
|
||||||
return error.OutOfMemory;
|
|
||||||
})[0 .. size];
|
|
||||||
}
|
|
||||||
|
|
||||||
return @as([*]u8, ext.SDL_malloc(size) orelse return error.OutOfMemory)[0 .. size];
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
1502
source/ona/kym.zig
1502
source/ona/kym.zig
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,570 @@
|
||||||
|
const Compiler = @import("./Compiler.zig");
|
||||||
|
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const file = @import("../file.zig");
|
||||||
|
|
||||||
|
const kym = @import("../kym.zig");
|
||||||
|
|
||||||
|
const tree = @import("./tree.zig");
|
||||||
|
|
||||||
|
name: *kym.RuntimeRef,
|
||||||
|
arity: u8,
|
||||||
|
opcodes: OpcodeList,
|
||||||
|
constants: ConstList,
|
||||||
|
|
||||||
|
const Builtin = enum {
|
||||||
|
import,
|
||||||
|
print,
|
||||||
|
vec2,
|
||||||
|
vec3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConstList = coral.list.Stack(*kym.RuntimeRef);
|
||||||
|
|
||||||
|
const OpcodeList = coral.list.Stack(union (enum) {
|
||||||
|
pop,
|
||||||
|
push_nil,
|
||||||
|
push_true,
|
||||||
|
push_false,
|
||||||
|
push_const: u16,
|
||||||
|
push_local: u8,
|
||||||
|
push_top,
|
||||||
|
push_table: u32,
|
||||||
|
push_builtin: Builtin,
|
||||||
|
push_closure: u8,
|
||||||
|
push_self,
|
||||||
|
push_boxed,
|
||||||
|
set_local: u8,
|
||||||
|
get_dynamic,
|
||||||
|
set_dynamic,
|
||||||
|
call: u8,
|
||||||
|
|
||||||
|
not,
|
||||||
|
neg,
|
||||||
|
|
||||||
|
add,
|
||||||
|
sub,
|
||||||
|
mul,
|
||||||
|
div,
|
||||||
|
|
||||||
|
eql,
|
||||||
|
cgt,
|
||||||
|
clt,
|
||||||
|
cge,
|
||||||
|
cle,
|
||||||
|
|
||||||
|
jt: u32,
|
||||||
|
jf: u32,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeRef {
|
||||||
|
var opcode_cursor = @as(u32, 0);
|
||||||
|
var buffer = coral.list.ByteStack.make(env.allocator);
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) {
|
||||||
|
_ = coral.utf8.print_formatted(writer, "[{instruction}]: ", .{.instruction = opcode_cursor});
|
||||||
|
|
||||||
|
_ = switch (chunk.opcodes.values[opcode_cursor]) {
|
||||||
|
.pop => coral.utf8.print_string(writer, "pop\n"),
|
||||||
|
.push_nil => coral.utf8.print_string(writer, "push nil\n"),
|
||||||
|
.push_true => coral.utf8.print_string(writer, "push true\n"),
|
||||||
|
.push_false => coral.utf8.print_string(writer, "push false\n"),
|
||||||
|
|
||||||
|
.push_const => |push_const| print: {
|
||||||
|
if (push_const >= chunk.constants.values.len) {
|
||||||
|
return env.raise(error.IllegalState, "invalid constant");
|
||||||
|
}
|
||||||
|
|
||||||
|
const string_ref = try env.to_string(chunk.constants.values[push_const]);
|
||||||
|
|
||||||
|
defer env.discard(string_ref);
|
||||||
|
|
||||||
|
const string = string_ref.as_string();
|
||||||
|
|
||||||
|
coral.debug.assert(string != null);
|
||||||
|
|
||||||
|
break: print coral.utf8.print_formatted(writer, "push const <{value}>\n", .{.value = string.?});
|
||||||
|
},
|
||||||
|
|
||||||
|
.push_local => |push_local| coral.utf8.print_formatted(writer, "push local <{local}>\n", .{
|
||||||
|
.local = push_local,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.push_top => coral.utf8.print_string(writer, "push top\n"),
|
||||||
|
|
||||||
|
.push_table => |push_table| coral.utf8.print_formatted(writer, "push table <{count}>\n", .{
|
||||||
|
.count = push_table,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.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_builtin => |push_builtin| coral.utf8.print_formatted(writer, "push builtin <{builtin}>\n", .{
|
||||||
|
.builtin = switch (push_builtin) {
|
||||||
|
.import => "import",
|
||||||
|
.print => "print",
|
||||||
|
.vec2 => "vec2",
|
||||||
|
.vec3 => "vec3",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
.set_local => |local_set| coral.utf8.print_formatted(writer, "set local <{local}>\n", .{.local = local_set}),
|
||||||
|
.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}),
|
||||||
|
.not => coral.utf8.print_string(writer, "not\n"),
|
||||||
|
.neg => coral.utf8.print_string(writer, "neg\n"),
|
||||||
|
.add => coral.utf8.print_string(writer, "add\n"),
|
||||||
|
.sub => coral.utf8.print_string(writer, "sub\n"),
|
||||||
|
.mul => coral.utf8.print_string(writer, "mul\n"),
|
||||||
|
.div => coral.utf8.print_string(writer, "div\n"),
|
||||||
|
.eql => coral.utf8.print_string(writer, "eql\n"),
|
||||||
|
.cgt => coral.utf8.print_string(writer, "cgt\n"),
|
||||||
|
.clt => coral.utf8.print_string(writer, "clt\n"),
|
||||||
|
.cge => coral.utf8.print_string(writer, "cge\n"),
|
||||||
|
.cle => coral.utf8.print_string(writer, "cle\n"),
|
||||||
|
.jf => |jf| coral.utf8.print_formatted(writer, "jf <{instruction}>\n", .{.instruction = jf}),
|
||||||
|
.jt => |jt| coral.utf8.print_formatted(writer, "jt <{instruction}>\n", .{.instruction = jt}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.new_string(buffer.values);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(chunk: 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]) {
|
||||||
|
.pop => {
|
||||||
|
if (try env.pop_local()) |ref| {
|
||||||
|
env.discard(ref);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.push_nil => try env.locals.push_one(null),
|
||||||
|
.push_true => try env.locals.push_one(try env.new_boolean(true)),
|
||||||
|
.push_false => try env.locals.push_one(try env.new_boolean(false)),
|
||||||
|
|
||||||
|
.push_const => |push_const| {
|
||||||
|
if (push_const >= chunk.constants.values.len) {
|
||||||
|
return env.raise(error.IllegalState, "invalid constant");
|
||||||
|
}
|
||||||
|
|
||||||
|
try env.locals.push_one(chunk.constants.values[push_const].acquire());
|
||||||
|
},
|
||||||
|
|
||||||
|
.push_local => |push_local| {
|
||||||
|
if (push_local >= env.locals.values.len) {
|
||||||
|
return env.raise(error.IllegalState, "invalid local");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env.locals.values[frame.locals_top + push_local]) |local| {
|
||||||
|
try env.locals.push_one(local.acquire());
|
||||||
|
} else {
|
||||||
|
try env.locals.push_one(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.push_top => {
|
||||||
|
const frame_locals = env.locals.values[frame.locals_top ..];
|
||||||
|
|
||||||
|
if (frame_locals.len == 0) {
|
||||||
|
return env.raise(error.IllegalState, "stack overflow");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame_locals[frame_locals.len - 1]) |local| {
|
||||||
|
try env.locals.push_one(local.acquire());
|
||||||
|
} else {
|
||||||
|
try env.locals.push_one(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.push_table => |push_table| {
|
||||||
|
const table = try env.new_table();
|
||||||
|
|
||||||
|
errdefer env.discard(table);
|
||||||
|
|
||||||
|
{
|
||||||
|
var popped = @as(usize, 0);
|
||||||
|
|
||||||
|
while (popped < push_table) : (popped += 1) {
|
||||||
|
const index = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(index);
|
||||||
|
|
||||||
|
if (try env.pop_local()) |value| {
|
||||||
|
defer env.discard(value);
|
||||||
|
|
||||||
|
try env.set(table, index, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try env.locals.push_one(table);
|
||||||
|
},
|
||||||
|
|
||||||
|
.push_boxed => {
|
||||||
|
// TODO: Implement.
|
||||||
|
unreachable;
|
||||||
|
},
|
||||||
|
|
||||||
|
.push_self => {
|
||||||
|
// TODO: Implement.
|
||||||
|
unreachable;
|
||||||
|
},
|
||||||
|
|
||||||
|
.push_closure => |push_closure| {
|
||||||
|
// TODO: Implement.
|
||||||
|
_ = push_closure;
|
||||||
|
unreachable;
|
||||||
|
},
|
||||||
|
|
||||||
|
.push_builtin => |push_builtin| {
|
||||||
|
const builtin_syscall = try env.new_syscall(switch (push_builtin) {
|
||||||
|
.import => syscall_import,
|
||||||
|
.print => syscall_print,
|
||||||
|
.vec2 => syscall_vec2,
|
||||||
|
.vec3 => syscall_vec3,
|
||||||
|
});
|
||||||
|
|
||||||
|
errdefer env.discard(builtin_syscall);
|
||||||
|
|
||||||
|
try env.locals.push_one(builtin_syscall);
|
||||||
|
},
|
||||||
|
|
||||||
|
.set_local => |local_set| {
|
||||||
|
const local = &env.locals.values[frame.locals_top + local_set];
|
||||||
|
|
||||||
|
if (local.*) |previous_local| {
|
||||||
|
env.discard(previous_local);
|
||||||
|
}
|
||||||
|
|
||||||
|
local.* = try env.pop_local();
|
||||||
|
},
|
||||||
|
|
||||||
|
.get_dynamic => {
|
||||||
|
const index = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(index);
|
||||||
|
|
||||||
|
const indexable = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(indexable);
|
||||||
|
|
||||||
|
const value = try env.get(indexable, index);
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
if (value) |ref| {
|
||||||
|
env.discard(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try env.locals.push_one(value);
|
||||||
|
},
|
||||||
|
|
||||||
|
.set_dynamic => {
|
||||||
|
const value = try env.pop_local();
|
||||||
|
|
||||||
|
defer {
|
||||||
|
if (value) |ref| {
|
||||||
|
env.discard(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = try env.pop_local() orelse {
|
||||||
|
return env.raise(error.TypeMismatch, "nil is not a valid index");
|
||||||
|
};
|
||||||
|
|
||||||
|
defer env.discard(index);
|
||||||
|
|
||||||
|
const indexable = try env.pop_local() orelse {
|
||||||
|
return env.raise(error.TypeMismatch, "nil is not a valid indexable");
|
||||||
|
};
|
||||||
|
|
||||||
|
defer env.discard(indexable);
|
||||||
|
|
||||||
|
try env.set(indexable, index, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
.call => |call| {
|
||||||
|
const result = call: {
|
||||||
|
const callable = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(callable);
|
||||||
|
|
||||||
|
const call_frame = try env.push_frame(call);
|
||||||
|
|
||||||
|
defer env.pop_frame();
|
||||||
|
|
||||||
|
break: call try env.call_frame(callable, call_frame);
|
||||||
|
};
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
if (result) |ref| {
|
||||||
|
env.discard(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try env.locals.push_one(result);
|
||||||
|
},
|
||||||
|
|
||||||
|
.not => {
|
||||||
|
if (try env.pop_local()) |value| {
|
||||||
|
defer env.discard(value);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.new_boolean(!value.is_truthy()));
|
||||||
|
} else {
|
||||||
|
try env.locals.push_one(try env.new_boolean(true));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.neg => {
|
||||||
|
const value = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(value);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.neg(value));
|
||||||
|
},
|
||||||
|
|
||||||
|
.add => {
|
||||||
|
const rhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(rhs);
|
||||||
|
|
||||||
|
const lhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(lhs);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.add(lhs, rhs));
|
||||||
|
},
|
||||||
|
|
||||||
|
.sub => {
|
||||||
|
const rhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(rhs);
|
||||||
|
|
||||||
|
const lhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(lhs);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.sub(lhs, rhs));
|
||||||
|
},
|
||||||
|
|
||||||
|
.mul => {
|
||||||
|
const rhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(rhs);
|
||||||
|
|
||||||
|
const lhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(lhs);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.mul(lhs, rhs));
|
||||||
|
},
|
||||||
|
|
||||||
|
.div => {
|
||||||
|
const rhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(rhs);
|
||||||
|
|
||||||
|
const lhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(lhs);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.div(lhs, rhs));
|
||||||
|
},
|
||||||
|
|
||||||
|
.eql => {
|
||||||
|
if (try env.pop_local()) |rhs| {
|
||||||
|
env.discard(rhs);
|
||||||
|
|
||||||
|
if (try env.pop_local()) |lhs| {
|
||||||
|
env.discard(lhs);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.new_boolean(lhs.equals(rhs)));
|
||||||
|
} else {
|
||||||
|
try env.locals.push_one(try env.new_boolean(false));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (try env.pop_local()) |lhs| {
|
||||||
|
env.discard(lhs);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.new_boolean(false));
|
||||||
|
} else {
|
||||||
|
try env.locals.push_one(try env.new_boolean(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.cgt => {
|
||||||
|
const rhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(rhs);
|
||||||
|
|
||||||
|
const lhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(lhs);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) > 0));
|
||||||
|
},
|
||||||
|
|
||||||
|
.clt => {
|
||||||
|
const rhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(rhs);
|
||||||
|
|
||||||
|
const lhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(lhs);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) < 0));
|
||||||
|
},
|
||||||
|
|
||||||
|
.cge => {
|
||||||
|
const rhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(rhs);
|
||||||
|
|
||||||
|
const lhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(lhs);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) >= 0));
|
||||||
|
},
|
||||||
|
|
||||||
|
.cle => {
|
||||||
|
const rhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(rhs);
|
||||||
|
|
||||||
|
const lhs = try env.expect(try env.pop_local());
|
||||||
|
|
||||||
|
defer env.discard(lhs);
|
||||||
|
|
||||||
|
try env.locals.push_one(try env.new_boolean(try env.compare(lhs, rhs) <= 0));
|
||||||
|
},
|
||||||
|
|
||||||
|
.jf => |jf| {
|
||||||
|
if (try env.pop_local()) |condition| {
|
||||||
|
defer env.discard(condition);
|
||||||
|
|
||||||
|
if (!condition.is_truthy()) {
|
||||||
|
opcode_cursor = jf;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opcode_cursor = jf;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.jt => |jt| {
|
||||||
|
if (try env.pop_local()) |condition| {
|
||||||
|
defer env.discard(condition);
|
||||||
|
|
||||||
|
if (condition.is_truthy()) {
|
||||||
|
opcode_cursor = jt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.pop_local();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free(self: *Self, env: *kym.RuntimeEnv) void {
|
||||||
|
while (self.constants.pop()) |constant| {
|
||||||
|
env.discard(constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.constants.free();
|
||||||
|
self.opcodes.free();
|
||||||
|
env.discard(self.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) kym.RuntimeError!Self {
|
||||||
|
var chunk = Self{
|
||||||
|
.name = try env.new_symbol(name),
|
||||||
|
.opcodes = OpcodeList.make(env.allocator),
|
||||||
|
.constants = ConstList.make(env.allocator),
|
||||||
|
.arity = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
var compiler = Compiler{
|
||||||
|
.chunk = &chunk,
|
||||||
|
.env = env,
|
||||||
|
};
|
||||||
|
|
||||||
|
try compiler.compile_environment(environment);
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syscall_import(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
|
||||||
|
return env.import(file.Path.from(&.{try env.unbox_string(try frame.get_arg(env, 0))}));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syscall_print(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
|
||||||
|
env.print(try env.unbox_string(try frame.get_arg(env, 0)));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syscall_vec2(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
|
||||||
|
const x = @as(f32, @floatCast(try env.unbox_float(try frame.get_arg(env, 0))));
|
||||||
|
|
||||||
|
if (frame.has_arg(env, 1)) |y| {
|
||||||
|
return env.new_vector2(x, @floatCast(try env.unbox_float(y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.new_vector2(x, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syscall_vec3(env: *kym.RuntimeEnv, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
|
||||||
|
const x = @as(f32, @floatCast(try env.unbox_float(try frame.get_arg(env, 0))));
|
||||||
|
|
||||||
|
if (frame.has_arg(env, 1)) |y| {
|
||||||
|
return env.new_vector3(
|
||||||
|
x,
|
||||||
|
@floatCast(try env.unbox_float(y)),
|
||||||
|
@floatCast(try env.unbox_float(try frame.get_arg(env, 2))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return env.new_vector3(x, x, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const typeinfo = &kym.Typeinfo{
|
||||||
|
.size = @sizeOf(Self),
|
||||||
|
.name = "lambda",
|
||||||
|
.destruct = typeinfo_destruct,
|
||||||
|
.call = typeinfo_call,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn typeinfo_call(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, frame: kym.Frame) kym.RuntimeError!?*kym.RuntimeRef {
|
||||||
|
const chunk = @as(*Self, @ptrCast(@alignCast(userdata)));
|
||||||
|
|
||||||
|
if (frame.arg_count < chunk.arity) {
|
||||||
|
return env.raise(error.BadOperation, "expected more arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunk.execute(env, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typeinfo_destruct(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void {
|
||||||
|
@as(*Self, @ptrCast(@alignCast(userdata))).free(env);
|
||||||
|
}
|
|
@ -0,0 +1,370 @@
|
||||||
|
const Chunk = @import("./Chunk.zig");
|
||||||
|
|
||||||
|
const Expr = @import("./Expr.zig");
|
||||||
|
|
||||||
|
const Stmt = @import("./Stmt.zig");
|
||||||
|
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const kym = @import("../kym.zig");
|
||||||
|
|
||||||
|
const tree = @import("./tree.zig");
|
||||||
|
|
||||||
|
chunk: *Chunk,
|
||||||
|
env: *kym.RuntimeEnv,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
fn compile_argument(self: Self, environment: *const tree.Environment, initial_argument: ?*const Expr) kym.RuntimeError!u8 {
|
||||||
|
// TODO: Exceeding 255 arguments will make the VM crash.
|
||||||
|
var maybe_argument = initial_argument;
|
||||||
|
var argument_count = @as(u8, 0);
|
||||||
|
|
||||||
|
while (maybe_argument) |argument| {
|
||||||
|
try self.compile_expression(environment, argument);
|
||||||
|
|
||||||
|
maybe_argument = argument.next;
|
||||||
|
argument_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return argument_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_expression(self: Self, environment: *const tree.Environment, expression: *const Expr) kym.RuntimeError!void {
|
||||||
|
const number_format = coral.utf8.DecimalFormat{
|
||||||
|
.delimiter = "_",
|
||||||
|
.positive_prefix = .none,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (expression.kind) {
|
||||||
|
.nil_literal => try self.chunk.opcodes.push_one(.push_nil),
|
||||||
|
.true_literal => try self.chunk.opcodes.push_one(.push_true),
|
||||||
|
.false_literal => try self.chunk.opcodes.push_one(.push_false),
|
||||||
|
|
||||||
|
.number_literal => |literal| {
|
||||||
|
for (literal) |codepoint| {
|
||||||
|
if (codepoint == '.') {
|
||||||
|
return self.chunk.opcodes.push_one(.{
|
||||||
|
.push_const = try self.declare_float(number_format.parse(literal, kym.Float) orelse unreachable),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.chunk.opcodes.push_one(.{
|
||||||
|
.push_const = try self.declare_fixed(number_format.parse(literal, kym.Fixed) orelse unreachable),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.string_literal => |literal| {
|
||||||
|
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_string(literal)});
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_literal => |literal| {
|
||||||
|
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(literal)});
|
||||||
|
},
|
||||||
|
|
||||||
|
.table_construct => |table_construct| {
|
||||||
|
var table_entry = table_construct.entry;
|
||||||
|
var field_count = @as(u32, 0);
|
||||||
|
|
||||||
|
while (table_entry) |entry| : (table_entry = entry.next) {
|
||||||
|
try self.compile_expression(environment, entry);
|
||||||
|
|
||||||
|
if (entry.kind != .key_value) {
|
||||||
|
try self.chunk.opcodes.push_one(.push_top);
|
||||||
|
}
|
||||||
|
|
||||||
|
field_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.chunk.opcodes.push_one(.{.push_table = field_count});
|
||||||
|
},
|
||||||
|
|
||||||
|
.key_value => |key_value| {
|
||||||
|
try self.compile_expression(environment, key_value.value);
|
||||||
|
try self.compile_expression(environment, key_value.key);
|
||||||
|
},
|
||||||
|
|
||||||
|
.lambda_construct => |lambda_construct| {
|
||||||
|
var chunk = try Chunk.make(self.env, "<lambda>", lambda_construct.environment);
|
||||||
|
|
||||||
|
errdefer chunk.free(self.env);
|
||||||
|
|
||||||
|
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| {
|
||||||
|
try self.chunk.opcodes.push_one(.{.push_local = environment.captures[capture]});
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.chunk.opcodes.push_one(.{.push_closure = lambda_construct.environment.capture_count});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.binary_op => |binary_op| {
|
||||||
|
try self.compile_expression(environment, binary_op.lhs_operand);
|
||||||
|
try self.compile_expression(environment, binary_op.rhs_operand);
|
||||||
|
|
||||||
|
try self.chunk.opcodes.push_one(switch (binary_op.operation) {
|
||||||
|
.addition => .add,
|
||||||
|
.subtraction => .sub,
|
||||||
|
.multiplication => .mul,
|
||||||
|
.divsion => .div,
|
||||||
|
.greater_equals_comparison => .cge,
|
||||||
|
.greater_than_comparison => .cgt,
|
||||||
|
.equals_comparison => .eql,
|
||||||
|
.less_than_comparison => .clt,
|
||||||
|
.less_equals_comparison => .cle,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.unary_op => |unary_op| {
|
||||||
|
try self.compile_expression(environment, unary_op.operand);
|
||||||
|
|
||||||
|
try self.chunk.opcodes.push_one(switch (unary_op.operation) {
|
||||||
|
.boolean_negation => .not,
|
||||||
|
.numeric_negation => .neg,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.invoke => |invoke| {
|
||||||
|
const argument_count = try self.compile_argument(environment, invoke.argument);
|
||||||
|
|
||||||
|
try self.compile_expression(environment, invoke.object);
|
||||||
|
try self.chunk.opcodes.push_one(.{.call = argument_count});
|
||||||
|
},
|
||||||
|
|
||||||
|
.group => |group| try self.compile_expression(environment, group),
|
||||||
|
.import_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .import}),
|
||||||
|
.print_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .print}),
|
||||||
|
.vec2_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .vec2}),
|
||||||
|
.vec3_builtin => try self.chunk.opcodes.push_one(.{.push_builtin = .vec3}),
|
||||||
|
|
||||||
|
.declaration_get => |declaration_get| {
|
||||||
|
if (get_local_index(environment, declaration_get.declaration)) |index| {
|
||||||
|
return self.chunk.opcodes.push_one(.{.push_local = index});
|
||||||
|
}
|
||||||
|
|
||||||
|
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)});
|
||||||
|
|
||||||
|
return self.chunk.opcodes.push_one(.get_dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.env.raise(error.IllegalState, "local out of scope");
|
||||||
|
},
|
||||||
|
|
||||||
|
.declaration_set => |declaration_set| {
|
||||||
|
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)});
|
||||||
|
|
||||||
|
return self.chunk.opcodes.push_one(.set_dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.env.raise(error.IllegalState, "local out of scope");
|
||||||
|
},
|
||||||
|
|
||||||
|
.field_get => |field_get| {
|
||||||
|
try self.compile_expression(environment, field_get.object);
|
||||||
|
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(field_get.identifier)});
|
||||||
|
try self.chunk.opcodes.push_one(.get_dynamic);
|
||||||
|
},
|
||||||
|
|
||||||
|
.field_set => |field_set| {
|
||||||
|
try self.compile_expression(environment, field_set.object);
|
||||||
|
try self.chunk.opcodes.push_one(.{.push_const = try self.declare_symbol(field_set.identifier)});
|
||||||
|
try self.compile_expression(environment, field_set.assign);
|
||||||
|
try self.chunk.opcodes.push_one(.set_dynamic);
|
||||||
|
},
|
||||||
|
|
||||||
|
.subscript_get => |subscript_get| {
|
||||||
|
try self.compile_expression(environment, subscript_get.object);
|
||||||
|
try self.compile_expression(environment, subscript_get.index);
|
||||||
|
try self.chunk.opcodes.push_one(.get_dynamic);
|
||||||
|
},
|
||||||
|
|
||||||
|
.subscript_set => |subscript_set| {
|
||||||
|
try self.compile_expression(environment, subscript_set.object);
|
||||||
|
try self.compile_expression(environment, subscript_set.index);
|
||||||
|
try self.compile_expression(environment, subscript_set.assign);
|
||||||
|
try self.chunk.opcodes.push_one(.set_dynamic);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compile_environment(self: Self, environment: *const tree.Environment) kym.RuntimeError!void {
|
||||||
|
if (environment.statement) |statement| {
|
||||||
|
const last_statement = try self.compile_statement(environment, statement);
|
||||||
|
|
||||||
|
if (last_statement.kind != .@"return") {
|
||||||
|
try self.chunk.opcodes.push_one(.push_nil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_statement(self: Self, environment: *const tree.Environment, initial_statement: *const Stmt) kym.RuntimeError!*const Stmt {
|
||||||
|
var current_statement = initial_statement;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (current_statement.kind) {
|
||||||
|
.@"return" => |@"return"| {
|
||||||
|
if (@"return".returned_expression) |expression| {
|
||||||
|
try self.compile_expression(environment, expression);
|
||||||
|
} else {
|
||||||
|
try self.chunk.opcodes.push_one(.push_nil);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.@"while" => |@"while"| {
|
||||||
|
try self.compile_expression(environment, @"while".loop_expression);
|
||||||
|
try self.chunk.opcodes.push_one(.{.jf = 0});
|
||||||
|
|
||||||
|
const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1));
|
||||||
|
|
||||||
|
_ = try self.compile_statement(environment, @"while".loop);
|
||||||
|
self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1);
|
||||||
|
|
||||||
|
try self.compile_expression(environment, @"while".loop_expression);
|
||||||
|
try self.chunk.opcodes.push_one(.{.jt = origin_index});
|
||||||
|
},
|
||||||
|
|
||||||
|
.@"if" => |@"if"| {
|
||||||
|
try self.compile_expression(environment, @"if".then_expression);
|
||||||
|
try self.chunk.opcodes.push_one(.{.jf = 0});
|
||||||
|
|
||||||
|
const origin_index = @as(u32, @intCast(self.chunk.opcodes.values.len - 1));
|
||||||
|
|
||||||
|
_ = try self.compile_statement(environment, @"if".@"then");
|
||||||
|
self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1);
|
||||||
|
|
||||||
|
if (@"if".@"else") |@"else"| {
|
||||||
|
_ = try self.compile_statement(environment, @"else");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.declare => |declare| {
|
||||||
|
try self.compile_expression(environment, declare.initial_expression);
|
||||||
|
|
||||||
|
if (declare.declaration.is_captured and !declare.declaration.is_readonly) {
|
||||||
|
try self.chunk.opcodes.push_one(.push_boxed);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.top_expression => |top_expression| {
|
||||||
|
try self.compile_expression(environment, top_expression);
|
||||||
|
|
||||||
|
if (top_expression.kind == .invoke) {
|
||||||
|
try self.chunk.opcodes.push_one(.pop);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
current_statement = current_statement.next orelse return current_statement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declare_chunk(self: Self, chunk: Chunk) kym.RuntimeError!u16 {
|
||||||
|
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
errdefer self.env.discard(constant);
|
||||||
|
|
||||||
|
try self.chunk.constants.push_one(constant);
|
||||||
|
|
||||||
|
return @intCast(self.chunk.constants.values.len - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declare_fixed(self: Self, fixed: kym.Fixed) kym.RuntimeError!u16 {
|
||||||
|
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
|
||||||
|
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants");
|
||||||
|
}
|
||||||
|
|
||||||
|
const constant = try self.env.new_fixed(fixed);
|
||||||
|
|
||||||
|
errdefer self.env.discard(constant);
|
||||||
|
|
||||||
|
try self.chunk.constants.push_one(constant);
|
||||||
|
|
||||||
|
return @intCast(self.chunk.constants.values.len - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declare_float(self: Self, float: kym.Float) kym.RuntimeError!u16 {
|
||||||
|
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
|
||||||
|
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants");
|
||||||
|
}
|
||||||
|
|
||||||
|
const constant = try self.env.new_float(float);
|
||||||
|
|
||||||
|
errdefer self.env.discard(constant);
|
||||||
|
|
||||||
|
try self.chunk.constants.push_one(constant);
|
||||||
|
|
||||||
|
return @intCast(self.chunk.constants.values.len - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declare_string(self: Self, string: []const coral.io.Byte) kym.RuntimeError!u16 {
|
||||||
|
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
|
||||||
|
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants");
|
||||||
|
}
|
||||||
|
|
||||||
|
const constant = try self.env.new_string(string);
|
||||||
|
|
||||||
|
errdefer self.env.discard(constant);
|
||||||
|
|
||||||
|
try self.chunk.constants.push_one(constant);
|
||||||
|
|
||||||
|
return @intCast(self.chunk.constants.values.len - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declare_symbol(self: Self, symbol: []const coral.io.Byte) kym.RuntimeError!u16 {
|
||||||
|
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
|
||||||
|
return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants");
|
||||||
|
}
|
||||||
|
|
||||||
|
const constant = try self.env.new_symbol(symbol);
|
||||||
|
|
||||||
|
errdefer self.env.discard(constant);
|
||||||
|
|
||||||
|
try self.chunk.constants.push_one(constant);
|
||||||
|
|
||||||
|
return @intCast(self.chunk.constants.values.len - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_capture_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
|
||||||
|
if (self.enclosing) |enclosing_environment| {
|
||||||
|
var capture_index = @as(u8, 0);
|
||||||
|
|
||||||
|
while (capture_index < self.capture_count) : (capture_index += 1) {
|
||||||
|
if (&enclosing_environment.local_declarations[capture_index] == declaration) {
|
||||||
|
return capture_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_local_index(self: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
|
||||||
|
var remaining = self.local_declaration_count;
|
||||||
|
|
||||||
|
while (remaining != 0) {
|
||||||
|
remaining -= 1;
|
||||||
|
|
||||||
|
if (&self.local_declarations[remaining] == declaration) {
|
||||||
|
return remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -0,0 +1,620 @@
|
||||||
|
const Stmt = @import("./Stmt.zig");
|
||||||
|
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const tokens = @import("./tokens.zig");
|
||||||
|
|
||||||
|
const tree = @import("./tree.zig");
|
||||||
|
|
||||||
|
next: ?*const Self = null,
|
||||||
|
|
||||||
|
kind: union (enum) {
|
||||||
|
nil_literal,
|
||||||
|
true_literal,
|
||||||
|
false_literal,
|
||||||
|
number_literal: []const coral.io.Byte,
|
||||||
|
string_literal: []const coral.io.Byte,
|
||||||
|
symbol_literal: []const coral.io.Byte,
|
||||||
|
table_construct: TableConstruct,
|
||||||
|
key_value: KeyValue,
|
||||||
|
group: *Self,
|
||||||
|
lambda_construct: LambdaConstruct,
|
||||||
|
declaration_get: DeclarationGet,
|
||||||
|
declaration_set: DeclarationSet,
|
||||||
|
field_get: FieldGet,
|
||||||
|
field_set: FieldSet,
|
||||||
|
subscript_get: SubscriptGet,
|
||||||
|
subscript_set: SubscriptSet,
|
||||||
|
binary_op: BinaryOp,
|
||||||
|
unary_op: UnaryOp,
|
||||||
|
invoke: Invoke,
|
||||||
|
import_builtin,
|
||||||
|
print_builtin,
|
||||||
|
vec2_builtin,
|
||||||
|
vec3_builtin,
|
||||||
|
},
|
||||||
|
|
||||||
|
pub const BinaryOp = struct {
|
||||||
|
rhs_operand: *Self,
|
||||||
|
lhs_operand: *Self,
|
||||||
|
operation: Operation,
|
||||||
|
|
||||||
|
pub const Operation = enum {
|
||||||
|
addition,
|
||||||
|
subtraction,
|
||||||
|
multiplication,
|
||||||
|
divsion,
|
||||||
|
equals_comparison,
|
||||||
|
greater_than_comparison,
|
||||||
|
greater_equals_comparison,
|
||||||
|
less_than_comparison,
|
||||||
|
less_equals_comparison,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn parser(comptime parse_next: Parser, comptime operations: []const BinaryOp.Operation) Parser {
|
||||||
|
const BinaryOpParser = struct {
|
||||||
|
fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
|
||||||
|
var expression = try parse_next(root, stream, environment);
|
||||||
|
|
||||||
|
inline for (operations) |operation| {
|
||||||
|
const token = comptime @as(tokens.Token, switch (operation) {
|
||||||
|
.addition => .symbol_plus,
|
||||||
|
.subtraction => .symbol_minus,
|
||||||
|
.multiplication => .symbol_asterisk,
|
||||||
|
.divsion => .symbol_forward_slash,
|
||||||
|
.equals_comparison => .symbol_double_equals,
|
||||||
|
.greater_than_comparison => .symbol_greater_than,
|
||||||
|
.greater_equals_comparison => .symbol_greater_equals,
|
||||||
|
.less_than_comparison => .symbol_less_than,
|
||||||
|
.less_equals_comparison => .symbol_less_equals,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (stream.token == coral.io.tag_of(token)) {
|
||||||
|
stream.step();
|
||||||
|
|
||||||
|
if (stream.token == .end) {
|
||||||
|
return root.report_error(stream, "expected other half of expression after `" ++ comptime token.text() ++ "`", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove once Zig has fixed struct self-reassignment.
|
||||||
|
const unnecessary_temp = expression;
|
||||||
|
|
||||||
|
expression = try root.create_expr(.{
|
||||||
|
.kind = .{
|
||||||
|
.binary_op = .{
|
||||||
|
.rhs_operand = try parse_next(root, stream, environment),
|
||||||
|
.operation = operation,
|
||||||
|
.lhs_operand = unnecessary_temp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return BinaryOpParser.parse;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DeclarationGet = struct {
|
||||||
|
declaration: *const tree.Declaration,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DeclarationSet = struct {
|
||||||
|
declaration: *const tree.Declaration,
|
||||||
|
assign: *const Self,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FieldGet = struct {
|
||||||
|
identifier: []const coral.io.Byte,
|
||||||
|
object: *const Self,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const FieldSet = struct {
|
||||||
|
identifier: []const coral.io.Byte,
|
||||||
|
object: *const Self,
|
||||||
|
assign: *const Self,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Invoke = struct {
|
||||||
|
argument: ?*const Self,
|
||||||
|
object: *const Self,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const KeyValue = struct {
|
||||||
|
key: *const Self,
|
||||||
|
value: *const Self,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const LambdaConstruct = struct {
|
||||||
|
environment: *const tree.Environment,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Parser = fn (root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self;
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const SubscriptGet = struct {
|
||||||
|
index: *const Self,
|
||||||
|
object: *const Self,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SubscriptSet = struct {
|
||||||
|
index: *const Self,
|
||||||
|
object: *const Self,
|
||||||
|
assign: *const Self,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TableConstruct = struct {
|
||||||
|
entry: ?*const Self,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const UnaryOp = struct {
|
||||||
|
operand: *Self,
|
||||||
|
operation: Operation,
|
||||||
|
|
||||||
|
pub const Operation = enum {
|
||||||
|
numeric_negation,
|
||||||
|
boolean_negation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
|
||||||
|
const expression = try parse_additive(root, stream, environment);
|
||||||
|
|
||||||
|
if (stream.token == .symbol_equals) {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
if (stream.token == .end) {
|
||||||
|
return root.report_error(stream, "expected assignment after `=`", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.create_expr(.{
|
||||||
|
.kind = switch (expression.kind) {
|
||||||
|
.declaration_get => |declaration_get| .{
|
||||||
|
.declaration_set = .{
|
||||||
|
.assign = try parse(root, stream, environment),
|
||||||
|
.declaration = declaration_get.declaration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
.field_get => |field_get| .{
|
||||||
|
.field_set = .{
|
||||||
|
.assign = try parse(root, stream, environment),
|
||||||
|
.object = field_get.object,
|
||||||
|
.identifier = field_get.identifier,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
.subscript_get => |subscript_get| .{
|
||||||
|
.subscript_set = .{
|
||||||
|
.assign = try parse(root, stream, environment),
|
||||||
|
.object = subscript_get.object,
|
||||||
|
.index = subscript_get.index,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return root.report_error(stream, "expected local or field on left-hand side of expression", .{}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parse_additive = BinaryOp.parser(parse_equality, &.{
|
||||||
|
.addition,
|
||||||
|
.subtraction,
|
||||||
|
});
|
||||||
|
|
||||||
|
const parse_comparison = BinaryOp.parser(parse_term, &.{
|
||||||
|
.greater_than_comparison,
|
||||||
|
.greater_equals_comparison,
|
||||||
|
.less_than_comparison,
|
||||||
|
.less_equals_comparison
|
||||||
|
});
|
||||||
|
|
||||||
|
const parse_equality = BinaryOp.parser(parse_comparison, &.{
|
||||||
|
.equals_comparison,
|
||||||
|
});
|
||||||
|
|
||||||
|
fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
|
||||||
|
var expression = try parse_operand(root, stream, environment);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (stream.token) {
|
||||||
|
.symbol_period => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
||||||
|
const unnecessary_temp = expression;
|
||||||
|
|
||||||
|
expression = try root.create_expr(.{
|
||||||
|
.next = null,
|
||||||
|
|
||||||
|
.kind = .{
|
||||||
|
.field_get = .{
|
||||||
|
.identifier = switch (stream.token) {
|
||||||
|
.identifier => |field_identifier| field_identifier,
|
||||||
|
else => return root.report_error(stream, "expected identifier after `.`", .{}),
|
||||||
|
},
|
||||||
|
|
||||||
|
.object = unnecessary_temp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_bracket_left => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
||||||
|
const unnecessary_temp = expression;
|
||||||
|
|
||||||
|
expression = try root.create_expr(.{
|
||||||
|
.next = null,
|
||||||
|
|
||||||
|
.kind = .{
|
||||||
|
.subscript_get = .{
|
||||||
|
.index = try parse(root, stream, environment),
|
||||||
|
.object = unnecessary_temp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (stream.token != .symbol_bracket_right) {
|
||||||
|
return root.report_error(stream, "expected closing `]` on subscript", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_paren_left => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
var first_argument = @as(?*Self, null);
|
||||||
|
|
||||||
|
if (stream.token != .symbol_paren_right) {
|
||||||
|
var argument = try parse(root, stream, environment);
|
||||||
|
|
||||||
|
first_argument = argument;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (stream.token) {
|
||||||
|
.symbol_comma => stream.skip_newlines(),
|
||||||
|
.symbol_paren_right => break,
|
||||||
|
else => return root.report_error(stream, "expected `,` or `)` after lambda argument", .{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const next_argument = try parse(root, stream, environment);
|
||||||
|
|
||||||
|
argument.next = next_argument;
|
||||||
|
argument = next_argument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
||||||
|
const unnecessary_temp = expression;
|
||||||
|
|
||||||
|
expression = try root.create_expr(.{
|
||||||
|
.next = null,
|
||||||
|
|
||||||
|
.kind = .{
|
||||||
|
.invoke = .{
|
||||||
|
.argument = first_argument,
|
||||||
|
.object = unnecessary_temp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
else => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
|
||||||
|
switch (stream.token) {
|
||||||
|
.symbol_paren_left => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
const expression = try parse(root, stream, environment);
|
||||||
|
|
||||||
|
if (stream.token != .symbol_paren_right) {
|
||||||
|
return root.report_error(stream, "expected a closing `)` after expression", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{.kind = .{.group = expression}});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_nil => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{.kind = .nil_literal});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_true => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{.kind = .true_literal});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_false => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{.kind = .false_literal});
|
||||||
|
},
|
||||||
|
|
||||||
|
.number => |value| {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{.kind = .{.number_literal = value}});
|
||||||
|
},
|
||||||
|
|
||||||
|
.string => |value| {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{.kind = .{.string_literal = value}});
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_at => {
|
||||||
|
stream.step();
|
||||||
|
|
||||||
|
const identifier = switch (stream.token) {
|
||||||
|
.identifier => |identifier| identifier,
|
||||||
|
else => return root.report_error(stream, "expected identifier after `@`", .{}),
|
||||||
|
};
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
if (coral.io.are_equal(identifier, "import")) {
|
||||||
|
return root.create_expr(.{.kind = .import_builtin});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coral.io.are_equal(identifier, "print")) {
|
||||||
|
return root.create_expr(.{.kind = .print_builtin});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coral.io.are_equal(identifier, "vec2")) {
|
||||||
|
return root.create_expr(.{.kind = .vec2_builtin});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coral.io.are_equal(identifier, "vec3")) {
|
||||||
|
return root.create_expr(.{.kind = .vec3_builtin});
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.report_error(stream, "unexpected identifier after `@`", .{});
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_period => {
|
||||||
|
stream.step();
|
||||||
|
|
||||||
|
const identifier = switch (stream.token) {
|
||||||
|
.identifier => |identifier| identifier,
|
||||||
|
else => return root.report_error(stream, "expected identifier after `.`", .{}),
|
||||||
|
};
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{.kind = .{.symbol_literal = identifier}});
|
||||||
|
},
|
||||||
|
|
||||||
|
.identifier => |identifier| {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{
|
||||||
|
.next = null,
|
||||||
|
|
||||||
|
.kind = .{
|
||||||
|
.declaration_get = .{
|
||||||
|
.declaration = (try environment.resolve_declaration(identifier)) orelse {
|
||||||
|
return root.report_error(stream, "undefined identifier `{identifier}`", .{
|
||||||
|
.identifier = identifier,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_lambda => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
if (stream.token != .symbol_paren_left) {
|
||||||
|
return root.report_error(stream, "expected `(` after opening lambda block", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
var lambda_environment = try environment.create_enclosed(root);
|
||||||
|
|
||||||
|
while (stream.token != .symbol_paren_right) {
|
||||||
|
const identifier = switch (stream.token) {
|
||||||
|
.identifier => |identifier| identifier,
|
||||||
|
else => return root.report_error(stream, "expected identifier", .{}),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (try lambda_environment.declare(identifier) == null) {
|
||||||
|
return root.report_error(stream, "declaration `{identifier}` already exists", .{
|
||||||
|
.identifier = identifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
lambda_environment.argument_count += 1;
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
switch (stream.token) {
|
||||||
|
.symbol_comma => stream.skip_newlines(),
|
||||||
|
.symbol_paren_right => break,
|
||||||
|
else => return root.report_error(stream, "expected `,` or `)` after identifier", .{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
if (stream.token != .symbol_colon) {
|
||||||
|
return root.report_error(stream, "expected `:` after closing `)` of lambda identifiers", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
if (stream.token != .keyword_end) {
|
||||||
|
const first_statement = try Stmt.parse(root, stream, lambda_environment);
|
||||||
|
var current_statement = first_statement;
|
||||||
|
|
||||||
|
while (stream.token != .keyword_end) {
|
||||||
|
const next_statement = try Stmt.parse(root, stream, lambda_environment);
|
||||||
|
|
||||||
|
current_statement.next = next_statement;
|
||||||
|
current_statement = next_statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
lambda_environment.statement = first_statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{.kind = .{.lambda_construct = .{.environment = lambda_environment}}});
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_brace_left => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
if (stream.token == .symbol_brace_right) {
|
||||||
|
return root.create_expr(.{.kind = .{.table_construct = .{.entry = null}}});
|
||||||
|
}
|
||||||
|
|
||||||
|
const first_entry = try parse_table_entry(root, stream, environment);
|
||||||
|
var entry = first_entry;
|
||||||
|
|
||||||
|
while (stream.token == .symbol_comma) {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
if (stream.token == .symbol_brace_right) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expression = try parse_table_entry(root, stream, environment);
|
||||||
|
|
||||||
|
entry.next = expression;
|
||||||
|
entry = expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream.token != .symbol_brace_right) {
|
||||||
|
return root.report_error(stream, "expected closing `}` on table construct", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{.kind = .{.table_construct = .{.entry = first_entry}}});
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_minus => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{
|
||||||
|
.next = null,
|
||||||
|
|
||||||
|
.kind = .{
|
||||||
|
.unary_op = .{
|
||||||
|
.operand = try parse_factor(root, stream, environment),
|
||||||
|
.operation = .numeric_negation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_bang => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{
|
||||||
|
.next = null,
|
||||||
|
|
||||||
|
.kind = .{
|
||||||
|
.unary_op = .{
|
||||||
|
.operand = try parse_factor(root, stream, environment),
|
||||||
|
.operation = .boolean_negation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return root.report_error(stream, "unexpected token in expression", .{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_table_entry(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
|
||||||
|
switch (stream.token) {
|
||||||
|
.symbol_period => {
|
||||||
|
stream.step();
|
||||||
|
|
||||||
|
const field = switch (stream.token) {
|
||||||
|
.identifier => |identifier| identifier,
|
||||||
|
else => return root.report_error(stream, "expected identifier in field symbol literal", .{}),
|
||||||
|
};
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
if (stream.token != .symbol_equals) {
|
||||||
|
return root.report_error(stream, "expected `=` after table symbol key", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{
|
||||||
|
.kind = .{
|
||||||
|
.key_value = .{
|
||||||
|
.value = try parse(root, stream, environment),
|
||||||
|
.key = try root.create_expr(.{.kind = .{.symbol_literal = field}}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_bracket_left => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
const key = try parse(root, stream, environment);
|
||||||
|
|
||||||
|
if (stream.token != .symbol_bracket_right) {
|
||||||
|
return root.report_error(stream, "expected `]` after subscript index expression", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
if (stream.token != .symbol_equals) {
|
||||||
|
return root.report_error(stream, "expected `=` after table expression key", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_expr(.{
|
||||||
|
.kind = .{
|
||||||
|
.key_value = .{
|
||||||
|
.value = try parse(root, stream, environment),
|
||||||
|
.key = key,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return parse(root, stream, environment),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parse_term = BinaryOp.parser(parse_factor, &.{
|
||||||
|
.multiplication,
|
||||||
|
.divsion,
|
||||||
|
});
|
|
@ -0,0 +1,229 @@
|
||||||
|
const Expr = @import("./Expr.zig");
|
||||||
|
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const tokens = @import("./tokens.zig");
|
||||||
|
|
||||||
|
const tree = @import("./tree.zig");
|
||||||
|
|
||||||
|
next: ?*const Self = null,
|
||||||
|
|
||||||
|
kind: union (enum) {
|
||||||
|
top_expression: *const Expr,
|
||||||
|
@"return": Return,
|
||||||
|
declare: Declare,
|
||||||
|
@"if": If,
|
||||||
|
@"while": While,
|
||||||
|
},
|
||||||
|
|
||||||
|
pub const Declare = struct {
|
||||||
|
declaration: *const tree.Declaration,
|
||||||
|
initial_expression: *const Expr,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const If = struct {
|
||||||
|
then_expression: *const Expr,
|
||||||
|
@"then": *const Self,
|
||||||
|
@"else": ?*const Self,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Return = struct {
|
||||||
|
returned_expression: ?*const Expr,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const While = struct {
|
||||||
|
loop_expression: *const Expr,
|
||||||
|
loop: *const Self,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
|
||||||
|
switch (stream.token) {
|
||||||
|
.keyword_return => {
|
||||||
|
stream.step();
|
||||||
|
|
||||||
|
if (stream.token != .end and stream.token != .newline) {
|
||||||
|
return root.create_stmt(.{
|
||||||
|
.kind = .{.@"return" = .{.returned_expression = try Expr.parse(root, stream, environment)}},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream.token != .end and stream.token != .newline) {
|
||||||
|
return root.report_error(stream, "expected end or newline after return statement", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.create_stmt(.{.kind = .{.@"return" = .{.returned_expression = null}}});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_while => {
|
||||||
|
defer stream.skip_newlines();
|
||||||
|
|
||||||
|
stream.step();
|
||||||
|
|
||||||
|
const condition_expression = try Expr.parse(root, stream, environment);
|
||||||
|
|
||||||
|
if (stream.token != .symbol_colon) {
|
||||||
|
return root.report_error(stream, "expected `:` after `while` statement", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
const first_statement = try parse(root, stream, environment);
|
||||||
|
|
||||||
|
{
|
||||||
|
var current_statement = first_statement;
|
||||||
|
|
||||||
|
while (stream.token != .keyword_end) {
|
||||||
|
const next_statement = try parse(root, stream, environment);
|
||||||
|
|
||||||
|
current_statement.next = next_statement;
|
||||||
|
current_statement = next_statement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.create_stmt(.{
|
||||||
|
.kind = .{
|
||||||
|
.@"while" = .{
|
||||||
|
.loop = first_statement,
|
||||||
|
.loop_expression = condition_expression,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_var, .keyword_let => {
|
||||||
|
const storage_token = stream.token;
|
||||||
|
|
||||||
|
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()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
if (stream.token != .symbol_equals) {
|
||||||
|
return root.report_error(stream, "expected `=` after declaration `{identifier}`", .{
|
||||||
|
.identifier = identifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_stmt(.{
|
||||||
|
.kind = .{
|
||||||
|
.declare = .{
|
||||||
|
.initial_expression = try Expr.parse(root, stream, environment),
|
||||||
|
|
||||||
|
.declaration = (try root.environment.declare(identifier)) orelse {
|
||||||
|
return root.report_error(stream, "declaration `{identifier}` already exists", .{
|
||||||
|
.identifier = identifier,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_if => return parse_branch(root, stream, environment),
|
||||||
|
|
||||||
|
else => return root.create_stmt(.{.kind = .{.top_expression = try Expr.parse(root, stream, environment)}}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
|
||||||
|
stream.step();
|
||||||
|
|
||||||
|
const expression = try Expr.parse(root, stream, environment);
|
||||||
|
|
||||||
|
if (stream.token != .symbol_colon) {
|
||||||
|
return root.report_error(stream, "expected `:` after `{token}`", .{.token = stream.token.text()});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
const first_then_statement = try parse(root, stream, environment);
|
||||||
|
var current_then_statement = first_then_statement;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (stream.token) {
|
||||||
|
.keyword_end => {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_stmt(.{
|
||||||
|
.next = null,
|
||||||
|
|
||||||
|
.kind = .{
|
||||||
|
.@"if" = .{
|
||||||
|
.then_expression = expression,
|
||||||
|
.@"then" = first_then_statement,
|
||||||
|
.@"else" = null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_else => {
|
||||||
|
stream.step();
|
||||||
|
|
||||||
|
if (stream.token != .symbol_colon) {
|
||||||
|
return root.report_error(stream, "expected `:` after `if` statement condition", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
const first_else_statement = try parse(root, stream, environment);
|
||||||
|
var current_else_statement = first_else_statement;
|
||||||
|
|
||||||
|
while (stream.token != .keyword_end) {
|
||||||
|
const next_statement = try parse(root, stream, environment);
|
||||||
|
|
||||||
|
current_else_statement.next = next_statement;
|
||||||
|
current_else_statement = next_statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
return root.create_stmt(.{
|
||||||
|
.next = null,
|
||||||
|
|
||||||
|
.kind = .{
|
||||||
|
.@"if" = .{
|
||||||
|
.@"else" = first_else_statement,
|
||||||
|
.@"then" = first_then_statement,
|
||||||
|
.then_expression = expression,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_elif => {
|
||||||
|
return root.create_stmt(.{
|
||||||
|
.next = null,
|
||||||
|
|
||||||
|
.kind = .{
|
||||||
|
.@"if" = .{
|
||||||
|
.@"else" = try parse_branch(root, stream, environment),
|
||||||
|
.@"then" = first_then_statement,
|
||||||
|
.then_expression = expression,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
const next_statement = try parse(root, stream, environment);
|
||||||
|
|
||||||
|
current_then_statement.next = next_statement;
|
||||||
|
current_then_statement = next_statement;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const kym = @import("../kym.zig");
|
||||||
|
|
||||||
|
associative: RefTable,
|
||||||
|
contiguous: RefList,
|
||||||
|
|
||||||
|
const RefList = coral.list.Stack(?*kym.RuntimeRef);
|
||||||
|
|
||||||
|
const RefTable = coral.map.Table(*kym.RuntimeRef, *kym.RuntimeRef, struct {
|
||||||
|
pub const hash = kym.RuntimeRef.hash;
|
||||||
|
|
||||||
|
pub const equals = kym.RuntimeRef.equals;
|
||||||
|
});
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn free(self: *Self, env: *kym.RuntimeEnv) void {
|
||||||
|
{
|
||||||
|
var field_iterable = self.associative.as_iterable();
|
||||||
|
|
||||||
|
while (field_iterable.next()) |entry| {
|
||||||
|
env.discard(entry.key);
|
||||||
|
env.discard(entry.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.associative.free();
|
||||||
|
|
||||||
|
while (self.contiguous.pop()) |value| {
|
||||||
|
if (value) |ref| {
|
||||||
|
env.discard(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.contiguous.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make(env: *kym.RuntimeEnv) Self {
|
||||||
|
return .{
|
||||||
|
.associative = RefTable.make(env.allocator, .{}),
|
||||||
|
.contiguous = RefList.make(env.allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const typeinfo = &kym.Typeinfo{
|
||||||
|
.size = @sizeOf(Self),
|
||||||
|
.name = "table",
|
||||||
|
.destruct = typeinfo_destruct,
|
||||||
|
.get = typeinfo_get,
|
||||||
|
.set = typeinfo_set,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn typeinfo_destruct(env: *kym.RuntimeEnv, userdata: []coral.io.Byte) void {
|
||||||
|
@as(*Self, @ptrCast(@alignCast(userdata))).free(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typeinfo_get(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const kym.RuntimeRef) kym.RuntimeError!?*kym.RuntimeRef {
|
||||||
|
const table = @as(*Self, @ptrCast(@alignCast(userdata)));
|
||||||
|
const acquired_index = index.acquire();
|
||||||
|
|
||||||
|
defer env.discard(acquired_index);
|
||||||
|
|
||||||
|
if (acquired_index.as_fixed()) |fixed| {
|
||||||
|
if (fixed < 0) {
|
||||||
|
// TODO: Negative indexing.
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fixed < table.contiguous.values.len) {
|
||||||
|
return (table.contiguous.values[@intCast(fixed)] orelse return null).acquire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (table.associative.lookup(acquired_index)) |value| {
|
||||||
|
return value.acquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typeinfo_set(env: *kym.RuntimeEnv, userdata: []coral.io.Byte, index: *const kym.RuntimeRef, value: ?*const kym.RuntimeRef) kym.RuntimeError!void {
|
||||||
|
const table = @as(*Self, @ptrCast(@alignCast(userdata)));
|
||||||
|
const acquired_index = index.acquire();
|
||||||
|
|
||||||
|
errdefer env.discard(acquired_index);
|
||||||
|
|
||||||
|
if (acquired_index.as_fixed()) |fixed| {
|
||||||
|
if (fixed < 0) {
|
||||||
|
// TODO: Negative indexing.
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fixed < table.contiguous.values.len) {
|
||||||
|
const maybe_replacing = &table.contiguous.values[@intCast(fixed)];
|
||||||
|
|
||||||
|
if (maybe_replacing.*) |replacing| {
|
||||||
|
env.discard(replacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_replacing.* = if (value) |ref| ref.acquire() else null;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const acquired_value = (value orelse {
|
||||||
|
if (table.associative.remove(acquired_index)) |removed| {
|
||||||
|
env.discard(removed.key);
|
||||||
|
env.discard(removed.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}).acquire();
|
||||||
|
|
||||||
|
errdefer env.discard(acquired_value);
|
||||||
|
|
||||||
|
if (try table.associative.replace(acquired_index, acquired_value)) |replaced| {
|
||||||
|
env.discard(replaced.key);
|
||||||
|
env.discard(replaced.value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,796 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const tokens = @import("./tokens.zig");
|
|
||||||
|
|
||||||
pub const BinaryOperator = enum {
|
|
||||||
addition,
|
|
||||||
subtraction,
|
|
||||||
multiplication,
|
|
||||||
divsion,
|
|
||||||
equals_comparison,
|
|
||||||
greater_than_comparison,
|
|
||||||
greater_equals_comparison,
|
|
||||||
less_than_comparison,
|
|
||||||
less_equals_comparison,
|
|
||||||
|
|
||||||
fn builder(comptime build_next: ExpressionBuilder, comptime operators: []const BinaryOperator) ExpressionBuilder {
|
|
||||||
const Builder = struct {
|
|
||||||
fn build(self: *Tree) ParseError!Expression {
|
|
||||||
const allocator = self.arena.as_allocator();
|
|
||||||
var expression = try build_next(self);
|
|
||||||
|
|
||||||
inline for (operators) |operator| {
|
|
||||||
const token = @as(tokens.Token, switch (operator) {
|
|
||||||
.addition => .symbol_plus,
|
|
||||||
.subtraction => .symbol_minus,
|
|
||||||
.multiplication => .symbol_asterisk,
|
|
||||||
.divsion => .symbol_forward_slash,
|
|
||||||
.equals_comparison => .symbol_double_equals,
|
|
||||||
.greater_than_comparison => .symbol_greater_than,
|
|
||||||
.greater_equals_comparison => .symbol_greater_equals,
|
|
||||||
.less_than_comparison => .symbol_less_than,
|
|
||||||
.less_equals_comparison => .symbol_less_equals,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (self.tokenizer.token == coral.io.tag_of(token)) {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .end) {
|
|
||||||
return self.report(
|
|
||||||
"expected other half of expression after `" ++
|
|
||||||
comptime token.text() ++
|
|
||||||
"`");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove once Zig has fixed struct self-reassignment.
|
|
||||||
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
|
||||||
|
|
||||||
expression = .{
|
|
||||||
.binary_operation = .{
|
|
||||||
.operator = operator,
|
|
||||||
.lhs_expression = unnecessary_temp,
|
|
||||||
.rhs_expression = try coral.io.allocate_one(allocator, try build_next(self)),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Builder.build;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Expression = union (enum) {
|
|
||||||
nil_literal,
|
|
||||||
true_literal,
|
|
||||||
false_literal,
|
|
||||||
builtin: []const coral.io.Byte,
|
|
||||||
number_literal: []const coral.io.Byte,
|
|
||||||
string_literal: []const coral.io.Byte,
|
|
||||||
symbol_literal: []const coral.io.Byte,
|
|
||||||
table_literal: TableLiteral,
|
|
||||||
grouped_expression: *Expression,
|
|
||||||
|
|
||||||
lambda_literal: struct {
|
|
||||||
argument_identifiers: IdentifierList,
|
|
||||||
block_statements: Statement.List,
|
|
||||||
},
|
|
||||||
|
|
||||||
local_get: struct {
|
|
||||||
identifier: []const coral.io.Byte,
|
|
||||||
},
|
|
||||||
|
|
||||||
local_set: struct {
|
|
||||||
identifier: []const coral.io.Byte,
|
|
||||||
value_expression: *Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
field_get: struct {
|
|
||||||
object_expression: *Expression,
|
|
||||||
identifier: []const coral.io.Byte,
|
|
||||||
},
|
|
||||||
|
|
||||||
field_set: struct {
|
|
||||||
object_expression: *Expression,
|
|
||||||
identifier: []const coral.io.Byte,
|
|
||||||
value_expression: *Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
subscript_get: struct {
|
|
||||||
object_expression: *Expression,
|
|
||||||
subscript_expression: *Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
subscript_set: struct {
|
|
||||||
object_expression: *Expression,
|
|
||||||
subscript_expression: *Expression,
|
|
||||||
value_expression: *Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
binary_operation: struct {
|
|
||||||
operator: BinaryOperator,
|
|
||||||
lhs_expression: *Expression,
|
|
||||||
rhs_expression: *Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
unary_operation: struct {
|
|
||||||
operator: UnaryOperator,
|
|
||||||
expression: *Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
invoke: struct {
|
|
||||||
object_expression: *Expression,
|
|
||||||
argument_expressions: List,
|
|
||||||
},
|
|
||||||
|
|
||||||
const List = coral.list.Stack(Expression);
|
|
||||||
|
|
||||||
const TableLiteral = coral.list.Stack(struct {
|
|
||||||
key_expression: Expression,
|
|
||||||
value_expression: Expression,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const ExpressionBuilder = fn (self: *Tree) ParseError!Expression;
|
|
||||||
|
|
||||||
const IdentifierList = coral.list.Stack([]const coral.io.Byte);
|
|
||||||
|
|
||||||
pub const ParseError = error {
|
|
||||||
OutOfMemory,
|
|
||||||
BadSyntax,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Statement = union (enum) {
|
|
||||||
@"return": ?Expression,
|
|
||||||
|
|
||||||
declare: struct {
|
|
||||||
storage: DeclarationStorage,
|
|
||||||
identifier: []const coral.io.Byte,
|
|
||||||
assigned_expression: Expression,
|
|
||||||
},
|
|
||||||
|
|
||||||
@"if": struct {
|
|
||||||
condition_expression: Expression,
|
|
||||||
block_statements: List,
|
|
||||||
else_statement: ?*Statement,
|
|
||||||
},
|
|
||||||
|
|
||||||
@"while": struct {
|
|
||||||
condition_expression: Expression,
|
|
||||||
block_statements: List,
|
|
||||||
},
|
|
||||||
|
|
||||||
block: List,
|
|
||||||
expression: Expression,
|
|
||||||
|
|
||||||
pub const DeclarationStorage = enum {
|
|
||||||
@"var",
|
|
||||||
let,
|
|
||||||
};
|
|
||||||
|
|
||||||
const List = coral.list.Stack(Statement);
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Tree = struct {
|
|
||||||
name: []const coral.io.Byte,
|
|
||||||
allocator: coral.io.Allocator,
|
|
||||||
arena: coral.arena.Stacking,
|
|
||||||
error_buffer: coral.list.ByteStack,
|
|
||||||
tokenizer: tokens.Tokenizer,
|
|
||||||
parsed_statements: Statement.List,
|
|
||||||
has_returned: bool,
|
|
||||||
|
|
||||||
pub fn error_message(self: Tree) []const coral.io.Byte {
|
|
||||||
return self.error_buffer.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn free(self: *Tree) void {
|
|
||||||
self.parsed_statements.free();
|
|
||||||
self.error_buffer.free();
|
|
||||||
self.arena.free();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn make(allocator: coral.io.Allocator, ast_name: []const coral.io.Byte) Tree {
|
|
||||||
return .{
|
|
||||||
.arena = coral.arena.Stacking.make(allocator, 4096),
|
|
||||||
.error_buffer = coral.list.ByteStack.make(allocator),
|
|
||||||
.parsed_statements = Statement.List.make(allocator),
|
|
||||||
.tokenizer = .{.source = ""},
|
|
||||||
.allocator = allocator,
|
|
||||||
.name = ast_name,
|
|
||||||
.has_returned = false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse(self: *Tree, data: []const coral.io.Byte) ParseError![]const Statement {
|
|
||||||
self.free();
|
|
||||||
|
|
||||||
self.tokenizer = .{.source = data};
|
|
||||||
self.has_returned = false;
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
while (self.tokenizer.token != .end) {
|
|
||||||
try self.parsed_statements.push_one(try self.parse_statement());
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.parsed_statements.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parse_additive = BinaryOperator.builder(parse_equality, &.{
|
|
||||||
.addition,
|
|
||||||
.subtraction,
|
|
||||||
});
|
|
||||||
|
|
||||||
fn parse_branch(self: *Tree) ParseError!Statement {
|
|
||||||
const allocator = self.arena.as_allocator();
|
|
||||||
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
const condition_expression = try self.parse_expression();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_colon) {
|
|
||||||
return self.report("expected `:` after `if` statement condition");
|
|
||||||
}
|
|
||||||
|
|
||||||
var statements = Statement.List.make(allocator);
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.keyword_end => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.@"if" = .{
|
|
||||||
.condition_expression = condition_expression,
|
|
||||||
.block_statements = statements,
|
|
||||||
.else_statement = null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_else => {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_colon) {
|
|
||||||
return self.report("expected `:` after `if` statement condition");
|
|
||||||
}
|
|
||||||
|
|
||||||
var else_statements = Statement.List.make(allocator);
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
while (self.tokenizer.token != .keyword_end) {
|
|
||||||
try else_statements.push_one(try self.parse_statement());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.@"if" = .{
|
|
||||||
.else_statement = try coral.io.allocate_one(allocator, Statement{.block = else_statements}),
|
|
||||||
.condition_expression = condition_expression,
|
|
||||||
.block_statements = statements,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_elif => {
|
|
||||||
return .{
|
|
||||||
.@"if" = .{
|
|
||||||
.else_statement = try coral.io.allocate_one(allocator, try self.parse_branch()),
|
|
||||||
.condition_expression = condition_expression,
|
|
||||||
.block_statements = statements,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => try statements.push_one(try self.parse_statement()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parse_comparison = BinaryOperator.builder(parse_term, &.{
|
|
||||||
.greater_than_comparison,
|
|
||||||
.greater_equals_comparison,
|
|
||||||
.less_than_comparison,
|
|
||||||
.less_equals_comparison
|
|
||||||
});
|
|
||||||
|
|
||||||
const parse_equality = BinaryOperator.builder(parse_comparison, &.{
|
|
||||||
.equals_comparison,
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn parse_expression(self: *Tree) ParseError!Expression {
|
|
||||||
const allocator = self.arena.as_allocator();
|
|
||||||
const expression = try self.parse_additive();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .symbol_equals) {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .end) {
|
|
||||||
return self.report("expected assignment after `=`");
|
|
||||||
}
|
|
||||||
|
|
||||||
return switch (expression) {
|
|
||||||
.local_get => |local_get| .{
|
|
||||||
.local_set = .{
|
|
||||||
.identifier = local_get.identifier,
|
|
||||||
.value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.field_get => |field_get| .{
|
|
||||||
.field_set = .{
|
|
||||||
.object_expression = field_get.object_expression,
|
|
||||||
.identifier = field_get.identifier,
|
|
||||||
.value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.subscript_get => |subscript_get| .{
|
|
||||||
.subscript_set = .{
|
|
||||||
.object_expression = subscript_get.object_expression,
|
|
||||||
.subscript_expression = subscript_get.subscript_expression,
|
|
||||||
.value_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
else => self.report("expected local or field on left-hand side of expression"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_factor(self: *Tree) ParseError!Expression {
|
|
||||||
const allocator = self.arena.as_allocator();
|
|
||||||
|
|
||||||
var expression = @as(Expression, parse: {
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_paren_left => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .end) {
|
|
||||||
return self.report("expected an expression after `(`");
|
|
||||||
}
|
|
||||||
|
|
||||||
const expression = try self.parse_expression();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_paren_right) {
|
|
||||||
return self.report("expected a closing `)` after expression");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.grouped_expression = try coral.io.allocate_one(allocator, expression)};
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_nil => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .nil_literal;
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_true => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .true_literal;
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_false => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .false_literal;
|
|
||||||
},
|
|
||||||
|
|
||||||
.number => |value| {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.number_literal = value};
|
|
||||||
},
|
|
||||||
|
|
||||||
.string => |value| {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.string_literal = value};
|
|
||||||
},
|
|
||||||
|
|
||||||
.identifier => |identifier| {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.local_get = .{.identifier = identifier}};
|
|
||||||
},
|
|
||||||
|
|
||||||
.builtin => |builtin| {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
var table_literal = Expression.TableLiteral.make(allocator);
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_brace_right => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.table_literal = table_literal};
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_period => {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
const identifier = switch (self.tokenizer.token) {
|
|
||||||
.identifier => |identifier| identifier,
|
|
||||||
else => return self.report("expected identifier after `.`"),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_equals) {
|
|
||||||
return self.report("expected `=` after symbol");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
try table_literal.push_one(.{
|
|
||||||
.value_expression = try self.parse_expression(),
|
|
||||||
.key_expression = .{.symbol_literal = identifier},
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_comma => self.tokenizer.skip_newlines(),
|
|
||||||
|
|
||||||
.symbol_brace_right => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.table_literal = table_literal};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("expected `,` or `}` after expression"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_bracket_left => {
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
const subscript_expression = try self.parse_expression();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_bracket_right) {
|
|
||||||
return self.report("expected `]` after subscript expression");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_equals) {
|
|
||||||
return self.report("expected `=` after `]`");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
try table_literal.push_one(.{
|
|
||||||
.value_expression = try self.parse_expression(),
|
|
||||||
.key_expression = subscript_expression,
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_comma => self.tokenizer.skip_newlines(),
|
|
||||||
|
|
||||||
.symbol_brace_right => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
break: parse .{.table_literal = table_literal};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("expected `,` or `}` after expression"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("expected `}` or fields in table literal"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_minus => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .end) {
|
|
||||||
return self.report("expected expression after numeric negation (`-`)");
|
|
||||||
}
|
|
||||||
|
|
||||||
break: parse .{
|
|
||||||
.unary_operation = .{
|
|
||||||
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
|
||||||
.operator = .numeric_negation,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_bang => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token == .end) {
|
|
||||||
return self.report("expected expression after boolean negation (`!`)");
|
|
||||||
}
|
|
||||||
|
|
||||||
break: parse .{
|
|
||||||
.unary_operation = .{
|
|
||||||
.expression = try coral.io.allocate_one(allocator, try self.parse_factor()),
|
|
||||||
.operator = .boolean_negation,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return self.report("unexpected token in expression"),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_period => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
|
||||||
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
|
||||||
|
|
||||||
expression = .{
|
|
||||||
.field_get = .{
|
|
||||||
.identifier = switch (self.tokenizer.token) {
|
|
||||||
.identifier => |field_identifier| field_identifier,
|
|
||||||
else => return self.report("expected identifier after `.`"),
|
|
||||||
},
|
|
||||||
|
|
||||||
.object_expression = unnecessary_temp,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_bracket_left => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
|
||||||
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
|
||||||
|
|
||||||
expression = .{
|
|
||||||
.subscript_get = .{
|
|
||||||
.subscript_expression = try coral.io.allocate_one(allocator, try self.parse_expression()),
|
|
||||||
.object_expression = unnecessary_temp,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_bracket_right) {
|
|
||||||
return self.report("expected `]` subscript expression");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_paren_left => {
|
|
||||||
var argument_expressions = Expression.List.make(allocator);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_paren_right => break,
|
|
||||||
|
|
||||||
else => {
|
|
||||||
try argument_expressions.push_one(try self.parse_expression());
|
|
||||||
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.symbol_comma => continue,
|
|
||||||
.symbol_paren_right => break,
|
|
||||||
else => return self.report("expected `,` or `)` after function argument expression"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
|
|
||||||
const unnecessary_temp = try coral.io.allocate_one(allocator, expression);
|
|
||||||
|
|
||||||
expression = .{
|
|
||||||
.invoke = .{
|
|
||||||
.argument_expressions = argument_expressions,
|
|
||||||
.object_expression = unnecessary_temp,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expression;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_statement(self: *Tree) ParseError!Statement {
|
|
||||||
const allocator = self.arena.as_allocator();
|
|
||||||
|
|
||||||
switch (self.tokenizer.token) {
|
|
||||||
.keyword_return => {
|
|
||||||
if (self.has_returned) {
|
|
||||||
return self.report("multiple returns in lambda scope but expected only one");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .end and self.tokenizer.token != .newline) {
|
|
||||||
return .{.@"return" = try self.parse_expression()};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .end and self.tokenizer.token != .newline) {
|
|
||||||
return self.report("expected end or newline after return statement");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.has_returned = true;
|
|
||||||
|
|
||||||
return .{.@"return" = null};
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_while => {
|
|
||||||
defer self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
self.tokenizer.step();
|
|
||||||
|
|
||||||
const condition_expression = try self.parse_expression();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_colon) {
|
|
||||||
return self.report("expected `:` after `while` statement");
|
|
||||||
}
|
|
||||||
|
|
||||||
var statements = Statement.List.make(allocator);
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
while (self.tokenizer.token != .keyword_end) {
|
|
||||||
try statements.push_one(try self.parse_statement());
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.@"while" = .{
|
|
||||||
.block_statements = statements,
|
|
||||||
.condition_expression = condition_expression,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_var => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
const identifier = switch (self.tokenizer.token) {
|
|
||||||
.identifier => |identifier| identifier,
|
|
||||||
else => return self.report("expected identifier after `var` declaration statement"),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_equals) {
|
|
||||||
return self.report("expected `=` after declaration identifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.declare = .{
|
|
||||||
.assigned_expression = try self.parse_expression(),
|
|
||||||
.storage = .@"var",
|
|
||||||
.identifier = identifier,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_let => {
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
const identifier = switch (self.tokenizer.token) {
|
|
||||||
.identifier => |identifier| identifier,
|
|
||||||
else => return self.report("expected identifier after `let` declaration statement"),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
if (self.tokenizer.token != .symbol_equals) {
|
|
||||||
return self.report("expected `=` after declaration identifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tokenizer.skip_newlines();
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.declare = .{
|
|
||||||
.assigned_expression = try self.parse_expression(),
|
|
||||||
.storage = .let,
|
|
||||||
.identifier = identifier,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_if => return self.parse_branch(),
|
|
||||||
else => return .{.expression = try self.parse_expression()},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parse_term = BinaryOperator.builder(parse_factor, &.{
|
|
||||||
.multiplication,
|
|
||||||
.divsion,
|
|
||||||
});
|
|
||||||
|
|
||||||
fn report(self: *Tree, message: []const coral.io.Byte) ParseError {
|
|
||||||
coral.utf8.print_formatted(coral.list.stack_as_writer(&self.error_buffer), "{name}@{line}: {message}", .{
|
|
||||||
.name = self.name,
|
|
||||||
.line = self.tokenizer.lines_stepped,
|
|
||||||
.message = message,
|
|
||||||
}) catch return error.OutOfMemory;
|
|
||||||
|
|
||||||
return error.BadSyntax;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const UnaryOperator = enum {
|
|
||||||
boolean_negation,
|
|
||||||
numeric_negation,
|
|
||||||
};
|
|
|
@ -101,12 +101,12 @@ pub const Token = union(enum) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Tokenizer = struct {
|
pub const Stream = struct {
|
||||||
source: []const coral.io.Byte,
|
source: []const coral.io.Byte,
|
||||||
lines_stepped: usize = 1,
|
lines_stepped: usize = 1,
|
||||||
token: Token = .newline,
|
token: Token = .newline,
|
||||||
|
|
||||||
pub fn skip_newlines(self: *Tokenizer) void {
|
pub fn skip_newlines(self: *Stream) void {
|
||||||
self.step();
|
self.step();
|
||||||
|
|
||||||
while (self.token == .newline) {
|
while (self.token == .newline) {
|
||||||
|
@ -114,7 +114,7 @@ pub const Tokenizer = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(self: *Tokenizer) void {
|
pub fn step(self: *Stream) void {
|
||||||
var cursor = @as(usize, 0);
|
var cursor = @as(usize, 0);
|
||||||
|
|
||||||
defer self.source = self.source[cursor ..];
|
defer self.source = self.source[cursor ..];
|
||||||
|
@ -184,7 +184,7 @@ pub const Tokenizer = struct {
|
||||||
|
|
||||||
switch (identifier[0]) {
|
switch (identifier[0]) {
|
||||||
'c' => {
|
'c' => {
|
||||||
if (coral.io.ends_with(identifier, "onst")) {
|
if (coral.io.are_equal(identifier[1 ..], "onst")) {
|
||||||
self.token = .keyword_const;
|
self.token = .keyword_const;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -192,7 +192,7 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
'd' => {
|
'd' => {
|
||||||
if (coral.io.ends_with(identifier, "o")) {
|
if (coral.io.are_equal(identifier[1 ..], "o")) {
|
||||||
self.token = .keyword_do;
|
self.token = .keyword_do;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -200,19 +200,19 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
'e' => {
|
'e' => {
|
||||||
if (coral.io.ends_with(identifier, "lse")) {
|
if (coral.io.are_equal(identifier[1 ..], "lse")) {
|
||||||
self.token = .keyword_else;
|
self.token = .keyword_else;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coral.io.ends_with(identifier, "lif")) {
|
if (coral.io.are_equal(identifier[1 ..], "lif")) {
|
||||||
self.token = .keyword_elif;
|
self.token = .keyword_elif;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coral.io.ends_with(identifier, "nd")) {
|
if (coral.io.are_equal(identifier[1 ..], "nd")) {
|
||||||
self.token = .keyword_end;
|
self.token = .keyword_end;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -220,7 +220,7 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
'f' => {
|
'f' => {
|
||||||
if (coral.io.ends_with(identifier, "alse")) {
|
if (coral.io.are_equal(identifier[1 ..], "alse")) {
|
||||||
self.token = .keyword_false;
|
self.token = .keyword_false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -228,7 +228,7 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
'i' => {
|
'i' => {
|
||||||
if (coral.io.ends_with(identifier, "f")) {
|
if (coral.io.are_equal(identifier[1 ..], "f")) {
|
||||||
self.token = .keyword_if;
|
self.token = .keyword_if;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -236,13 +236,13 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
'l' => {
|
'l' => {
|
||||||
if (coral.io.ends_with(identifier, "ambda")) {
|
if (coral.io.are_equal(identifier[1 ..], "ambda")) {
|
||||||
self.token = .keyword_lambda;
|
self.token = .keyword_lambda;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coral.io.ends_with(identifier, "et")) {
|
if (coral.io.are_equal(identifier[1 ..], "et")) {
|
||||||
self.token = .keyword_let;
|
self.token = .keyword_let;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -250,7 +250,7 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
'n' => {
|
'n' => {
|
||||||
if (coral.io.ends_with(identifier, "il")) {
|
if (coral.io.are_equal(identifier[1 ..], "il")) {
|
||||||
self.token = .keyword_nil;
|
self.token = .keyword_nil;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -258,7 +258,7 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
'r' => {
|
'r' => {
|
||||||
if (coral.io.ends_with(identifier, "eturn")) {
|
if (coral.io.are_equal(identifier[1 ..], "eturn")) {
|
||||||
self.token = .keyword_return;
|
self.token = .keyword_return;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -266,7 +266,7 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
's' => {
|
's' => {
|
||||||
if (coral.io.ends_with(identifier, "elf")) {
|
if (coral.io.are_equal(identifier[1 ..], "elf")) {
|
||||||
self.token = .keyword_self;
|
self.token = .keyword_self;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -274,7 +274,7 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
't' => {
|
't' => {
|
||||||
if (coral.io.ends_with(identifier, "rue")) {
|
if (coral.io.are_equal(identifier[1 ..], "rue")) {
|
||||||
self.token = .keyword_true;
|
self.token = .keyword_true;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -282,7 +282,7 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
'v' => {
|
'v' => {
|
||||||
if (coral.io.ends_with(identifier, "ar")) {
|
if (coral.io.are_equal(identifier[1 ..], "ar")) {
|
||||||
self.token = .keyword_var;
|
self.token = .keyword_var;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -290,7 +290,7 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
|
|
||||||
'w' => {
|
'w' => {
|
||||||
if (coral.io.ends_with(identifier, "hile")) {
|
if (coral.io.are_equal(identifier[1 ..], "hile")) {
|
||||||
self.token = .keyword_while;
|
self.token = .keyword_while;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -305,21 +305,6 @@ pub const Tokenizer = struct {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
'@' => {
|
|
||||||
cursor += 1;
|
|
||||||
|
|
||||||
const begin = cursor;
|
|
||||||
|
|
||||||
while (cursor < self.source.len) switch (self.source[cursor]) {
|
|
||||||
'0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1,
|
|
||||||
else => break,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.token = if (begin == cursor) .{.unknown = '@'} else .{.builtin = self.source[begin .. cursor]};
|
|
||||||
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
'"' => {
|
'"' => {
|
||||||
cursor += 1;
|
cursor += 1;
|
||||||
|
|
||||||
|
@ -485,6 +470,13 @@ pub const Tokenizer = struct {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'@' => {
|
||||||
|
self.token = .symbol_at;
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
else => {
|
else => {
|
||||||
self.token = .{.unknown = self.source[cursor]};
|
self.token = .{.unknown = self.source[cursor]};
|
||||||
cursor += 1;
|
cursor += 1;
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
const Expr = @import("./Expr.zig");
|
||||||
|
|
||||||
|
const Stmt = @import("./Stmt.zig");
|
||||||
|
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const tokens = @import("./tokens.zig");
|
||||||
|
|
||||||
|
pub const Declaration = struct {
|
||||||
|
identifier: []const coral.io.Byte,
|
||||||
|
is_readonly: bool = false,
|
||||||
|
is_captured: 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,
|
||||||
|
argument_count: u8 = 0,
|
||||||
|
statement: ?*const Stmt = null,
|
||||||
|
enclosing: ?*Environment = null,
|
||||||
|
|
||||||
|
const capture_max = coral.math.max_int(@typeInfo(u8).Int);
|
||||||
|
|
||||||
|
const declaration_max = coral.math.max_int(@typeInfo(u8).Int);
|
||||||
|
|
||||||
|
pub fn create_enclosed(self: *Environment, root: *Root) coral.io.AllocationError!*Environment {
|
||||||
|
return coral.io.allocate_one(root.arena.as_allocator(), Environment{
|
||||||
|
.enclosing = self,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn declare(self: *Environment, identifier: []const coral.io.Byte) 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];
|
||||||
|
|
||||||
|
declaration.* = .{.identifier = identifier};
|
||||||
|
self.local_declaration_count += 1;
|
||||||
|
|
||||||
|
return declaration;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = self.local_declaration_count;
|
||||||
|
|
||||||
|
while (remaining_count != 0) {
|
||||||
|
remaining_count -= 1;
|
||||||
|
|
||||||
|
const declaration = &self.local_declarations[remaining_count];
|
||||||
|
|
||||||
|
if (coral.io.are_equal(declaration.identifier, identifier)) {
|
||||||
|
if (ancestry != 0) {
|
||||||
|
declaration.is_captured = true;
|
||||||
|
environment = self;
|
||||||
|
|
||||||
|
while (ancestry != 0) : (ancestry -= 1) {
|
||||||
|
if (environment.capture_count == self.captures.len) {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
environment.captures[environment.capture_count] = remaining_count;
|
||||||
|
environment.capture_count += 1;
|
||||||
|
|
||||||
|
coral.debug.assert(environment.enclosing != null);
|
||||||
|
|
||||||
|
environment = environment.enclosing.?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return declaration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
environment = environment.enclosing orelse return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ParseError = coral.io.AllocationError || error {
|
||||||
|
BadSyntax,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Root = struct {
|
||||||
|
arena: coral.arena.Stacking,
|
||||||
|
environment: Environment,
|
||||||
|
error_messages: MessageList,
|
||||||
|
|
||||||
|
const MessageList = coral.list.Stack([]coral.io.Byte);
|
||||||
|
|
||||||
|
pub fn report_error(self: *Root, stream: *tokens.Stream, comptime format: []const u8, args: anytype) ParseError {
|
||||||
|
const allocator = self.arena.as_allocator();
|
||||||
|
|
||||||
|
try self.error_messages.push_one(try coral.utf8.alloc_formatted(allocator, "{line}: {message}", .{
|
||||||
|
.message = try coral.utf8.alloc_formatted(allocator, format, args),
|
||||||
|
.line = stream.lines_stepped,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return error.BadSyntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_expr(self: *Root, expr: Expr) coral.io.AllocationError!*Expr {
|
||||||
|
return coral.io.allocate_one(self.arena.as_allocator(), expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_stmt(self: *Root, stmt: Stmt) coral.io.AllocationError!*Stmt {
|
||||||
|
return coral.io.allocate_one(self.arena.as_allocator(), stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn free(self: *Root) void {
|
||||||
|
self.error_messages.free();
|
||||||
|
self.arena.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make(allocator: coral.io.Allocator) coral.io.AllocationError!Root {
|
||||||
|
const arena_page_size = 4096;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.arena = coral.arena.Stacking.make(allocator, arena_page_size),
|
||||||
|
.error_messages = MessageList.make(allocator),
|
||||||
|
.environment = .{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(self: *Root, stream: *tokens.Stream) ParseError!void {
|
||||||
|
stream.skip_newlines();
|
||||||
|
|
||||||
|
const first_statement = try Stmt.parse(self, stream, &self.environment);
|
||||||
|
var current_statement = first_statement;
|
||||||
|
|
||||||
|
while (stream.token != .end) {
|
||||||
|
const next_statement = try Stmt.parse(self, stream, &self.environment);
|
||||||
|
|
||||||
|
current_statement.next = next_statement;
|
||||||
|
current_statement = next_statement;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.environment.statement = first_statement;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue