General fixes
This commit is contained in:
parent
e9f12785f9
commit
1e0dff6dba
|
@ -5,14 +5,7 @@
|
|||
"editor.rulers": [120],
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
"spellright.language": [
|
||||
"en-US-10-1."
|
||||
],
|
||||
"spellright.documentTypes": [
|
||||
"markdown",
|
||||
"plaintext",
|
||||
"zig"
|
||||
],
|
||||
"zig.formattingProvider": "off",
|
||||
"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.
|
||||
{
|
||||
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("ona", ona_module);
|
||||
ona_exe.addModule("oar", oar_module);
|
||||
ona_exe.addModule("kym", kym_module);
|
||||
|
||||
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.
|
||||
|
||||
* 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
|
||||
|
||||
|
|
|
@ -7,7 +7,11 @@ pub const Fixed = struct {
|
|||
write_index: usize = 0,
|
||||
|
||||
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 {
|
||||
|
|
|
@ -4,10 +4,14 @@ pub const debug = @import("./debug.zig");
|
|||
|
||||
pub const format = @import("./format.zig");
|
||||
|
||||
pub const heap = @import("./heap.zig");
|
||||
|
||||
pub const io = @import("./io.zig");
|
||||
|
||||
pub const math = @import("./math.zig");
|
||||
|
||||
pub const slots = @import("./slots.zig");
|
||||
|
||||
pub const stack = @import("./stack.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;
|
||||
|
||||
for (values) |value| written += switch (value) {
|
||||
.newline => try writer.write("\n"),
|
||||
.string => |string| try writer.write(string),
|
||||
.newline => try writer.invoke("\n"),
|
||||
.string => |string| try writer.invoke(string),
|
||||
.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 {
|
||||
if (value == 0) return writer.write("0");
|
||||
if (value == 0) return writer.invoke("0");
|
||||
|
||||
var buffer = [_]u8{0} ** 39;
|
||||
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");
|
||||
|
||||
pub const MemoryArena = struct {
|
||||
|
||||
|
||||
pub fn as_allocator(self: *MemoryArena) MemoryAllocator {
|
||||
return MemoryAllocator.bind(self, MemoryArena);
|
||||
}
|
||||
pub const AllocationError = error {
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
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,
|
||||
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_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 .{
|
||||
.context = state,
|
||||
|
||||
.call = struct {
|
||||
fn reallocate(context: *anyopaque, maybe_allocation: ?[*]u8, size: usize) ?[*]u8 {
|
||||
return Actions.reallocate(@ptrCast(State, @alignCast(@alignOf(state_info.Pointer.child), context)), maybe_allocation, size);
|
||||
.invoker = struct {
|
||||
fn invoke_opaque(context: *anyopaque, input: Input) Output {
|
||||
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 {
|
||||
return @ptrCast(?[*]Type, @alignCast(@alignOf(Type), self.call(self.context, null, @sizeOf(Type) * amount)));
|
||||
pub fn invoke(self: Self, input: Input) Output {
|
||||
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,
|
||||
};
|
||||
|
||||
pub const Reader = struct {
|
||||
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 const Reader = Functor(ReadError!usize, []u8);
|
||||
|
||||
pub fn Tag(comptime Element: type) type {
|
||||
return switch (@typeInfo(Element)) {
|
||||
|
@ -96,31 +63,31 @@ pub const WriteError = error{
|
|||
IoUnavailable,
|
||||
};
|
||||
|
||||
pub const Writer = struct {
|
||||
context: *anyopaque,
|
||||
call: *const fn (context: *anyopaque, buffer: []const u8) WriteError!usize,
|
||||
pub const Writer = Functor(WriteError!usize, []const u8);
|
||||
|
||||
pub fn bind(state: anytype, comptime Actions: type) Writer {
|
||||
const State = @TypeOf(state);
|
||||
const state_info = @typeInfo(State);
|
||||
|
||||
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 allocate_many(comptime Type: type, amount: usize, allocator: Allocator) AllocationError![]Type {
|
||||
if (@sizeOf(Type) == 0) {
|
||||
@compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type));
|
||||
}
|
||||
|
||||
pub fn write(self: Writer, buffer: []const u8) WriteError!usize {
|
||||
return self.call(self.context, buffer);
|
||||
if (amount == 0) {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
var total_written: u64 = 0;
|
||||
var read = try input.read(buffer);
|
||||
var read = try input.invoke(buffer);
|
||||
|
||||
while (read != 0) {
|
||||
total_written += try output.write(buffer[0..read]);
|
||||
read = try input.read(buffer);
|
||||
total_written += try output.invoke(buffer[0..read]);
|
||||
read = try input.invoke(buffer);
|
||||
}
|
||||
|
||||
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 {
|
||||
return if (this > that) this else that;
|
||||
return @max(this, that);
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return struct {
|
||||
allocator: io.MemoryAllocator,
|
||||
capacity: usize,
|
||||
values: []Element,
|
||||
capacity: usize = 0,
|
||||
values: []Element = &.{},
|
||||
|
||||
const Self = @This();
|
||||
|
||||
|
@ -16,8 +15,8 @@ pub fn Dense(comptime Element: type) type {
|
|||
self.values = self.values[0 .. 0];
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.allocator.deallocate(self.values.ptr);
|
||||
pub fn deinit(self: *Self, allocator: io.Allocator) void {
|
||||
io.deallocate(allocator, self.values);
|
||||
}
|
||||
|
||||
pub fn drop(self: *Self, amount: usize) bool {
|
||||
|
@ -28,27 +27,19 @@ pub fn Dense(comptime Element: type) type {
|
|||
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;
|
||||
|
||||
self.values = (self.allocator.reallocate(Element, self.values.ptr,
|
||||
grown_capacity) orelse return error.OutOfMemory)[0 .. self.values.len];
|
||||
|
||||
self.values = (try io.reallocate(allocator, self.values, grown_capacity))[0 .. self.values.len];
|
||||
self.capacity = grown_capacity;
|
||||
}
|
||||
|
||||
pub fn init(allocator: io.MemoryAllocator, initial_capacity: usize) !Self {
|
||||
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 {
|
||||
pub fn push_all(self: *Self, allocator: io.Allocator, values: []const Element) io.AllocationError!void {
|
||||
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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -77,8 +70,10 @@ pub fn Dense(comptime Element: type) type {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn push_one(self: *Self, value: Element) GrowError!void {
|
||||
if (self.values.len == self.capacity) try self.grow(math.min(usize, 1, self.capacity));
|
||||
pub fn push_one(self: *Self, allocator: io.MemoryAllocator, value: Element) io.AllocationError!void {
|
||||
if (self.values.len == self.capacity) {
|
||||
try self.grow(allocator, math.max(usize, 1, self.capacity));
|
||||
}
|
||||
|
||||
const offset_index = self.values.len;
|
||||
|
||||
|
@ -89,12 +84,8 @@ pub fn Dense(comptime Element: type) type {
|
|||
};
|
||||
}
|
||||
|
||||
pub const GrowError = error {
|
||||
OutOfMemory
|
||||
};
|
||||
|
||||
pub fn as_dense_allocator(stack: *Dense(u8)) io.MemoryAllocator {
|
||||
return io.MemoryAllocator.bind(stack, struct {
|
||||
pub fn as_dense_allocator(stack: *Dense(u8)) io.Allocator {
|
||||
return io.Allocator.bind(stack, struct {
|
||||
pub fn reallocate(writable_stack: *Dense(u8), existing_allocation: ?[*]u8, allocation_size: usize) ?[*]u8 {
|
||||
if (allocation_size == 0) return null;
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
const io = @import("./io.zig");
|
||||
|
||||
const math = @import("./math.zig");
|
||||
|
||||
pub fn Hashed(comptime key: Key, comptime Element: type) type {
|
||||
const Entry = struct {
|
||||
key: key.Element,
|
||||
|
@ -7,44 +9,38 @@ pub fn Hashed(comptime key: Key, comptime Element: type) type {
|
|||
};
|
||||
|
||||
return struct {
|
||||
allocator: io.MemoryAllocator,
|
||||
entries: []?Entry,
|
||||
entries: []?Entry = &.{},
|
||||
|
||||
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 {
|
||||
for (self.entries) |*entry| entry.* = null;
|
||||
// TODO: Implement.
|
||||
_ = self;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.allocator.deallocate(self.entries.ptr);
|
||||
pub fn deinit(self: *Self, allocator: io.MemoryAllocator) void {
|
||||
// TODO: Implement.
|
||||
_ = self;
|
||||
_ = allocator;
|
||||
}
|
||||
|
||||
pub fn init(allocator: io.MemoryAllocator) !Self {
|
||||
const size = 4;
|
||||
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 {
|
||||
pub fn insert(self: *Self, key_element: key.Element, value_element: Element) io.AllocationError!bool {
|
||||
// TODO: Implement.
|
||||
_ = self;
|
||||
_ = key_element;
|
||||
_ = value_element;
|
||||
}
|
||||
|
||||
pub fn insert(self: *Self, key_element: key.Element, value_element: Element) !void {
|
||||
_ = self;
|
||||
_ = key_element;
|
||||
_ = value_element;
|
||||
}
|
||||
|
||||
pub fn lookup(self: *Self, key_element: key.Element) ?Element {
|
||||
pub fn lookup(self: Self, key_element: key.Element) ?Element {
|
||||
// TODO: Implement.
|
||||
_ = self;
|
||||
_ = key_element;
|
||||
|
||||
|
@ -57,6 +53,12 @@ pub const Key = struct {
|
|||
Element: type,
|
||||
};
|
||||
|
||||
pub fn unsigned_key(comptime bits: comptime_int) Key {
|
||||
return .{
|
||||
.Element = math.Unsigned(bits),
|
||||
};
|
||||
}
|
||||
|
||||
pub const string_key = Key{
|
||||
.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");
|
||||
|
||||
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;
|
||||
}
|
||||
pub const vm = @import("./vm.zig");
|
||||
|
|
|
@ -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 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 {
|
||||
|
|
|
@ -1,16 +1,68 @@
|
|||
const coral = @import("coral");
|
||||
|
||||
pub const Canvas = struct {
|
||||
pub fn create_sprite(_: *Canvas, _: SpriteProperties) void {
|
||||
const ext = @import("./ext.zig");
|
||||
|
||||
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 {
|
||||
|
||||
slot: coral.slots.Slot(coral.slots.addressable_key),
|
||||
};
|
||||
|
||||
pub const SpriteProperties = struct {
|
||||
position: coral.math.Vector2,
|
||||
rotation: f32,
|
||||
pub const Transform2D = extern struct {
|
||||
x: coral.math.Vector2,
|
||||
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 {
|
||||
last_event: ext.SDL_Event,
|
||||
base_file_sandbox: files.FileSandbox,
|
||||
window: *ext.SDL_Window,
|
||||
renderer: *ext.SDL_Renderer,
|
||||
canvas: gfx.Canvas,
|
||||
|
||||
fn cast(self: *App) *State {
|
||||
|
@ -40,15 +42,48 @@ pub const App = opaque {
|
|||
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 {
|
||||
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 {
|
||||
return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}});
|
||||
};
|
||||
|
||||
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,
|
||||
.window = window,
|
||||
.renderer = renderer,
|
||||
.canvas = gfx.Canvas.init(allocator),
|
||||
|
||||
.base_file_sandbox = .{
|
||||
.prefix = coral.io.slice_sentineled(u8, 0, base_prefix),
|
||||
|
@ -58,34 +93,40 @@ pub const App = opaque {
|
|||
.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 {
|
||||
live_allocations: usize = 0,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn reallocate(self: *Self, maybe_allocation: ?*anyopaque, size: usize) ?[*]u8 {
|
||||
if (size == 0) {
|
||||
ext.SDL_free(maybe_allocation);
|
||||
pub fn reallocate(self: *Self, options: coral.io.AllocationOptions) ?[]u8 {
|
||||
if (options.size == 0) {
|
||||
if (options.allocation) |allocation| {
|
||||
ext.SDL_free(allocation.ptr);
|
||||
|
||||
self.live_allocations -= 1;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
|
|
@ -4,40 +4,72 @@ const kym = @import("kym");
|
|||
|
||||
const ona = @import("ona");
|
||||
|
||||
fn create_canvas_binding(binding_vm: *kym.Vm, canvas: *ona.gfx.Canvas) !*kym.Object {
|
||||
const canvas_object = try binding_vm.new(.{.native = canvas}, .{});
|
||||
const Object = kym.Object;
|
||||
const Value = kym.Value;
|
||||
const transform2d_typename = @typeName(ona.gfx.Transform2D);
|
||||
|
||||
try binding_vm.set_field(canvas_object, "create_sprite", .{.object = try binding_vm.new_closure(struct {
|
||||
fn call(calling_vm: *kym.Vm, _: *Object, context: *Object, arguments: []const Value) kym.RuntimeError!Value {
|
||||
const properties = try kym.object_argument(calling_vm, arguments, 0);
|
||||
fn bind_canvas(environment: *kym.vm.Environment, canvas: *ona.gfx.Canvas) !void {
|
||||
const object = try environment.new(.{});
|
||||
|
||||
@ptrCast(*ona.gfx.Canvas, context.userdata().native).create_sprite(.{
|
||||
.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),
|
||||
});
|
||||
defer environment.release(object);
|
||||
|
||||
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;
|
||||
}
|
||||
}.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 {
|
||||
return ona.App.run(anyerror, start);
|
||||
}
|
||||
|
||||
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,
|
||||
.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";
|
||||
|
@ -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}});
|
||||
|
||||
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]};
|
||||
|
||||
|
@ -59,10 +91,19 @@ fn start(app: *ona.App) anyerror!void {
|
|||
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()) {
|
||||
|
||||
app.present();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue