Refactor project structure and Ona APIs
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
b1739f0cf8
commit
bb8cb43843
14
build.zig
14
build.zig
|
@ -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");
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
pub const vm = @import("./vm.zig");
|
|
@ -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);
|
|
@ -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));
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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);
|
|
@ -0,0 +1,5 @@
|
|||
pub const Environment = @import("./kym/Environment.zig");
|
||||
|
||||
const coral = @import("coral");
|
||||
|
||||
const types = @import("./kym/types.zig");
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
coral.io.deallocate(self.allocator, self.name);
|
||||
self.bytecodes.deinit(self.allocator);
|
||||
self.objects.deinit(self.allocator);
|
||||
}
|
||||
|
||||
fn emit_byte(self: *Chunk, byte: u8) !void {
|
||||
return self.bytecode_buffer.push_one(byte);
|
||||
fn emit_byte(self: *Chunk, byte: u8) coral.io.AllocationError!void {
|
||||
return self.bytecodes.push_one(self.allocator, byte);
|
||||
}
|
||||
|
||||
fn emit_float(self: *Chunk, value: Float) !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_integer(self: *Chunk, value: Integer) !void {
|
||||
return self.bytecode_buffer.push_all(coral.io.bytes_of(&value));
|
||||
fn emit_float(self: *Chunk, value: types.Float) coral.io.AllocationError!void {
|
||||
return self.bytecodes.push_all(self.allocator, coral.io.bytes_of(&value));
|
||||
}
|
||||
|
||||
fn emit_opcode(self: *Chunk, opcode: Opcode) !void {
|
||||
return self.bytecode_buffer.push_one(@enumToInt(opcode));
|
||||
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_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_opcode(self: *Chunk, opcode: Opcode) coral.io.AllocationError!void {
|
||||
return self.bytecodes.push_one(self.allocator, @enumToInt(opcode));
|
||||
}
|
||||
|
||||
pub fn fetch_float(self: Chunk, cursor: *usize) ?Float {
|
||||
const operand_size = @sizeOf(Float);
|
||||
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(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);
|
||||
return try self.bytecodes.push_all(self.allocator, coral.io.bytes_of(&interned_string));
|
||||
}
|
||||
|
||||
pub fn fetch_integer(self: Chunk, cursor: *usize) ?Integer {
|
||||
const operand_size = @sizeOf(Integer);
|
||||
const updated_cursor = cursor.* + operand_size;
|
||||
|
||||
if (updated_cursor > self.bytecode_buffer.values.len) return null;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
|
@ -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},
|
||||
};
|
||||
}
|
||||
};
|
|
@ -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 width = try env.get_field(manifest_ref, try env.intern("width"));
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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),
|
||||
pub fn run_app(base_file_system: file.System) void {
|
||||
const Logger = struct {
|
||||
const Self = @This();
|
||||
|
||||
fn cast(self: *App) *State {
|
||||
return @ptrCast(*State, @alignCast(@alignOf(State), self));
|
||||
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_RenderClear(state.renderer) != 0) {
|
||||
return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}});
|
||||
}
|
||||
|
||||
for (state.canvas_items.values) |canvas_item| {
|
||||
switch (canvas_item.options) {
|
||||
.sprite => {},
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
if (ext.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0) != 0) {
|
||||
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError());
|
||||
}
|
||||
|
||||
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_RenderClear(renderer) != 0) {
|
||||
return ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%s\n", ext.SDL_GetError());
|
||||
}
|
||||
|
||||
// TODO: Render here.
|
||||
|
||||
ext.SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue