| ... | @@ -10,6 +10,42 @@ const lossyCast = std.math.lossyCast; | ... | @@ -10,6 +10,42 @@ const lossyCast = std.math.lossyCast; |
| 10 | | 10 | |
| 11 | pub const default_max_depth = 3; | 11 | pub const default_max_depth = 3; |
| 12 | | 12 | |
| | 13 | pub const Alignment = enum { |
| | 14 | Left, |
| | 15 | Center, |
| | 16 | Right, |
| | 17 | }; |
| | 18 | |
| | 19 | pub const FormatOptions = struct { |
| | 20 | precision: ?usize = null, |
| | 21 | width: ?usize = null, |
| | 22 | alignment: ?Alignment = null, |
| | 23 | fill: u8 = ' ', |
| | 24 | }; |
| | 25 | |
| | 26 | fn nextArg(comptime used_pos_args: *u32, comptime maybe_pos_arg: ?comptime_int, comptime next_arg: *comptime_int) comptime_int { |
| | 27 | if (maybe_pos_arg) |pos_arg| { |
| | 28 | used_pos_args.* |= 1 << pos_arg; |
| | 29 | return pos_arg; |
| | 30 | } else { |
| | 31 | const arg = next_arg.*; |
| | 32 | next_arg.* += 1; |
| | 33 | return arg; |
| | 34 | } |
| | 35 | } |
| | 36 | |
| | 37 | fn peekIsAlign(comptime fmt: []const u8) bool { |
| | 38 | // Should only be called during a state transition to the format segment. |
| | 39 | std.debug.assert(fmt[0] == ':'); |
| | 40 | |
| | 41 | inline for (([_]u8{ 1, 2 })[0..]) |i| { |
| | 42 | if (fmt.len > i and (fmt[i] == '<' or fmt[i] == '^' or fmt[i] == '>')) { |
| | 43 | return true; |
| | 44 | } |
| | 45 | } |
| | 46 | return false; |
| | 47 | } |
| | 48 | |
| 13 | /// Renders fmt string with args, calling output with slices of bytes. | 49 | /// Renders fmt string with args, calling output with slices of bytes. |
| 14 | /// If `output` returns an error, the error is returned from `format` and | 50 | /// If `output` returns an error, the error is returned from `format` and |
| 15 | /// `output` is not called again. | 51 | /// `output` is not called again. |
| ... | @@ -20,17 +56,30 @@ pub fn format( | ... | @@ -20,17 +56,30 @@ pub fn format( |
| 20 | comptime fmt: []const u8, | 56 | comptime fmt: []const u8, |
| 21 | args: ..., | 57 | args: ..., |
| 22 | ) Errors!void { | 58 | ) Errors!void { |
| | 59 | const ArgSetType = @IntType(false, 32); |
| | 60 | if (args.len > ArgSetType.bit_count) { |
| | 61 | @compileError("32 arguments max are supported per format call"); |
| | 62 | } |
| | 63 | |
| 23 | const State = enum { | 64 | const State = enum { |
| 24 | Start, | 65 | Start, |
| 25 | OpenBrace, | 66 | Positional, |
| 26 | CloseBrace, | 67 | CloseBrace, |
| 27 | FormatString, | 68 | Specifier, |
| | 69 | FormatFillAndAlign, |
| | 70 | FormatWidth, |
| | 71 | FormatPrecision, |
| 28 | Pointer, | 72 | Pointer, |
| 29 | }; | 73 | }; |
| 30 | | 74 | |
| 31 | comptime var start_index = 0; | 75 | comptime var start_index = 0; |
| 32 | comptime var state = State.Start; | 76 | comptime var state = State.Start; |
| 33 | comptime var next_arg = 0; | 77 | comptime var next_arg = 0; |
| | 78 | comptime var maybe_pos_arg: ?comptime_int = null; |
| | 79 | comptime var used_pos_args: ArgSetType = 0; |
| | 80 | comptime var specifier_start = 0; |
| | 81 | comptime var specifier_end = 0; |
| | 82 | comptime var options = FormatOptions{}; |
| 34 | | 83 | |
| 35 | inline for (fmt) |c, i| { | 84 | inline for (fmt) |c, i| { |
| 36 | switch (state) { | 85 | switch (state) { |
| ... | @@ -39,58 +88,183 @@ pub fn format( | ... | @@ -39,58 +88,183 @@ pub fn format( |
| 39 | if (start_index < i) { | 88 | if (start_index < i) { |
| 40 | try output(context, fmt[start_index..i]); | 89 | try output(context, fmt[start_index..i]); |
| 41 | } | 90 | } |
| | 91 | |
| 42 | start_index = i; | 92 | start_index = i; |
| 43 | state = State.OpenBrace; | 93 | specifier_start = i + 1; |
| | 94 | specifier_end = i + 1; |
| | 95 | maybe_pos_arg = null; |
| | 96 | state = .Positional; |
| | 97 | options = FormatOptions{}; |
| 44 | }, | 98 | }, |
| 45 | | | |
| 46 | '}' => { | 99 | '}' => { |
| 47 | if (start_index < i) { | 100 | if (start_index < i) { |
| 48 | try output(context, fmt[start_index..i]); | 101 | try output(context, fmt[start_index..i]); |
| 49 | } | 102 | } |
| 50 | state = State.CloseBrace; | 103 | state = .CloseBrace; |
| 51 | }, | 104 | }, |
| 52 | else => {}, | 105 | else => {}, |
| 53 | }, | 106 | }, |
| 54 | .OpenBrace => switch (c) { | 107 | .Positional => switch (c) { |
| 55 | '{' => { | 108 | '{' => { |
| 56 | state = State.Start; | 109 | state = .Start; |
| 57 | start_index = i; | 110 | start_index = i; |
| 58 | }, | 111 | }, |
| | 112 | '*' => { |
| | 113 | state = .Pointer; |
| | 114 | }, |
| | 115 | ':' => { |
| | 116 | state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth; |
| | 117 | specifier_end = i; |
| | 118 | }, |
| | 119 | '0'...'9' => { |
| | 120 | if (maybe_pos_arg == null) { |
| | 121 | maybe_pos_arg = 0; |
| | 122 | } |
| | 123 | |
| | 124 | maybe_pos_arg.? *= 10; |
| | 125 | maybe_pos_arg.? += c - '0'; |
| | 126 | specifier_start = i + 1; |
| | 127 | |
| | 128 | if (maybe_pos_arg.? >= args.len) { |
| | 129 | @compileError("Positional value refers to non-existent argument"); |
| | 130 | } |
| | 131 | }, |
| 59 | '}' => { | 132 | '}' => { |
| 60 | try formatType(args[next_arg], fmt[0..0], context, Errors, output, default_max_depth); | 133 | const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); |
| 61 | next_arg += 1; | 134 | |
| 62 | state = State.Start; | 135 | try formatType( |
| | 136 | args[arg_to_print], |
| | 137 | fmt[0..0], |
| | 138 | options, |
| | 139 | context, |
| | 140 | Errors, |
| | 141 | output, |
| | 142 | default_max_depth, |
| | 143 | ); |
| | 144 | |
| | 145 | state = .Start; |
| 63 | start_index = i + 1; | 146 | start_index = i + 1; |
| 64 | }, | 147 | }, |
| 65 | '*' => state = State.Pointer, | | |
| 66 | else => { | 148 | else => { |
| 67 | state = State.FormatString; | 149 | state = .Specifier; |
| | 150 | specifier_start = i; |
| 68 | }, | 151 | }, |
| 69 | }, | 152 | }, |
| 70 | .CloseBrace => switch (c) { | 153 | .CloseBrace => switch (c) { |
| 71 | '}' => { | 154 | '}' => { |
| 72 | state = State.Start; | 155 | state = .Start; |
| 73 | start_index = i; | 156 | start_index = i; |
| 74 | }, | 157 | }, |
| 75 | else => @compileError("Single '}' encountered in format string"), | 158 | else => @compileError("Single '}' encountered in format string"), |
| 76 | }, | 159 | }, |
| 77 | .FormatString => switch (c) { | 160 | .Specifier => switch (c) { |
| | 161 | ':' => { |
| | 162 | specifier_end = i; |
| | 163 | state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth; |
| | 164 | }, |
| 78 | '}' => { | 165 | '}' => { |
| 79 | const s = start_index + 1; | 166 | const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); |
| 80 | try formatType(args[next_arg], fmt[s..i], context, Errors, output, default_max_depth); | 167 | |
| 81 | next_arg += 1; | 168 | try formatType( |
| 82 | state = State.Start; | 169 | args[arg_to_print], |
| | 170 | fmt[specifier_start..i], |
| | 171 | options, |
| | 172 | context, |
| | 173 | Errors, |
| | 174 | output, |
| | 175 | default_max_depth, |
| | 176 | ); |
| | 177 | state = .Start; |
| 83 | start_index = i + 1; | 178 | start_index = i + 1; |
| 84 | }, | 179 | }, |
| 85 | else => {}, | 180 | else => {}, |
| 86 | }, | 181 | }, |
| | 182 | // Only entered if the format string contains a fill/align segment. |
| | 183 | .FormatFillAndAlign => switch (c) { |
| | 184 | '<' => { |
| | 185 | options.alignment = Alignment.Left; |
| | 186 | state = .FormatWidth; |
| | 187 | }, |
| | 188 | '^' => { |
| | 189 | options.alignment = Alignment.Center; |
| | 190 | state = .FormatWidth; |
| | 191 | }, |
| | 192 | '>' => { |
| | 193 | options.alignment = Alignment.Right; |
| | 194 | state = .FormatWidth; |
| | 195 | }, |
| | 196 | else => { |
| | 197 | options.fill = c; |
| | 198 | }, |
| | 199 | }, |
| | 200 | .FormatWidth => switch (c) { |
| | 201 | '0'...'9' => { |
| | 202 | if (options.width == null) { |
| | 203 | options.width = 0; |
| | 204 | } |
| | 205 | |
| | 206 | options.width.? *= 10; |
| | 207 | options.width.? += c - '0'; |
| | 208 | }, |
| | 209 | '.' => { |
| | 210 | state = .FormatPrecision; |
| | 211 | }, |
| | 212 | '}' => { |
| | 213 | const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); |
| | 214 | |
| | 215 | try formatType( |
| | 216 | args[arg_to_print], |
| | 217 | fmt[specifier_start..specifier_end], |
| | 218 | options, |
| | 219 | context, |
| | 220 | Errors, |
| | 221 | output, |
| | 222 | default_max_depth, |
| | 223 | ); |
| | 224 | state = .Start; |
| | 225 | start_index = i + 1; |
| | 226 | }, |
| | 227 | else => { |
| | 228 | @compileError("Unexpected character in width value: " ++ [_]u8{c}); |
| | 229 | }, |
| | 230 | }, |
| | 231 | .FormatPrecision => switch (c) { |
| | 232 | '0'...'9' => { |
| | 233 | if (options.precision == null) { |
| | 234 | options.precision = 0; |
| | 235 | } |
| | 236 | |
| | 237 | options.precision.? *= 10; |
| | 238 | options.precision.? += c - '0'; |
| | 239 | }, |
| | 240 | '}' => { |
| | 241 | const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); |
| | 242 | |
| | 243 | try formatType( |
| | 244 | args[arg_to_print], |
| | 245 | fmt[specifier_start..specifier_end], |
| | 246 | options, |
| | 247 | context, |
| | 248 | Errors, |
| | 249 | output, |
| | 250 | default_max_depth, |
| | 251 | ); |
| | 252 | state = .Start; |
| | 253 | start_index = i + 1; |
| | 254 | }, |
| | 255 | else => { |
| | 256 | @compileError("Unexpected character in precision value: " ++ [_]u8{c}); |
| | 257 | }, |
| | 258 | }, |
| 87 | .Pointer => switch (c) { | 259 | .Pointer => switch (c) { |
| 88 | '}' => { | 260 | '}' => { |
| 89 | try output(context, @typeName(@typeOf(args[next_arg]).Child)); | 261 | const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); |
| | 262 | |
| | 263 | try output(context, @typeName(@typeOf(args[arg_to_print]).Child)); |
| 90 | try output(context, "@"); | 264 | try output(context, "@"); |
| 91 | try formatInt(@ptrToInt(args[next_arg]), 16, false, 0, context, Errors, output); | 265 | try formatInt(@ptrToInt(args[arg_to_print]), 16, false, 0, context, Errors, output); |
| 92 | next_arg += 1; | 266 | |
| 93 | state = State.Start; | 267 | state = .Start; |
| 94 | start_index = i + 1; | 268 | start_index = i + 1; |
| 95 | }, | 269 | }, |
| 96 | else => @compileError("Unexpected format character after '*'"), | 270 | else => @compileError("Unexpected format character after '*'"), |
| ... | @@ -98,7 +272,13 @@ pub fn format( | ... | @@ -98,7 +272,13 @@ pub fn format( |
| 98 | } | 272 | } |
| 99 | } | 273 | } |
| 100 | comptime { | 274 | comptime { |
| 101 | if (args.len != next_arg) { | 275 | // All arguments must have been printed but we allow mixing positional and fixed to achieve this. |
| | 276 | var i: usize = 0; |
| | 277 | inline while (i < next_arg) : (i += 1) { |
| | 278 | used_pos_args |= 1 << i; |
| | 279 | } |
| | 280 | |
| | 281 | if (@popCount(ArgSetType, used_pos_args) != args.len) { |
| 102 | @compileError("Unused arguments"); | 282 | @compileError("Unused arguments"); |
| 103 | } | 283 | } |
| 104 | if (state != State.Start) { | 284 | if (state != State.Start) { |
| ... | @@ -113,6 +293,7 @@ pub fn format( | ... | @@ -113,6 +293,7 @@ pub fn format( |
| 113 | pub fn formatType( | 293 | pub fn formatType( |
| 114 | value: var, | 294 | value: var, |
| 115 | comptime fmt: []const u8, | 295 | comptime fmt: []const u8, |
| | 296 | comptime options: FormatOptions, |
| 116 | context: var, | 297 | context: var, |
| 117 | comptime Errors: type, | 298 | comptime Errors: type, |
| 118 | output: fn (@typeOf(context), []const u8) Errors!void, | 299 | output: fn (@typeOf(context), []const u8) Errors!void, |
| ... | @@ -121,7 +302,7 @@ pub fn formatType( | ... | @@ -121,7 +302,7 @@ pub fn formatType( |
| 121 | const T = @typeOf(value); | 302 | const T = @typeOf(value); |
| 122 | switch (@typeInfo(T)) { | 303 | switch (@typeInfo(T)) { |
| 123 | .ComptimeInt, .Int, .Float => { | 304 | .ComptimeInt, .Int, .Float => { |
| 124 | return formatValue(value, fmt, context, Errors, output); | 305 | return formatValue(value, fmt, options, context, Errors, output); |
| 125 | }, | 306 | }, |
| 126 | .Void => { | 307 | .Void => { |
| 127 | return output(context, "void"); | 308 | return output(context, "void"); |
| ... | @@ -131,16 +312,16 @@ pub fn formatType( | ... | @@ -131,16 +312,16 @@ pub fn formatType( |
| 131 | }, | 312 | }, |
| 132 | .Optional => { | 313 | .Optional => { |
| 133 | if (value) |payload| { | 314 | if (value) |payload| { |
| 134 | return formatType(payload, fmt, context, Errors, output, max_depth); | 315 | return formatType(payload, fmt, options, context, Errors, output, max_depth); |
| 135 | } else { | 316 | } else { |
| 136 | return output(context, "null"); | 317 | return output(context, "null"); |
| 137 | } | 318 | } |
| 138 | }, | 319 | }, |
| 139 | .ErrorUnion => { | 320 | .ErrorUnion => { |
| 140 | if (value) |payload| { | 321 | if (value) |payload| { |
| 141 | return formatType(payload, fmt, context, Errors, output, max_depth); | 322 | return formatType(payload, fmt, options, context, Errors, output, max_depth); |
| 142 | } else |err| { | 323 | } else |err| { |
| 143 | return formatType(err, fmt, context, Errors, output, max_depth); | 324 | return formatType(err, fmt, options, context, Errors, output, max_depth); |
| 144 | } | 325 | } |
| 145 | }, | 326 | }, |
| 146 | .ErrorSet => { | 327 | .ErrorSet => { |
| ... | @@ -152,16 +333,16 @@ pub fn formatType( | ... | @@ -152,16 +333,16 @@ pub fn formatType( |
| 152 | }, | 333 | }, |
| 153 | .Enum => { | 334 | .Enum => { |
| 154 | if (comptime std.meta.trait.hasFn("format")(T)) { | 335 | if (comptime std.meta.trait.hasFn("format")(T)) { |
| 155 | return value.format(fmt, context, Errors, output); | 336 | return value.format(fmt, options, context, Errors, output); |
| 156 | } | 337 | } |
| 157 | | 338 | |
| 158 | try output(context, @typeName(T)); | 339 | try output(context, @typeName(T)); |
| 159 | try output(context, "."); | 340 | try output(context, "."); |
| 160 | return formatType(@tagName(value), "", context, Errors, output, max_depth); | 341 | return formatType(@tagName(value), "", options, context, Errors, output, max_depth); |
| 161 | }, | 342 | }, |
| 162 | .Union => { | 343 | .Union => { |
| 163 | if (comptime std.meta.trait.hasFn("format")(T)) { | 344 | if (comptime std.meta.trait.hasFn("format")(T)) { |
| 164 | return value.format(fmt, context, Errors, output); | 345 | return value.format(fmt, options, context, Errors, output); |
| 165 | } | 346 | } |
| 166 | | 347 | |
| 167 | try output(context, @typeName(T)); | 348 | try output(context, @typeName(T)); |
| ... | @@ -175,7 +356,7 @@ pub fn formatType( | ... | @@ -175,7 +356,7 @@ pub fn formatType( |
| 175 | try output(context, " = "); | 356 | try output(context, " = "); |
| 176 | inline for (info.fields) |u_field| { | 357 | inline for (info.fields) |u_field| { |
| 177 | if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) { | 358 | if (@enumToInt(UnionTagType(value)) == u_field.enum_field.?.value) { |
| 178 | try formatType(@field(value, u_field.name), "", context, Errors, output, max_depth - 1); | 359 | try formatType(@field(value, u_field.name), "", options, context, Errors, output, max_depth - 1); |
| 179 | } | 360 | } |
| 180 | } | 361 | } |
| 181 | try output(context, " }"); | 362 | try output(context, " }"); |
| ... | @@ -185,7 +366,7 @@ pub fn formatType( | ... | @@ -185,7 +366,7 @@ pub fn formatType( |
| 185 | }, | 366 | }, |
| 186 | .Struct => { | 367 | .Struct => { |
| 187 | if (comptime std.meta.trait.hasFn("format")(T)) { | 368 | if (comptime std.meta.trait.hasFn("format")(T)) { |
| 188 | return value.format(fmt, context, Errors, output); | 369 | return value.format(fmt, options, context, Errors, output); |
| 189 | } | 370 | } |
| 190 | | 371 | |
| 191 | try output(context, @typeName(T)); | 372 | try output(context, @typeName(T)); |
| ... | @@ -201,7 +382,7 @@ pub fn formatType( | ... | @@ -201,7 +382,7 @@ pub fn formatType( |
| 201 | } | 382 | } |
| 202 | try output(context, @memberName(T, field_i)); | 383 | try output(context, @memberName(T, field_i)); |
| 203 | try output(context, " = "); | 384 | try output(context, " = "); |
| 204 | try formatType(@field(value, @memberName(T, field_i)), "", context, Errors, output, max_depth - 1); | 385 | try formatType(@field(value, @memberName(T, field_i)), "", options, context, Errors, output, max_depth - 1); |
| 205 | } | 386 | } |
| 206 | try output(context, " }"); | 387 | try output(context, " }"); |
| 207 | }, | 388 | }, |
| ... | @@ -209,12 +390,12 @@ pub fn formatType( | ... | @@ -209,12 +390,12 @@ pub fn formatType( |
| 209 | .One => switch (@typeInfo(ptr_info.child)) { | 390 | .One => switch (@typeInfo(ptr_info.child)) { |
| 210 | builtin.TypeId.Array => |info| { | 391 | builtin.TypeId.Array => |info| { |
| 211 | if (info.child == u8) { | 392 | if (info.child == u8) { |
| 212 | return formatText(value, fmt, context, Errors, output); | 393 | return formatText(value, fmt, options, context, Errors, output); |
| 213 | } | 394 | } |
| 214 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); | 395 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); |
| 215 | }, | 396 | }, |
| 216 | builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { | 397 | builtin.TypeId.Enum, builtin.TypeId.Union, builtin.TypeId.Struct => { |
| 217 | return formatType(value.*, fmt, context, Errors, output, max_depth); | 398 | return formatType(value.*, fmt, options, context, Errors, output, max_depth); |
| 218 | }, | 399 | }, |
| 219 | else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)), | 400 | else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)), |
| 220 | }, | 401 | }, |
| ... | @@ -222,17 +403,17 @@ pub fn formatType( | ... | @@ -222,17 +403,17 @@ pub fn formatType( |
| 222 | if (ptr_info.child == u8) { | 403 | if (ptr_info.child == u8) { |
| 223 | if (fmt.len > 0 and fmt[0] == 's') { | 404 | if (fmt.len > 0 and fmt[0] == 's') { |
| 224 | const len = mem.len(u8, value); | 405 | const len = mem.len(u8, value); |
| 225 | return formatText(value[0..len], fmt, context, Errors, output); | 406 | return formatText(value[0..len], fmt, options, context, Errors, output); |
| 226 | } | 407 | } |
| 227 | } | 408 | } |
| 228 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); | 409 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); |
| 229 | }, | 410 | }, |
| 230 | .Slice => { | 411 | .Slice => { |
| 231 | if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { | 412 | if (fmt.len > 0 and ((fmt[0] == 'x') or (fmt[0] == 'X'))) { |
| 232 | return formatText(value, fmt, context, Errors, output); | 413 | return formatText(value, fmt, options, context, Errors, output); |
| 233 | } | 414 | } |
| 234 | if (ptr_info.child == u8) { | 415 | if (ptr_info.child == u8) { |
| 235 | return formatText(value, fmt, context, Errors, output); | 416 | return formatText(value, fmt, options, context, Errors, output); |
| 236 | } | 417 | } |
| 237 | return format(context, Errors, output, "{}@{x}", @typeName(ptr_info.child), @ptrToInt(value.ptr)); | 418 | return format(context, Errors, output, "{}@{x}", @typeName(ptr_info.child), @ptrToInt(value.ptr)); |
| 238 | }, | 419 | }, |
| ... | @@ -242,7 +423,7 @@ pub fn formatType( | ... | @@ -242,7 +423,7 @@ pub fn formatType( |
| 242 | }, | 423 | }, |
| 243 | .Array => |info| { | 424 | .Array => |info| { |
| 244 | if (info.child == u8) { | 425 | if (info.child == u8) { |
| 245 | return formatText(value, fmt, context, Errors, output); | 426 | return formatText(value, fmt, options, context, Errors, output); |
| 246 | } | 427 | } |
| 247 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(&value)); | 428 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(&value)); |
| 248 | }, | 429 | }, |
| ... | @@ -256,28 +437,25 @@ pub fn formatType( | ... | @@ -256,28 +437,25 @@ pub fn formatType( |
| 256 | fn formatValue( | 437 | fn formatValue( |
| 257 | value: var, | 438 | value: var, |
| 258 | comptime fmt: []const u8, | 439 | comptime fmt: []const u8, |
| | 440 | comptime options: FormatOptions, |
| 259 | context: var, | 441 | context: var, |
| 260 | comptime Errors: type, | 442 | comptime Errors: type, |
| 261 | output: fn (@typeOf(context), []const u8) Errors!void, | 443 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 262 | ) Errors!void { | 444 | ) Errors!void { |
| 263 | if (fmt.len > 0 and fmt[0] == 'B') { | 445 | if (comptime std.mem.eql(u8, fmt, "B")) { |
| 264 | comptime var width: ?usize = null; | 446 | // TODO https://github.com/ziglang/zig/issues/2725 |
| 265 | if (fmt.len > 1) { | 447 | if (options.width) |w| return formatBytes(value, w, 1000, context, Errors, output); |
| 266 | if (fmt[1] == 'i') { | 448 | return formatBytes(value, null, 1000, context, Errors, output); |
| 267 | if (fmt.len > 2) { | 449 | } else if (comptime std.mem.eql(u8, fmt, "Bi")) { |
| 268 | width = comptime (parseUnsigned(usize, fmt[2..], 10) catch unreachable); | 450 | // TODO https://github.com/ziglang/zig/issues/2725 |
| 269 | } | 451 | if (options.width) |w| return formatBytes(value, w, 1024, context, Errors, output); |
| 270 | return formatBytes(value, width, 1024, context, Errors, output); | 452 | return formatBytes(value, null, 1024, context, Errors, output); |
| 271 | } | | |
| 272 | width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); | | |
| 273 | } | | |
| 274 | return formatBytes(value, width, 1000, context, Errors, output); | | |
| 275 | } | 453 | } |
| 276 | | 454 | |
| 277 | const T = @typeOf(value); | 455 | const T = @typeOf(value); |
| 278 | switch (@typeId(T)) { | 456 | switch (@typeId(T)) { |
| 279 | .Float => return formatFloatValue(value, fmt, context, Errors, output), | 457 | .Float => return formatFloatValue(value, fmt, options, context, Errors, output), |
| 280 | .Int, .ComptimeInt => return formatIntValue(value, fmt, context, Errors, output), | 458 | .Int, .ComptimeInt => return formatIntValue(value, fmt, options, context, Errors, output), |
| 281 | else => comptime unreachable, | 459 | else => comptime unreachable, |
| 282 | } | 460 | } |
| 283 | } | 461 | } |
| ... | @@ -285,13 +463,13 @@ fn formatValue( | ... | @@ -285,13 +463,13 @@ fn formatValue( |
| 285 | pub fn formatIntValue( | 463 | pub fn formatIntValue( |
| 286 | value: var, | 464 | value: var, |
| 287 | comptime fmt: []const u8, | 465 | comptime fmt: []const u8, |
| | 466 | comptime options: FormatOptions, |
| 288 | context: var, | 467 | context: var, |
| 289 | comptime Errors: type, | 468 | comptime Errors: type, |
| 290 | output: fn (@typeOf(context), []const u8) Errors!void, | 469 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 291 | ) Errors!void { | 470 | ) Errors!void { |
| 292 | comptime var radix = 10; | 471 | comptime var radix = 10; |
| 293 | comptime var uppercase = false; | 472 | comptime var uppercase = false; |
| 294 | comptime var width = 0; | | |
| 295 | | 473 | |
| 296 | const int_value = if (@typeOf(value) == comptime_int) blk: { | 474 | const int_value = if (@typeOf(value) == comptime_int) blk: { |
| 297 | const Int = math.IntFittingRange(value, value); | 475 | const Int = math.IntFittingRange(value, value); |
| ... | @@ -299,83 +477,75 @@ pub fn formatIntValue( | ... | @@ -299,83 +477,75 @@ pub fn formatIntValue( |
| 299 | } else | 477 | } else |
| 300 | value; | 478 | value; |
| 301 | | 479 | |
| 302 | if (fmt.len > 0) { | 480 | if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) { |
| 303 | switch (fmt[0]) { | 481 | radix = 10; |
| 304 | 'c' => { | 482 | uppercase = false; |
| 305 | if (@typeOf(int_value).bit_count <= 8) { | 483 | } else if (comptime std.mem.eql(u8, fmt, "c")) { |
| 306 | if (fmt.len > 1) | 484 | if (@typeOf(int_value).bit_count <= 8) { |
| 307 | @compileError("Unknown format character: " ++ [_]u8{fmt[1]}); | 485 | return formatAsciiChar(u8(int_value), context, Errors, output); |
| 308 | return formatAsciiChar(u8(int_value), context, Errors, output); | 486 | } else { |
| 309 | } | 487 | @compileError("Cannot print integer that is larger than 8 bits as a ascii"); |
| 310 | }, | | |
| 311 | 'b' => { | | |
| 312 | radix = 2; | | |
| 313 | uppercase = false; | | |
| 314 | width = 0; | | |
| 315 | }, | | |
| 316 | 'd' => { | | |
| 317 | radix = 10; | | |
| 318 | uppercase = false; | | |
| 319 | width = 0; | | |
| 320 | }, | | |
| 321 | 'x' => { | | |
| 322 | radix = 16; | | |
| 323 | uppercase = false; | | |
| 324 | width = 0; | | |
| 325 | }, | | |
| 326 | 'X' => { | | |
| 327 | radix = 16; | | |
| 328 | uppercase = true; | | |
| 329 | width = 0; | | |
| 330 | }, | | |
| 331 | else => @compileError("Unknown format character: " ++ [_]u8{fmt[0]}), | | |
| 332 | } | 488 | } |
| 333 | if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); | 489 | } else if (comptime std.mem.eql(u8, fmt, "b")) { |
| | 490 | radix = 2; |
| | 491 | uppercase = false; |
| | 492 | } else if (comptime std.mem.eql(u8, fmt, "x")) { |
| | 493 | radix = 16; |
| | 494 | uppercase = false; |
| | 495 | } else if (comptime std.mem.eql(u8, fmt, "X")) { |
| | 496 | radix = 16; |
| | 497 | uppercase = true; |
| | 498 | } else { |
| | 499 | @compileError("Unknown format string: '" ++ fmt ++ "'"); |
| 334 | } | 500 | } |
| 335 | return formatInt(int_value, radix, uppercase, width, context, Errors, output); | 501 | |
| | 502 | // TODO https://github.com/ziglang/zig/issues/2725 |
| | 503 | if (options.width) |w| return formatInt(int_value, radix, uppercase, w, context, Errors, output); |
| | 504 | return formatInt(int_value, radix, uppercase, 0, context, Errors, output); |
| 336 | } | 505 | } |
| 337 | | 506 | |
| 338 | fn formatFloatValue( | 507 | fn formatFloatValue( |
| 339 | value: var, | 508 | value: var, |
| 340 | comptime fmt: []const u8, | 509 | comptime fmt: []const u8, |
| | 510 | comptime options: FormatOptions, |
| 341 | context: var, | 511 | context: var, |
| 342 | comptime Errors: type, | 512 | comptime Errors: type, |
| 343 | output: fn (@typeOf(context), []const u8) Errors!void, | 513 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 344 | ) Errors!void { | 514 | ) Errors!void { |
| 345 | comptime var width: ?usize = null; | 515 | if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { |
| 346 | comptime var float_fmt = 'e'; | 516 | // TODO https://github.com/ziglang/zig/issues/2725 |
| 347 | if (fmt.len > 0) { | 517 | if (options.precision) |p| return formatFloatScientific(value, p, context, Errors, output); |
| 348 | float_fmt = fmt[0]; | 518 | return formatFloatScientific(value, null, context, Errors, output); |
| 349 | if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); | 519 | } else if (comptime std.mem.eql(u8, fmt, "d")) { |
| 350 | } | 520 | // TODO https://github.com/ziglang/zig/issues/2725 |
| 351 | | 521 | if (options.precision) |p| return formatFloatDecimal(value, p, context, Errors, output); |
| 352 | switch (float_fmt) { | 522 | return formatFloatDecimal(value, null, context, Errors, output); |
| 353 | 'e' => try formatFloatScientific(value, width, context, Errors, output), | 523 | } else { |
| 354 | '.' => try formatFloatDecimal(value, width, context, Errors, output), | 524 | @compileError("Unknown format string: '" ++ fmt ++ "'"); |
| 355 | else => @compileError("Unknown format character: " ++ [_]u8{float_fmt}), | | |
| 356 | } | 525 | } |
| 357 | } | 526 | } |
| 358 | | 527 | |
| 359 | pub fn formatText( | 528 | pub fn formatText( |
| 360 | bytes: []const u8, | 529 | bytes: []const u8, |
| 361 | comptime fmt: []const u8, | 530 | comptime fmt: []const u8, |
| | 531 | comptime options: FormatOptions, |
| 362 | context: var, | 532 | context: var, |
| 363 | comptime Errors: type, | 533 | comptime Errors: type, |
| 364 | output: fn (@typeOf(context), []const u8) Errors!void, | 534 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 365 | ) Errors!void { | 535 | ) Errors!void { |
| 366 | if (fmt.len > 0) { | 536 | if (fmt.len == 0) { |
| 367 | if (fmt[0] == 's') { | 537 | return output(context, bytes); |
| 368 | comptime var width = 0; | 538 | } else if (comptime std.mem.eql(u8, fmt, "s")) { |
| 369 | if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); | 539 | if (options.width) |w| return formatBuf(bytes, w, context, Errors, output); |
| 370 | return formatBuf(bytes, width, context, Errors, output); | 540 | return formatBuf(bytes, 0, context, Errors, output); |
| 371 | } else if ((fmt[0] == 'x') or (fmt[0] == 'X')) { | 541 | } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) { |
| 372 | for (bytes) |c| { | 542 | for (bytes) |c| { |
| 373 | try formatInt(c, 16, fmt[0] == 'X', 2, context, Errors, output); | 543 | try formatInt(c, 16, fmt[0] == 'X', 2, context, Errors, output); |
| 374 | } | 544 | } |
| 375 | return; | 545 | return; |
| 376 | } else @compileError("Unknown format character: " ++ [_]u8{fmt[0]}); | 546 | } else { |
| | 547 | @compileError("Unknown format string: '" ++ fmt ++ "'"); |
| 377 | } | 548 | } |
| 378 | return output(context, bytes); | | |
| 379 | } | 549 | } |
| 380 | | 550 | |
| 381 | pub fn formatAsciiChar( | 551 | pub fn formatAsciiChar( |
| ... | @@ -868,7 +1038,7 @@ test "parseUnsigned" { | ... | @@ -868,7 +1038,7 @@ test "parseUnsigned" { |
| 868 | | 1038 | |
| 869 | pub const parseFloat = @import("fmt/parse_float.zig").parseFloat; | 1039 | pub const parseFloat = @import("fmt/parse_float.zig").parseFloat; |
| 870 | | 1040 | |
| 871 | test "fmt.parseFloat" { | 1041 | test "parseFloat" { |
| 872 | _ = @import("fmt/parse_float.zig"); | 1042 | _ = @import("fmt/parse_float.zig"); |
| 873 | } | 1043 | } |
| 874 | | 1044 | |
| ... | @@ -960,7 +1130,7 @@ test "parse unsigned comptime" { | ... | @@ -960,7 +1130,7 @@ test "parse unsigned comptime" { |
| 960 | } | 1130 | } |
| 961 | } | 1131 | } |
| 962 | | 1132 | |
| 963 | test "fmt.optional" { | 1133 | test "optional" { |
| 964 | { | 1134 | { |
| 965 | const value: ?i32 = 1234; | 1135 | const value: ?i32 = 1234; |
| 966 | try testFmt("optional: 1234\n", "optional: {}\n", value); | 1136 | try testFmt("optional: 1234\n", "optional: {}\n", value); |
| ... | @@ -971,7 +1141,7 @@ test "fmt.optional" { | ... | @@ -971,7 +1141,7 @@ test "fmt.optional" { |
| 971 | } | 1141 | } |
| 972 | } | 1142 | } |
| 973 | | 1143 | |
| 974 | test "fmt.error" { | 1144 | test "error" { |
| 975 | { | 1145 | { |
| 976 | const value: anyerror!i32 = 1234; | 1146 | const value: anyerror!i32 = 1234; |
| 977 | try testFmt("error union: 1234\n", "error union: {}\n", value); | 1147 | try testFmt("error union: 1234\n", "error union: {}\n", value); |
| ... | @@ -982,14 +1152,14 @@ test "fmt.error" { | ... | @@ -982,14 +1152,14 @@ test "fmt.error" { |
| 982 | } | 1152 | } |
| 983 | } | 1153 | } |
| 984 | | 1154 | |
| 985 | test "fmt.int.small" { | 1155 | test "int.small" { |
| 986 | { | 1156 | { |
| 987 | const value: u3 = 0b101; | 1157 | const value: u3 = 0b101; |
| 988 | try testFmt("u3: 5\n", "u3: {}\n", value); | 1158 | try testFmt("u3: 5\n", "u3: {}\n", value); |
| 989 | } | 1159 | } |
| 990 | } | 1160 | } |
| 991 | | 1161 | |
| 992 | test "fmt.int.specifier" { | 1162 | test "int.specifier" { |
| 993 | { | 1163 | { |
| 994 | const value: u8 = 'a'; | 1164 | const value: u8 = 'a'; |
| 995 | try testFmt("u8: a\n", "u8: {c}\n", value); | 1165 | try testFmt("u8: a\n", "u8: {c}\n", value); |
| ... | @@ -1000,27 +1170,31 @@ test "fmt.int.specifier" { | ... | @@ -1000,27 +1170,31 @@ test "fmt.int.specifier" { |
| 1000 | } | 1170 | } |
| 1001 | } | 1171 | } |
| 1002 | | 1172 | |
| 1003 | test "fmt.buffer" { | 1173 | test "int.padded" { |
| | 1174 | try testFmt("u8: '0001'", "u8: '{:4}'", u8(1)); |
| | 1175 | } |
| | 1176 | |
| | 1177 | test "buffer" { |
| 1004 | { | 1178 | { |
| 1005 | var buf1: [32]u8 = undefined; | 1179 | var buf1: [32]u8 = undefined; |
| 1006 | var context = BufPrintContext{ .remaining = buf1[0..] }; | 1180 | var context = BufPrintContext{ .remaining = buf1[0..] }; |
| 1007 | try formatType(1234, "", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); | 1181 | try formatType(1234, "", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); |
| 1008 | var res = buf1[0 .. buf1.len - context.remaining.len]; | 1182 | var res = buf1[0 .. buf1.len - context.remaining.len]; |
| 1009 | testing.expect(mem.eql(u8, res, "1234")); | 1183 | testing.expect(mem.eql(u8, res, "1234")); |
| 1010 | | 1184 | |
| 1011 | context = BufPrintContext{ .remaining = buf1[0..] }; | 1185 | context = BufPrintContext{ .remaining = buf1[0..] }; |
| 1012 | try formatType('a', "c", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); | 1186 | try formatType('a', "c", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); |
| 1013 | res = buf1[0 .. buf1.len - context.remaining.len]; | 1187 | res = buf1[0 .. buf1.len - context.remaining.len]; |
| 1014 | testing.expect(mem.eql(u8, res, "a")); | 1188 | testing.expect(mem.eql(u8, res, "a")); |
| 1015 | | 1189 | |
| 1016 | context = BufPrintContext{ .remaining = buf1[0..] }; | 1190 | context = BufPrintContext{ .remaining = buf1[0..] }; |
| 1017 | try formatType(0b1100, "b", &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); | 1191 | try formatType(0b1100, "b", FormatOptions{}, &context, error{BufferTooSmall}, bufPrintWrite, default_max_depth); |
| 1018 | res = buf1[0 .. buf1.len - context.remaining.len]; | 1192 | res = buf1[0 .. buf1.len - context.remaining.len]; |
| 1019 | testing.expect(mem.eql(u8, res, "1100")); | 1193 | testing.expect(mem.eql(u8, res, "1100")); |
| 1020 | } | 1194 | } |
| 1021 | } | 1195 | } |
| 1022 | | 1196 | |
| 1023 | test "fmt.array" { | 1197 | test "array" { |
| 1024 | { | 1198 | { |
| 1025 | const value: [3]u8 = "abc"; | 1199 | const value: [3]u8 = "abc"; |
| 1026 | try testFmt("array: abc\n", "array: {}\n", value); | 1200 | try testFmt("array: abc\n", "array: {}\n", value); |
| ... | @@ -1035,7 +1209,7 @@ test "fmt.array" { | ... | @@ -1035,7 +1209,7 @@ test "fmt.array" { |
| 1035 | } | 1209 | } |
| 1036 | } | 1210 | } |
| 1037 | | 1211 | |
| 1038 | test "fmt.slice" { | 1212 | test "slice" { |
| 1039 | { | 1213 | { |
| 1040 | const value: []const u8 = "abc"; | 1214 | const value: []const u8 = "abc"; |
| 1041 | try testFmt("slice: abc\n", "slice: {}\n", value); | 1215 | try testFmt("slice: abc\n", "slice: {}\n", value); |
| ... | @@ -1045,11 +1219,11 @@ test "fmt.slice" { | ... | @@ -1045,11 +1219,11 @@ test "fmt.slice" { |
| 1045 | try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", value); | 1219 | try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", value); |
| 1046 | } | 1220 | } |
| 1047 | | 1221 | |
| 1048 | try testFmt("buf: Test \n", "buf: {s5}\n", "Test"); | 1222 | try testFmt("buf: Test \n", "buf: {s:5}\n", "Test"); |
| 1049 | try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", "Test"); | 1223 | try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", "Test"); |
| 1050 | } | 1224 | } |
| 1051 | | 1225 | |
| 1052 | test "fmt.pointer" { | 1226 | test "pointer" { |
| 1053 | { | 1227 | { |
| 1054 | const value = @intToPtr(*i32, 0xdeadbeef); | 1228 | const value = @intToPtr(*i32, 0xdeadbeef); |
| 1055 | try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", value); | 1229 | try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", value); |
| ... | @@ -1065,17 +1239,17 @@ test "fmt.pointer" { | ... | @@ -1065,17 +1239,17 @@ test "fmt.pointer" { |
| 1065 | } | 1239 | } |
| 1066 | } | 1240 | } |
| 1067 | | 1241 | |
| 1068 | test "fmt.cstr" { | 1242 | test "cstr" { |
| 1069 | try testFmt("cstr: Test C\n", "cstr: {s}\n", c"Test C"); | 1243 | try testFmt("cstr: Test C\n", "cstr: {s}\n", c"Test C"); |
| 1070 | try testFmt("cstr: Test C \n", "cstr: {s10}\n", c"Test C"); | 1244 | try testFmt("cstr: Test C \n", "cstr: {s:10}\n", c"Test C"); |
| 1071 | } | 1245 | } |
| 1072 | | 1246 | |
| 1073 | test "fmt.filesize" { | 1247 | test "filesize" { |
| 1074 | try testFmt("file size: 63MiB\n", "file size: {Bi}\n", usize(63 * 1024 * 1024)); | 1248 | try testFmt("file size: 63MiB\n", "file size: {Bi}\n", usize(63 * 1024 * 1024)); |
| 1075 | try testFmt("file size: 66.06MB\n", "file size: {B2}\n", usize(63 * 1024 * 1024)); | 1249 | try testFmt("file size: 66.06MB\n", "file size: {B:2}\n", usize(63 * 1024 * 1024)); |
| 1076 | } | 1250 | } |
| 1077 | | 1251 | |
| 1078 | test "fmt.struct" { | 1252 | test "struct" { |
| 1079 | { | 1253 | { |
| 1080 | const Struct = struct { | 1254 | const Struct = struct { |
| 1081 | field: u8, | 1255 | field: u8, |
| ... | @@ -1094,7 +1268,7 @@ test "fmt.struct" { | ... | @@ -1094,7 +1268,7 @@ test "fmt.struct" { |
| 1094 | } | 1268 | } |
| 1095 | } | 1269 | } |
| 1096 | | 1270 | |
| 1097 | test "fmt.enum" { | 1271 | test "enum" { |
| 1098 | const Enum = enum { | 1272 | const Enum = enum { |
| 1099 | One, | 1273 | One, |
| 1100 | Two, | 1274 | Two, |
| ... | @@ -1104,229 +1278,71 @@ test "fmt.enum" { | ... | @@ -1104,229 +1278,71 @@ test "fmt.enum" { |
| 1104 | try testFmt("enum: Enum.Two\n", "enum: {}\n", &value); | 1278 | try testFmt("enum: Enum.Two\n", "enum: {}\n", &value); |
| 1105 | } | 1279 | } |
| 1106 | | 1280 | |
| 1107 | test "fmt.float.scientific" { | 1281 | test "float.scientific" { |
| 1108 | { | 1282 | try testFmt("f32: 1.34000003e+00", "f32: {e}", f32(1.34)); |
| 1109 | var buf1: [32]u8 = undefined; | 1283 | try testFmt("f32: 1.23400001e+01", "f32: {e}", f32(12.34)); |
| 1110 | const value: f32 = 1.34; | 1284 | try testFmt("f64: -1.234e+11", "f64: {e}", f64(-12.34e10)); |
| 1111 | const result = try bufPrint(buf1[0..], "f32: {e}\n", value); | 1285 | try testFmt("f64: 9.99996e-40", "f64: {e}", f64(9.999960e-40)); |
| 1112 | testing.expect(mem.eql(u8, result, "f32: 1.34000003e+00\n")); | | |
| 1113 | } | | |
| 1114 | { | | |
| 1115 | var buf1: [32]u8 = undefined; | | |
| 1116 | const value: f32 = 12.34; | | |
| 1117 | const result = try bufPrint(buf1[0..], "f32: {e}\n", value); | | |
| 1118 | testing.expect(mem.eql(u8, result, "f32: 1.23400001e+01\n")); | | |
| 1119 | } | | |
| 1120 | { | | |
| 1121 | var buf1: [32]u8 = undefined; | | |
| 1122 | const value: f64 = -12.34e10; | | |
| 1123 | const result = try bufPrint(buf1[0..], "f64: {e}\n", value); | | |
| 1124 | testing.expect(mem.eql(u8, result, "f64: -1.234e+11\n")); | | |
| 1125 | } | | |
| 1126 | { | | |
| 1127 | // This fails on release due to a minor rounding difference. | | |
| 1128 | // --release-fast outputs 9.999960000000001e-40 vs. the expected. | | |
| 1129 | // TODO fix this, it should be the same in Debug and ReleaseFast | | |
| 1130 | if (builtin.mode == builtin.Mode.Debug) { | | |
| 1131 | var buf1: [32]u8 = undefined; | | |
| 1132 | const value: f64 = 9.999960e-40; | | |
| 1133 | const result = try bufPrint(buf1[0..], "f64: {e}\n", value); | | |
| 1134 | testing.expect(mem.eql(u8, result, "f64: 9.99996e-40\n")); | | |
| 1135 | } | | |
| 1136 | } | | |
| 1137 | } | 1286 | } |
| 1138 | | 1287 | |
| 1139 | test "fmt.float.scientific.precision" { | 1288 | test "float.scientific.precision" { |
| 1140 | { | 1289 | try testFmt("f64: 1.40971e-42", "f64: {e:.5}", f64(1.409706e-42)); |
| 1141 | var buf1: [32]u8 = undefined; | 1290 | try testFmt("f64: 1.00000e-09", "f64: {e:.5}", f64(@bitCast(f32, u32(814313563)))); |
| 1142 | const value: f64 = 1.409706e-42; | 1291 | try testFmt("f64: 7.81250e-03", "f64: {e:.5}", f64(@bitCast(f32, u32(1006632960)))); |
| 1143 | const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); | 1292 | // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05. |
| 1144 | testing.expect(mem.eql(u8, result, "f64: 1.40971e-42\n")); | 1293 | // In fact, libc doesn't round a lot of 5 cases up when one past the precision point. |
| 1145 | } | 1294 | try testFmt("f64: 1.00001e+05", "f64: {e:.5}", f64(@bitCast(f32, u32(1203982400)))); |
| 1146 | { | | |
| 1147 | var buf1: [32]u8 = undefined; | | |
| 1148 | const value: f64 = @bitCast(f32, u32(814313563)); | | |
| 1149 | const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); | | |
| 1150 | testing.expect(mem.eql(u8, result, "f64: 1.00000e-09\n")); | | |
| 1151 | } | | |
| 1152 | { | | |
| 1153 | var buf1: [32]u8 = undefined; | | |
| 1154 | const value: f64 = @bitCast(f32, u32(1006632960)); | | |
| 1155 | const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); | | |
| 1156 | testing.expect(mem.eql(u8, result, "f64: 7.81250e-03\n")); | | |
| 1157 | } | | |
| 1158 | { | | |
| 1159 | // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05. | | |
| 1160 | // In fact, libc doesn't round a lot of 5 cases up when one past the precision point. | | |
| 1161 | var buf1: [32]u8 = undefined; | | |
| 1162 | const value: f64 = @bitCast(f32, u32(1203982400)); | | |
| 1163 | const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); | | |
| 1164 | testing.expect(mem.eql(u8, result, "f64: 1.00001e+05\n")); | | |
| 1165 | } | | |
| 1166 | } | 1295 | } |
| 1167 | | 1296 | |
| 1168 | test "fmt.float.special" { | 1297 | test "float.special" { |
| 1169 | { | 1298 | try testFmt("f64: nan", "f64: {}", math.nan_f64); |
| 1170 | var buf1: [32]u8 = undefined; | 1299 | // negative nan is not defined by IEE 754, |
| 1171 | const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64); | 1300 | // and ARM thus normalizes it to positive nan |
| 1172 | testing.expect(mem.eql(u8, result, "f64: nan\n")); | | |
| 1173 | } | | |
| 1174 | if (builtin.arch != builtin.Arch.arm) { | 1301 | if (builtin.arch != builtin.Arch.arm) { |
| 1175 | // negative nan is not defined by IEE 754, | 1302 | try testFmt("f64: -nan", "f64: {}", -math.nan_f64); |
| 1176 | // and ARM thus normalizes it to positive nan | | |
| 1177 | var buf1: [32]u8 = undefined; | | |
| 1178 | const result = try bufPrint(buf1[0..], "f64: {}\n", -math.nan_f64); | | |
| 1179 | testing.expect(mem.eql(u8, result, "f64: -nan\n")); | | |
| 1180 | } | | |
| 1181 | { | | |
| 1182 | var buf1: [32]u8 = undefined; | | |
| 1183 | const result = try bufPrint(buf1[0..], "f64: {}\n", math.inf_f64); | | |
| 1184 | testing.expect(mem.eql(u8, result, "f64: inf\n")); | | |
| 1185 | } | | |
| 1186 | { | | |
| 1187 | var buf1: [32]u8 = undefined; | | |
| 1188 | const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64); | | |
| 1189 | testing.expect(mem.eql(u8, result, "f64: -inf\n")); | | |
| 1190 | } | 1303 | } |
| | 1304 | try testFmt("f64: inf", "f64: {}", math.inf_f64); |
| | 1305 | try testFmt("f64: -inf", "f64: {}", -math.inf_f64); |
| 1191 | } | 1306 | } |
| 1192 | | 1307 | |
| 1193 | test "fmt.float.decimal" { | 1308 | test "float.decimal" { |
| 1194 | { | 1309 | try testFmt("f64: 152314000000000000000000000000", "f64: {d}", f64(1.52314e+29)); |
| 1195 | var buf1: [64]u8 = undefined; | 1310 | try testFmt("f32: 1.1", "f32: {d:.1}", f32(1.1234)); |
| 1196 | const value: f64 = 1.52314e+29; | 1311 | try testFmt("f32: 1234.57", "f32: {d:.2}", f32(1234.567)); |
| 1197 | const result = try bufPrint(buf1[0..], "f64: {.}\n", value); | 1312 | // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64). |
| 1198 | testing.expect(mem.eql(u8, result, "f64: 152314000000000000000000000000\n")); | 1313 | // -11.12339... is rounded back up to -11.1234 |
| 1199 | } | 1314 | try testFmt("f32: -11.1234", "f32: {d:.4}", f32(-11.1234)); |
| 1200 | { | 1315 | try testFmt("f32: 91.12345", "f32: {d:.5}", f32(91.12345)); |
| 1201 | var buf1: [32]u8 = undefined; | 1316 | try testFmt("f64: 91.1234567890", "f64: {d:.10}", f64(91.12345678901235)); |
| 1202 | const value: f32 = 1.1234; | 1317 | try testFmt("f64: 0.00000", "f64: {d:.5}", f64(0.0)); |
| 1203 | const result = try bufPrint(buf1[0..], "f32: {.1}\n", value); | 1318 | try testFmt("f64: 6", "f64: {d:.0}", f64(5.700)); |
| 1204 | testing.expect(mem.eql(u8, result, "f32: 1.1\n")); | 1319 | try testFmt("f64: 10.0", "f64: {d:.1}", f64(9.999)); |
| 1205 | } | 1320 | try testFmt("f64: 1.000", "f64: {d:.3}", f64(1.0)); |
| 1206 | { | 1321 | try testFmt("f64: 0.00030000", "f64: {d:.8}", f64(0.0003)); |
| 1207 | var buf1: [32]u8 = undefined; | 1322 | try testFmt("f64: 0.00000", "f64: {d:.5}", f64(1.40130e-45)); |
| 1208 | const value: f32 = 1234.567; | 1323 | try testFmt("f64: 0.00000", "f64: {d:.5}", f64(9.999960e-40)); |
| 1209 | const result = try bufPrint(buf1[0..], "f32: {.2}\n", value); | | |
| 1210 | testing.expect(mem.eql(u8, result, "f32: 1234.57\n")); | | |
| 1211 | } | | |
| 1212 | { | | |
| 1213 | var buf1: [32]u8 = undefined; | | |
| 1214 | const value: f32 = -11.1234; | | |
| 1215 | const result = try bufPrint(buf1[0..], "f32: {.4}\n", value); | | |
| 1216 | // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64). | | |
| 1217 | // -11.12339... is rounded back up to -11.1234 | | |
| 1218 | testing.expect(mem.eql(u8, result, "f32: -11.1234\n")); | | |
| 1219 | } | | |
| 1220 | { | | |
| 1221 | var buf1: [32]u8 = undefined; | | |
| 1222 | const value: f32 = 91.12345; | | |
| 1223 | const result = try bufPrint(buf1[0..], "f32: {.5}\n", value); | | |
| 1224 | testing.expect(mem.eql(u8, result, "f32: 91.12345\n")); | | |
| 1225 | } | | |
| 1226 | { | | |
| 1227 | var buf1: [32]u8 = undefined; | | |
| 1228 | const value: f64 = 91.12345678901235; | | |
| 1229 | const result = try bufPrint(buf1[0..], "f64: {.10}\n", value); | | |
| 1230 | testing.expect(mem.eql(u8, result, "f64: 91.1234567890\n")); | | |
| 1231 | } | | |
| 1232 | { | | |
| 1233 | var buf1: [32]u8 = undefined; | | |
| 1234 | const value: f64 = 0.0; | | |
| 1235 | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); | | |
| 1236 | testing.expect(mem.eql(u8, result, "f64: 0.00000\n")); | | |
| 1237 | } | | |
| 1238 | { | | |
| 1239 | var buf1: [32]u8 = undefined; | | |
| 1240 | const value: f64 = 5.700; | | |
| 1241 | const result = try bufPrint(buf1[0..], "f64: {.0}\n", value); | | |
| 1242 | testing.expect(mem.eql(u8, result, "f64: 6\n")); | | |
| 1243 | } | | |
| 1244 | { | | |
| 1245 | var buf1: [32]u8 = undefined; | | |
| 1246 | const value: f64 = 9.999; | | |
| 1247 | const result = try bufPrint(buf1[0..], "f64: {.1}\n", value); | | |
| 1248 | testing.expect(mem.eql(u8, result, "f64: 10.0\n")); | | |
| 1249 | } | | |
| 1250 | { | | |
| 1251 | var buf1: [32]u8 = undefined; | | |
| 1252 | const value: f64 = 1.0; | | |
| 1253 | const result = try bufPrint(buf1[0..], "f64: {.3}\n", value); | | |
| 1254 | testing.expect(mem.eql(u8, result, "f64: 1.000\n")); | | |
| 1255 | } | | |
| 1256 | { | | |
| 1257 | var buf1: [32]u8 = undefined; | | |
| 1258 | const value: f64 = 0.0003; | | |
| 1259 | const result = try bufPrint(buf1[0..], "f64: {.8}\n", value); | | |
| 1260 | testing.expect(mem.eql(u8, result, "f64: 0.00030000\n")); | | |
| 1261 | } | | |
| 1262 | { | | |
| 1263 | var buf1: [32]u8 = undefined; | | |
| 1264 | const value: f64 = 1.40130e-45; | | |
| 1265 | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); | | |
| 1266 | testing.expect(mem.eql(u8, result, "f64: 0.00000\n")); | | |
| 1267 | } | | |
| 1268 | { | | |
| 1269 | var buf1: [32]u8 = undefined; | | |
| 1270 | const value: f64 = 9.999960e-40; | | |
| 1271 | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); | | |
| 1272 | testing.expect(mem.eql(u8, result, "f64: 0.00000\n")); | | |
| 1273 | } | | |
| 1274 | } | 1324 | } |
| 1275 | | 1325 | |
| 1276 | test "fmt.float.libc.sanity" { | 1326 | test "float.libc.sanity" { |
| 1277 | { | 1327 | try testFmt("f64: 0.00001", "f64: {d:.5}", f64(@bitCast(f32, u32(916964781)))); |
| 1278 | var buf1: [32]u8 = undefined; | 1328 | try testFmt("f64: 0.00001", "f64: {d:.5}", f64(@bitCast(f32, u32(925353389)))); |
| 1279 | const value: f64 = f64(@bitCast(f32, u32(916964781))); | 1329 | try testFmt("f64: 0.10000", "f64: {d:.5}", f64(@bitCast(f32, u32(1036831278)))); |
| 1280 | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); | 1330 | try testFmt("f64: 1.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1065353133)))); |
| 1281 | testing.expect(mem.eql(u8, result, "f64: 0.00001\n")); | 1331 | try testFmt("f64: 10.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1092616192)))); |
| 1282 | } | 1332 | |
| 1283 | { | | |
| 1284 | var buf1: [32]u8 = undefined; | | |
| 1285 | const value: f64 = f64(@bitCast(f32, u32(925353389))); | | |
| 1286 | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); | | |
| 1287 | testing.expect(mem.eql(u8, result, "f64: 0.00001\n")); | | |
| 1288 | } | | |
| 1289 | { | | |
| 1290 | var buf1: [32]u8 = undefined; | | |
| 1291 | const value: f64 = f64(@bitCast(f32, u32(1036831278))); | | |
| 1292 | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); | | |
| 1293 | testing.expect(mem.eql(u8, result, "f64: 0.10000\n")); | | |
| 1294 | } | | |
| 1295 | { | | |
| 1296 | var buf1: [32]u8 = undefined; | | |
| 1297 | const value: f64 = f64(@bitCast(f32, u32(1065353133))); | | |
| 1298 | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); | | |
| 1299 | testing.expect(mem.eql(u8, result, "f64: 1.00000\n")); | | |
| 1300 | } | | |
| 1301 | { | | |
| 1302 | var buf1: [32]u8 = undefined; | | |
| 1303 | const value: f64 = f64(@bitCast(f32, u32(1092616192))); | | |
| 1304 | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); | | |
| 1305 | testing.expect(mem.eql(u8, result, "f64: 10.00000\n")); | | |
| 1306 | } | | |
| 1307 | // libc differences | 1333 | // libc differences |
| 1308 | { | 1334 | // |
| 1309 | var buf1: [32]u8 = undefined; | 1335 | // This is 0.015625 exactly according to gdb. We thus round down, |
| 1310 | // This is 0.015625 exactly according to gdb. We thus round down, | 1336 | // however glibc rounds up for some reason. This occurs for all |
| 1311 | // however glibc rounds up for some reason. This occurs for all | 1337 | // floats of the form x.yyyy25 on a precision point. |
| 1312 | // floats of the form x.yyyy25 on a precision point. | 1338 | try testFmt("f64: 0.01563", "f64: {d:.5}", f64(@bitCast(f32, u32(1015021568)))); |
| 1313 | const value: f64 = f64(@bitCast(f32, u32(1015021568))); | 1339 | // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3 |
| 1314 | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); | 1340 | // also rounds to 630 so I'm inclined to believe libc is not |
| 1315 | testing.expect(mem.eql(u8, result, "f64: 0.01563\n")); | 1341 | // optimal here. |
| 1316 | } | 1342 | try testFmt("f64: 18014400656965630.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1518338049)))); |
| 1317 | // std-windows-x86_64-Debug-bare test case fails | | |
| 1318 | { | | |
| 1319 | // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3 | | |
| 1320 | // also rounds to 630 so I'm inclined to believe libc is not | | |
| 1321 | // optimal here. | | |
| 1322 | var buf1: [32]u8 = undefined; | | |
| 1323 | const value: f64 = f64(@bitCast(f32, u32(1518338049))); | | |
| 1324 | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); | | |
| 1325 | testing.expect(mem.eql(u8, result, "f64: 18014400656965630.00000\n")); | | |
| 1326 | } | | |
| 1327 | } | 1343 | } |
| 1328 | | 1344 | |
| 1329 | test "fmt.custom" { | 1345 | test "custom" { |
| 1330 | const Vec2 = struct { | 1346 | const Vec2 = struct { |
| 1331 | const SelfType = @This(); | 1347 | const SelfType = @This(); |
| 1332 | x: f32, | 1348 | x: f32, |
| ... | @@ -1335,20 +1351,17 @@ test "fmt.custom" { | ... | @@ -1335,20 +1351,17 @@ test "fmt.custom" { |
| 1335 | pub fn format( | 1351 | pub fn format( |
| 1336 | self: SelfType, | 1352 | self: SelfType, |
| 1337 | comptime fmt: []const u8, | 1353 | comptime fmt: []const u8, |
| | 1354 | comptime options: FormatOptions, |
| 1338 | context: var, | 1355 | context: var, |
| 1339 | comptime Errors: type, | 1356 | comptime Errors: type, |
| 1340 | output: fn (@typeOf(context), []const u8) Errors!void, | 1357 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 1341 | ) Errors!void { | 1358 | ) Errors!void { |
| 1342 | switch (fmt.len) { | 1359 | if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { |
| 1343 | 0 => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y), | 1360 | return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", self.x, self.y); |
| 1344 | 1 => switch (fmt[0]) { | 1361 | } else if (comptime std.mem.eql(u8, fmt, "d")) { |
| 1345 | //point format | 1362 | return std.fmt.format(context, Errors, output, "{d:.3}x{d:.3}", self.x, self.y); |
| 1346 | 'p' => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y), | 1363 | } else { |
| 1347 | //dimension format | 1364 | @compileError("Unknown format character: '" ++ fmt ++ "'"); |
| 1348 | 'd' => return std.fmt.format(context, Errors, output, "{.3}x{.3}", self.x, self.y), | | |
| 1349 | else => unreachable, | | |
| 1350 | }, | | |
| 1351 | else => unreachable, | | |
| 1352 | } | 1365 | } |
| 1353 | } | 1366 | } |
| 1354 | }; | 1367 | }; |
| ... | @@ -1366,7 +1379,7 @@ test "fmt.custom" { | ... | @@ -1366,7 +1379,7 @@ test "fmt.custom" { |
| 1366 | try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", value); | 1379 | try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", value); |
| 1367 | } | 1380 | } |
| 1368 | | 1381 | |
| 1369 | test "fmt.struct" { | 1382 | test "struct" { |
| 1370 | const S = struct { | 1383 | const S = struct { |
| 1371 | a: u32, | 1384 | a: u32, |
| 1372 | b: anyerror, | 1385 | b: anyerror, |
| ... | @@ -1380,7 +1393,7 @@ test "fmt.struct" { | ... | @@ -1380,7 +1393,7 @@ test "fmt.struct" { |
| 1380 | try testFmt("S{ .a = 456, .b = error.Unused }", "{}", inst); | 1393 | try testFmt("S{ .a = 456, .b = error.Unused }", "{}", inst); |
| 1381 | } | 1394 | } |
| 1382 | | 1395 | |
| 1383 | test "fmt.union" { | 1396 | test "union" { |
| 1384 | const TU = union(enum) { | 1397 | const TU = union(enum) { |
| 1385 | float: f32, | 1398 | float: f32, |
| 1386 | int: u32, | 1399 | int: u32, |
| ... | @@ -1410,7 +1423,7 @@ test "fmt.union" { | ... | @@ -1410,7 +1423,7 @@ test "fmt.union" { |
| 1410 | testing.expect(mem.eql(u8, uu_result[0..3], "EU@")); | 1423 | testing.expect(mem.eql(u8, uu_result[0..3], "EU@")); |
| 1411 | } | 1424 | } |
| 1412 | | 1425 | |
| 1413 | test "fmt.enum" { | 1426 | test "enum" { |
| 1414 | const E = enum { | 1427 | const E = enum { |
| 1415 | One, | 1428 | One, |
| 1416 | Two, | 1429 | Two, |
| ... | @@ -1422,7 +1435,7 @@ test "fmt.enum" { | ... | @@ -1422,7 +1435,7 @@ test "fmt.enum" { |
| 1422 | try testFmt("E.Two", "{}", inst); | 1435 | try testFmt("E.Two", "{}", inst); |
| 1423 | } | 1436 | } |
| 1424 | | 1437 | |
| 1425 | test "fmt.struct.self-referential" { | 1438 | test "struct.self-referential" { |
| 1426 | const S = struct { | 1439 | const S = struct { |
| 1427 | const SelfType = @This(); | 1440 | const SelfType = @This(); |
| 1428 | a: ?*SelfType, | 1441 | a: ?*SelfType, |
| ... | @@ -1436,7 +1449,7 @@ test "fmt.struct.self-referential" { | ... | @@ -1436,7 +1449,7 @@ test "fmt.struct.self-referential" { |
| 1436 | try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst); | 1449 | try testFmt("S{ .a = S{ .a = S{ .a = S{ ... } } } }", "{}", inst); |
| 1437 | } | 1450 | } |
| 1438 | | 1451 | |
| 1439 | test "fmt.bytes.hex" { | 1452 | test "bytes.hex" { |
| 1440 | const some_bytes = "\xCA\xFE\xBA\xBE"; | 1453 | const some_bytes = "\xCA\xFE\xBA\xBE"; |
| 1441 | try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", some_bytes); | 1454 | try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", some_bytes); |
| 1442 | try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", some_bytes); | 1455 | try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", some_bytes); |
| ... | @@ -1478,7 +1491,7 @@ pub fn trim(buf: []const u8) []const u8 { | ... | @@ -1478,7 +1491,7 @@ pub fn trim(buf: []const u8) []const u8 { |
| 1478 | return buf[start..end]; | 1491 | return buf[start..end]; |
| 1479 | } | 1492 | } |
| 1480 | | 1493 | |
| 1481 | test "fmt.trim" { | 1494 | test "trim" { |
| 1482 | testing.expect(mem.eql(u8, "abc", trim("\n abc \t"))); | 1495 | testing.expect(mem.eql(u8, "abc", trim("\n abc \t"))); |
| 1483 | testing.expect(mem.eql(u8, "", trim(" "))); | 1496 | testing.expect(mem.eql(u8, "", trim(" "))); |
| 1484 | testing.expect(mem.eql(u8, "", trim(""))); | 1497 | testing.expect(mem.eql(u8, "", trim(""))); |
| ... | @@ -1505,22 +1518,22 @@ pub fn hexToBytes(out: []u8, input: []const u8) !void { | ... | @@ -1505,22 +1518,22 @@ pub fn hexToBytes(out: []u8, input: []const u8) !void { |
| 1505 | } | 1518 | } |
| 1506 | } | 1519 | } |
| 1507 | | 1520 | |
| 1508 | test "fmt.hexToBytes" { | 1521 | test "hexToBytes" { |
| 1509 | const test_hex_str = "909A312BB12ED1F819B3521AC4C1E896F2160507FFC1C8381E3B07BB16BD1706"; | 1522 | const test_hex_str = "909A312BB12ED1F819B3521AC4C1E896F2160507FFC1C8381E3B07BB16BD1706"; |
| 1510 | var pb: [32]u8 = undefined; | 1523 | var pb: [32]u8 = undefined; |
| 1511 | try hexToBytes(pb[0..], test_hex_str); | 1524 | try hexToBytes(pb[0..], test_hex_str); |
| 1512 | try testFmt(test_hex_str, "{X}", pb); | 1525 | try testFmt(test_hex_str, "{X}", pb); |
| 1513 | } | 1526 | } |
| 1514 | | 1527 | |
| 1515 | test "fmt.formatIntValue with comptime_int" { | 1528 | test "formatIntValue with comptime_int" { |
| 1516 | const value: comptime_int = 123456789123456789; | 1529 | const value: comptime_int = 123456789123456789; |
| 1517 | | 1530 | |
| 1518 | var buf = try std.Buffer.init(std.debug.global_allocator, ""); | 1531 | var buf = try std.Buffer.init(std.debug.global_allocator, ""); |
| 1519 | try formatIntValue(value, "", &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append); | 1532 | try formatIntValue(value, "", FormatOptions{}, &buf, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append); |
| 1520 | assert(mem.eql(u8, buf.toSlice(), "123456789123456789")); | 1533 | assert(mem.eql(u8, buf.toSlice(), "123456789123456789")); |
| 1521 | } | 1534 | } |
| 1522 | | 1535 | |
| 1523 | test "fmt.formatType max_depth" { | 1536 | test "formatType max_depth" { |
| 1524 | const Vec2 = struct { | 1537 | const Vec2 = struct { |
| 1525 | const SelfType = @This(); | 1538 | const SelfType = @This(); |
| 1526 | x: f32, | 1539 | x: f32, |
| ... | @@ -1529,11 +1542,16 @@ test "fmt.formatType max_depth" { | ... | @@ -1529,11 +1542,16 @@ test "fmt.formatType max_depth" { |
| 1529 | pub fn format( | 1542 | pub fn format( |
| 1530 | self: SelfType, | 1543 | self: SelfType, |
| 1531 | comptime fmt: []const u8, | 1544 | comptime fmt: []const u8, |
| | 1545 | comptime options: FormatOptions, |
| 1532 | context: var, | 1546 | context: var, |
| 1533 | comptime Errors: type, | 1547 | comptime Errors: type, |
| 1534 | output: fn (@typeOf(context), []const u8) Errors!void, | 1548 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 1535 | ) Errors!void { | 1549 | ) Errors!void { |
| 1536 | return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y); | 1550 | if (fmt.len == 0) { |
| | 1551 | return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", self.x, self.y); |
| | 1552 | } else { |
| | 1553 | @compileError("Unknown format string: '" ++ fmt ++ "'"); |
| | 1554 | } |
| 1537 | } | 1555 | } |
| 1538 | }; | 1556 | }; |
| 1539 | const E = enum { | 1557 | const E = enum { |
| ... | @@ -1565,18 +1583,34 @@ test "fmt.formatType max_depth" { | ... | @@ -1565,18 +1583,34 @@ test "fmt.formatType max_depth" { |
| 1565 | inst.tu.ptr = &inst.tu; | 1583 | inst.tu.ptr = &inst.tu; |
| 1566 | | 1584 | |
| 1567 | var buf0 = try std.Buffer.init(std.debug.global_allocator, ""); | 1585 | var buf0 = try std.Buffer.init(std.debug.global_allocator, ""); |
| 1568 | try formatType(inst, "", &buf0, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 0); | 1586 | try formatType(inst, "", FormatOptions{}, &buf0, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 0); |
| 1569 | assert(mem.eql(u8, buf0.toSlice(), "S{ ... }")); | 1587 | assert(mem.eql(u8, buf0.toSlice(), "S{ ... }")); |
| 1570 | | 1588 | |
| 1571 | var buf1 = try std.Buffer.init(std.debug.global_allocator, ""); | 1589 | var buf1 = try std.Buffer.init(std.debug.global_allocator, ""); |
| 1572 | try formatType(inst, "", &buf1, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 1); | 1590 | try formatType(inst, "", FormatOptions{}, &buf1, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 1); |
| 1573 | assert(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }")); | 1591 | assert(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }")); |
| 1574 | | 1592 | |
| 1575 | var buf2 = try std.Buffer.init(std.debug.global_allocator, ""); | 1593 | var buf2 = try std.Buffer.init(std.debug.global_allocator, ""); |
| 1576 | try formatType(inst, "", &buf2, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 2); | 1594 | try formatType(inst, "", FormatOptions{}, &buf2, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 2); |
| 1577 | assert(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) }")); | 1595 | assert(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) }")); |
| 1578 | | 1596 | |
| 1579 | var buf3 = try std.Buffer.init(std.debug.global_allocator, ""); | 1597 | var buf3 = try std.Buffer.init(std.debug.global_allocator, ""); |
| 1580 | try formatType(inst, "", &buf3, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 3); | 1598 | try formatType(inst, "", FormatOptions{}, &buf3, @typeOf(std.Buffer.append).ReturnType.ErrorSet, std.Buffer.append, 3); |
| 1581 | assert(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) }")); | 1599 | assert(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) }")); |
| 1582 | } | 1600 | } |
| | 1601 | |
| | 1602 | test "positional" { |
| | 1603 | try testFmt("2 1 0", "{2} {1} {0}", usize(0), usize(1), usize(2)); |
| | 1604 | try testFmt("2 1 0", "{2} {1} {}", usize(0), usize(1), usize(2)); |
| | 1605 | try testFmt("0 0", "{0} {0}", usize(0)); |
| | 1606 | try testFmt("0 1", "{} {1}", usize(0), usize(1)); |
| | 1607 | try testFmt("1 0 0 1", "{1} {} {0} {}", usize(0), usize(1)); |
| | 1608 | } |
| | 1609 | |
| | 1610 | test "positional with specifier" { |
| | 1611 | try testFmt("10.0", "{0d:.1}", f64(9.999)); |
| | 1612 | } |
| | 1613 | |
| | 1614 | test "positional/alignment/width/precision" { |
| | 1615 | try testFmt("10.0", "{0d: >3.1}", f64(9.999)); |
| | 1616 | } |