diff options
| author | Loris Cro <kappaloris@gmail.com> | 2023-06-18 09:06:40 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-06-18 09:06:40 +0200 |
| commit | 216ef10dc471e4db60a30208be178d6c59efeaaf (patch) | |
| tree | 8c239dab283ae9cb3b7fe099bae240bcc53f894e /lib/std/fmt.zig | |
| parent | 0fc1d396495c1ab482197021dedac8bea3f9401c (diff) | |
| parent | 729a051e9e38674233190aea23c0ac8c134f2d67 (diff) | |
| download | zig-216ef10dc471e4db60a30208be178d6c59efeaaf.tar.gz zig-216ef10dc471e4db60a30208be178d6c59efeaaf.zip | |
Merge branch 'master' into autodoc-searchkey
Diffstat (limited to 'lib/std/fmt.zig')
| -rw-r--r-- | lib/std/fmt.zig | 442 |
1 files changed, 246 insertions, 196 deletions
diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index cf791df1a6..c9d8e611ca 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -14,15 +14,15 @@ const expectFmt = std.testing.expectFmt; pub const default_max_depth = 3; pub const Alignment = enum { - Left, - Center, - Right, + left, + center, + right, }; pub const FormatOptions = struct { precision: ?usize = null, width: ?usize = null, - alignment: Alignment = .Right, + alignment: Alignment = .right, fill: u8 = ' ', }; @@ -148,7 +148,7 @@ pub fn format( comptime assert(fmt[i] == '}'); i += 1; - const placeholder = comptime parsePlaceholder(fmt[fmt_begin..fmt_end].*); + const placeholder = comptime Placeholder.parse(fmt[fmt_begin..fmt_end].*); const arg_pos = comptime switch (placeholder.arg) { .none => null, .number => |pos| pos, @@ -205,102 +205,102 @@ pub fn format( } } -fn parsePlaceholder(comptime str: anytype) Placeholder { - comptime var parser = Parser{ .buf = &str }; +fn cacheString(str: anytype) []const u8 { + return &str; +} - // Parse the positional argument number - const arg = comptime parser.specifier() catch |err| - @compileError(@errorName(err)); +pub const Placeholder = struct { + specifier_arg: []const u8, + fill: u8, + alignment: Alignment, + arg: Specifier, + width: Specifier, + precision: Specifier, - // Parse the format specifier - const specifier_arg = comptime parser.until(':'); + pub fn parse(comptime str: anytype) Placeholder { + comptime var parser = Parser{ .buf = &str }; - // Skip the colon, if present - if (comptime parser.char()) |ch| { - if (ch != ':') { - @compileError("expected : or }, found '" ++ [1]u8{ch} ++ "'"); - } - } + // Parse the positional argument number + const arg = comptime parser.specifier() catch |err| + @compileError(@errorName(err)); - // Parse the fill character - // The fill parameter requires the alignment parameter to be specified - // too - const fill = comptime if (parser.peek(1)) |ch| - switch (ch) { - '<', '^', '>' => parser.char().?, - else => ' ', - } - else - ' '; + // Parse the format specifier + const specifier_arg = comptime parser.until(':'); - // Parse the alignment parameter - const alignment: Alignment = comptime if (parser.peek(0)) |ch| init: { - switch (ch) { - '<', '^', '>' => _ = parser.char(), - else => {}, + // Skip the colon, if present + if (comptime parser.char()) |ch| { + if (ch != ':') { + @compileError("expected : or }, found '" ++ [1]u8{ch} ++ "'"); + } } - break :init switch (ch) { - '<' => .Left, - '^' => .Center, - else => .Right, - }; - } else .Right; - // Parse the width parameter - const width = comptime parser.specifier() catch |err| - @compileError(@errorName(err)); + // Parse the fill character + // The fill parameter requires the alignment parameter to be specified + // too + const fill = comptime if (parser.peek(1)) |ch| + switch (ch) { + '<', '^', '>' => parser.char().?, + else => ' ', + } + else + ' '; - // Skip the dot, if present - if (comptime parser.char()) |ch| { - if (ch != '.') { - @compileError("expected . or }, found '" ++ [1]u8{ch} ++ "'"); - } - } + // Parse the alignment parameter + const alignment: Alignment = comptime if (parser.peek(0)) |ch| init: { + switch (ch) { + '<', '^', '>' => _ = parser.char(), + else => {}, + } + break :init switch (ch) { + '<' => .left, + '^' => .center, + else => .right, + }; + } else .right; - // Parse the precision parameter - const precision = comptime parser.specifier() catch |err| - @compileError(@errorName(err)); + // Parse the width parameter + const width = comptime parser.specifier() catch |err| + @compileError(@errorName(err)); - if (comptime parser.char()) |ch| { - @compileError("extraneous trailing character '" ++ [1]u8{ch} ++ "'"); - } + // Skip the dot, if present + if (comptime parser.char()) |ch| { + if (ch != '.') { + @compileError("expected . or }, found '" ++ [1]u8{ch} ++ "'"); + } + } - return Placeholder{ - .specifier_arg = cacheString(specifier_arg[0..specifier_arg.len].*), - .fill = fill, - .alignment = alignment, - .arg = arg, - .width = width, - .precision = precision, - }; -} + // Parse the precision parameter + const precision = comptime parser.specifier() catch |err| + @compileError(@errorName(err)); -fn cacheString(str: anytype) []const u8 { - return &str; -} + if (comptime parser.char()) |ch| { + @compileError("extraneous trailing character '" ++ [1]u8{ch} ++ "'"); + } -const Placeholder = struct { - specifier_arg: []const u8, - fill: u8, - alignment: Alignment, - arg: Specifier, - width: Specifier, - precision: Specifier, + return Placeholder{ + .specifier_arg = cacheString(specifier_arg[0..specifier_arg.len].*), + .fill = fill, + .alignment = alignment, + .arg = arg, + .width = width, + .precision = precision, + }; + } }; -const Specifier = union(enum) { +pub const Specifier = union(enum) { none, number: usize, named: []const u8, }; -const Parser = struct { +pub const Parser = struct { buf: []const u8, pos: usize = 0, // Returns a decimal number or null if the current character is not a // digit - fn number(self: *@This()) ?usize { + pub fn number(self: *@This()) ?usize { var r: ?usize = null; while (self.pos < self.buf.len) : (self.pos += 1) { @@ -319,7 +319,7 @@ const Parser = struct { // Returns a substring of the input starting from the current position // and ending where `ch` is found or until the end if not found - fn until(self: *@This(), ch: u8) []const u8 { + pub fn until(self: *@This(), ch: u8) []const u8 { const start = self.pos; if (start >= self.buf.len) @@ -332,7 +332,7 @@ const Parser = struct { } // Returns one character, if available - fn char(self: *@This()) ?u8 { + pub fn char(self: *@This()) ?u8 { if (self.pos < self.buf.len) { const ch = self.buf[self.pos]; self.pos += 1; @@ -341,7 +341,7 @@ const Parser = struct { return null; } - fn maybe(self: *@This(), val: u8) bool { + pub fn maybe(self: *@This(), val: u8) bool { if (self.pos < self.buf.len and self.buf[self.pos] == val) { self.pos += 1; return true; @@ -351,7 +351,7 @@ const Parser = struct { // Returns a decimal number or null if the current character is not a // digit - fn specifier(self: *@This()) !Specifier { + pub fn specifier(self: *@This()) !Specifier { if (self.maybe('[')) { const arg_name = self.until(']'); @@ -367,24 +367,24 @@ const Parser = struct { } // Returns the n-th next character or null if that's past the end - fn peek(self: *@This(), n: usize) ?u8 { + pub fn peek(self: *@This(), n: usize) ?u8 { return if (self.pos + n < self.buf.len) self.buf[self.pos + n] else null; } }; -const ArgSetType = u32; +pub const ArgSetType = u32; const max_format_args = @typeInfo(ArgSetType).Int.bits; -const ArgState = struct { +pub const ArgState = struct { next_arg: usize = 0, used_args: ArgSetType = 0, args_len: usize, - fn hasUnusedArgs(self: *@This()) bool { + pub fn hasUnusedArgs(self: *@This()) bool { return @popCount(self.used_args) != self.args_len; } - fn nextArg(self: *@This(), arg_index: ?usize) ?usize { + pub fn nextArg(self: *@This(), arg_index: ?usize) ?usize { const next_index = arg_index orelse init: { const arg = self.next_arg; self.next_arg += 1; @@ -430,7 +430,7 @@ pub fn formatAddress(value: anytype, options: FormatOptions, writer: anytype) @T // This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948 const ANY = "any"; -fn defaultSpec(comptime T: type) [:0]const u8 { +pub fn defaultSpec(comptime T: type) [:0]const u8 { switch (@typeInfo(T)) { .Array => |_| return ANY, .Pointer => |ptr_info| switch (ptr_info.size) { @@ -727,12 +727,6 @@ fn formatValue( options: FormatOptions, writer: anytype, ) !void { - if (comptime std.mem.eql(u8, fmt, "B")) { - @compileError("specifier 'B' has been deprecated, wrap your argument in std.fmt.fmtIntSizeDec instead"); - } else if (comptime std.mem.eql(u8, fmt, "Bi")) { - @compileError("specifier 'Bi' has been deprecated, wrap your argument in std.fmt.fmtIntSizeBin instead"); - } - const T = @TypeOf(value); switch (@typeInfo(T)) { .Float, .ComptimeFloat => return formatFloatValue(value, fmt, options, writer), @@ -748,7 +742,7 @@ pub fn formatIntValue( options: FormatOptions, writer: anytype, ) !void { - comptime var radix = 10; + comptime var base = 10; comptime var case: Case = .lower; const int_value = if (@TypeOf(value) == comptime_int) blk: { @@ -757,7 +751,7 @@ pub fn formatIntValue( } else value; if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) { - radix = 10; + base = 10; case = .lower; } else if (comptime std.mem.eql(u8, fmt, "c")) { if (@typeInfo(@TypeOf(int_value)).Int.bits <= 8) { @@ -772,22 +766,22 @@ pub fn formatIntValue( @compileError("cannot print integer that is larger than 21 bits as an UTF-8 sequence"); } } else if (comptime std.mem.eql(u8, fmt, "b")) { - radix = 2; + base = 2; case = .lower; } else if (comptime std.mem.eql(u8, fmt, "x")) { - radix = 16; + base = 16; case = .lower; } else if (comptime std.mem.eql(u8, fmt, "X")) { - radix = 16; + base = 16; case = .upper; } else if (comptime std.mem.eql(u8, fmt, "o")) { - radix = 8; + base = 8; case = .lower; } else { invalidFmtError(fmt, value); } - return formatInt(int_value, radix, case, options, writer); + return formatInt(int_value, base, case, options, writer); } fn formatFloatValue( @@ -906,7 +900,7 @@ pub fn fmtSliceEscapeUpper(bytes: []const u8) std.fmt.Formatter(formatSliceEscap return .{ .data = bytes }; } -fn formatSizeImpl(comptime radix: comptime_int) type { +fn formatSizeImpl(comptime base: comptime_int) type { return struct { fn formatSizeImpl( value: u64, @@ -926,13 +920,13 @@ fn formatSizeImpl(comptime radix: comptime_int) type { const mags_iec = " KMGTPEZY"; const log2 = math.log2(value); - const magnitude = switch (radix) { - 1000 => math.min(log2 / comptime math.log2(1000), mags_si.len - 1), - 1024 => math.min(log2 / 10, mags_iec.len - 1), + const magnitude = switch (base) { + 1000 => @min(log2 / comptime math.log2(1000), mags_si.len - 1), + 1024 => @min(log2 / 10, mags_iec.len - 1), else => unreachable, }; - const new_value = lossyCast(f64, value) / math.pow(f64, lossyCast(f64, radix), lossyCast(f64, magnitude)); - const suffix = switch (radix) { + const new_value = lossyCast(f64, value) / math.pow(f64, lossyCast(f64, base), lossyCast(f64, magnitude)); + const suffix = switch (base) { 1000 => mags_si[magnitude], 1024 => mags_iec[magnitude], else => unreachable, @@ -944,7 +938,7 @@ fn formatSizeImpl(comptime radix: comptime_int) type { bufstream.writer().writeAll(if (suffix == ' ') "B" - else switch (radix) { + else switch (base) { 1000 => &[_]u8{ suffix, 'B' }, 1024 => &[_]u8{ suffix, 'i', 'B' }, else => unreachable, @@ -977,12 +971,10 @@ fn checkTextFmt(comptime fmt: []const u8) void { if (fmt.len != 1) @compileError("unsupported format string '" ++ fmt ++ "' when formatting text"); switch (fmt[0]) { + // Example of deprecation: + // '[deprecated_specifier]' => @compileError("specifier '[deprecated_specifier]' has been deprecated, wrap your argument in `std.some_function` instead"), 'x' => @compileError("specifier 'x' has been deprecated, wrap your argument in std.fmt.fmtSliceHexLower instead"), 'X' => @compileError("specifier 'X' has been deprecated, wrap your argument in std.fmt.fmtSliceHexUpper instead"), - 'e' => @compileError("specifier 'e' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeLower instead"), - 'E' => @compileError("specifier 'E' has been deprecated, wrap your argument in std.fmt.fmtSliceEscapeUpper instead"), - 'z' => @compileError("specifier 'z' has been deprecated, wrap your argument in std.zig.fmtId instead"), - 'Z' => @compileError("specifier 'Z' has been deprecated, wrap your argument in std.zig.fmtEscapes instead"), else => {}, } } @@ -1034,18 +1026,18 @@ pub fn formatBuf( return writer.writeAll(buf); switch (options.alignment) { - .Left => { + .left => { try writer.writeAll(buf); try writer.writeByteNTimes(options.fill, padding); }, - .Center => { + .center => { const left_padding = padding / 2; const right_padding = (padding + 1) / 2; try writer.writeByteNTimes(options.fill, left_padding); try writer.writeAll(buf); try writer.writeByteNTimes(options.fill, right_padding); }, - .Right => { + .right => { try writer.writeByteNTimes(options.fill, padding); try writer.writeAll(buf); }, @@ -1111,7 +1103,7 @@ pub fn formatFloatScientific( var printed: usize = 0; if (float_decimal.digits.len > 1) { - const num_digits = math.min(float_decimal.digits.len, precision + 1); + const num_digits = @min(float_decimal.digits.len, precision + 1); try writer.writeAll(float_decimal.digits[1..num_digits]); printed += num_digits - 1; } @@ -1124,7 +1116,7 @@ pub fn formatFloatScientific( try writer.writeAll(float_decimal.digits[0..1]); try writer.writeAll("."); if (float_decimal.digits.len > 1) { - const num_digits = if (@TypeOf(value) == f32) math.min(@as(usize, 9), float_decimal.digits.len) else float_decimal.digits.len; + const num_digits = if (@TypeOf(value) == f32) @min(@as(usize, 9), float_decimal.digits.len) else float_decimal.digits.len; try writer.writeAll(float_decimal.digits[1..num_digits]); } else { @@ -1307,7 +1299,7 @@ pub fn formatFloatDecimal( var num_digits_whole = if (float_decimal.exp > 0) @intCast(usize, float_decimal.exp) else 0; // the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this. - var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len); + var num_digits_whole_no_pad = @min(num_digits_whole, float_decimal.digits.len); if (num_digits_whole > 0) { // We may have to zero pad, for instance 1e4 requires zero padding. @@ -1334,7 +1326,7 @@ pub fn formatFloatDecimal( // Zero-fill until we reach significant digits or run out of precision. if (float_decimal.exp <= 0) { const zero_digit_count = @intCast(usize, -float_decimal.exp); - const zeros_to_print = math.min(zero_digit_count, precision); + const zeros_to_print = @min(zero_digit_count, precision); var i: usize = 0; while (i < zeros_to_print) : (i += 1) { @@ -1365,7 +1357,7 @@ pub fn formatFloatDecimal( var num_digits_whole = if (float_decimal.exp > 0) @intCast(usize, float_decimal.exp) else 0; // the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this. - var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len); + var num_digits_whole_no_pad = @min(num_digits_whole, float_decimal.digits.len); if (num_digits_whole > 0) { // We may have to zero pad, for instance 1e4 requires zero padding. @@ -1418,12 +1410,12 @@ pub fn formatInt( // The type must have the same size as `base` or be wider in order for the // division to work - const min_int_bits = comptime math.max(value_info.bits, 8); + const min_int_bits = comptime @max(value_info.bits, 8); const MinInt = std.meta.Int(.unsigned, min_int_bits); const abs_value = math.absCast(int_value); // The worst case in terms of space needed is base 2, plus 1 for the sign - var buf: [1 + math.max(value_info.bits, 1)]u8 = undefined; + var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined; var a: MinInt = abs_value; var index: usize = buf.len; @@ -1699,7 +1691,7 @@ pub const ParseIntError = error{ /// The result cannot fit in the type specified Overflow, - /// The input was empty or had a byte that was not a digit + /// The input was empty or contained an invalid character InvalidCharacter, }; @@ -1730,21 +1722,21 @@ pub fn Formatter(comptime format_fn: anytype) type { } /// Parses the string `buf` as signed or unsigned representation in the -/// specified radix of an integral value of type `T`. +/// specified base of an integral value of type `T`. /// -/// When `radix` is zero the string prefix is examined to detect the true radix: -/// * A prefix of "0b" implies radix=2, -/// * A prefix of "0o" implies radix=8, -/// * A prefix of "0x" implies radix=16, -/// * Otherwise radix=10 is assumed. +/// When `base` is zero the string prefix is examined to detect the true base: +/// * A prefix of "0b" implies base=2, +/// * A prefix of "0o" implies base=8, +/// * A prefix of "0x" implies base=16, +/// * Otherwise base=10 is assumed. /// /// Ignores '_' character in `buf`. /// See also `parseUnsigned`. -pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) ParseIntError!T { +pub fn parseInt(comptime T: type, buf: []const u8, base: u8) ParseIntError!T { if (buf.len == 0) return error.InvalidCharacter; - if (buf[0] == '+') return parseWithSign(T, buf[1..], radix, .Pos); - if (buf[0] == '-') return parseWithSign(T, buf[1..], radix, .Neg); - return parseWithSign(T, buf, radix, .Pos); + if (buf[0] == '+') return parseWithSign(T, buf[1..], base, .pos); + if (buf[0] == '-') return parseWithSign(T, buf[1..], base, .neg); + return parseWithSign(T, buf, base, .pos); } test "parseInt" { @@ -1766,6 +1758,7 @@ test "parseInt" { try std.testing.expect((try parseInt(u8, "+0", 10)) == 0); // ensure minInt is parsed correctly + try std.testing.expect((try parseInt(i1, "-1", 10)) == math.minInt(i1)); try std.testing.expect((try parseInt(i8, "-128", 10)) == math.minInt(i8)); try std.testing.expect((try parseInt(i43, "-4398046511104", 10)) == math.minInt(i43)); @@ -1777,7 +1770,7 @@ test "parseInt" { try std.testing.expectError(error.InvalidCharacter, parseInt(u32, "-", 10)); try std.testing.expectError(error.InvalidCharacter, parseInt(i32, "-", 10)); - // autodectect the radix + // autodectect the base try std.testing.expect((try parseInt(i32, "111", 0)) == 111); try std.testing.expect((try parseInt(i32, "1_1_1", 0)) == 111); try std.testing.expect((try parseInt(i32, "1_1_1", 0)) == 111); @@ -1804,29 +1797,29 @@ test "parseInt" { fn parseWithSign( comptime T: type, buf: []const u8, - radix: u8, - comptime sign: enum { Pos, Neg }, + base: u8, + comptime sign: enum { pos, neg }, ) ParseIntError!T { if (buf.len == 0) return error.InvalidCharacter; - var buf_radix = radix; + var buf_base = base; var buf_start = buf; - if (radix == 0) { + if (base == 0) { // Treat is as a decimal number by default. - buf_radix = 10; - // Detect the radix by looking at buf prefix. + buf_base = 10; + // Detect the base by looking at buf prefix. if (buf.len > 2 and buf[0] == '0') { switch (std.ascii.toLower(buf[1])) { 'b' => { - buf_radix = 2; + buf_base = 2; buf_start = buf[2..]; }, 'o' => { - buf_radix = 8; + buf_base = 8; buf_start = buf[2..]; }, 'x' => { - buf_radix = 16; + buf_base = 16; buf_start = buf[2..]; }, else => {}, @@ -1835,8 +1828,8 @@ fn parseWithSign( } const add = switch (sign) { - .Pos => math.add, - .Neg => math.sub, + .pos => math.add, + .neg => math.sub, }; var x: T = 0; @@ -1845,28 +1838,36 @@ fn parseWithSign( for (buf_start) |c| { if (c == '_') continue; - const digit = try charToDigit(c, buf_radix); - - if (x != 0) x = try math.mul(T, x, math.cast(T, buf_radix) orelse return error.Overflow); + const digit = try charToDigit(c, buf_base); + + if (x != 0) { + x = try math.mul(T, x, math.cast(T, buf_base) orelse return error.Overflow); + } else if (sign == .neg) { + // The first digit of a negative number. + // Consider parsing "-4" as an i3. + // This should work, but positive 4 overflows i3, so we can't cast the digit to T and subtract. + x = math.cast(T, -@intCast(i8, digit)) orelse return error.Overflow; + continue; + } x = try add(T, x, math.cast(T, digit) orelse return error.Overflow); } return x; } -/// Parses the string `buf` as unsigned representation in the specified radix +/// Parses the string `buf` as unsigned representation in the specified base /// of an integral value of type `T`. /// -/// When `radix` is zero the string prefix is examined to detect the true radix: -/// * A prefix of "0b" implies radix=2, -/// * A prefix of "0o" implies radix=8, -/// * A prefix of "0x" implies radix=16, -/// * Otherwise radix=10 is assumed. +/// When `base` is zero the string prefix is examined to detect the true base: +/// * A prefix of "0b" implies base=2, +/// * A prefix of "0o" implies base=8, +/// * A prefix of "0x" implies base=16, +/// * Otherwise base=10 is assumed. /// /// Ignores '_' character in `buf`. /// See also `parseInt`. -pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) ParseIntError!T { - return parseWithSign(T, buf, radix, .Pos); +pub fn parseUnsigned(comptime T: type, buf: []const u8, base: u8) ParseIntError!T { + return parseWithSign(T, buf, base, .pos); } test "parseUnsigned" { @@ -1889,7 +1890,7 @@ test "parseUnsigned" { try std.testing.expect((try parseUnsigned(u32, "NUMBER", 36)) == 1442151747); - // these numbers should fit even though the radix itself doesn't fit in the destination type + // these numbers should fit even though the base itself doesn't fit in the destination type try std.testing.expect((try parseUnsigned(u1, "0", 10)) == 0); try std.testing.expect((try parseUnsigned(u1, "1", 10)) == 1); try std.testing.expectError(error.Overflow, parseUnsigned(u1, "2", 10)); @@ -1905,14 +1906,64 @@ test "parseUnsigned" { try std.testing.expectError(error.InvalidCharacter, parseUnsigned(u8, "", 10)); } +/// Parses a number like '2G', '2Gi', or '2GiB'. +pub fn parseIntSizeSuffix(buf: []const u8, digit_base: u8) ParseIntError!usize { + var without_B = buf; + if (mem.endsWith(u8, buf, "B")) without_B.len -= 1; + var without_i = without_B; + var magnitude_base: usize = 1000; + if (mem.endsWith(u8, without_B, "i")) { + without_i.len -= 1; + magnitude_base = 1024; + } + if (without_i.len == 0) return error.InvalidCharacter; + const orders_of_magnitude: usize = switch (without_i[without_i.len - 1]) { + 'k', 'K' => 1, + 'M' => 2, + 'G' => 3, + 'T' => 4, + 'P' => 5, + 'E' => 6, + 'Z' => 7, + 'Y' => 8, + 'R' => 9, + 'Q' => 10, + else => 0, + }; + var without_suffix = without_i; + if (orders_of_magnitude > 0) { + without_suffix.len -= 1; + } else if (without_i.len != without_B.len) { + return error.InvalidCharacter; + } + const multiplier = math.powi(usize, magnitude_base, orders_of_magnitude) catch |err| switch (err) { + error.Underflow => unreachable, + error.Overflow => return error.Overflow, + }; + const number = try std.fmt.parseInt(usize, without_suffix, digit_base); + return math.mul(usize, number, multiplier); +} + +test "parseIntSizeSuffix" { + try std.testing.expect(try parseIntSizeSuffix("2", 10) == 2); + try std.testing.expect(try parseIntSizeSuffix("2B", 10) == 2); + try std.testing.expect(try parseIntSizeSuffix("2kB", 10) == 2000); + try std.testing.expect(try parseIntSizeSuffix("2k", 10) == 2000); + try std.testing.expect(try parseIntSizeSuffix("2KiB", 10) == 2048); + try std.testing.expect(try parseIntSizeSuffix("2Ki", 10) == 2048); + try std.testing.expect(try parseIntSizeSuffix("aKiB", 16) == 10240); + try std.testing.expect(parseIntSizeSuffix("", 10) == error.InvalidCharacter); + try std.testing.expect(parseIntSizeSuffix("2iB", 10) == error.InvalidCharacter); +} + pub const parseFloat = @import("fmt/parse_float.zig").parseFloat; pub const ParseFloatError = @import("fmt/parse_float.zig").ParseFloatError; test { - _ = parseFloat; + _ = &parseFloat; } -pub fn charToDigit(c: u8, radix: u8) (error{InvalidCharacter}!u8) { +pub fn charToDigit(c: u8, base: u8) (error{InvalidCharacter}!u8) { const value = switch (c) { '0'...'9' => c - '0', 'A'...'Z' => c - 'A' + 10, @@ -1920,7 +1971,7 @@ pub fn charToDigit(c: u8, radix: u8) (error{InvalidCharacter}!u8) { else => return error.InvalidCharacter, }; - if (value >= radix) return error.InvalidCharacter; + if (value >= base) return error.InvalidCharacter; return value; } @@ -2244,8 +2295,8 @@ test "struct" { field: u8, }; const value = Struct{ .field = 42 }; - try expectFmt("struct: Struct{ .field = 42 }\n", "struct: {}\n", .{value}); - try expectFmt("struct: Struct{ .field = 42 }\n", "struct: {}\n", .{&value}); + try expectFmt("struct: fmt.test.struct.Struct{ .field = 42 }\n", "struct: {}\n", .{value}); + try expectFmt("struct: fmt.test.struct.Struct{ .field = 42 }\n", "struct: {}\n", .{&value}); } { const Struct = struct { @@ -2253,8 +2304,24 @@ test "struct" { b: u1, }; const value = Struct{ .a = 0, .b = 1 }; - try expectFmt("struct: Struct{ .a = 0, .b = 1 }\n", "struct: {}\n", .{value}); + try expectFmt("struct: fmt.test.struct.Struct{ .a = 0, .b = 1 }\n", "struct: {}\n", .{value}); } + + const S = struct { + a: u32, + b: anyerror, + }; + + const inst = S{ + .a = 456, + .b = error.Unused, + }; + + try expectFmt("fmt.test.struct.S{ .a = 456, .b = error.Unused }", "{}", .{inst}); + // Tuples + try expectFmt("{ }", "{}", .{.{}}); + try expectFmt("{ -1 }", "{}", .{.{-1}}); + try expectFmt("{ -1, 42, 2.5e+04 }", "{}", .{.{ -1, 42, 0.25e5 }}); } test "enum" { @@ -2263,13 +2330,26 @@ test "enum" { Two, }; const value = Enum.Two; - try expectFmt("enum: Enum.Two\n", "enum: {}\n", .{value}); - try expectFmt("enum: Enum.Two\n", "enum: {}\n", .{&value}); - try expectFmt("enum: Enum.One\n", "enum: {}\n", .{Enum.One}); - try expectFmt("enum: Enum.Two\n", "enum: {}\n", .{Enum.Two}); + try expectFmt("enum: fmt.test.enum.Enum.Two\n", "enum: {}\n", .{value}); + try expectFmt("enum: fmt.test.enum.Enum.Two\n", "enum: {}\n", .{&value}); + try expectFmt("enum: fmt.test.enum.Enum.One\n", "enum: {}\n", .{Enum.One}); + try expectFmt("enum: fmt.test.enum.Enum.Two\n", "enum: {}\n", .{Enum.Two}); // test very large enum to verify ct branch quota is large enough - try expectFmt("enum: os.windows.win32error.Win32Error.INVALID_FUNCTION\n", "enum: {}\n", .{std.os.windows.Win32Error.INVALID_FUNCTION}); + // TODO: https://github.com/ziglang/zig/issues/15609 + if (!((builtin.cpu.arch == .wasm32) and builtin.mode == .Debug)) { + try expectFmt("enum: os.windows.win32error.Win32Error.INVALID_FUNCTION\n", "enum: {}\n", .{std.os.windows.Win32Error.INVALID_FUNCTION}); + } + + const E = enum { + One, + Two, + Three, + }; + + const inst = E.Two; + + try expectFmt("fmt.test.enum.E.Two", "{}", .{inst}); } test "non-exhaustive enum" { @@ -2445,24 +2525,6 @@ test "custom" { try expectFmt("dim: 10.200x2.220\n", "dim: {d}\n", .{value}); } -test "struct" { - const S = struct { - a: u32, - b: anyerror, - }; - - const inst = S{ - .a = 456, - .b = error.Unused, - }; - - try expectFmt("fmt.test.struct.S{ .a = 456, .b = error.Unused }", "{}", .{inst}); - // Tuples - try expectFmt("{ }", "{}", .{.{}}); - try expectFmt("{ -1 }", "{}", .{.{-1}}); - try expectFmt("{ -1, 42, 2.5e+04 }", "{}", .{.{ -1, 42, 0.25e5 }}); -} - test "union" { const TU = union(enum) { float: f32, @@ -2493,18 +2555,6 @@ test "union" { try std.testing.expect(mem.eql(u8, eu_result[0..18], "fmt.test.union.EU@")); } -test "enum" { - const E = enum { - One, - Two, - Three, - }; - - const inst = E.Two; - - try expectFmt("fmt.test.enum.E.Two", "{}", .{inst}); -} - test "struct.self-referential" { const S = struct { const SelfType = @This(); |
