diff options
| author | Benjamin Feng <benjamin.feng@glassdoor.com> | 2020-03-06 16:59:21 -0600 |
|---|---|---|
| committer | Benjamin Feng <benjamin.feng@glassdoor.com> | 2020-03-12 10:41:09 -0500 |
| commit | 4aae55b4ccf44fa3c2c2a81a6a34f3c898dece30 (patch) | |
| tree | a4116c5259e3f39df939008f23ac66f65ce61194 /lib/std/fmtstream.zig | |
| parent | ed7f30e1cd0c00c82c511ad826fe8d8b60b2f57f (diff) | |
| download | zig-4aae55b4ccf44fa3c2c2a81a6a34f3c898dece30.tar.gz zig-4aae55b4ccf44fa3c2c2a81a6a34f3c898dece30.zip | |
Replace fmt with new fmtstream
Diffstat (limited to 'lib/std/fmtstream.zig')
| -rw-r--r-- | lib/std/fmtstream.zig | 1685 |
1 files changed, 0 insertions, 1685 deletions
diff --git a/lib/std/fmtstream.zig b/lib/std/fmtstream.zig deleted file mode 100644 index 93734d42aa..0000000000 --- a/lib/std/fmtstream.zig +++ /dev/null @@ -1,1685 +0,0 @@ -const std = @import("std.zig"); -const math = std.math; -const assert = std.debug.assert; -const mem = std.mem; -const builtin = @import("builtin"); -const errol = @import("fmt/errol.zig"); -const lossyCast = std.math.lossyCast; - -pub const default_max_depth = 3; - -pub const Alignment = enum { - Left, - Center, - Right, -}; - -pub const FormatOptions = struct { - precision: ?usize = null, - width: ?usize = null, - alignment: ?Alignment = null, - fill: u8 = ' ', -}; - -fn peekIsAlign(comptime fmt: []const u8) bool { - // Should only be called during a state transition to the format segment. - comptime assert(fmt[0] == ':'); - - inline for (([_]u8{ 1, 2 })[0..]) |i| { - if (fmt.len > i and (fmt[i] == '<' or fmt[i] == '^' or fmt[i] == '>')) { - return true; - } - } - return false; -} - -/// Renders fmt string with args, calling output with slices of bytes. -/// If `output` returns an error, the error is returned from `format` and -/// `output` is not called again. -/// -/// The format string must be comptime known and may contain placeholders following -/// this format: -/// `{[position][specifier]:[fill][alignment][width].[precision]}` -/// -/// Each word between `[` and `]` is a parameter you have to replace with something: -/// -/// - *position* is the index of the argument that should be inserted -/// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below) -/// - *fill* is a single character which is used to pad the formatted text -/// - *alignment* is one of the three characters `<`, `^` or `>`. they define if the text is *left*, *center*, or *right* aligned -/// - *width* is the total width of the field in characters -/// - *precision* specifies how many decimals a formatted number should have -/// -/// Note that most of the parameters are optional and may be omitted. Also you can leave out separators like `:` and `.` when -/// all parameters after the separator are omitted. -/// Only exception is the *fill* parameter. If *fill* is required, one has to specify *alignment* as well, as otherwise -/// the digits after `:` is interpreted as *width*, not *fill*. -/// -/// The *specifier* has several options for types: -/// - `x` and `X`: -/// - format the non-numeric value as a string of bytes in hexadecimal notation ("binary dump") in either lower case or upper case -/// - output numeric value in hexadecimal notation -/// - `s`: print a pointer-to-many as a c-string, use zero-termination -/// - `B` and `Bi`: output a memory size in either metric (1000) or power-of-two (1024) based notation. works for both float and integer values. -/// - `e`: output floating point value in scientific notation -/// - `d`: output numeric value in decimal notation -/// - `b`: output integer value in binary notation -/// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max. -/// - `*`: output the address of the value instead of the value itself. -/// -/// If a formatted user type contains a function of the type -/// ``` -/// fn format(value: ?, comptime fmt: []const u8, options: std.fmtstream.FormatOptions, out_stream: var) !void -/// ``` -/// with `?` being the type formatted, this function will be called instead of the default implementation. -/// This allows user types to be formatted in a logical manner instead of dumping all fields of the type. -/// -/// A user type may be a `struct`, `vector`, `union` or `enum` type. -pub fn format( - out_stream: var, - comptime fmt: []const u8, - args: var, -) !void { - const ArgSetType = u32; - if (@typeInfo(@TypeOf(args)) != .Struct) { - @compileError("Expected tuple or struct argument, found " ++ @typeName(@TypeOf(args))); - } - if (args.len > ArgSetType.bit_count) { - @compileError("32 arguments max are supported per format call"); - } - - const State = enum { - Start, - Positional, - CloseBrace, - Specifier, - FormatFillAndAlign, - FormatWidth, - FormatPrecision, - }; - - comptime var start_index = 0; - comptime var state = State.Start; - comptime var maybe_pos_arg: ?comptime_int = null; - comptime var specifier_start = 0; - comptime var specifier_end = 0; - comptime var options = FormatOptions{}; - comptime var arg_state: struct { - next_arg: usize = 0, - used_args: ArgSetType = 0, - args_len: usize = args.len, - - fn hasUnusedArgs(comptime self: *@This()) bool { - return (@popCount(ArgSetType, self.used_args) != self.args_len); - } - - fn nextArg(comptime self: *@This(), comptime pos_arg: ?comptime_int) comptime_int { - const next_idx = pos_arg orelse blk: { - const arg = self.next_arg; - self.next_arg += 1; - break :blk arg; - }; - - if (next_idx >= self.args_len) { - @compileError("Too few arguments"); - } - - // Mark this argument as used - self.used_args |= 1 << next_idx; - - return next_idx; - } - } = .{}; - - inline for (fmt) |c, i| { - switch (state) { - .Start => switch (c) { - '{' => { - if (start_index < i) { - try out_stream.writeAll(fmt[start_index..i]); - } - - start_index = i; - specifier_start = i + 1; - specifier_end = i + 1; - maybe_pos_arg = null; - state = .Positional; - options = FormatOptions{}; - }, - '}' => { - if (start_index < i) { - try out_stream.writeAll(fmt[start_index..i]); - } - state = .CloseBrace; - }, - else => {}, - }, - .Positional => switch (c) { - '{' => { - state = .Start; - start_index = i; - }, - ':' => { - state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth; - specifier_end = i; - }, - '0'...'9' => { - if (maybe_pos_arg == null) { - maybe_pos_arg = 0; - } - - maybe_pos_arg.? *= 10; - maybe_pos_arg.? += c - '0'; - specifier_start = i + 1; - - if (maybe_pos_arg.? >= args.len) { - @compileError("Positional value refers to non-existent argument"); - } - }, - '}' => { - const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg); - - try formatType( - args[arg_to_print], - fmt[0..0], - options, - out_stream, - default_max_depth, - ); - - state = .Start; - start_index = i + 1; - }, - else => { - state = .Specifier; - specifier_start = i; - }, - }, - .CloseBrace => switch (c) { - '}' => { - state = .Start; - start_index = i; - }, - else => @compileError("Single '}' encountered in format string"), - }, - .Specifier => switch (c) { - ':' => { - specifier_end = i; - state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth; - }, - '}' => { - const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg); - - try formatType( - args[arg_to_print], - fmt[specifier_start..i], - options, - out_stream, - default_max_depth, - ); - state = .Start; - start_index = i + 1; - }, - else => {}, - }, - // Only entered if the format string contains a fill/align segment. - .FormatFillAndAlign => switch (c) { - '<' => { - options.alignment = Alignment.Left; - state = .FormatWidth; - }, - '^' => { - options.alignment = Alignment.Center; - state = .FormatWidth; - }, - '>' => { - options.alignment = Alignment.Right; - state = .FormatWidth; - }, - else => { - options.fill = c; - }, - }, - .FormatWidth => switch (c) { - '0'...'9' => { - if (options.width == null) { - options.width = 0; - } - - options.width.? *= 10; - options.width.? += c - '0'; - }, - '.' => { - state = .FormatPrecision; - }, - '}' => { - const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg); - - try formatType( - args[arg_to_print], - fmt[specifier_start..specifier_end], - options, - out_stream, - default_max_depth, - ); - state = .Start; - start_index = i + 1; - }, - else => { - @compileError("Unexpected character in width value: " ++ [_]u8{c}); - }, - }, - .FormatPrecision => switch (c) { - '0'...'9' => { - if (options.precision == null) { - options.precision = 0; - } - - options.precision.? *= 10; - options.precision.? += c - '0'; - }, - '}' => { - const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg); - - try formatType( - args[arg_to_print], - fmt[specifier_start..specifier_end], - options, - out_stream, - default_max_depth, - ); - state = .Start; - start_index = i + 1; - }, - else => { - @compileError("Unexpected character in precision value: " ++ [_]u8{c}); - }, - }, - } - } - comptime { - if (comptime arg_state.hasUnusedArgs()) { - @compileError("Unused arguments"); - } - if (state != State.Start) { - @compileError("Incomplete format string: " ++ fmt); - } - } - if (start_index < fmt.len) { - try out_stream.writeAll(fmt[start_index..]); - } -} - -pub fn formatType( - value: var, - comptime fmt: []const u8, - options: FormatOptions, - out_stream: var, - max_depth: usize, -) @TypeOf(out_stream).Error!void { - if (comptime std.mem.eql(u8, fmt, "*")) { - try out_stream.writeAll(@typeName(@TypeOf(value).Child)); - try out_stream.writeAll("@"); - try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, out_stream); - return; - } - - const T = @TypeOf(value); - if (comptime std.meta.trait.hasFn("format")(T)) { - return try value.format(fmt, options, out_stream); - } - - switch (@typeInfo(T)) { - .ComptimeInt, .Int, .Float => { - return formatValue(value, fmt, options, out_stream); - }, - .Void => { - return out_stream.writeAll("void"); - }, - .Bool => { - return out_stream.writeAll(if (value) "true" else "false"); - }, - .Optional => { - if (value) |payload| { - return formatType(payload, fmt, options, out_stream, max_depth); - } else { - return out_stream.writeAll("null"); - } - }, - .ErrorUnion => { - if (value) |payload| { - return formatType(payload, fmt, options, out_stream, max_depth); - } else |err| { - return formatType(err, fmt, options, out_stream, max_depth); - } - }, - .ErrorSet => { - try out_stream.writeAll("error."); - return out_stream.writeAll(@errorName(value)); - }, - .Enum => |enumInfo| { - try out_stream.writeAll(@typeName(T)); - if (enumInfo.is_exhaustive) { - try out_stream.writeAll("."); - try out_stream.writeAll(@tagName(value)); - } else { - // TODO: when @tagName works on exhaustive enums print known enum strings - try out_stream.writeAll("("); - try formatType(@enumToInt(value), fmt, options, out_stream, max_depth); - try out_stream.writeAll(")"); - } - }, - .Union => { - try out_stream.writeAll(@typeName(T)); - if (max_depth == 0) { - return out_stream.writeAll("{ ... }"); - } - const info = @typeInfo(T).Union; - if (info.tag_type) |UnionTagType| { - try out_stream.writeAll("{ ."); - try out_stream.writeAll(@tagName(@as(UnionTagType, value))); - try out_stream.writeAll(" = "); - inline for (info.fields) |u_field| { - if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { - try formatType(@field(value, u_field.name), fmt, options, out_stream, max_depth - 1); - } - } - try out_stream.writeAll(" }"); - } else { - try format(out_stream, "@{x}", .{@ptrToInt(&value)}); - } - }, - .Struct => |StructT| { - try out_stream.writeAll(@typeName(T)); - if (max_depth == 0) { - return out_stream.writeAll("{ ... }"); - } - try out_stream.writeAll("{"); - inline for (StructT.fields) |f, i| { - if (i == 0) { - try out_stream.writeAll(" ."); - } else { - try out_stream.writeAll(", ."); - } - try out_stream.writeAll(f.name); - try out_stream.writeAll(" = "); - try formatType(@field(value, f.name), fmt, options, out_stream, max_depth - 1); - } - try out_stream.writeAll(" }"); - }, - .Pointer => |ptr_info| switch (ptr_info.size) { - .One => switch (@typeInfo(ptr_info.child)) { - .Array => |info| { - if (info.child == u8) { - return formatText(value, fmt, options, out_stream); - } - return format(out_stream, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); - }, - .Enum, .Union, .Struct => { - return formatType(value.*, fmt, options, out_stream, max_depth); - }, - else => return format(out_stream, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }), - }, - .Many, .C => { - if (ptr_info.sentinel) |sentinel| { - return formatType(mem.span(value), fmt, options, out_stream, max_depth); - } - if (ptr_info.child == u8) { - if (fmt.len > 0 and fmt[0] == 's') { - return formatText(mem.span(value), fmt, options, out_stream); - } - } - return format(out_stream, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); - }, - .Slice => { - if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { - return formatText(value, fmt, options, out_stream); - } - if (ptr_info.child == u8) { - return formatText(value, fmt, options, out_stream); - } - return format(out_stream, "{}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value.ptr) }); - }, - }, - .Array => |info| { - const Slice = @Type(builtin.TypeInfo{ - .Pointer = .{ - .size = .Slice, - .is_const = true, - .is_volatile = false, - .is_allowzero = false, - .alignment = @alignOf(info.child), - .child = info.child, - .sentinel = null, - }, - }); - return formatType(@as(Slice, &value), fmt, options, out_stream, max_depth); - }, - .Vector => { - const len = @typeInfo(T).Vector.len; - try out_stream.writeAll("{ "); - var i: usize = 0; - while (i < len) : (i += 1) { - try formatValue(value[i], fmt, options, out_stream); - if (i < len - 1) { - try out_stream.writeAll(", "); - } - } - try out_stream.writeAll(" }"); - }, - .Fn => { - return format(out_stream, "{}@{x}", .{ @typeName(T), @ptrToInt(value) }); - }, - .Type => return out_stream.writeAll(@typeName(T)), - .EnumLiteral => { - const buffer = [_]u8{'.'} ++ @tagName(value); - return formatType(buffer, fmt, options, out_stream, max_depth); - }, - else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), - } -} - -fn formatValue( - value: var, - comptime fmt: []const u8, - options: FormatOptions, - out_stream: var, -) !void { - if (comptime std.mem.eql(u8, fmt, "B")) { - return formatBytes(value, options, 1000, out_stream); - } else if (comptime std.mem.eql(u8, fmt, "Bi")) { - return formatBytes(value, options, 1024, out_stream); - } - - const T = @TypeOf(value); - switch (@typeInfo(T)) { - .Float => return formatFloatValue(value, fmt, options, out_stream), - .Int, .ComptimeInt => return formatIntValue(value, fmt, options, out_stream), - .Bool => return out_stream.writeAll(if (value) "true" else "false"), - else => comptime unreachable, - } -} - -pub fn formatIntValue( - value: var, - comptime fmt: []const u8, - options: FormatOptions, - out_stream: var, -) !void { - comptime var radix = 10; - comptime var uppercase = false; - - const int_value = if (@TypeOf(value) == comptime_int) blk: { - const Int = math.IntFittingRange(value, value); - break :blk @as(Int, value); - } else - value; - - if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) { - radix = 10; - uppercase = false; - } else if (comptime std.mem.eql(u8, fmt, "c")) { - if (@TypeOf(int_value).bit_count <= 8) { - return formatAsciiChar(@as(u8, int_value), options, out_stream); - } else { - @compileError("Cannot print integer that is larger than 8 bits as a ascii"); - } - } else if (comptime std.mem.eql(u8, fmt, "b")) { - radix = 2; - uppercase = false; - } else if (comptime std.mem.eql(u8, fmt, "x")) { - radix = 16; - uppercase = false; - } else if (comptime std.mem.eql(u8, fmt, "X")) { - radix = 16; - uppercase = true; - } else { - @compileError("Unknown format string: '" ++ fmt ++ "'"); - } - - return formatInt(int_value, radix, uppercase, options, out_stream); -} - -fn formatFloatValue( - value: var, - comptime fmt: []const u8, - options: FormatOptions, - out_stream: var, -) !void { - if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { - return formatFloatScientific(value, options, out_stream); - } else if (comptime std.mem.eql(u8, fmt, "d")) { - return formatFloatDecimal(value, options, out_stream); - } else { - @compileError("Unknown format string: '" ++ fmt ++ "'"); - } -} - -pub fn formatText( - bytes: []const u8, - comptime fmt: []const u8, - options: FormatOptions, - out_stream: var, -) !void { - if (fmt.len == 0) { - return out_stream.writeAll(bytes); - } else if (comptime std.mem.eql(u8, fmt, "s")) { - return formatBuf(bytes, options, out_stream); - } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) { - for (bytes) |c| { - try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, out_stream); - } - return; - } else { - @compileError("Unknown format string: '" ++ fmt ++ "'"); - } -} - -pub fn formatAsciiChar( - c: u8, - options: FormatOptions, - out_stream: var, -) !void { - return out_stream.writeAll(@as(*const [1]u8, &c)[0..]); -} - -pub fn formatBuf( - buf: []const u8, - options: FormatOptions, - out_stream: var, -) !void { - try out_stream.writeAll(buf); - - const width = options.width orelse 0; - var leftover_padding = if (width > buf.len) (width - buf.len) else return; - const pad_byte: u8 = options.fill; - while (leftover_padding > 0) : (leftover_padding -= 1) { - try out_stream.writeAll(@as(*const [1]u8, &pad_byte)[0..1]); - } -} - -// Print a float in scientific notation to the specified precision. Null uses full precision. -// It should be the case that every full precision, printed value can be re-parsed back to the -// same type unambiguously. -pub fn formatFloatScientific( - value: var, - options: FormatOptions, - out_stream: var, -) !void { - var x = @floatCast(f64, value); - - // Errol doesn't handle these special cases. - if (math.signbit(x)) { - try out_stream.writeAll("-"); - x = -x; - } - - if (math.isNan(x)) { - return out_stream.writeAll("nan"); - } - if (math.isPositiveInf(x)) { - return out_stream.writeAll("inf"); - } - if (x == 0.0) { - try out_stream.writeAll("0"); - - if (options.precision) |precision| { - if (precision != 0) { - try out_stream.writeAll("."); - var i: usize = 0; - while (i < precision) : (i += 1) { - try out_stream.writeAll("0"); - } - } - } else { - try out_stream.writeAll(".0"); - } - - try out_stream.writeAll("e+00"); - return; - } - - var buffer: [32]u8 = undefined; - var float_decimal = errol.errol3(x, buffer[0..]); - - if (options.precision) |precision| { - errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific); - - try out_stream.writeAll(float_decimal.digits[0..1]); - - // {e0} case prints no `.` - if (precision != 0) { - try out_stream.writeAll("."); - - var printed: usize = 0; - if (float_decimal.digits.len > 1) { - const num_digits = math.min(float_decimal.digits.len, precision + 1); - try out_stream.writeAll(float_decimal.digits[1..num_digits]); - printed += num_digits - 1; - } - - while (printed < precision) : (printed += 1) { - try out_stream.writeAll("0"); - } - } - } else { - try out_stream.writeAll(float_decimal.digits[0..1]); - try out_stream.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; - - try out_stream.writeAll(float_decimal.digits[1..num_digits]); - } else { - try out_stream.writeAll("0"); - } - } - - try out_stream.writeAll("e"); - const exp = float_decimal.exp - 1; - - if (exp >= 0) { - try out_stream.writeAll("+"); - if (exp > -10 and exp < 10) { - try out_stream.writeAll("0"); - } - try formatInt(exp, 10, false, FormatOptions{ .width = 0 }, out_stream); - } else { - try out_stream.writeAll("-"); - if (exp > -10 and exp < 10) { - try out_stream.writeAll("0"); - } - try formatInt(-exp, 10, false, FormatOptions{ .width = 0 }, out_stream); - } -} - -// Print a float of the format x.yyyyy where the number of y is specified by the precision argument. -// By default floats are printed at full precision (no rounding). -pub fn formatFloatDecimal( - value: var, - options: FormatOptions, - out_stream: var, -) !void { - var x = @as(f64, value); - - // Errol doesn't handle these special cases. - if (math.signbit(x)) { - try out_stream.writeAll("-"); - x = -x; - } - - if (math.isNan(x)) { - return out_stream.writeAll("nan"); - } - if (math.isPositiveInf(x)) { - return out_stream.writeAll("inf"); - } - if (x == 0.0) { - try out_stream.writeAll("0"); - - if (options.precision) |precision| { - if (precision != 0) { - try out_stream.writeAll("."); - var i: usize = 0; - while (i < precision) : (i += 1) { - try out_stream.writeAll("0"); - } - } else { - try out_stream.writeAll(".0"); - } - } - - return; - } - - // non-special case, use errol3 - var buffer: [32]u8 = undefined; - var float_decimal = errol.errol3(x, buffer[0..]); - - if (options.precision) |precision| { - errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal); - - // exp < 0 means the leading is always 0 as errol result is normalized. - 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); - - if (num_digits_whole > 0) { - // We may have to zero pad, for instance 1e4 requires zero padding. - try out_stream.writeAll(float_decimal.digits[0..num_digits_whole_no_pad]); - - var i = num_digits_whole_no_pad; - while (i < num_digits_whole) : (i += 1) { - try out_stream.writeAll("0"); - } - } else { - try out_stream.writeAll("0"); - } - - // {.0} special case doesn't want a trailing '.' - if (precision == 0) { - return; - } - - try out_stream.writeAll("."); - - // Keep track of fractional count printed for case where we pre-pad then post-pad with 0's. - var printed: usize = 0; - - // 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); - - var i: usize = 0; - while (i < zeros_to_print) : (i += 1) { - try out_stream.writeAll("0"); - printed += 1; - } - - if (printed >= precision) { - return; - } - } - - // Remaining fractional portion, zero-padding if insufficient. - assert(precision >= printed); - if (num_digits_whole_no_pad + precision - printed < float_decimal.digits.len) { - try out_stream.writeAll(float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]); - return; - } else { - try out_stream.writeAll(float_decimal.digits[num_digits_whole_no_pad..]); - printed += float_decimal.digits.len - num_digits_whole_no_pad; - - while (printed < precision) : (printed += 1) { - try out_stream.writeAll("0"); - } - } - } else { - // exp < 0 means the leading is always 0 as errol result is normalized. - 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); - - if (num_digits_whole > 0) { - // We may have to zero pad, for instance 1e4 requires zero padding. - try out_stream.writeAll(float_decimal.digits[0..num_digits_whole_no_pad]); - - var i = num_digits_whole_no_pad; - while (i < num_digits_whole) : (i += 1) { - try out_stream.writeAll("0"); - } - } else { - try out_stream.writeAll("0"); - } - - // Omit `.` if no fractional portion - if (float_decimal.exp >= 0 and num_digits_whole_no_pad == float_decimal.digits.len) { - return; - } - - try out_stream.writeAll("."); - - // 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); - - var i: usize = 0; - while (i < zero_digit_count) : (i += 1) { - try out_stream.writeAll("0"); - } - } - - try out_stream.writeAll(float_decimal.digits[num_digits_whole_no_pad..]); - } -} - -pub fn formatBytes( - value: var, - options: FormatOptions, - comptime radix: usize, - out_stream: var, -) !void { - if (value == 0) { - return out_stream.writeAll("0B"); - } - - const mags_si = " kMGTPEZY"; - const mags_iec = " KMGTPEZY"; - const magnitude = switch (radix) { - 1000 => math.min(math.log2(value) / comptime math.log2(1000), mags_si.len - 1), - 1024 => math.min(math.log2(value) / 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) { - 1000 => mags_si[magnitude], - 1024 => mags_iec[magnitude], - else => unreachable, - }; - - try formatFloatDecimal(new_value, options, out_stream); - - if (suffix == ' ') { - return out_stream.writeAll("B"); - } - - const buf = switch (radix) { - 1000 => &[_]u8{ suffix, 'B' }, - 1024 => &[_]u8{ suffix, 'i', 'B' }, - else => unreachable, - }; - return out_stream.writeAll(buf); -} - -pub fn formatInt( - value: var, - base: u8, - uppercase: bool, - options: FormatOptions, - out_stream: var, -) !void { - const int_value = if (@TypeOf(value) == comptime_int) blk: { - const Int = math.IntFittingRange(value, value); - break :blk @as(Int, value); - } else - value; - - if (@TypeOf(int_value).is_signed) { - return formatIntSigned(int_value, base, uppercase, options, out_stream); - } else { - return formatIntUnsigned(int_value, base, uppercase, options, out_stream); - } -} - -fn formatIntSigned( - value: var, - base: u8, - uppercase: bool, - options: FormatOptions, - out_stream: var, -) !void { - const new_options = FormatOptions{ - .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null, - .precision = options.precision, - .fill = options.fill, - }; - const bit_count = @typeInfo(@TypeOf(value)).Int.bits; - const Uint = std.meta.IntType(false, bit_count); - if (value < 0) { - try out_stream.writeAll("-"); - const new_value = math.absCast(value); - return formatIntUnsigned(new_value, base, uppercase, new_options, out_stream); - } else if (options.width == null or options.width.? == 0) { - return formatIntUnsigned(@intCast(Uint, value), base, uppercase, options, out_stream); - } else { - try out_stream.writeAll("+"); - const new_value = @intCast(Uint, value); - return formatIntUnsigned(new_value, base, uppercase, new_options, out_stream); - } -} - -fn formatIntUnsigned( - value: var, - base: u8, - uppercase: bool, - options: FormatOptions, - out_stream: var, -) !void { - assert(base >= 2); - var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined; - const min_int_bits = comptime math.max(@TypeOf(value).bit_count, @TypeOf(base).bit_count); - const MinInt = std.meta.IntType(@TypeOf(value).is_signed, min_int_bits); - var a: MinInt = value; - var index: usize = buf.len; - - while (true) { - const digit = a % base; - index -= 1; - buf[index] = digitToChar(@intCast(u8, digit), uppercase); - a /= base; - if (a == 0) break; - } - - const digits_buf = buf[index..]; - const width = options.width orelse 0; - const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0; - - if (padding > index) { - const zero_byte: u8 = options.fill; - var leftover_padding = padding - index; - while (true) { - try out_stream.writeAll(@as(*const [1]u8, &zero_byte)[0..]); - leftover_padding -= 1; - if (leftover_padding == 0) break; - } - mem.set(u8, buf[0..index], options.fill); - return out_stream.writeAll(&buf); - } else { - const padded_buf = buf[index - padding ..]; - mem.set(u8, padded_buf[0..padding], options.fill); - return out_stream.writeAll(padded_buf); - } -} - -pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) usize { - var fbs = std.io.fixedBufferStream(out_buf); - formatInt(value, base, uppercase, options, fbs.outStream()) catch unreachable; - return fbs.pos; -} - -pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) !T { - if (!T.is_signed) return parseUnsigned(T, buf, radix); - if (buf.len == 0) return @as(T, 0); - if (buf[0] == '-') { - return math.negate(try parseUnsigned(T, buf[1..], radix)); - } else if (buf[0] == '+') { - return parseUnsigned(T, buf[1..], radix); - } else { - return parseUnsigned(T, buf, radix); - } -} - -test "parseInt" { - std.testing.expect((parseInt(i32, "-10", 10) catch unreachable) == -10); - std.testing.expect((parseInt(i32, "+10", 10) catch unreachable) == 10); - std.testing.expect(if (parseInt(i32, " 10", 10)) |_| false else |err| err == error.InvalidCharacter); - std.testing.expect(if (parseInt(i32, "10 ", 10)) |_| false else |err| err == error.InvalidCharacter); - std.testing.expect(if (parseInt(u32, "-10", 10)) |_| false else |err| err == error.InvalidCharacter); - std.testing.expect((parseInt(u8, "255", 10) catch unreachable) == 255); - std.testing.expect(if (parseInt(u8, "256", 10)) |_| false else |err| err == error.Overflow); -} - -pub const ParseUnsignedError = error{ - /// The result cannot fit in the type specified - Overflow, - - /// The input had a byte that was not a digit - InvalidCharacter, -}; - -pub fn parseUnsigned(comptime T: type, buf: []const u8, radix: u8) ParseUnsignedError!T { - var x: T = 0; - - for (buf) |c| { - const digit = try charToDigit(c, radix); - - if (x != 0) x = try math.mul(T, x, try math.cast(T, radix)); - x = try math.add(T, x, try math.cast(T, digit)); - } - - return x; -} - -test "parseUnsigned" { - std.testing.expect((try parseUnsigned(u16, "050124", 10)) == 50124); - std.testing.expect((try parseUnsigned(u16, "65535", 10)) == 65535); - std.testing.expectError(error.Overflow, parseUnsigned(u16, "65536", 10)); - - std.testing.expect((try parseUnsigned(u64, "0ffffffffffffffff", 16)) == 0xffffffffffffffff); - std.testing.expectError(error.Overflow, parseUnsigned(u64, "10000000000000000", 16)); - - std.testing.expect((try parseUnsigned(u32, "DeadBeef", 16)) == 0xDEADBEEF); - - std.testing.expect((try parseUnsigned(u7, "1", 10)) == 1); - std.testing.expect((try parseUnsigned(u7, "1000", 2)) == 8); - - std.testing.expectError(error.InvalidCharacter, parseUnsigned(u32, "f", 10)); - std.testing.expectError(error.InvalidCharacter, parseUnsigned(u8, "109", 8)); - - 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 - std.testing.expect((try parseUnsigned(u1, "0", 10)) == 0); - std.testing.expect((try parseUnsigned(u1, "1", 10)) == 1); - std.testing.expectError(error.Overflow, parseUnsigned(u1, "2", 10)); - std.testing.expect((try parseUnsigned(u1, "001", 16)) == 1); - std.testing.expect((try parseUnsigned(u2, "3", 16)) == 3); - std.testing.expectError(error.Overflow, parseUnsigned(u2, "4", 16)); -} - -pub const parseFloat = @import("fmt/parse_float.zig").parseFloat; - -test "parseFloat" { - _ = @import("fmt/parse_float.zig"); -} - -pub fn charToDigit(c: u8, radix: u8) (error{InvalidCharacter}!u8) { - const value = switch (c) { - '0'...'9' => c - '0', - 'A'...'Z' => c - 'A' + 10, - 'a'...'z' => c - 'a' + 10, - else => return error.InvalidCharacter, - }; - - if (value >= radix) return error.InvalidCharacter; - - return value; -} - -fn digitToChar(digit: u8, uppercase: bool) u8 { - return switch (digit) { - 0...9 => digit + '0', - 10...35 => digit + ((if (uppercase) @as(u8, 'A') else @as(u8, 'a')) - 10), - else => unreachable, - }; -} - -pub const BufPrintError = error{ - /// As much as possible was written to the buffer, but it was too small to fit all the printed bytes. - BufferTooSmall, -}; -pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: var) BufPrintError![]u8 { - var fbs = std.io.fixedBufferStream(buf); - format(fbs.outStream(), fmt, args) catch |err| switch (err) { - error.NoSpaceLeft => return error.BufferTooSmall, - }; - //TODO: should we change one of these return signatures? - //return fbs.getWritten(); - return buf[0..fbs.pos]; -} - -// Count the characters needed for format. Useful for preallocating memory -pub fn count(comptime fmt: []const u8, args: var) !usize { - var counting_stream = std.io.countingOutStream(std.io.null_out_stream); - format(counting_stream.outStream(), fmt, args) catch |err| switch (err) {}; - return std.math.cast(usize, counting_stream.bytes_written); -} - -pub const AllocPrintError = error{OutOfMemory}; - -pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: var) AllocPrintError![]u8 { - const size = count(fmt, args) catch |err| switch (err) { - // Output too long. Can't possibly allocate enough memory to display it. - error.Overflow => return error.OutOfMemory, - }; - const buf = try allocator.alloc(u8, size); - return bufPrint(buf, fmt, args) catch |err| switch (err) { - error.BufferTooSmall => unreachable, // we just counted the size above - }; -} - -pub fn allocPrint0(allocator: *mem.Allocator, comptime fmt: []const u8, args: var) AllocPrintError![:0]u8 { - const result = try allocPrint(allocator, fmt ++ "\x00", args); - return result[0 .. result.len - 1 :0]; -} - -test "bufPrintInt" { - var buffer: [100]u8 = undefined; - const buf = buffer[0..]; - - std.testing.expectEqualSlices(u8, "-1", bufPrintIntToSlice(buf, @as(i1, -1), 10, false, FormatOptions{})); - - std.testing.expectEqualSlices(u8, "-101111000110000101001110", bufPrintIntToSlice(buf, @as(i32, -12345678), 2, false, FormatOptions{})); - std.testing.expectEqualSlices(u8, "-12345678", bufPrintIntToSlice(buf, @as(i32, -12345678), 10, false, FormatOptions{})); - std.testing.expectEqualSlices(u8, "-bc614e", bufPrintIntToSlice(buf, @as(i32, -12345678), 16, false, FormatOptions{})); - std.testing.expectEqualSlices(u8, "-BC614E", bufPrintIntToSlice(buf, @as(i32, -12345678), 16, true, FormatOptions{})); - - std.testing.expectEqualSlices(u8, "12345678", bufPrintIntToSlice(buf, @as(u32, 12345678), 10, true, FormatOptions{})); - - std.testing.expectEqualSlices(u8, " 666", bufPrintIntToSlice(buf, @as(u32, 666), 10, false, FormatOptions{ .width = 6 })); - std.testing.expectEqualSlices(u8, " 1234", bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, false, FormatOptions{ .width = 6 })); - std.testing.expectEqualSlices(u8, "1234", bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, false, FormatOptions{ .width = 1 })); - - std.testing.expectEqualSlices(u8, "+42", bufPrintIntToSlice(buf, @as(i32, 42), 10, false, FormatOptions{ .width = 3 })); - std.testing.expectEqualSlices(u8, "-42", bufPrintIntToSlice(buf, @as(i32, -42), 10, false, FormatOptions{ .width = 3 })); -} - -fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) []u8 { - return buf[0..formatIntBuf(buf, value, base, uppercase, options)]; -} - -test "parse u64 digit too big" { - _ = parseUnsigned(u64, "123a", 10) catch |err| { - if (err == error.InvalidCharacter) return; - unreachable; - }; - unreachable; -} - -test "parse unsigned comptime" { - comptime { - std.testing.expect((try parseUnsigned(usize, "2", 10)) == 2); - } -} - -test "optional" { - { - const value: ?i32 = 1234; - try testFmt("optional: 1234\n", "optional: {}\n", .{value}); - } - { - const value: ?i32 = null; - try testFmt("optional: null\n", "optional: {}\n", .{value}); - } -} - -test "error" { - { - const value: anyerror!i32 = 1234; - try testFmt("error union: 1234\n", "error union: {}\n", .{value}); - } - { - const value: anyerror!i32 = error.InvalidChar; - try testFmt("error union: error.InvalidChar\n", "error union: {}\n", .{value}); - } -} - -test "int.small" { - { - const value: u3 = 0b101; - try testFmt("u3: 5\n", "u3: {}\n", .{value}); - } -} - -test "int.specifier" { - { - const value: u8 = 'a'; - try testFmt("u8: a\n", "u8: {c}\n", .{value}); - } - { - const value: u8 = 0b1100; - try testFmt("u8: 0b1100\n", "u8: 0b{b}\n", .{value}); - } -} - -test "int.padded" { - try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)}); - try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", .{@as(u8, 1)}); -} - -test "buffer" { - { - var buf1: [32]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf1); - try formatType(1234, "", FormatOptions{}, fbs.outStream(), default_max_depth); - std.testing.expect(mem.eql(u8, fbs.getWritten(), "1234")); - - fbs.reset(); - try formatType('a', "c", FormatOptions{}, fbs.outStream(), default_max_depth); - std.testing.expect(mem.eql(u8, fbs.getWritten(), "a")); - - fbs.reset(); - try formatType(0b1100, "b", FormatOptions{}, fbs.outStream(), default_max_depth); - std.testing.expect(mem.eql(u8, fbs.getWritten(), "1100")); - } -} - -test "array" { - { - const value: [3]u8 = "abc".*; - try testFmt("array: abc\n", "array: {}\n", .{value}); - try testFmt("array: abc\n", "array: {}\n", .{&value}); - - var buf: [100]u8 = undefined; - try testFmt( - try bufPrint(buf[0..], "array: [3]u8@{x}\n", .{@ptrToInt(&value)}), - "array: {*}\n", - .{&value}, - ); - } -} - -test "slice" { - { - const value: []const u8 = "abc"; - try testFmt("slice: abc\n", "slice: {}\n", .{value}); - } - { - const value = @intToPtr([*]align(1) const []const u8, 0xdeadbeef)[0..0]; - try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", .{value}); - } - - try testFmt("buf: Test \n", "buf: {s:5}\n", .{"Test"}); - try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"}); -} - -test "pointer" { - { - const value = @intToPtr(*align(1) i32, 0xdeadbeef); - try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", .{value}); - try testFmt("pointer: i32@deadbeef\n", "pointer: {*}\n", .{value}); - } - { - const value = @intToPtr(fn () void, 0xdeadbeef); - try testFmt("pointer: fn() void@deadbeef\n", "pointer: {}\n", .{value}); - } - { - const value = @intToPtr(fn () void, 0xdeadbeef); - try testFmt("pointer: fn() void@deadbeef\n", "pointer: {}\n", .{value}); - } -} - -test "cstr" { - try testFmt( - "cstr: Test C\n", - "cstr: {s}\n", - .{@ptrCast([*c]const u8, "Test C")}, - ); - try testFmt( - "cstr: Test C \n", - "cstr: {s:10}\n", - .{@ptrCast([*c]const u8, "Test C")}, - ); -} - -test "filesize" { - try testFmt("file size: 63MiB\n", "file size: {Bi}\n", .{@as(usize, 63 * 1024 * 1024)}); - try testFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{@as(usize, 63 * 1024 * 1024)}); -} - -test "struct" { - { - const Struct = struct { - field: u8, - }; - const value = Struct{ .field = 42 }; - try testFmt("struct: Struct{ .field = 42 }\n", "struct: {}\n", .{value}); - try testFmt("struct: Struct{ .field = 42 }\n", "struct: {}\n", .{&value}); - } - { - const Struct = struct { - a: u0, - b: u1, - }; - const value = Struct{ .a = 0, .b = 1 }; - try testFmt("struct: Struct{ .a = 0, .b = 1 }\n", "struct: {}\n", .{value}); - } -} - -test "enum" { - const Enum = enum { - One, - Two, - }; - const value = Enum.Two; - try testFmt("enum: Enum.Two\n", "enum: {}\n", .{value}); - try testFmt("enum: Enum.Two\n", "enum: {}\n", .{&value}); -} - -test "non-exhaustive enum" { - const Enum = enum(u16) { - One = 0x000f, - Two = 0xbeef, - _, - }; - try testFmt("enum: Enum(15)\n", "enum: {}\n", .{Enum.One}); - try testFmt("enum: Enum(48879)\n", "enum: {}\n", .{Enum.Two}); - try testFmt("enum: Enum(4660)\n", "enum: {}\n", .{@intToEnum(Enum, 0x1234)}); - try testFmt("enum: Enum(f)\n", "enum: {x}\n", .{Enum.One}); - try testFmt("enum: Enum(beef)\n", "enum: {x}\n", .{Enum.Two}); - try testFmt("enum: Enum(1234)\n", "enum: {x}\n", .{@intToEnum(Enum, 0x1234)}); -} - -test "float.scientific" { - try testFmt("f32: 1.34000003e+00", "f32: {e}", .{@as(f32, 1.34)}); - try testFmt("f32: 1.23400001e+01", "f32: {e}", .{@as(f32, 12.34)}); - try testFmt("f64: -1.234e+11", "f64: {e}", .{@as(f64, -12.34e10)}); - try testFmt("f64: 9.99996e-40", "f64: {e}", .{@as(f64, 9.999960e-40)}); -} - -test "float.scientific.precision" { - try testFmt("f64: 1.40971e-42", "f64: {e:.5}", .{@as(f64, 1.409706e-42)}); - try testFmt("f64: 1.00000e-09", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 814313563)))}); - try testFmt("f64: 7.81250e-03", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1006632960)))}); - // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05. - // In fact, libc doesn't round a lot of 5 cases up when one past the precision point. - try testFmt("f64: 1.00001e+05", "f64: {e:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1203982400)))}); -} - -test "float.special" { - try testFmt("f64: nan", "f64: {}", .{math.nan_f64}); - // negative nan is not defined by IEE 754, - // and ARM thus normalizes it to positive nan - if (builtin.arch != builtin.Arch.arm) { - try testFmt("f64: -nan", "f64: {}", .{-math.nan_f64}); - } - try testFmt("f64: inf", "f64: {}", .{math.inf_f64}); - try testFmt("f64: -inf", "f64: {}", .{-math.inf_f64}); -} - -test "float.decimal" { - try testFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)}); - try testFmt("f32: 0", "f32: {d}", .{@as(f32, 0.0)}); - try testFmt("f32: 1.1", "f32: {d:.1}", .{@as(f32, 1.1234)}); - try testFmt("f32: 1234.57", "f32: {d:.2}", .{@as(f32, 1234.567)}); - // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64). - // -11.12339... is rounded back up to -11.1234 - try testFmt("f32: -11.1234", "f32: {d:.4}", .{@as(f32, -11.1234)}); - try testFmt("f32: 91.12345", "f32: {d:.5}", .{@as(f32, 91.12345)}); - try testFmt("f64: 91.1234567890", "f64: {d:.10}", .{@as(f64, 91.12345678901235)}); - try testFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 0.0)}); - try testFmt("f64: 6", "f64: {d:.0}", .{@as(f64, 5.700)}); - try testFmt("f64: 10.0", "f64: {d:.1}", .{@as(f64, 9.999)}); - try testFmt("f64: 1.000", "f64: {d:.3}", .{@as(f64, 1.0)}); - try testFmt("f64: 0.00030000", "f64: {d:.8}", .{@as(f64, 0.0003)}); - try testFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 1.40130e-45)}); - try testFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 9.999960e-40)}); -} - -test "float.libc.sanity" { - try testFmt("f64: 0.00001", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 916964781)))}); - try testFmt("f64: 0.00001", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 925353389)))}); - try testFmt("f64: 0.10000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1036831278)))}); - try testFmt("f64: 1.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1065353133)))}); - try testFmt("f64: 10.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1092616192)))}); - - // libc differences - // - // This is 0.015625 exactly according to gdb. We thus round down, - // however glibc rounds up for some reason. This occurs for all - // floats of the form x.yyyy25 on a precision point. - try testFmt("f64: 0.01563", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1015021568)))}); - // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3 - // also rounds to 630 so I'm inclined to believe libc is not - // optimal here. - try testFmt("f64: 18014400656965630.00000", "f64: {d:.5}", .{@as(f64, @bitCast(f32, @as(u32, 1518338049)))}); -} - -test "custom" { - const Vec2 = struct { - const SelfType = @This(); - x: f32, - y: f32, - - pub fn format( - self: SelfType, - comptime fmt: []const u8, - options: FormatOptions, - out_stream: var, - ) !void { - if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { - return std.fmtstream.format(out_stream, "({d:.3},{d:.3})", .{ self.x, self.y }); - } else if (comptime std.mem.eql(u8, fmt, "d")) { - return std.fmtstream.format(out_stream, "{d:.3}x{d:.3}", .{ self.x, self.y }); - } else { - @compileError("Unknown format character: '" ++ fmt ++ "'"); - } - } - }; - - var buf1: [32]u8 = undefined; - var value = Vec2{ - .x = 10.2, - .y = 2.22, - }; - try testFmt("point: (10.200,2.220)\n", "point: {}\n", .{&value}); - try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", .{&value}); - - // same thing but not passing a pointer - try testFmt("point: (10.200,2.220)\n", "point: {}\n", .{value}); - try testFmt("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 testFmt("S{ .a = 456, .b = error.Unused }", "{}", .{inst}); -} - -test "union" { - const TU = union(enum) { - float: f32, - int: u32, - }; - - const UU = union { - float: f32, - int: u32, - }; - - const EU = extern union { - float: f32, - int: u32, - }; - - const tu_inst = TU{ .int = 123 }; - const uu_inst = UU{ .int = 456 }; - const eu_inst = EU{ .float = 321.123 }; - - try testFmt("TU{ .int = 123 }", "{}", .{tu_inst}); - - var buf: [100]u8 = undefined; - const uu_result = try bufPrint(buf[0..], "{}", .{uu_inst}); - std.testing.expect(mem.eql(u8, uu_result[0..3], "UU@")); - - const eu_result = try bufPrint(buf[0..], "{}", .{eu_inst}); - std.testing.expect(mem.eql(u8, uu_result[0..3], "EU@")); -} - -test "enum" { - const E = enum { - One, - Two, - Three, - }; - - const inst = E.Two; - - try testFmt("E.Two", "{}", .{inst}); -} - -test "struct.self-referential" { - const S = struct { - const SelfType = @This(); - a: ?*SelfType, - }; - - var inst = S{ - .a = null, - }; - inst.a = &inst; - - try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", .{inst}); -} - -test "struct.zero-size" { - const A = struct { - fn foo() void {} - }; - const B = struct { - a: A, - c: i32, - }; - - const a = A{}; - const b = B{ .a = a, .c = 0 }; - - try testFmt("B{ .a = A{ }, .c = 0 }", "{}", .{b}); -} - -test "bytes.hex" { - const some_bytes = "\xCA\xFE\xBA\xBE"; - try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes}); - try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes}); - //Test Slices - try testFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]}); - try testFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]}); - const bytes_with_zeros = "\x00\x0E\xBA\xBE"; - try testFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros}); -} - -fn testFmt(expected: []const u8, comptime template: []const u8, args: var) !void { - var buf: [100]u8 = undefined; - const result = try bufPrint(buf[0..], template, args); - if (mem.eql(u8, result, expected)) return; - - std.debug.warn("\n====== expected this output: =========\n", .{}); - std.debug.warn("{}", .{expected}); - std.debug.warn("\n======== instead found this: =========\n", .{}); - std.debug.warn("{}", .{result}); - std.debug.warn("\n======================================\n", .{}); - return error.TestFailed; -} - -pub fn trim(buf: []const u8) []const u8 { - var start: usize = 0; - while (start < buf.len and isWhiteSpace(buf[start])) : (start += 1) {} - - var end: usize = buf.len; - while (true) { - if (end > start) { - const new_end = end - 1; - if (isWhiteSpace(buf[new_end])) { - end = new_end; - continue; - } - } - break; - } - return buf[start..end]; -} - -test "trim" { - std.testing.expect(mem.eql(u8, "abc", trim("\n abc \t"))); - std.testing.expect(mem.eql(u8, "", trim(" "))); - std.testing.expect(mem.eql(u8, "", trim(""))); - std.testing.expect(mem.eql(u8, "abc", trim(" abc"))); - std.testing.expect(mem.eql(u8, "abc", trim("abc "))); -} - -pub fn isWhiteSpace(byte: u8) bool { - return switch (byte) { - ' ', '\t', '\n', '\r' => true, - else => false, - }; -} - -pub fn hexToBytes(out: []u8, input: []const u8) !void { - if (out.len * 2 < input.len) - return error.InvalidLength; - - var in_i: usize = 0; - while (in_i != input.len) : (in_i += 2) { - const hi = try charToDigit(input[in_i], 16); - const lo = try charToDigit(input[in_i + 1], 16); - out[in_i / 2] = (hi << 4) | lo; - } -} - -test "hexToBytes" { - const test_hex_str = "909A312BB12ED1F819B3521AC4C1E896F2160507FFC1C8381E3B07BB16BD1706"; - var pb: [32]u8 = undefined; - try hexToBytes(pb[0..], test_hex_str); - try testFmt(test_hex_str, "{X}", .{pb}); -} - -test "formatIntValue with comptime_int" { - const value: comptime_int = 123456789123456789; - - var buf: [20]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - try formatIntValue(value, "", FormatOptions{}, fbs.outStream()); - std.testing.expect(mem.eql(u8, fbs.getWritten(), "123456789123456789")); -} - -test "formatType max_depth" { - const Vec2 = struct { - const SelfType = @This(); - x: f32, - y: f32, - - pub fn format( - self: SelfType, - comptime fmt: []const u8, - options: FormatOptions, - out_stream: var, - ) !void { - if (fmt.len == 0) { - return std.fmtstream.format(out_stream, "({d:.3},{d:.3})", .{ self.x, self.y }); - } else { - @compileError("Unknown format string: '" ++ fmt ++ "'"); - } - } - }; - const E = enum { - One, - Two, - Three, - }; - const TU = union(enum) { - const SelfType = @This(); - float: f32, - int: u32, - ptr: ?*SelfType, - }; - const S = struct { - const SelfType = @This(); - a: ?*SelfType, - tu: TU, - e: E, - vec: Vec2, - }; - - var inst = S{ - .a = null, - .tu = TU{ .ptr = null }, - .e = E.Two, - .vec = Vec2{ .x = 10.2, .y = 2.22 }, - }; - inst.a = &inst; - inst.tu.ptr = &inst.tu; - - var buf: [1000]u8 = undefined; - var fbs = std.io.fixedBufferStream(&buf); - try formatType(inst, "", FormatOptions{}, fbs.outStream(), 0); - std.testing.expect(mem.eql(u8, fbs.getWritten(), "S{ ... }")); - - fbs.reset(); - try formatType(inst, "", FormatOptions{}, fbs.outStream(), 1); - std.testing.expect(mem.eql(u8, fbs.getWritten(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }")); - - fbs.reset(); - try formatType(inst, "", FormatOptions{}, fbs.outStream(), 2); - std.testing.expect(mem.eql(u8, fbs.getWritten(), "S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }")); - - fbs.reset(); - try formatType(inst, "", FormatOptions{}, fbs.outStream(), 3); - std.testing.expect(mem.eql(u8, fbs.getWritten(), "S{ .a = S{ .a = S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ ... } }, .e = E.Two, .vec = (10.200,2.220) }, .tu = TU{ .ptr = TU{ .ptr = TU{ ... } } }, .e = E.Two, .vec = (10.200,2.220) }")); -} - -test "positional" { - try testFmt("2 1 0", "{2} {1} {0}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) }); - try testFmt("2 1 0", "{2} {1} {}", .{ @as(usize, 0), @as(usize, 1), @as(usize, 2) }); - try testFmt("0 0", "{0} {0}", .{@as(usize, 0)}); - try testFmt("0 1", "{} {1}", .{ @as(usize, 0), @as(usize, 1) }); - try testFmt("1 0 0 1", "{1} {} {0} {}", .{ @as(usize, 0), @as(usize, 1) }); -} - -test "positional with specifier" { - try testFmt("10.0", "{0d:.1}", .{@as(f64, 9.999)}); -} - -test "positional/alignment/width/precision" { - try testFmt("10.0", "{0d: >3.1}", .{@as(f64, 9.999)}); -} - -test "vector" { - // https://github.com/ziglang/zig/issues/3317 - if (builtin.arch == .mipsel) return error.SkipZigTest; - - const vbool: @Vector(4, bool) = [_]bool{ true, false, true, false }; - const vi64: @Vector(4, i64) = [_]i64{ -2, -1, 0, 1 }; - const vu64: @Vector(4, u64) = [_]u64{ 1000, 2000, 3000, 4000 }; - - try testFmt("{ true, false, true, false }", "{}", .{vbool}); - try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64}); - try testFmt("{ - 2, - 1, + 0, + 1 }", "{d:5}", .{vi64}); - try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64}); - try testFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64}); - try testFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64}); - try testFmt("{ 1000B, 1.953125KiB, 2.9296875KiB, 3.90625KiB }", "{Bi}", .{vu64}); -} - -test "enum-literal" { - try testFmt(".hello_world", "{}", .{.hello_world}); -} |
