Replace System Objects with Builtins and Syscalls #33

Merged
kayomn merged 3 commits from kym-builtins into main 2023-08-13 02:35:26 +02:00
6 changed files with 163 additions and 195 deletions
Showing only changes of commit 429f7a52ad - Show all commits

View File

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

View File

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

View File

@ -4,30 +4,14 @@ const coral = @import("coral");
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 Float = f64;
pub const Frame = struct {
name: []const coral.io.Byte,
arg_count: u8,
locals_top: usize,
};
pub const RuntimeEnv = struct {
options: Options,
interned_symbols: SymbolSet,
allocator: coral.io.Allocator,
error_handler: ErrorHandler,
system_bindings: RefTable,
locals: RefList,
frames: FrameStack,
@ -37,6 +21,11 @@ pub const RuntimeEnv = struct {
opcodes: OpcodeList,
constants: ConstList,
const Builtin = enum {
import,
print,
};
const Constant = union (enum) {
fixed: Fixed,
float: Float,
@ -51,7 +40,7 @@ pub const RuntimeEnv = struct {
push_const: u16,
push_local: u8,
push_table: u32,
push_system: u16,
push_builtin: Builtin,
local_set: u8,
object_get,
object_set,
@ -175,10 +164,12 @@ pub const RuntimeEnv = struct {
try self.compile_expression(chunk, grouped_expression.*);
},
.get_system => |get_system| {
try chunk.opcodes.push_one(.{
.push_system = try chunk.declare_constant(.{.symbol = get_system}),
});
.import => {
try chunk.opcodes.push_one(.{.push_builtin = .import});
},
.print => {
try chunk.opcodes.push_one(.{.push_builtin = .print});
},
.local_get => |local_get| {
@ -360,12 +351,15 @@ pub const RuntimeEnv = struct {
try self.env.locals.push_one(table);
},
.push_system => |push_system| {
if (self.env.system_bindings.lookup(self.constants.values[push_system])) |syscallable| {
try self.env.locals.push_one(try self.env.acquire(syscallable));
} else {
try self.env.locals.push_one(null);
}
.push_builtin => |push_builtin| {
const builtin_syscall = try self.env.new_syscall(switch (push_builtin) {
.import => syscall_import,
.print => syscall_print,
});
errdefer self.env.discard(builtin_syscall);
try self.env.locals.push_one(builtin_syscall);
},
.local_set => |local_set| {
@ -441,6 +435,8 @@ pub const RuntimeEnv = struct {
defer coral.debug.assert(self.env.frames.pop() != null);
break: call try switch (callable.object().payload) {
.syscall => |syscall| syscall(self.env),
.dynamic => |dynamic| dynamic.typeinfo().call(.{
.userdata = dynamic.userdata(),
.env = self.env,
@ -799,7 +795,19 @@ pub const RuntimeEnv = struct {
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,
print: *const Printer,
print_error: *const Printer,
};
kayomn marked this conversation as resolved
Review

Options should provide reasonable defaults if they're intended to be optional.

Options should provide reasonable defaults if they're intended to be **option**al.
pub const Printer = fn (buffer: []const coral.io.Byte) void;
const RefList = coral.list.Stack(?*RuntimeRef);
@ -916,7 +924,7 @@ pub const RuntimeEnv = struct {
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");
if (index < frame.arg_count) {
@ -957,6 +965,8 @@ pub const RuntimeEnv = struct {
defer coral.io.assert(self.frames.pop() != null);
return switch (callable.object().payload) {
.syscall => |syscall| syscall(self.env),
.dynamic => |dynamic| dynamic.typeinfo.call(.{
.userdata = dynamic.userdata(),
.env = self,
@ -975,7 +985,7 @@ pub const RuntimeEnv = struct {
if (object.ref_count == 0) {
switch (object.payload) {
.false, .true, .float, .fixed, .symbol => {},
.false, .true, .float, .fixed, .symbol, .syscall => {},
.string => |string| {
coral.debug.assert(string.len >= 0);
@ -998,8 +1008,8 @@ pub const RuntimeEnv = struct {
}
}
pub fn execute_file(self: *RuntimeEnv, file_access: file.Access, file_path: file.Path) RuntimeError!?*RuntimeRef {
const file_data = (try file.allocate_and_load(self.allocator, file_access, file_path)) orelse {
pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!?*RuntimeRef {
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");
};
@ -1043,18 +1053,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.locals.free();
self.system_bindings.free();
self.interned_symbols.free();
}
@ -1069,13 +1069,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{
.locals = RefList.make(allocator),
.frames = FrameStack.make(allocator),
.system_bindings = RefTable.make(allocator, .{}),
.interned_symbols = SymbolSet.make(allocator, .{}),
.error_handler = error_handler,
.options = options,
.allocator = allocator,
};
}
@ -1158,6 +1157,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 {
var table = Table{
.associative = RefTable.make(self.allocator, .{}),
@ -1178,11 +1184,24 @@ pub const RuntimeEnv = struct {
});
}
pub fn print(self: *RuntimeEnv, buffer: []const coral.io.Byte) void {
self.options.print(buffer);
}
pub fn raise(self: *RuntimeEnv, error_value: RuntimeError, message: []const coral.io.Byte) RuntimeError {
self.error_handler.invoke(.{
.message = message,
.frames = self.frames.values,
});
self.options.print_error(message);
if (!self.frames.is_empty()) {
self.options.print_error("stack trace:");
var remaining_frames = self.frames.values.len;
while (remaining_frames != 0) {
remaining_frames -= 1;
self.options.print_error(self.frames.values[remaining_frames].name);
}
}
return error_value;
}
@ -1254,6 +1273,7 @@ pub const RuntimeRef = opaque {
float: Float,
fixed: Fixed,
symbol: [*:0]const coral.io.Byte,
syscall: *const Syscall,
string: struct {
ptr: [*]coral.io.Byte,
@ -1323,6 +1343,11 @@ pub const RuntimeRef = opaque {
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 => |other_string| coral.io.equals(self_string.unpack(), other_string.unpack()),
else => false,
@ -1344,7 +1369,8 @@ pub const RuntimeRef = opaque {
.true => 1231,
.float => |float| @bitCast(float),
.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()),
.dynamic => |dynamic| @intFromPtr(dynamic.typeinfo()) ^ @intFromPtr(dynamic.userdata().ptr),
};
@ -1364,12 +1390,15 @@ pub const RuntimeRef = opaque {
.float => |float| float != 0,
.fixed => |fixed| fixed != 0,
.symbol => true,
.syscall => true,
.string => |string| string.len != 0,
.dynamic => true,
};
}
};
pub const Syscall = fn (env: *RuntimeEnv) RuntimeError!?*RuntimeRef;
pub const Typeinfo = struct {
name: []const coral.io.Byte,
size: usize,
@ -1396,14 +1425,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 {
const field_symbol = try env.new_symbol(field);
@ -1428,18 +1449,20 @@ pub fn get_key(env: *RuntimeEnv, indexable: *RuntimeRef, key: []const coral.io.B
return env.get(indexable, key_string);
}
pub fn new_caller(env: *RuntimeEnv, value: Caller) RuntimeError!*RuntimeRef {
const Callable = struct {
fn call(method: Typeinfo.Method) RuntimeError!?*RuntimeRef {
coral.debug.assert(method.userdata.len == @sizeOf(Caller));
fn syscall_import(env: *RuntimeEnv) RuntimeError!?*RuntimeRef {
const arg = try env.expect(try env.acquire_arg(0));
return @as(*Caller, @ptrCast(@alignCast(method.userdata))).invoke(method.env);
}
};
defer env.discard(arg);
return env.new_dynamic(coral.io.bytes_of(&value).ptr, &.{
.name = "<native>",
.size = @sizeOf(Caller),
.call = Callable.call,
});
return env.import(file.Path.from(&.{try env.unbox_string(arg)}));
}
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,13 @@ pub const Expression = union (enum) {
nil_literal,
true_literal,
false_literal,
import,
print,
number_literal: []const coral.io.Byte,
string_literal: []const coral.io.Byte,
symbol_literal: []const coral.io.Byte,
table_literal: TableLiteral,
grouped_expression: *Expression,
get_system: []const coral.io.Byte,
local_get: []const coral.io.Byte,
local_set: []const coral.io.Byte,
@ -351,10 +352,27 @@ fn parse_factor(self: *Self) ParseError!Expression {
break: parse .{.local_get = identifier};
},
.system_identifier => |system_identifier| {
.builtin => |builtin| {
self.tokenizer.skip_newlines();
coral.debug.assert(builtin.len != 0);
break: parse .{.get_system = system_identifier};
switch (builtin[0]) {
'i' => {
if (coral.io.ends_with(builtin, "mport")) {
break: parse .import;
}
},
'p' => {
if (coral.io.ends_with(builtin, "rint")) {
break: parse .print;
}
},
else => {},
}
return self.report("unknown builtin");
},
kayomn marked this conversation as resolved
Review

Should discerning the specific builtin type be the job of the AST itself? I feel like it should be the job of the compiler during AST parsing.

Should discerning the specific builtin type be the job of the AST itself? I feel like it should be the job of the compiler during AST parsing.
.symbol_brace_left => {

View File

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

View File

@ -10,52 +10,6 @@ const heap = @import("./heap.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 {
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();
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");
};
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{};
manifest.load(&script_env, file_access) catch return;
manifest.load(&script_env) catch return;
const window = create: {
const pos = ext.SDL_WINDOWPOS_CENTERED;