From 429f7a52ad428c4f0d267308703b7fc48834b1e1 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 13 Aug 2023 01:14:08 +0100 Subject: [PATCH 1/3] Replace system objects with builtins and syscalls --- debug/app.ona | 6 +- source/ona/app.zig | 4 +- source/ona/kym.zig | 165 ++++++++++++++++++++++---------------- source/ona/kym/Ast.zig | 24 +++++- source/ona/kym/tokens.zig | 93 +++++++++------------ source/ona/ona.zig | 66 ++------------- 6 files changed, 163 insertions(+), 195 deletions(-) diff --git a/debug/app.ona b/debug/app.ona index dad79ec..8a50cd9 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -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 diff --git a/source/ona/app.zig b/source/ona/app.zig index 296b82f..fc3aa33 100644 --- a/source/ona/app.zig +++ b/source/ona/app.zig @@ -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); diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 179eaa3..7ec2b26 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -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, + }; + + 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 = "", - .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; } diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index f286ab7..39ae3f8 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -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"); }, .symbol_brace_left => { diff --git a/source/ona/kym/tokens.zig b/source/ona/kym/tokens.zig index 2604bf8..826b6ab 100755 --- a/source/ona/kym/tokens.zig +++ b/source/ona/kym/tokens.zig @@ -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")) { - self.token = .keyword_const; + 'c' => { + if (coral.io.ends_with(identifier, "onst")) { + self.token = .keyword_const; - return; + return; + } }, - 'n' => if (coral.io.ends_with(identifier, "il")) { - self.token = .keyword_nil; + 'n' => { + if (coral.io.ends_with(identifier, "il")) { + self.token = .keyword_nil; - return; + return; + } }, - 'f' => if (coral.io.ends_with(identifier, "alse")) { - self.token = .keyword_false; + 'f' => { + if (coral.io.ends_with(identifier, "alse")) { + self.token = .keyword_false; - return; + return; + } }, - 't' => if (coral.io.ends_with(identifier, "rue")) { - self.token = .keyword_true; + 't' => { + if (coral.io.ends_with(identifier, "rue")) { + self.token = .keyword_true; - return; + return; + } }, - 'r' => if (coral.io.ends_with(identifier, "eturn")) { - self.token = .keyword_return; + 'r' => { + if (coral.io.ends_with(identifier, "eturn")) { + self.token = .keyword_return; - return; + return; + } }, - 's' => if (coral.io.ends_with(identifier, "elf")) { - self.token = .keyword_self; + 's' => { + if (coral.io.ends_with(identifier, "elf")) { + self.token = .keyword_self; - return; + 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; + 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 => {}, + while (cursor < self.source.len) switch (self.source[cursor]) { + '0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1, + else => break, }; - self.token = .symbol_at; + self.token = if (begin == cursor) .{.unknown = '@'} else .{.builtin = self.source[begin .. cursor]}; return; }, diff --git a/source/ona/ona.zig b/source/ona/ona.zig index ee81b03..ef84c1f 100644 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -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; From 9a0a594345d7d5cb0cdc33797f4032875f9c386d Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 13 Aug 2023 01:27:13 +0100 Subject: [PATCH 2/3] Make Kym env options optional --- source/ona/file.zig | 5 +++++ source/ona/kym.zig | 22 +++++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/source/ona/file.zig b/source/ona/file.zig index 1296aec..b05ae92 100644 --- a/source/ona/file.zig +++ b/source/ona/file.zig @@ -3,10 +3,13 @@ const coral = @import("coral"); const ext = @import("./ext.zig"); pub const Access = union (enum) { + null, sandboxed_path: *const Path, pub fn open_readable(self: Access, readable_path: Path) ?*Readable { switch (self) { + .null => return null, + .sandboxed_path => |sandboxed_path| { 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 { switch (self) { + .null => return null, + .sandboxed_path => |sandboxed_path| { const path_string = sandboxed_path.joined(path).to_string() orelse return null; const rw_ops = ext.SDL_RWFromFile(path_string, "rb") orelse return null; diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 7ec2b26..241d95b 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -802,9 +802,9 @@ pub const RuntimeEnv = struct { }); pub const Options = struct { - import_access: file.Access, - print: *const Printer, - print_error: *const Printer, + import_access: file.Access = .null, + print: ?*const Printer = null, + print_error: ?*const Printer = null, }; pub const Printer = fn (buffer: []const coral.io.Byte) void; @@ -1185,21 +1185,29 @@ pub const RuntimeEnv = struct { } pub fn print(self: *RuntimeEnv, buffer: []const coral.io.Byte) void { - self.options.print(buffer); + 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 { - self.options.print_error(message); + self.print_error(message); if (!self.frames.is_empty()) { - self.options.print_error("stack trace:"); + self.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); + self.print_error(self.frames.values[remaining_frames].name); } } From 8e42aa57fd40fe639863cbe13623ba160c32f177 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 13 Aug 2023 01:34:32 +0100 Subject: [PATCH 3/3] Move builtin decoding responsibility to compiler --- source/ona/kym.zig | 16 +++++++++++----- source/ona/kym/Ast.zig | 22 ++-------------------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 241d95b..920ca6f 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -164,12 +164,18 @@ pub const RuntimeEnv = struct { try self.compile_expression(chunk, grouped_expression.*); }, - .import => { - try chunk.opcodes.push_one(.{.push_builtin = .import}); - }, + .builtin => |builtin| { + coral.debug.assert(builtin.len != 0); - .print => { - try chunk.opcodes.push_one(.{.push_builtin = .print}); + 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| { diff --git a/source/ona/kym/Ast.zig b/source/ona/kym/Ast.zig index 39ae3f8..346f432 100755 --- a/source/ona/kym/Ast.zig +++ b/source/ona/kym/Ast.zig @@ -13,8 +13,7 @@ pub const Expression = union (enum) { nil_literal, true_literal, false_literal, - import, - print, + builtin: []const coral.io.Byte, number_literal: []const coral.io.Byte, string_literal: []const coral.io.Byte, symbol_literal: []const coral.io.Byte, @@ -354,25 +353,8 @@ fn parse_factor(self: *Self) ParseError!Expression { .builtin => |builtin| { self.tokenizer.skip_newlines(); - coral.debug.assert(builtin.len != 0); - 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"); + break: parse .{.builtin = builtin}; }, .symbol_brace_left => {