General fixes
This commit is contained in:
parent
e9f12785f9
commit
1e0dff6dba
|
@ -5,14 +5,7 @@
|
||||||
"editor.rulers": [120],
|
"editor.rulers": [120],
|
||||||
"files.trimTrailingWhitespace": true,
|
"files.trimTrailingWhitespace": true,
|
||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
"spellright.language": [
|
|
||||||
"en-US-10-1."
|
|
||||||
],
|
|
||||||
"spellright.documentTypes": [
|
|
||||||
"markdown",
|
|
||||||
"plaintext",
|
|
||||||
"zig"
|
|
||||||
],
|
|
||||||
"zig.formattingProvider": "off",
|
"zig.formattingProvider": "off",
|
||||||
"zig.zls.enableAutofix": false,
|
"zig.zls.enableAutofix": false,
|
||||||
|
"editor.formatOnSave": false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,22 +25,6 @@ pub fn build(builder: *std.Build) void {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const oar_module = builder.createModule(.{
|
|
||||||
.source_file = .{.path = "./source/oar/oar.zig"},
|
|
||||||
|
|
||||||
.dependencies = &.{
|
|
||||||
.{
|
|
||||||
.name = "coral",
|
|
||||||
.module = coral_module
|
|
||||||
},
|
|
||||||
|
|
||||||
.{
|
|
||||||
.name = "ona",
|
|
||||||
.module = ona_module
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ona Runner.
|
// Ona Runner.
|
||||||
{
|
{
|
||||||
const ona_exe = builder.addExecutable(.{
|
const ona_exe = builder.addExecutable(.{
|
||||||
|
@ -52,7 +36,6 @@ pub fn build(builder: *std.Build) void {
|
||||||
|
|
||||||
ona_exe.addModule("coral", coral_module);
|
ona_exe.addModule("coral", coral_module);
|
||||||
ona_exe.addModule("ona", ona_module);
|
ona_exe.addModule("ona", ona_module);
|
||||||
ona_exe.addModule("oar", oar_module);
|
|
||||||
ona_exe.addModule("kym", kym_module);
|
ona_exe.addModule("kym", kym_module);
|
||||||
|
|
||||||
ona_exe.install();
|
ona_exe.install();
|
||||||
|
|
|
@ -31,7 +31,7 @@ Ona is also the Catalan word for "wave".
|
||||||
|
|
||||||
* Provide a simple scripting interface for people who want to do something quick and a powerful plug-in API for engine-level extensions and speed-critical application logic.
|
* Provide a simple scripting interface for people who want to do something quick and a powerful plug-in API for engine-level extensions and speed-critical application logic.
|
||||||
|
|
||||||
* One data serialization and configuration system to rule them all backed by the Kym scripting language.
|
* One data serialization and configuration system to rule them all backed by a scripting language.
|
||||||
|
|
||||||
## Technical Details
|
## Technical Details
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,11 @@ pub const Fixed = struct {
|
||||||
write_index: usize = 0,
|
write_index: usize = 0,
|
||||||
|
|
||||||
pub fn as_writer(self: *Fixed) io.Writer {
|
pub fn as_writer(self: *Fixed) io.Writer {
|
||||||
return io.Writer.bind(self, Fixed);
|
return io.Writer.bind(self, struct {
|
||||||
|
fn fallible_write(fixed: *Fixed, buffer: []const u8) io.WriteError!usize {
|
||||||
|
return fixed.write(buffer);
|
||||||
|
}
|
||||||
|
}.fallible_write);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remaining(self: Fixed) usize {
|
pub fn remaining(self: Fixed) usize {
|
||||||
|
|
|
@ -4,10 +4,14 @@ pub const debug = @import("./debug.zig");
|
||||||
|
|
||||||
pub const format = @import("./format.zig");
|
pub const format = @import("./format.zig");
|
||||||
|
|
||||||
|
pub const heap = @import("./heap.zig");
|
||||||
|
|
||||||
pub const io = @import("./io.zig");
|
pub const io = @import("./io.zig");
|
||||||
|
|
||||||
pub const math = @import("./math.zig");
|
pub const math = @import("./math.zig");
|
||||||
|
|
||||||
|
pub const slots = @import("./slots.zig");
|
||||||
|
|
||||||
pub const stack = @import("./stack.zig");
|
pub const stack = @import("./stack.zig");
|
||||||
|
|
||||||
pub const table = @import("./table.zig");
|
pub const table = @import("./table.zig");
|
||||||
|
|
|
@ -10,8 +10,8 @@ pub fn print(writer: io.Writer, values: []const Value) io.WriteError!usize {
|
||||||
var written: usize = 0;
|
var written: usize = 0;
|
||||||
|
|
||||||
for (values) |value| written += switch (value) {
|
for (values) |value| written += switch (value) {
|
||||||
.newline => try writer.write("\n"),
|
.newline => try writer.invoke("\n"),
|
||||||
.string => |string| try writer.write(string),
|
.string => |string| try writer.invoke(string),
|
||||||
.unsigned => |unsigned| try print_unsigned(writer, unsigned),
|
.unsigned => |unsigned| try print_unsigned(writer, unsigned),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ pub fn print(writer: io.Writer, values: []const Value) io.WriteError!usize {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_unsigned(writer: io.Writer, value: u128) io.WriteError!usize {
|
pub fn print_unsigned(writer: io.Writer, value: u128) io.WriteError!usize {
|
||||||
if (value == 0) return writer.write("0");
|
if (value == 0) return writer.invoke("0");
|
||||||
|
|
||||||
var buffer = [_]u8{0} ** 39;
|
var buffer = [_]u8{0} ** 39;
|
||||||
var buffer_count: usize = 0;
|
var buffer_count: usize = 0;
|
||||||
|
@ -41,5 +41,5 @@ pub fn print_unsigned(writer: io.Writer, value: u128) io.WriteError!usize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return writer.write(buffer[0 .. buffer_count]);
|
return writer.invoke(buffer[0 .. buffer_count]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
const debug = @import("./debug.zig");
|
||||||
|
|
||||||
|
const io = @import("./io.zig");
|
||||||
|
|
||||||
|
const math = @import("./math.zig");
|
||||||
|
|
||||||
|
const table = @import("./table.zig");
|
||||||
|
|
||||||
|
pub const Bucketed = struct {
|
||||||
|
base_allocator: io.Allocator,
|
||||||
|
slab_table: SlabTable,
|
||||||
|
|
||||||
|
const Slab = struct {
|
||||||
|
count: usize = 0,
|
||||||
|
buffer: []u8 = &.{},
|
||||||
|
erased: []usize = &.{},
|
||||||
|
|
||||||
|
fn create(self: *Slab, allocator: io.Allocator) ?[]u8 {
|
||||||
|
if (self.count == self.erased.len) {
|
||||||
|
const buffer = io.allocate_many(allocator, u8, math.max(1, self.buffer.len * 2)) orelse return null;
|
||||||
|
|
||||||
|
errdefer io.deallocate(allocator, buffer);
|
||||||
|
|
||||||
|
const erased = io.allocate_many(allocator, usize, math.max(1, self.erased.len * 2)) orelse return null;
|
||||||
|
|
||||||
|
errdefer io.deallocate(allocator, erased);
|
||||||
|
|
||||||
|
self.buffer = buffer;
|
||||||
|
self.erased = erased;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(self: *Slab) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SlabTable = table.Hashed(table.unsigned_key(@bitSizeOf(usize)), *Slab);
|
||||||
|
|
||||||
|
fn acquire_slab(self: *Bucketed, slab_element_size: usize) ?*Slab {
|
||||||
|
if (slab_element_size == 0) return null;
|
||||||
|
|
||||||
|
return self.slab_table.lookup(slab_element_size) orelse create_slab: {
|
||||||
|
const allocated_slab = io.allocate_one(self.base_allocator, Slab);
|
||||||
|
|
||||||
|
errdefer io.deallocate(self.base_allocator, allocated_slab);
|
||||||
|
|
||||||
|
allocated_slab.* = .{.size = slab_element_size};
|
||||||
|
|
||||||
|
debug.assert(self.size_buckets.insert(slab_element_size, allocated_slab) catch return null);
|
||||||
|
|
||||||
|
break: create_slab allocated_slab;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_allocator(self: *Bucketed) io.Allocator {
|
||||||
|
return io.Allocator.bind(self, reallocate);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Bucketed) void {
|
||||||
|
var slab_iterator = SlabTable.Iterator{.table = self.slab_table};
|
||||||
|
|
||||||
|
while (slab_iterator.next()) |slab| {
|
||||||
|
slab.free(self.base_allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.size_buckets.free(self.base_allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(base_allocator: io.Allocator) io.AllocationError!Bucketed {
|
||||||
|
return Bucketed{
|
||||||
|
.base_allocator = base_allocator,
|
||||||
|
.size_buckets = &.{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn owns(self: Bucketed, memory: []const u8) bool {
|
||||||
|
return io.overlaps(memory.ptr, (self.slab_table.lookup(memory.len) orelse return false).buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reallocate(self: *Bucketed, options: io.AllocationOptions) ?[]u8 {
|
||||||
|
const origin_slab = self.acquire_slab(options.size) orelse return null;
|
||||||
|
const existing_allocation = options.allocation orelse return origin_slab.create(self.base_allocator);
|
||||||
|
|
||||||
|
defer origin_slab.destroy(existing_allocation);
|
||||||
|
|
||||||
|
const target_slab = self.acquire_slab(existing_allocation.len) orelse return null;
|
||||||
|
const updated_allocation = target_slab.create(existing_allocation.len);
|
||||||
|
|
||||||
|
io.copy(updated_allocation, existing_allocation);
|
||||||
|
|
||||||
|
return updated_allocation;
|
||||||
|
}
|
||||||
|
};
|
|
@ -2,87 +2,54 @@ const debug = @import("./debug.zig");
|
||||||
|
|
||||||
const math = @import("./math.zig");
|
const math = @import("./math.zig");
|
||||||
|
|
||||||
pub const MemoryArena = struct {
|
pub const AllocationError = error {
|
||||||
|
OutOfMemory,
|
||||||
|
|
||||||
pub fn as_allocator(self: *MemoryArena) MemoryAllocator {
|
|
||||||
return MemoryAllocator.bind(self, MemoryArena);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const MemoryAllocator = struct {
|
pub const AllocationOptions = struct {
|
||||||
|
allocation: ?[]u8 = null,
|
||||||
|
size: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Allocator = Functor(?[]u8, AllocationOptions);
|
||||||
|
|
||||||
|
pub fn Functor(comptime Output: type, comptime Input: type) type {
|
||||||
|
return struct {
|
||||||
context: *anyopaque,
|
context: *anyopaque,
|
||||||
call: *const fn (capture: *anyopaque, maybe_allocation: ?[*]u8, size: usize) ?[*]u8,
|
invoker: *const fn (capture: *anyopaque, input: Input) Output,
|
||||||
|
|
||||||
const Capture = [@sizeOf(usize)]u8;
|
const Self = @This();
|
||||||
|
|
||||||
pub fn bind(state: anytype, comptime Actions: type) MemoryAllocator {
|
pub fn bind(state: anytype, comptime invoker: fn (capture: @TypeOf(state), input: Input) Output) Self {
|
||||||
const State = @TypeOf(state);
|
const State = @TypeOf(state);
|
||||||
const state_info = @typeInfo(State);
|
const state_info = @typeInfo(State);
|
||||||
|
|
||||||
if (state_info != .Pointer) @compileError("`@typeOf(state)` must be a pointer type");
|
if (state_info != .Pointer) {
|
||||||
|
@compileError("`@typeOf(state)` must be a pointer type");
|
||||||
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.context = state,
|
.context = state,
|
||||||
|
|
||||||
.call = struct {
|
.invoker = struct {
|
||||||
fn reallocate(context: *anyopaque, maybe_allocation: ?[*]u8, size: usize) ?[*]u8 {
|
fn invoke_opaque(context: *anyopaque, input: Input) Output {
|
||||||
return Actions.reallocate(@ptrCast(State, @alignCast(@alignOf(state_info.Pointer.child), context)), maybe_allocation, size);
|
return invoker(@ptrCast(State, @alignCast(@alignOf(state_info.Pointer.child), context)), input);
|
||||||
}
|
}
|
||||||
}.reallocate,
|
}.invoke_opaque,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocate_many(self: MemoryAllocator, comptime Type: type, amount: usize) ?[*]Type {
|
pub fn invoke(self: Self, input: Input) Output {
|
||||||
return @ptrCast(?[*]Type, @alignCast(@alignOf(Type), self.call(self.context, null, @sizeOf(Type) * amount)));
|
return self.invoker(self.context, input);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn allocate_one(self: MemoryAllocator, comptime Type: type) ?*Type {
|
|
||||||
return @ptrCast(?*Type, @alignCast(@alignOf(Type), self.call(self.context, null, @sizeOf(Type))));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deallocate(self: MemoryAllocator, maybe_allocation: anytype) void {
|
|
||||||
if (@typeInfo(@TypeOf(maybe_allocation)) != .Pointer)
|
|
||||||
@compileError("`maybe_allocation` must be a pointer type");
|
|
||||||
|
|
||||||
debug.assert(self.call(self.context, @ptrCast(?[*]u8, maybe_allocation), 0) == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reallocate(self: MemoryAllocator, comptime Type: type, maybe_allocation: ?[*]Type, amount: usize) ?[*]Type {
|
|
||||||
return @ptrCast(?[*]Type, @alignCast(@alignOf(Type),
|
|
||||||
self.call(self.context, @ptrCast(?[*]u8, maybe_allocation), @sizeOf(Type) * amount)));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub const ReadError = error {
|
pub const ReadError = error {
|
||||||
IoUnavailable,
|
IoUnavailable,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Reader = struct {
|
pub const Reader = Functor(ReadError!usize, []u8);
|
||||||
context: *anyopaque,
|
|
||||||
call: *const fn (context: *anyopaque, buffer: []u8) ReadError!usize,
|
|
||||||
|
|
||||||
pub fn bind(state: anytype, comptime Actions: type) Reader {
|
|
||||||
const State = @TypeOf(state);
|
|
||||||
const state_info = @typeInfo(State);
|
|
||||||
|
|
||||||
if (@typeInfo(State) != .Pointer) @compileError("`@typeOf(state)` must be a pointer type");
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.context = @ptrCast(*anyopaque, state),
|
|
||||||
|
|
||||||
.call = struct {
|
|
||||||
fn read(context: *anyopaque, buffer: []u8) ReadError!usize {
|
|
||||||
return Actions.read(@ptrCast(State, @alignCast(@alignOf(state_info.Pointer.child), context)), buffer);
|
|
||||||
}
|
|
||||||
}.read,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(self: Reader, buffer: []u8) ReadError!usize {
|
|
||||||
return self.call(self.context, buffer);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn Tag(comptime Element: type) type {
|
pub fn Tag(comptime Element: type) type {
|
||||||
return switch (@typeInfo(Element)) {
|
return switch (@typeInfo(Element)) {
|
||||||
|
@ -96,31 +63,31 @@ pub const WriteError = error{
|
||||||
IoUnavailable,
|
IoUnavailable,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Writer = struct {
|
pub const Writer = Functor(WriteError!usize, []const u8);
|
||||||
context: *anyopaque,
|
|
||||||
call: *const fn (context: *anyopaque, buffer: []const u8) WriteError!usize,
|
|
||||||
|
|
||||||
pub fn bind(state: anytype, comptime Actions: type) Writer {
|
pub fn allocate_many(comptime Type: type, amount: usize, allocator: Allocator) AllocationError![]Type {
|
||||||
const State = @TypeOf(state);
|
if (@sizeOf(Type) == 0) {
|
||||||
const state_info = @typeInfo(State);
|
@compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type));
|
||||||
|
|
||||||
if (state_info != .Pointer) @compileError("`@typeOf(state)` must be a pointer type");
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.context = @ptrCast(*anyopaque, state),
|
|
||||||
|
|
||||||
.call = struct {
|
|
||||||
fn write(context: *anyopaque, buffer: []const u8) ReadError!usize {
|
|
||||||
return Actions.write(@ptrCast(State, @alignCast(@alignOf(state_info.Pointer.child), context)), buffer);
|
|
||||||
}
|
|
||||||
}.write,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(self: Writer, buffer: []const u8) WriteError!usize {
|
if (amount == 0) {
|
||||||
return self.call(self.context, buffer);
|
return &.{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return @ptrCast([*]Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type) * amount}) orelse {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}))[0 .. amount];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocate_one(comptime Type: type, allocator: Allocator) AllocationError!*Type {
|
||||||
|
if (@sizeOf(Type) == 0) {
|
||||||
|
@compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type));
|
||||||
|
}
|
||||||
|
|
||||||
|
return @ptrCast(*Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type)}) orelse {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
pub fn bytes_of(value: anytype) []const u8 {
|
pub fn bytes_of(value: anytype) []const u8 {
|
||||||
const pointer_info = @typeInfo(@TypeOf(value)).Pointer;
|
const pointer_info = @typeInfo(@TypeOf(value)).Pointer;
|
||||||
|
@ -143,6 +110,28 @@ pub fn compare(this: []const u8, that: []const u8) isize {
|
||||||
return @intCast(isize, this.len) - @intCast(isize, that.len);
|
return @intCast(isize, this.len) - @intCast(isize, that.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deallocate(allocator: Allocator, allocation: anytype) void {
|
||||||
|
const Element = @TypeOf(allocation);
|
||||||
|
|
||||||
|
switch (@typeInfo(Element).Pointer.size) {
|
||||||
|
.One => {
|
||||||
|
debug.assert(allocator.invoke(.{
|
||||||
|
.allocation = @ptrCast([*]u8, allocation)[0 .. @sizeOf(Element)],
|
||||||
|
.size = 0
|
||||||
|
}) == null);
|
||||||
|
},
|
||||||
|
|
||||||
|
.Slice => {
|
||||||
|
debug.assert(allocator.invoke(.{
|
||||||
|
.allocation = @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(Element) * allocation.len)],
|
||||||
|
.size = 0
|
||||||
|
}) == null);
|
||||||
|
},
|
||||||
|
|
||||||
|
.Many, .C => @compileError("length of allocation must be known to deallocate"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn copy(target: []u8, source: []const u8) void {
|
pub fn copy(target: []u8, source: []const u8) void {
|
||||||
var index: usize = 0;
|
var index: usize = 0;
|
||||||
|
|
||||||
|
@ -183,6 +172,36 @@ pub const null_writer = Writer.bind(&null_context, struct {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub fn overlaps(pointer: [*]u8, memory_range: []u8) bool {
|
||||||
|
return (pointer >= memory_range.ptr) and (pointer < (memory_range.ptr + memory_range.len));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bytes_to(comptime Type: type, source_bytes: []const u8) ?Type {
|
||||||
|
const type_size = @sizeOf(Type);
|
||||||
|
|
||||||
|
if (source_bytes.len != type_size) return null;
|
||||||
|
|
||||||
|
var target_bytes = @as([type_size]u8, undefined);
|
||||||
|
|
||||||
|
copy(&target_bytes, source_bytes);
|
||||||
|
|
||||||
|
return @bitCast(Type, target_bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reallocate(allocator: Allocator, allocation: anytype, amount: usize) AllocationError![]@typeInfo(@TypeOf(allocation)).Pointer.child {
|
||||||
|
const pointer_info = @typeInfo(@TypeOf(allocation)).Pointer;
|
||||||
|
const Element = pointer_info.child;
|
||||||
|
|
||||||
|
return @ptrCast([*]Element, @alignCast(@alignOf(Element), (allocator.invoke(switch (pointer_info.size) {
|
||||||
|
.Slice => .{
|
||||||
|
.allocation = @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(Element) * allocation.len)],
|
||||||
|
.size = @sizeOf(Element) * amount,
|
||||||
|
},
|
||||||
|
|
||||||
|
.Many, .C, .One => @compileError("allocation must be a slice to reallocate"),
|
||||||
|
}) orelse return error.OutOfMemory).ptr))[0 .. amount];
|
||||||
|
}
|
||||||
|
|
||||||
pub fn slice_sentineled(comptime element: type, comptime sentinel: element, sequence: [*:sentinel]const element) []const element {
|
pub fn slice_sentineled(comptime element: type, comptime sentinel: element, sequence: [*:sentinel]const element) []const element {
|
||||||
var length: usize = 0;
|
var length: usize = 0;
|
||||||
|
|
||||||
|
@ -193,11 +212,11 @@ pub fn slice_sentineled(comptime element: type, comptime sentinel: element, sequ
|
||||||
|
|
||||||
pub fn stream(output: Writer, input: Reader, buffer: []u8) (ReadError || WriteError)!u64 {
|
pub fn stream(output: Writer, input: Reader, buffer: []u8) (ReadError || WriteError)!u64 {
|
||||||
var total_written: u64 = 0;
|
var total_written: u64 = 0;
|
||||||
var read = try input.read(buffer);
|
var read = try input.invoke(buffer);
|
||||||
|
|
||||||
while (read != 0) {
|
while (read != 0) {
|
||||||
total_written += try output.write(buffer[0..read]);
|
total_written += try output.invoke(buffer[0..read]);
|
||||||
read = try input.read(buffer);
|
read = try input.invoke(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return total_written;
|
return total_written;
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub fn clamp(comptime Scalar: type, value: Scalar, min_value: Scalar, max_value:
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max(comptime Scalar: type, this: Scalar, that: Scalar) Scalar {
|
pub fn max(comptime Scalar: type, this: Scalar, that: Scalar) Scalar {
|
||||||
return if (this > that) this else that;
|
return @max(this, that);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_int(comptime Int: type) Int {
|
pub fn max_int(comptime Int: type) Int {
|
||||||
|
@ -69,5 +69,5 @@ pub fn max_int(comptime Int: type) Int {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn min(comptime Scalar: type, this: Scalar, that: Scalar) Scalar {
|
pub fn min(comptime Scalar: type, this: Scalar, that: Scalar) Scalar {
|
||||||
return if (this < that) this else that;
|
return @min(this, that);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
const debug = @import("./debug.zig");
|
||||||
|
|
||||||
|
const io = @import("./io.zig");
|
||||||
|
|
||||||
|
const math = @import("./math.zig");
|
||||||
|
|
||||||
|
const stack = @import("./stack.zig");
|
||||||
|
|
||||||
|
pub fn Dense(comptime key: Key, comptime Element: type) type {
|
||||||
|
const KeySlot = Slot(key);
|
||||||
|
const Index = math.Unsigned(key.index_bits);
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
capacity: usize = 0,
|
||||||
|
values: []Element = &.{},
|
||||||
|
slots: ?[*]KeySlot = null,
|
||||||
|
erase: ?[*]Index = null,
|
||||||
|
next_free: Index = 0,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn fetch(self: Self, slot: KeySlot) ?*Element {
|
||||||
|
if (slot.index >= self.values.len) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const redirect = &self.slots[slot.index];
|
||||||
|
|
||||||
|
if (slot.salt != redirect.salt) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &self.values[redirect.index];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(self: *Self) void {
|
||||||
|
self.next_free = 0;
|
||||||
|
self.values = self.values[0 .. 0];
|
||||||
|
|
||||||
|
{
|
||||||
|
var index = @as(usize, 0);
|
||||||
|
|
||||||
|
while (index < self.capacity) : (index += 1) {
|
||||||
|
const slot = &self.slots[index];
|
||||||
|
|
||||||
|
slot.salt = math.max(slot.salt +% 1, 1);
|
||||||
|
slot.index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self, allocator: io.Allocator) void {
|
||||||
|
io.deallocate(allocator, self.values.ptr);
|
||||||
|
io.deallocate(allocator, self.slots);
|
||||||
|
io.deallocate(allocator, self.erase);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void {
|
||||||
|
const grown_capacity = self.capacity + growth_amount;
|
||||||
|
|
||||||
|
self.values = try io.reallocate(allocator, self.values, grown_capacity);
|
||||||
|
self.slots = (try io.reallocate(allocator, self.slots.?[0 .. self.values.len], grown_capacity)).ptr;
|
||||||
|
self.erase = (try io.reallocate(allocator, self.erase.?[0 .. self.values.len], grown_capacity)).ptr;
|
||||||
|
self.capacity = grown_capacity;
|
||||||
|
|
||||||
|
// Add new values to the freelist
|
||||||
|
{
|
||||||
|
var index = @intCast(Index, self.values.len);
|
||||||
|
|
||||||
|
while (index < self.capacity) : (index += 1) {
|
||||||
|
const slot = &self.slots.?[index];
|
||||||
|
|
||||||
|
slot.salt = 1;
|
||||||
|
slot.index = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(self: *Self, allocator: io.Allocator, value: Element) io.AllocationError!KeySlot {
|
||||||
|
if (self.values.len == self.capacity) {
|
||||||
|
try self.grow(allocator, math.max(usize, 1, self.capacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
const index_of_redirect = self.next_free;
|
||||||
|
const redirect = &self.slots.?[index_of_redirect];
|
||||||
|
|
||||||
|
// redirect.index points to the next free slot.
|
||||||
|
self.next_free = redirect.index;
|
||||||
|
redirect.index = @intCast(Index, self.values.len);
|
||||||
|
self.values = self.values.ptr[0 .. self.values.len + 1];
|
||||||
|
self.values[redirect.index] = value;
|
||||||
|
self.erase.?[redirect.index] = index_of_redirect;
|
||||||
|
|
||||||
|
return KeySlot{
|
||||||
|
.index = index_of_redirect,
|
||||||
|
.salt = redirect.salt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(_: *Self, _: u128) bool {
|
||||||
|
// TODO: Implement.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Key = struct {
|
||||||
|
index_bits: usize,
|
||||||
|
salt_bits: usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn Slot(comptime key: Key) type {
|
||||||
|
return extern struct {
|
||||||
|
index: math.Unsigned(key.index_bits),
|
||||||
|
salt: math.Unsigned(key.salt_bits),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const addressable_key = Key{
|
||||||
|
.index_bits = (@bitSizeOf(usize) / 2),
|
||||||
|
.salt_bits = (@bitSizeOf(usize) / 2),
|
||||||
|
};
|
|
@ -6,9 +6,8 @@ const math = @import("./math.zig");
|
||||||
|
|
||||||
pub fn Dense(comptime Element: type) type {
|
pub fn Dense(comptime Element: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
allocator: io.MemoryAllocator,
|
capacity: usize = 0,
|
||||||
capacity: usize,
|
values: []Element = &.{},
|
||||||
values: []Element,
|
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
@ -16,8 +15,8 @@ pub fn Dense(comptime Element: type) type {
|
||||||
self.values = self.values[0 .. 0];
|
self.values = self.values[0 .. 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self, allocator: io.Allocator) void {
|
||||||
self.allocator.deallocate(self.values.ptr);
|
io.deallocate(allocator, self.values);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drop(self: *Self, amount: usize) bool {
|
pub fn drop(self: *Self, amount: usize) bool {
|
||||||
|
@ -28,27 +27,19 @@ pub fn Dense(comptime Element: type) type {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grow(self: *Self, growth_amount: usize) GrowError!void {
|
pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void {
|
||||||
const grown_capacity = self.capacity + growth_amount;
|
const grown_capacity = self.capacity + growth_amount;
|
||||||
|
|
||||||
self.values = (self.allocator.reallocate(Element, self.values.ptr,
|
self.values = (try io.reallocate(allocator, self.values, grown_capacity))[0 .. self.values.len];
|
||||||
grown_capacity) orelse return error.OutOfMemory)[0 .. self.values.len];
|
|
||||||
|
|
||||||
self.capacity = grown_capacity;
|
self.capacity = grown_capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(allocator: io.MemoryAllocator, initial_capacity: usize) !Self {
|
pub fn push_all(self: *Self, allocator: io.Allocator, values: []const Element) io.AllocationError!void {
|
||||||
return Self{
|
|
||||||
.values = (allocator.allocate_many(Element, initial_capacity) orelse return error.OutOfMemory)[0 .. 0],
|
|
||||||
.allocator = allocator,
|
|
||||||
.capacity = initial_capacity,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_all(self: *Self, values: []const Element) GrowError!void {
|
|
||||||
const new_length = self.values.len + values.len;
|
const new_length = self.values.len + values.len;
|
||||||
|
|
||||||
if (new_length >= self.capacity) try self.grow(math.min(usize, new_length, self.capacity));
|
if (new_length >= self.capacity) {
|
||||||
|
try self.grow(allocator, math.min(usize, new_length, self.capacity));
|
||||||
|
}
|
||||||
|
|
||||||
const offset_index = self.values.len;
|
const offset_index = self.values.len;
|
||||||
|
|
||||||
|
@ -61,10 +52,12 @@ pub fn Dense(comptime Element: type) type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_many(self: *Self, value: Element, amount: usize) GrowError!void {
|
pub fn push_many(self: *Self, allocator: io.Allocator, value: Element, amount: usize) io.AllocationError!void {
|
||||||
const new_length = self.values.len + amount;
|
const new_length = self.values.len + amount;
|
||||||
|
|
||||||
if (new_length >= self.capacity) try self.grow(math.min(usize, new_length, self.capacity));
|
if (new_length >= self.capacity) {
|
||||||
|
try self.grow(allocator, math.max(usize, new_length, self.capacity));
|
||||||
|
}
|
||||||
|
|
||||||
const offset_index = self.values.len;
|
const offset_index = self.values.len;
|
||||||
|
|
||||||
|
@ -77,8 +70,10 @@ pub fn Dense(comptime Element: type) type {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_one(self: *Self, value: Element) GrowError!void {
|
pub fn push_one(self: *Self, allocator: io.MemoryAllocator, value: Element) io.AllocationError!void {
|
||||||
if (self.values.len == self.capacity) try self.grow(math.min(usize, 1, self.capacity));
|
if (self.values.len == self.capacity) {
|
||||||
|
try self.grow(allocator, math.max(usize, 1, self.capacity));
|
||||||
|
}
|
||||||
|
|
||||||
const offset_index = self.values.len;
|
const offset_index = self.values.len;
|
||||||
|
|
||||||
|
@ -89,12 +84,8 @@ pub fn Dense(comptime Element: type) type {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const GrowError = error {
|
pub fn as_dense_allocator(stack: *Dense(u8)) io.Allocator {
|
||||||
OutOfMemory
|
return io.Allocator.bind(stack, struct {
|
||||||
};
|
|
||||||
|
|
||||||
pub fn as_dense_allocator(stack: *Dense(u8)) io.MemoryAllocator {
|
|
||||||
return io.MemoryAllocator.bind(stack, struct {
|
|
||||||
pub fn reallocate(writable_stack: *Dense(u8), existing_allocation: ?[*]u8, allocation_size: usize) ?[*]u8 {
|
pub fn reallocate(writable_stack: *Dense(u8), existing_allocation: ?[*]u8, allocation_size: usize) ?[*]u8 {
|
||||||
if (allocation_size == 0) return null;
|
if (allocation_size == 0) return null;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
const io = @import("./io.zig");
|
const io = @import("./io.zig");
|
||||||
|
|
||||||
|
const math = @import("./math.zig");
|
||||||
|
|
||||||
pub fn Hashed(comptime key: Key, comptime Element: type) type {
|
pub fn Hashed(comptime key: Key, comptime Element: type) type {
|
||||||
const Entry = struct {
|
const Entry = struct {
|
||||||
key: key.Element,
|
key: key.Element,
|
||||||
|
@ -7,44 +9,38 @@ pub fn Hashed(comptime key: Key, comptime Element: type) type {
|
||||||
};
|
};
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
allocator: io.MemoryAllocator,
|
entries: []?Entry = &.{},
|
||||||
entries: []?Entry,
|
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn assign(self: *Self, allocator: io.Allocator, key_element: key.Element, value_element: Element) io.AllocationError!void {
|
||||||
|
// TODO: Implement.
|
||||||
|
_ = self;
|
||||||
|
_ = allocator;
|
||||||
|
_ = key_element;
|
||||||
|
_ = value_element;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear(self: *Self) void {
|
pub fn clear(self: *Self) void {
|
||||||
for (self.entries) |*entry| entry.* = null;
|
// TODO: Implement.
|
||||||
|
_ = self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self, allocator: io.MemoryAllocator) void {
|
||||||
self.allocator.deallocate(self.entries.ptr);
|
// TODO: Implement.
|
||||||
|
_ = self;
|
||||||
|
_ = allocator;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(allocator: io.MemoryAllocator) !Self {
|
pub fn insert(self: *Self, key_element: key.Element, value_element: Element) io.AllocationError!bool {
|
||||||
const size = 4;
|
// TODO: Implement.
|
||||||
const entries = (allocator.allocate_many(?Entry, size) orelse return error.OutOfMemory)[0 .. size];
|
|
||||||
|
|
||||||
errdefer allocator.deallocate(entries);
|
|
||||||
|
|
||||||
return Self{
|
|
||||||
.entries = entries,
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn assign(self: *Self, key_element: key.Element, value_element: Element) !void {
|
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = key_element;
|
_ = key_element;
|
||||||
_ = value_element;
|
_ = value_element;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(self: *Self, key_element: key.Element, value_element: Element) !void {
|
pub fn lookup(self: Self, key_element: key.Element) ?Element {
|
||||||
_ = self;
|
// TODO: Implement.
|
||||||
_ = key_element;
|
|
||||||
_ = value_element;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lookup(self: *Self, key_element: key.Element) ?Element {
|
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = key_element;
|
_ = key_element;
|
||||||
|
|
||||||
|
@ -57,6 +53,12 @@ pub const Key = struct {
|
||||||
Element: type,
|
Element: type,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn unsigned_key(comptime bits: comptime_int) Key {
|
||||||
|
return .{
|
||||||
|
.Element = math.Unsigned(bits),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub const string_key = Key{
|
pub const string_key = Key{
|
||||||
.Element = []const u8,
|
.Element = []const u8,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,746 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const tokens = @import("./tokens.zig");
|
|
||||||
|
|
||||||
pub const Chunk = struct {
|
|
||||||
constant_buffer: Buffer,
|
|
||||||
bytecode_buffer: Buffer,
|
|
||||||
constants: Constants,
|
|
||||||
locals: SmallStack(Local, .{.name = "", .depth = 0}) = .{},
|
|
||||||
|
|
||||||
const Buffer = coral.stack.Dense(u8);
|
|
||||||
|
|
||||||
const Constants = coral.stack.Dense(Constant);
|
|
||||||
|
|
||||||
const Local = struct {
|
|
||||||
name: []const u8,
|
|
||||||
depth: u16,
|
|
||||||
|
|
||||||
const empty = Local{ .name = "", .depth = 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn compile(self: *Chunk, script: []const u8) ParseError!void {
|
|
||||||
self.reset();
|
|
||||||
|
|
||||||
var tokenizer = tokens.Tokenizer{.source = script};
|
|
||||||
|
|
||||||
errdefer self.reset();
|
|
||||||
|
|
||||||
var parser = Parser{
|
|
||||||
.chunk = self,
|
|
||||||
.tokenizer = &tokenizer,
|
|
||||||
};
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
parser.step() catch |step_error| switch (step_error) {
|
|
||||||
error.UnexpectedEnd => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
try parser.parse_statement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn declare_local(self: *Chunk, name: []const u8) !void {
|
|
||||||
return self.locals.push(.{
|
|
||||||
.name = name,
|
|
||||||
.depth = 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Chunk) void {
|
|
||||||
self.bytecode_buffer.deinit();
|
|
||||||
self.constant_buffer.deinit();
|
|
||||||
self.constants.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_byte(self: *Chunk, byte: u8) !void {
|
|
||||||
return self.bytecode_buffer.push_one(byte);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_opcode(self: *Chunk, opcode: Opcode) !void {
|
|
||||||
return self.bytecode_buffer.push_one(@enumToInt(opcode));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_operand(self: *Chunk, operand: Operand) !void {
|
|
||||||
return self.bytecode_buffer.push_all(coral.io.bytes_of(&operand));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fetch_byte(self: Chunk, cursor: *usize) ?u8 {
|
|
||||||
if (cursor.* >= self.bytecode_buffer.values.len) return null;
|
|
||||||
|
|
||||||
defer cursor.* += 1;
|
|
||||||
|
|
||||||
return self.bytecode_buffer.values[cursor.*];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fetch_constant(self: Chunk, cursor: *usize) ?*Constant {
|
|
||||||
return &self.constants.values[self.fetch_operand(cursor) orelse return null];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fetch_opcode(self: Chunk, cursor: *usize) ?Opcode {
|
|
||||||
return @intToEnum(Opcode, self.fetch_byte(cursor) orelse return null);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fetch_operand(self: Chunk, cursor: *usize) ?Operand {
|
|
||||||
const operand_size = @sizeOf(Operand);
|
|
||||||
const updated_cursor = cursor.* + operand_size;
|
|
||||||
|
|
||||||
if (updated_cursor > self.bytecode_buffer.values.len) return null;
|
|
||||||
|
|
||||||
var operand_bytes align(@alignOf(Operand)) = [_]u8{0} ** operand_size;
|
|
||||||
|
|
||||||
coral.io.copy(&operand_bytes, self.bytecode_buffer.values[cursor.* .. updated_cursor]);
|
|
||||||
|
|
||||||
cursor.* = updated_cursor;
|
|
||||||
|
|
||||||
return @bitCast(Operand, operand_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(allocator: coral.io.MemoryAllocator) !Chunk {
|
|
||||||
const page_size = 1024;
|
|
||||||
var constant_buffer = try Buffer.init(allocator, page_size);
|
|
||||||
|
|
||||||
errdefer constant_buffer.deinit();
|
|
||||||
|
|
||||||
const assumed_average_bytecode_size = 1024;
|
|
||||||
var bytecode_buffer = try Buffer.init(allocator, assumed_average_bytecode_size);
|
|
||||||
|
|
||||||
errdefer bytecode_buffer.deinit();
|
|
||||||
|
|
||||||
const assumed_average_constant_count = 512;
|
|
||||||
var constants = try Constants.init(allocator, assumed_average_constant_count);
|
|
||||||
|
|
||||||
errdefer constants.deinit();
|
|
||||||
|
|
||||||
return Chunk{
|
|
||||||
.constant_buffer = constant_buffer,
|
|
||||||
.bytecode_buffer = bytecode_buffer,
|
|
||||||
.constants = constants,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn intern_string(self: *Chunk, string: []const u8) !u64 {
|
|
||||||
var constant_slot = @as(u64, 0);
|
|
||||||
|
|
||||||
for (self.constants.values) |interned_constant| {
|
|
||||||
switch (interned_constant) {
|
|
||||||
.string => |interned_string| if (coral.io.equals(interned_string, string)) return constant_slot,
|
|
||||||
}
|
|
||||||
|
|
||||||
constant_slot += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const constant_allocator = coral.stack.as_dense_allocator(&self.constant_buffer);
|
|
||||||
const allocation = constant_allocator.allocate_many(u8, string.len + 1) orelse return error.OutOfMemory;
|
|
||||||
|
|
||||||
errdefer constant_allocator.deallocate(allocation);
|
|
||||||
|
|
||||||
// Zero-terminate string.
|
|
||||||
allocation[string.len] = 0;
|
|
||||||
|
|
||||||
// Write string contents.
|
|
||||||
{
|
|
||||||
const allocated_string = allocation[0 .. string.len];
|
|
||||||
|
|
||||||
coral.io.copy(allocated_string, string);
|
|
||||||
try self.constants.push_one(.{.string = @ptrCast([:0]u8, allocated_string)});
|
|
||||||
}
|
|
||||||
|
|
||||||
return constant_slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(self: *Chunk) void {
|
|
||||||
self.bytecode_buffer.clear();
|
|
||||||
self.constant_buffer.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_local(self: *Chunk, name: []const u8) ?u8 {
|
|
||||||
var count = @as(u8, self.locals.buffer.len);
|
|
||||||
|
|
||||||
while (count != 0) {
|
|
||||||
const index = count - 1;
|
|
||||||
|
|
||||||
if (coral.io.equals(name, self.locals.buffer[index].name)) return index;
|
|
||||||
|
|
||||||
count = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Constant = union (enum) {
|
|
||||||
string: [:0]u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Opcode = enum(u8) {
|
|
||||||
pop,
|
|
||||||
push_nil,
|
|
||||||
push_true,
|
|
||||||
push_false,
|
|
||||||
push_zero,
|
|
||||||
push_integer,
|
|
||||||
push_float,
|
|
||||||
push_string,
|
|
||||||
push_array,
|
|
||||||
push_table,
|
|
||||||
|
|
||||||
not,
|
|
||||||
neg,
|
|
||||||
add,
|
|
||||||
sub,
|
|
||||||
div,
|
|
||||||
mul,
|
|
||||||
|
|
||||||
call,
|
|
||||||
get_index,
|
|
||||||
set_index,
|
|
||||||
get_x,
|
|
||||||
set_x,
|
|
||||||
get_y,
|
|
||||||
set_y,
|
|
||||||
get_global,
|
|
||||||
set_global,
|
|
||||||
get_local,
|
|
||||||
set_local,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Operand = u64;
|
|
||||||
|
|
||||||
pub const ParseError = Parser.StepError || tokens.Token.ExpectError || error {
|
|
||||||
OutOfMemory,
|
|
||||||
IntOverflow,
|
|
||||||
UndefinedLocal,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Parser = struct {
|
|
||||||
chunk: *Chunk,
|
|
||||||
tokenizer: *tokens.Tokenizer,
|
|
||||||
current_token: tokens.Token = .newline,
|
|
||||||
previous_token: tokens.Token = .newline,
|
|
||||||
|
|
||||||
const Operator = enum {
|
|
||||||
not,
|
|
||||||
negate,
|
|
||||||
add,
|
|
||||||
subtract,
|
|
||||||
divide,
|
|
||||||
multiply,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
fn opcode(self: Self) Opcode {
|
|
||||||
return switch (self) {
|
|
||||||
.not => .not,
|
|
||||||
.negate => .neg,
|
|
||||||
.add => .add,
|
|
||||||
.subtract => .sub,
|
|
||||||
.multiply => .mul,
|
|
||||||
.divide => .div,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn precedence(self: Self) isize {
|
|
||||||
return switch (self) {
|
|
||||||
.not => 13,
|
|
||||||
.negate => 13,
|
|
||||||
.add => 11,
|
|
||||||
.subtract => 11,
|
|
||||||
.divide => 12,
|
|
||||||
.multiply => 12,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const OperatorStack = SmallStack(Operator, .not);
|
|
||||||
|
|
||||||
const StepError = error {
|
|
||||||
UnexpectedEnd,
|
|
||||||
};
|
|
||||||
|
|
||||||
const operator_tokens = &.{.symbol_assign, .symbol_plus,
|
|
||||||
.symbol_dash, .symbol_asterisk, .symbol_forward_slash, .symbol_paren_left, .symbol_comma};
|
|
||||||
|
|
||||||
fn parse_expression(self: *Parser) ParseError!void {
|
|
||||||
var operators = OperatorStack{};
|
|
||||||
var local_depth = @as(usize, 0);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
switch (self.current_token) {
|
|
||||||
.keyword_nil => {
|
|
||||||
try self.previous_token.expect_any(operator_tokens);
|
|
||||||
try self.chunk.emit_opcode(.push_nil);
|
|
||||||
|
|
||||||
self.step() catch |step_error| switch (step_error) {
|
|
||||||
error.UnexpectedEnd => return,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_true => {
|
|
||||||
try self.previous_token.expect_any(operator_tokens);
|
|
||||||
try self.chunk.emit_opcode(.push_true);
|
|
||||||
|
|
||||||
self.step() catch |step_error| switch (step_error) {
|
|
||||||
error.UnexpectedEnd => return,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_false => {
|
|
||||||
try self.previous_token.expect_any(operator_tokens);
|
|
||||||
try self.chunk.emit_opcode(.push_false);
|
|
||||||
|
|
||||||
self.step() catch |step_error| switch (step_error) {
|
|
||||||
error.UnexpectedEnd => return,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.integer_literal => |literal| {
|
|
||||||
try self.previous_token.expect_any(operator_tokens);
|
|
||||||
|
|
||||||
const value = coral.utf8.parse_signed(@bitSizeOf(i64), literal)
|
|
||||||
catch |parse_error| switch (parse_error) {
|
|
||||||
error.BadSyntax => unreachable,
|
|
||||||
error.IntOverflow => return error.IntOverflow,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (value == 0) {
|
|
||||||
try self.chunk.emit_opcode(.push_zero);
|
|
||||||
} else {
|
|
||||||
try self.chunk.emit_opcode(.push_integer);
|
|
||||||
try self.chunk.emit_operand(@bitCast(u64, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.step();
|
|
||||||
},
|
|
||||||
|
|
||||||
.real_literal => |literal| {
|
|
||||||
try self.previous_token.expect_any(operator_tokens);
|
|
||||||
|
|
||||||
try self.chunk.emit_operand(@bitCast(u64, coral.utf8.parse_float(@bitSizeOf(f64), literal)
|
|
||||||
catch |parse_error| switch (parse_error) {
|
|
||||||
// Already validated to be a real by the tokenizer so this cannot fail, as real syntax is a
|
|
||||||
// subset of float syntax.
|
|
||||||
error.BadSyntax => unreachable,
|
|
||||||
}));
|
|
||||||
|
|
||||||
try self.step();
|
|
||||||
},
|
|
||||||
|
|
||||||
.string_literal => |literal| {
|
|
||||||
try self.previous_token.expect_any(operator_tokens);
|
|
||||||
try self.chunk.emit_opcode(.push_string);
|
|
||||||
try self.chunk.emit_operand(try self.chunk.intern_string(literal));
|
|
||||||
try self.step();
|
|
||||||
},
|
|
||||||
|
|
||||||
.global_identifier, .local_identifier => {
|
|
||||||
try self.previous_token.expect_any(&.{.symbol_assign, .symbol_plus,
|
|
||||||
.symbol_dash, .symbol_asterisk, .symbol_forward_slash, .symbol_period});
|
|
||||||
|
|
||||||
try self.step();
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_bang => {
|
|
||||||
try self.previous_token.expect_any(operator_tokens);
|
|
||||||
try operators.push(.not);
|
|
||||||
try self.step();
|
|
||||||
|
|
||||||
local_depth = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_plus => {
|
|
||||||
try self.parse_operator(&operators, .add);
|
|
||||||
|
|
||||||
local_depth = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_dash => {
|
|
||||||
try self.parse_operator(&operators, .subtract);
|
|
||||||
|
|
||||||
local_depth = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_asterisk => {
|
|
||||||
try self.parse_operator(&operators, .multiply);
|
|
||||||
|
|
||||||
local_depth = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_forward_slash => {
|
|
||||||
try self.parse_operator(&operators, .divide);
|
|
||||||
|
|
||||||
local_depth = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_period => {
|
|
||||||
switch (self.previous_token) {
|
|
||||||
.global_identifier => |identifier| {
|
|
||||||
try self.chunk.emit_opcode(.get_global);
|
|
||||||
try self.chunk.emit_operand(try self.chunk.intern_string(identifier));
|
|
||||||
},
|
|
||||||
|
|
||||||
.local_identifier => |identifier| {
|
|
||||||
if (local_depth == 0) {
|
|
||||||
try self.chunk.emit_byte(self.chunk.resolve_local(identifier) orelse {
|
|
||||||
return error.UndefinedLocal;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
try self.chunk.emit_opcode(.get_index);
|
|
||||||
try self.chunk.emit_operand(try self.chunk.intern_string(identifier));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return error.UnexpectedToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.step();
|
|
||||||
|
|
||||||
local_depth += 1;
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_paren_left => {
|
|
||||||
switch (self.previous_token) {
|
|
||||||
.local_identifier => |identifier| {
|
|
||||||
if (local_depth == 0) {
|
|
||||||
try self.chunk.emit_byte(self.chunk.resolve_local(identifier) orelse {
|
|
||||||
return error.UndefinedLocal;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
try self.chunk.emit_opcode(.get_index);
|
|
||||||
try self.chunk.emit_operand(try self.chunk.intern_string(identifier));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.global_identifier => |identifier| {
|
|
||||||
try self.chunk.emit_opcode(.get_global);
|
|
||||||
try self.chunk.emit_operand(try self.chunk.intern_string(identifier));
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
try self.parse_expression();
|
|
||||||
try self.previous_token.expect(.symbol_paren_right);
|
|
||||||
try self.step();
|
|
||||||
|
|
||||||
local_depth = 0;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
local_depth += 1;
|
|
||||||
|
|
||||||
var argument_count = @as(Operand, 0);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
try self.step();
|
|
||||||
|
|
||||||
try switch (self.current_token) {
|
|
||||||
.symbol_paren_right => break,
|
|
||||||
else => self.parse_expression(),
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (self.current_token) {
|
|
||||||
.symbol_paren_right => break,
|
|
||||||
.symbol_comma => {},
|
|
||||||
else => return error.UnexpectedToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
argument_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.chunk.emit_opcode(.call);
|
|
||||||
try self.chunk.emit_operand(argument_count);
|
|
||||||
try self.step();
|
|
||||||
|
|
||||||
local_depth = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_brace_left => {
|
|
||||||
const is_call_argument = switch (self.previous_token) {
|
|
||||||
.local_identifier, .global_identifier => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
var field_count = @as(Operand, 0);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
try self.step();
|
|
||||||
|
|
||||||
switch (self.current_token) {
|
|
||||||
.newline => {},
|
|
||||||
|
|
||||||
.local_identifier => {
|
|
||||||
// Create local copy of identifier because step() will overwrite captures.
|
|
||||||
const interned_identifier =
|
|
||||||
try self.chunk.intern_string(self.current_token.local_identifier);
|
|
||||||
|
|
||||||
try self.chunk.emit_opcode(.push_string);
|
|
||||||
try self.chunk.emit_operand(interned_identifier);
|
|
||||||
try self.step();
|
|
||||||
|
|
||||||
switch (self.current_token) {
|
|
||||||
.symbol_assign => {
|
|
||||||
try self.step();
|
|
||||||
try self.parse_expression();
|
|
||||||
|
|
||||||
field_count += 1;
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_brace_right => {
|
|
||||||
try self.chunk.emit_opcode(.push_string);
|
|
||||||
try self.chunk.emit_operand(interned_identifier);
|
|
||||||
|
|
||||||
field_count += 1;
|
|
||||||
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_comma => {
|
|
||||||
try self.chunk.emit_opcode(.push_string);
|
|
||||||
try self.chunk.emit_operand(interned_identifier);
|
|
||||||
|
|
||||||
field_count += 1;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return error.UnexpectedToken,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_brace_right => break,
|
|
||||||
else => return error.UnexpectedToken,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.chunk.emit_opcode(.push_table);
|
|
||||||
try self.chunk.emit_operand(field_count);
|
|
||||||
|
|
||||||
if (is_call_argument) {
|
|
||||||
try self.chunk.emit_opcode(.call);
|
|
||||||
try self.chunk.emit_operand(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.step() catch |step_error| switch (step_error) {
|
|
||||||
error.UnexpectedEnd => return,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
try self.previous_token.expect_any(&.{.keyword_nil, .keyword_true, .keyword_false, .integer_literal,
|
|
||||||
.real_literal, .string_literal, .global_identifier, .local_identifier, .symbol_brace_right,
|
|
||||||
.symbol_paren_right});
|
|
||||||
|
|
||||||
while (operators.pop()) |operator| {
|
|
||||||
try self.chunk.emit_opcode(operator.opcode());
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_operator(self: *Parser, operators: *OperatorStack, rhs_operator: Operator) ParseError!void {
|
|
||||||
try self.previous_token.expect_any(operator_tokens);
|
|
||||||
|
|
||||||
while (operators.pop()) |lhs_operator| {
|
|
||||||
if (rhs_operator.precedence() < lhs_operator.precedence()) break try operators.push(lhs_operator);
|
|
||||||
|
|
||||||
try self.chunk.emit_opcode(lhs_operator.opcode());
|
|
||||||
}
|
|
||||||
|
|
||||||
try operators.push(rhs_operator);
|
|
||||||
try self.step();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_statement(self: *Parser) ParseError!void {
|
|
||||||
var local_depth = @as(usize, 0);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
switch (self.current_token) {
|
|
||||||
.newline => self.step() catch |step_error| switch (step_error) {
|
|
||||||
error.UnexpectedEnd => return,
|
|
||||||
},
|
|
||||||
|
|
||||||
.keyword_return => {
|
|
||||||
try self.previous_token.expect(.newline);
|
|
||||||
|
|
||||||
self.step() catch |step_error| switch (step_error) {
|
|
||||||
error.UnexpectedEnd => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
try self.parse_expression();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
self.step() catch |step_error| switch (step_error) {
|
|
||||||
error.UnexpectedEnd => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
try self.current_token.expect(.newline);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.local_identifier => {
|
|
||||||
try self.previous_token.expect_any(&.{.newline, .symbol_period});
|
|
||||||
try self.step();
|
|
||||||
},
|
|
||||||
|
|
||||||
.global_identifier => {
|
|
||||||
try self.previous_token.expect(.newline);
|
|
||||||
try self.step();
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_period => switch (self.previous_token) {
|
|
||||||
.global_identifier => {
|
|
||||||
// Create local copy of identifier because step() will overwrite captures.
|
|
||||||
const identifier = self.previous_token.global_identifier;
|
|
||||||
|
|
||||||
try self.step();
|
|
||||||
try self.current_token.expect(.local_identifier);
|
|
||||||
try self.chunk.emit_opcode(.get_global);
|
|
||||||
try self.chunk.emit_operand(try self.chunk.intern_string(identifier));
|
|
||||||
|
|
||||||
local_depth += 1;
|
|
||||||
},
|
|
||||||
|
|
||||||
.local_identifier => {
|
|
||||||
// Create local copy of identifier because step() will overwrite captures.
|
|
||||||
const identifier = self.previous_token.local_identifier;
|
|
||||||
|
|
||||||
try self.step();
|
|
||||||
try self.current_token.expect(.local_identifier);
|
|
||||||
|
|
||||||
if (local_depth == 0) {
|
|
||||||
try self.chunk.emit_byte(self.chunk.resolve_local(identifier) orelse {
|
|
||||||
return error.UndefinedLocal;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
try self.chunk.emit_opcode(.get_index);
|
|
||||||
try self.chunk.emit_operand(try self.chunk.intern_string(identifier));
|
|
||||||
}
|
|
||||||
|
|
||||||
local_depth += 1;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return error.UnexpectedToken,
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_assign => {
|
|
||||||
try self.previous_token.expect(.local_identifier);
|
|
||||||
|
|
||||||
const identifier = self.previous_token.local_identifier;
|
|
||||||
|
|
||||||
if (local_depth == 0) {
|
|
||||||
if (self.chunk.resolve_local(identifier)) |local_slot| {
|
|
||||||
try self.chunk.emit_opcode(.set_local);
|
|
||||||
try self.chunk.emit_byte(local_slot);
|
|
||||||
} else {
|
|
||||||
try self.chunk.declare_local(identifier);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try self.chunk.emit_opcode(.set_index);
|
|
||||||
try self.chunk.emit_operand(try self.chunk.intern_string(identifier));
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.step();
|
|
||||||
try self.parse_expression();
|
|
||||||
|
|
||||||
local_depth = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
.symbol_paren_left => {
|
|
||||||
switch (self.previous_token) {
|
|
||||||
.local_identifier => |identifier| {
|
|
||||||
if (local_depth == 0) {
|
|
||||||
try self.chunk.emit_byte(self.chunk.resolve_local(identifier) orelse {
|
|
||||||
return error.UndefinedLocal;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
try self.chunk.emit_opcode(.get_index);
|
|
||||||
try self.chunk.emit_operand(try self.chunk.intern_string(identifier));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.global_identifier => |identifier| {
|
|
||||||
try self.chunk.emit_opcode(.get_global);
|
|
||||||
try self.chunk.emit_operand(try self.chunk.intern_string(identifier));
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return error.UnexpectedToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
var argument_count = @as(Operand, 0);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
try self.step();
|
|
||||||
|
|
||||||
try switch (self.current_token) {
|
|
||||||
.symbol_paren_right => break,
|
|
||||||
else => self.parse_expression(),
|
|
||||||
};
|
|
||||||
|
|
||||||
argument_count += 1;
|
|
||||||
|
|
||||||
switch (self.current_token) {
|
|
||||||
.symbol_paren_right => break,
|
|
||||||
.symbol_comma => {},
|
|
||||||
else => return error.UnexpectedToken,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.chunk.emit_opcode(.call);
|
|
||||||
try self.chunk.emit_operand(argument_count);
|
|
||||||
try self.chunk.emit_opcode(.pop);
|
|
||||||
|
|
||||||
self.step() catch |step_error| switch (step_error) {
|
|
||||||
error.UnexpectedEnd => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
local_depth = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return error.UnexpectedToken,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn step(self: *Parser) StepError!void {
|
|
||||||
self.previous_token = self.current_token;
|
|
||||||
self.current_token = self.tokenizer.next() orelse return error.UnexpectedEnd;
|
|
||||||
|
|
||||||
@import("std").debug.print("{s}\n", .{self.current_token.text()});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn SmallStack(comptime Element: type, comptime default: Element) type {
|
|
||||||
const maximum = 255;
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
buffer: [maximum]Element = [_]Element{default} ** maximum,
|
|
||||||
count: u8 = 0,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
fn peek(self: Self) ?Element {
|
|
||||||
if (self.count == 0) return null;
|
|
||||||
|
|
||||||
return self.buffer[self.count - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop(self: *Self) ?Element {
|
|
||||||
if (self.count == 0) return null;
|
|
||||||
|
|
||||||
self.count -= 1;
|
|
||||||
|
|
||||||
return self.buffer[self.count];
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(self: *Self, element: Element) !void {
|
|
||||||
if (self.count == maximum) return error.OutOfMemory;
|
|
||||||
|
|
||||||
self.buffer[self.count] = element;
|
|
||||||
self.count += 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const SymbolTable = coral.table.Hashed(coral.table.string_key, usize);
|
|
|
@ -1,460 +1 @@
|
||||||
const bytecode = @import("./bytecode.zig");
|
pub const vm = @import("./vm.zig");
|
||||||
|
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
pub const NewError = error {
|
|
||||||
OutOfMemory,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Object = opaque {
|
|
||||||
fn cast(object_instance: *ObjectInstance) *Object {
|
|
||||||
return @ptrCast(*Object, object_instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn userdata(object: *Object) ObjectUserdata {
|
|
||||||
return ObjectInstance.cast(object).userdata;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ObjectBehavior = struct {
|
|
||||||
caller: *const ObjectCaller = default_call,
|
|
||||||
getter: *const ObjectGetter = default_get,
|
|
||||||
setter: *const ObjectSetter = default_set,
|
|
||||||
|
|
||||||
fn default_call(_: *Vm, _: *Object, _: *Object, _: []const Value) RuntimeError!Value {
|
|
||||||
return error.IllegalOperation;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_get(vm: *Vm, object: *Object, index: Value) RuntimeError!Value {
|
|
||||||
return vm.get_field(object, ObjectInstance.cast(try vm.new_string_value(index)).userdata.string);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_set(vm: *Vm, object: *Object, index: Value, value: Value) RuntimeError!void {
|
|
||||||
try vm.set_field(object, ObjectInstance.cast(try vm.new_string_value(index)).userdata.string, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ObjectCaller = fn (vm: *Vm, object: *Object, context: *Object, arguments: []const Value) RuntimeError!Value;
|
|
||||||
|
|
||||||
pub const ObjectGetter = fn (vm: *Vm, object: *Object, index: Value) RuntimeError!Value;
|
|
||||||
|
|
||||||
const ObjectInstance = struct {
|
|
||||||
behavior: ObjectBehavior,
|
|
||||||
userdata: ObjectUserdata,
|
|
||||||
fields: ?ValueTable = null,
|
|
||||||
|
|
||||||
fn cast(object: *Object) *ObjectInstance {
|
|
||||||
return @ptrCast(*ObjectInstance, @alignCast(@alignOf(ObjectInstance), object));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ObjectSetter = fn (vm: *Vm, object: *Object, index: Value, value: Value) RuntimeError!void;
|
|
||||||
|
|
||||||
pub const ObjectUserdata = union (enum) {
|
|
||||||
none,
|
|
||||||
native: *anyopaque,
|
|
||||||
string: []u8,
|
|
||||||
chunk: bytecode.Chunk
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const RuntimeError = NewError || error {
|
|
||||||
StackOverflow,
|
|
||||||
IllegalOperation,
|
|
||||||
UnsupportedOperation,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Value = union(enum) {
|
|
||||||
nil,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
float: Float,
|
|
||||||
integer: Integer,
|
|
||||||
vector2: coral.math.Vector2,
|
|
||||||
object: *Object,
|
|
||||||
|
|
||||||
pub const Integer = i64;
|
|
||||||
|
|
||||||
pub const Float = f64;
|
|
||||||
|
|
||||||
pub fn to_float(self: Value) ?Float {
|
|
||||||
return switch (self) {
|
|
||||||
.float => |float| float,
|
|
||||||
.integer => |integer| @intToFloat(Float, integer),
|
|
||||||
else => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_object(self: Value) ?*Object {
|
|
||||||
return switch (self) {
|
|
||||||
.object => |object| object,
|
|
||||||
else => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_integer(self: Value) ?Integer {
|
|
||||||
return switch (self) {
|
|
||||||
.integer => |integer| integer,
|
|
||||||
// TODO: Verify safety of cast.
|
|
||||||
.float => |float| @floatToInt(Float, float),
|
|
||||||
else => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_vector2(self: Value) ?coral.math.Vector2 {
|
|
||||||
return switch (self) {
|
|
||||||
.vector2 => |vector2| vector2,
|
|
||||||
else => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const ValueTable = coral.table.Hashed(coral.table.string_key, Value);
|
|
||||||
|
|
||||||
pub const Vm = struct {
|
|
||||||
allocator: coral.io.MemoryAllocator,
|
|
||||||
|
|
||||||
heap: struct {
|
|
||||||
count: u32 = 0,
|
|
||||||
free_head: u32 = 0,
|
|
||||||
allocations: []HeapAllocation,
|
|
||||||
global_instance: ObjectInstance,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
},
|
|
||||||
|
|
||||||
stack: struct {
|
|
||||||
top: u32 = 0,
|
|
||||||
values: []Value,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
fn pop(self: *Self) ?Value {
|
|
||||||
if (self.top == 0) return null;
|
|
||||||
|
|
||||||
self.top -= 1;
|
|
||||||
|
|
||||||
return self.values[self.top];
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(self: *Self, value: Value) !void {
|
|
||||||
if (self.top == self.values.len) return error.StackOverflow;
|
|
||||||
|
|
||||||
self.values[self.top] = value;
|
|
||||||
self.top += 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
pub const CompileError = bytecode.ParseError;
|
|
||||||
|
|
||||||
const HeapAllocation = union(enum) {
|
|
||||||
next_free: u32,
|
|
||||||
instance: ObjectInstance,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const InitOptions = struct {
|
|
||||||
stack_max: u32,
|
|
||||||
objects_max: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn call_get(self: *Vm, object: *Object, index: Value, arguments: []const Value) RuntimeError!Value {
|
|
||||||
return switch (self.get(object, index)) {
|
|
||||||
.object => |callable| ObjectInstance.cast(object).behavior.caller(self, callable, object, arguments),
|
|
||||||
else => error.IllegalOperation,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn call_self(self: *Vm, object: *Object, arguments: []const Value) RuntimeError!Value {
|
|
||||||
return ObjectInstance.cast(object).behavior.caller(self, object, self.globals(), arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Vm) void {
|
|
||||||
self.allocator.deallocate(self.heap.allocations.ptr);
|
|
||||||
self.allocator.deallocate(self.stack.values.ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn globals(self: *Vm) *Object {
|
|
||||||
return Object.cast(&self.heap.global_instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(allocator: coral.io.MemoryAllocator, init_options: InitOptions) !Vm {
|
|
||||||
const heap_allocations = (allocator.allocate_many(HeapAllocation,
|
|
||||||
init_options.objects_max) orelse return error.OutOfMemory)[0 .. init_options.objects_max];
|
|
||||||
|
|
||||||
errdefer allocator.deallocate(heap_allocations);
|
|
||||||
|
|
||||||
for (heap_allocations) |*heap_allocation| heap_allocation.* = .{.next_free = 0};
|
|
||||||
|
|
||||||
const values = (allocator.allocate_many(Value, init_options.stack_max) orelse return error.OutOfMemory)[0 .. init_options.stack_max];
|
|
||||||
|
|
||||||
errdefer allocator.deallocate(values);
|
|
||||||
|
|
||||||
for (values) |*value| value.* = .nil;
|
|
||||||
|
|
||||||
const global_values = try ValueTable.init(allocator);
|
|
||||||
|
|
||||||
errdefer global_values.deinit();
|
|
||||||
|
|
||||||
var vm = Vm{
|
|
||||||
.allocator = allocator,
|
|
||||||
.stack = .{.values = values},
|
|
||||||
|
|
||||||
.heap = .{
|
|
||||||
.allocations = heap_allocations,
|
|
||||||
|
|
||||||
.global_instance = .{
|
|
||||||
.behavior = .{},
|
|
||||||
.userdata = .none,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return vm;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: *Vm, index: Value) RuntimeError!Value {
|
|
||||||
return ObjectInstance.cast(self).behavior.getter(self, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_field(_: *Vm, object: *Object, field: []const u8) Value {
|
|
||||||
const fields = &(ObjectInstance.cast(object).fields orelse return .nil);
|
|
||||||
|
|
||||||
return fields.lookup(field) orelse .nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(self: *Vm, object_userdata: ObjectUserdata, object_behavior: ObjectBehavior) NewError!*Object {
|
|
||||||
if (self.heap.count == self.heap.allocations.len) return error.OutOfMemory;
|
|
||||||
|
|
||||||
defer self.heap.count += 1;
|
|
||||||
|
|
||||||
if (self.heap.free_head != self.heap.count) {
|
|
||||||
const free_list_next = self.heap.allocations[self.heap.free_head].next_free;
|
|
||||||
const index = self.heap.free_head;
|
|
||||||
const allocation = &self.heap.allocations[index];
|
|
||||||
|
|
||||||
allocation.* = .{.instance = .{
|
|
||||||
.userdata = object_userdata,
|
|
||||||
.behavior = object_behavior,
|
|
||||||
}};
|
|
||||||
|
|
||||||
self.heap.free_head = free_list_next;
|
|
||||||
|
|
||||||
return Object.cast(&allocation.instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
const allocation = &self.heap.allocations[self.heap.count];
|
|
||||||
|
|
||||||
allocation.* = .{.instance = .{
|
|
||||||
.userdata = object_userdata,
|
|
||||||
.behavior = object_behavior,
|
|
||||||
}};
|
|
||||||
|
|
||||||
self.heap.free_head += 1;
|
|
||||||
|
|
||||||
return Object.cast(&allocation.instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_array(self: *Vm, _: Value.Integer) NewError!*Object {
|
|
||||||
// TODO: Implement.
|
|
||||||
return self.new(.none, .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_closure(self: *Vm, caller: *const ObjectCaller) NewError!*Object {
|
|
||||||
// TODO: Implement.
|
|
||||||
return self.new(.none, .{.caller = caller});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_script(self: *Vm, script_source: []const u8) CompileError!*Object {
|
|
||||||
var chunk = try bytecode.Chunk.init(self.allocator);
|
|
||||||
|
|
||||||
errdefer chunk.deinit();
|
|
||||||
|
|
||||||
try chunk.compile(script_source);
|
|
||||||
|
|
||||||
return self.new(.{.chunk = chunk}, .{
|
|
||||||
.caller = struct {
|
|
||||||
fn chunk_cast(context: *Object) *bytecode.Chunk {
|
|
||||||
return @ptrCast(*bytecode.Chunk, @alignCast(@alignOf(bytecode.Chunk), context.userdata().native));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(vm: *Vm, object: *Object, _: *Object, arguments: []const Value) RuntimeError!Value {
|
|
||||||
return execute_chunk(chunk_cast(object).*, vm, arguments);
|
|
||||||
}
|
|
||||||
}.call,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_string(self: *Vm, string_data: []const u8) NewError!*Object {
|
|
||||||
return self.new(.{.string = allocate_copy: {
|
|
||||||
if (string_data.len == 0) break: allocate_copy &.{};
|
|
||||||
|
|
||||||
const string_copy = (self.allocator.allocate_many(
|
|
||||||
u8, string_data.len) orelse return error.OutOfMemory)[0 .. string_data.len];
|
|
||||||
|
|
||||||
coral.io.copy(string_copy, string_data);
|
|
||||||
|
|
||||||
break: allocate_copy string_copy;
|
|
||||||
}}, .{
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_string_value(self: *Vm, value: Value) NewError!*Object {
|
|
||||||
// TODO: Implement.
|
|
||||||
return switch (value) {
|
|
||||||
.nil => self.new_string(""),
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_table(self: *Vm) NewError!*Object {
|
|
||||||
// TODO: Implement.
|
|
||||||
return self.new(.none, .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(self: *Vm, object: *Object, index: Value, value: Value) RuntimeError!void {
|
|
||||||
return ObjectInstance.cast(object).behavior.setter(self, object, index, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_field(self: *Vm, object: *Object, field: []const u8, value: Value) NewError!void {
|
|
||||||
const object_instance = ObjectInstance.cast(object);
|
|
||||||
|
|
||||||
if (object_instance.fields == null) object_instance.fields = try ValueTable.init(self.allocator);
|
|
||||||
|
|
||||||
try object_instance.fields.?.assign(field, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn execute_chunk(chunk: bytecode.Chunk, vm: *Vm, arguments: []const Value) RuntimeError!Value {
|
|
||||||
const old_stack_top = vm.stack.top;
|
|
||||||
|
|
||||||
errdefer vm.stack.top = old_stack_top;
|
|
||||||
|
|
||||||
for (arguments) |argument| try vm.stack.push(argument);
|
|
||||||
|
|
||||||
if (arguments.len > coral.math.max_int(Value.Integer)) return error.IllegalOperation;
|
|
||||||
|
|
||||||
try vm.stack.push(.{.integer = @intCast(Value.Integer, arguments.len)});
|
|
||||||
|
|
||||||
{
|
|
||||||
var cursor = @as(usize, 0);
|
|
||||||
|
|
||||||
while (chunk.fetch_opcode(&cursor)) |code| switch (code) {
|
|
||||||
.push_nil => try vm.stack.push(.nil),
|
|
||||||
.push_true => try vm.stack.push(.true),
|
|
||||||
.push_false => try vm.stack.push(.false),
|
|
||||||
.push_zero => try vm.stack.push(.{.integer = 0}),
|
|
||||||
|
|
||||||
.push_integer => try vm.stack.push(.{
|
|
||||||
.integer = @bitCast(Value.Integer, chunk.fetch_operand(&cursor) orelse {
|
|
||||||
return error.IllegalOperation;
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
|
|
||||||
.push_float => try vm.stack.push(.{.float = @bitCast(Value.Float, chunk.fetch_operand(&cursor) orelse {
|
|
||||||
return error.IllegalOperation;
|
|
||||||
})}),
|
|
||||||
|
|
||||||
.push_string => {
|
|
||||||
const constant = chunk.fetch_constant(&cursor) orelse {
|
|
||||||
return error.IllegalOperation;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (constant.* != .string) return error.IllegalOperation;
|
|
||||||
|
|
||||||
// TODO: Implement string behavior.
|
|
||||||
try vm.stack.push(.{.object = try vm.new(.{.string = constant.string}, .{})});
|
|
||||||
},
|
|
||||||
|
|
||||||
.push_array => {
|
|
||||||
const element_count = @bitCast(Value.Integer,
|
|
||||||
chunk.fetch_operand(&cursor) orelse return error.IllegalOperation);
|
|
||||||
|
|
||||||
const array_object = try vm.new_array(element_count);
|
|
||||||
|
|
||||||
{
|
|
||||||
var element_index = Value{.integer = 0};
|
|
||||||
var array_start = @intCast(Value.Integer, vm.stack.top) - element_count;
|
|
||||||
|
|
||||||
while (element_index.integer < element_count) : (element_index.integer += 1) {
|
|
||||||
try vm.set(array_object, element_index, vm.stack.values[
|
|
||||||
@intCast(usize, array_start + element_index.integer)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.stack.top = @intCast(u32, array_start);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.push_table => {
|
|
||||||
const field_count = chunk.fetch_operand(&cursor) orelse return error.IllegalOperation;
|
|
||||||
|
|
||||||
if (field_count > coral.math.max_int(Value.Integer)) return error.OutOfMemory;
|
|
||||||
|
|
||||||
const table_object = try vm.new_table();
|
|
||||||
|
|
||||||
{
|
|
||||||
var field_index = @as(bytecode.Operand, 0);
|
|
||||||
|
|
||||||
while (field_index < field_count) : (field_index += 1) {
|
|
||||||
// Assigned to temporaries to explicitly preserve stack popping order.
|
|
||||||
const field_key = vm.stack.pop() orelse return error.IllegalOperation;
|
|
||||||
const field_value = vm.stack.pop() orelse return error.IllegalOperation;
|
|
||||||
|
|
||||||
try vm.set(table_object, field_key, field_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try vm.stack.push(.{.object = table_object});
|
|
||||||
},
|
|
||||||
|
|
||||||
.get_local => {
|
|
||||||
try vm.stack.push(vm.stack.values[
|
|
||||||
vm.stack.top - (chunk.fetch_byte(&cursor) orelse return error.IllegalOperation)]);
|
|
||||||
},
|
|
||||||
|
|
||||||
.get_global => {
|
|
||||||
const field = chunk.fetch_constant(&cursor) orelse return error.IllegalOperation;
|
|
||||||
|
|
||||||
if (field.* != .string) return error.IllegalOperation;
|
|
||||||
|
|
||||||
try vm.stack.push(vm.get_field(vm.globals(), field.string));
|
|
||||||
},
|
|
||||||
|
|
||||||
.not => {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
// .neg,
|
|
||||||
// .add,
|
|
||||||
// .sub,
|
|
||||||
// .div,
|
|
||||||
// .mul,
|
|
||||||
|
|
||||||
// .call,
|
|
||||||
// .set,
|
|
||||||
// .get,
|
|
||||||
|
|
||||||
else => return error.IllegalOperation,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const return_value = vm.stack.pop() orelse return error.IllegalOperation;
|
|
||||||
|
|
||||||
vm.stack.top = coral.math.checked_sub(vm.stack.top, @intCast(u32, arguments.len + 1)) catch |sub_error| {
|
|
||||||
switch (sub_error) {
|
|
||||||
error.IntOverflow => return error.IllegalOperation,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return return_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn object_argument(_: *Vm, arguments: []const Value, argument_index: usize) RuntimeError!*Object {
|
|
||||||
// TODO: Record error message in Vm.
|
|
||||||
if (argument_index >= arguments.len) return error.IllegalOperation;
|
|
||||||
|
|
||||||
const argument = arguments[argument_index];
|
|
||||||
|
|
||||||
if (argument != .object) return error.IllegalOperation;
|
|
||||||
|
|
||||||
return argument.object;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,295 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
pub const Token = union(enum) {
|
|
||||||
unknown: u8,
|
|
||||||
newline,
|
|
||||||
|
|
||||||
global_identifier: []const u8,
|
|
||||||
local_identifier: []const u8,
|
|
||||||
|
|
||||||
symbol_assign,
|
|
||||||
symbol_plus,
|
|
||||||
symbol_dash,
|
|
||||||
symbol_asterisk,
|
|
||||||
symbol_forward_slash,
|
|
||||||
symbol_paren_left,
|
|
||||||
symbol_paren_right,
|
|
||||||
symbol_bang,
|
|
||||||
symbol_comma,
|
|
||||||
symbol_at,
|
|
||||||
symbol_brace_left,
|
|
||||||
symbol_brace_right,
|
|
||||||
symbol_bracket_left,
|
|
||||||
symbol_bracket_right,
|
|
||||||
symbol_period,
|
|
||||||
|
|
||||||
integer_literal: []const u8,
|
|
||||||
real_literal: []const u8,
|
|
||||||
string_literal: []const u8,
|
|
||||||
|
|
||||||
keyword_nil,
|
|
||||||
keyword_false,
|
|
||||||
keyword_true,
|
|
||||||
keyword_return,
|
|
||||||
keyword_self,
|
|
||||||
|
|
||||||
pub const ExpectError = error {
|
|
||||||
UnexpectedToken,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn expect(self: Token, tag: coral.io.Tag(Token)) ExpectError!void {
|
|
||||||
if (self != tag) return error.UnexpectedToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expect_any(self: Token, tags: []const coral.io.Tag(Token)) ExpectError!void {
|
|
||||||
for (tags) |tag| {
|
|
||||||
if (self == tag) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return error.UnexpectedToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text(self: Token) []const u8 {
|
|
||||||
return switch (self) {
|
|
||||||
.unknown => |unknown| @ptrCast([*]const u8, &unknown)[0 .. 1],
|
|
||||||
.newline => "newline",
|
|
||||||
.global_identifier => |identifier| identifier,
|
|
||||||
.local_identifier => |identifier| identifier,
|
|
||||||
|
|
||||||
.symbol_assign => "=",
|
|
||||||
.symbol_plus => "+",
|
|
||||||
.symbol_dash => "-",
|
|
||||||
.symbol_asterisk => "*",
|
|
||||||
.symbol_forward_slash => "/",
|
|
||||||
.symbol_paren_left => "(",
|
|
||||||
.symbol_paren_right => ")",
|
|
||||||
.symbol_bang => "!",
|
|
||||||
.symbol_comma => ",",
|
|
||||||
.symbol_at => "@",
|
|
||||||
.symbol_brace_left => "{",
|
|
||||||
.symbol_brace_right => "}",
|
|
||||||
.symbol_bracket_left => "[",
|
|
||||||
.symbol_bracket_right => "]",
|
|
||||||
.symbol_period => ".",
|
|
||||||
|
|
||||||
.integer_literal => |literal| literal,
|
|
||||||
.real_literal => |literal| literal,
|
|
||||||
.string_literal => |literal| literal,
|
|
||||||
|
|
||||||
.keyword_nil => "nil",
|
|
||||||
.keyword_false => "false",
|
|
||||||
.keyword_true => "true",
|
|
||||||
.keyword_return => "return",
|
|
||||||
.keyword_self => "self",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Tokenizer = struct {
|
|
||||||
source: []const u8,
|
|
||||||
cursor: usize = 0,
|
|
||||||
|
|
||||||
pub fn has_next(self: Tokenizer) bool {
|
|
||||||
return self.cursor < self.source.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(self: *Tokenizer) ?Token {
|
|
||||||
while (self.has_next()) switch (self.source[self.cursor]) {
|
|
||||||
' ', '\t' => self.cursor += 1,
|
|
||||||
|
|
||||||
'\n' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .newline;
|
|
||||||
},
|
|
||||||
|
|
||||||
'0' ... '9' => {
|
|
||||||
const begin = self.cursor;
|
|
||||||
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
while (self.has_next()) switch (self.source[self.cursor]) {
|
|
||||||
'0' ... '9' => self.cursor += 1,
|
|
||||||
|
|
||||||
'.' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
while (self.has_next()) switch (self.source[self.cursor]) {
|
|
||||||
'0' ... '9' => self.cursor += 1,
|
|
||||||
else => break,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Token{.real_literal = self.source[begin .. self.cursor]};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => break,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Token{.integer_literal = self.source[begin .. self.cursor]};
|
|
||||||
},
|
|
||||||
|
|
||||||
'A' ... 'Z', 'a' ... 'z', '_' => {
|
|
||||||
const begin = self.cursor;
|
|
||||||
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
while (self.cursor < self.source.len) switch (self.source[self.cursor]) {
|
|
||||||
'0'...'9', 'A'...'Z', 'a'...'z', '_' => self.cursor += 1,
|
|
||||||
else => break,
|
|
||||||
};
|
|
||||||
|
|
||||||
const identifier = self.source[begin..self.cursor];
|
|
||||||
|
|
||||||
coral.debug.assert(identifier.len != 0);
|
|
||||||
|
|
||||||
switch (identifier[0]) {
|
|
||||||
'n' => if (coral.io.ends_with(identifier, "il")) return .keyword_nil,
|
|
||||||
'f' => if (coral.io.ends_with(identifier, "alse")) return .keyword_false,
|
|
||||||
't' => if (coral.io.ends_with(identifier, "rue")) return .keyword_true,
|
|
||||||
'r' => if (coral.io.ends_with(identifier, "eturn")) return .keyword_return,
|
|
||||||
's' => if (coral.io.ends_with(identifier, "elf")) return .keyword_self,
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
return Token{.local_identifier = identifier};
|
|
||||||
},
|
|
||||||
|
|
||||||
'@' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
if (self.has_next()) switch (self.source[self.cursor]) {
|
|
||||||
'A'...'Z', 'a'...'z', '_' => {
|
|
||||||
const begin = self.cursor;
|
|
||||||
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
while (self.has_next()) switch (self.source[self.cursor]) {
|
|
||||||
'0'...'9', 'A'...'Z', 'a'...'z', '_' => self.cursor += 1,
|
|
||||||
else => break,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Token{.global_identifier = self.source[begin..self.cursor]};
|
|
||||||
},
|
|
||||||
|
|
||||||
'"' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
const begin = self.cursor;
|
|
||||||
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
while (self.has_next()) switch (self.source[self.cursor]) {
|
|
||||||
'"' => break,
|
|
||||||
else => self.cursor += 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
defer self.cursor += 1;
|
|
||||||
|
|
||||||
return Token{.global_identifier = self.source[begin..self.cursor]};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {},
|
|
||||||
};
|
|
||||||
|
|
||||||
return .symbol_at;
|
|
||||||
},
|
|
||||||
|
|
||||||
'"' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
const begin = self.cursor;
|
|
||||||
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
while (self.has_next()) switch (self.source[self.cursor]) {
|
|
||||||
'"' => break,
|
|
||||||
else => self.cursor += 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
defer self.cursor += 1;
|
|
||||||
|
|
||||||
return Token{.string_literal = self.source[begin..self.cursor]};
|
|
||||||
},
|
|
||||||
|
|
||||||
'{' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_brace_left;
|
|
||||||
},
|
|
||||||
|
|
||||||
'}' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_brace_right;
|
|
||||||
},
|
|
||||||
|
|
||||||
',' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_comma;
|
|
||||||
},
|
|
||||||
|
|
||||||
'!' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_bang;
|
|
||||||
},
|
|
||||||
|
|
||||||
')' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_paren_right;
|
|
||||||
},
|
|
||||||
|
|
||||||
'(' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_paren_left;
|
|
||||||
},
|
|
||||||
|
|
||||||
'/' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_forward_slash;
|
|
||||||
},
|
|
||||||
|
|
||||||
'*' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_asterisk;
|
|
||||||
},
|
|
||||||
|
|
||||||
'-' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_dash;
|
|
||||||
},
|
|
||||||
|
|
||||||
'+' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_plus;
|
|
||||||
},
|
|
||||||
|
|
||||||
'=' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_assign;
|
|
||||||
},
|
|
||||||
|
|
||||||
'.' => {
|
|
||||||
self.cursor += 1;
|
|
||||||
|
|
||||||
return .symbol_period;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
defer self.cursor += 1;
|
|
||||||
|
|
||||||
return Token{.unknown = self.source[self.cursor]};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -0,0 +1,321 @@
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
pub const CompileError = coral.io.AllocationError || error {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Environment = struct {
|
||||||
|
allocator: coral.io.Allocator,
|
||||||
|
globals: ValueTable,
|
||||||
|
stack: coral.stack.Dense(Value),
|
||||||
|
calls: coral.stack.Dense(CallFrame),
|
||||||
|
|
||||||
|
const CallFrame = struct {
|
||||||
|
object: ?*Object,
|
||||||
|
ip: usize,
|
||||||
|
slots: []Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const InitOptions = struct {
|
||||||
|
stack_max: u32,
|
||||||
|
calls_max: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NewOptions = struct {
|
||||||
|
userdata_size: usize = 0,
|
||||||
|
userinfo: usize = 0,
|
||||||
|
behavior: *const Object.Behavior = &.{},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NewScriptOptions = struct {
|
||||||
|
name: []const u8,
|
||||||
|
data: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn call(self: *Environment, object: *Object, arguments: []const Value) RuntimeError!Value {
|
||||||
|
var global_object = Object{
|
||||||
|
.ref_count = 0,
|
||||||
|
.userinfo = 0,
|
||||||
|
.userdata = &.{},
|
||||||
|
.fields = self.globals,
|
||||||
|
.behavior = &.{},
|
||||||
|
};
|
||||||
|
|
||||||
|
return object.behavior.caller(.{
|
||||||
|
.environment = self,
|
||||||
|
.arguments = arguments,
|
||||||
|
.object = &global_object,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Environment) void {
|
||||||
|
self.stack.deinit(self.allocator);
|
||||||
|
self.calls.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn global_set(self: *Environment, global_name: []const u8, value: Value) coral.io.AllocationError!void {
|
||||||
|
try self.globals.assign(self.allocator, global_name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: coral.io.Allocator, init_options: InitOptions) !Environment {
|
||||||
|
var environment = Environment{
|
||||||
|
.allocator = allocator,
|
||||||
|
.globals = .{},
|
||||||
|
.stack = .{},
|
||||||
|
.calls = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
environment.stack.deinit(allocator);
|
||||||
|
environment.calls.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
try environment.stack.grow(allocator, init_options.stack_max);
|
||||||
|
try environment.calls.grow(allocator, init_options.calls_max);
|
||||||
|
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(self: *Environment, options: NewOptions) coral.io.AllocationError!*Object {
|
||||||
|
const object = try coral.io.allocate_one(Object, self.allocator);
|
||||||
|
|
||||||
|
errdefer coral.io.deallocate(self.allocator, object);
|
||||||
|
|
||||||
|
const userdata = try coral.io.allocate_many(u8, options.userdata_size, self.allocator);
|
||||||
|
|
||||||
|
errdefer coral.io.deallocate(self.allocator, userdata);
|
||||||
|
|
||||||
|
object.* = .{
|
||||||
|
.userdata = userdata,
|
||||||
|
.ref_count = 1,
|
||||||
|
.userinfo = options.userinfo,
|
||||||
|
.behavior = options.behavior,
|
||||||
|
.fields = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_array(self: *Environment) coral.io.AllocationError!*Object {
|
||||||
|
// TODO: Implement.
|
||||||
|
return self.new(.none, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_script(self: *Environment, options: NewScriptOptions) RuntimeError!*Object {
|
||||||
|
// TODO: Implement.
|
||||||
|
_ = self;
|
||||||
|
_ = options;
|
||||||
|
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_string(self: *Environment, string_data: []const u8) coral.io.AllocationError!*Object {
|
||||||
|
const string_behavior = &.{};
|
||||||
|
|
||||||
|
if (string_data.len == 0) {
|
||||||
|
// Empty string.
|
||||||
|
return self.new(.{.behavior = string_behavior});
|
||||||
|
}
|
||||||
|
|
||||||
|
const string_object = try self.new(.{
|
||||||
|
.userdata_size = string_data.len,
|
||||||
|
.behavior = string_behavior
|
||||||
|
});
|
||||||
|
|
||||||
|
errdefer self.release(string_object);
|
||||||
|
|
||||||
|
coral.io.copy(string_object.userdata, string_data);
|
||||||
|
|
||||||
|
return string_object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_string_from_float(self: *Environment, float: Float) coral.io.AllocationError!*Object {
|
||||||
|
// TODO: Implement.
|
||||||
|
_ = float;
|
||||||
|
|
||||||
|
return self.new_string("0.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_string_from_integer(self: *Environment, integer: Integer) coral.io.AllocationError!*Object {
|
||||||
|
// TODO: Implement.
|
||||||
|
_ = integer;
|
||||||
|
|
||||||
|
return self.new_string("0");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_string_from_object(self: *Environment, object: *Object) coral.io.AllocationError!*Object {
|
||||||
|
// TODO: Implement.
|
||||||
|
_ = object;
|
||||||
|
|
||||||
|
return self.new_string("");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_table(self: *Environment) coral.io.AllocationError!*Object {
|
||||||
|
// TODO: Implement.
|
||||||
|
return self.new(.none, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw_get(_: Environment, object: *Object, field_name: []const u8) Value {
|
||||||
|
return object.fields.lookup(field_name) orelse .nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raw_set(environment: Environment, object: *Object, field_name: []const u8, value: Value) coral.io.AllocationError!void {
|
||||||
|
try object.fields.assign(environment.allocator, field_name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(self: Environment, object: *Object) void {
|
||||||
|
coral.debug.assert(object.ref_count != 0);
|
||||||
|
|
||||||
|
object.ref_count -= 1;
|
||||||
|
|
||||||
|
if (object.ref_count == 0) {
|
||||||
|
coral.io.deallocate(self.allocator, object.userdata);
|
||||||
|
coral.io.deallocate(self.allocator, object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn virtual_call(self: *Environment, object: *Object, index: Value, arguments: []const Value) RuntimeError!Value {
|
||||||
|
const value = try self.virtual_get(object, index);
|
||||||
|
|
||||||
|
switch (value) {
|
||||||
|
.object => |callee| {
|
||||||
|
defer self.release(callee);
|
||||||
|
|
||||||
|
return callee.behavior.caller(.{
|
||||||
|
.environment = self,
|
||||||
|
.object = callee,
|
||||||
|
.caller = object,
|
||||||
|
.arguments = arguments,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return error.InvalidOperation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn virtual_get(self: *Environment, object: *Object, index: Value) RuntimeError!Value {
|
||||||
|
return object.behavior.getter(.{
|
||||||
|
.environment = self,
|
||||||
|
.object = object,
|
||||||
|
.index = index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn virtual_set(self: *Environment, object: *Object, index: Value, value: Value) RuntimeError!void {
|
||||||
|
return object.behavior.setter(.{
|
||||||
|
.environment = self,
|
||||||
|
.object = object,
|
||||||
|
.index = index,
|
||||||
|
.value = value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Float = f32;
|
||||||
|
|
||||||
|
pub const Integer = i32;
|
||||||
|
|
||||||
|
pub const Object = struct {
|
||||||
|
ref_count: usize,
|
||||||
|
userinfo: usize,
|
||||||
|
userdata: []u8,
|
||||||
|
behavior: *const Behavior,
|
||||||
|
fields: ValueTable,
|
||||||
|
|
||||||
|
pub const Behavior = struct {
|
||||||
|
caller: *const Caller = default_call,
|
||||||
|
getter: *const Getter = default_get,
|
||||||
|
setter: *const Setter = default_set,
|
||||||
|
destructor: *const Destructor = default_destruct,
|
||||||
|
|
||||||
|
fn default_call(_: CallContext) RuntimeError!Value {
|
||||||
|
return error.InvalidOperation;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_destruct(_: DestructContext) void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_get(context: GetContext) RuntimeError!Value {
|
||||||
|
const index = try switch (context.index) {
|
||||||
|
.object => |object| context.environment.new_string_from_object(object),
|
||||||
|
.integer => |integer| context.environment.new_string_from_integer(integer),
|
||||||
|
.float => |float| context.environment.new_string_from_float(float),
|
||||||
|
else => return error.InvalidOperation,
|
||||||
|
};
|
||||||
|
|
||||||
|
defer context.environment.release(index);
|
||||||
|
|
||||||
|
// A string is just a serious of bytes (i.e. userdata with no userinfo).
|
||||||
|
coral.debug.assert(index.userinfo == 0);
|
||||||
|
|
||||||
|
return context.object.fields.lookup(index.userdata) orelse .nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_set(_: SetContext) RuntimeError!void {
|
||||||
|
return error.InvalidOperation;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CallContext = struct {
|
||||||
|
environment: *Environment,
|
||||||
|
object: *Object,
|
||||||
|
arguments: []const Value,
|
||||||
|
|
||||||
|
pub fn check(self: CallContext, condition: bool, failure_message: []const u8) RuntimeError!void {
|
||||||
|
if (condition) return;
|
||||||
|
|
||||||
|
// TODO: Emit failure message.
|
||||||
|
_ = self;
|
||||||
|
_ = failure_message;
|
||||||
|
|
||||||
|
return error.CheckFailure;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Caller = fn (context: CallContext) RuntimeError!Value;
|
||||||
|
|
||||||
|
pub const DestructContext = struct {
|
||||||
|
environment: *Environment,
|
||||||
|
object: *Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Destructor = fn (context: DestructContext) void;
|
||||||
|
|
||||||
|
pub const GetContext = struct {
|
||||||
|
environment: *Environment,
|
||||||
|
object: *const Object,
|
||||||
|
index: Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Getter = fn (context: GetContext) RuntimeError!Value;
|
||||||
|
|
||||||
|
pub const SetContext = struct {
|
||||||
|
environment: *Environment,
|
||||||
|
object: *Object,
|
||||||
|
index: Value,
|
||||||
|
value: Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Setter = fn (context: SetContext) RuntimeError!void;
|
||||||
|
|
||||||
|
pub fn as_value(self: *Object) Value {
|
||||||
|
return .{.object = self};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RuntimeError = coral.io.AllocationError || error {
|
||||||
|
InvalidOperation,
|
||||||
|
CheckFailure,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Value = union(enum) {
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
float: Float,
|
||||||
|
integer: Integer,
|
||||||
|
object: *Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueTable = coral.table.Hashed(coral.table.string_key, Value);
|
|
@ -1,230 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const ona = @import("ona");
|
|
||||||
|
|
||||||
pub const Archive = struct {
|
|
||||||
state_table: [state_max]State = [_]State{.{}} ** state_max,
|
|
||||||
file_accessor: ona.files.FileAccessor,
|
|
||||||
file_path: []const u8,
|
|
||||||
|
|
||||||
const State = struct {
|
|
||||||
readable_file: ?*ona.files.ReadableFile = null,
|
|
||||||
data_head: u64 = 0,
|
|
||||||
data_size: u64 = 0,
|
|
||||||
data_cursor: u64 = 0,
|
|
||||||
|
|
||||||
fn cast(archived_file: *ArchivedFile) *Archive.State {
|
|
||||||
return @ptrCast(*State, @alignCast(@alignOf(State), archived_file));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const state_max = 64;
|
|
||||||
|
|
||||||
pub fn open_archived(self: *Archive, path: Path) OpenError!*ArchivedFile {
|
|
||||||
const state_index = find_available_state: {
|
|
||||||
var index: usize = 0;
|
|
||||||
|
|
||||||
while (index < self.state_table.len) : (index += 1) {
|
|
||||||
if (self.state_table[index].readable_file == null) break :find_available_state index;
|
|
||||||
}
|
|
||||||
|
|
||||||
return error.TooManyFiles;
|
|
||||||
};
|
|
||||||
|
|
||||||
const archive_file = try self.file_accessor.open_readable(self.file_path);
|
|
||||||
|
|
||||||
errdefer _ = archive_file.close();
|
|
||||||
|
|
||||||
var archive_header = Header.empty;
|
|
||||||
|
|
||||||
if ((try archive_file.read(&archive_header.bytes)) != Header.size) return error.ArchiveInvalid;
|
|
||||||
|
|
||||||
// Read file table.
|
|
||||||
var head: u64 = 0;
|
|
||||||
var tail: u64 = archive_header.layout.entry_count - 1;
|
|
||||||
const path_hash = path.hash();
|
|
||||||
|
|
||||||
while (head <= tail) {
|
|
||||||
const midpoint = head + ((tail - head) / 2);
|
|
||||||
var archive_block = Block.empty;
|
|
||||||
|
|
||||||
try archive_file.seek(Header.size + archive_header.layout.total_data_size + (Block.size * midpoint));
|
|
||||||
|
|
||||||
if ((try archive_file.read(&archive_block.bytes)) != Block.size) return error.ArchiveInvalid;
|
|
||||||
|
|
||||||
const path_hash_comparison = path_hash - archive_block.layout.path_hash;
|
|
||||||
|
|
||||||
if (path_hash_comparison > 0) {
|
|
||||||
head = (midpoint + 1);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path_hash_comparison < 0) {
|
|
||||||
tail = (midpoint - 1);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const path_comparison = path.compare(archive_block.layout.path);
|
|
||||||
|
|
||||||
if (path_comparison > 0) {
|
|
||||||
head = (midpoint + 1);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path_comparison < 0) {
|
|
||||||
tail = (midpoint - 1);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.state_table[state_index] = .{
|
|
||||||
.readable_file = archive_file,
|
|
||||||
.data_head = archive_block.layout.data_head,
|
|
||||||
.data_size = archive_block.layout.data_size,
|
|
||||||
.data_cursor = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
return @ptrCast(*ArchivedFile, &(self.state_table[state_index]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return error.FileNotFound;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ArchivedFile = opaque {
|
|
||||||
pub fn as_reader(self: *ArchivedFile) coral.io.Reader {
|
|
||||||
return coral.io.Reader.bind(self, ArchivedFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn close(self: *ArchivedFile) bool {
|
|
||||||
const state = Archive.State.cast(self);
|
|
||||||
|
|
||||||
if (state.readable_file) |readable_file| {
|
|
||||||
defer state.readable_file = null;
|
|
||||||
|
|
||||||
return readable_file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read(self: *ArchivedFile, buffer: []u8) coral.io.ReadError!usize {
|
|
||||||
const state = Archive.State.cast(self);
|
|
||||||
|
|
||||||
if (state.readable_file) |readable_file| {
|
|
||||||
const actual_cursor = coral.math.min(u64,
|
|
||||||
state.data_head + state.data_cursor, state.data_head + state.data_size);
|
|
||||||
|
|
||||||
try readable_file.seek(actual_cursor);
|
|
||||||
|
|
||||||
const buffer_read = coral.math.min(usize, buffer.len, state.data_size - actual_cursor);
|
|
||||||
|
|
||||||
defer state.data_cursor += buffer_read;
|
|
||||||
|
|
||||||
return readable_file.read(buffer[0..buffer_read]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return error.IoUnavailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(self: *ArchivedFile) u64 {
|
|
||||||
return Archive.State.cast(self).data_size;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Block = extern union {
|
|
||||||
layout: extern struct {
|
|
||||||
path: Path,
|
|
||||||
path_hash: u64,
|
|
||||||
data_head: u64,
|
|
||||||
data_size: u64,
|
|
||||||
},
|
|
||||||
|
|
||||||
bytes: [size]u8,
|
|
||||||
|
|
||||||
const empty = Block{ .bytes = [_]u8{0} ** size };
|
|
||||||
|
|
||||||
const size = 512;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Header = extern union {
|
|
||||||
layout: extern struct {
|
|
||||||
signature: [signature_magic.len]u8,
|
|
||||||
entry_count: u32,
|
|
||||||
total_data_size: u64,
|
|
||||||
},
|
|
||||||
|
|
||||||
bytes: [size]u8,
|
|
||||||
|
|
||||||
const empty = Header{ .bytes = [_]u8{0} ** size };
|
|
||||||
|
|
||||||
const signature_magic = [_]u8{ 'o', 'a', 'r', 1 };
|
|
||||||
|
|
||||||
const size = 16;
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const OpenError = ona.files.OpenError || coral.io.ReadError || error{
|
|
||||||
ArchiveInvalid,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Path = extern struct {
|
|
||||||
buffer: [maximum + 1]u8,
|
|
||||||
|
|
||||||
pub const DataError = error{
|
|
||||||
PathCorrupt,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ParseError = error{
|
|
||||||
TooLong,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn compare(self: Path, other: Path) isize {
|
|
||||||
return coral.io.compare(&self.buffer, &other.buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data(self: Path) DataError![:0]const u8 {
|
|
||||||
// Verify presence of zero terminator.
|
|
||||||
if (self.buffer[self.filled()] != 0) return error.PathCorrupt;
|
|
||||||
|
|
||||||
return @ptrCast([:0]const u8, self.buffer[0..self.filled()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn filled(self: Path) usize {
|
|
||||||
return maximum - self.remaining();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hash(self: Path) u64 {
|
|
||||||
// Fowler–Noll–Vo hash function is used here as it has a lower collision rate for smaller inputs.
|
|
||||||
const fnv_prime = 0x100000001b3;
|
|
||||||
var hash_code = @as(u64, 0xcbf29ce484222325);
|
|
||||||
|
|
||||||
for (self.buffer[0..self.filled()]) |byte| {
|
|
||||||
hash_code = hash_code ^ byte;
|
|
||||||
hash_code = hash_code *% fnv_prime;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash_code;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const maximum = 255;
|
|
||||||
|
|
||||||
pub fn parse(bytes: []const u8) ParseError!Path {
|
|
||||||
if (bytes.len > maximum) return error.TooLong;
|
|
||||||
|
|
||||||
// Int cast is safe as bytes length is confirmed to be smaller than or equal to u8 maximum.
|
|
||||||
var parsed_path = Path{ .buffer = ([_]u8{0} ** maximum) ++ [_]u8{maximum - @intCast(u8, bytes.len)} };
|
|
||||||
|
|
||||||
coral.io.copy(&parsed_path.buffer, bytes);
|
|
||||||
|
|
||||||
return parsed_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remaining(self: Path) usize {
|
|
||||||
return self.buffer[maximum];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const seperator = '/';
|
|
||||||
};
|
|
|
@ -119,7 +119,7 @@ pub const OpenError = QueryError || error {TooManyFiles};
|
||||||
|
|
||||||
pub const ReadableFile = opaque {
|
pub const ReadableFile = opaque {
|
||||||
pub fn as_reader(self: *ReadableFile) coral.io.Reader {
|
pub fn as_reader(self: *ReadableFile) coral.io.Reader {
|
||||||
return coral.io.Reader.bind(self, ReadableFile);
|
return coral.io.Reader.bind(self, read);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_rw_ops(self: *ReadableFile) *ext.SDL_RWops {
|
fn as_rw_ops(self: *ReadableFile) *ext.SDL_RWops {
|
||||||
|
|
|
@ -1,16 +1,68 @@
|
||||||
const coral = @import("coral");
|
const coral = @import("coral");
|
||||||
|
|
||||||
pub const Canvas = struct {
|
const ext = @import("./ext.zig");
|
||||||
pub fn create_sprite(_: *Canvas, _: SpriteProperties) void {
|
|
||||||
|
|
||||||
|
pub const Canvas = struct {
|
||||||
|
allocator: coral.io.Allocator,
|
||||||
|
items: coral.slots.Dense(coral.slots.addressable_key, Item),
|
||||||
|
|
||||||
|
const Item = union (enum) {
|
||||||
|
sprite: struct {
|
||||||
|
transform: Transform2D,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create_sprite(self: *Canvas, transform: Transform2D) coral.io.AllocationError!Sprite {
|
||||||
|
const slot = try self.items.insert(self.allocator, .{.sprite = .{
|
||||||
|
.transform = transform,
|
||||||
|
}});
|
||||||
|
|
||||||
|
errdefer coral.debug.assert(self.items.remove(slot));
|
||||||
|
|
||||||
|
return Sprite{
|
||||||
|
.slot = slot,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Canvas) void {
|
||||||
|
self.items.free(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy_sprite(self: *Canvas, sprite: Sprite) void {
|
||||||
|
self.items.remove(sprite) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: coral.io.Allocator) Canvas {
|
||||||
|
return .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.items = .{},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Label = struct {
|
||||||
|
index: u32,
|
||||||
|
version: u32,
|
||||||
|
};
|
||||||
|
|
||||||
pub const Sprite = struct {
|
pub const Sprite = struct {
|
||||||
|
slot: coral.slots.Slot(coral.slots.addressable_key),
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const SpriteProperties = struct {
|
pub const Transform2D = extern struct {
|
||||||
position: coral.math.Vector2,
|
x: coral.math.Vector2,
|
||||||
rotation: f32,
|
y: coral.math.Vector2,
|
||||||
|
origin: coral.math.Vector2,
|
||||||
|
|
||||||
|
pub fn from_trs(translation_x: f32, translation_y: f32, rotation: f32, scale_x: f32, scale_y: f32) Transform2D {
|
||||||
|
return identity.scaled(scale_x, scale_y).multiply(
|
||||||
|
identity.rotated(rotation).multiply(
|
||||||
|
identity.translated(translation_x, translation_y)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const identity = Transform2D{
|
||||||
|
.x = coral.math.Vector2{1, 0},
|
||||||
|
.y = coral.math.Vector2{0, 1},
|
||||||
|
.origin = coral.math.Vector2{0, 0},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,8 @@ pub const App = opaque {
|
||||||
const State = struct {
|
const State = struct {
|
||||||
last_event: ext.SDL_Event,
|
last_event: ext.SDL_Event,
|
||||||
base_file_sandbox: files.FileSandbox,
|
base_file_sandbox: files.FileSandbox,
|
||||||
|
window: *ext.SDL_Window,
|
||||||
|
renderer: *ext.SDL_Renderer,
|
||||||
canvas: gfx.Canvas,
|
canvas: gfx.Canvas,
|
||||||
|
|
||||||
fn cast(self: *App) *State {
|
fn cast(self: *App) *State {
|
||||||
|
@ -40,15 +42,48 @@ pub const App = opaque {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn present(self: *App) void {
|
||||||
|
for (self.canvas().items.values) |item| {
|
||||||
|
switch (item) {
|
||||||
|
.sprite => {
|
||||||
|
// TODO: Implement.
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(comptime errors: type, start: *const Starter(errors)) errors!void {
|
pub fn run(comptime errors: type, start: *const Starter(errors)) errors!void {
|
||||||
|
if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) {
|
||||||
|
return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}});
|
||||||
|
}
|
||||||
|
|
||||||
const base_prefix = ext.SDL_GetBasePath() orelse {
|
const base_prefix = ext.SDL_GetBasePath() orelse {
|
||||||
return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}});
|
return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}});
|
||||||
};
|
};
|
||||||
|
|
||||||
defer ext.SDL_free(base_prefix);
|
defer ext.SDL_free(base_prefix);
|
||||||
|
|
||||||
var state = App.State{
|
const window_flags = 0;
|
||||||
|
|
||||||
|
const window = ext.SDL_CreateWindow("ona", -1, -1, 640, 480, window_flags) orelse {
|
||||||
|
return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}});
|
||||||
|
};
|
||||||
|
|
||||||
|
defer ext.SDL_DestroyWindow(window);
|
||||||
|
|
||||||
|
const renderer_flags = 0;
|
||||||
|
|
||||||
|
const renderer = ext.SDL_CreateRenderer(window, -1, renderer_flags) orelse {
|
||||||
|
return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}});
|
||||||
|
};
|
||||||
|
|
||||||
|
defer ext.SDL_DestroyRenderer(renderer);
|
||||||
|
|
||||||
|
return start(@ptrCast(*App, &App.State{
|
||||||
.last_event = undefined,
|
.last_event = undefined,
|
||||||
|
.window = window,
|
||||||
|
.renderer = renderer,
|
||||||
|
.canvas = gfx.Canvas.init(allocator),
|
||||||
|
|
||||||
.base_file_sandbox = .{
|
.base_file_sandbox = .{
|
||||||
.prefix = coral.io.slice_sentineled(u8, 0, base_prefix),
|
.prefix = coral.io.slice_sentineled(u8, 0, base_prefix),
|
||||||
|
@ -58,34 +93,40 @@ pub const App = opaque {
|
||||||
.is_queryable = true,
|
.is_queryable = true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
}));
|
||||||
.canvas = .{},
|
|
||||||
};
|
|
||||||
|
|
||||||
return start(@ptrCast(*App, &state));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const allocator = coral.io.MemoryAllocator.bind(&heap, @TypeOf(heap));
|
pub const allocator = coral.io.Allocator.bind(&heap, @TypeOf(heap).reallocate);
|
||||||
|
|
||||||
var heap = struct {
|
var heap = struct {
|
||||||
live_allocations: usize = 0,
|
live_allocations: usize = 0,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
pub fn reallocate(self: *Self, maybe_allocation: ?*anyopaque, size: usize) ?[*]u8 {
|
pub fn reallocate(self: *Self, options: coral.io.AllocationOptions) ?[]u8 {
|
||||||
if (size == 0) {
|
if (options.size == 0) {
|
||||||
ext.SDL_free(maybe_allocation);
|
if (options.allocation) |allocation| {
|
||||||
|
ext.SDL_free(allocation.ptr);
|
||||||
|
|
||||||
self.live_allocations -= 1;
|
self.live_allocations -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ext.SDL_realloc(maybe_allocation, size)) |allocation| {
|
if (options.allocation) |allocation| {
|
||||||
|
if (ext.SDL_realloc(allocation.ptr, options.size)) |reallocation| {
|
||||||
self.live_allocations += 1;
|
self.live_allocations += 1;
|
||||||
|
|
||||||
return @ptrCast([*]u8, allocation);
|
return @ptrCast([*]u8, reallocation)[0 .. options.size];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ext.SDL_malloc(options.size)) |allocation| {
|
||||||
|
self.live_allocations += 1;
|
||||||
|
|
||||||
|
return @ptrCast([*]u8, allocation)[0 .. options.size];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -4,40 +4,72 @@ const kym = @import("kym");
|
||||||
|
|
||||||
const ona = @import("ona");
|
const ona = @import("ona");
|
||||||
|
|
||||||
fn create_canvas_binding(binding_vm: *kym.Vm, canvas: *ona.gfx.Canvas) !*kym.Object {
|
const transform2d_typename = @typeName(ona.gfx.Transform2D);
|
||||||
const canvas_object = try binding_vm.new(.{.native = canvas}, .{});
|
|
||||||
const Object = kym.Object;
|
|
||||||
const Value = kym.Value;
|
|
||||||
|
|
||||||
try binding_vm.set_field(canvas_object, "create_sprite", .{.object = try binding_vm.new_closure(struct {
|
fn bind_canvas(environment: *kym.vm.Environment, canvas: *ona.gfx.Canvas) !void {
|
||||||
fn call(calling_vm: *kym.Vm, _: *Object, context: *Object, arguments: []const Value) kym.RuntimeError!Value {
|
const object = try environment.new(.{});
|
||||||
const properties = try kym.object_argument(calling_vm, arguments, 0);
|
|
||||||
|
|
||||||
@ptrCast(*ona.gfx.Canvas, context.userdata().native).create_sprite(.{
|
defer environment.release(object);
|
||||||
.position = calling_vm.get_field(properties, "position").to_vector2() orelse coral.math.Vector2.zero,
|
|
||||||
.rotation = @floatCast(f32, calling_vm.get_field(properties, "rotation").to_float() orelse 0.0),
|
const sprite_creator = try environment.new(.{
|
||||||
});
|
.userinfo = @ptrToInt(canvas),
|
||||||
|
|
||||||
|
.behavior = &.{
|
||||||
|
.caller = struct {
|
||||||
|
fn call(context: kym.vm.Object.CallContext) kym.vm.RuntimeError!kym.vm.Value {
|
||||||
|
try context.check(context.arguments.len == 2, "2 arguments expected");
|
||||||
|
|
||||||
|
const transform = context.arguments[0].object;
|
||||||
|
|
||||||
|
try context.check(transform.userinfo == @ptrToInt(transform2d_typename), "`transform2d` expected");
|
||||||
|
|
||||||
|
_ = try @intToPtr(*ona.gfx.Canvas, context.object.userinfo).create_sprite(
|
||||||
|
coral.io.bytes_to(ona.gfx.Transform2D, transform.userdata).?);
|
||||||
|
|
||||||
return .nil;
|
return .nil;
|
||||||
}
|
}
|
||||||
}.call)});
|
}.call,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return canvas_object;
|
defer environment.release(sprite_creator);
|
||||||
|
|
||||||
|
try environment.raw_set(object, "create_sprite", sprite_creator.as_value());
|
||||||
|
try environment.global_set("canvas", object.as_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn bind_transform_2d(environment: *kym.Environment) !*kym.Object {
|
||||||
|
// const allocator = environment.allocator();
|
||||||
|
// const transform_2d = try allocator.allocate_one(ona.gfx.Transform2D);
|
||||||
|
|
||||||
|
// errdefer allocator.deallocate(transform_2d);
|
||||||
|
|
||||||
|
// const object = try environment.new(@ptrToInt(transform_2d), .{
|
||||||
|
// .destructor = struct {
|
||||||
|
// fn destruct() void {
|
||||||
|
// allocator.deallocate(transform_2d);
|
||||||
|
// }
|
||||||
|
// }.destruct,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// defer environment.release(object);
|
||||||
|
|
||||||
|
// try environment.set_field(environment.globals(), object);
|
||||||
|
// }
|
||||||
|
|
||||||
pub fn main() anyerror!void {
|
pub fn main() anyerror!void {
|
||||||
return ona.App.run(anyerror, start);
|
return ona.App.run(anyerror, start);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(app: *ona.App) anyerror!void {
|
fn start(app: *ona.App) anyerror!void {
|
||||||
var vm = try kym.Vm.init(ona.allocator, .{
|
var kym_env = try kym.vm.Environment.init(ona.allocator, .{
|
||||||
.stack_max = 256,
|
.stack_max = 256,
|
||||||
.objects_max = 512,
|
.calls_max = 256,
|
||||||
});
|
});
|
||||||
|
|
||||||
defer vm.deinit();
|
defer kym_env.deinit();
|
||||||
|
|
||||||
try vm.set_field(vm.globals(), "events", .{.object = try create_canvas_binding(&vm, app.canvas())});
|
try bind_canvas(&kym_env, app.canvas());
|
||||||
|
|
||||||
{
|
{
|
||||||
const index_path = "index.kym";
|
const index_path = "index.kym";
|
||||||
|
@ -46,9 +78,9 @@ fn start(app: *ona.App) anyerror!void {
|
||||||
defer if (!index_file.close()) ona.log_error(&.{.{.string = "failed to close "}, .{.string = index_path}});
|
defer if (!index_file.close()) ona.log_error(&.{.{.string = "failed to close "}, .{.string = index_path}});
|
||||||
|
|
||||||
const index_size = (try app.data_fs().query(index_path)).size;
|
const index_size = (try app.data_fs().query(index_path)).size;
|
||||||
const index_allocation = ona.allocator.allocate_many(u8, index_size) orelse return error.OutOfMemory;
|
const index_allocation = try coral.io.allocate_many(u8, index_size, ona.allocator);
|
||||||
|
|
||||||
defer ona.allocator.deallocate(index_allocation);
|
defer coral.io.deallocate(ona.allocator, index_allocation);
|
||||||
|
|
||||||
var index_buffer = coral.buffer.Fixed{.data = index_allocation[0 .. index_size]};
|
var index_buffer = coral.buffer.Fixed{.data = index_allocation[0 .. index_size]};
|
||||||
|
|
||||||
|
@ -59,10 +91,19 @@ fn start(app: *ona.App) anyerror!void {
|
||||||
return error.IoUnavailable;
|
return error.IoUnavailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = try vm.call_self(try vm.new_script(index_buffer.data), &.{});
|
{
|
||||||
|
const index_script = try kym_env.new_script(.{
|
||||||
|
.name = index_path,
|
||||||
|
.data = index_buffer.data
|
||||||
|
});
|
||||||
|
|
||||||
|
defer kym_env.release(index_script);
|
||||||
|
|
||||||
|
_ = try kym_env.call(index_script, &.{});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (app.poll()) {
|
while (app.poll()) {
|
||||||
|
app.present();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue