1 // Written in the D programming language. 2 3 /* 4 Copyright: Copyright The D Language Foundation 2000-2013. 5 6 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 7 8 Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, 9 Andrei Alexandrescu), and Kenji Hara 10 11 Source: $(PHOBOSSRC std/format/internal/write.d) 12 */ 13 module std.format.internal.write; 14 15 import std.format.spec : FormatSpec; 16 import std.range.primitives : isInputRange; 17 import std.traits; 18 19 version (StdUnittest) 20 { 21 import std.exception : assertCTFEable; 22 import std.format : format; 23 } 24 25 package(std.format): 26 27 /* 28 `bool`s are formatted as `"true"` or `"false"` with `%s` and as `1` or 29 `0` with integral-specific format specs. 30 */ 31 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f) 32 if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 33 { 34 BooleanTypeOf!T val = obj; 35 36 if (f.spec == 's') 37 writeAligned(w, val ? "true" : "false", f); 38 else 39 formatValueImpl(w, cast(byte) val, f); 40 } 41 42 @safe pure unittest 43 { 44 assertCTFEable!( 45 { 46 formatTest(false, "false"); 47 formatTest(true, "true"); 48 }); 49 } 50 51 @safe unittest 52 { 53 struct S1 54 { 55 bool val; 56 alias val this; 57 } 58 59 struct S2 60 { 61 bool val; 62 alias val this; 63 string toString() const { return "S"; } 64 } 65 66 formatTest(S1(false), "false"); 67 formatTest(S1(true), "true"); 68 formatTest(S2(false), "S"); 69 formatTest(S2(true), "S"); 70 } 71 72 @safe pure unittest 73 { 74 string t1 = format("[%6s] [%6s] [%-6s]", true, false, true); 75 assert(t1 == "[ true] [ false] [true ]"); 76 77 string t2 = format("[%3s] [%-2s]", true, false); 78 assert(t2 == "[true] [false]"); 79 } 80 81 // https://issues.dlang.org/show_bug.cgi?id=20534 82 @safe pure unittest 83 { 84 assert(format("%r",false) == "\0"); 85 } 86 87 @safe pure unittest 88 { 89 assert(format("%07s",true) == " true"); 90 } 91 92 @safe pure unittest 93 { 94 assert(format("%=8s",true) == " true "); 95 assert(format("%=9s",false) == " false "); 96 assert(format("%=9s",true) == " true "); 97 assert(format("%-=9s",true) == " true "); 98 assert(format("%=10s",false) == " false "); 99 assert(format("%-=10s",false) == " false "); 100 } 101 102 /* 103 `null` literal is formatted as `"null"` 104 */ 105 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f) 106 if (is(immutable T == immutable typeof(null)) && !is(T == enum) && !hasToString!(T, Char)) 107 { 108 import std.format : enforceFmt; 109 110 const spec = f.spec; 111 enforceFmt(spec == 's', "null literal cannot match %" ~ spec); 112 113 writeAligned(w, "null", f); 114 } 115 116 @safe pure unittest 117 { 118 import std.exception : collectExceptionMsg; 119 import std.format : FormatException; 120 import std.range.primitives : back; 121 122 assert(collectExceptionMsg!FormatException(format("%p", null)).back == 'p'); 123 124 assertCTFEable!( 125 { 126 formatTest(null, "null"); 127 }); 128 } 129 130 @safe pure unittest 131 { 132 string t = format("[%6s] [%-6s]", null, null); 133 assert(t == "[ null] [null ]"); 134 } 135 136 /* 137 Integrals are formatted like $(REF printf, core, stdc, stdio). 138 */ 139 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f) 140 if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 141 { 142 alias U = IntegralTypeOf!T; 143 U val = obj; // Extracting alias this may be impure/system/may-throw 144 145 if (f.spec == 'r') 146 { 147 // raw write, skip all else and write the thing 148 auto raw = (ref val) @trusted { 149 return (cast(const char*) &val)[0 .. val.sizeof]; 150 }(val); 151 import std.range.primitives : put; 152 if (needToSwapEndianess(f)) 153 foreach_reverse (c; raw) 154 put(w, c); 155 else 156 foreach (c; raw) 157 put(w, c); 158 return; 159 } 160 161 static if (isSigned!U) 162 { 163 const negative = val < 0 && f.spec != 'x' && f.spec != 'X' && f.spec != 'b' && f.spec != 'o' && f.spec != 'u'; 164 ulong arg = negative ? -cast(ulong) val : val; 165 } 166 else 167 { 168 const negative = false; 169 ulong arg = val; 170 } 171 arg &= Unsigned!U.max; 172 173 formatValueImplUlong!(Writer, Char)(w, arg, negative, f); 174 } 175 176 // Helper function for `formatValueImpl` that avoids template bloat 177 private void formatValueImplUlong(Writer, Char)(auto ref Writer w, ulong arg, in bool negative, 178 scope const ref FormatSpec!Char f) 179 { 180 immutable uint base = baseOfSpec(f.spec); 181 182 const bool zero = arg == 0; 183 char[64] digits = void; 184 size_t pos = digits.length - 1; 185 do 186 { 187 /* `cast(char)` is needed because value range propagation (VRP) cannot 188 * analyze `base` because it’s computed in a separate function 189 * (`baseOfSpec`). */ 190 digits[pos--] = cast(char) ('0' + arg % base); 191 if (base > 10 && digits[pos + 1] > '9') 192 digits[pos + 1] += ((f.spec == 'x' || f.spec == 'a') ? 'a' : 'A') - '0' - 10; 193 arg /= base; 194 } while (arg > 0); 195 196 char[3] prefix = void; 197 size_t left = 2; 198 size_t right = 2; 199 200 // add sign 201 if (f.spec != 'x' && f.spec != 'X' && f.spec != 'b' && f.spec != 'o' && f.spec != 'u') 202 { 203 if (negative) 204 prefix[right++] = '-'; 205 else if (f.flPlus) 206 prefix[right++] = '+'; 207 else if (f.flSpace) 208 prefix[right++] = ' '; 209 } 210 211 // not a floating point like spec 212 if (f.spec == 'x' || f.spec == 'X' || f.spec == 'b' || f.spec == 'o' || f.spec == 'u' 213 || f.spec == 'd' || f.spec == 's') 214 { 215 if (f.flHash && (base == 16) && !zero) 216 { 217 prefix[--left] = f.spec; 218 prefix[--left] = '0'; 219 } 220 if (f.flHash && (base == 8) && !zero 221 && (digits.length - (pos + 1) >= f.precision || f.precision == f.UNSPECIFIED)) 222 prefix[--left] = '0'; 223 224 writeAligned(w, prefix[left .. right], digits[pos + 1 .. $], "", f, true); 225 return; 226 } 227 228 FormatSpec!Char fs = f; 229 if (f.precision == f.UNSPECIFIED) 230 fs.precision = cast(typeof(fs.precision)) (digits.length - pos - 2); 231 232 // %f like output 233 if (f.spec == 'f' || f.spec == 'F' 234 || ((f.spec == 'g' || f.spec == 'G') && (fs.precision >= digits.length - pos - 2))) 235 { 236 if (f.precision == f.UNSPECIFIED) 237 fs.precision = 0; 238 239 writeAligned(w, prefix[left .. right], digits[pos + 1 .. $], ".", "", fs, 240 (f.spec == 'g' || f.spec == 'G') ? PrecisionType.allDigits : PrecisionType.fractionalDigits); 241 242 return; 243 } 244 245 import std.algorithm.searching : all; 246 247 // at least one digit for %g 248 if ((f.spec == 'g' || f.spec == 'G') && fs.precision == 0) 249 fs.precision = 1; 250 251 // rounding 252 size_t digit_end = pos + fs.precision + ((f.spec == 'g' || f.spec == 'G') ? 1 : 2); 253 if (digit_end <= digits.length) 254 { 255 RoundingClass rt = RoundingClass.ZERO; 256 if (digit_end < digits.length) 257 { 258 auto tie = (f.spec == 'a' || f.spec == 'A') ? '8' : '5'; 259 if (digits[digit_end] >= tie) 260 { 261 rt = RoundingClass.UPPER; 262 if (digits[digit_end] == tie && digits[digit_end + 1 .. $].all!(a => a == '0')) 263 rt = RoundingClass.FIVE; 264 } 265 else 266 { 267 rt = RoundingClass.LOWER; 268 if (digits[digit_end .. $].all!(a => a == '0')) 269 rt = RoundingClass.ZERO; 270 } 271 } 272 273 if (round(digits, pos + 1, digit_end, rt, negative, 274 f.spec == 'a' ? 'f' : (f.spec == 'A' ? 'F' : '9'))) 275 { 276 pos--; 277 digit_end--; 278 } 279 } 280 281 // convert to scientific notation 282 char[1] int_digit = void; 283 int_digit[0] = digits[pos + 1]; 284 digits[pos + 1] = '.'; 285 286 char[4] suffix = void; 287 288 if (f.spec == 'e' || f.spec == 'E' || f.spec == 'g' || f.spec == 'G') 289 { 290 suffix[0] = (f.spec == 'e' || f.spec == 'g') ? 'e' : 'E'; 291 suffix[1] = '+'; 292 suffix[2] = cast(char) ('0' + (digits.length - pos - 2) / 10); 293 suffix[3] = cast(char) ('0' + (digits.length - pos - 2) % 10); 294 } 295 else 296 { 297 if (right == 3) 298 prefix[0] = prefix[2]; 299 prefix[1] = '0'; 300 prefix[2] = f.spec == 'a' ? 'x' : 'X'; 301 302 left = right == 3 ? 0 : 1; 303 right = 3; 304 305 suffix[0] = f.spec == 'a' ? 'p' : 'P'; 306 suffix[1] = '+'; 307 suffix[2] = cast(char) ('0' + ((digits.length - pos - 2) * 4) / 10); 308 suffix[3] = cast(char) ('0' + ((digits.length - pos - 2) * 4) % 10); 309 } 310 311 import std.algorithm.comparison : min; 312 313 // remove trailing zeros 314 if ((f.spec == 'g' || f.spec == 'G') && !f.flHash) 315 { 316 digit_end = min(digit_end, digits.length); 317 while (digit_end > pos + 1 && 318 (digits[digit_end - 1] == '0' || digits[digit_end - 1] == '.')) 319 digit_end--; 320 } 321 322 writeAligned(w, prefix[left .. right], int_digit[0 .. $], 323 digits[pos + 1 .. min(digit_end, $)], 324 suffix[0 .. $], fs, 325 (f.spec == 'g' || f.spec == 'G') ? PrecisionType.allDigits : PrecisionType.fractionalDigits); 326 } 327 328 private uint baseOfSpec(in char spec) @safe pure 329 { 330 typeof(return) base = 331 spec == 'x' || spec == 'X' || spec == 'a' || spec == 'A' ? 16 : 332 spec == 'o' ? 8 : 333 spec == 'b' ? 2 : 334 spec == 's' || spec == 'd' || spec == 'u' 335 || spec == 'e' || spec == 'E' || spec == 'f' || spec == 'F' 336 || spec == 'g' || spec == 'G' ? 10 : 337 0; 338 339 import std.format : enforceFmt; 340 enforceFmt(base > 0, 341 "incompatible format character for integral argument: %" ~ spec); 342 343 return base; 344 } 345 346 @safe pure unittest 347 { 348 assertCTFEable!( 349 { 350 formatTest(byte.min, "-128"); 351 formatTest(byte.max, "127"); 352 formatTest(short.min, "-32768"); 353 formatTest(short.max, "32767"); 354 formatTest(int.min, "-2147483648"); 355 formatTest(int.max, "2147483647"); 356 formatTest(long.min, "-9223372036854775808"); 357 formatTest(long.max, "9223372036854775807"); 358 359 formatTest(ubyte.min, "0"); 360 formatTest(ubyte.max, "255"); 361 formatTest(ushort.min, "0"); 362 formatTest(ushort.max, "65535"); 363 formatTest(uint.min, "0"); 364 formatTest(uint.max, "4294967295"); 365 formatTest(ulong.min, "0"); 366 formatTest(ulong.max, "18446744073709551615"); 367 }); 368 } 369 370 // https://issues.dlang.org/show_bug.cgi?id=18838 371 @safe pure unittest 372 { 373 assert("%12,d".format(0) == " 0"); 374 } 375 376 @safe pure unittest 377 { 378 import std.exception : collectExceptionMsg; 379 import std.format : FormatException; 380 import std.range.primitives : back; 381 382 assert(collectExceptionMsg!FormatException(format("%c", 5)).back == 'c'); 383 384 assertCTFEable!( 385 { 386 formatTest(9, "9"); 387 formatTest(10, "10"); 388 }); 389 } 390 391 @safe unittest 392 { 393 struct S1 394 { 395 long val; 396 alias val this; 397 } 398 399 struct S2 400 { 401 long val; 402 alias val this; 403 string toString() const { return "S"; } 404 } 405 406 formatTest(S1(10), "10"); 407 formatTest(S2(10), "S"); 408 } 409 410 // https://issues.dlang.org/show_bug.cgi?id=20064 411 @safe unittest 412 { 413 assert(format( "%03,d", 1234) == "1,234"); 414 assert(format( "%04,d", 1234) == "1,234"); 415 assert(format( "%05,d", 1234) == "1,234"); 416 assert(format( "%06,d", 1234) == "01,234"); 417 assert(format( "%07,d", 1234) == "001,234"); 418 assert(format( "%08,d", 1234) == "0,001,234"); 419 assert(format( "%09,d", 1234) == "0,001,234"); 420 assert(format("%010,d", 1234) == "00,001,234"); 421 assert(format("%011,d", 1234) == "000,001,234"); 422 assert(format("%012,d", 1234) == "0,000,001,234"); 423 assert(format("%013,d", 1234) == "0,000,001,234"); 424 assert(format("%014,d", 1234) == "00,000,001,234"); 425 assert(format("%015,d", 1234) == "000,000,001,234"); 426 assert(format("%016,d", 1234) == "0,000,000,001,234"); 427 assert(format("%017,d", 1234) == "0,000,000,001,234"); 428 429 assert(format( "%03,d", -1234) == "-1,234"); 430 assert(format( "%04,d", -1234) == "-1,234"); 431 assert(format( "%05,d", -1234) == "-1,234"); 432 assert(format( "%06,d", -1234) == "-1,234"); 433 assert(format( "%07,d", -1234) == "-01,234"); 434 assert(format( "%08,d", -1234) == "-001,234"); 435 assert(format( "%09,d", -1234) == "-0,001,234"); 436 assert(format("%010,d", -1234) == "-0,001,234"); 437 assert(format("%011,d", -1234) == "-00,001,234"); 438 assert(format("%012,d", -1234) == "-000,001,234"); 439 assert(format("%013,d", -1234) == "-0,000,001,234"); 440 assert(format("%014,d", -1234) == "-0,000,001,234"); 441 assert(format("%015,d", -1234) == "-00,000,001,234"); 442 assert(format("%016,d", -1234) == "-000,000,001,234"); 443 assert(format("%017,d", -1234) == "-0,000,000,001,234"); 444 } 445 446 @safe pure unittest 447 { 448 string t1 = format("[%6s] [%-6s]", 123, 123); 449 assert(t1 == "[ 123] [123 ]"); 450 451 string t2 = format("[%6s] [%-6s]", -123, -123); 452 assert(t2 == "[ -123] [-123 ]"); 453 } 454 455 @safe pure unittest 456 { 457 formatTest(byte.min, "-128"); 458 formatTest(short.min, "-32768"); 459 formatTest(int.min, "-2147483648"); 460 formatTest(long.min, "-9223372036854775808"); 461 } 462 463 // https://issues.dlang.org/show_bug.cgi?id=21777 464 @safe pure unittest 465 { 466 assert(format!"%20.5,d"(cast(short) 120) == " 00,120"); 467 assert(format!"%20.5,o"(cast(short) 120) == " 00,170"); 468 assert(format!"%20.5,x"(cast(short) 120) == " 00,078"); 469 assert(format!"%20.5,2d"(cast(short) 120) == " 0,01,20"); 470 assert(format!"%20.5,2o"(cast(short) 120) == " 0,01,70"); 471 assert(format!"%20.5,4d"(cast(short) 120) == " 0,0120"); 472 assert(format!"%20.5,4o"(cast(short) 120) == " 0,0170"); 473 assert(format!"%20.5,4x"(cast(short) 120) == " 0,0078"); 474 assert(format!"%20.5,2x"(3000) == " 0,0b,b8"); 475 assert(format!"%20.5,4d"(3000) == " 0,3000"); 476 assert(format!"%20.5,4o"(3000) == " 0,5670"); 477 assert(format!"%20.5,4x"(3000) == " 0,0bb8"); 478 assert(format!"%20.5,d"(-400) == " -00,400"); 479 assert(format!"%20.30d"(-400) == "-000000000000000000000000000400"); 480 assert(format!"%20.5,4d"(0) == " 0,0000"); 481 assert(format!"%0#.8,2s"(12345) == "00,01,23,45"); 482 assert(format!"%0#.9,3x"(55) == "0x000,000,037"); 483 } 484 485 // https://issues.dlang.org/show_bug.cgi?id=21814 486 @safe pure unittest 487 { 488 assert(format("%,0d",1000) == "1000"); 489 } 490 491 // https://issues.dlang.org/show_bug.cgi?id=21817 492 @safe pure unittest 493 { 494 assert(format!"%u"(-5) == "4294967291"); 495 } 496 497 // https://issues.dlang.org/show_bug.cgi?id=21820 498 @safe pure unittest 499 { 500 assert(format!"%#.0o"(0) == "0"); 501 } 502 503 @safe pure unittest 504 { 505 assert(format!"%e"(10000) == "1.0000e+04"); 506 assert(format!"%.2e"(10000) == "1.00e+04"); 507 assert(format!"%.10e"(10000) == "1.0000000000e+04"); 508 509 assert(format!"%e"(9999) == "9.999e+03"); 510 assert(format!"%.2e"(9999) == "1.00e+04"); 511 assert(format!"%.10e"(9999) == "9.9990000000e+03"); 512 513 assert(format!"%f"(10000) == "10000"); 514 assert(format!"%.2f"(10000) == "10000.00"); 515 516 assert(format!"%g"(10000) == "10000"); 517 assert(format!"%.2g"(10000) == "1e+04"); 518 assert(format!"%.10g"(10000) == "10000"); 519 520 assert(format!"%#g"(10000) == "10000."); 521 assert(format!"%#.2g"(10000) == "1.0e+04"); 522 assert(format!"%#.10g"(10000) == "10000.00000"); 523 524 assert(format!"%g"(9999) == "9999"); 525 assert(format!"%.2g"(9999) == "1e+04"); 526 assert(format!"%.10g"(9999) == "9999"); 527 528 assert(format!"%a"(0x10000) == "0x1.0000p+16"); 529 assert(format!"%.2a"(0x10000) == "0x1.00p+16"); 530 assert(format!"%.10a"(0x10000) == "0x1.0000000000p+16"); 531 532 assert(format!"%a"(0xffff) == "0xf.fffp+12"); 533 assert(format!"%.2a"(0xffff) == "0x1.00p+16"); 534 assert(format!"%.10a"(0xffff) == "0xf.fff0000000p+12"); 535 } 536 537 @safe pure unittest 538 { 539 assert(format!"%.3e"(ulong.max) == "1.845e+19"); 540 assert(format!"%.3f"(ulong.max) == "18446744073709551615.000"); 541 assert(format!"%.3g"(ulong.max) == "1.84e+19"); 542 assert(format!"%.3a"(ulong.max) == "0x1.000p+64"); 543 544 assert(format!"%.3e"(long.min) == "-9.223e+18"); 545 assert(format!"%.3f"(long.min) == "-9223372036854775808.000"); 546 assert(format!"%.3g"(long.min) == "-9.22e+18"); 547 assert(format!"%.3a"(long.min) == "-0x8.000p+60"); 548 549 assert(format!"%e"(0) == "0e+00"); 550 assert(format!"%f"(0) == "0"); 551 assert(format!"%g"(0) == "0"); 552 assert(format!"%a"(0) == "0x0p+00"); 553 } 554 555 @safe pure unittest 556 { 557 assert(format!"%.0g"(1500) == "2e+03"); 558 } 559 560 // https://issues.dlang.org/show_bug.cgi?id=21900# 561 @safe pure unittest 562 { 563 assert(format!"%.1a"(472) == "0x1.ep+08"); 564 } 565 566 /* 567 Floating-point values are formatted like $(REF printf, core, stdc, stdio) 568 */ 569 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, 570 scope const ref FormatSpec!Char f) 571 if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 572 { 573 import std.format : enforceFmt; 574 import std.range.primitives : put; 575 import std.format.internal.floats : printFloat, isFloatSpec; 576 577 FloatingPointTypeOf!T val = obj; 578 const char spec = f.spec; 579 580 if (spec == 'r') 581 { 582 // raw write, skip all else and write the thing 583 auto raw = (ref val) @trusted { 584 return (cast(const char*) &val)[0 .. val.sizeof]; 585 }(val); 586 587 if (needToSwapEndianess(f)) 588 { 589 foreach_reverse (c; raw) 590 put(w, c); 591 } 592 else 593 { 594 foreach (c; raw) 595 put(w, c); 596 } 597 return; 598 } 599 600 FormatSpec!Char fs = f; // fs is copy for change its values. 601 fs.spec = spec == 's' ? 'g' : spec; 602 enforceFmt(isFloatSpec(fs.spec), "incompatible format character for floating point argument: %" ~ spec); 603 604 static if (is(T == float) || is(T == double) 605 || (is(T == real) && (T.mant_dig == double.mant_dig || T.mant_dig == 64))) 606 { 607 alias tval = val; 608 } 609 else 610 { 611 import std.math.traits : isInfinity; 612 import std.math.operations : nextUp; 613 614 // reals that are not supported by printFloat are cast to double. 615 double tval = val; 616 617 // Numbers greater than double.max are converted to double.max: 618 if (val > double.max && !isInfinity(val)) 619 tval = double.max; 620 if (val < -double.max && !isInfinity(val)) 621 tval = -double.max; 622 623 // Numbers between the smallest representable double subnormal and 0.0 624 // are converted to the smallest representable double subnormal: 625 enum doubleLowest = nextUp(0.0); 626 if (val > 0 && val < doubleLowest) 627 tval = doubleLowest; 628 if (val < 0 && val > -doubleLowest) 629 tval = -doubleLowest; 630 } 631 632 printFloat(w, tval, fs); 633 } 634 635 @safe unittest 636 { 637 assert(format("%.1f", 1337.7) == "1337.7"); 638 assert(format("%,3.2f", 1331.982) == "1,331.98"); 639 assert(format("%,3.0f", 1303.1982) == "1,303"); 640 assert(format("%#,3.4f", 1303.1982) == "1,303.1982"); 641 assert(format("%#,3.0f", 1303.1982) == "1,303."); 642 } 643 644 @safe pure unittest 645 { 646 import std.conv : to; 647 import std.exception : collectExceptionMsg; 648 import std.format : FormatException; 649 import std.meta : AliasSeq; 650 import std.range.primitives : back; 651 652 assert(collectExceptionMsg!FormatException(format("%d", 5.1)).back == 'd'); 653 654 static foreach (T; AliasSeq!(float, double, real)) 655 { 656 formatTest(to!( T)(5.5), "5.5"); 657 formatTest(to!( const T)(5.5), "5.5"); 658 formatTest(to!(immutable T)(5.5), "5.5"); 659 660 formatTest(T.nan, "nan"); 661 } 662 } 663 664 @safe unittest 665 { 666 formatTest(2.25, "2.25"); 667 668 struct S1 669 { 670 double val; 671 alias val this; 672 } 673 struct S2 674 { 675 double val; 676 alias val this; 677 string toString() const { return "S"; } 678 } 679 680 formatTest(S1(2.25), "2.25"); 681 formatTest(S2(2.25), "S"); 682 } 683 684 // https://issues.dlang.org/show_bug.cgi?id=19939 685 @safe unittest 686 { 687 assert(format("^%13,3.2f$", 1.00) == "^ 1.00$"); 688 assert(format("^%13,3.2f$", 10.00) == "^ 10.00$"); 689 assert(format("^%13,3.2f$", 100.00) == "^ 100.00$"); 690 assert(format("^%13,3.2f$", 1_000.00) == "^ 1,000.00$"); 691 assert(format("^%13,3.2f$", 10_000.00) == "^ 10,000.00$"); 692 assert(format("^%13,3.2f$", 100_000.00) == "^ 100,000.00$"); 693 assert(format("^%13,3.2f$", 1_000_000.00) == "^ 1,000,000.00$"); 694 assert(format("^%13,3.2f$", 10_000_000.00) == "^10,000,000.00$"); 695 } 696 697 // https://issues.dlang.org/show_bug.cgi?id=20069 698 @safe unittest 699 { 700 assert(format("%012,f", -1234.0) == "-1,234.000000"); 701 assert(format("%013,f", -1234.0) == "-1,234.000000"); 702 assert(format("%014,f", -1234.0) == "-01,234.000000"); 703 assert(format("%011,f", 1234.0) == "1,234.000000"); 704 assert(format("%012,f", 1234.0) == "1,234.000000"); 705 assert(format("%013,f", 1234.0) == "01,234.000000"); 706 assert(format("%014,f", 1234.0) == "001,234.000000"); 707 assert(format("%015,f", 1234.0) == "0,001,234.000000"); 708 assert(format("%016,f", 1234.0) == "0,001,234.000000"); 709 710 assert(format( "%08,.2f", -1234.0) == "-1,234.00"); 711 assert(format( "%09,.2f", -1234.0) == "-1,234.00"); 712 assert(format("%010,.2f", -1234.0) == "-01,234.00"); 713 assert(format("%011,.2f", -1234.0) == "-001,234.00"); 714 assert(format("%012,.2f", -1234.0) == "-0,001,234.00"); 715 assert(format("%013,.2f", -1234.0) == "-0,001,234.00"); 716 assert(format("%014,.2f", -1234.0) == "-00,001,234.00"); 717 assert(format( "%08,.2f", 1234.0) == "1,234.00"); 718 assert(format( "%09,.2f", 1234.0) == "01,234.00"); 719 assert(format("%010,.2f", 1234.0) == "001,234.00"); 720 assert(format("%011,.2f", 1234.0) == "0,001,234.00"); 721 assert(format("%012,.2f", 1234.0) == "0,001,234.00"); 722 assert(format("%013,.2f", 1234.0) == "00,001,234.00"); 723 assert(format("%014,.2f", 1234.0) == "000,001,234.00"); 724 assert(format("%015,.2f", 1234.0) == "0,000,001,234.00"); 725 assert(format("%016,.2f", 1234.0) == "0,000,001,234.00"); 726 } 727 728 @safe unittest 729 { 730 import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined 731 732 // std.math's FloatingPointControl isn't available on all target platforms 733 static if (is(FloatingPointControl)) 734 { 735 assert(FloatingPointControl.rounding == FloatingPointControl.roundToNearest); 736 } 737 738 // https://issues.dlang.org/show_bug.cgi?id=20320 739 real a = 0.16; 740 real b = 0.016; 741 assert(format("%.1f", a) == "0.2"); 742 assert(format("%.2f", b) == "0.02"); 743 744 double a1 = 0.16; 745 double b1 = 0.016; 746 assert(format("%.1f", a1) == "0.2"); 747 assert(format("%.2f", b1) == "0.02"); 748 749 // https://issues.dlang.org/show_bug.cgi?id=9889 750 assert(format("%.1f", 0.09) == "0.1"); 751 assert(format("%.1f", -0.09) == "-0.1"); 752 assert(format("%.1f", 0.095) == "0.1"); 753 assert(format("%.1f", -0.095) == "-0.1"); 754 assert(format("%.1f", 0.094) == "0.1"); 755 assert(format("%.1f", -0.094) == "-0.1"); 756 } 757 758 @safe unittest 759 { 760 double a = 123.456; 761 double b = -123.456; 762 double c = 123.0; 763 764 assert(format("%10.4f",a) == " 123.4560"); 765 assert(format("%-10.4f",a) == "123.4560 "); 766 assert(format("%+10.4f",a) == " +123.4560"); 767 assert(format("% 10.4f",a) == " 123.4560"); 768 assert(format("%010.4f",a) == "00123.4560"); 769 assert(format("%#10.4f",a) == " 123.4560"); 770 771 assert(format("%10.4f",b) == " -123.4560"); 772 assert(format("%-10.4f",b) == "-123.4560 "); 773 assert(format("%+10.4f",b) == " -123.4560"); 774 assert(format("% 10.4f",b) == " -123.4560"); 775 assert(format("%010.4f",b) == "-0123.4560"); 776 assert(format("%#10.4f",b) == " -123.4560"); 777 778 assert(format("%10.0f",c) == " 123"); 779 assert(format("%-10.0f",c) == "123 "); 780 assert(format("%+10.0f",c) == " +123"); 781 assert(format("% 10.0f",c) == " 123"); 782 assert(format("%010.0f",c) == "0000000123"); 783 assert(format("%#10.0f",c) == " 123."); 784 785 assert(format("%+010.4f",a) == "+0123.4560"); 786 assert(format("% 010.4f",a) == " 0123.4560"); 787 assert(format("% +010.4f",a) == "+0123.4560"); 788 } 789 790 @safe unittest 791 { 792 string t1 = format("[%6s] [%-6s]", 12.3, 12.3); 793 assert(t1 == "[ 12.3] [12.3 ]"); 794 795 string t2 = format("[%6s] [%-6s]", -12.3, -12.3); 796 assert(t2 == "[ -12.3] [-12.3 ]"); 797 } 798 799 // https://issues.dlang.org/show_bug.cgi?id=20396 800 @safe unittest 801 { 802 import std.math.operations : nextUp; 803 804 assert(format!"%a"(nextUp(0.0f)) == "0x0.000002p-126"); 805 assert(format!"%a"(nextUp(0.0)) == "0x0.0000000000001p-1022"); 806 } 807 808 // https://issues.dlang.org/show_bug.cgi?id=20371 809 @safe unittest 810 { 811 assert(format!"%.1000a"(1.0).length == 1007); 812 assert(format!"%.600f"(0.1).length == 602); 813 assert(format!"%.600e"(0.1L).length == 606); 814 } 815 816 @safe unittest 817 { 818 import std.math.hardware; // cannot be selective, because FloatingPointControl might not be defined 819 820 // std.math's FloatingPointControl isn't available on all target platforms 821 static if (is(FloatingPointControl)) 822 { 823 FloatingPointControl fpctrl; 824 825 fpctrl.rounding = FloatingPointControl.roundUp; 826 assert(format!"%.0e"(3.5) == "4e+00"); 827 assert(format!"%.0e"(4.5) == "5e+00"); 828 assert(format!"%.0e"(-3.5) == "-3e+00"); 829 assert(format!"%.0e"(-4.5) == "-4e+00"); 830 831 fpctrl.rounding = FloatingPointControl.roundDown; 832 assert(format!"%.0e"(3.5) == "3e+00"); 833 assert(format!"%.0e"(4.5) == "4e+00"); 834 assert(format!"%.0e"(-3.5) == "-4e+00"); 835 assert(format!"%.0e"(-4.5) == "-5e+00"); 836 837 fpctrl.rounding = FloatingPointControl.roundToZero; 838 assert(format!"%.0e"(3.5) == "3e+00"); 839 assert(format!"%.0e"(4.5) == "4e+00"); 840 assert(format!"%.0e"(-3.5) == "-3e+00"); 841 assert(format!"%.0e"(-4.5) == "-4e+00"); 842 843 fpctrl.rounding = FloatingPointControl.roundToNearest; 844 assert(format!"%.0e"(3.5) == "4e+00"); 845 assert(format!"%.0e"(4.5) == "4e+00"); 846 assert(format!"%.0e"(-3.5) == "-4e+00"); 847 assert(format!"%.0e"(-4.5) == "-4e+00"); 848 } 849 } 850 851 @safe pure unittest 852 { 853 static assert(format("%e",1.0) == "1.000000e+00"); 854 static assert(format("%e",-1.234e156) == "-1.234000e+156"); 855 static assert(format("%a",1.0) == "0x1p+0"); 856 static assert(format("%a",-1.234e156) == "-0x1.7024c96ca3ce4p+518"); 857 static assert(format("%f",1.0) == "1.000000"); 858 static assert(format("%f",-1.234e156) == 859 "-123399999999999990477495546305353609103201879173427886566531" ~ 860 "0740685826234179310516880117527217443004051984432279880308552" ~ 861 "009640198043032289366552939010719744.000000"); 862 static assert(format("%g",1.0) == "1"); 863 static assert(format("%g",-1.234e156) == "-1.234e+156"); 864 865 static assert(format("%e",1.0f) == "1.000000e+00"); 866 static assert(format("%e",-1.234e23f) == "-1.234000e+23"); 867 static assert(format("%a",1.0f) == "0x1p+0"); 868 static assert(format("%a",-1.234e23f) == "-0x1.a2187p+76"); 869 static assert(format("%f",1.0f) == "1.000000"); 870 static assert(format("%f",-1.234e23f) == "-123399998884238311030784.000000"); 871 static assert(format("%g",1.0f) == "1"); 872 static assert(format("%g",-1.234e23f) == "-1.234e+23"); 873 } 874 875 // https://issues.dlang.org/show_bug.cgi?id=21641 876 @safe unittest 877 { 878 float a = -999999.8125; 879 assert(format("%#.5g",a) == "-1.0000e+06"); 880 assert(format("%#.6g",a) == "-1.00000e+06"); 881 } 882 883 // https://issues.dlang.org/show_bug.cgi?id=8424 884 @safe pure unittest 885 { 886 static assert(format("%s", 0.6f) == "0.6"); 887 static assert(format("%s", 0.6) == "0.6"); 888 static assert(format("%s", 0.6L) == "0.6"); 889 } 890 891 // https://issues.dlang.org/show_bug.cgi?id=9297 892 @safe pure unittest 893 { 894 static if (real.mant_dig == 64) // 80 bit reals 895 { 896 assert(format("%.25f", 1.6180339887_4989484820_4586834365L) == "1.6180339887498948482072100"); 897 } 898 } 899 900 // https://issues.dlang.org/show_bug.cgi?id=21853 901 @safe pure unittest 902 { 903 import std.math.exponential : log2; 904 905 static if (real.mant_dig == 64) // 80 bit reals 906 { 907 // log2 is broken for x87-reals on some computers in CTFE 908 // the following test excludes these computers from the test 909 // (https://issues.dlang.org/show_bug.cgi?id=21757) 910 enum test = cast(int) log2(3.05e2312L); 911 static if (test == 7681) 912 static assert(format!"%e"(real.max) == "1.189731e+4932"); 913 } 914 } 915 916 // https://issues.dlang.org/show_bug.cgi?id=21842 917 @safe pure unittest 918 { 919 assert(format!"%-+05,g"(1.0) == "+1 "); 920 } 921 922 // https://issues.dlang.org/show_bug.cgi?id=20536 923 @safe pure unittest 924 { 925 real r = .00000095367431640625L; 926 assert(format("%a", r) == "0x1p-20"); 927 } 928 929 // https://issues.dlang.org/show_bug.cgi?id=21840 930 @safe pure unittest 931 { 932 assert(format!"% 0,e"(0.0) == " 0.000000e+00"); 933 } 934 935 // https://issues.dlang.org/show_bug.cgi?id=21841 936 @safe pure unittest 937 { 938 assert(format!"%0.0,e"(0.0) == "0e+00"); 939 } 940 941 // https://issues.dlang.org/show_bug.cgi?id=21836 942 @safe pure unittest 943 { 944 assert(format!"%-5,1g"(0.0) == "0 "); 945 } 946 947 // https://issues.dlang.org/show_bug.cgi?id=21838 948 @safe pure unittest 949 { 950 assert(format!"%#,a"(0.0) == "0x0.p+0"); 951 } 952 953 /* 954 Formatting a `creal` is deprecated but still kept around for a while. 955 */ 956 deprecated("Use of complex types is deprecated. Use std.complex") 957 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f) 958 if (is(immutable T : immutable creal) && !is(T == enum) && !hasToString!(T, Char)) 959 { 960 import std.range.primitives : put; 961 962 immutable creal val = obj; 963 964 formatValueImpl(w, val.re, f); 965 if (val.im >= 0) 966 { 967 put(w, '+'); 968 } 969 formatValueImpl(w, val.im, f); 970 put(w, 'i'); 971 } 972 973 /* 974 Formatting an `ireal` is deprecated but still kept around for a while. 975 */ 976 deprecated("Use of imaginary types is deprecated. Use std.complex") 977 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f) 978 if (is(immutable T : immutable ireal) && !is(T == enum) && !hasToString!(T, Char)) 979 { 980 import std.range.primitives : put; 981 982 immutable ireal val = obj; 983 984 formatValueImpl(w, val.im, f); 985 put(w, 'i'); 986 } 987 988 /* 989 Individual characters are formatted as Unicode characters with `%s` 990 and as integers with integral-specific format specs 991 */ 992 void formatValueImpl(Writer, T, Char)(auto ref Writer w, const(T) obj, scope const ref FormatSpec!Char f) 993 if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 994 { 995 import std.meta : AliasSeq; 996 997 CharTypeOf!T[1] val = obj; 998 999 if (f.spec == 's' || f.spec == 'c') 1000 writeAligned(w, val[], f); 1001 else 1002 { 1003 alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2]; 1004 formatValueImpl(w, cast(U) val[0], f); 1005 } 1006 } 1007 1008 @safe pure unittest 1009 { 1010 assertCTFEable!( 1011 { 1012 formatTest('c', "c"); 1013 }); 1014 } 1015 1016 @safe unittest 1017 { 1018 struct S1 1019 { 1020 char val; 1021 alias val this; 1022 } 1023 1024 struct S2 1025 { 1026 char val; 1027 alias val this; 1028 string toString() const { return "S"; } 1029 } 1030 1031 formatTest(S1('c'), "c"); 1032 formatTest(S2('c'), "S"); 1033 } 1034 1035 @safe unittest 1036 { 1037 //Little Endian 1038 formatTest("%-r", cast( char)'c', ['c' ]); 1039 formatTest("%-r", cast(wchar)'c', ['c', 0 ]); 1040 formatTest("%-r", cast(dchar)'c', ['c', 0, 0, 0]); 1041 formatTest("%-r", '本', ['\x2c', '\x67'] ); 1042 1043 //Big Endian 1044 formatTest("%+r", cast( char)'c', [ 'c']); 1045 formatTest("%+r", cast(wchar)'c', [0, 'c']); 1046 formatTest("%+r", cast(dchar)'c', [0, 0, 0, 'c']); 1047 formatTest("%+r", '本', ['\x67', '\x2c']); 1048 } 1049 1050 1051 @safe pure unittest 1052 { 1053 string t1 = format("[%6s] [%-6s]", 'A', 'A'); 1054 assert(t1 == "[ A] [A ]"); 1055 string t2 = format("[%6s] [%-6s]", '本', '本'); 1056 assert(t2 == "[ 本] [本 ]"); 1057 } 1058 1059 /* 1060 Strings are formatted like $(REF printf, core, stdc, stdio) 1061 */ 1062 void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope const(T) obj, 1063 scope const ref FormatSpec!Char f) 1064 if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 1065 { 1066 Unqual!(const(StringTypeOf!T)) val = obj; // for `alias this`, see bug5371 1067 formatRange(w, val, f); 1068 } 1069 1070 @safe unittest 1071 { 1072 formatTest("abc", "abc"); 1073 } 1074 1075 @safe pure unittest 1076 { 1077 import std.exception : collectExceptionMsg; 1078 import std.range.primitives : back; 1079 1080 assert(collectExceptionMsg(format("%d", "hi")).back == 'd'); 1081 } 1082 1083 @safe unittest 1084 { 1085 // Test for bug 5371 for structs 1086 struct S1 1087 { 1088 const string var; 1089 alias var this; 1090 } 1091 1092 struct S2 1093 { 1094 string var; 1095 alias var this; 1096 } 1097 1098 formatTest(S1("s1"), "s1"); 1099 formatTest(S2("s2"), "s2"); 1100 } 1101 1102 @safe unittest 1103 { 1104 struct S3 1105 { 1106 string val; alias val this; 1107 string toString() const { return "S"; } 1108 } 1109 1110 formatTest(S3("s3"), "S"); 1111 } 1112 1113 @safe pure unittest 1114 { 1115 //Little Endian 1116 formatTest("%-r", "ab"c, ['a' , 'b' ]); 1117 formatTest("%-r", "ab"w, ['a', 0 , 'b', 0 ]); 1118 formatTest("%-r", "ab"d, ['a', 0, 0, 0, 'b', 0, 0, 0]); 1119 formatTest("%-r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', 1120 '\xe8', '\xaa', '\x9e']); 1121 formatTest("%-r", "日本語"w, ['\xe5', '\x65', '\x2c', '\x67', '\x9e', '\x8a']); 1122 formatTest("%-r", "日本語"d, ['\xe5', '\x65', '\x00', '\x00', '\x2c', '\x67', 1123 '\x00', '\x00', '\x9e', '\x8a', '\x00', '\x00']); 1124 1125 //Big Endian 1126 formatTest("%+r", "ab"c, [ 'a', 'b']); 1127 formatTest("%+r", "ab"w, [ 0, 'a', 0, 'b']); 1128 formatTest("%+r", "ab"d, [0, 0, 0, 'a', 0, 0, 0, 'b']); 1129 formatTest("%+r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', 1130 '\xe8', '\xaa', '\x9e']); 1131 formatTest("%+r", "日本語"w, ['\x65', '\xe5', '\x67', '\x2c', '\x8a', '\x9e']); 1132 formatTest("%+r", "日本語"d, ['\x00', '\x00', '\x65', '\xe5', '\x00', '\x00', 1133 '\x67', '\x2c', '\x00', '\x00', '\x8a', '\x9e']); 1134 } 1135 1136 @safe pure unittest 1137 { 1138 string t1 = format("[%6s] [%-6s]", "AB", "AB"); 1139 assert(t1 == "[ AB] [AB ]"); 1140 string t2 = format("[%6s] [%-6s]", "本Ä", "本Ä"); 1141 assert(t2 == "[ 本Ä] [本Ä ]"); 1142 } 1143 1144 // https://issues.dlang.org/show_bug.cgi?id=6640 1145 @safe unittest 1146 { 1147 import std.range.primitives : front, popFront; 1148 1149 struct Range 1150 { 1151 @safe: 1152 1153 string value; 1154 @property bool empty() const { return !value.length; } 1155 @property dchar front() const { return value.front; } 1156 void popFront() { value.popFront(); } 1157 1158 @property size_t length() const { return value.length; } 1159 } 1160 immutable table = 1161 [ 1162 ["[%s]", "[string]"], 1163 ["[%10s]", "[ string]"], 1164 ["[%-10s]", "[string ]"], 1165 ["[%(%02x %)]", "[73 74 72 69 6e 67]"], 1166 ["[%(%c %)]", "[s t r i n g]"], 1167 ]; 1168 foreach (e; table) 1169 { 1170 formatTest(e[0], "string", e[1]); 1171 formatTest(e[0], Range("string"), e[1]); 1172 } 1173 } 1174 1175 @safe unittest 1176 { 1177 import std.meta : AliasSeq; 1178 1179 // string literal from valid UTF sequence is encoding free. 1180 static foreach (StrType; AliasSeq!(string, wstring, dstring)) 1181 { 1182 // Valid and printable (ASCII) 1183 formatTest([cast(StrType)"hello"], 1184 `["hello"]`); 1185 1186 // 1 character escape sequences (' is not escaped in strings) 1187 formatTest([cast(StrType)"\"'\0\\\a\b\f\n\r\t\v"], 1188 `["\"'\0\\\a\b\f\n\r\t\v"]`); 1189 1190 // 1 character optional escape sequences 1191 formatTest([cast(StrType)"\'\?"], 1192 `["'?"]`); 1193 1194 // Valid and non-printable code point (<= U+FF) 1195 formatTest([cast(StrType)"\x10\x1F\x20test"], 1196 `["\x10\x1F test"]`); 1197 1198 // Valid and non-printable code point (<= U+FFFF) 1199 formatTest([cast(StrType)"\u200B..\u200F"], 1200 `["\u200B..\u200F"]`); 1201 1202 // Valid and non-printable code point (<= U+10FFFF) 1203 formatTest([cast(StrType)"\U000E0020..\U000E007F"], 1204 `["\U000E0020..\U000E007F"]`); 1205 } 1206 1207 // invalid UTF sequence needs hex-string literal postfix (c/w/d) 1208 () @trusted 1209 { 1210 // U+FFFF with UTF-8 (Invalid code point for interchange) 1211 formatTest([cast(string)[0xEF, 0xBF, 0xBF]], 1212 `[[cast(char) 0xEF, cast(char) 0xBF, cast(char) 0xBF]]`); 1213 1214 // U+FFFF with UTF-16 (Invalid code point for interchange) 1215 formatTest([cast(wstring)[0xFFFF]], 1216 `[[cast(wchar) 0xFFFF]]`); 1217 1218 // U+FFFF with UTF-32 (Invalid code point for interchange) 1219 formatTest([cast(dstring)[0xFFFF]], 1220 `[[cast(dchar) 0xFFFF]]`); 1221 } (); 1222 } 1223 1224 /* 1225 Static-size arrays are formatted as dynamic arrays. 1226 */ 1227 void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T obj, 1228 scope const ref FormatSpec!Char f) 1229 if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 1230 { 1231 formatValueImpl(w, obj[], f); 1232 } 1233 1234 // Test for https://issues.dlang.org/show_bug.cgi?id=8310 1235 @safe unittest 1236 { 1237 import std.array : appender; 1238 import std.format : formatValue; 1239 1240 FormatSpec!char f; 1241 auto w = appender!string(); 1242 1243 char[2] two = ['a', 'b']; 1244 formatValue(w, two, f); 1245 1246 char[2] getTwo() { return two; } 1247 formatValue(w, getTwo(), f); 1248 } 1249 1250 // https://issues.dlang.org/show_bug.cgi?id=18205 1251 @safe pure unittest 1252 { 1253 assert("|%8s|".format("abc") == "| abc|"); 1254 assert("|%8s|".format("αβγ") == "| αβγ|"); 1255 assert("|%8s|".format(" ") == "| |"); 1256 assert("|%8s|".format("été"d) == "| été|"); 1257 assert("|%8s|".format("été 2018"w) == "|été 2018|"); 1258 1259 assert("%2s".format("e\u0301"w) == " e\u0301"); 1260 assert("%2s".format("a\u0310\u0337"d) == " a\u0310\u0337"); 1261 } 1262 1263 /* 1264 Dynamic arrays are formatted as input ranges. 1265 */ 1266 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) 1267 if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 1268 { 1269 static if (is(immutable(ArrayTypeOf!T) == immutable(void[]))) 1270 { 1271 formatValueImpl(w, cast(const ubyte[]) obj, f); 1272 } 1273 else static if (!isInputRange!T) 1274 { 1275 alias U = Unqual!(ArrayTypeOf!T); 1276 static assert(isInputRange!U, U.stringof ~ " must be an InputRange"); 1277 U val = obj; 1278 formatValueImpl(w, val, f); 1279 } 1280 else 1281 { 1282 formatRange(w, obj, f); 1283 } 1284 } 1285 1286 // https://issues.dlang.org/show_bug.cgi?id=20848 1287 @safe unittest 1288 { 1289 class C 1290 { 1291 immutable(void)[] data; 1292 } 1293 1294 import std.typecons : Nullable; 1295 Nullable!C c; 1296 } 1297 1298 // alias this, input range I/F, and toString() 1299 @safe unittest 1300 { 1301 struct S(int flags) 1302 { 1303 int[] arr; 1304 static if (flags & 1) 1305 alias arr this; 1306 1307 static if (flags & 2) 1308 { 1309 @property bool empty() const { return arr.length == 0; } 1310 @property int front() const { return arr[0] * 2; } 1311 void popFront() { arr = arr[1 .. $]; } 1312 } 1313 1314 static if (flags & 4) 1315 string toString() const { return "S"; } 1316 } 1317 1318 formatTest(S!0b000([0, 1, 2]), "S!0([0, 1, 2])"); 1319 formatTest(S!0b001([0, 1, 2]), "[0, 1, 2]"); // Test for bug 7628 1320 formatTest(S!0b010([0, 1, 2]), "[0, 2, 4]"); 1321 formatTest(S!0b011([0, 1, 2]), "[0, 2, 4]"); 1322 formatTest(S!0b100([0, 1, 2]), "S"); 1323 formatTest(S!0b101([0, 1, 2]), "S"); // Test for bug 7628 1324 formatTest(S!0b110([0, 1, 2]), "S"); 1325 formatTest(S!0b111([0, 1, 2]), "S"); 1326 } 1327 1328 @safe unittest 1329 { 1330 // void[] 1331 void[] val0; 1332 formatTest(val0, "[]"); 1333 1334 void[] val = cast(void[]) cast(ubyte[])[1, 2, 3]; 1335 formatTest(val, "[1, 2, 3]"); 1336 1337 void[0] sval0 = []; 1338 formatTest(sval0, "[]"); 1339 1340 void[3] sval = () @trusted { return cast(void[3]) cast(ubyte[3])[1, 2, 3]; } (); 1341 formatTest(sval, "[1, 2, 3]"); 1342 } 1343 1344 @safe unittest 1345 { 1346 // const(T[]) -> const(T)[] 1347 const short[] a = [1, 2, 3]; 1348 formatTest(a, "[1, 2, 3]"); 1349 1350 struct S 1351 { 1352 const(int[]) arr; 1353 alias arr this; 1354 } 1355 1356 auto s = S([1,2,3]); 1357 formatTest(s, "[1, 2, 3]"); 1358 } 1359 1360 @safe unittest 1361 { 1362 // nested range formatting with array of string 1363 formatTest("%({%(%02x %)}%| %)", ["test", "msg"], 1364 `{74 65 73 74} {6d 73 67}`); 1365 } 1366 1367 @safe unittest 1368 { 1369 // stop auto escaping inside range formatting 1370 auto arr = ["hello", "world"]; 1371 formatTest("%(%s, %)", arr, `"hello", "world"`); 1372 formatTest("%-(%s, %)", arr, `hello, world`); 1373 1374 auto aa1 = [1:"hello", 2:"world"]; 1375 formatTest("%(%s:%s, %)", aa1, [`1:"hello", 2:"world"`, `2:"world", 1:"hello"`]); 1376 formatTest("%-(%s:%s, %)", aa1, [`1:hello, 2:world`, `2:world, 1:hello`]); 1377 1378 auto aa2 = [1:["ab", "cd"], 2:["ef", "gh"]]; 1379 formatTest("%-(%s:%s, %)", aa2, [`1:["ab", "cd"], 2:["ef", "gh"]`, `2:["ef", "gh"], 1:["ab", "cd"]`]); 1380 formatTest("%-(%s:%(%s%), %)", aa2, [`1:"ab""cd", 2:"ef""gh"`, `2:"ef""gh", 1:"ab""cd"`]); 1381 formatTest("%-(%s:%-(%s%)%|, %)", aa2, [`1:abcd, 2:efgh`, `2:efgh, 1:abcd`]); 1382 } 1383 1384 // https://issues.dlang.org/show_bug.cgi?id=18778 1385 @safe pure unittest 1386 { 1387 assert(format("%-(%1$s - %1$s, %)", ["A", "B", "C"]) == "A - A, B - B, C - C"); 1388 } 1389 1390 @safe pure unittest 1391 { 1392 int[] a = [ 1, 3, 2 ]; 1393 formatTest("testing %(%s & %) embedded", a, 1394 "testing 1 & 3 & 2 embedded"); 1395 formatTest("testing %((%s) %)) wyda3", a, 1396 "testing (1) (3) (2) wyda3"); 1397 1398 int[0] empt = []; 1399 formatTest("(%s)", empt, "([])"); 1400 } 1401 1402 // input range formatting 1403 private void formatRange(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f) 1404 if (isInputRange!T) 1405 { 1406 import std.conv : text; 1407 import std.format : FormatException, formatValue, NoOpSink; 1408 import std.range.primitives : ElementType, empty, front, hasLength, 1409 walkLength, isForwardRange, isInfinite, popFront, put; 1410 1411 // in this mode, we just want to do a representative print to discover 1412 // if the format spec is valid 1413 enum formatTestMode = is(Writer == NoOpSink); 1414 1415 // Formatting character ranges like string 1416 if (f.spec == 's') 1417 { 1418 alias E = ElementType!T; 1419 1420 static if (!is(E == enum) && is(CharTypeOf!E)) 1421 { 1422 static if (is(StringTypeOf!T)) 1423 writeAligned(w, val[0 .. f.precision < $ ? f.precision : $], f); 1424 else 1425 { 1426 if (!f.flDash) 1427 { 1428 static if (hasLength!T) 1429 { 1430 // right align 1431 auto len = val.length; 1432 } 1433 else static if (isForwardRange!T && !isInfinite!T) 1434 { 1435 auto len = walkLength(val.save); 1436 } 1437 else 1438 { 1439 import std.format : enforceFmt; 1440 enforceFmt(f.width == 0, "Cannot right-align a range without length"); 1441 size_t len = 0; 1442 } 1443 if (f.precision != f.UNSPECIFIED && len > f.precision) 1444 len = f.precision; 1445 1446 if (f.width > len) 1447 foreach (i ; 0 .. f.width - len) 1448 put(w, ' '); 1449 if (f.precision == f.UNSPECIFIED) 1450 put(w, val); 1451 else 1452 { 1453 size_t printed = 0; 1454 for (; !val.empty && printed < f.precision; val.popFront(), ++printed) 1455 put(w, val.front); 1456 } 1457 } 1458 else 1459 { 1460 size_t printed = void; 1461 1462 // left align 1463 if (f.precision == f.UNSPECIFIED) 1464 { 1465 static if (hasLength!T) 1466 { 1467 printed = val.length; 1468 put(w, val); 1469 } 1470 else 1471 { 1472 printed = 0; 1473 for (; !val.empty; val.popFront(), ++printed) 1474 { 1475 put(w, val.front); 1476 static if (formatTestMode) break; // one is enough to test 1477 } 1478 } 1479 } 1480 else 1481 { 1482 printed = 0; 1483 for (; !val.empty && printed < f.precision; val.popFront(), ++printed) 1484 put(w, val.front); 1485 } 1486 1487 if (f.width > printed) 1488 foreach (i ; 0 .. f.width - printed) 1489 put(w, ' '); 1490 } 1491 } 1492 } 1493 else 1494 { 1495 put(w, f.seqBefore); 1496 if (!val.empty) 1497 { 1498 formatElement(w, val.front, f); 1499 val.popFront(); 1500 for (size_t i; !val.empty; val.popFront(), ++i) 1501 { 1502 put(w, f.seqSeparator); 1503 formatElement(w, val.front, f); 1504 static if (formatTestMode) break; // one is enough to test 1505 } 1506 } 1507 static if (!isInfinite!T) put(w, f.seqAfter); 1508 } 1509 } 1510 else if (f.spec == 'r') 1511 { 1512 static if (is(DynamicArrayTypeOf!T)) 1513 { 1514 alias ARR = DynamicArrayTypeOf!T; 1515 scope a = cast(ARR) val; 1516 foreach (e ; a) 1517 { 1518 formatValue(w, e, f); 1519 static if (formatTestMode) break; // one is enough to test 1520 } 1521 } 1522 else 1523 { 1524 for (size_t i; !val.empty; val.popFront(), ++i) 1525 { 1526 formatValue(w, val.front, f); 1527 static if (formatTestMode) break; // one is enough to test 1528 } 1529 } 1530 } 1531 else if (f.spec == '(') 1532 { 1533 if (val.empty) 1534 return; 1535 // Nested specifier is to be used 1536 for (;;) 1537 { 1538 auto fmt = FormatSpec!Char(f.nested); 1539 w: while (true) 1540 { 1541 immutable r = fmt.writeUpToNextSpec(w); 1542 // There was no format specifier, so break 1543 if (!r) 1544 break; 1545 if (f.flDash) 1546 formatValue(w, val.front, fmt); 1547 else 1548 formatElement(w, val.front, fmt); 1549 // Check if there will be a format specifier farther on in the 1550 // string. If so, continue the loop, otherwise break. This 1551 // prevents extra copies of the `sep` from showing up. 1552 foreach (size_t i; 0 .. fmt.trailing.length) 1553 if (fmt.trailing[i] == '%') 1554 continue w; 1555 break w; 1556 } 1557 static if (formatTestMode) 1558 { 1559 break; // one is enough to test 1560 } 1561 else 1562 { 1563 if (f.sep !is null) 1564 { 1565 put(w, fmt.trailing); 1566 val.popFront(); 1567 if (val.empty) 1568 break; 1569 put(w, f.sep); 1570 } 1571 else 1572 { 1573 val.popFront(); 1574 if (val.empty) 1575 break; 1576 put(w, fmt.trailing); 1577 } 1578 } 1579 } 1580 } 1581 else 1582 throw new FormatException(text("Incorrect format specifier for range: %", f.spec)); 1583 } 1584 1585 // https://issues.dlang.org/show_bug.cgi?id=20218 1586 @safe pure unittest 1587 { 1588 void notCalled() 1589 { 1590 import std.range : repeat; 1591 1592 auto value = 1.repeat; 1593 1594 // test that range is not evaluated to completion at compiletime 1595 format!"%s"(value); 1596 } 1597 } 1598 1599 // character formatting with ecaping 1600 void formatChar(Writer)(ref Writer w, in dchar c, in char quote) 1601 { 1602 import std.format : formattedWrite; 1603 import std.range.primitives : put; 1604 import std.uni : isGraphical; 1605 1606 string fmt; 1607 if (isGraphical(c)) 1608 { 1609 if (c == quote || c == '\\') 1610 put(w, '\\'); 1611 put(w, c); 1612 return; 1613 } 1614 else if (c <= 0xFF) 1615 { 1616 if (c < 0x20) 1617 { 1618 foreach (i, k; "\n\r\t\a\b\f\v\0") 1619 { 1620 if (c == k) 1621 { 1622 put(w, '\\'); 1623 put(w, "nrtabfv0"[i]); 1624 return; 1625 } 1626 } 1627 } 1628 fmt = "\\x%02X"; 1629 } 1630 else if (c <= 0xFFFF) 1631 fmt = "\\u%04X"; 1632 else 1633 fmt = "\\U%08X"; 1634 1635 formattedWrite(w, fmt, cast(uint) c); 1636 } 1637 1638 /* 1639 Associative arrays are formatted by using `':'` and $(D ", ") as 1640 separators, and enclosed by `'['` and `']'`. 1641 */ 1642 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) 1643 if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 1644 { 1645 import std.format : enforceFmt, formatValue; 1646 import std.range.primitives : put; 1647 1648 AssocArrayTypeOf!T val = obj; 1649 const spec = f.spec; 1650 1651 enforceFmt(spec == 's' || spec == '(', 1652 "incompatible format character for associative array argument: %" ~ spec); 1653 1654 enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator; 1655 auto fmtSpec = spec == '(' ? f.nested : defSpec; 1656 1657 auto key_first = true; 1658 1659 // testing correct nested format spec 1660 import std.format : NoOpSink; 1661 auto noop = NoOpSink(); 1662 auto test = FormatSpec!Char(fmtSpec); 1663 enforceFmt(test.writeUpToNextSpec(noop), 1664 "nested format string for associative array contains no format specifier"); 1665 enforceFmt(test.indexStart <= 2, 1666 "positional parameter in nested format string for associative array may only be 1 or 2"); 1667 if (test.indexStart == 2) 1668 key_first = false; 1669 1670 enforceFmt(test.writeUpToNextSpec(noop), 1671 "nested format string for associative array contains only one format specifier"); 1672 enforceFmt(test.indexStart <= 2, 1673 "positional parameter in nested format string for associative array may only be 1 or 2"); 1674 enforceFmt(test.indexStart == 0 || ((test.indexStart == 2) == key_first), 1675 "wrong combination of positional parameters in nested format string"); 1676 1677 enforceFmt(!test.writeUpToNextSpec(noop), 1678 "nested format string for associative array contains more than two format specifiers"); 1679 1680 size_t i = 0; 1681 immutable end = val.length; 1682 1683 if (spec == 's') 1684 put(w, f.seqBefore); 1685 foreach (k, ref v; val) 1686 { 1687 auto fmt = FormatSpec!Char(fmtSpec); 1688 1689 foreach (pos; 1 .. 3) 1690 { 1691 fmt.writeUpToNextSpec(w); 1692 1693 if (key_first == (pos == 1)) 1694 { 1695 if (f.flDash) 1696 formatValue(w, k, fmt); 1697 else 1698 formatElement(w, k, fmt); 1699 } 1700 else 1701 { 1702 if (f.flDash) 1703 formatValue(w, v, fmt); 1704 else 1705 formatElement(w, v, fmt); 1706 } 1707 } 1708 1709 if (f.sep !is null) 1710 { 1711 fmt.writeUpToNextSpec(w); 1712 if (++i != end) 1713 put(w, f.sep); 1714 } 1715 else 1716 { 1717 if (++i != end) 1718 fmt.writeUpToNextSpec(w); 1719 } 1720 } 1721 if (spec == 's') 1722 put(w, f.seqAfter); 1723 } 1724 1725 @safe unittest 1726 { 1727 import std.exception : collectExceptionMsg; 1728 import std.format : FormatException; 1729 import std.range.primitives : back; 1730 1731 assert(collectExceptionMsg!FormatException(format("%d", [0:1])).back == 'd'); 1732 1733 int[string] aa0; 1734 formatTest(aa0, `[]`); 1735 1736 // elements escaping 1737 formatTest(["aaa":1, "bbb":2], 1738 [`["aaa":1, "bbb":2]`, `["bbb":2, "aaa":1]`]); 1739 formatTest(['c':"str"], 1740 `['c':"str"]`); 1741 formatTest(['"':"\"", '\'':"'"], 1742 [`['"':"\"", '\'':"'"]`, `['\'':"'", '"':"\""]`]); 1743 1744 // range formatting for AA 1745 auto aa3 = [1:"hello", 2:"world"]; 1746 // escape 1747 formatTest("{%(%s:%s $ %)}", aa3, 1748 [`{1:"hello" $ 2:"world"}`, `{2:"world" $ 1:"hello"}`]); 1749 // use range formatting for key and value, and use %| 1750 formatTest("{%([%04d->%(%c.%)]%| $ %)}", aa3, 1751 [`{[0001->h.e.l.l.o] $ [0002->w.o.r.l.d]}`, 1752 `{[0002->w.o.r.l.d] $ [0001->h.e.l.l.o]}`]); 1753 1754 // https://issues.dlang.org/show_bug.cgi?id=12135 1755 formatTest("%(%s:<%s>%|,%)", [1:2], "1:<2>"); 1756 formatTest("%(%s:<%s>%|%)" , [1:2], "1:<2>"); 1757 } 1758 1759 @safe unittest 1760 { 1761 struct S1 1762 { 1763 int[char] val; 1764 alias val this; 1765 } 1766 1767 struct S2 1768 { 1769 int[char] val; 1770 alias val this; 1771 string toString() const { return "S"; } 1772 } 1773 1774 formatTest(S1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`]); 1775 formatTest(S2(['c':1, 'd':2]), "S"); 1776 } 1777 1778 // https://issues.dlang.org/show_bug.cgi?id=21875 1779 @safe unittest 1780 { 1781 import std.exception : assertThrown; 1782 import std.format : FormatException; 1783 1784 auto aa = [ 1 : "x", 2 : "y", 3 : "z" ]; 1785 1786 assertThrown!FormatException(format("%(%)", aa)); 1787 assertThrown!FormatException(format("%(%s%)", aa)); 1788 assertThrown!FormatException(format("%(%s%s%s%)", aa)); 1789 } 1790 1791 @safe unittest 1792 { 1793 import std.exception : assertThrown; 1794 import std.format : FormatException; 1795 1796 auto aa = [ 1 : "x", 2 : "y", 3 : "z" ]; 1797 1798 assertThrown!FormatException(format("%(%3$s%s%)", aa)); 1799 assertThrown!FormatException(format("%(%s%3$s%)", aa)); 1800 assertThrown!FormatException(format("%(%1$s%1$s%)", aa)); 1801 assertThrown!FormatException(format("%(%2$s%2$s%)", aa)); 1802 assertThrown!FormatException(format("%(%s%1$s%)", aa)); 1803 } 1804 1805 // https://issues.dlang.org/show_bug.cgi?id=21808 1806 @safe unittest 1807 { 1808 auto spelled = [ 1 : "one" ]; 1809 assert(format("%-(%2$s (%1$s)%|, %)", spelled) == "one (1)"); 1810 1811 spelled[2] = "two"; 1812 auto result = format("%-(%2$s (%1$s)%|, %)", spelled); 1813 assert(result == "one (1), two (2)" || result == "two (2), one (1)"); 1814 } 1815 1816 enum HasToStringResult 1817 { 1818 none, 1819 hasSomeToString, 1820 inCharSink, 1821 inCharSinkFormatString, 1822 inCharSinkFormatSpec, 1823 constCharSink, 1824 constCharSinkFormatString, 1825 constCharSinkFormatSpec, 1826 customPutWriter, 1827 customPutWriterFormatSpec, 1828 } 1829 1830 private alias DScannerBug895 = int[256]; 1831 private immutable bool hasPreviewIn = ((in DScannerBug895 a) { return __traits(isRef, a); })(DScannerBug895.init); 1832 1833 template hasToString(T, Char) 1834 { 1835 static if (isPointer!T) 1836 { 1837 // X* does not have toString, even if X is aggregate type has toString. 1838 enum hasToString = HasToStringResult.none; 1839 } 1840 else static if (is(typeof( 1841 (T val) { 1842 const FormatSpec!Char f; 1843 static struct S 1844 { 1845 @disable this(this); 1846 void put(scope Char s){} 1847 } 1848 S s; 1849 val.toString(s, f); 1850 }))) 1851 { 1852 enum hasToString = HasToStringResult.customPutWriterFormatSpec; 1853 } 1854 else static if (is(typeof( 1855 (T val) { 1856 static struct S 1857 { 1858 @disable this(this); 1859 void put(scope Char s){} 1860 } 1861 S s; 1862 val.toString(s); 1863 }))) 1864 { 1865 enum hasToString = HasToStringResult.customPutWriter; 1866 } 1867 else static if (is(typeof((T val) { FormatSpec!Char f; val.toString((scope const(char)[] s){}, f); }))) 1868 { 1869 enum hasToString = HasToStringResult.constCharSinkFormatSpec; 1870 } 1871 else static if (is(typeof((T val) { val.toString((scope const(char)[] s){}, "%s"); }))) 1872 { 1873 enum hasToString = HasToStringResult.constCharSinkFormatString; 1874 } 1875 else static if (is(typeof((T val) { val.toString((scope const(char)[] s){}); }))) 1876 { 1877 enum hasToString = HasToStringResult.constCharSink; 1878 } 1879 1880 else static if (hasPreviewIn && 1881 is(typeof((T val) { FormatSpec!Char f; val.toString((in char[] s){}, f); }))) 1882 { 1883 enum hasToString = HasToStringResult.inCharSinkFormatSpec; 1884 } 1885 else static if (hasPreviewIn && 1886 is(typeof((T val) { val.toString((in char[] s){}, "%s"); }))) 1887 { 1888 enum hasToString = HasToStringResult.inCharSinkFormatString; 1889 } 1890 else static if (hasPreviewIn && 1891 is(typeof((T val) { val.toString((in char[] s){}); }))) 1892 { 1893 enum hasToString = HasToStringResult.inCharSink; 1894 } 1895 1896 else static if (is(ReturnType!((T val) { return val.toString(); }) S) && isSomeString!S) 1897 { 1898 enum hasToString = HasToStringResult.hasSomeToString; 1899 } 1900 else 1901 { 1902 enum hasToString = HasToStringResult.none; 1903 } 1904 } 1905 1906 @safe unittest 1907 { 1908 import std.range.primitives : isOutputRange; 1909 1910 static struct A 1911 { 1912 void toString(Writer)(ref Writer w) 1913 if (isOutputRange!(Writer, string)) 1914 {} 1915 } 1916 static struct B 1917 { 1918 void toString(scope void delegate(scope const(char)[]) sink, scope FormatSpec!char fmt) {} 1919 } 1920 static struct C 1921 { 1922 void toString(scope void delegate(scope const(char)[]) sink, string fmt) {} 1923 } 1924 static struct D 1925 { 1926 void toString(scope void delegate(scope const(char)[]) sink) {} 1927 } 1928 static struct E 1929 { 1930 string toString() {return "";} 1931 } 1932 static struct F 1933 { 1934 void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) 1935 if (isOutputRange!(Writer, string)) 1936 {} 1937 } 1938 static struct G 1939 { 1940 string toString() {return "";} 1941 void toString(Writer)(ref Writer w) 1942 if (isOutputRange!(Writer, string)) {} 1943 } 1944 static struct H 1945 { 1946 string toString() {return "";} 1947 void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) 1948 if (isOutputRange!(Writer, string)) 1949 {} 1950 } 1951 static struct I 1952 { 1953 void toString(Writer)(ref Writer w) 1954 if (isOutputRange!(Writer, string)) {} 1955 void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) 1956 if (isOutputRange!(Writer, string)) 1957 {} 1958 } 1959 static struct J 1960 { 1961 string toString() {return "";} 1962 void toString(Writer)(ref Writer w, scope ref FormatSpec!char fmt) 1963 if (isOutputRange!(Writer, string)) 1964 {} 1965 } 1966 static struct K 1967 { 1968 void toString(Writer)(Writer w, scope const ref FormatSpec!char fmt) 1969 if (isOutputRange!(Writer, string)) 1970 {} 1971 } 1972 static struct L 1973 { 1974 void toString(Writer)(ref Writer w, scope const FormatSpec!char fmt) 1975 if (isOutputRange!(Writer, string)) 1976 {} 1977 } 1978 static struct M 1979 { 1980 void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt) {} 1981 } 1982 static struct N 1983 { 1984 void toString(scope void delegate(in char[]) sink, string fmt) {} 1985 } 1986 static struct O 1987 { 1988 void toString(scope void delegate(in char[]) sink) {} 1989 } 1990 1991 with(HasToStringResult) 1992 { 1993 static assert(hasToString!(A, char) == customPutWriter); 1994 static assert(hasToString!(B, char) == constCharSinkFormatSpec); 1995 static assert(hasToString!(C, char) == constCharSinkFormatString); 1996 static assert(hasToString!(D, char) == constCharSink); 1997 static assert(hasToString!(E, char) == hasSomeToString); 1998 static assert(hasToString!(F, char) == customPutWriterFormatSpec); 1999 static assert(hasToString!(G, char) == customPutWriter); 2000 static assert(hasToString!(H, char) == customPutWriterFormatSpec); 2001 static assert(hasToString!(I, char) == customPutWriterFormatSpec); 2002 static assert(hasToString!(J, char) == hasSomeToString 2003 || hasToString!(J, char) == constCharSinkFormatSpec); // depends on -preview=rvaluerefparam 2004 static assert(hasToString!(K, char) == constCharSinkFormatSpec); 2005 static assert(hasToString!(L, char) == customPutWriterFormatSpec); 2006 static if (hasPreviewIn) 2007 { 2008 static assert(hasToString!(M, char) == inCharSinkFormatSpec); 2009 static assert(hasToString!(N, char) == inCharSinkFormatString); 2010 static assert(hasToString!(O, char) == inCharSink); 2011 } 2012 } 2013 } 2014 2015 // const toString methods 2016 @safe unittest 2017 { 2018 import std.range.primitives : isOutputRange; 2019 2020 static struct A 2021 { 2022 void toString(Writer)(ref Writer w) const 2023 if (isOutputRange!(Writer, string)) 2024 {} 2025 } 2026 static struct B 2027 { 2028 void toString(scope void delegate(scope const(char)[]) sink, scope FormatSpec!char fmt) const {} 2029 } 2030 static struct C 2031 { 2032 void toString(scope void delegate(scope const(char)[]) sink, string fmt) const {} 2033 } 2034 static struct D 2035 { 2036 void toString(scope void delegate(scope const(char)[]) sink) const {} 2037 } 2038 static struct E 2039 { 2040 string toString() const {return "";} 2041 } 2042 static struct F 2043 { 2044 void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) const 2045 if (isOutputRange!(Writer, string)) 2046 {} 2047 } 2048 static struct G 2049 { 2050 string toString() const {return "";} 2051 void toString(Writer)(ref Writer w) const 2052 if (isOutputRange!(Writer, string)) {} 2053 } 2054 static struct H 2055 { 2056 string toString() const {return "";} 2057 void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) const 2058 if (isOutputRange!(Writer, string)) 2059 {} 2060 } 2061 static struct I 2062 { 2063 void toString(Writer)(ref Writer w) const 2064 if (isOutputRange!(Writer, string)) {} 2065 void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) const 2066 if (isOutputRange!(Writer, string)) 2067 {} 2068 } 2069 static struct J 2070 { 2071 string toString() const {return "";} 2072 void toString(Writer)(ref Writer w, scope ref FormatSpec!char fmt) const 2073 if (isOutputRange!(Writer, string)) 2074 {} 2075 } 2076 static struct K 2077 { 2078 void toString(Writer)(Writer w, scope const ref FormatSpec!char fmt) const 2079 if (isOutputRange!(Writer, string)) 2080 {} 2081 } 2082 static struct L 2083 { 2084 void toString(Writer)(ref Writer w, scope const FormatSpec!char fmt) const 2085 if (isOutputRange!(Writer, string)) 2086 {} 2087 } 2088 static struct M 2089 { 2090 void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt) const {} 2091 } 2092 static struct N 2093 { 2094 void toString(scope void delegate(in char[]) sink, string fmt) const {} 2095 } 2096 static struct O 2097 { 2098 void toString(scope void delegate(in char[]) sink) const {} 2099 } 2100 2101 with(HasToStringResult) 2102 { 2103 static assert(hasToString!(A, char) == customPutWriter); 2104 static assert(hasToString!(B, char) == constCharSinkFormatSpec); 2105 static assert(hasToString!(C, char) == constCharSinkFormatString); 2106 static assert(hasToString!(D, char) == constCharSink); 2107 static assert(hasToString!(E, char) == hasSomeToString); 2108 static assert(hasToString!(F, char) == customPutWriterFormatSpec); 2109 static assert(hasToString!(G, char) == customPutWriter); 2110 static assert(hasToString!(H, char) == customPutWriterFormatSpec); 2111 static assert(hasToString!(I, char) == customPutWriterFormatSpec); 2112 static assert(hasToString!(J, char) == hasSomeToString 2113 || hasToString!(J, char) == constCharSinkFormatSpec); // depends on -preview=rvaluerefparam 2114 static assert(hasToString!(K, char) == constCharSinkFormatSpec); 2115 static assert(hasToString!(L, char) == HasToStringResult.customPutWriterFormatSpec); 2116 static if (hasPreviewIn) 2117 { 2118 static assert(hasToString!(M, char) == inCharSinkFormatSpec); 2119 static assert(hasToString!(N, char) == inCharSinkFormatString); 2120 static assert(hasToString!(O, char) == inCharSink); 2121 } 2122 2123 // https://issues.dlang.org/show_bug.cgi?id=22873 2124 static assert(hasToString!(inout(A), char) == customPutWriter); 2125 static assert(hasToString!(inout(B), char) == constCharSinkFormatSpec); 2126 static assert(hasToString!(inout(C), char) == constCharSinkFormatString); 2127 static assert(hasToString!(inout(D), char) == constCharSink); 2128 static assert(hasToString!(inout(E), char) == hasSomeToString); 2129 static assert(hasToString!(inout(F), char) == customPutWriterFormatSpec); 2130 static assert(hasToString!(inout(G), char) == customPutWriter); 2131 static assert(hasToString!(inout(H), char) == customPutWriterFormatSpec); 2132 static assert(hasToString!(inout(I), char) == customPutWriterFormatSpec); 2133 static assert(hasToString!(inout(J), char) == hasSomeToString 2134 || hasToString!(inout(J), char) == constCharSinkFormatSpec); // depends on -preview=rvaluerefparam 2135 static assert(hasToString!(inout(K), char) == constCharSinkFormatSpec); 2136 static assert(hasToString!(inout(L), char) == customPutWriterFormatSpec); 2137 static if (hasPreviewIn) 2138 { 2139 static assert(hasToString!(inout(M), char) == inCharSinkFormatSpec); 2140 static assert(hasToString!(inout(N), char) == inCharSinkFormatString); 2141 static assert(hasToString!(inout(O), char) == inCharSink); 2142 } 2143 } 2144 } 2145 2146 // object formatting with toString 2147 private void formatObject(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f) 2148 if (hasToString!(T, Char)) 2149 { 2150 import std.format : NoOpSink; 2151 import std.range.primitives : put; 2152 2153 enum overload = hasToString!(T, Char); 2154 2155 enum noop = is(Writer == NoOpSink); 2156 2157 static if (overload == HasToStringResult.customPutWriterFormatSpec) 2158 { 2159 static if (!noop) val.toString(w, f); 2160 } 2161 else static if (overload == HasToStringResult.customPutWriter) 2162 { 2163 static if (!noop) val.toString(w); 2164 } 2165 else static if (overload == HasToStringResult.constCharSinkFormatSpec) 2166 { 2167 static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f); 2168 } 2169 else static if (overload == HasToStringResult.constCharSinkFormatString) 2170 { 2171 static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f.getCurFmtStr()); 2172 } 2173 else static if (overload == HasToStringResult.constCharSink) 2174 { 2175 static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }); 2176 } 2177 else static if (overload == HasToStringResult.inCharSinkFormatSpec) 2178 { 2179 static if (!noop) val.toString((in char[] s) { put(w, s); }, f); 2180 } 2181 else static if (overload == HasToStringResult.inCharSinkFormatString) 2182 { 2183 static if (!noop) val.toString((in char[] s) { put(w, s); }, f.getCurFmtStr()); 2184 } 2185 else static if (overload == HasToStringResult.inCharSink) 2186 { 2187 static if (!noop) val.toString((in char[] s) { put(w, s); }); 2188 } 2189 else static if (overload == HasToStringResult.hasSomeToString) 2190 { 2191 static if (!noop) put(w, val.toString()); 2192 } 2193 else 2194 { 2195 static assert(0, "No way found to format " ~ T.stringof ~ " as string"); 2196 } 2197 } 2198 2199 @system unittest 2200 { 2201 import std.exception : assertThrown; 2202 import std.format : FormatException; 2203 2204 static interface IF1 { } 2205 class CIF1 : IF1 { } 2206 static struct SF1 { } 2207 static union UF1 { } 2208 static class CF1 { } 2209 2210 static interface IF2 { string toString(); } 2211 static class CIF2 : IF2 { override string toString() { return ""; } } 2212 static struct SF2 { string toString() { return ""; } } 2213 static union UF2 { string toString() { return ""; } } 2214 static class CF2 { override string toString() { return ""; } } 2215 2216 static interface IK1 { void toString(scope void delegate(scope const(char)[]) sink, 2217 FormatSpec!char) const; } 2218 static class CIK1 : IK1 { override void toString(scope void delegate(scope const(char)[]) sink, 2219 FormatSpec!char) const { sink("CIK1"); } } 2220 static struct KS1 { void toString(scope void delegate(scope const(char)[]) sink, 2221 FormatSpec!char) const { sink("KS1"); } } 2222 2223 static union KU1 { void toString(scope void delegate(scope const(char)[]) sink, 2224 FormatSpec!char) const { sink("KU1"); } } 2225 2226 static class KC1 { void toString(scope void delegate(scope const(char)[]) sink, 2227 FormatSpec!char) const { sink("KC1"); } } 2228 2229 IF1 cif1 = new CIF1; 2230 assertThrown!FormatException(format("%f", cif1)); 2231 assertThrown!FormatException(format("%f", SF1())); 2232 assertThrown!FormatException(format("%f", UF1())); 2233 assertThrown!FormatException(format("%f", new CF1())); 2234 2235 IF2 cif2 = new CIF2; 2236 assertThrown!FormatException(format("%f", cif2)); 2237 assertThrown!FormatException(format("%f", SF2())); 2238 assertThrown!FormatException(format("%f", UF2())); 2239 assertThrown!FormatException(format("%f", new CF2())); 2240 2241 IK1 cik1 = new CIK1; 2242 assert(format("%f", cik1) == "CIK1"); 2243 assert(format("%f", KS1()) == "KS1"); 2244 assert(format("%f", KU1()) == "KU1"); 2245 assert(format("%f", new KC1()) == "KC1"); 2246 } 2247 2248 /* 2249 Aggregates 2250 */ 2251 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 2252 if (is(T == class) && !is(T == enum)) 2253 { 2254 import std.range.primitives : put; 2255 2256 enforceValidFormatSpec!(T, Char)(f); 2257 2258 // TODO: remove this check once `@disable override` deprecation cycle is finished 2259 static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString)) 2260 static assert(!__traits(isDisabled, T.toString), T.stringof ~ 2261 " cannot be formatted because its `toString` is marked with `@disable`"); 2262 2263 if (val is null) 2264 put(w, "null"); 2265 else 2266 { 2267 import std.algorithm.comparison : among; 2268 enum overload = hasToString!(T, Char); 2269 with(HasToStringResult) 2270 static if ((is(T == immutable) || is(T == const) || is(T == shared)) && overload == none) 2271 { 2272 // Remove this when Object gets const toString 2273 // https://issues.dlang.org/show_bug.cgi?id=7879 2274 static if (is(T == immutable)) 2275 put(w, "immutable("); 2276 else static if (is(T == const)) 2277 put(w, "const("); 2278 else static if (is(T == shared)) 2279 put(w, "shared("); 2280 2281 put(w, typeid(Unqual!T).name); 2282 put(w, ')'); 2283 } 2284 else static if (overload.among(constCharSink, constCharSinkFormatString, constCharSinkFormatSpec) || 2285 (!isInputRange!T && !is(BuiltinTypeOf!T))) 2286 { 2287 formatObject!(Writer, T, Char)(w, val, f); 2288 } 2289 else 2290 { 2291 static if (!is(__traits(parent, T.toString) == Object)) // not inherited Object.toString 2292 { 2293 formatObject(w, val, f); 2294 } 2295 else static if (isInputRange!T) 2296 { 2297 formatRange(w, val, f); 2298 } 2299 else static if (is(BuiltinTypeOf!T X)) 2300 { 2301 X x = val; 2302 formatValueImpl(w, x, f); 2303 } 2304 else 2305 { 2306 formatObject(w, val, f); 2307 } 2308 } 2309 } 2310 } 2311 2312 @system unittest 2313 { 2314 import std.array : appender; 2315 import std.range.interfaces : inputRangeObject; 2316 2317 // class range (https://issues.dlang.org/show_bug.cgi?id=5154) 2318 auto c = inputRangeObject([1,2,3,4]); 2319 formatTest(c, "[1, 2, 3, 4]"); 2320 assert(c.empty); 2321 c = null; 2322 formatTest(c, "null"); 2323 } 2324 2325 @system unittest 2326 { 2327 // https://issues.dlang.org/show_bug.cgi?id=5354 2328 // If the class has both range I/F and custom toString, the use of custom 2329 // toString routine is prioritized. 2330 2331 // Enable the use of custom toString that gets a sink delegate 2332 // for class formatting. 2333 2334 enum inputRangeCode = 2335 q{ 2336 int[] arr; 2337 this(int[] a){ arr = a; } 2338 @property int front() const { return arr[0]; } 2339 @property bool empty() const { return arr.length == 0; } 2340 void popFront(){ arr = arr[1 .. $]; } 2341 }; 2342 2343 class C1 2344 { 2345 mixin(inputRangeCode); 2346 void toString(scope void delegate(scope const(char)[]) dg, 2347 scope const ref FormatSpec!char f) const 2348 { 2349 dg("[012]"); 2350 } 2351 } 2352 class C2 2353 { 2354 mixin(inputRangeCode); 2355 void toString(scope void delegate(const(char)[]) dg, string f) const { dg("[012]"); } 2356 } 2357 class C3 2358 { 2359 mixin(inputRangeCode); 2360 void toString(scope void delegate(const(char)[]) dg) const { dg("[012]"); } 2361 } 2362 class C4 2363 { 2364 mixin(inputRangeCode); 2365 override string toString() const { return "[012]"; } 2366 } 2367 class C5 2368 { 2369 mixin(inputRangeCode); 2370 } 2371 2372 formatTest(new C1([0, 1, 2]), "[012]"); 2373 formatTest(new C2([0, 1, 2]), "[012]"); 2374 formatTest(new C3([0, 1, 2]), "[012]"); 2375 formatTest(new C4([0, 1, 2]), "[012]"); 2376 formatTest(new C5([0, 1, 2]), "[0, 1, 2]"); 2377 } 2378 2379 // outside the unittest block, otherwise the FQN of the 2380 // class contains the line number of the unittest 2381 version (StdUnittest) 2382 { 2383 private class C {} 2384 } 2385 2386 // https://issues.dlang.org/show_bug.cgi?id=7879 2387 @safe unittest 2388 { 2389 const(C) c; 2390 auto s = format("%s", c); 2391 assert(s == "null"); 2392 2393 immutable(C) c2 = new C(); 2394 s = format("%s", c2); 2395 assert(s == "immutable(std.format.internal.write.C)"); 2396 2397 const(C) c3 = new C(); 2398 s = format("%s", c3); 2399 assert(s == "const(std.format.internal.write.C)"); 2400 2401 shared(C) c4 = new C(); 2402 s = format("%s", c4); 2403 assert(s == "shared(std.format.internal.write.C)"); 2404 } 2405 2406 // https://issues.dlang.org/show_bug.cgi?id=7879 2407 @safe unittest 2408 { 2409 class F 2410 { 2411 override string toString() const @safe 2412 { 2413 return "Foo"; 2414 } 2415 } 2416 2417 const(F) c; 2418 auto s = format("%s", c); 2419 assert(s == "null"); 2420 2421 const(F) c2 = new F(); 2422 s = format("%s", c2); 2423 assert(s == "Foo", s); 2424 } 2425 2426 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 2427 if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum)) 2428 { 2429 import std.range.primitives : put; 2430 2431 enforceValidFormatSpec!(T, Char)(f); 2432 if (val is null) 2433 put(w, "null"); 2434 else 2435 { 2436 static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString)) 2437 static assert(!__traits(isDisabled, T.toString), T.stringof ~ 2438 " cannot be formatted because its `toString` is marked with `@disable`"); 2439 2440 static if (hasToString!(T, Char) != HasToStringResult.none) 2441 { 2442 formatObject(w, val, f); 2443 } 2444 else static if (isInputRange!T) 2445 { 2446 formatRange(w, val, f); 2447 } 2448 else 2449 { 2450 version (Windows) 2451 { 2452 import core.sys.windows.com : IUnknown; 2453 static if (is(T : IUnknown)) 2454 { 2455 formatValueImpl(w, *cast(void**)&val, f); 2456 } 2457 else 2458 { 2459 formatValueImpl(w, cast(Object) val, f); 2460 } 2461 } 2462 else 2463 { 2464 formatValueImpl(w, cast(Object) val, f); 2465 } 2466 } 2467 } 2468 } 2469 2470 @system unittest 2471 { 2472 import std.range.interfaces : InputRange, inputRangeObject; 2473 2474 // interface 2475 InputRange!int i = inputRangeObject([1,2,3,4]); 2476 formatTest(i, "[1, 2, 3, 4]"); 2477 assert(i.empty); 2478 i = null; 2479 formatTest(i, "null"); 2480 2481 // interface (downcast to Object) 2482 interface Whatever {} 2483 class C : Whatever 2484 { 2485 override @property string toString() const { return "ab"; } 2486 } 2487 Whatever val = new C; 2488 formatTest(val, "ab"); 2489 2490 // https://issues.dlang.org/show_bug.cgi?id=11175 2491 version (Windows) 2492 { 2493 import core.sys.windows.com : IID, IUnknown; 2494 import core.sys.windows.windef : HRESULT; 2495 2496 interface IUnknown2 : IUnknown { } 2497 2498 class D : IUnknown2 2499 { 2500 extern(Windows) HRESULT QueryInterface(const(IID)* riid, void** pvObject) { return typeof(return).init; } 2501 extern(Windows) uint AddRef() { return 0; } 2502 extern(Windows) uint Release() { return 0; } 2503 } 2504 2505 IUnknown2 d = new D; 2506 string expected = format("%X", cast(void*) d); 2507 formatTest(d, expected); 2508 } 2509 } 2510 2511 // Maybe T is noncopyable struct, so receive it by 'auto ref'. 2512 void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T val, 2513 scope const ref FormatSpec!Char f) 2514 if ((is(T == struct) || is(T == union)) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) 2515 && !is(T == enum)) 2516 { 2517 import std.range.primitives : put; 2518 2519 static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString)) 2520 static assert(!__traits(isDisabled, T.toString), T.stringof ~ 2521 " cannot be formatted because its `toString` is marked with `@disable`"); 2522 2523 enforceValidFormatSpec!(T, Char)(f); 2524 static if (hasToString!(T, Char)) 2525 { 2526 formatObject(w, val, f); 2527 } 2528 else static if (isInputRange!T) 2529 { 2530 formatRange(w, val, f); 2531 } 2532 else static if (is(T == struct)) 2533 { 2534 enum left = T.stringof~"("; 2535 enum separator = ", "; 2536 enum right = ")"; 2537 2538 put(w, left); 2539 static foreach (i; 0 .. T.tupleof.length) 2540 {{ 2541 static if (__traits(identifier, val.tupleof[i]) == "this") 2542 { 2543 // ignore hidden context pointer 2544 } 2545 else static if (0 < i && T.tupleof[i-1].offsetof == T.tupleof[i].offsetof) 2546 { 2547 static if (i == T.tupleof.length - 1 || T.tupleof[i].offsetof != T.tupleof[i+1].offsetof) 2548 { 2549 enum el = separator ~ __traits(identifier, T.tupleof[i]) ~ "}"; 2550 put(w, el); 2551 } 2552 else 2553 { 2554 enum el = separator ~ __traits(identifier, T.tupleof[i]); 2555 put(w, el); 2556 } 2557 } 2558 else static if (i+1 < T.tupleof.length && T.tupleof[i].offsetof == T.tupleof[i+1].offsetof) 2559 { 2560 enum el = (i > 0 ? separator : "") ~ "#{overlap " ~ __traits(identifier, T.tupleof[i]); 2561 put(w, el); 2562 } 2563 else 2564 { 2565 static if (i > 0) 2566 put(w, separator); 2567 formatElement(w, val.tupleof[i], f); 2568 } 2569 }} 2570 put(w, right); 2571 } 2572 else 2573 { 2574 put(w, T.stringof); 2575 } 2576 } 2577 2578 // https://issues.dlang.org/show_bug.cgi?id=9588 2579 @safe pure unittest 2580 { 2581 struct S { int x; bool empty() { return false; } } 2582 formatTest(S(), "S(0)"); 2583 } 2584 2585 // https://issues.dlang.org/show_bug.cgi?id=4638 2586 @safe unittest 2587 { 2588 struct U8 { string toString() const { return "blah"; } } 2589 struct U16 { wstring toString() const { return "blah"; } } 2590 struct U32 { dstring toString() const { return "blah"; } } 2591 formatTest(U8(), "blah"); 2592 formatTest(U16(), "blah"); 2593 formatTest(U32(), "blah"); 2594 } 2595 2596 // https://issues.dlang.org/show_bug.cgi?id=3890 2597 @safe unittest 2598 { 2599 struct Int{ int n; } 2600 struct Pair{ string s; Int i; } 2601 formatTest(Pair("hello", Int(5)), 2602 `Pair("hello", Int(5))`); 2603 } 2604 2605 // https://issues.dlang.org/show_bug.cgi?id=9117 2606 @safe unittest 2607 { 2608 import std.format : formattedWrite; 2609 2610 static struct Frop {} 2611 2612 static struct Foo 2613 { 2614 int n = 0; 2615 alias n this; 2616 T opCast(T) () 2617 if (is(T == Frop)) 2618 { 2619 return Frop(); 2620 } 2621 string toString() 2622 { 2623 return "Foo"; 2624 } 2625 } 2626 2627 static struct Bar 2628 { 2629 Foo foo; 2630 alias foo this; 2631 string toString() 2632 { 2633 return "Bar"; 2634 } 2635 } 2636 2637 const(char)[] result; 2638 void put(scope const char[] s) { result ~= s; } 2639 2640 Foo foo; 2641 formattedWrite(&put, "%s", foo); // OK 2642 assert(result == "Foo"); 2643 2644 result = null; 2645 2646 Bar bar; 2647 formattedWrite(&put, "%s", bar); // NG 2648 assert(result == "Bar"); 2649 2650 result = null; 2651 2652 int i = 9; 2653 formattedWrite(&put, "%s", 9); 2654 assert(result == "9"); 2655 } 2656 2657 @safe unittest 2658 { 2659 // union formatting without toString 2660 union U1 2661 { 2662 int n; 2663 string s; 2664 } 2665 U1 u1; 2666 formatTest(u1, "U1"); 2667 2668 // union formatting with toString 2669 union U2 2670 { 2671 int n; 2672 string s; 2673 string toString() @trusted const { return s; } 2674 } 2675 U2 u2; 2676 () @trusted { u2.s = "hello"; } (); 2677 formatTest(u2, "hello"); 2678 } 2679 2680 @safe unittest 2681 { 2682 import std.array : appender; 2683 import std.format : formatValue; 2684 2685 // https://issues.dlang.org/show_bug.cgi?id=7230 2686 static struct Bug7230 2687 { 2688 string s = "hello"; 2689 union { 2690 string a; 2691 int b; 2692 double c; 2693 } 2694 long x = 10; 2695 } 2696 2697 Bug7230 bug; 2698 bug.b = 123; 2699 2700 FormatSpec!char f; 2701 auto w = appender!(char[])(); 2702 formatValue(w, bug, f); 2703 assert(w.data == `Bug7230("hello", #{overlap a, b, c}, 10)`); 2704 } 2705 2706 @safe unittest 2707 { 2708 import std.array : appender; 2709 import std.format : formatValue; 2710 2711 static struct S{ @disable this(this); } 2712 S s; 2713 2714 FormatSpec!char f; 2715 auto w = appender!string(); 2716 formatValue(w, s, f); 2717 assert(w.data == "S()"); 2718 } 2719 2720 @safe unittest 2721 { 2722 import std.array : appender; 2723 import std.format : formatValue; 2724 2725 //struct Foo { @disable string toString(); } 2726 //Foo foo; 2727 2728 interface Bar { @disable string toString(); } 2729 Bar bar; 2730 2731 auto w = appender!(char[])(); 2732 FormatSpec!char f; 2733 2734 // NOTE: structs cant be tested : the assertion is correct so compilation 2735 // continues and fails when trying to link the unimplemented toString. 2736 //static assert(!__traits(compiles, formatValue(w, foo, f))); 2737 static assert(!__traits(compiles, formatValue(w, bar, f))); 2738 } 2739 2740 // https://issues.dlang.org/show_bug.cgi?id=21722 2741 @safe unittest 2742 { 2743 struct Bar 2744 { 2745 void toString (scope void delegate (scope const(char)[]) sink, string fmt) 2746 { 2747 sink("Hello"); 2748 } 2749 } 2750 2751 Bar b; 2752 auto result = () @trusted { return format("%b", b); } (); 2753 assert(result == "Hello"); 2754 2755 static if (hasPreviewIn) 2756 { 2757 struct Foo 2758 { 2759 void toString(scope void delegate(in char[]) sink, in FormatSpec!char fmt) 2760 { 2761 sink("Hello"); 2762 } 2763 } 2764 2765 Foo f; 2766 assert(format("%b", f) == "Hello"); 2767 2768 struct Foo2 2769 { 2770 void toString(scope void delegate(in char[]) sink, string fmt) 2771 { 2772 sink("Hello"); 2773 } 2774 } 2775 2776 Foo2 f2; 2777 assert(format("%b", f2) == "Hello"); 2778 } 2779 } 2780 2781 @safe unittest 2782 { 2783 import std.array : appender; 2784 import std.format : singleSpec; 2785 2786 // Bug #17269. Behavior similar to `struct A { Nullable!string B; }` 2787 struct StringAliasThis 2788 { 2789 @property string value() const { assert(0); } 2790 alias value this; 2791 string toString() { return "helloworld"; } 2792 private string _value; 2793 } 2794 struct TestContainer 2795 { 2796 StringAliasThis testVar; 2797 } 2798 2799 auto w = appender!string(); 2800 auto spec = singleSpec("%s"); 2801 formatElement(w, TestContainer(), spec); 2802 2803 assert(w.data == "TestContainer(helloworld)", w.data); 2804 } 2805 2806 // https://issues.dlang.org/show_bug.cgi?id=17269 2807 @safe unittest 2808 { 2809 import std.typecons : Nullable; 2810 2811 struct Foo 2812 { 2813 Nullable!string bar; 2814 } 2815 2816 Foo f; 2817 formatTest(f, "Foo(Nullable.null)"); 2818 } 2819 2820 // https://issues.dlang.org/show_bug.cgi?id=19003 2821 @safe unittest 2822 { 2823 struct S 2824 { 2825 int i; 2826 2827 @disable this(); 2828 2829 invariant { assert(this.i); } 2830 2831 this(int i) @safe in { assert(i); } do { this.i = i; } 2832 2833 string toString() { return "S"; } 2834 } 2835 2836 S s = S(1); 2837 2838 format!"%s"(s); 2839 } 2840 2841 void enforceValidFormatSpec(T, Char)(scope const ref FormatSpec!Char f) 2842 { 2843 import std.format : enforceFmt; 2844 import std.range : isInputRange; 2845 import std.format.internal.write : hasToString, HasToStringResult; 2846 2847 enum overload = hasToString!(T, Char); 2848 static if ( 2849 overload != HasToStringResult.constCharSinkFormatSpec && 2850 overload != HasToStringResult.constCharSinkFormatString && 2851 overload != HasToStringResult.inCharSinkFormatSpec && 2852 overload != HasToStringResult.inCharSinkFormatString && 2853 overload != HasToStringResult.customPutWriterFormatSpec && 2854 !isInputRange!T) 2855 { 2856 enforceFmt(f.spec == 's', 2857 "Expected '%s' format specifier for type '" ~ T.stringof ~ "'"); 2858 } 2859 } 2860 2861 /* 2862 `enum`s are formatted like their base value 2863 */ 2864 void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 2865 if (is(T == enum)) 2866 { 2867 import std.array : appender; 2868 import std.range.primitives : put; 2869 2870 if (f.spec != 's') 2871 return formatValueImpl(w, cast(OriginalType!T) val, f); 2872 2873 foreach (immutable member; __traits(allMembers, T)) 2874 if (val == __traits(getMember, T, member)) 2875 return formatValueImpl(w, member, f); 2876 2877 auto w2 = appender!string(); 2878 2879 // val is not a member of T, output cast(T) rawValue instead. 2880 enum prefix = "cast(" ~ T.stringof ~ ")"; 2881 put(w2, prefix); 2882 static assert(!is(OriginalType!T == T), "OriginalType!" ~ T.stringof ~ 2883 "must not be equal to " ~ T.stringof); 2884 2885 FormatSpec!Char f2 = f; 2886 f2.width = 0; 2887 formatValueImpl(w2, cast(OriginalType!T) val, f2); 2888 writeAligned(w, w2.data, f); 2889 } 2890 2891 @safe unittest 2892 { 2893 enum A { first, second, third } 2894 formatTest(A.second, "second"); 2895 formatTest(cast(A) 72, "cast(A)72"); 2896 } 2897 @safe unittest 2898 { 2899 enum A : string { one = "uno", two = "dos", three = "tres" } 2900 formatTest(A.three, "three"); 2901 formatTest(cast(A)"mill\ón", "cast(A)mill\ón"); 2902 } 2903 @safe unittest 2904 { 2905 enum A : bool { no, yes } 2906 formatTest(A.yes, "yes"); 2907 formatTest(A.no, "no"); 2908 } 2909 @safe unittest 2910 { 2911 // Test for bug 6892 2912 enum Foo { A = 10 } 2913 formatTest("%s", Foo.A, "A"); 2914 formatTest(">%4s<", Foo.A, "> A<"); 2915 formatTest("%04d", Foo.A, "0010"); 2916 formatTest("%+2u", Foo.A, "10"); 2917 formatTest("%02x", Foo.A, "0a"); 2918 formatTest("%3o", Foo.A, " 12"); 2919 formatTest("%b", Foo.A, "1010"); 2920 } 2921 2922 @safe pure unittest 2923 { 2924 enum A { one, two, three } 2925 2926 string t1 = format("[%6s] [%-6s]", A.one, A.one); 2927 assert(t1 == "[ one] [one ]"); 2928 string t2 = format("[%10s] [%-10s]", cast(A) 10, cast(A) 10); 2929 assert(t2 == "[ cast(A)" ~ "10] [cast(A)" ~ "10 ]"); // due to bug in style checker 2930 } 2931 2932 // https://issues.dlang.org/show_bug.cgi?id=8921 2933 @safe unittest 2934 { 2935 enum E : char { A = 'a', B = 'b', C = 'c' } 2936 E[3] e = [E.A, E.B, E.C]; 2937 formatTest(e, "[A, B, C]"); 2938 2939 E[] e2 = [E.A, E.B, E.C]; 2940 formatTest(e2, "[A, B, C]"); 2941 } 2942 2943 /* 2944 Pointers are formatted as hex integers. 2945 */ 2946 void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope const(T) val, scope const ref FormatSpec!Char f) 2947 if (isPointer!T && !is(T == enum) && !hasToString!(T, Char)) 2948 { 2949 static if (is(typeof({ shared const void* p = val; }))) 2950 alias SharedOf(T) = shared(T); 2951 else 2952 alias SharedOf(T) = T; 2953 2954 const SharedOf!(void*) p = val; 2955 const pnum = () @trusted { return cast(ulong) p; }(); 2956 2957 if (f.spec == 's') 2958 { 2959 if (p is null) 2960 { 2961 writeAligned(w, "null", f); 2962 return; 2963 } 2964 FormatSpec!Char fs = f; // fs is copy for change its values. 2965 fs.spec = 'X'; 2966 formatValueImpl(w, pnum, fs); 2967 } 2968 else 2969 { 2970 import std.format : enforceFmt; 2971 enforceFmt(f.spec == 'X' || f.spec == 'x', 2972 "Expected one of %s, %x or %X for pointer type."); 2973 formatValueImpl(w, pnum, f); 2974 } 2975 } 2976 2977 @safe pure unittest 2978 { 2979 int* p; 2980 2981 string t1 = format("[%6s] [%-6s]", p, p); 2982 assert(t1 == "[ null] [null ]"); 2983 } 2984 2985 @safe pure unittest 2986 { 2987 int* p = null; 2988 formatTest(p, "null"); 2989 2990 auto q = () @trusted { return cast(void*) 0xFFEECCAA; }(); 2991 formatTest(q, "FFEECCAA"); 2992 } 2993 2994 // https://issues.dlang.org/show_bug.cgi?id=11782 2995 @safe pure unittest 2996 { 2997 import std.range : iota; 2998 2999 auto a = iota(0, 10); 3000 auto b = iota(0, 10); 3001 auto p = () @trusted { auto result = &a; return result; }(); 3002 3003 assert(format("%s",p) != format("%s",b)); 3004 } 3005 3006 @safe pure unittest 3007 { 3008 // Test for https://issues.dlang.org/show_bug.cgi?id=7869 3009 struct S 3010 { 3011 string toString() const { return ""; } 3012 } 3013 S* p = null; 3014 formatTest(p, "null"); 3015 3016 S* q = () @trusted { return cast(S*) 0xFFEECCAA; } (); 3017 formatTest(q, "FFEECCAA"); 3018 } 3019 3020 // https://issues.dlang.org/show_bug.cgi?id=9336 3021 @system pure unittest 3022 { 3023 shared int i; 3024 format("%s", &i); 3025 } 3026 3027 // https://issues.dlang.org/show_bug.cgi?id=11778 3028 @safe pure unittest 3029 { 3030 import std.exception : assertThrown; 3031 import std.format : FormatException; 3032 3033 int* p = null; 3034 assertThrown!FormatException(format("%d", p)); 3035 assertThrown!FormatException(format("%04d", () @trusted { return p + 2; } ())); 3036 } 3037 3038 // https://issues.dlang.org/show_bug.cgi?id=12505 3039 @safe pure unittest 3040 { 3041 void* p = null; 3042 formatTest("%08X", p, "00000000"); 3043 } 3044 3045 /* 3046 SIMD vectors are formatted as arrays. 3047 */ 3048 void formatValueImpl(Writer, V, Char)(auto ref Writer w, const(V) val, scope const ref FormatSpec!Char f) 3049 if (isSIMDVector!V) 3050 { 3051 formatValueImpl(w, val.array, f); 3052 } 3053 3054 @safe unittest 3055 { 3056 import core.simd; // cannot be selective, because float4 might not be defined 3057 3058 static if (is(float4)) 3059 { 3060 version (X86) 3061 { 3062 version (OSX) {/* https://issues.dlang.org/show_bug.cgi?id=17823 */} 3063 } 3064 else 3065 { 3066 float4 f; 3067 f.array[0] = 1; 3068 f.array[1] = 2; 3069 f.array[2] = 3; 3070 f.array[3] = 4; 3071 formatTest(f, "[1, 2, 3, 4]"); 3072 } 3073 } 3074 } 3075 3076 /* 3077 Delegates are formatted by `ReturnType delegate(Parameters) FunctionAttributes` 3078 3079 Known bug: Because of issue https://issues.dlang.org/show_bug.cgi?id=18269 3080 the FunctionAttributes might be wrong. 3081 */ 3082 void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope const(T), scope const ref FormatSpec!Char f) 3083 if (isDelegate!T) 3084 { 3085 formatValueImpl(w, T.stringof, f); 3086 } 3087 3088 @safe unittest 3089 { 3090 import std.array : appender; 3091 import std.format : formatValue; 3092 3093 void func() @system { __gshared int x; ++x; throw new Exception("msg"); } 3094 version (linux) 3095 { 3096 FormatSpec!char f; 3097 auto w = appender!string(); 3098 formatValue(w, &func, f); 3099 assert(w.data.length >= 15 && w.data[0 .. 15] == "void delegate()"); 3100 } 3101 } 3102 3103 // string elements are formatted like UTF-8 string literals. 3104 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 3105 if (is(StringTypeOf!T) && !hasToString!(T, Char) && !is(T == enum)) 3106 { 3107 import std.array : appender; 3108 import std.format.write : formattedWrite, formatValue; 3109 import std.range.primitives : put; 3110 import std.utf : decode, UTFException; 3111 3112 StringTypeOf!T str = val; // https://issues.dlang.org/show_bug.cgi?id=8015 3113 3114 if (f.spec == 's') 3115 { 3116 try 3117 { 3118 // ignore other specifications and quote 3119 for (size_t i = 0; i < str.length; ) 3120 { 3121 auto c = decode(str, i); 3122 // \uFFFE and \uFFFF are considered valid by isValidDchar, 3123 // so need checking for interchange. 3124 if (c == 0xFFFE || c == 0xFFFF) 3125 goto LinvalidSeq; 3126 } 3127 put(w, '\"'); 3128 for (size_t i = 0; i < str.length; ) 3129 { 3130 auto c = decode(str, i); 3131 formatChar(w, c, '"'); 3132 } 3133 put(w, '\"'); 3134 return; 3135 } 3136 catch (UTFException) 3137 { 3138 } 3139 3140 // If val contains invalid UTF sequence, formatted like HexString literal 3141 LinvalidSeq: 3142 static if (is(typeof(str[0]) : const(char))) 3143 { 3144 enum type = ""; 3145 alias IntArr = const(ubyte)[]; 3146 } 3147 else static if (is(typeof(str[0]) : const(wchar))) 3148 { 3149 enum type = "w"; 3150 alias IntArr = const(ushort)[]; 3151 } 3152 else static if (is(typeof(str[0]) : const(dchar))) 3153 { 3154 enum type = "d"; 3155 alias IntArr = const(uint)[]; 3156 } 3157 formattedWrite(w, "[%(cast(" ~ type ~ "char) 0x%X%|, %)]", cast(IntArr) str); 3158 } 3159 else 3160 formatValue(w, str, f); 3161 } 3162 3163 @safe pure unittest 3164 { 3165 import std.array : appender; 3166 import std.format.spec : singleSpec; 3167 3168 auto w = appender!string(); 3169 auto spec = singleSpec("%s"); 3170 formatElement(w, "Hello World", spec); 3171 3172 assert(w.data == "\"Hello World\""); 3173 } 3174 3175 @safe unittest 3176 { 3177 import std.array : appender; 3178 import std.format.spec : singleSpec; 3179 3180 auto w = appender!string(); 3181 auto spec = singleSpec("%s"); 3182 formatElement(w, "H", spec); 3183 3184 assert(w.data == "\"H\"", w.data); 3185 } 3186 3187 // https://issues.dlang.org/show_bug.cgi?id=15888 3188 @safe pure unittest 3189 { 3190 import std.array : appender; 3191 import std.format.spec : singleSpec; 3192 3193 ushort[] a = [0xFF_FE, 0x42]; 3194 auto w = appender!string(); 3195 auto spec = singleSpec("%s"); 3196 formatElement(w, cast(wchar[]) a, spec); 3197 assert(w.data == `[cast(wchar) 0xFFFE, cast(wchar) 0x42]`); 3198 3199 uint[] b = [0x0F_FF_FF_FF, 0x42]; 3200 w = appender!string(); 3201 spec = singleSpec("%s"); 3202 formatElement(w, cast(dchar[]) b, spec); 3203 assert(w.data == `[cast(dchar) 0xFFFFFFF, cast(dchar) 0x42]`); 3204 } 3205 3206 // Character elements are formatted like UTF-8 character literals. 3207 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 3208 if (is(CharTypeOf!T) && !is(T == enum)) 3209 { 3210 import std.range.primitives : put; 3211 import std.format.write : formatValue; 3212 3213 if (f.spec == 's') 3214 { 3215 put(w, '\''); 3216 formatChar(w, val, '\''); 3217 put(w, '\''); 3218 } 3219 else 3220 formatValue(w, val, f); 3221 } 3222 3223 // Maybe T is noncopyable struct, so receive it by 'auto ref'. 3224 void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f) 3225 if ((!is(StringTypeOf!T) || hasToString!(T, Char)) && !is(CharTypeOf!T) || is(T == enum)) 3226 { 3227 import std.format.write : formatValue; 3228 3229 formatValue(w, val, f); 3230 } 3231 3232 // Fix for https://issues.dlang.org/show_bug.cgi?id=1591 3233 int getNthInt(string kind, A...)(uint index, A args) 3234 { 3235 return getNth!(kind, isIntegral, int)(index, args); 3236 } 3237 3238 T getNth(string kind, alias Condition, T, A...)(uint index, A args) 3239 { 3240 import std.conv : text, to; 3241 import std.format : FormatException; 3242 3243 switch (index) 3244 { 3245 foreach (n, _; A) 3246 { 3247 case n: 3248 static if (Condition!(typeof(args[n]))) 3249 { 3250 return to!T(args[n]); 3251 } 3252 else 3253 { 3254 throw new FormatException( 3255 text(kind, " expected, not ", typeof(args[n]).stringof, 3256 " for argument #", index + 1)); 3257 } 3258 } 3259 default: 3260 throw new FormatException(text("Missing ", kind, " argument")); 3261 } 3262 } 3263 3264 private bool needToSwapEndianess(Char)(scope const ref FormatSpec!Char f) 3265 { 3266 import std.system : endian, Endian; 3267 3268 return endian == Endian.littleEndian && f.flPlus 3269 || endian == Endian.bigEndian && f.flDash; 3270 } 3271 3272 void writeAligned(Writer, T, Char)(auto ref Writer w, T s, scope const ref FormatSpec!Char f) 3273 if (isSomeString!T) 3274 { 3275 FormatSpec!Char fs = f; 3276 fs.flZero = false; 3277 writeAligned(w, "", "", s, fs); 3278 } 3279 3280 @safe pure unittest 3281 { 3282 import std.array : appender; 3283 import std.format : singleSpec; 3284 3285 auto w = appender!string(); 3286 auto spec = singleSpec("%s"); 3287 writeAligned(w, "a本Ä", spec); 3288 assert(w.data == "a本Ä", w.data); 3289 } 3290 3291 @safe pure unittest 3292 { 3293 import std.array : appender; 3294 import std.format : singleSpec; 3295 3296 auto w = appender!string(); 3297 auto spec = singleSpec("%10s"); 3298 writeAligned(w, "a本Ä", spec); 3299 assert(w.data == " a本Ä", "|" ~ w.data ~ "|"); 3300 } 3301 3302 @safe pure unittest 3303 { 3304 import std.array : appender; 3305 import std.format : singleSpec; 3306 3307 auto w = appender!string(); 3308 auto spec = singleSpec("%-10s"); 3309 writeAligned(w, "a本Ä", spec); 3310 assert(w.data == "a本Ä ", w.data); 3311 } 3312 3313 enum PrecisionType 3314 { 3315 none, 3316 integer, 3317 fractionalDigits, 3318 allDigits, 3319 } 3320 3321 void writeAligned(Writer, T1, T2, T3, Char)(auto ref Writer w, 3322 T1 prefix, T2 grouped, T3 suffix, scope const ref FormatSpec!Char f, 3323 bool integer_precision = false) 3324 if (isSomeString!T1 && isSomeString!T2 && isSomeString!T3) 3325 { 3326 writeAligned(w, prefix, grouped, "", suffix, f, 3327 integer_precision ? PrecisionType.integer : PrecisionType.none); 3328 } 3329 3330 void writeAligned(Writer, T1, T2, T3, T4, Char)(auto ref Writer w, 3331 T1 prefix, T2 grouped, T3 fracts, T4 suffix, scope const ref FormatSpec!Char f, 3332 PrecisionType p = PrecisionType.none) 3333 if (isSomeString!T1 && isSomeString!T2 && isSomeString!T3 && isSomeString!T4) 3334 { 3335 // writes: left padding, prefix, leading zeros, grouped, fracts, suffix, right padding 3336 3337 if (p == PrecisionType.integer && f.precision == f.UNSPECIFIED) 3338 p = PrecisionType.none; 3339 3340 import std.range.primitives : put; 3341 3342 long prefixWidth; 3343 long groupedWidth = grouped.length; // TODO: does not take graphemes into account 3344 long fractsWidth = fracts.length; // TODO: does not take graphemes into account 3345 long suffixWidth; 3346 3347 // TODO: remove this workaround which hides https://issues.dlang.org/show_bug.cgi?id=21815 3348 if (f.width > 0) 3349 { 3350 prefixWidth = getWidth(prefix); 3351 suffixWidth = getWidth(suffix); 3352 } 3353 3354 auto doGrouping = f.flSeparator && groupedWidth > 0 3355 && f.separators > 0 && f.separators != f.UNSPECIFIED; 3356 // front = number of symbols left of the leftmost separator 3357 long front = doGrouping ? (groupedWidth - 1) % f.separators + 1 : 0; 3358 // sepCount = number of separators to be inserted 3359 long sepCount = doGrouping ? (groupedWidth - 1) / f.separators : 0; 3360 3361 long trailingZeros = 0; 3362 if (p == PrecisionType.fractionalDigits) 3363 trailingZeros = f.precision - (fractsWidth - 1); 3364 if (p == PrecisionType.allDigits && f.flHash) 3365 { 3366 if (grouped != "0") 3367 trailingZeros = f.precision - (fractsWidth - 1) - groupedWidth; 3368 else 3369 { 3370 trailingZeros = f.precision - fractsWidth; 3371 foreach (i;0 .. fracts.length) 3372 if (fracts[i] != '0' && fracts[i] != '.') 3373 { 3374 trailingZeros = f.precision - (fracts.length - i); 3375 break; 3376 } 3377 } 3378 } 3379 3380 auto nodot = fracts == "." && trailingZeros == 0 && !f.flHash; 3381 3382 if (nodot) fractsWidth = 0; 3383 3384 long width = prefixWidth + sepCount + groupedWidth + fractsWidth + trailingZeros + suffixWidth; 3385 long delta = f.width - width; 3386 3387 // with integers, precision is considered the minimum number of digits; 3388 // if digits are missing, we have to recalculate everything 3389 long pregrouped = 0; 3390 if (p == PrecisionType.integer && groupedWidth < f.precision) 3391 { 3392 pregrouped = f.precision - groupedWidth; 3393 delta -= pregrouped; 3394 if (doGrouping) 3395 { 3396 front = ((front - 1) + pregrouped) % f.separators + 1; 3397 delta -= (f.precision - 1) / f.separators - sepCount; 3398 } 3399 } 3400 3401 // left padding 3402 if ((!f.flZero || p == PrecisionType.integer) && delta > 0) 3403 { 3404 if (f.flEqual) 3405 { 3406 foreach (i ; 0 .. delta / 2 + ((delta % 2 == 1 && !f.flDash) ? 1 : 0)) 3407 put(w, ' '); 3408 } 3409 else if (!f.flDash) 3410 { 3411 foreach (i ; 0 .. delta) 3412 put(w, ' '); 3413 } 3414 } 3415 3416 // prefix 3417 put(w, prefix); 3418 3419 // leading grouped zeros 3420 if (f.flZero && p != PrecisionType.integer && !f.flDash && delta > 0) 3421 { 3422 if (doGrouping) 3423 { 3424 // front2 and sepCount2 are the same as above for the leading zeros 3425 long front2 = (delta + front - 1) % (f.separators + 1) + 1; 3426 long sepCount2 = (delta + front - 1) / (f.separators + 1); 3427 delta -= sepCount2; 3428 3429 // according to POSIX: if the first symbol is a separator, 3430 // an additional zero is put left of it, even if that means, that 3431 // the total width is one more then specified 3432 if (front2 > f.separators) { front2 = 1; } 3433 3434 foreach (i ; 0 .. delta) 3435 { 3436 if (front2 == 0) 3437 { 3438 put(w, f.separatorChar); 3439 front2 = f.separators; 3440 } 3441 front2--; 3442 3443 put(w, '0'); 3444 } 3445 3446 // separator between zeros and grouped 3447 if (front == f.separators) 3448 put(w, f.separatorChar); 3449 } 3450 else 3451 foreach (i ; 0 .. delta) 3452 put(w, '0'); 3453 } 3454 3455 // grouped content 3456 if (doGrouping) 3457 { 3458 // TODO: this does not take graphemes into account 3459 foreach (i;0 .. pregrouped + grouped.length) 3460 { 3461 if (front == 0) 3462 { 3463 put(w, f.separatorChar); 3464 front = f.separators; 3465 } 3466 front--; 3467 3468 put(w, i < pregrouped ? '0' : grouped[cast(size_t) (i - pregrouped)]); 3469 } 3470 } 3471 else 3472 { 3473 foreach (i;0 .. pregrouped) 3474 put(w, '0'); 3475 put(w, grouped); 3476 } 3477 3478 // fracts 3479 if (!nodot) 3480 put(w, fracts); 3481 3482 // trailing zeros 3483 foreach (i ; 0 .. trailingZeros) 3484 put(w, '0'); 3485 3486 // suffix 3487 put(w, suffix); 3488 3489 // right padding 3490 if (delta > 0) 3491 { 3492 if (f.flEqual) 3493 { 3494 foreach (i ; 0 .. delta / 2 + ((delta % 2 == 1 && f.flDash) ? 1 : 0)) 3495 put(w, ' '); 3496 } 3497 else if (f.flDash) 3498 { 3499 foreach (i ; 0 .. delta) 3500 put(w, ' '); 3501 } 3502 } 3503 } 3504 3505 @safe pure unittest 3506 { 3507 import std.array : appender; 3508 import std.format : singleSpec; 3509 3510 auto w = appender!string(); 3511 auto spec = singleSpec("%s"); 3512 writeAligned(w, "pre", "grouping", "suf", spec); 3513 assert(w.data == "pregroupingsuf", w.data); 3514 3515 w = appender!string(); 3516 spec = singleSpec("%20s"); 3517 writeAligned(w, "pre", "grouping", "suf", spec); 3518 assert(w.data == " pregroupingsuf", w.data); 3519 3520 w = appender!string(); 3521 spec = singleSpec("%-20s"); 3522 writeAligned(w, "pre", "grouping", "suf", spec); 3523 assert(w.data == "pregroupingsuf ", w.data); 3524 3525 w = appender!string(); 3526 spec = singleSpec("%020s"); 3527 writeAligned(w, "pre", "grouping", "suf", spec); 3528 assert(w.data == "pre000000groupingsuf", w.data); 3529 3530 w = appender!string(); 3531 spec = singleSpec("%-020s"); 3532 writeAligned(w, "pre", "grouping", "suf", spec); 3533 assert(w.data == "pregroupingsuf ", w.data); 3534 3535 w = appender!string(); 3536 spec = singleSpec("%20,1s"); 3537 writeAligned(w, "pre", "grouping", "suf", spec); 3538 assert(w.data == "preg,r,o,u,p,i,n,gsuf", w.data); 3539 3540 w = appender!string(); 3541 spec = singleSpec("%20,2s"); 3542 writeAligned(w, "pre", "grouping", "suf", spec); 3543 assert(w.data == " pregr,ou,pi,ngsuf", w.data); 3544 3545 w = appender!string(); 3546 spec = singleSpec("%20,3s"); 3547 writeAligned(w, "pre", "grouping", "suf", spec); 3548 assert(w.data == " pregr,oup,ingsuf", w.data); 3549 3550 w = appender!string(); 3551 spec = singleSpec("%20,10s"); 3552 writeAligned(w, "pre", "grouping", "suf", spec); 3553 assert(w.data == " pregroupingsuf", w.data); 3554 3555 w = appender!string(); 3556 spec = singleSpec("%020,1s"); 3557 writeAligned(w, "pre", "grouping", "suf", spec); 3558 assert(w.data == "preg,r,o,u,p,i,n,gsuf", w.data); 3559 3560 w = appender!string(); 3561 spec = singleSpec("%020,2s"); 3562 writeAligned(w, "pre", "grouping", "suf", spec); 3563 assert(w.data == "pre00,gr,ou,pi,ngsuf", w.data); 3564 3565 w = appender!string(); 3566 spec = singleSpec("%020,3s"); 3567 writeAligned(w, "pre", "grouping", "suf", spec); 3568 assert(w.data == "pre00,0gr,oup,ingsuf", w.data); 3569 3570 w = appender!string(); 3571 spec = singleSpec("%020,10s"); 3572 writeAligned(w, "pre", "grouping", "suf", spec); 3573 assert(w.data == "pre000,00groupingsuf", w.data); 3574 3575 w = appender!string(); 3576 spec = singleSpec("%021,3s"); 3577 writeAligned(w, "pre", "grouping", "suf", spec); 3578 assert(w.data == "pre000,0gr,oup,ingsuf", w.data); 3579 3580 // According to https://github.com/dlang/phobos/pull/7112 this 3581 // is defined by POSIX standard: 3582 w = appender!string(); 3583 spec = singleSpec("%022,3s"); 3584 writeAligned(w, "pre", "grouping", "suf", spec); 3585 assert(w.data == "pre0,000,0gr,oup,ingsuf", w.data); 3586 3587 w = appender!string(); 3588 spec = singleSpec("%023,3s"); 3589 writeAligned(w, "pre", "grouping", "suf", spec); 3590 assert(w.data == "pre0,000,0gr,oup,ingsuf", w.data); 3591 3592 w = appender!string(); 3593 spec = singleSpec("%,3s"); 3594 writeAligned(w, "pre", "grouping", "suf", spec); 3595 assert(w.data == "pregr,oup,ingsuf", w.data); 3596 } 3597 3598 @safe pure unittest 3599 { 3600 import std.array : appender; 3601 import std.format : singleSpec; 3602 3603 auto w = appender!string(); 3604 auto spec = singleSpec("%.10s"); 3605 writeAligned(w, "pre", "grouping", "suf", spec, true); 3606 assert(w.data == "pre00groupingsuf", w.data); 3607 3608 w = appender!string(); 3609 spec = singleSpec("%.10,3s"); 3610 writeAligned(w, "pre", "grouping", "suf", spec, true); 3611 assert(w.data == "pre0,0gr,oup,ingsuf", w.data); 3612 3613 w = appender!string(); 3614 spec = singleSpec("%25.10,3s"); 3615 writeAligned(w, "pre", "grouping", "suf", spec, true); 3616 assert(w.data == " pre0,0gr,oup,ingsuf", w.data); 3617 3618 // precision has precedence over zero flag 3619 w = appender!string(); 3620 spec = singleSpec("%025.12,3s"); 3621 writeAligned(w, "pre", "grouping", "suf", spec, true); 3622 assert(w.data == " pre000,0gr,oup,ingsuf", w.data); 3623 3624 w = appender!string(); 3625 spec = singleSpec("%025.13,3s"); 3626 writeAligned(w, "pre", "grouping", "suf", spec, true); 3627 assert(w.data == " pre0,000,0gr,oup,ingsuf", w.data); 3628 } 3629 3630 @safe unittest 3631 { 3632 assert(format("%,d", 1000) == "1,000"); 3633 assert(format("%,f", 1234567.891011) == "1,234,567.891011"); 3634 assert(format("%,?d", '?', 1000) == "1?000"); 3635 assert(format("%,1d", 1000) == "1,0,0,0", format("%,1d", 1000)); 3636 assert(format("%,*d", 4, -12345) == "-1,2345"); 3637 assert(format("%,*?d", 4, '_', -12345) == "-1_2345"); 3638 assert(format("%,6?d", '_', -12345678) == "-12_345678"); 3639 assert(format("%12,3.3f", 1234.5678) == " 1,234.568", "'" ~ 3640 format("%12,3.3f", 1234.5678) ~ "'"); 3641 } 3642 3643 private long getWidth(T)(T s) 3644 { 3645 import std.algorithm.searching : all; 3646 import std.uni : graphemeStride; 3647 3648 // check for non-ascii character 3649 if (s.all!(a => a <= 0x7F)) return s.length; 3650 3651 //TODO: optimize this 3652 long width = 0; 3653 for (size_t i; i < s.length; i += graphemeStride(s, i)) 3654 ++width; 3655 return width; 3656 } 3657 3658 enum RoundingClass { ZERO, LOWER, FIVE, UPPER } 3659 enum RoundingMode { up, down, toZero, toNearestTiesToEven, toNearestTiesAwayFromZero } 3660 3661 bool round(T)(ref T sequence, size_t left, size_t right, RoundingClass type, bool negative, char max = '9') 3662 in (left >= 0) // should be left > 0, but if you know ahead, that there's no carry, left == 0 is fine 3663 in (left < sequence.length) 3664 in (right >= 0) 3665 in (right <= sequence.length) 3666 in (right >= left) 3667 in (max == '9' || max == 'f' || max == 'F') 3668 { 3669 import std.math.hardware; 3670 3671 auto mode = RoundingMode.toNearestTiesToEven; 3672 3673 if (!__ctfe) 3674 { 3675 // std.math's FloatingPointControl isn't available on all target platforms 3676 static if (is(FloatingPointControl)) 3677 { 3678 switch (FloatingPointControl.rounding) 3679 { 3680 case FloatingPointControl.roundUp: 3681 mode = RoundingMode.up; 3682 break; 3683 case FloatingPointControl.roundDown: 3684 mode = RoundingMode.down; 3685 break; 3686 case FloatingPointControl.roundToZero: 3687 mode = RoundingMode.toZero; 3688 break; 3689 case FloatingPointControl.roundToNearest: 3690 mode = RoundingMode.toNearestTiesToEven; 3691 break; 3692 default: assert(false, "Unknown floating point rounding mode"); 3693 } 3694 } 3695 } 3696 3697 bool roundUp = false; 3698 if (mode == RoundingMode.up) 3699 roundUp = type != RoundingClass.ZERO && !negative; 3700 else if (mode == RoundingMode.down) 3701 roundUp = type != RoundingClass.ZERO && negative; 3702 else if (mode == RoundingMode.toZero) 3703 roundUp = false; 3704 else 3705 { 3706 roundUp = type == RoundingClass.UPPER; 3707 3708 if (type == RoundingClass.FIVE) 3709 { 3710 // IEEE754 allows for two different ways of implementing roundToNearest: 3711 3712 if (mode == RoundingMode.toNearestTiesAwayFromZero) 3713 roundUp = true; 3714 else 3715 { 3716 // Round to nearest, ties to even 3717 auto last = sequence[right - 1]; 3718 if (last == '.') last = sequence[right - 2]; 3719 roundUp = (last <= '9' && last % 2 != 0) || (last > '9' && last % 2 == 0); 3720 } 3721 } 3722 } 3723 3724 if (!roundUp) return false; 3725 3726 foreach_reverse (i;left .. right) 3727 { 3728 if (sequence[i] == '.') continue; 3729 if (sequence[i] == max) 3730 sequence[i] = '0'; 3731 else 3732 { 3733 if (max != '9' && sequence[i] == '9') 3734 sequence[i] = max == 'f' ? 'a' : 'A'; 3735 else 3736 sequence[i]++; 3737 return false; 3738 } 3739 } 3740 3741 sequence[left - 1] = '1'; 3742 return true; 3743 } 3744 3745 @safe unittest 3746 { 3747 char[10] c; 3748 size_t left = 5; 3749 size_t right = 8; 3750 3751 c[4 .. 8] = "x.99"; 3752 assert(round(c, left, right, RoundingClass.UPPER, false) == true); 3753 assert(c[4 .. 8] == "1.00"); 3754 3755 c[4 .. 8] = "x.99"; 3756 assert(round(c, left, right, RoundingClass.FIVE, false) == true); 3757 assert(c[4 .. 8] == "1.00"); 3758 3759 c[4 .. 8] = "x.99"; 3760 assert(round(c, left, right, RoundingClass.LOWER, false) == false); 3761 assert(c[4 .. 8] == "x.99"); 3762 3763 c[4 .. 8] = "x.99"; 3764 assert(round(c, left, right, RoundingClass.ZERO, false) == false); 3765 assert(c[4 .. 8] == "x.99"); 3766 3767 import std.math.hardware; 3768 static if (is(FloatingPointControl)) 3769 { 3770 FloatingPointControl fpctrl; 3771 3772 fpctrl.rounding = FloatingPointControl.roundUp; 3773 3774 c[4 .. 8] = "x.99"; 3775 assert(round(c, left, right, RoundingClass.UPPER, false) == true); 3776 assert(c[4 .. 8] == "1.00"); 3777 3778 c[4 .. 8] = "x.99"; 3779 assert(round(c, left, right, RoundingClass.FIVE, false) == true); 3780 assert(c[4 .. 8] == "1.00"); 3781 3782 c[4 .. 8] = "x.99"; 3783 assert(round(c, left, right, RoundingClass.LOWER, false) == true); 3784 assert(c[4 .. 8] == "1.00"); 3785 3786 c[4 .. 8] = "x.99"; 3787 assert(round(c, left, right, RoundingClass.ZERO, false) == false); 3788 assert(c[4 .. 8] == "x.99"); 3789 3790 fpctrl.rounding = FloatingPointControl.roundDown; 3791 3792 c[4 .. 8] = "x.99"; 3793 assert(round(c, left, right, RoundingClass.UPPER, false) == false); 3794 assert(c[4 .. 8] == "x.99"); 3795 3796 c[4 .. 8] = "x.99"; 3797 assert(round(c, left, right, RoundingClass.FIVE, false) == false); 3798 assert(c[4 .. 8] == "x.99"); 3799 3800 c[4 .. 8] = "x.99"; 3801 assert(round(c, left, right, RoundingClass.LOWER, false) == false); 3802 assert(c[4 .. 8] == "x.99"); 3803 3804 c[4 .. 8] = "x.99"; 3805 assert(round(c, left, right, RoundingClass.ZERO, false) == false); 3806 assert(c[4 .. 8] == "x.99"); 3807 3808 fpctrl.rounding = FloatingPointControl.roundToZero; 3809 3810 c[4 .. 8] = "x.99"; 3811 assert(round(c, left, right, RoundingClass.UPPER, false) == false); 3812 assert(c[4 .. 8] == "x.99"); 3813 3814 c[4 .. 8] = "x.99"; 3815 assert(round(c, left, right, RoundingClass.FIVE, false) == false); 3816 assert(c[4 .. 8] == "x.99"); 3817 3818 c[4 .. 8] = "x.99"; 3819 assert(round(c, left, right, RoundingClass.LOWER, false) == false); 3820 assert(c[4 .. 8] == "x.99"); 3821 3822 c[4 .. 8] = "x.99"; 3823 assert(round(c, left, right, RoundingClass.ZERO, false) == false); 3824 assert(c[4 .. 8] == "x.99"); 3825 } 3826 } 3827 3828 @safe unittest 3829 { 3830 char[10] c; 3831 size_t left = 5; 3832 size_t right = 8; 3833 3834 c[4 .. 8] = "x8.5"; 3835 assert(round(c, left, right, RoundingClass.UPPER, true) == false); 3836 assert(c[4 .. 8] == "x8.6"); 3837 3838 c[4 .. 8] = "x8.5"; 3839 assert(round(c, left, right, RoundingClass.FIVE, true) == false); 3840 assert(c[4 .. 8] == "x8.6"); 3841 3842 c[4 .. 8] = "x8.4"; 3843 assert(round(c, left, right, RoundingClass.FIVE, true) == false); 3844 assert(c[4 .. 8] == "x8.4"); 3845 3846 c[4 .. 8] = "x8.5"; 3847 assert(round(c, left, right, RoundingClass.LOWER, true) == false); 3848 assert(c[4 .. 8] == "x8.5"); 3849 3850 c[4 .. 8] = "x8.5"; 3851 assert(round(c, left, right, RoundingClass.ZERO, true) == false); 3852 assert(c[4 .. 8] == "x8.5"); 3853 3854 import std.math.hardware; 3855 static if (is(FloatingPointControl)) 3856 { 3857 FloatingPointControl fpctrl; 3858 3859 fpctrl.rounding = FloatingPointControl.roundUp; 3860 3861 c[4 .. 8] = "x8.5"; 3862 assert(round(c, left, right, RoundingClass.UPPER, true) == false); 3863 assert(c[4 .. 8] == "x8.5"); 3864 3865 c[4 .. 8] = "x8.5"; 3866 assert(round(c, left, right, RoundingClass.FIVE, true) == false); 3867 assert(c[4 .. 8] == "x8.5"); 3868 3869 c[4 .. 8] = "x8.5"; 3870 assert(round(c, left, right, RoundingClass.LOWER, true) == false); 3871 assert(c[4 .. 8] == "x8.5"); 3872 3873 c[4 .. 8] = "x8.5"; 3874 assert(round(c, left, right, RoundingClass.ZERO, true) == false); 3875 assert(c[4 .. 8] == "x8.5"); 3876 3877 fpctrl.rounding = FloatingPointControl.roundDown; 3878 3879 c[4 .. 8] = "x8.5"; 3880 assert(round(c, left, right, RoundingClass.UPPER, true) == false); 3881 assert(c[4 .. 8] == "x8.6"); 3882 3883 c[4 .. 8] = "x8.5"; 3884 assert(round(c, left, right, RoundingClass.FIVE, true) == false); 3885 assert(c[4 .. 8] == "x8.6"); 3886 3887 c[4 .. 8] = "x8.5"; 3888 assert(round(c, left, right, RoundingClass.LOWER, true) == false); 3889 assert(c[4 .. 8] == "x8.6"); 3890 3891 c[4 .. 8] = "x8.5"; 3892 assert(round(c, left, right, RoundingClass.ZERO, true) == false); 3893 assert(c[4 .. 8] == "x8.5"); 3894 3895 fpctrl.rounding = FloatingPointControl.roundToZero; 3896 3897 c[4 .. 8] = "x8.5"; 3898 assert(round(c, left, right, RoundingClass.UPPER, true) == false); 3899 assert(c[4 .. 8] == "x8.5"); 3900 3901 c[4 .. 8] = "x8.5"; 3902 assert(round(c, left, right, RoundingClass.FIVE, true) == false); 3903 assert(c[4 .. 8] == "x8.5"); 3904 3905 c[4 .. 8] = "x8.5"; 3906 assert(round(c, left, right, RoundingClass.LOWER, true) == false); 3907 assert(c[4 .. 8] == "x8.5"); 3908 3909 c[4 .. 8] = "x8.5"; 3910 assert(round(c, left, right, RoundingClass.ZERO, true) == false); 3911 assert(c[4 .. 8] == "x8.5"); 3912 } 3913 } 3914 3915 @safe unittest 3916 { 3917 char[10] c; 3918 size_t left = 5; 3919 size_t right = 8; 3920 3921 c[4 .. 8] = "x8.9"; 3922 assert(round(c, left, right, RoundingClass.UPPER, true, 'f') == false); 3923 assert(c[4 .. 8] == "x8.a"); 3924 3925 c[4 .. 8] = "x8.9"; 3926 assert(round(c, left, right, RoundingClass.UPPER, true, 'F') == false); 3927 assert(c[4 .. 8] == "x8.A"); 3928 3929 c[4 .. 8] = "x8.f"; 3930 assert(round(c, left, right, RoundingClass.UPPER, true, 'f') == false); 3931 assert(c[4 .. 8] == "x9.0"); 3932 } 3933 3934 version (StdUnittest) 3935 private void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__) 3936 { 3937 formatTest(val, [expected], ln, fn); 3938 } 3939 3940 version (StdUnittest) 3941 private void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__) @safe 3942 { 3943 formatTest(fmt, val, [expected], ln, fn); 3944 } 3945 3946 version (StdUnittest) 3947 private void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) 3948 { 3949 import core.exception : AssertError; 3950 import std.algorithm.searching : canFind; 3951 import std.array : appender; 3952 import std.conv : text; 3953 import std.exception : enforce; 3954 import std.format.write : formatValue; 3955 3956 FormatSpec!char f; 3957 auto w = appender!string(); 3958 formatValue(w, val, f); 3959 enforce!AssertError(expected.canFind(w.data), 3960 text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln); 3961 } 3962 3963 version (StdUnittest) 3964 private void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) @safe 3965 { 3966 import core.exception : AssertError; 3967 import std.algorithm.searching : canFind; 3968 import std.array : appender; 3969 import std.conv : text; 3970 import std.exception : enforce; 3971 import std.format.write : formattedWrite; 3972 3973 auto w = appender!string(); 3974 formattedWrite(w, fmt, val); 3975 enforce!AssertError(expected.canFind(w.data), 3976 text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln); 3977 }