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