From d9e601a9984bf01ea3d42f7bf8d11131c9c6b95d Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Thu, 2 Jan 2020 21:28:59 -0600 Subject: [PATCH 01/33] Copy fmt.zig --- lib/std/fmtgen.zig | 1702 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1702 insertions(+) create mode 100644 lib/std/fmtgen.zig diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig new file mode 100644 index 000000000000..711145c130dd --- /dev/null +++ b/lib/std/fmtgen.zig @@ -0,0 +1,1702 @@ +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 nextArg(comptime used_pos_args: *u32, comptime maybe_pos_arg: ?comptime_int, comptime next_arg: *comptime_int) comptime_int { + if (maybe_pos_arg) |pos_arg| { + used_pos_args.* |= 1 << pos_arg; + return pos_arg; + } else { + const arg = next_arg.*; + next_arg.* += 1; + return arg; + } +} + +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.fmt.FormatOptions, context: var, comptime Errors: type, output: fn (@TypeOf(context), []const u8) Errors!void) Errors!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`, `union` or `enum` type. +// pub fn format( +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// comptime fmt: []const u8, +// args: var, +// ) Errors!void { +// const ArgSetType = @IntType(false, 32); +// 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 next_arg = 0; +// comptime var maybe_pos_arg: ?comptime_int = null; +// comptime var used_pos_args: ArgSetType = 0; +// comptime var specifier_start = 0; +// comptime var specifier_end = 0; +// comptime var options = FormatOptions{}; + +// inline for (fmt) |c, i| { +// switch (state) { +// .Start => switch (c) { +// '{' => { +// if (start_index < i) { +// try output(context, 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 output(context, 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 nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + +// if (arg_to_print >= args.len) { +// @compileError("Too few arguments"); +// } + +// try formatType( +// args[arg_to_print], +// fmt[0..0], +// options, +// context, +// Errors, +// output, +// 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 nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + +// try formatType( +// args[arg_to_print], +// fmt[specifier_start..i], +// options, +// context, +// Errors, +// output, +// 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 nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + +// try formatType( +// args[arg_to_print], +// fmt[specifier_start..specifier_end], +// options, +// context, +// Errors, +// output, +// 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 nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + +// try formatType( +// args[arg_to_print], +// fmt[specifier_start..specifier_end], +// options, +// context, +// Errors, +// output, +// default_max_depth, +// ); +// state = .Start; +// start_index = i + 1; +// }, +// else => { +// @compileError("Unexpected character in precision value: " ++ [_]u8{c}); +// }, +// }, +// } +// } +// comptime { +// // All arguments must have been printed but we allow mixing positional and fixed to achieve this. +// var i: usize = 0; +// inline while (i < next_arg) : (i += 1) { +// used_pos_args |= 1 << i; +// } + +// if (@popCount(ArgSetType, used_pos_args) != args.len) { +// @compileError("Unused arguments"); +// } +// if (state != State.Start) { +// @compileError("Incomplete format string: " ++ fmt); +// } +// } +// if (start_index < fmt.len) { +// try output(context, fmt[start_index..]); +// } +// } + +// pub fn formatType( +// value: var, +// comptime fmt: []const u8, +// options: FormatOptions, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// max_depth: usize, +// ) Errors!void { +// if (comptime std.mem.eql(u8, fmt, "*")) { +// try output(context, @typeName(@TypeOf(value).Child)); +// try output(context, "@"); +// try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, context, Errors, output); +// return; +// } + +// const T = @TypeOf(value); +// switch (@typeInfo(T)) { +// .ComptimeInt, .Int, .Float => { +// return formatValue(value, fmt, options, context, Errors, output); +// }, +// .Void => { +// return output(context, "void"); +// }, +// .Bool => { +// return output(context, if (value) "true" else "false"); +// }, +// .Optional => { +// if (value) |payload| { +// return formatType(payload, fmt, options, context, Errors, output, max_depth); +// } else { +// return output(context, "null"); +// } +// }, +// .ErrorUnion => { +// if (value) |payload| { +// return formatType(payload, fmt, options, context, Errors, output, max_depth); +// } else |err| { +// return formatType(err, fmt, options, context, Errors, output, max_depth); +// } +// }, +// .ErrorSet => { +// try output(context, "error."); +// return output(context, @errorName(value)); +// }, +// .Enum => { +// if (comptime std.meta.trait.hasFn("format")(T)) { +// return value.format(fmt, options, context, Errors, output); +// } + +// try output(context, @typeName(T)); +// try output(context, "."); +// return formatType(@tagName(value), "", options, context, Errors, output, max_depth); +// }, +// .Union => { +// if (comptime std.meta.trait.hasFn("format")(T)) { +// return value.format(fmt, options, context, Errors, output); +// } + +// try output(context, @typeName(T)); +// if (max_depth == 0) { +// return output(context, "{ ... }"); +// } +// const info = @typeInfo(T).Union; +// if (info.tag_type) |UnionTagType| { +// try output(context, "{ ."); +// try output(context, @tagName(@as(UnionTagType, value))); +// try output(context, " = "); +// inline for (info.fields) |u_field| { +// if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { +// try formatType(@field(value, u_field.name), "", options, context, Errors, output, max_depth - 1); +// } +// } +// try output(context, " }"); +// } else { +// try format(context, Errors, output, "@{x}", .{@ptrToInt(&value)}); +// } +// }, +// .Struct => { +// if (comptime std.meta.trait.hasFn("format")(T)) { +// return value.format(fmt, options, context, Errors, output); +// } + +// try output(context, @typeName(T)); +// if (max_depth == 0) { +// return output(context, "{ ... }"); +// } +// comptime var field_i = 0; +// try output(context, "{"); +// inline while (field_i < @memberCount(T)) : (field_i += 1) { +// if (field_i == 0) { +// try output(context, " ."); +// } else { +// try output(context, ", ."); +// } +// try output(context, @memberName(T, field_i)); +// try output(context, " = "); +// try formatType(@field(value, @memberName(T, field_i)), "", options, context, Errors, output, max_depth - 1); +// } +// try output(context, " }"); +// }, +// .Pointer => |ptr_info| switch (ptr_info.size) { +// .One => switch (@typeInfo(ptr_info.child)) { +// builtin.TypeId.Array => |info| { +// if (info.child == u8) { +// return formatText(value, fmt, options, context, Errors, output); +// } +// return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); +// }, +// builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { +// return formatType(value.*, fmt, options, context, Errors, output, max_depth); +// }, +// else => return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }), +// }, +// .Many => { +// if (ptr_info.child == u8) { +// if (fmt.len > 0 and fmt[0] == 's') { +// const len = mem.len(u8, value); +// return formatText(value[0..len], fmt, options, context, Errors, output); +// } +// } +// return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); +// }, +// .Slice => { +// if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { +// return formatText(value, fmt, options, context, Errors, output); +// } +// if (ptr_info.child == u8) { +// return formatText(value, fmt, options, context, Errors, output); +// } +// return format(context, Errors, output, "{}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value.ptr) }); +// }, +// .C => { +// return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); +// }, +// }, +// .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, context, Errors, output, max_depth); +// }, +// .Fn => { +// return format(context, Errors, output, "{}@{x}", .{ @typeName(T), @ptrToInt(value) }); +// }, +// .Type => return output(context, @typeName(T)), +// else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), +// } +// } + +// fn formatValue( +// value: var, +// comptime fmt: []const u8, +// options: FormatOptions, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!void { +// if (comptime std.mem.eql(u8, fmt, "B")) { +// return formatBytes(value, options, 1000, context, Errors, output); +// } else if (comptime std.mem.eql(u8, fmt, "Bi")) { +// return formatBytes(value, options, 1024, context, Errors, output); +// } + +// const T = @TypeOf(value); +// switch (@typeId(T)) { +// .Float => return formatFloatValue(value, fmt, options, context, Errors, output), +// .Int, .ComptimeInt => return formatIntValue(value, fmt, options, context, Errors, output), +// else => comptime unreachable, +// } +// } + +// pub fn formatIntValue( +// value: var, +// comptime fmt: []const u8, +// options: FormatOptions, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!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, context, Errors, output); +// } 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, context, Errors, output); +// } + +// fn formatFloatValue( +// value: var, +// comptime fmt: []const u8, +// options: FormatOptions, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!void { +// if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { +// return formatFloatScientific(value, options, context, Errors, output); +// } else if (comptime std.mem.eql(u8, fmt, "d")) { +// return formatFloatDecimal(value, options, context, Errors, output); +// } else { +// @compileError("Unknown format string: '" ++ fmt ++ "'"); +// } +// } + +// pub fn formatText( +// bytes: []const u8, +// comptime fmt: []const u8, +// options: FormatOptions, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!void { +// if (fmt.len == 0) { +// return output(context, bytes); +// } else if (comptime std.mem.eql(u8, fmt, "s")) { +// return formatBuf(bytes, options, context, Errors, output); +// } 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' }, context, Errors, output); +// } +// return; +// } else { +// @compileError("Unknown format string: '" ++ fmt ++ "'"); +// } +// } + +// pub fn formatAsciiChar( +// c: u8, +// options: FormatOptions, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!void { +// if (std.ascii.isPrint(c)) +// return output(context, @as(*const [1]u8, &c)[0..]); +// return format(context, Errors, output, "\\x{x:0<2}", .{c}); +// } + +// pub fn formatBuf( +// buf: []const u8, +// options: FormatOptions, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!void { +// try output(context, 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 output(context, @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, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!void { +// var x = @floatCast(f64, value); + +// // Errol doesn't handle these special cases. +// if (math.signbit(x)) { +// try output(context, "-"); +// x = -x; +// } + +// if (math.isNan(x)) { +// return output(context, "nan"); +// } +// if (math.isPositiveInf(x)) { +// return output(context, "inf"); +// } +// if (x == 0.0) { +// try output(context, "0"); + +// if (options.precision) |precision| { +// if (precision != 0) { +// try output(context, "."); +// var i: usize = 0; +// while (i < precision) : (i += 1) { +// try output(context, "0"); +// } +// } +// } else { +// try output(context, ".0"); +// } + +// try output(context, "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 output(context, float_decimal.digits[0..1]); + +// // {e0} case prints no `.` +// if (precision != 0) { +// try output(context, "."); + +// var printed: usize = 0; +// if (float_decimal.digits.len > 1) { +// const num_digits = math.min(float_decimal.digits.len, precision + 1); +// try output(context, float_decimal.digits[1..num_digits]); +// printed += num_digits - 1; +// } + +// while (printed < precision) : (printed += 1) { +// try output(context, "0"); +// } +// } +// } else { +// try output(context, float_decimal.digits[0..1]); +// try output(context, "."); +// 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 output(context, float_decimal.digits[1..num_digits]); +// } else { +// try output(context, "0"); +// } +// } + +// try output(context, "e"); +// const exp = float_decimal.exp - 1; + +// if (exp >= 0) { +// try output(context, "+"); +// if (exp > -10 and exp < 10) { +// try output(context, "0"); +// } +// try formatInt(exp, 10, false, FormatOptions{ .width = 0 }, context, Errors, output); +// } else { +// try output(context, "-"); +// if (exp > -10 and exp < 10) { +// try output(context, "0"); +// } +// try formatInt(-exp, 10, false, FormatOptions{ .width = 0 }, context, Errors, output); +// } +// } + +// // 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, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!void { +// var x = @as(f64, value); + +// // Errol doesn't handle these special cases. +// if (math.signbit(x)) { +// try output(context, "-"); +// x = -x; +// } + +// if (math.isNan(x)) { +// return output(context, "nan"); +// } +// if (math.isPositiveInf(x)) { +// return output(context, "inf"); +// } +// if (x == 0.0) { +// try output(context, "0"); + +// if (options.precision) |precision| { +// if (precision != 0) { +// try output(context, "."); +// var i: usize = 0; +// while (i < precision) : (i += 1) { +// try output(context, "0"); +// } +// } else { +// try output(context, ".0"); +// } +// } else { +// try output(context, "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 output(context, float_decimal.digits[0..num_digits_whole_no_pad]); + +// var i = num_digits_whole_no_pad; +// while (i < num_digits_whole) : (i += 1) { +// try output(context, "0"); +// } +// } else { +// try output(context, "0"); +// } + +// // {.0} special case doesn't want a trailing '.' +// if (precision == 0) { +// return; +// } + +// try output(context, "."); + +// // 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 output(context, "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 output(context, float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]); +// return; +// } else { +// try output(context, float_decimal.digits[num_digits_whole_no_pad..]); +// printed += float_decimal.digits.len - num_digits_whole_no_pad; + +// while (printed < precision) : (printed += 1) { +// try output(context, "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 output(context, float_decimal.digits[0..num_digits_whole_no_pad]); + +// var i = num_digits_whole_no_pad; +// while (i < num_digits_whole) : (i += 1) { +// try output(context, "0"); +// } +// } else { +// try output(context, "0"); +// } + +// // Omit `.` if no fractional portion +// if (float_decimal.exp >= 0 and num_digits_whole_no_pad == float_decimal.digits.len) { +// return; +// } + +// try output(context, "."); + +// // 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 output(context, "0"); +// } +// } + +// try output(context, float_decimal.digits[num_digits_whole_no_pad..]); +// } +// } + +// pub fn formatBytes( +// value: var, +// options: FormatOptions, +// comptime radix: usize, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!void { +// if (value == 0) { +// return output(context, "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, context, Errors, output); + +// if (suffix == ' ') { +// return output(context, "B"); +// } + +// const buf = switch (radix) { +// 1000 => &[_]u8{ suffix, 'B' }, +// 1024 => &[_]u8{ suffix, 'i', 'B' }, +// else => unreachable, +// }; +// return output(context, buf); +// } + +// pub fn formatInt( +// value: var, +// base: u8, +// uppercase: bool, +// options: FormatOptions, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!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, context, Errors, output); +// } else { +// return formatIntUnsigned(int_value, base, uppercase, options, context, Errors, output); +// } +// } + +// fn formatIntSigned( +// value: var, +// base: u8, +// uppercase: bool, +// options: FormatOptions, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!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 uint = @IntType(false, @TypeOf(value).bit_count); +// if (value < 0) { +// const minus_sign: u8 = '-'; +// try output(context, @as(*const [1]u8, &minus_sign)[0..]); +// const new_value = @intCast(uint, -(value + 1)) + 1; +// return formatIntUnsigned(new_value, base, uppercase, new_options, context, Errors, output); +// } else if (options.width == null or options.width.? == 0) { +// return formatIntUnsigned(@intCast(uint, value), base, uppercase, options, context, Errors, output); +// } else { +// const plus_sign: u8 = '+'; +// try output(context, @as(*const [1]u8, &plus_sign)[0..]); +// const new_value = @intCast(uint, value); +// return formatIntUnsigned(new_value, base, uppercase, new_options, context, Errors, output); +// } +// } + +// fn formatIntUnsigned( +// value: var, +// base: u8, +// uppercase: bool, +// options: FormatOptions, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!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 = @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 output(context, @as(*const [1]u8, &zero_byte)[0..]); +// leftover_padding -= 1; +// if (leftover_padding == 0) break; +// } +// mem.set(u8, buf[0..index], options.fill); +// return output(context, &buf); +// } else { +// const padded_buf = buf[index - padding ..]; +// mem.set(u8, padded_buf[0..padding], options.fill); +// return output(context, padded_buf); +// } +// } + +// pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) usize { +// var context = FormatIntBuf{ +// .out_buf = out_buf, +// .index = 0, +// }; +// formatInt(value, base, uppercase, options, &context, error{}, formatIntCallback) catch unreachable; +// return context.index; +// } +// const FormatIntBuf = struct { +// out_buf: []u8, +// index: usize, +// }; +// fn formatIntCallback(context: *FormatIntBuf, bytes: []const u8) (error{}!void) { +// mem.copy(u8, context.out_buf[context.index..], bytes); +// context.index += bytes.len; +// } + +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, + }; +} + +// const BufPrintContext = struct { +// remaining: []u8, +// }; + +// fn bufPrintWrite(context: *BufPrintContext, bytes: []const u8) !void { +// if (context.remaining.len < bytes.len) { +// mem.copy(u8, context.remaining, bytes[0..context.remaining.len]); +// return error.BufferTooSmall; +// } +// mem.copy(u8, context.remaining, bytes); +// context.remaining = context.remaining[bytes.len..]; +// } + +// 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 context = BufPrintContext{ .remaining = buf }; +// try format(&context, BufPrintError, bufPrintWrite, fmt, args); +// return buf[0 .. buf.len - context.remaining.len]; +// } + +// pub const AllocPrintError = error{OutOfMemory}; + +// pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: var) AllocPrintError![]u8 { +// var size: usize = 0; +// format(&size, error{}, countSize, fmt, args) catch |err| switch (err) {}; +// 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 +// }; +// } + +// fn countSize(size: *usize, bytes: []const u8) (error{}!void) { +// size.* += bytes.len; +// } + +// test "bufPrintInt" { +// var buffer: [100]u8 = undefined; +// const buf = buffer[0..]; +// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 2, false, FormatOptions{}), "-101111000110000101001110")); +// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 10, false, FormatOptions{}), "-12345678")); +// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 16, false, FormatOptions{}), "-bc614e")); +// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 16, true, FormatOptions{}), "-BC614E")); + +// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 12345678), 10, true, FormatOptions{}), "12345678")); + +// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 666), 10, false, FormatOptions{ .width = 6 }), " 666")); +// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, false, FormatOptions{ .width = 6 }), " 1234")); +// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, false, FormatOptions{ .width = 1 }), "1234")); + +// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, 42), 10, false, FormatOptions{ .width = 3 }), "+42")); +// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -42), 10, false, FormatOptions{ .width = 3 }), "-42")); +// } + +// 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 context = BufPrintContext{ .remaining = buf1[0..] }; +// try formatType(1234, "", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); +// var res = buf1[0 .. buf1.len - context.remaining.len]; +// std.testing.expect(mem.eql(u8, res, "1234")); + +// context = BufPrintContext{ .remaining = buf1[0..] }; +// try formatType('a', "c", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); +// res = buf1[0 .. buf1.len - context.remaining.len]; +// std.testing.expect(mem.eql(u8, res, "a")); + +// context = BufPrintContext{ .remaining = buf1[0..] }; +// try formatType(0b1100, "b", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); +// res = buf1[0 .. buf1.len - context.remaining.len]; +// std.testing.expect(mem.eql(u8, res, "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", .{"Test C"}); +// try testFmt("cstr: Test C \n", "cstr: {s:10}\n", .{"Test C"}); +// } + +// test "filesize" { +// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { +// // TODO https://github.com/ziglang/zig/issues/3289 +// return error.SkipZigTest; +// } +// 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 "float.scientific" { +// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { +// // TODO https://github.com/ziglang/zig/issues/3289 +// return error.SkipZigTest; +// } +// 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" { +// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { +// // TODO https://github.com/ziglang/zig/issues/3289 +// return error.SkipZigTest; +// } +// 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" { +// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { +// // TODO https://github.com/ziglang/zig/issues/3289 +// return error.SkipZigTest; +// } +// 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" { +// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { +// // TODO https://github.com/ziglang/zig/issues/3289 +// return error.SkipZigTest; +// } +// try testFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)}); +// 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" { +// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { +// // TODO https://github.com/ziglang/zig/issues/3289 +// return error.SkipZigTest; +// } +// 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, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!void { +// if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { +// return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y }); +// } else if (comptime std.mem.eql(u8, fmt, "d")) { +// return std.fmt.format(context, Errors, output, "{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 = try std.Buffer.init(std.debug.global_allocator, ""); +// try formatIntValue(value, "", FormatOptions{}, &buf, @TypeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append); +// std.testing.expect(mem.eql(u8, buf.toSlice(), "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, +// context: var, +// comptime Errors: type, +// output: fn (@TypeOf(context), []const u8) Errors!void, +// ) Errors!void { +// if (fmt.len == 0) { +// return std.fmt.format(context, Errors, output, "({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 buf0 = try std.Buffer.init(std.debug.global_allocator, ""); +// try formatType(inst, "", FormatOptions{}, &buf0, @TypeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 0); +// std.testing.expect(mem.eql(u8, buf0.toSlice(), "S{ ... }")); + +// var buf1 = try std.Buffer.init(std.debug.global_allocator, ""); +// try formatType(inst, "", FormatOptions{}, &buf1, @TypeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 1); +// std.testing.expect(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }")); + +// var buf2 = try std.Buffer.init(std.debug.global_allocator, ""); +// try formatType(inst, "", FormatOptions{}, &buf2, @TypeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 2); +// std.testing.expect(mem.eql(u8, buf2.toSlice(), "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) }")); + +// var buf3 = try std.Buffer.init(std.debug.global_allocator, ""); +// try formatType(inst, "", FormatOptions{}, &buf3, @TypeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 3); +// std.testing.expect(mem.eql(u8, buf3.toSlice(), "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)}); +// } From f81822382abafb0295a9fb7b27ed572940bf6c57 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Thu, 2 Jan 2020 21:36:59 -0600 Subject: [PATCH 02/33] Toy with generator based format --- lib/std/fmtgen.zig | 635 ++++++++++++++++++++++++--------------------- 1 file changed, 334 insertions(+), 301 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 711145c130dd..a9399955bb79 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -21,6 +21,38 @@ pub const FormatOptions = struct { fill: u8 = ' ', }; +pub fn Generator(comptime Out: type) type { + return struct { + suspended: ?anyframe = null, + out: ?Out = undefined, + fresh: bool = true, + + pub fn yield(self: *@This(), out: Out) void { + assert(self.suspended == null); + assert(self.out == null); + self.out = out; + self.suspended = @frame(); + suspend; + } + + pub fn next(self: *@This()) ?Out { + if (self.fresh) { + self.fresh = false; + return self.out; + } else if (self.suspended) |suspended| { + // Copy elision... bug? + const copy = suspended; + self.suspended = null; + self.out = null; + resume copy; + return self.out; + } else { + return null; + } + } + }; +} + fn nextArg(comptime used_pos_args: *u32, comptime maybe_pos_arg: ?comptime_int, comptime next_arg: *comptime_int) comptime_int { if (maybe_pos_arg) |pos_arg| { used_pos_args.* |= 1 << pos_arg; @@ -44,278 +76,276 @@ fn peekIsAlign(comptime fmt: []const u8) bool { 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.fmt.FormatOptions, context: var, comptime Errors: type, output: fn (@TypeOf(context), []const u8) Errors!void) Errors!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`, `union` or `enum` type. -// pub fn format( -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// comptime fmt: []const u8, -// args: var, -// ) Errors!void { -// const ArgSetType = @IntType(false, 32); -// 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 next_arg = 0; -// comptime var maybe_pos_arg: ?comptime_int = null; -// comptime var used_pos_args: ArgSetType = 0; -// comptime var specifier_start = 0; -// comptime var specifier_end = 0; -// comptime var options = FormatOptions{}; - -// inline for (fmt) |c, i| { -// switch (state) { -// .Start => switch (c) { -// '{' => { -// if (start_index < i) { -// try output(context, 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 output(context, 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 nextArg(&used_pos_args, maybe_pos_arg, &next_arg); - -// if (arg_to_print >= args.len) { -// @compileError("Too few arguments"); -// } - -// try formatType( -// args[arg_to_print], -// fmt[0..0], -// options, -// context, -// Errors, -// output, -// 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 nextArg(&used_pos_args, maybe_pos_arg, &next_arg); - -// try formatType( -// args[arg_to_print], -// fmt[specifier_start..i], -// options, -// context, -// Errors, -// output, -// 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; -// } +/// 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.fmt.FormatOptions, context: var, comptime Errors: type, output: fn (@TypeOf(context), []const u8) Errors!void) Errors!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`, `union` or `enum` type. +pub fn format( + generator: *Generator([]const u8), + comptime fmt: []const u8, + args: var, +) void { + const ArgSetType = @IntType(false, 32); + 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"); + } -// options.width.? *= 10; -// options.width.? += c - '0'; -// }, -// '.' => { -// state = .FormatPrecision; -// }, -// '}' => { -// const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); - -// try formatType( -// args[arg_to_print], -// fmt[specifier_start..specifier_end], -// options, -// context, -// Errors, -// output, -// 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; -// } + const State = enum { + Start, + Positional, + CloseBrace, + Specifier, + FormatFillAndAlign, + FormatWidth, + FormatPrecision, + }; -// options.precision.? *= 10; -// options.precision.? += c - '0'; -// }, -// '}' => { -// const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); - -// try formatType( -// args[arg_to_print], -// fmt[specifier_start..specifier_end], -// options, -// context, -// Errors, -// output, -// default_max_depth, -// ); -// state = .Start; -// start_index = i + 1; -// }, -// else => { -// @compileError("Unexpected character in precision value: " ++ [_]u8{c}); -// }, -// }, -// } -// } -// comptime { -// // All arguments must have been printed but we allow mixing positional and fixed to achieve this. -// var i: usize = 0; -// inline while (i < next_arg) : (i += 1) { -// used_pos_args |= 1 << i; -// } + comptime var start_index = 0; + comptime var state = State.Start; + comptime var next_arg = 0; + comptime var maybe_pos_arg: ?comptime_int = null; + comptime var used_pos_args: ArgSetType = 0; + comptime var specifier_start = 0; + comptime var specifier_end = 0; + comptime var options = FormatOptions{}; + + inline for (fmt) |c, i| { + switch (state) { + .Start => switch (c) { + '{' => { + if (start_index < i) { + generator.yield(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) { + generator.yield(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 nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + + if (arg_to_print >= args.len) { + @compileError("Too few arguments"); + } + + // try formatType( + // args[arg_to_print], + // fmt[0..0], + // options, + // generator, + // error{}, + // output, + // 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 nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + + // try formatType( + // args[arg_to_print], + // fmt[specifier_start..i], + // options, + // generator, + // error{}, + // output, + // 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 nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + + // try formatType( + // args[arg_to_print], + // fmt[specifier_start..specifier_end], + // options, + // generator, + // error{}, + // output, + // 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 nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + + // try formatType( + // args[arg_to_print], + // fmt[specifier_start..specifier_end], + // options, + // generator, + // error{}, + // output, + // default_max_depth, + // ); + state = .Start; + start_index = i + 1; + }, + else => { + @compileError("Unexpected character in precision value: " ++ [_]u8{c}); + }, + }, + } + } + comptime { + // All arguments must have been printed but we allow mixing positional and fixed to achieve this. + var i: usize = 0; + inline while (i < next_arg) : (i += 1) { + used_pos_args |= 1 << i; + } -// if (@popCount(ArgSetType, used_pos_args) != args.len) { -// @compileError("Unused arguments"); -// } -// if (state != State.Start) { -// @compileError("Incomplete format string: " ++ fmt); -// } -// } -// if (start_index < fmt.len) { -// try output(context, fmt[start_index..]); -// } -// } + if (@popCount(ArgSetType, used_pos_args) != args.len) { + @compileError("Unused arguments"); + } + if (state != State.Start) { + @compileError("Incomplete format string: " ++ fmt); + } + } + if (start_index < fmt.len) { + generator.yield(fmt[start_index..]); + } +} // pub fn formatType( // value: var, @@ -1102,43 +1132,46 @@ fn digitToChar(digit: u8, uppercase: bool) u8 { }; } -// const BufPrintContext = struct { -// remaining: []u8, -// }; +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 remaining = buf; + + var generator = Generator([]const u8){}; + _ = async format(&generator, fmt, args); + while (generator.next()) |bytes| { + if (remaining.len < bytes.len) { + mem.copy(u8, remaining, bytes[0..remaining.len]); + return error.BufferTooSmall; + } + mem.copy(u8, remaining, bytes); + remaining = remaining[bytes.len..]; + } + return buf[0 .. buf.len - remaining.len]; +} -// fn bufPrintWrite(context: *BufPrintContext, bytes: []const u8) !void { -// if (context.remaining.len < bytes.len) { -// mem.copy(u8, context.remaining, bytes[0..context.remaining.len]); -// return error.BufferTooSmall; -// } -// mem.copy(u8, context.remaining, bytes); -// context.remaining = context.remaining[bytes.len..]; -// } +pub const AllocPrintError = error{OutOfMemory}; -// 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 context = BufPrintContext{ .remaining = buf }; -// try format(&context, BufPrintError, bufPrintWrite, fmt, args); -// return buf[0 .. buf.len - context.remaining.len]; -// } +pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: var) AllocPrintError![]u8 { + const buf = try allocator.alloc(u8, countSize(fmt, args)); + return bufPrint(buf, fmt, args) catch |err| switch (err) { + error.BufferTooSmall => unreachable, // we just counted the size above + }; +} -// pub const AllocPrintError = error{OutOfMemory}; +fn countSize(comptime fmt: []const u8, args: var) usize { + var size: usize = 0; -// pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: var) AllocPrintError![]u8 { -// var size: usize = 0; -// format(&size, error{}, countSize, fmt, args) catch |err| switch (err) {}; -// 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 -// }; -// } + var generator = Generator([]const u8){}; + _ = async format(&generator, fmt, args); + while (generator.next()) |bytes| { + size += bytes.len; + } -// fn countSize(size: *usize, bytes: []const u8) (error{}!void) { -// size.* += bytes.len; -// } + return size; +} // test "bufPrintInt" { // var buffer: [100]u8 = undefined; From bcbcd3b276369aacbb41d14de0a54c200cb8279a Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Thu, 2 Jan 2020 21:13:03 -0600 Subject: [PATCH 03/33] Translate formatInt* --- lib/std/fmtgen.zig | 239 +++++++++++++++++++++------------------------ 1 file changed, 114 insertions(+), 125 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index a9399955bb79..8c26ed230acd 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -920,120 +920,109 @@ pub fn format( // return output(context, buf); // } -// pub fn formatInt( -// value: var, -// base: u8, -// uppercase: bool, -// options: FormatOptions, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!void { -// const int_value = if (@TypeOf(value) == comptime_int) blk: { -// const Int = math.IntFittingRange(value, value); -// break :blk @as(Int, value); -// } else -// value; +pub fn formatInt( + value: var, + base: u8, + uppercase: bool, + options: FormatOptions, + generator: *Generator([]const u8), +) 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, generator); + } else { + return formatIntUnsigned(int_value, base, uppercase, options, generator); + } +} -// if (@TypeOf(int_value).is_signed) { -// return formatIntSigned(int_value, base, uppercase, options, context, Errors, output); -// } else { -// return formatIntUnsigned(int_value, base, uppercase, options, context, Errors, output); -// } -// } +fn formatIntSigned( + value: var, + base: u8, + uppercase: bool, + options: FormatOptions, + generator: *Generator([]const u8), +) 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, + }; -// fn formatIntSigned( -// value: var, -// base: u8, -// uppercase: bool, -// options: FormatOptions, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!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 uint = @IntType(false, @TypeOf(value).bit_count); + if (value < 0) { + const minus_sign: u8 = '-'; + generator.yield(@as(*const [1]u8, &minus_sign)[0..]); + const new_value = @intCast(uint, -(value + 1)) + 1; + return formatIntUnsigned(new_value, base, uppercase, new_options, generator); + } else if (options.width == null or options.width.? == 0) { + return formatIntUnsigned(@intCast(uint, value), base, uppercase, options, generator); + } else { + const plus_sign: u8 = '+'; + generator.yield(@as(*const [1]u8, &plus_sign)[0..]); + const new_value = @intCast(uint, value); + return formatIntUnsigned(new_value, base, uppercase, new_options, generator); + } +} -// const uint = @IntType(false, @TypeOf(value).bit_count); -// if (value < 0) { -// const minus_sign: u8 = '-'; -// try output(context, @as(*const [1]u8, &minus_sign)[0..]); -// const new_value = @intCast(uint, -(value + 1)) + 1; -// return formatIntUnsigned(new_value, base, uppercase, new_options, context, Errors, output); -// } else if (options.width == null or options.width.? == 0) { -// return formatIntUnsigned(@intCast(uint, value), base, uppercase, options, context, Errors, output); -// } else { -// const plus_sign: u8 = '+'; -// try output(context, @as(*const [1]u8, &plus_sign)[0..]); -// const new_value = @intCast(uint, value); -// return formatIntUnsigned(new_value, base, uppercase, new_options, context, Errors, output); -// } -// } +fn formatIntUnsigned( + value: var, + base: u8, + uppercase: bool, + options: FormatOptions, + generator: *Generator([]const u8), +) 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 = @IntType(@TypeOf(value).is_signed, min_int_bits); + var a: MinInt = value; + var index: usize = buf.len; -// fn formatIntUnsigned( -// value: var, -// base: u8, -// uppercase: bool, -// options: FormatOptions, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!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 = @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; -// } + 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 output(context, @as(*const [1]u8, &zero_byte)[0..]); -// leftover_padding -= 1; -// if (leftover_padding == 0) break; -// } -// mem.set(u8, buf[0..index], options.fill); -// return output(context, &buf); -// } else { -// const padded_buf = buf[index - padding ..]; -// mem.set(u8, padded_buf[0..padding], options.fill); -// return output(context, padded_buf); -// } -// } + 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) { + generator.yield(@as(*const [1]u8, &zero_byte)[0..]); + leftover_padding -= 1; + if (leftover_padding == 0) break; + } + mem.set(u8, buf[0..index], options.fill); + return generator.yield(&buf); + } else { + const padded_buf = buf[index - padding ..]; + mem.set(u8, padded_buf[0..padding], options.fill); + return generator.yield(padded_buf); + } +} -// pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) usize { -// var context = FormatIntBuf{ -// .out_buf = out_buf, -// .index = 0, -// }; -// formatInt(value, base, uppercase, options, &context, error{}, formatIntCallback) catch unreachable; -// return context.index; -// } -// const FormatIntBuf = struct { -// out_buf: []u8, -// index: usize, -// }; -// fn formatIntCallback(context: *FormatIntBuf, bytes: []const u8) (error{}!void) { -// mem.copy(u8, context.out_buf[context.index..], bytes); -// context.index += bytes.len; -// } +pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) usize { + var index: usize = 0; + + var generator = Generator([]const u8){}; + _ = async formatInt(value, base, uppercase, options, &generator); + while (generator.next()) |bytes| { + mem.copy(u8, out_buf[index..], bytes); + index += bytes.len; + } + return index; +} pub fn parseInt(comptime T: type, buf: []const u8, radix: u8) !T { if (!T.is_signed) return parseUnsigned(T, buf, radix); @@ -1173,27 +1162,27 @@ fn countSize(comptime fmt: []const u8, args: var) usize { return size; } -// test "bufPrintInt" { -// var buffer: [100]u8 = undefined; -// const buf = buffer[0..]; -// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 2, false, FormatOptions{}), "-101111000110000101001110")); -// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 10, false, FormatOptions{}), "-12345678")); -// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 16, false, FormatOptions{}), "-bc614e")); -// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 16, true, FormatOptions{}), "-BC614E")); +test "bufPrintInt" { + var buffer: [100]u8 = undefined; + const buf = buffer[0..]; + std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 2, false, FormatOptions{}), "-101111000110000101001110")); + std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 10, false, FormatOptions{}), "-12345678")); + std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 16, false, FormatOptions{}), "-bc614e")); + std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -12345678), 16, true, FormatOptions{}), "-BC614E")); -// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 12345678), 10, true, FormatOptions{}), "12345678")); + std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 12345678), 10, true, FormatOptions{}), "12345678")); -// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 666), 10, false, FormatOptions{ .width = 6 }), " 666")); -// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, false, FormatOptions{ .width = 6 }), " 1234")); -// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, false, FormatOptions{ .width = 1 }), "1234")); + std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 666), 10, false, FormatOptions{ .width = 6 }), " 666")); + std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, false, FormatOptions{ .width = 6 }), " 1234")); + std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(u32, 0x1234), 16, false, FormatOptions{ .width = 1 }), "1234")); -// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, 42), 10, false, FormatOptions{ .width = 3 }), "+42")); -// std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -42), 10, false, FormatOptions{ .width = 3 }), "-42")); -// } + std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, 42), 10, false, FormatOptions{ .width = 3 }), "+42")); + std.testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, @as(i32, -42), 10, false, FormatOptions{ .width = 3 }), "-42")); +} -// fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) []u8 { -// return buf[0..formatIntBuf(buf, value, base, uppercase, options)]; -// } +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| { From 1e20c102766eb658abd8952f9e1f526ab3d080f3 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Thu, 2 Jan 2020 21:51:22 -0600 Subject: [PATCH 04/33] Re-enable formatInt --- lib/std/fmtgen.zig | 591 ++++++++++++++++++++++----------------------- 1 file changed, 290 insertions(+), 301 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 8c26ed230acd..197ca4160523 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -202,15 +202,13 @@ pub fn format( @compileError("Too few arguments"); } - // try formatType( - // args[arg_to_print], - // fmt[0..0], - // options, - // generator, - // error{}, - // output, - // default_max_depth, - // ); + formatType( + args[arg_to_print], + fmt[0..0], + options, + generator, + default_max_depth, + ); state = .Start; start_index = i + 1; @@ -235,15 +233,13 @@ pub fn format( '}' => { const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); - // try formatType( - // args[arg_to_print], - // fmt[specifier_start..i], - // options, - // generator, - // error{}, - // output, - // default_max_depth, - // ); + formatType( + args[arg_to_print], + fmt[specifier_start..i], + options, + generator, + default_max_depth, + ); state = .Start; start_index = i + 1; }, @@ -282,15 +278,13 @@ pub fn format( '}' => { const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); - // try formatType( - // args[arg_to_print], - // fmt[specifier_start..specifier_end], - // options, - // generator, - // error{}, - // output, - // default_max_depth, - // ); + formatType( + args[arg_to_print], + fmt[specifier_start..specifier_end], + options, + generator, + default_max_depth, + ); state = .Start; start_index = i + 1; }, @@ -310,15 +304,13 @@ pub fn format( '}' => { const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); - // try formatType( - // args[arg_to_print], - // fmt[specifier_start..specifier_end], - // options, - // generator, - // error{}, - // output, - // default_max_depth, - // ); + formatType( + args[arg_to_print], + fmt[specifier_start..specifier_end], + options, + generator, + default_max_depth, + ); state = .Start; start_index = i + 1; }, @@ -347,227 +339,220 @@ pub fn format( } } -// pub fn formatType( -// value: var, -// comptime fmt: []const u8, -// options: FormatOptions, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// max_depth: usize, -// ) Errors!void { -// if (comptime std.mem.eql(u8, fmt, "*")) { -// try output(context, @typeName(@TypeOf(value).Child)); -// try output(context, "@"); -// try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, context, Errors, output); -// return; -// } - -// const T = @TypeOf(value); -// switch (@typeInfo(T)) { -// .ComptimeInt, .Int, .Float => { -// return formatValue(value, fmt, options, context, Errors, output); -// }, -// .Void => { -// return output(context, "void"); -// }, -// .Bool => { -// return output(context, if (value) "true" else "false"); -// }, -// .Optional => { -// if (value) |payload| { -// return formatType(payload, fmt, options, context, Errors, output, max_depth); -// } else { -// return output(context, "null"); -// } -// }, -// .ErrorUnion => { -// if (value) |payload| { -// return formatType(payload, fmt, options, context, Errors, output, max_depth); -// } else |err| { -// return formatType(err, fmt, options, context, Errors, output, max_depth); -// } -// }, -// .ErrorSet => { -// try output(context, "error."); -// return output(context, @errorName(value)); -// }, -// .Enum => { -// if (comptime std.meta.trait.hasFn("format")(T)) { -// return value.format(fmt, options, context, Errors, output); -// } - -// try output(context, @typeName(T)); -// try output(context, "."); -// return formatType(@tagName(value), "", options, context, Errors, output, max_depth); -// }, -// .Union => { -// if (comptime std.meta.trait.hasFn("format")(T)) { -// return value.format(fmt, options, context, Errors, output); -// } - -// try output(context, @typeName(T)); -// if (max_depth == 0) { -// return output(context, "{ ... }"); -// } -// const info = @typeInfo(T).Union; -// if (info.tag_type) |UnionTagType| { -// try output(context, "{ ."); -// try output(context, @tagName(@as(UnionTagType, value))); -// try output(context, " = "); -// inline for (info.fields) |u_field| { -// if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { -// try formatType(@field(value, u_field.name), "", options, context, Errors, output, max_depth - 1); -// } -// } -// try output(context, " }"); -// } else { -// try format(context, Errors, output, "@{x}", .{@ptrToInt(&value)}); -// } -// }, -// .Struct => { -// if (comptime std.meta.trait.hasFn("format")(T)) { -// return value.format(fmt, options, context, Errors, output); -// } +pub fn formatType( + value: var, + comptime fmt: []const u8, + options: FormatOptions, + generator: *Generator([]const u8), + max_depth: usize, +) void { + // if (comptime std.mem.eql(u8, fmt, "*")) { + // try output(context, @typeName(@TypeOf(value).Child)); + // try output(context, "@"); + // try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, context, Errors, output); + // return; + // } + const T = @TypeOf(value); + switch (@typeInfo(T)) { + .ComptimeInt, .Int, .Float => { + return formatValue(value, fmt, options, generator); + }, + // .Void => { + // return output(context, "void"); + // }, + // .Bool => { + // return output(context, if (value) "true" else "false"); + // }, + // .Optional => { + // if (value) |payload| { + // return formatType(payload, fmt, options, context, Errors, output, max_depth); + // } else { + // return output(context, "null"); + // } + // }, + // .ErrorUnion => { + // if (value) |payload| { + // return formatType(payload, fmt, options, context, Errors, output, max_depth); + // } else |err| { + // return formatType(err, fmt, options, context, Errors, output, max_depth); + // } + // }, + // .ErrorSet => { + // try output(context, "error."); + // return output(context, @errorName(value)); + // }, + // .Enum => { + // if (comptime std.meta.trait.hasFn("format")(T)) { + // return value.format(fmt, options, context, Errors, output); + // } + + // try output(context, @typeName(T)); + // try output(context, "."); + // return formatType(@tagName(value), "", options, context, Errors, output, max_depth); + // }, + // .Union => { + // if (comptime std.meta.trait.hasFn("format")(T)) { + // return value.format(fmt, options, context, Errors, output); + // } + + // try output(context, @typeName(T)); + // if (max_depth == 0) { + // return output(context, "{ ... }"); + // } + // const info = @typeInfo(T).Union; + // if (info.tag_type) |UnionTagType| { + // try output(context, "{ ."); + // try output(context, @tagName(@as(UnionTagType, value))); + // try output(context, " = "); + // inline for (info.fields) |u_field| { + // if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { + // try formatType(@field(value, u_field.name), "", options, context, Errors, output, max_depth - 1); + // } + // } + // try output(context, " }"); + // } else { + // try format(context, Errors, output, "@{x}", .{@ptrToInt(&value)}); + // } + // }, + // .Struct => { + // if (comptime std.meta.trait.hasFn("format")(T)) { + // return value.format(fmt, options, context, Errors, output); + // } + + // try output(context, @typeName(T)); + // if (max_depth == 0) { + // return output(context, "{ ... }"); + // } + // comptime var field_i = 0; + // try output(context, "{"); + // inline while (field_i < @memberCount(T)) : (field_i += 1) { + // if (field_i == 0) { + // try output(context, " ."); + // } else { + // try output(context, ", ."); + // } + // try output(context, @memberName(T, field_i)); + // try output(context, " = "); + // try formatType(@field(value, @memberName(T, field_i)), "", options, context, Errors, output, max_depth - 1); + // } + // try output(context, " }"); + // }, + // .Pointer => |ptr_info| switch (ptr_info.size) { + // .One => switch (@typeInfo(ptr_info.child)) { + // builtin.TypeId.Array => |info| { + // if (info.child == u8) { + // return formatText(value, fmt, options, context, Errors, output); + // } + // return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); + // }, + // builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { + // return formatType(value.*, fmt, options, context, Errors, output, max_depth); + // }, + // else => return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }), + // }, + // .Many => { + // if (ptr_info.child == u8) { + // if (fmt.len > 0 and fmt[0] == 's') { + // const len = mem.len(u8, value); + // return formatText(value[0..len], fmt, options, context, Errors, output); + // } + // } + // return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); + // }, + // .Slice => { + // if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { + // return formatText(value, fmt, options, context, Errors, output); + // } + // if (ptr_info.child == u8) { + // return formatText(value, fmt, options, context, Errors, output); + // } + // return format(context, Errors, output, "{}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value.ptr) }); + // }, + // .C => { + // return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); + // }, + // }, + // .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, context, Errors, output, max_depth); + // }, + // .Fn => { + // return format(context, Errors, output, "{}@{x}", .{ @typeName(T), @ptrToInt(value) }); + // }, + // .Type => return output(context, @typeName(T)), + // else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), + else => {}, + } +} -// try output(context, @typeName(T)); -// if (max_depth == 0) { -// return output(context, "{ ... }"); -// } -// comptime var field_i = 0; -// try output(context, "{"); -// inline while (field_i < @memberCount(T)) : (field_i += 1) { -// if (field_i == 0) { -// try output(context, " ."); -// } else { -// try output(context, ", ."); -// } -// try output(context, @memberName(T, field_i)); -// try output(context, " = "); -// try formatType(@field(value, @memberName(T, field_i)), "", options, context, Errors, output, max_depth - 1); -// } -// try output(context, " }"); -// }, -// .Pointer => |ptr_info| switch (ptr_info.size) { -// .One => switch (@typeInfo(ptr_info.child)) { -// builtin.TypeId.Array => |info| { -// if (info.child == u8) { -// return formatText(value, fmt, options, context, Errors, output); -// } -// return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); -// }, -// builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { -// return formatType(value.*, fmt, options, context, Errors, output, max_depth); -// }, -// else => return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }), -// }, -// .Many => { -// if (ptr_info.child == u8) { -// if (fmt.len > 0 and fmt[0] == 's') { -// const len = mem.len(u8, value); -// return formatText(value[0..len], fmt, options, context, Errors, output); -// } -// } -// return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); -// }, -// .Slice => { -// if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { -// return formatText(value, fmt, options, context, Errors, output); -// } -// if (ptr_info.child == u8) { -// return formatText(value, fmt, options, context, Errors, output); -// } -// return format(context, Errors, output, "{}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value.ptr) }); -// }, -// .C => { -// return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); -// }, -// }, -// .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, context, Errors, output, max_depth); -// }, -// .Fn => { -// return format(context, Errors, output, "{}@{x}", .{ @typeName(T), @ptrToInt(value) }); -// }, -// .Type => return output(context, @typeName(T)), -// else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), -// } -// } +fn formatValue( + value: var, + comptime fmt: []const u8, + options: FormatOptions, + generator: *Generator([]const u8), +) void { + // if (comptime std.mem.eql(u8, fmt, "B")) { + // return formatBytes(value, options, 1000, context, Errors, output); + // } else if (comptime std.mem.eql(u8, fmt, "Bi")) { + // return formatBytes(value, options, 1024, context, Errors, output); + // } + const T = @TypeOf(value); + switch (@typeId(T)) { + // .Float => return formatFloatValue(value, fmt, options, context, Errors, output), + .Int, .ComptimeInt => return formatIntValue(value, fmt, options, generator), + else => comptime unreachable, + } +} -// fn formatValue( -// value: var, -// comptime fmt: []const u8, -// options: FormatOptions, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!void { -// if (comptime std.mem.eql(u8, fmt, "B")) { -// return formatBytes(value, options, 1000, context, Errors, output); -// } else if (comptime std.mem.eql(u8, fmt, "Bi")) { -// return formatBytes(value, options, 1024, context, Errors, output); -// } +pub fn formatIntValue( + value: var, + comptime fmt: []const u8, + options: FormatOptions, + generator: *Generator([]const u8), +) void { + comptime var radix = 10; + comptime var uppercase = false; -// const T = @TypeOf(value); -// switch (@typeId(T)) { -// .Float => return formatFloatValue(value, fmt, options, context, Errors, output), -// .Int, .ComptimeInt => return formatIntValue(value, fmt, options, context, Errors, output), -// else => comptime unreachable, -// } -// } + const int_value = if (@TypeOf(value) == comptime_int) blk: { + const Int = math.IntFittingRange(value, value); + break :blk @as(Int, value); + } else + value; -// pub fn formatIntValue( -// value: var, -// comptime fmt: []const u8, -// options: FormatOptions, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!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, context, Errors, output); -// } 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 ++ "'"); -// } + 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, generator); + } 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, context, Errors, output); -// } + return formatInt(int_value, radix, uppercase, options, generator); +} // fn formatFloatValue( // value: var, @@ -608,17 +593,16 @@ pub fn format( // } // } -// pub fn formatAsciiChar( -// c: u8, -// options: FormatOptions, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!void { -// if (std.ascii.isPrint(c)) -// return output(context, @as(*const [1]u8, &c)[0..]); -// return format(context, Errors, output, "\\x{x:0<2}", .{c}); -// } +pub fn formatAsciiChar( + c: u8, + options: FormatOptions, + generator: *Generator([]const u8), +) void { + if (std.ascii.isPrint(c)) + return generator.yield(@as(*const [1]u8, &c)[0..]); + @panic("FIXME"); + // return format(context, Errors, output, "\\x{x:0<2}", .{c}); +} // pub fn formatBuf( // buf: []const u8, @@ -1220,28 +1204,28 @@ test "parse unsigned comptime" { // } // } -// test "int.small" { -// { -// const value: u3 = 0b101; -// try testFmt("u3: 5\n", "u3: {}\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.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 "int.padded" { + try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)}); + try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", .{@as(u8, 1)}); +} // test "buffer" { // { @@ -1568,18 +1552,18 @@ test "parse unsigned comptime" { // 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; -// } +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; @@ -1633,13 +1617,18 @@ pub fn isWhiteSpace(byte: u8) bool { // try testFmt(test_hex_str, "{X}", .{pb}); // } -// test "formatIntValue with comptime_int" { -// const value: comptime_int = 123456789123456789; +test "formatIntValue with comptime_int" { + const value: comptime_int = 123456789123456789; -// var buf = try std.Buffer.init(std.debug.global_allocator, ""); -// try formatIntValue(value, "", FormatOptions{}, &buf, @TypeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append); -// std.testing.expect(mem.eql(u8, buf.toSlice(), "123456789123456789")); -// } + var buf = try std.Buffer.init(std.debug.global_allocator, ""); + + var generator = Generator([]const u8){}; + _ = async formatIntValue(value, "", FormatOptions{}, &generator); + while (generator.next()) |bytes| { + try buf.append(bytes); + } + std.testing.expect(mem.eql(u8, buf.toSlice(), "123456789123456789")); +} // test "formatType max_depth" { // const Vec2 = struct { From c48a83a2b9ced23f9c659c3970c9aceb1e1ef42b Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Thu, 2 Jan 2020 23:08:10 -0600 Subject: [PATCH 05/33] Re-enable simple transforms --- lib/std/fmtgen.zig | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 197ca4160523..71ec7b8cdcfd 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -357,12 +357,12 @@ pub fn formatType( .ComptimeInt, .Int, .Float => { return formatValue(value, fmt, options, generator); }, - // .Void => { - // return output(context, "void"); - // }, - // .Bool => { - // return output(context, if (value) "true" else "false"); - // }, + .Void => { + return generator.yield("void"); + }, + .Bool => { + return generator.yield(if (value) "true" else "false"); + }, // .Optional => { // if (value) |payload| { // return formatType(payload, fmt, options, context, Errors, output, max_depth); @@ -490,8 +490,7 @@ pub fn formatType( // return format(context, Errors, output, "{}@{x}", .{ @typeName(T), @ptrToInt(value) }); // }, // .Type => return output(context, @typeName(T)), - // else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), - else => {}, + else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), } } From 6ddadb4d6f522e4ce48f9e6e658ac6c505820b11 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Thu, 2 Jan 2020 23:09:01 -0600 Subject: [PATCH 06/33] Add back some positional tests --- lib/std/fmtgen.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 71ec7b8cdcfd..af0cb577fcf3 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -1695,13 +1695,13 @@ test "formatIntValue with comptime_int" { // std.testing.expect(mem.eql(u8, buf3.toSlice(), "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" { + 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)}); From 9c26d518ad6fb9f002ab4d5f0fd7a008f15e721c Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 08:23:56 -0600 Subject: [PATCH 07/33] Enable format pointers --- lib/std/fmtgen.zig | 122 +++++++++++++++++++++++---------------------- 1 file changed, 63 insertions(+), 59 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index af0cb577fcf3..fae45b5dc3fd 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -346,12 +346,10 @@ pub fn formatType( generator: *Generator([]const u8), max_depth: usize, ) void { - // if (comptime std.mem.eql(u8, fmt, "*")) { - // try output(context, @typeName(@TypeOf(value).Child)); - // try output(context, "@"); - // try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, context, Errors, output); - // return; - // } + if (comptime std.mem.eql(u8, fmt, "*")) { + return formatPtr(@TypeOf(value).Child, @ptrToInt(value), generator); + } + const T = @TypeOf(value); switch (@typeInfo(T)) { .ComptimeInt, .Int, .Float => { @@ -437,41 +435,41 @@ pub fn formatType( // } // try output(context, " }"); // }, - // .Pointer => |ptr_info| switch (ptr_info.size) { - // .One => switch (@typeInfo(ptr_info.child)) { - // builtin.TypeId.Array => |info| { - // if (info.child == u8) { - // return formatText(value, fmt, options, context, Errors, output); - // } - // return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); - // }, - // builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { - // return formatType(value.*, fmt, options, context, Errors, output, max_depth); - // }, - // else => return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }), - // }, - // .Many => { - // if (ptr_info.child == u8) { - // if (fmt.len > 0 and fmt[0] == 's') { - // const len = mem.len(u8, value); - // return formatText(value[0..len], fmt, options, context, Errors, output); - // } - // } - // return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); - // }, - // .Slice => { - // if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { - // return formatText(value, fmt, options, context, Errors, output); - // } - // if (ptr_info.child == u8) { - // return formatText(value, fmt, options, context, Errors, output); - // } - // return format(context, Errors, output, "{}@{x}", .{ @typeName(ptr_info.child), @ptrToInt(value.ptr) }); - // }, - // .C => { - // return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); - // }, - // }, + .Pointer => |ptr_info| switch (ptr_info.size) { + .One => switch (@typeInfo(ptr_info.child)) { + // builtin.TypeId.Array => |info| { + // if (info.child == u8) { + // return formatText(value, fmt, options, context, Errors, output); + // } + // return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); + // }, + // builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { + // return formatType(value.*, fmt, options, context, Errors, output, max_depth); + // }, + else => return formatPtr(T.Child, @ptrToInt(value), generator), + }, + .Many => { + // if (ptr_info.child == u8) { + // if (fmt.len > 0 and fmt[0] == 's') { + // const len = mem.len(u8, value); + // return formatText(value[0..len], fmt, options, context, Errors, output); + // } + // } + return formatPtr(T.Child, @ptrToInt(value), generator); + }, + .Slice => { + // if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { + // return formatText(value, fmt, options, context, Errors, output); + // } + // if (ptr_info.child == u8) { + // return formatText(value, fmt, options, context, Errors, output); + // } + return formatPtr(ptr_info.child, @ptrToInt(value.ptr), generator); + }, + .C => { + return formatPtr(T.Child, @ptrToInt(value), generator); + }, + }, // .Array => |info| { // const Slice = @Type(builtin.TypeInfo{ // .Pointer = .{ @@ -486,14 +484,20 @@ pub fn formatType( // }); // return formatType(@as(Slice, &value), fmt, options, context, Errors, output, max_depth); // }, - // .Fn => { - // return format(context, Errors, output, "{}@{x}", .{ @typeName(T), @ptrToInt(value) }); - // }, + .Fn => { + return formatPtr(T, @ptrToInt(value), generator); + }, // .Type => return output(context, @typeName(T)), else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), } } +fn formatPtr(comptime T: type, ptr: usize, generator: *Generator([]const u8)) void { + generator.yield(@typeName(T)); + generator.yield("@"); + return formatInt(ptr, 16, false, FormatOptions{}, generator); +} + fn formatValue( value: var, comptime fmt: []const u8, @@ -1275,21 +1279,21 @@ test "int.padded" { // 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 "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", .{"Test C"}); From 8a0bec6d077d3cf7349b2a7cbde077bdd5559b1a Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 08:25:35 -0600 Subject: [PATCH 08/33] Enable optionals --- lib/std/fmtgen.zig | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index fae45b5dc3fd..fb9b6eb47aec 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -361,13 +361,13 @@ pub fn formatType( .Bool => { return generator.yield(if (value) "true" else "false"); }, - // .Optional => { - // if (value) |payload| { - // return formatType(payload, fmt, options, context, Errors, output, max_depth); - // } else { - // return output(context, "null"); - // } - // }, + .Optional => { + if (value) |payload| { + return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, max_depth }); + } else { + return generator.yield("null"); + } + }, // .ErrorUnion => { // if (value) |payload| { // return formatType(payload, fmt, options, context, Errors, output, max_depth); @@ -1185,16 +1185,16 @@ test "parse unsigned comptime" { } } -// 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 "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" { // { From f4883ce8604119d7bf19e7dc32c134a2091b9700 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 08:29:21 -0600 Subject: [PATCH 09/33] Enable errors --- lib/std/fmtgen.zig | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index fb9b6eb47aec..de5fbd5df584 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -368,17 +368,17 @@ pub fn formatType( return generator.yield("null"); } }, - // .ErrorUnion => { - // if (value) |payload| { - // return formatType(payload, fmt, options, context, Errors, output, max_depth); - // } else |err| { - // return formatType(err, fmt, options, context, Errors, output, max_depth); - // } - // }, - // .ErrorSet => { - // try output(context, "error."); - // return output(context, @errorName(value)); - // }, + .ErrorUnion => { + if (value) |payload| { + return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, max_depth }); + } else |err| { + return @call(.{ .modifier = .always_tail }, formatType, .{ err, fmt, options, generator, max_depth }); + } + }, + .ErrorSet => { + generator.yield("error."); + return generator.yield(@errorName(value)); + }, // .Enum => { // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); @@ -1196,16 +1196,16 @@ test "optional" { } } -// 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 "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" { { From 4698f871068f56de55890b1204970cf96eb931af Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 08:37:15 -0600 Subject: [PATCH 10/33] Enable string printing --- lib/std/fmtgen.zig | 136 ++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 70 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index de5fbd5df584..3f73b2ae6b44 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -437,33 +437,33 @@ pub fn formatType( // }, .Pointer => |ptr_info| switch (ptr_info.size) { .One => switch (@typeInfo(ptr_info.child)) { - // builtin.TypeId.Array => |info| { - // if (info.child == u8) { - // return formatText(value, fmt, options, context, Errors, output); - // } - // return format(context, Errors, output, "{}@{x}", .{ @typeName(T.Child), @ptrToInt(value) }); - // }, + builtin.TypeId.Array => |info| { + if (info.child == u8) { + return formatText(value, fmt, options, generator); + } + return formatPtr(T.Child, @ptrToInt(value), generator); + }, // builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { // return formatType(value.*, fmt, options, context, Errors, output, max_depth); // }, else => return formatPtr(T.Child, @ptrToInt(value), generator), }, .Many => { - // if (ptr_info.child == u8) { - // if (fmt.len > 0 and fmt[0] == 's') { - // const len = mem.len(u8, value); - // return formatText(value[0..len], fmt, options, context, Errors, output); - // } - // } + if (ptr_info.child == u8) { + if (fmt.len > 0 and fmt[0] == 's') { + const len = mem.len(u8, value); + return formatText(value[0..len], fmt, generator); + } + } return formatPtr(T.Child, @ptrToInt(value), generator); }, .Slice => { - // if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { - // return formatText(value, fmt, options, context, Errors, output); - // } - // if (ptr_info.child == u8) { - // return formatText(value, fmt, options, context, Errors, output); - // } + if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { + return formatText(value, fmt, options, generator); + } + if (ptr_info.child == u8) { + return formatText(value, fmt, options, generator); + } return formatPtr(ptr_info.child, @ptrToInt(value.ptr), generator); }, .C => { @@ -574,27 +574,25 @@ pub fn formatIntValue( // } // } -// pub fn formatText( -// bytes: []const u8, -// comptime fmt: []const u8, -// options: FormatOptions, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!void { -// if (fmt.len == 0) { -// return output(context, bytes); -// } else if (comptime std.mem.eql(u8, fmt, "s")) { -// return formatBuf(bytes, options, context, Errors, output); -// } 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' }, context, Errors, output); -// } -// return; -// } else { -// @compileError("Unknown format string: '" ++ fmt ++ "'"); -// } -// } +pub fn formatText( + bytes: []const u8, + comptime fmt: []const u8, + options: FormatOptions, + generator: *Generator([]const u8), +) void { + if (fmt.len == 0) { + generator.yield(bytes); + } else if (comptime std.mem.eql(u8, fmt, "s")) { + return formatBuf(bytes, options, generator); + } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) { + for (bytes) |c| { + formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, generator); + } + return; + } else { + @compileError("Unknown format string: '" ++ fmt ++ "'"); + } +} pub fn formatAsciiChar( c: u8, @@ -607,22 +605,20 @@ pub fn formatAsciiChar( // return format(context, Errors, output, "\\x{x:0<2}", .{c}); } -// pub fn formatBuf( -// buf: []const u8, -// options: FormatOptions, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!void { -// try output(context, buf); +pub fn formatBuf( + buf: []const u8, + options: FormatOptions, + generator: *Generator([]const u8), +) void { + generator.yield(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 output(context, @as(*const [1]u8, &pad_byte)[0..1]); -// } -// } + 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) { + generator.yield(@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 @@ -1265,19 +1261,19 @@ test "int.padded" { // } // } -// 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}); -// } +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"}); -// } + 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" { { @@ -1295,10 +1291,10 @@ test "pointer" { } } -// test "cstr" { -// try testFmt("cstr: Test C\n", "cstr: {s}\n", .{"Test C"}); -// try testFmt("cstr: Test C \n", "cstr: {s:10}\n", .{"Test C"}); -// } +test "cstr" { + try testFmt("cstr: Test C\n", "cstr: {s}\n", .{"Test C"}); + try testFmt("cstr: Test C \n", "cstr: {s:10}\n", .{"Test C"}); +} // test "filesize" { // if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { From ce407088d96ec587c8560425fb02de0f1088ec98 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 08:39:23 -0600 Subject: [PATCH 11/33] Enable arrays --- lib/std/fmtgen.zig | 108 ++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 3f73b2ae6b44..b065cc070856 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -470,20 +470,20 @@ pub fn formatType( return formatPtr(T.Child, @ptrToInt(value), generator); }, }, - // .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, context, Errors, output, max_depth); - // }, + .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 @call(.{ .modifier = .always_tail }, formatType, .{ @as(Slice, &value), fmt, options, generator, max_depth }); + }, .Fn => { return formatPtr(T, @ptrToInt(value), generator); }, @@ -1246,20 +1246,20 @@ test "int.padded" { // } // } -// 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 "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" { { @@ -1540,16 +1540,16 @@ test "cstr" { // 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}); -// } +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; @@ -1597,24 +1597,24 @@ pub fn isWhiteSpace(byte: u8) bool { }; } -// pub fn hexToBytes(out: []u8, input: []const u8) !void { -// if (out.len * 2 < input.len) -// return error.InvalidLength; +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; -// } -// } + 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 "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; From a774b797142c70a4823405cdbd7d0cb512329328 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 08:41:24 -0600 Subject: [PATCH 12/33] Enable more trivial cases --- lib/std/fmtgen.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index b065cc070856..6852f3ad451f 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -443,9 +443,9 @@ pub fn formatType( } return formatPtr(T.Child, @ptrToInt(value), generator); }, - // builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { - // return formatType(value.*, fmt, options, context, Errors, output, max_depth); - // }, + builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { + return @call(.{ .modifier = .always_tail }, formatType, .{ value.*, fmt, options, generator, max_depth }); + }, else => return formatPtr(T.Child, @ptrToInt(value), generator), }, .Many => { @@ -487,7 +487,7 @@ pub fn formatType( .Fn => { return formatPtr(T, @ptrToInt(value), generator); }, - // .Type => return output(context, @typeName(T)), + .Type => return generator.yield(@typeName(T)), else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), } } From 6ed5502e2b6def05053910972f108ba7d749bb21 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 08:45:13 -0600 Subject: [PATCH 13/33] Enable enums --- lib/std/fmtgen.zig | 53 +++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 6852f3ad451f..b9009abcc81b 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -379,15 +379,14 @@ pub fn formatType( generator.yield("error."); return generator.yield(@errorName(value)); }, - // .Enum => { - // if (comptime std.meta.trait.hasFn("format")(T)) { - // return value.format(fmt, options, context, Errors, output); - // } - - // try output(context, @typeName(T)); - // try output(context, "."); - // return formatType(@tagName(value), "", options, context, Errors, output, max_depth); - // }, + .Enum => { + // if (comptime std.meta.trait.hasFn("format")(T)) { + // return value.format(fmt, options, context, Errors, output); + // } + generator.yield(@typeName(T)); + generator.yield("."); + return @call(.{ .modifier = .always_tail }, formatType, .{ @tagName(value), "", options, generator, max_depth }); + }, // .Union => { // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); @@ -1324,15 +1323,15 @@ test "cstr" { // } // } -// 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 "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 "float.scientific" { // if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { @@ -1499,17 +1498,17 @@ test "cstr" { // std.testing.expect(mem.eql(u8, uu_result[0..3], "EU@")); // } -// test "enum" { -// const E = enum { -// One, -// Two, -// Three, -// }; +test "enum" { + const E = enum { + One, + Two, + Three, + }; -// const inst = E.Two; + const inst = E.Two; -// try testFmt("E.Two", "{}", .{inst}); -// } + try testFmt("E.Two", "{}", .{inst}); +} // test "struct.self-referential" { // const S = struct { From f2060ef16c8508b40edc2345649c0f04a7a554a8 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 08:55:20 -0600 Subject: [PATCH 14/33] Enable unions --- lib/std/fmtgen.zig | 95 +++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index b9009abcc81b..614ac48fabc3 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -387,30 +387,31 @@ pub fn formatType( generator.yield("."); return @call(.{ .modifier = .always_tail }, formatType, .{ @tagName(value), "", options, generator, max_depth }); }, - // .Union => { - // if (comptime std.meta.trait.hasFn("format")(T)) { - // return value.format(fmt, options, context, Errors, output); - // } - - // try output(context, @typeName(T)); - // if (max_depth == 0) { - // return output(context, "{ ... }"); - // } - // const info = @typeInfo(T).Union; - // if (info.tag_type) |UnionTagType| { - // try output(context, "{ ."); - // try output(context, @tagName(@as(UnionTagType, value))); - // try output(context, " = "); - // inline for (info.fields) |u_field| { - // if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { - // try formatType(@field(value, u_field.name), "", options, context, Errors, output, max_depth - 1); - // } - // } - // try output(context, " }"); - // } else { - // try format(context, Errors, output, "@{x}", .{@ptrToInt(&value)}); - // } - // }, + .Union => { + // if (comptime std.meta.trait.hasFn("format")(T)) { + // return value.format(fmt, options, context, Errors, output); + // } + generator.yield(@typeName(T)); + if (max_depth == 0) { + return generator.yield("{ ... }"); + } + const info = @typeInfo(T).Union; + if (info.tag_type) |UnionTagType| { + generator.yield("{ ."); + generator.yield(@tagName(@as(UnionTagType, value))); + generator.yield(" = "); + inline for (info.fields) |u_field| { + if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { + // TODO: investigate why recursion just works + formatType(@field(value, u_field.name), "", options, generator, max_depth - 1); + } + } + generator.yield(" }"); + } else { + generator.yield("@"); + return formatInt(@ptrToInt(&value), 16, false, FormatOptions{}, generator); + } + }, // .Struct => { // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); @@ -1468,35 +1469,35 @@ test "enum" { // try testFmt("S{ .a = 456, .b = error.Unused }", "{}", .{inst}); // } -// test "union" { -// const TU = union(enum) { -// float: f32, -// int: u32, -// }; +test "union" { + const TU = union(enum) { + float: f32, + int: u32, + }; -// const UU = union { -// float: f32, -// int: u32, -// }; + const UU = union { + float: f32, + int: u32, + }; -// const EU = extern 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 }; + 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}); + 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@")); + 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@")); -// } + 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 { From 3f0ecdfa5fbb61ffaa66c0e124eeab1a3d9282fc Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 09:01:05 -0600 Subject: [PATCH 15/33] Enable structs --- lib/std/fmtgen.zig | 103 ++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 614ac48fabc3..61bda83c1bf3 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -412,29 +412,28 @@ pub fn formatType( return formatInt(@ptrToInt(&value), 16, false, FormatOptions{}, generator); } }, - // .Struct => { - // if (comptime std.meta.trait.hasFn("format")(T)) { - // return value.format(fmt, options, context, Errors, output); - // } - - // try output(context, @typeName(T)); - // if (max_depth == 0) { - // return output(context, "{ ... }"); - // } - // comptime var field_i = 0; - // try output(context, "{"); - // inline while (field_i < @memberCount(T)) : (field_i += 1) { - // if (field_i == 0) { - // try output(context, " ."); - // } else { - // try output(context, ", ."); - // } - // try output(context, @memberName(T, field_i)); - // try output(context, " = "); - // try formatType(@field(value, @memberName(T, field_i)), "", options, context, Errors, output, max_depth - 1); - // } - // try output(context, " }"); - // }, + .Struct => { + // if (comptime std.meta.trait.hasFn("format")(T)) { + // return value.format(fmt, options, context, Errors, output); + // } + generator.yield(@typeName(T)); + if (max_depth == 0) { + return generator.yield("{ ... }"); + } + comptime var field_i = 0; + generator.yield("{"); + inline while (field_i < @memberCount(T)) : (field_i += 1) { + if (field_i == 0) { + generator.yield(" ."); + } else { + generator.yield(", ."); + } + generator.yield(@memberName(T, field_i)); + generator.yield(" = "); + formatType(@field(value, @memberName(T, field_i)), "", options, generator, max_depth - 1); + } + generator.yield(" }"); + }, .Pointer => |ptr_info| switch (ptr_info.size) { .One => switch (@typeInfo(ptr_info.child)) { builtin.TypeId.Array => |info| { @@ -1305,24 +1304,24 @@ test "cstr" { // 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 "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 { @@ -1455,19 +1454,19 @@ test "enum" { // try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", .{value}); // } -// test "struct" { -// const S = struct { -// a: u32, -// b: anyerror, -// }; +test "struct" { + const S = struct { + a: u32, + b: anyerror, + }; -// const inst = S{ -// .a = 456, -// .b = error.Unused, -// }; + const inst = S{ + .a = 456, + .b = error.Unused, + }; -// try testFmt("S{ .a = 456, .b = error.Unused }", "{}", .{inst}); -// } + try testFmt("S{ .a = 456, .b = error.Unused }", "{}", .{inst}); +} test "union" { const TU = union(enum) { From 7e50a2ec5a02e54da69c1dcb1fe19dadc9137b99 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 10:07:03 -0600 Subject: [PATCH 16/33] Enable recursion! --- lib/std/fmtgen.zig | 96 ++++++++++++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 61bda83c1bf3..797b04168f70 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -150,6 +150,10 @@ pub fn format( comptime var specifier_end = 0; comptime var options = FormatOptions{}; + // TODO: calculate this + var recur_stack: [0x10000]u8 = undefined; + var recur_allocator = std.heap.FixedBufferAllocator.init(&recur_stack); + inline for (fmt) |c, i| { switch (state) { .Start => switch (c) { @@ -207,6 +211,7 @@ pub fn format( fmt[0..0], options, generator, + &recur_allocator.allocator, default_max_depth, ); @@ -238,6 +243,7 @@ pub fn format( fmt[specifier_start..i], options, generator, + &recur_allocator.allocator, default_max_depth, ); state = .Start; @@ -283,6 +289,7 @@ pub fn format( fmt[specifier_start..specifier_end], options, generator, + &recur_allocator.allocator, default_max_depth, ); state = .Start; @@ -309,6 +316,7 @@ pub fn format( fmt[specifier_start..specifier_end], options, generator, + &recur_allocator.allocator, default_max_depth, ); state = .Start; @@ -344,6 +352,7 @@ pub fn formatType( comptime fmt: []const u8, options: FormatOptions, generator: *Generator([]const u8), + allocator: *mem.Allocator, max_depth: usize, ) void { if (comptime std.mem.eql(u8, fmt, "*")) { @@ -363,16 +372,25 @@ pub fn formatType( }, .Optional => { if (value) |payload| { - return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, max_depth }); + // return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, allocator, max_depth }); + // TODO: reenable tail call once it is fixed https://github.com/ziglang/zig/issues/4060 + var frame = allocator.create( + @TypeOf(async formatType(payload, fmt, options, generator, allocator, max_depth)), + ) catch |err| switch (err) { + error.OutOfMemory => return generator.yield(" ??OOM?? "), + }; + defer allocator.destroy(frame); + frame.* = async formatType(payload, fmt, options, generator, allocator, max_depth); + await frame.*; } else { return generator.yield("null"); } }, .ErrorUnion => { if (value) |payload| { - return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, max_depth }); + return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, allocator, max_depth }); } else |err| { - return @call(.{ .modifier = .always_tail }, formatType, .{ err, fmt, options, generator, max_depth }); + return @call(.{ .modifier = .always_tail }, formatType, .{ err, fmt, options, generator, allocator, max_depth }); } }, .ErrorSet => { @@ -385,7 +403,7 @@ pub fn formatType( // } generator.yield(@typeName(T)); generator.yield("."); - return @call(.{ .modifier = .always_tail }, formatType, .{ @tagName(value), "", options, generator, max_depth }); + return @call(.{ .modifier = .always_tail }, formatType, .{ @tagName(value), "", options, generator, allocator, max_depth }); }, .Union => { // if (comptime std.meta.trait.hasFn("format")(T)) { @@ -402,8 +420,14 @@ pub fn formatType( generator.yield(" = "); inline for (info.fields) |u_field| { if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { - // TODO: investigate why recursion just works - formatType(@field(value, u_field.name), "", options, generator, max_depth - 1); + var frame = allocator.create( + @TypeOf(async formatType(@field(value, u_field.name), "", options, generator, allocator, max_depth - 1)), + ) catch |err| switch (err) { + error.OutOfMemory => return generator.yield(" ??OOM?? "), + }; + defer allocator.destroy(frame); + frame.* = async formatType(@field(value, u_field.name), "", options, generator, allocator, max_depth - 1); + await frame.*; } } generator.yield(" }"); @@ -430,7 +454,15 @@ pub fn formatType( } generator.yield(@memberName(T, field_i)); generator.yield(" = "); - formatType(@field(value, @memberName(T, field_i)), "", options, generator, max_depth - 1); + + var frame = allocator.create( + @TypeOf(async formatType(@field(value, @memberName(T, field_i)), "", options, generator, allocator, max_depth - 1)), + ) catch |err| switch (err) { + error.OutOfMemory => return generator.yield(" ??OOM?? "), + }; + defer allocator.destroy(frame); + frame.* = async formatType(@field(value, @memberName(T, field_i)), "", options, generator, allocator, max_depth - 1); + await frame.*; } generator.yield(" }"); }, @@ -443,7 +475,7 @@ pub fn formatType( return formatPtr(T.Child, @ptrToInt(value), generator); }, builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { - return @call(.{ .modifier = .always_tail }, formatType, .{ value.*, fmt, options, generator, max_depth }); + return @call(.{ .modifier = .always_tail }, formatType, .{ value.*, fmt, options, generator, allocator, max_depth }); }, else => return formatPtr(T.Child, @ptrToInt(value), generator), }, @@ -481,7 +513,7 @@ pub fn formatType( .sentinel = null, }, }); - return @call(.{ .modifier = .always_tail }, formatType, .{ @as(Slice, &value), fmt, options, generator, max_depth }); + return @call(.{ .modifier = .always_tail }, formatType, .{ @as(Slice, &value), fmt, options, generator, allocator, max_depth }); }, .Fn => { return formatPtr(T, @ptrToInt(value), generator); @@ -1510,34 +1542,34 @@ test "enum" { try testFmt("E.Two", "{}", .{inst}); } -// test "struct.self-referential" { -// const S = struct { -// const SelfType = @This(); -// a: ?*SelfType, -// }; +test "struct.self-referential" { + const S = struct { + const SelfType = @This(); + a: ?*SelfType, + }; -// var inst = S{ -// .a = null, -// }; -// inst.a = &inst; + var inst = S{ + .a = null, + }; + inst.a = &inst; -// try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", .{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, -// }; +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 }; + const a = A{}; + const b = B{ .a = a, .c = 0 }; -// try testFmt("B{ .a = A{ }, .c = 0 }", "{}", .{b}); -// } + try testFmt("B{ .a = A{ }, .c = 0 }", "{}", .{b}); +} test "bytes.hex" { const some_bytes = "\xCA\xFE\xBA\xBE"; From 33299007d0c012c03a69a44af2c14630055f1334 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 13:10:03 -0600 Subject: [PATCH 17/33] Partially working formatFloatExponent --- lib/std/fmtgen.zig | 212 ++++++++++++++++++++++----------------------- 1 file changed, 105 insertions(+), 107 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 797b04168f70..75e26d3765fc 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -542,7 +542,7 @@ fn formatValue( // } const T = @TypeOf(value); switch (@typeId(T)) { - // .Float => return formatFloatValue(value, fmt, options, context, Errors, output), + .Float => return formatFloatValue(value, fmt, options, generator), .Int, .ComptimeInt => return formatIntValue(value, fmt, options, generator), else => comptime unreachable, } @@ -588,22 +588,20 @@ pub fn formatIntValue( return formatInt(int_value, radix, uppercase, options, generator); } -// fn formatFloatValue( -// value: var, -// comptime fmt: []const u8, -// options: FormatOptions, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!void { -// if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { -// return formatFloatScientific(value, options, context, Errors, output); -// } else if (comptime std.mem.eql(u8, fmt, "d")) { -// return formatFloatDecimal(value, options, context, Errors, output); -// } else { -// @compileError("Unknown format string: '" ++ fmt ++ "'"); -// } -// } +fn formatFloatValue( + value: var, + comptime fmt: []const u8, + options: FormatOptions, + generator: *Generator([]const u8), +) void { + if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { + return formatFloatScientific(value, options, generator); + } else if (comptime std.mem.eql(u8, fmt, "d")) { + return formatFloatDecimal(value, options, generator); + } else { + @compileError("Unknown format string: '" ++ fmt ++ "'"); + } +} pub fn formatText( bytes: []const u8, @@ -654,98 +652,98 @@ pub fn formatBuf( // // 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, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!void { -// var x = @floatCast(f64, value); - -// // Errol doesn't handle these special cases. -// if (math.signbit(x)) { -// try output(context, "-"); -// x = -x; -// } +pub fn formatFloatScientific( + value: var, + options: FormatOptions, + generator: *Generator([]const u8), +) void { + var x = @floatCast(f64, value); -// if (math.isNan(x)) { -// return output(context, "nan"); -// } -// if (math.isPositiveInf(x)) { -// return output(context, "inf"); -// } -// if (x == 0.0) { -// try output(context, "0"); + // Errol doesn't handle these special cases. + if (math.signbit(x)) { + generator.yield("-"); + x = -x; + } -// if (options.precision) |precision| { -// if (precision != 0) { -// try output(context, "."); -// var i: usize = 0; -// while (i < precision) : (i += 1) { -// try output(context, "0"); -// } -// } -// } else { -// try output(context, ".0"); -// } + if (math.isNan(x)) { + return generator.yield("nan"); + } + if (math.isPositiveInf(x)) { + return generator.yield("inf"); + } + if (x == 0.0) { + generator.yield("0"); + + if (options.precision) |precision| { + if (precision != 0) { + generator.yield("."); + var i: usize = 0; + // FIXME: Instruction does not dominate all uses! + // while (i < precision) : (i += 1) { + // generator.yield( "0"); + // } + } + } else { + generator.yield(".0"); + } -// try output(context, "e+00"); -// return; -// } + generator.yield("e+00"); + return; + } -// var buffer: [32]u8 = undefined; -// var float_decimal = errol.errol3(x, buffer[0..]); + 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); + if (options.precision) |precision| { + errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific); -// try output(context, float_decimal.digits[0..1]); + generator.yield(float_decimal.digits[0..1]); -// // {e0} case prints no `.` -// if (precision != 0) { -// try output(context, "."); + // {e0} case prints no `.` + // FIXME: Instruction does not dominate all uses! + // if (precision != 0) { + // generator.yield("."); -// var printed: usize = 0; -// if (float_decimal.digits.len > 1) { -// const num_digits = math.min(float_decimal.digits.len, precision + 1); -// try output(context, float_decimal.digits[1..num_digits]); -// printed += num_digits - 1; -// } + // var printed: usize = 0; + // if (float_decimal.digits.len > 1) { + // const num_digits = math.min(float_decimal.digits.len, precision + 1); + // generator.yield(float_decimal.digits[1..num_digits]); + // printed += num_digits - 1; + // } -// while (printed < precision) : (printed += 1) { -// try output(context, "0"); -// } -// } -// } else { -// try output(context, float_decimal.digits[0..1]); -// try output(context, "."); -// 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; + // while (printed < precision) : (printed += 1) { + // generator.yield("0"); + // } + // } + } else { + generator.yield(float_decimal.digits[0..1]); + generator.yield("."); + 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 output(context, float_decimal.digits[1..num_digits]); -// } else { -// try output(context, "0"); -// } -// } + generator.yield(float_decimal.digits[1..num_digits]); + } else { + generator.yield("0"); + } + } -// try output(context, "e"); -// const exp = float_decimal.exp - 1; + generator.yield("e"); + const exp = float_decimal.exp - 1; -// if (exp >= 0) { -// try output(context, "+"); -// if (exp > -10 and exp < 10) { -// try output(context, "0"); -// } -// try formatInt(exp, 10, false, FormatOptions{ .width = 0 }, context, Errors, output); -// } else { -// try output(context, "-"); -// if (exp > -10 and exp < 10) { -// try output(context, "0"); -// } -// try formatInt(-exp, 10, false, FormatOptions{ .width = 0 }, context, Errors, output); -// } -// } + if (exp >= 0) { + generator.yield("+"); + if (exp > -10 and exp < 10) { + generator.yield("0"); + } + formatInt(exp, 10, false, FormatOptions{ .width = 0 }, generator); + } else { + generator.yield("-"); + if (exp > -10 and exp < 10) { + generator.yield("0"); + } + formatInt(-exp, 10, false, FormatOptions{ .width = 0 }, generator); + } +} // // 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). @@ -1365,16 +1363,16 @@ test "enum" { try testFmt("enum: Enum.Two\n", "enum: {}\n", .{&value}); } -// test "float.scientific" { -// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { -// // TODO https://github.com/ziglang/zig/issues/3289 -// return error.SkipZigTest; -// } -// 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" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + 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" { // if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { From e77efae2e713730d272355a3e1c3d72385ca818c Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 20:40:14 -0600 Subject: [PATCH 18/33] Work around async spill bug --- lib/std/fmtgen.zig | 96 +++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 75e26d3765fc..530067bc6c94 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -674,14 +674,15 @@ pub fn formatFloatScientific( if (x == 0.0) { generator.yield("0"); - if (options.precision) |precision| { + if (options.precision) |prec_orig| { + // TODO: remove copy once this is fixed https://github.com/ziglang/zig/issues/4065 + const precision = prec_orig; if (precision != 0) { generator.yield("."); var i: usize = 0; - // FIXME: Instruction does not dominate all uses! - // while (i < precision) : (i += 1) { - // generator.yield( "0"); - // } + while (i < precision) : (i += 1) { + generator.yield("0"); + } } } else { generator.yield(".0"); @@ -694,27 +695,28 @@ pub fn formatFloatScientific( var buffer: [32]u8 = undefined; var float_decimal = errol.errol3(x, buffer[0..]); - if (options.precision) |precision| { + if (options.precision) |prec_orig| { + // TODO: remove copy once this is fixed https://github.com/ziglang/zig/issues/4065 + const precision = prec_orig; errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific); generator.yield(float_decimal.digits[0..1]); // {e0} case prints no `.` - // FIXME: Instruction does not dominate all uses! - // if (precision != 0) { - // generator.yield("."); - - // var printed: usize = 0; - // if (float_decimal.digits.len > 1) { - // const num_digits = math.min(float_decimal.digits.len, precision + 1); - // generator.yield(float_decimal.digits[1..num_digits]); - // printed += num_digits - 1; - // } - - // while (printed < precision) : (printed += 1) { - // generator.yield("0"); - // } - // } + if (precision != 0) { + generator.yield("."); + + var printed: usize = 0; + if (float_decimal.digits.len > 1) { + const num_digits = math.min(float_decimal.digits.len, precision + 1); + generator.yield(float_decimal.digits[1..num_digits]); + printed += num_digits - 1; + } + + while (printed < precision) : (printed += 1) { + generator.yield("0"); + } + } } else { generator.yield(float_decimal.digits[0..1]); generator.yield("."); @@ -1374,33 +1376,33 @@ test "float.scientific" { try testFmt("f64: 9.99996e-40", "f64: {e}", .{@as(f64, 9.999960e-40)}); } -// test "float.scientific.precision" { -// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { -// // TODO https://github.com/ziglang/zig/issues/3289 -// return error.SkipZigTest; -// } -// 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.scientific.precision" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + 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" { -// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { -// // TODO https://github.com/ziglang/zig/issues/3289 -// return error.SkipZigTest; -// } -// 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.special" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + 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" { // if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { From 7cb8cc8193de96e64762eaf7fc7e51e14f12b0e3 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 20:46:03 -0600 Subject: [PATCH 19/33] Enable formatFloatDecimals --- lib/std/fmtgen.zig | 332 +++++++++++++++++++++++---------------------- 1 file changed, 167 insertions(+), 165 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 530067bc6c94..26caf0de218a 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -747,152 +747,154 @@ pub fn formatFloatScientific( } } -// // 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, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!void { -// var x = @as(f64, value); +// 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, + generator: *Generator([]const u8), +) void { + var x = @as(f64, value); -// // Errol doesn't handle these special cases. -// if (math.signbit(x)) { -// try output(context, "-"); -// x = -x; -// } + // Errol doesn't handle these special cases. + if (math.signbit(x)) { + generator.yield("-"); + x = -x; + } -// if (math.isNan(x)) { -// return output(context, "nan"); -// } -// if (math.isPositiveInf(x)) { -// return output(context, "inf"); -// } -// if (x == 0.0) { -// try output(context, "0"); - -// if (options.precision) |precision| { -// if (precision != 0) { -// try output(context, "."); -// var i: usize = 0; -// while (i < precision) : (i += 1) { -// try output(context, "0"); -// } -// } else { -// try output(context, ".0"); -// } -// } else { -// try output(context, "0"); -// } + if (math.isNan(x)) { + return generator.yield("nan"); + } + if (math.isPositiveInf(x)) { + return generator.yield("inf"); + } + if (x == 0.0) { + generator.yield("0"); -// return; -// } + if (options.precision) |prec_orig| { + // TODO: remove copy once this is fixed https://github.com/ziglang/zig/issues/4065 + const precision = prec_orig; + if (precision != 0) { + generator.yield("."); + var i: usize = 0; + while (i < precision) : (i += 1) { + generator.yield("0"); + } + } else { + generator.yield(".0"); + } + } else { + generator.yield("0"); + } -// // non-special case, use errol3 -// var buffer: [32]u8 = undefined; -// var float_decimal = errol.errol3(x, buffer[0..]); + return; + } -// if (options.precision) |precision| { -// errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal); + // non-special case, use errol3 + var buffer: [32]u8 = undefined; + var float_decimal = errol.errol3(x, buffer[0..]); -// // 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; + if (options.precision) |prec_orig| { + // TODO: remove copy once this is fixed https://github.com/ziglang/zig/issues/4065 + const precision = prec_orig; + errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal); -// // 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); + // 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; -// if (num_digits_whole > 0) { -// // We may have to zero pad, for instance 1e4 requires zero padding. -// try output(context, float_decimal.digits[0..num_digits_whole_no_pad]); + // 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 i = num_digits_whole_no_pad; -// while (i < num_digits_whole) : (i += 1) { -// try output(context, "0"); -// } -// } else { -// try output(context, "0"); -// } + if (num_digits_whole > 0) { + // We may have to zero pad, for instance 1e4 requires zero padding. + generator.yield(float_decimal.digits[0..num_digits_whole_no_pad]); -// // {.0} special case doesn't want a trailing '.' -// if (precision == 0) { -// return; -// } + var i = num_digits_whole_no_pad; + while (i < num_digits_whole) : (i += 1) { + generator.yield("0"); + } + } else { + generator.yield("0"); + } -// try output(context, "."); + // {.0} special case doesn't want a trailing '.' + if (precision == 0) { + return; + } -// // Keep track of fractional count printed for case where we pre-pad then post-pad with 0's. -// var printed: usize = 0; + generator.yield("."); -// // 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); + // Keep track of fractional count printed for case where we pre-pad then post-pad with 0's. + var printed: usize = 0; -// var i: usize = 0; -// while (i < zeros_to_print) : (i += 1) { -// try output(context, "0"); -// printed += 1; -// } + // 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); -// if (printed >= precision) { -// return; -// } -// } + var i: usize = 0; + while (i < zeros_to_print) : (i += 1) { + generator.yield("0"); + printed += 1; + } -// // Remaining fractional portion, zero-padding if insufficient. -// assert(precision >= printed); -// if (num_digits_whole_no_pad + precision - printed < float_decimal.digits.len) { -// try output(context, float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]); -// return; -// } else { -// try output(context, float_decimal.digits[num_digits_whole_no_pad..]); -// printed += float_decimal.digits.len - num_digits_whole_no_pad; - -// while (printed < precision) : (printed += 1) { -// try output(context, "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; + if (printed >= precision) { + return; + } + } -// // 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); + // Remaining fractional portion, zero-padding if insufficient. + assert(precision >= printed); + if (num_digits_whole_no_pad + precision - printed < float_decimal.digits.len) { + generator.yield(float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]); + return; + } else { + generator.yield(float_decimal.digits[num_digits_whole_no_pad..]); + printed += float_decimal.digits.len - num_digits_whole_no_pad; -// if (num_digits_whole > 0) { -// // We may have to zero pad, for instance 1e4 requires zero padding. -// try output(context, float_decimal.digits[0..num_digits_whole_no_pad]); + while (printed < precision) : (printed += 1) { + generator.yield("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; -// var i = num_digits_whole_no_pad; -// while (i < num_digits_whole) : (i += 1) { -// try output(context, "0"); -// } -// } else { -// try output(context, "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); -// // Omit `.` if no fractional portion -// if (float_decimal.exp >= 0 and num_digits_whole_no_pad == float_decimal.digits.len) { -// return; -// } + if (num_digits_whole > 0) { + // We may have to zero pad, for instance 1e4 requires zero padding. + generator.yield(float_decimal.digits[0..num_digits_whole_no_pad]); -// try output(context, "."); + var i = num_digits_whole_no_pad; + while (i < num_digits_whole) : (i += 1) { + generator.yield("0"); + } + } else { + generator.yield("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); + // Omit `.` if no fractional portion + if (float_decimal.exp >= 0 and num_digits_whole_no_pad == float_decimal.digits.len) { + return; + } -// var i: usize = 0; -// while (i < zero_digit_count) : (i += 1) { -// try output(context, "0"); -// } -// } + generator.yield("."); -// try output(context, float_decimal.digits[num_digits_whole_no_pad..]); -// } -// } + // 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) { + generator.yield("0"); + } + } + + generator.yield(float_decimal.digits[num_digits_whole_no_pad..]); + } +} // pub fn formatBytes( // value: var, @@ -1404,50 +1406,50 @@ test "float.special" { try testFmt("f64: -inf", "f64: {}", .{-math.inf_f64}); } -// test "float.decimal" { -// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { -// // TODO https://github.com/ziglang/zig/issues/3289 -// return error.SkipZigTest; -// } -// try testFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)}); -// 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.decimal" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + try testFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)}); + 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" { -// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { -// // TODO https://github.com/ziglang/zig/issues/3289 -// return error.SkipZigTest; -// } -// 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 "float.libc.sanity" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + 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 { From 98e3d889934437f8ac606767349945aa0818e4b6 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Fri, 3 Jan 2020 20:49:50 -0600 Subject: [PATCH 20/33] Enable formatBytes --- lib/std/fmtgen.zig | 106 ++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 54 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 26caf0de218a..c22bf8849304 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -535,11 +535,11 @@ fn formatValue( options: FormatOptions, generator: *Generator([]const u8), ) void { - // if (comptime std.mem.eql(u8, fmt, "B")) { - // return formatBytes(value, options, 1000, context, Errors, output); - // } else if (comptime std.mem.eql(u8, fmt, "Bi")) { - // return formatBytes(value, options, 1024, context, Errors, output); - // } + if (comptime std.mem.eql(u8, fmt, "B")) { + return formatBytes(value, options, 1000, generator); + } else if (comptime std.mem.eql(u8, fmt, "Bi")) { + return formatBytes(value, options, 1024, generator); + } const T = @TypeOf(value); switch (@typeId(T)) { .Float => return formatFloatValue(value, fmt, options, generator), @@ -896,45 +896,43 @@ pub fn formatFloatDecimal( } } -// pub fn formatBytes( -// value: var, -// options: FormatOptions, -// comptime radix: usize, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!void { -// if (value == 0) { -// return output(context, "0B"); -// } +pub fn formatBytes( + value: var, + options: FormatOptions, + comptime radix: usize, + generator: *Generator([]const u8), +) void { + if (value == 0) { + return generator.yield("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, -// }; + 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, context, Errors, output); + formatFloatDecimal(new_value, options, generator); -// if (suffix == ' ') { -// return output(context, "B"); -// } + if (suffix == ' ') { + return generator.yield("B"); + } -// const buf = switch (radix) { -// 1000 => &[_]u8{ suffix, 'B' }, -// 1024 => &[_]u8{ suffix, 'i', 'B' }, -// else => unreachable, -// }; -// return output(context, buf); -// } + const buf = switch (radix) { + 1000 => &[_]u8{ suffix, 'B' }, + 1024 => &[_]u8{ suffix, 'i', 'B' }, + else => unreachable, + }; + return generator.yield(buf); +} pub fn formatInt( value: var, @@ -1329,14 +1327,14 @@ test "cstr" { try testFmt("cstr: Test C \n", "cstr: {s:10}\n", .{"Test C"}); } -// test "filesize" { -// if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { -// // TODO https://github.com/ziglang/zig/issues/3289 -// return error.SkipZigTest; -// } -// 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 "filesize" { + if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { + // TODO https://github.com/ziglang/zig/issues/3289 + return error.SkipZigTest; + } + 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" { { @@ -1736,10 +1734,10 @@ test "positional" { 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 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 "positional/alignment/width/precision" { + try testFmt("10.0", "{0d: >3.1}", .{@as(f64, 9.999)}); +} From 9d8e84c5720f460398dd207731c3d11fe5172b89 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Sun, 5 Jan 2020 15:13:09 -0600 Subject: [PATCH 21/33] Migrate most fmt.* references to fmtgen --- doc/docgen.zig | 10 +++---- lib/std/atomic/queue.zig | 4 +-- lib/std/buffer.zig | 12 ++------ lib/std/fifo.zig | 6 +++- lib/std/fmtgen.zig | 2 +- lib/std/net.zig | 2 +- lib/std/os.zig | 2 +- lib/std/progress.zig | 6 ++-- lib/std/special/build_runner.zig | 6 ++-- lib/std/std.zig | 1 + lib/std/target.zig | 6 ++-- src-self-hosted/compilation.zig | 6 ++-- src-self-hosted/dep_tokenizer.zig | 2 +- src-self-hosted/libc_installation.zig | 2 +- src-self-hosted/link.zig | 20 ++++++------- src-self-hosted/test.zig | 6 ++-- src-self-hosted/translate_c.zig | 41 +++++++++++++-------------- src-self-hosted/type.zig | 8 +++--- test/tests.zig | 18 ++++++------ tools/process_headers.zig | 4 +-- tools/update_glibc.zig | 2 +- 21 files changed, 81 insertions(+), 85 deletions(-) diff --git a/doc/docgen.zig b/doc/docgen.zig index 83855b3b5075..5a52b57e9c6b 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -1048,7 +1048,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var try out.write("
");
                 try tokenizeAndPrint(tokenizer, out, code.source_token);
                 try out.write("
"); - const name_plus_ext = try std.fmt.allocPrint(allocator, "{}.zig", .{code.name}); + const name_plus_ext = try std.fmtgen.allocPrint(allocator, "{}.zig", .{code.name}); const tmp_source_file_name = try fs.path.join( allocator, &[_][]const u8{ tmp_dir_name, name_plus_ext }, @@ -1057,7 +1057,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var switch (code.id) { Code.Id.Exe => |expected_outcome| code_block: { - const name_plus_bin_ext = try std.fmt.allocPrint(allocator, "{}{}", .{ code.name, exe_ext }); + const name_plus_bin_ext = try std.fmtgen.allocPrint(allocator, "{}{}", .{ code.name, exe_ext }); var build_args = std.ArrayList([]const u8).init(allocator); defer build_args.deinit(); try build_args.appendSlice(&[_][]const u8{ @@ -1088,7 +1088,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var }, } for (code.link_objects) |link_object| { - const name_with_ext = try std.fmt.allocPrint(allocator, "{}{}", .{ link_object, obj_ext }); + const name_with_ext = try std.fmtgen.allocPrint(allocator, "{}{}", .{ link_object, obj_ext }); const full_path_object = try fs.path.join( allocator, &[_][]const u8{ tmp_dir_name, name_with_ext }, @@ -1353,7 +1353,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var }); }, Code.Id.Obj => |maybe_error_match| { - const name_plus_obj_ext = try std.fmt.allocPrint(allocator, "{}{}", .{ code.name, obj_ext }); + const name_plus_obj_ext = try std.fmtgen.allocPrint(allocator, "{}{}", .{ code.name, obj_ext }); const tmp_obj_file_name = try fs.path.join( allocator, &[_][]const u8{ tmp_dir_name, name_plus_obj_ext }, @@ -1361,7 +1361,7 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var var build_args = std.ArrayList([]const u8).init(allocator); defer build_args.deinit(); - const name_plus_h_ext = try std.fmt.allocPrint(allocator, "{}.h", .{code.name}); + const name_plus_h_ext = try std.fmtgen.allocPrint(allocator, "{}.h", .{code.name}); const output_h_file_name = try fs.path.join( allocator, &[_][]const u8{ tmp_dir_name, name_plus_h_ext }, diff --git a/lib/std/atomic/queue.zig b/lib/std/atomic/queue.zig index f5dbd04da774..8480125fae00 100644 --- a/lib/std/atomic/queue.zig +++ b/lib/std/atomic/queue.zig @@ -344,7 +344,7 @@ test "std.atomic.Queue dump" { sos.reset(); try queue.dumpToStream(SliceOutStream.Error, &sos.stream); - var expected = try std.fmt.bufPrint(expected_buffer[0..], + var expected = try std.fmtgen.bufPrint(expected_buffer[0..], \\head: 0x{x}=1 \\ (null) \\tail: 0x{x}=1 @@ -364,7 +364,7 @@ test "std.atomic.Queue dump" { sos.reset(); try queue.dumpToStream(SliceOutStream.Error, &sos.stream); - expected = try std.fmt.bufPrint(expected_buffer[0..], + expected = try std.fmtgen.bufPrint(expected_buffer[0..], \\head: 0x{x}=1 \\ 0x{x}=2 \\ (null) diff --git a/lib/std/buffer.zig b/lib/std/buffer.zig index 6313d693b71c..4a6f8e599469 100644 --- a/lib/std/buffer.zig +++ b/lib/std/buffer.zig @@ -65,16 +65,8 @@ pub const Buffer = struct { } pub fn allocPrint(allocator: *Allocator, comptime format: []const u8, args: var) !Buffer { - const countSize = struct { - fn countSize(size: *usize, bytes: []const u8) (error{}!void) { - size.* += bytes.len; - } - }.countSize; - var size: usize = 0; - std.fmt.format(&size, error{}, countSize, format, args) catch |err| switch (err) {}; - var self = try Buffer.initSize(allocator, size); - assert((std.fmt.bufPrint(self.list.items, format, args) catch unreachable).len == size); - return self; + const slice = try std.fmtgen.allocPrint(allocator, format, args); + return Buffer.fromOwnedSlice(allocator, slice); } pub fn deinit(self: *Buffer) void { diff --git a/lib/std/fifo.zig b/lib/std/fifo.zig index 7870979abb6f..935901672bad 100644 --- a/lib/std/fifo.zig +++ b/lib/std/fifo.zig @@ -294,7 +294,11 @@ pub fn LinearFifo( pub usingnamespace if (T == u8) struct { pub fn print(self: *Self, comptime format: []const u8, args: var) !void { - return std.fmt.format(self, error{OutOfMemory}, Self.write, format, args); + var generator = std.fmtgen.Generator([]const u8){}; + _ = async std.fmtgen.format(&generator, format, args); + while (generator.next()) |bytes| { + try self.write(bytes); + } } } else diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index c22bf8849304..804fbb1d5a42 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -483,7 +483,7 @@ pub fn formatType( if (ptr_info.child == u8) { if (fmt.len > 0 and fmt[0] == 's') { const len = mem.len(u8, value); - return formatText(value[0..len], fmt, generator); + return formatText(value[0..len], fmt, options, generator); } } return formatPtr(T.Child, @ptrToInt(value), generator); diff --git a/lib/std/net.zig b/lib/std/net.zig index d4e68b2e1779..cfbd020f67cd 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -437,7 +437,7 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !* const name_c = try std.cstr.addNullByte(allocator, name); defer allocator.free(name_c); - const port_c = try std.fmt.allocPrint(allocator, "{}\x00", .{port}); + const port_c = try std.fmtgen.allocPrint(allocator, "{}\x00", .{port}); defer allocator.free(port_c); const hints = os.addrinfo{ diff --git a/lib/std/os.zig b/lib/std/os.zig index 878d1a57e782..efe2e6cb6ddd 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2605,7 +2605,7 @@ pub fn realpathC(pathname: [*:0]const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealP defer close(fd); var procfs_buf: ["/proc/self/fd/-2147483648".len:0]u8 = undefined; - const proc_path = std.fmt.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable; + const proc_path = std.fmtgen.bufPrint(procfs_buf[0..], "/proc/self/fd/{}\x00", .{fd}) catch unreachable; return readlinkC(@ptrCast([*:0]const u8, proc_path.ptr), out_buffer); } diff --git a/lib/std/progress.zig b/lib/std/progress.zig index 1c5ecd261ae7..af3e29a930b5 100644 --- a/lib/std/progress.zig +++ b/lib/std/progress.zig @@ -130,11 +130,11 @@ pub const Progress = struct { var end: usize = 0; if (self.columns_written > 0) { // restore cursor position - end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", .{self.columns_written}) catch unreachable).len; + end += (std.fmtgen.bufPrint(self.output_buffer[end..], "\x1b[{}D", .{self.columns_written}) catch unreachable).len; self.columns_written = 0; // clear rest of line - end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len; + end += (std.fmtgen.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len; } if (!self.done) { @@ -185,7 +185,7 @@ pub const Progress = struct { } fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: var) void { - if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| { + if (std.fmtgen.bufPrint(self.output_buffer[end.*..], format, args)) |written| { const amt = written.len; end.* += amt; self.columns_written += amt; diff --git a/lib/std/special/build_runner.zig b/lib/std/special/build_runner.zig index 0a5e435a54f3..12f9ac0c30b1 100644 --- a/lib/std/special/build_runner.zig +++ b/lib/std/special/build_runner.zig @@ -2,7 +2,7 @@ const root = @import("@build"); const std = @import("std"); const builtin = @import("builtin"); const io = std.io; -const fmt = std.fmt; +const fmtgen = std.fmtgen; const Builder = std.build.Builder; const mem = std.mem; const process = std.process; @@ -150,7 +150,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: var) !void { const allocator = builder.allocator; for (builder.top_level_steps.toSliceConst()) |top_level_step| { const name = if (&top_level_step.step == builder.default_step) - try fmt.allocPrint(allocator, "{} (default)", .{top_level_step.step.name}) + try fmtgen.allocPrint(allocator, "{} (default)", .{top_level_step.step.name}) else top_level_step.step.name; try out_stream.print(" {s:22} {}\n", .{ name, top_level_step.description }); @@ -172,7 +172,7 @@ fn usage(builder: *Builder, already_ran_build: bool, out_stream: var) !void { try out_stream.print(" (none)\n", .{}); } else { for (builder.available_options_list.toSliceConst()) |option| { - const name = try fmt.allocPrint(allocator, " -D{}=[{}]", .{ + const name = try fmtgen.allocPrint(allocator, " -D{}=[{}]", .{ option.name, Builder.typeIdName(option.type_id), }); diff --git a/lib/std/std.zig b/lib/std/std.zig index dd4d968efbc9..f63e4f7a2591 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -39,6 +39,7 @@ pub const elf = @import("elf.zig"); pub const event = @import("event.zig"); pub const fifo = @import("fifo.zig"); pub const fmt = @import("fmt.zig"); +pub const fmtgen = @import("fmtgen.zig"); pub const fs = @import("fs.zig"); pub const hash = @import("hash.zig"); pub const hash_map = @import("hash_map.zig"); diff --git a/lib/std/target.zig b/lib/std/target.zig index 22fea691c4b5..b3d65076e257 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -321,7 +321,7 @@ pub const Target = union(enum) { pub const stack_align = 16; pub fn zigTriple(self: Target, allocator: *mem.Allocator) ![]u8 { - return std.fmt.allocPrint(allocator, "{}{}-{}-{}", .{ + return std.fmtgen.allocPrint(allocator, "{}{}-{}-{}", .{ @tagName(self.getArch()), Target.archSubArchName(self.getArch()), @tagName(self.getOs()), @@ -370,7 +370,7 @@ pub const Target = union(enum) { } pub fn zigTripleNoSubArch(self: Target, allocator: *mem.Allocator) ![]u8 { - return std.fmt.allocPrint(allocator, "{}-{}-{}", .{ + return std.fmtgen.allocPrint(allocator, "{}-{}-{}", .{ @tagName(self.getArch()), @tagName(self.getOs()), @tagName(self.getAbi()), @@ -378,7 +378,7 @@ pub const Target = union(enum) { } pub fn linuxTriple(self: Target, allocator: *mem.Allocator) ![]u8 { - return std.fmt.allocPrint(allocator, "{}-{}-{}", .{ + return std.fmtgen.allocPrint(allocator, "{}-{}-{}", .{ @tagName(self.getArch()), @tagName(self.getOs()), @tagName(self.getAbi()), diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index a7ba07342ed3..37759bc2b594 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -1049,7 +1049,7 @@ pub const Compilation = struct { } fn addCompileError(self: *Compilation, tree_scope: *Scope.AstTree, span: Span, comptime fmt: []const u8, args: var) !void { - const text = try std.fmt.allocPrint(self.gpa(), fmt, args); + const text = try std.fmtgen.allocPrint(self.gpa(), fmt, args); errdefer self.gpa().free(text); const msg = try Msg.createFromScope(self, tree_scope, span, text); @@ -1059,7 +1059,7 @@ pub const Compilation = struct { } fn addCompileErrorCli(self: *Compilation, realpath: []const u8, comptime fmt: []const u8, args: var) !void { - const text = try std.fmt.allocPrint(self.gpa(), fmt, args); + const text = try std.fmtgen.allocPrint(self.gpa(), fmt, args); errdefer self.gpa().free(text); const msg = try Msg.createFromCli(self, realpath, text); @@ -1152,7 +1152,7 @@ pub const Compilation = struct { const tmp_dir = try self.getTmpDir(); const file_prefix = self.getRandomFileName(); - const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix }); + const file_name = try std.fmtgen.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix }); defer self.gpa().free(file_name); const full_path = try std.fs.path.join(self.gpa(), &[_][]const u8{ tmp_dir, file_name[0..] }); diff --git a/src-self-hosted/dep_tokenizer.zig b/src-self-hosted/dep_tokenizer.zig index eb3500cad17b..2c266a93cbb3 100644 --- a/src-self-hosted/dep_tokenizer.zig +++ b/src-self-hosted/dep_tokenizer.zig @@ -894,7 +894,7 @@ fn printSection(out: var, label: []const u8, bytes: []const u8) !void { fn printLabel(out: var, label: []const u8, bytes: []const u8) !void { var buf: [80]u8 = undefined; - var text = try std.fmt.bufPrint(buf[0..], "{} {} bytes ", label, bytes.len); + var text = try std.fmtgen.bufPrint(buf[0..], "{} {} bytes ", label, bytes.len); try out.write(text); var i: usize = text.len; const end = 79; diff --git a/src-self-hosted/libc_installation.zig b/src-self-hosted/libc_installation.zig index 701823c4be79..0669891c9d32 100644 --- a/src-self-hosted/libc_installation.zig +++ b/src-self-hosted/libc_installation.zig @@ -393,7 +393,7 @@ pub const LibCInstallation = struct { /// caller owns returned memory fn ccPrintFileName(allocator: *Allocator, o_file: []const u8, want_dirname: bool) ![]u8 { const cc_exe = std.os.getenv("CC") orelse "cc"; - const arg1 = try std.fmt.allocPrint(allocator, "-print-file-name={}", .{o_file}); + const arg1 = try std.fmtgen.allocPrint(allocator, "-print-file-name={}", .{o_file}); defer allocator.free(arg1); const argv = [_][]const u8{ cc_exe, arg1 }; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index afe4d10c654f..0a8319280cf4 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -293,13 +293,13 @@ fn constructLinkerArgsCoff(ctx: *Context) !void { const is_library = ctx.comp.kind == .Lib; - const out_arg = try std.fmt.allocPrint(&ctx.arena.allocator, "-OUT:{}\x00", .{ctx.out_file_path.toSliceConst()}); + const out_arg = try std.fmtgen.allocPrint(&ctx.arena.allocator, "-OUT:{}\x00", .{ctx.out_file_path.toSliceConst()}); try ctx.args.append(@ptrCast([*:0]const u8, out_arg.ptr)); if (ctx.comp.haveLibC()) { - try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", .{ctx.libc.msvc_lib_dir.?})).ptr)); - try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", .{ctx.libc.kernel32_lib_dir.?})).ptr)); - try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", .{ctx.libc.lib_dir.?})).ptr)); + try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmtgen.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", .{ctx.libc.msvc_lib_dir.?})).ptr)); + try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmtgen.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", .{ctx.libc.kernel32_lib_dir.?})).ptr)); + try ctx.args.append(@ptrCast([*:0]const u8, (try std.fmtgen.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", .{ctx.libc.lib_dir.?})).ptr)); } if (ctx.link_in_crt) { @@ -307,20 +307,20 @@ fn constructLinkerArgsCoff(ctx: *Context) !void { const d_str = if (ctx.comp.build_mode == .Debug) "d" else ""; if (ctx.comp.is_static) { - const cmt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "libcmt{}.lib\x00", .{d_str}); + const cmt_lib_name = try std.fmtgen.allocPrint(&ctx.arena.allocator, "libcmt{}.lib\x00", .{d_str}); try ctx.args.append(@ptrCast([*:0]const u8, cmt_lib_name.ptr)); } else { - const msvcrt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "msvcrt{}.lib\x00", .{d_str}); + const msvcrt_lib_name = try std.fmtgen.allocPrint(&ctx.arena.allocator, "msvcrt{}.lib\x00", .{d_str}); try ctx.args.append(@ptrCast([*:0]const u8, msvcrt_lib_name.ptr)); } - const vcruntime_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "{}vcruntime{}.lib\x00", .{ + const vcruntime_lib_name = try std.fmtgen.allocPrint(&ctx.arena.allocator, "{}vcruntime{}.lib\x00", .{ lib_str, d_str, }); try ctx.args.append(@ptrCast([*:0]const u8, vcruntime_lib_name.ptr)); - const crt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "{}ucrt{}.lib\x00", .{ lib_str, d_str }); + const crt_lib_name = try std.fmtgen.allocPrint(&ctx.arena.allocator, "{}ucrt{}.lib\x00", .{ lib_str, d_str }); try ctx.args.append(@ptrCast([*:0]const u8, crt_lib_name.ptr)); // Visual C++ 2015 Conformance Changes @@ -380,7 +380,7 @@ fn constructLinkerArgsMachO(ctx: *Context) !void { .IPhoneOS => try ctx.args.append("-iphoneos_version_min"), .IPhoneOSSimulator => try ctx.args.append("-ios_simulator_version_min"), } - const ver_str = try std.fmt.allocPrint(&ctx.arena.allocator, "{}.{}.{}\x00", .{ + const ver_str = try std.fmtgen.allocPrint(&ctx.arena.allocator, "{}.{}.{}\x00", .{ platform.major, platform.minor, platform.micro, @@ -442,7 +442,7 @@ fn constructLinkerArgsMachO(ctx: *Context) !void { try ctx.args.append("-lSystem"); } else { if (mem.indexOfScalar(u8, lib.name, '/') == null) { - const arg = try std.fmt.allocPrint(&ctx.arena.allocator, "-l{}\x00", .{lib.name}); + const arg = try std.fmtgen.allocPrint(&ctx.arena.allocator, "-l{}\x00", .{lib.name}); try ctx.args.append(@ptrCast([*:0]const u8, arg.ptr)); } else { const arg = try std.cstr.addNullByte(&ctx.arena.allocator, lib.name); diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index 62b7914dbc8d..a30eb99cd8ca 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -81,7 +81,7 @@ pub const TestContext = struct { msg: []const u8, ) !void { var file_index_buf: [20]u8 = undefined; - const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", self.file_index.incr()); + const file_index = try std.fmtgen.bufPrint(file_index_buf[0..], "{}", self.file_index.incr()); const file1_path = try std.fs.path.join(allocator, [_][]const u8{ tmp_dir_name, file_index, file1 }); if (std.fs.path.dirname(file1_path)) |dirname| { @@ -114,10 +114,10 @@ pub const TestContext = struct { expected_output: []const u8, ) !void { var file_index_buf: [20]u8 = undefined; - const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", self.file_index.incr()); + const file_index = try std.fmtgen.bufPrint(file_index_buf[0..], "{}", self.file_index.incr()); const file1_path = try std.fs.path.join(allocator, [_][]const u8{ tmp_dir_name, file_index, file1 }); - const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", file1_path, (Target{ .Native = {} }).exeFileExt()); + const output_file = try std.fmtgen.allocPrint(allocator, "{}-out{}", file1_path, (Target{ .Native = {} }).exeFileExt()); if (std.fs.path.dirname(file1_path)) |dirname| { try std.fs.makePath(allocator, dirname); } diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index 0b8fabc6c2ce..b93623e0e211 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -88,7 +88,7 @@ const Scope = struct { var proposed_name = name; while (scope.contains(proposed_name)) { scope.mangle_count += 1; - proposed_name = try std.fmt.allocPrint(c.a(), "{}_{}", .{ name, scope.mangle_count }); + proposed_name = try std.fmtgen.allocPrint(c.a(), "{}_{}", .{ name, scope.mangle_count }); } try scope.variables.push(.{ .name = name, .alias = proposed_name }); return proposed_name; @@ -245,7 +245,7 @@ pub const Context = struct { const line = ZigClangSourceManager_getSpellingLineNumber(c.source_manager, spelling_loc); const column = ZigClangSourceManager_getSpellingColumnNumber(c.source_manager, spelling_loc); - return std.fmt.allocPrint(c.a(), "{}:{}:{}", .{ filename, line, column }); + return std.fmtgen.allocPrint(c.a(), "{}:{}:{}", .{ filename, line, column }); } }; @@ -489,7 +489,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const ZigClangFunctionDecl) Error!void { const mangled_param_name = try block_scope.makeMangledName(c, param_name); const arg_name = blk: { - const bare_arg_name = try std.fmt.allocPrint(c.a(), "arg_{}", .{mangled_param_name}); + const bare_arg_name = try std.fmtgen.allocPrint(c.a(), "arg_{}", .{mangled_param_name}); break :blk try block_scope.makeMangledName(c, bare_arg_name); }; @@ -529,7 +529,7 @@ fn visitVarDecl(c: *Context, var_decl: *const ZigClangVarDecl) Error!void { // TODO https://github.com/ziglang/zig/issues/3756 // TODO https://github.com/ziglang/zig/issues/1802 - const checked_name = if (isZigPrimitiveType(var_name)) try std.fmt.allocPrint(c.a(), "_{}", .{var_name}) else var_name; + const checked_name = if (isZigPrimitiveType(var_name)) try std.fmtgen.allocPrint(c.a(), "_{}", .{var_name}) else var_name; const var_decl_loc = ZigClangVarDecl_getLocation(var_decl); const qual_type = ZigClangVarDecl_getTypeSourceInfo_getType(var_decl); @@ -588,7 +588,7 @@ fn visitVarDecl(c: *Context, var_decl: *const ZigClangVarDecl) Error!void { _ = try appendToken(rp.c, .LParen, "("); const expr = try transCreateNodeStringLiteral( rp.c, - try std.fmt.allocPrint(rp.c.a(), "\"{}\"", .{str_ptr[0..str_len]}), + try std.fmtgen.allocPrint(rp.c.a(), "\"{}\"", .{str_ptr[0..str_len]}), ); _ = try appendToken(rp.c, .RParen, ")"); @@ -645,7 +645,7 @@ fn transTypeDef(c: *Context, typedef_decl: *const ZigClangTypedefNameDecl) Error // TODO https://github.com/ziglang/zig/issues/3756 // TODO https://github.com/ziglang/zig/issues/1802 - const checked_name = if (isZigPrimitiveType(typedef_name)) try std.fmt.allocPrint(c.a(), "_{}", .{typedef_name}) else typedef_name; + const checked_name = if (isZigPrimitiveType(typedef_name)) try std.fmtgen.allocPrint(c.a(), "_{}", .{typedef_name}) else typedef_name; if (mem.eql(u8, checked_name, "uint8_t")) return transTypeDefAsBuiltin(c, typedef_decl, "u8") @@ -700,7 +700,7 @@ fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?* var bare_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, record_decl))); var is_unnamed = false; if (ZigClangRecordDecl_isAnonymousStructOrUnion(record_decl) or bare_name.len == 0) { - bare_name = try std.fmt.allocPrint(c.a(), "unnamed_{}", .{c.getMangle()}); + bare_name = try std.fmtgen.allocPrint(c.a(), "unnamed_{}", .{c.getMangle()}); is_unnamed = true; } @@ -717,7 +717,7 @@ fn transRecordDecl(c: *Context, record_decl: *const ZigClangRecordDecl) Error!?* return null; } - const name = try std.fmt.allocPrint(c.a(), "{}_{}", .{ container_kind_name, bare_name }); + const name = try std.fmtgen.allocPrint(c.a(), "{}_{}", .{ container_kind_name, bare_name }); _ = try c.decl_table.put(@ptrToInt(ZigClangRecordDecl_getCanonicalDecl(record_decl)), name); const node = try transCreateNodeVarDecl(c, !is_unnamed, true, name); @@ -805,11 +805,11 @@ fn transEnumDecl(c: *Context, enum_decl: *const ZigClangEnumDecl) Error!?*ast.No var bare_name = try c.str(ZigClangDecl_getName_bytes_begin(@ptrCast(*const ZigClangDecl, enum_decl))); var is_unnamed = false; if (bare_name.len == 0) { - bare_name = try std.fmt.allocPrint(c.a(), "unnamed_{}", .{c.getMangle()}); + bare_name = try std.fmtgen.allocPrint(c.a(), "unnamed_{}", .{c.getMangle()}); is_unnamed = true; } - const name = try std.fmt.allocPrint(c.a(), "enum_{}", .{bare_name}); + const name = try std.fmtgen.allocPrint(c.a(), "enum_{}", .{bare_name}); _ = try c.decl_table.put(@ptrToInt(ZigClangEnumDecl_getCanonicalDecl(enum_decl)), name); const node = try transCreateNodeVarDecl(c, !is_unnamed, true, name); node.eq_token = try appendToken(c, .Equal, "="); @@ -1626,7 +1626,7 @@ fn escapeChar(c: u8, char_buf: *[4]u8) []const u8 { '\n' => "\\n"[0..], '\r' => "\\r"[0..], '\t' => "\\t"[0..], - else => std.fmt.bufPrint(char_buf[0..], "{c}", .{c}) catch unreachable, + else => std.fmtgen.bufPrint(char_buf[0..], "{c}", .{c}) catch unreachable, }; } @@ -2216,7 +2216,7 @@ fn transCase( ) TransError!*ast.Node { const block_scope = scope.findBlockScope(rp.c) catch unreachable; const switch_scope = scope.getSwitch(); - const label = try std.fmt.allocPrint(rp.c.a(), "__case_{}", .{switch_scope.cases.len - @boolToInt(switch_scope.has_default)}); + const label = try std.fmtgen.allocPrint(rp.c.a(), "__case_{}", .{switch_scope.cases.len - @boolToInt(switch_scope.has_default)}); _ = try appendToken(rp.c, .Semicolon, ";"); const expr = if (ZigClangCaseStmt_getRHS(stmt)) |rhs| blk: { @@ -4252,7 +4252,7 @@ fn finishTransFnProto( _ = try appendToken(rp.c, .LParen, "("); const expr = try transCreateNodeStringLiteral( rp.c, - try std.fmt.allocPrint(rp.c.a(), "\"{}\"", .{str_ptr[0..str_len]}), + try std.fmtgen.allocPrint(rp.c.a(), "\"{}\"", .{str_ptr[0..str_len]}), ); _ = try appendToken(rp.c, .RParen, ")"); @@ -4388,15 +4388,14 @@ fn appendToken(c: *Context, token_id: Token.Id, bytes: []const u8) !ast.TokenInd } fn appendTokenFmt(c: *Context, token_id: Token.Id, comptime format: []const u8, args: var) !ast.TokenIndex { - const S = struct { - fn callback(context: *Context, bytes: []const u8) error{OutOfMemory}!void { - return context.source_buffer.append(bytes); - } - }; const start_index = c.source_buffer.len(); errdefer c.source_buffer.shrink(start_index); - try std.fmt.format(c, error{OutOfMemory}, S.callback, format, args); + var generator = std.fmtgen.Generator([]const u8){}; + _ = async std.fmtgen.format(&generator, format, args); + while (generator.next()) |bytes| { + try c.source_buffer.append(bytes); + } const end_index = c.source_buffer.len(); const token_index = c.tree.tokens.len; const new_token = try c.tree.tokens.addOne(); @@ -4499,7 +4498,7 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { const name = try c.str(raw_name); // TODO https://github.com/ziglang/zig/issues/3756 // TODO https://github.com/ziglang/zig/issues/1802 - const mangled_name = if (isZigPrimitiveType(name)) try std.fmt.allocPrint(c.a(), "_{}", .{name}) else name; + const mangled_name = if (isZigPrimitiveType(name)) try std.fmtgen.allocPrint(c.a(), "_{}", .{name}) else name; if (scope.containsNow(mangled_name)) { continue; } @@ -4725,7 +4724,7 @@ fn parseCNumLit(c: *Context, tok: *CToken, source_loc: ZigClangSourceLocation) P switch (tok.bytes[1]) { '0'...'7' => { // octal - return transCreateNodeInt(c, try std.fmt.allocPrint(c.a(), "0o{}", .{tok.bytes})); + return transCreateNodeInt(c, try std.fmtgen.allocPrint(c.a(), "0o{}", .{tok.bytes})); }, else => {}, } diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index d3f3ba746a74..b45e5f04205a 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -580,7 +580,7 @@ pub const Type = struct { errdefer comp.gpa().destroy(self); const u_or_i = "ui"[@boolToInt(key.is_signed)]; - const name = try std.fmt.allocPrint(comp.gpa(), "{c}{}", .{ u_or_i, key.bit_count }); + const name = try std.fmtgen.allocPrint(comp.gpa(), "{c}{}", .{ u_or_i, key.bit_count }); errdefer comp.gpa().free(name); self.base.init(comp, .Int, name); @@ -763,13 +763,13 @@ pub const Type = struct { .Non => "", }; const name = switch (self.key.alignment) { - .Abi => try std.fmt.allocPrint(comp.gpa(), "{}{}{}{}", .{ + .Abi => try std.fmtgen.allocPrint(comp.gpa(), "{}{}{}{}", .{ size_str, mut_str, vol_str, self.key.child_type.name, }), - .Override => |alignment| try std.fmt.allocPrint(comp.gpa(), "{}align<{}> {}{}{}", .{ + .Override => |alignment| try std.fmtgen.allocPrint(comp.gpa(), "{}align<{}> {}{}{}", .{ size_str, alignment, mut_str, @@ -844,7 +844,7 @@ pub const Type = struct { }; errdefer comp.gpa().destroy(self); - const name = try std.fmt.allocPrint(comp.gpa(), "[{}]{}", .{ key.len, key.elem_type.name }); + const name = try std.fmtgen.allocPrint(comp.gpa(), "[{}]{}", .{ key.len, key.elem_type.name }); errdefer comp.gpa().free(name); self.base.init(comp, .Array, name); diff --git a/test/tests.zig b/test/tests.zig index 46723598028e..53fed5a15d80 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -8,7 +8,7 @@ const Buffer = std.Buffer; const io = std.io; const fs = std.fs; const mem = std.mem; -const fmt = std.fmt; +const fmtgen = std.fmtgen; const ArrayList = std.ArrayList; const builtin = @import("builtin"); const Mode = builtin.Mode; @@ -735,7 +735,7 @@ pub const CompareOutputContext = struct { switch (case.special) { Special.Asm => { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "assemble-and-link {}", .{ + const annotated_case_name = fmtgen.allocPrint(self.b.allocator, "assemble-and-link {}", .{ case.name, }) catch unreachable; if (self.test_filter) |filter| { @@ -766,7 +766,7 @@ pub const CompareOutputContext = struct { }, Special.None => { for (self.modes) |mode| { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {} ({})", .{ + const annotated_case_name = fmtgen.allocPrint(self.b.allocator, "{} {} ({})", .{ "compare-output", case.name, @tagName(mode), @@ -802,7 +802,7 @@ pub const CompareOutputContext = struct { } }, Special.RuntimeSafety => { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "safety {}", .{case.name}) catch unreachable; + const annotated_case_name = fmtgen.allocPrint(self.b.allocator, "safety {}", .{case.name}) catch unreachable; if (self.test_filter) |filter| { if (mem.indexOf(u8, annotated_case_name, filter) == null) return; } @@ -855,7 +855,7 @@ pub const StackTracesContext = struct { const expect_for_mode = expect[@enumToInt(mode)]; if (expect_for_mode.len == 0) continue; - const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {} ({})", .{ + const annotated_case_name = fmtgen.allocPrint(self.b.allocator, "{} {} ({})", .{ "stack-trace", name, @tagName(mode), @@ -1319,7 +1319,7 @@ pub const CompileErrorContext = struct { pub fn addCase(self: *CompileErrorContext, case: *const TestCase) void { const b = self.b; - const annotated_case_name = fmt.allocPrint(self.b.allocator, "compile-error {}", .{ + const annotated_case_name = fmtgen.allocPrint(self.b.allocator, "compile-error {}", .{ case.name, }) catch unreachable; if (self.test_filter) |filter| { @@ -1389,7 +1389,7 @@ pub const StandaloneContext = struct { const b = self.b; for (self.modes) |mode| { - const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {} ({})", .{ + const annotated_case_name = fmtgen.allocPrint(self.b.allocator, "build {} ({})", .{ root_src, @tagName(mode), }) catch unreachable; @@ -1616,7 +1616,7 @@ pub const TranslateCContext = struct { const b = self.b; const translate_c_cmd = "translate-c"; - const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {}", .{ translate_c_cmd, case.name }) catch unreachable; + const annotated_case_name = fmtgen.allocPrint(self.b.allocator, "{} {}", .{ translate_c_cmd, case.name }) catch unreachable; if (self.test_filter) |filter| { if (mem.indexOf(u8, annotated_case_name, filter) == null) return; } @@ -1760,7 +1760,7 @@ pub const GenHContext = struct { ) catch unreachable; const mode = builtin.Mode.Debug; - const annotated_case_name = fmt.allocPrint(self.b.allocator, "gen-h {} ({})", .{ case.name, @tagName(mode) }) catch unreachable; + const annotated_case_name = fmtgen.allocPrint(self.b.allocator, "gen-h {} ({})", .{ case.name, @tagName(mode) }) catch unreachable; if (self.test_filter) |filter| { if (mem.indexOf(u8, annotated_case_name, filter) == null) return; } diff --git a/tools/process_headers.zig b/tools/process_headers.zig index fd39cb93addb..0ece2edc335f 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -300,7 +300,7 @@ pub fn main() !void { std.debug.warn("unrecognized C ABI: {}\n", abi_name); usageAndExit(args[0]); }; - const generic_name = try std.fmt.allocPrint(allocator, "generic-{}", abi_name); + const generic_name = try std.fmtgen.allocPrint(allocator, "generic-{}", abi_name); // TODO compiler crashed when I wrote this the canonical way var libc_targets: []const LibCTarget = undefined; @@ -440,7 +440,7 @@ pub fn main() !void { .specific => |a| @tagName(a), else => @tagName(dest_target.arch), }; - const out_subpath = try std.fmt.allocPrint( + const out_subpath = try std.fmtgen.allocPrint( allocator, "{}-{}-{}", arch_name, diff --git a/tools/update_glibc.zig b/tools/update_glibc.zig index ca2ee295ecc7..79d7946185b7 100644 --- a/tools/update_glibc.zig +++ b/tools/update_glibc.zig @@ -155,7 +155,7 @@ pub fn main() !void { const fn_set = &target_funcs_gop.kv.value.list; for (lib_names) |lib_name, lib_name_index| { - const basename = try fmt.allocPrint(allocator, "lib{}.abilist", lib_name); + const basename = try fmtgen.allocPrint(allocator, "lib{}.abilist", lib_name); const abi_list_filename = blk: { if (abi_list.targets[0].abi == .gnuabi64 and std.mem.eql(u8, lib_name, "c")) { break :blk try fs.path.join(allocator, [_][]const u8{ prefix, abi_list.path, "n64", basename }); From 42a47eb0efc3c1047e2644268cbf4e3050677e7b Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Sun, 5 Jan 2020 23:56:33 -0600 Subject: [PATCH 22/33] Leverage comptime recursion --- lib/std/fmtgen.zig | 56 +++++++++++++--------------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 804fbb1d5a42..52af00ebee4f 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -150,10 +150,6 @@ pub fn format( comptime var specifier_end = 0; comptime var options = FormatOptions{}; - // TODO: calculate this - var recur_stack: [0x10000]u8 = undefined; - var recur_allocator = std.heap.FixedBufferAllocator.init(&recur_stack); - inline for (fmt) |c, i| { switch (state) { .Start => switch (c) { @@ -211,7 +207,6 @@ pub fn format( fmt[0..0], options, generator, - &recur_allocator.allocator, default_max_depth, ); @@ -243,7 +238,6 @@ pub fn format( fmt[specifier_start..i], options, generator, - &recur_allocator.allocator, default_max_depth, ); state = .Start; @@ -289,7 +283,6 @@ pub fn format( fmt[specifier_start..specifier_end], options, generator, - &recur_allocator.allocator, default_max_depth, ); state = .Start; @@ -316,7 +309,6 @@ pub fn format( fmt[specifier_start..specifier_end], options, generator, - &recur_allocator.allocator, default_max_depth, ); state = .Start; @@ -352,9 +344,14 @@ pub fn formatType( comptime fmt: []const u8, options: FormatOptions, generator: *Generator([]const u8), - allocator: *mem.Allocator, - max_depth: usize, + comptime max_depth: comptime_int, ) void { + if (max_depth < 0) { + // This shouldn't ever be reached as we account for it later in the function. + // But the compiler analysis phase gets confused and generates infinite versions of this function if there's no check. + @compileError("max_depth less than 0"); + } + if (comptime std.mem.eql(u8, fmt, "*")) { return formatPtr(@TypeOf(value).Child, @ptrToInt(value), generator); } @@ -372,25 +369,16 @@ pub fn formatType( }, .Optional => { if (value) |payload| { - // return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, allocator, max_depth }); - // TODO: reenable tail call once it is fixed https://github.com/ziglang/zig/issues/4060 - var frame = allocator.create( - @TypeOf(async formatType(payload, fmt, options, generator, allocator, max_depth)), - ) catch |err| switch (err) { - error.OutOfMemory => return generator.yield(" ??OOM?? "), - }; - defer allocator.destroy(frame); - frame.* = async formatType(payload, fmt, options, generator, allocator, max_depth); - await frame.*; + return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, max_depth }); } else { return generator.yield("null"); } }, .ErrorUnion => { if (value) |payload| { - return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, allocator, max_depth }); + return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, max_depth }); } else |err| { - return @call(.{ .modifier = .always_tail }, formatType, .{ err, fmt, options, generator, allocator, max_depth }); + return @call(.{ .modifier = .always_tail }, formatType, .{ err, fmt, options, generator, max_depth }); } }, .ErrorSet => { @@ -403,7 +391,7 @@ pub fn formatType( // } generator.yield(@typeName(T)); generator.yield("."); - return @call(.{ .modifier = .always_tail }, formatType, .{ @tagName(value), "", options, generator, allocator, max_depth }); + return @call(.{ .modifier = .always_tail }, formatType, .{ @tagName(value), "", options, generator, max_depth }); }, .Union => { // if (comptime std.meta.trait.hasFn("format")(T)) { @@ -420,14 +408,7 @@ pub fn formatType( generator.yield(" = "); inline for (info.fields) |u_field| { if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { - var frame = allocator.create( - @TypeOf(async formatType(@field(value, u_field.name), "", options, generator, allocator, max_depth - 1)), - ) catch |err| switch (err) { - error.OutOfMemory => return generator.yield(" ??OOM?? "), - }; - defer allocator.destroy(frame); - frame.* = async formatType(@field(value, u_field.name), "", options, generator, allocator, max_depth - 1); - await frame.*; + formatType(@field(value, u_field.name), "", options, generator, max_depth - 1); } } generator.yield(" }"); @@ -455,14 +436,7 @@ pub fn formatType( generator.yield(@memberName(T, field_i)); generator.yield(" = "); - var frame = allocator.create( - @TypeOf(async formatType(@field(value, @memberName(T, field_i)), "", options, generator, allocator, max_depth - 1)), - ) catch |err| switch (err) { - error.OutOfMemory => return generator.yield(" ??OOM?? "), - }; - defer allocator.destroy(frame); - frame.* = async formatType(@field(value, @memberName(T, field_i)), "", options, generator, allocator, max_depth - 1); - await frame.*; + formatType(@field(value, @memberName(T, field_i)), "", options, generator, max_depth - 1); } generator.yield(" }"); }, @@ -475,7 +449,7 @@ pub fn formatType( return formatPtr(T.Child, @ptrToInt(value), generator); }, builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { - return @call(.{ .modifier = .always_tail }, formatType, .{ value.*, fmt, options, generator, allocator, max_depth }); + return @call(.{ .modifier = .always_tail }, formatType, .{ value.*, fmt, options, generator, max_depth }); }, else => return formatPtr(T.Child, @ptrToInt(value), generator), }, @@ -513,7 +487,7 @@ pub fn formatType( .sentinel = null, }, }); - return @call(.{ .modifier = .always_tail }, formatType, .{ @as(Slice, &value), fmt, options, generator, allocator, max_depth }); + return @call(.{ .modifier = .always_tail }, formatType, .{ @as(Slice, &value), fmt, options, generator, max_depth }); }, .Fn => { return formatPtr(T, @ptrToInt(value), generator); From 5d0b2d90253b4d62c70195cabf0b68bdfd0d3add Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Mon, 6 Jan 2020 18:21:23 -0600 Subject: [PATCH 23/33] Convert Generator to union based state machine --- lib/std/fifo.zig | 2 +- lib/std/fmtgen.zig | 85 +++++++++++++++++++++++---------- src-self-hosted/translate_c.zig | 2 +- 3 files changed, 62 insertions(+), 27 deletions(-) diff --git a/lib/std/fifo.zig b/lib/std/fifo.zig index 935901672bad..eeb3702d2d28 100644 --- a/lib/std/fifo.zig +++ b/lib/std/fifo.zig @@ -294,7 +294,7 @@ pub fn LinearFifo( pub usingnamespace if (T == u8) struct { pub fn print(self: *Self, comptime format: []const u8, args: var) !void { - var generator = std.fmtgen.Generator([]const u8){}; + var generator = std.fmtgen.Generator([]const u8).init(); _ = async std.fmtgen.format(&generator, format, args); while (generator.next()) |bytes| { try self.write(bytes); diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 52af00ebee4f..8af2662ad182 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -22,32 +22,67 @@ pub const FormatOptions = struct { }; pub fn Generator(comptime Out: type) type { - return struct { - suspended: ?anyframe = null, - out: ?Out = undefined, - fresh: bool = true, + return union(enum) { + /// Newly initialized or completed. + inactive: void, + + /// Currently running. This is not a reliable check and exists to help prevent accidental concurrent writes. + running: void, + + /// Async function yielded a value that has not been consumed yet. + /// This exists because "priming the generator" dumps in a value without a next(). + yielded: YieldResult, + + /// Previously yielded result was consumed. next() will resume the suspended frame. + consumed: YieldResult, + + const YieldResult = struct { + frame: anyframe, + out: Out, + }; + + pub fn init() @This() { + return .{ .inactive = {} }; + } pub fn yield(self: *@This(), out: Out) void { - assert(self.suspended == null); - assert(self.out == null); - self.out = out; - self.suspended = @frame(); - suspend; + const state = std.meta.activeTag(self.*); + assert(state == .inactive or state == .running); + + suspend { + self.* = .{ + .yielded = .{ + .frame = @frame(), + .out = out, + }, + }; + } + } + + fn consume(self: *@This(), result: YieldResult) Out { + self.* = .{ .consumed = result }; + return result.out; } pub fn next(self: *@This()) ?Out { - if (self.fresh) { - self.fresh = false; - return self.out; - } else if (self.suspended) |suspended| { - // Copy elision... bug? - const copy = suspended; - self.suspended = null; - self.out = null; - resume copy; - return self.out; - } else { - return null; + switch (self.*) { + .running => unreachable, // Generator is already running. Probably a concurrency bug. + .inactive => return null, + .yielded => |result| return self.consume(result), + .consumed => |orig| { + // Copy elision footgun + const copy = orig; + self.* = .{ .running = {} }; + resume copy.frame; + switch (self.*) { + .inactive, .consumed => unreachable, // Bad state. Probably a concurrency bug. + .yielded => |result| return self.consume(result), + .running => { + self.* = .{ .inactive = {} }; + return null; + }, + } + }, } } }; @@ -1003,7 +1038,7 @@ fn formatIntUnsigned( pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) usize { var index: usize = 0; - var generator = Generator([]const u8){}; + var generator = Generator([]const u8).init(); _ = async formatInt(value, base, uppercase, options, &generator); while (generator.next()) |bytes| { mem.copy(u8, out_buf[index..], bytes); @@ -1116,7 +1151,7 @@ pub const BufPrintError = error{ pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: var) BufPrintError![]u8 { var remaining = buf; - var generator = Generator([]const u8){}; + var generator = Generator([]const u8).init(); _ = async format(&generator, fmt, args); while (generator.next()) |bytes| { if (remaining.len < bytes.len) { @@ -1141,7 +1176,7 @@ pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: var fn countSize(comptime fmt: []const u8, args: var) usize { var size: usize = 0; - var generator = Generator([]const u8){}; + var generator = Generator([]const u8).init(); _ = async format(&generator, fmt, args); while (generator.next()) |bytes| { size += bytes.len; @@ -1626,7 +1661,7 @@ test "formatIntValue with comptime_int" { var buf = try std.Buffer.init(std.debug.global_allocator, ""); - var generator = Generator([]const u8){}; + var generator = Generator([]const u8).init(); _ = async formatIntValue(value, "", FormatOptions{}, &generator); while (generator.next()) |bytes| { try buf.append(bytes); diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index b93623e0e211..60656fb7a89b 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -4391,7 +4391,7 @@ fn appendTokenFmt(c: *Context, token_id: Token.Id, comptime format: []const u8, const start_index = c.source_buffer.len(); errdefer c.source_buffer.shrink(start_index); - var generator = std.fmtgen.Generator([]const u8){}; + var generator = std.fmtgen.Generator([]const u8).init(); _ = async std.fmtgen.format(&generator, format, args); while (generator.next()) |bytes| { try c.source_buffer.append(bytes); From 24e43df682f432f9b2695b4fb6addd942aaf4234 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Mon, 6 Jan 2020 20:15:23 -0600 Subject: [PATCH 24/33] General cleanup --- lib/std/fmtgen.zig | 54 +++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 8af2662ad182..5373603fce8f 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -404,16 +404,16 @@ pub fn formatType( }, .Optional => { if (value) |payload| { - return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, max_depth }); + return formatType(payload, fmt, options, generator, max_depth); } else { return generator.yield("null"); } }, .ErrorUnion => { if (value) |payload| { - return @call(.{ .modifier = .always_tail }, formatType, .{ payload, fmt, options, generator, max_depth }); + return formatType(payload, fmt, options, generator, max_depth); } else |err| { - return @call(.{ .modifier = .always_tail }, formatType, .{ err, fmt, options, generator, max_depth }); + return formatType(err, fmt, options, generator, max_depth); } }, .ErrorSet => { @@ -426,33 +426,31 @@ pub fn formatType( // } generator.yield(@typeName(T)); generator.yield("."); - return @call(.{ .modifier = .always_tail }, formatType, .{ @tagName(value), "", options, generator, max_depth }); + return generator.yield(@tagName(value)); }, - .Union => { + .Union => |union_info| { // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); // } - generator.yield(@typeName(T)); - if (max_depth == 0) { - return generator.yield("{ ... }"); - } - const info = @typeInfo(T).Union; - if (info.tag_type) |UnionTagType| { + if (union_info.tag_type) |UnionTagType| { + generator.yield(@typeName(T)); + if (max_depth == 0) { + return generator.yield("{ ... }"); + } generator.yield("{ ."); generator.yield(@tagName(@as(UnionTagType, value))); generator.yield(" = "); - inline for (info.fields) |u_field| { + inline for (union_info.fields) |u_field| { if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { formatType(@field(value, u_field.name), "", options, generator, max_depth - 1); } } generator.yield(" }"); } else { - generator.yield("@"); - return formatInt(@ptrToInt(&value), 16, false, FormatOptions{}, generator); + return formatPtr(T, @ptrToInt(&value), generator); } }, - .Struct => { + .Struct => |struct_info| { // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); // } @@ -460,18 +458,18 @@ pub fn formatType( if (max_depth == 0) { return generator.yield("{ ... }"); } - comptime var field_i = 0; + generator.yield("{"); - inline while (field_i < @memberCount(T)) : (field_i += 1) { - if (field_i == 0) { + inline for (struct_info.fields) |field, i| { + if (i == 0) { generator.yield(" ."); } else { generator.yield(", ."); } - generator.yield(@memberName(T, field_i)); + generator.yield(field.name); generator.yield(" = "); - formatType(@field(value, @memberName(T, field_i)), "", options, generator, max_depth - 1); + formatType(@field(value, field.name), "", options, generator, max_depth - 1); } generator.yield(" }"); }, @@ -484,7 +482,7 @@ pub fn formatType( return formatPtr(T.Child, @ptrToInt(value), generator); }, builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { - return @call(.{ .modifier = .always_tail }, formatType, .{ value.*, fmt, options, generator, max_depth }); + return formatType(value.*, fmt, options, generator, max_depth); }, else => return formatPtr(T.Child, @ptrToInt(value), generator), }, @@ -522,7 +520,7 @@ pub fn formatType( .sentinel = null, }, }); - return @call(.{ .modifier = .always_tail }, formatType, .{ @as(Slice, &value), fmt, options, generator, max_depth }); + return formatType(@as(Slice, &value), fmt, options, generator, max_depth); }, .Fn => { return formatPtr(T, @ptrToInt(value), generator); @@ -638,7 +636,7 @@ pub fn formatAsciiChar( generator: *Generator([]const u8), ) void { if (std.ascii.isPrint(c)) - return generator.yield(@as(*const [1]u8, &c)[0..]); + return generator.yield(@as(*const [1]u8, &c)); @panic("FIXME"); // return format(context, Errors, output, "\\x{x:0<2}", .{c}); } @@ -654,7 +652,7 @@ pub fn formatBuf( 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) { - generator.yield(@as(*const [1]u8, &pad_byte)[0..1]); + generator.yield(@as(*const [1]u8, &pad_byte)); } } @@ -978,15 +976,13 @@ fn formatIntSigned( const uint = @IntType(false, @TypeOf(value).bit_count); if (value < 0) { - const minus_sign: u8 = '-'; - generator.yield(@as(*const [1]u8, &minus_sign)[0..]); + generator.yield("-"); const new_value = @intCast(uint, -(value + 1)) + 1; return formatIntUnsigned(new_value, base, uppercase, new_options, generator); } else if (options.width == null or options.width.? == 0) { return formatIntUnsigned(@intCast(uint, value), base, uppercase, options, generator); } else { - const plus_sign: u8 = '+'; - generator.yield(@as(*const [1]u8, &plus_sign)[0..]); + generator.yield("+"); const new_value = @intCast(uint, value); return formatIntUnsigned(new_value, base, uppercase, new_options, generator); } @@ -1022,7 +1018,7 @@ fn formatIntUnsigned( const zero_byte: u8 = options.fill; var leftover_padding = padding - index; while (true) { - generator.yield(@as(*const [1]u8, &zero_byte)[0..]); + generator.yield(@as(*const [1]u8, &zero_byte)); leftover_padding -= 1; if (leftover_padding == 0) break; } From 940fa5b5258165a546ad01198a19f7947295280c Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Tue, 7 Jan 2020 22:40:51 -0600 Subject: [PATCH 25/33] Migrate outStream (except debug.warn) --- lib/std/debug.zig | 8 ++-- lib/std/fs/file.zig | 21 +++++++++ lib/std/io.zig | 1 + lib/std/io/out_stream.zig | 6 ++- lib/std/io/out_stream_old.zig | 87 +++++++++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 lib/std/io/out_stream_old.zig diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 94f81908daff..e762ee981eb2 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -41,9 +41,9 @@ const Module = struct { /// Tries to write to stderr, unbuffered, and ignores any error returned. /// Does not append a newline. var stderr_file: File = undefined; -var stderr_file_out_stream: File.OutStream = undefined; +var stderr_file_out_stream: File.OutStreamOld = undefined; -var stderr_stream: ?*io.OutStream(File.WriteError) = null; +var stderr_stream: ?*io.OutStreamOld(File.WriteError) = null; var stderr_mutex = std.Mutex.init(); pub fn warn(comptime fmt: []const u8, args: var) void { @@ -53,12 +53,12 @@ pub fn warn(comptime fmt: []const u8, args: var) void { stderr.print(fmt, args) catch return; } -pub fn getStderrStream() *io.OutStream(File.WriteError) { +pub fn getStderrStream() *io.OutStreamOld(File.WriteError) { if (stderr_stream) |st| { return st; } else { stderr_file = io.getStdErr(); - stderr_file_out_stream = stderr_file.outStream(); + stderr_file_out_stream = stderr_file.outStreamOld(); const st = &stderr_file_out_stream.stream; stderr_stream = st; return st; diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index c7265a8923fc..dbc18a4fa90b 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -318,6 +318,13 @@ pub const File = struct { }; } + pub fn outStreamOld(file: File) OutStreamOld { + return OutStreamOld{ + .file = file, + .stream = OutStreamOld.Stream{ .writeFn = OutStreamOld.writeFn }, + }; + } + pub fn seekableStream(file: File) SeekableStream { return SeekableStream{ .file = file, @@ -358,6 +365,20 @@ pub const File = struct { } }; + // TODO: delete + pub const OutStreamOld = struct { + file: File, + stream: Stream, + + pub const Error = WriteError; + pub const Stream = io.OutStreamOld(Error); + + fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { + const self = @fieldParentPtr(OutStreamOld, "stream", out_stream); + return self.file.write(bytes); + } + }; + /// Implementation of io.SeekableStream trait for File pub const SeekableStream = struct { file: File, diff --git a/lib/std/io.zig b/lib/std/io.zig index 93f2ae7680da..172fd36a4ee3 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -87,6 +87,7 @@ pub const SliceSeekableInStream = @import("io/seekable_stream.zig").SliceSeekabl pub const COutStream = @import("io/c_out_stream.zig").COutStream; pub const InStream = @import("io/in_stream.zig").InStream; pub const OutStream = @import("io/out_stream.zig").OutStream; +pub const OutStreamOld = @import("io/out_stream_old.zig").OutStreamOld; /// Deprecated; use `std.fs.Dir.writeFile`. pub fn writeFile(path: []const u8, data: []const u8) !void { diff --git a/lib/std/io/out_stream.zig b/lib/std/io/out_stream.zig index b8f5db6fff72..71876d4ea2ed 100644 --- a/lib/std/io/out_stream.zig +++ b/lib/std/io/out_stream.zig @@ -36,7 +36,11 @@ pub fn OutStream(comptime WriteError: type) type { } pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void { - return std.fmt.format(self, Error, self.writeFn, format, args); + var generator = std.fmtgen.Generator([]const u8).init(); + _ = async std.fmtgen.format(&generator, format, args); + while (generator.next()) |bytes| { + try self.writeFn(self, bytes); + } } pub fn writeByte(self: *Self, byte: u8) Error!void { diff --git a/lib/std/io/out_stream_old.zig b/lib/std/io/out_stream_old.zig new file mode 100644 index 000000000000..a53c707da122 --- /dev/null +++ b/lib/std/io/out_stream_old.zig @@ -0,0 +1,87 @@ +const std = @import("../std.zig"); +const builtin = @import("builtin"); +const root = @import("root"); +const mem = std.mem; + +pub const default_stack_size = 1 * 1024 * 1024; +pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream")) + root.stack_size_std_io_OutStream +else + default_stack_size; + +/// TODO this is not integrated with evented I/O yet. +/// https://github.com/ziglang/zig/issues/3557 +pub fn OutStreamOld(comptime WriteError: type) type { + return struct { + const Self = @This(); + pub const Error = WriteError; + // TODO https://github.com/ziglang/zig/issues/3557 + pub const WriteFn = if (std.io.is_async and false) + async fn (self: *Self, bytes: []const u8) Error!void + else + fn (self: *Self, bytes: []const u8) Error!void; + + writeFn: WriteFn, + + pub fn write(self: *Self, bytes: []const u8) Error!void { + // TODO https://github.com/ziglang/zig/issues/3557 + if (std.io.is_async and false) { + // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write. + @setRuntimeSafety(false); + var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined; + return await @asyncCall(&stack_frame, {}, self.writeFn, self, bytes); + } else { + return self.writeFn(self, bytes); + } + } + + pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void { + return std.fmt.format(self, Error, self.writeFn, format, args); + } + + pub fn writeByte(self: *Self, byte: u8) Error!void { + const slice = @as(*const [1]u8, &byte)[0..]; + return self.writeFn(self, slice); + } + + pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void { + const slice = @as(*const [1]u8, &byte)[0..]; + var i: usize = 0; + while (i < n) : (i += 1) { + try self.writeFn(self, slice); + } + } + + /// Write a native-endian integer. + pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void { + var bytes: [(T.bit_count + 7) / 8]u8 = undefined; + mem.writeIntNative(T, &bytes, value); + return self.writeFn(self, &bytes); + } + + /// Write a foreign-endian integer. + pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void { + var bytes: [(T.bit_count + 7) / 8]u8 = undefined; + mem.writeIntForeign(T, &bytes, value); + return self.writeFn(self, &bytes); + } + + pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void { + var bytes: [(T.bit_count + 7) / 8]u8 = undefined; + mem.writeIntLittle(T, &bytes, value); + return self.writeFn(self, &bytes); + } + + pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void { + var bytes: [(T.bit_count + 7) / 8]u8 = undefined; + mem.writeIntBig(T, &bytes, value); + return self.writeFn(self, &bytes); + } + + pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void { + var bytes: [(T.bit_count + 7) / 8]u8 = undefined; + mem.writeInt(T, &bytes, value, endian); + return self.writeFn(self, &bytes); + } + }; +} From 3915433b36b8c8f0f64a2219e2308a6953ab5be2 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Tue, 14 Jan 2020 00:58:52 -0600 Subject: [PATCH 26/33] Combine yields as it's pretty expensive --- lib/std/fmtgen.zig | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 5373603fce8f..a33e29a32e2a 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -424,8 +424,7 @@ pub fn formatType( // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); // } - generator.yield(@typeName(T)); - generator.yield("."); + generator.yield(@typeName(T) ++ "."); return generator.yield(@tagName(value)); }, .Union => |union_info| { @@ -461,13 +460,8 @@ pub fn formatType( generator.yield("{"); inline for (struct_info.fields) |field, i| { - if (i == 0) { - generator.yield(" ."); - } else { - generator.yield(", ."); - } - generator.yield(field.name); - generator.yield(" = "); + const prefix = if (i == 0) " ." else ", ."; + generator.yield(prefix ++ field.name ++ " = "); formatType(@field(value, field.name), "", options, generator, max_depth - 1); } @@ -531,8 +525,7 @@ pub fn formatType( } fn formatPtr(comptime T: type, ptr: usize, generator: *Generator([]const u8)) void { - generator.yield(@typeName(T)); - generator.yield("@"); + generator.yield(@typeName(T) ++ "@"); return formatInt(ptr, 16, false, FormatOptions{}, generator); } From 9163e253691af1d203b6ab9250bf083e0a44a594 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Tue, 14 Jan 2020 00:23:18 -0600 Subject: [PATCH 27/33] Simplified generator state as it's tested to be slightly faster --- lib/std/fmtgen.zig | 57 +++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index a33e29a32e2a..1445501f9578 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -26,61 +26,50 @@ pub fn Generator(comptime Out: type) type { /// Newly initialized or completed. inactive: void, - /// Currently running. This is not a reliable check and exists to help prevent accidental concurrent writes. - running: void, - - /// Async function yielded a value that has not been consumed yet. - /// This exists because "priming the generator" dumps in a value without a next(). - yielded: YieldResult, - - /// Previously yielded result was consumed. next() will resume the suspended frame. - consumed: YieldResult, - - const YieldResult = struct { + /// Async function yielded a value. + active: struct { frame: anyframe, out: Out, - }; + + /// This exists because "priming the generator" dumps in a value before next() is called + consumed: bool, + }, pub fn init() @This() { return .{ .inactive = {} }; } pub fn yield(self: *@This(), out: Out) void { - const state = std.meta.activeTag(self.*); - assert(state == .inactive or state == .running); + std.debug.assert(self.* == .inactive or self.active.consumed); suspend { self.* = .{ - .yielded = .{ + .active = .{ .frame = @frame(), .out = out, + .consumed = false, }, }; } } - fn consume(self: *@This(), result: YieldResult) Out { - self.* = .{ .consumed = result }; - return result.out; - } - pub fn next(self: *@This()) ?Out { switch (self.*) { - .running => unreachable, // Generator is already running. Probably a concurrency bug. .inactive => return null, - .yielded => |result| return self.consume(result), - .consumed => |orig| { - // Copy elision footgun - const copy = orig; - self.* = .{ .running = {} }; - resume copy.frame; - switch (self.*) { - .inactive, .consumed => unreachable, // Bad state. Probably a concurrency bug. - .yielded => |result| return self.consume(result), - .running => { - self.* = .{ .inactive = {} }; - return null; - }, + .active => |*active| { + if (!active.consumed) { + active.consumed = true; + return active.out; + } + + resume active.frame; + std.debug.assert(self.* == .active); + if (!active.consumed) { + active.consumed = true; + return active.out; + } else { + self.* = .inactive; + return null; } }, } From 2272b9032bc225e0c794cc1eb7e4cbca88bbbf35 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Tue, 14 Jan 2020 14:00:49 -0600 Subject: [PATCH 28/33] Use intrusive generator yields --- lib/std/fmtgen.zig | 224 ++++++++++++++++++++++++--------------------- 1 file changed, 122 insertions(+), 102 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 1445501f9578..cfd67d6492b8 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -31,7 +31,7 @@ pub fn Generator(comptime Out: type) type { frame: anyframe, out: Out, - /// This exists because "priming the generator" dumps in a value before next() is called + /// This exists because "priming the generator" dumps in a value without a next(). consumed: bool, }, @@ -39,38 +39,37 @@ pub fn Generator(comptime Out: type) type { return .{ .inactive = {} }; } - pub fn yield(self: *@This(), out: Out) void { + /// Example usage: `suspend generator.yield(@frame(), value);` + /// Requiring suspend and @frame() to be outside of this function improves performance by ~35% + pub fn yield(self: *@This(), frame: anyframe, out: Out) void { std.debug.assert(self.* == .inactive or self.active.consumed); - suspend { - self.* = .{ - .active = .{ - .frame = @frame(), - .out = out, - .consumed = false, - }, - }; - } + self.* = .{ + .active = .{ + .frame = frame, + .out = out, + .consumed = false, + }, + }; } pub fn next(self: *@This()) ?Out { switch (self.*) { .inactive => return null, .active => |*active| { - if (!active.consumed) { - active.consumed = true; - return active.out; + if (active.consumed) { + resume active.frame; + + std.debug.assert(self.* == .active); + // Consumed didn't change == no yield == generator complete + if (active.consumed) { + self.* = .inactive; + return null; + } } - resume active.frame; - std.debug.assert(self.* == .active); - if (!active.consumed) { - active.consumed = true; - return active.out; - } else { - self.* = .inactive; - return null; - } + active.consumed = true; + return active.out; }, } } @@ -179,7 +178,7 @@ pub fn format( .Start => switch (c) { '{' => { if (start_index < i) { - generator.yield(fmt[start_index..i]); + suspend generator.yield(@frame(), fmt[start_index..i]); } start_index = i; @@ -191,7 +190,7 @@ pub fn format( }, '}' => { if (start_index < i) { - generator.yield(fmt[start_index..i]); + suspend generator.yield(@frame(), fmt[start_index..i]); } state = .CloseBrace; }, @@ -359,7 +358,7 @@ pub fn format( } } if (start_index < fmt.len) { - generator.yield(fmt[start_index..]); + suspend generator.yield(@frame(), fmt[start_index..]); } } @@ -386,16 +385,19 @@ pub fn formatType( return formatValue(value, fmt, options, generator); }, .Void => { - return generator.yield("void"); + suspend generator.yield(@frame(), "void"); + return; }, .Bool => { - return generator.yield(if (value) "true" else "false"); + suspend generator.yield(@frame(), if (value) "true" else "false"); + return; }, .Optional => { if (value) |payload| { return formatType(payload, fmt, options, generator, max_depth); } else { - return generator.yield("null"); + suspend generator.yield(@frame(), "null"); + return; } }, .ErrorUnion => { @@ -406,34 +408,37 @@ pub fn formatType( } }, .ErrorSet => { - generator.yield("error."); - return generator.yield(@errorName(value)); + suspend generator.yield(@frame(), "error."); + suspend generator.yield(@frame(), @errorName(value)); + return; }, .Enum => { // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); // } - generator.yield(@typeName(T) ++ "."); - return generator.yield(@tagName(value)); + suspend generator.yield(@frame(), @typeName(T) ++ "."); + suspend generator.yield(@frame(), @tagName(value)); + return; }, .Union => |union_info| { // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); // } if (union_info.tag_type) |UnionTagType| { - generator.yield(@typeName(T)); + suspend generator.yield(@frame(), @typeName(T)); if (max_depth == 0) { - return generator.yield("{ ... }"); + suspend generator.yield(@frame(), "{ ... }"); + return; } - generator.yield("{ ."); - generator.yield(@tagName(@as(UnionTagType, value))); - generator.yield(" = "); + suspend generator.yield(@frame(), "{ ."); + suspend generator.yield(@frame(), @tagName(@as(UnionTagType, value))); + suspend generator.yield(@frame(), " = "); inline for (union_info.fields) |u_field| { if (@enumToInt(@as(UnionTagType, value)) == u_field.enum_field.?.value) { formatType(@field(value, u_field.name), "", options, generator, max_depth - 1); } } - generator.yield(" }"); + suspend generator.yield(@frame(), " }"); } else { return formatPtr(T, @ptrToInt(&value), generator); } @@ -442,19 +447,20 @@ pub fn formatType( // if (comptime std.meta.trait.hasFn("format")(T)) { // return value.format(fmt, options, context, Errors, output); // } - generator.yield(@typeName(T)); + suspend generator.yield(@frame(), @typeName(T)); if (max_depth == 0) { - return generator.yield("{ ... }"); + suspend generator.yield(@frame(), "{ ... }"); + return; } - generator.yield("{"); + suspend generator.yield(@frame(), "{"); inline for (struct_info.fields) |field, i| { const prefix = if (i == 0) " ." else ", ."; - generator.yield(prefix ++ field.name ++ " = "); + suspend generator.yield(@frame(), prefix ++ field.name ++ " = "); formatType(@field(value, field.name), "", options, generator, max_depth - 1); } - generator.yield(" }"); + suspend generator.yield(@frame(), " }"); }, .Pointer => |ptr_info| switch (ptr_info.size) { .One => switch (@typeInfo(ptr_info.child)) { @@ -508,13 +514,16 @@ pub fn formatType( .Fn => { return formatPtr(T, @ptrToInt(value), generator); }, - .Type => return generator.yield(@typeName(T)), + .Type => { + suspend generator.yield(@frame(), @typeName(T)); + return; + }, else => @compileError("Unable to format type '" ++ @typeName(T) ++ "'"), } } fn formatPtr(comptime T: type, ptr: usize, generator: *Generator([]const u8)) void { - generator.yield(@typeName(T) ++ "@"); + suspend generator.yield(@frame(), @typeName(T) ++ "@"); return formatInt(ptr, 16, false, FormatOptions{}, generator); } @@ -599,7 +608,7 @@ pub fn formatText( generator: *Generator([]const u8), ) void { if (fmt.len == 0) { - generator.yield(bytes); + suspend generator.yield(@frame(), bytes); } else if (comptime std.mem.eql(u8, fmt, "s")) { return formatBuf(bytes, options, generator); } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) { @@ -617,8 +626,10 @@ pub fn formatAsciiChar( options: FormatOptions, generator: *Generator([]const u8), ) void { - if (std.ascii.isPrint(c)) - return generator.yield(@as(*const [1]u8, &c)); + if (std.ascii.isPrint(c)) { + suspend generator.yield(@frame(), @as(*const [1]u8, &c)); + return; + } @panic("FIXME"); // return format(context, Errors, output, "\\x{x:0<2}", .{c}); } @@ -628,13 +639,13 @@ pub fn formatBuf( options: FormatOptions, generator: *Generator([]const u8), ) void { - generator.yield(buf); + suspend generator.yield(@frame(), 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) { - generator.yield(@as(*const [1]u8, &pad_byte)); + suspend generator.yield(@frame(), @as(*const [1]u8, &pad_byte)); } } @@ -650,34 +661,36 @@ pub fn formatFloatScientific( // Errol doesn't handle these special cases. if (math.signbit(x)) { - generator.yield("-"); + suspend generator.yield(@frame(), "-"); x = -x; } if (math.isNan(x)) { - return generator.yield("nan"); + suspend generator.yield(@frame(), "nan"); + return; } if (math.isPositiveInf(x)) { - return generator.yield("inf"); + suspend generator.yield(@frame(), "inf"); + return; } if (x == 0.0) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); if (options.precision) |prec_orig| { // TODO: remove copy once this is fixed https://github.com/ziglang/zig/issues/4065 const precision = prec_orig; if (precision != 0) { - generator.yield("."); + suspend generator.yield(@frame(), "."); var i: usize = 0; while (i < precision) : (i += 1) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } } } else { - generator.yield(".0"); + suspend generator.yield(@frame(), ".0"); } - generator.yield("e+00"); + suspend generator.yield(@frame(), "e+00"); return; } @@ -689,48 +702,48 @@ pub fn formatFloatScientific( const precision = prec_orig; errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific); - generator.yield(float_decimal.digits[0..1]); + suspend generator.yield(@frame(), float_decimal.digits[0..1]); // {e0} case prints no `.` if (precision != 0) { - generator.yield("."); + suspend generator.yield(@frame(), "."); var printed: usize = 0; if (float_decimal.digits.len > 1) { const num_digits = math.min(float_decimal.digits.len, precision + 1); - generator.yield(float_decimal.digits[1..num_digits]); + suspend generator.yield(@frame(), float_decimal.digits[1..num_digits]); printed += num_digits - 1; } while (printed < precision) : (printed += 1) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } } } else { - generator.yield(float_decimal.digits[0..1]); - generator.yield("."); + suspend generator.yield(@frame(), float_decimal.digits[0..1]); + suspend generator.yield(@frame(), "."); 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; - generator.yield(float_decimal.digits[1..num_digits]); + suspend generator.yield(@frame(), float_decimal.digits[1..num_digits]); } else { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } } - generator.yield("e"); + suspend generator.yield(@frame(), "e"); const exp = float_decimal.exp - 1; if (exp >= 0) { - generator.yield("+"); + suspend generator.yield(@frame(), "+"); if (exp > -10 and exp < 10) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } formatInt(exp, 10, false, FormatOptions{ .width = 0 }, generator); } else { - generator.yield("-"); + suspend generator.yield(@frame(), "-"); if (exp > -10 and exp < 10) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } formatInt(-exp, 10, false, FormatOptions{ .width = 0 }, generator); } @@ -747,33 +760,35 @@ pub fn formatFloatDecimal( // Errol doesn't handle these special cases. if (math.signbit(x)) { - generator.yield("-"); + suspend generator.yield(@frame(), "-"); x = -x; } if (math.isNan(x)) { - return generator.yield("nan"); + suspend generator.yield(@frame(), "nan"); + return; } if (math.isPositiveInf(x)) { - return generator.yield("inf"); + suspend generator.yield(@frame(), "inf"); + return; } if (x == 0.0) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); if (options.precision) |prec_orig| { // TODO: remove copy once this is fixed https://github.com/ziglang/zig/issues/4065 const precision = prec_orig; if (precision != 0) { - generator.yield("."); + suspend generator.yield(@frame(), "."); var i: usize = 0; while (i < precision) : (i += 1) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } } else { - generator.yield(".0"); + suspend generator.yield(@frame(), ".0"); } } else { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } return; @@ -796,14 +811,14 @@ pub fn formatFloatDecimal( if (num_digits_whole > 0) { // We may have to zero pad, for instance 1e4 requires zero padding. - generator.yield(float_decimal.digits[0..num_digits_whole_no_pad]); + suspend generator.yield(@frame(), float_decimal.digits[0..num_digits_whole_no_pad]); var i = num_digits_whole_no_pad; while (i < num_digits_whole) : (i += 1) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } } else { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } // {.0} special case doesn't want a trailing '.' @@ -811,7 +826,7 @@ pub fn formatFloatDecimal( return; } - generator.yield("."); + suspend generator.yield(@frame(), "."); // Keep track of fractional count printed for case where we pre-pad then post-pad with 0's. var printed: usize = 0; @@ -823,7 +838,7 @@ pub fn formatFloatDecimal( var i: usize = 0; while (i < zeros_to_print) : (i += 1) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); printed += 1; } @@ -835,14 +850,14 @@ pub fn formatFloatDecimal( // Remaining fractional portion, zero-padding if insufficient. assert(precision >= printed); if (num_digits_whole_no_pad + precision - printed < float_decimal.digits.len) { - generator.yield(float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]); + suspend generator.yield(@frame(), float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]); return; } else { - generator.yield(float_decimal.digits[num_digits_whole_no_pad..]); + suspend generator.yield(@frame(), float_decimal.digits[num_digits_whole_no_pad..]); printed += float_decimal.digits.len - num_digits_whole_no_pad; while (printed < precision) : (printed += 1) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } } } else { @@ -854,14 +869,14 @@ pub fn formatFloatDecimal( if (num_digits_whole > 0) { // We may have to zero pad, for instance 1e4 requires zero padding. - generator.yield(float_decimal.digits[0..num_digits_whole_no_pad]); + suspend generator.yield(@frame(), float_decimal.digits[0..num_digits_whole_no_pad]); var i = num_digits_whole_no_pad; while (i < num_digits_whole) : (i += 1) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } } else { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } // Omit `.` if no fractional portion @@ -869,7 +884,7 @@ pub fn formatFloatDecimal( return; } - generator.yield("."); + suspend generator.yield(@frame(), "."); // Zero-fill until we reach significant digits or run out of precision. if (float_decimal.exp < 0) { @@ -877,11 +892,11 @@ pub fn formatFloatDecimal( var i: usize = 0; while (i < zero_digit_count) : (i += 1) { - generator.yield("0"); + suspend generator.yield(@frame(), "0"); } } - generator.yield(float_decimal.digits[num_digits_whole_no_pad..]); + suspend generator.yield(@frame(), float_decimal.digits[num_digits_whole_no_pad..]); } } @@ -892,7 +907,8 @@ pub fn formatBytes( generator: *Generator([]const u8), ) void { if (value == 0) { - return generator.yield("0B"); + suspend generator.yield(@frame(), "0B"); + return; } const mags_si = " kMGTPEZY"; @@ -912,7 +928,8 @@ pub fn formatBytes( formatFloatDecimal(new_value, options, generator); if (suffix == ' ') { - return generator.yield("B"); + suspend generator.yield(@frame(), "B"); + return; } const buf = switch (radix) { @@ -920,7 +937,8 @@ pub fn formatBytes( 1024 => &[_]u8{ suffix, 'i', 'B' }, else => unreachable, }; - return generator.yield(buf); + suspend generator.yield(@frame(), buf); + return; } pub fn formatInt( @@ -958,13 +976,13 @@ fn formatIntSigned( const uint = @IntType(false, @TypeOf(value).bit_count); if (value < 0) { - generator.yield("-"); + suspend generator.yield(@frame(), "-"); const new_value = @intCast(uint, -(value + 1)) + 1; return formatIntUnsigned(new_value, base, uppercase, new_options, generator); } else if (options.width == null or options.width.? == 0) { return formatIntUnsigned(@intCast(uint, value), base, uppercase, options, generator); } else { - generator.yield("+"); + suspend generator.yield(@frame(), "+"); const new_value = @intCast(uint, value); return formatIntUnsigned(new_value, base, uppercase, new_options, generator); } @@ -1000,16 +1018,18 @@ fn formatIntUnsigned( const zero_byte: u8 = options.fill; var leftover_padding = padding - index; while (true) { - generator.yield(@as(*const [1]u8, &zero_byte)); + suspend generator.yield(@frame(), @as(*const [1]u8, &zero_byte)); leftover_padding -= 1; if (leftover_padding == 0) break; } mem.set(u8, buf[0..index], options.fill); - return generator.yield(&buf); + suspend generator.yield(@frame(), &buf); + return; } else { const padded_buf = buf[index - padding ..]; mem.set(u8, padded_buf[0..padding], options.fill); - return generator.yield(padded_buf); + suspend generator.yield(@frame(), padded_buf); + return; } } From 3b64d09f992bc2f1b1813e5134685105f09cd11e Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Tue, 21 Jan 2020 09:10:50 -0600 Subject: [PATCH 29/33] Sync with #4102, #4192, #4219 --- lib/std/fmtgen.zig | 48 ++++++++++++---------------------------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index cfd67d6492b8..8fa9a9e48ef4 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -475,7 +475,7 @@ pub fn formatType( }, else => return formatPtr(T.Child, @ptrToInt(value), generator), }, - .Many => { + .Many, .C => { if (ptr_info.child == u8) { if (fmt.len > 0 and fmt[0] == 's') { const len = mem.len(u8, value); @@ -493,9 +493,6 @@ pub fn formatType( } return formatPtr(ptr_info.child, @ptrToInt(value.ptr), generator); }, - .C => { - return formatPtr(T.Child, @ptrToInt(value), generator); - }, }, .Array => |info| { const Slice = @Type(builtin.TypeInfo{ @@ -626,12 +623,7 @@ pub fn formatAsciiChar( options: FormatOptions, generator: *Generator([]const u8), ) void { - if (std.ascii.isPrint(c)) { - suspend generator.yield(@frame(), @as(*const [1]u8, &c)); - return; - } - @panic("FIXME"); - // return format(context, Errors, output, "\\x{x:0<2}", .{c}); + suspend generator.yield(@frame(), @as(*const [1]u8, &c)); } pub fn formatBuf( @@ -1330,15 +1322,19 @@ test "pointer" { } test "cstr" { - try testFmt("cstr: Test C\n", "cstr: {s}\n", .{"Test C"}); - try testFmt("cstr: Test C \n", "cstr: {s:10}\n", .{"Test C"}); + 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" { - if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { - // TODO https://github.com/ziglang/zig/issues/3289 - return error.SkipZigTest; - } 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)}); } @@ -1373,10 +1369,6 @@ test "enum" { } test "float.scientific" { - if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { - // TODO https://github.com/ziglang/zig/issues/3289 - return error.SkipZigTest; - } 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)}); @@ -1384,10 +1376,6 @@ test "float.scientific" { } test "float.scientific.precision" { - if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { - // TODO https://github.com/ziglang/zig/issues/3289 - return error.SkipZigTest; - } 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)))}); @@ -1397,10 +1385,6 @@ test "float.scientific.precision" { } test "float.special" { - if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { - // TODO https://github.com/ziglang/zig/issues/3289 - return error.SkipZigTest; - } try testFmt("f64: nan", "f64: {}", .{math.nan_f64}); // negative nan is not defined by IEE 754, // and ARM thus normalizes it to positive nan @@ -1412,10 +1396,6 @@ test "float.special" { } test "float.decimal" { - if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { - // TODO https://github.com/ziglang/zig/issues/3289 - return error.SkipZigTest; - } try testFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)}); try testFmt("f32: 1.1", "f32: {d:.1}", .{@as(f32, 1.1234)}); try testFmt("f32: 1234.57", "f32: {d:.2}", .{@as(f32, 1234.567)}); @@ -1434,10 +1414,6 @@ test "float.decimal" { } test "float.libc.sanity" { - if (builtin.os == .linux and builtin.arch == .arm and builtin.abi == .musleabihf) { - // TODO https://github.com/ziglang/zig/issues/3289 - return error.SkipZigTest; - } 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)))}); From 52a88b1e346949a3cdf04b544ffa925dbbbabd32 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Thu, 30 Jan 2020 20:55:51 -0600 Subject: [PATCH 30/33] Pull in changes from fmt.zig --- lib/std/fmtgen.zig | 79 +++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 8fa9a9e48ef4..ad10e0140616 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -76,17 +76,6 @@ pub fn Generator(comptime Out: type) type { }; } -fn nextArg(comptime used_pos_args: *u32, comptime maybe_pos_arg: ?comptime_int, comptime next_arg: *comptime_int) comptime_int { - if (maybe_pos_arg) |pos_arg| { - used_pos_args.* |= 1 << pos_arg; - return pos_arg; - } else { - const arg = next_arg.*; - next_arg.* += 1; - return arg; - } -} - fn peekIsAlign(comptime fmt: []const u8) bool { // Should only be called during a state transition to the format segment. comptime assert(fmt[0] == ':'); @@ -166,12 +155,36 @@ pub fn format( comptime var start_index = 0; comptime var state = State.Start; - comptime var next_arg = 0; comptime var maybe_pos_arg: ?comptime_int = null; - comptime var used_pos_args: ArgSetType = 0; 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) { @@ -219,11 +232,7 @@ pub fn format( } }, '}' => { - const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); - - if (arg_to_print >= args.len) { - @compileError("Too few arguments"); - } + const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg); formatType( args[arg_to_print], @@ -254,7 +263,7 @@ pub fn format( state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth; }, '}' => { - const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg); formatType( args[arg_to_print], @@ -299,7 +308,7 @@ pub fn format( state = .FormatPrecision; }, '}' => { - const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg); formatType( args[arg_to_print], @@ -325,7 +334,7 @@ pub fn format( options.precision.? += c - '0'; }, '}' => { - const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); + const arg_to_print = comptime arg_state.nextArg(maybe_pos_arg); formatType( args[arg_to_print], @@ -344,13 +353,7 @@ pub fn format( } } comptime { - // All arguments must have been printed but we allow mixing positional and fixed to achieve this. - var i: usize = 0; - inline while (i < next_arg) : (i += 1) { - used_pos_args |= 1 << i; - } - - if (@popCount(ArgSetType, used_pos_args) != args.len) { + if (comptime arg_state.hasUnusedArgs()) { @compileError("Unused arguments"); } if (state != State.Start) { @@ -1175,6 +1178,11 @@ fn countSize(comptime fmt: []const u8, args: var) usize { return size; } +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..]; @@ -1633,7 +1641,8 @@ test "hexToBytes" { test "formatIntValue with comptime_int" { const value: comptime_int = 123456789123456789; - var buf = try std.Buffer.init(std.debug.global_allocator, ""); + var buf = try std.Buffer.init(std.testing.allocator, ""); + defer buf.deinit(); var generator = Generator([]const u8).init(); _ = async formatIntValue(value, "", FormatOptions{}, &generator); @@ -1692,19 +1701,23 @@ test "formatIntValue with comptime_int" { // inst.a = &inst; // inst.tu.ptr = &inst.tu; -// var buf0 = try std.Buffer.init(std.debug.global_allocator, ""); +// var buf0 = try std.Buffer.init(std.testing.allocator, ""); +// defer buf0.deinit(); // try formatType(inst, "", FormatOptions{}, &buf0, @TypeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 0); // std.testing.expect(mem.eql(u8, buf0.toSlice(), "S{ ... }")); -// var buf1 = try std.Buffer.init(std.debug.global_allocator, ""); +// var buf1 = try std.Buffer.init(std.testing.allocator, ""); +// defer buf1.deinit(); // try formatType(inst, "", FormatOptions{}, &buf1, @TypeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 1); // std.testing.expect(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }")); -// var buf2 = try std.Buffer.init(std.debug.global_allocator, ""); +// var buf2 = try std.Buffer.init(std.testing.allocator, ""); +// defer buf2.deinit(); // try formatType(inst, "", FormatOptions{}, &buf2, @TypeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 2); // std.testing.expect(mem.eql(u8, buf2.toSlice(), "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) }")); -// var buf3 = try std.Buffer.init(std.debug.global_allocator, ""); +// var buf3 = try std.Buffer.init(std.testing.allocator, ""); +// defer buf3.deinit(); // try formatType(inst, "", FormatOptions{}, &buf3, @TypeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 3); // std.testing.expect(mem.eql(u8, buf3.toSlice(), "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) }")); // } From 7c8e3588196256d0054de012b6754045cffb2f3f Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Mon, 10 Feb 2020 09:14:50 -0600 Subject: [PATCH 31/33] Wire debug.warn into new OutStream --- lib/std/debug.zig | 8 ++-- lib/std/io.zig | 1 - lib/std/io/out_stream_old.zig | 87 ----------------------------------- 3 files changed, 4 insertions(+), 92 deletions(-) delete mode 100644 lib/std/io/out_stream_old.zig diff --git a/lib/std/debug.zig b/lib/std/debug.zig index f87272ff7d85..efe4f1fa760a 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -41,9 +41,9 @@ const Module = struct { /// Tries to write to stderr, unbuffered, and ignores any error returned. /// Does not append a newline. var stderr_file: File = undefined; -var stderr_file_out_stream: File.OutStreamOld = undefined; +var stderr_file_out_stream: File.OutStream = undefined; -var stderr_stream: ?*io.OutStreamOld(File.WriteError) = null; +var stderr_stream: ?*io.OutStream(File.WriteError) = null; var stderr_mutex = std.Mutex.init(); pub fn warn(comptime fmt: []const u8, args: var) void { @@ -53,12 +53,12 @@ pub fn warn(comptime fmt: []const u8, args: var) void { noasync stderr.print(fmt, args) catch return; } -pub fn getStderrStream() *io.OutStreamOld(File.WriteError) { +pub fn getStderrStream() *io.OutStream(File.WriteError) { if (stderr_stream) |st| { return st; } else { stderr_file = io.getStdErr(); - stderr_file_out_stream = stderr_file.outStreamOld(); + stderr_file_out_stream = stderr_file.outStream(); const st = &stderr_file_out_stream.stream; stderr_stream = st; return st; diff --git a/lib/std/io.zig b/lib/std/io.zig index a55adb3f5211..341b73e33ca8 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -97,7 +97,6 @@ pub const SliceSeekableInStream = @import("io/seekable_stream.zig").SliceSeekabl pub const COutStream = @import("io/c_out_stream.zig").COutStream; pub const InStream = @import("io/in_stream.zig").InStream; pub const OutStream = @import("io/out_stream.zig").OutStream; -pub const OutStreamOld = @import("io/out_stream_old.zig").OutStreamOld; /// Deprecated; use `std.fs.Dir.writeFile`. pub fn writeFile(path: []const u8, data: []const u8) !void { diff --git a/lib/std/io/out_stream_old.zig b/lib/std/io/out_stream_old.zig deleted file mode 100644 index a53c707da122..000000000000 --- a/lib/std/io/out_stream_old.zig +++ /dev/null @@ -1,87 +0,0 @@ -const std = @import("../std.zig"); -const builtin = @import("builtin"); -const root = @import("root"); -const mem = std.mem; - -pub const default_stack_size = 1 * 1024 * 1024; -pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream")) - root.stack_size_std_io_OutStream -else - default_stack_size; - -/// TODO this is not integrated with evented I/O yet. -/// https://github.com/ziglang/zig/issues/3557 -pub fn OutStreamOld(comptime WriteError: type) type { - return struct { - const Self = @This(); - pub const Error = WriteError; - // TODO https://github.com/ziglang/zig/issues/3557 - pub const WriteFn = if (std.io.is_async and false) - async fn (self: *Self, bytes: []const u8) Error!void - else - fn (self: *Self, bytes: []const u8) Error!void; - - writeFn: WriteFn, - - pub fn write(self: *Self, bytes: []const u8) Error!void { - // TODO https://github.com/ziglang/zig/issues/3557 - if (std.io.is_async and false) { - // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write. - @setRuntimeSafety(false); - var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined; - return await @asyncCall(&stack_frame, {}, self.writeFn, self, bytes); - } else { - return self.writeFn(self, bytes); - } - } - - pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void { - return std.fmt.format(self, Error, self.writeFn, format, args); - } - - pub fn writeByte(self: *Self, byte: u8) Error!void { - const slice = @as(*const [1]u8, &byte)[0..]; - return self.writeFn(self, slice); - } - - pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void { - const slice = @as(*const [1]u8, &byte)[0..]; - var i: usize = 0; - while (i < n) : (i += 1) { - try self.writeFn(self, slice); - } - } - - /// Write a native-endian integer. - pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void { - var bytes: [(T.bit_count + 7) / 8]u8 = undefined; - mem.writeIntNative(T, &bytes, value); - return self.writeFn(self, &bytes); - } - - /// Write a foreign-endian integer. - pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void { - var bytes: [(T.bit_count + 7) / 8]u8 = undefined; - mem.writeIntForeign(T, &bytes, value); - return self.writeFn(self, &bytes); - } - - pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void { - var bytes: [(T.bit_count + 7) / 8]u8 = undefined; - mem.writeIntLittle(T, &bytes, value); - return self.writeFn(self, &bytes); - } - - pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void { - var bytes: [(T.bit_count + 7) / 8]u8 = undefined; - mem.writeIntBig(T, &bytes, value); - return self.writeFn(self, &bytes); - } - - pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void { - var bytes: [(T.bit_count + 7) / 8]u8 = undefined; - mem.writeInt(T, &bytes, value, endian); - return self.writeFn(self, &bytes); - } - }; -} From ab19af441932351a600d0e35a67eae7010d54646 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Mon, 10 Feb 2020 09:19:10 -0600 Subject: [PATCH 32/33] Enable custom formatter --- lib/std/fmtgen.zig | 80 +++++++++++++++++++--------------------- lib/std/http/headers.zig | 18 ++++----- lib/std/math/big/int.zig | 10 ++--- lib/std/net.zig | 28 +++++++------- lib/std/net/test.zig | 4 +- lib/std/os/uefi.zig | 10 ++--- 6 files changed, 68 insertions(+), 82 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index ad10e0140616..5aea99909837 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -383,6 +383,11 @@ pub fn formatType( } const T = @TypeOf(value); + + if (comptime std.meta.trait.hasFn("format")(T)) { + return value.format(fmt, options, generator); + } + switch (@typeInfo(T)) { .ComptimeInt, .Int, .Float => { return formatValue(value, fmt, options, generator); @@ -416,17 +421,11 @@ pub fn formatType( return; }, .Enum => { - // if (comptime std.meta.trait.hasFn("format")(T)) { - // return value.format(fmt, options, context, Errors, output); - // } suspend generator.yield(@frame(), @typeName(T) ++ "."); suspend generator.yield(@frame(), @tagName(value)); return; }, .Union => |union_info| { - // if (comptime std.meta.trait.hasFn("format")(T)) { - // return value.format(fmt, options, context, Errors, output); - // } if (union_info.tag_type) |UnionTagType| { suspend generator.yield(@frame(), @typeName(T)); if (max_depth == 0) { @@ -447,9 +446,6 @@ pub fn formatType( } }, .Struct => |struct_info| { - // if (comptime std.meta.trait.hasFn("format")(T)) { - // return value.format(fmt, options, context, Errors, output); - // } suspend generator.yield(@frame(), @typeName(T)); if (max_depth == 0) { suspend generator.yield(@frame(), "{ ... }"); @@ -1440,42 +1436,40 @@ test "float.libc.sanity" { 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, -// context: var, -// comptime Errors: type, -// output: fn (@TypeOf(context), []const u8) Errors!void, -// ) Errors!void { -// if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { -// return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y }); -// } else if (comptime std.mem.eql(u8, fmt, "d")) { -// return std.fmt.format(context, Errors, output, "{d:.3}x{d:.3}", .{ self.x, self.y }); -// } else { -// @compileError("Unknown format character: '" ++ fmt ++ "'"); -// } -// } -// }; +test "custom" { + const Vec2 = struct { + const SelfType = @This(); + x: f32, + y: f32, + + pub fn format( + self: SelfType, + comptime fmt: []const u8, + options: FormatOptions, + generator: *Generator([]const u8), + ) void { + if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { + return std.fmtgen.format(generator, "({d:.3},{d:.3})", .{ self.x, self.y }); + } else if (comptime std.mem.eql(u8, fmt, "d")) { + return std.fmtgen.format(generator, "{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}); + 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}); -// } + // 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 { diff --git a/lib/std/http/headers.zig b/lib/std/http/headers.zig index dfe53fe7501a..9a7b1e6b1cc5 100644 --- a/lib/std/http/headers.zig +++ b/lib/std/http/headers.zig @@ -353,16 +353,14 @@ pub const Headers = struct { pub fn format( self: Self, comptime fmt: []const u8, - options: std.fmt.FormatOptions, - context: var, - comptime Errors: type, - output: fn (@TypeOf(context), []const u8) Errors!void, - ) Errors!void { + options: std.fmtgen.FormatOptions, + generator: *std.fmtgen.Generator([]const u8), + ) void { for (self.toSlice()) |entry| { - try output(context, entry.name); - try output(context, ": "); - try output(context, entry.value); - try output(context, "\n"); + suspend generator.yield(@frame(), entry.name); + suspend generator.yield(@frame(), ": "); + suspend generator.yield(@frame(), entry.value); + suspend generator.yield(@frame(), "\n"); } } }; @@ -597,5 +595,5 @@ test "Headers.format" { \\foo: bar \\cookie: somevalue \\ - , try std.fmt.bufPrint(buf[0..], "{}", .{h})); + , try std.fmtgen.bufPrint(buf[0..], "{}", .{h})); } diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 7d8d3a195bc8..12dfd9825efd 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -522,17 +522,15 @@ pub const Int = struct { pub fn format( self: Int, comptime fmt: []const u8, - options: std.fmt.FormatOptions, - context: var, - comptime FmtError: type, - output: fn (@TypeOf(context), []const u8) FmtError!void, - ) FmtError!void { + options: std.fmtgen.FormatOptions, + generator: *std.fmtgen.Generator([]const u8), + ) void { self.assertWritable(); // TODO look at fmt and support other bases // TODO support read-only fixed integers const str = self.toString(self.allocator.?, 10) catch @panic("TODO make this non allocating"); defer self.allocator.?.free(str); - return output(context, str); + suspend generator.yield(@frame(), str); } /// Returns -1, 0, 1 if |a| < |b|, |a| == |b| or |a| > |b| respectively. diff --git a/lib/std/net.zig b/lib/std/net.zig index f98a49b67a9e..273147738f0a 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -128,7 +128,7 @@ pub const Address = extern union { ip_slice[15] = ptr[3]; return result; } else { - const digit = try std.fmt.charToDigit(c, 16); + const digit = try std.fmtgen.charToDigit(c, 16); if (@mulWithOverflow(u16, x, 16, &x)) { return error.Overflow; } @@ -268,16 +268,14 @@ pub const Address = extern union { pub fn format( self: Address, comptime fmt: []const u8, - options: std.fmt.FormatOptions, - context: var, - comptime Errors: type, - comptime output: fn (@TypeOf(context), []const u8) Errors!void, - ) !void { + options: std.fmtgen.FormatOptions, + generator: *std.fmtgen.Generator([]const u8), + ) void { switch (self.any.family) { os.AF_INET => { const port = mem.bigToNative(u16, self.in.port); const bytes = @ptrCast(*const [4]u8, &self.in.addr); - try std.fmt.format(context, Errors, output, "{}.{}.{}.{}:{}", .{ + std.fmtgen.format(generator, "{}.{}.{}.{}:{}", .{ bytes[0], bytes[1], bytes[2], @@ -288,7 +286,7 @@ pub const Address = extern union { os.AF_INET6 => { const port = mem.bigToNative(u16, self.in6.port); if (mem.eql(u8, self.in6.addr[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { - try std.fmt.format(context, Errors, output, "[::ffff:{}.{}.{}.{}]:{}", .{ + std.fmtgen.format(generator, "[::ffff:{}.{}.{}.{}]:{}", .{ self.in6.addr[12], self.in6.addr[13], self.in6.addr[14], @@ -308,30 +306,30 @@ pub const Address = extern union { break :blk buf; }, }; - try output(context, "["); + suspend generator.yield(@frame(), "["); var i: usize = 0; var abbrv = false; while (i < native_endian_parts.len) : (i += 1) { if (native_endian_parts[i] == 0) { if (!abbrv) { - try output(context, if (i == 0) "::" else ":"); + suspend generator.yield(@frame(), if (i == 0) "::" else ":"); abbrv = true; } continue; } - try std.fmt.format(context, Errors, output, "{x}", .{native_endian_parts[i]}); + std.fmtgen.format(generator, "{x}", .{native_endian_parts[i]}); if (i != native_endian_parts.len - 1) { - try output(context, ":"); + suspend generator.yield(@frame(), ":"); } } - try std.fmt.format(context, Errors, output, "]:{}", .{port}); + std.fmtgen.format(generator, "]:{}", .{port}); }, os.AF_UNIX => { if (!has_unix_sockets) { unreachable; } - try std.fmt.format(context, Errors, output, "{}", .{&self.un.path}); + std.fmtgen.format(generator, "{}", .{&self.un.path}); }, else => unreachable, } @@ -1030,7 +1028,7 @@ fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void { var colon_it = mem.separate(sub_tok, ":"); const name = colon_it.next().?; const value_txt = colon_it.next() orelse continue; - const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) { + const value = std.fmtgen.parseInt(u8, value_txt, 10) catch |err| switch (err) { error.Overflow => 255, error.InvalidCharacter => continue, }; diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig index 703a804bfb27..86c9921393d0 100644 --- a/lib/std/net/test.zig +++ b/lib/std/net/test.zig @@ -29,7 +29,7 @@ test "parse and render IPv6 addresses" { }; for (ips) |ip, i| { var addr = net.Address.parseIp6(ip, 0) catch unreachable; - var newIp = std.fmt.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable; + var newIp = std.fmtgen.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable; std.testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3])); } @@ -51,7 +51,7 @@ test "parse and render IPv4 addresses" { "127.0.0.1", }) |ip| { var addr = net.Address.parseIp4(ip, 0) catch unreachable; - var newIp = std.fmt.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable; + var newIp = std.fmtgen.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable; std.testing.expect(std.mem.eql(u8, ip, newIp[0 .. newIp.len - 2])); } diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 81d13ac1c314..ba81de0843cd 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -5,7 +5,7 @@ pub const protocols = @import("uefi/protocols.zig"); pub const status = @import("uefi/status.zig"); pub const tables = @import("uefi/tables.zig"); -const fmt = @import("std").fmt; +const fmtgen = @import("std").fmtgen; /// The EFI image's handle that is passed to its entry point. pub var handle: Handle = undefined; @@ -29,13 +29,11 @@ pub const Guid = extern struct { pub fn format( self: @This(), comptime f: []const u8, - options: fmt.FormatOptions, - context: var, - comptime Errors: type, - output: fn (@TypeOf(context), []const u8) Errors!void, + options: fmtgen.FormatOptions, + generator: *std.fmtgen.Generator([]const u8), ) Errors!void { if (f.len == 0) { - return fmt.format(context, Errors, output, "{x:0>8}-{x:0>4}-{x:0>4}-{x:0>2}{x:0>2}-{x:0>12}", .{ + fmtgen.format(generator, "{x:0>8}-{x:0>4}-{x:0>4}-{x:0>2}{x:0>2}-{x:0>12}", .{ self.time_low, self.time_mid, self.time_high_and_version, From 1c6d6d2dc14c17bd158a90ec902827194e5b5384 Mon Sep 17 00:00:00 2001 From: Benjamin Feng Date: Mon, 10 Feb 2020 09:22:53 -0600 Subject: [PATCH 33/33] Exhaustive enums --- lib/std/fmtgen.zig | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/std/fmtgen.zig b/lib/std/fmtgen.zig index 5aea99909837..fdbaadd4d0b9 100644 --- a/lib/std/fmtgen.zig +++ b/lib/std/fmtgen.zig @@ -420,10 +420,17 @@ pub fn formatType( suspend generator.yield(@frame(), @errorName(value)); return; }, - .Enum => { - suspend generator.yield(@frame(), @typeName(T) ++ "."); - suspend generator.yield(@frame(), @tagName(value)); - return; + .Enum => |enumInfo| { + suspend generator.yield(@frame(), @typeName(T)); + if (enumInfo.is_exhaustive) { + suspend generator.yield(@frame(), "."); + return formatType(@tagName(value), fmt, options, generator, max_depth); + } else { + // TODO: when @tagName works on exhaustive enums print known enum strings + suspend generator.yield(@frame(), "("); + formatType(@enumToInt(value), fmt, options, generator, max_depth); + suspend generator.yield(@frame(), ")"); + } }, .Union => |union_info| { if (union_info.tag_type) |UnionTagType| {