Renders fmt string with args, calling w with slices of bytes.
The format string must be comptime-known and may contain placeholders following this format:
{[argument][specifier]:[fill][alignment][width].[precision]}
Above, each word including its surrounding [ and ] is a parameter to be replaced with:
{[score]...} as opposed to the numeric index form which can be written e.g. {2...}.Most of the parameters are optional and may be omitted. The separators (':' and '.') may be omitted when all parameters afterwards are omitted.
The fill parameter is an exception. If a non-zero fill character is required at the same time as width is specified, alignment is required, otherwise the digit following ':' is interpreted as width.
specifier supports:
x and X: numeric value in hexadecimal notation, or string in hexadecimal bytess:t:b64: string as standard base64e: floating point value in scientific notationd: numeric value in decimal notationb: integer value in binary notationo: integer value in octal notationc: integer as an ASCII character. Integer type must have 8 bits at max.u: integer as an UTF-8 sequence. Integer type must have 21 bits at max.B: bytes in SI units (decimal)Bi: bytes in IEC units (binary)?: optional value as either the unwrapped value, or null; may be
followed by a format specifier for the underlying value.!: error union value as either the unwrapped value, or the formatted
error value; may be followed by a format specifier for the underlying
value.*: the address of the value instead of the value itself.any: a value of any type using its default format.f: delegates to the format method of the type, passing *Writer and
expecting Error!void returned.A user type may be a struct, vector, union or enum type.
Literal curly braces can be escaped in the format string via doubling, e.g.
{{ or }}.
pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void
pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void {
const ArgsType = @TypeOf(args);
const args_type_info = @typeInfo(ArgsType);
if (args_type_info != .@"struct") {
@compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType));
}
const field_names = args_type_info.@"struct".field_names;
const max_format_args = @typeInfo(std.fmt.ArgSetType).int.bits;
if (field_names.len > max_format_args) {
@compileError("32 arguments max are supported per format call");
}
@setEvalBranchQuota(@as(comptime_int, fmt.len) * 1000); // NOTE: We're upcasting as 16-bit usize overflows.
comptime var arg_state: std.fmt.ArgState = .{ .args_len = field_names.len };
comptime var i = 0;
comptime var literal: []const u8 = "";
inline while (true) {
const start_index = i;
inline while (i < fmt.len) : (i += 1) {
switch (fmt[i]) {
'{', '}' => break,
else => {},
}
}
comptime var end_index = i;
comptime var unescape_brace = false;
// Handle {{ and }}, those are un-escaped as single braces
if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) {
unescape_brace = true;
// Make the first brace part of the literal...
end_index += 1;
// ...and skip both
i += 2;
}
literal = literal ++ fmt[start_index..end_index];
// We've already skipped the other brace, restart the loop
if (unescape_brace) continue;
// Write out the literal
if (literal.len != 0) {
try w.writeAll(literal);
literal = "";
}
if (i >= fmt.len) break;
if (fmt[i] == '}') {
@compileError("missing opening {");
}
// Get past the {
comptime assert(fmt[i] == '{');
i += 1;
const fmt_begin = i;
// Find the closing brace
inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {}
const fmt_end = i;
if (i >= fmt.len) {
@compileError("missing closing }");
}
// Get past the }
comptime assert(fmt[i] == '}');
i += 1;
const placeholder_array = fmt[fmt_begin..fmt_end].*;
const placeholder = comptime std.fmt.Placeholder.parse(&placeholder_array);
const arg_pos = comptime switch (placeholder.arg) {
.none => null,
.number => |pos| pos,
.named => |arg_name| std.meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'"),
};
const width = switch (placeholder.width) {
.none => null,
.number => |v| v,
.named => |arg_name| blk: {
const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'");
_ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
break :blk @field(args, arg_name);
},
};
const precision = switch (placeholder.precision) {
.none => null,
.number => |v| v,
.named => |arg_name| blk: {
const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
@compileError("no argument with name '" ++ arg_name ++ "'");
_ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
break :blk @field(args, arg_name);
},
};
const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
@compileError("too few arguments");
try w.printValue(
placeholder.specifier_arg,
.{
.fill = placeholder.fill,
.alignment = placeholder.alignment,
.width = width,
.precision = precision,
},
@field(args, field_names[arg_to_print]),
std.options.fmt_max_depth,
);
}
if (comptime arg_state.hasUnusedArgs()) {
const missing_count = arg_state.args_len - @popCount(arg_state.used_args);
switch (missing_count) {
0 => unreachable,
1 => @compileError("unused argument in '" ++ fmt ++ "'"),
else => @compileError(std.fmt.comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"),
}
}
}