From 0965779e8353f495b753f2059a4aaf66ff026927 Mon Sep 17 00:00:00 2001 From: kayomn Date: Sun, 29 Oct 2023 21:48:21 +0000 Subject: [PATCH] Rewrite Ona Script implementation --- .vscode/settings.json | 21 +- debug/app.ona | 6 +- source/coral/io.zig | 26 +- source/coral/list.zig | 10 + source/coral/utf8.zig | 86 +- source/ona/heap.zig | 112 ++- source/ona/kym.zig | 1502 +++++++---------------------------- source/ona/kym/Chunk.zig | 570 +++++++++++++ source/ona/kym/Compiler.zig | 370 +++++++++ source/ona/kym/Expr.zig | 620 +++++++++++++++ source/ona/kym/Stmt.zig | 229 ++++++ source/ona/kym/Table.zig | 122 +++ source/ona/kym/ast.zig | 796 ------------------- source/ona/kym/tokens.zig | 58 +- source/ona/kym/tree.zig | 148 ++++ 15 files changed, 2525 insertions(+), 2151 deletions(-) create mode 100644 source/ona/kym/Chunk.zig create mode 100644 source/ona/kym/Compiler.zig create mode 100644 source/ona/kym/Expr.zig create mode 100644 source/ona/kym/Stmt.zig create mode 100644 source/ona/kym/Table.zig delete mode 100644 source/ona/kym/ast.zig create mode 100644 source/ona/kym/tree.zig diff --git a/.vscode/settings.json b/.vscode/settings.json index 13fdcff..0fc961c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,14 @@ { - "files.insertFinalNewline": true, - "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "files.trimTrailingWhitespace": true, + "zig.initialSetupDone": true, - "[zig]": { - "editor.formatOnSave": false, - "files.eol": "\n", - "editor.minimap.maxColumn": 120, - "editor.detectIndentation": false, - "editor.insertSpaces": false, - "editor.rulers": [120], - } + "[zig]": { + "editor.formatOnSave": false, + "files.eol": "\n", + "editor.minimap.maxColumn": 120, + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.rulers": [120], + } } diff --git a/debug/app.ona b/debug/app.ona index 5a20c03..f05118d 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,15 +1,15 @@ -var printer = lambda (pfx): +let printer = lambda (): return lambda (msg): @print("This is a func call") @print(msg) end end -let pr = printer("This is a func call") +let pr = printer() var i = 0 -pr("") +pr("test") while i < 5: pr("hello, world") diff --git a/source/coral/io.zig b/source/coral/io.zig index ed90db8..6e21d88 100644 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -217,12 +217,12 @@ pub fn all_equals(target: []const Byte, match: Byte) bool { return true; } -pub fn allocate_copy(allocator: Allocator, source: []const Byte) AllocationError![]Byte { - const allocation = try allocator.actions.reallocate(allocator.context, @returnAddress(), null, source.len); +pub fn allocate_copy(comptime Element: type, allocator: Allocator, source: []const Element) AllocationError![]Element { + const allocation = try allocator.actions.reallocate(allocator.context, @returnAddress(), null, @sizeOf(Element) * source.len); - copy(allocation, source); + copy(allocation, bytes_of(source)); - return allocation; + return @as([*]Element, @ptrCast(@alignCast(allocation.ptr)))[0 .. source.len]; } pub fn allocate_many(allocator: Allocator, count: usize, value: anytype) AllocationError![]@TypeOf(value) { @@ -332,24 +332,6 @@ pub fn djb2_hash(comptime int: std.builtin.Type.Int, target: []const Byte) math. return hash_code; } -pub fn ends_with(target: []const Byte, match: []const Byte) bool { - if (target.len < match.len) { - return false; - } - - { - var index = @as(usize, 0); - - while (index < match.len) : (index += 1) { - if (target[target.len - (1 + index)] != match[match.len - (1 + index)]) { - return false; - } - } - } - - return true; -} - pub fn find_first(haystack: []const Byte, needle: Byte) ?usize { for (0 .. haystack.len) |i| { if (haystack[i] == needle) { diff --git a/source/coral/list.zig b/source/coral/list.zig index 56797d8..a9f48ea 100644 --- a/source/coral/list.zig +++ b/source/coral/list.zig @@ -16,6 +16,16 @@ pub fn Stack(comptime Value: type) type { self.values = self.values[0 .. 0]; } + pub fn drop(self: *Self, count: usize) bool { + if (math.checked_sub(self.values, count)) |updated_count| { + self.values = self.values[0 .. updated_count]; + + return true; + } + + return false; + } + pub fn free(self: *Self) void { if (self.capacity == 0) { return; diff --git a/source/coral/utf8.zig b/source/coral/utf8.zig index c1e7843..dee3f8b 100644 --- a/source/coral/utf8.zig +++ b/source/coral/utf8.zig @@ -8,7 +8,7 @@ pub const DecimalFormat = struct { delimiter: []const io.Byte, positive_prefix: enum {none, plus, space}, - const default = DecimalFormat{ + pub const default = DecimalFormat{ .delimiter = "", .positive_prefix = .none, }; @@ -113,7 +113,7 @@ pub const DecimalFormat = struct { } } - pub fn print(self: DecimalFormat, writer: io.Writer, value: anytype) PrintError!void { + pub fn print(self: DecimalFormat, writer: io.Writer, value: anytype) ?usize { if (value == 0) { return print_string(writer, switch (self.positive_prefix) { .none => "0", @@ -134,8 +134,8 @@ pub const DecimalFormat = struct { var decomposable_value = value; while (decomposable_value != 0) : (buffer_start -= 1) { - buffer[buffer_start] = @intCast((decomposable_value % radix) + '0'); - decomposable_value = (decomposable_value / radix); + buffer[buffer_start] = @intCast(@mod(decomposable_value, radix) + '0'); + decomposable_value = @divTrunc(decomposable_value, radix); } } @@ -149,10 +149,33 @@ pub const DecimalFormat = struct { } } - try print_string(writer, buffer[buffer_start ..]); + return print_string(writer, buffer[buffer_start ..]); }, - else => unformattableMessage(ValueType), + .Float => |float| { + var length = @as(usize, 0); + + if (value < 0) { + length += print_string(writer, "-") orelse return null; + } + + const Float = @TypeOf(value); + + const Int = math.Int(.{ + .bits = float.bits, + .signedness = .unsigned, + }); + + const integer = @as(Int, @intFromFloat(value)); + + length += self.print(writer, integer) orelse return null; + length += print_string(writer, ".") orelse return null; + length += self.print(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100))) orelse return null; + + return length; + }, + + else => @compileError(unformattableMessage(ValueType)), } } }; @@ -168,7 +191,7 @@ pub const HexadecimalFormat = struct { .casing = .lower, }; - pub fn print(self: HexadecimalFormat, writer: io.Writer, value: anytype) PrintError!void { + pub fn print(self: HexadecimalFormat, writer: io.Writer, value: anytype) ?usize { // TODO: Implement. _ = self; _ = writer; @@ -176,19 +199,32 @@ pub const HexadecimalFormat = struct { } }; -pub const PrintError = error { - PrintFailed, - PrintIncomplete, -}; +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); -pub fn print_string(writer: io.Writer, utf8: []const io.Byte) PrintError!void { - if ((writer.invoke(utf8) orelse return error.PrintFailed) != utf8.len) { - return error.PrintIncomplete; + debug.assert(formatted_len != null); + + const allocation = try allocator.reallocate(null, formatted_len.?); + + errdefer allocator.deallocate(allocation); + + { + var fixed_buffer = io.FixedBuffer{.bytes = allocation}; + + debug.assert(print_formatted(fixed_buffer.as_writer(), format, args) == formatted_len); } + + return allocation; } -pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments: anytype) PrintError!void { - switch (@typeInfo(@TypeOf(arguments))) { +pub fn print_string(writer: io.Writer, utf8: []const u8) ?usize { + return writer.invoke(utf8); +} + +pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) ?usize { + var length = @as(usize, 0); + + switch (@typeInfo(@TypeOf(args))) { .Struct => |arguments_struct| { comptime var arg_index = 0; comptime var head = 0; @@ -204,8 +240,7 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments switch (format[tail]) { '{' => { - try print_string(writer, format[head .. (tail - 1)]); - + length += print_string(writer, format[head .. (tail - 1)]) orelse return null; tail += 1; head = tail; }, @@ -215,8 +250,7 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments @compileError("all format specifiers must be named when using a named struct"); } - try print_string(writer, arguments[arg_index]); - + length += print_string(writer, args[arg_index]) orelse return null; arg_index += 1; tail += 1; head = tail; @@ -227,8 +261,7 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments @compileError("format specifiers cannot be named when using a tuple struct"); } - try print_string(writer, format[head .. (tail - 1)]); - + length += print_string(writer, format[head .. (tail - 1)]) orelse return null; head = tail; tail += 1; @@ -244,21 +277,24 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments debug.assert(tail < format.len); } - try print_value(writer, @field(arguments, format[head .. tail])); - + length += print_value(writer, @field(args, format[head .. tail])) orelse return null; tail += 1; head = tail; } } } } + + length += print_string(writer, format[head .. ]) orelse return null; }, else => @compileError("`arguments` must be a struct type"), } + + return length; } -noinline fn print_value(writer: io.Writer, value: anytype) PrintError!void { +noinline fn print_value(writer: io.Writer, value: anytype) ?usize { const Value = @TypeOf(value); return switch (@typeInfo(Value)) { diff --git a/source/ona/heap.zig b/source/ona/heap.zig index 8f5d785..7154e43 100644 --- a/source/ona/heap.zig +++ b/source/ona/heap.zig @@ -61,88 +61,86 @@ const AllocationNode = struct { const Context = struct { head: ?*AllocationNode = null, - fn deallocate(self: *Context, allocation: []u8) void { - switch (builtin.mode) { - .Debug, .ReleaseSafe => { - const panic_message = "incorrect allocation address for deallocating"; - var current_node = self.head orelse @panic(panic_message); + fn deallocate(_: *Context, allocation: []coral.io.Byte) void { + // switch (builtin.mode) { + // .Debug, .ReleaseSafe => { + // const panic_message = "incorrect allocation address for deallocating"; + // var current_node = self.head orelse @panic(panic_message); - if (current_node.owns_userdata(allocation)) { - self.head = current_node.next; + // if (current_node.owns_userdata(allocation)) { + // self.head = current_node.next; - return current_node.dealloc(); - } + // return current_node.dealloc(); + // } - while (true) { - const next_node = current_node.next orelse @panic(panic_message); + // while (true) { + // const next_node = current_node.next orelse @panic(panic_message); - if (next_node.owns_userdata(allocation)) { - current_node.next = next_node.next; + // if (next_node.owns_userdata(allocation)) { + // current_node.next = next_node.next; - return next_node.dealloc(); - } + // return next_node.dealloc(); + // } - current_node = next_node; - } - }, + // current_node = next_node; + // } + // }, - .ReleaseFast, .ReleaseSmall => { + // .ReleaseFast, .ReleaseSmall => { ext.SDL_free(allocation.ptr); - }, - } + // }, + // } } - fn reallocate(self: *Context, return_address: usize, existing_allocation: ?[]u8, size: usize) coral.io.AllocationError![]u8 { - switch (builtin.mode) { - .Debug, .ReleaseSafe => { - if (existing_allocation) |allocation| { - const panic_message = "incorrect allocation address for reallocating"; - var current_node = self.head orelse @panic(panic_message); + fn reallocate(_: *Context, _: usize, existing_allocation: ?[]coral.io.Byte, size: usize) coral.io.AllocationError![]coral.io.Byte { + // switch (builtin.mode) { + // .Debug, .ReleaseSafe => { + // if (existing_allocation) |allocation| { + // const panic_message = "incorrect allocation address for reallocating"; + // var current_node = self.head orelse @panic(panic_message); - if (current_node.owns_userdata(allocation)) { - const node = current_node.realloc(size, return_address); + // if (current_node.owns_userdata(allocation)) { + // const node = current_node.realloc(size, return_address); - self.head = node; + // self.head = node; - return node.userdata(); - } + // return node.userdata(); + // } - while (true) { - const next_node = current_node.next orelse @panic(panic_message); + // while (true) { + // const next_node = current_node.next orelse @panic(panic_message); - if (next_node.owns_userdata(allocation)) { - const node = next_node.realloc(size, return_address); + // if (next_node.owns_userdata(allocation)) { + // const node = next_node.realloc(size, return_address); - current_node.next = node; + // current_node.next = node; - return node.userdata(); - } + // return node.userdata(); + // } - current_node = next_node; - } - } else { - const node = AllocationNode.alloc(size, return_address); + // current_node = next_node; + // } + // } else { + // const node = AllocationNode.alloc(size, return_address); - if (self.head) |head| { - node.next = head; - } + // if (self.head) |head| { + // node.next = head; + // } - self.head = node; + // self.head = node; - return node.userdata(); - } - }, + // return node.userdata(); + // } + // }, - .ReleaseFast, .ReleaseSmall => { + // .ReleaseFast, .ReleaseSmall => { if (existing_allocation) |allocation | { - return @as([*]u8, ext.SDL_realloc(allocation.ptr, size) orelse { - return error.OutOfMemory; - })[0 .. size]; + return @as([*]coral.io.Byte, @ptrCast(ext.SDL_realloc(allocation.ptr, size) orelse return error.OutOfMemory))[0 .. size]; } - return @as([*]u8, ext.SDL_malloc(size) orelse return error.OutOfMemory)[0 .. size]; - }, - } + return @as([*]u8, @ptrCast(ext.SDL_malloc(size) orelse return error.OutOfMemory))[0 .. size]; + // }, + // } } }; diff --git a/source/ona/kym.zig b/source/ona/kym.zig index a310dc0..d56c80f 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -1,9 +1,19 @@ -const ast = @import("./kym/ast.zig"); +const Chunk = @import("./kym/Chunk.zig"); + +const Table = @import("./kym/Table.zig"); + +const app = @import("./app.zig"); + +const builtin = @import("builtin"); const coral = @import("coral"); const file = @import("./file.zig"); +const tokens = @import("./kym/tokens.zig"); + +const tree = @import("./kym/tree.zig"); + pub const Frame = struct { name: []const coral.io.Byte = "", arg_count: u8, @@ -30,997 +40,13 @@ pub const RuntimeEnv = struct { options: Options, interned_symbols: SymbolSet, allocator: coral.io.Allocator, - locals: RefList, + locals: LocalList, frames: FrameStack, - const Chunk = struct { - env: *RuntimeEnv, - name: []coral.io.Byte, - arity: u8, - opcodes: OpcodeList, - constants: ConstList, - - const Builtin = enum { - import, - print, - vec2, - vec3, - }; - - const Constant = union (enum) { - fixed: Fixed, - float: Float, - string: []const coral.io.Byte, - symbol: []const coral.io.Byte, - chunk: Chunk, - }; - - const Opcode = union (enum) { - pop, - push_nil, - push_true, - push_false, - push_const: u16, - push_local: u8, - push_table: u32, - push_builtin: Builtin, - local_set: u8, - object_get, - object_set, - object_call: u8, - - not, - neg, - - add, - sub, - mul, - div, - - eql, - cgt, - clt, - cge, - cle, - - jt: u32, - jf: u32, - }; - - const OpcodeList = coral.list.Stack(Opcode); - - const CompilationUnit = struct { - locals_buffer: [255]Local = [_]Local{.{}} ** 255, - locals_count: u8 = 0, - - const ResolvedLocal = struct { - index: u8, - is_readonly: bool = false, - }; - - const Local = struct { - identifier: []const coral.io.Byte = "", - is_readonly: bool = false, - }; - - fn compile_expression(self: *CompilationUnit, chunk: *Chunk, expression: ast.Expression) RuntimeError!void { - const number_format = coral.utf8.DecimalFormat{ - .delimiter = "_", - .positive_prefix = .none, - }; - - switch (expression) { - .nil_literal => try chunk.opcodes.push_one(.push_nil), - .true_literal => try chunk.opcodes.push_one(.push_true), - .false_literal => try chunk.opcodes.push_one(.push_false), - - .number_literal => |literal| { - for (literal) |codepoint| { - if (codepoint == '.') { - return chunk.opcodes.push_one(.{ - .push_const = try chunk.declare_constant(.{ - .float = number_format.parse(literal, Float) orelse unreachable, - }), - }); - } - } - - try chunk.opcodes.push_one(.{ - .push_const = try chunk.declare_constant(.{ - .fixed = number_format.parse(literal, Fixed) orelse unreachable, - }), - }); - }, - - .string_literal => |literal| { - try chunk.opcodes.push_one(.{ - .push_const = try chunk.declare_constant(.{.string = literal}), - }); - }, - - .symbol_literal => |literal| { - try chunk.opcodes.push_one(.{ - .push_const = try chunk.declare_constant(.{.symbol = literal}), - }); - }, - - .table_literal => |literal| { - if (literal.values.len > coral.math.max_int(@typeInfo(u32).Int)) { - return error.OutOfMemory; - } - - for (literal.values) |field| { - try self.compile_expression(chunk, field.value_expression); - try self.compile_expression(chunk, field.key_expression); - } - - try chunk.opcodes.push_one(.{.push_table = @intCast(literal.values.len)}); - }, - - .lambda_literal => |literal| { - if (literal.argument_identifiers.values.len > coral.math.max_int(@typeInfo(u8).Int)) { - return error.OutOfMemory; - } - - var lambda_chunk = try Chunk.make( - chunk.env, - "", - @intCast(literal.argument_identifiers.values.len), - ); - - errdefer lambda_chunk.free(); - - try lambda_chunk.compile(literal.block_statements.values, literal.argument_identifiers.values); - - try chunk.opcodes.push_one(.{ - .push_const = try chunk.declare_constant(.{ - .chunk = lambda_chunk, - }), - }); - }, - - .binary_operation => |operation| { - try self.compile_expression(chunk, operation.lhs_expression.*); - try self.compile_expression(chunk, operation.rhs_expression.*); - - try chunk.opcodes.push_one(switch (operation.operator) { - .addition => .add, - .subtraction => .sub, - .multiplication => .mul, - .divsion => .div, - .greater_equals_comparison => .cge, - .greater_than_comparison => .cgt, - .equals_comparison => .eql, - .less_than_comparison => .clt, - .less_equals_comparison => .cle, - }); - }, - - .unary_operation => |operation| { - try self.compile_expression(chunk, operation.expression.*); - - try chunk.opcodes.push_one(switch (operation.operator) { - .boolean_negation => .not, - .numeric_negation => .neg, - }); - }, - - .invoke => |invoke| { - if (invoke.argument_expressions.values.len > coral.math.max_int(@typeInfo(u8).Int)) { - return chunk.env.raise(error.BadSyntax, "lambdas may contain a maximum of 255 arguments"); - } - - for (invoke.argument_expressions.values) |argument_expression| { - try self.compile_expression(chunk, argument_expression); - } - - try self.compile_expression(chunk, invoke.object_expression.*); - try chunk.opcodes.push_one(.{.object_call = @intCast(invoke.argument_expressions.values.len)}); - }, - - .grouped_expression => |grouped_expression| { - try self.compile_expression(chunk, grouped_expression.*); - }, - - .builtin => |builtin| { - coral.debug.assert(builtin.len != 0); - - switch (builtin[0]) { - 'i' => { - if (coral.io.ends_with(builtin, "mport")) { - return chunk.opcodes.push_one(.{.push_builtin = .import}); - } - }, - - 'p' => { - if (coral.io.ends_with(builtin, "rint")) { - return chunk.opcodes.push_one(.{.push_builtin = .print}); - } - }, - - 'v' => { - if (coral.io.ends_with(builtin, "ec2")) { - return chunk.opcodes.push_one(.{.push_builtin = .vec2}); - } - - if (coral.io.ends_with(builtin, "ec3")) { - return chunk.opcodes.push_one(.{.push_builtin = .vec3}); - } - }, - - else => {}, - } - - return chunk.env.raise(error.BadSyntax, "unknown builtin"); - }, - - .local_get => |local_get| { - if (self.resolve_local(local_get.identifier)) |local| { - return chunk.opcodes.push_one(.{.push_local = local.index}); - } - - return chunk.env.raise(error.OutOfMemory, "undefined local"); - }, - - .local_set => |local_set| { - try self.compile_expression(chunk, local_set.value_expression.*); - - const resolved_local = self.resolve_local(local_set.identifier) orelse { - return chunk.env.raise(error.BadSyntax, "undefined local"); - }; - - if (resolved_local.is_readonly) { - return chunk.env.raise(error.BadSyntax, "cannot set a read-only declaration"); - } - - try chunk.opcodes.push_one(.{.local_set = resolved_local.index}); - }, - - .field_get => |field_get| { - try self.compile_expression(chunk, field_get.object_expression.*); - - try chunk.opcodes.push_one(.{ - .push_const = try chunk.declare_constant(.{.symbol = field_get.identifier}), - }); - - try chunk.opcodes.push_one(.object_get); - }, - - .field_set => |field_set| { - try self.compile_expression(chunk, field_set.object_expression.*); - - try chunk.opcodes.push_one(.{ - .push_const = try chunk.declare_constant(.{.symbol = field_set.identifier}), - }); - - try self.compile_expression(chunk, field_set.value_expression.*); - try chunk.opcodes.push_one(.object_set); - }, - - .subscript_get => |subscript_get| { - try self.compile_expression(chunk, subscript_get.object_expression.*); - try self.compile_expression(chunk, subscript_get.subscript_expression.*); - try chunk.opcodes.push_one(.object_get); - }, - - .subscript_set => |subscript_set| { - try self.compile_expression(chunk, subscript_set.object_expression.*); - try self.compile_expression(chunk, subscript_set.subscript_expression.*); - try self.compile_expression(chunk, subscript_set.value_expression.*); - try chunk.opcodes.push_one(.object_set); - }, - } - } - - fn compile_statement(self: *CompilationUnit, chunk: *Chunk, statement: ast.Statement) RuntimeError!void { - switch (statement) { - .@"return" => |@"return"| { - if (@"return") |expression| { - try self.compile_expression(chunk, expression); - } else { - try chunk.opcodes.push_one(.push_nil); - } - }, - - .declare => |declare| { - if (self.resolve_local(declare.identifier) != null) { - return chunk.env.raise(error.BadSyntax, "declaration shadows existing one"); - } - - try self.compile_expression(chunk, declare.assigned_expression); - - switch (declare.storage) { - .@"var" => { - if (!self.declare_local(.{ - .identifier = declare.identifier, - .is_readonly = false, - })) { - return chunk.env.raise(error.BadSyntax, "too many locals"); - } - }, - - .let => { - // TODO: investigate constant folding. - if (!self.declare_local(.{ - .identifier = declare.identifier, - .is_readonly = false, - })) { - return chunk.env.raise(error.BadSyntax, "too many locals"); - } - }, - } - }, - - .block => |block| { - for (block.values) |block_statement| { - try self.compile_statement(chunk, block_statement); - } - }, - - .@"while" => |@"while"| { - try self.compile_expression(chunk, @"while".condition_expression); - try chunk.opcodes.push_one(.{.jf = 0}); - - const origin_index = @as(u32, @intCast(chunk.opcodes.values.len - 1)); - - for (@"while".block_statements.values) |block_statement| { - try self.compile_statement(chunk, block_statement); - } - - chunk.opcodes.values[origin_index].jf = @intCast(chunk.opcodes.values.len - 1); - - try self.compile_expression(chunk, @"while".condition_expression); - try chunk.opcodes.push_one(.{.jt = origin_index}); - }, - - .@"if" => |@"if"| { - try self.compile_expression(chunk, @"if".condition_expression); - try chunk.opcodes.push_one(.{.jf = 0}); - - const origin_index = @as(u32, @intCast(chunk.opcodes.values.len - 1)); - - for (@"if".block_statements.values) |block_statement| { - try self.compile_statement(chunk, block_statement); - } - - chunk.opcodes.values[origin_index].jf = @intCast(chunk.opcodes.values.len - 1); - - if (@"if".else_statement) |else_statement| { - try self.compile_statement(chunk, else_statement.*); - } - }, - - .expression => |expression| { - try self.compile_expression(chunk, expression); - - if (expression == .invoke) { - try chunk.opcodes.push_one(.pop); - } - }, - } - } - - fn declare_local(self: *CompilationUnit, local: Local) bool { - if (self.locals_count == self.locals_buffer.len) { - return false; - } - - self.locals_buffer[self.locals_count] = local; - self.locals_count += 1; - - return true; - } - - fn resolve_local(self: *CompilationUnit, local_identifier: []const coral.io.Byte) ?ResolvedLocal { - if (self.locals_count == 0) { - return null; - } - - var index = @as(u8, self.locals_count - 1); - - while (true) : (index -= 1) { - const local = &self.locals_buffer[index]; - - if (coral.io.are_equal(local_identifier, local.identifier)) { - return .{ - .index = index, - .is_readonly = local.is_readonly, - }; - } - - if (index == 0) { - return null; - } - } - } - }; - - fn compile(self: *Chunk, statements: []const ast.Statement, args: []const []const coral.io.Byte) RuntimeError!void { - var unit = CompilationUnit{}; - var has_returned = false; - - for (args) |arg| { - if (!unit.declare_local(.{ - .is_readonly = true, - .identifier = arg, - })) { - return self.env.raise(error.BadSyntax, "too many arguments"); - } - } - - for (statements) |statement| { - try unit.compile_statement(self, statement); - - if (statement == .@"return") { - has_returned = true; - } - } - - if (!has_returned) { - try self.opcodes.push_one(.push_nil); - } - } - - fn declare_constant(self: *Chunk, constant: Constant) RuntimeError!u16 { - if (self.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than 65,535 constants"); - } - - const constant_object = try switch (constant) { - .fixed => |fixed| self.env.new_fixed(fixed), - .float => |float| self.env.new_float(float), - .string => |string| self.env.new_string(string), - .symbol => |symbol| self.env.new_symbol(symbol), - - .chunk => |chunk| self.env.new_dynamic(coral.io.bytes_of(&chunk).ptr, &.{ - .size = @sizeOf(Chunk), - .name = "lambda", - .destruct = typeinfo_destruct, - .call = typeinfo_call, - }), - }; - - errdefer self.env.discard(constant_object); - try self.constants.push_one(constant_object); - - return @intCast(self.constants.values.len - 1); - } - - fn execute(self: *Chunk, frame: Frame) RuntimeError!?*RuntimeRef { - var opcode_cursor = @as(u32, 0); - - while (opcode_cursor < self.opcodes.values.len) : (opcode_cursor += 1) { - switch (self.opcodes.values[opcode_cursor]) { - .pop => { - if (try self.pop_local()) |ref| { - self.env.discard(ref); - } - }, - - .push_nil => try self.env.locals.push_one(null), - .push_true => try self.env.locals.push_one(try self.env.new_boolean(true)), - .push_false => try self.env.locals.push_one(try self.env.new_boolean(false)), - - .push_const => |push_const| { - if (push_const >= self.constants.values.len) { - return self.env.raise(error.IllegalState, "invalid const"); - } - - try self.env.locals.push_one(try self.env.acquire(self.constants.values[push_const])); - }, - - .push_local => |push_local| { - if (push_local >= self.env.locals.values.len) { - return self.env.raise(error.IllegalState, "invalid local"); - } - - if (self.env.locals.values[frame.locals_top + push_local]) |local| { - try self.env.locals.push_one(try self.env.acquire(local)); - } else { - try self.env.locals.push_one(null); - } - }, - - .push_table => |push_table| { - const table = try self.env.new_table(); - - errdefer self.env.discard(table); - - { - const dynamic = table.object().payload.dynamic; - const userdata = dynamic.userdata(); - const table_set = dynamic.typeinfo().set; - var popped = @as(usize, 0); - - while (popped < push_table) : (popped += 1) { - const index = try self.expect(try self.pop_local()); - - defer self.env.discard(index); - - if (try self.pop_local()) |value| { - defer self.env.discard(value); - - try table_set(self.env, userdata, index, value); - } - } - } - - try self.env.locals.push_one(table); - }, - - .push_builtin => |push_builtin| { - const builtin_syscall = try self.env.new_syscall(switch (push_builtin) { - .import => syscall_import, - .print => syscall_print, - .vec2 => syscall_vec2, - .vec3 => syscall_vec3, - }); - - errdefer self.env.discard(builtin_syscall); - - try self.env.locals.push_one(builtin_syscall); - }, - - .local_set => |local_set| { - const local = &self.env.locals.values[frame.locals_top + local_set]; - - if (local.*) |previous_local| { - self.env.discard(previous_local); - } - - local.* = try self.pop_local(); - }, - - .object_get => { - const index = try self.expect(try self.pop_local()); - - defer self.env.discard(index); - - const indexable = try self.expect(try self.pop_local()); - - defer self.env.discard(indexable); - - const value = try self.env.get(indexable, index); - - errdefer { - if (value) |ref| { - self.env.discard(ref); - } - } - - try self.env.locals.push_one(value); - }, - - .object_set => { - const value = try self.pop_local(); - - defer { - if (value) |ref| { - self.env.discard(ref); - } - } - - const index = try self.pop_local() orelse { - return self.env.raise(error.TypeMismatch, "nil is not a valid index"); - }; - - defer self.env.discard(index); - - const indexable = try self.pop_local() orelse { - return self.env.raise(error.TypeMismatch, "nil is not a valid indexable"); - }; - - defer self.env.discard(indexable); - - try self.env.set(indexable, index, value); - }, - - .object_call => |object_call| { - const result = call: { - const callable = try self.expect(try self.pop_local()); - - defer self.env.discard(callable); - - const call_frame = try self.env.push_frame(object_call); - - defer self.env.pop_frame(); - - break: call try switch (callable.object().payload) { - .syscall => |syscall| syscall(self.env, call_frame), - .dynamic => |dynamic| dynamic.typeinfo().call(self.env, dynamic.userdata(), call_frame), - else => self.env.raise(error.TypeMismatch, "object is not callable"), - }; - }; - - errdefer { - if (result) |ref| { - self.env.discard(ref); - } - } - - try self.env.locals.push_one(result); - }, - - .not => { - if (try self.pop_local()) |value| { - defer self.env.discard(value); - - try self.env.locals.push_one(try self.env.new_boolean(!value.is_truthy())); - } else { - try self.env.locals.push_one(try self.env.new_boolean(true)); - } - }, - - .neg => { - const value = try self.expect(try self.pop_local()); - - defer self.env.discard(value); - - try self.env.locals.push_one(try switch (value.object().payload) { - .fixed => |fixed| self.env.new_fixed(-fixed), - .float => |float| self.env.new_float(-float), - else => self.env.raise(error.TypeMismatch, "object is not negatable"), - }); - }, - - .add => { - const rhs = try self.expect(try self.pop_local()); - - defer self.env.discard(rhs); - - const lhs = try self.expect(try self.pop_local()); - - defer self.env.discard(lhs); - - try self.env.locals.push_one(try switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| add: { - if (coral.math.checked_add(lhs_fixed, rhs_fixed)) |fixed| { - break: add self.env.new_fixed(fixed); - } - - break: add self.env.new_float(@as(Float, - @floatFromInt(lhs_fixed)) + - @as(Float, @floatFromInt(rhs_fixed))); - }, - - .float => |rhs_float| self.env.new_float( - @as(Float, @floatFromInt(lhs_fixed)) + rhs_float), - - else => self.env.raise(error.TypeMismatch, "right-hand object is not addable"), - }, - - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| self.env.new_float(lhs_float + rhs_float), - - .fixed => |rhs_fixed| self.env.new_float( - lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - - else => self.env.raise(error.TypeMismatch, "right-hand object is not addable"), - }, - - else => self.env.raise(error.TypeMismatch, "left-hand object is not addable"), - }); - }, - - .sub => { - const rhs = try self.expect(try self.pop_local()); - - defer self.env.discard(rhs); - - const lhs = try self.expect(try self.pop_local()); - - defer self.env.discard(lhs); - - try self.env.locals.push_one(try switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| sub: { - if (coral.math.checked_sub(lhs_fixed, rhs_fixed)) |fixed| { - break: sub self.env.new_fixed(fixed); - } - - break: sub self.env.new_float(@as(Float, - @floatFromInt(lhs_fixed)) - - @as(Float, @floatFromInt(rhs_fixed))); - }, - - .float => |rhs_float| self.env.new_float( - @as(Float, @floatFromInt(lhs_fixed)) - rhs_float), - - else => self.env.raise(error.TypeMismatch, "right-hand object is not subtractable"), - }, - - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| self.env.new_float(lhs_float - rhs_float), - - .fixed => |rhs_fixed| self.env.new_float( - lhs_float - @as(Float, @floatFromInt(rhs_fixed))), - - else => self.env.raise(error.TypeMismatch, "right-hand object is not subtractable"), - }, - - else => self.env.raise(error.TypeMismatch, "left-hand object is not subtractable"), - }); - }, - - .mul => { - const rhs = try self.expect(try self.pop_local()); - - defer self.env.discard(rhs); - - const lhs = try self.expect(try self.pop_local()); - - defer self.env.discard(lhs); - - try self.env.locals.push_one(try switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| mul: { - if (coral.math.checked_mul(lhs_fixed, rhs_fixed)) |fixed| { - break: mul self.env.new_fixed(fixed); - } - - break: mul self.env.new_float(@as(Float, - @floatFromInt(lhs_fixed)) * - @as(Float, @floatFromInt(rhs_fixed))); - }, - - .float => |rhs_float| self.env.new_float( - @as(Float, @floatFromInt(lhs_fixed)) * rhs_float), - - else => self.env.raise(error.TypeMismatch, "right-hand object is not multiplicable"), - }, - - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| self.env.new_float(lhs_float * rhs_float), - - .fixed => |rhs_fixed| self.env.new_float( - lhs_float * @as(Float, @floatFromInt(rhs_fixed))), - - else => self.env.raise(error.TypeMismatch, "right-hand object is not multiplicable"), - }, - - else => self.env.raise(error.TypeMismatch, "left-hand object is not multiplicable"), - }); - }, - - .div => { - const rhs = try self.expect(try self.pop_local()); - - defer self.env.discard(rhs); - - const lhs = try self.expect(try self.pop_local()); - - defer self.env.discard(lhs); - - try self.env.locals.push_one(try switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| self.env.new_float(@as(Float, - @floatFromInt(lhs_fixed)) / - @as(Float, @floatFromInt(rhs_fixed))), - - .float => |rhs_float| self.env.new_float( - @as(Float, @floatFromInt(lhs_fixed)) / rhs_float), - - else => self.env.raise(error.TypeMismatch, "right-hand object is not divisible"), - }, - - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| self.env.new_float(lhs_float / rhs_float), - - .fixed => |rhs_fixed| self.env.new_float( - lhs_float / @as(Float, @floatFromInt(rhs_fixed))), - - else => self.env.raise(error.TypeMismatch, "right-hand object is not divisible"), - }, - - else => self.env.raise(error.TypeMismatch, "left-hand object is not divisible"), - }); - }, - - .eql => { - if (try self.pop_local()) |rhs| { - self.env.discard(rhs); - - if (try self.pop_local()) |lhs| { - self.env.discard(lhs); - - try self.env.locals.push_one(try self.env.new_boolean(lhs.equals(rhs))); - } else { - try self.env.locals.push_one(try self.env.new_boolean(false)); - } - } else { - if (try self.pop_local()) |lhs| { - self.env.discard(lhs); - - try self.env.locals.push_one(try self.env.new_boolean(false)); - } else { - try self.env.locals.push_one(try self.env.new_boolean(true)); - } - } - }, - - .cgt => { - const rhs = try self.expect(try self.pop_local()); - - defer self.env.discard(rhs); - - const lhs = try self.expect(try self.pop_local()); - - defer self.env.discard(lhs); - - try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| lhs_fixed > rhs_fixed, - .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) > rhs_float, - else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), - }, - - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| lhs_float > rhs_float, - .fixed => |rhs_fixed| lhs_float > @as(Float, @floatFromInt(rhs_fixed)), - else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), - }, - - else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"), - })); - }, - - .clt => { - const rhs = try self.expect(try self.pop_local()); - - defer self.env.discard(rhs); - - const lhs = try self.expect(try self.pop_local()); - - defer self.env.discard(lhs); - - try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| lhs_fixed < rhs_fixed, - .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) < rhs_float, - else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), - }, - - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| lhs_float < rhs_float, - .fixed => |rhs_fixed| lhs_float < @as(Float, @floatFromInt(rhs_fixed)), - else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), - }, - - else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"), - })); - }, - - .cge => { - const rhs = try self.expect(try self.pop_local()); - - defer self.env.discard(rhs); - - const lhs = try self.expect(try self.pop_local()); - - defer self.env.discard(lhs); - - try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| lhs_fixed >= rhs_fixed, - .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) >= rhs_float, - else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), - }, - - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| lhs_float >= rhs_float, - .fixed => |rhs_fixed| lhs_float >= @as(Float, @floatFromInt(rhs_fixed)), - else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), - }, - - else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"), - })); - }, - - .cle => { - const rhs = try self.expect(try self.pop_local()); - - defer self.env.discard(rhs); - - const lhs = try self.expect(try self.pop_local()); - - defer self.env.discard(lhs); - - try self.env.locals.push_one(try self.env.new_boolean(switch (lhs.object().payload) { - .fixed => |lhs_fixed| switch (rhs.object().payload) { - .fixed => |rhs_fixed| lhs_fixed <= rhs_fixed, - .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) <= rhs_float, - else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), - }, - - .float => |lhs_float| switch (rhs.object().payload) { - .float => |rhs_float| lhs_float <= rhs_float, - .fixed => |rhs_fixed| lhs_float <= @as(Float, @floatFromInt(rhs_fixed)), - else => return self.env.raise(error.TypeMismatch, "right-hand object is not comparable"), - }, - - else => return self.env.raise(error.TypeMismatch, "left-hand object is not comparable"), - })); - }, - - .jf => |jf| { - const condition = (try self.pop_local()) orelse { - opcode_cursor = jf; - - continue; - }; - - self.env.discard(condition); - - if (!condition.is_truthy()) { - opcode_cursor = jf; - } - }, - - .jt => |jt| { - const condition = (try self.pop_local()) orelse continue; - - self.env.discard(condition); - - if (condition.is_truthy()) { - opcode_cursor = jt; - } - }, - } - } - - return self.pop_local(); - } - - fn expect(self: *Chunk, value: ?*RuntimeRef) RuntimeError!*RuntimeRef { - return value orelse self.env.raise(error.TypeMismatch, "nil reference"); - } - - fn free(self: *Chunk) void { - while (self.constants.pop()) |constant| { - self.env.discard(constant); - } - - self.constants.free(); - self.opcodes.free(); - self.env.allocator.deallocate(self.name); - } - - fn make(env: *RuntimeEnv, name: []const coral.io.Byte, arity: u8) coral.io.AllocationError!Chunk { - return .{ - .name = try coral.io.allocate_copy(env.allocator, name), - .opcodes = OpcodeList.make(env.allocator), - .constants = ConstList.make(env.allocator), - .arity = arity, - .env = env, - }; - } - - fn pop_local(self: *Chunk) RuntimeError!?*RuntimeRef { - return self.env.locals.pop() orelse self.env.raise(error.IllegalState, "stack underflow"); - } - - fn typeinfo_call(env: *RuntimeEnv, userdata: []coral.io.Byte, frame: Frame) RuntimeError!?*RuntimeRef { - const chunk = @as(*Chunk, @ptrCast(@alignCast(userdata))); - - if (frame.arg_count < chunk.arity) { - return env.raise(error.BadOperation, "expected more arguments"); - } - - return chunk.execute(frame); - } - - fn typeinfo_destruct(env: *RuntimeEnv, userdata: []coral.io.Byte) void { - _ = env; - - @as(*Chunk, @ptrCast(@alignCast(userdata))).free(); - } - }; - - const ConstList = coral.list.Stack(*RuntimeRef); - const FrameStack = coral.list.Stack(Frame); + const LocalList = coral.list.Stack(?*RuntimeRef); + pub const Options = struct { import_access: file.Access = .null, print: ?*const Printer = null, @@ -1029,142 +55,72 @@ pub const RuntimeEnv = struct { pub const Printer = fn (buffer: []const coral.io.Byte) void; - const RefList = coral.list.Stack(?*RuntimeRef); - - const RefTable = coral.map.Table(*RuntimeRef, *RuntimeRef, struct { - pub const hash = RuntimeRef.hash; - - pub const equals = RuntimeRef.equals; - }); - const SymbolSet = coral.map.StringTable([:0]coral.io.Byte); - const Table = struct { - associative: RefTable, - contiguous: RefList, - - fn typeinfo_destruct(env: *RuntimeEnv, userdata: []coral.io.Byte) void { - const table = @as(*Table, @ptrCast(@alignCast(userdata))); - - { - var field_iterable = table.associative.as_iterable(); - - while (field_iterable.next()) |entry| { - env.discard(entry.key); - env.discard(entry.value); - } - } - - table.associative.free(); - - while (table.contiguous.pop()) |value| { - if (value) |ref| { - env.discard(ref); - } - } - - table.contiguous.free(); - } - - fn typeinfo_get(env: *RuntimeEnv, userdata: []coral.io.Byte, index: *const RuntimeRef) RuntimeError!?*RuntimeRef { - const table = @as(*Table, @ptrCast(@alignCast(userdata))); - const acquired_index = try env.acquire(index); - - defer env.discard(acquired_index); - - if (acquired_index.is_fixed()) |fixed| { - if (fixed < 0) { - // TODO: Negative indexing. - unreachable; - } - - if (fixed < table.contiguous.values.len) { - return env.acquire(table.contiguous.values[@intCast(fixed)] orelse return null); - } - } - - if (table.associative.lookup(acquired_index)) |value_ref| { - return env.acquire(value_ref); - } - - return null; - } - - fn typeinfo_set(env: *RuntimeEnv, userdata: []coral.io.Byte, index: *const RuntimeRef, value: ?*const RuntimeRef) RuntimeError!void { - const table = @as(*Table, @ptrCast(@alignCast(userdata))); - const acquired_index = try env.acquire(index); - - errdefer env.discard(acquired_index); - - if (acquired_index.is_fixed()) |fixed| { - if (fixed < 0) { - // TODO: Negative indexing. - unreachable; - } - - if (fixed < table.contiguous.values.len) { - const maybe_replacing = &table.contiguous.values[@intCast(fixed)]; - - if (maybe_replacing.*) |replacing| { - env.discard(replacing); + pub fn add(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!*RuntimeRef { + return switch (lhs.object().payload) { + .fixed => |lhs_fixed| switch (rhs.object().payload) { + .fixed => |rhs_fixed| add: { + if (coral.math.checked_add(lhs_fixed, rhs_fixed)) |fixed| { + break: add self.new_fixed(fixed); } - maybe_replacing.* = if (value) |ref| try env.acquire(ref) else null; + break: add self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + @as(Float, @floatFromInt(rhs_fixed))); + }, - return; - } - } + .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float), + else => self.raise(error.TypeMismatch, "right-hand object is not addable"), + }, - const acquired_value = try env.acquire(value orelse { - if (table.associative.remove(acquired_index)) |removed| { - env.discard(removed.key); - env.discard(removed.value); - } + .float => |lhs_float| switch (rhs.object().payload) { + .float => |rhs_float| self.new_float(lhs_float + rhs_float), + .fixed => |rhs_fixed| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), + else => self.raise(error.TypeMismatch, "right-hand object is not addable"), + }, - return; - }); - - errdefer env.discard(acquired_value); - - if (try table.associative.replace(acquired_index, acquired_value)) |replaced| { - env.discard(replaced.key); - env.discard(replaced.value); - } - } - }; - - pub fn acquire(self: *RuntimeEnv, value: *const RuntimeRef) RuntimeError!*RuntimeRef { - const object = value.object(); - - object.ref_count = coral.math.checked_add(object.ref_count, 1) orelse { - return self.raise(error.IllegalState, "reference overflow"); + else => self.raise(error.TypeMismatch, "left-hand object is not addable"), }; - - return @ptrCast(object); } - pub fn call(self: *RuntimeEnv, callable: *RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef { + pub fn call(self: *RuntimeEnv, callable: *const RuntimeRef, args: []const *RuntimeRef) RuntimeError!?*RuntimeRef { // TODO: Handle errors. for (args) |arg| { try self.locals.push_one(try self.acquire(arg)); } - try self.push_frame(args.len); + const frame = try self.push_frame(args.len); defer self.pop_frame(); + return self.call_frame(callable, frame); + } + + pub fn call_frame(self: *RuntimeEnv, callable: *const RuntimeRef, frame: Frame) RuntimeError!?*RuntimeRef { return switch (callable.object().payload) { - .syscall => |syscall| syscall(self.env), - - .dynamic => |dynamic| dynamic.typeinfo.call(.{ - .userdata = dynamic.userdata(), - .env = self, - }), - + .syscall => |syscall| syscall(self, frame), + .dynamic => |dynamic| dynamic.typeinfo().call(self, dynamic.userdata(), frame), else => self.raise(error.TypeMismatch, "object is not callable"), }; } + pub fn compare(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!Float { + return switch (lhs.object().payload) { + .fixed => |lhs_fixed| switch (rhs.object().payload) { + .fixed => |rhs_fixed| @as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed)), + .float => |rhs_float| @as(Float, @floatFromInt(lhs_fixed)) - rhs_float, + else => return self.raise(error.TypeMismatch, "right-hand object is not comparable"), + }, + + .float => |lhs_float| switch (rhs.object().payload) { + .float => |rhs_float| lhs_float - rhs_float, + .fixed => |rhs_fixed| lhs_float - @as(Float, @floatFromInt(rhs_fixed)), + else => return self.raise(error.TypeMismatch, "right-hand object is not comparable"), + }, + + else => return self.raise(error.TypeMismatch, "left-hand object is not comparable"), + }; + } + pub fn discard(self: *RuntimeEnv, value: *RuntimeRef) void { var object = value.object(); @@ -1194,32 +150,26 @@ pub const RuntimeEnv = struct { } } - 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"); + pub fn div(self: *RuntimeEnv, lhs: *const RuntimeRef, rhs: *const RuntimeRef) RuntimeError!*RuntimeRef { + return switch (lhs.object().payload) { + .fixed => |lhs_fixed| switch (rhs.object().payload) { + .fixed => |rhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))), + .float => |rhs_float| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float), + else => self.raise(error.TypeMismatch, "right-hand object is not divisible"), + }, + + .float => |lhs_float| switch (rhs.object().payload) { + .float => |rhs_float| self.new_float(lhs_float / rhs_float), + .fixed => |rhs_fixed| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))), + else => self.raise(error.TypeMismatch, "right-hand object is not divisible"), + }, + + else => self.raise(error.TypeMismatch, "left-hand object is not divisible"), }; + } - defer self.allocator.deallocate(file_data); - - const file_name = file_path.to_string() orelse "