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 $(H3 $(LNAME2 aggregates, Structs, Unions, Classes, 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 if (spec.indexEnd == spec.indexEnd.max) 652 break; 653 else if (spec.indexEnd == spec.indexStart) 654 throw new FormatException( 655 text("Positional specifier %", spec.indexStart, '$', spec.spec, 656 " index exceeds ", Args.length)); 657 else 658 throw new FormatException( 659 text("Positional specifier %", spec.indexStart, ":", spec.indexEnd, '$', spec.spec, 660 " index exceeds ", Args.length)); 661 } 662 } 663 return currentArg; 664 } 665 666 /// 667 @safe pure unittest 668 { 669 import std.array : appender; 670 671 auto writer1 = appender!string(); 672 formattedWrite(writer1, "%s is the ultimate %s.", 42, "answer"); 673 assert(writer1[] == "42 is the ultimate answer."); 674 675 auto writer2 = appender!string(); 676 formattedWrite(writer2, "Increase: %7.2f %%", 17.4285); 677 assert(writer2[] == "Increase: 17.43 %"); 678 } 679 680 /// ditto 681 uint formattedWrite(alias fmt, Writer, Args...)(auto ref Writer w, Args args) 682 if (isSomeString!(typeof(fmt))) 683 { 684 import std.format : checkFormatException; 685 686 alias e = checkFormatException!(fmt, Args); 687 static assert(!e, e); 688 return .formattedWrite(w, fmt, args); 689 } 690 691 /// The format string can be checked at compile-time: 692 @safe pure unittest 693 { 694 import std.array : appender; 695 696 auto writer = appender!string(); 697 writer.formattedWrite!"%d is the ultimate %s."(42, "answer"); 698 assert(writer[] == "42 is the ultimate answer."); 699 700 // This line doesn't compile, because 3.14 cannot be formatted with %d: 701 // writer.formattedWrite!"%d is the ultimate %s."(3.14, "answer"); 702 } 703 704 @safe pure unittest 705 { 706 import std.array : appender; 707 708 auto stream = appender!string(); 709 formattedWrite(stream, "%s", 1.1); 710 assert(stream.data == "1.1", stream.data); 711 } 712 713 @safe pure unittest 714 { 715 import std.array; 716 717 auto w = appender!string(); 718 formattedWrite(w, "%s %d", "@safe/pure", 42); 719 assert(w.data == "@safe/pure 42"); 720 } 721 722 @safe pure unittest 723 { 724 char[20] buf; 725 auto w = buf[]; 726 formattedWrite(w, "%s %d", "@safe/pure", 42); 727 assert(buf[0 .. $ - w.length] == "@safe/pure 42"); 728 } 729 730 @safe pure unittest 731 { 732 import std.algorithm.iteration : map; 733 import std.array : appender; 734 735 auto stream = appender!string(); 736 formattedWrite(stream, "%s", map!"a*a"([2, 3, 5])); 737 assert(stream.data == "[4, 9, 25]", stream.data); 738 739 // Test shared data. 740 stream = appender!string(); 741 shared int s = 6; 742 formattedWrite(stream, "%s", s); 743 assert(stream.data == "6"); 744 } 745 746 @safe pure unittest 747 { 748 // testing positional parameters 749 import std.array : appender; 750 import std.exception : collectExceptionMsg; 751 import std.format : FormatException; 752 753 auto w = appender!(char[])(); 754 formattedWrite(w, 755 "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated", 756 42, 0); 757 assert(w.data == "Numbers 0 and 42 are reversed and 420 repeated", 758 w.data); 759 assert(collectExceptionMsg!FormatException(formattedWrite(w, "%1$s, %3$s", 1, 2)) 760 == "Positional specifier %3$s index exceeds 2"); 761 762 w.clear(); 763 formattedWrite(w, "asd%s", 23); 764 assert(w.data == "asd23", w.data); 765 w.clear(); 766 formattedWrite(w, "%s%s", 23, 45); 767 assert(w.data == "2345", w.data); 768 } 769 770 // https://issues.dlang.org/show_bug.cgi?id=3479 771 @safe unittest 772 { 773 import std.array : appender; 774 775 auto stream = appender!(char[])(); 776 formattedWrite(stream, "%2$.*1$d", 12, 10); 777 assert(stream.data == "000000000010", stream.data); 778 } 779 780 // https://issues.dlang.org/show_bug.cgi?id=6893 781 @safe unittest 782 { 783 import std.array : appender; 784 785 enum E : ulong { A, B, C } 786 auto stream = appender!(char[])(); 787 formattedWrite(stream, "%s", E.C); 788 assert(stream.data == "C"); 789 } 790 791 @safe pure unittest 792 { 793 import std.array : appender; 794 795 auto stream = appender!string(); 796 formattedWrite(stream, "%u", 42); 797 assert(stream.data == "42", stream.data); 798 } 799 800 @safe pure unittest 801 { 802 // testing raw writes 803 import std.array : appender; 804 805 auto w = appender!(char[])(); 806 uint a = 0x02030405; 807 formattedWrite(w, "%+r", a); 808 assert(w.data.length == 4 && w.data[0] == 2 && w.data[1] == 3 809 && w.data[2] == 4 && w.data[3] == 5); 810 811 w.clear(); 812 formattedWrite(w, "%-r", a); 813 assert(w.data.length == 4 && w.data[0] == 5 && w.data[1] == 4 814 && w.data[2] == 3 && w.data[3] == 2); 815 } 816 817 @safe unittest 818 { 819 import std.array : appender; 820 import std.conv : text, octal; 821 822 auto stream = appender!(char[])(); 823 824 formattedWrite(stream, "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo"); 825 assert(stream.data == "hello world! true 57 ", stream.data); 826 stream.clear(); 827 828 formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan); 829 assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", stream.data); 830 stream.clear(); 831 832 formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF); 833 assert(stream.data == "1234af AFAFAFAF"); 834 stream.clear(); 835 836 formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF); 837 assert(stream.data == "100100011010010101111 25753727657"); 838 stream.clear(); 839 840 formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF); 841 assert(stream.data == "1193135 2947526575"); 842 stream.clear(); 843 844 formattedWrite(stream, "%a %A", 1.32, 6.78f); 845 assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2"); 846 stream.clear(); 847 848 formattedWrite(stream, "%#06.*f", 2, 12.345); 849 assert(stream.data == "012.35"); 850 stream.clear(); 851 852 formattedWrite(stream, "%#0*.*f", 6, 2, 12.345); 853 assert(stream.data == "012.35"); 854 stream.clear(); 855 856 const real constreal = 1; 857 formattedWrite(stream, "%g",constreal); 858 assert(stream.data == "1"); 859 stream.clear(); 860 861 formattedWrite(stream, "%7.4g:", 12.678); 862 assert(stream.data == " 12.68:"); 863 stream.clear(); 864 865 formattedWrite(stream, "%7.4g:", 12.678L); 866 assert(stream.data == " 12.68:"); 867 stream.clear(); 868 869 formattedWrite(stream, "%04f|%05d|%#05x|%#5x", -4.0, -10, 1, 1); 870 assert(stream.data == "-4.000000|-0010|0x001| 0x1", stream.data); 871 stream.clear(); 872 873 int i; 874 string s; 875 876 i = -10; 877 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 878 assert(stream.data == "-10|-10|-10|-10|-10.0000"); 879 stream.clear(); 880 881 i = -5; 882 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 883 assert(stream.data == "-5| -5|-05|-5|-5.0000"); 884 stream.clear(); 885 886 i = 0; 887 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 888 assert(stream.data == "0| 0|000|0|0.0000"); 889 stream.clear(); 890 891 i = 5; 892 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 893 assert(stream.data == "5| 5|005|5|5.0000"); 894 stream.clear(); 895 896 i = 10; 897 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 898 assert(stream.data == "10| 10|010|10|10.0000"); 899 stream.clear(); 900 901 formattedWrite(stream, "%.0d", 0); 902 assert(stream.data == "0"); 903 stream.clear(); 904 905 formattedWrite(stream, "%.g", .34); 906 assert(stream.data == "0.3"); 907 stream.clear(); 908 909 stream.clear(); 910 formattedWrite(stream, "%.0g", .34); 911 assert(stream.data == "0.3"); 912 913 stream.clear(); 914 formattedWrite(stream, "%.2g", .34); 915 assert(stream.data == "0.34"); 916 917 stream.clear(); 918 formattedWrite(stream, "%0.0008f", 1e-08); 919 assert(stream.data == "0.00000001"); 920 921 stream.clear(); 922 formattedWrite(stream, "%0.0008f", 1e-05); 923 assert(stream.data == "0.00001000"); 924 925 s = "helloworld"; 926 string r; 927 stream.clear(); 928 formattedWrite(stream, "%.2s", s[0 .. 5]); 929 assert(stream.data == "he"); 930 stream.clear(); 931 formattedWrite(stream, "%.20s", s[0 .. 5]); 932 assert(stream.data == "hello"); 933 stream.clear(); 934 formattedWrite(stream, "%8s", s[0 .. 5]); 935 assert(stream.data == " hello"); 936 937 byte[] arrbyte = new byte[4]; 938 arrbyte[0] = 100; 939 arrbyte[1] = -99; 940 arrbyte[3] = 0; 941 stream.clear(); 942 formattedWrite(stream, "%s", arrbyte); 943 assert(stream.data == "[100, -99, 0, 0]", stream.data); 944 945 ubyte[] arrubyte = new ubyte[4]; 946 arrubyte[0] = 100; 947 arrubyte[1] = 200; 948 arrubyte[3] = 0; 949 stream.clear(); 950 formattedWrite(stream, "%s", arrubyte); 951 assert(stream.data == "[100, 200, 0, 0]", stream.data); 952 953 short[] arrshort = new short[4]; 954 arrshort[0] = 100; 955 arrshort[1] = -999; 956 arrshort[3] = 0; 957 stream.clear(); 958 formattedWrite(stream, "%s", arrshort); 959 assert(stream.data == "[100, -999, 0, 0]"); 960 stream.clear(); 961 formattedWrite(stream, "%s", arrshort); 962 assert(stream.data == "[100, -999, 0, 0]"); 963 964 ushort[] arrushort = new ushort[4]; 965 arrushort[0] = 100; 966 arrushort[1] = 20_000; 967 arrushort[3] = 0; 968 stream.clear(); 969 formattedWrite(stream, "%s", arrushort); 970 assert(stream.data == "[100, 20000, 0, 0]"); 971 972 int[] arrint = new int[4]; 973 arrint[0] = 100; 974 arrint[1] = -999; 975 arrint[3] = 0; 976 stream.clear(); 977 formattedWrite(stream, "%s", arrint); 978 assert(stream.data == "[100, -999, 0, 0]"); 979 stream.clear(); 980 formattedWrite(stream, "%s", arrint); 981 assert(stream.data == "[100, -999, 0, 0]"); 982 983 long[] arrlong = new long[4]; 984 arrlong[0] = 100; 985 arrlong[1] = -999; 986 arrlong[3] = 0; 987 stream.clear(); 988 formattedWrite(stream, "%s", arrlong); 989 assert(stream.data == "[100, -999, 0, 0]"); 990 stream.clear(); 991 formattedWrite(stream, "%s",arrlong); 992 assert(stream.data == "[100, -999, 0, 0]"); 993 994 ulong[] arrulong = new ulong[4]; 995 arrulong[0] = 100; 996 arrulong[1] = 999; 997 arrulong[3] = 0; 998 stream.clear(); 999 formattedWrite(stream, "%s", arrulong); 1000 assert(stream.data == "[100, 999, 0, 0]"); 1001 1002 string[] arr2 = new string[4]; 1003 arr2[0] = "hello"; 1004 arr2[1] = "world"; 1005 arr2[3] = "foo"; 1006 stream.clear(); 1007 formattedWrite(stream, "%s", arr2); 1008 assert(stream.data == `["hello", "world", "", "foo"]`, stream.data); 1009 1010 stream.clear(); 1011 formattedWrite(stream, "%.8d", 7); 1012 assert(stream.data == "00000007"); 1013 1014 stream.clear(); 1015 formattedWrite(stream, "%.8x", 10); 1016 assert(stream.data == "0000000a"); 1017 1018 stream.clear(); 1019 formattedWrite(stream, "%-3d", 7); 1020 assert(stream.data == "7 "); 1021 1022 stream.clear(); 1023 formattedWrite(stream, "%*d", -3, 7); 1024 assert(stream.data == "7 "); 1025 1026 stream.clear(); 1027 formattedWrite(stream, "%.*d", -3, 7); 1028 assert(stream.data == "7"); 1029 1030 stream.clear(); 1031 formattedWrite(stream, "%s", "abc"c); 1032 assert(stream.data == "abc"); 1033 stream.clear(); 1034 formattedWrite(stream, "%s", "def"w); 1035 assert(stream.data == "def", text(stream.data.length)); 1036 stream.clear(); 1037 formattedWrite(stream, "%s", "ghi"d); 1038 assert(stream.data == "ghi"); 1039 1040 @trusted void* deadBeef() { return cast(void*) 0xDEADBEEF; } 1041 stream.clear(); 1042 formattedWrite(stream, "%s", deadBeef()); 1043 assert(stream.data == "DEADBEEF", stream.data); 1044 1045 stream.clear(); 1046 formattedWrite(stream, "%#x", 0xabcd); 1047 assert(stream.data == "0xabcd"); 1048 stream.clear(); 1049 formattedWrite(stream, "%#X", 0xABCD); 1050 assert(stream.data == "0XABCD"); 1051 1052 stream.clear(); 1053 formattedWrite(stream, "%#o", octal!12345); 1054 assert(stream.data == "012345"); 1055 stream.clear(); 1056 formattedWrite(stream, "%o", 9); 1057 assert(stream.data == "11"); 1058 1059 stream.clear(); 1060 formattedWrite(stream, "%+d", 123); 1061 assert(stream.data == "+123"); 1062 stream.clear(); 1063 formattedWrite(stream, "%+d", -123); 1064 assert(stream.data == "-123"); 1065 stream.clear(); 1066 formattedWrite(stream, "% d", 123); 1067 assert(stream.data == " 123"); 1068 stream.clear(); 1069 formattedWrite(stream, "% d", -123); 1070 assert(stream.data == "-123"); 1071 1072 stream.clear(); 1073 formattedWrite(stream, "%%"); 1074 assert(stream.data == "%"); 1075 1076 stream.clear(); 1077 formattedWrite(stream, "%d", true); 1078 assert(stream.data == "1"); 1079 stream.clear(); 1080 formattedWrite(stream, "%d", false); 1081 assert(stream.data == "0"); 1082 1083 stream.clear(); 1084 formattedWrite(stream, "%d", 'a'); 1085 assert(stream.data == "97", stream.data); 1086 wchar wc = 'a'; 1087 stream.clear(); 1088 formattedWrite(stream, "%d", wc); 1089 assert(stream.data == "97"); 1090 dchar dc = 'a'; 1091 stream.clear(); 1092 formattedWrite(stream, "%d", dc); 1093 assert(stream.data == "97"); 1094 1095 byte b = byte.max; 1096 stream.clear(); 1097 formattedWrite(stream, "%x", b); 1098 assert(stream.data == "7f"); 1099 stream.clear(); 1100 formattedWrite(stream, "%x", ++b); 1101 assert(stream.data == "80"); 1102 stream.clear(); 1103 formattedWrite(stream, "%x", ++b); 1104 assert(stream.data == "81"); 1105 1106 short sh = short.max; 1107 stream.clear(); 1108 formattedWrite(stream, "%x", sh); 1109 assert(stream.data == "7fff"); 1110 stream.clear(); 1111 formattedWrite(stream, "%x", ++sh); 1112 assert(stream.data == "8000"); 1113 stream.clear(); 1114 formattedWrite(stream, "%x", ++sh); 1115 assert(stream.data == "8001"); 1116 1117 i = int.max; 1118 stream.clear(); 1119 formattedWrite(stream, "%x", i); 1120 assert(stream.data == "7fffffff"); 1121 stream.clear(); 1122 formattedWrite(stream, "%x", ++i); 1123 assert(stream.data == "80000000"); 1124 stream.clear(); 1125 formattedWrite(stream, "%x", ++i); 1126 assert(stream.data == "80000001"); 1127 1128 stream.clear(); 1129 formattedWrite(stream, "%x", 10); 1130 assert(stream.data == "a"); 1131 stream.clear(); 1132 formattedWrite(stream, "%X", 10); 1133 assert(stream.data == "A"); 1134 stream.clear(); 1135 formattedWrite(stream, "%x", 15); 1136 assert(stream.data == "f"); 1137 stream.clear(); 1138 formattedWrite(stream, "%X", 15); 1139 assert(stream.data == "F"); 1140 1141 @trusted void ObjectTest() 1142 { 1143 Object c = null; 1144 stream.clear(); 1145 formattedWrite(stream, "%s", c); 1146 assert(stream.data == "null"); 1147 } 1148 ObjectTest(); 1149 1150 enum TestEnum 1151 { 1152 Value1, Value2 1153 } 1154 stream.clear(); 1155 formattedWrite(stream, "%s", TestEnum.Value2); 1156 assert(stream.data == "Value2", stream.data); 1157 stream.clear(); 1158 formattedWrite(stream, "%s", cast(TestEnum) 5); 1159 assert(stream.data == "cast(TestEnum)5", stream.data); 1160 1161 //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 1162 //stream.clear(); 1163 //formattedWrite(stream, "%s", aa.values); 1164 //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]"); 1165 //stream.clear(); 1166 //formattedWrite(stream, "%s", aa); 1167 //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]"); 1168 1169 static const dchar[] ds = ['a','b']; 1170 for (int j = 0; j < ds.length; ++j) 1171 { 1172 stream.clear(); formattedWrite(stream, " %d", ds[j]); 1173 if (j == 0) 1174 assert(stream.data == " 97"); 1175 else 1176 assert(stream.data == " 98"); 1177 } 1178 1179 stream.clear(); 1180 formattedWrite(stream, "%.-3d", 7); 1181 assert(stream.data == "7", ">" ~ stream.data ~ "<"); 1182 } 1183 1184 @safe unittest 1185 { 1186 import std.array : appender; 1187 import std.meta : AliasSeq; 1188 1189 immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 1190 assert(aa[3] == "hello"); 1191 assert(aa[4] == "betty"); 1192 1193 auto stream = appender!(char[])(); 1194 alias AllNumerics = 1195 AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, 1196 float, double, real); 1197 foreach (T; AllNumerics) 1198 { 1199 T value = 1; 1200 stream.clear(); 1201 formattedWrite(stream, "%s", value); 1202 assert(stream.data == "1"); 1203 } 1204 1205 stream.clear(); 1206 formattedWrite(stream, "%s", aa); 1207 } 1208 1209 // https://github.com/dlang/phobos/issues/10699 1210 @safe pure unittest 1211 { 1212 import std.array : appender; 1213 auto w = appender!(char[])(); 1214 1215 formattedWrite(w, "%1:$d", 1, 2, 3); 1216 assert(w.data == "123"); 1217 } 1218 1219 /** 1220 Formats a value of any type according to a format specifier and 1221 writes the result to an output range. 1222 1223 More details about how types are formatted, and how the format 1224 specifier influences the outcome, can be found in the definition of a 1225 $(MREF_ALTTEXT format string, std,format). 1226 1227 Params: 1228 w = an $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) where 1229 the formatted value is written to 1230 val = the value to write 1231 f = a $(REF_ALTTEXT FormatSpec, FormatSpec, std, format, spec) defining the 1232 format specifier 1233 Writer = the type of the output range `w` 1234 T = the type of value `val` 1235 Char = the character type used for `f` 1236 1237 Throws: 1238 A $(LREF FormatException) if formatting did not succeed. 1239 1240 Note: 1241 In theory this function should be `@nogc`. But with the current 1242 implementation there are some cases where allocations occur. 1243 See $(REF_ALTTEXT $(D sformat), sformat, std, format) for more details. 1244 1245 See_Also: 1246 $(LREF formattedWrite) which formats several values at once. 1247 */ 1248 void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f) 1249 { 1250 import std.format : enforceFmt; 1251 1252 enforceFmt(f.width != f.DYNAMIC && f.precision != f.DYNAMIC 1253 && f.separators != f.DYNAMIC && !f.dynamicSeparatorChar, 1254 "Dynamic argument not allowed for `formatValue`"); 1255 1256 formatValueImpl(w, val, f); 1257 } 1258 1259 /// 1260 @safe pure unittest 1261 { 1262 import std.array : appender; 1263 import std.format.spec : singleSpec; 1264 1265 auto writer = appender!string(); 1266 auto spec = singleSpec("%08b"); 1267 writer.formatValue(42, spec); 1268 assert(writer.data == "00101010"); 1269 1270 spec = singleSpec("%2s"); 1271 writer.formatValue('=', spec); 1272 assert(writer.data == "00101010 ="); 1273 1274 spec = singleSpec("%+14.6e"); 1275 writer.formatValue(42.0, spec); 1276 assert(writer.data == "00101010 = +4.200000e+01"); 1277 } 1278 1279 // https://issues.dlang.org/show_bug.cgi?id=15386 1280 @safe pure unittest 1281 { 1282 import std.array : appender; 1283 import std.format.spec : FormatSpec; 1284 import std.format : FormatException; 1285 import std.exception : assertThrown; 1286 1287 auto w = appender!(char[])(); 1288 auto dor = appender!(char[])(); 1289 auto fs = FormatSpec!char("%.*s"); 1290 fs.writeUpToNextSpec(dor); 1291 assertThrown!FormatException(formatValue(w, 0, fs)); 1292 1293 fs = FormatSpec!char("%*s"); 1294 fs.writeUpToNextSpec(dor); 1295 assertThrown!FormatException(formatValue(w, 0, fs)); 1296 1297 fs = FormatSpec!char("%,*s"); 1298 fs.writeUpToNextSpec(dor); 1299 assertThrown!FormatException(formatValue(w, 0, fs)); 1300 1301 fs = FormatSpec!char("%,?s"); 1302 fs.writeUpToNextSpec(dor); 1303 assertThrown!FormatException(formatValue(w, 0, fs)); 1304 1305 assertThrown!FormatException(formattedWrite(w, "%(%0*d%)", new int[1])); 1306 } 1307 1308 // https://issues.dlang.org/show_bug.cgi?id=22609 1309 @safe pure unittest 1310 { 1311 static enum State: ubyte { INACTIVE } 1312 static struct S { 1313 State state = State.INACTIVE; 1314 int generation = 1; 1315 alias state this; 1316 // DMDBUG: https://issues.dlang.org/show_bug.cgi?id=16657 1317 auto opEquals(S other) const { return state == other.state && generation == other.generation; } 1318 auto opEquals(State other) const { return state == other; } 1319 } 1320 1321 import std.array : appender; 1322 import std.format.spec : singleSpec; 1323 1324 auto writer = appender!string(); 1325 const spec = singleSpec("%s"); 1326 S a; 1327 writer.formatValue(a, spec); 1328 assert(writer.data == "0"); 1329 } 1330 1331 // https://issues.dlang.org/show_bug.cgi?id=23400 1332 @safe pure unittest 1333 { 1334 import std.range : nullSink; 1335 import std.format.spec : singleSpec; 1336 1337 static struct S 1338 { 1339 // non-const opEquals method 1340 bool opEquals(S rhs) { return false; } 1341 } 1342 1343 enum E { a = S() } 1344 1345 E e; 1346 auto writer = nullSink; 1347 const spec = singleSpec("%s"); 1348 writer.formatValue(e, spec); 1349 }