Native "Syscall" Interface for Kym #21

Merged
kayomn merged 6 commits from kym-native-function into main 2023-07-22 15:03:22 +02:00
11 changed files with 380 additions and 286 deletions
Showing only changes of commit 1ea115b277 - Show all commits

View File

@ -1,8 +1,8 @@
title = "Afterglow" @log_info("game is loading")
return { return {
title = title, title = "Afterglow",
width = 1280, width = 1280,
height = 800, height = 800,
tick_rate = 60, tick_rate = 60,

View File

@ -116,10 +116,7 @@ pub fn Functor(comptime Output: type, comptime Input: type) type {
pub fn bind(comptime State: type, state: *const State, comptime invoker: fn (capture: *const State, input: Input) Output) Self { pub fn bind(comptime State: type, state: *const State, comptime invoker: fn (capture: *const State, input: Input) Output) Self {
const is_zero_aligned = @alignOf(State) == 0; const is_zero_aligned = @alignOf(State) == 0;
return .{ const Invoker = struct {
.context = if (is_zero_aligned) state else @ptrCast(state),
.invoker = struct {
fn invoke(context: *const anyopaque, input: Input) Output { fn invoke(context: *const anyopaque, input: Input) Output {
if (is_zero_aligned) { if (is_zero_aligned) {
return invoker(@ptrCast(context), input); return invoker(@ptrCast(context), input);
@ -127,7 +124,24 @@ pub fn Functor(comptime Output: type, comptime Input: type) type {
return invoker(@ptrCast(@alignCast(context)), input); return invoker(@ptrCast(@alignCast(context)), input);
} }
}.invoke, };
return .{
.context = if (is_zero_aligned) state else @ptrCast(state),
.invoker = Invoker.invoke,
};
}
pub fn from(comptime invoker: fn (input: Input) Output) Self {
const Invoker = struct {
fn invoke(_: *const anyopaque, input: Input) Output {
return invoker(input);
}
};
return .{
.context = &.{},
.invoker = Invoker.invoke,
}; };
} }
@ -162,6 +176,19 @@ pub fn Generator(comptime Output: type, comptime Input: type) type {
}; };
} }
pub fn from(comptime invoker: fn (input: Input) Output) Self {
const Invoker = struct {
fn invoke(_: *const anyopaque, input: Input) Output {
return invoker(input);
}
};
return .{
.context = &.{},
.invoker = Invoker.invoke,
};
}
pub fn invoke(self: Self, input: Input) Output { pub fn invoke(self: Self, input: Input) Output {
return self.invoker(self.context, input); return self.invoker(self.context, input);
} }

View File

@ -78,7 +78,7 @@ pub fn Stack(comptime Value: type) type {
return null; return null;
} }
return &self.values[self.values.len - 1]; return self.values[self.values.len - 1];
} }
pub fn pop(self: *Self) ?Value { pub fn pop(self: *Self) ?Value {
@ -93,6 +93,22 @@ pub fn Stack(comptime Value: type) type {
return self.values[last_index]; return self.values[last_index];
} }
pub fn push_all(self: *Self, values: []const Value) io.AllocationError!void {
const new_length = self.values.len + values.len;
if (new_length > self.capacity) {
try self.grow(values.len + values.len);
}
const offset_index = self.values.len;
self.values = self.values.ptr[0 .. new_length];
for (0 .. values.len) |index| {
self.values[offset_index + index] = values[index];
}
}
pub fn push_one(self: *Self, value: Value) io.AllocationError!void { pub fn push_one(self: *Self, value: Value) io.AllocationError!void {
if (self.values.len == self.capacity) { if (self.values.len == self.capacity) {
try self.grow(math.max(1, self.capacity)); try self.grow(math.max(1, self.capacity));
@ -106,3 +122,13 @@ pub fn Stack(comptime Value: type) type {
} }
}; };
} }
pub fn stack_as_writer(self: *ByteStack) io.Writer {
return io.Writer.bind(ByteStack, self, write_stack);
}
fn write_stack(stack: *ByteStack, bytes: []const io.Byte) ?usize {
stack.push_all(bytes) catch return null;
kayomn marked this conversation as resolved Outdated

Can be simplified to single expression

Can be simplified to single expression
return bytes.len;
}

View File

@ -103,6 +103,13 @@ pub fn Slab(comptime Value: type) type {
}; };
} }
pub fn StringTable(comptime Value: type) type {
return Table([]const io.Byte, Value, .{
.hash = hash_string,
.match = io.equals,
});
}
pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTraits(Key)) type { pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTraits(Key)) type {
kayomn marked this conversation as resolved
Review

A lot of duplication with @typeInfo(usize).Int and getting min / max limits of int types in general. These could be reduced into a constants declared in function body to reduce repetition.

A lot of duplication with `@typeInfo(usize).Int` and getting min / max limits of int types in general. These could be reduced into a constants declared in function body to reduce repetition.
const load_max = 0.75; const load_max = 0.75;
@ -143,7 +150,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
pub const Iterable = struct { pub const Iterable = struct {
table: *Self, table: *Self,
iterations: usize = 0, iterations: usize,
pub fn next(self: *Iterable) ?Entry { pub fn next(self: *Iterable) ?Entry {
while (self.iterations < self.table.entries.len) { while (self.iterations < self.table.entries.len) {
@ -160,6 +167,13 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
const Self = @This(); const Self = @This();
pub fn as_iterable(self: *Self) Iterable {
return .{
.table = self,
.iterations = 0,
};
}
pub fn remove(self: *Self, key: Key) ?Entry { pub fn remove(self: *Self, key: Key) ?Entry {
const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len);
var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max);
@ -332,8 +346,3 @@ fn hash_string(key: []const io.Byte) usize {
return hash_code; return hash_code;
} }
pub const string_table_traits = TableTraits([]const io.Byte){
.hash = hash_string,
.match = io.equals,
};

View File

@ -12,14 +12,14 @@ pub const Manifest = struct {
height: u16 = 480, height: u16 = 480,
tick_rate: f32 = 60.0, tick_rate: f32 = 60.0,
pub fn load(self: *Manifest, env: *kym.RuntimeEnv, global_ref: ?*const kym.RuntimeRef, file_access: file.Access) kym.RuntimeError!void { pub fn load(self: *Manifest, env: *kym.RuntimeEnv, file_access: file.Access) kym.RuntimeError!void {
kayomn marked this conversation as resolved
Review

Resolve TODOs in the function body.

Resolve TODOs in the function body.
var manifest_ref = try env.execute_file(global_ref, file_access, file.Path.from(&.{"app.ona"})); const manifest_ref = try env.execute_file(file_access, file.Path.from(&.{"app.ona"}));
defer env.discard(&manifest_ref); defer env.discard(manifest_ref);
var title_ref = try kym.get_dynamic_field(env, manifest_ref, "title"); const title_ref = try kym.get_field(env, manifest_ref, "title");
defer env.discard(&title_ref); defer env.discard(title_ref);
const title_string = switch (env.unbox(title_ref)) { const title_string = switch (env.unbox(title_ref)) {
.string => |string| string, .string => |string| string,
@ -27,9 +27,9 @@ pub const Manifest = struct {
}; };
const width = @as(u16, get: { const width = @as(u16, get: {
var ref = try kym.get_dynamic_field(env, manifest_ref, "width"); const ref = try kym.get_field(env, manifest_ref, "width");
defer env.discard(&ref); defer env.discard(ref);
// TODO: Add safety-checks to int cast. // TODO: Add safety-checks to int cast.
break: get switch (env.unbox(ref)) { break: get switch (env.unbox(ref)) {
@ -39,9 +39,9 @@ pub const Manifest = struct {
}); });
const height = @as(u16, get: { const height = @as(u16, get: {
var ref = try kym.get_dynamic_field(env, manifest_ref, "height"); const ref = try kym.get_field(env, manifest_ref, "height");
defer env.discard(&ref); defer env.discard(ref);
// TODO: Add safety-checks to int cast. // TODO: Add safety-checks to int cast.
break: get switch (env.unbox(ref)) { break: get switch (env.unbox(ref)) {
@ -51,9 +51,9 @@ pub const Manifest = struct {
}); });
const tick_rate = @as(f32, get: { const tick_rate = @as(f32, get: {
var ref = try kym.get_dynamic_field(env, manifest_ref, "tick_rate"); const ref = try kym.get_field(env, manifest_ref, "tick_rate");
defer env.discard(&ref); defer env.discard(ref);
break: get switch (env.unbox(ref)) { break: get switch (env.unbox(ref)) {
.number => |number| @floatCast(number), .number => |number| @floatCast(number),
@ -74,69 +74,14 @@ pub const Manifest = struct {
} }
}; };
pub const LogSeverity = enum { pub fn log_info(message: []const coral.io.Byte) void {
info, ext.SDL_LogInfo(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr);
kayomn marked this conversation as resolved
Review

Potentially (but very unlikely to ever trigger) unsafe length cast.

Could do with clamping the length to the max buffer to be safe.

Potentially (but very unlikely to ever trigger) unsafe length cast. Could do with clamping the length to the max buffer to be safe.
warn,
fail,
};
pub const WritableLog = struct {
severity: LogSeverity,
write_buffer: coral.list.ByteStack,
pub fn as_writer(self: *WritableLog) coral.io.Writer {
return coral.io.Writer.bind(WritableLog, self, struct {
fn write(writable_log: *WritableLog, bytes: []const coral.io.Byte) ?usize {
writable_log.write(bytes) catch return null;
return bytes.len;
}
}.write);
} }
pub fn free(self: *WritableLog) void { pub fn log_warn(message: []const coral.io.Byte) void {
self.write_buffer.free(); ext.SDL_LogWarn(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr);
kayomn marked this conversation as resolved Outdated

Potentially (but very unlikely to ever trigger) unsafe length cast.

Could do with clamping the length to the max buffer to be safe.

Potentially (but very unlikely to ever trigger) unsafe length cast. Could do with clamping the length to the max buffer to be safe.
} }
pub fn make(log_severity: LogSeverity, allocator: coral.io.Allocator) WritableLog { pub fn log_fail(message: []const coral.io.Byte) void {
return .{ ext.SDL_LogError(ext.SDL_LOG_CATEGORY_APPLICATION, "%.*s", @as(c_int, @intCast(message.len)), message.ptr);
kayomn marked this conversation as resolved Outdated

Potentially (but very unlikely to ever trigger) unsafe length cast.

Could do with clamping the length to the max buffer to be safe.

Potentially (but very unlikely to ever trigger) unsafe length cast. Could do with clamping the length to the max buffer to be safe.
.severity = log_severity,
.write_buffer = coral.list.ByteStack.make(allocator),
};
} }
pub fn write(self: *WritableLog, bytes: []const coral.io.Byte) coral.io.AllocationError!void {
const format_string = "%.*s";
var line_written = @as(usize, 0);
for (bytes) |byte| {
if (byte == '\n') {
ext.SDL_LogError(
ext.SDL_LOG_CATEGORY_APPLICATION,
format_string,
self.write_buffer.values.len,
self.write_buffer.values.ptr);
self.write_buffer.clear();
line_written = 0;
continue;
}
try self.write_buffer.push_one(byte);
line_written += 1;
}
if (self.write_buffer.values.len == 0) {
ext.SDL_LogError(
ext.SDL_LOG_CATEGORY_APPLICATION,
format_string,
self.write_buffer.values.len,
self.write_buffer.values.ptr);
self.write_buffer.clear();
}
}
};

View File

@ -10,30 +10,34 @@ const file = @import("./file.zig");
const tokens = @import("./kym/tokens.zig"); const tokens = @import("./kym/tokens.zig");
pub const Args = []const ?*const RuntimeRef;
pub const CallContext = struct { pub const CallContext = struct {
env: *RuntimeEnv, env: *RuntimeEnv,
caller: ?*const RuntimeRef, caller: ?*const RuntimeRef,
userdata: []coral.io.Byte, userdata: []coral.io.Byte,
args: []const ?*const RuntimeRef = &.{},
pub fn arg_at(self: CallContext, index: u8) RuntimeError!*const RuntimeRef {
if (!coral.math.is_clamped(index, 0, self.args.len - 1)) {
return self.env.check_fail("argument out of bounds");
}
return self.args[@as(usize, index)];
}
}; };
pub const Callable = coral.io.Generator(RuntimeError!?*RuntimeRef, CallContext);
pub const DynamicObject = struct { pub const DynamicObject = struct {
userdata: []coral.io.Byte, userdata: []coral.io.Byte,
typeinfo: *const Typeinfo, typeinfo: *const Typeinfo,
}; };
pub const ErrorHandler = coral.io.Generator(void, ErrorInfo);
pub const ErrorInfo = struct {
message: []const coral.io.Byte,
frames: []const Frame,
};
pub const Float = f64; pub const Float = f64;
pub const Frame = struct {
name: []const coral.io.Byte,
arg_count: u8,
locals_top: usize,
};
pub const IndexContext = struct { pub const IndexContext = struct {
env: *RuntimeEnv, env: *RuntimeEnv,
userdata: []coral.io.Byte, userdata: []coral.io.Byte,
@ -42,23 +46,18 @@ pub const IndexContext = struct {
pub const RuntimeEnv = struct { pub const RuntimeEnv = struct {
allocator: coral.io.Allocator, allocator: coral.io.Allocator,
err_writer: coral.io.Writer, error_handler: ErrorHandler,
syscallers: SyscallerTable,
local_refs: RefStack, local_refs: RefStack,
frames: FrameStack, frames: FrameStack,
ref_values: RefSlab, ref_values: RefSlab,
const FrameStack = coral.list.Stack(struct { const FrameStack = coral.list.Stack(Frame);
arg_count: u8,
locals_top: usize, const SyscallerTable = coral.map.StringTable(Syscaller);
});
const RefStack = coral.list.Stack(?*RuntimeRef); const RefStack = coral.list.Stack(?*RuntimeRef);
pub const Options = struct {
out_writer: coral.io.Writer = coral.io.null_writer,
err_writer: coral.io.Writer = coral.io.null_writer,
};
const RefSlab = coral.map.Slab(struct { const RefSlab = coral.map.Slab(struct {
ref_count: usize, ref_count: usize,
@ -71,9 +70,9 @@ pub const RuntimeEnv = struct {
}, },
}); });
pub const ScriptSource = struct { pub const Syscall = struct {
name: []const coral.io.Byte, name: []const coral.io.Byte,
data: []const coral.io.Byte, caller: Syscaller,
}; };
pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef { pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef {
@ -89,22 +88,10 @@ pub const RuntimeEnv = struct {
return @ptrFromInt(key); return @ptrFromInt(key);
} }
pub fn call( pub fn bind_syscalls(self: *RuntimeEnv, syscalls: []const Syscall) RuntimeError!void {
self: *RuntimeEnv, for (syscalls) |syscall| {
caller_ref: ?*const RuntimeRef, _ = try self.syscallers.replace(syscall.name, syscall.caller);
callable_ref: ?*const RuntimeRef, }
arg_refs: []const *const RuntimeRef,
) RuntimeError!?*RuntimeRef {
return switch (self.unbox(callable_ref)) {
.dynamic => |dynamic| dynamic.typeinfo.call(.{
.env = self,
.caller = caller_ref,
.userdata = dynamic.userdata,
.args = arg_refs,
}),
else => self.raise(error.TypeMismatch, "type is not callable"),
};
} }
pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void { pub fn discard(self: *RuntimeEnv, ref: ?*RuntimeRef) void {
@ -126,58 +113,29 @@ pub const RuntimeEnv = struct {
} }
} }
pub fn dynamic_get(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef) RuntimeError!?*RuntimeRef { pub fn execute_file(self: *RuntimeEnv, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef {
return switch (self.unbox(indexable_ref)) { if ((try file.allocate_and_load(self.allocator, file_access, file_path))) |file_data| {
.nil => self.raise(error.BadOperation, "nil has no such index"),
.boolean => self.raise(error.BadOperation, "boolean has no such index"),
.number => self.raise(error.BadOperation, "number has no such index"),
.string => self.raise(error.BadOperation, "string has no such index"),
.dynamic => |dynamic| dynamic.typeinfo.get(.{
.userdata = dynamic.userdata,
.env = self,
.index_ref = index_ref,
}),
};
}
pub fn dynamic_set(self: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, index_ref: ?*const RuntimeRef, value_ref: ?*const RuntimeRef) RuntimeError!void {
return switch (self.unbox(indexable_ref)) {
.nil => self.raise(error.BadOperation, "nil is immutable"),
.boolean => self.raise(error.BadOperation, "boolean is immutable"),
.number => self.raise(error.BadOperation, "number is immutable"),
.string => self.raise(error.BadOperation, "string is immutable"),
.dynamic => |dynamic| dynamic.typeinfo.set(.{
.userdata = dynamic.userdata,
.env = self,
.index_ref = index_ref,
}, value_ref),
};
}
pub fn execute_file(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef {
const error_message = "failed to load file";
const file_data = (try file.allocate_and_load(self.allocator, file_access, file_path)) orelse {
return self.raise(error.SystemFailure, error_message);
};
defer self.allocator.deallocate(file_data); defer self.allocator.deallocate(file_data);
return self.execute_script(global_ref, .{ if (file_path.to_string()) |string| {
.name = file_path.to_string() orelse return self.raise(error.SystemFailure, error_message), return self.execute_script(string, file_data);
.data = file_data, }
});
} }
pub fn execute_script(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, source: ScriptSource) RuntimeError!?*RuntimeRef { return self.raise(error.BadOperation, "failed to load file");
}
pub fn execute_script(
self: *RuntimeEnv,
name: []const coral.io.Byte,
data: []const coral.io.Byte,
) RuntimeError!?*RuntimeRef {
var ast = Ast.make(self.allocator); var ast = Ast.make(self.allocator);
defer ast.free(); defer ast.free();
{ {
var tokenizer = tokens.Tokenizer{.source = source.data}; var tokenizer = tokens.Tokenizer{.source = data};
ast.parse(&tokenizer) catch |parse_error| switch (parse_error) { ast.parse(&tokenizer) catch |parse_error| switch (parse_error) {
error.BadSyntax => return self.raise(error.BadSyntax, ast.error_message), error.BadSyntax => return self.raise(error.BadSyntax, ast.error_message),
@ -191,7 +149,7 @@ pub const RuntimeEnv = struct {
try exe.compile_ast(ast); try exe.compile_ast(ast);
return exe.execute(global_ref, source.name); return try exe.execute(name);
} }
pub fn frame_pop(self: *RuntimeEnv) void { pub fn frame_pop(self: *RuntimeEnv) void {
@ -201,13 +159,12 @@ pub const RuntimeEnv = struct {
coral.debug.assert(self.local_refs.drop((self.local_refs.values.len - frame.?.locals_top) + frame.?.arg_count)); coral.debug.assert(self.local_refs.drop((self.local_refs.values.len - frame.?.locals_top) + frame.?.arg_count));
} }
pub fn frame_push(self: *RuntimeEnv, arg_count: u8) RuntimeError![]const *const RuntimeRef { pub fn frame_push(self: *RuntimeEnv, frame_name: []const coral.io.Byte, arg_count: u8) RuntimeError!void {
try self.frames.push_one(.{ try self.frames.push_one(.{
.name = frame_name,
.arg_count = arg_count, .arg_count = arg_count,
.locals_top = self.local_refs.values.len, .locals_top = self.local_refs.values.len,
}); });
return @as([]const *const RuntimeRef, @ptrCast(self.local_refs.values[(self.local_refs.values.len - arg_count) .. self.local_refs.values.len]));
} }
pub fn free(self: *RuntimeEnv) void { pub fn free(self: *RuntimeEnv) void {
@ -215,6 +172,16 @@ pub const RuntimeEnv = struct {
self.ref_values.free(); self.ref_values.free();
} }
pub fn get_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*const RuntimeRef {
const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow");
if (index >= frame.arg_count) {
return null;
}
return self.local_refs.values[frame.locals_top - (1 + index)];
}
pub fn local_get(self: *RuntimeEnv, local: u8) ?*RuntimeRef { pub fn local_get(self: *RuntimeEnv, local: u8) ?*RuntimeRef {
return self.local_refs.values[local]; return self.local_refs.values[local];
} }
@ -243,12 +210,13 @@ pub const RuntimeEnv = struct {
self.local_refs.values[local] = self.acquire(value); self.local_refs.values[local] = self.acquire(value);
} }
pub fn make(allocator: coral.io.Allocator, options: Options) RuntimeError!RuntimeEnv { pub fn make(allocator: coral.io.Allocator, error_handler: ErrorHandler) coral.io.AllocationError!RuntimeEnv {
return .{ return RuntimeEnv{
.local_refs = RefStack.make(allocator), .local_refs = RefStack.make(allocator),
.ref_values = RefSlab.make(allocator), .ref_values = RefSlab.make(allocator),
.frames = FrameStack.make(allocator), .frames = FrameStack.make(allocator),
.err_writer = options.err_writer, .syscallers = SyscallerTable.make(allocator),
.error_handler = error_handler,
.allocator = allocator, .allocator = allocator,
}; };
} }
@ -260,7 +228,11 @@ pub const RuntimeEnv = struct {
})); }));
} }
pub fn new_dynamic(self: *RuntimeEnv, userdata: []const coral.io.Byte, typeinfo: *const Typeinfo) RuntimeError!*RuntimeRef { pub fn new_dynamic(
self: *RuntimeEnv,
userdata: []const coral.io.Byte,
typeinfo: *const Typeinfo,
) RuntimeError!*RuntimeRef {
const userdata_copy = try coral.io.allocate_copy(self.allocator, userdata); const userdata_copy = try coral.io.allocate_copy(self.allocator, userdata);
errdefer self.allocator.deallocate(userdata_copy); errdefer self.allocator.deallocate(userdata_copy);
@ -296,17 +268,19 @@ pub const RuntimeEnv = struct {
})); }));
} }
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, error_message: []const coral.io.Byte) RuntimeError { pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError {
// TODO: Print stack trace from state. self.error_handler.invoke(.{
coral.utf8.print_formatted(self.err_writer, "{name}@{line}: {message}", .{ .message = message,
.name = "???", .frames = self.frames.values,
.line = @as(u64, 0), });
.message = error_message,
}) catch {};
return error_value; return error_value;
} }
pub fn syscaller(self: *RuntimeEnv, name: []const coral.io.Byte) RuntimeError!Syscaller {
return self.syscallers.lookup(name) orelse self.raise(error.BadOperation, "attempt to call undefined syscall");
}
pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Unboxed { pub fn unbox(self: *RuntimeEnv, ref: ?*const RuntimeRef) Unboxed {
const ref_data = self.ref_values.lookup(@intFromPtr(ref orelse return .nil)); const ref_data = self.ref_values.lookup(@intFromPtr(ref orelse return .nil));
@ -320,18 +294,48 @@ pub const RuntimeEnv = struct {
.dynamic => |dynamic| .{.dynamic = dynamic}, .dynamic => |dynamic| .{.dynamic = dynamic},
}; };
} }
pub fn unbox_dynamic(self: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError![]const coral.io.Byte {
if (ref) |live_ref| {
const ref_data = self.ref_values.lookup(@intFromPtr(live_ref));
coral.debug.assert(ref_data != null);
if (ref_data.?.object == .dynamic) {
return ref_data.?.object.dynamic;
}
}
return self.raise(error.TypeMismatch, "expected dynamic");
}
pub fn unbox_string(self: *RuntimeEnv, ref: ?*const RuntimeRef) RuntimeError![]const coral.io.Byte {
if (ref) |live_ref| {
const ref_data = self.ref_values.lookup(@intFromPtr(live_ref));
coral.debug.assert(ref_data != null);
if (ref_data.?.object == .string) {
return ref_data.?.object.string;
}
}
return self.raise(error.TypeMismatch, "expected string");
}
}; };
pub const RuntimeError = coral.io.AllocationError || error { pub const RuntimeError = coral.io.AllocationError || error {
IllegalState, IllegalState,
SystemFailure,
TypeMismatch, TypeMismatch,
BadOperation, BadOperation,
BadSyntax, BadSyntax,
Assertion,
}; };
pub const RuntimeRef = opaque {}; pub const RuntimeRef = opaque {};
pub const Syscaller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv);
pub const TestContext = struct { pub const TestContext = struct {
env: *RuntimeEnv, env: *RuntimeEnv,
userdata: []const coral.io.Byte, userdata: []const coral.io.Byte,
@ -339,6 +343,7 @@ pub const TestContext = struct {
}; };
pub const Typeinfo = struct { pub const Typeinfo = struct {
name: []const coral.io.Byte,
call: *const fn (context: CallContext) RuntimeError!?*RuntimeRef = default_call, call: *const fn (context: CallContext) RuntimeError!?*RuntimeRef = default_call,
clean: *const fn (userdata: []coral.io.Byte) void = default_clean, clean: *const fn (userdata: []coral.io.Byte) void = default_clean,
get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef = default_get, get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef = default_get,
@ -380,26 +385,87 @@ pub const Unboxed = union (enum) {
number: Float, number: Float,
string: []const coral.io.Byte, string: []const coral.io.Byte,
dynamic: *const DynamicObject, dynamic: *const DynamicObject,
pub fn expect_dynamic(self: Unboxed, env: *RuntimeEnv) RuntimeError!*const DynamicObject {
return switch (self) {
.dynamic => |dynamic| dynamic,
else => env.raise(error.TypeMismatch, "expected dynamic"),
};
}
}; };
pub fn get_dynamic_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte) RuntimeError!?*RuntimeRef { pub fn assert(env: *RuntimeEnv, condition: bool, message: []const coral.io.Byte) RuntimeError!void {
if (!condition) {
return env.raise(error.Assertion, message);
}
}
pub fn call(
env: *RuntimeEnv,
caller_ref: ?*const RuntimeRef,
callable_ref: ?*const RuntimeRef,
arg_refs: Args,
) RuntimeError!?*RuntimeRef {
for (arg_refs) |arg_ref| {
try env.local_push_ref(arg_ref);
}
env.frame_push("", arg_refs.len);
defer env.frame_pop();
const dynamic = try env.unbox(callable_ref).expect_dynamic();
return dynamic.type_info.call(.{
.env = env,
.caller = caller_ref,
.userdata = dynamic.userdata,
});
}
pub fn get(
env: *RuntimeEnv,
indexable_ref: ?*const RuntimeRef,
index_ref: ?*const RuntimeRef,
) RuntimeError!?*RuntimeRef {
const dynamic = try env.unbox(indexable_ref).expect_dynamic(env);
return dynamic.typeinfo.get(.{
.userdata = dynamic.userdata,
.env = env,
.index_ref = index_ref,
});
}
pub fn get_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte) RuntimeError!?*RuntimeRef {
const field_name_ref = try env.new_string(field_name); const field_name_ref = try env.new_string(field_name);
defer env.discard(field_name_ref); defer env.discard(field_name_ref);
return env.dynamic_get(indexable_ref, field_name_ref); return get(env, indexable_ref, field_name_ref);
} }
pub fn new_callable(env: *RuntimeEnv, generator: Callable) RuntimeError!?*RuntimeRef { pub fn set(
const Concrete = struct { env: *RuntimeEnv,
fn call(context: CallContext) RuntimeError!?*RuntimeRef { indexable_ref: ?*const RuntimeRef,
return @as(*Callable, @ptrCast(@alignCast(context.userdata.ptr))).invoke(context); index_ref: ?*const RuntimeRef,
} value_ref: ?*const RuntimeRef,
}; ) RuntimeError!void {
const dynamic = try env.unbox(indexable_ref).expect_dynamic(env);
return env.new_dynamic(coral.io.bytes_of(&generator), &.{ return dynamic.typeinfo.set(.{
.call = Concrete.call, .userdata = dynamic.userdata,
}); .env = env,
.index_ref = index_ref,
}, value_ref);
}
pub fn set_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte, value_ref: ?*const RuntimeRef) RuntimeError!void {
const field_name_ref = try env.new_string(field_name);
defer env.discard(field_name_ref);
return set(env, indexable_ref, field_name_ref, value_ref);
} }
pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef { pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
@ -410,14 +476,6 @@ pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
return try env.new_dynamic(coral.io.bytes_of(&table), &Table.typeinfo); return try env.new_dynamic(coral.io.bytes_of(&table), &Table.typeinfo);
} }
pub fn set_dynamic_field(env: *RuntimeEnv, indexable_ref: ?*const RuntimeRef, field_name: []const coral.io.Byte, value_ref: ?*const RuntimeRef) RuntimeError!void {
const field_name_ref = try env.new_string(field_name);
defer env.discard(field_name_ref);
return env.dynamic_set(indexable_ref, field_name_ref, value_ref);
}
pub fn test_difference(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!Float { pub fn test_difference(env: *RuntimeEnv, lhs_ref: ?*const RuntimeRef, rhs_ref: ?*const RuntimeRef) RuntimeError!Float {
return switch (env.unbox(lhs_ref)) { return switch (env.unbox(lhs_ref)) {
.nil => env.raise(error.TypeMismatch, "cannot compare nil objects"), .nil => env.raise(error.TypeMismatch, "cannot compare nil objects"),

View File

@ -17,7 +17,7 @@ pub const Expression = union (enum) {
grouped_expression: *Expression, grouped_expression: *Expression,
get_local: []const coral.io.Byte, get_local: []const coral.io.Byte,
call_global: struct { call_system: struct {
identifier: []const coral.io.Byte, identifier: []const coral.io.Byte,
argument_expressions: List, argument_expressions: List,
}, },
@ -85,7 +85,7 @@ pub const Statement = union (enum) {
expression: Expression, expression: Expression,
}, },
call_global: struct { call_system: struct {
identifier: []const coral.io.Byte, identifier: []const coral.io.Byte,
argument_expressions: Expression.List, argument_expressions: Expression.List,
}, },
@ -249,7 +249,7 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
tokenizer.step(); tokenizer.step();
try self.statements.push_one(.{ try self.statements.push_one(.{
.call_global = .{ .call_system = .{
.argument_expressions = expressions_list, .argument_expressions = expressions_list,
.identifier = identifier, .identifier = identifier,
}, },
@ -344,7 +344,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
tokenizer.step(); tokenizer.step();
return Expression{ return Expression{
.call_global = .{ .call_system = .{
.identifier = identifier, .identifier = identifier,
.argument_expressions = expression_list, .argument_expressions = expression_list,
}, },
@ -361,7 +361,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
tokenizer.step(); tokenizer.step();
return Expression{ return Expression{
.call_global = .{ .call_system = .{
.identifier = identifier, .identifier = identifier,
.argument_expressions = expression_list, .argument_expressions = expression_list,
}, },

View File

@ -90,7 +90,7 @@ const AstCompiler = struct {
}); });
}, },
.call_global => |call| { .call_system => |call| {
if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) {
return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals"); return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals");
} }
@ -100,7 +100,8 @@ const AstCompiler = struct {
} }
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)}); try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)});
try self.chunk.append_opcode(.{.call = @intCast(call.argument_expressions.values.len)}); try self.chunk.append_opcode(.{.syscall = @intCast(call.argument_expressions.values.len)});
try self.chunk.append_opcode(.pop);
}, },
} }
} }
@ -120,7 +121,7 @@ const AstCompiler = struct {
} }
}, },
.call_global => |call| { .call_system => |call| {
if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { if (call.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) {
return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals"); return self.chunk.env.raise(error.OutOfMemory, "functions may receive a maximum of 255 locals");
} }
@ -130,8 +131,8 @@ const AstCompiler = struct {
} }
try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)}); try self.chunk.append_opcode(.{.push_const = try self.chunk.declare_constant_string(call.identifier)});
try self.chunk.append_opcode(.get_global); try self.chunk.append_opcode(.{.syscall = @intCast(call.argument_expressions.values.len)});
try self.chunk.append_opcode(.{.call = @intCast(call.argument_expressions.values.len)}); try self.chunk.append_opcode(.pop);
} }
} }
} }
@ -171,6 +172,7 @@ const RefList = coral.list.Stack(?*kym.RuntimeRef);
const LocalsList = coral.list.Stack([]const u8); const LocalsList = coral.list.Stack([]const u8);
pub const Opcode = union (enum) { pub const Opcode = union (enum) {
pop,
push_nil, push_nil,
push_true, push_true,
push_false, push_false,
@ -178,8 +180,8 @@ pub const Opcode = union (enum) {
push_local: u8, push_local: u8,
push_table: u32, push_table: u32,
set_local: u8, set_local: u8,
get_global,
call: u8, call: u8,
syscall: u8,
not, not,
neg, neg,
@ -251,14 +253,14 @@ pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym
return @intCast(tail); return @intCast(tail);
} }
pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef { pub fn execute(self: *Self, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef {
_ = name; try self.env.frame_push(name, 0);
_ = try self.env.frame_push(0);
defer self.env.frame_pop(); defer self.env.frame_pop();
for (self.opcodes.values) |opcode| { for (self.opcodes.values) |opcode| {
switch (opcode) { switch (opcode) {
.pop => self.env.discard(self.env.local_pop()),
.push_nil => try self.env.local_push_ref(null), .push_nil => try self.env.local_push_ref(null),
.push_true => try self.env.local_push_boolean(true), .push_true => try self.env.local_push_boolean(true),
.push_false => try self.env.local_push_boolean(false), .push_false => try self.env.local_push_boolean(false),
@ -270,10 +272,18 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co
defer self.env.discard(table_ref); defer self.env.discard(table_ref);
{ {
const dynamic = try self.env.unbox(table_ref).expect_dynamic(self.env);
var popped = @as(usize, 0); var popped = @as(usize, 0);
while (popped < field_count) : (popped += 1) { while (popped < field_count) : (popped += 1) {
try self.env.dynamic_set(table_ref, self.env.local_pop(), self.env.local_pop()); const index_ref = self.env.local_pop();
const value_ref = self.env.local_pop();
try dynamic.typeinfo.set(.{
.userdata = dynamic.userdata,
.env = self.env,
.index_ref = index_ref,
}, value_ref);
} }
} }
@ -288,18 +298,6 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co
try self.env.local_push_ref(ref); try self.env.local_push_ref(ref);
}, },
.get_global => {
const identifier_ref = self.env.local_pop();
defer self.env.discard(identifier_ref);
const ref = try self.env.dynamic_get(global_ref, identifier_ref);
defer self.env.discard(ref);
try self.env.local_push_ref(ref);
},
.set_local => |local| { .set_local => |local| {
const ref = self.env.local_pop(); const ref = self.env.local_pop();
@ -313,17 +311,43 @@ pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const co
defer self.env.discard(callable_ref); defer self.env.discard(callable_ref);
const args = try self.env.frame_push(arg_count); try self.env.frame_push("", arg_count);
defer self.env.frame_pop(); defer self.env.frame_pop();
const result_ref = try self.env.call(global_ref, callable_ref, args); const dynamic = try self.env.unbox(callable_ref).expect_dynamic(self.env);
const result_ref = try dynamic.typeinfo.call(.{
.env = self.env,
.caller = null,
.userdata = dynamic.userdata,
});
defer self.env.discard(result_ref); defer self.env.discard(result_ref);
try self.env.local_push_ref(result_ref); try self.env.local_push_ref(result_ref);
}, },
.syscall => |arg_count| {
const identifier_ref = self.env.local_pop();
defer self.env.discard(identifier_ref);
const identifier = try self.env.unbox_string(identifier_ref);
try self.env.frame_push(identifier, arg_count);
errdefer self.env.frame_pop();
const result_ref = try (try self.env.syscaller(identifier)).invoke(self.env);
defer self.env.discard(result_ref);
self.env.frame_pop();
try self.env.local_push_ref(result_ref);
},
.neg => try self.env.local_push_number(switch (self.env.unbox(self.env.local_pop())) { .neg => try self.env.local_push_number(switch (self.env.unbox(self.env.local_pop())) {
.number => |number| -number, .number => |number| -number,
else => return self.env.raise(error.TypeMismatch, "object is not scalar negatable"), else => return self.env.raise(error.TypeMismatch, "object is not scalar negatable"),

View File

@ -4,10 +4,10 @@ const kym = @import("../kym.zig");
fields: FieldTable, fields: FieldTable,
const FieldTable = coral.map.Table([]const coral.io.Byte, struct { const FieldTable = coral.map.StringTable(struct {
key_ref: ?*kym.RuntimeRef, key_ref: ?*kym.RuntimeRef,
value_ref: ?*kym.RuntimeRef, value_ref: ?*kym.RuntimeRef,
}, coral.map.string_table_traits); });
const Self = @This(); const Self = @This();
@ -22,6 +22,7 @@ pub fn make(env: *kym.RuntimeEnv) Self {
} }
pub const typeinfo = kym.Typeinfo{ pub const typeinfo = kym.Typeinfo{
.name = "table",
.clean = typeinfo_clean, .clean = typeinfo_clean,
kayomn marked this conversation as resolved Outdated

Does this need to be public?

Does this need to be public?
.get = typeinfo_get, .get = typeinfo_get,
.set = typeinfo_set, .set = typeinfo_set,

View File

@ -10,81 +10,85 @@ const heap = @import("./heap.zig");
const kym = @import("./kym.zig"); const kym = @import("./kym.zig");
pub const RuntimeError = error { fn kym_handle_errors(info: kym.ErrorInfo) void {
OutOfMemory, var remaining_frames = info.frames.len;
InitFailure,
BadManifest, while (remaining_frames != 0) {
}; remaining_frames -= 1;
app.log_fail(info.frames[remaining_frames].name);
}
}
fn kym_log_info(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
app.log_info(try env.unbox_string(try env.get_arg(0)));
return null;
}
fn kym_log_warn(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
app.log_warn(try env.unbox_string(try env.get_arg(0)));
return null;
}
fn kym_log_fail(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
app.log_fail(try env.unbox_string(try env.get_arg(0)));
return null;
}
fn last_sdl_error() [:0]const u8 { fn last_sdl_error() [:0]const u8 {
return coral.io.slice_sentineled(@as(u8, 0), @as([*:0]const u8, @ptrCast(ext.SDL_GetError()))); return coral.io.slice_sentineled(@as(u8, 0), @as([*:0]const u8, @ptrCast(ext.SDL_GetError())));
} }
pub fn run_app(file_access: file.Access) RuntimeError!void { pub fn run_app(file_access: file.Access) void {
var info_log = app.WritableLog.make(.info, heap.allocator);
defer info_log.free();
var fail_log = app.WritableLog.make(.fail, heap.allocator);
defer fail_log.free();
if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) {
try fail_log.write(last_sdl_error()); return app.log_fail(last_sdl_error());
return error.InitFailure;
} }
defer ext.SDL_Quit(); defer ext.SDL_Quit();
var script_env = kym.RuntimeEnv.make(heap.allocator, .{ var script_env = kym.RuntimeEnv.make(heap.allocator, kym.ErrorHandler.from(kym_handle_errors)) catch {
.out_writer = info_log.as_writer(), return app.log_fail("failed to initialize script runtime");
.err_writer = fail_log.as_writer(),
}) catch {
try fail_log.write("failed to initialize script runtime");
return error.InitFailure;
}; };
defer script_env.free(); defer script_env.free();
var globals_ref = kym.new_table(&script_env) catch { script_env.bind_syscalls(&.{
try fail_log.write("failed to initialize script runtime"); .{
.name = "log_info",
return error.InitFailure; .caller = kym.Syscaller.from(kym_log_info),
},
.{
.name = "log_fail",
.caller = kym.Syscaller.from(kym_log_fail),
},
}) catch {
return app.log_fail("failed to initialize script runtime");
}; };
kayomn marked this conversation as resolved Outdated

Inaccurate log message.

Inaccurate log message.
defer script_env.discard(&globals_ref);
var manifest = app.Manifest{}; var manifest = app.Manifest{};
manifest.load(&script_env, globals_ref, file_access) catch { manifest.load(&script_env, file_access) catch return;
fail_log.write("failed to load / execute app.ona manifest") catch {};
return error.BadManifest;
};
const window = create: { const window = create: {
const pos = ext.SDL_WINDOWPOS_CENTERED; const pos = ext.SDL_WINDOWPOS_CENTERED;
const flags = 0; const flags = 0;
break: create ext.SDL_CreateWindow(&manifest.title, pos, pos, manifest.width, manifest.height, flags) orelse { break: create ext.SDL_CreateWindow(&manifest.title, pos, pos, manifest.width, manifest.height, flags) orelse {
fail_log.write(last_sdl_error()) catch {}; return app.log_fail(last_sdl_error());
return error.InitFailure;
}; };
}; };
defer ext.SDL_DestroyWindow(window); defer ext.SDL_DestroyWindow(window);
const renderer = create: { const renderer = create: {
const defaultDriverIndex = -1; const default_driver_index = -1;
const flags = ext.SDL_RENDERER_ACCELERATED; const flags = ext.SDL_RENDERER_ACCELERATED;
break: create ext.SDL_CreateRenderer(window, defaultDriverIndex, flags) orelse { break: create ext.SDL_CreateRenderer(window, default_driver_index, flags) orelse {
fail_log.write(last_sdl_error()) catch {}; return app.log_fail(last_sdl_error());
return error.InitFailure;
}; };
}; };

View File

@ -1,5 +1,5 @@
const ona = @import("ona"); const ona = @import("ona");
pub fn main() ona.RuntimeError!void { pub fn main() anyerror!void {
kayomn marked this conversation as resolved Outdated

Error union is unused.

Error union is unused.
try ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd}); ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd});
} }