aboutsummaryrefslogtreecommitdiff
path: root/lib/std/fmt.zig
diff options
context:
space:
mode:
authorLoris Cro <kappaloris@gmail.com>2023-06-18 09:06:40 +0200
committerGitHub <noreply@github.com>2023-06-18 09:06:40 +0200
commit216ef10dc471e4db60a30208be178d6c59efeaaf (patch)
tree8c239dab283ae9cb3b7fe099bae240bcc53f894e /lib/std/fmt.zig
parent0fc1d396495c1ab482197021dedac8bea3f9401c (diff)
parent729a051e9e38674233190aea23c0ac8c134f2d67 (diff)
downloadzig-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.zig442
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();