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