diff --git a/.drone.yml b/.drone.yml old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/.vscode/settings.json b/.vscode/settings.json index 2dfaba5..98e9a93 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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, } diff --git a/build.zig b/build.zig old mode 100644 new mode 100755 index ac48ef9..209a0e0 --- a/build.zig +++ b/build.zig @@ -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(); diff --git a/debug/index.kym b/debug/index.kym old mode 100644 new mode 100755 diff --git a/readme.md b/readme.md old mode 100644 new mode 100755 index f851dc0..4221fb4 --- a/readme.md +++ b/readme.md @@ -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 diff --git a/source/coral/buffer.zig b/source/coral/buffer.zig old mode 100644 new mode 100755 index 621dada..401931a --- a/source/coral/buffer.zig +++ b/source/coral/buffer.zig @@ -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 { diff --git a/source/coral/coral.zig b/source/coral/coral.zig old mode 100644 new mode 100755 index 33e24f5..9c3aa81 --- a/source/coral/coral.zig +++ b/source/coral/coral.zig @@ -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"); diff --git a/source/coral/debug.zig b/source/coral/debug.zig old mode 100644 new mode 100755 diff --git a/source/coral/format.zig b/source/coral/format.zig old mode 100644 new mode 100755 index bf5a4f2..4f072d3 --- a/source/coral/format.zig +++ b/source/coral/format.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]); } diff --git a/source/coral/heap.zig b/source/coral/heap.zig new file mode 100644 index 0000000..4eced2b --- /dev/null +++ b/source/coral/heap.zig @@ -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; + } +}; diff --git a/source/coral/io.zig b/source/coral/io.zig index deba662..58fb24b 100755 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -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 { - context: *anyopaque, - call: *const fn (capture: *anyopaque, maybe_allocation: ?[*]u8, size: usize) ?[*]u8, - - const Capture = [@sizeOf(usize)]u8; - - pub fn bind(state: anytype, comptime Actions: type) MemoryAllocator { - const State = @TypeOf(state); - const state_info = @typeInfo(State); - - 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); - } - }.reallocate, - }; - } - - 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 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 AllocationOptions = struct { + allocation: ?[]u8 = null, + size: usize, }; -pub const ReadError = error{ +pub const Allocator = Functor(?[]u8, AllocationOptions); + +pub fn Functor(comptime Output: type, comptime Input: type) type { + return struct { + context: *anyopaque, + invoker: *const fn (capture: *anyopaque, input: Input) Output, + + const Self = @This(); + + 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"); + } + + return .{ + .context = state, + + .invoker = struct { + fn invoke_opaque(context: *anyopaque, input: Input) Output { + return invoker(@ptrCast(State, @alignCast(@alignOf(state_info.Pointer.child), context)), input); + } + }.invoke_opaque, + }; + } + + pub fn invoke(self: Self, input: Input) Output { + return self.invoker(self.context, input); + } + }; +} + +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; diff --git a/source/coral/math.zig b/source/coral/math.zig old mode 100644 new mode 100755 index 68c8a44..51f1f41 --- a/source/coral/math.zig +++ b/source/coral/math.zig @@ -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); } diff --git a/source/coral/slots.zig b/source/coral/slots.zig new file mode 100755 index 0000000..2906ae7 --- /dev/null +++ b/source/coral/slots.zig @@ -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), +}; diff --git a/source/coral/stack.zig b/source/coral/stack.zig old mode 100644 new mode 100755 index ea5dec8..6e130a6 --- a/source/coral/stack.zig +++ b/source/coral/stack.zig @@ -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; diff --git a/source/coral/table.zig b/source/coral/table.zig old mode 100644 new mode 100755 index 41b563d..1ddab16 --- a/source/coral/table.zig +++ b/source/coral/table.zig @@ -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, }; diff --git a/source/coral/utf8.zig b/source/coral/utf8.zig old mode 100644 new mode 100755 diff --git a/source/kym/bytecode.zig b/source/kym/bytecode.zig deleted file mode 100755 index 28797db..0000000 --- a/source/kym/bytecode.zig +++ /dev/null @@ -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); diff --git a/source/kym/kym.zig b/source/kym/kym.zig old mode 100644 new mode 100755 index f875648..8f9f30c --- a/source/kym/kym.zig +++ b/source/kym/kym.zig @@ -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"); diff --git a/source/kym/tokens.zig b/source/kym/tokens.zig deleted file mode 100644 index df1d228..0000000 --- a/source/kym/tokens.zig +++ /dev/null @@ -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; - } -}; diff --git a/source/kym/vm.zig b/source/kym/vm.zig new file mode 100755 index 0000000..76fc09c --- /dev/null +++ b/source/kym/vm.zig @@ -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); diff --git a/source/oar/oar.zig b/source/oar/oar.zig deleted file mode 100644 index 988c48a..0000000 --- a/source/oar/oar.zig +++ /dev/null @@ -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 = '/'; -}; diff --git a/source/ona/ext.zig b/source/ona/ext.zig old mode 100644 new mode 100755 diff --git a/source/ona/files.zig b/source/ona/files.zig old mode 100644 new mode 100755 index e0be9fc..edf35a7 --- a/source/ona/files.zig +++ b/source/ona/files.zig @@ -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 { diff --git a/source/ona/gfx.zig b/source/ona/gfx.zig old mode 100644 new mode 100755 index da63017..ea7ede7 --- a/source/ona/gfx.zig +++ b/source/ona/gfx.zig @@ -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}, + }; }; diff --git a/source/ona/ona.zig b/source/ona/ona.zig old mode 100644 new mode 100755 index e8d318e..12b7b73 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -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; + 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, reallocation)[0 .. options.size]; + } + } + + if (ext.SDL_malloc(options.size)) |allocation| { self.live_allocations += 1; - return @ptrCast([*]u8, allocation); + return @ptrCast([*]u8, allocation)[0 .. options.size]; } return null; diff --git a/source/runner.zig b/source/runner.zig old mode 100644 new mode 100755 index b4eec6f..a40f9d2 --- a/source/runner.zig +++ b/source/runner.zig @@ -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); - return .nil; - } - }.call)}); + const sprite_creator = try environment.new(.{ + .userinfo = @ptrToInt(canvas), - return canvas_object; + .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, + }, + }); + + 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(); } }