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 {
title = title,
title = "Afterglow",
width = 1280,
height = 800,
tick_rate = 60,

View File

@ -116,18 +116,32 @@ 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 {
const is_zero_aligned = @alignOf(State) == 0;
const Invoker = struct {
fn invoke(context: *const anyopaque, input: Input) Output {
if (is_zero_aligned) {
return invoker(@ptrCast(context), input);
}
return invoker(@ptrCast(@alignCast(context)), input);
}
};
return .{
.context = if (is_zero_aligned) state else @ptrCast(state),
.invoker = Invoker.invoke,
};
}
.invoker = struct {
fn invoke(context: *const anyopaque, input: Input) Output {
if (is_zero_aligned) {
return invoker(@ptrCast(context), input);
}
pub fn from(comptime invoker: fn (input: Input) Output) Self {
const Invoker = struct {
fn invoke(_: *const anyopaque, input: Input) Output {
return invoker(input);
}
};
return invoker(@ptrCast(@alignCast(context)), input);
}
}.invoke,
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 {
return self.invoker(self.context, input);
}

View File

@ -78,7 +78,7 @@ pub fn Stack(comptime Value: type) type {
return null;
}
return &self.values[self.values.len - 1];
return self.values[self.values.len - 1];
}
pub fn pop(self: *Self) ?Value {
@ -93,6 +93,22 @@ pub fn Stack(comptime Value: type) type {
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 {
if (self.values.len == 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 {
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;
@ -143,7 +150,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTra
pub const Iterable = struct {
table: *Self,
iterations: usize = 0,
iterations: usize,
pub fn next(self: *Iterable) ?Entry {
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();
pub fn as_iterable(self: *Self) Iterable {
return .{
.table = self,
.iterations = 0,
};
}
pub fn remove(self: *Self, key: Key) ?Entry {
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);
@ -332,8 +346,3 @@ fn hash_string(key: []const io.Byte) usize {
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,
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 {
var manifest_ref = try env.execute_file(global_ref, file_access, file.Path.from(&.{"app.ona"}));
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.
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)) {
.string => |string| string,
@ -27,9 +27,9 @@ pub const Manifest = struct {
};
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.
break: get switch (env.unbox(ref)) {
@ -39,9 +39,9 @@ pub const Manifest = struct {
});
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.
break: get switch (env.unbox(ref)) {
@ -51,9 +51,9 @@ pub const Manifest = struct {
});
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)) {
.number => |number| @floatCast(number),
@ -74,69 +74,14 @@ pub const Manifest = struct {
}
};
pub const LogSeverity = enum {
info,
warn,
fail,
};
pub fn log_info(message: []const coral.io.Byte) void {
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.
}
pub const WritableLog = struct {
severity: LogSeverity,
write_buffer: coral.list.ByteStack,
pub fn log_warn(message: []const coral.io.Byte) void {
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 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 {
self.write_buffer.free();
}
pub fn make(log_severity: LogSeverity, allocator: coral.io.Allocator) WritableLog {
return .{
.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();
}
}
};
pub fn log_fail(message: []const coral.io.Byte) void {
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.
}

View File

@ -10,30 +10,34 @@ const file = @import("./file.zig");
const tokens = @import("./kym/tokens.zig");
pub const Args = []const ?*const RuntimeRef;
pub const CallContext = struct {
env: *RuntimeEnv,
caller: ?*const RuntimeRef,
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 {
userdata: []coral.io.Byte,
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 Frame = struct {
name: []const coral.io.Byte,
arg_count: u8,
locals_top: usize,
};
pub const IndexContext = struct {
env: *RuntimeEnv,
userdata: []coral.io.Byte,
@ -42,23 +46,18 @@ pub const IndexContext = struct {
pub const RuntimeEnv = struct {
allocator: coral.io.Allocator,
err_writer: coral.io.Writer,
error_handler: ErrorHandler,
syscallers: SyscallerTable,
local_refs: RefStack,
frames: FrameStack,
ref_values: RefSlab,
const FrameStack = coral.list.Stack(struct {
arg_count: u8,
locals_top: usize,
});
const FrameStack = coral.list.Stack(Frame);
const SyscallerTable = coral.map.StringTable(Syscaller);
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 {
ref_count: usize,
@ -71,9 +70,9 @@ pub const RuntimeEnv = struct {
},
});
pub const ScriptSource = struct {
pub const Syscall = struct {
name: []const coral.io.Byte,
data: []const coral.io.Byte,
caller: Syscaller,
};
pub fn acquire(self: *RuntimeEnv, ref: ?*const RuntimeRef) ?*RuntimeRef {
@ -89,22 +88,10 @@ pub const RuntimeEnv = struct {
return @ptrFromInt(key);
}
pub fn call(
self: *RuntimeEnv,
caller_ref: ?*const RuntimeRef,
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 bind_syscalls(self: *RuntimeEnv, syscalls: []const Syscall) RuntimeError!void {
for (syscalls) |syscall| {
_ = try self.syscallers.replace(syscall.name, syscall.caller);
}
}
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 {
return switch (self.unbox(indexable_ref)) {
.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"),
pub fn execute_file(self: *RuntimeEnv, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef {
if ((try file.allocate_and_load(self.allocator, file_access, file_path))) |file_data| {
defer self.allocator.deallocate(file_data);
.dynamic => |dynamic| dynamic.typeinfo.get(.{
.userdata = dynamic.userdata,
.env = self,
.index_ref = index_ref,
}),
};
if (file_path.to_string()) |string| {
return self.execute_script(string, file_data);
}
}
return self.raise(error.BadOperation, "failed to load file");
}
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);
return self.execute_script(global_ref, .{
.name = file_path.to_string() orelse return self.raise(error.SystemFailure, error_message),
.data = file_data,
});
}
pub fn execute_script(self: *RuntimeEnv, global_ref: ?*const RuntimeRef, source: ScriptSource) RuntimeError!?*RuntimeRef {
pub fn execute_script(
self: *RuntimeEnv,
name: []const coral.io.Byte,
data: []const coral.io.Byte,
) RuntimeError!?*RuntimeRef {
var ast = Ast.make(self.allocator);
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) {
error.BadSyntax => return self.raise(error.BadSyntax, ast.error_message),
@ -191,7 +149,7 @@ pub const RuntimeEnv = struct {
try exe.compile_ast(ast);
return exe.execute(global_ref, source.name);
return try exe.execute(name);
}
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));
}
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(.{
.name = frame_name,
.arg_count = arg_count,
.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 {
@ -215,6 +172,16 @@ pub const RuntimeEnv = struct {
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 {
return self.local_refs.values[local];
}
@ -243,12 +210,13 @@ pub const RuntimeEnv = struct {
self.local_refs.values[local] = self.acquire(value);
}
pub fn make(allocator: coral.io.Allocator, options: Options) RuntimeError!RuntimeEnv {
return .{
pub fn make(allocator: coral.io.Allocator, error_handler: ErrorHandler) coral.io.AllocationError!RuntimeEnv {
return RuntimeEnv{
.local_refs = RefStack.make(allocator),
.ref_values = RefSlab.make(allocator),
.frames = FrameStack.make(allocator),
.err_writer = options.err_writer,
.syscallers = SyscallerTable.make(allocator),
.error_handler = error_handler,
.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);
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 {
// TODO: Print stack trace from state.
coral.utf8.print_formatted(self.err_writer, "{name}@{line}: {message}", .{
.name = "???",
.line = @as(u64, 0),
.message = error_message,
}) catch {};
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError {
self.error_handler.invoke(.{
.message = message,
.frames = self.frames.values,
});
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 {
const ref_data = self.ref_values.lookup(@intFromPtr(ref orelse return .nil));
@ -320,18 +294,48 @@ pub const RuntimeEnv = struct {
.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 {
IllegalState,
SystemFailure,
TypeMismatch,
BadOperation,
BadSyntax,
Assertion,
};
pub const RuntimeRef = opaque {};
pub const Syscaller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv);
pub const TestContext = struct {
env: *RuntimeEnv,
userdata: []const coral.io.Byte,
@ -339,6 +343,7 @@ pub const TestContext = struct {
};
pub const Typeinfo = struct {
name: []const coral.io.Byte,
call: *const fn (context: CallContext) RuntimeError!?*RuntimeRef = default_call,
clean: *const fn (userdata: []coral.io.Byte) void = default_clean,
get: *const fn (context: IndexContext) RuntimeError!?*RuntimeRef = default_get,
@ -380,26 +385,87 @@ pub const Unboxed = union (enum) {
number: Float,
string: []const coral.io.Byte,
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);
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 {
const Concrete = struct {
fn call(context: CallContext) RuntimeError!?*RuntimeRef {
return @as(*Callable, @ptrCast(@alignCast(context.userdata.ptr))).invoke(context);
}
};
pub fn set(
env: *RuntimeEnv,
indexable_ref: ?*const RuntimeRef,
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), &.{
.call = Concrete.call,
});
return dynamic.typeinfo.set(.{
.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 {
@ -410,14 +476,6 @@ pub fn new_table(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
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 {
return switch (env.unbox(lhs_ref)) {
.nil => env.raise(error.TypeMismatch, "cannot compare nil objects"),

View File

@ -17,7 +17,7 @@ pub const Expression = union (enum) {
grouped_expression: *Expression,
get_local: []const coral.io.Byte,
call_global: struct {
call_system: struct {
identifier: []const coral.io.Byte,
argument_expressions: List,
},
@ -85,7 +85,7 @@ pub const Statement = union (enum) {
expression: Expression,
},
call_global: struct {
call_system: struct {
identifier: []const coral.io.Byte,
argument_expressions: Expression.List,
},
@ -249,7 +249,7 @@ pub fn parse(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!void {
tokenizer.step();
try self.statements.push_one(.{
.call_global = .{
.call_system = .{
.argument_expressions = expressions_list,
.identifier = identifier,
},
@ -344,7 +344,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
tokenizer.step();
return Expression{
.call_global = .{
.call_system = .{
.identifier = identifier,
.argument_expressions = expression_list,
},
@ -361,7 +361,7 @@ fn parse_factor(self: *Self, tokenizer: *tokens.Tokenizer) ParseError!Expression
tokenizer.step();
return Expression{
.call_global = .{
.call_system = .{
.identifier = identifier,
.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)) {
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(.{.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)) {
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(.get_global);
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);
}
}
}
@ -171,6 +172,7 @@ const RefList = coral.list.Stack(?*kym.RuntimeRef);
const LocalsList = coral.list.Stack([]const u8);
pub const Opcode = union (enum) {
pop,
push_nil,
push_true,
push_false,
@ -178,8 +180,8 @@ pub const Opcode = union (enum) {
push_local: u8,
push_table: u32,
set_local: u8,
get_global,
call: u8,
syscall: u8,
not,
neg,
@ -251,14 +253,14 @@ pub fn declare_constant_string(self: *Self, constant: []const coral.io.Byte) kym
return @intCast(tail);
}
pub fn execute(self: *Self, global_ref: ?*const kym.RuntimeRef, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef {
_ = name;
_ = try self.env.frame_push(0);
pub fn execute(self: *Self, name: []const coral.io.Byte) kym.RuntimeError!?*kym.RuntimeRef {
try self.env.frame_push(name, 0);
defer self.env.frame_pop();
for (self.opcodes.values) |opcode| {
switch (opcode) {
.pop => self.env.discard(self.env.local_pop()),
.push_nil => try self.env.local_push_ref(null),
.push_true => try self.env.local_push_boolean(true),
.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);
{
const dynamic = try self.env.unbox(table_ref).expect_dynamic(self.env);
var popped = @as(usize, 0);
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);
},
.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| {
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);
const args = try self.env.frame_push(arg_count);
try self.env.frame_push("", arg_count);
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);
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())) {
.number => |number| -number,
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,
const FieldTable = coral.map.Table([]const coral.io.Byte, struct {
const FieldTable = coral.map.StringTable(struct {
key_ref: ?*kym.RuntimeRef,
value_ref: ?*kym.RuntimeRef,
}, coral.map.string_table_traits);
});
const Self = @This();
@ -22,6 +22,7 @@ pub fn make(env: *kym.RuntimeEnv) Self {
}
pub const typeinfo = kym.Typeinfo{
.name = "table",
.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,
.set = typeinfo_set,

View File

@ -10,81 +10,85 @@ const heap = @import("./heap.zig");
const kym = @import("./kym.zig");
pub const RuntimeError = error {
OutOfMemory,
InitFailure,
BadManifest,
};
fn kym_handle_errors(info: kym.ErrorInfo) void {
var remaining_frames = info.frames.len;
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 {
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 {
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();
pub fn run_app(file_access: file.Access) void {
if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) {
try fail_log.write(last_sdl_error());
return error.InitFailure;
return app.log_fail(last_sdl_error());
}
defer ext.SDL_Quit();
var script_env = kym.RuntimeEnv.make(heap.allocator, .{
.out_writer = info_log.as_writer(),
.err_writer = fail_log.as_writer(),
}) catch {
try fail_log.write("failed to initialize script runtime");
return error.InitFailure;
var script_env = kym.RuntimeEnv.make(heap.allocator, kym.ErrorHandler.from(kym_handle_errors)) catch {
return app.log_fail("failed to initialize script runtime");
};
defer script_env.free();
var globals_ref = kym.new_table(&script_env) catch {
try fail_log.write("failed to initialize script runtime");
return error.InitFailure;
script_env.bind_syscalls(&.{
.{
.name = "log_info",
.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{};
manifest.load(&script_env, globals_ref, file_access) catch {
fail_log.write("failed to load / execute app.ona manifest") catch {};
return error.BadManifest;
};
manifest.load(&script_env, file_access) catch return;
const window = create: {
const pos = ext.SDL_WINDOWPOS_CENTERED;
const flags = 0;
break: create ext.SDL_CreateWindow(&manifest.title, pos, pos, manifest.width, manifest.height, flags) orelse {
fail_log.write(last_sdl_error()) catch {};
return error.InitFailure;
return app.log_fail(last_sdl_error());
};
};
defer ext.SDL_DestroyWindow(window);
const renderer = create: {
const defaultDriverIndex = -1;
const default_driver_index = -1;
const flags = ext.SDL_RENDERER_ACCELERATED;
break: create ext.SDL_CreateRenderer(window, defaultDriverIndex, flags) orelse {
fail_log.write(last_sdl_error()) catch {};
return error.InitFailure;
break: create ext.SDL_CreateRenderer(window, default_driver_index, flags) orelse {
return app.log_fail(last_sdl_error());
};
};

View File

@ -1,5 +1,5 @@
const ona = @import("ona");
pub fn main() ona.RuntimeError!void {
try ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd});
pub fn main() anyerror!void {
kayomn marked this conversation as resolved Outdated

Error union is unused.

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