General fixes

This commit is contained in:
kayomn 2023-05-06 01:47:52 +00:00
parent e9f12785f9
commit 1e0dff6dba
27 changed files with 897 additions and 1959 deletions

0
.drone.yml Normal file → Executable file
View File

0
.gitignore vendored Normal file → Executable file
View File

View File

@ -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,
}

17
build.zig Normal file → Executable file
View File

@ -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();

0
debug/index.kym Normal file → Executable file
View File

2
readme.md Normal file → Executable file
View File

@ -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

6
source/coral/buffer.zig Normal file → Executable file
View File

@ -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
source/coral/coral.zig Normal file → Executable file
View File

@ -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");

0
source/coral/debug.zig Normal file → Executable file
View File

8
source/coral/format.zig Normal file → Executable file
View File

@ -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]);
}

96
source/coral/heap.zig Normal file
View File

@ -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;
}
};

View File

@ -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;

4
source/coral/math.zig Normal file → Executable file
View File

@ -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);
}

121
source/coral/slots.zig Executable file
View File

@ -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),
};

49
source/coral/stack.zig Normal file → Executable file
View File

@ -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;

52
source/coral/table.zig Normal file → Executable file
View File

@ -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,
};

0
source/coral/utf8.zig Normal file → Executable file
View File

View File

@ -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);

461
source/kym/kym.zig Normal file → Executable file
View File

@ -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");

View File

@ -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;
}
};

321
source/kym/vm.zig Executable file
View File

@ -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);

View File

@ -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 {
// FowlerNollVo 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 = '/';
};

0
source/ona/ext.zig Normal file → Executable file
View File

2
source/ona/files.zig Normal file → Executable file
View File

@ -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 {

64
source/ona/gfx.zig Normal file → Executable file
View File

@ -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},
};
};

65
source/ona/ona.zig Normal file → Executable file
View File

@ -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;

83
source/runner.zig Normal file → Executable file
View File

@ -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();
}
}