1// Written in the D programming language. 2 3/** 4 This module implements the formatting functionality for strings and 5 I/O. It's comparable to C99's $(D vsprintf()) and uses a similar 6 _format encoding scheme. 7 8 For an introductory look at $(B std._format)'s capabilities and how to use 9 this module see the dedicated 10 $(LINK2 http://wiki.dlang.org/Defining_custom_print_format_specifiers, DWiki article). 11 12 This module centers around two functions: 13 14$(BOOKTABLE , 15$(TR $(TH Function Name) $(TH Description) 16) 17 $(TR $(TD $(LREF formattedRead)) 18 $(TD Reads values according to the _format string from an InputRange. 19 )) 20 $(TR $(TD $(LREF formattedWrite)) 21 $(TD Formats its arguments according to the _format string and puts them 22 to an OutputRange. 23 )) 24) 25 26 Please see the documentation of function $(LREF formattedWrite) for a 27 description of the _format string. 28 29 Two functions have been added for convenience: 30 31$(BOOKTABLE , 32$(TR $(TH Function Name) $(TH Description) 33) 34 $(TR $(TD $(LREF _format)) 35 $(TD Returns a GC-allocated string with the formatting result. 36 )) 37 $(TR $(TD $(LREF sformat)) 38 $(TD Puts the formatting result into a preallocated array. 39 )) 40) 41 42 These two functions are publicly imported by $(MREF std, string) 43 to be easily available. 44 45 The functions $(LREF formatValue) and $(LREF unformatValue) are 46 used for the plumbing. 47 Copyright: Copyright Digital Mars 2000-2013. 48 49 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 50 51 Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, 52 Andrei Alexandrescu), and Kenji Hara 53 54 Source: $(PHOBOSSRC std/_format.d) 55 */ 56module std.format; 57 58//debug=format; // uncomment to turn on debugging printf's 59 60import core.vararg; 61import std.exception; 62import std.meta; 63import std.range.primitives; 64import std.traits; 65 66 67/********************************************************************** 68 * Signals a mismatch between a format and its corresponding argument. 69 */ 70class FormatException : Exception 71{ 72 @safe pure nothrow 73 this() 74 { 75 super("format error"); 76 } 77 78 @safe pure nothrow 79 this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) 80 { 81 super(msg, fn, ln, next); 82 } 83} 84 85private alias enforceFmt = enforceEx!FormatException; 86 87 88/********************************************************************** 89 Interprets variadic argument list $(D args), formats them according 90 to $(D fmt), and sends the resulting characters to $(D w). The 91 encoding of the output is the same as $(D Char). The type $(D Writer) 92 must satisfy $(D $(REF isOutputRange, std,range,primitives)!(Writer, Char)). 93 94 The variadic arguments are normally consumed in order. POSIX-style 95 $(HTTP opengroup.org/onlinepubs/009695399/functions/printf.html, 96 positional parameter syntax) is also supported. Each argument is 97 formatted into a sequence of chars according to the format 98 specification, and the characters are passed to $(D w). As many 99 arguments as specified in the format string are consumed and 100 formatted. If there are fewer arguments than format specifiers, a 101 $(D FormatException) is thrown. If there are more remaining arguments 102 than needed by the format specification, they are ignored but only 103 if at least one argument was formatted. 104 105 The format string supports the formatting of array and nested array elements 106 via the grouping format specifiers $(B %() and $(B %)). Each 107 matching pair of $(B %() and $(B %)) corresponds with a single array 108 argument. The enclosed sub-format string is applied to individual array 109 elements. The trailing portion of the sub-format string following the 110 conversion specifier for the array element is interpreted as the array 111 delimiter, and is therefore omitted following the last array element. The 112 $(B %|) specifier may be used to explicitly indicate the start of the 113 delimiter, so that the preceding portion of the string will be included 114 following the last array element. (See below for explicit examples.) 115 116 Params: 117 118 w = Output is sent to this writer. Typical output writers include 119 $(REF Appender!string, std,array) and $(REF LockingTextWriter, std,stdio). 120 121 fmt = Format string. 122 123 args = Variadic argument list. 124 125 Returns: Formatted number of arguments. 126 127 Throws: Mismatched arguments and formats result in a $(D 128 FormatException) being thrown. 129 130 Format_String: <a name="format-string">$(I Format strings)</a> 131 consist of characters interspersed with $(I format 132 specifications). Characters are simply copied to the output (such 133 as putc) after any necessary conversion to the corresponding UTF-8 134 sequence. 135 136 The format string has the following grammar: 137 138$(PRE 139$(I FormatString): 140 $(I FormatStringItem)* 141$(I FormatStringItem): 142 $(B '%%') 143 $(B '%') $(I Position) $(I Flags) $(I Width) $(I Separator) $(I Precision) $(I FormatChar) 144 $(B '%$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)') 145 $(I OtherCharacterExceptPercent) 146$(I Position): 147 $(I empty) 148 $(I Integer) $(B '$') 149$(I Flags): 150 $(I empty) 151 $(B '-') $(I Flags) 152 $(B '+') $(I Flags) 153 $(B '#') $(I Flags) 154 $(B '0') $(I Flags) 155 $(B ' ') $(I Flags) 156$(I Width): 157 $(I empty) 158 $(I Integer) 159 $(B '*') 160$(I Separator): 161 $(I empty) 162 $(B ',') 163 $(B ',') $(B '?') 164 $(B ',') $(B '*') $(B '?') 165 $(B ',') $(I Integer) $(B '?') 166 $(B ',') $(B '*') 167 $(B ',') $(I Integer) 168$(I Precision): 169 $(I empty) 170 $(B '.') 171 $(B '.') $(I Integer) 172 $(B '.*') 173$(I Integer): 174 $(I Digit) 175 $(I Digit) $(I Integer) 176$(I Digit): 177 $(B '0')|$(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9') 178$(I FormatChar): 179 $(B 's')|$(B 'c')|$(B 'b')|$(B 'd')|$(B 'o')|$(B 'x')|$(B 'X')|$(B 'e')|$(B 'E')|$(B 'f')|$(B 'F')|$(B 'g')|$(B 'G')|$(B 'a')|$(B 'A')|$(B '|') 180) 181 182 $(BOOKTABLE Flags affect formatting depending on the specifier as 183 follows., $(TR $(TH Flag) $(TH Types affected) $(TH Semantics)) 184 185 $(TR $(TD $(B '-')) $(TD numeric) $(TD Left justify the result in 186 the field. It overrides any $(B 0) flag.)) 187 188 $(TR $(TD $(B '+')) $(TD numeric) $(TD Prefix positive numbers in 189 a signed conversion with a $(B +). It overrides any $(I space) 190 flag.)) 191 192 $(TR $(TD $(B '#')) $(TD integral ($(B 'o'))) $(TD Add to 193 precision as necessary so that the first digit of the octal 194 formatting is a '0', even if both the argument and the $(I 195 Precision) are zero.)) 196 197 $(TR $(TD $(B '#')) $(TD integral ($(B 'x'), $(B 'X'))) $(TD If 198 non-zero, prefix result with $(B 0x) ($(B 0X)).)) 199 200 $(TR $(TD $(B '#')) $(TD floating) $(TD Always insert the decimal 201 point and print trailing zeros.)) 202 203 $(TR $(TD $(B '0')) $(TD numeric) $(TD Use leading 204 zeros to pad rather than spaces (except for the floating point 205 values $(D nan) and $(D infinity)). Ignore if there's a $(I 206 Precision).)) 207 208 $(TR $(TD $(B ' ')) $(TD numeric) $(TD Prefix positive 209 numbers in a signed conversion with a space.))) 210 211 $(DL 212 $(DT $(I Width)) 213 $(DD 214 Specifies the minimum field width. 215 If the width is a $(B *), an additional argument of type $(B int), 216 preceding the actual argument, is taken as the width. 217 If the width is negative, it is as if the $(B -) was given 218 as a $(I Flags) character.) 219 220 $(DT $(I Precision)) 221 $(DD Gives the precision for numeric conversions. 222 If the precision is a $(B *), an additional argument of type $(B int), 223 preceding the actual argument, is taken as the precision. 224 If it is negative, it is as if there was no $(I Precision) specifier.) 225 226 $(DT $(I Separator)) 227 $(DD Inserts the separator symbols ',' every $(I X) digits, from right 228 to left, into numeric values to increase readability. 229 The fractional part of floating point values inserts the separator 230 from left to right. 231 Entering an integer after the ',' allows to specify $(I X). 232 If a '*' is placed after the ',' then $(I X) is specified by an 233 additional parameter to the format function. 234 Adding a '?' after the ',' or $(I X) specifier allows to specify 235 the separator character as an additional parameter. 236 ) 237 238 $(DT $(I FormatChar)) 239 $(DD 240 $(DL 241 $(DT $(B 's')) 242 $(DD The corresponding argument is formatted in a manner consistent 243 with its type: 244 $(DL 245 $(DT $(B bool)) 246 $(DD The result is $(D "true") or $(D "false").) 247 $(DT integral types) 248 $(DD The $(B %d) format is used.) 249 $(DT floating point types) 250 $(DD The $(B %g) format is used.) 251 $(DT string types) 252 $(DD The result is the string converted to UTF-8. 253 A $(I Precision) specifies the maximum number of characters 254 to use in the result.) 255 $(DT structs) 256 $(DD If the struct defines a $(B toString()) method the result is 257 the string returned from this function. Otherwise the result is 258 StructName(field<sub>0</sub>, field<sub>1</sub>, ...) where 259 field<sub>n</sub> is the nth element formatted with the default 260 format.) 261 $(DT classes derived from $(B Object)) 262 $(DD The result is the string returned from the class instance's 263 $(B .toString()) method. 264 A $(I Precision) specifies the maximum number of characters 265 to use in the result.) 266 $(DT unions) 267 $(DD If the union defines a $(B toString()) method the result is 268 the string returned from this function. Otherwise the result is 269 the name of the union, without its contents.) 270 $(DT non-string static and dynamic arrays) 271 $(DD The result is [s<sub>0</sub>, s<sub>1</sub>, ...] 272 where s<sub>n</sub> is the nth element 273 formatted with the default format.) 274 $(DT associative arrays) 275 $(DD The result is the equivalent of what the initializer 276 would look like for the contents of the associative array, 277 e.g.: ["red" : 10, "blue" : 20].) 278 )) 279 280 $(DT $(B 'c')) 281 $(DD The corresponding argument must be a character type.) 282 283 $(DT $(B 'b','d','o','x','X')) 284 $(DD The corresponding argument must be an integral type 285 and is formatted as an integer. If the argument is a signed type 286 and the $(I FormatChar) is $(B d) it is converted to 287 a signed string of characters, otherwise it is treated as 288 unsigned. An argument of type $(B bool) is formatted as '1' 289 or '0'. The base used is binary for $(B b), octal for $(B o), 290 decimal 291 for $(B d), and hexadecimal for $(B x) or $(B X). 292 $(B x) formats using lower case letters, $(B X) uppercase. 293 If there are fewer resulting digits than the $(I Precision), 294 leading zeros are used as necessary. 295 If the $(I Precision) is 0 and the number is 0, no digits 296 result.) 297 298 $(DT $(B 'e','E')) 299 $(DD A floating point number is formatted as one digit before 300 the decimal point, $(I Precision) digits after, the $(I FormatChar), 301 ±, followed by at least a two digit exponent: 302 $(I d.dddddd)e$(I ±dd). 303 If there is no $(I Precision), six 304 digits are generated after the decimal point. 305 If the $(I Precision) is 0, no decimal point is generated.) 306 307 $(DT $(B 'f','F')) 308 $(DD A floating point number is formatted in decimal notation. 309 The $(I Precision) specifies the number of digits generated 310 after the decimal point. It defaults to six. At least one digit 311 is generated before the decimal point. If the $(I Precision) 312 is zero, no decimal point is generated.) 313 314 $(DT $(B 'g','G')) 315 $(DD A floating point number is formatted in either $(B e) or 316 $(B f) format for $(B g); $(B E) or $(B F) format for 317 $(B G). 318 The $(B f) format is used if the exponent for an $(B e) format 319 is greater than -5 and less than the $(I Precision). 320 The $(I Precision) specifies the number of significant 321 digits, and defaults to six. 322 Trailing zeros are elided after the decimal point, if the fractional 323 part is zero then no decimal point is generated.) 324 325 $(DT $(B 'a','A')) 326 $(DD A floating point number is formatted in hexadecimal 327 exponential notation 0x$(I h.hhhhhh)p$(I ±d). 328 There is one hexadecimal digit before the decimal point, and as 329 many after as specified by the $(I Precision). 330 If the $(I Precision) is zero, no decimal point is generated. 331 If there is no $(I Precision), as many hexadecimal digits as 332 necessary to exactly represent the mantissa are generated. 333 The exponent is written in as few digits as possible, 334 but at least one, is in decimal, and represents a power of 2 as in 335 $(I h.hhhhhh)*2<sup>$(I ±d)</sup>. 336 The exponent for zero is zero. 337 The hexadecimal digits, x and p are in upper case if the 338 $(I FormatChar) is upper case.) 339 )) 340 ) 341 342 Floating point NaN's are formatted as $(B nan) if the 343 $(I FormatChar) is lower case, or $(B NAN) if upper. 344 Floating point infinities are formatted as $(B inf) or 345 $(B infinity) if the 346 $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper. 347 348 The positional and non-positional styles can be mixed in the same 349 format string. (POSIX leaves this behavior undefined.) The internal 350 counter for non-positional parameters tracks the next parameter after 351 the largest positional parameter already used. 352 353 Example using array and nested array formatting: 354 ------------------------- 355 import std.stdio; 356 357 void main() 358 { 359 writefln("My items are %(%s %).", [1,2,3]); 360 writefln("My items are %(%s, %).", [1,2,3]); 361 } 362 ------------------------- 363 The output is: 364$(CONSOLE 365My items are 1 2 3. 366My items are 1, 2, 3. 367) 368 369 The trailing end of the sub-format string following the specifier for each 370 item is interpreted as the array delimiter, and is therefore omitted 371 following the last array item. The $(B %|) delimiter specifier may be used 372 to indicate where the delimiter begins, so that the portion of the format 373 string prior to it will be retained in the last array element: 374 ------------------------- 375 import std.stdio; 376 377 void main() 378 { 379 writefln("My items are %(-%s-%|, %).", [1,2,3]); 380 } 381 ------------------------- 382 which gives the output: 383$(CONSOLE 384My items are -1-, -2-, -3-. 385) 386 387 These compound format specifiers may be nested in the case of a nested 388 array argument: 389 ------------------------- 390 import std.stdio; 391 void main() { 392 auto mat = [[1, 2, 3], 393 [4, 5, 6], 394 [7, 8, 9]]; 395 396 writefln("%(%(%d %)\n%)", mat); 397 writeln(); 398 399 writefln("[%(%(%d %)\n %)]", mat); 400 writeln(); 401 402 writefln("[%([%(%d %)]%|\n %)]", mat); 403 writeln(); 404 } 405 ------------------------- 406 The output is: 407$(CONSOLE 4081 2 3 4094 5 6 4107 8 9 411 412[1 2 3 413 4 5 6 414 7 8 9] 415 416[[1 2 3] 417 [4 5 6] 418 [7 8 9]] 419) 420 421 Inside a compound format specifier, strings and characters are escaped 422 automatically. To avoid this behavior, add $(B '-') flag to 423 $(D "%$(LPAREN)"). 424 ------------------------- 425 import std.stdio; 426 427 void main() 428 { 429 writefln("My friends are %s.", ["John", "Nancy"]); 430 writefln("My friends are %(%s, %).", ["John", "Nancy"]); 431 writefln("My friends are %-(%s, %).", ["John", "Nancy"]); 432 } 433 ------------------------- 434 which gives the output: 435$(CONSOLE 436My friends are ["John", "Nancy"]. 437My friends are "John", "Nancy". 438My friends are John, Nancy. 439) 440 */ 441uint formattedWrite(alias fmt, Writer, A...)(auto ref Writer w, A args) 442if (isSomeString!(typeof(fmt))) 443{ 444 alias e = checkFormatException!(fmt, A); 445 static assert(!e, e.msg); 446 return .formattedWrite(w, fmt, args); 447} 448 449/// The format string can be checked at compile-time (see $(LREF format) for details): 450@safe pure unittest 451{ 452 import std.array : appender; 453 import std.format : formattedWrite; 454 455 auto writer = appender!string(); 456 writer.formattedWrite!"%s is the ultimate %s."(42, "answer"); 457 assert(writer.data == "42 is the ultimate answer."); 458 459 // Clear the writer 460 writer = appender!string(); 461 formattedWrite(writer, "Date: %2$s %1$s", "October", 5); 462 assert(writer.data == "Date: 5 October"); 463} 464 465/// ditto 466uint formattedWrite(Writer, Char, A...)(auto ref Writer w, in Char[] fmt, A args) 467{ 468 import std.conv : text; 469 470 auto spec = FormatSpec!Char(fmt); 471 472 // Are we already done with formats? Then just dump each parameter in turn 473 uint currentArg = 0; 474 while (spec.writeUpToNextSpec(w)) 475 { 476 if (currentArg == A.length && !spec.indexStart) 477 { 478 // leftover spec? 479 enforceFmt(fmt.length == 0, 480 text("Orphan format specifier: %", spec.spec)); 481 break; 482 } 483 484 if (spec.width == spec.DYNAMIC) 485 { 486 auto width = getNthInt!"integer width"(currentArg, args); 487 if (width < 0) 488 { 489 spec.flDash = true; 490 width = -width; 491 } 492 spec.width = width; 493 ++currentArg; 494 } 495 else if (spec.width < 0) 496 { 497 // means: get width as a positional parameter 498 auto index = cast(uint) -spec.width; 499 assert(index > 0); 500 auto width = getNthInt!"integer width"(index - 1, args); 501 if (currentArg < index) currentArg = index; 502 if (width < 0) 503 { 504 spec.flDash = true; 505 width = -width; 506 } 507 spec.width = width; 508 } 509 510 if (spec.precision == spec.DYNAMIC) 511 { 512 auto precision = getNthInt!"integer precision"(currentArg, args); 513 if (precision >= 0) spec.precision = precision; 514 // else negative precision is same as no precision 515 else spec.precision = spec.UNSPECIFIED; 516 ++currentArg; 517 } 518 else if (spec.precision < 0) 519 { 520 // means: get precision as a positional parameter 521 auto index = cast(uint) -spec.precision; 522 assert(index > 0); 523 auto precision = getNthInt!"integer precision"(index- 1, args); 524 if (currentArg < index) currentArg = index; 525 if (precision >= 0) spec.precision = precision; 526 // else negative precision is same as no precision 527 else spec.precision = spec.UNSPECIFIED; 528 } 529 530 if (spec.separators == spec.DYNAMIC) 531 { 532 auto separators = getNthInt!"separator digit width"(currentArg, args); 533 spec.separators = separators; 534 ++currentArg; 535 } 536 537 if (spec.separatorCharPos == spec.DYNAMIC) 538 { 539 auto separatorChar = 540 getNth!("separator character", isSomeChar, dchar)(currentArg, args); 541 spec.separatorChar = separatorChar; 542 ++currentArg; 543 } 544 545 if (currentArg == A.length && !spec.indexStart) 546 { 547 // leftover spec? 548 enforceFmt(fmt.length == 0, 549 text("Orphan format specifier: %", spec.spec)); 550 break; 551 } 552 553 // Format an argument 554 // This switch uses a static foreach to generate a jump table. 555 // Currently `spec.indexStart` use the special value '0' to signal 556 // we should use the current argument. An enhancement would be to 557 // always store the index. 558 size_t index = currentArg; 559 if (spec.indexStart != 0) 560 index = spec.indexStart - 1; 561 else 562 ++currentArg; 563 SWITCH: switch (index) 564 { 565 foreach (i, Tunused; A) 566 { 567 case i: 568 formatValue(w, args[i], spec); 569 if (currentArg < spec.indexEnd) 570 currentArg = spec.indexEnd; 571 // A little know feature of format is to format a range 572 // of arguments, e.g. `%1:3$` will format the first 3 573 // arguments. Since they have to be consecutive we can 574 // just use explicit fallthrough to cover that case. 575 if (i + 1 < spec.indexEnd) 576 { 577 // You cannot goto case if the next case is the default 578 static if (i + 1 < A.length) 579 goto case; 580 else 581 goto default; 582 } 583 else 584 break SWITCH; 585 } 586 default: 587 throw new FormatException( 588 text("Positional specifier %", spec.indexStart, '$', spec.spec, 589 " index exceeds ", A.length)); 590 } 591 } 592 return currentArg; 593} 594 595/// 596@safe unittest 597{ 598 assert(format("%,d", 1000) == "1,000"); 599 assert(format("%,f", 1234567.891011) == "1,234,567.891,011"); 600 assert(format("%,?d", '?', 1000) == "1?000"); 601 assert(format("%,1d", 1000) == "1,0,0,0", format("%,1d", 1000)); 602 assert(format("%,*d", 4, -12345) == "-1,2345"); 603 assert(format("%,*?d", 4, '_', -12345) == "-1_2345"); 604 assert(format("%,6?d", '_', -12345678) == "-12_345678"); 605 assert(format("%12,3.3f", 1234.5678) == " 1,234.568", "'" ~ 606 format("%12,3.3f", 1234.5678) ~ "'"); 607} 608 609@safe pure unittest 610{ 611 import std.array; 612 auto w = appender!string(); 613 formattedWrite(w, "%s %d", "@safe/pure", 42); 614 assert(w.data == "@safe/pure 42"); 615} 616 617/** 618Reads characters from input range $(D r), converts them according 619to $(D fmt), and writes them to $(D args). 620 621Params: 622 r = The range to read from. 623 fmt = The format of the data to read. 624 args = The drain of the data read. 625 626Returns: 627 628On success, the function returns the number of variables filled. This count 629can match the expected number of readings or fewer, even zero, if a 630matching failure happens. 631 632Throws: 633 An `Exception` if `S.length == 0` and `fmt` has format specifiers. 634 */ 635uint formattedRead(alias fmt, R, S...)(ref R r, auto ref S args) 636if (isSomeString!(typeof(fmt))) 637{ 638 alias e = checkFormatException!(fmt, S); 639 static assert(!e, e.msg); 640 return .formattedRead(r, fmt, args); 641} 642 643/// ditto 644uint formattedRead(R, Char, S...)(ref R r, const(Char)[] fmt, auto ref S args) 645{ 646 import std.typecons : isTuple; 647 648 auto spec = FormatSpec!Char(fmt); 649 static if (!S.length) 650 { 651 spec.readUpToNextSpec(r); 652 enforce(spec.trailing.empty, "Trailing characters in formattedRead format string"); 653 return 0; 654 } 655 else 656 { 657 enum hasPointer = isPointer!(typeof(args[0])); 658 659 // The function below accounts for '*' == fields meant to be 660 // read and skipped 661 void skipUnstoredFields() 662 { 663 for (;;) 664 { 665 spec.readUpToNextSpec(r); 666 if (spec.width != spec.DYNAMIC) break; 667 // must skip this field 668 skipData(r, spec); 669 } 670 } 671 672 skipUnstoredFields(); 673 if (r.empty) 674 { 675 // Input is empty, nothing to read 676 return 0; 677 } 678 static if (hasPointer) 679 alias A = typeof(*args[0]); 680 else 681 alias A = typeof(args[0]); 682 683 static if (isTuple!A) 684 { 685 foreach (i, T; A.Types) 686 { 687 static if (hasPointer) 688 (*args[0])[i] = unformatValue!(T)(r, spec); 689 else 690 args[0][i] = unformatValue!(T)(r, spec); 691 skipUnstoredFields(); 692 } 693 } 694 else 695 { 696 static if (hasPointer) 697 *args[0] = unformatValue!(A)(r, spec); 698 else 699 args[0] = unformatValue!(A)(r, spec); 700 } 701 return 1 + formattedRead(r, spec.trailing, args[1 .. $]); 702 } 703} 704 705/// The format string can be checked at compile-time (see $(LREF format) for details): 706@safe pure unittest 707{ 708 string s = "hello!124:34.5"; 709 string a; 710 int b; 711 double c; 712 s.formattedRead!"%s!%s:%s"(a, b, c); 713 assert(a == "hello" && b == 124 && c == 34.5); 714} 715 716@safe unittest 717{ 718 import std.math; 719 string s = " 1.2 3.4 "; 720 double x, y, z; 721 assert(formattedRead(s, " %s %s %s ", x, y, z) == 2); 722 assert(s.empty); 723 assert(approxEqual(x, 1.2)); 724 assert(approxEqual(y, 3.4)); 725 assert(isNaN(z)); 726} 727 728// for backwards compatibility 729@system pure unittest 730{ 731 string s = "hello!124:34.5"; 732 string a; 733 int b; 734 double c; 735 formattedRead(s, "%s!%s:%s", &a, &b, &c); 736 assert(a == "hello" && b == 124 && c == 34.5); 737 738 // mix pointers and auto-ref 739 s = "world!200:42.25"; 740 formattedRead(s, "%s!%s:%s", a, &b, &c); 741 assert(a == "world" && b == 200 && c == 42.25); 742 743 s = "world1!201:42.5"; 744 formattedRead(s, "%s!%s:%s", &a, &b, c); 745 assert(a == "world1" && b == 201 && c == 42.5); 746 747 s = "world2!202:42.75"; 748 formattedRead(s, "%s!%s:%s", a, b, &c); 749 assert(a == "world2" && b == 202 && c == 42.75); 750} 751 752// for backwards compatibility 753@system pure unittest 754{ 755 import std.math; 756 string s = " 1.2 3.4 "; 757 double x, y, z; 758 assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2); 759 assert(s.empty); 760 assert(approxEqual(x, 1.2)); 761 assert(approxEqual(y, 3.4)); 762 assert(isNaN(z)); 763} 764 765@system pure unittest 766{ 767 string line; 768 769 bool f1; 770 771 line = "true"; 772 formattedRead(line, "%s", &f1); 773 assert(f1); 774 775 line = "TrUE"; 776 formattedRead(line, "%s", &f1); 777 assert(f1); 778 779 line = "false"; 780 formattedRead(line, "%s", &f1); 781 assert(!f1); 782 783 line = "fALsE"; 784 formattedRead(line, "%s", &f1); 785 assert(!f1); 786 787 line = "1"; 788 formattedRead(line, "%d", &f1); 789 assert(f1); 790 791 line = "-1"; 792 formattedRead(line, "%d", &f1); 793 assert(f1); 794 795 line = "0"; 796 formattedRead(line, "%d", &f1); 797 assert(!f1); 798 799 line = "-0"; 800 formattedRead(line, "%d", &f1); 801 assert(!f1); 802} 803 804@system pure unittest 805{ 806 union B 807 { 808 char[int.sizeof] untyped; 809 int typed; 810 } 811 B b; 812 b.typed = 5; 813 char[] input = b.untyped[]; 814 int witness; 815 formattedRead(input, "%r", &witness); 816 assert(witness == b.typed); 817} 818 819@system pure unittest 820{ 821 union A 822 { 823 char[float.sizeof] untyped; 824 float typed; 825 } 826 A a; 827 a.typed = 5.5; 828 char[] input = a.untyped[]; 829 float witness; 830 formattedRead(input, "%r", &witness); 831 assert(witness == a.typed); 832} 833 834@system pure unittest 835{ 836 import std.typecons; 837 char[] line = "1 2".dup; 838 int a, b; 839 formattedRead(line, "%s %s", &a, &b); 840 assert(a == 1 && b == 2); 841 842 line = "10 2 3".dup; 843 formattedRead(line, "%d ", &a); 844 assert(a == 10); 845 assert(line == "2 3"); 846 847 Tuple!(int, float) t; 848 line = "1 2.125".dup; 849 formattedRead(line, "%d %g", &t); 850 assert(t[0] == 1 && t[1] == 2.125); 851 852 line = "1 7643 2.125".dup; 853 formattedRead(line, "%s %*u %s", &t); 854 assert(t[0] == 1 && t[1] == 2.125); 855} 856 857@system pure unittest 858{ 859 string line; 860 861 char c1, c2; 862 863 line = "abc"; 864 formattedRead(line, "%s%c", &c1, &c2); 865 assert(c1 == 'a' && c2 == 'b'); 866 assert(line == "c"); 867} 868 869@system pure unittest 870{ 871 string line; 872 873 line = "[1,2,3]"; 874 int[] s1; 875 formattedRead(line, "%s", &s1); 876 assert(s1 == [1,2,3]); 877} 878 879@system pure unittest 880{ 881 string line; 882 883 line = "[1,2,3]"; 884 int[] s1; 885 formattedRead(line, "[%(%s,%)]", &s1); 886 assert(s1 == [1,2,3]); 887 888 line = `["hello", "world"]`; 889 string[] s2; 890 formattedRead(line, "[%(%s, %)]", &s2); 891 assert(s2 == ["hello", "world"]); 892 893 line = "123 456"; 894 int[] s3; 895 formattedRead(line, "%(%s %)", &s3); 896 assert(s3 == [123, 456]); 897 898 line = "h,e,l,l,o; w,o,r,l,d"; 899 string[] s4; 900 formattedRead(line, "%(%(%c,%); %)", &s4); 901 assert(s4 == ["hello", "world"]); 902} 903 904@system pure unittest 905{ 906 string line; 907 908 int[4] sa1; 909 line = `[1,2,3,4]`; 910 formattedRead(line, "%s", &sa1); 911 assert(sa1 == [1,2,3,4]); 912 913 int[4] sa2; 914 line = `[1,2,3]`; 915 assertThrown(formattedRead(line, "%s", &sa2)); 916 917 int[4] sa3; 918 line = `[1,2,3,4,5]`; 919 assertThrown(formattedRead(line, "%s", &sa3)); 920} 921 922@system pure unittest 923{ 924 string input; 925 926 int[4] sa1; 927 input = `[1,2,3,4]`; 928 formattedRead(input, "[%(%s,%)]", &sa1); 929 assert(sa1 == [1,2,3,4]); 930 931 int[4] sa2; 932 input = `[1,2,3]`; 933 assertThrown(formattedRead(input, "[%(%s,%)]", &sa2)); 934} 935 936@system pure unittest 937{ 938 string line; 939 940 string s1, s2; 941 942 line = "hello, world"; 943 formattedRead(line, "%s", &s1); 944 assert(s1 == "hello, world", s1); 945 946 line = "hello, world;yah"; 947 formattedRead(line, "%s;%s", &s1, &s2); 948 assert(s1 == "hello, world", s1); 949 assert(s2 == "yah", s2); 950 951 line = `['h','e','l','l','o']`; 952 string s3; 953 formattedRead(line, "[%(%s,%)]", &s3); 954 assert(s3 == "hello"); 955 956 line = `"hello"`; 957 string s4; 958 formattedRead(line, "\"%(%c%)\"", &s4); 959 assert(s4 == "hello"); 960} 961 962@system pure unittest 963{ 964 string line; 965 966 string[int] aa1; 967 line = `[1:"hello", 2:"world"]`; 968 formattedRead(line, "%s", &aa1); 969 assert(aa1 == [1:"hello", 2:"world"]); 970 971 int[string] aa2; 972 line = `{"hello"=1; "world"=2}`; 973 formattedRead(line, "{%(%s=%s; %)}", &aa2); 974 assert(aa2 == ["hello":1, "world":2]); 975 976 int[string] aa3; 977 line = `{[hello=1]; [world=2]}`; 978 formattedRead(line, "{%([%(%c%)=%s]%|; %)}", &aa3); 979 assert(aa3 == ["hello":1, "world":2]); 980} 981 982template FormatSpec(Char) 983if (!is(Unqual!Char == Char)) 984{ 985 alias FormatSpec = FormatSpec!(Unqual!Char); 986} 987 988/** 989 * A General handler for $(D printf) style format specifiers. Used for building more 990 * specific formatting functions. 991 */ 992struct FormatSpec(Char) 993if (is(Unqual!Char == Char)) 994{ 995 import std.algorithm.searching : startsWith; 996 import std.ascii : isDigit, isPunctuation, isAlpha; 997 import std.conv : parse, text, to; 998 999 /** 1000 Minimum _width, default $(D 0). 1001 */ 1002 int width = 0; 1003 1004 /** 1005 Precision. Its semantics depends on the argument type. For 1006 floating point numbers, _precision dictates the number of 1007 decimals printed. 1008 */ 1009 int precision = UNSPECIFIED; 1010 1011 /** 1012 Number of digits printed between _separators. 1013 */ 1014 int separators = UNSPECIFIED; 1015 1016 /** 1017 Set to `DYNAMIC` when the separator character is supplied at runtime. 1018 */ 1019 int separatorCharPos = UNSPECIFIED; 1020 1021 /** 1022 Character to insert between digits. 1023 */ 1024 dchar separatorChar = ','; 1025 1026 /** 1027 Special value for width and precision. $(D DYNAMIC) width or 1028 precision means that they were specified with $(D '*') in the 1029 format string and are passed at runtime through the varargs. 1030 */ 1031 enum int DYNAMIC = int.max; 1032 1033 /** 1034 Special value for precision, meaning the format specifier 1035 contained no explicit precision. 1036 */ 1037 enum int UNSPECIFIED = DYNAMIC - 1; 1038 1039 /** 1040 The actual format specifier, $(D 's') by default. 1041 */ 1042 char spec = 's'; 1043 1044 /** 1045 Index of the argument for positional parameters, from $(D 1) to 1046 $(D ubyte.max). ($(D 0) means not used). 1047 */ 1048 ubyte indexStart; 1049 1050 /** 1051 Index of the last argument for positional parameter range, from 1052 $(D 1) to $(D ubyte.max). ($(D 0) means not used). 1053 */ 1054 ubyte indexEnd; 1055 1056 version (StdDdoc) 1057 { 1058 /** 1059 The format specifier contained a $(D '-') ($(D printf) 1060 compatibility). 1061 */ 1062 bool flDash; 1063 1064 /** 1065 The format specifier contained a $(D '0') ($(D printf) 1066 compatibility). 1067 */ 1068 bool flZero; 1069 1070 /** 1071 The format specifier contained a $(D ' ') ($(D printf) 1072 compatibility). 1073 */ 1074 bool flSpace; 1075 1076 /** 1077 The format specifier contained a $(D '+') ($(D printf) 1078 compatibility). 1079 */ 1080 bool flPlus; 1081 1082 /** 1083 The format specifier contained a $(D '#') ($(D printf) 1084 compatibility). 1085 */ 1086 bool flHash; 1087 1088 /** 1089 The format specifier contained a $(D ',') 1090 */ 1091 bool flSeparator; 1092 1093 // Fake field to allow compilation 1094 ubyte allFlags; 1095 } 1096 else 1097 { 1098 union 1099 { 1100 import std.bitmanip : bitfields; 1101 mixin(bitfields!( 1102 bool, "flDash", 1, 1103 bool, "flZero", 1, 1104 bool, "flSpace", 1, 1105 bool, "flPlus", 1, 1106 bool, "flHash", 1, 1107 bool, "flSeparator", 1, 1108 ubyte, "", 2)); 1109 ubyte allFlags; 1110 } 1111 } 1112 1113 /** 1114 In case of a compound format specifier starting with $(D 1115 "%$(LPAREN)") and ending with $(D "%$(RPAREN)"), $(D _nested) 1116 contains the string contained within the two separators. 1117 */ 1118 const(Char)[] nested; 1119 1120 /** 1121 In case of a compound format specifier, $(D _sep) contains the 1122 string positioning after $(D "%|"). 1123 `sep is null` means no separator else `sep.empty` means 0 length 1124 separator. 1125 */ 1126 const(Char)[] sep; 1127 1128 /** 1129 $(D _trailing) contains the rest of the format string. 1130 */ 1131 const(Char)[] trailing; 1132 1133 /* 1134 This string is inserted before each sequence (e.g. array) 1135 formatted (by default $(D "[")). 1136 */ 1137 enum immutable(Char)[] seqBefore = "["; 1138 1139 /* 1140 This string is inserted after each sequence formatted (by 1141 default $(D "]")). 1142 */ 1143 enum immutable(Char)[] seqAfter = "]"; 1144 1145 /* 1146 This string is inserted after each element keys of a sequence (by 1147 default $(D ":")). 1148 */ 1149 enum immutable(Char)[] keySeparator = ":"; 1150 1151 /* 1152 This string is inserted in between elements of a sequence (by 1153 default $(D ", ")). 1154 */ 1155 enum immutable(Char)[] seqSeparator = ", "; 1156 1157 /** 1158 Construct a new $(D FormatSpec) using the format string $(D fmt), no 1159 processing is done until needed. 1160 */ 1161 this(in Char[] fmt) @safe pure 1162 { 1163 trailing = fmt; 1164 } 1165 1166 bool writeUpToNextSpec(OutputRange)(ref OutputRange writer) 1167 { 1168 if (trailing.empty) 1169 return false; 1170 for (size_t i = 0; i < trailing.length; ++i) 1171 { 1172 if (trailing[i] != '%') continue; 1173 put(writer, trailing[0 .. i]); 1174 trailing = trailing[i .. $]; 1175 enforceFmt(trailing.length >= 2, `Unterminated format specifier: "%"`); 1176 trailing = trailing[1 .. $]; 1177 1178 if (trailing[0] != '%') 1179 { 1180 // Spec found. Fill up the spec, and bailout 1181 fillUp(); 1182 return true; 1183 } 1184 // Doubled! Reset and Keep going 1185 i = 0; 1186 } 1187 // no format spec found 1188 put(writer, trailing); 1189 trailing = null; 1190 return false; 1191 } 1192 1193 @safe unittest 1194 { 1195 import std.array; 1196 auto w = appender!(char[])(); 1197 auto f = FormatSpec("abc%sdef%sghi"); 1198 f.writeUpToNextSpec(w); 1199 assert(w.data == "abc", w.data); 1200 assert(f.trailing == "def%sghi", text(f.trailing)); 1201 f.writeUpToNextSpec(w); 1202 assert(w.data == "abcdef", w.data); 1203 assert(f.trailing == "ghi"); 1204 // test with embedded %%s 1205 f = FormatSpec("ab%%cd%%ef%sg%%h%sij"); 1206 w.clear(); 1207 f.writeUpToNextSpec(w); 1208 assert(w.data == "ab%cd%ef" && f.trailing == "g%%h%sij", w.data); 1209 f.writeUpToNextSpec(w); 1210 assert(w.data == "ab%cd%efg%h" && f.trailing == "ij"); 1211 // bug4775 1212 f = FormatSpec("%%%s"); 1213 w.clear(); 1214 f.writeUpToNextSpec(w); 1215 assert(w.data == "%" && f.trailing == ""); 1216 f = FormatSpec("%%%%%s%%"); 1217 w.clear(); 1218 while (f.writeUpToNextSpec(w)) continue; 1219 assert(w.data == "%%%"); 1220 1221 f = FormatSpec("a%%b%%c%"); 1222 w.clear(); 1223 assertThrown!FormatException(f.writeUpToNextSpec(w)); 1224 assert(w.data == "a%b%c" && f.trailing == "%"); 1225 } 1226 1227 private void fillUp() 1228 { 1229 // Reset content 1230 if (__ctfe) 1231 { 1232 flDash = false; 1233 flZero = false; 1234 flSpace = false; 1235 flPlus = false; 1236 flHash = false; 1237 flSeparator = false; 1238 } 1239 else 1240 { 1241 allFlags = 0; 1242 } 1243 1244 width = 0; 1245 precision = UNSPECIFIED; 1246 nested = null; 1247 // Parse the spec (we assume we're past '%' already) 1248 for (size_t i = 0; i < trailing.length; ) 1249 { 1250 switch (trailing[i]) 1251 { 1252 case '(': 1253 // Embedded format specifier. 1254 auto j = i + 1; 1255 // Get the matching balanced paren 1256 for (uint innerParens;;) 1257 { 1258 enforceFmt(j + 1 < trailing.length, 1259 text("Incorrect format specifier: %", trailing[i .. $])); 1260 if (trailing[j++] != '%') 1261 { 1262 // skip, we're waiting for %( and %) 1263 continue; 1264 } 1265 if (trailing[j] == '-') // for %-( 1266 { 1267 ++j; // skip 1268 enforceFmt(j < trailing.length, 1269 text("Incorrect format specifier: %", trailing[i .. $])); 1270 } 1271 if (trailing[j] == ')') 1272 { 1273 if (innerParens-- == 0) break; 1274 } 1275 else if (trailing[j] == '|') 1276 { 1277 if (innerParens == 0) break; 1278 } 1279 else if (trailing[j] == '(') 1280 { 1281 ++innerParens; 1282 } 1283 } 1284 if (trailing[j] == '|') 1285 { 1286 auto k = j; 1287 for (++j;;) 1288 { 1289 if (trailing[j++] != '%') 1290 continue; 1291 if (trailing[j] == '%') 1292 ++j; 1293 else if (trailing[j] == ')') 1294 break; 1295 else 1296 throw new Exception( 1297 text("Incorrect format specifier: %", 1298 trailing[j .. $])); 1299 } 1300 nested = trailing[i + 1 .. k - 1]; 1301 sep = trailing[k + 1 .. j - 1]; 1302 } 1303 else 1304 { 1305 nested = trailing[i + 1 .. j - 1]; 1306 sep = null; // no separator 1307 } 1308 //this = FormatSpec(innerTrailingSpec); 1309 spec = '('; 1310 // We practically found the format specifier 1311 trailing = trailing[j + 1 .. $]; 1312 return; 1313 case '-': flDash = true; ++i; break; 1314 case '+': flPlus = true; ++i; break; 1315 case '#': flHash = true; ++i; break; 1316 case '0': flZero = true; ++i; break; 1317 case ' ': flSpace = true; ++i; break; 1318 case '*': 1319 if (isDigit(trailing[++i])) 1320 { 1321 // a '*' followed by digits and '$' is a 1322 // positional format 1323 trailing = trailing[1 .. $]; 1324 width = -parse!(typeof(width))(trailing); 1325 i = 0; 1326 enforceFmt(trailing[i++] == '$', 1327 "$ expected"); 1328 } 1329 else 1330 { 1331 // read result 1332 width = DYNAMIC; 1333 } 1334 break; 1335 case '1': .. case '9': 1336 auto tmp = trailing[i .. $]; 1337 const widthOrArgIndex = parse!uint(tmp); 1338 enforceFmt(tmp.length, 1339 text("Incorrect format specifier %", trailing[i .. $])); 1340 i = arrayPtrDiff(tmp, trailing); 1341 if (tmp.startsWith('$')) 1342 { 1343 // index of the form %n$ 1344 indexEnd = indexStart = to!ubyte(widthOrArgIndex); 1345 ++i; 1346 } 1347 else if (tmp.startsWith(':')) 1348 { 1349 // two indexes of the form %m:n$, or one index of the form %m:$ 1350 indexStart = to!ubyte(widthOrArgIndex); 1351 tmp = tmp[1 .. $]; 1352 if (tmp.startsWith('$')) 1353 { 1354 indexEnd = indexEnd.max; 1355 } 1356 else 1357 { 1358 indexEnd = parse!(typeof(indexEnd))(tmp); 1359 } 1360 i = arrayPtrDiff(tmp, trailing); 1361 enforceFmt(trailing[i++] == '$', 1362 "$ expected"); 1363 } 1364 else 1365 { 1366 // width 1367 width = to!int(widthOrArgIndex); 1368 } 1369 break; 1370 case ',': 1371 // Precision 1372 ++i; 1373 flSeparator = true; 1374 1375 if (trailing[i] == '*') 1376 { 1377 ++i; 1378 // read result 1379 separators = DYNAMIC; 1380 } 1381 else if (isDigit(trailing[i])) 1382 { 1383 auto tmp = trailing[i .. $]; 1384 separators = parse!int(tmp); 1385 i = arrayPtrDiff(tmp, trailing); 1386 } 1387 else 1388 { 1389 // "," was specified, but nothing after it 1390 separators = 3; 1391 } 1392 1393 if (trailing[i] == '?') 1394 { 1395 separatorCharPos = DYNAMIC; 1396 ++i; 1397 } 1398 1399 break; 1400 case '.': 1401 // Precision 1402 if (trailing[++i] == '*') 1403 { 1404 if (isDigit(trailing[++i])) 1405 { 1406 // a '.*' followed by digits and '$' is a 1407 // positional precision 1408 trailing = trailing[i .. $]; 1409 i = 0; 1410 precision = -parse!int(trailing); 1411 enforceFmt(trailing[i++] == '$', 1412 "$ expected"); 1413 } 1414 else 1415 { 1416 // read result 1417 precision = DYNAMIC; 1418 } 1419 } 1420 else if (trailing[i] == '-') 1421 { 1422 // negative precision, as good as 0 1423 precision = 0; 1424 auto tmp = trailing[i .. $]; 1425 parse!int(tmp); // skip digits 1426 i = arrayPtrDiff(tmp, trailing); 1427 } 1428 else if (isDigit(trailing[i])) 1429 { 1430 auto tmp = trailing[i .. $]; 1431 precision = parse!int(tmp); 1432 i = arrayPtrDiff(tmp, trailing); 1433 } 1434 else 1435 { 1436 // "." was specified, but nothing after it 1437 precision = 0; 1438 } 1439 break; 1440 default: 1441 // this is the format char 1442 spec = cast(char) trailing[i++]; 1443 trailing = trailing[i .. $]; 1444 return; 1445 } // end switch 1446 } // end for 1447 throw new Exception(text("Incorrect format specifier: ", trailing)); 1448 } 1449 1450 //-------------------------------------------------------------------------- 1451 private bool readUpToNextSpec(R)(ref R r) 1452 { 1453 import std.ascii : isLower, isWhite; 1454 import std.utf : stride; 1455 1456 // Reset content 1457 if (__ctfe) 1458 { 1459 flDash = false; 1460 flZero = false; 1461 flSpace = false; 1462 flPlus = false; 1463 flHash = false; 1464 flSeparator = false; 1465 } 1466 else 1467 { 1468 allFlags = 0; 1469 } 1470 width = 0; 1471 precision = UNSPECIFIED; 1472 nested = null; 1473 // Parse the spec 1474 while (trailing.length) 1475 { 1476 const c = trailing[0]; 1477 if (c == '%' && trailing.length > 1) 1478 { 1479 const c2 = trailing[1]; 1480 if (c2 == '%') 1481 { 1482 assert(!r.empty); 1483 // Require a '%' 1484 if (r.front != '%') break; 1485 trailing = trailing[2 .. $]; 1486 r.popFront(); 1487 } 1488 else 1489 { 1490 enforce(isLower(c2) || c2 == '*' || 1491 c2 == '(', 1492 text("'%", c2, 1493 "' not supported with formatted read")); 1494 trailing = trailing[1 .. $]; 1495 fillUp(); 1496 return true; 1497 } 1498 } 1499 else 1500 { 1501 if (c == ' ') 1502 { 1503 while (!r.empty && isWhite(r.front)) r.popFront(); 1504 //r = std.algorithm.find!(not!(isWhite))(r); 1505 } 1506 else 1507 { 1508 enforce(!r.empty, 1509 text("parseToFormatSpec: Cannot find character '", 1510 c, "' in the input string.")); 1511 if (r.front != trailing.front) break; 1512 r.popFront(); 1513 } 1514 trailing = trailing[stride(trailing, 0) .. $]; 1515 } 1516 } 1517 return false; 1518 } 1519 1520 private string getCurFmtStr() const 1521 { 1522 import std.array : appender; 1523 auto w = appender!string(); 1524 auto f = FormatSpec!Char("%s"); // for stringnize 1525 1526 put(w, '%'); 1527 if (indexStart != 0) 1528 { 1529 formatValue(w, indexStart, f); 1530 put(w, '$'); 1531 } 1532 if (flDash) put(w, '-'); 1533 if (flZero) put(w, '0'); 1534 if (flSpace) put(w, ' '); 1535 if (flPlus) put(w, '+'); 1536 if (flHash) put(w, '#'); 1537 if (flSeparator) put(w, ','); 1538 if (width != 0) 1539 formatValue(w, width, f); 1540 if (precision != FormatSpec!Char.UNSPECIFIED) 1541 { 1542 put(w, '.'); 1543 formatValue(w, precision, f); 1544 } 1545 put(w, spec); 1546 return w.data; 1547 } 1548 1549 @safe unittest 1550 { 1551 // issue 5237 1552 import std.array; 1553 auto w = appender!string(); 1554 auto f = FormatSpec!char("%.16f"); 1555 f.writeUpToNextSpec(w); // dummy eating 1556 assert(f.spec == 'f'); 1557 auto fmt = f.getCurFmtStr(); 1558 assert(fmt == "%.16f"); 1559 } 1560 1561 private const(Char)[] headUpToNextSpec() 1562 { 1563 import std.array : appender; 1564 auto w = appender!(typeof(return))(); 1565 auto tr = trailing; 1566 1567 while (tr.length) 1568 { 1569 if (tr[0] == '%') 1570 { 1571 if (tr.length > 1 && tr[1] == '%') 1572 { 1573 tr = tr[2 .. $]; 1574 w.put('%'); 1575 } 1576 else 1577 break; 1578 } 1579 else 1580 { 1581 w.put(tr.front); 1582 tr.popFront(); 1583 } 1584 } 1585 return w.data; 1586 } 1587 1588 string toString() 1589 { 1590 return text("address = ", cast(void*) &this, 1591 "\nwidth = ", width, 1592 "\nprecision = ", precision, 1593 "\nspec = ", spec, 1594 "\nindexStart = ", indexStart, 1595 "\nindexEnd = ", indexEnd, 1596 "\nflDash = ", flDash, 1597 "\nflZero = ", flZero, 1598 "\nflSpace = ", flSpace, 1599 "\nflPlus = ", flPlus, 1600 "\nflHash = ", flHash, 1601 "\nflSeparator = ", flSeparator, 1602 "\nnested = ", nested, 1603 "\ntrailing = ", trailing, "\n"); 1604 } 1605} 1606 1607/// 1608@safe pure unittest 1609{ 1610 import std.array; 1611 auto a = appender!(string)(); 1612 auto fmt = "Number: %2.4e\nString: %s"; 1613 auto f = FormatSpec!char(fmt); 1614 1615 f.writeUpToNextSpec(a); 1616 1617 assert(a.data == "Number: "); 1618 assert(f.trailing == "\nString: %s"); 1619 assert(f.spec == 'e'); 1620 assert(f.width == 2); 1621 assert(f.precision == 4); 1622 1623 f.writeUpToNextSpec(a); 1624 1625 assert(a.data == "Number: \nString: "); 1626 assert(f.trailing == ""); 1627 assert(f.spec == 's'); 1628} 1629 1630// Issue 14059 1631@safe unittest 1632{ 1633 import std.array : appender; 1634 auto a = appender!(string)(); 1635 1636 auto f = FormatSpec!char("%-(%s%"); // %)") 1637 assertThrown(f.writeUpToNextSpec(a)); 1638 1639 f = FormatSpec!char("%(%-"); // %)") 1640 assertThrown(f.writeUpToNextSpec(a)); 1641} 1642 1643@safe unittest 1644{ 1645 import std.array : appender; 1646 auto a = appender!(string)(); 1647 1648 auto f = FormatSpec!char("%,d"); 1649 f.writeUpToNextSpec(a); 1650 1651 assert(f.spec == 'd', format("%s", f.spec)); 1652 assert(f.precision == FormatSpec!char.UNSPECIFIED); 1653 assert(f.separators == 3); 1654 1655 f = FormatSpec!char("%5,10f"); 1656 f.writeUpToNextSpec(a); 1657 assert(f.spec == 'f', format("%s", f.spec)); 1658 assert(f.separators == 10); 1659 assert(f.width == 5); 1660 1661 f = FormatSpec!char("%5,10.4f"); 1662 f.writeUpToNextSpec(a); 1663 assert(f.spec == 'f', format("%s", f.spec)); 1664 assert(f.separators == 10); 1665 assert(f.width == 5); 1666 assert(f.precision == 4); 1667} 1668 1669/** 1670Helper function that returns a $(D FormatSpec) for a single specifier given 1671in $(D fmt). 1672 1673Params: 1674 fmt = A format specifier. 1675 1676Returns: 1677 A $(D FormatSpec) with the specifier parsed. 1678Throws: 1679 An `Exception` when more than one specifier is given or the specifier 1680 is malformed. 1681 */ 1682FormatSpec!Char singleSpec(Char)(Char[] fmt) 1683{ 1684 import std.conv : text; 1685 enforce(fmt.length >= 2, "fmt must be at least 2 characters long"); 1686 enforce(fmt.front == '%', "fmt must start with a '%' character"); 1687 1688 static struct DummyOutputRange { 1689 void put(C)(C[] buf) {} // eat elements 1690 } 1691 auto a = DummyOutputRange(); 1692 auto spec = FormatSpec!Char(fmt); 1693 //dummy write 1694 spec.writeUpToNextSpec(a); 1695 1696 enforce(spec.trailing.empty, 1697 text("Trailing characters in fmt string: '", spec.trailing)); 1698 1699 return spec; 1700} 1701 1702/// 1703@safe pure unittest 1704{ 1705 import std.exception : assertThrown; 1706 auto spec = singleSpec("%2.3e"); 1707 1708 assert(spec.trailing == ""); 1709 assert(spec.spec == 'e'); 1710 assert(spec.width == 2); 1711 assert(spec.precision == 3); 1712 1713 assertThrown(singleSpec("")); 1714 assertThrown(singleSpec("2.3e")); 1715 assertThrown(singleSpec("%2.3eTest")); 1716} 1717 1718/** 1719$(D bool)s are formatted as "true" or "false" with %s and as "1" or 1720"0" with integral-specific format specs. 1721 1722Params: 1723 w = The $(D OutputRange) to write to. 1724 obj = The value to write. 1725 f = The $(D FormatSpec) defining how to write the value. 1726 */ 1727void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) 1728if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 1729{ 1730 BooleanTypeOf!T val = obj; 1731 1732 if (f.spec == 's') 1733 { 1734 string s = val ? "true" : "false"; 1735 if (!f.flDash) 1736 { 1737 // right align 1738 if (f.width > s.length) 1739 foreach (i ; 0 .. f.width - s.length) put(w, ' '); 1740 put(w, s); 1741 } 1742 else 1743 { 1744 // left align 1745 put(w, s); 1746 if (f.width > s.length) 1747 foreach (i ; 0 .. f.width - s.length) put(w, ' '); 1748 } 1749 } 1750 else 1751 formatValue(w, cast(int) val, f); 1752} 1753 1754/// 1755@safe pure unittest 1756{ 1757 import std.array : appender; 1758 auto w = appender!string(); 1759 auto spec = singleSpec("%s"); 1760 formatValue(w, true, spec); 1761 1762 assert(w.data == "true"); 1763} 1764 1765@safe pure unittest 1766{ 1767 assertCTFEable!( 1768 { 1769 formatTest( false, "false" ); 1770 formatTest( true, "true" ); 1771 }); 1772} 1773@system unittest 1774{ 1775 class C1 { bool val; alias val this; this(bool v){ val = v; } } 1776 class C2 { bool val; alias val this; this(bool v){ val = v; } 1777 override string toString() const { return "C"; } } 1778 formatTest( new C1(false), "false" ); 1779 formatTest( new C1(true), "true" ); 1780 formatTest( new C2(false), "C" ); 1781 formatTest( new C2(true), "C" ); 1782 1783 struct S1 { bool val; alias val this; } 1784 struct S2 { bool val; alias val this; 1785 string toString() const { return "S"; } } 1786 formatTest( S1(false), "false" ); 1787 formatTest( S1(true), "true" ); 1788 formatTest( S2(false), "S" ); 1789 formatTest( S2(true), "S" ); 1790} 1791 1792@safe pure unittest 1793{ 1794 string t1 = format("[%6s] [%6s] [%-6s]", true, false, true); 1795 assert(t1 == "[ true] [ false] [true ]"); 1796 1797 string t2 = format("[%3s] [%-2s]", true, false); 1798 assert(t2 == "[true] [false]"); 1799} 1800 1801/** 1802$(D null) literal is formatted as $(D "null"). 1803 1804Params: 1805 w = The $(D OutputRange) to write to. 1806 obj = The value to write. 1807 f = The $(D FormatSpec) defining how to write the value. 1808 */ 1809void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) 1810if (is(Unqual!T == typeof(null)) && !is(T == enum) && !hasToString!(T, Char)) 1811{ 1812 enforceFmt(f.spec == 's', 1813 "null literal cannot match %" ~ f.spec); 1814 1815 put(w, "null"); 1816} 1817 1818/// 1819@safe pure unittest 1820{ 1821 import std.array : appender; 1822 auto w = appender!string(); 1823 auto spec = singleSpec("%s"); 1824 formatValue(w, null, spec); 1825 1826 assert(w.data == "null"); 1827} 1828 1829@safe pure unittest 1830{ 1831 assert(collectExceptionMsg!FormatException(format("%p", null)).back == 'p'); 1832 1833 assertCTFEable!( 1834 { 1835 formatTest( null, "null" ); 1836 }); 1837} 1838 1839/** 1840Integrals are formatted like $(D printf) does. 1841 1842Params: 1843 w = The $(D OutputRange) to write to. 1844 obj = The value to write. 1845 f = The $(D FormatSpec) defining how to write the value. 1846 */ 1847void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) 1848if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 1849{ 1850 alias U = IntegralTypeOf!T; 1851 U val = obj; // Extracting alias this may be impure/system/may-throw 1852 1853 if (f.spec == 'r') 1854 { 1855 // raw write, skip all else and write the thing 1856 auto raw = (ref val)@trusted{ 1857 return (cast(const char*) &val)[0 .. val.sizeof]; 1858 }(val); 1859 if (needToSwapEndianess(f)) 1860 { 1861 foreach_reverse (c; raw) 1862 put(w, c); 1863 } 1864 else 1865 { 1866 foreach (c; raw) 1867 put(w, c); 1868 } 1869 return; 1870 } 1871 1872 immutable uint base = 1873 f.spec == 'x' || f.spec == 'X' ? 16 : 1874 f.spec == 'o' ? 8 : 1875 f.spec == 'b' ? 2 : 1876 f.spec == 's' || f.spec == 'd' || f.spec == 'u' ? 10 : 1877 0; 1878 enforceFmt(base > 0, 1879 "incompatible format character for integral argument: %" ~ f.spec); 1880 1881 // Forward on to formatIntegral to handle both U and const(U) 1882 // Saves duplication of code for both versions. 1883 static if (is(ucent) && (is(U == cent) || is(U == ucent))) 1884 alias C = U; 1885 else static if (isSigned!U) 1886 alias C = long; 1887 else 1888 alias C = ulong; 1889 formatIntegral(w, cast(C) val, f, base, Unsigned!U.max); 1890} 1891 1892/// 1893@safe pure unittest 1894{ 1895 import std.array : appender; 1896 auto w = appender!string(); 1897 auto spec = singleSpec("%d"); 1898 formatValue(w, 1337, spec); 1899 1900 assert(w.data == "1337"); 1901} 1902 1903private void formatIntegral(Writer, T, Char)(ref Writer w, const(T) val, const ref FormatSpec!Char fs, 1904 uint base, ulong mask) 1905{ 1906 T arg = val; 1907 1908 immutable negative = (base == 10 && arg < 0); 1909 if (negative) 1910 { 1911 arg = -arg; 1912 } 1913 1914 // All unsigned integral types should fit in ulong. 1915 static if (is(ucent) && is(typeof(arg) == ucent)) 1916 formatUnsigned(w, (cast(ucent) arg) & mask, fs, base, negative); 1917 else 1918 formatUnsigned(w, (cast(ulong) arg) & mask, fs, base, negative); 1919} 1920 1921private void formatUnsigned(Writer, T, Char) 1922(ref Writer w, T arg, const ref FormatSpec!Char fs, uint base, bool negative) 1923{ 1924 /* Write string: 1925 * leftpad prefix1 prefix2 zerofill digits rightpad 1926 */ 1927 1928 /* Convert arg to digits[]. 1929 * Note that 0 becomes an empty digits[] 1930 */ 1931 char[64] buffer = void; // 64 bits in base 2 at most 1932 char[] digits; 1933 if (arg < base && base <= 10 && arg) 1934 { 1935 // Most numbers are a single digit - avoid expensive divide 1936 buffer[0] = cast(char)(arg + '0'); 1937 digits = buffer[0 .. 1]; 1938 } 1939 else 1940 { 1941 size_t i = buffer.length; 1942 while (arg) 1943 { 1944 --i; 1945 char c = cast(char) (arg % base); 1946 arg /= base; 1947 if (c < 10) 1948 buffer[i] = cast(char)(c + '0'); 1949 else 1950 buffer[i] = cast(char)(c + (fs.spec == 'x' ? 'a' - 10 : 'A' - 10)); 1951 } 1952 digits = buffer[i .. $]; // got the digits without the sign 1953 } 1954 1955 1956 immutable precision = (fs.precision == fs.UNSPECIFIED) ? 1 : fs.precision; 1957 1958 char padChar = 0; 1959 if (!fs.flDash) 1960 { 1961 padChar = (fs.flZero && fs.precision == fs.UNSPECIFIED) ? '0' : ' '; 1962 } 1963 1964 // Compute prefix1 and prefix2 1965 char prefix1 = 0; 1966 char prefix2 = 0; 1967 if (base == 10) 1968 { 1969 if (negative) 1970 prefix1 = '-'; 1971 else if (fs.flPlus) 1972 prefix1 = '+'; 1973 else if (fs.flSpace) 1974 prefix1 = ' '; 1975 } 1976 else if (base == 16 && fs.flHash && digits.length) 1977 { 1978 prefix1 = '0'; 1979 prefix2 = fs.spec == 'x' ? 'x' : 'X'; 1980 } 1981 // adjust precision to print a '0' for octal if alternate format is on 1982 else if (base == 8 && fs.flHash && 1983 (precision <= 1 || precision <= digits.length) && // too low precision 1984 digits.length > 0) 1985 prefix1 = '0'; 1986 1987 size_t zerofill = precision > digits.length ? precision - digits.length : 0; 1988 size_t leftpad = 0; 1989 size_t rightpad = 0; 1990 1991 immutable ptrdiff_t spacesToPrint = 1992 fs.width - ( 1993 (prefix1 != 0) 1994 + (prefix2 != 0) 1995 + zerofill 1996 + digits.length 1997 + ((fs.flSeparator != 0) * (digits.length / fs.separators)) 1998 ); 1999 if (spacesToPrint > 0) // need to do some padding 2000 { 2001 if (padChar == '0') 2002 zerofill += spacesToPrint; 2003 else if (padChar) 2004 leftpad = spacesToPrint; 2005 else 2006 rightpad = spacesToPrint; 2007 } 2008 2009 // Print 2010 foreach (i ; 0 .. leftpad) 2011 put(w, ' '); 2012 2013 if (prefix1) put(w, prefix1); 2014 if (prefix2) put(w, prefix2); 2015 2016 foreach (i ; 0 .. zerofill) 2017 put(w, '0'); 2018 2019 if (fs.flSeparator) 2020 { 2021 for (size_t j = 0; j < digits.length; ++j) 2022 { 2023 if (j != 0 && (digits.length - j) % fs.separators == 0) 2024 { 2025 put(w, fs.separatorChar); 2026 } 2027 put(w, digits[j]); 2028 } 2029 } 2030 else 2031 { 2032 put(w, digits); 2033 } 2034 2035 foreach (i ; 0 .. rightpad) 2036 put(w, ' '); 2037} 2038 2039@safe pure unittest 2040{ 2041 assert(collectExceptionMsg!FormatException(format("%c", 5)).back == 'c'); 2042 2043 assertCTFEable!( 2044 { 2045 formatTest(9, "9"); 2046 formatTest( 10, "10" ); 2047 }); 2048} 2049 2050@system unittest 2051{ 2052 class C1 { long val; alias val this; this(long v){ val = v; } } 2053 class C2 { long val; alias val this; this(long v){ val = v; } 2054 override string toString() const { return "C"; } } 2055 formatTest( new C1(10), "10" ); 2056 formatTest( new C2(10), "C" ); 2057 2058 struct S1 { long val; alias val this; } 2059 struct S2 { long val; alias val this; 2060 string toString() const { return "S"; } } 2061 formatTest( S1(10), "10" ); 2062 formatTest( S2(10), "S" ); 2063} 2064 2065// bugzilla 9117 2066@safe unittest 2067{ 2068 static struct Frop {} 2069 2070 static struct Foo 2071 { 2072 int n = 0; 2073 alias n this; 2074 T opCast(T) () if (is(T == Frop)) 2075 { 2076 return Frop(); 2077 } 2078 string toString() 2079 { 2080 return "Foo"; 2081 } 2082 } 2083 2084 static struct Bar 2085 { 2086 Foo foo; 2087 alias foo this; 2088 string toString() 2089 { 2090 return "Bar"; 2091 } 2092 } 2093 2094 const(char)[] result; 2095 void put(const char[] s){ result ~= s; } 2096 2097 Foo foo; 2098 formattedWrite(&put, "%s", foo); // OK 2099 assert(result == "Foo"); 2100 2101 result = null; 2102 2103 Bar bar; 2104 formattedWrite(&put, "%s", bar); // NG 2105 assert(result == "Bar"); 2106 2107 result = null; 2108 2109 int i = 9; 2110 formattedWrite(&put, "%s", 9); 2111 assert(result == "9"); 2112} 2113 2114private enum ctfpMessage = "Cannot format floating point types at compile-time"; 2115 2116/** 2117Floating-point values are formatted like $(D printf) does. 2118 2119Params: 2120 w = The $(D OutputRange) to write to. 2121 obj = The value to write. 2122 f = The $(D FormatSpec) defining how to write the value. 2123 */ 2124void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) 2125if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 2126{ 2127 import std.algorithm.comparison : min; 2128 import std.algorithm.searching : find; 2129 import std.string : indexOf, indexOfAny, indexOfNeither; 2130 2131 FormatSpec!Char fs = f; // fs is copy for change its values. 2132 FloatingPointTypeOf!T val = obj; 2133 2134 if (fs.spec == 'r') 2135 { 2136 // raw write, skip all else and write the thing 2137 auto raw = (ref val)@trusted{ 2138 return (cast(const char*) &val)[0 .. val.sizeof]; 2139 }(val); 2140 if (needToSwapEndianess(f)) 2141 { 2142 foreach_reverse (c; raw) 2143 put(w, c); 2144 } 2145 else 2146 { 2147 foreach (c; raw) 2148 put(w, c); 2149 } 2150 return; 2151 } 2152 enforceFmt(find("fgFGaAeEs", fs.spec).length, 2153 "incompatible format character for floating point argument: %" ~ fs.spec); 2154 enforceFmt(!__ctfe, ctfpMessage); 2155 2156 version (CRuntime_Microsoft) 2157 { 2158 import std.math : isNaN, isInfinity; 2159 immutable double tval = val; // convert early to get "inf" in case of overflow 2160 string s; 2161 if (isNaN(tval)) 2162 s = "nan"; // snprintf writes 1.#QNAN 2163 else if (isInfinity(tval)) 2164 s = val >= 0 ? "inf" : "-inf"; // snprintf writes 1.#INF 2165 2166 if (s.length > 0) 2167 { 2168 version (none) 2169 { 2170 return formatValue(w, s, f); 2171 } 2172 else // FIXME:workaround 2173 { 2174 s = s[0 .. f.precision < $ ? f.precision : $]; 2175 if (!f.flDash) 2176 { 2177 // right align 2178 if (f.width > s.length) 2179 foreach (j ; 0 .. f.width - s.length) put(w, ' '); 2180 put(w, s); 2181 } 2182 else 2183 { 2184 // left align 2185 put(w, s); 2186 if (f.width > s.length) 2187 foreach (j ; 0 .. f.width - s.length) put(w, ' '); 2188 } 2189 return; 2190 } 2191 } 2192 } 2193 else 2194 alias tval = val; 2195 if (fs.spec == 's') fs.spec = 'g'; 2196 char[1 /*%*/ + 5 /*flags*/ + 3 /*width.prec*/ + 2 /*format*/ 2197 + 1 /*\0*/] sprintfSpec = void; 2198 sprintfSpec[0] = '%'; 2199 uint i = 1; 2200 if (fs.flDash) sprintfSpec[i++] = '-'; 2201 if (fs.flPlus) sprintfSpec[i++] = '+'; 2202 if (fs.flZero) sprintfSpec[i++] = '0'; 2203 if (fs.flSpace) sprintfSpec[i++] = ' '; 2204 if (fs.flHash) sprintfSpec[i++] = '#'; 2205 sprintfSpec[i .. i + 3] = "*.*"; 2206 i += 3; 2207 if (is(Unqual!(typeof(val)) == real)) sprintfSpec[i++] = 'L'; 2208 sprintfSpec[i++] = fs.spec; 2209 sprintfSpec[i] = 0; 2210 //printf("format: '%s'; geeba: %g\n", sprintfSpec.ptr, val); 2211 char[512] buf = void; 2212 2213 immutable n = ()@trusted{ 2214 import core.stdc.stdio : snprintf; 2215 return snprintf(buf.ptr, buf.length, 2216 sprintfSpec.ptr, 2217 fs.width, 2218 // negative precision is same as no precision specified 2219 fs.precision == fs.UNSPECIFIED ? -1 : fs.precision, 2220 tval); 2221 }(); 2222 2223 enforceFmt(n >= 0, 2224 "floating point formatting failure"); 2225 2226 auto len = min(n, buf.length-1); 2227 ptrdiff_t dot = buf[0 .. len].indexOf('.'); 2228 if (fs.flSeparator && dot != -1) 2229 { 2230 ptrdiff_t firstDigit = buf[0 .. len].indexOfAny("0123456789"); 2231 ptrdiff_t ePos = buf[0 .. len].indexOf('e'); 2232 size_t j; 2233 2234 ptrdiff_t firstLen = dot - firstDigit; 2235 2236 size_t separatorScoreCnt = firstLen / fs.separators; 2237 2238 size_t afterDotIdx; 2239 if (ePos != -1) 2240 { 2241 afterDotIdx = ePos; 2242 } 2243 else 2244 { 2245 afterDotIdx = len; 2246 } 2247 2248 if (dot != -1) 2249 { 2250 ptrdiff_t mantissaLen = afterDotIdx - (dot + 1); 2251 separatorScoreCnt += (mantissaLen > 0) ? (mantissaLen - 1) / fs.separators : 0; 2252 } 2253 2254 // plus, minus prefix 2255 ptrdiff_t digitsBegin = buf[0 .. separatorScoreCnt].indexOfNeither(" "); 2256 if (digitsBegin == -1) 2257 { 2258 digitsBegin = separatorScoreCnt; 2259 } 2260 put(w, buf[digitsBegin .. firstDigit]); 2261 2262 // digits until dot with separator 2263 for (j = 0; j < firstLen; ++j) 2264 { 2265 if (j > 0 && (firstLen - j) % fs.separators == 0) 2266 { 2267 put(w, fs.separatorChar); 2268 } 2269 put(w, buf[j + firstDigit]); 2270 } 2271 put(w, '.'); 2272 2273 // digits after dot 2274 for (j = dot + 1; j < afterDotIdx; ++j) 2275 { 2276 auto realJ = (j - (dot + 1)); 2277 if (realJ != 0 && realJ % fs.separators == 0) 2278 { 2279 put(w, fs.separatorChar); 2280 } 2281 put(w, buf[j]); 2282 } 2283 2284 // rest 2285 if (ePos != -1) 2286 { 2287 put(w, buf[afterDotIdx .. len]); 2288 } 2289 } 2290 else 2291 { 2292 put(w, buf[0 .. len]); 2293 } 2294} 2295 2296/// 2297@safe unittest 2298{ 2299 import std.array : appender; 2300 auto w = appender!string(); 2301 auto spec = singleSpec("%.1f"); 2302 formatValue(w, 1337.7, spec); 2303 2304 assert(w.data == "1337.7"); 2305} 2306 2307@safe /*pure*/ unittest // formatting floating point values is now impure 2308{ 2309 import std.conv : to; 2310 2311 assert(collectExceptionMsg!FormatException(format("%d", 5.1)).back == 'd'); 2312 2313 foreach (T; AliasSeq!(float, double, real)) 2314 { 2315 formatTest( to!( T)(5.5), "5.5" ); 2316 formatTest( to!( const T)(5.5), "5.5" ); 2317 formatTest( to!(immutable T)(5.5), "5.5" ); 2318 2319 formatTest( T.nan, "nan" ); 2320 } 2321} 2322 2323@system unittest 2324{ 2325 formatTest( 2.25, "2.25" ); 2326 2327 class C1 { double val; alias val this; this(double v){ val = v; } } 2328 class C2 { double val; alias val this; this(double v){ val = v; } 2329 override string toString() const { return "C"; } } 2330 formatTest( new C1(2.25), "2.25" ); 2331 formatTest( new C2(2.25), "C" ); 2332 2333 struct S1 { double val; alias val this; } 2334 struct S2 { double val; alias val this; 2335 string toString() const { return "S"; } } 2336 formatTest( S1(2.25), "2.25" ); 2337 formatTest( S2(2.25), "S" ); 2338} 2339 2340/* 2341Formatting a $(D creal) is deprecated but still kept around for a while. 2342 2343Params: 2344 w = The $(D OutputRange) to write to. 2345 obj = The value to write. 2346 f = The $(D FormatSpec) defining how to write the value. 2347 */ 2348void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) 2349if (is(Unqual!T : creal) && !is(T == enum) && !hasToString!(T, Char)) 2350{ 2351 immutable creal val = obj; 2352 2353 formatValue(w, val.re, f); 2354 if (val.im >= 0) 2355 { 2356 put(w, '+'); 2357 } 2358 formatValue(w, val.im, f); 2359 put(w, 'i'); 2360} 2361 2362@safe /*pure*/ unittest // formatting floating point values is now impure 2363{ 2364 import std.conv : to; 2365 foreach (T; AliasSeq!(cfloat, cdouble, creal)) 2366 { 2367 formatTest( to!( T)(1 + 1i), "1+1i" ); 2368 formatTest( to!( const T)(1 + 1i), "1+1i" ); 2369 formatTest( to!(immutable T)(1 + 1i), "1+1i" ); 2370 } 2371 foreach (T; AliasSeq!(cfloat, cdouble, creal)) 2372 { 2373 formatTest( to!( T)(0 - 3i), "0-3i" ); 2374 formatTest( to!( const T)(0 - 3i), "0-3i" ); 2375 formatTest( to!(immutable T)(0 - 3i), "0-3i" ); 2376 } 2377} 2378 2379@system unittest 2380{ 2381 formatTest( 3+2.25i, "3+2.25i" ); 2382 2383 class C1 { cdouble val; alias val this; this(cdouble v){ val = v; } } 2384 class C2 { cdouble val; alias val this; this(cdouble v){ val = v; } 2385 override string toString() const { return "C"; } } 2386 formatTest( new C1(3+2.25i), "3+2.25i" ); 2387 formatTest( new C2(3+2.25i), "C" ); 2388 2389 struct S1 { cdouble val; alias val this; } 2390 struct S2 { cdouble val; alias val this; 2391 string toString() const { return "S"; } } 2392 formatTest( S1(3+2.25i), "3+2.25i" ); 2393 formatTest( S2(3+2.25i), "S" ); 2394} 2395 2396/* 2397 Formatting an $(D ireal) is deprecated but still kept around for a while. 2398 2399Params: 2400 w = The $(D OutputRange) to write to. 2401 obj = The value to write. 2402 f = The $(D FormatSpec) defining how to write the value. 2403 */ 2404void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) 2405if (is(Unqual!T : ireal) && !is(T == enum) && !hasToString!(T, Char)) 2406{ 2407 immutable ireal val = obj; 2408 2409 formatValue(w, val.im, f); 2410 put(w, 'i'); 2411} 2412 2413@safe /*pure*/ unittest // formatting floating point values is now impure 2414{ 2415 import std.conv : to; 2416 foreach (T; AliasSeq!(ifloat, idouble, ireal)) 2417 { 2418 formatTest( to!( T)(1i), "1i" ); 2419 formatTest( to!( const T)(1i), "1i" ); 2420 formatTest( to!(immutable T)(1i), "1i" ); 2421 } 2422} 2423 2424@system unittest 2425{ 2426 formatTest( 2.25i, "2.25i" ); 2427 2428 class C1 { idouble val; alias val this; this(idouble v){ val = v; } } 2429 class C2 { idouble val; alias val this; this(idouble v){ val = v; } 2430 override string toString() const { return "C"; } } 2431 formatTest( new C1(2.25i), "2.25i" ); 2432 formatTest( new C2(2.25i), "C" ); 2433 2434 struct S1 { idouble val; alias val this; } 2435 struct S2 { idouble val; alias val this; 2436 string toString() const { return "S"; } } 2437 formatTest( S1(2.25i), "2.25i" ); 2438 formatTest( S2(2.25i), "S" ); 2439} 2440 2441/** 2442Individual characters ($(D char), $(D wchar), or $(D dchar)) are formatted as 2443Unicode characters with %s and as integers with integral-specific format 2444specs. 2445 2446Params: 2447 w = The $(D OutputRange) to write to. 2448 obj = The value to write. 2449 f = The $(D FormatSpec) defining how to write the value. 2450 */ 2451void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) 2452if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 2453{ 2454 CharTypeOf!T val = obj; 2455 2456 if (f.spec == 's' || f.spec == 'c') 2457 { 2458 put(w, val); 2459 } 2460 else 2461 { 2462 alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2]; 2463 formatValue(w, cast(U) val, f); 2464 } 2465} 2466 2467/// 2468@safe pure unittest 2469{ 2470 import std.array : appender; 2471 auto w = appender!string(); 2472 auto spec = singleSpec("%c"); 2473 formatValue(w, 'a', spec); 2474 2475 assert(w.data == "a"); 2476} 2477 2478@safe pure unittest 2479{ 2480 assertCTFEable!( 2481 { 2482 formatTest( 'c', "c" ); 2483 }); 2484} 2485 2486@system unittest 2487{ 2488 class C1 { char val; alias val this; this(char v){ val = v; } } 2489 class C2 { char val; alias val this; this(char v){ val = v; } 2490 override string toString() const { return "C"; } } 2491 formatTest( new C1('c'), "c" ); 2492 formatTest( new C2('c'), "C" ); 2493 2494 struct S1 { char val; alias val this; } 2495 struct S2 { char val; alias val this; 2496 string toString() const { return "S"; } } 2497 formatTest( S1('c'), "c" ); 2498 formatTest( S2('c'), "S" ); 2499} 2500 2501@safe unittest 2502{ 2503 //Little Endian 2504 formatTest( "%-r", cast( char)'c', ['c' ] ); 2505 formatTest( "%-r", cast(wchar)'c', ['c', 0 ] ); 2506 formatTest( "%-r", cast(dchar)'c', ['c', 0, 0, 0] ); 2507 formatTest( "%-r", '���', ['\x2c', '\x67'] ); 2508 2509 //Big Endian 2510 formatTest( "%+r", cast( char)'c', [ 'c'] ); 2511 formatTest( "%+r", cast(wchar)'c', [0, 'c'] ); 2512 formatTest( "%+r", cast(dchar)'c', [0, 0, 0, 'c'] ); 2513 formatTest( "%+r", '���', ['\x67', '\x2c'] ); 2514} 2515 2516/** 2517Strings are formatted like $(D printf) does. 2518 2519Params: 2520 w = The $(D OutputRange) to write to. 2521 obj = The value to write. 2522 f = The $(D FormatSpec) defining how to write the value. 2523 */ 2524void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) 2525if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 2526{ 2527 Unqual!(StringTypeOf!T) val = obj; // for `alias this`, see bug5371 2528 formatRange(w, val, f); 2529} 2530 2531/// 2532@safe pure unittest 2533{ 2534 import std.array : appender; 2535 auto w = appender!string(); 2536 auto spec = singleSpec("%s"); 2537 formatValue(w, "hello", spec); 2538 2539 assert(w.data == "hello"); 2540} 2541 2542@safe unittest 2543{ 2544 formatTest( "abc", "abc" ); 2545} 2546 2547@system unittest 2548{ 2549 // Test for bug 5371 for classes 2550 class C1 { const string var; alias var this; this(string s){ var = s; } } 2551 class C2 { string var; alias var this; this(string s){ var = s; } } 2552 formatTest( new C1("c1"), "c1" ); 2553 formatTest( new C2("c2"), "c2" ); 2554 2555 // Test for bug 5371 for structs 2556 struct S1 { const string var; alias var this; } 2557 struct S2 { string var; alias var this; } 2558 formatTest( S1("s1"), "s1" ); 2559 formatTest( S2("s2"), "s2" ); 2560} 2561 2562@system unittest 2563{ 2564 class C3 { string val; alias val this; this(string s){ val = s; } 2565 override string toString() const { return "C"; } } 2566 formatTest( new C3("c3"), "C" ); 2567 2568 struct S3 { string val; alias val this; 2569 string toString() const { return "S"; } } 2570 formatTest( S3("s3"), "S" ); 2571} 2572 2573@safe pure unittest 2574{ 2575 //Little Endian 2576 formatTest( "%-r", "ab"c, ['a' , 'b' ] ); 2577 formatTest( "%-r", "ab"w, ['a', 0 , 'b', 0 ] ); 2578 formatTest( "%-r", "ab"d, ['a', 0, 0, 0, 'b', 0, 0, 0] ); 2579 formatTest( "%-r", "���������"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] ); 2580 formatTest( "%-r", "���������"w, ['\xe5', '\x65', '\x2c', '\x67', '\x9e', '\x8a']); 2581 formatTest( "%-r", "���������"d, ['\xe5', '\x65', '\x00', '\x00', '\x2c', '\x67', 2582 '\x00', '\x00', '\x9e', '\x8a', '\x00', '\x00'] ); 2583 2584 //Big Endian 2585 formatTest( "%+r", "ab"c, [ 'a', 'b'] ); 2586 formatTest( "%+r", "ab"w, [ 0, 'a', 0, 'b'] ); 2587 formatTest( "%+r", "ab"d, [0, 0, 0, 'a', 0, 0, 0, 'b'] ); 2588 formatTest( "%+r", "���������"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] ); 2589 formatTest( "%+r", "���������"w, ['\x65', '\xe5', '\x67', '\x2c', '\x8a', '\x9e'] ); 2590 formatTest( "%+r", "���������"d, ['\x00', '\x00', '\x65', '\xe5', '\x00', '\x00', 2591 '\x67', '\x2c', '\x00', '\x00', '\x8a', '\x9e'] ); 2592} 2593 2594/** 2595Static-size arrays are formatted as dynamic arrays. 2596 2597Params: 2598 w = The $(D OutputRange) to write to. 2599 obj = The value to write. 2600 f = The $(D FormatSpec) defining how to write the value. 2601 */ 2602void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T obj, const ref FormatSpec!Char f) 2603if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 2604{ 2605 formatValue(w, obj[], f); 2606} 2607 2608/// 2609@safe pure unittest 2610{ 2611 import std.array : appender; 2612 auto w = appender!string(); 2613 auto spec = singleSpec("%s"); 2614 char[2] two = ['a', 'b']; 2615 formatValue(w, two, spec); 2616 2617 assert(w.data == "ab"); 2618} 2619 2620@safe unittest // Test for issue 8310 2621{ 2622 import std.array : appender; 2623 FormatSpec!char f; 2624 auto w = appender!string(); 2625 2626 char[2] two = ['a', 'b']; 2627 formatValue(w, two, f); 2628 2629 char[2] getTwo(){ return two; } 2630 formatValue(w, getTwo(), f); 2631} 2632 2633/** 2634Dynamic arrays are formatted as input ranges. 2635 2636Specializations: 2637 $(UL $(LI $(D void[]) is formatted like $(D ubyte[]).) 2638 $(LI Const array is converted to input range by removing its qualifier.)) 2639 2640Params: 2641 w = The $(D OutputRange) to write to. 2642 obj = The value to write. 2643 f = The $(D FormatSpec) defining how to write the value. 2644 */ 2645void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) 2646if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 2647{ 2648 static if (is(const(ArrayTypeOf!T) == const(void[]))) 2649 { 2650 formatValue(w, cast(const ubyte[]) obj, f); 2651 } 2652 else static if (!isInputRange!T) 2653 { 2654 alias U = Unqual!(ArrayTypeOf!T); 2655 static assert(isInputRange!U); 2656 U val = obj; 2657 formatValue(w, val, f); 2658 } 2659 else 2660 { 2661 formatRange(w, obj, f); 2662 } 2663} 2664 2665/// 2666@safe pure unittest 2667{ 2668 import std.array : appender; 2669 auto w = appender!string(); 2670 auto spec = singleSpec("%s"); 2671 auto two = [1, 2]; 2672 formatValue(w, two, spec); 2673 2674 assert(w.data == "[1, 2]"); 2675} 2676 2677// alias this, input range I/F, and toString() 2678@system unittest 2679{ 2680 struct S(int flags) 2681 { 2682 int[] arr; 2683 static if (flags & 1) 2684 alias arr this; 2685 2686 static if (flags & 2) 2687 { 2688 @property bool empty() const { return arr.length == 0; } 2689 @property int front() const { return arr[0] * 2; } 2690 void popFront() { arr = arr[1..$]; } 2691 } 2692 2693 static if (flags & 4) 2694 string toString() const { return "S"; } 2695 } 2696 formatTest(S!0b000([0, 1, 2]), "S!0([0, 1, 2])"); 2697 formatTest(S!0b001([0, 1, 2]), "[0, 1, 2]"); // Test for bug 7628 2698 formatTest(S!0b010([0, 1, 2]), "[0, 2, 4]"); 2699 formatTest(S!0b011([0, 1, 2]), "[0, 2, 4]"); 2700 formatTest(S!0b100([0, 1, 2]), "S"); 2701 formatTest(S!0b101([0, 1, 2]), "S"); // Test for bug 7628 2702 formatTest(S!0b110([0, 1, 2]), "S"); 2703 formatTest(S!0b111([0, 1, 2]), "S"); 2704 2705 class C(uint flags) 2706 { 2707 int[] arr; 2708 static if (flags & 1) 2709 alias arr this; 2710 2711 this(int[] a) { arr = a; } 2712 2713 static if (flags & 2) 2714 { 2715 @property bool empty() const { return arr.length == 0; } 2716 @property int front() const { return arr[0] * 2; } 2717 void popFront() { arr = arr[1..$]; } 2718 } 2719 2720 static if (flags & 4) 2721 override string toString() const { return "C"; } 2722 } 2723 formatTest(new C!0b000([0, 1, 2]), (new C!0b000([])).toString()); 2724 formatTest(new C!0b001([0, 1, 2]), "[0, 1, 2]"); // Test for bug 7628 2725 formatTest(new C!0b010([0, 1, 2]), "[0, 2, 4]"); 2726 formatTest(new C!0b011([0, 1, 2]), "[0, 2, 4]"); 2727 formatTest(new C!0b100([0, 1, 2]), "C"); 2728 formatTest(new C!0b101([0, 1, 2]), "C"); // Test for bug 7628 2729 formatTest(new C!0b110([0, 1, 2]), "C"); 2730 formatTest(new C!0b111([0, 1, 2]), "C"); 2731} 2732 2733@system unittest 2734{ 2735 // void[] 2736 void[] val0; 2737 formatTest( val0, "[]" ); 2738 2739 void[] val = cast(void[]) cast(ubyte[])[1, 2, 3]; 2740 formatTest( val, "[1, 2, 3]" ); 2741 2742 void[0] sval0 = []; 2743 formatTest( sval0, "[]"); 2744 2745 void[3] sval = cast(void[3]) cast(ubyte[3])[1, 2, 3]; 2746 formatTest( sval, "[1, 2, 3]" ); 2747} 2748 2749@safe unittest 2750{ 2751 // const(T[]) -> const(T)[] 2752 const short[] a = [1, 2, 3]; 2753 formatTest( a, "[1, 2, 3]" ); 2754 2755 struct S { const(int[]) arr; alias arr this; } 2756 auto s = S([1,2,3]); 2757 formatTest( s, "[1, 2, 3]" ); 2758} 2759 2760@safe unittest 2761{ 2762 // 6640 2763 struct Range 2764 { 2765 @safe: 2766 string value; 2767 @property bool empty() const { return !value.length; } 2768 @property dchar front() const { return value.front; } 2769 void popFront() { value.popFront(); } 2770 2771 @property size_t length() const { return value.length; } 2772 } 2773 immutable table = 2774 [ 2775 ["[%s]", "[string]"], 2776 ["[%10s]", "[ string]"], 2777 ["[%-10s]", "[string ]"], 2778 ["[%(%02x %)]", "[73 74 72 69 6e 67]"], 2779 ["[%(%c %)]", "[s t r i n g]"], 2780 ]; 2781 foreach (e; table) 2782 { 2783 formatTest(e[0], "string", e[1]); 2784 formatTest(e[0], Range("string"), e[1]); 2785 } 2786} 2787 2788@system unittest 2789{ 2790 // string literal from valid UTF sequence is encoding free. 2791 foreach (StrType; AliasSeq!(string, wstring, dstring)) 2792 { 2793 // Valid and printable (ASCII) 2794 formatTest( [cast(StrType)"hello"], 2795 `["hello"]` ); 2796 2797 // 1 character escape sequences (' is not escaped in strings) 2798 formatTest( [cast(StrType)"\"'\0\\\a\b\f\n\r\t\v"], 2799 `["\"'\0\\\a\b\f\n\r\t\v"]` ); 2800 2801 // 1 character optional escape sequences 2802 formatTest( [cast(StrType)"\'\?"], 2803 `["'?"]` ); 2804 2805 // Valid and non-printable code point (<= U+FF) 2806 formatTest( [cast(StrType)"\x10\x1F\x20test"], 2807 `["\x10\x1F test"]` ); 2808 2809 // Valid and non-printable code point (<= U+FFFF) 2810 formatTest( [cast(StrType)"\u200B..\u200F"], 2811 `["\u200B..\u200F"]` ); 2812 2813 // Valid and non-printable code point (<= U+10FFFF) 2814 formatTest( [cast(StrType)"\U000E0020..\U000E007F"], 2815 `["\U000E0020..\U000E007F"]` ); 2816 } 2817 2818 // invalid UTF sequence needs hex-string literal postfix (c/w/d) 2819 { 2820 // U+FFFF with UTF-8 (Invalid code point for interchange) 2821 formatTest( [cast(string)[0xEF, 0xBF, 0xBF]], 2822 `[x"EF BF BF"c]` ); 2823 2824 // U+FFFF with UTF-16 (Invalid code point for interchange) 2825 formatTest( [cast(wstring)[0xFFFF]], 2826 `[x"FFFF"w]` ); 2827 2828 // U+FFFF with UTF-32 (Invalid code point for interchange) 2829 formatTest( [cast(dstring)[0xFFFF]], 2830 `[x"FFFF"d]` ); 2831 } 2832} 2833 2834@safe unittest 2835{ 2836 // nested range formatting with array of string 2837 formatTest( "%({%(%02x %)}%| %)", ["test", "msg"], 2838 `{74 65 73 74} {6d 73 67}` ); 2839} 2840 2841@safe unittest 2842{ 2843 // stop auto escaping inside range formatting 2844 auto arr = ["hello", "world"]; 2845 formatTest( "%(%s, %)", arr, `"hello", "world"` ); 2846 formatTest( "%-(%s, %)", arr, `hello, world` ); 2847 2848 auto aa1 = [1:"hello", 2:"world"]; 2849 formatTest( "%(%s:%s, %)", aa1, [`1:"hello", 2:"world"`, `2:"world", 1:"hello"`] ); 2850 formatTest( "%-(%s:%s, %)", aa1, [`1:hello, 2:world`, `2:world, 1:hello`] ); 2851 2852 auto aa2 = [1:["ab", "cd"], 2:["ef", "gh"]]; 2853 formatTest( "%-(%s:%s, %)", aa2, [`1:["ab", "cd"], 2:["ef", "gh"]`, `2:["ef", "gh"], 1:["ab", "cd"]`] ); 2854 formatTest( "%-(%s:%(%s%), %)", aa2, [`1:"ab""cd", 2:"ef""gh"`, `2:"ef""gh", 1:"ab""cd"`] ); 2855 formatTest( "%-(%s:%-(%s%)%|, %)", aa2, [`1:abcd, 2:efgh`, `2:efgh, 1:abcd`] ); 2856} 2857 2858// input range formatting 2859private void formatRange(Writer, T, Char)(ref Writer w, ref T val, const ref FormatSpec!Char f) 2860if (isInputRange!T) 2861{ 2862 import std.conv : text; 2863 2864 // Formatting character ranges like string 2865 if (f.spec == 's') 2866 { 2867 alias E = ElementType!T; 2868 2869 static if (!is(E == enum) && is(CharTypeOf!E)) 2870 { 2871 static if (is(StringTypeOf!T)) 2872 { 2873 auto s = val[0 .. f.precision < $ ? f.precision : $]; 2874 if (!f.flDash) 2875 { 2876 // right align 2877 if (f.width > s.length) 2878 foreach (i ; 0 .. f.width - s.length) put(w, ' '); 2879 put(w, s); 2880 } 2881 else 2882 { 2883 // left align 2884 put(w, s); 2885 if (f.width > s.length) 2886 foreach (i ; 0 .. f.width - s.length) put(w, ' '); 2887 } 2888 } 2889 else 2890 { 2891 if (!f.flDash) 2892 { 2893 static if (hasLength!T) 2894 { 2895 // right align 2896 auto len = val.length; 2897 } 2898 else static if (isForwardRange!T && !isInfinite!T) 2899 { 2900 auto len = walkLength(val.save); 2901 } 2902 else 2903 { 2904 enforce(f.width == 0, "Cannot right-align a range without length"); 2905 size_t len = 0; 2906 } 2907 if (f.precision != f.UNSPECIFIED && len > f.precision) 2908 len = f.precision; 2909 2910 if (f.width > len) 2911 foreach (i ; 0 .. f.width - len) 2912 put(w, ' '); 2913 if (f.precision == f.UNSPECIFIED) 2914 put(w, val); 2915 else 2916 { 2917 size_t printed = 0; 2918 for (; !val.empty && printed < f.precision; val.popFront(), ++printed) 2919 put(w, val.front); 2920 } 2921 } 2922 else 2923 { 2924 size_t printed = void; 2925 2926 // left align 2927 if (f.precision == f.UNSPECIFIED) 2928 { 2929 static if (hasLength!T) 2930 { 2931 printed = val.length; 2932 put(w, val); 2933 } 2934 else 2935 { 2936 printed = 0; 2937 for (; !val.empty; val.popFront(), ++printed) 2938 put(w, val.front); 2939 } 2940 } 2941 else 2942 { 2943 printed = 0; 2944 for (; !val.empty && printed < f.precision; val.popFront(), ++printed) 2945 put(w, val.front); 2946 } 2947 2948 if (f.width > printed) 2949 foreach (i ; 0 .. f.width - printed) 2950 put(w, ' '); 2951 } 2952 } 2953 } 2954 else 2955 { 2956 put(w, f.seqBefore); 2957 if (!val.empty) 2958 { 2959 formatElement(w, val.front, f); 2960 val.popFront(); 2961 for (size_t i; !val.empty; val.popFront(), ++i) 2962 { 2963 put(w, f.seqSeparator); 2964 formatElement(w, val.front, f); 2965 } 2966 } 2967 static if (!isInfinite!T) put(w, f.seqAfter); 2968 } 2969 } 2970 else if (f.spec == 'r') 2971 { 2972 static if (is(DynamicArrayTypeOf!T)) 2973 { 2974 alias ARR = DynamicArrayTypeOf!T; 2975 foreach (e ; cast(ARR) val) 2976 { 2977 formatValue(w, e, f); 2978 } 2979 } 2980 else 2981 { 2982 for (size_t i; !val.empty; val.popFront(), ++i) 2983 { 2984 formatValue(w, val.front, f); 2985 } 2986 } 2987 } 2988 else if (f.spec == '(') 2989 { 2990 if (val.empty) 2991 return; 2992 // Nested specifier is to be used 2993 for (;;) 2994 { 2995 auto fmt = FormatSpec!Char(f.nested); 2996 fmt.writeUpToNextSpec(w); 2997 if (f.flDash) 2998 formatValue(w, val.front, fmt); 2999 else 3000 formatElement(w, val.front, fmt); 3001 if (f.sep !is null) 3002 { 3003 put(w, fmt.trailing); 3004 val.popFront(); 3005 if (val.empty) 3006 break; 3007 put(w, f.sep); 3008 } 3009 else 3010 { 3011 val.popFront(); 3012 if (val.empty) 3013 break; 3014 put(w, fmt.trailing); 3015 } 3016 } 3017 } 3018 else 3019 throw new Exception(text("Incorrect format specifier for range: %", f.spec)); 3020} 3021 3022@safe pure unittest 3023{ 3024 assert(collectExceptionMsg(format("%d", "hi")).back == 'd'); 3025} 3026 3027// character formatting with ecaping 3028private void formatChar(Writer)(ref Writer w, in dchar c, in char quote) 3029{ 3030 import std.uni : isGraphical; 3031 3032 string fmt; 3033 if (isGraphical(c)) 3034 { 3035 if (c == quote || c == '\\') 3036 put(w, '\\'); 3037 put(w, c); 3038 return; 3039 } 3040 else if (c <= 0xFF) 3041 { 3042 if (c < 0x20) 3043 { 3044 foreach (i, k; "\n\r\t\a\b\f\v\0") 3045 { 3046 if (c == k) 3047 { 3048 put(w, '\\'); 3049 put(w, "nrtabfv0"[i]); 3050 return; 3051 } 3052 } 3053 } 3054 fmt = "\\x%02X"; 3055 } 3056 else if (c <= 0xFFFF) 3057 fmt = "\\u%04X"; 3058 else 3059 fmt = "\\U%08X"; 3060 3061 formattedWrite(w, fmt, cast(uint) c); 3062} 3063 3064// undocumented because of deprecation 3065// string elements are formatted like UTF-8 string literals. 3066void formatElement(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) 3067if (is(StringTypeOf!T) && !is(T == enum)) 3068{ 3069 import std.array : appender; 3070 import std.utf : UTFException; 3071 3072 StringTypeOf!T str = val; // bug 8015 3073 3074 if (f.spec == 's') 3075 { 3076 try 3077 { 3078 // ignore other specifications and quote 3079 auto app = appender!(typeof(val[0])[])(); 3080 put(app, '\"'); 3081 for (size_t i = 0; i < str.length; ) 3082 { 3083 import std.utf : decode; 3084 3085 auto c = decode(str, i); 3086 // \uFFFE and \uFFFF are considered valid by isValidDchar, 3087 // so need checking for interchange. 3088 if (c == 0xFFFE || c == 0xFFFF) 3089 goto LinvalidSeq; 3090 formatChar(app, c, '"'); 3091 } 3092 put(app, '\"'); 3093 put(w, app.data); 3094 return; 3095 } 3096 catch (UTFException) 3097 { 3098 } 3099 3100 // If val contains invalid UTF sequence, formatted like HexString literal 3101 LinvalidSeq: 3102 static if (is(typeof(str[0]) : const(char))) 3103 { 3104 enum postfix = 'c'; 3105 alias IntArr = const(ubyte)[]; 3106 } 3107 else static if (is(typeof(str[0]) : const(wchar))) 3108 { 3109 enum postfix = 'w'; 3110 alias IntArr = const(ushort)[]; 3111 } 3112 else static if (is(typeof(str[0]) : const(dchar))) 3113 { 3114 enum postfix = 'd'; 3115 alias IntArr = const(uint)[]; 3116 } 3117 formattedWrite(w, "x\"%(%02X %)\"%s", cast(IntArr) str, postfix); 3118 } 3119 else 3120 formatValue(w, str, f); 3121} 3122 3123@safe pure unittest 3124{ 3125 import std.array : appender; 3126 auto w = appender!string(); 3127 auto spec = singleSpec("%s"); 3128 formatElement(w, "Hello World", spec); 3129 3130 assert(w.data == "\"Hello World\""); 3131} 3132 3133@safe unittest 3134{ 3135 // Test for bug 8015 3136 import std.typecons; 3137 3138 struct MyStruct { 3139 string str; 3140 @property string toStr() { 3141 return str; 3142 } 3143 alias toStr this; 3144 } 3145 3146 Tuple!(MyStruct) t; 3147} 3148 3149// undocumented because of deprecation 3150// Character elements are formatted like UTF-8 character literals. 3151void formatElement(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) 3152if (is(CharTypeOf!T) && !is(T == enum)) 3153{ 3154 if (f.spec == 's') 3155 { 3156 put(w, '\''); 3157 formatChar(w, val, '\''); 3158 put(w, '\''); 3159 } 3160 else 3161 formatValue(w, val, f); 3162} 3163 3164/// 3165@safe unittest 3166{ 3167 import std.array : appender; 3168 auto w = appender!string(); 3169 auto spec = singleSpec("%s"); 3170 formatElement(w, "H", spec); 3171 3172 assert(w.data == "\"H\"", w.data); 3173} 3174 3175// undocumented 3176// Maybe T is noncopyable struct, so receive it by 'auto ref'. 3177void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, const ref FormatSpec!Char f) 3178if (!is(StringTypeOf!T) && !is(CharTypeOf!T) || is(T == enum)) 3179{ 3180 formatValue(w, val, f); 3181} 3182 3183/** 3184 Associative arrays are formatted by using $(D ':') and $(D ", ") as 3185 separators, and enclosed by $(D '[') and $(D ']'). 3186 3187Params: 3188 w = The $(D OutputRange) to write to. 3189 obj = The value to write. 3190 f = The $(D FormatSpec) defining how to write the value. 3191 */ 3192void formatValue(Writer, T, Char)(auto ref Writer w, T obj, const ref FormatSpec!Char f) 3193if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 3194{ 3195 AssocArrayTypeOf!T val = obj; 3196 3197 enforceFmt(f.spec == 's' || f.spec == '(', 3198 "incompatible format character for associative array argument: %" ~ f.spec); 3199 3200 enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator; 3201 auto fmtSpec = f.spec == '(' ? f.nested : defSpec; 3202 3203 size_t i = 0; 3204 immutable end = val.length; 3205 3206 if (f.spec == 's') 3207 put(w, f.seqBefore); 3208 foreach (k, ref v; val) 3209 { 3210 auto fmt = FormatSpec!Char(fmtSpec); 3211 fmt.writeUpToNextSpec(w); 3212 if (f.flDash) 3213 { 3214 formatValue(w, k, fmt); 3215 fmt.writeUpToNextSpec(w); 3216 formatValue(w, v, fmt); 3217 } 3218 else 3219 { 3220 formatElement(w, k, fmt); 3221 fmt.writeUpToNextSpec(w); 3222 formatElement(w, v, fmt); 3223 } 3224 if (f.sep !is null) 3225 { 3226 fmt.writeUpToNextSpec(w); 3227 if (++i != end) 3228 put(w, f.sep); 3229 } 3230 else 3231 { 3232 if (++i != end) 3233 fmt.writeUpToNextSpec(w); 3234 } 3235 } 3236 if (f.spec == 's') 3237 put(w, f.seqAfter); 3238} 3239 3240/// 3241@safe pure unittest 3242{ 3243 import std.array : appender; 3244 auto w = appender!string(); 3245 auto spec = singleSpec("%s"); 3246 auto aa = ["H":"W"]; 3247 formatElement(w, aa, spec); 3248 3249 assert(w.data == "[\"H\":\"W\"]", w.data); 3250} 3251 3252@safe unittest 3253{ 3254 assert(collectExceptionMsg!FormatException(format("%d", [0:1])).back == 'd'); 3255 3256 int[string] aa0; 3257 formatTest( aa0, `[]` ); 3258 3259 // elements escaping 3260 formatTest( ["aaa":1, "bbb":2], 3261 [`["aaa":1, "bbb":2]`, `["bbb":2, "aaa":1]`] ); 3262 formatTest( ['c':"str"], 3263 `['c':"str"]` ); 3264 formatTest( ['"':"\"", '\'':"'"], 3265 [`['"':"\"", '\'':"'"]`, `['\'':"'", '"':"\""]`] ); 3266 3267 // range formatting for AA 3268 auto aa3 = [1:"hello", 2:"world"]; 3269 // escape 3270 formatTest( "{%(%s:%s $ %)}", aa3, 3271 [`{1:"hello" $ 2:"world"}`, `{2:"world" $ 1:"hello"}`]); 3272 // use range formatting for key and value, and use %| 3273 formatTest( "{%([%04d->%(%c.%)]%| $ %)}", aa3, 3274 [`{[0001->h.e.l.l.o] $ [0002->w.o.r.l.d]}`, `{[0002->w.o.r.l.d] $ [0001->h.e.l.l.o]}`] ); 3275 3276 // issue 12135 3277 formatTest("%(%s:<%s>%|,%)", [1:2], "1:<2>"); 3278 formatTest("%(%s:<%s>%|%)" , [1:2], "1:<2>"); 3279} 3280 3281@system unittest 3282{ 3283 class C1 { int[char] val; alias val this; this(int[char] v){ val = v; } } 3284 class C2 { int[char] val; alias val this; this(int[char] v){ val = v; } 3285 override string toString() const { return "C"; } } 3286 formatTest( new C1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] ); 3287 formatTest( new C2(['c':1, 'd':2]), "C" ); 3288 3289 struct S1 { int[char] val; alias val this; } 3290 struct S2 { int[char] val; alias val this; 3291 string toString() const { return "S"; } } 3292 formatTest( S1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] ); 3293 formatTest( S2(['c':1, 'd':2]), "S" ); 3294} 3295 3296@safe unittest // Issue 8921 3297{ 3298 enum E : char { A = 'a', B = 'b', C = 'c' } 3299 E[3] e = [E.A, E.B, E.C]; 3300 formatTest(e, "[A, B, C]"); 3301 3302 E[] e2 = [E.A, E.B, E.C]; 3303 formatTest(e2, "[A, B, C]"); 3304} 3305 3306template hasToString(T, Char) 3307{ 3308 static if (isPointer!T && !isAggregateType!T) 3309 { 3310 // X* does not have toString, even if X is aggregate type has toString. 3311 enum hasToString = 0; 3312 } 3313 else static if (is(typeof({ T val = void; FormatSpec!Char f; val.toString((const(char)[] s){}, f); }))) 3314 { 3315 enum hasToString = 4; 3316 } 3317 else static if (is(typeof({ T val = void; val.toString((const(char)[] s){}, "%s"); }))) 3318 { 3319 enum hasToString = 3; 3320 } 3321 else static if (is(typeof({ T val = void; val.toString((const(char)[] s){}); }))) 3322 { 3323 enum hasToString = 2; 3324 } 3325 else static if (is(typeof({ T val = void; return val.toString(); }()) S) && isSomeString!S) 3326 { 3327 enum hasToString = 1; 3328 } 3329 else 3330 { 3331 enum hasToString = 0; 3332 } 3333} 3334 3335// object formatting with toString 3336private void formatObject(Writer, T, Char)(ref Writer w, ref T val, const ref FormatSpec!Char f) 3337if (hasToString!(T, Char)) 3338{ 3339 static if (is(typeof(val.toString((const(char)[] s){}, f)))) 3340 { 3341 val.toString((const(char)[] s) { put(w, s); }, f); 3342 } 3343 else static if (is(typeof(val.toString((const(char)[] s){}, "%s")))) 3344 { 3345 val.toString((const(char)[] s) { put(w, s); }, f.getCurFmtStr()); 3346 } 3347 else static if (is(typeof(val.toString((const(char)[] s){})))) 3348 { 3349 val.toString((const(char)[] s) { put(w, s); }); 3350 } 3351 else static if (is(typeof(val.toString()) S) && isSomeString!S) 3352 { 3353 put(w, val.toString()); 3354 } 3355 else 3356 static assert(0); 3357} 3358 3359void enforceValidFormatSpec(T, Char)(const ref FormatSpec!Char f) 3360{ 3361 static if (!isInputRange!T && hasToString!(T, Char) != 4) 3362 { 3363 enforceFmt(f.spec == 's', 3364 "Expected '%s' format specifier for type '" ~ T.stringof ~ "'"); 3365 } 3366} 3367 3368@system unittest 3369{ 3370 static interface IF1 { } 3371 class CIF1 : IF1 { } 3372 static struct SF1 { } 3373 static union UF1 { } 3374 static class CF1 { } 3375 3376 static interface IF2 { string toString(); } 3377 static class CIF2 : IF2 { override string toString() { return ""; } } 3378 static struct SF2 { string toString() { return ""; } } 3379 static union UF2 { string toString() { return ""; } } 3380 static class CF2 { override string toString() { return ""; } } 3381 3382 static interface IK1 { void toString(scope void delegate(const(char)[]) sink, 3383 FormatSpec!char) const; } 3384 static class CIK1 : IK1 { override void toString(scope void delegate(const(char)[]) sink, 3385 FormatSpec!char) const { sink("CIK1"); } } 3386 static struct KS1 { void toString(scope void delegate(const(char)[]) sink, 3387 FormatSpec!char) const { sink("KS1"); } } 3388 3389 static union KU1 { void toString(scope void delegate(const(char)[]) sink, 3390 FormatSpec!char) const { sink("KU1"); } } 3391 3392 static class KC1 { void toString(scope void delegate(const(char)[]) sink, 3393 FormatSpec!char) const { sink("KC1"); } } 3394 3395 IF1 cif1 = new CIF1; 3396 assertThrown!FormatException(format("%f", cif1)); 3397 assertThrown!FormatException(format("%f", SF1())); 3398 assertThrown!FormatException(format("%f", UF1())); 3399 assertThrown!FormatException(format("%f", new CF1())); 3400 3401 IF2 cif2 = new CIF2; 3402 assertThrown!FormatException(format("%f", cif2)); 3403 assertThrown!FormatException(format("%f", SF2())); 3404 assertThrown!FormatException(format("%f", UF2())); 3405 assertThrown!FormatException(format("%f", new CF2())); 3406 3407 IK1 cik1 = new CIK1; 3408 assert(format("%f", cik1) == "CIK1"); 3409 assert(format("%f", KS1()) == "KS1"); 3410 assert(format("%f", KU1()) == "KU1"); 3411 assert(format("%f", new KC1()) == "KC1"); 3412} 3413 3414/** 3415 Aggregates ($(D struct), $(D union), $(D class), and $(D interface)) are 3416 basically formatted by calling $(D toString). 3417 $(D toString) should have one of the following signatures: 3418 3419--- 3420const void toString(scope void delegate(const(char)[]) sink, FormatSpec fmt); 3421const void toString(scope void delegate(const(char)[]) sink, string fmt); 3422const void toString(scope void delegate(const(char)[]) sink); 3423const string toString(); 3424--- 3425 3426 For the class objects which have input range interface, 3427 $(UL $(LI If the instance $(D toString) has overridden 3428 $(D Object.toString), it is used.) 3429 $(LI Otherwise, the objects are formatted as input range.)) 3430 3431 For the struct and union objects which does not have $(D toString), 3432 $(UL $(LI If they have range interface, formatted as input range.) 3433 $(LI Otherwise, they are formatted like $(D Type(field1, filed2, ...)).)) 3434 3435 Otherwise, are formatted just as their type name. 3436 */ 3437void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) 3438if (is(T == class) && !is(T == enum)) 3439{ 3440 enforceValidFormatSpec!(T, Char)(f); 3441 // TODO: Change this once toString() works for shared objects. 3442 static assert(!is(T == shared), "unable to format shared objects"); 3443 3444 if (val is null) 3445 put(w, "null"); 3446 else 3447 { 3448 static if (hasToString!(T, Char) > 1 || (!isInputRange!T && !is(BuiltinTypeOf!T))) 3449 { 3450 formatObject!(Writer, T, Char)(w, val, f); 3451 } 3452 else 3453 { 3454 //string delegate() dg = &val.toString; 3455 Object o = val; // workaround 3456 string delegate() dg = &o.toString; 3457 if (dg.funcptr != &Object.toString) // toString is overridden 3458 { 3459 formatObject(w, val, f); 3460 } 3461 else static if (isInputRange!T) 3462 { 3463 formatRange(w, val, f); 3464 } 3465 else static if (is(BuiltinTypeOf!T X)) 3466 { 3467 X x = val; 3468 formatValue(w, x, f); 3469 } 3470 else 3471 { 3472 formatObject(w, val, f); 3473 } 3474 } 3475 } 3476} 3477 3478/++ 3479 $(D formatValue) allows to reuse existing format specifiers: 3480 +/ 3481@system unittest 3482{ 3483 import std.format; 3484 3485 struct Point 3486 { 3487 int x, y; 3488 3489 void toString(scope void delegate(const(char)[]) sink, 3490 FormatSpec!char fmt) const 3491 { 3492 sink("("); 3493 sink.formatValue(x, fmt); 3494 sink(","); 3495 sink.formatValue(y, fmt); 3496 sink(")"); 3497 } 3498 } 3499 3500 auto p = Point(16,11); 3501 assert(format("%03d", p) == "(016,011)"); 3502 assert(format("%02x", p) == "(10,0b)"); 3503} 3504 3505/++ 3506 The following code compares the use of $(D formatValue) and $(D formattedWrite). 3507 +/ 3508@safe pure unittest 3509{ 3510 import std.array : appender; 3511 import std.format; 3512 3513 auto writer1 = appender!string(); 3514 writer1.formattedWrite("%08b", 42); 3515 3516 auto writer2 = appender!string(); 3517 auto f = singleSpec("%08b"); 3518 writer2.formatValue(42, f); 3519 3520 assert(writer1.data == writer2.data && writer1.data == "00101010"); 3521} 3522 3523@system unittest 3524{ 3525 import std.array : appender; 3526 import std.range.interfaces; 3527 // class range (issue 5154) 3528 auto c = inputRangeObject([1,2,3,4]); 3529 formatTest( c, "[1, 2, 3, 4]" ); 3530 assert(c.empty); 3531 c = null; 3532 formatTest( c, "null" ); 3533} 3534 3535@system unittest 3536{ 3537 // 5354 3538 // If the class has both range I/F and custom toString, the use of custom 3539 // toString routine is prioritized. 3540 3541 // Enable the use of custom toString that gets a sink delegate 3542 // for class formatting. 3543 3544 enum inputRangeCode = 3545 q{ 3546 int[] arr; 3547 this(int[] a){ arr = a; } 3548 @property int front() const { return arr[0]; } 3549 @property bool empty() const { return arr.length == 0; } 3550 void popFront(){ arr = arr[1..$]; } 3551 }; 3552 3553 class C1 3554 { 3555 mixin(inputRangeCode); 3556 void toString(scope void delegate(const(char)[]) dg, const ref FormatSpec!char f) const { dg("[012]"); } 3557 } 3558 class C2 3559 { 3560 mixin(inputRangeCode); 3561 void toString(scope void delegate(const(char)[]) dg, string f) const { dg("[012]"); } 3562 } 3563 class C3 3564 { 3565 mixin(inputRangeCode); 3566 void toString(scope void delegate(const(char)[]) dg) const { dg("[012]"); } 3567 } 3568 class C4 3569 { 3570 mixin(inputRangeCode); 3571 override string toString() const { return "[012]"; } 3572 } 3573 class C5 3574 { 3575 mixin(inputRangeCode); 3576 } 3577 3578 formatTest( new C1([0, 1, 2]), "[012]" ); 3579 formatTest( new C2([0, 1, 2]), "[012]" ); 3580 formatTest( new C3([0, 1, 2]), "[012]" ); 3581 formatTest( new C4([0, 1, 2]), "[012]" ); 3582 formatTest( new C5([0, 1, 2]), "[0, 1, 2]" ); 3583} 3584 3585/// ditto 3586void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) 3587if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum)) 3588{ 3589 enforceValidFormatSpec!(T, Char)(f); 3590 if (val is null) 3591 put(w, "null"); 3592 else 3593 { 3594 static if (hasToString!(T, Char)) 3595 { 3596 formatObject(w, val, f); 3597 } 3598 else static if (isInputRange!T) 3599 { 3600 formatRange(w, val, f); 3601 } 3602 else 3603 { 3604 version (Windows) 3605 { 3606 import core.sys.windows.com : IUnknown; 3607 static if (is(T : IUnknown)) 3608 { 3609 formatValue(w, *cast(void**)&val, f); 3610 } 3611 else 3612 { 3613 formatValue(w, cast(Object) val, f); 3614 } 3615 } 3616 else 3617 { 3618 formatValue(w, cast(Object) val, f); 3619 } 3620 } 3621 } 3622} 3623 3624@system unittest 3625{ 3626 // interface 3627 import std.range.interfaces; 3628 InputRange!int i = inputRangeObject([1,2,3,4]); 3629 formatTest( i, "[1, 2, 3, 4]" ); 3630 assert(i.empty); 3631 i = null; 3632 formatTest( i, "null" ); 3633 3634 // interface (downcast to Object) 3635 interface Whatever {} 3636 class C : Whatever 3637 { 3638 override @property string toString() const { return "ab"; } 3639 } 3640 Whatever val = new C; 3641 formatTest( val, "ab" ); 3642 3643 // Issue 11175 3644 version (Windows) 3645 { 3646 import core.sys.windows.com : IUnknown, IID; 3647 import core.sys.windows.windows : HRESULT; 3648 3649 interface IUnknown2 : IUnknown { } 3650 3651 class D : IUnknown2 3652 { 3653 extern(Windows) HRESULT QueryInterface(const(IID)* riid, void** pvObject) { return typeof(return).init; } 3654 extern(Windows) uint AddRef() { return 0; } 3655 extern(Windows) uint Release() { return 0; } 3656 } 3657 3658 IUnknown2 d = new D; 3659 string expected = format("%X", cast(void*) d); 3660 formatTest(d, expected); 3661 } 3662} 3663 3664/// ditto 3665// Maybe T is noncopyable struct, so receive it by 'auto ref'. 3666void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, const ref FormatSpec!Char f) 3667if ((is(T == struct) || is(T == union)) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum)) 3668{ 3669 enforceValidFormatSpec!(T, Char)(f); 3670 static if (hasToString!(T, Char)) 3671 { 3672 formatObject(w, val, f); 3673 } 3674 else static if (isInputRange!T) 3675 { 3676 formatRange(w, val, f); 3677 } 3678 else static if (is(T == struct)) 3679 { 3680 enum left = T.stringof~"("; 3681 enum separator = ", "; 3682 enum right = ")"; 3683 3684 put(w, left); 3685 foreach (i, e; val.tupleof) 3686 { 3687 static if (0 < i && val.tupleof[i-1].offsetof == val.tupleof[i].offsetof) 3688 { 3689 static if (i == val.tupleof.length - 1 || val.tupleof[i].offsetof != val.tupleof[i+1].offsetof) 3690 put(w, separator~val.tupleof[i].stringof[4..$]~"}"); 3691 else 3692 put(w, separator~val.tupleof[i].stringof[4..$]); 3693 } 3694 else 3695 { 3696 static if (i+1 < val.tupleof.length && val.tupleof[i].offsetof == val.tupleof[i+1].offsetof) 3697 put(w, (i > 0 ? separator : "")~"#{overlap "~val.tupleof[i].stringof[4..$]); 3698 else 3699 { 3700 static if (i > 0) 3701 put(w, separator); 3702 formatElement(w, e, f); 3703 } 3704 } 3705 } 3706 put(w, right); 3707 } 3708 else 3709 { 3710 put(w, T.stringof); 3711 } 3712} 3713 3714@safe unittest 3715{ 3716 // bug 4638 3717 struct U8 { string toString() const { return "blah"; } } 3718 struct U16 { wstring toString() const { return "blah"; } } 3719 struct U32 { dstring toString() const { return "blah"; } } 3720 formatTest( U8(), "blah" ); 3721 formatTest( U16(), "blah" ); 3722 formatTest( U32(), "blah" ); 3723} 3724 3725@safe unittest 3726{ 3727 // 3890 3728 struct Int{ int n; } 3729 struct Pair{ string s; Int i; } 3730 formatTest( Pair("hello", Int(5)), 3731 `Pair("hello", Int(5))` ); 3732} 3733 3734@system unittest 3735{ 3736 // union formatting without toString 3737 union U1 3738 { 3739 int n; 3740 string s; 3741 } 3742 U1 u1; 3743 formatTest( u1, "U1" ); 3744 3745 // union formatting with toString 3746 union U2 3747 { 3748 int n; 3749 string s; 3750 string toString() const { return s; } 3751 } 3752 U2 u2; 3753 u2.s = "hello"; 3754 formatTest( u2, "hello" ); 3755} 3756 3757@system unittest 3758{ 3759 import std.array; 3760 // 7230 3761 static struct Bug7230 3762 { 3763 string s = "hello"; 3764 union { 3765 string a; 3766 int b; 3767 double c; 3768 } 3769 long x = 10; 3770 } 3771 3772 Bug7230 bug; 3773 bug.b = 123; 3774 3775 FormatSpec!char f; 3776 auto w = appender!(char[])(); 3777 formatValue(w, bug, f); 3778 assert(w.data == `Bug7230("hello", #{overlap a, b, c}, 10)`); 3779} 3780 3781@safe unittest 3782{ 3783 import std.array; 3784 static struct S{ @disable this(this); } 3785 S s; 3786 3787 FormatSpec!char f; 3788 auto w = appender!string(); 3789 formatValue(w, s, f); 3790 assert(w.data == "S()"); 3791} 3792 3793/** 3794$(D enum) is formatted like its base value. 3795 3796Params: 3797 w = The $(D OutputRange) to write to. 3798 val = The value to write. 3799 f = The $(D FormatSpec) defining how to write the value. 3800 */ 3801void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) 3802if (is(T == enum)) 3803{ 3804 if (f.spec == 's') 3805 { 3806 foreach (i, e; EnumMembers!T) 3807 { 3808 if (val == e) 3809 { 3810 formatValue(w, __traits(allMembers, T)[i], f); 3811 return; 3812 } 3813 } 3814 3815 // val is not a member of T, output cast(T) rawValue instead. 3816 put(w, "cast(" ~ T.stringof ~ ")"); 3817 static assert(!is(OriginalType!T == T)); 3818 } 3819 formatValue(w, cast(OriginalType!T) val, f); 3820} 3821 3822/// 3823@safe pure unittest 3824{ 3825 import std.array : appender; 3826 auto w = appender!string(); 3827 auto spec = singleSpec("%s"); 3828 3829 enum A { first, second, third } 3830 3831 formatElement(w, A.second, spec); 3832 3833 assert(w.data == "second"); 3834} 3835 3836@safe unittest 3837{ 3838 enum A { first, second, third } 3839 formatTest( A.second, "second" ); 3840 formatTest( cast(A) 72, "cast(A)72" ); 3841} 3842@safe unittest 3843{ 3844 enum A : string { one = "uno", two = "dos", three = "tres" } 3845 formatTest( A.three, "three" ); 3846 formatTest( cast(A)"mill\ón", "cast(A)mill\ón" ); 3847} 3848@safe unittest 3849{ 3850 enum A : bool { no, yes } 3851 formatTest( A.yes, "yes" ); 3852 formatTest( A.no, "no" ); 3853} 3854@safe unittest 3855{ 3856 // Test for bug 6892 3857 enum Foo { A = 10 } 3858 formatTest("%s", Foo.A, "A"); 3859 formatTest(">%4s<", Foo.A, "> A<"); 3860 formatTest("%04d", Foo.A, "0010"); 3861 formatTest("%+2u", Foo.A, "+10"); 3862 formatTest("%02x", Foo.A, "0a"); 3863 formatTest("%3o", Foo.A, " 12"); 3864 formatTest("%b", Foo.A, "1010"); 3865} 3866 3867/** 3868 Pointers are formatted as hex integers. 3869 */ 3870void formatValue(Writer, T, Char)(auto ref Writer w, T val, const ref FormatSpec!Char f) 3871if (isPointer!T && !is(T == enum) && !hasToString!(T, Char)) 3872{ 3873 static if (isInputRange!T) 3874 { 3875 if (val !is null) 3876 { 3877 formatRange(w, *val, f); 3878 return; 3879 } 3880 } 3881 3882 static if (is(typeof({ shared const void* p = val; }))) 3883 alias SharedOf(T) = shared(T); 3884 else 3885 alias SharedOf(T) = T; 3886 3887 const SharedOf!(void*) p = val; 3888 const pnum = ()@trusted{ return cast(ulong) p; }(); 3889 3890 if (f.spec == 's') 3891 { 3892 if (p is null) 3893 { 3894 put(w, "null"); 3895 return; 3896 } 3897 FormatSpec!Char fs = f; // fs is copy for change its values. 3898 fs.spec = 'X'; 3899 formatValue(w, pnum, fs); 3900 } 3901 else 3902 { 3903 enforceFmt(f.spec == 'X' || f.spec == 'x', 3904 "Expected one of %s, %x or %X for pointer type."); 3905 formatValue(w, pnum, f); 3906 } 3907} 3908 3909@safe pure unittest 3910{ 3911 // pointer 3912 import std.range; 3913 auto r = retro([1,2,3,4]); 3914 auto p = ()@trusted{ auto p = &r; return p; }(); 3915 formatTest( p, "[4, 3, 2, 1]" ); 3916 assert(p.empty); 3917 p = null; 3918 formatTest( p, "null" ); 3919 3920 auto q = ()@trusted{ return cast(void*) 0xFFEECCAA; }(); 3921 formatTest( q, "FFEECCAA" ); 3922} 3923 3924@system pure unittest 3925{ 3926 // Test for issue 7869 3927 struct S 3928 { 3929 string toString() const { return ""; } 3930 } 3931 S* p = null; 3932 formatTest( p, "null" ); 3933 3934 S* q = cast(S*) 0xFFEECCAA; 3935 formatTest( q, "FFEECCAA" ); 3936} 3937 3938@system unittest 3939{ 3940 // Test for issue 8186 3941 class B 3942 { 3943 int*a; 3944 this(){ a = new int; } 3945 alias a this; 3946 } 3947 formatTest( B.init, "null" ); 3948} 3949 3950@system pure unittest 3951{ 3952 // Test for issue 9336 3953 shared int i; 3954 format("%s", &i); 3955} 3956 3957@system pure unittest 3958{ 3959 // Test for issue 11778 3960 int* p = null; 3961 assertThrown(format("%d", p)); 3962 assertThrown(format("%04d", p + 2)); 3963} 3964 3965@safe pure unittest 3966{ 3967 // Test for issue 12505 3968 void* p = null; 3969 formatTest( "%08X", p, "00000000" ); 3970} 3971 3972/** 3973 Delegates are formatted by 'ReturnType delegate(Parameters) FunctionAttributes' 3974 */ 3975void formatValue(Writer, T, Char)(auto ref Writer w, scope T, const ref FormatSpec!Char f) 3976if (isDelegate!T) 3977{ 3978 formatValue(w, T.stringof, f); 3979} 3980 3981/// 3982@safe pure unittest 3983{ 3984 import std.conv : to; 3985 3986 int i; 3987 3988 int foo(short k) @nogc 3989 { 3990 return i + k; 3991 } 3992 3993 @system int delegate(short) @nogc bar() nothrow pure 3994 { 3995 int* p = new int; 3996 return &foo; 3997 } 3998 3999 assert(to!string(&bar) == "int delegate(short) @nogc delegate() pure nothrow @system"); 4000} 4001 4002@safe unittest 4003{ 4004 void func() @system { __gshared int x; ++x; throw new Exception("msg"); } 4005 version (linux) formatTest( &func, "void delegate() @system" ); 4006} 4007 4008@safe pure unittest 4009{ 4010 int[] a = [ 1, 3, 2 ]; 4011 formatTest( "testing %(%s & %) embedded", a, 4012 "testing 1 & 3 & 2 embedded"); 4013 formatTest( "testing %((%s) %)) wyda3", a, 4014 "testing (1) (3) (2) wyda3" ); 4015 4016 int[0] empt = []; 4017 formatTest( "(%s)", empt, 4018 "([])" ); 4019} 4020 4021//------------------------------------------------------------------------------ 4022// Fix for issue 1591 4023private int getNthInt(string kind, A...)(uint index, A args) 4024{ 4025 return getNth!(kind, isIntegral,int)(index, args); 4026} 4027 4028private T getNth(string kind, alias Condition, T, A...)(uint index, A args) 4029{ 4030 import std.conv : text, to; 4031 4032 switch (index) 4033 { 4034 foreach (n, _; A) 4035 { 4036 case n: 4037 static if (Condition!(typeof(args[n]))) 4038 { 4039 return to!T(args[n]); 4040 } 4041 else 4042 { 4043 throw new FormatException( 4044 text(kind, " expected, not ", typeof(args[n]).stringof, 4045 " for argument #", index + 1)); 4046 } 4047 } 4048 default: 4049 throw new FormatException( 4050 text("Missing ", kind, " argument")); 4051 } 4052} 4053 4054@safe unittest 4055{ 4056 // width/precision 4057 assert(collectExceptionMsg!FormatException(format("%*.d", 5.1, 2)) 4058 == "integer width expected, not double for argument #1"); 4059 assert(collectExceptionMsg!FormatException(format("%-1*.d", 5.1, 2)) 4060 == "integer width expected, not double for argument #1"); 4061 4062 assert(collectExceptionMsg!FormatException(format("%.*d", '5', 2)) 4063 == "integer precision expected, not char for argument #1"); 4064 assert(collectExceptionMsg!FormatException(format("%-1.*d", 4.7, 3)) 4065 == "integer precision expected, not double for argument #1"); 4066 assert(collectExceptionMsg!FormatException(format("%.*d", 5)) 4067 == "Orphan format specifier: %d"); 4068 assert(collectExceptionMsg!FormatException(format("%*.*d", 5)) 4069 == "Missing integer precision argument"); 4070 4071 // separatorCharPos 4072 assert(collectExceptionMsg!FormatException(format("%,?d", 5)) 4073 == "separator character expected, not int for argument #1"); 4074 assert(collectExceptionMsg!FormatException(format("%,?d", '?')) 4075 == "Orphan format specifier: %d"); 4076 assert(collectExceptionMsg!FormatException(format("%.*,*?d", 5)) 4077 == "Missing separator digit width argument"); 4078} 4079 4080/* ======================== Unit Tests ====================================== */ 4081 4082version (unittest) 4083void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__) 4084{ 4085 import core.exception : AssertError; 4086 import std.array : appender; 4087 import std.conv : text; 4088 FormatSpec!char f; 4089 auto w = appender!string(); 4090 formatValue(w, val, f); 4091 enforce!AssertError( 4092 w.data == expected, 4093 text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln); 4094} 4095 4096version (unittest) 4097void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__) @safe 4098{ 4099 import core.exception : AssertError; 4100 import std.array : appender; 4101 import std.conv : text; 4102 auto w = appender!string(); 4103 formattedWrite(w, fmt, val); 4104 enforce!AssertError( 4105 w.data == expected, 4106 text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln); 4107} 4108 4109version (unittest) 4110void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) 4111{ 4112 import core.exception : AssertError; 4113 import std.array : appender; 4114 import std.conv : text; 4115 FormatSpec!char f; 4116 auto w = appender!string(); 4117 formatValue(w, val, f); 4118 foreach (cur; expected) 4119 { 4120 if (w.data == cur) return; 4121 } 4122 enforce!AssertError( 4123 false, 4124 text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln); 4125} 4126 4127version (unittest) 4128void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) @safe 4129{ 4130 import core.exception : AssertError; 4131 import std.array : appender; 4132 import std.conv : text; 4133 auto w = appender!string(); 4134 formattedWrite(w, fmt, val); 4135 foreach (cur; expected) 4136 { 4137 if (w.data == cur) return; 4138 } 4139 enforce!AssertError( 4140 false, 4141 text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln); 4142} 4143 4144@safe /*pure*/ unittest // formatting floating point values is now impure 4145{ 4146 import std.array; 4147 4148 auto stream = appender!string(); 4149 formattedWrite(stream, "%s", 1.1); 4150 assert(stream.data == "1.1", stream.data); 4151} 4152 4153@safe pure unittest 4154{ 4155 import std.algorithm; 4156 import std.array; 4157 4158 auto stream = appender!string(); 4159 formattedWrite(stream, "%s", map!"a*a"([2, 3, 5])); 4160 assert(stream.data == "[4, 9, 25]", stream.data); 4161 4162 // Test shared data. 4163 stream = appender!string(); 4164 shared int s = 6; 4165 formattedWrite(stream, "%s", s); 4166 assert(stream.data == "6"); 4167} 4168 4169@safe pure unittest 4170{ 4171 import std.array; 4172 auto stream = appender!string(); 4173 formattedWrite(stream, "%u", 42); 4174 assert(stream.data == "42", stream.data); 4175} 4176 4177@safe pure unittest 4178{ 4179 // testing raw writes 4180 import std.array; 4181 auto w = appender!(char[])(); 4182 uint a = 0x02030405; 4183 formattedWrite(w, "%+r", a); 4184 assert(w.data.length == 4 && w.data[0] == 2 && w.data[1] == 3 4185 && w.data[2] == 4 && w.data[3] == 5); 4186 w.clear(); 4187 formattedWrite(w, "%-r", a); 4188 assert(w.data.length == 4 && w.data[0] == 5 && w.data[1] == 4 4189 && w.data[2] == 3 && w.data[3] == 2); 4190} 4191 4192@safe pure unittest 4193{ 4194 // testing positional parameters 4195 import std.array; 4196 auto w = appender!(char[])(); 4197 formattedWrite(w, 4198 "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated", 4199 42, 0); 4200 assert(w.data == "Numbers 0 and 42 are reversed and 420 repeated", 4201 w.data); 4202 assert(collectExceptionMsg!FormatException(formattedWrite(w, "%1$s, %3$s", 1, 2)) 4203 == "Positional specifier %3$s index exceeds 2"); 4204 4205 w.clear(); 4206 formattedWrite(w, "asd%s", 23); 4207 assert(w.data == "asd23", w.data); 4208 w.clear(); 4209 formattedWrite(w, "%s%s", 23, 45); 4210 assert(w.data == "2345", w.data); 4211} 4212 4213@safe unittest 4214{ 4215 import core.stdc.string : strlen; 4216 import std.array : appender; 4217 import std.conv : text, octal; 4218 import core.stdc.stdio : snprintf; 4219 4220 debug(format) printf("std.format.format.unittest\n"); 4221 4222 auto stream = appender!(char[])(); 4223 //goto here; 4224 4225 formattedWrite(stream, 4226 "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo"); 4227 assert(stream.data == "hello world! true 57 ", 4228 stream.data); 4229 4230 stream.clear(); 4231 formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan); 4232 // core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); 4233 4234 /* The host C library is used to format floats. C99 doesn't 4235 * specify what the hex digit before the decimal point is for 4236 * %A. */ 4237 4238 version (CRuntime_Glibc) 4239 { 4240 assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", 4241 stream.data); 4242 } 4243 else version (OSX) 4244 { 4245 assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", 4246 stream.data); 4247 } 4248 else version (MinGW) 4249 { 4250 assert(stream.data == "1.67 -0XA.3D70A3D70A3D8P-3 nan", 4251 stream.data); 4252 } 4253 else version (CRuntime_Microsoft) 4254 { 4255 assert(stream.data == "1.67 -0X1.47AE14P+0 nan" 4256 || stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", // MSVCRT 14+ (VS 2015) 4257 stream.data); 4258 } 4259 else 4260 { 4261 assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", 4262 stream.data); 4263 } 4264 stream.clear(); 4265 4266 formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF); 4267 assert(stream.data == "1234af AFAFAFAF"); 4268 stream.clear(); 4269 4270 formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF); 4271 assert(stream.data == "100100011010010101111 25753727657"); 4272 stream.clear(); 4273 4274 formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF); 4275 assert(stream.data == "1193135 2947526575"); 4276 stream.clear(); 4277 4278 // formattedWrite(stream, "%s", 1.2 + 3.4i); 4279 // assert(stream.data == "1.2+3.4i"); 4280 // stream.clear(); 4281 4282 formattedWrite(stream, "%a %A", 1.32, 6.78f); 4283 //formattedWrite(stream, "%x %X", 1.32); 4284 version (CRuntime_Microsoft) 4285 assert(stream.data == "0x1.51eb85p+0 0X1.B1EB86P+2" 4286 || stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB860000000P+2"); // MSVCRT 14+ (VS 2015) 4287 else 4288 assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2"); 4289 stream.clear(); 4290 4291 formattedWrite(stream, "%#06.*f",2,12.345); 4292 assert(stream.data == "012.35"); 4293 stream.clear(); 4294 4295 formattedWrite(stream, "%#0*.*f",6,2,12.345); 4296 assert(stream.data == "012.35"); 4297 stream.clear(); 4298 4299 const real constreal = 1; 4300 formattedWrite(stream, "%g",constreal); 4301 assert(stream.data == "1"); 4302 stream.clear(); 4303 4304 formattedWrite(stream, "%7.4g:", 12.678); 4305 assert(stream.data == " 12.68:"); 4306 stream.clear(); 4307 4308 formattedWrite(stream, "%7.4g:", 12.678L); 4309 assert(stream.data == " 12.68:"); 4310 stream.clear(); 4311 4312 formattedWrite(stream, "%04f|%05d|%#05x|%#5x",-4.0,-10,1,1); 4313 assert(stream.data == "-4.000000|-0010|0x001| 0x1", 4314 stream.data); 4315 stream.clear(); 4316 4317 int i; 4318 string s; 4319 4320 i = -10; 4321 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 4322 assert(stream.data == "-10|-10|-10|-10|-10.0000"); 4323 stream.clear(); 4324 4325 i = -5; 4326 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 4327 assert(stream.data == "-5| -5|-05|-5|-5.0000"); 4328 stream.clear(); 4329 4330 i = 0; 4331 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 4332 assert(stream.data == "0| 0|000|0|0.0000"); 4333 stream.clear(); 4334 4335 i = 5; 4336 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 4337 assert(stream.data == "5| 5|005|5|5.0000"); 4338 stream.clear(); 4339 4340 i = 10; 4341 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 4342 assert(stream.data == "10| 10|010|10|10.0000"); 4343 stream.clear(); 4344 4345 formattedWrite(stream, "%.0d", 0); 4346 assert(stream.data == ""); 4347 stream.clear(); 4348 4349 formattedWrite(stream, "%.g", .34); 4350 assert(stream.data == "0.3"); 4351 stream.clear(); 4352 4353 stream.clear(); formattedWrite(stream, "%.0g", .34); 4354 assert(stream.data == "0.3"); 4355 4356 stream.clear(); formattedWrite(stream, "%.2g", .34); 4357 assert(stream.data == "0.34"); 4358 4359 stream.clear(); formattedWrite(stream, "%0.0008f", 1e-08); 4360 assert(stream.data == "0.00000001"); 4361 4362 stream.clear(); formattedWrite(stream, "%0.0008f", 1e-05); 4363 assert(stream.data == "0.00001000"); 4364 4365 //return; 4366 //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); 4367 4368 s = "helloworld"; 4369 string r; 4370 stream.clear(); formattedWrite(stream, "%.2s", s[0 .. 5]); 4371 assert(stream.data == "he"); 4372 stream.clear(); formattedWrite(stream, "%.20s", s[0 .. 5]); 4373 assert(stream.data == "hello"); 4374 stream.clear(); formattedWrite(stream, "%8s", s[0 .. 5]); 4375 assert(stream.data == " hello"); 4376 4377 byte[] arrbyte = new byte[4]; 4378 arrbyte[0] = 100; 4379 arrbyte[1] = -99; 4380 arrbyte[3] = 0; 4381 stream.clear(); formattedWrite(stream, "%s", arrbyte); 4382 assert(stream.data == "[100, -99, 0, 0]", stream.data); 4383 4384 ubyte[] arrubyte = new ubyte[4]; 4385 arrubyte[0] = 100; 4386 arrubyte[1] = 200; 4387 arrubyte[3] = 0; 4388 stream.clear(); formattedWrite(stream, "%s", arrubyte); 4389 assert(stream.data == "[100, 200, 0, 0]", stream.data); 4390 4391 short[] arrshort = new short[4]; 4392 arrshort[0] = 100; 4393 arrshort[1] = -999; 4394 arrshort[3] = 0; 4395 stream.clear(); formattedWrite(stream, "%s", arrshort); 4396 assert(stream.data == "[100, -999, 0, 0]"); 4397 stream.clear(); formattedWrite(stream, "%s",arrshort); 4398 assert(stream.data == "[100, -999, 0, 0]"); 4399 4400 ushort[] arrushort = new ushort[4]; 4401 arrushort[0] = 100; 4402 arrushort[1] = 20_000; 4403 arrushort[3] = 0; 4404 stream.clear(); formattedWrite(stream, "%s", arrushort); 4405 assert(stream.data == "[100, 20000, 0, 0]"); 4406 4407 int[] arrint = new int[4]; 4408 arrint[0] = 100; 4409 arrint[1] = -999; 4410 arrint[3] = 0; 4411 stream.clear(); formattedWrite(stream, "%s", arrint); 4412 assert(stream.data == "[100, -999, 0, 0]"); 4413 stream.clear(); formattedWrite(stream, "%s",arrint); 4414 assert(stream.data == "[100, -999, 0, 0]"); 4415 4416 long[] arrlong = new long[4]; 4417 arrlong[0] = 100; 4418 arrlong[1] = -999; 4419 arrlong[3] = 0; 4420 stream.clear(); formattedWrite(stream, "%s", arrlong); 4421 assert(stream.data == "[100, -999, 0, 0]"); 4422 stream.clear(); formattedWrite(stream, "%s",arrlong); 4423 assert(stream.data == "[100, -999, 0, 0]"); 4424 4425 ulong[] arrulong = new ulong[4]; 4426 arrulong[0] = 100; 4427 arrulong[1] = 999; 4428 arrulong[3] = 0; 4429 stream.clear(); formattedWrite(stream, "%s", arrulong); 4430 assert(stream.data == "[100, 999, 0, 0]"); 4431 4432 string[] arr2 = new string[4]; 4433 arr2[0] = "hello"; 4434 arr2[1] = "world"; 4435 arr2[3] = "foo"; 4436 stream.clear(); formattedWrite(stream, "%s", arr2); 4437 assert(stream.data == `["hello", "world", "", "foo"]`, stream.data); 4438 4439 stream.clear(); formattedWrite(stream, "%.8d", 7); 4440 assert(stream.data == "00000007"); 4441 4442 stream.clear(); formattedWrite(stream, "%.8x", 10); 4443 assert(stream.data == "0000000a"); 4444 4445 stream.clear(); formattedWrite(stream, "%-3d", 7); 4446 assert(stream.data == "7 "); 4447 4448 stream.clear(); formattedWrite(stream, "%*d", -3, 7); 4449 assert(stream.data == "7 "); 4450 4451 stream.clear(); formattedWrite(stream, "%.*d", -3, 7); 4452 //writeln(stream.data); 4453 assert(stream.data == "7"); 4454 4455 stream.clear(); formattedWrite(stream, "%s", "abc"c); 4456 assert(stream.data == "abc"); 4457 stream.clear(); formattedWrite(stream, "%s", "def"w); 4458 assert(stream.data == "def", text(stream.data.length)); 4459 stream.clear(); formattedWrite(stream, "%s", "ghi"d); 4460 assert(stream.data == "ghi"); 4461 4462here: 4463 @trusted void* deadBeef() { return cast(void*) 0xDEADBEEF; } 4464 stream.clear(); formattedWrite(stream, "%s", deadBeef()); 4465 assert(stream.data == "DEADBEEF", stream.data); 4466 4467 stream.clear(); formattedWrite(stream, "%#x", 0xabcd); 4468 assert(stream.data == "0xabcd"); 4469 stream.clear(); formattedWrite(stream, "%#X", 0xABCD); 4470 assert(stream.data == "0XABCD"); 4471 4472 stream.clear(); formattedWrite(stream, "%#o", octal!12345); 4473 assert(stream.data == "012345"); 4474 stream.clear(); formattedWrite(stream, "%o", 9); 4475 assert(stream.data == "11"); 4476 4477 stream.clear(); formattedWrite(stream, "%+d", 123); 4478 assert(stream.data == "+123"); 4479 stream.clear(); formattedWrite(stream, "%+d", -123); 4480 assert(stream.data == "-123"); 4481 stream.clear(); formattedWrite(stream, "% d", 123); 4482 assert(stream.data == " 123"); 4483 stream.clear(); formattedWrite(stream, "% d", -123); 4484 assert(stream.data == "-123"); 4485 4486 stream.clear(); formattedWrite(stream, "%%"); 4487 assert(stream.data == "%"); 4488 4489 stream.clear(); formattedWrite(stream, "%d", true); 4490 assert(stream.data == "1"); 4491 stream.clear(); formattedWrite(stream, "%d", false); 4492 assert(stream.data == "0"); 4493 4494 stream.clear(); formattedWrite(stream, "%d", 'a'); 4495 assert(stream.data == "97", stream.data); 4496 wchar wc = 'a'; 4497 stream.clear(); formattedWrite(stream, "%d", wc); 4498 assert(stream.data == "97"); 4499 dchar dc = 'a'; 4500 stream.clear(); formattedWrite(stream, "%d", dc); 4501 assert(stream.data == "97"); 4502 4503 byte b = byte.max; 4504 stream.clear(); formattedWrite(stream, "%x", b); 4505 assert(stream.data == "7f"); 4506 stream.clear(); formattedWrite(stream, "%x", ++b); 4507 assert(stream.data == "80"); 4508 stream.clear(); formattedWrite(stream, "%x", ++b); 4509 assert(stream.data == "81"); 4510 4511 short sh = short.max; 4512 stream.clear(); formattedWrite(stream, "%x", sh); 4513 assert(stream.data == "7fff"); 4514 stream.clear(); formattedWrite(stream, "%x", ++sh); 4515 assert(stream.data == "8000"); 4516 stream.clear(); formattedWrite(stream, "%x", ++sh); 4517 assert(stream.data == "8001"); 4518 4519 i = int.max; 4520 stream.clear(); formattedWrite(stream, "%x", i); 4521 assert(stream.data == "7fffffff"); 4522 stream.clear(); formattedWrite(stream, "%x", ++i); 4523 assert(stream.data == "80000000"); 4524 stream.clear(); formattedWrite(stream, "%x", ++i); 4525 assert(stream.data == "80000001"); 4526 4527 stream.clear(); formattedWrite(stream, "%x", 10); 4528 assert(stream.data == "a"); 4529 stream.clear(); formattedWrite(stream, "%X", 10); 4530 assert(stream.data == "A"); 4531 stream.clear(); formattedWrite(stream, "%x", 15); 4532 assert(stream.data == "f"); 4533 stream.clear(); formattedWrite(stream, "%X", 15); 4534 assert(stream.data == "F"); 4535 4536 @trusted void ObjectTest() 4537 { 4538 Object c = null; 4539 stream.clear(); formattedWrite(stream, "%s", c); 4540 assert(stream.data == "null"); 4541 } 4542 ObjectTest(); 4543 4544 enum TestEnum 4545 { 4546 Value1, Value2 4547 } 4548 stream.clear(); formattedWrite(stream, "%s", TestEnum.Value2); 4549 assert(stream.data == "Value2", stream.data); 4550 stream.clear(); formattedWrite(stream, "%s", cast(TestEnum) 5); 4551 assert(stream.data == "cast(TestEnum)5", stream.data); 4552 4553 //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 4554 //stream.clear(); formattedWrite(stream, "%s", aa.values); 4555 //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); 4556 //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]"); 4557 //stream.clear(); formattedWrite(stream, "%s", aa); 4558 //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]"); 4559 4560 static const dchar[] ds = ['a','b']; 4561 for (int j = 0; j < ds.length; ++j) 4562 { 4563 stream.clear(); formattedWrite(stream, " %d", ds[j]); 4564 if (j == 0) 4565 assert(stream.data == " 97"); 4566 else 4567 assert(stream.data == " 98"); 4568 } 4569 4570 stream.clear(); formattedWrite(stream, "%.-3d", 7); 4571 assert(stream.data == "7", ">" ~ stream.data ~ "<"); 4572} 4573 4574@safe unittest 4575{ 4576 import std.array; 4577 import std.stdio; 4578 4579 immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 4580 assert(aa[3] == "hello"); 4581 assert(aa[4] == "betty"); 4582 4583 auto stream = appender!(char[])(); 4584 alias AllNumerics = 4585 AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, 4586 float, double, real); 4587 foreach (T; AllNumerics) 4588 { 4589 T value = 1; 4590 stream.clear(); 4591 formattedWrite(stream, "%s", value); 4592 assert(stream.data == "1"); 4593 } 4594 4595 stream.clear(); 4596 formattedWrite(stream, "%s", aa); 4597} 4598 4599@system unittest 4600{ 4601 string s = "hello!124:34.5"; 4602 string a; 4603 int b; 4604 double c; 4605 formattedRead(s, "%s!%s:%s", &a, &b, &c); 4606 assert(a == "hello" && b == 124 && c == 34.5); 4607} 4608 4609version (unittest) 4610void formatReflectTest(T)(ref T val, string fmt, string formatted, string fn = __FILE__, size_t ln = __LINE__) 4611{ 4612 import core.exception : AssertError; 4613 import std.array : appender; 4614 auto w = appender!string(); 4615 formattedWrite(w, fmt, val); 4616 4617 auto input = w.data; 4618 enforce!AssertError( 4619 input == formatted, 4620 input, fn, ln); 4621 4622 T val2; 4623 formattedRead(input, fmt, &val2); 4624 static if (isAssociativeArray!T) 4625 if (__ctfe) 4626 { 4627 alias aa1 = val; 4628 alias aa2 = val2; 4629 assert(aa1 == aa2); 4630 4631 assert(aa1.length == aa2.length); 4632 4633 assert(aa1.keys == aa2.keys); 4634 4635 assert(aa1.values == aa2.values); 4636 assert(aa1.values.length == aa2.values.length); 4637 foreach (i; 0 .. aa1.values.length) 4638 assert(aa1.values[i] == aa2.values[i]); 4639 4640 foreach (i, key; aa1.keys) 4641 assert(aa1.values[i] == aa1[key]); 4642 foreach (i, key; aa2.keys) 4643 assert(aa2.values[i] == aa2[key]); 4644 return; 4645 } 4646 enforce!AssertError( 4647 val == val2, 4648 input, fn, ln); 4649} 4650 4651version (unittest) 4652void formatReflectTest(T)(ref T val, string fmt, string[] formatted, string fn = __FILE__, size_t ln = __LINE__) 4653{ 4654 import core.exception : AssertError; 4655 import std.array : appender; 4656 auto w = appender!string(); 4657 formattedWrite(w, fmt, val); 4658 4659 auto input = w.data; 4660 4661 foreach (cur; formatted) 4662 { 4663 if (input == cur) return; 4664 } 4665 enforce!AssertError( 4666 false, 4667 input, 4668 fn, 4669 ln); 4670 4671 T val2; 4672 formattedRead(input, fmt, &val2); 4673 static if (isAssociativeArray!T) 4674 if (__ctfe) 4675 { 4676 alias aa1 = val; 4677 alias aa2 = val2; 4678 assert(aa1 == aa2); 4679 4680 assert(aa1.length == aa2.length); 4681 4682 assert(aa1.keys == aa2.keys); 4683 4684 assert(aa1.values == aa2.values); 4685 assert(aa1.values.length == aa2.values.length); 4686 foreach (i; 0 .. aa1.values.length) 4687 assert(aa1.values[i] == aa2.values[i]); 4688 4689 foreach (i, key; aa1.keys) 4690 assert(aa1.values[i] == aa1[key]); 4691 foreach (i, key; aa2.keys) 4692 assert(aa2.values[i] == aa2[key]); 4693 return; 4694 } 4695 enforce!AssertError( 4696 val == val2, 4697 input, fn, ln); 4698} 4699 4700@system unittest 4701{ 4702 void booleanTest() 4703 { 4704 auto b = true; 4705 formatReflectTest(b, "%s", `true`); 4706 formatReflectTest(b, "%b", `1`); 4707 formatReflectTest(b, "%o", `1`); 4708 formatReflectTest(b, "%d", `1`); 4709 formatReflectTest(b, "%u", `1`); 4710 formatReflectTest(b, "%x", `1`); 4711 } 4712 4713 void integerTest() 4714 { 4715 auto n = 127; 4716 formatReflectTest(n, "%s", `127`); 4717 formatReflectTest(n, "%b", `1111111`); 4718 formatReflectTest(n, "%o", `177`); 4719 formatReflectTest(n, "%d", `127`); 4720 formatReflectTest(n, "%u", `127`); 4721 formatReflectTest(n, "%x", `7f`); 4722 } 4723 4724 void floatingTest() 4725 { 4726 auto f = 3.14; 4727 formatReflectTest(f, "%s", `3.14`); 4728 version (MinGW) 4729 formatReflectTest(f, "%e", `3.140000e+000`); 4730 else 4731 formatReflectTest(f, "%e", `3.140000e+00`); 4732 formatReflectTest(f, "%f", `3.140000`); 4733 formatReflectTest(f, "%g", `3.14`); 4734 } 4735 4736 void charTest() 4737 { 4738 auto c = 'a'; 4739 formatReflectTest(c, "%s", `a`); 4740 formatReflectTest(c, "%c", `a`); 4741 formatReflectTest(c, "%b", `1100001`); 4742 formatReflectTest(c, "%o", `141`); 4743 formatReflectTest(c, "%d", `97`); 4744 formatReflectTest(c, "%u", `97`); 4745 formatReflectTest(c, "%x", `61`); 4746 } 4747 4748 void strTest() 4749 { 4750 auto s = "hello"; 4751 formatReflectTest(s, "%s", `hello`); 4752 formatReflectTest(s, "%(%c,%)", `h,e,l,l,o`); 4753 formatReflectTest(s, "%(%s,%)", `'h','e','l','l','o'`); 4754 formatReflectTest(s, "[%(<%c>%| $ %)]", `[<h> $ <e> $ <l> $ <l> $ <o>]`); 4755 } 4756 4757 void daTest() 4758 { 4759 auto a = [1,2,3,4]; 4760 formatReflectTest(a, "%s", `[1, 2, 3, 4]`); 4761 formatReflectTest(a, "[%(%s; %)]", `[1; 2; 3; 4]`); 4762 formatReflectTest(a, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`); 4763 } 4764 4765 void saTest() 4766 { 4767 int[4] sa = [1,2,3,4]; 4768 formatReflectTest(sa, "%s", `[1, 2, 3, 4]`); 4769 formatReflectTest(sa, "[%(%s; %)]", `[1; 2; 3; 4]`); 4770 formatReflectTest(sa, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`); 4771 } 4772 4773 void aaTest() 4774 { 4775 auto aa = [1:"hello", 2:"world"]; 4776 formatReflectTest(aa, "%s", [`[1:"hello", 2:"world"]`, `[2:"world", 1:"hello"]`]); 4777 formatReflectTest(aa, "[%(%s->%s, %)]", [`[1->"hello", 2->"world"]`, `[2->"world", 1->"hello"]`]); 4778 formatReflectTest(aa, "{%([%s=%(%c%)]%|; %)}", [`{[1=hello]; [2=world]}`, `{[2=world]; [1=hello]}`]); 4779 } 4780 4781 import std.exception; 4782 assertCTFEable!( 4783 { 4784 booleanTest(); 4785 integerTest(); 4786 if (!__ctfe) floatingTest(); // snprintf 4787 charTest(); 4788 strTest(); 4789 daTest(); 4790 saTest(); 4791 aaTest(); 4792 return true; 4793 }); 4794} 4795 4796//------------------------------------------------------------------------------ 4797private void skipData(Range, Char)(ref Range input, const ref FormatSpec!Char spec) 4798{ 4799 import std.ascii : isDigit; 4800 import std.conv : text; 4801 4802 switch (spec.spec) 4803 { 4804 case 'c': input.popFront(); break; 4805 case 'd': 4806 if (input.front == '+' || input.front == '-') input.popFront(); 4807 goto case 'u'; 4808 case 'u': 4809 while (!input.empty && isDigit(input.front)) input.popFront(); 4810 break; 4811 default: 4812 assert(false, 4813 text("Format specifier not understood: %", spec.spec)); 4814 } 4815} 4816 4817private template acceptedSpecs(T) 4818{ 4819 static if (isIntegral!T) enum acceptedSpecs = "bdosuxX"; 4820 else static if (isFloatingPoint!T) enum acceptedSpecs = "seEfgG"; 4821 else static if (isSomeChar!T) enum acceptedSpecs = "bcdosuxX"; // integral + 'c' 4822 else enum acceptedSpecs = ""; 4823} 4824 4825/** 4826 * Reads a value from the given _input range according to spec 4827 * and returns it as type `T`. 4828 * 4829 * Params: 4830 * T = the type to return 4831 * input = the _input range to read from 4832 * spec = the `FormatSpec` to use when reading from `input` 4833 * Returns: 4834 * A value from `input` of type `T` 4835 * Throws: 4836 * An `Exception` if `spec` cannot read a type `T` 4837 * See_Also: 4838 * $(REF parse, std, conv) and $(REF to, std, conv) 4839 */ 4840T unformatValue(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) 4841{ 4842 return unformatValueImpl!T(input, spec); 4843} 4844 4845/// Booleans 4846@safe pure unittest 4847{ 4848 auto str = "false"; 4849 auto spec = singleSpec("%s"); 4850 assert(unformatValue!bool(str, spec) == false); 4851 4852 str = "1"; 4853 spec = singleSpec("%d"); 4854 assert(unformatValue!bool(str, spec)); 4855} 4856 4857/// Null values 4858@safe pure unittest 4859{ 4860 auto str = "null"; 4861 auto spec = singleSpec("%s"); 4862 assert(str.unformatValue!(typeof(null))(spec) == null); 4863} 4864 4865/// Integrals 4866@safe pure unittest 4867{ 4868 auto str = "123"; 4869 auto spec = singleSpec("%s"); 4870 assert(str.unformatValue!int(spec) == 123); 4871 4872 str = "ABC"; 4873 spec = singleSpec("%X"); 4874 assert(str.unformatValue!int(spec) == 2748); 4875 4876 str = "11610"; 4877 spec = singleSpec("%o"); 4878 assert(str.unformatValue!int(spec) == 5000); 4879} 4880 4881/// Floating point numbers 4882@safe pure unittest 4883{ 4884 import std.math : approxEqual; 4885 4886 auto str = "123.456"; 4887 auto spec = singleSpec("%s"); 4888 assert(str.unformatValue!double(spec).approxEqual(123.456)); 4889} 4890 4891/// Character input ranges 4892@safe pure unittest 4893{ 4894 auto str = "aaa"; 4895 auto spec = singleSpec("%s"); 4896 assert(str.unformatValue!char(spec) == 'a'); 4897 4898 // Using a numerical format spec reads a Unicode value from a string 4899 str = "65"; 4900 spec = singleSpec("%d"); 4901 assert(str.unformatValue!char(spec) == 'A'); 4902 4903 str = "41"; 4904 spec = singleSpec("%x"); 4905 assert(str.unformatValue!char(spec) == 'A'); 4906 4907 str = "10003"; 4908 spec = singleSpec("%d"); 4909 assert(str.unformatValue!dchar(spec) == '���'); 4910} 4911 4912/// Arrays and static arrays 4913@safe pure unittest 4914{ 4915 string str = "aaa"; 4916 auto spec = singleSpec("%s"); 4917 assert(str.unformatValue!(dchar[])(spec) == "aaa"d); 4918 4919 str = "aaa"; 4920 spec = singleSpec("%s"); 4921 dchar[3] ret = ['a', 'a', 'a']; 4922 assert(str.unformatValue!(dchar[3])(spec) == ret); 4923 4924 str = "[1, 2, 3, 4]"; 4925 spec = singleSpec("%s"); 4926 assert(str.unformatValue!(int[])(spec) == [1, 2, 3, 4]); 4927 4928 str = "[1, 2, 3, 4]"; 4929 spec = singleSpec("%s"); 4930 int[4] ret2 = [1, 2, 3, 4]; 4931 assert(str.unformatValue!(int[4])(spec) == ret2); 4932} 4933 4934/// Associative arrays 4935@safe pure unittest 4936{ 4937 auto str = `["one": 1, "two": 2]`; 4938 auto spec = singleSpec("%s"); 4939 assert(str.unformatValue!(int[string])(spec) == ["one": 1, "two": 2]); 4940} 4941 4942@safe pure unittest 4943{ 4944 // 7241 4945 string input = "a"; 4946 auto spec = FormatSpec!char("%s"); 4947 spec.readUpToNextSpec(input); 4948 auto result = unformatValue!(dchar[1])(input, spec); 4949 assert(result[0] == 'a'); 4950} 4951 4952private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) 4953if (isInputRange!Range && is(Unqual!T == bool)) 4954{ 4955 import std.algorithm.searching : find; 4956 import std.conv : parse, text; 4957 4958 if (spec.spec == 's') return parse!T(input); 4959 4960 enforce(find(acceptedSpecs!long, spec.spec).length, 4961 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 4962 4963 return unformatValue!long(input, spec) != 0; 4964} 4965 4966private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) 4967if (isInputRange!Range && is(T == typeof(null))) 4968{ 4969 import std.conv : parse, text; 4970 enforce(spec.spec == 's', 4971 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 4972 4973 return parse!T(input); 4974} 4975 4976/// ditto 4977private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) 4978if (isInputRange!Range && isIntegral!T && !is(T == enum) && isSomeChar!(ElementType!Range)) 4979{ 4980 4981 import std.algorithm.searching : find; 4982 import std.conv : parse, text; 4983 4984 if (spec.spec == 'r') 4985 { 4986 static if (is(Unqual!(ElementEncodingType!Range) == char) 4987 || is(Unqual!(ElementEncodingType!Range) == byte) 4988 || is(Unqual!(ElementEncodingType!Range) == ubyte)) 4989 return rawRead!T(input); 4990 else 4991 throw new Exception("The raw read specifier %r may only be used with narrow strings and ranges of bytes."); 4992 } 4993 4994 enforce(find(acceptedSpecs!T, spec.spec).length, 4995 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 4996 4997 enforce(spec.width == 0, "Parsing integers with a width specification is not implemented"); // TODO 4998 4999 immutable uint base = 5000 spec.spec == 'x' || spec.spec == 'X' ? 16 : 5001 spec.spec == 'o' ? 8 : 5002 spec.spec == 'b' ? 2 : 5003 spec.spec == 's' || spec.spec == 'd' || spec.spec == 'u' ? 10 : 0; 5004 assert(base != 0); 5005 5006 return parse!T(input, base); 5007 5008} 5009 5010/// ditto 5011private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) 5012if (isFloatingPoint!T && !is(T == enum) && isInputRange!Range 5013 && isSomeChar!(ElementType!Range)&& !is(Range == enum)) 5014{ 5015 import std.algorithm.searching : find; 5016 import std.conv : parse, text; 5017 5018 if (spec.spec == 'r') 5019 { 5020 static if (is(Unqual!(ElementEncodingType!Range) == char) 5021 || is(Unqual!(ElementEncodingType!Range) == byte) 5022 || is(Unqual!(ElementEncodingType!Range) == ubyte)) 5023 return rawRead!T(input); 5024 else 5025 throw new Exception("The raw read specifier %r may only be used with narrow strings and ranges of bytes."); 5026 } 5027 5028 enforce(find(acceptedSpecs!T, spec.spec).length, 5029 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 5030 5031 return parse!T(input); 5032} 5033 5034/// ditto 5035private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) 5036if (isInputRange!Range && isSomeChar!T && !is(T == enum) && isSomeChar!(ElementType!Range)) 5037{ 5038 import std.algorithm.searching : find; 5039 import std.conv : to, text; 5040 if (spec.spec == 's' || spec.spec == 'c') 5041 { 5042 auto result = to!T(input.front); 5043 input.popFront(); 5044 return result; 5045 } 5046 enforce(find(acceptedSpecs!T, spec.spec).length, 5047 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 5048 5049 static if (T.sizeof == 1) 5050 return unformatValue!ubyte(input, spec); 5051 else static if (T.sizeof == 2) 5052 return unformatValue!ushort(input, spec); 5053 else static if (T.sizeof == 4) 5054 return unformatValue!uint(input, spec); 5055 else 5056 static assert(0); 5057} 5058 5059/// ditto 5060private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) 5061if (isInputRange!Range && is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum)) 5062{ 5063 import std.conv : text; 5064 5065 if (spec.spec == '(') 5066 { 5067 return unformatRange!T(input, spec); 5068 } 5069 enforce(spec.spec == 's', 5070 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 5071 5072 static if (isStaticArray!T) 5073 { 5074 T result; 5075 auto app = result[]; 5076 } 5077 else 5078 { 5079 import std.array : appender; 5080 auto app = appender!T(); 5081 } 5082 if (spec.trailing.empty) 5083 { 5084 for (; !input.empty; input.popFront()) 5085 { 5086 static if (isStaticArray!T) 5087 if (app.empty) 5088 break; 5089 app.put(input.front); 5090 } 5091 } 5092 else 5093 { 5094 immutable end = spec.trailing.front; 5095 for (; !input.empty && input.front != end; input.popFront()) 5096 { 5097 static if (isStaticArray!T) 5098 if (app.empty) 5099 break; 5100 app.put(input.front); 5101 } 5102 } 5103 static if (isStaticArray!T) 5104 { 5105 enforce(app.empty, "need more input"); 5106 return result; 5107 } 5108 else 5109 return app.data; 5110} 5111 5112/// ditto 5113private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) 5114if (isInputRange!Range && isArray!T && !is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum)) 5115{ 5116 import std.conv : parse, text; 5117 if (spec.spec == '(') 5118 { 5119 return unformatRange!T(input, spec); 5120 } 5121 enforce(spec.spec == 's', 5122 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 5123 5124 return parse!T(input); 5125} 5126 5127/// ditto 5128private T unformatValueImpl(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) 5129if (isInputRange!Range && isAssociativeArray!T && !is(T == enum)) 5130{ 5131 import std.conv : parse, text; 5132 if (spec.spec == '(') 5133 { 5134 return unformatRange!T(input, spec); 5135 } 5136 enforce(spec.spec == 's', 5137 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 5138 5139 return parse!T(input); 5140} 5141 5142/** 5143 * Function that performs raw reading. Used by unformatValue 5144 * for integral and float types. 5145 */ 5146private T rawRead(T, Range)(ref Range input) 5147if (is(Unqual!(ElementEncodingType!Range) == char) 5148 || is(Unqual!(ElementEncodingType!Range) == byte) 5149 || is(Unqual!(ElementEncodingType!Range) == ubyte)) 5150{ 5151 union X 5152 { 5153 ubyte[T.sizeof] raw; 5154 T typed; 5155 } 5156 X x; 5157 foreach (i; 0 .. T.sizeof) 5158 { 5159 static if (isSomeString!Range) 5160 { 5161 x.raw[i] = input[0]; 5162 input = input[1 .. $]; 5163 } 5164 else 5165 { 5166 // TODO: recheck this 5167 x.raw[i] = input.front; 5168 input.popFront(); 5169 } 5170 } 5171 return x.typed; 5172} 5173 5174//debug = unformatRange; 5175 5176private T unformatRange(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) 5177in 5178{ 5179 assert(spec.spec == '('); 5180} 5181body 5182{ 5183 debug (unformatRange) printf("unformatRange:\n"); 5184 5185 T result; 5186 static if (isStaticArray!T) 5187 { 5188 size_t i; 5189 } 5190 5191 const(Char)[] cont = spec.trailing; 5192 for (size_t j = 0; j < spec.trailing.length; ++j) 5193 { 5194 if (spec.trailing[j] == '%') 5195 { 5196 cont = spec.trailing[0 .. j]; 5197 break; 5198 } 5199 } 5200 debug (unformatRange) printf("\t"); 5201 debug (unformatRange) if (!input.empty) printf("input.front = %c, ", input.front); 5202 debug (unformatRange) printf("cont = %.*s\n", cont); 5203 5204 bool checkEnd() 5205 { 5206 return input.empty || !cont.empty && input.front == cont.front; 5207 } 5208 5209 if (!checkEnd()) 5210 { 5211 for (;;) 5212 { 5213 auto fmt = FormatSpec!Char(spec.nested); 5214 fmt.readUpToNextSpec(input); 5215 enforce(!input.empty, "Unexpected end of input when parsing range"); 5216 5217 debug (unformatRange) printf("\t) spec = %c, front = %c ", fmt.spec, input.front); 5218 static if (isStaticArray!T) 5219 { 5220 result[i++] = unformatElement!(typeof(T.init[0]))(input, fmt); 5221 } 5222 else static if (isDynamicArray!T) 5223 { 5224 result ~= unformatElement!(ElementType!T)(input, fmt); 5225 } 5226 else static if (isAssociativeArray!T) 5227 { 5228 auto key = unformatElement!(typeof(T.init.keys[0]))(input, fmt); 5229 fmt.readUpToNextSpec(input); // eat key separator 5230 5231 result[key] = unformatElement!(typeof(T.init.values[0]))(input, fmt); 5232 } 5233 debug (unformatRange) { 5234 if (input.empty) printf("-> front = [empty] "); 5235 else printf("-> front = %c ", input.front); 5236 } 5237 5238 static if (isStaticArray!T) 5239 { 5240 debug (unformatRange) printf("i = %u < %u\n", i, T.length); 5241 enforce(i <= T.length, "Too many format specifiers for static array of length %d".format(T.length)); 5242 } 5243 5244 if (spec.sep !is null) 5245 fmt.readUpToNextSpec(input); 5246 auto sep = spec.sep !is null ? spec.sep 5247 : fmt.trailing; 5248 debug (unformatRange) { 5249 if (!sep.empty && !input.empty) printf("-> %c, sep = %.*s\n", input.front, sep); 5250 else printf("\n"); 5251 } 5252 5253 if (checkEnd()) 5254 break; 5255 5256 if (!sep.empty && input.front == sep.front) 5257 { 5258 while (!sep.empty) 5259 { 5260 enforce(!input.empty, "Unexpected end of input when parsing range separator"); 5261 enforce(input.front == sep.front, "Unexpected character when parsing range separator"); 5262 input.popFront(); 5263 sep.popFront(); 5264 } 5265 debug (unformatRange) printf("input.front = %c\n", input.front); 5266 } 5267 } 5268 } 5269 static if (isStaticArray!T) 5270 { 5271 enforce(i == T.length, "Too few (%d) format specifiers for static array of length %d".format(i, T.length)); 5272 } 5273 return result; 5274} 5275 5276// Undocumented 5277T unformatElement(T, Range, Char)(ref Range input, const ref FormatSpec!Char spec) 5278if (isInputRange!Range) 5279{ 5280 import std.conv : parseElement; 5281 static if (isSomeString!T) 5282 { 5283 if (spec.spec == 's') 5284 { 5285 return parseElement!T(input); 5286 } 5287 } 5288 else static if (isSomeChar!T) 5289 { 5290 if (spec.spec == 's') 5291 { 5292 return parseElement!T(input); 5293 } 5294 } 5295 5296 return unformatValue!T(input, spec); 5297} 5298 5299 5300// Legacy implementation 5301 5302enum Mangle : char 5303{ 5304 Tvoid = 'v', 5305 Tbool = 'b', 5306 Tbyte = 'g', 5307 Tubyte = 'h', 5308 Tshort = 's', 5309 Tushort = 't', 5310 Tint = 'i', 5311 Tuint = 'k', 5312 Tlong = 'l', 5313 Tulong = 'm', 5314 Tfloat = 'f', 5315 Tdouble = 'd', 5316 Treal = 'e', 5317 5318 Tifloat = 'o', 5319 Tidouble = 'p', 5320 Tireal = 'j', 5321 Tcfloat = 'q', 5322 Tcdouble = 'r', 5323 Tcreal = 'c', 5324 5325 Tchar = 'a', 5326 Twchar = 'u', 5327 Tdchar = 'w', 5328 5329 Tarray = 'A', 5330 Tsarray = 'G', 5331 Taarray = 'H', 5332 Tpointer = 'P', 5333 Tfunction = 'F', 5334 Tident = 'I', 5335 Tclass = 'C', 5336 Tstruct = 'S', 5337 Tenum = 'E', 5338 Ttypedef = 'T', 5339 Tdelegate = 'D', 5340 5341 Tconst = 'x', 5342 Timmutable = 'y', 5343} 5344 5345// return the TypeInfo for a primitive type and null otherwise. This 5346// is required since for arrays of ints we only have the mangled char 5347// to work from. If arrays always subclassed TypeInfo_Array this 5348// routine could go away. 5349private TypeInfo primitiveTypeInfo(Mangle m) 5350{ 5351 // BUG: should fix this in static this() to avoid double checked locking bug 5352 __gshared TypeInfo[Mangle] dic; 5353 if (!dic.length) 5354 { 5355 dic = [ 5356 Mangle.Tvoid : typeid(void), 5357 Mangle.Tbool : typeid(bool), 5358 Mangle.Tbyte : typeid(byte), 5359 Mangle.Tubyte : typeid(ubyte), 5360 Mangle.Tshort : typeid(short), 5361 Mangle.Tushort : typeid(ushort), 5362 Mangle.Tint : typeid(int), 5363 Mangle.Tuint : typeid(uint), 5364 Mangle.Tlong : typeid(long), 5365 Mangle.Tulong : typeid(ulong), 5366 Mangle.Tfloat : typeid(float), 5367 Mangle.Tdouble : typeid(double), 5368 Mangle.Treal : typeid(real), 5369 Mangle.Tifloat : typeid(ifloat), 5370 Mangle.Tidouble : typeid(idouble), 5371 Mangle.Tireal : typeid(ireal), 5372 Mangle.Tcfloat : typeid(cfloat), 5373 Mangle.Tcdouble : typeid(cdouble), 5374 Mangle.Tcreal : typeid(creal), 5375 Mangle.Tchar : typeid(char), 5376 Mangle.Twchar : typeid(wchar), 5377 Mangle.Tdchar : typeid(dchar) 5378 ]; 5379 } 5380 auto p = m in dic; 5381 return p ? *p : null; 5382} 5383 5384private bool needToSwapEndianess(Char)(const ref FormatSpec!Char f) 5385{ 5386 import std.system : endian, Endian; 5387 5388 return endian == Endian.littleEndian && f.flPlus 5389 || endian == Endian.bigEndian && f.flDash; 5390} 5391 5392/* ======================== Unit Tests ====================================== */ 5393 5394@system unittest 5395{ 5396 import std.conv : octal; 5397 5398 int i; 5399 string s; 5400 5401 debug(format) printf("std.format.format.unittest\n"); 5402 5403 s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo"); 5404 assert(s == "hello world! true 57 1000000000x foo"); 5405 5406 s = format("%s %A %s", 1.67, -1.28, float.nan); 5407 /* The host C library is used to format floats. 5408 * C99 doesn't specify what the hex digit before the decimal point 5409 * is for %A. 5410 */ 5411 //version (linux) 5412 // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan"); 5413 //else version (OSX) 5414 // assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s); 5415 //else 5416 version (MinGW) 5417 assert(s == "1.67 -0XA.3D70A3D70A3D8P-3 nan", s); 5418 else version (CRuntime_Microsoft) 5419 assert(s == "1.67 -0X1.47AE14P+0 nan" 5420 || s == "1.67 -0X1.47AE147AE147BP+0 nan", s); // MSVCRT 14+ (VS 2015) 5421 else 5422 assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s); 5423 5424 s = format("%x %X", 0x1234AF, 0xAFAFAFAF); 5425 assert(s == "1234af AFAFAFAF"); 5426 5427 s = format("%b %o", 0x1234AF, 0xAFAFAFAF); 5428 assert(s == "100100011010010101111 25753727657"); 5429 5430 s = format("%d %s", 0x1234AF, 0xAFAFAFAF); 5431 assert(s == "1193135 2947526575"); 5432 5433 //version (X86_64) 5434 //{ 5435 // pragma(msg, "several format tests disabled on x86_64 due to bug 5625"); 5436 //} 5437 //else 5438 //{ 5439 s = format("%s", 1.2 + 3.4i); 5440 assert(s == "1.2+3.4i", s); 5441 5442 //s = format("%x %X", 1.32, 6.78f); 5443 //assert(s == "3ff51eb851eb851f 40D8F5C3"); 5444 5445 //} 5446 5447 s = format("%#06.*f",2,12.345); 5448 assert(s == "012.35"); 5449 5450 s = format("%#0*.*f",6,2,12.345); 5451 assert(s == "012.35"); 5452 5453 s = format("%7.4g:", 12.678); 5454 assert(s == " 12.68:"); 5455 5456 s = format("%7.4g:", 12.678L); 5457 assert(s == " 12.68:"); 5458 5459 s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1); 5460 assert(s == "-4.000000|-0010|0x001| 0x1"); 5461 5462 i = -10; 5463 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 5464 assert(s == "-10|-10|-10|-10|-10.0000"); 5465 5466 i = -5; 5467 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 5468 assert(s == "-5| -5|-05|-5|-5.0000"); 5469 5470 i = 0; 5471 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 5472 assert(s == "0| 0|000|0|0.0000"); 5473 5474 i = 5; 5475 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 5476 assert(s == "5| 5|005|5|5.0000"); 5477 5478 i = 10; 5479 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 5480 assert(s == "10| 10|010|10|10.0000"); 5481 5482 s = format("%.0d", 0); 5483 assert(s == ""); 5484 5485 s = format("%.g", .34); 5486 assert(s == "0.3"); 5487 5488 s = format("%.0g", .34); 5489 assert(s == "0.3"); 5490 5491 s = format("%.2g", .34); 5492 assert(s == "0.34"); 5493 5494 s = format("%0.0008f", 1e-08); 5495 assert(s == "0.00000001"); 5496 5497 s = format("%0.0008f", 1e-05); 5498 assert(s == "0.00001000"); 5499 5500 s = "helloworld"; 5501 string r; 5502 r = format("%.2s", s[0 .. 5]); 5503 assert(r == "he"); 5504 r = format("%.20s", s[0 .. 5]); 5505 assert(r == "hello"); 5506 r = format("%8s", s[0 .. 5]); 5507 assert(r == " hello"); 5508 5509 byte[] arrbyte = new byte[4]; 5510 arrbyte[0] = 100; 5511 arrbyte[1] = -99; 5512 arrbyte[3] = 0; 5513 r = format("%s", arrbyte); 5514 assert(r == "[100, -99, 0, 0]"); 5515 5516 ubyte[] arrubyte = new ubyte[4]; 5517 arrubyte[0] = 100; 5518 arrubyte[1] = 200; 5519 arrubyte[3] = 0; 5520 r = format("%s", arrubyte); 5521 assert(r == "[100, 200, 0, 0]"); 5522 5523 short[] arrshort = new short[4]; 5524 arrshort[0] = 100; 5525 arrshort[1] = -999; 5526 arrshort[3] = 0; 5527 r = format("%s", arrshort); 5528 assert(r == "[100, -999, 0, 0]"); 5529 5530 ushort[] arrushort = new ushort[4]; 5531 arrushort[0] = 100; 5532 arrushort[1] = 20_000; 5533 arrushort[3] = 0; 5534 r = format("%s", arrushort); 5535 assert(r == "[100, 20000, 0, 0]"); 5536 5537 int[] arrint = new int[4]; 5538 arrint[0] = 100; 5539 arrint[1] = -999; 5540 arrint[3] = 0; 5541 r = format("%s", arrint); 5542 assert(r == "[100, -999, 0, 0]"); 5543 5544 long[] arrlong = new long[4]; 5545 arrlong[0] = 100; 5546 arrlong[1] = -999; 5547 arrlong[3] = 0; 5548 r = format("%s", arrlong); 5549 assert(r == "[100, -999, 0, 0]"); 5550 5551 ulong[] arrulong = new ulong[4]; 5552 arrulong[0] = 100; 5553 arrulong[1] = 999; 5554 arrulong[3] = 0; 5555 r = format("%s", arrulong); 5556 assert(r == "[100, 999, 0, 0]"); 5557 5558 string[] arr2 = new string[4]; 5559 arr2[0] = "hello"; 5560 arr2[1] = "world"; 5561 arr2[3] = "foo"; 5562 r = format("%s", arr2); 5563 assert(r == `["hello", "world", "", "foo"]`); 5564 5565 r = format("%.8d", 7); 5566 assert(r == "00000007"); 5567 r = format("%.8x", 10); 5568 assert(r == "0000000a"); 5569 5570 r = format("%-3d", 7); 5571 assert(r == "7 "); 5572 5573 r = format("%-1*d", 4, 3); 5574 assert(r == "3 "); 5575 5576 r = format("%*d", -3, 7); 5577 assert(r == "7 "); 5578 5579 r = format("%.*d", -3, 7); 5580 assert(r == "7"); 5581 5582 r = format("%-1.*f", 2, 3.1415); 5583 assert(r == "3.14"); 5584 5585 r = format("abc"c); 5586 assert(r == "abc"); 5587 5588 //format() returns the same type as inputted. 5589 wstring wr; 5590 wr = format("def"w); 5591 assert(wr == "def"w); 5592 5593 dstring dr; 5594 dr = format("ghi"d); 5595 assert(dr == "ghi"d); 5596 5597 void* p = cast(void*) 0xDEADBEEF; 5598 r = format("%s", p); 5599 assert(r == "DEADBEEF"); 5600 5601 r = format("%#x", 0xabcd); 5602 assert(r == "0xabcd"); 5603 r = format("%#X", 0xABCD); 5604 assert(r == "0XABCD"); 5605 5606 r = format("%#o", octal!12345); 5607 assert(r == "012345"); 5608 r = format("%o", 9); 5609 assert(r == "11"); 5610 r = format("%#o", 0); // issue 15663 5611 assert(r == "0"); 5612 5613 r = format("%+d", 123); 5614 assert(r == "+123"); 5615 r = format("%+d", -123); 5616 assert(r == "-123"); 5617 r = format("% d", 123); 5618 assert(r == " 123"); 5619 r = format("% d", -123); 5620 assert(r == "-123"); 5621 5622 r = format("%%"); 5623 assert(r == "%"); 5624 5625 r = format("%d", true); 5626 assert(r == "1"); 5627 r = format("%d", false); 5628 assert(r == "0"); 5629 5630 r = format("%d", 'a'); 5631 assert(r == "97"); 5632 wchar wc = 'a'; 5633 r = format("%d", wc); 5634 assert(r == "97"); 5635 dchar dc = 'a'; 5636 r = format("%d", dc); 5637 assert(r == "97"); 5638 5639 byte b = byte.max; 5640 r = format("%x", b); 5641 assert(r == "7f"); 5642 r = format("%x", ++b); 5643 assert(r == "80"); 5644 r = format("%x", ++b); 5645 assert(r == "81"); 5646 5647 short sh = short.max; 5648 r = format("%x", sh); 5649 assert(r == "7fff"); 5650 r = format("%x", ++sh); 5651 assert(r == "8000"); 5652 r = format("%x", ++sh); 5653 assert(r == "8001"); 5654 5655 i = int.max; 5656 r = format("%x", i); 5657 assert(r == "7fffffff"); 5658 r = format("%x", ++i); 5659 assert(r == "80000000"); 5660 r = format("%x", ++i); 5661 assert(r == "80000001"); 5662 5663 r = format("%x", 10); 5664 assert(r == "a"); 5665 r = format("%X", 10); 5666 assert(r == "A"); 5667 r = format("%x", 15); 5668 assert(r == "f"); 5669 r = format("%X", 15); 5670 assert(r == "F"); 5671 5672 Object c = null; 5673 r = format("%s", c); 5674 assert(r == "null"); 5675 5676 enum TestEnum 5677 { 5678 Value1, Value2 5679 } 5680 r = format("%s", TestEnum.Value2); 5681 assert(r == "Value2"); 5682 5683 immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 5684 r = format("%s", aa.values); 5685 assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`); 5686 r = format("%s", aa); 5687 assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`); 5688 5689 static const dchar[] ds = ['a','b']; 5690 for (int j = 0; j < ds.length; ++j) 5691 { 5692 r = format(" %d", ds[j]); 5693 if (j == 0) 5694 assert(r == " 97"); 5695 else 5696 assert(r == " 98"); 5697 } 5698 5699 r = format(">%14d<, %s", 15, [1,2,3]); 5700 assert(r == "> 15<, [1, 2, 3]"); 5701 5702 assert(format("%8s", "bar") == " bar"); 5703 assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4"); 5704} 5705 5706@safe unittest 5707{ 5708 // bugzilla 3479 5709 import std.array; 5710 auto stream = appender!(char[])(); 5711 formattedWrite(stream, "%2$.*1$d", 12, 10); 5712 assert(stream.data == "000000000010", stream.data); 5713} 5714 5715@safe unittest 5716{ 5717 // bug 6893 5718 import std.array; 5719 enum E : ulong { A, B, C } 5720 auto stream = appender!(char[])(); 5721 formattedWrite(stream, "%s", E.C); 5722 assert(stream.data == "C"); 5723} 5724 5725// Used to check format strings are compatible with argument types 5726package static const checkFormatException(alias fmt, Args...) = 5727{ 5728 try 5729 .format(fmt, Args.init); 5730 catch (Exception e) 5731 return (e.msg == ctfpMessage) ? null : e; 5732 return null; 5733}(); 5734 5735/***************************************************** 5736 * Format arguments into a string. 5737 * 5738 * Params: fmt = Format string. For detailed specification, see $(LREF formattedWrite). 5739 * args = Variadic list of arguments to _format into returned string. 5740 */ 5741typeof(fmt) format(alias fmt, Args...)(Args args) 5742if (isSomeString!(typeof(fmt))) 5743{ 5744 alias e = checkFormatException!(fmt, Args); 5745 static assert(!e, e.msg); 5746 return .format(fmt, args); 5747} 5748 5749/// Type checking can be done when fmt is known at compile-time: 5750@safe unittest 5751{ 5752 auto s = format!"%s is %s"("Pi", 3.14); 5753 assert(s == "Pi is 3.14"); 5754 5755 static assert(!__traits(compiles, {s = format!"%l"();})); // missing arg 5756 static assert(!__traits(compiles, {s = format!""(404);})); // surplus arg 5757 static assert(!__traits(compiles, {s = format!"%d"(4.03);})); // incompatible arg 5758} 5759 5760/// ditto 5761immutable(Char)[] format(Char, Args...)(in Char[] fmt, Args args) 5762if (isSomeChar!Char) 5763{ 5764 import std.array : appender; 5765 import std.format : formattedWrite, FormatException; 5766 auto w = appender!(immutable(Char)[]); 5767 auto n = formattedWrite(w, fmt, args); 5768 version (all) 5769 { 5770 // In the future, this check will be removed to increase consistency 5771 // with formattedWrite 5772 import std.conv : text; 5773 import std.exception : enforce; 5774 enforce(n == args.length, new FormatException( 5775 text("Orphan format arguments: args[", n, "..", args.length, "]"))); 5776 } 5777 return w.data; 5778} 5779 5780@safe pure unittest 5781{ 5782 import core.exception; 5783 import std.exception; 5784 import std.format; 5785 assertCTFEable!( 5786 { 5787// assert(format(null) == ""); 5788 assert(format("foo") == "foo"); 5789 assert(format("foo%%") == "foo%"); 5790 assert(format("foo%s", 'C') == "fooC"); 5791 assert(format("%s foo", "bar") == "bar foo"); 5792 assert(format("%s foo %s", "bar", "abc") == "bar foo abc"); 5793 assert(format("foo %d", -123) == "foo -123"); 5794 assert(format("foo %d", 123) == "foo 123"); 5795 5796 assertThrown!FormatException(format("foo %s")); 5797 assertThrown!FormatException(format("foo %s", 123, 456)); 5798 5799 assert(format("hel%slo%s%s%s", "world", -138, 'c', true) == 5800 "helworldlo-138ctrue"); 5801 }); 5802 5803 assert(is(typeof(format("happy")) == string)); 5804 assert(is(typeof(format("happy"w)) == wstring)); 5805 assert(is(typeof(format("happy"d)) == dstring)); 5806} 5807 5808// https://issues.dlang.org/show_bug.cgi?id=16661 5809@safe unittest 5810{ 5811 assert(format("%.2f"d, 0.4) == "0.40"); 5812 assert("%02d"d.format(1) == "01"d); 5813} 5814 5815/***************************************************** 5816 * Format arguments into buffer $(I buf) which must be large 5817 * enough to hold the result. 5818 * 5819 * Returns: 5820 * The slice of `buf` containing the formatted string. 5821 * 5822 * Throws: 5823 * A `RangeError` if `buf` isn't large enough to hold the 5824 * formatted string. 5825 * 5826 * A $(LREF FormatException) if the length of `args` is different 5827 * than the number of format specifiers in `fmt`. 5828 */ 5829char[] sformat(alias fmt, Args...)(char[] buf, Args args) 5830if (isSomeString!(typeof(fmt))) 5831{ 5832 alias e = checkFormatException!(fmt, Args); 5833 static assert(!e, e.msg); 5834 return .sformat(buf, fmt, args); 5835} 5836 5837/// ditto 5838char[] sformat(Char, Args...)(char[] buf, in Char[] fmt, Args args) 5839{ 5840 import core.exception : RangeError; 5841 import std.format : formattedWrite, FormatException; 5842 import std.utf : encode; 5843 5844 size_t i; 5845 5846 struct Sink 5847 { 5848 void put(dchar c) 5849 { 5850 char[4] enc; 5851 auto n = encode(enc, c); 5852 5853 if (buf.length < i + n) 5854 throw new RangeError(__FILE__, __LINE__); 5855 5856 buf[i .. i + n] = enc[0 .. n]; 5857 i += n; 5858 } 5859 void put(const(char)[] s) 5860 { 5861 if (buf.length < i + s.length) 5862 throw new RangeError(__FILE__, __LINE__); 5863 5864 buf[i .. i + s.length] = s[]; 5865 i += s.length; 5866 } 5867 void put(const(wchar)[] s) 5868 { 5869 for (; !s.empty; s.popFront()) 5870 put(s.front); 5871 } 5872 void put(const(dchar)[] s) 5873 { 5874 for (; !s.empty; s.popFront()) 5875 put(s.front); 5876 } 5877 } 5878 auto n = formattedWrite(Sink(), fmt, args); 5879 version (all) 5880 { 5881 // In the future, this check will be removed to increase consistency 5882 // with formattedWrite 5883 import std.conv : text; 5884 import std.exception : enforce; 5885 enforce!FormatException( 5886 n == args.length, 5887 text("Orphan format arguments: args[", n, " .. ", args.length, "]") 5888 ); 5889 } 5890 return buf[0 .. i]; 5891} 5892 5893/// The format string can be checked at compile-time (see $(LREF format) for details): 5894@system unittest 5895{ 5896 char[10] buf; 5897 5898 assert(buf[].sformat!"foo%s"('C') == "fooC"); 5899 assert(sformat(buf[], "%s foo", "bar") == "bar foo"); 5900} 5901 5902@system unittest 5903{ 5904 import core.exception; 5905 import std.format; 5906 5907 debug(string) trustedPrintf("std.string.sformat.unittest\n"); 5908 5909 import std.exception; 5910 assertCTFEable!( 5911 { 5912 char[10] buf; 5913 5914 assert(sformat(buf[], "foo") == "foo"); 5915 assert(sformat(buf[], "foo%%") == "foo%"); 5916 assert(sformat(buf[], "foo%s", 'C') == "fooC"); 5917 assert(sformat(buf[], "%s foo", "bar") == "bar foo"); 5918 assertThrown!RangeError(sformat(buf[], "%s foo %s", "bar", "abc")); 5919 assert(sformat(buf[], "foo %d", -123) == "foo -123"); 5920 assert(sformat(buf[], "foo %d", 123) == "foo 123"); 5921 5922 assertThrown!FormatException(sformat(buf[], "foo %s")); 5923 assertThrown!FormatException(sformat(buf[], "foo %s", 123, 456)); 5924 5925 assert(sformat(buf[], "%s %s %s", "c"c, "w"w, "d"d) == "c w d"); 5926 }); 5927} 5928 5929/***************************** 5930 * The .ptr is unsafe because it could be dereferenced and the length of the array may be 0. 5931 * Returns: 5932 * the difference between the starts of the arrays 5933 */ 5934@trusted private pure nothrow @nogc 5935 ptrdiff_t arrayPtrDiff(T)(const T[] array1, const T[] array2) 5936{ 5937 return array1.ptr - array2.ptr; 5938} 5939 5940@safe unittest 5941{ 5942 assertCTFEable!({ 5943 auto tmp = format("%,d", 1000); 5944 assert(tmp == "1,000", "'" ~ tmp ~ "'"); 5945 5946 tmp = format("%,?d", 'z', 1234567); 5947 assert(tmp == "1z234z567", "'" ~ tmp ~ "'"); 5948 5949 tmp = format("%10,?d", 'z', 1234567); 5950 assert(tmp == " 1z234z567", "'" ~ tmp ~ "'"); 5951 5952 tmp = format("%11,2?d", 'z', 1234567); 5953 assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'"); 5954 5955 tmp = format("%11,*?d", 2, 'z', 1234567); 5956 assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'"); 5957 5958 tmp = format("%11,*d", 2, 1234567); 5959 assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'"); 5960 5961 tmp = format("%11,2d", 1234567); 5962 assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'"); 5963 }); 5964} 5965 5966@safe unittest 5967{ 5968 auto tmp = format("%,f", 1000.0); 5969 assert(tmp == "1,000.000,000", "'" ~ tmp ~ "'"); 5970 5971 tmp = format("%,f", 1234567.891011); 5972 assert(tmp == "1,234,567.891,011", "'" ~ tmp ~ "'"); 5973 5974 tmp = format("%,f", -1234567.891011); 5975 assert(tmp == "-1,234,567.891,011", "'" ~ tmp ~ "'"); 5976 5977 tmp = format("%,2f", 1234567.891011); 5978 assert(tmp == "1,23,45,67.89,10,11", "'" ~ tmp ~ "'"); 5979 5980 tmp = format("%18,f", 1234567.891011); 5981 assert(tmp == " 1,234,567.891,011", "'" ~ tmp ~ "'"); 5982 5983 tmp = format("%18,?f", '.', 1234567.891011); 5984 assert(tmp == " 1.234.567.891.011", "'" ~ tmp ~ "'"); 5985 5986 tmp = format("%,?.3f", '��', 1234567.891011); 5987 assert(tmp == "1��234��567.891", "'" ~ tmp ~ "'"); 5988 5989 tmp = format("%,*?.3f", 1, '��', 1234567.891011); 5990 assert(tmp == "1��2��3��4��5��6��7.8��9��1", "'" ~ tmp ~ "'"); 5991 5992 tmp = format("%,4?.3f", '_', 1234567.891011); 5993 assert(tmp == "123_4567.891", "'" ~ tmp ~ "'"); 5994 5995 tmp = format("%12,3.3f", 1234.5678); 5996 assert(tmp == " 1,234.568", "'" ~ tmp ~ "'"); 5997 5998 tmp = format("%,e", 3.141592653589793238462); 5999 assert(tmp == "3.141,593e+00", "'" ~ tmp ~ "'"); 6000 6001 tmp = format("%15,e", 3.141592653589793238462); 6002 assert(tmp == " 3.141,593e+00", "'" ~ tmp ~ "'"); 6003 6004 tmp = format("%15,e", -3.141592653589793238462); 6005 assert(tmp == " -3.141,593e+00", "'" ~ tmp ~ "'"); 6006 6007 tmp = format("%.4,*e", 2, 3.141592653589793238462); 6008 assert(tmp == "3.14,16e+00", "'" ~ tmp ~ "'"); 6009 6010 tmp = format("%13.4,*e", 2, 3.141592653589793238462); 6011 assert(tmp == " 3.14,16e+00", "'" ~ tmp ~ "'"); 6012 6013 tmp = format("%,.0f", 3.14); 6014 assert(tmp == "3", "'" ~ tmp ~ "'"); 6015 6016 tmp = format("%3,g", 1_000_000.123456); 6017 assert(tmp == "1e+06", "'" ~ tmp ~ "'"); 6018 6019 tmp = format("%19,?f", '.', -1234567.891011); 6020 assert(tmp == " -1.234.567.891.011", "'" ~ tmp ~ "'"); 6021} 6022 6023// Test for multiple indexes 6024@safe unittest 6025{ 6026 auto tmp = format("%2:5$s", 1, 2, 3, 4, 5); 6027 assert(tmp == "2345", tmp); 6028} 6029