const debug = @import("./debug.zig"); const io = @import("./io.zig"); const math = @import("./math.zig"); 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; } switch (@typeInfo(Decimal)) { .Int => |int| { var has_sign = switch (utf8[0]) { '-', '+', ' ' => true, else => false, }; var result = @as(Decimal, 0); for (@intFromBool(has_sign) .. utf8.len) |index| { const radix = 10; const code = utf8[index]; switch (code) { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { result = try math.checked_add( try math.checked_mul(result, radix), try math.checked_sub(code, '0')); }, else => { if (self.delimiter.len == 0 or !io.equals(self.delimiter, utf8[index ..])) { return null; } }, } } switch (int.signedness) { .signed => { return result * @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1); }, .unsigned => { if (has_sign and utf8[0] == '-') { return null; } return result; }, } }, .Float => { var has_sign = switch (utf8[0]) { '-', '+', ' ' => true, else => false, }; // "-" if (has_sign and utf8.len == 1) { return null; } const sign_offset = @intFromBool(has_sign); var has_decimal = utf8[sign_offset] == '.'; // "-." if (has_decimal and (utf8.len == 2)) { return null; } var result = @as(Decimal, 0); var factor = @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1); for (utf8[sign_offset + @intFromBool(has_decimal) .. utf8.len]) |code| { switch (code) { '.' => { if (has_decimal) { return null; } has_decimal = true; }, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { if (has_decimal) { factor /= 10.0; } result = ((result * 10.0) + @as(Decimal, @floatFromInt(code - '0'))); }, else => return null, } } return result * factor; }, 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"; }