1 // Written in the D programming language. 2 3 /** 4 This is a submodule of $(MREF std, format). 5 6 It provides two functions for writing formatted output: $(LREF 7 formatValue) and $(LREF formattedWrite). The former writes a single 8 value. The latter writes several values at once, interspersed with 9 unformatted text. 10 11 The following combinations of format characters and types are 12 available: 13 14 $(BOOKTABLE , 15 $(TR $(TH) $(TH s) $(TH c) $(TH d, u, b, o) $(TH x, X) $(TH e, E, f, F, g, G, a, A) $(TH r) $(TH compound)) 16 $(TR $(TD `bool`) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH))) 17 $(TR $(TD `null`) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) 18 $(TR $(TD $(I integer)) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH))) 19 $(TR $(TD $(I floating point)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes) $(TD $(MDASH))) 20 $(TR $(TD $(I character)) $(TD yes) $(TD yes) $(TD yes) $(TD yes) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH))) 21 $(TR $(TD $(I string)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes)) 22 $(TR $(TD $(I array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes)) 23 $(TR $(TD $(I associative array)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes)) 24 $(TR $(TD $(I pointer)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH))) 25 $(TR $(TD $(I SIMD vectors)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes)) 26 $(TR $(TD $(I delegates)) $(TD yes) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD $(MDASH)) $(TD yes) $(TD yes)) 27 ) 28 29 Enums can be used with all format characters of the base type. 30 31 $(SECTION3 Structs$(COMMA) Unions$(COMMA) Classes$(COMMA) and Interfaces) 32 33 Aggregate types can define various `toString` functions. If this 34 function takes a $(REF_ALTTEXT FormatSpec, FormatSpec, std, format, 35 spec) or a $(I format string) as argument, the function decides 36 which format characters are accepted. If no `toString` is defined and 37 the aggregate is an $(REF_ALTTEXT input range, isInputRange, std, 38 range, primitives), it is treated like a range, that is $(B 's'), $(B 39 'r') and a compound specifier are accepted. In all other cases 40 aggregate types only accept $(B 's'). 41 42 `toString` should have one of the following signatures: 43 44 --- 45 void toString(Writer, Char)(ref Writer w, const ref FormatSpec!Char fmt) 46 void toString(Writer)(ref Writer w) 47 string toString(); 48 --- 49 50 Where `Writer` is an $(REF_ALTTEXT output range, isOutputRange, 51 std,range,primitives) which accepts characters $(LPAREN)of type 52 `Char` in the first version$(RPAREN). The template type does not have 53 to be called `Writer`. 54 55 Sometimes it's not possible to use a template, for example when 56 `toString` overrides `Object.toString`. In this case, the following 57 $(LPAREN)slower and less flexible$(RPAREN) functions can be used: 58 59 --- 60 void toString(void delegate(const(char)[]) sink, const ref FormatSpec!char fmt); 61 void toString(void delegate(const(char)[]) sink, string fmt); 62 void toString(void delegate(const(char)[]) sink); 63 --- 64 65 When several of the above `toString` versions are available, the 66 versions with `Writer` take precedence over the versions with a 67 `sink`. `string toString()` has the lowest priority. 68 69 If none of the above mentioned `toString` versions are available, the 70 aggregates will be formatted by other means, in the following 71 order: 72 73 If an aggregate is an $(REF_ALTTEXT input range, isInputRange, std, 74 range, primitives), it is formatted like an input range. 75 76 If an aggregate is a builtin type (using `alias this`), it is formatted 77 like the builtin type. 78 79 If all else fails, structs are formatted like `Type(field1, field2, ...)`, 80 classes and interfaces are formatted with their fully qualified name 81 and unions with their base name. 82 83 Copyright: Copyright The D Language Foundation 2000-2013. 84 85 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 86 87 Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, 88 Andrei Alexandrescu), and Kenji Hara 89 90 Source: $(PHOBOSSRC std/format/write.d) 91 */ 92 module std.format.write; 93 94 /** 95 `bool`s are formatted as `"true"` or `"false"` with `%s` and like the 96 `byte`s 1 and 0 with all other format characters. 97 */ 98 @safe pure unittest 99 { 100 import std.array : appender; 101 import std.format.spec : singleSpec; 102 103 auto w1 = appender!string(); 104 auto spec1 = singleSpec("%s"); 105 formatValue(w1, true, spec1); 106 107 assert(w1.data == "true"); 108 109 auto w2 = appender!string(); 110 auto spec2 = singleSpec("%#x"); 111 formatValue(w2, true, spec2); 112 113 assert(w2.data == "0x1"); 114 } 115 116 /// The `null` literal is formatted as `"null"`. 117 @safe pure unittest 118 { 119 import std.array : appender; 120 import std.format.spec : singleSpec; 121 122 auto w = appender!string(); 123 auto spec = singleSpec("%s"); 124 formatValue(w, null, spec); 125 126 assert(w.data == "null"); 127 } 128 129 /** 130 Integrals are formatted in (signed) every day notation with `%s` and 131 `%d` and as an (unsigned) image of the underlying bit representation 132 with `%b` (binary), `%u` (decimal), `%o` (octal), and `%x` (hexadecimal). 133 */ 134 @safe pure unittest 135 { 136 import std.array : appender; 137 import std.format.spec : singleSpec; 138 139 auto w1 = appender!string(); 140 auto spec1 = singleSpec("%d"); 141 formatValue(w1, -1337, spec1); 142 143 assert(w1.data == "-1337"); 144 145 auto w2 = appender!string(); 146 auto spec2 = singleSpec("%x"); 147 formatValue(w2, -1337, spec2); 148 149 assert(w2.data == "fffffac7"); 150 } 151 152 /** 153 Floating-point values are formatted in natural notation with `%f`, in 154 scientific notation with `%e`, in short notation with `%g`, and in 155 hexadecimal scientific notation with `%a`. If a rounding mode is 156 available, they are rounded according to this rounding mode, otherwise 157 they are rounded to the nearest value, ties to even. 158 */ 159 @safe unittest 160 { 161 import std.array : appender; 162 import std.format.spec : singleSpec; 163 164 auto w1 = appender!string(); 165 auto spec1 = singleSpec("%.3f"); 166 formatValue(w1, 1337.7779, spec1); 167 168 assert(w1.data == "1337.778"); 169 170 auto w2 = appender!string(); 171 auto spec2 = singleSpec("%.3e"); 172 formatValue(w2, 1337.7779, spec2); 173 174 assert(w2.data == "1.338e+03"); 175 176 auto w3 = appender!string(); 177 auto spec3 = singleSpec("%.3g"); 178 formatValue(w3, 1337.7779, spec3); 179 180 assert(w3.data == "1.34e+03"); 181 182 auto w4 = appender!string(); 183 auto spec4 = singleSpec("%.3a"); 184 formatValue(w4, 1337.7779, spec4); 185 186 assert(w4.data == "0x1.4e7p+10"); 187 } 188 189 /** 190 Individual characters (`char`, `wchar`, or `dchar`) are formatted as 191 Unicode characters with `%s` and `%c` and as integers (`ubyte`, 192 `ushort`, `uint`) with all other format characters. With 193 $(MREF_ALTTEXT compound specifiers, std,format) characters are 194 treated differently. 195 */ 196 @safe pure unittest 197 { 198 import std.array : appender; 199 import std.format.spec : singleSpec; 200 201 auto w1 = appender!string(); 202 auto spec1 = singleSpec("%c"); 203 formatValue(w1, 'ì', spec1); 204 205 assert(w1.data == "ì"); 206 207 auto w2 = appender!string(); 208 auto spec2 = singleSpec("%#x"); 209 formatValue(w2, 'ì', spec2); 210 211 assert(w2.data == "0xec"); 212 } 213 214 /** 215 Strings are formatted as a sequence of characters with `%s`. 216 Non-printable characters are not escaped. With a compound specifier 217 the string is treated like a range of characters. With $(MREF_ALTTEXT 218 compound specifiers, std,format) strings are treated differently. 219 */ 220 @safe pure unittest 221 { 222 import std.array : appender; 223 import std.format.spec : singleSpec; 224 225 auto w1 = appender!string(); 226 auto spec1 = singleSpec("%s"); 227 formatValue(w1, "hello", spec1); 228 229 assert(w1.data == "hello"); 230 231 auto w2 = appender!string(); 232 auto spec2 = singleSpec("%(%#x%|/%)"); 233 formatValue(w2, "hello", spec2); 234 235 assert(w2.data == "0x68/0x65/0x6c/0x6c/0x6f"); 236 } 237 238 /// Static arrays are formatted as dynamic arrays. 239 @safe pure unittest 240 { 241 import std.array : appender; 242 import std.format.spec : singleSpec; 243 244 auto w = appender!string(); 245 auto spec = singleSpec("%s"); 246 int[2] two = [1, 2]; 247 formatValue(w, two, spec); 248 249 assert(w.data == "[1, 2]"); 250 } 251 252 /** 253 Dynamic arrays are formatted as input ranges. 254 */ 255 @safe pure unittest 256 { 257 import std.array : appender; 258 import std.format.spec : singleSpec; 259 260 auto w1 = appender!string(); 261 auto spec1 = singleSpec("%s"); 262 auto two = [1, 2]; 263 formatValue(w1, two, spec1); 264 265 assert(w1.data == "[1, 2]"); 266 267 auto w2 = appender!string(); 268 auto spec2 = singleSpec("%(%g%|, %)"); 269 auto consts = [3.1415926, 299792458, 6.67430e-11]; 270 formatValue(w2, consts, spec2); 271 272 assert(w2.data == "3.14159, 2.99792e+08, 6.6743e-11"); 273 274 // void[] is treated like ubyte[] 275 auto w3 = appender!string(); 276 auto spec3 = singleSpec("%s"); 277 void[] val = cast(void[]) cast(ubyte[])[1, 2, 3]; 278 formatValue(w3, val, spec3); 279 280 assert(w3.data == "[1, 2, 3]"); 281 } 282 283 /** 284 Associative arrays are formatted by using `':'` and `", "` as 285 separators, enclosed by `'['` and `']'` when used with `%s`. It's 286 also possible to use a compound specifier for better control. 287 288 Please note, that the order of the elements is not defined, therefore 289 the result of this function might differ. 290 */ 291 @safe pure unittest 292 { 293 import std.array : appender; 294 import std.format.spec : singleSpec; 295 296 auto aa = [10:17.5, 20:9.99]; 297 298 auto w1 = appender!string(); 299 auto spec1 = singleSpec("%s"); 300 formatValue(w1, aa, spec1); 301 302 assert(w1.data == "[10:17.5, 20:9.99]" || w1.data == "[20:9.99, 10:17.5]"); 303 304 auto w2 = appender!string(); 305 auto spec2 = singleSpec("%(%x = %.0e%| # %)"); 306 formatValue(w2, aa, spec2); 307 308 assert(w2.data == "a = 2e+01 # 14 = 1e+01" || w2.data == "14 = 1e+01 # a = 2e+01"); 309 } 310 311 /** 312 `enum`s are formatted as their name when used with `%s` and like 313 their base value else. 314 */ 315 @safe pure unittest 316 { 317 import std.array : appender; 318 import std.format.spec : singleSpec; 319 320 enum A { first, second, third } 321 322 auto w1 = appender!string(); 323 auto spec1 = singleSpec("%s"); 324 formatValue(w1, A.second, spec1); 325 326 assert(w1.data == "second"); 327 328 auto w2 = appender!string(); 329 auto spec2 = singleSpec("%d"); 330 formatValue(w2, A.second, spec2); 331 332 assert(w2.data == "1"); 333 334 // values of an enum that have no name are formatted with %s using a cast 335 A a = A.third; 336 a++; 337 338 auto w3 = appender!string(); 339 auto spec3 = singleSpec("%s"); 340 formatValue(w3, a, spec3); 341 342 assert(w3.data == "cast(A)3"); 343 } 344 345 /** 346 `structs`, `unions`, `classes` and `interfaces` can be formatted in 347 several different ways. The following example highlights `struct` 348 formatting, however, it applies to other aggregates as well. 349 */ 350 @safe unittest 351 { 352 import std.array : appender; 353 import std.format.spec : FormatSpec, singleSpec; 354 355 // Using a `toString` with a writer 356 static struct Point1 357 { 358 import std.range.primitives : isOutputRange, put; 359 360 int x, y; 361 362 void toString(W)(ref W writer, scope const ref FormatSpec!char f) 363 if (isOutputRange!(W, char)) 364 { 365 put(writer, "("); 366 formatValue(writer, x, f); 367 put(writer, ","); 368 formatValue(writer, y, f); 369 put(writer, ")"); 370 } 371 } 372 373 auto w1 = appender!string(); 374 auto spec1 = singleSpec("%s"); 375 auto p1 = Point1(16, 11); 376 377 formatValue(w1, p1, spec1); 378 assert(w1.data == "(16,11)"); 379 380 // Using a `toString` with a sink 381 static struct Point2 382 { 383 int x, y; 384 385 void toString(scope void delegate(scope const(char)[]) @safe sink, 386 scope const FormatSpec!char fmt) const 387 { 388 sink("("); 389 sink.formatValue(x, fmt); 390 sink(","); 391 sink.formatValue(y, fmt); 392 sink(")"); 393 } 394 } 395 396 auto w2 = appender!string(); 397 auto spec2 = singleSpec("%03d"); 398 auto p2 = Point2(16,11); 399 400 formatValue(w2, p2, spec2); 401 assert(w2.data == "(016,011)"); 402 403 // Using `string toString()` 404 static struct Point3 405 { 406 int x, y; 407 408 string toString() 409 { 410 import std.conv : to; 411 412 return "(" ~ to!string(x) ~ "," ~ to!string(y) ~ ")"; 413 } 414 } 415 416 auto w3 = appender!string(); 417 auto spec3 = singleSpec("%s"); // has to be %s 418 auto p3 = Point3(16,11); 419 420 formatValue(w3, p3, spec3); 421 assert(w3.data == "(16,11)"); 422 423 // without `toString` 424 static struct Point4 425 { 426 int x, y; 427 } 428 429 auto w4 = appender!string(); 430 auto spec4 = singleSpec("%s"); // has to be %s 431 auto p4 = Point4(16,11); 432 433 formatValue(w4, p4, spec3); 434 assert(w4.data == "Point4(16, 11)"); 435 } 436 437 /// Pointers are formatted as hexadecimal integers. 438 @safe pure unittest 439 { 440 import std.array : appender; 441 import std.format.spec : singleSpec; 442 443 auto w1 = appender!string(); 444 auto spec1 = singleSpec("%s"); 445 auto p1 = () @trusted { return cast(void*) 0xFFEECCAA; } (); 446 formatValue(w1, p1, spec1); 447 448 assert(w1.data == "FFEECCAA"); 449 450 // null pointers are printed as `"null"` when used with `%s` and as hexadecimal integer else 451 auto w2 = appender!string(); 452 auto spec2 = singleSpec("%s"); 453 auto p2 = () @trusted { return cast(void*) 0x00000000; } (); 454 formatValue(w2, p2, spec2); 455 456 assert(w2.data == "null"); 457 458 auto w3 = appender!string(); 459 auto spec3 = singleSpec("%x"); 460 formatValue(w3, p2, spec3); 461 462 assert(w3.data == "0"); 463 } 464 465 /// SIMD vectors are formatted as arrays. 466 @safe unittest 467 { 468 import core.simd; // cannot be selective, because float4 might not be defined 469 import std.array : appender; 470 import std.format.spec : singleSpec; 471 472 auto w = appender!string(); 473 auto spec = singleSpec("%s"); 474 475 static if (is(float4)) 476 { 477 version (X86) {} 478 else 479 { 480 float4 f4; 481 f4.array[0] = 1; 482 f4.array[1] = 2; 483 f4.array[2] = 3; 484 f4.array[3] = 4; 485 486 formatValue(w, f4, spec); 487 assert(w.data == "[1, 2, 3, 4]"); 488 } 489 } 490 } 491 492 import std.format.internal.write; 493 494 import std.format.spec : FormatSpec; 495 import std.traits : isSomeString; 496 497 /** 498 Converts its arguments according to a format string and writes 499 the result to an output range. 500 501 The second version of `formattedWrite` takes the format string as a 502 template argument. In this case, it is checked for consistency at 503 compile-time. 504 505 Params: 506 w = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives), 507 where the formatted result is written to 508 fmt = a $(MREF_ALTTEXT format string, std,format) 509 args = a variadic list of arguments to be formatted 510 Writer = the type of the writer `w` 511 Char = character type of `fmt` 512 Args = a variadic list of types of the arguments 513 514 Returns: 515 The index of the last argument that was formatted. If no positional 516 arguments are used, this is the number of arguments that where formatted. 517 518 Throws: 519 A $(REF_ALTTEXT FormatException, FormatException, std, format) 520 if formatting did not succeed. 521 522 Note: 523 In theory this function should be `@nogc`. But with the current 524 implementation there are some cases where allocations occur. 525 See $(REF_ALTTEXT $(D sformat), sformat, std, format) for more details. 526 */ 527 uint formattedWrite(Writer, Char, Args...)(auto ref Writer w, const scope Char[] fmt, Args args) 528 { 529 import std.conv : text; 530 import std.format : enforceFmt, FormatException; 531 import std.traits : isSomeChar; 532 533 auto spec = FormatSpec!Char(fmt); 534 535 // Are we already done with formats? Then just dump each parameter in turn 536 uint currentArg = 0; 537 while (spec.writeUpToNextSpec(w)) 538 { 539 if (currentArg == Args.length && !spec.indexStart) 540 { 541 // leftover spec? 542 enforceFmt(fmt.length == 0, 543 text("Orphan format specifier: %", spec.spec)); 544 break; 545 } 546 547 if (spec.width == spec.DYNAMIC) 548 { 549 auto width = getNthInt!"integer width"(currentArg, args); 550 if (width < 0) 551 { 552 spec.flDash = true; 553 width = -width; 554 } 555 spec.width = width; 556 ++currentArg; 557 } 558 else if (spec.width < 0) 559 { 560 // means: get width as a positional parameter 561 auto index = cast(uint) -spec.width; 562 assert(index > 0, "The index must be greater than zero"); 563 auto width = getNthInt!"integer width"(index - 1, args); 564 if (currentArg < index) currentArg = index; 565 if (width < 0) 566 { 567 spec.flDash = true; 568 width = -width; 569 } 570 spec.width = width; 571 } 572 573 if (spec.precision == spec.DYNAMIC) 574 { 575 auto precision = getNthInt!"integer precision"(currentArg, args); 576 if (precision >= 0) spec.precision = precision; 577 // else negative precision is same as no precision 578 else spec.precision = spec.UNSPECIFIED; 579 ++currentArg; 580 } 581 else if (spec.precision < 0) 582 { 583 // means: get precision as a positional parameter 584 auto index = cast(uint) -spec.precision; 585 assert(index > 0, "The precision must be greater than zero"); 586 auto precision = getNthInt!"integer precision"(index- 1, args); 587 if (currentArg < index) currentArg = index; 588 if (precision >= 0) spec.precision = precision; 589 // else negative precision is same as no precision 590 else spec.precision = spec.UNSPECIFIED; 591 } 592 593 if (spec.separators == spec.DYNAMIC) 594 { 595 auto separators = getNthInt!"separator digit width"(currentArg, args); 596 spec.separators = separators; 597 ++currentArg; 598 } 599 600 if (spec.dynamicSeparatorChar) 601 { 602 auto separatorChar = 603 getNth!("separator character", isSomeChar, dchar)(currentArg, args); 604 spec.separatorChar = separatorChar; 605 spec.dynamicSeparatorChar = false; 606 ++currentArg; 607 } 608 609 if (currentArg == Args.length && !spec.indexStart) 610 { 611 // leftover spec? 612 enforceFmt(fmt.length == 0, 613 text("Orphan format specifier: %", spec.spec)); 614 break; 615 } 616 617 // Format an argument 618 // This switch uses a static foreach to generate a jump table. 619 // Currently `spec.indexStart` use the special value '0' to signal 620 // we should use the current argument. An enhancement would be to 621 // always store the index. 622 size_t index = currentArg; 623 if (spec.indexStart != 0) 624 index = spec.indexStart - 1; 625 else 626 ++currentArg; 627 SWITCH: switch (index) 628 { 629 foreach (i, Tunused; Args) 630 { 631 case i: 632 formatValue(w, args[i], spec); 633 if (currentArg < spec.indexEnd) 634 currentArg = spec.indexEnd; 635 // A little know feature of format is to format a range 636 // of arguments, e.g. `%1:3$` will format the first 3 637 // arguments. Since they have to be consecutive we can 638 // just use explicit fallthrough to cover that case. 639 if (i + 1 < spec.indexEnd) 640 { 641 // You cannot goto case if the next case is the default 642 static if (i + 1 < Args.length) 643 goto case; 644 else 645 goto default; 646 } 647 else 648 break SWITCH; 649 } 650 default: 651 throw new FormatException( 652 text("Positional specifier %", spec.indexStart, '$', spec.spec, 653 " index exceeds ", Args.length)); 654 } 655 } 656 return currentArg; 657 } 658 659 /// 660 @safe pure unittest 661 { 662 import std.array : appender; 663 664 auto writer1 = appender!string(); 665 formattedWrite(writer1, "%s is the ultimate %s.", 42, "answer"); 666 assert(writer1[] == "42 is the ultimate answer."); 667 668 auto writer2 = appender!string(); 669 formattedWrite(writer2, "Increase: %7.2f %%", 17.4285); 670 assert(writer2[] == "Increase: 17.43 %"); 671 } 672 673 /// ditto 674 uint formattedWrite(alias fmt, Writer, Args...)(auto ref Writer w, Args args) 675 if (isSomeString!(typeof(fmt))) 676 { 677 import std.format : checkFormatException; 678 679 alias e = checkFormatException!(fmt, Args); 680 static assert(!e, e); 681 return .formattedWrite(w, fmt, args); 682 } 683 684 /// The format string can be checked at compile-time: 685 @safe pure unittest 686 { 687 import std.array : appender; 688 689 auto writer = appender!string(); 690 writer.formattedWrite!"%d is the ultimate %s."(42, "answer"); 691 assert(writer[] == "42 is the ultimate answer."); 692 693 // This line doesn't compile, because 3.14 cannot be formatted with %d: 694 // writer.formattedWrite!"%d is the ultimate %s."(3.14, "answer"); 695 } 696 697 @safe pure unittest 698 { 699 import std.array : appender; 700 701 auto stream = appender!string(); 702 formattedWrite(stream, "%s", 1.1); 703 assert(stream.data == "1.1", stream.data); 704 } 705 706 @safe pure unittest 707 { 708 import std.array; 709 710 auto w = appender!string(); 711 formattedWrite(w, "%s %d", "@safe/pure", 42); 712 assert(w.data == "@safe/pure 42"); 713 } 714 715 @safe pure unittest 716 { 717 char[20] buf; 718 auto w = buf[]; 719 formattedWrite(w, "%s %d", "@safe/pure", 42); 720 assert(buf[0 .. $ - w.length] == "@safe/pure 42"); 721 } 722 723 @safe pure unittest 724 { 725 import std.algorithm.iteration : map; 726 import std.array : appender; 727 728 auto stream = appender!string(); 729 formattedWrite(stream, "%s", map!"a*a"([2, 3, 5])); 730 assert(stream.data == "[4, 9, 25]", stream.data); 731 732 // Test shared data. 733 stream = appender!string(); 734 shared int s = 6; 735 formattedWrite(stream, "%s", s); 736 assert(stream.data == "6"); 737 } 738 739 @safe pure unittest 740 { 741 // testing positional parameters 742 import std.array : appender; 743 import std.exception : collectExceptionMsg; 744 import std.format : FormatException; 745 746 auto w = appender!(char[])(); 747 formattedWrite(w, 748 "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated", 749 42, 0); 750 assert(w.data == "Numbers 0 and 42 are reversed and 420 repeated", 751 w.data); 752 assert(collectExceptionMsg!FormatException(formattedWrite(w, "%1$s, %3$s", 1, 2)) 753 == "Positional specifier %3$s index exceeds 2"); 754 755 w.clear(); 756 formattedWrite(w, "asd%s", 23); 757 assert(w.data == "asd23", w.data); 758 w.clear(); 759 formattedWrite(w, "%s%s", 23, 45); 760 assert(w.data == "2345", w.data); 761 } 762 763 // https://issues.dlang.org/show_bug.cgi?id=3479 764 @safe unittest 765 { 766 import std.array : appender; 767 768 auto stream = appender!(char[])(); 769 formattedWrite(stream, "%2$.*1$d", 12, 10); 770 assert(stream.data == "000000000010", stream.data); 771 } 772 773 // https://issues.dlang.org/show_bug.cgi?id=6893 774 @safe unittest 775 { 776 import std.array : appender; 777 778 enum E : ulong { A, B, C } 779 auto stream = appender!(char[])(); 780 formattedWrite(stream, "%s", E.C); 781 assert(stream.data == "C"); 782 } 783 784 @safe pure unittest 785 { 786 import std.array : appender; 787 788 auto stream = appender!string(); 789 formattedWrite(stream, "%u", 42); 790 assert(stream.data == "42", stream.data); 791 } 792 793 @safe pure unittest 794 { 795 // testing raw writes 796 import std.array : appender; 797 798 auto w = appender!(char[])(); 799 uint a = 0x02030405; 800 formattedWrite(w, "%+r", a); 801 assert(w.data.length == 4 && w.data[0] == 2 && w.data[1] == 3 802 && w.data[2] == 4 && w.data[3] == 5); 803 804 w.clear(); 805 formattedWrite(w, "%-r", a); 806 assert(w.data.length == 4 && w.data[0] == 5 && w.data[1] == 4 807 && w.data[2] == 3 && w.data[3] == 2); 808 } 809 810 @safe unittest 811 { 812 import std.array : appender; 813 import std.conv : text, octal; 814 815 auto stream = appender!(char[])(); 816 817 formattedWrite(stream, "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo"); 818 assert(stream.data == "hello world! true 57 ", stream.data); 819 stream.clear(); 820 821 formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan); 822 assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", stream.data); 823 stream.clear(); 824 825 formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF); 826 assert(stream.data == "1234af AFAFAFAF"); 827 stream.clear(); 828 829 formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF); 830 assert(stream.data == "100100011010010101111 25753727657"); 831 stream.clear(); 832 833 formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF); 834 assert(stream.data == "1193135 2947526575"); 835 stream.clear(); 836 837 formattedWrite(stream, "%a %A", 1.32, 6.78f); 838 assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2"); 839 stream.clear(); 840 841 formattedWrite(stream, "%#06.*f", 2, 12.345); 842 assert(stream.data == "012.35"); 843 stream.clear(); 844 845 formattedWrite(stream, "%#0*.*f", 6, 2, 12.345); 846 assert(stream.data == "012.35"); 847 stream.clear(); 848 849 const real constreal = 1; 850 formattedWrite(stream, "%g",constreal); 851 assert(stream.data == "1"); 852 stream.clear(); 853 854 formattedWrite(stream, "%7.4g:", 12.678); 855 assert(stream.data == " 12.68:"); 856 stream.clear(); 857 858 formattedWrite(stream, "%7.4g:", 12.678L); 859 assert(stream.data == " 12.68:"); 860 stream.clear(); 861 862 formattedWrite(stream, "%04f|%05d|%#05x|%#5x", -4.0, -10, 1, 1); 863 assert(stream.data == "-4.000000|-0010|0x001| 0x1", stream.data); 864 stream.clear(); 865 866 int i; 867 string s; 868 869 i = -10; 870 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 871 assert(stream.data == "-10|-10|-10|-10|-10.0000"); 872 stream.clear(); 873 874 i = -5; 875 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 876 assert(stream.data == "-5| -5|-05|-5|-5.0000"); 877 stream.clear(); 878 879 i = 0; 880 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 881 assert(stream.data == "0| 0|000|0|0.0000"); 882 stream.clear(); 883 884 i = 5; 885 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 886 assert(stream.data == "5| 5|005|5|5.0000"); 887 stream.clear(); 888 889 i = 10; 890 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 891 assert(stream.data == "10| 10|010|10|10.0000"); 892 stream.clear(); 893 894 formattedWrite(stream, "%.0d", 0); 895 assert(stream.data == "0"); 896 stream.clear(); 897 898 formattedWrite(stream, "%.g", .34); 899 assert(stream.data == "0.3"); 900 stream.clear(); 901 902 stream.clear(); 903 formattedWrite(stream, "%.0g", .34); 904 assert(stream.data == "0.3"); 905 906 stream.clear(); 907 formattedWrite(stream, "%.2g", .34); 908 assert(stream.data == "0.34"); 909 910 stream.clear(); 911 formattedWrite(stream, "%0.0008f", 1e-08); 912 assert(stream.data == "0.00000001"); 913 914 stream.clear(); 915 formattedWrite(stream, "%0.0008f", 1e-05); 916 assert(stream.data == "0.00001000"); 917 918 s = "helloworld"; 919 string r; 920 stream.clear(); 921 formattedWrite(stream, "%.2s", s[0 .. 5]); 922 assert(stream.data == "he"); 923 stream.clear(); 924 formattedWrite(stream, "%.20s", s[0 .. 5]); 925 assert(stream.data == "hello"); 926 stream.clear(); 927 formattedWrite(stream, "%8s", s[0 .. 5]); 928 assert(stream.data == " hello"); 929 930 byte[] arrbyte = new byte[4]; 931 arrbyte[0] = 100; 932 arrbyte[1] = -99; 933 arrbyte[3] = 0; 934 stream.clear(); 935 formattedWrite(stream, "%s", arrbyte); 936 assert(stream.data == "[100, -99, 0, 0]", stream.data); 937 938 ubyte[] arrubyte = new ubyte[4]; 939 arrubyte[0] = 100; 940 arrubyte[1] = 200; 941 arrubyte[3] = 0; 942 stream.clear(); 943 formattedWrite(stream, "%s", arrubyte); 944 assert(stream.data == "[100, 200, 0, 0]", stream.data); 945 946 short[] arrshort = new short[4]; 947 arrshort[0] = 100; 948 arrshort[1] = -999; 949 arrshort[3] = 0; 950 stream.clear(); 951 formattedWrite(stream, "%s", arrshort); 952 assert(stream.data == "[100, -999, 0, 0]"); 953 stream.clear(); 954 formattedWrite(stream, "%s", arrshort); 955 assert(stream.data == "[100, -999, 0, 0]"); 956 957 ushort[] arrushort = new ushort[4]; 958 arrushort[0] = 100; 959 arrushort[1] = 20_000; 960 arrushort[3] = 0; 961 stream.clear(); 962 formattedWrite(stream, "%s", arrushort); 963 assert(stream.data == "[100, 20000, 0, 0]"); 964 965 int[] arrint = new int[4]; 966 arrint[0] = 100; 967 arrint[1] = -999; 968 arrint[3] = 0; 969 stream.clear(); 970 formattedWrite(stream, "%s", arrint); 971 assert(stream.data == "[100, -999, 0, 0]"); 972 stream.clear(); 973 formattedWrite(stream, "%s", arrint); 974 assert(stream.data == "[100, -999, 0, 0]"); 975 976 long[] arrlong = new long[4]; 977 arrlong[0] = 100; 978 arrlong[1] = -999; 979 arrlong[3] = 0; 980 stream.clear(); 981 formattedWrite(stream, "%s", arrlong); 982 assert(stream.data == "[100, -999, 0, 0]"); 983 stream.clear(); 984 formattedWrite(stream, "%s",arrlong); 985 assert(stream.data == "[100, -999, 0, 0]"); 986 987 ulong[] arrulong = new ulong[4]; 988 arrulong[0] = 100; 989 arrulong[1] = 999; 990 arrulong[3] = 0; 991 stream.clear(); 992 formattedWrite(stream, "%s", arrulong); 993 assert(stream.data == "[100, 999, 0, 0]"); 994 995 string[] arr2 = new string[4]; 996 arr2[0] = "hello"; 997 arr2[1] = "world"; 998 arr2[3] = "foo"; 999 stream.clear(); 1000 formattedWrite(stream, "%s", arr2); 1001 assert(stream.data == `["hello", "world", "", "foo"]`, stream.data); 1002 1003 stream.clear(); 1004 formattedWrite(stream, "%.8d", 7); 1005 assert(stream.data == "00000007"); 1006 1007 stream.clear(); 1008 formattedWrite(stream, "%.8x", 10); 1009 assert(stream.data == "0000000a"); 1010 1011 stream.clear(); 1012 formattedWrite(stream, "%-3d", 7); 1013 assert(stream.data == "7 "); 1014 1015 stream.clear(); 1016 formattedWrite(stream, "%*d", -3, 7); 1017 assert(stream.data == "7 "); 1018 1019 stream.clear(); 1020 formattedWrite(stream, "%.*d", -3, 7); 1021 assert(stream.data == "7"); 1022 1023 stream.clear(); 1024 formattedWrite(stream, "%s", "abc"c); 1025 assert(stream.data == "abc"); 1026 stream.clear(); 1027 formattedWrite(stream, "%s", "def"w); 1028 assert(stream.data == "def", text(stream.data.length)); 1029 stream.clear(); 1030 formattedWrite(stream, "%s", "ghi"d); 1031 assert(stream.data == "ghi"); 1032 1033 @trusted void* deadBeef() { return cast(void*) 0xDEADBEEF; } 1034 stream.clear(); 1035 formattedWrite(stream, "%s", deadBeef()); 1036 assert(stream.data == "DEADBEEF", stream.data); 1037 1038 stream.clear(); 1039 formattedWrite(stream, "%#x", 0xabcd); 1040 assert(stream.data == "0xabcd"); 1041 stream.clear(); 1042 formattedWrite(stream, "%#X", 0xABCD); 1043 assert(stream.data == "0XABCD"); 1044 1045 stream.clear(); 1046 formattedWrite(stream, "%#o", octal!12345); 1047 assert(stream.data == "012345"); 1048 stream.clear(); 1049 formattedWrite(stream, "%o", 9); 1050 assert(stream.data == "11"); 1051 1052 stream.clear(); 1053 formattedWrite(stream, "%+d", 123); 1054 assert(stream.data == "+123"); 1055 stream.clear(); 1056 formattedWrite(stream, "%+d", -123); 1057 assert(stream.data == "-123"); 1058 stream.clear(); 1059 formattedWrite(stream, "% d", 123); 1060 assert(stream.data == " 123"); 1061 stream.clear(); 1062 formattedWrite(stream, "% d", -123); 1063 assert(stream.data == "-123"); 1064 1065 stream.clear(); 1066 formattedWrite(stream, "%%"); 1067 assert(stream.data == "%"); 1068 1069 stream.clear(); 1070 formattedWrite(stream, "%d", true); 1071 assert(stream.data == "1"); 1072 stream.clear(); 1073 formattedWrite(stream, "%d", false); 1074 assert(stream.data == "0"); 1075 1076 stream.clear(); 1077 formattedWrite(stream, "%d", 'a'); 1078 assert(stream.data == "97", stream.data); 1079 wchar wc = 'a'; 1080 stream.clear(); 1081 formattedWrite(stream, "%d", wc); 1082 assert(stream.data == "97"); 1083 dchar dc = 'a'; 1084 stream.clear(); 1085 formattedWrite(stream, "%d", dc); 1086 assert(stream.data == "97"); 1087 1088 byte b = byte.max; 1089 stream.clear(); 1090 formattedWrite(stream, "%x", b); 1091 assert(stream.data == "7f"); 1092 stream.clear(); 1093 formattedWrite(stream, "%x", ++b); 1094 assert(stream.data == "80"); 1095 stream.clear(); 1096 formattedWrite(stream, "%x", ++b); 1097 assert(stream.data == "81"); 1098 1099 short sh = short.max; 1100 stream.clear(); 1101 formattedWrite(stream, "%x", sh); 1102 assert(stream.data == "7fff"); 1103 stream.clear(); 1104 formattedWrite(stream, "%x", ++sh); 1105 assert(stream.data == "8000"); 1106 stream.clear(); 1107 formattedWrite(stream, "%x", ++sh); 1108 assert(stream.data == "8001"); 1109 1110 i = int.max; 1111 stream.clear(); 1112 formattedWrite(stream, "%x", i); 1113 assert(stream.data == "7fffffff"); 1114 stream.clear(); 1115 formattedWrite(stream, "%x", ++i); 1116 assert(stream.data == "80000000"); 1117 stream.clear(); 1118 formattedWrite(stream, "%x", ++i); 1119 assert(stream.data == "80000001"); 1120 1121 stream.clear(); 1122 formattedWrite(stream, "%x", 10); 1123 assert(stream.data == "a"); 1124 stream.clear(); 1125 formattedWrite(stream, "%X", 10); 1126 assert(stream.data == "A"); 1127 stream.clear(); 1128 formattedWrite(stream, "%x", 15); 1129 assert(stream.data == "f"); 1130 stream.clear(); 1131 formattedWrite(stream, "%X", 15); 1132 assert(stream.data == "F"); 1133 1134 @trusted void ObjectTest() 1135 { 1136 Object c = null; 1137 stream.clear(); 1138 formattedWrite(stream, "%s", c); 1139 assert(stream.data == "null"); 1140 } 1141 ObjectTest(); 1142 1143 enum TestEnum 1144 { 1145 Value1, Value2 1146 } 1147 stream.clear(); 1148 formattedWrite(stream, "%s", TestEnum.Value2); 1149 assert(stream.data == "Value2", stream.data); 1150 stream.clear(); 1151 formattedWrite(stream, "%s", cast(TestEnum) 5); 1152 assert(stream.data == "cast(TestEnum)5", stream.data); 1153 1154 //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 1155 //stream.clear(); 1156 //formattedWrite(stream, "%s", aa.values); 1157 //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]"); 1158 //stream.clear(); 1159 //formattedWrite(stream, "%s", aa); 1160 //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]"); 1161 1162 static const dchar[] ds = ['a','b']; 1163 for (int j = 0; j < ds.length; ++j) 1164 { 1165 stream.clear(); formattedWrite(stream, " %d", ds[j]); 1166 if (j == 0) 1167 assert(stream.data == " 97"); 1168 else 1169 assert(stream.data == " 98"); 1170 } 1171 1172 stream.clear(); 1173 formattedWrite(stream, "%.-3d", 7); 1174 assert(stream.data == "7", ">" ~ stream.data ~ "<"); 1175 } 1176 1177 @safe unittest 1178 { 1179 import std.array : appender; 1180 import std.meta : AliasSeq; 1181 1182 immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 1183 assert(aa[3] == "hello"); 1184 assert(aa[4] == "betty"); 1185 1186 auto stream = appender!(char[])(); 1187 alias AllNumerics = 1188 AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, 1189 float, double, real); 1190 foreach (T; AllNumerics) 1191 { 1192 T value = 1; 1193 stream.clear(); 1194 formattedWrite(stream, "%s", value); 1195 assert(stream.data == "1"); 1196 } 1197 1198 stream.clear(); 1199 formattedWrite(stream, "%s", aa); 1200 } 1201 1202 /** 1203 Formats a value of any type according to a format specifier and 1204 writes the result to an output range. 1205 1206 More details about how types are formatted, and how the format 1207 specifier influences the outcome, can be found in the definition of a 1208 $(MREF_ALTTEXT format string, std,format). 1209 1210 Params: 1211 w = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) where 1212 the formatted value is written to 1213 val = the value to write 1214 f = a $(REF_ALTTEXT FormatSpec, FormatSpec, std, format, spec) defining the 1215 format specifier 1216 Writer = the type of the output range `w` 1217 T = the type of value `val` 1218 Char = the character type used for `f` 1219 1220 Throws: 1221 A $(LREF FormatException) if formatting did not succeed. 1222 1223 Note: 1224 In theory this function should be `@nogc`. But with the current 1225 implementation there are some cases where allocations occur. 1226 See $(REF_ALTTEXT $(D sformat), sformat, std, format) for more details. 1227 1228 See_Also: 1229 $(LREF formattedWrite) which formats several values at once. 1230 */ 1231 void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f) 1232 { 1233 import std.format : enforceFmt; 1234 1235 enforceFmt(f.width != f.DYNAMIC && f.precision != f.DYNAMIC 1236 && f.separators != f.DYNAMIC && !f.dynamicSeparatorChar, 1237 "Dynamic argument not allowed for `formatValue`"); 1238 1239 formatValueImpl(w, val, f); 1240 } 1241 1242 /// 1243 @safe pure unittest 1244 { 1245 import std.array : appender; 1246 import std.format.spec : singleSpec; 1247 1248 auto writer = appender!string(); 1249 auto spec = singleSpec("%08b"); 1250 writer.formatValue(42, spec); 1251 assert(writer.data == "00101010"); 1252 1253 spec = singleSpec("%2s"); 1254 writer.formatValue('=', spec); 1255 assert(writer.data == "00101010 ="); 1256 1257 spec = singleSpec("%+14.6e"); 1258 writer.formatValue(42.0, spec); 1259 assert(writer.data == "00101010 = +4.200000e+01"); 1260 } 1261 1262 // https://issues.dlang.org/show_bug.cgi?id=15386 1263 @safe pure unittest 1264 { 1265 import std.array : appender; 1266 import std.format.spec : FormatSpec; 1267 import std.format : FormatException; 1268 import std.exception : assertThrown; 1269 1270 auto w = appender!(char[])(); 1271 auto dor = appender!(char[])(); 1272 auto fs = FormatSpec!char("%.*s"); 1273 fs.writeUpToNextSpec(dor); 1274 assertThrown!FormatException(formatValue(w, 0, fs)); 1275 1276 fs = FormatSpec!char("%*s"); 1277 fs.writeUpToNextSpec(dor); 1278 assertThrown!FormatException(formatValue(w, 0, fs)); 1279 1280 fs = FormatSpec!char("%,*s"); 1281 fs.writeUpToNextSpec(dor); 1282 assertThrown!FormatException(formatValue(w, 0, fs)); 1283 1284 fs = FormatSpec!char("%,?s"); 1285 fs.writeUpToNextSpec(dor); 1286 assertThrown!FormatException(formatValue(w, 0, fs)); 1287 1288 assertThrown!FormatException(formattedWrite(w, "%(%0*d%)", new int[1])); 1289 } 1290 1291 // https://issues.dlang.org/show_bug.cgi?id=22609 1292 @safe pure unittest 1293 { 1294 static enum State: ubyte { INACTIVE } 1295 static struct S { 1296 State state = State.INACTIVE; 1297 int generation = 1; 1298 alias state this; 1299 // DMDBUG: https://issues.dlang.org/show_bug.cgi?id=16657 1300 auto opEquals(S other) const { return state == other.state && generation == other.generation; } 1301 auto opEquals(State other) const { return state == other; } 1302 } 1303 1304 import std.array : appender; 1305 import std.format.spec : singleSpec; 1306 1307 auto writer = appender!string(); 1308 const spec = singleSpec("%s"); 1309 S a; 1310 writer.formatValue(a, spec); 1311 assert(writer.data == "0"); 1312 } 1313 1314 // https://issues.dlang.org/show_bug.cgi?id=23400 1315 @safe pure unittest 1316 { 1317 import std.range : nullSink; 1318 import std.format.spec : singleSpec; 1319 1320 static struct S 1321 { 1322 // non-const opEquals method 1323 bool opEquals(S rhs) { return false; } 1324 } 1325 1326 enum E { a = S() } 1327 1328 E e; 1329 auto writer = nullSink; 1330 const spec = singleSpec("%s"); 1331 writer.formatValue(e, spec); 1332 }