| ... | ... | @@ -10,6 +10,42 @@ const lossyCast = std.math.lossyCast; |
| 10 | 10 | |
| 11 | 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 | 49 | /// Renders fmt string with args, calling output with slices of bytes. |
| 14 | 50 | /// If `output` returns an error, the error is returned from `format` and |
| 15 | 51 | /// `output` is not called again. |
| ... | ... | @@ -20,17 +56,30 @@ pub fn format( |
| 20 | 56 | comptime fmt: []const u8, |
| 21 | 57 | args: ..., |
| 22 | 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 | 64 | const State = enum { |
| 24 | 65 | Start, |
| 25 | | OpenBrace, |
| 66 | Positional, |
| 26 | 67 | CloseBrace, |
| 27 | | FormatString, |
| 68 | Specifier, |
| 69 | FormatFillAndAlign, |
| 70 | FormatWidth, |
| 71 | FormatPrecision, |
| 28 | 72 | Pointer, |
| 29 | 73 | }; |
| 30 | 74 | |
| 31 | 75 | comptime var start_index = 0; |
| 32 | 76 | comptime var state = State.Start; |
| 33 | 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 | 84 | inline for (fmt) |c, i| { |
| 36 | 85 | switch (state) { |
| ... | ... | @@ -39,58 +88,183 @@ pub fn format( |
| 39 | 88 | if (start_index < i) { |
| 40 | 89 | try output(context, fmt[start_index..i]); |
| 41 | 90 | } |
| 91 | |
| 42 | 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 | 100 | if (start_index < i) { |
| 48 | 101 | try output(context, fmt[start_index..i]); |
| 49 | 102 | } |
| 50 | | state = State.CloseBrace; |
| 103 | state = .CloseBrace; |
| 51 | 104 | }, |
| 52 | 105 | else => {}, |
| 53 | 106 | }, |
| 54 | | .OpenBrace => switch (c) { |
| 107 | .Positional => switch (c) { |
| 55 | 108 | '{' => { |
| 56 | | state = State.Start; |
| 109 | state = .Start; |
| 57 | 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); |
| 61 | | next_arg += 1; |
| 62 | | state = State.Start; |
| 133 | const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); |
| 134 | |
| 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 | 146 | start_index = i + 1; |
| 64 | 147 | }, |
| 65 | | '*' => state = State.Pointer, |
| 66 | 148 | else => { |
| 67 | | state = State.FormatString; |
| 149 | state = .Specifier; |
| 150 | specifier_start = i; |
| 68 | 151 | }, |
| 69 | 152 | }, |
| 70 | 153 | .CloseBrace => switch (c) { |
| 71 | 154 | '}' => { |
| 72 | | state = State.Start; |
| 155 | state = .Start; |
| 73 | 156 | start_index = i; |
| 74 | 157 | }, |
| 75 | 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; |
| 80 | | try formatType(args[next_arg], fmt[s..i], context, Errors, output, default_max_depth); |
| 81 | | next_arg += 1; |
| 82 | | state = State.Start; |
| 166 | const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); |
| 167 | |
| 168 | try formatType( |
| 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 | 178 | start_index = i + 1; |
| 84 | 179 | }, |
| 85 | 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 | 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 | 264 | try output(context, "@"); |
| 91 | | try formatInt(@ptrToInt(args[next_arg]), 16, false, 0, context, Errors, output); |
| 92 | | next_arg += 1; |
| 93 | | state = State.Start; |
| 265 | try formatInt(@ptrToInt(args[arg_to_print]), 16, false, 0, context, Errors, output); |
| 266 | |
| 267 | state = .Start; |
| 94 | 268 | start_index = i + 1; |
| 95 | 269 | }, |
| 96 | 270 | else => @compileError("Unexpected format character after '*'"), |
| ... | ... | @@ -98,7 +272,13 @@ pub fn format( |
| 98 | 272 | } |
| 99 | 273 | } |
| 100 | 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 | 282 | @compileError("Unused arguments"); |
| 103 | 283 | } |
| 104 | 284 | if (state != State.Start) { |
| ... | ... | @@ -113,6 +293,7 @@ pub fn format( |
| 113 | 293 | pub fn formatType( |
| 114 | 294 | value: var, |
| 115 | 295 | comptime fmt: []const u8, |
| 296 | comptime options: FormatOptions, |
| 116 | 297 | context: var, |
| 117 | 298 | comptime Errors: type, |
| 118 | 299 | output: fn (@typeOf(context), []const u8) Errors!void, |
| ... | ... | @@ -121,7 +302,7 @@ pub fn formatType( |
| 121 | 302 | const T = @typeOf(value); |
| 122 | 303 | switch (@typeInfo(T)) { |
| 123 | 304 | .ComptimeInt, .Int, .Float => { |
| 124 | | return formatValue(value, fmt, context, Errors, output); |
| 305 | return formatValue(value, fmt, options, context, Errors, output); |
| 125 | 306 | }, |
| 126 | 307 | .Void => { |
| 127 | 308 | return output(context, "void"); |
| ... | ... | @@ -131,16 +312,16 @@ pub fn formatType( |
| 131 | 312 | }, |
| 132 | 313 | .Optional => { |
| 133 | 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 | 316 | } else { |
| 136 | 317 | return output(context, "null"); |
| 137 | 318 | } |
| 138 | 319 | }, |
| 139 | 320 | .ErrorUnion => { |
| 140 | 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 | 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 | 327 | .ErrorSet => { |
| ... | ... | @@ -152,16 +333,16 @@ pub fn formatType( |
| 152 | 333 | }, |
| 153 | 334 | .Enum => { |
| 154 | 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 | 339 | try output(context, @typeName(T)); |
| 159 | 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 | 343 | .Union => { |
| 163 | 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 | 348 | try output(context, @typeName(T)); |
| ... | ... | @@ -175,7 +356,7 @@ pub fn formatType( |
| 175 | 356 | try output(context, " = "); |
| 176 | 357 | inline for (info.fields) |u_field| { |
| 177 | 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 | 362 | try output(context, " }"); |
| ... | ... | @@ -185,7 +366,7 @@ pub fn formatType( |
| 185 | 366 | }, |
| 186 | 367 | .Struct => { |
| 187 | 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 | 372 | try output(context, @typeName(T)); |
| ... | ... | @@ -201,7 +382,7 @@ pub fn formatType( |
| 201 | 382 | } |
| 202 | 383 | try output(context, @memberName(T, field_i)); |
| 203 | 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 | 387 | try output(context, " }"); |
| 207 | 388 | }, |
| ... | ... | @@ -209,12 +390,12 @@ pub fn formatType( |
| 209 | 390 | .One => switch (@typeInfo(ptr_info.child)) { |
| 210 | 391 | builtin.TypeId.Array => |info| { |
| 211 | 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 | 395 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); |
| 215 | 396 | }, |
| 216 | 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 | 400 | else => return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)), |
| 220 | 401 | }, |
| ... | ... | @@ -222,17 +403,17 @@ pub fn formatType( |
| 222 | 403 | if (ptr_info.child == u8) { |
| 223 | 404 | if (fmt.len > 0 and fmt[0] == 's') { |
| 224 | 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 | 409 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(value)); |
| 229 | 410 | }, |
| 230 | 411 | .Slice => { |
| 231 | 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 | 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 | 418 | return format(context, Errors, output, "{}@{x}", @typeName(ptr_info.child), @ptrToInt(value.ptr)); |
| 238 | 419 | }, |
| ... | ... | @@ -242,7 +423,7 @@ pub fn formatType( |
| 242 | 423 | }, |
| 243 | 424 | .Array => |info| { |
| 244 | 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 | 428 | return format(context, Errors, output, "{}@{x}", @typeName(T.Child), @ptrToInt(&value)); |
| 248 | 429 | }, |
| ... | ... | @@ -256,28 +437,25 @@ pub fn formatType( |
| 256 | 437 | fn formatValue( |
| 257 | 438 | value: var, |
| 258 | 439 | comptime fmt: []const u8, |
| 440 | comptime options: FormatOptions, |
| 259 | 441 | context: var, |
| 260 | 442 | comptime Errors: type, |
| 261 | 443 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 262 | 444 | ) Errors!void { |
| 263 | | if (fmt.len > 0 and fmt[0] == 'B') { |
| 264 | | comptime var width: ?usize = null; |
| 265 | | if (fmt.len > 1) { |
| 266 | | if (fmt[1] == 'i') { |
| 267 | | if (fmt.len > 2) { |
| 268 | | width = comptime (parseUnsigned(usize, fmt[2..], 10) catch unreachable); |
| 269 | | } |
| 270 | | return formatBytes(value, width, 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); |
| 445 | if (comptime std.mem.eql(u8, fmt, "B")) { |
| 446 | // TODO https://github.com/ziglang/zig/issues/2725 |
| 447 | if (options.width) |w| return formatBytes(value, w, 1000, context, Errors, output); |
| 448 | return formatBytes(value, null, 1000, context, Errors, output); |
| 449 | } else if (comptime std.mem.eql(u8, fmt, "Bi")) { |
| 450 | // TODO https://github.com/ziglang/zig/issues/2725 |
| 451 | if (options.width) |w| return formatBytes(value, w, 1024, context, Errors, output); |
| 452 | return formatBytes(value, null, 1024, context, Errors, output); |
| 275 | 453 | } |
| 276 | 454 | |
| 277 | 455 | const T = @typeOf(value); |
| 278 | 456 | switch (@typeId(T)) { |
| 279 | | .Float => return formatFloatValue(value, fmt, context, Errors, output), |
| 280 | | .Int, .ComptimeInt => return formatIntValue(value, fmt, context, Errors, output), |
| 457 | .Float => return formatFloatValue(value, fmt, options, context, Errors, output), |
| 458 | .Int, .ComptimeInt => return formatIntValue(value, fmt, options, context, Errors, output), |
| 281 | 459 | else => comptime unreachable, |
| 282 | 460 | } |
| 283 | 461 | } |
| ... | ... | @@ -285,13 +463,13 @@ fn formatValue( |
| 285 | 463 | pub fn formatIntValue( |
| 286 | 464 | value: var, |
| 287 | 465 | comptime fmt: []const u8, |
| 466 | comptime options: FormatOptions, |
| 288 | 467 | context: var, |
| 289 | 468 | comptime Errors: type, |
| 290 | 469 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 291 | 470 | ) Errors!void { |
| 292 | 471 | comptime var radix = 10; |
| 293 | 472 | comptime var uppercase = false; |
| 294 | | comptime var width = 0; |
| 295 | 473 | |
| 296 | 474 | const int_value = if (@typeOf(value) == comptime_int) blk: { |
| 297 | 475 | const Int = math.IntFittingRange(value, value); |
| ... | ... | @@ -299,83 +477,75 @@ pub fn formatIntValue( |
| 299 | 477 | } else |
| 300 | 478 | value; |
| 301 | 479 | |
| 302 | | if (fmt.len > 0) { |
| 303 | | switch (fmt[0]) { |
| 304 | | 'c' => { |
| 305 | | if (@typeOf(int_value).bit_count <= 8) { |
| 306 | | if (fmt.len > 1) |
| 307 | | @compileError("Unknown format character: " ++ [_]u8{fmt[1]}); |
| 308 | | return formatAsciiChar(u8(int_value), context, Errors, output); |
| 309 | | } |
| 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]}), |
| 480 | if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "d")) { |
| 481 | radix = 10; |
| 482 | uppercase = false; |
| 483 | } else if (comptime std.mem.eql(u8, fmt, "c")) { |
| 484 | if (@typeOf(int_value).bit_count <= 8) { |
| 485 | return formatAsciiChar(u8(int_value), context, Errors, output); |
| 486 | } else { |
| 487 | @compileError("Cannot print integer that is larger than 8 bits as a ascii"); |
| 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 | 507 | fn formatFloatValue( |
| 339 | 508 | value: var, |
| 340 | 509 | comptime fmt: []const u8, |
| 510 | comptime options: FormatOptions, |
| 341 | 511 | context: var, |
| 342 | 512 | comptime Errors: type, |
| 343 | 513 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 344 | 514 | ) Errors!void { |
| 345 | | comptime var width: ?usize = null; |
| 346 | | comptime var float_fmt = 'e'; |
| 347 | | if (fmt.len > 0) { |
| 348 | | float_fmt = fmt[0]; |
| 349 | | if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); |
| 350 | | } |
| 351 | | |
| 352 | | switch (float_fmt) { |
| 353 | | 'e' => try formatFloatScientific(value, width, context, Errors, output), |
| 354 | | '.' => try formatFloatDecimal(value, width, context, Errors, output), |
| 355 | | else => @compileError("Unknown format character: " ++ [_]u8{float_fmt}), |
| 515 | if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { |
| 516 | // TODO https://github.com/ziglang/zig/issues/2725 |
| 517 | if (options.precision) |p| return formatFloatScientific(value, p, context, Errors, output); |
| 518 | return formatFloatScientific(value, null, context, Errors, output); |
| 519 | } else if (comptime std.mem.eql(u8, fmt, "d")) { |
| 520 | // TODO https://github.com/ziglang/zig/issues/2725 |
| 521 | if (options.precision) |p| return formatFloatDecimal(value, p, context, Errors, output); |
| 522 | return formatFloatDecimal(value, null, context, Errors, output); |
| 523 | } else { |
| 524 | @compileError("Unknown format string: '" ++ fmt ++ "'"); |
| 356 | 525 | } |
| 357 | 526 | } |
| 358 | 527 | |
| 359 | 528 | pub fn formatText( |
| 360 | 529 | bytes: []const u8, |
| 361 | 530 | comptime fmt: []const u8, |
| 531 | comptime options: FormatOptions, |
| 362 | 532 | context: var, |
| 363 | 533 | comptime Errors: type, |
| 364 | 534 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 365 | 535 | ) Errors!void { |
| 366 | | if (fmt.len > 0) { |
| 367 | | if (fmt[0] == 's') { |
| 368 | | comptime var width = 0; |
| 369 | | if (fmt.len > 1) width = comptime (parseUnsigned(usize, fmt[1..], 10) catch unreachable); |
| 370 | | return formatBuf(bytes, width, context, Errors, output); |
| 371 | | } else if ((fmt[0] == 'x') or (fmt[0] == 'X')) { |
| 372 | | for (bytes) |c| { |
| 373 | | try formatInt(c, 16, fmt[0] == 'X', 2, context, Errors, output); |
| 374 | | } |
| 375 | | return; |
| 376 | | } else @compileError("Unknown format character: " ++ [_]u8{fmt[0]}); |
| 536 | if (fmt.len == 0) { |
| 537 | return output(context, bytes); |
| 538 | } else if (comptime std.mem.eql(u8, fmt, "s")) { |
| 539 | if (options.width) |w| return formatBuf(bytes, w, context, Errors, output); |
| 540 | return formatBuf(bytes, 0, context, Errors, output); |
| 541 | } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) { |
| 542 | for (bytes) |c| { |
| 543 | try formatInt(c, 16, fmt[0] == 'X', 2, context, Errors, output); |
| 544 | } |
| 545 | return; |
| 546 | } else { |
| 547 | @compileError("Unknown format string: '" ++ fmt ++ "'"); |
| 377 | 548 | } |
| 378 | | return output(context, bytes); |
| 379 | 549 | } |
| 380 | 550 | |
| 381 | 551 | pub fn formatAsciiChar( |
| ... | ... | @@ -868,7 +1038,7 @@ test "parseUnsigned" { |
| 868 | 1038 | |
| 869 | 1039 | pub const parseFloat = @import("fmt/parse_float.zig").parseFloat; |
| 870 | 1040 | |
| 871 | | test "fmt.parseFloat" { |
| 1041 | test "parseFloat" { |
| 872 | 1042 | _ = @import("fmt/parse_float.zig"); |
| 873 | 1043 | } |
| 874 | 1044 | |
| ... | ... | @@ -960,7 +1130,7 @@ test "parse unsigned comptime" { |
| 960 | 1130 | } |
| 961 | 1131 | } |
| 962 | 1132 | |
| 963 | | test "fmt.optional" { |
| 1133 | test "optional" { |
| 964 | 1134 | { |
| 965 | 1135 | const value: ?i32 = 1234; |
| 966 | 1136 | try testFmt("optional: 1234\n", "optional: {}\n", value); |
| ... | ... | @@ -971,7 +1141,7 @@ test "fmt.optional" { |
| 971 | 1141 | } |
| 972 | 1142 | } |
| 973 | 1143 | |
| 974 | | test "fmt.error" { |
| 1144 | test "error" { |
| 975 | 1145 | { |
| 976 | 1146 | const value: anyerror!i32 = 1234; |
| 977 | 1147 | try testFmt("error union: 1234\n", "error union: {}\n", value); |
| ... | ... | @@ -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 | 1157 | const value: u3 = 0b101; |
| 988 | 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 | 1164 | const value: u8 = 'a'; |
| 995 | 1165 | try testFmt("u8: a\n", "u8: {c}\n", value); |
| ... | ... | @@ -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 | 1179 | var buf1: [32]u8 = undefined; |
| 1006 | 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 | 1182 | var res = buf1[0 .. buf1.len - context.remaining.len]; |
| 1009 | 1183 | testing.expect(mem.eql(u8, res, "1234")); |
| 1010 | 1184 | |
| 1011 | 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 | 1187 | res = buf1[0 .. buf1.len - context.remaining.len]; |
| 1014 | 1188 | testing.expect(mem.eql(u8, res, "a")); |
| 1015 | 1189 | |
| 1016 | 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 | 1192 | res = buf1[0 .. buf1.len - context.remaining.len]; |
| 1019 | 1193 | testing.expect(mem.eql(u8, res, "1100")); |
| 1020 | 1194 | } |
| 1021 | 1195 | } |
| 1022 | 1196 | |
| 1023 | | test "fmt.array" { |
| 1197 | test "array" { |
| 1024 | 1198 | { |
| 1025 | 1199 | const value: [3]u8 = "abc"; |
| 1026 | 1200 | try testFmt("array: abc\n", "array: {}\n", value); |
| ... | ... | @@ -1035,7 +1209,7 @@ test "fmt.array" { |
| 1035 | 1209 | } |
| 1036 | 1210 | } |
| 1037 | 1211 | |
| 1038 | | test "fmt.slice" { |
| 1212 | test "slice" { |
| 1039 | 1213 | { |
| 1040 | 1214 | const value: []const u8 = "abc"; |
| 1041 | 1215 | try testFmt("slice: abc\n", "slice: {}\n", value); |
| ... | ... | @@ -1045,11 +1219,11 @@ test "fmt.slice" { |
| 1045 | 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 | 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 | 1228 | const value = @intToPtr(*i32, 0xdeadbeef); |
| 1055 | 1229 | try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", value); |
| ... | ... | @@ -1065,17 +1239,17 @@ test "fmt.pointer" { |
| 1065 | 1239 | } |
| 1066 | 1240 | } |
| 1067 | 1241 | |
| 1068 | | test "fmt.cstr" { |
| 1242 | test "cstr" { |
| 1069 | 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 | 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 | 1254 | const Struct = struct { |
| 1081 | 1255 | field: u8, |
| ... | ... | @@ -1094,7 +1268,7 @@ test "fmt.struct" { |
| 1094 | 1268 | } |
| 1095 | 1269 | } |
| 1096 | 1270 | |
| 1097 | | test "fmt.enum" { |
| 1271 | test "enum" { |
| 1098 | 1272 | const Enum = enum { |
| 1099 | 1273 | One, |
| 1100 | 1274 | Two, |
| ... | ... | @@ -1104,229 +1278,71 @@ test "fmt.enum" { |
| 1104 | 1278 | try testFmt("enum: Enum.Two\n", "enum: {}\n", &value); |
| 1105 | 1279 | } |
| 1106 | 1280 | |
| 1107 | | test "fmt.float.scientific" { |
| 1108 | | { |
| 1109 | | var buf1: [32]u8 = undefined; |
| 1110 | | const value: f32 = 1.34; |
| 1111 | | const result = try bufPrint(buf1[0..], "f32: {e}\n", value); |
| 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 | | } |
| 1281 | test "float.scientific" { |
| 1282 | try testFmt("f32: 1.34000003e+00", "f32: {e}", f32(1.34)); |
| 1283 | try testFmt("f32: 1.23400001e+01", "f32: {e}", f32(12.34)); |
| 1284 | try testFmt("f64: -1.234e+11", "f64: {e}", f64(-12.34e10)); |
| 1285 | try testFmt("f64: 9.99996e-40", "f64: {e}", f64(9.999960e-40)); |
| 1137 | 1286 | } |
| 1138 | 1287 | |
| 1139 | | test "fmt.float.scientific.precision" { |
| 1140 | | { |
| 1141 | | var buf1: [32]u8 = undefined; |
| 1142 | | const value: f64 = 1.409706e-42; |
| 1143 | | const result = try bufPrint(buf1[0..], "f64: {e5}\n", value); |
| 1144 | | testing.expect(mem.eql(u8, result, "f64: 1.40971e-42\n")); |
| 1145 | | } |
| 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 | | } |
| 1288 | test "float.scientific.precision" { |
| 1289 | try testFmt("f64: 1.40971e-42", "f64: {e:.5}", f64(1.409706e-42)); |
| 1290 | try testFmt("f64: 1.00000e-09", "f64: {e:.5}", f64(@bitCast(f32, u32(814313563)))); |
| 1291 | try testFmt("f64: 7.81250e-03", "f64: {e:.5}", f64(@bitCast(f32, u32(1006632960)))); |
| 1292 | // libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05. |
| 1293 | // In fact, libc doesn't round a lot of 5 cases up when one past the precision point. |
| 1294 | try testFmt("f64: 1.00001e+05", "f64: {e:.5}", f64(@bitCast(f32, u32(1203982400)))); |
| 1166 | 1295 | } |
| 1167 | 1296 | |
| 1168 | | test "fmt.float.special" { |
| 1169 | | { |
| 1170 | | var buf1: [32]u8 = undefined; |
| 1171 | | const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64); |
| 1172 | | testing.expect(mem.eql(u8, result, "f64: nan\n")); |
| 1173 | | } |
| 1297 | test "float.special" { |
| 1298 | try testFmt("f64: nan", "f64: {}", math.nan_f64); |
| 1299 | // negative nan is not defined by IEE 754, |
| 1300 | // and ARM thus normalizes it to positive nan |
| 1174 | 1301 | if (builtin.arch != builtin.Arch.arm) { |
| 1175 | | // negative nan is not defined by IEE 754, |
| 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")); |
| 1302 | try testFmt("f64: -nan", "f64: {}", -math.nan_f64); |
| 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" { |
| 1194 | | { |
| 1195 | | var buf1: [64]u8 = undefined; |
| 1196 | | const value: f64 = 1.52314e+29; |
| 1197 | | const result = try bufPrint(buf1[0..], "f64: {.}\n", value); |
| 1198 | | testing.expect(mem.eql(u8, result, "f64: 152314000000000000000000000000\n")); |
| 1199 | | } |
| 1200 | | { |
| 1201 | | var buf1: [32]u8 = undefined; |
| 1202 | | const value: f32 = 1.1234; |
| 1203 | | const result = try bufPrint(buf1[0..], "f32: {.1}\n", value); |
| 1204 | | testing.expect(mem.eql(u8, result, "f32: 1.1\n")); |
| 1205 | | } |
| 1206 | | { |
| 1207 | | var buf1: [32]u8 = undefined; |
| 1208 | | const value: f32 = 1234.567; |
| 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 | | } |
| 1308 | test "float.decimal" { |
| 1309 | try testFmt("f64: 152314000000000000000000000000", "f64: {d}", f64(1.52314e+29)); |
| 1310 | try testFmt("f32: 1.1", "f32: {d:.1}", f32(1.1234)); |
| 1311 | try testFmt("f32: 1234.57", "f32: {d:.2}", f32(1234.567)); |
| 1312 | // -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64). |
| 1313 | // -11.12339... is rounded back up to -11.1234 |
| 1314 | try testFmt("f32: -11.1234", "f32: {d:.4}", f32(-11.1234)); |
| 1315 | try testFmt("f32: 91.12345", "f32: {d:.5}", f32(91.12345)); |
| 1316 | try testFmt("f64: 91.1234567890", "f64: {d:.10}", f64(91.12345678901235)); |
| 1317 | try testFmt("f64: 0.00000", "f64: {d:.5}", f64(0.0)); |
| 1318 | try testFmt("f64: 6", "f64: {d:.0}", f64(5.700)); |
| 1319 | try testFmt("f64: 10.0", "f64: {d:.1}", f64(9.999)); |
| 1320 | try testFmt("f64: 1.000", "f64: {d:.3}", f64(1.0)); |
| 1321 | try testFmt("f64: 0.00030000", "f64: {d:.8}", f64(0.0003)); |
| 1322 | try testFmt("f64: 0.00000", "f64: {d:.5}", f64(1.40130e-45)); |
| 1323 | try testFmt("f64: 0.00000", "f64: {d:.5}", f64(9.999960e-40)); |
| 1274 | 1324 | } |
| 1275 | 1325 | |
| 1276 | | test "fmt.float.libc.sanity" { |
| 1277 | | { |
| 1278 | | var buf1: [32]u8 = undefined; |
| 1279 | | const value: f64 = f64(@bitCast(f32, u32(916964781))); |
| 1280 | | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); |
| 1281 | | testing.expect(mem.eql(u8, result, "f64: 0.00001\n")); |
| 1282 | | } |
| 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 | | } |
| 1326 | test "float.libc.sanity" { |
| 1327 | try testFmt("f64: 0.00001", "f64: {d:.5}", f64(@bitCast(f32, u32(916964781)))); |
| 1328 | try testFmt("f64: 0.00001", "f64: {d:.5}", f64(@bitCast(f32, u32(925353389)))); |
| 1329 | try testFmt("f64: 0.10000", "f64: {d:.5}", f64(@bitCast(f32, u32(1036831278)))); |
| 1330 | try testFmt("f64: 1.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1065353133)))); |
| 1331 | try testFmt("f64: 10.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1092616192)))); |
| 1332 | |
| 1307 | 1333 | // libc differences |
| 1308 | | { |
| 1309 | | var buf1: [32]u8 = undefined; |
| 1310 | | // This is 0.015625 exactly according to gdb. We thus round down, |
| 1311 | | // however glibc rounds up for some reason. This occurs for all |
| 1312 | | // floats of the form x.yyyy25 on a precision point. |
| 1313 | | const value: f64 = f64(@bitCast(f32, u32(1015021568))); |
| 1314 | | const result = try bufPrint(buf1[0..], "f64: {.5}\n", value); |
| 1315 | | testing.expect(mem.eql(u8, result, "f64: 0.01563\n")); |
| 1316 | | } |
| 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 | | } |
| 1334 | // |
| 1335 | // This is 0.015625 exactly according to gdb. We thus round down, |
| 1336 | // however glibc rounds up for some reason. This occurs for all |
| 1337 | // floats of the form x.yyyy25 on a precision point. |
| 1338 | try testFmt("f64: 0.01563", "f64: {d:.5}", f64(@bitCast(f32, u32(1015021568)))); |
| 1339 | // errol3 rounds to ... 630 but libc rounds to ...632. Grisu3 |
| 1340 | // also rounds to 630 so I'm inclined to believe libc is not |
| 1341 | // optimal here. |
| 1342 | try testFmt("f64: 18014400656965630.00000", "f64: {d:.5}", f64(@bitCast(f32, u32(1518338049)))); |
| 1327 | 1343 | } |
| 1328 | 1344 | |
| 1329 | | test "fmt.custom" { |
| 1345 | test "custom" { |
| 1330 | 1346 | const Vec2 = struct { |
| 1331 | 1347 | const SelfType = @This(); |
| 1332 | 1348 | x: f32, |
| ... | ... | @@ -1335,20 +1351,17 @@ test "fmt.custom" { |
| 1335 | 1351 | pub fn format( |
| 1336 | 1352 | self: SelfType, |
| 1337 | 1353 | comptime fmt: []const u8, |
| 1354 | comptime options: FormatOptions, |
| 1338 | 1355 | context: var, |
| 1339 | 1356 | comptime Errors: type, |
| 1340 | 1357 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 1341 | 1358 | ) Errors!void { |
| 1342 | | switch (fmt.len) { |
| 1343 | | 0 => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y), |
| 1344 | | 1 => switch (fmt[0]) { |
| 1345 | | //point format |
| 1346 | | 'p' => return std.fmt.format(context, Errors, output, "({.3},{.3})", self.x, self.y), |
| 1347 | | //dimension format |
| 1348 | | 'd' => return std.fmt.format(context, Errors, output, "{.3}x{.3}", self.x, self.y), |
| 1349 | | else => unreachable, |
| 1350 | | }, |
| 1351 | | else => unreachable, |
| 1359 | if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { |
| 1360 | return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", self.x, self.y); |
| 1361 | } else if (comptime std.mem.eql(u8, fmt, "d")) { |
| 1362 | return std.fmt.format(context, Errors, output, "{d:.3}x{d:.3}", self.x, self.y); |
| 1363 | } else { |
| 1364 | @compileError("Unknown format character: '" ++ fmt ++ "'"); |
| 1352 | 1365 | } |
| 1353 | 1366 | } |
| 1354 | 1367 | }; |
| ... | ... | @@ -1366,7 +1379,7 @@ test "fmt.custom" { |
| 1366 | 1379 | try testFmt("dim: 10.200x2.220\n", "dim: {d}\n", value); |
| 1367 | 1380 | } |
| 1368 | 1381 | |
| 1369 | | test "fmt.struct" { |
| 1382 | test "struct" { |
| 1370 | 1383 | const S = struct { |
| 1371 | 1384 | a: u32, |
| 1372 | 1385 | b: anyerror, |
| ... | ... | @@ -1380,7 +1393,7 @@ test "fmt.struct" { |
| 1380 | 1393 | try testFmt("S{ .a = 456, .b = error.Unused }", "{}", inst); |
| 1381 | 1394 | } |
| 1382 | 1395 | |
| 1383 | | test "fmt.union" { |
| 1396 | test "union" { |
| 1384 | 1397 | const TU = union(enum) { |
| 1385 | 1398 | float: f32, |
| 1386 | 1399 | int: u32, |
| ... | ... | @@ -1410,7 +1423,7 @@ test "fmt.union" { |
| 1410 | 1423 | testing.expect(mem.eql(u8, uu_result[0..3], "EU@")); |
| 1411 | 1424 | } |
| 1412 | 1425 | |
| 1413 | | test "fmt.enum" { |
| 1426 | test "enum" { |
| 1414 | 1427 | const E = enum { |
| 1415 | 1428 | One, |
| 1416 | 1429 | Two, |
| ... | ... | @@ -1422,7 +1435,7 @@ test "fmt.enum" { |
| 1422 | 1435 | try testFmt("E.Two", "{}", inst); |
| 1423 | 1436 | } |
| 1424 | 1437 | |
| 1425 | | test "fmt.struct.self-referential" { |
| 1438 | test "struct.self-referential" { |
| 1426 | 1439 | const S = struct { |
| 1427 | 1440 | const SelfType = @This(); |
| 1428 | 1441 | a: ?*SelfType, |
| ... | ... | @@ -1436,7 +1449,7 @@ test "fmt.struct.self-referential" { |
| 1436 | 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 | 1453 | const some_bytes = "\xCA\xFE\xBA\xBE"; |
| 1441 | 1454 | try testFmt("lowercase: cafebabe\n", "lowercase: {x}\n", some_bytes); |
| 1442 | 1455 | try testFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", some_bytes); |
| ... | ... | @@ -1478,7 +1491,7 @@ pub fn trim(buf: []const u8) []const u8 { |
| 1478 | 1491 | return buf[start..end]; |
| 1479 | 1492 | } |
| 1480 | 1493 | |
| 1481 | | test "fmt.trim" { |
| 1494 | test "trim" { |
| 1482 | 1495 | testing.expect(mem.eql(u8, "abc", trim("\n abc \t"))); |
| 1483 | 1496 | testing.expect(mem.eql(u8, "", trim(" "))); |
| 1484 | 1497 | testing.expect(mem.eql(u8, "", trim(""))); |
| ... | ... | @@ -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 | 1522 | const test_hex_str = "909A312BB12ED1F819B3521AC4C1E896F2160507FFC1C8381E3B07BB16BD1706"; |
| 1510 | 1523 | var pb: [32]u8 = undefined; |
| 1511 | 1524 | try hexToBytes(pb[0..], test_hex_str); |
| 1512 | 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 | 1529 | const value: comptime_int = 123456789123456789; |
| 1517 | 1530 | |
| 1518 | 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 | 1533 | assert(mem.eql(u8, buf.toSlice(), "123456789123456789")); |
| 1521 | 1534 | } |
| 1522 | 1535 | |
| 1523 | | test "fmt.formatType max_depth" { |
| 1536 | test "formatType max_depth" { |
| 1524 | 1537 | const Vec2 = struct { |
| 1525 | 1538 | const SelfType = @This(); |
| 1526 | 1539 | x: f32, |
| ... | ... | @@ -1529,11 +1542,16 @@ test "fmt.formatType max_depth" { |
| 1529 | 1542 | pub fn format( |
| 1530 | 1543 | self: SelfType, |
| 1531 | 1544 | comptime fmt: []const u8, |
| 1545 | comptime options: FormatOptions, |
| 1532 | 1546 | context: var, |
| 1533 | 1547 | comptime Errors: type, |
| 1534 | 1548 | output: fn (@typeOf(context), []const u8) Errors!void, |
| 1535 | 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 | 1557 | const E = enum { |
| ... | ... | @@ -1565,18 +1583,34 @@ test "fmt.formatType max_depth" { |
| 1565 | 1583 | inst.tu.ptr = &inst.tu; |
| 1566 | 1584 | |
| 1567 | 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 | 1587 | assert(mem.eql(u8, buf0.toSlice(), "S{ ... }")); |
| 1570 | 1588 | |
| 1571 | 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 | 1591 | assert(mem.eql(u8, buf1.toSlice(), "S{ .a = S{ ... }, .tu = TU{ ... }, .e = E.Two, .vec = (10.200,2.220) }")); |
| 1574 | 1592 | |
| 1575 | 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 | 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 | 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 | 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 | } |