diff --git a/source/coral/utf8.zig b/source/coral/utf8.zig index 78697d8..28e3ad7 100644 --- a/source/coral/utf8.zig +++ b/source/coral/utf8.zig @@ -1,3 +1,5 @@ +const debug = @import("./debug.zig"); + const io = @import("./io.zig"); const math = @import("./math.zig"); @@ -6,6 +8,11 @@ pub const DecimalFormat = struct { delimiter: []const io.Byte, positive_prefix: enum {none, plus, space}, + const default = DecimalFormat{ + .delimiter = "", + .positive_prefix = .none, + }; + pub fn parse(self: DecimalFormat, utf8: []const io.Byte, comptime Decimal: type) ?Decimal { if (utf8.len == 0) { return null; @@ -104,5 +111,170 @@ pub const DecimalFormat = struct { else => @compileError("`" ++ @typeName(Decimal) ++ "` cannot be formatted as a decimal string"), } } + + pub fn print(self: DecimalFormat, writer: io.Writer, value: anytype) PrintError!void { + if (value == 0) { + return print_string(writer, switch (self.positive_prefix) { + .none => "0", + .plus => "+0", + .space => " 0", + }); + } + + const ValueType = @TypeOf(value); + + switch (@typeInfo(ValueType)) { + .Int => |int| { + const radix = 10; + var buffer = [_]u8{0} ** (1 + math.max(int.bits, 1)); + var buffer_start = buffer.len - 1; + + { + var decomposable_value = value; + + while (decomposable_value != 0) : (buffer_start -= 1) { + buffer[buffer_start] = @intCast((decomposable_value % radix) + '0'); + decomposable_value = (decomposable_value / radix); + } + } + + if (int.signedness == .unsigned and value < 0) { + buffer[buffer_start] = '-'; + } else { + switch (self.positive_prefix) { + .none => buffer_start += 1, + .plus => buffer[buffer_start] = '+', + .space => buffer[buffer_start] = ' ', + } + } + + try print_string(writer, buffer[buffer_start ..]); + }, + + else => unformattableMessage(ValueType), + } + } }; +pub const HexadecimalFormat = struct { + delimiter: []const u8 = "", + positive_prefix: enum {none, plus, space} = .none, + casing: enum {lower, upper} = .lower, + + const default = HexadecimalFormat{ + .delimiter = "", + .positive_prefix = .none, + .casing = .lower, + }; + + pub fn print(self: HexadecimalFormat, writer: io.Writer, value: anytype) PrintError!void { + // TODO: Implement. + _ = self; + _ = writer; + _ = value; + + unreachable; + } +}; + +pub const PrintError = error { + PrintFailed, + PrintIncomplete, +}; + +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; + } +} + +pub fn print_formatted(writer: io.Writer, comptime format: []const u8, arguments: anytype) PrintError!void { + switch (@typeInfo(@TypeOf(arguments))) { + .Struct => |arguments_struct| { + comptime var arg_index = 0; + comptime var head = 0; + comptime var tail = 0; + + inline while (tail < format.len) : (tail += 1) { + if (format[tail] == '{') { + if (tail > format.len) { + @compileError("expected an idenifier after opening `{`"); + } + + tail += 1; + + switch (format[tail]) { + '{' => { + try print_string(writer, format[head .. (tail - 1)]); + + tail += 1; + head = tail; + }, + + '}' => { + if (!arguments_struct.is_tuple) { + @compileError("all format specifiers must be named when using a named struct"); + } + + try print_string(writer, arguments[arg_index]); + + arg_index += 1; + tail += 1; + head = tail; + }, + + else => { + if (arguments_struct.is_tuple) { + @compileError("format specifiers cannot be named when using a tuple struct"); + } + + try print_string(writer, format[head .. (tail - 1)]); + + head = tail; + tail += 1; + + if (tail >= format.len) { + @compileError("expected closing `}` or another `{` after opening `{`"); + } + + debug.assert(tail < format.len); + + inline while (format[tail] != '}') { + tail += 1; + + debug.assert(tail < format.len); + } + + try print_value(writer, @field(arguments, format[head .. tail])); + + tail += 1; + head = tail; + } + } + } + } + }, + + else => @compileError("`arguments` must be a struct type"), + } +} + +noinline fn print_value(writer: io.Writer, value: anytype) PrintError!void { + const Value = @TypeOf(value); + + return switch (@typeInfo(Value)) { + .Int => DecimalFormat.default.print(writer, value), + .Float => DecimalFormat.default.print(writer, value), + + .Pointer => |pointer| switch (pointer.size) { + .One, .Many, .C => HexadecimalFormat.default.print(writer, @intFromPtr(value)), + .Slice => if (pointer.child == u8) print_string(writer, value) else @compileError(unformattableMessage(Value)), + }, + + else => @compileError(unformattableMessage(Value)), + }; +} + +fn unformattableMessage(comptime Value: type) []const u8 { + return "type `" ++ @typeName(Value) ++ "` is not formattable with this formatter"; +} diff --git a/source/ona/kym.zig b/source/ona/kym.zig index 353fb6a..997a895 100644 --- a/source/ona/kym.zig +++ b/source/ona/kym.zig @@ -456,9 +456,12 @@ pub const RuntimeEnv = struct { } pub fn raise(self: *RuntimeEnv, runtime_error: RuntimeError, error_message: []const u8) RuntimeError { - if (self.err_writer.invoke(error_message) == null) { - return error.SystemFailure; - } + // TODO: Print stack trace from state. + coral.utf8.print_formatted(self.err_writer, "{name}@{line}: {message}", .{ + .name = "???", + .line = @as(u64, 0), + .message = error_message, + }) catch return error.SystemFailure; return runtime_error; }