Compare commits

...

3 Commits

Author SHA1 Message Date
kayomn b0c34d11b6 First pass of Ona middleware API
continuous-integration/drone/push Build is passing Details
2023-11-20 21:39:42 +00:00
kayomn e9a28f074c Fix add squaring on floats instead 2023-11-16 19:02:30 +00:00
kayomn 9cf2e671d3 Implement initial update loop for canvas usage in scripts 2023-11-16 00:34:28 +00:00
19 changed files with 821 additions and 641 deletions

View File

@ -2,6 +2,7 @@
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"zig.initialSetupDone": true,
"debug.console.collapseIdenticalLines": false,
"[zig]": {
"editor.formatOnSave": false,

View File

@ -25,6 +25,7 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});
compile_step.addModule("coral", coral_module);
compile_step.addModule("ona", ona_module);
compile_step.linkLibC();
compile_step.linkSystemLibrary("SDL2");

View File

@ -1,23 +1,20 @@
let events = @import("events")
let tool = "wrench"
var test_param = `monkey {tool} {2 + 1 - 1}`
var timer = 0
let printer = lambda (pfx):
@print(test_param)
events.on_update(lambda (dt):
timer = timer + dt
return lambda (msg):
@print(pfx)
@print(msg)
if (timer > 1):
@print("test")
timer = 0
end
end
let pr = printer("this is a final closure")
pr("goodbye")
end)
return {
.title = "Game",
.width = 1280,
.height = 800,
.res_width = 1280,
.res_height = 800,
.tick_rate = 60,
}

View File

@ -178,7 +178,7 @@ pub fn Generator(comptime Output: type, comptime Input: type) type {
};
}
pub fn from(comptime invoker: fn (input: Input) Output) Self {
pub fn from_fn(comptime invoker: fn (input: Input) Output) Self {
const Invoker = struct {
fn invoke(_: *const anyopaque, input: Input) Output {
return invoker(input);
@ -358,7 +358,7 @@ pub fn jenkins_hash(comptime int: std.builtin.Type.Int, bytes: []const Byte) mat
return hash;
}
pub const null_writer = Writer.from(write_null);
pub const null_writer = Writer.from_fn(write_null);
pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) {
var len = @as(usize, 0);

View File

@ -23,6 +23,7 @@ pub fn StringTable(comptime Value: type) type {
pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) type {
const load_max = 0.75;
const max_int = math.max_int(@typeInfo(usize).Int);
const has_traits = @sizeOf(Traits) != 0;
return struct {
allocator: io.Allocator,
@ -89,7 +90,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty
}
fn hash_key(self: Self, key: Key) usize {
return if (@sizeOf(Traits) == 0) Traits.hash(key) else self.traits.hash(key);
return if (has_traits) self.traits.hash(key) else Traits.hash(key);
}
pub fn remove(self: *Self, key: Key) ?Entry {
@ -119,7 +120,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty
{
const hash_max = @min(max_int, self.entries.len);
const has_context = @sizeOf(Traits) != 0;
var hashed_key = (if (has_context) self.traits.hash(key) else Traits.hash(key)) % hash_max;
var hashed_key = self.hash_key(key) % hash_max;
while (true) {
const entry = &(self.entries[hashed_key] orelse {
@ -222,7 +223,7 @@ pub fn Table(comptime Key: type, comptime Value: type, comptime Traits: type) ty
const hash_max = @min(max_int, self.entries.len);
const has_context = @sizeOf(Traits) != 0;
var hashed_key = (if (has_context) self.traits.hash(key) else Traits.hash(key)) % hash_max;
var hashed_key = self.hash_key(key) % hash_max;
var iterations = @as(usize, 0);
while (iterations < self.count) : (iterations += 1) {

View File

@ -14,6 +14,17 @@ pub fn checked_add(a: anytype, b: anytype) ?@TypeOf(a + b) {
return if (result.@"1" == 0) result.@"0" else null;
}
pub fn checked_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) ?@Type(.{.Int = dest_int}) {
const dest_min = min_int(dest_int);
const dest_max = max_int(dest_int);
if (value < dest_min or value > dest_max) {
return null;
}
return @intCast(value);
}
pub fn checked_mul(a: anytype, b: anytype) ?@TypeOf(a * b) {
const result = @mulWithOverflow(a, b);

View File

@ -201,6 +201,36 @@ pub const HexadecimalFormat = struct {
}
};
pub const SmallString = extern struct {
data: [256]u8 = [_]u8{0} ** 256,
pub const FromError = error {
InvalidUtf8,
TooBig,
};
pub fn from_units(units: []const u8) FromError!SmallString {
var self = SmallString{};
const units_range = self.data.len - 1;
if (units.len > units_range) {
return error.TooBig;
}
io.copy(self.data[0 .. units_range], units);
self.data[units_range] = @intCast(units_range - units.len);
return self;
}
pub fn to_units(self: *const SmallString) [:0]const u8 {
const units_range = self.data.len - 1;
return @ptrCast(self.data[0 .. units_range - self.data[units_range]]);
}
};
pub fn alloc_formatted(allocator: io.Allocator, comptime format: []const u8, args: anytype) io.AllocationError![]io.Byte {
const formatted_len = print_formatted(io.null_writer, format, args);
@ -302,6 +332,7 @@ noinline fn print_value(writer: io.Writer, value: anytype) ?usize {
return switch (@typeInfo(Value)) {
.Int => DecimalFormat.default.print(writer, value),
.Float => DecimalFormat.default.print(writer, value),
.ComptimeInt => print_value(writer, @as(@import("std").math.IntFittingRange(value, value), value)),
.Pointer => |pointer| switch (pointer.size) {
.Many, .C => HexadecimalFormat.default.print(writer, @intFromPtr(value)),

View File

@ -1,101 +0,0 @@
const coral = @import("coral");
const ext = @import("./ext.zig");
const file = @import("./file.zig");
const kym = @import("./kym.zig");
pub const Manifest = struct {
title: [255:0]coral.io.Byte = [_:0]coral.io.Byte{0} ** 255,
width: u16 = 640,
height: u16 = 480,
tick_rate: f32 = 60.0,
pub fn load(self: *Manifest, env: *kym.RuntimeEnv) kym.RuntimeError!void {
const manifest = (try env.import(file.Path.from(&.{"app.ona"}))).pop() orelse return;
defer env.release(manifest);
const width = @as(u16, get: {
if (try kym.get_field(env, manifest, "width")) |ref| {
defer env.release(ref);
const fixed = try env.expect_fixed(ref);
if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.width)).Int)) {
break: get @intCast(fixed);
}
}
break: get self.width;
});
const height = @as(u16, get: {
if (try kym.get_field(env, manifest, "height")) |ref| {
defer env.release(ref);
const fixed = try env.expect_fixed(ref);
if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(self.height)).Int)) {
break: get @intCast(fixed);
}
}
break: get self.height;
});
const tick_rate = @as(f32, get: {
if (try kym.get_field(env, manifest, "tick_rate")) |ref| {
defer env.release(ref);
break: get @floatCast(try env.expect_float(ref));
}
break: get self.tick_rate;
});
if (try kym.get_field(env, manifest, "title")) |ref| {
defer env.release(ref);
const title_string = try env.expect_string(ref);
const limited_title_len = @min(title_string.len, self.title.len);
coral.io.copy(&self.title, title_string[0 .. limited_title_len]);
coral.io.zero(self.title[limited_title_len .. self.title.len]);
} else {
coral.io.zero(&self.title);
}
self.tick_rate = tick_rate;
self.width = width;
self.height = height;
}
};
pub fn log_info(message: []const coral.io.Byte) void {
ext.SDL_LogInfo(
ext.SDL_LOG_CATEGORY_APPLICATION,
"%.*s",
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
message.ptr,
);
}
pub fn log_warn(message: []const coral.io.Byte) void {
ext.SDL_LogWarn(
ext.SDL_LOG_CATEGORY_APPLICATION,
"%.*s",
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
message.ptr,
);
}
pub fn log_fail(message: []const coral.io.Byte) void {
ext.SDL_LogError(
ext.SDL_LOG_CATEGORY_APPLICATION,
"%.*s",
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
message.ptr,
);
}

View File

@ -11,9 +11,9 @@ pub const Access = union (enum) {
.null => return null,
.sandboxed_path => |sandboxed_path| {
const path_string = sandboxed_path.joined(readable_path).get_string();
const path_bytes = sandboxed_path.joined(readable_path).to_bytes();
return @ptrCast(ext.SDL_RWFromFile(path_string.ptr, "rb"));
return @ptrCast(ext.SDL_RWFromFile(path_bytes.ptr, "rb"));
},
}
}
@ -23,8 +23,7 @@ pub const Access = union (enum) {
.null => return null,
.sandboxed_path => |sandboxed_path| {
const path_string = sandboxed_path.joined(path).get_string();
const rw_ops = ext.SDL_RWFromFile(path_string, "rb") orelse return null;
const rw_ops = ext.SDL_RWFromFile(sandboxed_path.joined(path).to_bytes(), "rb") orelse return null;
const file_size = ext.SDL_RWseek(rw_ops, 0, ext.RW_SEEK_END);
if (ext.SDL_RWclose(rw_ops) != 0 or file_size < 0) {
@ -43,26 +42,26 @@ pub const Info = struct {
size: u64,
};
pub const Path = extern struct {
pub const Path = struct {
data: [4096]coral.io.Byte = [_]coral.io.Byte{0} ** 4096,
pub const cwd = Path.from(&.{"./"});
pub const cwd = Path.of(&.{"./"});
pub fn from(components: []const []const u8) Path {
// TODO: Implement proper parsing / removal of duplicate path delimiters.
var path = Path{};
pub const FromError = error {
InvalidPath,
TooBig,
};
{
var writable_slice = coral.io.FixedBuffer{.bytes = &path.data};
pub fn from_bytes(bytes: []const coral.io.Byte) FromError!Path {
var self = Path{};
for (components) |component| {
if (writable_slice.write(component) != component.len) {
break;
}
}
if (bytes.len >= self.data.len) {
return error.TooBig;
}
return path;
coral.io.copy(&self.data, bytes);
return self;
}
pub fn joined(self: Path, other: Path) Path {
@ -96,7 +95,24 @@ pub const Path = extern struct {
return path;
}
pub fn get_string(self: Path) [:0]const coral.io.Byte {
pub fn of(components: []const []const u8) Path {
// TODO: Implement proper parsing / removal of duplicate path delimiters.
var path = Path{};
{
var writable_slice = coral.io.FixedBuffer{.bytes = &path.data};
for (components) |component| {
if (writable_slice.write(component) != component.len) {
break;
}
}
}
return path;
}
pub fn to_bytes(self: Path) [:0]const coral.io.Byte {
coral.debug.assert(self.data[self.data.len - 1] == 0);
return coral.io.slice_sentineled(@as(coral.io.Byte, 0), @as([*:0]const coral.io.Byte, @ptrCast(&self.data)));

View File

@ -1,181 +0,0 @@
pub const Vector2 = struct {
x: f32,
y: f32,
pub const Scalars = [2]f32;
pub fn equals(self: Vector2, vector: Vector2) bool {
return self.x == vector.x and self.y == vector.y;
}
pub fn from_scalar(scalar: f32) Vector2 {
return .{
.x = scalar,
.y = scalar,
};
}
pub fn from_scalars(scalars: Scalars) Vector2 {
return .{
.x = scalars[0],
.y = scalars[1],
};
}
pub fn scalar_added(self: Vector2, scalar: f32) Vector2 {
return .{
.x = self.x + scalar,
.y = self.y + scalar,
};
}
pub fn scalar_divided(self: Vector2, scalar: f32) Vector2 {
return .{
.x = self.x / scalar,
.y = self.y / scalar,
};
}
pub fn scalar_multiplied(self: Vector2, scalar: f32) Vector2 {
return .{
.x = self.x * scalar,
.y = self.y * scalar,
};
}
pub fn to_scalars(self: Vector2) Scalars {
return .{self.x, self.y};
}
pub fn scalar_subtracted(self: Vector2, scalar: f32) Vector2 {
return .{
.x = self.x - scalar,
.y = self.y - scalar,
};
}
pub fn vector_added(self: Vector2, vector: Vector2) Vector2 {
return .{
.x = self.x + vector.x,
.y = self.y + vector.y,
};
}
pub fn vector_divided(self: Vector2, vector: Vector2) Vector2 {
return .{
.x = self.x / vector.x,
.y = self.y / vector.y,
};
}
pub fn vector_multiplied(self: Vector2, vector: Vector2) Vector2 {
return .{
.x = self.x * vector.x,
.y = self.y * vector.y,
};
}
pub fn vector_subtracted(self: Vector2, vector: Vector2) Vector2 {
return .{
.x = self.x - vector.x,
.y = self.y - vector.y,
};
}
};
pub const Vector3 = struct {
x: f32,
y: f32,
z: f32,
pub const Scalars = [3]f32;
pub fn equals(self: Vector3, vector: Vector3) bool {
return self.x == vector.x and self.y == vector.y and self.z == vector.z;
}
pub fn from_scalar(scalar: f32) Vector3 {
return .{
.x = scalar,
.y = scalar,
.z = scalar,
};
}
pub fn from_scalars(scalars: Scalars) Vector3 {
return .{
.x = scalars[0],
.y = scalars[1],
.z = scalars[2],
};
}
pub fn scalar_added(self: Vector3, scalar: f32) Vector3 {
return .{
.x = self.x + scalar,
.y = self.y + scalar,
.z = self.z + scalar,
};
}
pub fn scalar_divided(self: Vector3, scalar: f32) Vector3 {
return .{
.x = self.x / scalar,
.y = self.y / scalar,
.z = self.z / scalar,
};
}
pub fn scalar_multiplied(self: Vector3, scalar: f32) Vector3 {
return .{
.x = self.x * scalar,
.y = self.y * scalar,
.z = self.z * scalar,
};
}
pub fn scalar_subtracted(self: Vector3, scalar: f32) Vector3 {
return .{
.x = self.x - scalar,
.y = self.y - scalar,
.z = self.z - scalar,
};
}
pub fn to_scalars(self: Vector3) Scalars {
return .{self.x, self.y, self.z};
}
pub fn vector_added(self: Vector3, other: Vector3) Vector3 {
return .{
.x = self.x + other.x,
.y = self.y + other.y,
.z = self.z + other.z,
};
}
pub fn vector_divided(self: Vector3, other: Vector3) Vector3 {
return .{
.x = self.x / other.x,
.y = self.y / other.y,
.z = self.z / other.z,
};
}
pub fn vector_multiplied(self: Vector3, other: Vector3) Vector3 {
return .{
.x = self.x * other.x,
.y = self.y * other.y,
.z = self.z * other.z,
};
}
pub fn vector_subtracted(self: Vector3, other: Vector3) Vector3 {
return .{
.x = self.x - other.x,
.y = self.y - other.y,
.z = self.z - other.z,
};
}
};

View File

@ -1,93 +1,238 @@
const app = @import("./app.zig");
const coral = @import("coral");
const ext = @import("./ext.zig");
pub const file = @import("./file.zig");
const heap = @import("./heap.zig");
pub const heap = @import("./heap.zig");
const kym = @import("./kym.zig");
pub const script = @import("./script.zig");
fn last_sdl_error() [:0]const u8 {
return coral.io.slice_sentineled(@as(u8, 0), @as([*:0]const u8, @ptrCast(ext.SDL_GetError())));
pub const GraphicsLoop = opaque {
const Internal = struct {
window: *ext.SDL_Window,
renderer: *ext.SDL_Renderer,
loaders: GraphicsLoaderList,
updaters: GraphicsUpdaterList,
target_frame_time: f64,
keys: struct {
pressed: [ext.SDL_NUM_SCANCODES]bool = [_]bool{false} ** ext.SDL_NUM_SCANCODES,
released: [ext.SDL_NUM_SCANCODES]bool = [_]bool{false} ** ext.SDL_NUM_SCANCODES,
held: [ext.SDL_NUM_SCANCODES]bool = [_]bool{false} ** ext.SDL_NUM_SCANCODES,
},
const GraphicsLoaderList = coral.list.Stack(GraphicsLoader);
const GraphicsUpdaterList = coral.list.Stack(GraphicsUpdater);
fn deinit(self: *Internal) void {
ext.SDL_DestroyWindow(self.window);
ext.SDL_DestroyRenderer(self.renderer);
}
pub fn run_app(file_access: file.Access) void {
defer heap.trace_leaks();
if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) {
return app.log_fail(last_sdl_error());
}
defer ext.SDL_Quit();
var script_env = kym.RuntimeEnv.init(heap.allocator, 255, .{
.print = app.log_info,
.print_error = app.log_fail,
.import_access = file_access,
}) catch {
return app.log_fail("failed to initialize script runtime");
};
defer script_env.deinit();
var manifest = app.Manifest{};
manifest.load(&script_env) catch return;
fn init(allocator: coral.io.Allocator) coral.io.AllocationError!Internal {
const window = create: {
const height = 480;
const width = 640;
const pos = ext.SDL_WINDOWPOS_CENTERED;
const flags = 0;
const flags = ext.SDL_WINDOW_HIDDEN;
break: create ext.SDL_CreateWindow(&manifest.title, pos, pos, manifest.width, manifest.height, flags) orelse {
return app.log_fail(last_sdl_error());
break: create ext.SDL_CreateWindow("", pos, pos, width, height, flags) orelse {
return error.OutOfMemory;
};
};
defer ext.SDL_DestroyWindow(window);
errdefer ext.SDL_DestroyWindow(window);
const renderer = create: {
const default_driver_index = -1;
const flags = ext.SDL_RENDERER_ACCELERATED;
break: create ext.SDL_CreateRenderer(window, default_driver_index, flags) orelse {
return app.log_fail(last_sdl_error());
return error.OutOfMemory;
};
};
defer ext.SDL_DestroyRenderer(renderer);
errdefer ext.SDL_DestroyRenderer(renderer);
{
var previous_ticks = ext.SDL_GetTicks64();
return .{
.loaders = GraphicsLoaderList.init(allocator),
.updaters = GraphicsUpdaterList.init(allocator),
.target_frame_time = 1.0 / 60.0,
.window = window,
.renderer = renderer,
.keys = .{},
};
}
while (true) {
{
fn run(self: *Internal) void {
var ticks_previous = ext.SDL_GetTicks64();
var accumulated_time = @as(f64, 0);
for (self.loaders.values) |loader| {
loader.invoke(@ptrCast(self));
}
ext.SDL_ShowWindow(self.window);
running_loop: while (true) {
const ticks_current = ext.SDL_GetTicks64();
const milliseconds_per_second = 1000.0;
const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second;
ticks_previous = ticks_current;
accumulated_time += delta_time;
while (accumulated_time >= self.target_frame_time) : (accumulated_time -= self.target_frame_time) {
var event = @as(ext.SDL_Event, undefined);
coral.io.zero(@ptrCast(&self.keys.pressed));
coral.io.zero(@ptrCast(&self.keys.released));
while (ext.SDL_PollEvent(&event) != 0) {
switch (event.type) {
ext.SDL_QUIT => return,
ext.SDL_QUIT => break: running_loop,
ext.SDL_KEYDOWN => {
self.keys.pressed[event.key.keysym.scancode] = true;
self.keys.held[event.key.keysym.scancode] = true;
},
ext.SDL_KEYUP => {
self.keys.released[event.key.keysym.scancode] = true;
self.keys.held[event.key.keysym.scancode] = false;
},
else => {},
}
}
}
{
// Based on https://fabiensanglard.net/timer_and_framerate/index.php.
const current_ticks = ext.SDL_GetTicks64();
const milliseconds_per_second = 1000.0;
const tick_frequency = @as(u64, @intFromFloat(milliseconds_per_second / manifest.tick_rate));
while (previous_ticks < current_ticks) {
previous_ticks += tick_frequency;
for (self.updaters.values) |updater| {
updater.invoke(.{
.loop = @ptrCast(self),
.delta_time = self.target_frame_time,
});
}
}
_ = ext.SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
_ = ext.SDL_RenderClear(renderer);
_ = ext.SDL_RenderPresent(renderer);
_ = ext.SDL_SetRenderDrawColor(self.renderer, 0, 0, 0, 255);
_ = ext.SDL_RenderClear(self.renderer);
_ = ext.SDL_RenderPresent(self.renderer);
ext.SDL_Delay(1);
}
}
fn use(self: *Internal, middleware: GraphicsMiddleware) coral.io.AllocationError!void {
try switch (middleware) {
.loader => |loader| self.loaders.push_one(loader),
.updater => |updater| self.updaters.push_one(updater),
};
}
};
fn internal(self: *GraphicsLoop) *Internal {
return @ptrCast(@alignCast(self));
}
pub fn is_key_pressed(self: *GraphicsLoop, key: Key) bool {
return self.internal().keys.pressed[@intFromEnum(key)];
}
pub fn set_resolution(self: *GraphicsLoop, width: u16, height: u16) callconv(.C) void {
const pos = ext.SDL_WINDOWPOS_CENTERED;
const internal_state = self.internal();
ext.SDL_SetWindowSize(internal_state.window, width, height);
ext.SDL_SetWindowPosition(internal_state.window, pos, pos);
ext.SDL_ShowWindow(internal_state.window);
}
pub fn set_tick_rate(self: *GraphicsLoop, tick_rate: f64) callconv(.C) void {
self.internal().target_frame_time = 1.0 / tick_rate;
}
pub fn set_title(self: *GraphicsLoop, title: coral.utf8.SmallString) callconv(.C) void {
ext.SDL_SetWindowTitle(self.internal().window, title.to_units());
}
};
pub const GraphicsUpdate = struct {
loop: *GraphicsLoop,
delta_time: f64,
};
pub const GraphicsMiddleware = union (enum) {
loader: GraphicsLoader,
updater: GraphicsUpdater,
};
pub const GraphicsLoader = coral.io.Generator(void, *GraphicsLoop);
pub const GraphicsUpdater = coral.io.Generator(void, GraphicsUpdate);
pub const Key = enum (usize) {
f5 = ext.SDL_SCANCODE_F5,
};
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 log_info(message: []const coral.io.Byte) void {
ext.SDL_LogInfo(
ext.SDL_LOG_CATEGORY_APPLICATION,
"%.*s",
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
message.ptr,
);
}
pub fn log_warn(message: []const coral.io.Byte) void {
ext.SDL_LogWarn(
ext.SDL_LOG_CATEGORY_APPLICATION,
"%.*s",
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
message.ptr,
);
}
pub fn log_fail(message: []const coral.io.Byte) void {
ext.SDL_LogError(
ext.SDL_LOG_CATEGORY_APPLICATION,
"%.*s",
coral.math.clamped_cast(@typeInfo(c_int).Int, message.len),
message.ptr,
);
}
pub fn start_graphics(middlewares: []const GraphicsMiddleware) void {
const init_flags = ext.SDL_INIT_EVERYTHING;
if (ext.SDL_WasInit(init_flags) == init_flags) {
return log_fail("a loop is already running");
}
if (ext.SDL_Init(init_flags) != 0) {
return log_fail(last_sdl_error());
}
defer ext.SDL_Quit();
var loop_internal = GraphicsLoop.Internal.init(heap.allocator) catch {
return log_fail(last_sdl_error());
};
defer loop_internal.deinit();
for (middlewares) |middleware| {
loop_internal.use(middleware) catch |allocation_error| {
return log_fail(switch (allocation_error) {
error.OutOfMemory => "could not use middleware: out of memory",
});
};
}
loop_internal.run();
}

View File

@ -1,16 +1,14 @@
const Chunk = @import("./kym/Chunk.zig");
const Chunk = @import("./script/Chunk.zig");
const Table = @import("./kym/Table.zig");
const Table = @import("./script/Table.zig");
const coral = @import("coral");
const file = @import("./file.zig");
const gfx = @import("./gfx.zig");
const tokens = @import("./script/tokens.zig");
const tokens = @import("./kym/tokens.zig");
const tree = @import("./kym/tree.zig");
const tree = @import("./script/tree.zig");
///
/// Fixed-length integer number type.
@ -22,6 +20,16 @@ pub const Fixed = i32;
///
pub const Float = f64;
///
/// Specialized import logic hook type for exposing native logic to scripts.
///
pub const Importer = coral.io.Generator(RuntimeError!?*RuntimeObj, *RuntimeEnv);
///
/// Byte string printing function.
///
pub const Print = fn ([]const coral.io.Byte) void;
///
/// Supertype for all numeric types.
///
@ -47,11 +55,13 @@ pub const RuntimeEnv = struct {
allocator: coral.io.Allocator,
options: Options,
interned_symbols: SymbolSet,
importer_overrides: ImporterTable,
locals: LocalList,
frames: FrameList,
const FrameList = coral.list.Stack(struct {
callable: *RuntimeObj,
caller: ?*RuntimeObj,
locals_top: usize,
arg_count: u8,
@ -66,24 +76,30 @@ pub const RuntimeEnv = struct {
}
});
const ImporterTable = coral.map.StringTable(Importer);
const LocalList = coral.list.Stack(?*RuntimeObj);
///
/// Optional settings for a [RuntimeEnv].
///
pub const Options = struct {
import_access: file.Access = .null,
print: ?*const Printer = null,
print_error: ?*const Printer = null,
///
/// Byte string printing function.
///
pub const Printer = fn ([]const coral.io.Byte) void;
import_access: file.Access = .{.sandboxed_path = &file.Path.cwd},
print_out: ?*const Print = null,
print_err: ?*const Print = null,
};
const SymbolSet = coral.map.StringTable([:0]coral.io.Byte);
///
///
///
pub fn acquire(_: *RuntimeEnv, object: *RuntimeObj) *RuntimeObj {
object.internal().ref_count += 1;
return object;
}
///
/// Attempts to push the argument located at `arg_index` to the top of `self`, pushing `null` instead if the given
/// argument does not exist or was provided as a nil value.
@ -95,7 +111,7 @@ pub const RuntimeEnv = struct {
///
/// `self` is returned for function chaining.
///
pub fn arg_get(self: *RuntimeEnv, arg_index: Local) RuntimeError!*RuntimeEnv {
pub fn arg(self: *RuntimeEnv, arg_index: Local) RuntimeError!*RuntimeEnv {
const frame = self.frames.peek() orelse {
return self.raise(error.IllegalState, "cannot get args outside of a call frame", .{});
};
@ -112,11 +128,13 @@ pub const RuntimeEnv = struct {
}
///
/// Attempts to pop the top-most value of `self` and call it with `local_arg_count` as the number of locals prior to
/// it in `self` that are intended to be arguments to it. Once the callable returns, the locals marked as arguments
/// are popped as well.
/// Attempts to pop the top-most value of `self` and call it with `arg_count` as the number of values prior to it in
/// `self` that are intended to be arguments to it. Once the callable returns, the locals marked as arguments are
/// popped as well.
///
/// A `local_arg_count` of `0` will call the function with no arguments and pop nothing other than the callable from
/// An optional `caller` may be specified for attaching an owning object to the call frame.
///
/// A `arg_count` value of `0` will call the function with no arguments and pop nothing other than the callable from
/// `self` during invocation.
///
/// A [RuntimeError] is returned if `self` is out of memory, the top-most local is nil or not callable, or the
@ -124,23 +142,35 @@ pub const RuntimeEnv = struct {
///
/// `self` is returned for function chaining.
///
pub fn call(self: *RuntimeEnv, local_arg_count: Local) RuntimeError!*RuntimeEnv {
pub fn call(self: *RuntimeEnv, arg_count: Local, caller: ?*RuntimeObj) RuntimeError!*RuntimeEnv {
const callable = try self.expect_object(self.pop());
defer self.release(callable);
if (callable.is_dynamic(Chunk.typeinfo)) |chunk_userdata| {
const arity = @as(*Chunk, @ptrCast(@alignCast(chunk_userdata))).arity;
if (arg_count < arity) {
return self.raise(error.BadOperation, "expected {expected} arguments, {provided} provided", .{
.expected = arity,
.provided = arg_count,
});
}
}
const result = get_result: {
try self.frames.push_one(.{
.locals_top = self.locals.values.len - local_arg_count,
.callable = callable.internal().acquire(),
.arg_count = local_arg_count,
.locals_top = self.locals.values.len - arg_count,
.callable = self.acquire(callable),
.caller = caller,
.arg_count = arg_count,
});
defer {
const popped_frame = self.frames.pop().?;
self.release(popped_frame.callable);
coral.debug.assert(popped_frame.locals_top < self.locals.values.len);
coral.debug.assert(popped_frame.locals_top <= self.locals.values.len);
var to_discard = self.locals.values.len - popped_frame.locals_top;
@ -510,8 +540,8 @@ pub const RuntimeEnv = struct {
self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + @as(Float, @floatFromInt(rhs_fixed))),
.float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_added(@floatFromInt(rhs_fixed))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_added(@floatFromInt(rhs_fixed))),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 + @as(Vector2, @splat(@floatFromInt(rhs_fixed)))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 + @as(Vector3, @splat(@floatFromInt(rhs_fixed)))),
else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{
.typename = addable.typename(),
@ -538,8 +568,8 @@ pub const RuntimeEnv = struct {
return switch (addable.internal().payload) {
.fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))),
.float => |lhs_float| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_divided(@floatFromInt(rhs_fixed))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_divided(@floatFromInt(rhs_fixed))),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 / @as(Vector2, @splat(@floatFromInt(rhs_fixed)))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 / @as(Vector3, @splat(@floatFromInt(rhs_fixed)))),
else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{
.typename = addable.typename(),
@ -567,8 +597,8 @@ pub const RuntimeEnv = struct {
self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * @as(Float, @floatFromInt(rhs_fixed))),
.float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_multiplied(@floatFromInt(rhs_fixed))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_multiplied(@floatFromInt(rhs_fixed))),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 * @as(Vector2, @splat(@floatFromInt(rhs_fixed)))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 * @as(Vector3, @splat(@floatFromInt(rhs_fixed)))),
else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{
.typename = addable.typename(),
@ -596,8 +626,8 @@ pub const RuntimeEnv = struct {
self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed))),
.float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_subtracted(@floatFromInt(rhs_fixed))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_subtracted(@floatFromInt(rhs_fixed))),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 - @as(Vector2, @splat(@floatFromInt(rhs_fixed)))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 - @as(Vector3, @splat(@floatFromInt(rhs_fixed)))),
else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{
.typename = addable.typename(),
@ -619,9 +649,9 @@ pub const RuntimeEnv = struct {
return switch (addable.internal().payload) {
.fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float),
.float => |lhs_float| self.new_float(lhs_float + lhs_float),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_added(@floatCast(rhs_float))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_added(@floatCast(rhs_float))),
.float => |lhs_float| self.new_float(lhs_float + rhs_float),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 + @as(Vector2, @splat(@floatCast(rhs_float)))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 + @as(Vector3, @splat(@floatCast(rhs_float)))),
else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{
.typename = addable.typename(),
@ -648,8 +678,8 @@ pub const RuntimeEnv = struct {
return switch (addable.internal().payload) {
.fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float),
.float => |lhs_float| self.new_float(lhs_float / lhs_float),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_divided(@floatCast(rhs_float))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_divided(@floatCast(rhs_float))),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 / @as(Vector2, @splat(@floatCast(rhs_float)))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 / @as(Vector3, @splat(@floatCast(rhs_float)))),
else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{
.typename = addable.typename(),
@ -673,8 +703,8 @@ pub const RuntimeEnv = struct {
return switch (addable.internal().payload) {
.fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float),
.float => |lhs_float| self.new_float(lhs_float * lhs_float),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_multiplied(@floatCast(rhs_float))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_multiplied(@floatCast(rhs_float))),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 * @as(Vector2, @splat(@floatCast(rhs_float)))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 * @as(Vector3, @splat(@floatCast(rhs_float)))),
else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{
.typename = addable.typename(),
@ -698,8 +728,8 @@ pub const RuntimeEnv = struct {
return switch (addable.internal().payload) {
.fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float),
.float => |lhs_float| self.new_float(lhs_float - lhs_float),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_subtracted(@floatCast(rhs_float))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_subtracted(@floatCast(rhs_float))),
.vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 - @as(Vector2, @splat(@floatCast(rhs_float)))),
.vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 - @as(Vector3, @splat(@floatCast(rhs_float)))),
else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{
.typename = addable.typename(),
@ -707,6 +737,27 @@ pub const RuntimeEnv = struct {
};
}
///
/// Attempts to return the caller of the current call frame, if one exists, otherwise returning `null`.
///
/// A [RuntimeError] is returned if `self` is out of memory or the virtual machine is not inside a managed call
/// frame.
///
/// *Note* any returned non-null pointer must be released via [RuntimeEnv.release] or else the resources belonging
/// to the objects will never be freed by the runtime.
///
pub fn get_caller(self: *RuntimeEnv) RuntimeError!?*RuntimeObj {
const frame = self.frames.peek() orelse {
return self.raise(error.IllegalState, "cannot get caller outside of a call frame", .{});
};
if (frame.caller) |object| {
return self.acquire(object);
}
return null;
}
///
/// Attempts to pop the top-most value and index into it with `index`, pushing the retrieved value.
///
@ -735,18 +786,13 @@ pub const RuntimeEnv = struct {
}
swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) {
'x' => vector2.x,
'y' => vector2.y,
'x' => vector2[0],
'y' => vector2[1],
0 => return switch (swizzle_count) {
1 => self.new_float(swizzle_buffer[0]),
2 => self.new_vector2(.{
.x = swizzle_buffer[0],
.y = swizzle_buffer[1],
}),
3 => self.new_vector3(Vector3.from_scalars(swizzle_buffer)),
2 => self.new_vector2(.{swizzle_buffer[0], swizzle_buffer[1]}),
3 => self.new_vector3(swizzle_buffer),
else => unreachable,
},
@ -770,19 +816,14 @@ pub const RuntimeEnv = struct {
}
swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) {
'x' => vector3.x,
'y' => vector3.y,
'z' => vector3.z,
'x' => vector3[0],
'y' => vector3[1],
'z' => vector3[2],
0 => return switch (swizzle_count) {
1 => self.new_float(swizzle_buffer[0]),
2 => self.new_vector2(.{
.x = swizzle_buffer[0],
.y = swizzle_buffer[1],
}),
3 => self.new_vector3(Vector3.from_scalars(swizzle_buffer)),
2 => self.new_vector2(.{swizzle_buffer[0], swizzle_buffer[1]}),
3 => self.new_vector3(swizzle_buffer),
else => unreachable,
},
@ -846,33 +887,33 @@ pub const RuntimeEnv = struct {
}
///
/// Attempts to import a script from the import [file.Access] (specified during [RuntimeEnv.init]) from `file_path`.
/// Attempts to import a script from the the [Importer] (specified during [RuntimeEnv.init]) from `path`.
///
/// If the loaded script is a plain text file, it will be read, parsed, and compiled into an optimized format before
/// being executed.
/// After loading, the result of the import is returned.
///
/// If the loaded script is already compiled, it is immediately executed.
///
/// After completing execution, the return value is pushed to the top of `self`.
///
/// A [RuntimeError] is returned if `self` is out of memory, no file at `file_path` could be found by the runtime,
/// the script is a source file and contains invalid syntax, or the imported script raised an error during
/// execution.
///
/// Any syntax errors are reported using [print_error] prior to raising a runtime error.
/// A [RuntimeError] is returned if `self` is out of memory, no import at `path` could be found by the runtime,
/// import raised an error during execution.
///
/// `self` is returned for function chaining.
///
pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!*RuntimeEnv {
{
var callable = new_chunk: {
const file_name = file_path.get_string();
const file_name = file_path.to_bytes();
const file_data =
(try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse {
return self.raise(error.BadOperation, "failed to open or read `{name}`", .{
.name = file_name,
});
if (self.importer_overrides.lookup(file_name)) |importer| {
if (try importer.invoke(self)) |object| {
errdefer self.release(object);
try self.locals.push_one(object);
} else {
try self.locals.push_one(null);
}
return self;
}
const callable = new_chunk: {
const file_data = (try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse {
return self.raise(error.BadOperation, "failed to open or read `{name}`", .{.name = file_name});
};
defer self.allocator.deallocate(file_data);
@ -886,10 +927,10 @@ pub const RuntimeEnv = struct {
root.parse(&stream) catch |parse_error| {
for (root.error_messages.values) |error_message| {
self.print_error(error_message);
self.print_err(error_message);
}
return self.raise(parse_error, "failed to parse `{name}`", .{.name = file_name});
return self.raise(parse_error, "failed to parse `{path}`", .{.path = file_name});
};
}
@ -897,15 +938,12 @@ pub const RuntimeEnv = struct {
errdefer chunk.deinit(self);
break: new_chunk (try self.new_dynamic(coral.io.bytes_of(&chunk), Chunk.typeinfo)).pop().?;
break: new_chunk (try self.new_dynamic(Chunk.typeinfo, chunk)).pop().?;
};
errdefer self.release(callable);
defer self.release(callable);
try self.locals.push_one(callable);
}
return self.call(0);
return (try self.push(callable)).call(0, null);
}
///
@ -929,6 +967,7 @@ pub const RuntimeEnv = struct {
return .{
.interned_symbols = SymbolSet.init(allocator, .{}),
.importer_overrides = ImporterTable.init(allocator, .{}),
.locals = locals,
.frames = frames,
.allocator = allocator,
@ -993,7 +1032,7 @@ pub const RuntimeEnv = struct {
self.release(previous_local);
}
local.* = if (value) |object| object.internal().acquire() else null;
local.* = if (value) |object| self.acquire(object) else null;
}
///
@ -1070,7 +1109,7 @@ pub const RuntimeEnv = struct {
pub fn new_boxed(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!*RuntimeEnv {
const boxed = try RuntimeObj.allocate(self.allocator, .{
.ref_count = 1,
.payload = .{.boxed = if (value) |object| object.internal().acquire() else null},
.payload = .{.boxed = if (value) |object| self.acquire(object) else null},
});
errdefer self.release(boxed);
@ -1083,14 +1122,14 @@ pub const RuntimeEnv = struct {
///
/// Attempts to create a new dynamic object from `userdata` and `typeinfo`, pushing it to the top of `self`.
///
/// *Note* that the number of bytes in `userdata` must match the size described in `typeinfo` exactly.
/// *Note* the size of the type specified in `userdata` must match the size described in `typeinfo` exactly.
///
/// A [RuntimeError] is returned if `self` is out of memory.
///
/// `self` is returned for function chaining.
///
pub fn new_dynamic(self: *RuntimeEnv, userdata: []const coral.io.Byte, typeinfo: *const Typeinfo) RuntimeError!*RuntimeEnv {
coral.debug.assert(userdata.len == typeinfo.size);
pub fn new_dynamic(self: *RuntimeEnv, typeinfo: *const Typeinfo, userdata: anytype) RuntimeError!*RuntimeEnv {
coral.debug.assert(@sizeOf(@TypeOf(userdata)) == typeinfo.size);
const dynamic = new: {
const dynamic = try self.allocator.reallocate(null, @sizeOf(usize) + typeinfo.size);
@ -1098,7 +1137,7 @@ pub const RuntimeEnv = struct {
errdefer self.allocator.deallocate(dynamic);
coral.io.copy(dynamic, coral.io.bytes_of(&typeinfo));
coral.io.copy(dynamic[@sizeOf(usize) ..], userdata[0 .. typeinfo.size]);
coral.io.copy(dynamic[@sizeOf(usize) ..], coral.io.bytes_of(&userdata));
break: new try RuntimeObj.allocate(self.allocator, .{
.ref_count = 1,
@ -1221,7 +1260,7 @@ pub const RuntimeEnv = struct {
errdefer table.deinit(self);
return self.new_dynamic(coral.io.bytes_of(&table), Table.typeinfo);
return self.new_dynamic(Table.typeinfo, table);
}
///
@ -1295,6 +1334,16 @@ pub const RuntimeEnv = struct {
return self;
}
///
/// Attempst to override the behavior of all imports at `path` to execute the import logic specified in `importer`
/// instead for `self`.
///
/// A [RuntimeError] is returned if `self` is out of memory.
///
pub fn override_import(self: *RuntimeEnv, path: []const coral.io.Byte, importer: Importer) RuntimeError!void {
_ = try self.importer_overrides.replace(path, importer);
}
///
/// Pops the top-most value of `self`, returning the value.
///
@ -1310,20 +1359,20 @@ pub const RuntimeEnv = struct {
}
///
/// Prints `buffer` to the printing operation specified in the options of `self`.
/// Prints `buffer` to the error printing operation specified in the options of `self.
///
pub fn print(self: *RuntimeEnv, buffer: []const coral.io.Byte) void {
if (self.options.print) |op| {
op(buffer);
pub fn print_err(self: *RuntimeEnv, buffer: []const coral.io.Byte) void {
if (self.options.print_err) |print| {
print(buffer);
}
}
///
/// Prints `buffer` to the error printing operation specified in the options of `self.
/// Prints `buffer` to the printing operation specified in the options of `self`.
///
pub fn print_error(self: *RuntimeEnv, buffer: []const coral.io.Byte) void {
if (self.options.print_error) |op| {
op(buffer);
pub fn print_out(self: *RuntimeEnv, buffer: []const coral.io.Byte) void {
if (self.options.print_out) |print| {
print(buffer);
}
}
@ -1336,7 +1385,7 @@ pub const RuntimeEnv = struct {
///
pub fn push(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!*RuntimeEnv {
if (value) |object| {
const acquired = object.internal().acquire();
const acquired = self.acquire(object);
errdefer self.release(acquired);
@ -1359,10 +1408,10 @@ pub const RuntimeEnv = struct {
defer self.allocator.deallocate(formatted_message);
self.print_error(formatted_message);
self.print_err(formatted_message);
if (!self.frames.is_empty()) {
self.print_error("stack trace:");
self.print_err("stack trace:");
var remaining_frames = self.frames.values.len;
@ -1392,9 +1441,9 @@ pub const RuntimeEnv = struct {
defer self.allocator.deallocate(chunk_name);
self.print_error(chunk_name);
self.print_err(chunk_name);
} else {
self.print_error(get_name: {
self.print_err(get_name: {
const string = name.is_string();
coral.debug.assert(string != null);
@ -1490,7 +1539,7 @@ pub const RuntimeEnv = struct {
.symbol => |symbol| self.new_string(coral.io.slice_sentineled(@as(coral.io.Byte, 0), symbol)),
.string => acquire: {
try self.locals.push_one(value.internal().acquire());
try self.locals.push_one(self.acquire(value));
break: acquire self;
},
@ -1498,7 +1547,11 @@ pub const RuntimeEnv = struct {
.vector2 => |vector2| convert: {
var string = [_:0]coral.io.Byte{0} ** 64;
var buffer = coral.io.FixedBuffer{.bytes = &string};
const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec2({x}, {y})", vector2).?;
const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec2({x}, {y})", .{
.x = vector2[0],
.y = vector2[1],
}).?;
break: convert self.new_string(string[0 .. length]);
},
@ -1506,7 +1559,12 @@ pub const RuntimeEnv = struct {
.vector3 => |vector3| convert: {
var string = [_:0]coral.io.Byte{0} ** 96;
var buffer = coral.io.FixedBuffer{.bytes = &string};
const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec3({x}, {y}, {z})", vector3).?;
const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec3({x}, {y}, {z})", .{
.x = vector3[0],
.y = vector3[1],
.z = vector3[2],
}).?;
break: convert self.new_string(string[0 .. length]);
},
@ -1541,7 +1599,7 @@ pub const RuntimeEnv = struct {
defer self.release(addable);
return if (addable.is_vector2()) |lhs_vector2|
self.new_vector2(lhs_vector2.vector_added(rhs_vector2))
self.new_vector2(lhs_vector2 + rhs_vector2)
else
self.raise(error.TypeMismatch, "vector2 types are not addable with {typename}", .{
.typename = addable.typename(),
@ -1557,7 +1615,7 @@ pub const RuntimeEnv = struct {
/// `self` is returned for function chaining.
///
pub fn vector2_divide(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv {
if (rhs_vector2.x == 0 or rhs_vector2.y == 0) {
if (rhs_vector2[0] == 0 or rhs_vector2[1] == 0) {
return self.raise(error.TypeMismatch, "cannot divide by zero", .{});
}
@ -1566,7 +1624,7 @@ pub const RuntimeEnv = struct {
defer self.release(addable);
return if (addable.is_vector2()) |lhs_vector2|
self.new_vector2(lhs_vector2.vector_divided(rhs_vector2))
self.new_vector2(lhs_vector2 / rhs_vector2)
else
self.raise(error.TypeMismatch, "vector2 types are not divisible with {typename}", .{
.typename = addable.typename(),
@ -1587,7 +1645,7 @@ pub const RuntimeEnv = struct {
defer self.release(addable);
return if (addable.is_vector2()) |lhs_vector2|
self.new_vector2(lhs_vector2.vector_multiplied(rhs_vector2))
self.new_vector2(lhs_vector2 * rhs_vector2)
else
self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{
.typename = addable.typename(),
@ -1608,7 +1666,7 @@ pub const RuntimeEnv = struct {
defer self.release(addable);
return if (addable.is_vector2()) |lhs_vector2|
self.new_vector2(lhs_vector2.vector_subtracted(rhs_vector2))
self.new_vector2(lhs_vector2 - rhs_vector2)
else
self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{
.typename = addable.typename(),
@ -1628,7 +1686,7 @@ pub const RuntimeEnv = struct {
defer self.release(addable);
return if (addable.is_vector3()) |lhs_vector3|
self.new_vector3(lhs_vector3.vector_added(rhs_vector3))
self.new_vector3(lhs_vector3 + rhs_vector3)
else
self.raise(error.TypeMismatch, "vector3 types are not addable with {typename}", .{
.typename = addable.typename(),
@ -1644,7 +1702,7 @@ pub const RuntimeEnv = struct {
/// `self` is returned for function chaining.
///
pub fn vector3_divide(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv {
if (rhs_vector3.x == 0 or rhs_vector3.y == 0 or rhs_vector3.z == 0) {
if (rhs_vector3[0] == 0 or rhs_vector3[1] == 0 or rhs_vector3[2] == 0) {
return self.raise(error.TypeMismatch, "cannot divide by zero", .{});
}
@ -1653,7 +1711,7 @@ pub const RuntimeEnv = struct {
defer self.release(addable);
return if (addable.is_vector3()) |lhs_vector3|
self.new_vector3(lhs_vector3.vector_divided(rhs_vector3))
self.new_vector3(lhs_vector3 / rhs_vector3)
else
self.raise(error.TypeMismatch, "vector3 types are not divisible with {typename}", .{
.typename = addable.typename(),
@ -1674,7 +1732,7 @@ pub const RuntimeEnv = struct {
defer self.release(addable);
return if (addable.is_vector3()) |lhs_vector3|
self.new_vector3(lhs_vector3.vector_multiplied(rhs_vector3))
self.new_vector3(lhs_vector3 * rhs_vector3)
else
self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{
.typename = addable.typename(),
@ -1695,7 +1753,7 @@ pub const RuntimeEnv = struct {
defer self.release(addable);
return if (addable.is_vector3()) |lhs_vector3|
self.new_vector3(lhs_vector3.vector_subtracted(rhs_vector3))
self.new_vector3(lhs_vector3 - rhs_vector3)
else
self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{
.typename = addable.typename(),
@ -1739,7 +1797,7 @@ pub const RuntimeEnv = struct {
self.release(unboxed_object);
}
unboxed_value.* = if (value) |object| object.internal().acquire() else null;
unboxed_value.* = if (value) |object| self.acquire(object) else null;
},
else => return self.raise(error.TypeMismatch, "{typename} is not unboxable", .{
@ -1813,12 +1871,6 @@ pub const RuntimeObj = opaque {
}
},
},
fn acquire(self: *Internal) *RuntimeObj {
self.ref_count += 1;
return @ptrCast(self);
}
};
fn allocate(allocator: coral.io.Allocator, data: Internal) coral.io.AllocationError!*RuntimeObj {
@ -1857,12 +1909,12 @@ pub const RuntimeObj = opaque {
.boxed => |boxed| if (boxed) |object| self.equals(object) else false,
.vector2 => |lhs_vector2| switch (other.internal().payload) {
.vector2 => |rhs_vector2| lhs_vector2.equals(rhs_vector2),
.vector2 => |rhs_vector2| lhs_vector2[0] == rhs_vector2[0] and lhs_vector2[1] == rhs_vector2[1],
else => false,
},
.vector3 => |lhs_vector3| switch (other.internal().payload) {
.vector3 => |rhs_vector3| lhs_vector3.equals(rhs_vector3),
.vector3 => |rhs_vector3| lhs_vector3[0] == rhs_vector3[0] and lhs_vector3[1] == rhs_vector3[1] and lhs_vector3[2] == rhs_vector3[2],
else => false,
},
@ -1896,8 +1948,8 @@ pub const RuntimeObj = opaque {
.float => |float| @bitCast(float),
.fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))),
.symbol => |symbol| @intFromPtr(symbol),
.vector2 => |vector| @bitCast(vector.to_scalars()),
.vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector.to_scalars())),
.vector2 => |vector| @bitCast(vector),
.vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector)),
.syscall => |syscall| @intFromPtr(syscall),
.boxed => |boxed| @intFromPtr(boxed),
.string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()),
@ -2083,10 +2135,10 @@ pub const Typeinfo = struct {
///
/// The [RuntimeEnv] is returned for function chaining.
///
pub fn push_index(self: *const GetContext) RuntimeError!*RuntimeEnv {
pub fn get_index(self: *const GetContext) *RuntimeObj {
coral.debug.assert(self.env.locals.values.len > 0);
return self.env.push(self.env.locals.values[self.env.locals.values.len - 1]);
return self.env.acquire(self.env.locals.values[self.env.locals.values.len - 1].?);
}
};
@ -2106,10 +2158,10 @@ pub const Typeinfo = struct {
///
/// The [RuntimeEnv] is returned for function chaining.
///
pub fn push_index(self: *const SetContext) RuntimeError!*RuntimeEnv {
pub fn get_index(self: *const SetContext) *RuntimeObj {
coral.debug.assert(self.env.locals.values.len > 0);
return self.env.push(self.env.locals.values[self.env.locals.values.len - 1]);
return self.env.acquire(self.env.locals.values[self.env.locals.values.len - 1].?);
}
///
@ -2119,10 +2171,10 @@ pub const Typeinfo = struct {
///
/// The [RuntimeEnv] is returned for function chaining.
///
pub fn push_value(self: *const SetContext) RuntimeError!*RuntimeEnv {
pub fn get_value(self: *const SetContext) ?*RuntimeObj {
coral.debug.assert(self.env.locals.values.len > 1);
return self.env.push(self.env.locals.values[self.env.locals.values.len - 2]);
return if (self.env.locals.values[self.env.locals.values.len - 2]) |object| self.env.acquire(object) else null;
}
};
@ -2154,12 +2206,12 @@ pub const Typeinfo = struct {
///
/// 2-component vector type.
///
pub const Vector2 = gfx.lina.Vector2;
pub const Vector2 = @Vector(2, f32);
///
/// 3-component vector type.
///
pub const Vector3 = gfx.lina.Vector3;
pub const Vector3 = @Vector(3, f32);
///
/// Higher-level wrapper for [RuntimeEnv.index_get] that makes it easier to index [Fixed] keys of objects.

View File

@ -1,22 +1,20 @@
const app = @import("../app.zig");
const coral = @import("coral");
const file = @import("../file.zig");
const kym = @import("../kym.zig");
const script = @import("../script.zig");
const tokens = @import("./tokens.zig");
const tree = @import("./tree.zig");
name: *kym.RuntimeObj,
name: *script.RuntimeObj,
arity: u8,
opcodes: OpcodeList,
lines: LineList,
cursor: usize,
constants: ConstList,
bindings: []?*kym.RuntimeObj,
bindings: []?*script.RuntimeObj,
const Builtin = enum {
import,
@ -27,9 +25,9 @@ const Builtin = enum {
const Compiler = struct {
chunk: *Self,
env: *kym.RuntimeEnv,
env: *script.RuntimeEnv,
fn compile_argument(self: *const Compiler, environment: *const tree.Environment, initial_argument: ?*const tree.Expr) kym.RuntimeError!u8 {
fn compile_argument(self: *const Compiler, environment: *const tree.Environment, initial_argument: ?*const tree.Expr) script.RuntimeError!u8 {
// TODO: Exceeding 255 arguments will make the VM crash.
var maybe_argument = initial_argument;
var argument_count = @as(u8, 0);
@ -44,7 +42,7 @@ const Compiler = struct {
return argument_count;
}
fn compile_expression(self: *const Compiler, environment: *const tree.Environment, expression: *const tree.Expr, name: ?[]const coral.io.Byte) kym.RuntimeError!void {
fn compile_expression(self: *const Compiler, environment: *const tree.Environment, expression: *const tree.Expr, name: ?[]const coral.io.Byte) script.RuntimeError!void {
const number_format = coral.utf8.DecimalFormat{
.delimiter = "_",
.positive_prefix = .none,
@ -59,13 +57,13 @@ const Compiler = struct {
for (literal) |codepoint| {
if (codepoint == '.') {
return self.chunk.write(expression.line, .{
.push_const = try self.declare_float(number_format.parse(literal, kym.Float) orelse unreachable),
.push_const = try self.declare_float(number_format.parse(literal, script.Float) orelse unreachable),
});
}
}
try self.chunk.write(expression.line, .{
.push_const = try self.declare_fixed(number_format.parse(literal, kym.Fixed) orelse unreachable),
.push_const = try self.declare_fixed(number_format.parse(literal, script.Fixed) orelse unreachable),
});
},
@ -183,11 +181,10 @@ const Compiler = struct {
.declaration_get => |declaration_get| {
if (get_local_index(environment, declaration_get.declaration)) |index| {
try self.chunk.write(expression.line, .{.push_local = index});
if (is_declaration_boxed(declaration_get.declaration)) {
try self.chunk.write(expression.line, .{.push_local = index});
try self.chunk.write(expression.line, .get_box);
} else {
try self.chunk.write(expression.line, .{.push_local = index});
}
return;
@ -221,8 +218,8 @@ const Compiler = struct {
}
if (try get_binding_index(environment, declaration_set.declaration)) |index| {
try self.compile_expression(environment, declaration_set.assign, null);
try self.chunk.write(expression.line, .{.push_binding = index});
try self.compile_expression(environment, declaration_set.assign, null);
if (is_declaration_boxed(declaration_set.declaration)) {
try self.chunk.write(expression.line, .set_box);
@ -247,6 +244,16 @@ const Compiler = struct {
try self.chunk.write(expression.line, .set_dynamic);
},
.field_invoke => |field_invoke| {
const argument_count = try self.compile_argument(environment, field_invoke.argument);
try self.compile_expression(environment, field_invoke.object, null);
try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(field_invoke.identifier)});
try self.chunk.write(expression.line, .get_dynamic);
try self.compile_expression(environment, field_invoke.object, null);
try self.chunk.write(expression.line, .{.call_from = argument_count});
},
.subscript_get => |subscript_get| {
try self.compile_expression(environment, subscript_get.object, null);
try self.compile_expression(environment, subscript_get.index, null);
@ -262,7 +269,7 @@ const Compiler = struct {
}
}
pub fn compile_environment(self: *const Compiler, environment: *const tree.Environment) kym.RuntimeError!void {
pub fn compile_environment(self: *const Compiler, environment: *const tree.Environment) script.RuntimeError!void {
if (environment.statement) |statement| {
const last_statement = try self.compile_statement(environment, statement);
@ -272,7 +279,7 @@ const Compiler = struct {
}
}
fn compile_statement(self: *const Compiler, environment: *const tree.Environment, initial_statement: *const tree.Stmt) kym.RuntimeError!*const tree.Stmt {
fn compile_statement(self: *const Compiler, environment: *const tree.Environment, initial_statement: *const tree.Stmt) script.RuntimeError!*const tree.Stmt {
var current_statement = initial_statement;
while (true) {
@ -338,14 +345,14 @@ const Compiler = struct {
const constants_max = @as(usize, coral.math.max_int(@typeInfo(u16).Int));
fn declare_chunk(self: *const Compiler, chunk: Self) kym.RuntimeError!u16 {
fn declare_chunk(self: *const Compiler, chunk: Self) script.RuntimeError!u16 {
if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = @as(usize, coral.math.max_int(@typeInfo(u16).Int)),
});
}
const constant = (try self.env.new_dynamic(coral.io.bytes_of(&chunk), typeinfo)).pop().?;
const constant = (try self.env.new_dynamic(typeinfo, chunk)).pop().?;
errdefer self.env.release(constant);
@ -354,7 +361,7 @@ const Compiler = struct {
return @intCast(self.chunk.constants.values.len - 1);
}
fn declare_fixed(self: *const Compiler, fixed: kym.Fixed) kym.RuntimeError!u16 {
fn declare_fixed(self: *const Compiler, fixed: script.Fixed) script.RuntimeError!u16 {
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
@ -370,7 +377,7 @@ const Compiler = struct {
return @intCast(self.chunk.constants.values.len - 1);
}
fn declare_float(self: *const Compiler, float: kym.Float) kym.RuntimeError!u16 {
fn declare_float(self: *const Compiler, float: script.Float) script.RuntimeError!u16 {
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
@ -386,7 +393,7 @@ const Compiler = struct {
return @intCast(self.chunk.constants.values.len - 1);
}
fn declare_string(self: *const Compiler, string: []const coral.io.Byte) kym.RuntimeError!u16 {
fn declare_string(self: *const Compiler, string: []const coral.io.Byte) script.RuntimeError!u16 {
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
@ -402,7 +409,7 @@ const Compiler = struct {
return @intCast(self.chunk.constants.values.len - 1);
}
fn declare_symbol(self: *const Compiler, symbol: []const coral.io.Byte) kym.RuntimeError!u16 {
fn declare_symbol(self: *const Compiler, symbol: []const coral.io.Byte) script.RuntimeError!u16 {
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
@ -418,7 +425,7 @@ const Compiler = struct {
return @intCast(self.chunk.constants.values.len - 1);
}
fn get_binding_index(environment: *const tree.Environment, declaration: *const tree.Declaration) kym.RuntimeError!?u8 {
fn get_binding_index(environment: *const tree.Environment, declaration: *const tree.Declaration) script.RuntimeError!?u8 {
var binding_index = @as(u8, 0);
while (binding_index < environment.capture_count) : (binding_index += 1) {
@ -459,7 +466,7 @@ const Compiler = struct {
}
};
const ConstList = coral.list.Stack(*kym.RuntimeObj);
const ConstList = coral.list.Stack(*script.RuntimeObj);
const LineList = coral.list.Stack(tokens.Line);
@ -483,6 +490,7 @@ pub const Opcode = union (enum) {
get_box,
set_box,
call: u8,
call_from: u8,
bind: u8,
not,
@ -507,7 +515,7 @@ const OpcodeList = coral.list.Stack(Opcode);
const Self = @This();
pub fn deinit(self: *Self, env: *kym.RuntimeEnv) void {
pub fn deinit(self: *Self, env: *script.RuntimeEnv) void {
while (self.constants.pop()) |constant| {
env.release(constant);
}
@ -529,7 +537,7 @@ pub fn deinit(self: *Self, env: *kym.RuntimeEnv) void {
self.bindings = &.{};
}
pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeObj {
pub fn dump(chunk: Self, env: *script.RuntimeEnv) script.RuntimeError!*script.RuntimeObj {
var opcode_cursor = @as(u32, 0);
var buffer = coral.list.ByteStack.init(env.allocator);
@ -620,7 +628,7 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeObj
return (try env.new_string(buffer.values)).pop().?;
}
pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj {
pub fn execute(self: *Self, env: *script.RuntimeEnv) script.RuntimeError!?*script.RuntimeObj {
self.cursor = 0;
while (self.cursor < self.opcodes.values.len) : (self.cursor += 1) {
@ -684,7 +692,7 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime
return env.raise(error.IllegalState, "cannot bind values to an already-bound chunk", .{});
}
chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*kym.RuntimeObj, null));
chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*script.RuntimeObj, null));
for (0 .. bind) |index| {
const value = env.pop();
@ -747,7 +755,15 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime
try env.index_set(index, value);
},
.call => |call| _ = try env.call(call),
.call => |call| _ = try env.call(call, null),
.call_from => |call_from| {
const caller = try env.expect_object(env.pop());
defer env.release(caller);
_ = try env.call(call_from, caller);
},
.not => {
const object = try env.expect_object(env.pop());
@ -880,7 +896,7 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime
return env.pop();
}
fn get_binding(self: *Self, env: *kym.RuntimeEnv, index: usize) kym.RuntimeError!?*kym.RuntimeObj {
fn get_binding(self: *Self, env: *script.RuntimeEnv, index: usize) script.RuntimeError!?*script.RuntimeObj {
if (index >= self.bindings.len) {
return env.raise(error.IllegalState, "invalid binding", .{});
}
@ -888,7 +904,7 @@ fn get_binding(self: *Self, env: *kym.RuntimeEnv, index: usize) kym.RuntimeError
return self.bindings[index];
}
fn get_constant(self: *const Self, env: *kym.RuntimeEnv, index: usize) kym.RuntimeError!*kym.RuntimeObj {
fn get_constant(self: *const Self, env: *script.RuntimeEnv, index: usize) script.RuntimeError!*script.RuntimeObj {
if (index >= self.constants.values.len) {
return env.raise(error.IllegalState, "invalid constant", .{});
}
@ -896,7 +912,7 @@ fn get_constant(self: *const Self, env: *kym.RuntimeEnv, index: usize) kym.Runti
return self.constants.values[index];
}
pub fn init(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) kym.RuntimeError!Self {
pub fn init(env: *script.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) script.RuntimeError!Self {
var chunk = Self{
.name = (try env.new_symbol(name)).pop().?,
.opcodes = OpcodeList.init(env.allocator),
@ -917,29 +933,31 @@ pub fn init(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *con
return chunk;
}
fn syscall_import(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj {
const arg = (try env.arg_get(0)).pop() orelse {
return env.raise(error.BadOperation, "`@import` requires one argument to be a valid import path", .{});
};
fn syscall_import(env: *script.RuntimeEnv) script.RuntimeError!?*script.RuntimeObj {
const arg = try env.expect_object((try env.arg(0)).pop());
defer env.release(arg);
return (try env.import(file.Path.from(&.{try env.expect_string(arg)}))).pop();
const file_path = file.Path.from_bytes(try env.expect_string(arg)) catch {
return env.raise(error.TypeMismatch, "`@import` requires argument `0` to be a valid import path", .{});
};
return (try env.import(file_path)).pop();
}
fn syscall_print(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj {
const string = (try (try env.arg_get(0)).to_string()).pop().?;
fn syscall_print(env: *script.RuntimeEnv) script.RuntimeError!?*script.RuntimeObj {
const string = (try (try env.arg(0)).to_string()).pop().?;
defer env.release(string);
env.print(string.is_string().?);
env.print_out(string.is_string().?);
return null;
}
fn syscall_vec2(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj {
fn syscall_vec2(env: *script.RuntimeEnv) script.RuntimeError!?*script.RuntimeObj {
const x = @as(f32, get_x: {
const x = (try env.arg_get(0)).pop() orelse {
const x = (try env.arg(0)).pop() orelse {
return env.raise(error.BadOperation, "a first argument is required to create a vector", .{});
};
@ -948,21 +966,18 @@ fn syscall_vec2(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj {
break: get_x @floatCast(try env.expect_float(x));
});
if ((try env.arg_get(1)).pop()) |y| {
if ((try env.arg(1)).pop()) |y| {
defer env.release(y);
return (try env.new_vector2(.{
.y = @floatCast(try env.expect_float(y)),
.x = x,
})).pop();
return (try env.new_vector2(.{x, @as(f32, @floatCast(try env.expect_float(y)))})).pop();
}
return (try env.new_vector2(kym.Vector2.from_scalar(x))).pop();
return (try env.new_vector2(@splat(x))).pop();
}
fn syscall_vec3(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj {
fn syscall_vec3(env: *script.RuntimeEnv) script.RuntimeError!?*script.RuntimeObj {
const x = @as(f32, get_x: {
const x = (try env.arg_get(0)).pop() orelse {
const x = (try env.arg(0)).pop() orelse {
return env.raise(error.BadOperation, "a first argument is required to create a vector", .{});
};
@ -971,12 +986,11 @@ fn syscall_vec3(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj {
break: get_x @floatCast(try env.expect_float(x));
});
if ((try env.arg_get(1)).pop()) |y| {
if ((try env.arg(1)).pop()) |y| {
defer env.release(y);
return (try env.new_vector3(.{
.z = @as(f32, get_z: {
const z = (try env.arg_get(0)).pop() orelse {
return (try env.new_vector3(.{x, @as(f32, @floatCast(try env.expect_float(y))), @as(f32, get_z: {
const z = (try env.arg(0)).pop() orelse {
return env.raise(error.BadOperation,
"a third argument is required to create a vector if a first and second exist", .{});
};
@ -984,17 +998,13 @@ fn syscall_vec3(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj {
defer env.release(z);
break: get_z @floatCast(try env.expect_float(z));
}),
.y = @floatCast(try env.expect_float(y)),
.x = x,
})).pop();
})})).pop();
}
return (try env.new_vector3(kym.Vector3.from_scalar(x))).pop();
return (try env.new_vector3(@splat(x))).pop();
}
pub const typeinfo = &kym.Typeinfo{
pub const typeinfo = &script.Typeinfo{
.size = @sizeOf(Self),
.name = "lambda",
.destruct = typeinfo_destruct,
@ -1002,15 +1012,15 @@ pub const typeinfo = &kym.Typeinfo{
.to_string = typeinfo_to_string,
};
fn typeinfo_call(context: kym.Typeinfo.CallContext) kym.RuntimeError!?*kym.RuntimeObj {
fn typeinfo_call(context: script.Typeinfo.CallContext) script.RuntimeError!?*script.RuntimeObj {
return @as(*Self, @ptrCast(@alignCast(context.userdata))).execute(context.env);
}
fn typeinfo_destruct(context: kym.Typeinfo.DestructContext) void {
fn typeinfo_destruct(context: script.Typeinfo.DestructContext) void {
@as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env);
}
fn typeinfo_to_string(context: kym.Typeinfo.ToStringContext) kym.RuntimeError!*kym.RuntimeObj {
fn typeinfo_to_string(context: script.Typeinfo.ToStringContext) script.RuntimeError!*script.RuntimeObj {
return (try (try context.env.push(@as(*Self, @ptrCast(@alignCast(context.userdata))).name)).to_string()).pop().?;
}

View File

@ -1,21 +1,21 @@
const coral = @import("coral");
const kym = @import("../kym.zig");
const script = @import("../script.zig");
associative: RefTable,
contiguous: RefList,
const RefList = coral.list.Stack(?*kym.RuntimeObj);
const RefList = coral.list.Stack(?*script.RuntimeObj);
const RefTable = coral.map.Table(*kym.RuntimeObj, *kym.RuntimeObj, struct {
pub const hash = kym.RuntimeObj.hash;
const RefTable = coral.map.Table(*script.RuntimeObj, *script.RuntimeObj, struct {
pub const hash = script.RuntimeObj.hash;
pub const equals = kym.RuntimeObj.equals;
pub const equals = script.RuntimeObj.equals;
});
const Self = @This();
pub fn deinit(self: *Self, env: *kym.RuntimeEnv) void {
pub fn deinit(self: *Self, env: *script.RuntimeEnv) void {
{
var field_iterable = self.associative.as_iterable();
@ -36,14 +36,14 @@ pub fn deinit(self: *Self, env: *kym.RuntimeEnv) void {
self.contiguous.deinit();
}
pub fn init(env: *kym.RuntimeEnv) Self {
pub fn init(env: *script.RuntimeEnv) Self {
return .{
.associative = RefTable.init(env.allocator, .{}),
.contiguous = RefList.init(env.allocator),
};
}
pub const typeinfo = &kym.Typeinfo{
pub const typeinfo = &script.Typeinfo{
.size = @sizeOf(Self),
.name = "table",
.destruct = typeinfo_destruct,
@ -51,13 +51,13 @@ pub const typeinfo = &kym.Typeinfo{
.set = typeinfo_set,
};
fn typeinfo_destruct(context: kym.Typeinfo.DestructContext) void {
fn typeinfo_destruct(context: script.Typeinfo.DestructContext) void {
@as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env);
}
fn typeinfo_get(context: kym.Typeinfo.GetContext) kym.RuntimeError!?*kym.RuntimeObj {
fn typeinfo_get(context: script.Typeinfo.GetContext) script.RuntimeError!?*script.RuntimeObj {
const table = @as(*Self, @ptrCast(@alignCast(context.userdata)));
const index = (try context.push_index()).pop().?;
const index = context.get_index();
defer context.env.release(index);
@ -79,9 +79,9 @@ fn typeinfo_get(context: kym.Typeinfo.GetContext) kym.RuntimeError!?*kym.Runtime
return null;
}
fn typeinfo_set(context: kym.Typeinfo.SetContext) kym.RuntimeError!void {
fn typeinfo_set(context: script.Typeinfo.SetContext) script.RuntimeError!void {
const table = @as(*Self, @ptrCast(@alignCast(context.userdata)));
const index = (try context.push_index()).pop().?;
const index = context.get_index();
errdefer context.env.release(index);
@ -98,7 +98,7 @@ fn typeinfo_set(context: kym.Typeinfo.SetContext) kym.RuntimeError!void {
context.env.release(replacing);
}
if ((try context.push_value()).pop()) |value| {
if (context.get_value()) |value| {
errdefer context.env.release(value);
maybe_replacing.* = value;
@ -110,7 +110,7 @@ fn typeinfo_set(context: kym.Typeinfo.SetContext) kym.RuntimeError!void {
}
}
const value = (try context.push_value()).pop() orelse {
const value = context.get_value() orelse {
if (table.associative.remove(index)) |removed| {
context.env.release(removed.key);
context.env.release(removed.value);

View File

@ -25,6 +25,7 @@ kind: union (enum) {
declaration_set: DeclarationSet,
field_get: FieldGet,
field_set: FieldSet,
field_invoke: FieldInvoke,
subscript_get: SubscriptGet,
subscript_set: SubscriptSet,
binary_op: BinaryOp,
@ -112,6 +113,12 @@ pub const DeclarationSet = struct {
assign: *const Self,
};
pub const FieldInvoke = struct {
argument: ?*const Self,
identifier: []const coral.io.Byte,
object: *const Self,
};
pub const FieldGet = struct {
identifier: []const coral.io.Byte,
object: *const Self,
@ -364,7 +371,7 @@ fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env
switch (stream.token) {
.symbol_comma => stream.skip_newlines(),
.symbol_paren_right => break,
else => return root.report_error(stream.line, "expected `,` or `)` after lambda argument", .{}),
else => return root.report_error(stream.line, "expected `,` or `)` after argument", .{}),
}
const next_argument = try parse(root, stream, environment);
@ -382,12 +389,22 @@ fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Env
expression = try root.create_expr(.{
.line = lines_stepped,
.kind = .{
.kind = switch (expression.kind) {
.field_get => |field_get| .{
.field_invoke = .{
.identifier = field_get.identifier,
.argument = first_argument,
.object = field_get.object,
},
},
else => .{
.invoke = .{
.argument = first_argument,
.object = unnecessary_temp,
},
},
}
});
},

View File

@ -1,5 +1,185 @@
const coral = @import("coral");
const ona = @import("ona");
pub fn main() void {
ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd});
pub const ScriptPlugin = struct {
env: ona.script.RuntimeEnv,
events: ?*ona.script.RuntimeObj,
const EventsObject = struct {
updaters: RuntimeObjList,
loop: *ona.GraphicsLoop,
fn get(context: ona.script.Typeinfo.GetContext) ona.script.RuntimeError!?*ona.script.RuntimeObj {
const index = context.get_index();
defer context.env.release(index);
inline for ([_][]const u8{"on_update"}) |name| {
const symbol = (try context.env.new_symbol(name)).pop().?;
defer context.env.release(symbol);
if (index.equals(symbol)) {
return (try context.env.new_syscall(@field(EventsObject, name))).pop();
}
}
return null;
}
fn on_update(env: *ona.script.RuntimeEnv) ona.script.RuntimeError!?*ona.script.RuntimeObj {
const caller = try env.expect_object(try env.get_caller());
defer env.release(caller);
const updater = try env.expect_object((try env.arg(0)).pop());
errdefer env.release(updater);
try @as(*EventsObject, @ptrCast(@alignCast(try env.expect_dynamic(caller, typeinfo)))).updaters.push_one(updater);
return null;
}
const typeinfo = &ona.script.Typeinfo{
.name = "events",
.size = @sizeOf(EventsObject),
.get = get,
};
fn update(env: *ona.script.RuntimeEnv, _: *ona.GraphicsLoop) void {
return ((env.arg(0) catch return).call(0, null) catch return).discard();
}
};
const RuntimeObjList = coral.list.Stack(*ona.script.RuntimeObj);
pub fn deinit(self: *ScriptPlugin) void {
if (self.events) |object| {
self.env.release(object);
}
self.env.deinit();
}
fn import_events(self: *ScriptPlugin, env: *ona.script.RuntimeEnv) ona.script.RuntimeError!?*ona.script.RuntimeObj {
return env.acquire(self.events.?);
}
pub fn init() coral.io.AllocationError!ScriptPlugin {
var env = try ona.script.RuntimeEnv.init(ona.heap.allocator, 255, .{
.print_out = ona.log_info,
.print_err = ona.log_fail,
});
errdefer env.deinit();
return .{
.env = env,
.events = null,
};
}
fn load(self: *ScriptPlugin, loop: *ona.GraphicsLoop) void {
self.events = (self.env.new_dynamic(EventsObject.typeinfo, EventsObject{
.updaters = RuntimeObjList.init(ona.heap.allocator),
.loop = loop,
}) catch {
return ona.log_fail("failed to instantiate events object");
}).pop();
self.env.override_import("events", ona.script.Importer.bind(ScriptPlugin, self, import_events)) catch {
return ona.log_fail("failed to overide native import paths");
};
self.reload(loop) catch {};
}
fn reload(self: *ScriptPlugin, loop: *ona.GraphicsLoop) ona.script.RuntimeError!void {
var title = comptime try coral.utf8.SmallString.from_units("Ona");
var res_width = @as(u16, 640);
var res_height = @as(u16, 480);
var tick_rate = @as(f64, 60);
if ((try self.env.import(comptime try ona.file.Path.from_bytes("app.ona"))).pop()) |manifest| {
defer self.env.release(manifest);
if (try ona.script.get_field(&self.env, manifest, "res_width")) |object| {
defer self.env.release(object);
const int = @typeInfo(@TypeOf(res_width)).Int;
res_width = coral.math.checked_cast(int, try self.env.expect_fixed(object)) orelse {
return self.env.raise(error.TypeMismatch, "`res_width` property cannot be greater than `{max}`", .{
.max = coral.math.max_int(int),
});
};
}
if (try ona.script.get_field(&self.env, manifest, "res_height")) |object| {
defer self.env.release(object);
const int = @typeInfo(@TypeOf(res_height)).Int;
res_height = coral.math.checked_cast(int, try self.env.expect_fixed(object)) orelse {
return self.env.raise(error.TypeMismatch, "`res_height` property cannot be greater than `{max}`", .{
.max = coral.math.max_int(int),
});
};
}
if (try ona.script.get_field(&self.env, manifest, "tick_rate")) |object| {
defer self.env.release(object);
tick_rate = try self.env.expect_float(object);
}
if (try ona.script.get_field(&self.env, manifest, "title")) |object| {
defer self.env.release(object);
title = coral.utf8.SmallString.from_units(try self.env.expect_string(object)) catch |from_error| {
return switch (from_error) {
error.InvalidUtf8 => self.env.raise(error.TypeMismatch, "`title` cannot contain invalid utf8", .{}),
error.TooBig => self.env.raise(error.TypeMismatch, "`title` is too long", .{}),
};
};
}
loop.set_resolution(res_width, res_height);
loop.set_title(title);
loop.set_tick_rate(tick_rate);
}
}
fn update(self: *ScriptPlugin, graphics_update: ona.GraphicsUpdate) void {
if (graphics_update.loop.is_key_pressed(ona.Key.f5)) {
self.reload(graphics_update.loop) catch {};
}
const eventsect = @as(*EventsObject, @ptrCast(@alignCast(self.env.expect_dynamic(self.events.?, EventsObject.typeinfo) catch {
return;
})));
const dt = (self.env.new_float(graphics_update.delta_time) catch return).pop().?;
defer self.env.release(dt);
for (eventsect.updaters.values) |updater| {
(((self.env.push(dt) catch return).push(updater) catch return).call(1, null) catch return).discard();
}
}
};
pub fn main() void {
var script_plugin = ScriptPlugin.init() catch {
return ona.log_fail("failed to initialize script plugin");
};
defer script_plugin.deinit();
ona.start_graphics(&.{
.{.loader = ona.GraphicsLoader.bind(ScriptPlugin, &script_plugin, ScriptPlugin.load)},
.{.updater = ona.GraphicsUpdater.bind(ScriptPlugin, &script_plugin, ScriptPlugin.update)},
});
}