Merge pull request 'Replace System Objects with Builtins and Syscalls' (#33) from kym-builtins into main
continuous-integration/drone/push Build is passing Details

Reviewed-on: #33
This commit is contained in:
kayomn 2023-08-13 02:35:25 +02:00
commit 3fecb795e9
7 changed files with 163 additions and 194 deletions

View File

@ -14,8 +14,8 @@ options = {
options["foo"] = "rab" options["foo"] = "rab"
options[42] = "24" options[42] = "24"
@log_info(options.title) @print(options.title)
@log_info(options["foo"]) @print(options["foo"])
@log_info(options[42]) @print(options[42])
return options return options

View File

@ -12,8 +12,8 @@ pub const Manifest = struct {
height: u16 = 480, height: u16 = 480,
tick_rate: f32 = 60.0, tick_rate: f32 = 60.0,
pub fn load(self: *Manifest, env: *kym.RuntimeEnv, file_access: file.Access) kym.RuntimeError!void { pub fn load(self: *Manifest, env: *kym.RuntimeEnv) kym.RuntimeError!void {
const manifest = try env.expect(try env.execute_file(file_access, file.Path.from(&.{"app.ona"}))); const manifest = try env.expect(try env.import(file.Path.from(&.{"app.ona"})));
defer env.discard(manifest); defer env.discard(manifest);

View File

@ -3,10 +3,13 @@ const coral = @import("coral");
const ext = @import("./ext.zig"); const ext = @import("./ext.zig");
pub const Access = union (enum) { pub const Access = union (enum) {
null,
sandboxed_path: *const Path, sandboxed_path: *const Path,
pub fn open_readable(self: Access, readable_path: Path) ?*Readable { pub fn open_readable(self: Access, readable_path: Path) ?*Readable {
switch (self) { switch (self) {
.null => return null,
.sandboxed_path => |sandboxed_path| { .sandboxed_path => |sandboxed_path| {
const readable_path_string = sandboxed_path.joined(readable_path).to_string() orelse return null; const readable_path_string = sandboxed_path.joined(readable_path).to_string() orelse return null;
@ -17,6 +20,8 @@ pub const Access = union (enum) {
pub fn query(self: Access, path: Path) ?Info { pub fn query(self: Access, path: Path) ?Info {
switch (self) { switch (self) {
.null => return null,
.sandboxed_path => |sandboxed_path| { .sandboxed_path => |sandboxed_path| {
const path_string = sandboxed_path.joined(path).to_string() orelse return null; const path_string = sandboxed_path.joined(path).to_string() orelse return null;
const rw_ops = ext.SDL_RWFromFile(path_string, "rb") orelse return null; const rw_ops = ext.SDL_RWFromFile(path_string, "rb") orelse return null;

View File

@ -4,30 +4,14 @@ const coral = @import("coral");
const file = @import("./file.zig"); const file = @import("./file.zig");
pub const Caller = coral.io.Generator(RuntimeError!?*RuntimeRef, *RuntimeEnv);
pub const ErrorHandler = coral.io.Generator(void, ErrorInfo);
pub const ErrorInfo = struct {
message: []const coral.io.Byte,
frames: []const Frame,
};
pub const Fixed = i32; pub const Fixed = i32;
pub const Float = f64; pub const Float = f64;
pub const Frame = struct {
name: []const coral.io.Byte,
arg_count: u8,
locals_top: usize,
};
pub const RuntimeEnv = struct { pub const RuntimeEnv = struct {
options: Options,
interned_symbols: SymbolSet, interned_symbols: SymbolSet,
allocator: coral.io.Allocator, allocator: coral.io.Allocator,
error_handler: ErrorHandler,
system_bindings: RefTable,
locals: RefList, locals: RefList,
frames: FrameStack, frames: FrameStack,
@ -37,6 +21,11 @@ pub const RuntimeEnv = struct {
opcodes: OpcodeList, opcodes: OpcodeList,
constants: ConstList, constants: ConstList,
const Builtin = enum {
import,
print,
};
const Constant = union (enum) { const Constant = union (enum) {
fixed: Fixed, fixed: Fixed,
float: Float, float: Float,
@ -51,7 +40,7 @@ pub const RuntimeEnv = struct {
push_const: u16, push_const: u16,
push_local: u8, push_local: u8,
push_table: u32, push_table: u32,
push_system: u16, push_builtin: Builtin,
local_set: u8, local_set: u8,
object_get, object_get,
object_set, object_set,
@ -175,10 +164,18 @@ pub const RuntimeEnv = struct {
try self.compile_expression(chunk, grouped_expression.*); try self.compile_expression(chunk, grouped_expression.*);
}, },
.get_system => |get_system| { .builtin => |builtin| {
try chunk.opcodes.push_one(.{ coral.debug.assert(builtin.len != 0);
.push_system = try chunk.declare_constant(.{.symbol = get_system}),
const decoded_builtin = @as(?Builtin, switch (builtin[0]) {
'i' => if (coral.io.ends_with(builtin, "mport")) .import else null,
'p' => if (coral.io.ends_with(builtin, "rint")) .print else null,
else => null,
}); });
try chunk.opcodes.push_one(.{.push_builtin = decoded_builtin orelse {
return chunk.env.raise(error.BadSyntax, "unknown builtin");
}});
}, },
.local_get => |local_get| { .local_get => |local_get| {
@ -360,12 +357,15 @@ pub const RuntimeEnv = struct {
try self.env.locals.push_one(table); try self.env.locals.push_one(table);
}, },
.push_system => |push_system| { .push_builtin => |push_builtin| {
if (self.env.system_bindings.lookup(self.constants.values[push_system])) |syscallable| { const builtin_syscall = try self.env.new_syscall(switch (push_builtin) {
try self.env.locals.push_one(try self.env.acquire(syscallable)); .import => syscall_import,
} else { .print => syscall_print,
try self.env.locals.push_one(null); });
}
errdefer self.env.discard(builtin_syscall);
try self.env.locals.push_one(builtin_syscall);
}, },
.local_set => |local_set| { .local_set => |local_set| {
@ -441,6 +441,8 @@ pub const RuntimeEnv = struct {
defer coral.debug.assert(self.env.frames.pop() != null); defer coral.debug.assert(self.env.frames.pop() != null);
break: call try switch (callable.object().payload) { break: call try switch (callable.object().payload) {
.syscall => |syscall| syscall(self.env),
.dynamic => |dynamic| dynamic.typeinfo().call(.{ .dynamic => |dynamic| dynamic.typeinfo().call(.{
.userdata = dynamic.userdata(), .userdata = dynamic.userdata(),
.env = self.env, .env = self.env,
@ -799,7 +801,19 @@ pub const RuntimeEnv = struct {
const ConstList = coral.list.Stack(*RuntimeRef); const ConstList = coral.list.Stack(*RuntimeRef);
const FrameStack = coral.list.Stack(Frame); const FrameStack = coral.list.Stack(struct {
name: []const coral.io.Byte,
arg_count: u8,
locals_top: usize,
});
pub const Options = struct {
import_access: file.Access = .null,
print: ?*const Printer = null,
print_error: ?*const Printer = null,
};
pub const Printer = fn (buffer: []const coral.io.Byte) void;
const RefList = coral.list.Stack(?*RuntimeRef); const RefList = coral.list.Stack(?*RuntimeRef);
@ -916,7 +930,7 @@ pub const RuntimeEnv = struct {
return @ptrCast(object); return @ptrCast(object);
} }
pub fn arg(self: *RuntimeEnv, index: usize) RuntimeError!?*RuntimeRef { pub fn acquire_arg(self: *RuntimeEnv, index: usize) RuntimeError!?*RuntimeRef {
const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow"); const frame = self.frames.peek() orelse return self.raise(error.IllegalState, "stack underflow");
if (index < frame.arg_count) { if (index < frame.arg_count) {
@ -957,6 +971,8 @@ pub const RuntimeEnv = struct {
defer coral.io.assert(self.frames.pop() != null); defer coral.io.assert(self.frames.pop() != null);
return switch (callable.object().payload) { return switch (callable.object().payload) {
.syscall => |syscall| syscall(self.env),
.dynamic => |dynamic| dynamic.typeinfo.call(.{ .dynamic => |dynamic| dynamic.typeinfo.call(.{
.userdata = dynamic.userdata(), .userdata = dynamic.userdata(),
.env = self, .env = self,
@ -975,7 +991,7 @@ pub const RuntimeEnv = struct {
if (object.ref_count == 0) { if (object.ref_count == 0) {
switch (object.payload) { switch (object.payload) {
.false, .true, .float, .fixed, .symbol => {}, .false, .true, .float, .fixed, .symbol, .syscall => {},
.string => |string| { .string => |string| {
coral.debug.assert(string.len >= 0); coral.debug.assert(string.len >= 0);
@ -998,8 +1014,8 @@ pub const RuntimeEnv = struct {
} }
} }
pub fn execute_file(self: *RuntimeEnv, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef { pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!?*RuntimeRef {
const file_data = (try file.allocate_and_load(self.allocator, file_access, file_path)) orelse { 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 file specified"); return self.raise(error.BadOperation, "failed to open or read file specified");
}; };
@ -1043,18 +1059,8 @@ pub const RuntimeEnv = struct {
} }
} }
{
var iterable = self.system_bindings.as_iterable();
while (iterable.next()) |entry| {
self.discard(entry.key);
self.discard(entry.value);
}
}
self.frames.free(); self.frames.free();
self.locals.free(); self.locals.free();
self.system_bindings.free();
self.interned_symbols.free(); self.interned_symbols.free();
} }
@ -1069,13 +1075,12 @@ pub const RuntimeEnv = struct {
}; };
} }
pub fn make(allocator: coral.io.Allocator, error_handler: ErrorHandler) coral.io.AllocationError!RuntimeEnv { pub fn make(allocator: coral.io.Allocator, options: Options) coral.io.AllocationError!RuntimeEnv {
return RuntimeEnv{ return RuntimeEnv{
.locals = RefList.make(allocator), .locals = RefList.make(allocator),
.frames = FrameStack.make(allocator), .frames = FrameStack.make(allocator),
.system_bindings = RefTable.make(allocator, .{}),
.interned_symbols = SymbolSet.make(allocator, .{}), .interned_symbols = SymbolSet.make(allocator, .{}),
.error_handler = error_handler, .options = options,
.allocator = allocator, .allocator = allocator,
}; };
} }
@ -1158,6 +1163,13 @@ pub const RuntimeEnv = struct {
}); });
} }
pub fn new_syscall(self: *RuntimeEnv, value: *const Syscall) RuntimeError!*RuntimeRef {
return RuntimeRef.allocate(self.allocator, .{
.ref_count = 1,
.payload = .{.syscall = value},
});
}
pub fn new_table(self: *RuntimeEnv) RuntimeError!*RuntimeRef { pub fn new_table(self: *RuntimeEnv) RuntimeError!*RuntimeRef {
var table = Table{ var table = Table{
.associative = RefTable.make(self.allocator, .{}), .associative = RefTable.make(self.allocator, .{}),
@ -1178,11 +1190,32 @@ pub const RuntimeEnv = struct {
}); });
} }
pub fn print(self: *RuntimeEnv, buffer: []const coral.io.Byte) void {
if (self.options.print) |bound_print| {
bound_print(buffer);
}
}
pub fn print_error(self: *RuntimeEnv, buffer: []const coral.io.Byte) void {
if (self.options.print_error) |bound_print_error| {
bound_print_error(buffer);
}
}
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError { pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError {
self.error_handler.invoke(.{ self.print_error(message);
.message = message,
.frames = self.frames.values, if (!self.frames.is_empty()) {
}); self.print_error("stack trace:");
var remaining_frames = self.frames.values.len;
while (remaining_frames != 0) {
remaining_frames -= 1;
self.print_error(self.frames.values[remaining_frames].name);
}
}
return error_value; return error_value;
} }
@ -1254,6 +1287,7 @@ pub const RuntimeRef = opaque {
float: Float, float: Float,
fixed: Fixed, fixed: Fixed,
symbol: [*:0]const coral.io.Byte, symbol: [*:0]const coral.io.Byte,
syscall: *const Syscall,
string: struct { string: struct {
ptr: [*]coral.io.Byte, ptr: [*]coral.io.Byte,
@ -1323,6 +1357,11 @@ pub const RuntimeRef = opaque {
else => false, else => false,
}, },
.syscall => |self_syscall| switch (other.object().payload) {
.syscall => |other_syscall| self_syscall == other_syscall,
else => false,
},
.string => |self_string| switch (other.object().payload) { .string => |self_string| switch (other.object().payload) {
.string => |other_string| coral.io.equals(self_string.unpack(), other_string.unpack()), .string => |other_string| coral.io.equals(self_string.unpack(), other_string.unpack()),
else => false, else => false,
@ -1344,7 +1383,8 @@ pub const RuntimeRef = opaque {
.true => 1231, .true => 1231,
.float => |float| @bitCast(float), .float => |float| @bitCast(float),
.fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))), .fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))),
.symbol => |symbol| @bitCast(@intFromPtr(symbol)), .symbol => |symbol| @intFromPtr(symbol),
.syscall => |syscall| @intFromPtr(syscall),
.string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()), .string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()),
.dynamic => |dynamic| @intFromPtr(dynamic.typeinfo()) ^ @intFromPtr(dynamic.userdata().ptr), .dynamic => |dynamic| @intFromPtr(dynamic.typeinfo()) ^ @intFromPtr(dynamic.userdata().ptr),
}; };
@ -1364,12 +1404,15 @@ pub const RuntimeRef = opaque {
.float => |float| float != 0, .float => |float| float != 0,
.fixed => |fixed| fixed != 0, .fixed => |fixed| fixed != 0,
.symbol => true, .symbol => true,
.syscall => true,
.string => |string| string.len != 0, .string => |string| string.len != 0,
.dynamic => true, .dynamic => true,
}; };
} }
}; };
pub const Syscall = fn (env: *RuntimeEnv) RuntimeError!?*RuntimeRef;
pub const Typeinfo = struct { pub const Typeinfo = struct {
name: []const coral.io.Byte, name: []const coral.io.Byte,
size: usize, size: usize,
@ -1396,14 +1439,6 @@ pub const Typeinfo = struct {
} }
}; };
pub fn bind_syscaller(env: *RuntimeEnv, name: []const coral.io.Byte, caller: Caller) RuntimeError!void {
const callable = try new_caller(env, caller);
defer env.discard(callable);
try env.bind_system(name, callable);
}
pub fn get_field(env: *RuntimeEnv, indexable: *RuntimeRef, field: []const coral.io.Byte) RuntimeError!?*RuntimeRef { pub fn get_field(env: *RuntimeEnv, indexable: *RuntimeRef, field: []const coral.io.Byte) RuntimeError!?*RuntimeRef {
const field_symbol = try env.new_symbol(field); const field_symbol = try env.new_symbol(field);
@ -1428,18 +1463,20 @@ pub fn get_key(env: *RuntimeEnv, indexable: *RuntimeRef, key: []const coral.io.B
return env.get(indexable, key_string); return env.get(indexable, key_string);
} }
pub fn new_caller(env: *RuntimeEnv, value: Caller) RuntimeError!*RuntimeRef { fn syscall_import(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
const Callable = struct { const arg = try env.expect(try env.acquire_arg(0));
fn call(method: Typeinfo.Method) RuntimeError!?*RuntimeRef {
coral.debug.assert(method.userdata.len == @sizeOf(Caller));
return @as(*Caller, @ptrCast(@alignCast(method.userdata))).invoke(method.env); defer env.discard(arg);
}
};
return env.new_dynamic(coral.io.bytes_of(&value).ptr, &.{ return env.import(file.Path.from(&.{try env.unbox_string(arg)}));
.name = "<native>", }
.size = @sizeOf(Caller),
.call = Callable.call, fn syscall_print(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
}); const arg = try env.expect(try env.acquire_arg(0));
defer env.discard(arg);
env.print(try env.unbox_string(arg));
return null;
} }

View File

@ -13,12 +13,12 @@ pub const Expression = union (enum) {
nil_literal, nil_literal,
true_literal, true_literal,
false_literal, false_literal,
builtin: []const coral.io.Byte,
number_literal: []const coral.io.Byte, number_literal: []const coral.io.Byte,
string_literal: []const coral.io.Byte, string_literal: []const coral.io.Byte,
symbol_literal: []const coral.io.Byte, symbol_literal: []const coral.io.Byte,
table_literal: TableLiteral, table_literal: TableLiteral,
grouped_expression: *Expression, grouped_expression: *Expression,
get_system: []const coral.io.Byte,
local_get: []const coral.io.Byte, local_get: []const coral.io.Byte,
local_set: []const coral.io.Byte, local_set: []const coral.io.Byte,
@ -351,10 +351,10 @@ fn parse_factor(self: *Self) ParseError!Expression {
break: parse .{.local_get = identifier}; break: parse .{.local_get = identifier};
}, },
.system_identifier => |system_identifier| { .builtin => |builtin| {
self.tokenizer.skip_newlines(); self.tokenizer.skip_newlines();
break: parse .{.get_system = system_identifier}; break: parse .{.builtin = builtin};
}, },
.symbol_brace_left => { .symbol_brace_left => {

View File

@ -4,9 +4,8 @@ pub const Token = union(enum) {
end, end,
unknown: coral.io.Byte, unknown: coral.io.Byte,
newline, newline,
system_identifier: []const coral.io.Byte,
identifier: []const coral.io.Byte, identifier: []const coral.io.Byte,
builtin: []const coral.io.Byte,
symbol_plus, symbol_plus,
symbol_minus, symbol_minus,
@ -46,8 +45,8 @@ pub const Token = union(enum) {
.unknown => |unknown| @as([*]const coral.io.Byte, @ptrCast(&unknown))[0 .. 1], .unknown => |unknown| @as([*]const coral.io.Byte, @ptrCast(&unknown))[0 .. 1],
.newline => "newline", .newline => "newline",
.system_identifier => |identifier| identifier,
.identifier => |identifier| identifier, .identifier => |identifier| identifier,
.builtin => |identifier| identifier,
.symbol_plus => "+", .symbol_plus => "+",
.symbol_minus => "-", .symbol_minus => "-",
@ -166,40 +165,52 @@ pub const Tokenizer = struct {
coral.debug.assert(identifier.len != 0); coral.debug.assert(identifier.len != 0);
switch (identifier[0]) { switch (identifier[0]) {
'c' => if (coral.io.ends_with(identifier, "onst")) { 'c' => {
self.token = .keyword_const; if (coral.io.ends_with(identifier, "onst")) {
self.token = .keyword_const;
return; return;
}
}, },
'n' => if (coral.io.ends_with(identifier, "il")) { 'n' => {
self.token = .keyword_nil; if (coral.io.ends_with(identifier, "il")) {
self.token = .keyword_nil;
return; return;
}
}, },
'f' => if (coral.io.ends_with(identifier, "alse")) { 'f' => {
self.token = .keyword_false; if (coral.io.ends_with(identifier, "alse")) {
self.token = .keyword_false;
return; return;
}
}, },
't' => if (coral.io.ends_with(identifier, "rue")) { 't' => {
self.token = .keyword_true; if (coral.io.ends_with(identifier, "rue")) {
self.token = .keyword_true;
return; return;
}
}, },
'r' => if (coral.io.ends_with(identifier, "eturn")) { 'r' => {
self.token = .keyword_return; if (coral.io.ends_with(identifier, "eturn")) {
self.token = .keyword_return;
return; return;
}
}, },
's' => if (coral.io.ends_with(identifier, "elf")) { 's' => {
self.token = .keyword_self; if (coral.io.ends_with(identifier, "elf")) {
self.token = .keyword_self;
return; return;
}
}, },
else => {}, else => {},
@ -213,44 +224,14 @@ pub const Tokenizer = struct {
'@' => { '@' => {
cursor += 1; cursor += 1;
if (cursor < self.source.len) switch (self.source[cursor]) { const begin = cursor;
'A'...'Z', 'a'...'z', '_' => {
const begin = cursor;
cursor += 1; while (cursor < self.source.len) switch (self.source[cursor]) {
'0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1,
while (cursor < self.source.len) switch (self.source[cursor]) { else => break,
'0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1,
else => break,
};
self.token = .{.system_identifier = self.source[begin .. cursor]};
return;
},
'"' => {
cursor += 1;
const begin = cursor;
cursor += 1;
while (cursor < self.source.len) switch (self.source[cursor]) {
'"' => break,
else => cursor += 1,
};
self.token = .{.system_identifier = self.source[begin .. cursor]};
cursor += 1;
return;
},
else => {},
}; };
self.token = .symbol_at; self.token = if (begin == cursor) .{.unknown = '@'} else .{.builtin = self.source[begin .. cursor]};
return; return;
}, },

View File

@ -10,52 +10,6 @@ const heap = @import("./heap.zig");
const kym = @import("./kym.zig"); const kym = @import("./kym.zig");
fn kym_handle_errors(info: kym.ErrorInfo) void {
app.log_fail(info.message);
var remaining_frames = info.frames.len;
if (remaining_frames != 0) {
app.log_fail("stack trace:");
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 {
const argument = try env.expect(try env.arg(0));
defer env.discard(argument);
app.log_info(try env.unbox_string(argument));
return null;
}
fn kym_log_warn(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
const argument = try env.expect(try env.arg(0));
defer env.discard(argument);
app.log_warn(try env.unbox_string(argument));
return null;
}
fn kym_log_fail(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeRef {
const argument = try env.expect(try env.arg(0));
defer env.discard(argument);
app.log_fail(try env.unbox_string(argument));
return null;
}
fn last_sdl_error() [:0]const u8 { fn last_sdl_error() [:0]const u8 {
return coral.io.slice_sentineled(@as(u8, 0), @as([*:0]const u8, @ptrCast(ext.SDL_GetError()))); return coral.io.slice_sentineled(@as(u8, 0), @as([*:0]const u8, @ptrCast(ext.SDL_GetError())));
} }
@ -69,27 +23,19 @@ pub fn run_app(file_access: file.Access) void {
defer ext.SDL_Quit(); defer ext.SDL_Quit();
var script_env = kym.RuntimeEnv.make(heap.allocator, kym.ErrorHandler.from(kym_handle_errors)) catch { var script_env = kym.RuntimeEnv.make(heap.allocator, .{
.print = app.log_info,
.print_error = app.log_fail,
.import_access = file_access,
}) catch {
return app.log_fail("failed to initialize script runtime"); return app.log_fail("failed to initialize script runtime");
}; };
defer script_env.free(); defer script_env.free();
kym.bind_syscaller(&script_env, "log_info", kym.Caller.from(kym_log_info)) catch {
return app.log_fail("failed to bind `log_info` syscall");
};
kym.bind_syscaller(&script_env, "log_warn", kym.Caller.from(kym_log_warn)) catch {
return app.log_fail("failed to bind `log_warn` syscall");
};
kym.bind_syscaller(&script_env, "log_fail", kym.Caller.from(kym_log_fail)) catch {
return app.log_fail("failed to bind `log_fail` syscall");
};
var manifest = app.Manifest{}; var manifest = app.Manifest{};
manifest.load(&script_env, file_access) catch return; manifest.load(&script_env) catch return;
const window = create: { const window = create: {
const pos = ext.SDL_WINDOWPOS_CENTERED; const pos = ext.SDL_WINDOWPOS_CENTERED;