Refactor project structure and Ona APIs
continuous-integration/drone/push Build is failing Details

This commit is contained in:
kayomn 2023-05-24 00:33:02 +00:00
parent b1739f0cf8
commit bb8cb43843
13 changed files with 917 additions and 994 deletions

View File

@ -9,18 +9,7 @@ pub fn build(builder: *std.Build) void {
.dependencies = &.{
.{
.name = "coral",
.module = coral_module
},
},
});
const kym_module = builder.createModule(.{
.source_file = .{.path = "./source/kym/kym.zig"},
.dependencies = &.{
.{
.name = "coral",
.module = coral_module
.module = coral_module,
},
},
});
@ -36,7 +25,6 @@ pub fn build(builder: *std.Build) void {
ona_exe.addModule("coral", coral_module);
ona_exe.addModule("ona", ona_module);
ona_exe.addModule("kym", kym_module);
// ona_exe.addIncludeDir("./ext");
ona_exe.linkSystemLibrary("SDL2");

View File

@ -1 +0,0 @@
pub const vm = @import("./vm.zig");

View File

@ -1,335 +0,0 @@
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: []const u8 = &.{},
identity: ?*const anyopaque = null,
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,
.identity = &self.globals,
.userdata = &.{},
.fields = self.globals,
.behavior = &.{},
};
return object.behavior.caller(.{
.env = self,
.args = arguments,
.caller = &global_object,
.callee = object,
});
}
pub fn check(self: Environment, condition: bool, failure_message: []const u8) RuntimeError!void {
if (condition) return;
// TODO: Emit failure message.
_ = self;
_ = failure_message;
return error.CheckFailure;
}
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, 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, options.stack_max);
try environment.calls.grow(allocator, 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.len, self.allocator);
errdefer coral.io.deallocate(self.allocator, userdata);
coral.io.copy(userdata, options.userdata);
object.* = .{
.userdata = userdata,
.ref_count = 1,
.identity = options.identity orelse userdata.ptr,
.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) coral.io.AllocationError!*Object {
// TODO: Implement.
_ = self;
_ = options;
return error.OutOfMemory;
}
pub fn new_string(self: *Environment, string_data: []const u8) coral.io.AllocationError!*Object {
const object = try self.new(.{
.userdata = string_data,
.behavior = &.{},
});
errdefer self.release(object);
return object;
}
pub fn new_string_from(self: *Environment, formats: []const coral.format.Value) coral.io.AllocationError!*Object {
// TODO: Implement.
coral.format.print(coral.io.null_writer, formats);
return self.new_string("");
}
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_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 user_cast(self: *Environment, object: *Object, expected_identity: *const anyopaque, comptime CastTarget: type) RuntimeError!*CastTarget {
// TODO: Emit failure message.
_ = self;
if (object.identity != expected_identity) {
// Identity does not match what was expected.
return error.InvalidOperation;
}
if (object.userdata.len != @sizeOf(CastTarget)) {
// Userdata size does not match target type.
return error.InvalidOperation;
}
return @ptrCast(*CastTarget, @alignCast(@alignOf(CastTarget), object.userdata));
}
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,
identity: *const anyopaque,
userdata: []u8,
behavior: *const Behavior,
fields: ValueTable,
pub const Behavior = struct {
caller: *const Caller = default_call,
deinitialize: *const Deinitializer = default_deinitialize,
getter: *const Getter = default_get,
setter: *const Setter = default_set,
fn default_call(_: CallContext) RuntimeError!Value {
return error.InvalidOperation;
}
fn default_deinitialize(_: DeinitializeContext) void {
// Nothing to deinitialize by default.
}
fn default_get(context: GetContext) RuntimeError!Value {
switch (context.index) {
.object => |index| {
if (!index.is_string()) return error.InvalidOperation;
return context.obj.fields.lookup(index.userdata) orelse .nil;
},
.integer => |integer| {
const index = context.env.new_string_from(&.{.{.signed = integer}});
defer context.env.release(index);
return context.obj.fields.lookup(index.userdata) orelse .nil;
},
else => return error.InvalidOperation,
}
}
fn default_set(_: SetContext) RuntimeError!void {
return error.InvalidOperation;
}
};
pub const CallContext = struct {
env: *Environment,
caller: *Object,
callee: *Object,
args: []const Value,
};
pub const Caller = fn (context: CallContext) RuntimeError!Value;
pub const DeinitializeContext = struct {
env: *Environment,
obj: *Object,
};
pub const Deinitializer = fn (context: DeinitializeContext) void;
pub const GetContext = struct {
env: *Environment,
obj: *const Object,
index: Value,
};
pub const Getter = fn (context: GetContext) RuntimeError!Value;
pub const SetContext = struct {
env: *Environment,
obj: *Object,
index: Value,
value: Value,
};
pub const Setter = fn (context: SetContext) RuntimeError!void;
pub fn as_value(self: *Object) Value {
return .{.object = self};
}
pub fn is_string(self: Object) bool {
// Userdata represents a string (of bytes) if it's identity is it's userdata.
return self.identity == @ptrCast(*const anyopaque, self.userdata.ptr);
}
};
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);

184
source/ona/file.zig Normal file
View File

@ -0,0 +1,184 @@
const coral = @import("coral");
const ext = @import("./ext.zig");
pub const Contents = struct {
allocator: coral.io.Allocator,
data: []u8,
pub const InitError = coral.io.AllocationError || Readable.ReadError;
pub fn deinit(self: *Contents) void {
coral.io.deallocate(self.allocator, self.data);
}
pub fn init(allocator: coral.io.Allocator, readable_file: *Readable) InitError!Contents {
const file_offset = try readable_file.skip(0);
const file_size = try readable_file.seek_end();
_ = try readable_file.seek(file_offset);
const allocation = try coral.io.allocate_many(u8, file_size, allocator);
errdefer coral.io.deallocate(allocator, allocation);
if (try readable_file.read(allocation) != allocation.len) {
// Read less than was allocated for.
return error.FileUnavailable;
}
return Contents{
.allocator = allocator,
.data = allocation,
};
}
};
pub const Path = extern struct {
data: [4096]u8 = [_]u8{0} ** 4096,
pub const cwd = Path.from(&.{"./"});
pub const ValidationError = error {
PathTooLong,
};
pub fn from(components: []const []const u8) Path {
// TODO: Implement proper parsing / removal of duplicate path delimiters.
var path = Path{};
{
var writable_slice = coral.io.WritableMemory{.slice = &path.data};
for (components) |component| {
if (writable_slice.write(component) != component.len) {
break;
}
}
}
return path;
}
pub fn joined(self: Path, other: Path) Path {
var path = Path{};
{
var writable = coral.io.WritableMemory{.slice = &path.data};
var written = @as(usize, 0);
for (&self.data) |byte| {
if ((byte == 0) or !(writable.put(byte))) {
break;
}
written += 1;
}
if ((written > 0) and (path.data[written - 1] != '/') and writable.put('/')) {
written += 1;
}
for (&other.data) |byte| {
if ((byte == 0) or !(writable.put(byte))) {
break;
}
written += 1;
}
}
return path;
}
pub fn to_string(self: Path) ValidationError![:0]const u8 {
const sentineled_data = get_sentineled_data: {
const last_index = self.data.len - 1;
if (self.data[last_index] != 0) {
return error.PathTooLong;
}
break: get_sentineled_data self.data[0 .. last_index:0];
};
return sentineled_data[0 .. coral.io.sentinel_index(u8, 0, sentineled_data):0];
}
};
pub const Readable = opaque {
pub const ReadError = error {
FileUnavailable,
};
pub fn close(self: *Readable) bool {
return ext.SDL_RWclose(rw_ops_cast(self)) == 0;
}
pub fn read(self: *Readable, buffer: []u8) ReadError!usize {
ext.SDL_ClearError();
const bytes_read = ext.SDL_RWread(rw_ops_cast(self), buffer.ptr, @sizeOf(u8), buffer.len);
if ((bytes_read == 0) and (ext.SDL_GetError() != null)) {
return error.FileUnavailable;
}
return bytes_read;
}
pub fn seek(self: *Readable, cursor: u64) ReadError!u64 {
// TODO: Fix safety of int cast.
const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), @intCast(i64, cursor), ext.RW_SEEK_SET);
if (byte_offset < 0) {
return error.FileUnavailable;
}
return @intCast(u64, byte_offset);
}
pub fn seek_end(self: *Readable) ReadError!usize {
const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), 0, ext.RW_SEEK_END);
if (byte_offset < 0) {
return error.FileUnavailable;
}
return @intCast(u64, byte_offset);
}
pub fn skip(self: *Readable, offset: i64) ReadError!u64 {
const byte_offset = ext.SDL_RWseek(rw_ops_cast(self), offset, ext.RW_SEEK_CUR);
if (byte_offset < 0) {
return error.FileUnavailable;
}
return @intCast(u64, byte_offset);
}
};
pub const System = union (enum) {
sandboxed_path: *const Path,
pub const OpenError = Path.ValidationError || error {
FileNotFound,
};
pub fn open_readable(self: System, path: Path) OpenError!*Readable {
switch (self) {
.sandboxed_path => |sandboxed_path| {
const absolute_path = sandboxed_path.joined(path);
return @ptrCast(*Readable, ext.SDL_RWFromFile(try absolute_path.to_string(), "rb") orelse {
return error.FileNotFound;
});
},
}
}
};
fn rw_ops_cast(ptr: *anyopaque) *ext.SDL_RWops {
return @ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), ptr));
}

View File

@ -1,201 +0,0 @@
const coral = @import("coral");
const ext = @import("./ext.zig");
pub const FileAccessor = struct {
context: *anyopaque,
actions: *const struct {
open_readable: *const fn (context: *anyopaque, file_path: []const u8) OpenError!*ReadableFile,
open_writable: *const fn (context: *anyopaque, file_path: []const u8) OpenError!*WritableFile,
query: *const fn (context: *anyopaque, file_path: []const u8) QueryError!FileInfo,
},
pub fn bind(comptime State: type, state: *State) FileAccessor {
const Actions = struct {
fn as_concrete(context: *anyopaque) *State {
return @ptrCast(*State, @alignCast(@alignOf(State), context));
}
fn open_readable(context: *anyopaque, file_path: []const u8) OpenError!*ReadableFile {
return as_concrete(context).open_readable(file_path);
}
fn open_writable(context: *anyopaque, file_path: []const u8) OpenError!*WritableFile {
return as_concrete(context).open_writable(file_path);
}
fn query(context: *anyopaque, file_path: []const u8) QueryError!FileInfo {
return as_concrete(context).query(file_path);
}
};
return .{
.context = @ptrCast(*anyopaque, state),
.actions = &.{
.open_readable = Actions.open_readable,
.open_writable = Actions.open_writable,
.query = Actions.query,
},
};
}
pub fn open_readable(self: FileAccessor, file_path: []const u8) OpenError!*ReadableFile {
return self.actions.open_readable(self.context, file_path);
}
pub fn open_writable(self: FileAccessor, file_path: []const u8) OpenError!*WritableFile {
return self.actions.open_readable(self.context, file_path);
}
pub fn query(self: FileAccessor, file_path: []const u8) QueryError!FileInfo {
return self.actions.query(self.context, file_path);
}
};
pub const FileInfo = struct {
size: u64,
};
pub const FileSandbox = struct {
prefix: []const u8,
flags: packed struct {
is_readable: bool = false,
is_writable: bool = false,
is_queryable: bool = false,
},
const native_path_max = 4095;
fn native_path_of(file_sandbox: *FileSandbox, file_path: []const u8) [native_path_max + 1]u8 {
var native_path = [_]u8{0} ** (native_path_max + 1);
if ((file_sandbox.prefix.len + file_path.len) < native_path_max) {
coral.io.copy(&native_path, file_sandbox.prefix);
coral.io.copy(native_path[file_sandbox.prefix.len ..], file_path);
}
return native_path;
}
pub fn open_readable(file_sandbox: *FileSandbox, file_path: []const u8) OpenError!*ReadableFile {
if (!file_sandbox.flags.is_readable) return error.AccessDenied;
return @ptrCast(*ReadableFile, ext.SDL_RWFromFile(&file_sandbox.native_path_of(file_path), "rb") orelse {
return error.FileNotFound;
});
}
pub fn open_writable(file_sandbox: *FileSandbox, file_path: []const u8) OpenError!*WritableFile {
if (!file_sandbox.flags.is_writable) return error.AccessDenied;
return @ptrCast(*WritableFile, ext.SDL_RWFromFile(&file_sandbox.native_path_of(file_path), "wb") orelse {
return error.FileNotFound;
});
}
pub fn query(file_sandbox: *FileSandbox, file_path: []const u8) QueryError!FileInfo {
if (!file_sandbox.flags.is_queryable) return error.AccessDenied;
const rw_ops = ext.SDL_RWFromFile(&file_sandbox.native_path_of(file_path), "rb") orelse {
return error.FileNotFound;
};
defer _ = ext.SDL_RWclose(rw_ops);
const file_size = ext.SDL_RWsize(rw_ops);
if (file_size < 0) return error.FileNotFound;
return FileInfo{
.size = @intCast(u64, file_size),
};
}
};
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, read);
}
fn as_rw_ops(self: *ReadableFile) *ext.SDL_RWops {
return @ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), self));
}
pub fn close(self: *ReadableFile) bool {
return ext.SDL_RWclose(self.as_rw_ops()) != 0;
}
pub fn read(self: *ReadableFile, buffer: []u8) coral.io.ReadError!usize {
ext.SDL_ClearError();
const buffer_read = ext.SDL_RWread(self.as_rw_ops(), buffer.ptr, @sizeOf(u8), buffer.len);
if ((buffer_read == 0) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable;
return buffer_read;
}
pub fn rewind(self: *ReadableFile) SeekError!void {
return self.seek(0);
}
pub fn seek(self: *ReadableFile, absolute: u64) SeekError!void {
ext.SDL_ClearError();
// TODO: Fix int cast.
const sought = ext.SDL_RWseek(self.as_rw_ops(), @intCast(i64, absolute), ext.RW_SEEK_SET);
if ((sought == -1) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable;
}
};
pub const QueryError = error {
FileNotFound,
AccessDenied,
};
pub const SeekError = error {
IoUnavailable,
};
pub const WritableFile = opaque {
pub fn as_writer(self: *WritableFile) coral.io.Writer {
return coral.io.Writer.bind(WritableFile, self);
}
fn as_rw_ops(self: *WritableFile) *ext.SDL_RWops {
return @ptrCast(*ext.SDL_RWops, @alignCast(@alignOf(ext.SDL_RWops), self));
}
pub fn close(self: *WritableFile) bool {
return ext.SDL_RWclose(self.as_rw_ops()) != 0;
}
pub fn rewind(self: *WritableFile) SeekError!void {
return self.seek(0);
}
pub fn seek(self: *WritableFile, absolute: u64) SeekError!void {
ext.SDL_ClearError();
// TODO: Fix int cast.
const sought = ext.SDL_RWseek(self.as_rw_ops(), @intCast(i64, absolute), ext.RW_SEEK_SET);
if ((sought == -1) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable;
}
pub fn write(self: *WritableFile, buffer: []const u8) coral.io.WriteError!usize {
ext.SDL_ClearError();
const buffer_read = ext.SDL_RWwrite(self.as_rw_ops(), buffer.ptr, @sizeOf(u8), buffer.len);
if ((buffer_read == 0) and (ext.SDL_GetError().* != 0)) return error.IoUnavailable;
return buffer_read;
}
};

46
source/ona/heap.zig Normal file
View File

@ -0,0 +1,46 @@
const coral = @import("coral");
const ext = @import("./ext.zig");
const Context = struct {
live_allocations: usize,
const Self = @This();
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 (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)[0 .. options.size];
}
return null;
}
};
var context = Context{
.live_allocations = 0,
};
///
/// Heap allocator.
///
pub const allocator = coral.io.Allocator.bind(Context, &context, Context.reallocate);

5
source/ona/kym.zig Executable file
View File

@ -0,0 +1,5 @@
pub const Environment = @import("./kym/Environment.zig");
const coral = @import("coral");
const types = @import("./kym/types.zig");

View File

@ -0,0 +1,420 @@
const bytecode = @import("./bytecode.zig");
const coral = @import("coral");
const file = @import("../file.zig");
const tokens = @import("./tokens.zig");
const types = @import("./types.zig");
allocator: coral.io.Allocator,
heap: ObjectSlab,
global_object: types.Object,
interned: InternTable,
reporter: Reporter,
values: ValueStack,
calls: CallStack,
const CallStack = coral.list.Stack(struct {
ip: usize,
slots: []types.Val,
});
pub const ExecuteFileError = file.System.OpenError || file.Readable.ReadError || types.CompileError;
pub const InitOptions = struct {
values_max: u32,
calls_max: u32,
reporter: Reporter,
};
const InternTable = coral.table.Hashed([]const u8, types.Object, coral.table.string_keyer);
const Object = struct {
ref_count: usize,
state: struct {
info: ObjectInfo,
userdata: []u8,
fields: Fields,
},
const Fields = coral.table.Hashed(*Object, types.Val, .{
.hasher = struct {
fn hash(object: *Object) coral.table.Hash {
coral.debug.assert(object.state.info.identity == null);
return coral.table.hash_string(object.state.userdata);
}
}.hash,
.comparer = struct {
fn compare(object_a: *Object, object_b: *Object) isize {
coral.debug.assert(object_a.state.info.identity == null);
coral.debug.assert(object_b.state.info.identity == null);
return coral.io.compare(object_a.state.userdata, object_b.state.userdata);
}
}.compare,
});
pub fn acquire(self: *Object) void {
coral.debug.assert(self.ref_count != 0);
self.ref_count += 1;
}
pub fn release(self: *Object, env: *Self) bool {
coral.debug.assert(self.ref_count != 0);
self.ref_count -= 1;
if (self.ref_count == 0) {
coral.io.deallocate(env.allocator, self.state.userdata);
self.state.fields.deinit(env.allocator);
return false;
}
return true;
}
};
pub const ObjectInfo = struct {
caller: *const Caller = default_call,
deinitializer: *const Deinitializer = default_deinitialize,
getter: *const Getter = default_get,
identity: ?*const anyopaque = null,
setter: *const Setter = default_set,
pub const CallContext = struct {
env: *Self,
caller: types.Ref,
callable: types.Ref,
args: []const types.Ref,
};
pub const Caller = fn (context: CallContext) types.RuntimeError!types.Val;
pub const DeinitializeContext = struct {
env: *Self,
obj: types.Ref,
};
pub const Deinitializer = fn (context: DeinitializeContext) void;
pub const GetContext = struct {
env: *Self,
indexable: types.Ref,
index: types.Ref,
};
pub const Getter = fn (context: GetContext) types.RuntimeError!types.Val;
pub const SetContext = struct {
env: *Self,
indexable: types.Ref,
index: types.Ref,
value: types.Ref,
};
pub const Setter = fn (context: SetContext) types.RuntimeError!void;
fn default_call(context: CallContext) types.RuntimeError!types.Val {
return context.env.fail("attempt to call non-callable");
}
fn default_deinitialize(_: DeinitializeContext) void {
// Nothing to deinitialize by default.
}
fn default_get(context: GetContext) types.RuntimeError!types.Val {
return context.env.get_field(context.indexable, context.index);
}
fn default_set(context: SetContext) types.RuntimeError!void {
return context.env.fail("attempt to set non-indexable");
}
};
const ObjectSlab = coral.slab.Map(u32, Object);
pub const Reporter = coral.io.Functor(void, []const u8);
const Self = @This();
const ValueStack = coral.list.Stack(types.Ref);
pub fn call(self: *Self, caller: types.Ref, maybe_index: ?types.Ref, args: []const types.Ref) types.RuntimeError!types.Val {
if (maybe_index) |index| {
try self.check(caller == .object, "invalid type conversion: object");
const callable = try self.get_object(caller, index);
defer self.discard(callable);
try self.check(callable == .object, "invalid type conversion: object");
return self.heap.fetch(callable.object).state.info.caller(.{
.env = self,
.callable = callable.as_ref(),
.caller = caller,
.args = args,
});
}
return self.heap.fetch(caller.object).state.info.caller(.{
.env = self,
.callable = caller,
.caller = .{.object = self.global_object},
.args = args,
});
}
pub fn check(self: *Self, condition: bool, failure_message: []const u8) !void {
if (condition) {
return;
}
return self.fail(failure_message);
}
pub fn deinit(self: *Self) void {
self.values.deinit(self.allocator);
self.calls.deinit(self.allocator);
}
pub fn discard(self: *Self, val: types.Val) void {
switch (val) {
.object => |object| {
if (!self.heap.fetch(object).release(self)) {
self.heap.remove(object);
}
},
else => {},
}
}
pub fn execute_file(self: *Self, allocator: coral.io.Allocator, fs: file.System, file_path: file.Path) ExecuteFileError!types.Val {
const typeid = "<chunk>";
const Behaviors = struct {
fn deinitialize(context: ObjectInfo.DeinitializeContext) void {
(context.env.native_cast(context.obj, typeid, bytecode.Chunk) catch unreachable).deinit();
}
};
var chunk = try bytecode.Chunk.init(allocator, self, try file_path.to_string());
errdefer chunk.deinit();
{
const readable_file = try fs.open_readable(file_path);
defer if (!readable_file.close()) {
@panic("Failed to close script file");
};
var file_contents = try file.Contents.init(allocator, readable_file);
defer file_contents.deinit();
var tokenizer = tokens.Tokenizer{.source = file_contents.data};
try chunk.parse(self, &tokenizer);
}
const script = try self.new_object(coral.io.bytes_of(&chunk), .{
.identity = typeid,
.deinitializer = Behaviors.deinitialize,
});
self.discard(script);
return try self.call(script.as_ref(), null, &.{});
}
pub fn fail(self: *Self, failure_message: []const u8) error { CheckFailed } {
self.reporter.invoke(failure_message);
return error.CheckFailed;
}
pub fn get_field(self: *Self, indexable: types.Ref, field: types.Ref) !types.Val {
try self.check(indexable == .object, "invalid type conversion: object");
try self.check(field == .object, "invalid type conversion: object");
const value = self.heap.fetch(indexable.object).state.fields.lookup(self.heap.fetch(field.object)) orelse {
return .nil;
};
if (value == .object) {
self.heap.fetch(value.object).acquire();
}
return value;
}
pub fn get_object(self: *Self, indexable: types.Ref, index: types.Ref) types.RuntimeError!types.Val {
try self.check(indexable == .object, "invalid type conversion: object");
return self.heap.fetch(indexable.object).state.info.getter(.{
.env = self,
.indexable = indexable,
.index = index,
});
}
pub fn init(allocator: coral.io.Allocator, options: InitOptions) !Self {
var values = try ValueStack.init(allocator, options.values_max * options.calls_max);
errdefer values.deinit(allocator);
var calls = try CallStack.init(allocator, options.calls_max);
errdefer calls.deinit(allocator);
var interned = try InternTable.init(allocator);
errdefer interned.deinit(allocator);
var heap = try ObjectSlab.init(allocator);
errdefer heap.deinit(allocator);
var environment = Self{
.global_object = 0,
.allocator = allocator,
.reporter = options.reporter,
.interned = interned,
.values = values,
.calls = calls,
.heap = heap,
};
const globals = try environment.new_object(&.{}, .{
.identity = "KYM GLOBAL OBJECT OC DO NOT STEAL",
});
coral.debug.assert(globals == .object);
environment.global_object = globals.object;
return environment;
}
pub fn intern(self: *Self, string: []const u8) coral.io.AllocationError!types.Ref {
return .{.object = self.interned.lookup(string) orelse {
const reference = try self.new_string(string);
coral.debug.assert(reference == .object);
coral.debug.assert(try self.interned.insert(self.allocator, string, reference.object));
return .{.object = reference.object};
}};
}
pub fn native_cast(self: *Self, castable: types.Ref, id: *const anyopaque, comptime Type: type) types.RuntimeError!*Type {
try self.check(castable == .object, "invalid type conversion: object");
const object = self.heap.fetch(castable.object);
const alignment = @alignOf(Type);
const is_expected_type = (object.state.info.identity == id) and (object.state.userdata.len == alignment);
try self.check(is_expected_type, "invalid object cast: native type");
return @ptrCast(*Type, @alignCast(alignment, object.state.userdata));
}
pub fn new_array(self: *Self) coral.io.AllocationError!types.Val {
return try self.new_object(.{
});
}
pub fn new_object(self: *Self, userdata: []const u8, info: ObjectInfo) coral.io.AllocationError!types.Val {
const allocation = try coral.io.allocate_many(u8, userdata.len, self.allocator);
errdefer coral.io.deallocate(self.allocator, allocation);
coral.io.copy(allocation, userdata);
var fields = try Object.Fields.init(self.allocator);
errdefer fields.deinit(self.allocator);
return .{.object = try self.heap.insert(self.allocator, .{
.ref_count = 1,
.state = .{
.info = info,
.userdata = allocation,
.fields = fields,
},
})};
}
pub fn new_string(self: *Self, data: []const u8) coral.io.AllocationError!types.Val {
const Behavior = struct {
fn get_byte(context: ObjectInfo.GetContext) types.RuntimeError!types.Val {
switch (context.index) {
.integer => |integer| {
const string = context.env.string_cast(context.indexable) catch unreachable;
try context.env.check(coral.math.is_clamped(integer, 0, string.len), "index out of string bounds");
return types.Val{.integer = string[@intCast(usize, integer)]};
},
else => return context.env.fail("attempt to index string with non-integer value"),
}
}
};
return try self.new_object(data, .{
.getter = Behavior.get_byte,
});
}
pub fn set_global(self: *Self, global_name: []const u8, value: types.Ref) coral.io.AllocationError!void {
try self.globals.assign(self.allocator, global_name, value);
}
pub fn set_object(self: *Self, obj: *Object, index: types.Ref, value: types.Ref) types.RuntimeError!void {
return obj.behavior.setter(.{
.env = self,
.obj = obj,
.index = index,
.value = value,
});
}
pub fn string_cast(self: *Self, value: types.Ref) ![]const u8 {
try self.check(value == .object, "invalid type conversion: object");
const object = self.heap.fetch(value.object);
try self.check(object.state.info.identity == null, "invalid object cast: string");
return object.state.userdata;
}
pub fn to_integer(self: *Self, value: types.Ref) !types.Integer {
const fail_message = "invalid type conversion: integer";
switch (value) {
.float => |float| {
const int = @typeInfo(types.Integer).Int;
if (coral.math.is_clamped(float, coral.math.min_int(int), coral.math.max_int(int))) {
return @floatToInt(types.Integer, float);
}
},
.integer => |integer| return integer,
else => {},
}
return self.fail(fail_message);
}

View File

@ -1,169 +1,103 @@
const Environment = @import("./Environment.zig");
const coral = @import("coral");
const tokens = @import("./tokens.zig");
const vm = @import("./vm.zig");
const types = @import("./types.zig");
pub const Chunk = struct {
vm_state: *vm.State,
env: *Environment,
allocator: coral.io.Allocator,
arity: usize,
name_string: *vm.ObjectInstance,
interned_instances: *vm.ObjectInstance,
bytecode_buffer: Buffer,
name: []u8,
bytecodes: Buffer,
objects: Objects,
const Buffer = coral.stack.Dense(u8);
const Buffer = coral.list.Stack(u8);
const InstanceStack = coral.stack.Dense(*vm.ObjectInstance);
const Objects = coral.list.Stack(types.Object);
pub fn deinit(self: *Chunk) void {
self.bytecode_buffer.deinit();
for (self.objects.values) |object| {
if (!self.env.heap.fetch(object).release(self.env)) {
self.env.heap.remove(object);
}
}
fn emit_byte(self: *Chunk, byte: u8) !void {
return self.bytecode_buffer.push_one(byte);
coral.io.deallocate(self.allocator, self.name);
self.bytecodes.deinit(self.allocator);
self.objects.deinit(self.allocator);
}
fn emit_float(self: *Chunk, value: Float) !void {
return self.bytecode_buffer.push_all(coral.io.bytes_of(&value));
fn emit_byte(self: *Chunk, byte: u8) coral.io.AllocationError!void {
return self.bytecodes.push_one(self.allocator, byte);
}
fn emit_integer(self: *Chunk, value: Integer) !void {
return self.bytecode_buffer.push_all(coral.io.bytes_of(&value));
fn emit_closure(self: *Chunk, chunk: Chunk) coral.io.AllocationError!void {
const value = try self.env.new_object(coral.io.bytes_of(&chunk), .{
});
coral.debug.assert(value == .object);
try self.objects.push_one(self.allocator, value.object);
}
fn emit_opcode(self: *Chunk, opcode: Opcode) !void {
return self.bytecode_buffer.push_one(@enumToInt(opcode));
fn emit_float(self: *Chunk, value: types.Float) coral.io.AllocationError!void {
return self.bytecodes.push_all(self.allocator, coral.io.bytes_of(&value));
}
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.*];
fn emit_integer(self: *Chunk, value: types.Integer) coral.io.AllocationError!void {
return self.bytecodes.push_all(self.allocator, coral.io.bytes_of(&value));
}
pub fn fetch_float(self: Chunk, cursor: *usize) ?Float {
const operand_size = @sizeOf(Float);
const updated_cursor = cursor.* + operand_size;
if (updated_cursor > self.bytecode_buffer.values.len) return null;
var operand_bytes align(@alignOf(Float)) = [_]u8{0} ** operand_size;
coral.io.copy(&operand_bytes, self.bytecode_buffer.values[cursor.* .. updated_cursor]);
cursor.* = updated_cursor;
return @bitCast(Float, operand_bytes);
fn emit_opcode(self: *Chunk, opcode: Opcode) coral.io.AllocationError!void {
return self.bytecodes.push_one(self.allocator, @enumToInt(opcode));
}
pub fn fetch_integer(self: Chunk, cursor: *usize) ?Integer {
const operand_size = @sizeOf(Integer);
const updated_cursor = cursor.* + operand_size;
fn emit_string(self: *Chunk, string: []const u8) coral.io.AllocationError!void {
const interned_string = try self.env.intern(string);
if (updated_cursor > self.bytecode_buffer.values.len) return null;
coral.debug.assert(interned_string == .object);
var operand_bytes align(@alignOf(Integer)) = [_]u8{0} ** operand_size;
coral.io.copy(&operand_bytes, self.bytecode_buffer.values[cursor.* .. updated_cursor]);
cursor.* = updated_cursor;
return @bitCast(Integer, operand_bytes);
return try self.bytecodes.push_all(self.allocator, coral.io.bytes_of(&interned_string));
}
pub fn fetch_interned(self: Chunk, cursor: *usize) ?*vm.ObjectInstance {
const interned_value = self.vm_state.get(self.interned_instances, .{
.integer = self.fetch_integer(cursor) orelse return null
}) catch return null;
coral.debug.assert(interned_value == .instance);
return interned_value.instance;
}
pub fn fetch_opcode(self: Chunk, cursor: *usize) ?Opcode {
return @intToEnum(Opcode, self.fetch_byte(cursor) orelse return null);
}
pub fn init(vm_state: *vm.State, name: []const u8) !Chunk {
pub fn init(allocator: coral.io.Allocator, env: *Environment, name: []const u8) !Chunk {
const assumed_average_bytecode_size = 1024;
var bytecode_buffer = try Buffer.init(vm_state.allocator, assumed_average_bytecode_size);
var bytecodes = try Buffer.init(allocator, assumed_average_bytecode_size);
errdefer bytecode_buffer.deinit();
errdefer bytecodes.deinit(allocator);
var objects = try Objects.init(allocator, 1);
errdefer objects.deinit(allocator);
const name_copy = try coral.io.allocate_many(u8, name.len, allocator);
errdefer coral.io.deallocate(name_copy);
coral.io.copy(name_copy, name);
return Chunk{
.name_string = try vm_state.new_string(name),
.interned_instances = try vm_state.new_array(),
.vm_state = vm_state,
.bytecode_buffer = bytecode_buffer,
.allocator = allocator,
.env = env,
.name = name_copy,
.bytecodes = bytecodes,
.objects = objects,
.arity = 0,
};
}
fn intern_chunk(self: *Chunk, chunk: *Chunk) !vm.Integer {
var constant_slot = @as(vm.Integer, 0);
const interned_count = try self.vm_state.count(self.interned_instances);
while (constant_slot < interned_count) : (constant_slot += 1) {
const interned_value = try self.vm_state.get(self.interned_instances, .{.integer = constant_slot});
coral.debug.assert(interned_value == .instance);
switch (self.vm_state.userdata_of(interned_value.instance).*) {
.chunk => |interned_chunk| if (interned_chunk == chunk) return constant_slot,
else => continue,
}
}
try self.vm_state.set(self.interned_instances, .{.integer = constant_slot}, .{
.instance = try self.vm_state.new(.{.chunk = chunk}, .{
.caller = struct {
fn call(state: *vm.State, instance: *vm.ObjectInstance, _: *vm.ObjectInstance, arguments: []const vm.Value) vm.RuntimeError!vm.Value {
const instance_chunk =
@ptrCast(*Chunk, @alignCast(@alignOf(Chunk), state.userdata_of(instance).native));
coral.debug.assert(instance_chunk.vm_state == state);
return vm.execute_chunk(instance_chunk, arguments);
}
}.call,
})
});
return constant_slot;
}
fn intern_string(self: *Chunk, string: []const u8) !vm.Integer {
var constant_slot = @as(vm.Integer, 0);
const interned_count = try self.vm_state.count(self.interned_instances);
while (constant_slot < interned_count) : (constant_slot += 1) {
const interned_value = try self.vm_state.get(self.interned_instances, .{.integer = constant_slot});
coral.debug.assert(interned_value == .instance);
switch (self.vm_state.userdata_of(interned_value.instance).*) {
.string => |interned_string| if (coral.io.equals(interned_string, string)) return constant_slot,
else => continue,
}
}
try self.vm_state.set(self.interned_instances, .{.integer = constant_slot}, .{
.instance = try self.vm_state.new_string(string)
});
return constant_slot;
}
pub fn parse(self: *Chunk, script_tokenizer: *tokens.Tokenizer) ParseError!void {
pub fn parse(self: *Chunk, env: *Environment, script_tokenizer: *tokens.Tokenizer) types.CompileError!void {
errdefer self.reset();
self.reset();
var parser = Parser{.tokenizer = script_tokenizer};
var parser = Parser{
.env = env,
.tokenizer = script_tokenizer
};
while (true) {
parser.step() catch |step_error| switch (step_error) {
@ -183,14 +117,10 @@ pub const Chunk = struct {
}
pub fn reset(self: *Chunk) void {
self.bytecode_buffer.clear();
self.bytecodes.clear();
}
};
pub const Float = f32;
pub const Integer = i32;
pub const Opcode = enum(u8) {
pop,
push_nil,
@ -199,9 +129,8 @@ pub const Opcode = enum(u8) {
push_zero,
push_integer,
push_float,
push_array,
push_object,
push_table,
push_interned,
not,
neg,
@ -219,13 +148,8 @@ pub const Opcode = enum(u8) {
set_local,
};
pub const ParseError = vm.RuntimeError || Parser.StepError || tokens.Token.ExpectError || error {
OutOfMemory,
IntOverflow,
UndefinedLocal,
};
const Parser = struct {
env: *Environment,
tokenizer: *tokens.Tokenizer,
current_token: tokens.Token = .newline,
previous_token: tokens.Token = .newline,
@ -287,12 +211,8 @@ const Parser = struct {
const operator_tokens = &.{.symbol_assign, .symbol_plus,
.symbol_dash, .symbol_asterisk, .symbol_forward_slash, .symbol_paren_left, .symbol_comma};
fn parse_closure(self: *Parser, parent_chunk: *Chunk) ParseError!void {
const closure_chunk = parent_chunk.vm_state.allocator.allocate_one(Chunk) orelse return error.OutOfMemory;
errdefer parent_chunk.vm_state.allocator.deallocate(closure_chunk);
closure_chunk.* = try Chunk.init(parent_chunk.vm_state, switch (self.previous_token) {
fn parse_closure(self: *Parser, parent_chunk: *Chunk) types.CompileError!void {
var closure_chunk = try Chunk.init(self.env.allocator, self.env, switch (self.previous_token) {
.local_identifier => |identifier| identifier,
.symbol_assign, .symbol_paren_left, .symbol_comma => "<closure>",
else => return error.UnexpectedToken,
@ -336,7 +256,7 @@ const Parser = struct {
switch (self.current_token) {
.symbol_brace_right => break,
else => if (try self.parse_statement(closure_chunk)) continue,
else => if (try self.parse_statement(&closure_chunk)) continue,
}
while (true) {
@ -352,12 +272,11 @@ const Parser = struct {
}
}
try parent_chunk.emit_opcode(.push_interned);
try parent_chunk.emit_integer(try parent_chunk.intern_chunk(closure_chunk));
try parent_chunk.emit_opcode(.push_closure);
try parent_chunk.emit_opcode(.push_object);
try parent_chunk.emit_closure(closure_chunk);
}
fn parse_expression(self: *Parser, chunk: *Chunk) ParseError!void {
fn parse_expression(self: *Parser, chunk: *Chunk) types.CompileError!void {
var operators = OperatorStack{};
var local_depth = @as(usize, 0);
@ -393,7 +312,7 @@ const Parser = struct {
.integer_literal => |literal| {
try self.previous_token.expect_any(operator_tokens);
const value = coral.utf8.parse_signed(@bitSizeOf(i64), literal)
const value = coral.utf8.parse_signed(@bitSizeOf(types.Integer), literal)
catch |parse_error| switch (parse_error) {
error.BadSyntax => unreachable,
error.IntOverflow => return error.IntOverflow,
@ -412,7 +331,7 @@ const Parser = struct {
.real_literal => |literal| {
try self.previous_token.expect_any(operator_tokens);
try chunk.emit_float(coral.utf8.parse_float(@bitSizeOf(f64), literal) catch |parse_error| {
try chunk.emit_float(coral.utf8.parse_float(@bitSizeOf(types.Float), 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.
@ -425,8 +344,8 @@ const Parser = struct {
.string_literal => |literal| {
try self.previous_token.expect_any(operator_tokens);
try chunk.emit_opcode(.push_interned);
try chunk.emit_integer(try chunk.intern_string(literal));
try chunk.emit_opcode(.push_object);
try chunk.emit_string(literal);
try self.step();
},
@ -479,7 +398,7 @@ const Parser = struct {
switch (self.previous_token) {
.global_identifier => |identifier| {
try chunk.emit_opcode(.get_global);
try chunk.emit_integer(try chunk.intern_string(identifier));
try chunk.emit_string(identifier);
},
.local_identifier => |identifier| {
@ -489,7 +408,7 @@ const Parser = struct {
});
} else {
try chunk.emit_opcode(.get_index);
try chunk.emit_integer(try chunk.intern_string(identifier));
try chunk.emit_string(identifier);
}
},
@ -510,13 +429,13 @@ const Parser = struct {
});
} else {
try chunk.emit_opcode(.get_index);
try chunk.emit_integer(try chunk.intern_string(identifier));
try chunk.emit_string(identifier);
}
},
.global_identifier => |identifier| {
try chunk.emit_opcode(.get_global);
try chunk.emit_integer(try chunk.intern_string(identifier));
try chunk.emit_string(identifier);
},
else => {
@ -532,7 +451,7 @@ const Parser = struct {
local_depth += 1;
var argument_count = @as(Integer, 0);
var argument_count = @as(types.Integer, 0);
while (true) {
try self.step();
@ -564,7 +483,7 @@ const Parser = struct {
else => false,
};
var field_count = @as(Integer, 0);
var field_count = @as(types.Integer, 0);
while (true) {
try self.step();
@ -574,11 +493,10 @@ const Parser = struct {
.local_identifier => {
// Create local copy of identifier because step() will overwrite captures.
const interned_identifier =
try chunk.intern_string(self.current_token.local_identifier);
const identifier = self.current_token.local_identifier;
try chunk.emit_opcode(.push_interned);
try chunk.emit_integer(interned_identifier);
try chunk.emit_opcode(.push_object);
try chunk.emit_string(identifier);
try self.step();
switch (self.current_token) {
@ -590,8 +508,8 @@ const Parser = struct {
},
.symbol_brace_right => {
try chunk.emit_opcode(.push_interned);
try chunk.emit_integer(interned_identifier);
try chunk.emit_opcode(.push_object);
try chunk.emit_string(identifier);
field_count += 1;
@ -599,8 +517,8 @@ const Parser = struct {
},
.symbol_comma => {
try chunk.emit_opcode(.push_interned);
try chunk.emit_integer(interned_identifier);
try chunk.emit_opcode(.push_object);
try chunk.emit_string(identifier);
field_count += 1;
},
@ -642,7 +560,7 @@ const Parser = struct {
}
}
fn parse_operator(self: *Parser, chunk: *Chunk, operators: *OperatorStack, rhs_operator: Operator) ParseError!void {
fn parse_operator(self: *Parser, chunk: *Chunk, operators: *OperatorStack, rhs_operator: Operator) types.CompileError!void {
try self.previous_token.expect_any(operator_tokens);
while (operators.pop()) |lhs_operator| {
@ -655,7 +573,7 @@ const Parser = struct {
try self.step();
}
fn parse_statement(self: *Parser, chunk: *Chunk) ParseError!bool {
fn parse_statement(self: *Parser, chunk: *Chunk) types.CompileError!bool {
var local_depth = @as(usize, 0);
while (true) {
@ -694,7 +612,7 @@ const Parser = struct {
try self.step();
try self.current_token.expect(.local_identifier);
try chunk.emit_opcode(.get_global);
try chunk.emit_integer(try chunk.intern_string(identifier));
try chunk.emit_string(identifier);
local_depth += 1;
},
@ -707,12 +625,10 @@ const Parser = struct {
try self.current_token.expect(.local_identifier);
if (local_depth == 0) {
try chunk.emit_byte(self.resolve_local(identifier) orelse {
return error.UndefinedLocal;
});
try chunk.emit_byte(self.resolve_local(identifier) orelse return error.UndefinedLocal);
} else {
try chunk.emit_opcode(.get_index);
try chunk.emit_integer(try chunk.intern_string(identifier));
try chunk.emit_string(identifier);
}
local_depth += 1;
@ -735,7 +651,7 @@ const Parser = struct {
}
} else {
try chunk.emit_opcode(.set_index);
try chunk.emit_integer(try chunk.intern_string(identifier));
try chunk.emit_string(identifier);
}
try self.step();
@ -759,19 +675,19 @@ const Parser = struct {
});
} else {
try chunk.emit_opcode(.get_index);
try chunk.emit_integer(try chunk.intern_string(identifier));
try chunk.emit_string(identifier);
}
},
.global_identifier => |identifier| {
try chunk.emit_opcode(.get_global);
try chunk.emit_integer(try chunk.intern_string(identifier));
try chunk.emit_string(identifier);
},
else => return error.UnexpectedToken,
}
var argument_count = @as(Integer, 0);
var argument_count = @as(types.Integer, 0);
while (true) {
try self.step();

56
source/ona/kym/types.zig Normal file
View File

@ -0,0 +1,56 @@
const coral = @import("coral");
pub const CompileError = coral.io.AllocationError || RuntimeError || error {
UnexpectedEnd,
UnexpectedToken,
UndefinedLocal,
};
pub const Float = f32;
pub const Integer = i32;
pub const Object = u32;
pub const Primitive = enum {
nil,
false,
true,
float,
integer,
object,
};
pub const Ref = union (Primitive) {
nil,
false,
true,
float: Float,
integer: Integer,
object: Object,
};
pub const RuntimeError = coral.io.AllocationError || error {
IntOverflow,
CheckFailed,
};
pub const Val = union (Primitive) {
nil,
false,
true,
float: Float,
integer: Integer,
object: Object,
pub fn as_ref(self: *const Val) Ref {
return switch (self.*) {
.nil => .nil,
.false => .false,
.true => .true,
.float => .{.float = self.float},
.integer => .{.integer = self.integer},
.object => .{.object = self.object},
};
}
};

View File

@ -1,38 +1,118 @@
pub const canvas = @import("./canvas.zig");
const builtin = @import("builtin");
const canvas = @import("./canvas.zig");
const coral = @import("coral");
const ext = @import("./ext.zig");
pub const files = @import("./files.zig");
pub const file = @import("./file.zig");
pub const App = opaque {
pub fn Starter(comptime errors: type) type {
return fn (app_state: *App) errors!void;
pub const heap = @import("./heap.zig");
const kym = @import("./kym.zig");
const AppManifest = struct {
title: [255:0]u8 = [_:0]u8{0} ** 255,
width: u16 = 640,
height: u16 = 480,
pub fn load_script(self: *AppManifest, env: *kym.Environment, fs: file.System, file_path: []const u8) !void {
const manifest = try env.execute_file(heap.allocator, fs, file.Path.from(&.{file_path}));
defer env.discard(manifest);
const manifest_ref = manifest.as_ref();
{
const title = try env.get_field(manifest_ref, try env.intern("title"));
defer env.discard(title);
const title_string = try env.string_cast(title.as_ref());
try env.check(title_string.len <= self.title.len, "`title` cannot exceed 255 bytes in length");
coral.io.copy(&self.title, title_string);
}
const State = struct {
base_file_sandbox: files.FileSandbox,
window: *ext.SDL_Window,
renderer: *ext.SDL_Renderer,
last_tick: u64,
tick_rate: u64 = 16,
canvas_items: coral.slots.Dense(coral.slots.addressable_key, canvas.Item),
{
const width = try env.get_field(manifest_ref, try env.intern("width"));
fn cast(self: *App) *State {
return @ptrCast(*State, @alignCast(@alignOf(State), self));
errdefer env.discard(width);
self.width = try coral.math.checked_cast(u16, try env.to_integer(width.as_ref()));
}
{
const height = try env.get_field(manifest_ref, try env.intern("height"));
errdefer env.discard(height);
self.width = try coral.math.checked_cast(u16, try env.to_integer(height.as_ref()));
}
}
};
pub fn run_app(base_file_system: file.System) void {
const Logger = struct {
const Self = @This();
fn log(_: *Self, message: []const u8) void {
ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", message.len, message.ptr);
}
};
pub fn data_fs(self: *App) files.FileAccessor {
return files.FileAccessor.bind(files.FileSandbox, &State.cast(self).base_file_sandbox);
var logger = Logger{};
var script_environment = kym.Environment.init(heap.allocator, .{
.values_max = 512,
.calls_max = 512,
.reporter = kym.Environment.Reporter.bind(Logger, &logger, Logger.log),
}) catch {
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to initialize Kym vm\n");
};
defer script_environment.deinit();
const app_file_name = "app.ona";
var app_manifest = AppManifest{};
app_manifest.load_script(&script_environment, base_file_system, app_file_name) catch {
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "failed to load %s\n", app_file_name);
};
if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) {
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError());
}
pub fn loop(self: *App) void {
const state = State.cast(self);
defer ext.SDL_Quit();
{
const base_prefix = ext.SDL_GetBasePath() orelse {
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError());
};
defer ext.SDL_free(base_prefix);
const window_flags = 0;
const window_pos = ext.SDL_WINDOWPOS_CENTERED;
const window = ext.SDL_CreateWindow(&app_manifest.title, window_pos, window_pos, app_manifest.width, app_manifest.height, window_flags) orelse {
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError());
};
defer ext.SDL_DestroyWindow(window);
const renderer_flags = 0;
const renderer = ext.SDL_CreateRenderer(window, -1, renderer_flags) orelse {
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError());
};
defer ext.SDL_DestroyRenderer(renderer);
while (true) {
const current_tick = ext.SDL_GetTicks64();
// TODO: Delta timing.
var event = @as(ext.SDL_Event, undefined);
while (ext.SDL_PollEvent(&event) != 0) {
@ -42,140 +122,17 @@ pub const App = opaque {
}
}
if ((current_tick - state.last_tick) >= state.tick_rate) {
// TODO: Perform game updates.
if (ext.SDL_SetRenderDrawColor(state.renderer, 255, 255, 255, 255) != 0) {
return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}});
if (ext.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0) != 0) {
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError());
}
if (ext.SDL_RenderClear(state.renderer) != 0) {
return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}});
if (ext.SDL_RenderClear(renderer) != 0) {
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError());
}
for (state.canvas_items.values) |canvas_item| {
switch (canvas_item.options) {
.sprite => {},
// TODO: Render here.
ext.SDL_RenderPresent(renderer);
}
}
ext.SDL_RenderPresent(state.renderer);
state.last_tick = current_tick;
}
}
}
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);
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{
.window = window,
.renderer = renderer,
.last_tick = 0,
.canvas_items = .{},
.base_file_sandbox = .{
.prefix = coral.io.slice_sentineled(u8, 0, base_prefix),
.flags = .{
.is_readable = true,
.is_queryable = true,
}
},
}));
}
};
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, 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 (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)[0 .. options.size];
}
return null;
}
}{};
pub const CanvasItem = struct {
app: *App,
id: coral.slots.Slot(coral.slots.addressable_key),
pub fn deinit(self: *CanvasItem) void {
coral.debug.assert(App.State.cast(self.app).canvas_items.remove(self.id));
}
pub fn init(app: *App, canvas_item: canvas.Item) coral.io.AllocationError!CanvasItem {
const slot = try App.State.cast(app).canvas_items.insert(allocator, canvas_item);
return CanvasItem{
.app = app,
.id = slot,
};
}
};
pub fn log_debug(values: []const coral.format.Value) void {
var message_memory = [_]u8{0} ** 4096;
var message_buffer = coral.buffer.Fixed{.data = &message_memory};
const message_length = coral.format.print(message_buffer.as_writer(), values) catch return;
ext.SDL_LogDebug(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s\n", message_length, &message_buffer);
}
pub fn log_error(values: []const coral.format.Value) void {
var message_memory = [_]u8{0} ** 4096;
var message_buffer = coral.buffer.Fixed{.data = &message_memory};
const message_length = coral.format.print(message_buffer.as_writer(), values) catch return;
ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s\n", message_length, &message_buffer);
}

View File

@ -1,117 +1,5 @@
const coral = @import("coral");
const kym = @import("kym");
const ona = @import("ona");
const transform_typeid = @typeName(ona.canvas.Transform);
fn new_canvas_item(environment: *kym.vm.Environment, canvas_item: ona.CanvasItem) !*kym.vm.Object {
const typeid = @typeName(ona.CanvasItem);
var object = try environment.new(.{
.userdata = coral.io.bytes_of(&canvas_item),
.identity = typeid,
});
errdefer environment.release(object);
return object;
}
fn new_transform(environment: *kym.Environment, transform: ona.canvas.Transform) !*kym.vm.Object {
var object = try environment.new(.{
.userdata = coral.io.bytes_of(&transform),
.identity = transform_typeid,
});
errdefer environment.release(object);
return object;
}
pub fn main() anyerror!void {
return ona.App.run(anyerror, start);
}
fn start(app: *ona.App) anyerror!void {
var kym_environment = try kym.vm.Environment.init(ona.allocator, .{
.stack_max = 256,
.calls_max = 256,
});
defer kym_environment.deinit();
const Ona = struct {
app: *ona.App,
const typeid = @typeName(ona.App);
};
var ona_lib = try kym_environment.new(.{
.userdata = coral.io.bytes_of(&Ona{
.app = app,
}),
.identity = Ona.typeid,
});
defer kym_environment.release(ona_lib);
try kym_environment.global_set("ona", ona_lib.as_value());
{
var sprite_creator = try kym_environment.new(.{.behavior = &.{.caller = struct {
fn call(context: kym.vm.Object.CallContext) kym.vm.RuntimeError!kym.vm.Value {
try context.env.check(context.args.len == 2, "2 arguments expected");
var item = try ona.CanvasItem.init((try context.env.user_cast(context.caller, Ona.typeid, Ona)).app, .{
.transform = (try context.env.user_cast(context.args[0].object, transform_typeid, ona.canvas.Transform)).*,
.options = .{.sprite = .{}},
});
errdefer item.deinit();
return (try new_canvas_item(context.env, item)).as_value();
}
}.call}});
defer kym_environment.release(sprite_creator);
try kym_environment.raw_set(ona_lib, "create_sprite", sprite_creator.as_value());
}
{
const index_path = "index.kym";
const index_file = try app.data_fs().open_readable(index_path);
defer if (!index_file.close()) ona.log_error(&.{.{.string = "failed to close "}, .{.string = index_path}});
const index_size = (try app.data_fs().query(index_path)).size;
const index_allocation = try coral.io.allocate_many(u8, index_size, ona.allocator);
defer coral.io.deallocate(ona.allocator, index_allocation);
var index_buffer = coral.buffer.Fixed{.data = index_allocation[0 .. index_size]};
{
var stream_buffer = [_]u8{0} ** 1024;
if ((try coral.io.stream(index_buffer.as_writer(), index_file.as_reader(), &stream_buffer)) != index_size)
return error.IoUnavailable;
}
{
const index_script = try kym_environment.new_script(.{
.name = index_path,
.data = index_buffer.data
});
defer kym_environment.release(index_script);
_ = try kym_environment.call(index_script, &.{});
}
}
app.loop();
pub fn main() !void {
ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd});
}