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 `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 The D Language Foundation 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 */ 56 module std.format; 57 58 //debug=format; // uncomment to turn on debugging printf's 59 60 import core.vararg; 61 import std.exception; 62 import std.meta; 63 import std.range.primitives; 64 import std.traits; 65 66 67 /** 68 Signals a mismatch between a format and its corresponding argument. 69 */ 70 class FormatException : Exception 71 { 72 @safe @nogc pure nothrow 73 this() 74 { 75 super("format error"); 76 } 77 78 @safe @nogc 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 85 /// 86 @safe unittest 87 { 88 import std.exception : assertThrown; 89 assertThrown!FormatException(format("%d", "foo")); 90 } 91 92 private alias enforceFmt = enforce!FormatException; 93 94 95 /********************************************************************** 96 Interprets variadic argument list `args`, formats them according 97 to `fmt`, and sends the resulting characters to `w`. The 98 encoding of the output is the same as `Char`. The type `Writer` 99 must satisfy $(REF isOutputRange, std,range,primitives)!(Writer, Char). 100 101 The variadic arguments are normally consumed in order. POSIX-style 102 $(HTTP opengroup.org/onlinepubs/009695399/functions/printf.html, 103 positional parameter syntax) is also supported. Each argument is 104 formatted into a sequence of chars according to the format 105 specification, and the characters are passed to `w`. As many 106 arguments as specified in the format string are consumed and 107 formatted. If there are fewer arguments than format specifiers, a 108 `FormatException` is thrown. If there are more remaining arguments 109 than needed by the format specification, they are ignored but only 110 if at least one argument was formatted. 111 112 The format string supports the formatting of array and nested array elements 113 via the grouping format specifiers $(B %$(LPAREN)) and $(B %$(RPAREN)). Each 114 matching pair of $(B %$(LPAREN)) and $(B %$(RPAREN)) corresponds with a single array 115 argument. The enclosed sub-format string is applied to individual array 116 elements. The trailing portion of the sub-format string following the 117 conversion specifier for the array element is interpreted as the array 118 delimiter, and is therefore omitted following the last array element. The 119 $(B %|) specifier may be used to explicitly indicate the start of the 120 delimiter, so that the preceding portion of the string will be included 121 following the last array element. (See below for explicit examples.) 122 123 Params: 124 125 w = Output is sent to this writer. Typical output writers include 126 $(REF Appender!string, std,array) and $(REF LockingTextWriter, std,stdio). 127 128 fmt = Format string. 129 130 args = Variadic argument list. 131 132 Returns: Formatted number of arguments. 133 134 Throws: Mismatched arguments and formats result in a $(D 135 FormatException) being thrown. 136 137 Format_String: $(I Format strings) 138 consist of characters interspersed with $(I format 139 specifications). Characters are simply copied to the output (such 140 as putc) after any necessary conversion to the corresponding UTF-8 141 sequence. 142 143 The format string has the following grammar: 144 145 $(PRE 146 $(I FormatString): 147 $(I FormatStringItem)* 148 $(I FormatStringItem): 149 $(B '%%') 150 $(B '%') $(I Position) $(I Flags) $(I Width) $(I Separator) $(I Precision) $(I FormatChar) 151 $(B '%$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)') 152 $(B '%-$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)') 153 $(I OtherCharacterExceptPercent) 154 $(I Position): 155 $(I empty) 156 $(I Integer) $(B '$') 157 $(I Flags): 158 $(I empty) 159 $(B '-') $(I Flags) 160 $(B '+') $(I Flags) 161 $(B '#') $(I Flags) 162 $(B '0') $(I Flags) 163 $(B ' ') $(I Flags) 164 $(I Width): 165 $(I empty) 166 $(I Integer) 167 $(B '*') 168 $(I Separator): 169 $(I empty) 170 $(B ',') 171 $(B ',') $(B '?') 172 $(B ',') $(B '*') $(B '?') 173 $(B ',') $(I Integer) $(B '?') 174 $(B ',') $(B '*') 175 $(B ',') $(I Integer) 176 $(I Precision): 177 $(I empty) 178 $(B '.') 179 $(B '.') $(I Integer) 180 $(B '.*') 181 $(I Integer): 182 $(I Digit) 183 $(I Digit) $(I Integer) 184 $(I Digit): 185 $(B '0')|$(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9') 186 $(I FormatChar): 187 $(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 '|') 188 ) 189 190 $(BOOKTABLE Flags affect formatting depending on the specifier as 191 follows., $(TR $(TH Flag) $(TH Types affected) $(TH Semantics)) 192 193 $(TR $(TD $(B '-')) $(TD numeric, bool, null, char, string, enum, pointer) $(TD Left justify the result in 194 the field. It overrides any $(B 0) flag.)) 195 196 $(TR $(TD $(B '+')) $(TD numeric) $(TD Prefix positive numbers in 197 a signed conversion with a $(B +). It overrides any $(I space) 198 flag.)) 199 200 $(TR $(TD $(B '#')) $(TD integral ($(B 'o'))) $(TD Add to 201 precision as necessary so that the first digit of the octal 202 formatting is a '0', even if both the argument and the $(I 203 Precision) are zero.)) 204 205 $(TR $(TD $(B '#')) $(TD integral ($(B 'x'), $(B 'X'))) $(TD If 206 non-zero, prefix result with $(B 0x) ($(B 0X)).)) 207 208 $(TR $(TD $(B '#')) $(TD floating) $(TD Always insert the decimal 209 point and print trailing zeros.)) 210 211 $(TR $(TD $(B '0')) $(TD numeric) $(TD Use leading 212 zeros to pad rather than spaces (except for the floating point 213 values `nan` and `infinity`). Ignore if there's a $(I 214 Precision).)) 215 216 $(TR $(TD $(B ' ')) $(TD numeric) $(TD Prefix positive 217 numbers in a signed conversion with a space.))) 218 219 $(DL 220 $(DT $(I Width)) 221 $(DD 222 Only used for numeric, bool, null, char, string, enum and pointer types. 223 Specifies the minimum field width. 224 If the width is a $(B *), an additional argument of type $(B int), 225 preceding the actual argument, is taken as the width. 226 If the width is negative, it is as if the $(B -) was given 227 as a $(I Flags) character.) 228 229 $(DT $(I Precision)) 230 $(DD Gives the precision for numeric conversions. 231 If the precision is a $(B *), an additional argument of type $(B int), 232 preceding the actual argument, is taken as the precision. 233 If it is negative, it is as if there was no $(I Precision) specifier.) 234 235 $(DT $(I Separator)) 236 $(DD Inserts the separator symbols ',' every $(I X) digits, from right 237 to left, into numeric values to increase readability. 238 The fractional part of floating point values inserts the separator 239 from left to right. 240 Entering an integer after the ',' allows to specify $(I X). 241 If a '*' is placed after the ',' then $(I X) is specified by an 242 additional parameter to the format function. 243 Adding a '?' after the ',' or $(I X) specifier allows to specify 244 the separator character as an additional parameter. 245 ) 246 247 $(DT $(I FormatChar)) 248 $(DD 249 $(DL 250 $(DT $(B 's')) 251 $(DD The corresponding argument is formatted in a manner consistent 252 with its type: 253 $(DL 254 $(DT $(B bool)) 255 $(DD The result is `"true"` or `"false"`.) 256 $(DT integral types) 257 $(DD The $(B %d) format is used.) 258 $(DT floating point types) 259 $(DD The $(B %g) format is used.) 260 $(DT string types) 261 $(DD The result is the string converted to UTF-8. 262 A $(I Precision) specifies the maximum number of characters 263 to use in the result.) 264 $(DT structs) 265 $(DD If the struct defines a $(B toString()) method the result is 266 the string returned from this function. Otherwise the result is 267 StructName(field$(SUB 0), field$(SUB 1), ...) where 268 field$(SUB n) is the nth element formatted with the default 269 format.) 270 $(DT classes derived from $(B Object)) 271 $(DD The result is the string returned from the class instance's 272 $(B .toString()) method. 273 A $(I Precision) specifies the maximum number of characters 274 to use in the result.) 275 $(DT unions) 276 $(DD If the union defines a $(B toString()) method the result is 277 the string returned from this function. Otherwise the result is 278 the name of the union, without its contents.) 279 $(DT non-string static and dynamic arrays) 280 $(DD The result is [s$(SUB 0), s$(SUB 1), ...] 281 where s$(SUB n) is the nth element 282 formatted with the default format.) 283 $(DT associative arrays) 284 $(DD The result is the equivalent of what the initializer 285 would look like for the contents of the associative array, 286 e.g.: ["red" : 10, "blue" : 20].) 287 )) 288 289 $(DT $(B 'c')) 290 $(DD The corresponding argument must be a character type.) 291 292 $(DT $(B 'b','d','o','x','X')) 293 $(DD The corresponding argument must be an integral type 294 and is formatted as an integer. If the argument is a signed type 295 and the $(I FormatChar) is $(B d) it is converted to 296 a signed string of characters, otherwise it is treated as 297 unsigned. An argument of type $(B bool) is formatted as '1' 298 or '0'. The base used is binary for $(B b), octal for $(B o), 299 decimal 300 for $(B d), and hexadecimal for $(B x) or $(B X). 301 $(B x) formats using lower case letters, $(B X) uppercase. 302 If there are fewer resulting digits than the $(I Precision), 303 leading zeros are used as necessary. 304 If the $(I Precision) is 0 and the number is 0, no digits 305 result.) 306 307 $(DT $(B 'e','E')) 308 $(DD A floating point number is formatted as one digit before 309 the decimal point, $(I Precision) digits after, the $(I FormatChar), 310 ±, followed by at least a two digit exponent: 311 $(I d.dddddd)e$(I ±dd). 312 If there is no $(I Precision), six 313 digits are generated after the decimal point. 314 If the $(I Precision) is 0, no decimal point is generated.) 315 316 $(DT $(B 'f','F')) 317 $(DD A floating point number is formatted in decimal notation. 318 The $(I Precision) specifies the number of digits generated 319 after the decimal point. It defaults to six. At least one digit 320 is generated before the decimal point. If the $(I Precision) 321 is zero, no decimal point is generated.) 322 323 $(DT $(B 'g','G')) 324 $(DD A floating point number is formatted in either $(B e) or 325 $(B f) format for $(B g); $(B E) or $(B F) format for 326 $(B G). 327 The $(B f) format is used if the exponent for an $(B e) format 328 is greater than -5 and less than the $(I Precision). 329 The $(I Precision) specifies the number of significant 330 digits, and defaults to six. 331 Trailing zeros are elided after the decimal point, if the fractional 332 part is zero then no decimal point is generated.) 333 334 $(DT $(B 'a','A')) 335 $(DD A floating point number is formatted in hexadecimal 336 exponential notation 0x$(I h.hhhhhh)p$(I ±d). 337 There is one hexadecimal digit before the decimal point, and as 338 many after as specified by the $(I Precision). 339 If the $(I Precision) is zero, no decimal point is generated. 340 If there is no $(I Precision), as many hexadecimal digits as 341 necessary to exactly represent the mantissa are generated. 342 The exponent is written in as few digits as possible, 343 but at least one, is in decimal, and represents a power of 2 as in 344 $(I h.hhhhhh)*2$(SUPERSCRIPT $(I ±d)). 345 The exponent for zero is zero. 346 The hexadecimal digits, x and p are in upper case if the 347 $(I FormatChar) is upper case.) 348 )) 349 ) 350 351 Floating point NaN's are formatted as $(B nan) if the 352 $(I FormatChar) is lower case, or $(B NAN) if upper. 353 Floating point infinities are formatted as $(B inf) or 354 $(B infinity) if the 355 $(I FormatChar) is lower case, or $(B INF) or $(B INFINITY) if upper. 356 357 The positional and non-positional styles can be mixed in the same 358 format string. (POSIX leaves this behavior undefined.) The internal 359 counter for non-positional parameters tracks the next parameter after 360 the largest positional parameter already used. 361 362 Example using array and nested array formatting: 363 ------------------------- 364 import std.stdio; 365 366 void main() 367 { 368 writefln("My items are %(%s %).", [1,2,3]); 369 writefln("My items are %(%s, %).", [1,2,3]); 370 } 371 ------------------------- 372 The output is: 373 $(CONSOLE 374 My items are 1 2 3. 375 My items are 1, 2, 3. 376 ) 377 378 The trailing end of the sub-format string following the specifier for each 379 item is interpreted as the array delimiter, and is therefore omitted 380 following the last array item. The $(B %|) delimiter specifier may be used 381 to indicate where the delimiter begins, so that the portion of the format 382 string prior to it will be retained in the last array element: 383 ------------------------- 384 import std.stdio; 385 386 void main() 387 { 388 writefln("My items are %(-%s-%|, %).", [1,2,3]); 389 } 390 ------------------------- 391 which gives the output: 392 $(CONSOLE 393 My items are -1-, -2-, -3-. 394 ) 395 396 These compound format specifiers may be nested in the case of a nested 397 array argument: 398 ------------------------- 399 import std.stdio; 400 void main() { 401 auto mat = [[1, 2, 3], 402 [4, 5, 6], 403 [7, 8, 9]]; 404 405 writefln("%(%(%d %)\n%)", mat); 406 writeln(); 407 408 writefln("[%(%(%d %)\n %)]", mat); 409 writeln(); 410 411 writefln("[%([%(%d %)]%|\n %)]", mat); 412 writeln(); 413 } 414 ------------------------- 415 The output is: 416 $(CONSOLE 417 1 2 3 418 4 5 6 419 7 8 9 420 421 [1 2 3 422 4 5 6 423 7 8 9] 424 425 [[1 2 3] 426 [4 5 6] 427 [7 8 9]] 428 ) 429 430 Inside a compound format specifier, strings and characters are escaped 431 automatically. To avoid this behavior, add $(B '-') flag to 432 `"%$(LPAREN)"`. 433 ------------------------- 434 import std.stdio; 435 436 void main() 437 { 438 writefln("My friends are %s.", ["John", "Nancy"]); 439 writefln("My friends are %(%s, %).", ["John", "Nancy"]); 440 writefln("My friends are %-(%s, %).", ["John", "Nancy"]); 441 } 442 ------------------------- 443 which gives the output: 444 $(CONSOLE 445 My friends are ["John", "Nancy"]. 446 My friends are "John", "Nancy". 447 My friends are John, Nancy. 448 ) 449 */ 450 uint formattedWrite(alias fmt, Writer, A...)(auto ref Writer w, A args) 451 if (isSomeString!(typeof(fmt))) 452 { 453 alias e = checkFormatException!(fmt, A); 454 static assert(!e, e.msg); 455 return .formattedWrite(w, fmt, args); 456 } 457 458 /// The format string can be checked at compile-time (see $(LREF format) for details): 459 @safe pure unittest 460 { 461 import std.array : appender; 462 463 auto writer = appender!string(); 464 writer.formattedWrite!"%s is the ultimate %s."(42, "answer"); 465 assert(writer.data == "42 is the ultimate answer."); 466 467 // Clear the writer 468 writer = appender!string(); 469 formattedWrite(writer, "Date: %2$s %1$s", "October", 5); 470 assert(writer.data == "Date: 5 October"); 471 } 472 473 /// ditto 474 uint formattedWrite(Writer, Char, A...)(auto ref Writer w, const scope Char[] fmt, A args) 475 { 476 import std.conv : text; 477 478 auto spec = FormatSpec!Char(fmt); 479 480 // Are we already done with formats? Then just dump each parameter in turn 481 uint currentArg = 0; 482 while (spec.writeUpToNextSpec(w)) 483 { 484 if (currentArg == A.length && !spec.indexStart) 485 { 486 // leftover spec? 487 enforceFmt(fmt.length == 0, 488 text("Orphan format specifier: %", spec.spec)); 489 break; 490 } 491 492 if (spec.width == spec.DYNAMIC) 493 { 494 auto width = getNthInt!"integer width"(currentArg, args); 495 if (width < 0) 496 { 497 spec.flDash = true; 498 width = -width; 499 } 500 spec.width = width; 501 ++currentArg; 502 } 503 else if (spec.width < 0) 504 { 505 // means: get width as a positional parameter 506 auto index = cast(uint) -spec.width; 507 assert(index > 0, "The index must be greater than zero"); 508 auto width = getNthInt!"integer width"(index - 1, args); 509 if (currentArg < index) currentArg = index; 510 if (width < 0) 511 { 512 spec.flDash = true; 513 width = -width; 514 } 515 spec.width = width; 516 } 517 518 if (spec.precision == spec.DYNAMIC) 519 { 520 auto precision = getNthInt!"integer precision"(currentArg, args); 521 if (precision >= 0) spec.precision = precision; 522 // else negative precision is same as no precision 523 else spec.precision = spec.UNSPECIFIED; 524 ++currentArg; 525 } 526 else if (spec.precision < 0) 527 { 528 // means: get precision as a positional parameter 529 auto index = cast(uint) -spec.precision; 530 assert(index > 0, "The precision must be greater than zero"); 531 auto precision = getNthInt!"integer precision"(index- 1, args); 532 if (currentArg < index) currentArg = index; 533 if (precision >= 0) spec.precision = precision; 534 // else negative precision is same as no precision 535 else spec.precision = spec.UNSPECIFIED; 536 } 537 538 if (spec.separators == spec.DYNAMIC) 539 { 540 auto separators = getNthInt!"separator digit width"(currentArg, args); 541 spec.separators = separators; 542 ++currentArg; 543 } 544 545 if (spec.separatorCharPos == spec.DYNAMIC) 546 { 547 auto separatorChar = 548 getNth!("separator character", isSomeChar, dchar)(currentArg, args); 549 spec.separatorChar = separatorChar; 550 ++currentArg; 551 } 552 553 if (currentArg == A.length && !spec.indexStart) 554 { 555 // leftover spec? 556 enforceFmt(fmt.length == 0, 557 text("Orphan format specifier: %", spec.spec)); 558 break; 559 } 560 561 // Format an argument 562 // This switch uses a static foreach to generate a jump table. 563 // Currently `spec.indexStart` use the special value '0' to signal 564 // we should use the current argument. An enhancement would be to 565 // always store the index. 566 size_t index = currentArg; 567 if (spec.indexStart != 0) 568 index = spec.indexStart - 1; 569 else 570 ++currentArg; 571 SWITCH: switch (index) 572 { 573 foreach (i, Tunused; A) 574 { 575 case i: 576 formatValue(w, args[i], spec); 577 if (currentArg < spec.indexEnd) 578 currentArg = spec.indexEnd; 579 // A little know feature of format is to format a range 580 // of arguments, e.g. `%1:3$` will format the first 3 581 // arguments. Since they have to be consecutive we can 582 // just use explicit fallthrough to cover that case. 583 if (i + 1 < spec.indexEnd) 584 { 585 // You cannot goto case if the next case is the default 586 static if (i + 1 < A.length) 587 goto case; 588 else 589 goto default; 590 } 591 else 592 break SWITCH; 593 } 594 default: 595 throw new FormatException( 596 text("Positional specifier %", spec.indexStart, '$', spec.spec, 597 " index exceeds ", A.length)); 598 } 599 } 600 return currentArg; 601 } 602 603 /// 604 @safe unittest 605 { 606 assert(format("%,d", 1000) == "1,000"); 607 assert(format("%,f", 1234567.891011) == "1,234,567.891011"); 608 assert(format("%,?d", '?', 1000) == "1?000"); 609 assert(format("%,1d", 1000) == "1,0,0,0", format("%,1d", 1000)); 610 assert(format("%,*d", 4, -12345) == "-1,2345"); 611 assert(format("%,*?d", 4, '_', -12345) == "-1_2345"); 612 assert(format("%,6?d", '_', -12345678) == "-12_345678"); 613 assert(format("%12,3.3f", 1234.5678) == " 1,234.568", "'" ~ 614 format("%12,3.3f", 1234.5678) ~ "'"); 615 } 616 617 @safe pure unittest 618 { 619 import std.array; 620 auto w = appender!string(); 621 formattedWrite(w, "%s %d", "@safe/pure", 42); 622 assert(w.data == "@safe/pure 42"); 623 } 624 625 @safe pure unittest 626 { 627 char[20] buf; 628 auto w = buf[]; 629 formattedWrite(w, "%s %d", "@safe/pure", 42); 630 assert(buf[0 .. $ - w.length] == "@safe/pure 42"); 631 } 632 633 /** 634 Reads characters from $(REF_ALTTEXT input range, isInputRange, std,range,primitives) 635 `r`, converts them according to `fmt`, and writes them to `args`. 636 637 Params: 638 r = The range to read from. 639 fmt = The format of the data to read. 640 args = The drain of the data read. 641 642 Returns: 643 644 On success, the function returns the number of variables filled. This count 645 can match the expected number of readings or fewer, even zero, if a 646 matching failure happens. 647 648 Throws: 649 A `FormatException` if `S.length == 0` and `fmt` has format specifiers. 650 */ 651 uint formattedRead(alias fmt, R, S...)(auto ref R r, auto ref S args) 652 if (isSomeString!(typeof(fmt))) 653 { 654 alias e = checkFormatException!(fmt, S); 655 static assert(!e, e.msg); 656 return .formattedRead(r, fmt, args); 657 } 658 659 /// ditto 660 uint formattedRead(R, Char, S...)(auto ref R r, const(Char)[] fmt, auto ref S args) 661 { 662 import std.typecons : isTuple; 663 664 auto spec = FormatSpec!Char(fmt); 665 static if (!S.length) 666 { 667 spec.readUpToNextSpec(r); 668 enforceFmt(spec.trailing.empty, "Trailing characters in formattedRead format string"); 669 return 0; 670 } 671 else 672 { 673 enum hasPointer = isPointer!(typeof(args[0])); 674 675 // The function below accounts for '*' == fields meant to be 676 // read and skipped 677 void skipUnstoredFields() 678 { 679 for (;;) 680 { 681 spec.readUpToNextSpec(r); 682 if (spec.width != spec.DYNAMIC) break; 683 // must skip this field 684 skipData(r, spec); 685 } 686 } 687 688 skipUnstoredFields(); 689 if (r.empty) 690 { 691 // Input is empty, nothing to read 692 return 0; 693 } 694 static if (hasPointer) 695 alias A = typeof(*args[0]); 696 else 697 alias A = typeof(args[0]); 698 699 static if (isTuple!A) 700 { 701 foreach (i, T; A.Types) 702 { 703 static if (hasPointer) 704 (*args[0])[i] = unformatValue!(T)(r, spec); 705 else 706 args[0][i] = unformatValue!(T)(r, spec); 707 skipUnstoredFields(); 708 } 709 } 710 else 711 { 712 static if (hasPointer) 713 *args[0] = unformatValue!(A)(r, spec); 714 else 715 args[0] = unformatValue!(A)(r, spec); 716 } 717 return 1 + formattedRead(r, spec.trailing, args[1 .. $]); 718 } 719 } 720 721 /// The format string can be checked at compile-time (see $(LREF format) for details): 722 @safe pure unittest 723 { 724 string s = "hello!124:34.5"; 725 string a; 726 int b; 727 double c; 728 s.formattedRead!"%s!%s:%s"(a, b, c); 729 assert(a == "hello" && b == 124 && c == 34.5); 730 } 731 732 @safe unittest 733 { 734 import std.math; 735 string s = " 1.2 3.4 "; 736 double x, y, z; 737 assert(formattedRead(s, " %s %s %s ", x, y, z) == 2); 738 assert(s.empty); 739 assert(approxEqual(x, 1.2)); 740 assert(approxEqual(y, 3.4)); 741 assert(isNaN(z)); 742 } 743 744 // for backwards compatibility 745 @system pure unittest 746 { 747 string s = "hello!124:34.5"; 748 string a; 749 int b; 750 double c; 751 formattedRead(s, "%s!%s:%s", &a, &b, &c); 752 assert(a == "hello" && b == 124 && c == 34.5); 753 754 // mix pointers and auto-ref 755 s = "world!200:42.25"; 756 formattedRead(s, "%s!%s:%s", a, &b, &c); 757 assert(a == "world" && b == 200 && c == 42.25); 758 759 s = "world1!201:42.5"; 760 formattedRead(s, "%s!%s:%s", &a, &b, c); 761 assert(a == "world1" && b == 201 && c == 42.5); 762 763 s = "world2!202:42.75"; 764 formattedRead(s, "%s!%s:%s", a, b, &c); 765 assert(a == "world2" && b == 202 && c == 42.75); 766 } 767 768 // for backwards compatibility 769 @system pure unittest 770 { 771 import std.math; 772 string s = " 1.2 3.4 "; 773 double x, y, z; 774 assert(formattedRead(s, " %s %s %s ", &x, &y, &z) == 2); 775 assert(s.empty); 776 assert(approxEqual(x, 1.2)); 777 assert(approxEqual(y, 3.4)); 778 assert(isNaN(z)); 779 } 780 781 @system pure unittest 782 { 783 string line; 784 785 bool f1; 786 787 line = "true"; 788 formattedRead(line, "%s", &f1); 789 assert(f1); 790 791 line = "TrUE"; 792 formattedRead(line, "%s", &f1); 793 assert(f1); 794 795 line = "false"; 796 formattedRead(line, "%s", &f1); 797 assert(!f1); 798 799 line = "fALsE"; 800 formattedRead(line, "%s", &f1); 801 assert(!f1); 802 803 line = "1"; 804 formattedRead(line, "%d", &f1); 805 assert(f1); 806 807 line = "-1"; 808 formattedRead(line, "%d", &f1); 809 assert(f1); 810 811 line = "0"; 812 formattedRead(line, "%d", &f1); 813 assert(!f1); 814 815 line = "-0"; 816 formattedRead(line, "%d", &f1); 817 assert(!f1); 818 } 819 820 @system pure unittest 821 { 822 union B 823 { 824 char[int.sizeof] untyped; 825 int typed; 826 } 827 B b; 828 b.typed = 5; 829 char[] input = b.untyped[]; 830 int witness; 831 formattedRead(input, "%r", &witness); 832 assert(witness == b.typed); 833 } 834 835 @system pure unittest 836 { 837 union A 838 { 839 char[float.sizeof] untyped; 840 float typed; 841 } 842 A a; 843 a.typed = 5.5; 844 char[] input = a.untyped[]; 845 float witness; 846 formattedRead(input, "%r", &witness); 847 assert(witness == a.typed); 848 } 849 850 @system pure unittest 851 { 852 import std.typecons; 853 char[] line = "1 2".dup; 854 int a, b; 855 formattedRead(line, "%s %s", &a, &b); 856 assert(a == 1 && b == 2); 857 858 line = "10 2 3".dup; 859 formattedRead(line, "%d ", &a); 860 assert(a == 10); 861 assert(line == "2 3"); 862 863 Tuple!(int, float) t; 864 line = "1 2.125".dup; 865 formattedRead(line, "%d %g", &t); 866 assert(t[0] == 1 && t[1] == 2.125); 867 868 line = "1 7643 2.125".dup; 869 formattedRead(line, "%s %*u %s", &t); 870 assert(t[0] == 1 && t[1] == 2.125); 871 } 872 873 @system pure unittest 874 { 875 string line; 876 877 char c1, c2; 878 879 line = "abc"; 880 formattedRead(line, "%s%c", &c1, &c2); 881 assert(c1 == 'a' && c2 == 'b'); 882 assert(line == "c"); 883 } 884 885 @system pure unittest 886 { 887 string line; 888 889 line = "[1,2,3]"; 890 int[] s1; 891 formattedRead(line, "%s", &s1); 892 assert(s1 == [1,2,3]); 893 } 894 895 @system pure unittest 896 { 897 string line; 898 899 line = "[1,2,3]"; 900 int[] s1; 901 formattedRead(line, "[%(%s,%)]", &s1); 902 assert(s1 == [1,2,3]); 903 904 line = `["hello", "world"]`; 905 string[] s2; 906 formattedRead(line, "[%(%s, %)]", &s2); 907 assert(s2 == ["hello", "world"]); 908 909 line = "123 456"; 910 int[] s3; 911 formattedRead(line, "%(%s %)", &s3); 912 assert(s3 == [123, 456]); 913 914 line = "h,e,l,l,o; w,o,r,l,d"; 915 string[] s4; 916 formattedRead(line, "%(%(%c,%); %)", &s4); 917 assert(s4 == ["hello", "world"]); 918 } 919 920 @system pure unittest 921 { 922 string line; 923 924 int[4] sa1; 925 line = `[1,2,3,4]`; 926 formattedRead(line, "%s", &sa1); 927 assert(sa1 == [1,2,3,4]); 928 929 int[4] sa2; 930 line = `[1,2,3]`; 931 assertThrown(formattedRead(line, "%s", &sa2)); 932 933 int[4] sa3; 934 line = `[1,2,3,4,5]`; 935 assertThrown(formattedRead(line, "%s", &sa3)); 936 } 937 938 @system pure unittest 939 { 940 string input; 941 942 int[4] sa1; 943 input = `[1,2,3,4]`; 944 formattedRead(input, "[%(%s,%)]", &sa1); 945 assert(sa1 == [1,2,3,4]); 946 947 int[4] sa2; 948 input = `[1,2,3]`; 949 assertThrown!FormatException(formattedRead(input, "[%(%s,%)]", &sa2)); 950 } 951 952 @system pure unittest 953 { 954 string line; 955 956 string s1, s2; 957 958 line = "hello, world"; 959 formattedRead(line, "%s", &s1); 960 assert(s1 == "hello, world", s1); 961 962 line = "hello, world;yah"; 963 formattedRead(line, "%s;%s", &s1, &s2); 964 assert(s1 == "hello, world", s1); 965 assert(s2 == "yah", s2); 966 967 line = `['h','e','l','l','o']`; 968 string s3; 969 formattedRead(line, "[%(%s,%)]", &s3); 970 assert(s3 == "hello"); 971 972 line = `"hello"`; 973 string s4; 974 formattedRead(line, "\"%(%c%)\"", &s4); 975 assert(s4 == "hello"); 976 } 977 978 @system pure unittest 979 { 980 string line; 981 982 string[int] aa1; 983 line = `[1:"hello", 2:"world"]`; 984 formattedRead(line, "%s", &aa1); 985 assert(aa1 == [1:"hello", 2:"world"]); 986 987 int[string] aa2; 988 line = `{"hello"=1; "world"=2}`; 989 formattedRead(line, "{%(%s=%s; %)}", &aa2); 990 assert(aa2 == ["hello":1, "world":2]); 991 992 int[string] aa3; 993 line = `{[hello=1]; [world=2]}`; 994 formattedRead(line, "{%([%(%c%)=%s]%|; %)}", &aa3); 995 assert(aa3 == ["hello":1, "world":2]); 996 } 997 998 // test rvalue using 999 @system pure unittest 1000 { 1001 string[int] aa1; 1002 formattedRead!("%s")(`[1:"hello", 2:"world"]`, aa1); 1003 assert(aa1 == [1:"hello", 2:"world"]); 1004 1005 int[string] aa2; 1006 formattedRead(`{"hello"=1; "world"=2}`, "{%(%s=%s; %)}", aa2); 1007 assert(aa2 == ["hello":1, "world":2]); 1008 } 1009 1010 template FormatSpec(Char) 1011 if (!is(Unqual!Char == Char)) 1012 { 1013 alias FormatSpec = FormatSpec!(Unqual!Char); 1014 } 1015 1016 /** 1017 * A General handler for `printf` style format specifiers. Used for building more 1018 * specific formatting functions. 1019 */ 1020 struct FormatSpec(Char) 1021 if (is(Unqual!Char == Char)) 1022 { 1023 import std.algorithm.searching : startsWith; 1024 import std.ascii : isDigit, isPunctuation, isAlpha; 1025 import std.conv : parse, text, to; 1026 1027 /** 1028 Minimum _width, default `0`. 1029 */ 1030 int width = 0; 1031 1032 /** 1033 Precision. Its semantics depends on the argument type. For 1034 floating point numbers, _precision dictates the number of 1035 decimals printed. 1036 */ 1037 int precision = UNSPECIFIED; 1038 1039 /** 1040 Number of digits printed between _separators. 1041 */ 1042 int separators = UNSPECIFIED; 1043 1044 /** 1045 Set to `DYNAMIC` when the separator character is supplied at runtime. 1046 */ 1047 int separatorCharPos = UNSPECIFIED; 1048 1049 /** 1050 Character to insert between digits. 1051 */ 1052 dchar separatorChar = ','; 1053 1054 /** 1055 Special value for width and precision. `DYNAMIC` width or 1056 precision means that they were specified with `'*'` in the 1057 format string and are passed at runtime through the varargs. 1058 */ 1059 enum int DYNAMIC = int.max; 1060 1061 /** 1062 Special value for precision, meaning the format specifier 1063 contained no explicit precision. 1064 */ 1065 enum int UNSPECIFIED = DYNAMIC - 1; 1066 1067 /** 1068 The actual format specifier, `'s'` by default. 1069 */ 1070 char spec = 's'; 1071 1072 /** 1073 Index of the argument for positional parameters, from `1` to 1074 `ubyte.max`. (`0` means not used). 1075 */ 1076 ubyte indexStart; 1077 1078 /** 1079 Index of the last argument for positional parameter range, from 1080 `1` to `ubyte.max`. (`0` means not used). 1081 */ 1082 ubyte indexEnd; 1083 1084 version (StdDdoc) 1085 { 1086 /** 1087 The format specifier contained a `'-'` (`printf` 1088 compatibility). 1089 */ 1090 bool flDash; 1091 1092 /** 1093 The format specifier contained a `'0'` (`printf` 1094 compatibility). 1095 */ 1096 bool flZero; 1097 1098 /** 1099 The format specifier contained a $(D ' ') (`printf` 1100 compatibility). 1101 */ 1102 bool flSpace; 1103 1104 /** 1105 The format specifier contained a `'+'` (`printf` 1106 compatibility). 1107 */ 1108 bool flPlus; 1109 1110 /** 1111 The format specifier contained a `'#'` (`printf` 1112 compatibility). 1113 */ 1114 bool flHash; 1115 1116 /** 1117 The format specifier contained a `','` 1118 */ 1119 bool flSeparator; 1120 1121 // Fake field to allow compilation 1122 ubyte allFlags; 1123 } 1124 else 1125 { 1126 union 1127 { 1128 import std.bitmanip : bitfields; 1129 mixin(bitfields!( 1130 bool, "flDash", 1, 1131 bool, "flZero", 1, 1132 bool, "flSpace", 1, 1133 bool, "flPlus", 1, 1134 bool, "flHash", 1, 1135 bool, "flSeparator", 1, 1136 ubyte, "", 2)); 1137 ubyte allFlags; 1138 } 1139 } 1140 1141 /** 1142 In case of a compound format specifier starting with $(D 1143 "%$(LPAREN)") and ending with `"%$(RPAREN)"`, `_nested` 1144 contains the string contained within the two separators. 1145 */ 1146 const(Char)[] nested; 1147 1148 /** 1149 In case of a compound format specifier, `_sep` contains the 1150 string positioning after `"%|"`. 1151 `sep is null` means no separator else `sep.empty` means 0 length 1152 separator. 1153 */ 1154 const(Char)[] sep; 1155 1156 /** 1157 `_trailing` contains the rest of the format string. 1158 */ 1159 const(Char)[] trailing; 1160 1161 /* 1162 This string is inserted before each sequence (e.g. array) 1163 formatted (by default `"["`). 1164 */ 1165 enum immutable(Char)[] seqBefore = "["; 1166 1167 /* 1168 This string is inserted after each sequence formatted (by 1169 default `"]"`). 1170 */ 1171 enum immutable(Char)[] seqAfter = "]"; 1172 1173 /* 1174 This string is inserted after each element keys of a sequence (by 1175 default `":"`). 1176 */ 1177 enum immutable(Char)[] keySeparator = ":"; 1178 1179 /* 1180 This string is inserted in between elements of a sequence (by 1181 default $(D ", ")). 1182 */ 1183 enum immutable(Char)[] seqSeparator = ", "; 1184 1185 /** 1186 Construct a new `FormatSpec` using the format string `fmt`, no 1187 processing is done until needed. 1188 */ 1189 this(in Char[] fmt) @safe pure 1190 { 1191 trailing = fmt; 1192 } 1193 1194 /** 1195 Write the format string to an output range until the next format 1196 specifier is found and parse that format specifier. 1197 1198 See $(LREF FormatSpec) for an example, how to use `writeUpToNextSpec`. 1199 1200 Params: 1201 writer = the $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) 1202 1203 Returns: 1204 True, when a format specifier is found. 1205 1206 Throws: 1207 A $(LREF FormatException) when the found format specifier 1208 could not be parsed. 1209 */ 1210 bool writeUpToNextSpec(OutputRange)(ref OutputRange writer) scope 1211 { 1212 if (trailing.empty) 1213 return false; 1214 for (size_t i = 0; i < trailing.length; ++i) 1215 { 1216 if (trailing[i] != '%') continue; 1217 put(writer, trailing[0 .. i]); 1218 trailing = trailing[i .. $]; 1219 enforceFmt(trailing.length >= 2, `Unterminated format specifier: "%"`); 1220 trailing = trailing[1 .. $]; 1221 1222 if (trailing[0] != '%') 1223 { 1224 // Spec found. Fill up the spec, and bailout 1225 fillUp(); 1226 return true; 1227 } 1228 // Doubled! Reset and Keep going 1229 i = 0; 1230 } 1231 // no format spec found 1232 put(writer, trailing); 1233 trailing = null; 1234 return false; 1235 } 1236 1237 private void fillUp() scope 1238 { 1239 // Reset content 1240 if (__ctfe) 1241 { 1242 flDash = false; 1243 flZero = false; 1244 flSpace = false; 1245 flPlus = false; 1246 flHash = false; 1247 flSeparator = false; 1248 } 1249 else 1250 { 1251 allFlags = 0; 1252 } 1253 1254 width = 0; 1255 precision = UNSPECIFIED; 1256 nested = null; 1257 // Parse the spec (we assume we're past '%' already) 1258 for (size_t i = 0; i < trailing.length; ) 1259 { 1260 switch (trailing[i]) 1261 { 1262 case '(': 1263 // Embedded format specifier. 1264 auto j = i + 1; 1265 // Get the matching balanced paren 1266 for (uint innerParens;;) 1267 { 1268 enforceFmt(j + 1 < trailing.length, 1269 text("Incorrect format specifier: %", trailing[i .. $])); 1270 if (trailing[j++] != '%') 1271 { 1272 // skip, we're waiting for %( and %) 1273 continue; 1274 } 1275 if (trailing[j] == '-') // for %-( 1276 { 1277 ++j; // skip 1278 enforceFmt(j < trailing.length, 1279 text("Incorrect format specifier: %", trailing[i .. $])); 1280 } 1281 if (trailing[j] == ')') 1282 { 1283 if (innerParens-- == 0) break; 1284 } 1285 else if (trailing[j] == '|') 1286 { 1287 if (innerParens == 0) break; 1288 } 1289 else if (trailing[j] == '(') 1290 { 1291 ++innerParens; 1292 } 1293 } 1294 if (trailing[j] == '|') 1295 { 1296 auto k = j; 1297 for (++j;;) 1298 { 1299 if (trailing[j++] != '%') 1300 continue; 1301 if (trailing[j] == '%') 1302 ++j; 1303 else if (trailing[j] == ')') 1304 break; 1305 else 1306 throw new FormatException( 1307 text("Incorrect format specifier: %", 1308 trailing[j .. $])); 1309 } 1310 nested = trailing[i + 1 .. k - 1]; 1311 sep = trailing[k + 1 .. j - 1]; 1312 } 1313 else 1314 { 1315 nested = trailing[i + 1 .. j - 1]; 1316 sep = null; // no separator 1317 } 1318 //this = FormatSpec(innerTrailingSpec); 1319 spec = '('; 1320 // We practically found the format specifier 1321 trailing = trailing[j + 1 .. $]; 1322 return; 1323 case '-': flDash = true; ++i; break; 1324 case '+': flPlus = true; ++i; break; 1325 case '#': flHash = true; ++i; break; 1326 case '0': flZero = true; ++i; break; 1327 case ' ': flSpace = true; ++i; break; 1328 case '*': 1329 if (isDigit(trailing[++i])) 1330 { 1331 // a '*' followed by digits and '$' is a 1332 // positional format 1333 trailing = trailing[1 .. $]; 1334 width = -parse!(typeof(width))(trailing); 1335 i = 0; 1336 enforceFmt(trailing[i++] == '$', 1337 "$ expected"); 1338 } 1339 else 1340 { 1341 // read result 1342 width = DYNAMIC; 1343 } 1344 break; 1345 case '1': .. case '9': 1346 auto tmp = trailing[i .. $]; 1347 const widthOrArgIndex = parse!uint(tmp); 1348 enforceFmt(tmp.length, 1349 text("Incorrect format specifier %", trailing[i .. $])); 1350 i = arrayPtrDiff(tmp, trailing); 1351 if (tmp.startsWith('$')) 1352 { 1353 // index of the form %n$ 1354 indexEnd = indexStart = to!ubyte(widthOrArgIndex); 1355 ++i; 1356 } 1357 else if (tmp.startsWith(':')) 1358 { 1359 // two indexes of the form %m:n$, or one index of the form %m:$ 1360 indexStart = to!ubyte(widthOrArgIndex); 1361 tmp = tmp[1 .. $]; 1362 if (tmp.startsWith('$')) 1363 { 1364 indexEnd = indexEnd.max; 1365 } 1366 else 1367 { 1368 indexEnd = parse!(typeof(indexEnd))(tmp); 1369 } 1370 i = arrayPtrDiff(tmp, trailing); 1371 enforceFmt(trailing[i++] == '$', 1372 "$ expected"); 1373 } 1374 else 1375 { 1376 // width 1377 width = to!int(widthOrArgIndex); 1378 } 1379 break; 1380 case ',': 1381 // Precision 1382 ++i; 1383 flSeparator = true; 1384 1385 if (trailing[i] == '*') 1386 { 1387 ++i; 1388 // read result 1389 separators = DYNAMIC; 1390 } 1391 else if (isDigit(trailing[i])) 1392 { 1393 auto tmp = trailing[i .. $]; 1394 separators = parse!int(tmp); 1395 i = arrayPtrDiff(tmp, trailing); 1396 } 1397 else 1398 { 1399 // "," was specified, but nothing after it 1400 separators = 3; 1401 } 1402 1403 if (trailing[i] == '?') 1404 { 1405 separatorCharPos = DYNAMIC; 1406 ++i; 1407 } 1408 1409 break; 1410 case '.': 1411 // Precision 1412 if (trailing[++i] == '*') 1413 { 1414 if (isDigit(trailing[++i])) 1415 { 1416 // a '.*' followed by digits and '$' is a 1417 // positional precision 1418 trailing = trailing[i .. $]; 1419 i = 0; 1420 precision = -parse!int(trailing); 1421 enforceFmt(trailing[i++] == '$', 1422 "$ expected"); 1423 } 1424 else 1425 { 1426 // read result 1427 precision = DYNAMIC; 1428 } 1429 } 1430 else if (trailing[i] == '-') 1431 { 1432 // negative precision, as good as 0 1433 precision = 0; 1434 auto tmp = trailing[i .. $]; 1435 parse!int(tmp); // skip digits 1436 i = arrayPtrDiff(tmp, trailing); 1437 } 1438 else if (isDigit(trailing[i])) 1439 { 1440 auto tmp = trailing[i .. $]; 1441 precision = parse!int(tmp); 1442 i = arrayPtrDiff(tmp, trailing); 1443 } 1444 else 1445 { 1446 // "." was specified, but nothing after it 1447 precision = 0; 1448 } 1449 break; 1450 default: 1451 // this is the format char 1452 spec = cast(char) trailing[i++]; 1453 trailing = trailing[i .. $]; 1454 return; 1455 } // end switch 1456 } // end for 1457 throw new FormatException(text("Incorrect format specifier: ", trailing)); 1458 } 1459 1460 //-------------------------------------------------------------------------- 1461 private bool readUpToNextSpec(R)(ref R r) scope 1462 { 1463 import std.ascii : isLower, isWhite; 1464 import std.utf : stride; 1465 1466 // Reset content 1467 if (__ctfe) 1468 { 1469 flDash = false; 1470 flZero = false; 1471 flSpace = false; 1472 flPlus = false; 1473 flHash = false; 1474 flSeparator = false; 1475 } 1476 else 1477 { 1478 allFlags = 0; 1479 } 1480 width = 0; 1481 precision = UNSPECIFIED; 1482 nested = null; 1483 // Parse the spec 1484 while (trailing.length) 1485 { 1486 const c = trailing[0]; 1487 if (c == '%' && trailing.length > 1) 1488 { 1489 const c2 = trailing[1]; 1490 if (c2 == '%') 1491 { 1492 assert(!r.empty, "Required at least one more input"); 1493 // Require a '%' 1494 if (r.front != '%') break; 1495 trailing = trailing[2 .. $]; 1496 r.popFront(); 1497 } 1498 else 1499 { 1500 enforceFmt(isLower(c2) || c2 == '*' || 1501 c2 == '(', 1502 text("'%", c2, 1503 "' not supported with formatted read")); 1504 trailing = trailing[1 .. $]; 1505 fillUp(); 1506 return true; 1507 } 1508 } 1509 else 1510 { 1511 if (c == ' ') 1512 { 1513 while (!r.empty && isWhite(r.front)) r.popFront(); 1514 //r = std.algorithm.find!(not!(isWhite))(r); 1515 } 1516 else 1517 { 1518 enforceFmt(!r.empty, 1519 text("parseToFormatSpec: Cannot find character '", 1520 c, "' in the input string.")); 1521 if (r.front != trailing.front) break; 1522 r.popFront(); 1523 } 1524 trailing = trailing[stride(trailing, 0) .. $]; 1525 } 1526 } 1527 return false; 1528 } 1529 1530 private string getCurFmtStr() const 1531 { 1532 import std.array : appender; 1533 auto w = appender!string(); 1534 auto f = FormatSpec!Char("%s"); // for stringnize 1535 1536 put(w, '%'); 1537 if (indexStart != 0) 1538 { 1539 formatValue(w, indexStart, f); 1540 put(w, '$'); 1541 } 1542 if (flDash) put(w, '-'); 1543 if (flZero) put(w, '0'); 1544 if (flSpace) put(w, ' '); 1545 if (flPlus) put(w, '+'); 1546 if (flHash) put(w, '#'); 1547 if (flSeparator) put(w, ','); 1548 if (width != 0) 1549 formatValue(w, width, f); 1550 if (precision != FormatSpec!Char.UNSPECIFIED) 1551 { 1552 put(w, '.'); 1553 formatValue(w, precision, f); 1554 } 1555 put(w, spec); 1556 return w.data; 1557 } 1558 1559 private const(Char)[] headUpToNextSpec() 1560 { 1561 import std.array : appender; 1562 auto w = appender!(typeof(return))(); 1563 auto tr = trailing; 1564 1565 while (tr.length) 1566 { 1567 if (tr[0] == '%') 1568 { 1569 if (tr.length > 1 && tr[1] == '%') 1570 { 1571 tr = tr[2 .. $]; 1572 w.put('%'); 1573 } 1574 else 1575 break; 1576 } 1577 else 1578 { 1579 w.put(tr.front); 1580 tr.popFront(); 1581 } 1582 } 1583 return w.data; 1584 } 1585 1586 /** 1587 * Gives a string containing all of the member variables on their own 1588 * line. 1589 * 1590 * Params: 1591 * writer = A `char` accepting 1592 * $(REF_ALTTEXT output range, isOutputRange, std, range, primitives) 1593 * Returns: 1594 * A `string` when not using an output range; `void` otherwise. 1595 */ 1596 string toString() const @safe pure 1597 { 1598 import std.array : appender; 1599 auto app = appender!string(); 1600 app.reserve(200 + trailing.length); 1601 toString(app); 1602 return app.data; 1603 } 1604 1605 /// ditto 1606 void toString(OutputRange)(ref OutputRange writer) const 1607 if (isOutputRange!(OutputRange, char)) 1608 { 1609 auto s = singleSpec("%s"); 1610 1611 put(writer, "address = "); 1612 formatValue(writer, &this, s); 1613 put(writer, "\nwidth = "); 1614 formatValue(writer, width, s); 1615 put(writer, "\nprecision = "); 1616 formatValue(writer, precision, s); 1617 put(writer, "\nspec = "); 1618 formatValue(writer, spec, s); 1619 put(writer, "\nindexStart = "); 1620 formatValue(writer, indexStart, s); 1621 put(writer, "\nindexEnd = "); 1622 formatValue(writer, indexEnd, s); 1623 put(writer, "\nflDash = "); 1624 formatValue(writer, flDash, s); 1625 put(writer, "\nflZero = "); 1626 formatValue(writer, flZero, s); 1627 put(writer, "\nflSpace = "); 1628 formatValue(writer, flSpace, s); 1629 put(writer, "\nflPlus = "); 1630 formatValue(writer, flPlus, s); 1631 put(writer, "\nflHash = "); 1632 formatValue(writer, flHash, s); 1633 put(writer, "\nflSeparator = "); 1634 formatValue(writer, flSeparator, s); 1635 put(writer, "\nnested = "); 1636 formatValue(writer, nested, s); 1637 put(writer, "\ntrailing = "); 1638 formatValue(writer, trailing, s); 1639 put(writer, '\n'); 1640 } 1641 } 1642 1643 @safe unittest 1644 { 1645 import std.array; 1646 import std.conv : text; 1647 auto w = appender!(char[])(); 1648 auto f = FormatSpec!char("abc%sdef%sghi"); 1649 f.writeUpToNextSpec(w); 1650 assert(w.data == "abc", w.data); 1651 assert(f.trailing == "def%sghi", text(f.trailing)); 1652 f.writeUpToNextSpec(w); 1653 assert(w.data == "abcdef", w.data); 1654 assert(f.trailing == "ghi"); 1655 // test with embedded %%s 1656 f = FormatSpec!char("ab%%cd%%ef%sg%%h%sij"); 1657 w.clear(); 1658 f.writeUpToNextSpec(w); 1659 assert(w.data == "ab%cd%ef" && f.trailing == "g%%h%sij", w.data); 1660 f.writeUpToNextSpec(w); 1661 assert(w.data == "ab%cd%efg%h" && f.trailing == "ij"); 1662 // https://issues.dlang.org/show_bug.cgi?id=4775 1663 f = FormatSpec!char("%%%s"); 1664 w.clear(); 1665 f.writeUpToNextSpec(w); 1666 assert(w.data == "%" && f.trailing == ""); 1667 f = FormatSpec!char("%%%%%s%%"); 1668 w.clear(); 1669 while (f.writeUpToNextSpec(w)) continue; 1670 assert(w.data == "%%%"); 1671 1672 f = FormatSpec!char("a%%b%%c%"); 1673 w.clear(); 1674 assertThrown!FormatException(f.writeUpToNextSpec(w)); 1675 assert(w.data == "a%b%c" && f.trailing == "%"); 1676 } 1677 1678 // https://issues.dlang.org/show_bug.cgi?id=5237 1679 @safe unittest 1680 { 1681 import std.array; 1682 auto w = appender!string(); 1683 auto f = FormatSpec!char("%.16f"); 1684 f.writeUpToNextSpec(w); // dummy eating 1685 assert(f.spec == 'f'); 1686 auto fmt = f.getCurFmtStr(); 1687 assert(fmt == "%.16f"); 1688 } 1689 1690 /// 1691 @safe pure unittest 1692 { 1693 import std.array; 1694 auto a = appender!(string)(); 1695 auto fmt = "Number: %6.4e\nString: %s"; 1696 auto f = FormatSpec!char(fmt); 1697 1698 assert(f.writeUpToNextSpec(a) == true); 1699 1700 assert(a.data == "Number: "); 1701 assert(f.trailing == "\nString: %s"); 1702 assert(f.spec == 'e'); 1703 assert(f.width == 6); 1704 assert(f.precision == 4); 1705 1706 assert(f.writeUpToNextSpec(a) == true); 1707 1708 assert(a.data == "Number: \nString: "); 1709 assert(f.trailing == ""); 1710 assert(f.spec == 's'); 1711 1712 assert(f.writeUpToNextSpec(a) == false); 1713 assert(a.data == "Number: \nString: "); 1714 } 1715 1716 // https://issues.dlang.org/show_bug.cgi?id=14059 1717 @safe unittest 1718 { 1719 import std.array : appender; 1720 auto a = appender!(string)(); 1721 1722 auto f = FormatSpec!char("%-(%s%"); // %)") 1723 assertThrown!FormatException(f.writeUpToNextSpec(a)); 1724 1725 f = FormatSpec!char("%(%-"); // %)") 1726 assertThrown!FormatException(f.writeUpToNextSpec(a)); 1727 } 1728 1729 @safe unittest 1730 { 1731 import std.array : appender; 1732 auto a = appender!(string)(); 1733 1734 auto f = FormatSpec!char("%,d"); 1735 f.writeUpToNextSpec(a); 1736 1737 assert(f.spec == 'd', format("%s", f.spec)); 1738 assert(f.precision == FormatSpec!char.UNSPECIFIED); 1739 assert(f.separators == 3); 1740 1741 f = FormatSpec!char("%5,10f"); 1742 f.writeUpToNextSpec(a); 1743 assert(f.spec == 'f', format("%s", f.spec)); 1744 assert(f.separators == 10); 1745 assert(f.width == 5); 1746 1747 f = FormatSpec!char("%5,10.4f"); 1748 f.writeUpToNextSpec(a); 1749 assert(f.spec == 'f', format("%s", f.spec)); 1750 assert(f.separators == 10); 1751 assert(f.width == 5); 1752 assert(f.precision == 4); 1753 } 1754 1755 @safe pure unittest 1756 { 1757 import std.algorithm.searching : canFind, findSplitBefore; 1758 auto expected = "width = 2" ~ 1759 "\nprecision = 5" ~ 1760 "\nspec = f" ~ 1761 "\nindexStart = 0" ~ 1762 "\nindexEnd = 0" ~ 1763 "\nflDash = false" ~ 1764 "\nflZero = false" ~ 1765 "\nflSpace = false" ~ 1766 "\nflPlus = false" ~ 1767 "\nflHash = false" ~ 1768 "\nflSeparator = false" ~ 1769 "\nnested = " ~ 1770 "\ntrailing = \n"; 1771 auto spec = singleSpec("%2.5f"); 1772 auto res = spec.toString(); 1773 // make sure the address exists, then skip it 1774 assert(res.canFind("address")); 1775 assert(res.findSplitBefore("width")[1] == expected); 1776 } 1777 1778 /** 1779 Helper function that returns a `FormatSpec` for a single specifier given 1780 in `fmt`. 1781 1782 Params: 1783 fmt = A format specifier. 1784 1785 Returns: 1786 A `FormatSpec` with the specifier parsed. 1787 Throws: 1788 A `FormatException` when more than one specifier is given or the specifier 1789 is malformed. 1790 */ 1791 FormatSpec!Char singleSpec(Char)(Char[] fmt) 1792 { 1793 import std.conv : text; 1794 enforceFmt(fmt.length >= 2, "fmt must be at least 2 characters long"); 1795 enforceFmt(fmt.front == '%', "fmt must start with a '%' character"); 1796 1797 static struct DummyOutputRange { 1798 void put(C)(scope const C[] buf) {} // eat elements 1799 } 1800 auto a = DummyOutputRange(); 1801 auto spec = FormatSpec!Char(fmt); 1802 //dummy write 1803 spec.writeUpToNextSpec(a); 1804 1805 enforceFmt(spec.trailing.empty, 1806 text("Trailing characters in fmt string: '", spec.trailing)); 1807 1808 return spec; 1809 } 1810 1811 /// 1812 @safe pure unittest 1813 { 1814 import std.exception : assertThrown; 1815 auto spec = singleSpec("%2.3e"); 1816 1817 assert(spec.trailing == ""); 1818 assert(spec.spec == 'e'); 1819 assert(spec.width == 2); 1820 assert(spec.precision == 3); 1821 1822 assertThrown!FormatException(singleSpec("")); 1823 assertThrown!FormatException(singleSpec("2.3e")); 1824 assertThrown!FormatException(singleSpec("%2.3eTest")); 1825 } 1826 1827 /** 1828 * Formats any value into `Char` accepting `OutputRange`, using the given `FormatSpec`. 1829 * 1830 * Aggregates: 1831 * `struct`, `union`, `class`, and `interface` are formatted by calling `toString`. 1832 * 1833 * `toString` should have one of the following signatures: 1834 * 1835 * --- 1836 * void toString(W)(ref W w, scope const ref FormatSpec fmt) 1837 * void toString(W)(ref W w) 1838 * string toString(); 1839 * --- 1840 * 1841 * Where `W` is an $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) 1842 * which accepts characters. The template type does not have to be called `W`. 1843 * 1844 * The following overloads are also accepted for legacy reasons or for use in virtual 1845 * functions. It's recommended that any new code forgo these overloads if possible for 1846 * speed and attribute acceptance reasons. 1847 * 1848 * --- 1849 * void toString(scope void delegate(const(char)[]) sink, const ref FormatSpec fmt); 1850 * void toString(scope void delegate(const(char)[]) sink, string fmt); 1851 * void toString(scope void delegate(const(char)[]) sink); 1852 * --- 1853 * 1854 * For the class objects which have input range interface, 1855 * $(UL 1856 * $(LI If the instance `toString` has overridden `Object.toString`, it is used.) 1857 * $(LI Otherwise, the objects are formatted as input range.) 1858 * ) 1859 * 1860 * For the `struct` and `union` objects which does not have `toString`, 1861 * $(UL 1862 * $(LI If they have range interface, formatted as input range.) 1863 * $(LI Otherwise, they are formatted like `Type(field1, filed2, ...)`.) 1864 * ) 1865 * 1866 * Otherwise, are formatted just as their type name. 1867 * 1868 * Params: 1869 * w = The $(REF_ALTTEXT output range, isOutputRange, std,range,primitives) to write to. 1870 * val = The value to write. 1871 * f = The $(REF FormatSpec, std, format) defining how to write the value. 1872 */ 1873 void formatValue(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f) 1874 { 1875 formatValueImpl(w, val, f); 1876 } 1877 1878 /++ 1879 The following code compares the use of `formatValue` and `formattedWrite`. 1880 +/ 1881 @safe pure unittest 1882 { 1883 import std.array : appender; 1884 1885 auto writer1 = appender!string(); 1886 writer1.formattedWrite("%08b", 42); 1887 1888 auto writer2 = appender!string(); 1889 auto f = singleSpec("%08b"); 1890 writer2.formatValue(42, f); 1891 1892 assert(writer1.data == writer2.data && writer1.data == "00101010"); 1893 } 1894 1895 /** 1896 * `bool`s are formatted as `"true"` or `"false"` with `%s` and as `1` or 1897 * `0` with integral-specific format specs. 1898 */ 1899 @safe pure unittest 1900 { 1901 import std.array : appender; 1902 auto w = appender!string(); 1903 auto spec = singleSpec("%s"); 1904 formatValue(w, true, spec); 1905 1906 assert(w.data == "true"); 1907 } 1908 1909 /// `null` literal is formatted as `"null"`. 1910 @safe pure unittest 1911 { 1912 import std.array : appender; 1913 auto w = appender!string(); 1914 auto spec = singleSpec("%s"); 1915 formatValue(w, null, spec); 1916 1917 assert(w.data == "null"); 1918 } 1919 1920 /// Integrals are formatted like $(REF printf, core, stdc, stdio). 1921 @safe pure unittest 1922 { 1923 import std.array : appender; 1924 auto w = appender!string(); 1925 auto spec = singleSpec("%d"); 1926 formatValue(w, 1337, spec); 1927 1928 assert(w.data == "1337"); 1929 } 1930 1931 /// Floating-point values are formatted like $(REF printf, core, stdc, stdio) 1932 @safe unittest 1933 { 1934 import std.array : appender; 1935 auto w = appender!string(); 1936 auto spec = singleSpec("%.1f"); 1937 formatValue(w, 1337.7, spec); 1938 1939 assert(w.data == "1337.7"); 1940 } 1941 1942 /** 1943 * Individual characters (`char, `wchar`, or `dchar`) are formatted as 1944 * Unicode characters with `%s` and as integers with integral-specific format 1945 * specs. 1946 */ 1947 @safe pure unittest 1948 { 1949 import std.array : appender; 1950 auto w = appender!string(); 1951 auto spec = singleSpec("%c"); 1952 formatValue(w, 'a', spec); 1953 1954 assert(w.data == "a"); 1955 } 1956 1957 /// Strings are formatted like $(REF printf, core, stdc, stdio) 1958 @safe pure unittest 1959 { 1960 import std.array : appender; 1961 auto w = appender!string(); 1962 auto spec = singleSpec("%s"); 1963 formatValue(w, "hello", spec); 1964 1965 assert(w.data == "hello"); 1966 } 1967 1968 /// Static-size arrays are formatted as dynamic arrays. 1969 @safe pure unittest 1970 { 1971 import std.array : appender; 1972 auto w = appender!string(); 1973 auto spec = singleSpec("%s"); 1974 char[2] two = ['a', 'b']; 1975 formatValue(w, two, spec); 1976 1977 assert(w.data == "ab"); 1978 } 1979 1980 /** 1981 * Dynamic arrays are formatted as input ranges. 1982 * 1983 * Specializations: 1984 * $(UL 1985 * $(LI `void[]` is formatted like `ubyte[]`.) 1986 * $(LI Const array is converted to input range by removing its qualifier.) 1987 * ) 1988 */ 1989 @safe pure unittest 1990 { 1991 import std.array : appender; 1992 auto w = appender!string(); 1993 auto spec = singleSpec("%s"); 1994 auto two = [1, 2]; 1995 formatValue(w, two, spec); 1996 1997 assert(w.data == "[1, 2]"); 1998 } 1999 2000 /** 2001 * Associative arrays are formatted by using `':'` and `", "` as 2002 * separators, and enclosed by `'['` and `']'`. 2003 */ 2004 @safe pure unittest 2005 { 2006 import std.array : appender; 2007 auto w = appender!string(); 2008 auto spec = singleSpec("%s"); 2009 auto aa = ["H":"W"]; 2010 formatValue(w, aa, spec); 2011 2012 assert(w.data == "[\"H\":\"W\"]", w.data); 2013 } 2014 2015 /// `enum`s are formatted like their base value 2016 @safe pure unittest 2017 { 2018 import std.array : appender; 2019 auto w = appender!string(); 2020 auto spec = singleSpec("%s"); 2021 2022 enum A { first, second, third } 2023 2024 formatValue(w, A.second, spec); 2025 2026 assert(w.data == "second"); 2027 } 2028 2029 /** 2030 * Formatting a struct by defining a method `toString`, which takes an output 2031 * range. 2032 * 2033 * It's recommended that any `toString` using $(REF_ALTTEXT output ranges, isOutputRange, std,range,primitives) 2034 * use $(REF put, std,range,primitives) rather than use the `put` method of the range 2035 * directly. 2036 */ 2037 @safe unittest 2038 { 2039 import std.array : appender; 2040 import std.range.primitives; 2041 2042 static struct Point 2043 { 2044 int x, y; 2045 2046 void toString(W)(ref W writer, scope const ref FormatSpec!char f) 2047 if (isOutputRange!(W, char)) 2048 { 2049 // std.range.primitives.put 2050 put(writer, "("); 2051 formatValue(writer, x, f); 2052 put(writer, ","); 2053 formatValue(writer, y, f); 2054 put(writer, ")"); 2055 } 2056 } 2057 2058 auto w = appender!string(); 2059 auto spec = singleSpec("%s"); 2060 auto p = Point(16, 11); 2061 2062 formatValue(w, p, spec); 2063 assert(w.data == "(16,11)"); 2064 } 2065 2066 /** 2067 * Another example of formatting a `struct` with a defined `toString`, 2068 * this time using the `scope delegate` method. 2069 * 2070 * $(RED This method is now discouraged for non-virtual functions). 2071 * If possible, please use the output range method instead. 2072 */ 2073 @safe unittest 2074 { 2075 static struct Point 2076 { 2077 int x, y; 2078 2079 void toString(scope void delegate(scope const(char)[]) @safe sink, 2080 scope const FormatSpec!char fmt) const 2081 { 2082 sink("("); 2083 sink.formatValue(x, fmt); 2084 sink(","); 2085 sink.formatValue(y, fmt); 2086 sink(")"); 2087 } 2088 } 2089 2090 auto p = Point(16,11); 2091 assert(format("%03d", p) == "(016,011)"); 2092 assert(format("%02x", p) == "(10,0b)"); 2093 } 2094 2095 /// Pointers are formatted as hex integers. 2096 @system pure unittest 2097 { 2098 import std.array : appender; 2099 auto w = appender!string(); 2100 auto spec = singleSpec("%s"); 2101 2102 auto q = cast(void*) 0xFFEECCAA; 2103 formatValue(w, q, spec); 2104 2105 assert(w.data == "FFEECCAA"); 2106 } 2107 2108 /// SIMD vectors are formatted as arrays. 2109 @safe unittest 2110 { 2111 import core.simd; 2112 import std.array : appender; 2113 auto w = appender!string(); 2114 auto spec = singleSpec("%s"); 2115 2116 static if (is(float4)) 2117 { 2118 version (X86) {} 2119 else 2120 { 2121 float4 f4; 2122 f4.array[0] = 1; 2123 f4.array[1] = 2; 2124 f4.array[2] = 3; 2125 f4.array[3] = 4; 2126 2127 formatValue(w, f4, spec); 2128 assert(w.data == "[1, 2, 3, 4]"); 2129 } 2130 } 2131 } 2132 2133 /// Delegates are formatted by `ReturnType delegate(Parameters) FunctionAttributes` 2134 @safe unittest 2135 { 2136 import std.conv : to; 2137 2138 int i; 2139 2140 int foo(short k) @nogc 2141 { 2142 return i + k; 2143 } 2144 2145 @system int delegate(short) @nogc bar() nothrow pure 2146 { 2147 int* p = new int(1); 2148 i = *p; 2149 return &foo; 2150 } 2151 2152 assert(to!string(&bar) == "int delegate(short) @nogc delegate() pure nothrow @system"); 2153 assert(() @trusted { return bar()(3); }() == 4); 2154 } 2155 2156 /* 2157 `bool`s are formatted as `"true"` or `"false"` with `%s` and as `1` or 2158 `0` with integral-specific format specs. 2159 */ 2160 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) 2161 if (is(BooleanTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 2162 { 2163 BooleanTypeOf!T val = obj; 2164 2165 if (f.spec == 's') 2166 writeAligned(w, val ? "true" : "false", f); 2167 else 2168 formatValueImpl(w, cast(int) val, f); 2169 } 2170 2171 @safe pure unittest 2172 { 2173 assertCTFEable!( 2174 { 2175 formatTest( false, "false" ); 2176 formatTest( true, "true" ); 2177 }); 2178 } 2179 @system unittest 2180 { 2181 class C1 { bool val; alias val this; this(bool v){ val = v; } } 2182 class C2 { bool val; alias val this; this(bool v){ val = v; } 2183 override string toString() const { return "C"; } } 2184 formatTest( new C1(false), "false" ); 2185 formatTest( new C1(true), "true" ); 2186 formatTest( new C2(false), "C" ); 2187 formatTest( new C2(true), "C" ); 2188 2189 struct S1 { bool val; alias val this; } 2190 struct S2 { bool val; alias val this; 2191 string toString() const { return "S"; } } 2192 formatTest( S1(false), "false" ); 2193 formatTest( S1(true), "true" ); 2194 formatTest( S2(false), "S" ); 2195 formatTest( S2(true), "S" ); 2196 } 2197 2198 @safe pure unittest 2199 { 2200 string t1 = format("[%6s] [%6s] [%-6s]", true, false, true); 2201 assert(t1 == "[ true] [ false] [true ]"); 2202 2203 string t2 = format("[%3s] [%-2s]", true, false); 2204 assert(t2 == "[true] [false]"); 2205 } 2206 2207 /* 2208 `null` literal is formatted as `"null"` 2209 */ 2210 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) 2211 if (is(immutable T == immutable typeof(null)) && !is(T == enum) && !hasToString!(T, Char)) 2212 { 2213 const spec = f.spec; 2214 enforceFmt(spec == 's', 2215 "null literal cannot match %" ~ spec); 2216 2217 writeAligned(w, "null", f); 2218 } 2219 2220 @safe pure unittest 2221 { 2222 assert(collectExceptionMsg!FormatException(format("%p", null)).back == 'p'); 2223 2224 assertCTFEable!( 2225 { 2226 formatTest( null, "null" ); 2227 }); 2228 } 2229 2230 @safe pure unittest 2231 { 2232 string t = format("[%6s] [%-6s]", null, null); 2233 assert(t == "[ null] [null ]"); 2234 } 2235 2236 /* 2237 Integrals are formatted like $(REF printf, core, stdc, stdio). 2238 */ 2239 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) 2240 if (is(IntegralTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 2241 { 2242 alias U = IntegralTypeOf!T; 2243 U val = obj; // Extracting alias this may be impure/system/may-throw 2244 2245 const spec = f.spec; 2246 if (spec == 'r') 2247 { 2248 // raw write, skip all else and write the thing 2249 auto raw = (ref val)@trusted{ 2250 return (cast(const char*) &val)[0 .. val.sizeof]; 2251 }(val); 2252 if (needToSwapEndianess(f)) 2253 { 2254 foreach_reverse (c; raw) 2255 put(w, c); 2256 } 2257 else 2258 { 2259 foreach (c; raw) 2260 put(w, c); 2261 } 2262 return; 2263 } 2264 2265 immutable uint base = 2266 spec == 'x' || spec == 'X' ? 16 : 2267 spec == 'o' ? 8 : 2268 spec == 'b' ? 2 : 2269 spec == 's' || spec == 'd' || spec == 'u' ? 10 : 2270 0; 2271 enforceFmt(base > 0, 2272 "incompatible format character for integral argument: %" ~ spec); 2273 2274 // Forward on to formatIntegral to handle both U and const(U) 2275 // Saves duplication of code for both versions. 2276 static if (is(ucent) && (is(U == cent) || is(U == ucent))) 2277 alias C = U; 2278 else static if (isSigned!U) 2279 alias C = long; 2280 else 2281 alias C = ulong; 2282 formatIntegral(w, cast(C) val, f, base, Unsigned!U.max); 2283 } 2284 2285 private void formatIntegral(Writer, T, Char)(ref Writer w, const(T) val, scope const ref FormatSpec!Char fs, 2286 uint base, ulong mask) 2287 { 2288 T arg = val; 2289 2290 immutable negative = (base == 10 && arg < 0); 2291 if (negative) 2292 { 2293 arg = -arg; 2294 } 2295 2296 // All unsigned integral types should fit in ulong. 2297 static if (is(ucent) && is(typeof(arg) == ucent)) 2298 formatUnsigned(w, (cast(ucent) arg) & mask, fs, base, negative); 2299 else 2300 formatUnsigned(w, (cast(ulong) arg) & mask, fs, base, negative); 2301 } 2302 2303 private void formatUnsigned(Writer, T, Char) 2304 (ref Writer w, T arg, scope const ref FormatSpec!Char fs, uint base, bool negative) 2305 { 2306 /* Write string: 2307 * leftpad prefix1 prefix2 zerofill digits rightpad 2308 */ 2309 2310 /* Convert arg to digits[]. 2311 * Note that 0 becomes an empty digits[] 2312 */ 2313 char[64] buffer = void; // 64 bits in base 2 at most 2314 char[] digits; 2315 if (arg < base && base <= 10 && arg) 2316 { 2317 // Most numbers are a single digit - avoid expensive divide 2318 buffer[0] = cast(char)(arg + '0'); 2319 digits = buffer[0 .. 1]; 2320 } 2321 else 2322 { 2323 size_t i = buffer.length; 2324 while (arg) 2325 { 2326 --i; 2327 char c = cast(char) (arg % base); 2328 arg /= base; 2329 if (c < 10) 2330 buffer[i] = cast(char)(c + '0'); 2331 else 2332 buffer[i] = cast(char)(c + (fs.spec == 'x' ? 'a' - 10 : 'A' - 10)); 2333 } 2334 digits = buffer[i .. $]; // got the digits without the sign 2335 } 2336 2337 2338 immutable precision = (fs.precision == fs.UNSPECIFIED) ? 1 : fs.precision; 2339 2340 char padChar = 0; 2341 if (!fs.flDash) 2342 { 2343 padChar = (fs.flZero && fs.precision == fs.UNSPECIFIED) ? '0' : ' '; 2344 } 2345 2346 // Compute prefix1 and prefix2 2347 char prefix1 = 0; 2348 char prefix2 = 0; 2349 if (base == 10) 2350 { 2351 if (negative) 2352 prefix1 = '-'; 2353 else if (fs.flPlus) 2354 prefix1 = '+'; 2355 else if (fs.flSpace) 2356 prefix1 = ' '; 2357 } 2358 else if (base == 16 && fs.flHash && digits.length) 2359 { 2360 prefix1 = '0'; 2361 prefix2 = fs.spec == 'x' ? 'x' : 'X'; 2362 } 2363 // adjust precision to print a '0' for octal if alternate format is on 2364 else if (base == 8 && fs.flHash && 2365 (precision <= 1 || precision <= digits.length) && // too low precision 2366 digits.length > 0) 2367 prefix1 = '0'; 2368 2369 size_t zerofill = precision > digits.length ? precision - digits.length : 0; 2370 size_t leftpad = 0; 2371 size_t rightpad = 0; 2372 2373 immutable prefixWidth = (prefix1 != 0) + (prefix2 != 0); 2374 size_t finalWidth, separatorsCount; 2375 if (fs.flSeparator != 0) 2376 { 2377 finalWidth = prefixWidth + digits.length + ((digits.length > 0) ? (digits.length - 1) / fs.separators : 0); 2378 if (finalWidth < fs.width) 2379 finalWidth = fs.width + (padChar == '0') * (((fs.width - prefixWidth) % (fs.separators + 1) == 0) ? 1 : 0); 2380 2381 separatorsCount = (padChar == '0') ? (finalWidth - prefixWidth - 1) / (fs.separators + 1) : 2382 ((digits.length > 0) ? (digits.length - 1) / fs.separators : 0); 2383 } 2384 else 2385 { 2386 import std.algorithm.comparison : max; 2387 finalWidth = max(fs.width, prefixWidth + digits.length); 2388 } 2389 2390 immutable ptrdiff_t spacesToPrint = 2391 finalWidth - ( 2392 + prefixWidth 2393 + zerofill 2394 + digits.length 2395 + separatorsCount 2396 ); 2397 if (spacesToPrint > 0) // need to do some padding 2398 { 2399 if (padChar == '0') 2400 zerofill += spacesToPrint; 2401 else if (padChar) 2402 leftpad = spacesToPrint; 2403 else 2404 rightpad = spacesToPrint; 2405 } 2406 2407 // Print 2408 foreach (i ; 0 .. leftpad) 2409 put(w, ' '); 2410 2411 if (prefix1) put(w, prefix1); 2412 if (prefix2) put(w, prefix2); 2413 2414 if (fs.flSeparator) 2415 { 2416 if (zerofill > 0) 2417 { 2418 put(w, '0'); 2419 --zerofill; 2420 } 2421 2422 int j = cast(int) (finalWidth - prefixWidth - separatorsCount - 1); 2423 for (size_t i = 0; i < zerofill; ++i, --j) 2424 { 2425 if (j % fs.separators == 0) 2426 { 2427 put(w, fs.separatorChar); 2428 } 2429 put(w, '0'); 2430 } 2431 } 2432 else 2433 { 2434 foreach (i ; 0 .. zerofill) 2435 put(w, '0'); 2436 } 2437 2438 if (fs.flSeparator) 2439 { 2440 for (size_t j = 0; j < digits.length; ++j) 2441 { 2442 if (((j != 0) || ((spacesToPrint > 0) && (padChar == '0'))) && (digits.length - j) % fs.separators == 0) 2443 { 2444 put(w, fs.separatorChar); 2445 } 2446 put(w, digits[j]); 2447 } 2448 } 2449 else 2450 { 2451 put(w, digits); 2452 } 2453 2454 foreach (i ; 0 .. rightpad) 2455 put(w, ' '); 2456 } 2457 2458 // https://issues.dlang.org/show_bug.cgi?id=18838 2459 @safe pure unittest 2460 { 2461 assert("%12,d".format(0) == " 0"); 2462 } 2463 2464 @safe pure unittest 2465 { 2466 assert(collectExceptionMsg!FormatException(format("%c", 5)).back == 'c'); 2467 2468 assertCTFEable!( 2469 { 2470 formatTest(9, "9"); 2471 formatTest( 10, "10" ); 2472 }); 2473 } 2474 2475 @system unittest 2476 { 2477 class C1 { long val; alias val this; this(long v){ val = v; } } 2478 class C2 { long val; alias val this; this(long v){ val = v; } 2479 override string toString() const { return "C"; } } 2480 formatTest( new C1(10), "10" ); 2481 formatTest( new C2(10), "C" ); 2482 2483 struct S1 { long val; alias val this; } 2484 struct S2 { long val; alias val this; 2485 string toString() const { return "S"; } } 2486 formatTest( S1(10), "10" ); 2487 formatTest( S2(10), "S" ); 2488 } 2489 2490 // https://issues.dlang.org/show_bug.cgi?id=9117 2491 @safe unittest 2492 { 2493 static struct Frop {} 2494 2495 static struct Foo 2496 { 2497 int n = 0; 2498 alias n this; 2499 T opCast(T) () if (is(T == Frop)) 2500 { 2501 return Frop(); 2502 } 2503 string toString() 2504 { 2505 return "Foo"; 2506 } 2507 } 2508 2509 static struct Bar 2510 { 2511 Foo foo; 2512 alias foo this; 2513 string toString() 2514 { 2515 return "Bar"; 2516 } 2517 } 2518 2519 const(char)[] result; 2520 void put(scope const char[] s){ result ~= s; } 2521 2522 Foo foo; 2523 formattedWrite(&put, "%s", foo); // OK 2524 assert(result == "Foo"); 2525 2526 result = null; 2527 2528 Bar bar; 2529 formattedWrite(&put, "%s", bar); // NG 2530 assert(result == "Bar"); 2531 2532 result = null; 2533 2534 int i = 9; 2535 formattedWrite(&put, "%s", 9); 2536 assert(result == "9"); 2537 } 2538 2539 // https://issues.dlang.org/show_bug.cgi?id=20064 2540 @safe unittest 2541 { 2542 assert(format( "%03,d", 1234) == "1,234"); 2543 assert(format( "%04,d", 1234) == "1,234"); 2544 assert(format( "%05,d", 1234) == "1,234"); 2545 assert(format( "%06,d", 1234) == "01,234"); 2546 assert(format( "%07,d", 1234) == "001,234"); 2547 assert(format( "%08,d", 1234) == "0,001,234"); 2548 assert(format( "%09,d", 1234) == "0,001,234"); 2549 assert(format("%010,d", 1234) == "00,001,234"); 2550 assert(format("%011,d", 1234) == "000,001,234"); 2551 assert(format("%012,d", 1234) == "0,000,001,234"); 2552 assert(format("%013,d", 1234) == "0,000,001,234"); 2553 assert(format("%014,d", 1234) == "00,000,001,234"); 2554 assert(format("%015,d", 1234) == "000,000,001,234"); 2555 assert(format("%016,d", 1234) == "0,000,000,001,234"); 2556 assert(format("%017,d", 1234) == "0,000,000,001,234"); 2557 2558 assert(format( "%03,d", -1234) == "-1,234"); 2559 assert(format( "%04,d", -1234) == "-1,234"); 2560 assert(format( "%05,d", -1234) == "-1,234"); 2561 assert(format( "%06,d", -1234) == "-1,234"); 2562 assert(format( "%07,d", -1234) == "-01,234"); 2563 assert(format( "%08,d", -1234) == "-001,234"); 2564 assert(format( "%09,d", -1234) == "-0,001,234"); 2565 assert(format("%010,d", -1234) == "-0,001,234"); 2566 assert(format("%011,d", -1234) == "-00,001,234"); 2567 assert(format("%012,d", -1234) == "-000,001,234"); 2568 assert(format("%013,d", -1234) == "-0,000,001,234"); 2569 assert(format("%014,d", -1234) == "-0,000,001,234"); 2570 assert(format("%015,d", -1234) == "-00,000,001,234"); 2571 assert(format("%016,d", -1234) == "-000,000,001,234"); 2572 assert(format("%017,d", -1234) == "-0,000,000,001,234"); 2573 } 2574 2575 @safe pure unittest 2576 { 2577 string t1 = format("[%6s] [%-6s]", 123, 123); 2578 assert(t1 == "[ 123] [123 ]"); 2579 2580 string t2 = format("[%6s] [%-6s]", -123, -123); 2581 assert(t2 == "[ -123] [-123 ]"); 2582 } 2583 2584 private enum ctfpMessage = "Cannot format floating point types at compile-time"; 2585 2586 private enum RoundingMode { up, down, toZero, toNearestTiesToEven, toNearestTiesAwayFromZero } 2587 2588 /* 2589 Floating-point values are formatted like $(REF printf, core, stdc, stdio) 2590 */ 2591 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) 2592 if (is(FloatingPointTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 2593 { 2594 import std.algorithm.comparison : min; 2595 import std.algorithm.searching : find; 2596 import std..string : indexOf, indexOfAny, indexOfNeither; 2597 import std.math : isInfinity, isNaN, signbit; 2598 import std.ascii : isUpper; 2599 2600 string nanInfStr(scope const ref FormatSpec!Char f, const bool nan, 2601 const bool inf, const int sb, const bool up) @safe pure nothrow 2602 { 2603 return nan 2604 ? up 2605 ? sb ? "-NAN" : f.flPlus ? "+NAN" : (f.flSpace ? " NAN" : "NAN") 2606 : sb ? "-nan" : f.flPlus ? "+nan" : (f.flSpace ? " nan" : "nan") 2607 : inf 2608 ? up 2609 ? sb ? "-INF" : f.flPlus ? "+INF" : (f.flSpace ? " INF" : "INF") 2610 : sb ? "-inf" : f.flPlus ? "+inf" : (f.flSpace ? " inf" : "inf") 2611 : ""; 2612 } 2613 2614 FloatingPointTypeOf!T val = obj; 2615 const char spec = f.spec; 2616 2617 if (spec == 'r') 2618 { 2619 // raw write, skip all else and write the thing 2620 auto raw = (ref val)@trusted{ 2621 return (cast(const char*) &val)[0 .. val.sizeof]; 2622 }(val); 2623 if (needToSwapEndianess(f)) 2624 { 2625 foreach_reverse (c; raw) 2626 put(w, c); 2627 } 2628 else 2629 { 2630 foreach (c; raw) 2631 put(w, c); 2632 } 2633 return; 2634 } 2635 enforceFmt(find("fgFGaAeEs", spec).length, 2636 "incompatible format character for floating point argument: %" ~ spec); 2637 enforceFmt(!__ctfe, ctfpMessage); 2638 2639 FormatSpec!Char fs = f; // fs is copy for change its values. 2640 const spec2 = spec == 's' ? 'g' : spec; 2641 2642 version (CRuntime_Microsoft) 2643 { 2644 // convert early to get "inf" in case of overflow 2645 // windows handels inf and nan strange 2646 // https://devblogs.microsoft.com/oldnewthing/20130228-01/?p=5103 2647 immutable double tval = val; 2648 } 2649 else 2650 { 2651 alias tval = val; 2652 } 2653 2654 const nan = isNaN(tval); 2655 const inf = isInfinity(tval); 2656 2657 char[512] buf2 = void; 2658 size_t len; 2659 char[] buf; 2660 if (fs.spec=='a' || fs.spec=='A' || fs.spec=='e' || fs.spec=='E') 2661 { 2662 static if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig)) 2663 { 2664 import std.math; 2665 2666 auto mode = RoundingMode.toNearestTiesToEven; 2667 2668 // std.math's FloatingPointControl isn't available on all target platforms 2669 static if (is(FloatingPointControl)) 2670 { 2671 switch (FloatingPointControl.rounding) 2672 { 2673 case FloatingPointControl.roundUp: 2674 mode = RoundingMode.up; 2675 break; 2676 case FloatingPointControl.roundDown: 2677 mode = RoundingMode.down; 2678 break; 2679 case FloatingPointControl.roundToZero: 2680 mode = RoundingMode.toZero; 2681 break; 2682 case FloatingPointControl.roundToNearest: 2683 mode = RoundingMode.toNearestTiesToEven; 2684 break; 2685 default: assert(false); 2686 } 2687 } 2688 2689 buf = printFloat(buf2[], val, fs, mode); 2690 len = buf.length; 2691 } 2692 else 2693 goto useSnprintf; 2694 } 2695 else 2696 { 2697 useSnprintf: 2698 if (nan || inf) 2699 { 2700 const sb = signbit(tval); 2701 const up = isUpper(spec); 2702 string ns = nanInfStr(f, nan, inf, sb, up); 2703 FormatSpec!Char co; 2704 co.spec = 's'; 2705 co.width = f.width; 2706 co.flDash = f.flDash; 2707 formatValue(w, ns, co); 2708 return; 2709 } 2710 2711 char[1 /*%*/ + 5 /*flags*/ + 3 /*width.prec*/ + 2 /*format*/ 2712 + 1 /*\0*/] sprintfSpec = void; 2713 sprintfSpec[0] = '%'; 2714 uint i = 1; 2715 if (fs.flDash) sprintfSpec[i++] = '-'; 2716 if (fs.flPlus) sprintfSpec[i++] = '+'; 2717 if (fs.flZero) sprintfSpec[i++] = '0'; 2718 if (fs.flSpace) sprintfSpec[i++] = ' '; 2719 if (fs.flHash) sprintfSpec[i++] = '#'; 2720 sprintfSpec[i .. i + 3] = "*.*"; 2721 i += 3; 2722 if (is(immutable typeof(val) == immutable real)) sprintfSpec[i++] = 'L'; 2723 sprintfSpec[i++] = spec2; 2724 sprintfSpec[i] = 0; 2725 //printf("format: '%s'; geeba: %g\n", sprintfSpec.ptr, val); 2726 2727 //writefln("'%s'", sprintfSpec[0 .. i]); 2728 2729 immutable n = ()@trusted{ 2730 import core.stdc.stdio : snprintf; 2731 return snprintf(buf2.ptr, buf2.length, 2732 sprintfSpec.ptr, 2733 fs.width, 2734 // negative precision is same as no precision specified 2735 fs.precision == fs.UNSPECIFIED ? -1 : fs.precision, 2736 tval); 2737 }(); 2738 2739 enforceFmt(n >= 0, 2740 "floating point formatting failure"); 2741 2742 len = min(n, buf2.length-1); 2743 buf = buf2; 2744 } 2745 2746 if (fs.flSeparator && !inf && !nan) 2747 { 2748 ptrdiff_t indexOfRemovable() 2749 { 2750 if (len < 2) 2751 return -1; 2752 2753 size_t start = (buf[0 .. 1].indexOfAny(" 0123456789") == -1) ? 1 : 0; 2754 if (len < 2 + start) 2755 return -1; 2756 if ((buf[start] == ' ') || (buf[start] == '0' && buf[start + 1] != '.')) 2757 return start; 2758 2759 return -1; 2760 } 2761 2762 ptrdiff_t dot, firstDigit, ePos, dotIdx, firstLen; 2763 size_t separatorScoreCnt; 2764 2765 while (true) 2766 { 2767 dot = buf[0 .. len].indexOf('.'); 2768 firstDigit = buf[0 .. len].indexOfAny("0123456789"); 2769 ePos = buf[0 .. len].indexOf('e'); 2770 dotIdx = dot == -1 ? ePos == -1 ? len : ePos : dot; 2771 2772 firstLen = dotIdx - firstDigit; 2773 separatorScoreCnt = (firstLen > 0) ? (firstLen - 1) / fs.separators : 0; 2774 2775 ptrdiff_t removableIdx = (len + separatorScoreCnt > fs.width) ? indexOfRemovable() : -1; 2776 if ((removableIdx != -1) && 2777 ((firstLen - (buf[removableIdx] == '0' ? 2 : 1)) / fs.separators + len - 1 >= fs.width)) 2778 { 2779 buf[removableIdx .. $ - 1] = buf.dup[removableIdx + 1 .. $]; 2780 len--; 2781 } 2782 else 2783 break; 2784 } 2785 2786 immutable afterDotIdx = (ePos != -1) ? ePos : len; 2787 2788 // plus, minus, prefix 2789 if (firstDigit > 0) 2790 { 2791 put(w, buf[0 .. firstDigit]); 2792 } 2793 2794 // digits until dot with separator 2795 for (auto j = 0; j < firstLen; ++j) 2796 { 2797 if (j > 0 && (firstLen - j) % fs.separators == 0) 2798 { 2799 put(w, fs.separatorChar); 2800 } 2801 put(w, buf[j + firstDigit]); 2802 } 2803 2804 // print dot for decimal numbers only or with '#' format specifier 2805 if (dot != -1 || fs.flHash) 2806 { 2807 put(w, '.'); 2808 } 2809 2810 // digits after dot 2811 for (auto j = dotIdx + 1; j < afterDotIdx; ++j) 2812 { 2813 put(w, buf[j]); 2814 } 2815 2816 // rest 2817 if (ePos != -1) 2818 { 2819 put(w, buf[afterDotIdx .. len]); 2820 } 2821 } 2822 else 2823 { 2824 put(w, buf[0 .. len]); 2825 } 2826 } 2827 2828 @safe unittest 2829 { 2830 assert(format("%.1f", 1337.7) == "1337.7"); 2831 assert(format("%,3.2f", 1331.982) == "1,331.98"); 2832 assert(format("%,3.0f", 1303.1982) == "1,303"); 2833 assert(format("%#,3.4f", 1303.1982) == "1,303.1982"); 2834 assert(format("%#,3.0f", 1303.1982) == "1,303."); 2835 } 2836 2837 @safe /*pure*/ unittest // formatting floating point values is now impure 2838 { 2839 import std.conv : to; 2840 2841 assert(collectExceptionMsg!FormatException(format("%d", 5.1)).back == 'd'); 2842 2843 static foreach (T; AliasSeq!(float, double, real)) 2844 { 2845 formatTest( to!( T)(5.5), "5.5" ); 2846 formatTest( to!( const T)(5.5), "5.5" ); 2847 formatTest( to!(immutable T)(5.5), "5.5" ); 2848 2849 formatTest( T.nan, "nan" ); 2850 } 2851 } 2852 2853 @system unittest 2854 { 2855 formatTest( 2.25, "2.25" ); 2856 2857 class C1 { double val; alias val this; this(double v){ val = v; } } 2858 class C2 { double val; alias val this; this(double v){ val = v; } 2859 override string toString() const { return "C"; } } 2860 formatTest( new C1(2.25), "2.25" ); 2861 formatTest( new C2(2.25), "C" ); 2862 2863 struct S1 { double val; alias val this; } 2864 struct S2 { double val; alias val this; 2865 string toString() const { return "S"; } } 2866 formatTest( S1(2.25), "2.25" ); 2867 formatTest( S2(2.25), "S" ); 2868 } 2869 2870 // https://issues.dlang.org/show_bug.cgi?id=19939 2871 @safe unittest 2872 { 2873 assert(format("^%13,3.2f$", 1.00) == "^ 1.00$"); 2874 assert(format("^%13,3.2f$", 10.00) == "^ 10.00$"); 2875 assert(format("^%13,3.2f$", 100.00) == "^ 100.00$"); 2876 assert(format("^%13,3.2f$", 1_000.00) == "^ 1,000.00$"); 2877 assert(format("^%13,3.2f$", 10_000.00) == "^ 10,000.00$"); 2878 assert(format("^%13,3.2f$", 100_000.00) == "^ 100,000.00$"); 2879 assert(format("^%13,3.2f$", 1_000_000.00) == "^ 1,000,000.00$"); 2880 assert(format("^%13,3.2f$", 10_000_000.00) == "^10,000,000.00$"); 2881 } 2882 2883 // https://issues.dlang.org/show_bug.cgi?id=20069 2884 @safe unittest 2885 { 2886 assert(format("%012,f", -1234.0) == "-1,234.000000"); 2887 assert(format("%013,f", -1234.0) == "-1,234.000000"); 2888 assert(format("%014,f", -1234.0) == "-01,234.000000"); 2889 assert(format("%011,f", 1234.0) == "1,234.000000"); 2890 assert(format("%012,f", 1234.0) == "1,234.000000"); 2891 assert(format("%013,f", 1234.0) == "01,234.000000"); 2892 assert(format("%014,f", 1234.0) == "001,234.000000"); 2893 assert(format("%015,f", 1234.0) == "0,001,234.000000"); 2894 assert(format("%016,f", 1234.0) == "0,001,234.000000"); 2895 2896 assert(format( "%08,.2f", -1234.0) == "-1,234.00"); 2897 assert(format( "%09,.2f", -1234.0) == "-1,234.00"); 2898 assert(format("%010,.2f", -1234.0) == "-01,234.00"); 2899 assert(format("%011,.2f", -1234.0) == "-001,234.00"); 2900 assert(format("%012,.2f", -1234.0) == "-0,001,234.00"); 2901 assert(format("%013,.2f", -1234.0) == "-0,001,234.00"); 2902 assert(format("%014,.2f", -1234.0) == "-00,001,234.00"); 2903 assert(format( "%08,.2f", 1234.0) == "1,234.00"); 2904 assert(format( "%09,.2f", 1234.0) == "01,234.00"); 2905 assert(format("%010,.2f", 1234.0) == "001,234.00"); 2906 assert(format("%011,.2f", 1234.0) == "0,001,234.00"); 2907 assert(format("%012,.2f", 1234.0) == "0,001,234.00"); 2908 assert(format("%013,.2f", 1234.0) == "00,001,234.00"); 2909 assert(format("%014,.2f", 1234.0) == "000,001,234.00"); 2910 assert(format("%015,.2f", 1234.0) == "0,000,001,234.00"); 2911 assert(format("%016,.2f", 1234.0) == "0,000,001,234.00"); 2912 } 2913 2914 @safe unittest 2915 { 2916 string t1 = format("[%6s] [%-6s]", 12.3, 12.3); 2917 assert(t1 == "[ 12.3] [12.3 ]"); 2918 2919 string t2 = format("[%6s] [%-6s]", -12.3, -12.3); 2920 assert(t2 == "[ -12.3] [-12.3 ]"); 2921 } 2922 2923 // https://issues.dlang.org/show_bug.cgi?id=20396 2924 @safe unittest 2925 { 2926 import std.math : nextUp; 2927 2928 assert(format!"%a"(nextUp(0.0f)) == "0x0.000002p-126"); 2929 assert(format!"%a"(nextUp(0.0)) == "0x0.0000000000001p-1022"); 2930 } 2931 2932 // https://issues.dlang.org/show_bug.cgi?id=20371 2933 @safe unittest 2934 { 2935 assert(format!"%.1000a"(1.0) == 2936 "0x1.000000000000000000000000000000000000000000000000000000000000000000000000000" 2937 ~"000000000000000000000000000000000000000000000000000000000000000000000000000000" 2938 ~"000000000000000000000000000000000000000000000000000000000000000000000000000000" 2939 ~"000000000000000000000000000000000000000000000000000000000000000000000000000000" 2940 ~"000000000000000000000000000000000000000000000000000000000000000000000000000000" 2941 ~"000000000000000000000000000000000000000000000000000000000000000000000000000000" 2942 ~"000000000000000000000000000000000000000000000000000000000000000000000000000000" 2943 ~"000000000000000000000000000000000000000000000000000000000000000000000000000000" 2944 ~"000000000000000000000000000000000000000000000000000000000000000000000000000000" 2945 ~"000000000000000000000000000000000000000000000000000000000000000000000000000000" 2946 ~"000000000000000000000000000000000000000000000000000000000000000000000000000000" 2947 ~"000000000000000000000000000000000000000000000000000000000000000000000000000000" 2948 ~"0000000000000000000000000000000000000000000000000000000000000000000p+0"); 2949 } 2950 2951 /* 2952 Formatting a `creal` is deprecated but still kept around for a while. 2953 */ 2954 deprecated("Use of complex types is deprecated. Use std.complex") 2955 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) 2956 if (is(immutable T : immutable creal) && !is(T == enum) && !hasToString!(T, Char)) 2957 { 2958 immutable creal val = obj; 2959 2960 formatValueImpl(w, val.re, f); 2961 if (val.im >= 0) 2962 { 2963 put(w, '+'); 2964 } 2965 formatValueImpl(w, val.im, f); 2966 put(w, 'i'); 2967 } 2968 2969 version (TestComplex) 2970 deprecated 2971 @safe /*pure*/ unittest // formatting floating point values is now impure 2972 { 2973 import std.conv : to; 2974 static foreach (T; AliasSeq!(cfloat, cdouble, creal)) 2975 { 2976 formatTest( to!( T)(1 + 1i), "1+1i" ); 2977 formatTest( to!( const T)(1 + 1i), "1+1i" ); 2978 formatTest( to!(immutable T)(1 + 1i), "1+1i" ); 2979 } 2980 static foreach (T; AliasSeq!(cfloat, cdouble, creal)) 2981 { 2982 formatTest( to!( T)(0 - 3i), "0-3i" ); 2983 formatTest( to!( const T)(0 - 3i), "0-3i" ); 2984 formatTest( to!(immutable T)(0 - 3i), "0-3i" ); 2985 } 2986 } 2987 2988 version (TestComplex) 2989 deprecated 2990 @system unittest 2991 { 2992 formatTest( 3+2.25i, "3+2.25i" ); 2993 2994 class C1 { cdouble val; alias val this; this(cdouble v){ val = v; } } 2995 class C2 { cdouble val; alias val this; this(cdouble v){ val = v; } 2996 override string toString() const { return "C"; } } 2997 formatTest( new C1(3+2.25i), "3+2.25i" ); 2998 formatTest( new C2(3+2.25i), "C" ); 2999 3000 struct S1 { cdouble val; alias val this; } 3001 struct S2 { cdouble val; alias val this; 3002 string toString() const { return "S"; } } 3003 formatTest( S1(3+2.25i), "3+2.25i" ); 3004 formatTest( S2(3+2.25i), "S" ); 3005 } 3006 3007 /* 3008 Formatting an `ireal` is deprecated but still kept around for a while. 3009 */ 3010 deprecated("Use of imaginary types is deprecated. Use std.complex") 3011 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) 3012 if (is(immutable T : immutable ireal) && !is(T == enum) && !hasToString!(T, Char)) 3013 { 3014 immutable ireal val = obj; 3015 3016 formatValueImpl(w, val.im, f); 3017 put(w, 'i'); 3018 } 3019 3020 version (TestComplex) 3021 deprecated 3022 @safe /*pure*/ unittest // formatting floating point values is now impure 3023 { 3024 import std.conv : to; 3025 static foreach (T; AliasSeq!(ifloat, idouble, ireal)) 3026 { 3027 formatTest( to!( T)(1i), "1i" ); 3028 formatTest( to!( const T)(1i), "1i" ); 3029 formatTest( to!(immutable T)(1i), "1i" ); 3030 } 3031 } 3032 3033 version (TestComplex) 3034 deprecated 3035 @system unittest 3036 { 3037 formatTest( 2.25i, "2.25i" ); 3038 3039 class C1 { idouble val; alias val this; this(idouble v){ val = v; } } 3040 class C2 { idouble val; alias val this; this(idouble v){ val = v; } 3041 override string toString() const { return "C"; } } 3042 formatTest( new C1(2.25i), "2.25i" ); 3043 formatTest( new C2(2.25i), "C" ); 3044 3045 struct S1 { idouble val; alias val this; } 3046 struct S2 { idouble val; alias val this; 3047 string toString() const { return "S"; } } 3048 formatTest( S1(2.25i), "2.25i" ); 3049 formatTest( S2(2.25i), "S" ); 3050 } 3051 3052 /* 3053 Individual characters are formatted as Unicode characters with `%s` 3054 and as integers with integral-specific format specs 3055 */ 3056 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) 3057 if (is(CharTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 3058 { 3059 CharTypeOf!T[1] val = obj; 3060 3061 if (f.spec == 's' || f.spec == 'c') 3062 writeAligned(w, val[], f); 3063 else 3064 { 3065 alias U = AliasSeq!(ubyte, ushort, uint)[CharTypeOf!T.sizeof/2]; 3066 formatValueImpl(w, cast(U) val[0], f); 3067 } 3068 } 3069 3070 @safe pure unittest 3071 { 3072 assertCTFEable!( 3073 { 3074 formatTest( 'c', "c" ); 3075 }); 3076 } 3077 3078 @system unittest 3079 { 3080 class C1 { char val; alias val this; this(char v){ val = v; } } 3081 class C2 { char val; alias val this; this(char v){ val = v; } 3082 override string toString() const { return "C"; } } 3083 formatTest( new C1('c'), "c" ); 3084 formatTest( new C2('c'), "C" ); 3085 3086 struct S1 { char val; alias val this; } 3087 struct S2 { char val; alias val this; 3088 string toString() const { return "S"; } } 3089 formatTest( S1('c'), "c" ); 3090 formatTest( S2('c'), "S" ); 3091 } 3092 3093 @safe unittest 3094 { 3095 //Little Endian 3096 formatTest( "%-r", cast( char)'c', ['c' ] ); 3097 formatTest( "%-r", cast(wchar)'c', ['c', 0 ] ); 3098 formatTest( "%-r", cast(dchar)'c', ['c', 0, 0, 0] ); 3099 formatTest( "%-r", '本', ['\x2c', '\x67'] ); 3100 3101 //Big Endian 3102 formatTest( "%+r", cast( char)'c', [ 'c'] ); 3103 formatTest( "%+r", cast(wchar)'c', [0, 'c'] ); 3104 formatTest( "%+r", cast(dchar)'c', [0, 0, 0, 'c'] ); 3105 formatTest( "%+r", '本', ['\x67', '\x2c'] ); 3106 } 3107 3108 3109 @safe pure unittest 3110 { 3111 string t1 = format("[%6s] [%-6s]", 'A', 'A'); 3112 assert(t1 == "[ A] [A ]"); 3113 string t2 = format("[%6s] [%-6s]", '本', '本'); 3114 assert(t2 == "[ 本] [本 ]"); 3115 } 3116 3117 /* 3118 Strings are formatted like $(REF printf, core, stdc, stdio) 3119 */ 3120 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T obj, scope const ref FormatSpec!Char f) 3121 if (is(StringTypeOf!T) && !is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 3122 { 3123 Unqual!(StringTypeOf!T) val = obj; // for `alias this`, see bug5371 3124 formatRange(w, val, f); 3125 } 3126 3127 @safe unittest 3128 { 3129 formatTest( "abc", "abc" ); 3130 } 3131 3132 @system unittest 3133 { 3134 // Test for bug 5371 for classes 3135 class C1 { const string var; alias var this; this(string s){ var = s; } } 3136 class C2 { string var; alias var this; this(string s){ var = s; } } 3137 formatTest( new C1("c1"), "c1" ); 3138 formatTest( new C2("c2"), "c2" ); 3139 3140 // Test for bug 5371 for structs 3141 struct S1 { const string var; alias var this; } 3142 struct S2 { string var; alias var this; } 3143 formatTest( S1("s1"), "s1" ); 3144 formatTest( S2("s2"), "s2" ); 3145 } 3146 3147 @system unittest 3148 { 3149 class C3 { string val; alias val this; this(string s){ val = s; } 3150 override string toString() const { return "C"; } } 3151 formatTest( new C3("c3"), "C" ); 3152 3153 struct S3 { string val; alias val this; 3154 string toString() const { return "S"; } } 3155 formatTest( S3("s3"), "S" ); 3156 } 3157 3158 @safe pure unittest 3159 { 3160 //Little Endian 3161 formatTest( "%-r", "ab"c, ['a' , 'b' ] ); 3162 formatTest( "%-r", "ab"w, ['a', 0 , 'b', 0 ] ); 3163 formatTest( "%-r", "ab"d, ['a', 0, 0, 0, 'b', 0, 0, 0] ); 3164 formatTest( "%-r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] ); 3165 formatTest( "%-r", "日本語"w, ['\xe5', '\x65', '\x2c', '\x67', '\x9e', '\x8a']); 3166 formatTest( "%-r", "日本語"d, ['\xe5', '\x65', '\x00', '\x00', '\x2c', '\x67', 3167 '\x00', '\x00', '\x9e', '\x8a', '\x00', '\x00'] ); 3168 3169 //Big Endian 3170 formatTest( "%+r", "ab"c, [ 'a', 'b'] ); 3171 formatTest( "%+r", "ab"w, [ 0, 'a', 0, 'b'] ); 3172 formatTest( "%+r", "ab"d, [0, 0, 0, 'a', 0, 0, 0, 'b'] ); 3173 formatTest( "%+r", "日本語"c, ['\xe6', '\x97', '\xa5', '\xe6', '\x9c', '\xac', '\xe8', '\xaa', '\x9e'] ); 3174 formatTest( "%+r", "日本語"w, ['\x65', '\xe5', '\x67', '\x2c', '\x8a', '\x9e'] ); 3175 formatTest( "%+r", "日本語"d, ['\x00', '\x00', '\x65', '\xe5', '\x00', '\x00', 3176 '\x67', '\x2c', '\x00', '\x00', '\x8a', '\x9e'] ); 3177 } 3178 3179 @safe pure unittest 3180 { 3181 string t1 = format("[%6s] [%-6s]", "AB", "AB"); 3182 assert(t1 == "[ AB] [AB ]"); 3183 string t2 = format("[%6s] [%-6s]", "本Ä", "本Ä"); 3184 assert(t2 == "[ 本Ä] [本Ä ]"); 3185 } 3186 3187 /* 3188 Static-size arrays are formatted as dynamic arrays. 3189 */ 3190 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T obj, scope const ref FormatSpec!Char f) 3191 if (is(StaticArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 3192 { 3193 formatValueImpl(w, obj[], f); 3194 } 3195 3196 // Test for https://issues.dlang.org/show_bug.cgi?id=8310 3197 @safe unittest 3198 { 3199 import std.array : appender; 3200 FormatSpec!char f; 3201 auto w = appender!string(); 3202 3203 char[2] two = ['a', 'b']; 3204 formatValue(w, two, f); 3205 3206 char[2] getTwo(){ return two; } 3207 formatValue(w, getTwo(), f); 3208 } 3209 3210 /* 3211 Dynamic arrays are formatted as input ranges. 3212 */ 3213 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) 3214 if (is(DynamicArrayTypeOf!T) && !is(StringTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 3215 { 3216 static if (is(const(ArrayTypeOf!T) == const(void[]))) 3217 { 3218 formatValueImpl(w, cast(const ubyte[]) obj, f); 3219 } 3220 else static if (!isInputRange!T) 3221 { 3222 alias U = Unqual!(ArrayTypeOf!T); 3223 static assert(isInputRange!U, U.stringof ~ " must be an InputRange"); 3224 U val = obj; 3225 formatValueImpl(w, val, f); 3226 } 3227 else 3228 { 3229 formatRange(w, obj, f); 3230 } 3231 } 3232 3233 // alias this, input range I/F, and toString() 3234 @system unittest 3235 { 3236 struct S(int flags) 3237 { 3238 int[] arr; 3239 static if (flags & 1) 3240 alias arr this; 3241 3242 static if (flags & 2) 3243 { 3244 @property bool empty() const { return arr.length == 0; } 3245 @property int front() const { return arr[0] * 2; } 3246 void popFront() { arr = arr[1..$]; } 3247 } 3248 3249 static if (flags & 4) 3250 string toString() const { return "S"; } 3251 } 3252 formatTest(S!0b000([0, 1, 2]), "S!0([0, 1, 2])"); 3253 formatTest(S!0b001([0, 1, 2]), "[0, 1, 2]"); // Test for bug 7628 3254 formatTest(S!0b010([0, 1, 2]), "[0, 2, 4]"); 3255 formatTest(S!0b011([0, 1, 2]), "[0, 2, 4]"); 3256 formatTest(S!0b100([0, 1, 2]), "S"); 3257 formatTest(S!0b101([0, 1, 2]), "S"); // Test for bug 7628 3258 formatTest(S!0b110([0, 1, 2]), "S"); 3259 formatTest(S!0b111([0, 1, 2]), "S"); 3260 3261 class C(uint flags) 3262 { 3263 int[] arr; 3264 static if (flags & 1) 3265 alias arr this; 3266 3267 this(int[] a) { arr = a; } 3268 3269 static if (flags & 2) 3270 { 3271 @property bool empty() const { return arr.length == 0; } 3272 @property int front() const { return arr[0] * 2; } 3273 void popFront() { arr = arr[1..$]; } 3274 } 3275 3276 static if (flags & 4) 3277 override string toString() const { return "C"; } 3278 } 3279 formatTest(new C!0b000([0, 1, 2]), (new C!0b000([])).toString()); 3280 formatTest(new C!0b001([0, 1, 2]), "[0, 1, 2]"); // Test for bug 7628 3281 formatTest(new C!0b010([0, 1, 2]), "[0, 2, 4]"); 3282 formatTest(new C!0b011([0, 1, 2]), "[0, 2, 4]"); 3283 formatTest(new C!0b100([0, 1, 2]), "C"); 3284 formatTest(new C!0b101([0, 1, 2]), "C"); // Test for bug 7628 3285 formatTest(new C!0b110([0, 1, 2]), "C"); 3286 formatTest(new C!0b111([0, 1, 2]), "C"); 3287 } 3288 3289 @system unittest 3290 { 3291 // void[] 3292 void[] val0; 3293 formatTest( val0, "[]" ); 3294 3295 void[] val = cast(void[]) cast(ubyte[])[1, 2, 3]; 3296 formatTest( val, "[1, 2, 3]" ); 3297 3298 void[0] sval0 = []; 3299 formatTest( sval0, "[]"); 3300 3301 void[3] sval = cast(void[3]) cast(ubyte[3])[1, 2, 3]; 3302 formatTest( sval, "[1, 2, 3]" ); 3303 } 3304 3305 @safe unittest 3306 { 3307 // const(T[]) -> const(T)[] 3308 const short[] a = [1, 2, 3]; 3309 formatTest( a, "[1, 2, 3]" ); 3310 3311 struct S { const(int[]) arr; alias arr this; } 3312 auto s = S([1,2,3]); 3313 formatTest( s, "[1, 2, 3]" ); 3314 } 3315 3316 // https://issues.dlang.org/show_bug.cgi?id=6640 3317 @safe unittest 3318 { 3319 struct Range 3320 { 3321 @safe: 3322 string value; 3323 @property bool empty() const { return !value.length; } 3324 @property dchar front() const { return value.front; } 3325 void popFront() { value.popFront(); } 3326 3327 @property size_t length() const { return value.length; } 3328 } 3329 immutable table = 3330 [ 3331 ["[%s]", "[string]"], 3332 ["[%10s]", "[ string]"], 3333 ["[%-10s]", "[string ]"], 3334 ["[%(%02x %)]", "[73 74 72 69 6e 67]"], 3335 ["[%(%c %)]", "[s t r i n g]"], 3336 ]; 3337 foreach (e; table) 3338 { 3339 formatTest(e[0], "string", e[1]); 3340 formatTest(e[0], Range("string"), e[1]); 3341 } 3342 } 3343 3344 @system unittest 3345 { 3346 // string literal from valid UTF sequence is encoding free. 3347 static foreach (StrType; AliasSeq!(string, wstring, dstring)) 3348 { 3349 // Valid and printable (ASCII) 3350 formatTest( [cast(StrType)"hello"], 3351 `["hello"]` ); 3352 3353 // 1 character escape sequences (' is not escaped in strings) 3354 formatTest( [cast(StrType)"\"'\0\\\a\b\f\n\r\t\v"], 3355 `["\"'\0\\\a\b\f\n\r\t\v"]` ); 3356 3357 // 1 character optional escape sequences 3358 formatTest( [cast(StrType)"\'\?"], 3359 `["'?"]` ); 3360 3361 // Valid and non-printable code point (<= U+FF) 3362 formatTest( [cast(StrType)"\x10\x1F\x20test"], 3363 `["\x10\x1F test"]` ); 3364 3365 // Valid and non-printable code point (<= U+FFFF) 3366 formatTest( [cast(StrType)"\u200B..\u200F"], 3367 `["\u200B..\u200F"]` ); 3368 3369 // Valid and non-printable code point (<= U+10FFFF) 3370 formatTest( [cast(StrType)"\U000E0020..\U000E007F"], 3371 `["\U000E0020..\U000E007F"]` ); 3372 } 3373 3374 // invalid UTF sequence needs hex-string literal postfix (c/w/d) 3375 { 3376 // U+FFFF with UTF-8 (Invalid code point for interchange) 3377 formatTest( [cast(string)[0xEF, 0xBF, 0xBF]], 3378 `[x"EF BF BF"c]` ); 3379 3380 // U+FFFF with UTF-16 (Invalid code point for interchange) 3381 formatTest( [cast(wstring)[0xFFFF]], 3382 `[x"FFFF"w]` ); 3383 3384 // U+FFFF with UTF-32 (Invalid code point for interchange) 3385 formatTest( [cast(dstring)[0xFFFF]], 3386 `[x"FFFF"d]` ); 3387 } 3388 } 3389 3390 @safe unittest 3391 { 3392 // nested range formatting with array of string 3393 formatTest( "%({%(%02x %)}%| %)", ["test", "msg"], 3394 `{74 65 73 74} {6d 73 67}` ); 3395 } 3396 3397 @safe unittest 3398 { 3399 // stop auto escaping inside range formatting 3400 auto arr = ["hello", "world"]; 3401 formatTest( "%(%s, %)", arr, `"hello", "world"` ); 3402 formatTest( "%-(%s, %)", arr, `hello, world` ); 3403 3404 auto aa1 = [1:"hello", 2:"world"]; 3405 formatTest( "%(%s:%s, %)", aa1, [`1:"hello", 2:"world"`, `2:"world", 1:"hello"`] ); 3406 formatTest( "%-(%s:%s, %)", aa1, [`1:hello, 2:world`, `2:world, 1:hello`] ); 3407 3408 auto aa2 = [1:["ab", "cd"], 2:["ef", "gh"]]; 3409 formatTest( "%-(%s:%s, %)", aa2, [`1:["ab", "cd"], 2:["ef", "gh"]`, `2:["ef", "gh"], 1:["ab", "cd"]`] ); 3410 formatTest( "%-(%s:%(%s%), %)", aa2, [`1:"ab""cd", 2:"ef""gh"`, `2:"ef""gh", 1:"ab""cd"`] ); 3411 formatTest( "%-(%s:%-(%s%)%|, %)", aa2, [`1:abcd, 2:efgh`, `2:efgh, 1:abcd`] ); 3412 } 3413 3414 // input range formatting 3415 private void formatRange(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f) 3416 if (isInputRange!T) 3417 { 3418 // in this mode, we just want to do a representative print to discover if the format spec is valid 3419 enum formatTestMode = is(Writer == NoOpSink); 3420 3421 import std.conv : text; 3422 3423 // Formatting character ranges like string 3424 if (f.spec == 's') 3425 { 3426 alias E = ElementType!T; 3427 3428 static if (!is(E == enum) && is(CharTypeOf!E)) 3429 { 3430 static if (is(StringTypeOf!T)) 3431 writeAligned(w, val[0 .. f.precision < $ ? f.precision : $], f); 3432 else 3433 { 3434 if (!f.flDash) 3435 { 3436 static if (hasLength!T) 3437 { 3438 // right align 3439 auto len = val.length; 3440 } 3441 else static if (isForwardRange!T && !isInfinite!T) 3442 { 3443 auto len = walkLength(val.save); 3444 } 3445 else 3446 { 3447 enforceFmt(f.width == 0, "Cannot right-align a range without length"); 3448 size_t len = 0; 3449 } 3450 if (f.precision != f.UNSPECIFIED && len > f.precision) 3451 len = f.precision; 3452 3453 if (f.width > len) 3454 foreach (i ; 0 .. f.width - len) 3455 put(w, ' '); 3456 if (f.precision == f.UNSPECIFIED) 3457 put(w, val); 3458 else 3459 { 3460 size_t printed = 0; 3461 for (; !val.empty && printed < f.precision; val.popFront(), ++printed) 3462 put(w, val.front); 3463 } 3464 } 3465 else 3466 { 3467 size_t printed = void; 3468 3469 // left align 3470 if (f.precision == f.UNSPECIFIED) 3471 { 3472 static if (hasLength!T) 3473 { 3474 printed = val.length; 3475 put(w, val); 3476 } 3477 else 3478 { 3479 printed = 0; 3480 for (; !val.empty; val.popFront(), ++printed) 3481 { 3482 put(w, val.front); 3483 static if (formatTestMode) break; // one is enough to test 3484 } 3485 } 3486 } 3487 else 3488 { 3489 printed = 0; 3490 for (; !val.empty && printed < f.precision; val.popFront(), ++printed) 3491 put(w, val.front); 3492 } 3493 3494 if (f.width > printed) 3495 foreach (i ; 0 .. f.width - printed) 3496 put(w, ' '); 3497 } 3498 } 3499 } 3500 else 3501 { 3502 put(w, f.seqBefore); 3503 if (!val.empty) 3504 { 3505 formatElement(w, val.front, f); 3506 val.popFront(); 3507 for (size_t i; !val.empty; val.popFront(), ++i) 3508 { 3509 put(w, f.seqSeparator); 3510 formatElement(w, val.front, f); 3511 static if (formatTestMode) break; // one is enough to test 3512 } 3513 } 3514 static if (!isInfinite!T) put(w, f.seqAfter); 3515 } 3516 } 3517 else if (f.spec == 'r') 3518 { 3519 static if (is(DynamicArrayTypeOf!T)) 3520 { 3521 alias ARR = DynamicArrayTypeOf!T; 3522 scope a = cast(ARR) val; 3523 foreach (e ; a) 3524 { 3525 formatValue(w, e, f); 3526 static if (formatTestMode) break; // one is enough to test 3527 } 3528 } 3529 else 3530 { 3531 for (size_t i; !val.empty; val.popFront(), ++i) 3532 { 3533 formatValue(w, val.front, f); 3534 static if (formatTestMode) break; // one is enough to test 3535 } 3536 } 3537 } 3538 else if (f.spec == '(') 3539 { 3540 if (val.empty) 3541 return; 3542 // Nested specifier is to be used 3543 for (;;) 3544 { 3545 auto fmt = FormatSpec!Char(f.nested); 3546 w: while (true) 3547 { 3548 immutable r = fmt.writeUpToNextSpec(w); 3549 // There was no format specifier, so break 3550 if (!r) 3551 break; 3552 if (f.flDash) 3553 formatValue(w, val.front, fmt); 3554 else 3555 formatElement(w, val.front, fmt); 3556 // Check if there will be a format specifier farther on in the 3557 // string. If so, continue the loop, otherwise break. This 3558 // prevents extra copies of the `sep` from showing up. 3559 foreach (size_t i; 0 .. fmt.trailing.length) 3560 if (fmt.trailing[i] == '%') 3561 continue w; 3562 break w; 3563 } 3564 static if (formatTestMode) 3565 { 3566 break; // one is enough to test 3567 } 3568 else 3569 { 3570 if (f.sep !is null) 3571 { 3572 put(w, fmt.trailing); 3573 val.popFront(); 3574 if (val.empty) 3575 break; 3576 put(w, f.sep); 3577 } 3578 else 3579 { 3580 val.popFront(); 3581 if (val.empty) 3582 break; 3583 put(w, fmt.trailing); 3584 } 3585 } 3586 } 3587 } 3588 else 3589 throw new FormatException(text("Incorrect format specifier for range: %", f.spec)); 3590 } 3591 3592 // https://issues.dlang.org/show_bug.cgi?id=18778 3593 @safe pure unittest 3594 { 3595 assert(format("%-(%1$s - %1$s, %)", ["A", "B", "C"]) == "A - A, B - B, C - C"); 3596 } 3597 3598 @safe pure unittest 3599 { 3600 assert(collectExceptionMsg(format("%d", "hi")).back == 'd'); 3601 } 3602 3603 // character formatting with ecaping 3604 private void formatChar(Writer)(ref Writer w, in dchar c, in char quote) 3605 { 3606 import std.uni : isGraphical; 3607 3608 string fmt; 3609 if (isGraphical(c)) 3610 { 3611 if (c == quote || c == '\\') 3612 put(w, '\\'); 3613 put(w, c); 3614 return; 3615 } 3616 else if (c <= 0xFF) 3617 { 3618 if (c < 0x20) 3619 { 3620 foreach (i, k; "\n\r\t\a\b\f\v\0") 3621 { 3622 if (c == k) 3623 { 3624 put(w, '\\'); 3625 put(w, "nrtabfv0"[i]); 3626 return; 3627 } 3628 } 3629 } 3630 fmt = "\\x%02X"; 3631 } 3632 else if (c <= 0xFFFF) 3633 fmt = "\\u%04X"; 3634 else 3635 fmt = "\\U%08X"; 3636 3637 formattedWrite(w, fmt, cast(uint) c); 3638 } 3639 3640 // undocumented because of deprecation 3641 // string elements are formatted like UTF-8 string literals. 3642 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 3643 if (is(StringTypeOf!T) && !is(T == enum)) 3644 { 3645 import std.array : appender; 3646 import std.utf : decode, UTFException; 3647 3648 StringTypeOf!T str = val; // https://issues.dlang.org/show_bug.cgi?id=8015 3649 3650 if (f.spec == 's') 3651 { 3652 try 3653 { 3654 // ignore other specifications and quote 3655 for (size_t i = 0; i < str.length; ) 3656 { 3657 auto c = decode(str, i); 3658 // \uFFFE and \uFFFF are considered valid by isValidDchar, 3659 // so need checking for interchange. 3660 if (c == 0xFFFE || c == 0xFFFF) 3661 goto LinvalidSeq; 3662 } 3663 put(w, '\"'); 3664 for (size_t i = 0; i < str.length; ) 3665 { 3666 auto c = decode(str, i); 3667 formatChar(w, c, '"'); 3668 } 3669 put(w, '\"'); 3670 return; 3671 } 3672 catch (UTFException) 3673 { 3674 } 3675 3676 // If val contains invalid UTF sequence, formatted like HexString literal 3677 LinvalidSeq: 3678 static if (is(typeof(str[0]) : const(char))) 3679 { 3680 enum postfix = 'c'; 3681 alias IntArr = const(ubyte)[]; 3682 } 3683 else static if (is(typeof(str[0]) : const(wchar))) 3684 { 3685 enum postfix = 'w'; 3686 alias IntArr = const(ushort)[]; 3687 } 3688 else static if (is(typeof(str[0]) : const(dchar))) 3689 { 3690 enum postfix = 'd'; 3691 alias IntArr = const(uint)[]; 3692 } 3693 formattedWrite(w, "x\"%(%02X %)\"%s", cast(IntArr) str, postfix); 3694 } 3695 else 3696 formatValue(w, str, f); 3697 } 3698 3699 @safe pure unittest 3700 { 3701 import std.array : appender; 3702 auto w = appender!string(); 3703 auto spec = singleSpec("%s"); 3704 formatElement(w, "Hello World", spec); 3705 3706 assert(w.data == "\"Hello World\""); 3707 } 3708 3709 @safe unittest 3710 { 3711 // Test for bug 8015 3712 import std.typecons; 3713 3714 struct MyStruct { 3715 string str; 3716 @property string toStr() { 3717 return str; 3718 } 3719 alias toStr this; 3720 } 3721 3722 Tuple!(MyStruct) t; 3723 } 3724 3725 // undocumented because of deprecation 3726 // Character elements are formatted like UTF-8 character literals. 3727 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 3728 if (is(CharTypeOf!T) && !is(T == enum)) 3729 { 3730 if (f.spec == 's') 3731 { 3732 put(w, '\''); 3733 formatChar(w, val, '\''); 3734 put(w, '\''); 3735 } 3736 else 3737 formatValue(w, val, f); 3738 } 3739 3740 /// 3741 @safe unittest 3742 { 3743 import std.array : appender; 3744 auto w = appender!string(); 3745 auto spec = singleSpec("%s"); 3746 formatElement(w, "H", spec); 3747 3748 assert(w.data == "\"H\"", w.data); 3749 } 3750 3751 // undocumented 3752 // Maybe T is noncopyable struct, so receive it by 'auto ref'. 3753 void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f) 3754 if (!is(StringTypeOf!T) && !is(CharTypeOf!T) || is(T == enum)) 3755 { 3756 formatValue(w, val, f); 3757 } 3758 3759 /* 3760 Associative arrays are formatted by using `':'` and $(D ", ") as 3761 separators, and enclosed by `'['` and `']'`. 3762 */ 3763 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T obj, scope const ref FormatSpec!Char f) 3764 if (is(AssocArrayTypeOf!T) && !is(T == enum) && !hasToString!(T, Char)) 3765 { 3766 AssocArrayTypeOf!T val = obj; 3767 const spec = f.spec; 3768 3769 enforceFmt(spec == 's' || spec == '(', 3770 "incompatible format character for associative array argument: %" ~ spec); 3771 3772 enum const(Char)[] defSpec = "%s" ~ f.keySeparator ~ "%s" ~ f.seqSeparator; 3773 auto fmtSpec = spec == '(' ? f.nested : defSpec; 3774 3775 size_t i = 0; 3776 immutable end = val.length; 3777 3778 if (spec == 's') 3779 put(w, f.seqBefore); 3780 foreach (k, ref v; val) 3781 { 3782 auto fmt = FormatSpec!Char(fmtSpec); 3783 fmt.writeUpToNextSpec(w); 3784 if (f.flDash) 3785 { 3786 formatValue(w, k, fmt); 3787 fmt.writeUpToNextSpec(w); 3788 formatValue(w, v, fmt); 3789 } 3790 else 3791 { 3792 formatElement(w, k, fmt); 3793 fmt.writeUpToNextSpec(w); 3794 formatElement(w, v, fmt); 3795 } 3796 if (f.sep !is null) 3797 { 3798 fmt.writeUpToNextSpec(w); 3799 if (++i != end) 3800 put(w, f.sep); 3801 } 3802 else 3803 { 3804 if (++i != end) 3805 fmt.writeUpToNextSpec(w); 3806 } 3807 } 3808 if (spec == 's') 3809 put(w, f.seqAfter); 3810 } 3811 3812 @safe unittest 3813 { 3814 assert(collectExceptionMsg!FormatException(format("%d", [0:1])).back == 'd'); 3815 3816 int[string] aa0; 3817 formatTest( aa0, `[]` ); 3818 3819 // elements escaping 3820 formatTest( ["aaa":1, "bbb":2], 3821 [`["aaa":1, "bbb":2]`, `["bbb":2, "aaa":1]`] ); 3822 formatTest( ['c':"str"], 3823 `['c':"str"]` ); 3824 formatTest( ['"':"\"", '\'':"'"], 3825 [`['"':"\"", '\'':"'"]`, `['\'':"'", '"':"\""]`] ); 3826 3827 // range formatting for AA 3828 auto aa3 = [1:"hello", 2:"world"]; 3829 // escape 3830 formatTest( "{%(%s:%s $ %)}", aa3, 3831 [`{1:"hello" $ 2:"world"}`, `{2:"world" $ 1:"hello"}`]); 3832 // use range formatting for key and value, and use %| 3833 formatTest( "{%([%04d->%(%c.%)]%| $ %)}", aa3, 3834 [`{[0001->h.e.l.l.o] $ [0002->w.o.r.l.d]}`, `{[0002->w.o.r.l.d] $ [0001->h.e.l.l.o]}`] ); 3835 3836 // https://issues.dlang.org/show_bug.cgi?id=12135 3837 formatTest("%(%s:<%s>%|,%)", [1:2], "1:<2>"); 3838 formatTest("%(%s:<%s>%|%)" , [1:2], "1:<2>"); 3839 } 3840 3841 @system unittest 3842 { 3843 class C1 { int[char] val; alias val this; this(int[char] v){ val = v; } } 3844 class C2 { int[char] val; alias val this; this(int[char] v){ val = v; } 3845 override string toString() const { return "C"; } } 3846 formatTest( new C1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] ); 3847 formatTest( new C2(['c':1, 'd':2]), "C" ); 3848 3849 struct S1 { int[char] val; alias val this; } 3850 struct S2 { int[char] val; alias val this; 3851 string toString() const { return "S"; } } 3852 formatTest( S1(['c':1, 'd':2]), [`['c':1, 'd':2]`, `['d':2, 'c':1]`] ); 3853 formatTest( S2(['c':1, 'd':2]), "S" ); 3854 } 3855 3856 // https://issues.dlang.org/show_bug.cgi?id=8921 3857 @safe unittest 3858 { 3859 enum E : char { A = 'a', B = 'b', C = 'c' } 3860 E[3] e = [E.A, E.B, E.C]; 3861 formatTest(e, "[A, B, C]"); 3862 3863 E[] e2 = [E.A, E.B, E.C]; 3864 formatTest(e2, "[A, B, C]"); 3865 } 3866 3867 private enum HasToStringResult 3868 { 3869 none, 3870 hasSomeToString, 3871 constCharSink, 3872 constCharSinkFormatString, 3873 constCharSinkFormatSpec, 3874 customPutWriter, 3875 customPutWriterFormatSpec, 3876 } 3877 3878 private template hasToString(T, Char) 3879 { 3880 static if (isPointer!T) 3881 { 3882 // X* does not have toString, even if X is aggregate type has toString. 3883 enum hasToString = HasToStringResult.none; 3884 } 3885 else static if (is(typeof( 3886 {T val = void; 3887 const FormatSpec!Char f; 3888 static struct S {void put(scope Char s){}} 3889 S s; 3890 val.toString(s, f); 3891 // force toString to take parameters by ref 3892 static assert(!__traits(compiles, val.toString(s, FormatSpec!Char()))); 3893 static assert(!__traits(compiles, val.toString(S(), f)));} 3894 ))) 3895 { 3896 enum hasToString = HasToStringResult.customPutWriterFormatSpec; 3897 } 3898 else static if (is(typeof( 3899 {T val = void; 3900 static struct S {void put(scope Char s){}} 3901 S s; 3902 val.toString(s); 3903 // force toString to take parameters by ref 3904 static assert(!__traits(compiles, val.toString(S())));} 3905 ))) 3906 { 3907 enum hasToString = HasToStringResult.customPutWriter; 3908 } 3909 else static if (is(typeof({ T val = void; FormatSpec!Char f; val.toString((scope const(char)[] s){}, f); }))) 3910 { 3911 enum hasToString = HasToStringResult.constCharSinkFormatSpec; 3912 } 3913 else static if (is(typeof({ T val = void; val.toString((scope const(char)[] s){}, "%s"); }))) 3914 { 3915 enum hasToString = HasToStringResult.constCharSinkFormatString; 3916 } 3917 else static if (is(typeof({ T val = void; val.toString((scope const(char)[] s){}); }))) 3918 { 3919 enum hasToString = HasToStringResult.constCharSink; 3920 } 3921 else static if (is(typeof({ T val = void; return val.toString(); }()) S) && isSomeString!S) 3922 { 3923 enum hasToString = HasToStringResult.hasSomeToString; 3924 } 3925 else 3926 { 3927 enum hasToString = HasToStringResult.none; 3928 } 3929 } 3930 3931 @safe unittest 3932 { 3933 static struct A 3934 { 3935 void toString(Writer)(ref Writer w) 3936 if (isOutputRange!(Writer, string)) 3937 {} 3938 } 3939 static struct B 3940 { 3941 void toString(scope void delegate(scope const(char)[]) sink, scope FormatSpec!char fmt) {} 3942 } 3943 static struct C 3944 { 3945 void toString(scope void delegate(scope const(char)[]) sink, string fmt) {} 3946 } 3947 static struct D 3948 { 3949 void toString(scope void delegate(scope const(char)[]) sink) {} 3950 } 3951 static struct E 3952 { 3953 string toString() {return "";} 3954 } 3955 static struct F 3956 { 3957 void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) 3958 if (isOutputRange!(Writer, string)) 3959 {} 3960 } 3961 static struct G 3962 { 3963 string toString() {return "";} 3964 void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {} 3965 } 3966 static struct H 3967 { 3968 string toString() {return "";} 3969 void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) 3970 if (isOutputRange!(Writer, string)) 3971 {} 3972 } 3973 static struct I 3974 { 3975 void toString(Writer)(ref Writer w) if (isOutputRange!(Writer, string)) {} 3976 void toString(Writer)(ref Writer w, scope const ref FormatSpec!char fmt) 3977 if (isOutputRange!(Writer, string)) 3978 {} 3979 } 3980 static struct J 3981 { 3982 string toString() {return "";} 3983 void toString(Writer)(ref Writer w, scope ref FormatSpec!char fmt) 3984 if (isOutputRange!(Writer, string)) 3985 {} 3986 } 3987 static struct K 3988 { 3989 void toString(Writer)(Writer w, scope const ref FormatSpec!char fmt) 3990 if (isOutputRange!(Writer, string)) 3991 {} 3992 } 3993 static struct L 3994 { 3995 void toString(Writer)(ref Writer w, scope const FormatSpec!char fmt) 3996 if (isOutputRange!(Writer, string)) 3997 {} 3998 } 3999 4000 with(HasToStringResult) 4001 { 4002 static assert(hasToString!(A, char) == customPutWriter); 4003 static assert(hasToString!(B, char) == constCharSinkFormatSpec); 4004 static assert(hasToString!(C, char) == constCharSinkFormatString); 4005 static assert(hasToString!(D, char) == constCharSink); 4006 static assert(hasToString!(E, char) == hasSomeToString); 4007 static assert(hasToString!(F, char) == customPutWriterFormatSpec); 4008 static assert(hasToString!(G, char) == customPutWriter); 4009 static assert(hasToString!(H, char) == customPutWriterFormatSpec); 4010 static assert(hasToString!(I, char) == customPutWriterFormatSpec); 4011 static assert(hasToString!(J, char) == hasSomeToString); 4012 static assert(hasToString!(K, char) == constCharSinkFormatSpec); 4013 static assert(hasToString!(L, char) == none); 4014 } 4015 } 4016 4017 // Like NullSink, but toString() isn't even called at all. Used to test the format string. 4018 private struct NoOpSink 4019 { 4020 void put(E)(scope const E) pure @safe @nogc nothrow {} 4021 } 4022 4023 // object formatting with toString 4024 private void formatObject(Writer, T, Char)(ref Writer w, ref T val, scope const ref FormatSpec!Char f) 4025 if (hasToString!(T, Char)) 4026 { 4027 enum overload = hasToString!(T, Char); 4028 4029 enum noop = is(Writer == NoOpSink); 4030 4031 static if (overload == HasToStringResult.customPutWriterFormatSpec) 4032 { 4033 static if (!noop) val.toString(w, f); 4034 } 4035 else static if (overload == HasToStringResult.customPutWriter) 4036 { 4037 static if (!noop) val.toString(w); 4038 } 4039 else static if (overload == HasToStringResult.constCharSinkFormatSpec) 4040 { 4041 static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f); 4042 } 4043 else static if (overload == HasToStringResult.constCharSinkFormatString) 4044 { 4045 static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }, f.getCurFmtStr()); 4046 } 4047 else static if (overload == HasToStringResult.constCharSink) 4048 { 4049 static if (!noop) val.toString((scope const(char)[] s) { put(w, s); }); 4050 } 4051 else static if (overload == HasToStringResult.hasSomeToString) 4052 { 4053 static if (!noop) put(w, val.toString()); 4054 } 4055 else 4056 { 4057 static assert(0, "No way found to format " ~ T.stringof ~ " as string"); 4058 } 4059 } 4060 4061 void enforceValidFormatSpec(T, Char)(scope const ref FormatSpec!Char f) 4062 { 4063 enum overload = hasToString!(T, Char); 4064 static if ( 4065 overload != HasToStringResult.constCharSinkFormatSpec && 4066 overload != HasToStringResult.customPutWriterFormatSpec && 4067 !isInputRange!T) 4068 { 4069 enforceFmt(f.spec == 's', 4070 "Expected '%s' format specifier for type '" ~ T.stringof ~ "'"); 4071 } 4072 } 4073 4074 @system unittest 4075 { 4076 static interface IF1 { } 4077 class CIF1 : IF1 { } 4078 static struct SF1 { } 4079 static union UF1 { } 4080 static class CF1 { } 4081 4082 static interface IF2 { string toString(); } 4083 static class CIF2 : IF2 { override string toString() { return ""; } } 4084 static struct SF2 { string toString() { return ""; } } 4085 static union UF2 { string toString() { return ""; } } 4086 static class CF2 { override string toString() { return ""; } } 4087 4088 static interface IK1 { void toString(scope void delegate(scope const(char)[]) sink, 4089 FormatSpec!char) const; } 4090 static class CIK1 : IK1 { override void toString(scope void delegate(scope const(char)[]) sink, 4091 FormatSpec!char) const { sink("CIK1"); } } 4092 static struct KS1 { void toString(scope void delegate(scope const(char)[]) sink, 4093 FormatSpec!char) const { sink("KS1"); } } 4094 4095 static union KU1 { void toString(scope void delegate(scope const(char)[]) sink, 4096 FormatSpec!char) const { sink("KU1"); } } 4097 4098 static class KC1 { void toString(scope void delegate(scope const(char)[]) sink, 4099 FormatSpec!char) const { sink("KC1"); } } 4100 4101 IF1 cif1 = new CIF1; 4102 assertThrown!FormatException(format("%f", cif1)); 4103 assertThrown!FormatException(format("%f", SF1())); 4104 assertThrown!FormatException(format("%f", UF1())); 4105 assertThrown!FormatException(format("%f", new CF1())); 4106 4107 IF2 cif2 = new CIF2; 4108 assertThrown!FormatException(format("%f", cif2)); 4109 assertThrown!FormatException(format("%f", SF2())); 4110 assertThrown!FormatException(format("%f", UF2())); 4111 assertThrown!FormatException(format("%f", new CF2())); 4112 4113 IK1 cik1 = new CIK1; 4114 assert(format("%f", cik1) == "CIK1"); 4115 assert(format("%f", KS1()) == "KS1"); 4116 assert(format("%f", KU1()) == "KU1"); 4117 assert(format("%f", new KC1()) == "KC1"); 4118 } 4119 4120 /* 4121 Aggregates 4122 */ 4123 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 4124 if (is(T == class) && !is(T == enum)) 4125 { 4126 enforceValidFormatSpec!(T, Char)(f); 4127 4128 // TODO: remove this check once `@disable override` deprecation cycle is finished 4129 static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString)) 4130 static assert(!__traits(isDisabled, T.toString), T.stringof ~ 4131 " cannot be formatted because its `toString` is marked with `@disable`"); 4132 4133 if (val is null) 4134 put(w, "null"); 4135 else 4136 { 4137 import std.algorithm.comparison : among; 4138 enum overload = hasToString!(T, Char); 4139 with(HasToStringResult) 4140 static if ((is(T == immutable) || is(T == const) || is(T == shared)) && overload == none) 4141 { 4142 // Remove this when Object gets const toString 4143 // https://issues.dlang.org/show_bug.cgi?id=7879 4144 static if (is(T == immutable)) 4145 put(w, "immutable("); 4146 else static if (is(T == const)) 4147 put(w, "const("); 4148 else static if (is(T == shared)) 4149 put(w, "shared("); 4150 4151 put(w, typeid(Unqual!T).name); 4152 put(w, ')'); 4153 } 4154 else static if (overload.among(constCharSink, constCharSinkFormatString, constCharSinkFormatSpec) || 4155 (!isInputRange!T && !is(BuiltinTypeOf!T))) 4156 { 4157 formatObject!(Writer, T, Char)(w, val, f); 4158 } 4159 else 4160 { 4161 //string delegate() dg = &val.toString; 4162 Object o = val; // workaround 4163 string delegate() dg = &o.toString; 4164 scope Object object = new Object(); 4165 if (dg.funcptr != (&object.toString).funcptr) // toString is overridden 4166 { 4167 formatObject(w, val, f); 4168 } 4169 else static if (isInputRange!T) 4170 { 4171 formatRange(w, val, f); 4172 } 4173 else static if (is(BuiltinTypeOf!T X)) 4174 { 4175 X x = val; 4176 formatValueImpl(w, x, f); 4177 } 4178 else 4179 { 4180 formatObject(w, val, f); 4181 } 4182 } 4183 } 4184 } 4185 4186 @system unittest 4187 { 4188 import std.array : appender; 4189 import std.range.interfaces; 4190 // class range (https://issues.dlang.org/show_bug.cgi?id=5154) 4191 auto c = inputRangeObject([1,2,3,4]); 4192 formatTest( c, "[1, 2, 3, 4]" ); 4193 assert(c.empty); 4194 c = null; 4195 formatTest( c, "null" ); 4196 } 4197 4198 @system unittest 4199 { 4200 // https://issues.dlang.org/show_bug.cgi?id=5354 4201 // If the class has both range I/F and custom toString, the use of custom 4202 // toString routine is prioritized. 4203 4204 // Enable the use of custom toString that gets a sink delegate 4205 // for class formatting. 4206 4207 enum inputRangeCode = 4208 q{ 4209 int[] arr; 4210 this(int[] a){ arr = a; } 4211 @property int front() const { return arr[0]; } 4212 @property bool empty() const { return arr.length == 0; } 4213 void popFront(){ arr = arr[1..$]; } 4214 }; 4215 4216 class C1 4217 { 4218 mixin(inputRangeCode); 4219 void toString(scope void delegate(scope const(char)[]) dg, 4220 scope const ref FormatSpec!char f) const 4221 { 4222 dg("[012]"); 4223 } 4224 } 4225 class C2 4226 { 4227 mixin(inputRangeCode); 4228 void toString(scope void delegate(const(char)[]) dg, string f) const { dg("[012]"); } 4229 } 4230 class C3 4231 { 4232 mixin(inputRangeCode); 4233 void toString(scope void delegate(const(char)[]) dg) const { dg("[012]"); } 4234 } 4235 class C4 4236 { 4237 mixin(inputRangeCode); 4238 override string toString() const { return "[012]"; } 4239 } 4240 class C5 4241 { 4242 mixin(inputRangeCode); 4243 } 4244 4245 formatTest( new C1([0, 1, 2]), "[012]" ); 4246 formatTest( new C2([0, 1, 2]), "[012]" ); 4247 formatTest( new C3([0, 1, 2]), "[012]" ); 4248 formatTest( new C4([0, 1, 2]), "[012]" ); 4249 formatTest( new C5([0, 1, 2]), "[0, 1, 2]" ); 4250 } 4251 4252 // outside the unittest block, otherwise the FQN of the 4253 // class contains the line number of the unittest 4254 version (StdUnittest) 4255 { 4256 private class C {} 4257 } 4258 4259 // https://issues.dlang.org/show_bug.cgi?id=7879 4260 @safe unittest 4261 { 4262 const(C) c; 4263 auto s = format("%s", c); 4264 assert(s == "null"); 4265 4266 immutable(C) c2 = new C(); 4267 s = format("%s", c2); 4268 assert(s == "immutable(std.format.C)"); 4269 4270 const(C) c3 = new C(); 4271 s = format("%s", c3); 4272 assert(s == "const(std.format.C)"); 4273 4274 shared(C) c4 = new C(); 4275 s = format("%s", c4); 4276 assert(s == "shared(std.format.C)"); 4277 } 4278 4279 // https://issues.dlang.org/show_bug.cgi?id=19003 4280 @safe unittest 4281 { 4282 struct S 4283 { 4284 int i; 4285 4286 @disable this(); 4287 4288 invariant { assert(this.i); } 4289 4290 this(int i) @safe in { assert(i); } do { this.i = i; } 4291 4292 string toString() { return "S"; } 4293 } 4294 4295 S s = S(1); 4296 4297 format!"%s"(s); 4298 } 4299 4300 // https://issues.dlang.org/show_bug.cgi?id=20218 4301 @safe pure unittest 4302 { 4303 void notCalled() 4304 { 4305 import std.range : repeat; 4306 4307 auto value = 1.repeat; 4308 4309 // test that range is not evaluated to completion at compiletime 4310 format!"%s"(value); 4311 } 4312 } 4313 4314 // https://issues.dlang.org/show_bug.cgi?id=7879 4315 @safe unittest 4316 { 4317 class F 4318 { 4319 override string toString() const @safe 4320 { 4321 return "Foo"; 4322 } 4323 } 4324 4325 const(F) c; 4326 auto s = format("%s", c); 4327 assert(s == "null"); 4328 4329 const(F) c2 = new F(); 4330 s = format("%s", c2); 4331 assert(s == "Foo", s); 4332 } 4333 4334 // ditto 4335 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 4336 if (is(T == interface) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum)) 4337 { 4338 enforceValidFormatSpec!(T, Char)(f); 4339 if (val is null) 4340 put(w, "null"); 4341 else 4342 { 4343 static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString)) 4344 static assert(!__traits(isDisabled, T.toString), T.stringof ~ 4345 " cannot be formatted because its `toString` is marked with `@disable`"); 4346 4347 static if (hasToString!(T, Char) != HasToStringResult.none) 4348 { 4349 formatObject(w, val, f); 4350 } 4351 else static if (isInputRange!T) 4352 { 4353 formatRange(w, val, f); 4354 } 4355 else 4356 { 4357 version (Windows) 4358 { 4359 import core.sys.windows.com : IUnknown; 4360 static if (is(T : IUnknown)) 4361 { 4362 formatValueImpl(w, *cast(void**)&val, f); 4363 } 4364 else 4365 { 4366 formatValueImpl(w, cast(Object) val, f); 4367 } 4368 } 4369 else 4370 { 4371 formatValueImpl(w, cast(Object) val, f); 4372 } 4373 } 4374 } 4375 } 4376 4377 @system unittest 4378 { 4379 // interface 4380 import std.range.interfaces; 4381 InputRange!int i = inputRangeObject([1,2,3,4]); 4382 formatTest( i, "[1, 2, 3, 4]" ); 4383 assert(i.empty); 4384 i = null; 4385 formatTest( i, "null" ); 4386 4387 // interface (downcast to Object) 4388 interface Whatever {} 4389 class C : Whatever 4390 { 4391 override @property string toString() const { return "ab"; } 4392 } 4393 Whatever val = new C; 4394 formatTest( val, "ab" ); 4395 4396 // https://issues.dlang.org/show_bug.cgi?id=11175 4397 version (Windows) 4398 { 4399 import core.sys.windows.com : IUnknown, IID; 4400 import core.sys.windows.windef : HRESULT; 4401 4402 interface IUnknown2 : IUnknown { } 4403 4404 class D : IUnknown2 4405 { 4406 extern(Windows) HRESULT QueryInterface(const(IID)* riid, void** pvObject) { return typeof(return).init; } 4407 extern(Windows) uint AddRef() { return 0; } 4408 extern(Windows) uint Release() { return 0; } 4409 } 4410 4411 IUnknown2 d = new D; 4412 string expected = format("%X", cast(void*) d); 4413 formatTest(d, expected); 4414 } 4415 } 4416 4417 /// ditto 4418 // Maybe T is noncopyable struct, so receive it by 'auto ref'. 4419 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f) 4420 if ((is(T == struct) || is(T == union)) && (hasToString!(T, Char) || !is(BuiltinTypeOf!T)) && !is(T == enum)) 4421 { 4422 4423 static if (__traits(hasMember, T, "toString") && isSomeFunction!(val.toString)) 4424 static assert(!__traits(isDisabled, T.toString), T.stringof ~ 4425 " cannot be formatted because its `toString` is marked with `@disable`"); 4426 4427 enforceValidFormatSpec!(T, Char)(f); 4428 static if (hasToString!(T, Char)) 4429 { 4430 formatObject(w, val, f); 4431 } 4432 else static if (isInputRange!T) 4433 { 4434 formatRange(w, val, f); 4435 } 4436 else static if (is(T == struct)) 4437 { 4438 enum left = T.stringof~"("; 4439 enum separator = ", "; 4440 enum right = ")"; 4441 4442 put(w, left); 4443 foreach (i, e; val.tupleof) 4444 { 4445 static if (__traits(identifier, val.tupleof[i]) == "this") 4446 continue; 4447 else static if (0 < i && val.tupleof[i-1].offsetof == val.tupleof[i].offsetof) 4448 { 4449 static if (i == val.tupleof.length - 1 || val.tupleof[i].offsetof != val.tupleof[i+1].offsetof) 4450 put(w, separator~val.tupleof[i].stringof[4..$]~"}"); 4451 else 4452 put(w, separator~val.tupleof[i].stringof[4..$]); 4453 } 4454 else static if (i+1 < val.tupleof.length && val.tupleof[i].offsetof == val.tupleof[i+1].offsetof) 4455 put(w, (i > 0 ? separator : "")~"#{overlap "~val.tupleof[i].stringof[4..$]); 4456 else 4457 { 4458 static if (i > 0) 4459 put(w, separator); 4460 formatElement(w, e, f); 4461 } 4462 } 4463 put(w, right); 4464 } 4465 else 4466 { 4467 put(w, T.stringof); 4468 } 4469 } 4470 4471 // https://issues.dlang.org/show_bug.cgi?id=9588 4472 @safe pure unittest 4473 { 4474 struct S { int x; bool empty() { return false; } } 4475 formatTest( S(), "S(0)" ); 4476 } 4477 4478 // https://issues.dlang.org/show_bug.cgi?id=4638 4479 @safe unittest 4480 { 4481 struct U8 { string toString() const { return "blah"; } } 4482 struct U16 { wstring toString() const { return "blah"; } } 4483 struct U32 { dstring toString() const { return "blah"; } } 4484 formatTest( U8(), "blah" ); 4485 formatTest( U16(), "blah" ); 4486 formatTest( U32(), "blah" ); 4487 } 4488 4489 // https://issues.dlang.org/show_bug.cgi?id=3890 4490 @safe unittest 4491 { 4492 struct Int{ int n; } 4493 struct Pair{ string s; Int i; } 4494 formatTest( Pair("hello", Int(5)), 4495 `Pair("hello", Int(5))` ); 4496 } 4497 4498 @system unittest 4499 { 4500 // union formatting without toString 4501 union U1 4502 { 4503 int n; 4504 string s; 4505 } 4506 U1 u1; 4507 formatTest( u1, "U1" ); 4508 4509 // union formatting with toString 4510 union U2 4511 { 4512 int n; 4513 string s; 4514 string toString() const { return s; } 4515 } 4516 U2 u2; 4517 u2.s = "hello"; 4518 formatTest( u2, "hello" ); 4519 } 4520 4521 @system unittest 4522 { 4523 import std.array; 4524 // https://issues.dlang.org/show_bug.cgi?id=7230 4525 static struct Bug7230 4526 { 4527 string s = "hello"; 4528 union { 4529 string a; 4530 int b; 4531 double c; 4532 } 4533 long x = 10; 4534 } 4535 4536 Bug7230 bug; 4537 bug.b = 123; 4538 4539 FormatSpec!char f; 4540 auto w = appender!(char[])(); 4541 formatValue(w, bug, f); 4542 assert(w.data == `Bug7230("hello", #{overlap a, b, c}, 10)`); 4543 } 4544 4545 @safe unittest 4546 { 4547 import std.array : appender; 4548 static struct S{ @disable this(this); } 4549 S s; 4550 4551 FormatSpec!char f; 4552 auto w = appender!string(); 4553 formatValue(w, s, f); 4554 assert(w.data == "S()"); 4555 } 4556 4557 @safe unittest 4558 { 4559 //struct Foo { @disable string toString(); } 4560 //Foo foo; 4561 4562 interface Bar { @disable string toString(); } 4563 Bar bar; 4564 4565 import std.array : appender; 4566 auto w = appender!(char[])(); 4567 FormatSpec!char f; 4568 4569 // NOTE: structs cant be tested : the assertion is correct so compilation 4570 // continues and fails when trying to link the unimplemented toString. 4571 //static assert(!__traits(compiles, formatValue(w, foo, f))); 4572 static assert(!__traits(compiles, formatValue(w, bar, f))); 4573 } 4574 4575 /* 4576 `enum`s are formatted like their base value 4577 */ 4578 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 4579 if (is(T == enum)) 4580 { 4581 if (f.spec == 's') 4582 { 4583 foreach (i, e; EnumMembers!T) 4584 { 4585 if (val == e) 4586 { 4587 formatValueImpl(w, __traits(allMembers, T)[i], f); 4588 return; 4589 } 4590 } 4591 4592 import std.array : appender; 4593 auto w2 = appender!string(); 4594 4595 // val is not a member of T, output cast(T) rawValue instead. 4596 put(w2, "cast(" ~ T.stringof ~ ")"); 4597 static assert(!is(OriginalType!T == T), "OriginalType!" ~ T.stringof ~ 4598 "must not be equal to " ~ T.stringof); 4599 4600 FormatSpec!Char f2 = f; 4601 f2.width = 0; 4602 formatValueImpl(w2, cast(OriginalType!T) val, f2); 4603 writeAligned(w, w2.data, f); 4604 return; 4605 } 4606 formatValueImpl(w, cast(OriginalType!T) val, f); 4607 } 4608 4609 @safe unittest 4610 { 4611 enum A { first, second, third } 4612 formatTest( A.second, "second" ); 4613 formatTest( cast(A) 72, "cast(A)72" ); 4614 } 4615 @safe unittest 4616 { 4617 enum A : string { one = "uno", two = "dos", three = "tres" } 4618 formatTest( A.three, "three" ); 4619 formatTest( cast(A)"mill\ón", "cast(A)mill\ón" ); 4620 } 4621 @safe unittest 4622 { 4623 enum A : bool { no, yes } 4624 formatTest( A.yes, "yes" ); 4625 formatTest( A.no, "no" ); 4626 } 4627 @safe unittest 4628 { 4629 // Test for bug 6892 4630 enum Foo { A = 10 } 4631 formatTest("%s", Foo.A, "A"); 4632 formatTest(">%4s<", Foo.A, "> A<"); 4633 formatTest("%04d", Foo.A, "0010"); 4634 formatTest("%+2u", Foo.A, "+10"); 4635 formatTest("%02x", Foo.A, "0a"); 4636 formatTest("%3o", Foo.A, " 12"); 4637 formatTest("%b", Foo.A, "1010"); 4638 } 4639 4640 @safe pure unittest 4641 { 4642 enum A { one, two, three } 4643 4644 string t1 = format("[%6s] [%-6s]", A.one, A.one); 4645 assert(t1 == "[ one] [one ]"); 4646 string t2 = format("[%10s] [%-10s]", cast(A) 10, cast(A) 10); 4647 assert(t2 == "[ cast(A)" ~ "10] [cast(A)" ~ "10 ]"); // due to bug in style checker 4648 } 4649 4650 /* 4651 Pointers are formatted as hex integers. 4652 */ 4653 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T val, scope const ref FormatSpec!Char f) 4654 if (isPointer!T && !is(T == enum) && !hasToString!(T, Char)) 4655 { 4656 static if (is(typeof({ shared const void* p = val; }))) 4657 alias SharedOf(T) = shared(T); 4658 else 4659 alias SharedOf(T) = T; 4660 4661 const SharedOf!(void*) p = val; 4662 const pnum = ()@trusted{ return cast(ulong) p; }(); 4663 4664 if (f.spec == 's') 4665 { 4666 if (p is null) 4667 { 4668 writeAligned(w, "null", f); 4669 return; 4670 } 4671 FormatSpec!Char fs = f; // fs is copy for change its values. 4672 fs.spec = 'X'; 4673 formatValueImpl(w, pnum, fs); 4674 } 4675 else 4676 { 4677 enforceFmt(f.spec == 'X' || f.spec == 'x', 4678 "Expected one of %s, %x or %X for pointer type."); 4679 formatValueImpl(w, pnum, f); 4680 } 4681 } 4682 4683 @safe pure unittest 4684 { 4685 int* p; 4686 4687 string t1 = format("[%6s] [%-6s]", p, p); 4688 assert(t1 == "[ null] [null ]"); 4689 } 4690 4691 /* 4692 SIMD vectors are formatted as arrays. 4693 */ 4694 private void formatValueImpl(Writer, V, Char)(auto ref Writer w, V val, scope const ref FormatSpec!Char f) 4695 if (isSIMDVector!V) 4696 { 4697 formatValueImpl(w, val.array, f); 4698 } 4699 4700 @safe unittest 4701 { 4702 import core.simd; 4703 static if (is(float4)) 4704 { 4705 version (X86) 4706 { 4707 version (OSX) {/* https://issues.dlang.org/show_bug.cgi?id=17823 */} 4708 } 4709 else 4710 { 4711 float4 f; 4712 f.array[0] = 1; 4713 f.array[1] = 2; 4714 f.array[2] = 3; 4715 f.array[3] = 4; 4716 formatTest(f, "[1, 2, 3, 4]"); 4717 } 4718 } 4719 } 4720 4721 @safe pure unittest 4722 { 4723 int* p = null; 4724 formatTest( p, "null" ); 4725 4726 auto q = ()@trusted{ return cast(void*) 0xFFEECCAA; }(); 4727 formatTest( q, "FFEECCAA" ); 4728 } 4729 4730 // https://issues.dlang.org/show_bug.cgi?id=11782 4731 @safe pure unittest 4732 { 4733 import std.range : iota; 4734 4735 auto a = iota(0, 10); 4736 auto b = iota(0, 10); 4737 auto p = ()@trusted{ auto p = &a; return p; }(); 4738 4739 assert(format("%s",p) != format("%s",b)); 4740 } 4741 4742 @system pure unittest 4743 { 4744 // Test for https://issues.dlang.org/show_bug.cgi?id=7869 4745 struct S 4746 { 4747 string toString() const { return ""; } 4748 } 4749 S* p = null; 4750 formatTest( p, "null" ); 4751 4752 S* q = cast(S*) 0xFFEECCAA; 4753 formatTest( q, "FFEECCAA" ); 4754 } 4755 4756 // https://issues.dlang.org/show_bug.cgi?id=8186 4757 @system unittest 4758 { 4759 class B 4760 { 4761 int*a; 4762 this(){ a = new int; } 4763 alias a this; 4764 } 4765 formatTest( B.init, "null" ); 4766 } 4767 4768 // https://issues.dlang.org/show_bug.cgi?id=9336 4769 @system pure unittest 4770 { 4771 shared int i; 4772 format("%s", &i); 4773 } 4774 4775 // https://issues.dlang.org/show_bug.cgi?id=11778 4776 @system pure unittest 4777 { 4778 int* p = null; 4779 assertThrown!FormatException(format("%d", p)); 4780 assertThrown!FormatException(format("%04d", p + 2)); 4781 } 4782 4783 // https://issues.dlang.org/show_bug.cgi?id=12505 4784 @safe pure unittest 4785 { 4786 void* p = null; 4787 formatTest( "%08X", p, "00000000" ); 4788 } 4789 4790 /* 4791 Delegates are formatted by `ReturnType delegate(Parameters) FunctionAttributes` 4792 */ 4793 private void formatValueImpl(Writer, T, Char)(auto ref Writer w, scope T, scope const ref FormatSpec!Char f) 4794 if (isDelegate!T) 4795 { 4796 formatValueImpl(w, T.stringof, f); 4797 } 4798 4799 @safe unittest 4800 { 4801 void func() @system { __gshared int x; ++x; throw new Exception("msg"); } 4802 version (linux) formatTest( &func, "void delegate() @system" ); 4803 } 4804 4805 @safe pure unittest 4806 { 4807 int[] a = [ 1, 3, 2 ]; 4808 formatTest( "testing %(%s & %) embedded", a, 4809 "testing 1 & 3 & 2 embedded"); 4810 formatTest( "testing %((%s) %)) wyda3", a, 4811 "testing (1) (3) (2) wyda3" ); 4812 4813 int[0] empt = []; 4814 formatTest( "(%s)", empt, 4815 "([])" ); 4816 } 4817 4818 // Fix for https://issues.dlang.org/show_bug.cgi?id=1591 4819 private int getNthInt(string kind, A...)(uint index, A args) 4820 { 4821 return getNth!(kind, isIntegral,int)(index, args); 4822 } 4823 4824 private T getNth(string kind, alias Condition, T, A...)(uint index, A args) 4825 { 4826 import std.conv : text, to; 4827 4828 switch (index) 4829 { 4830 foreach (n, _; A) 4831 { 4832 case n: 4833 static if (Condition!(typeof(args[n]))) 4834 { 4835 return to!T(args[n]); 4836 } 4837 else 4838 { 4839 throw new FormatException( 4840 text(kind, " expected, not ", typeof(args[n]).stringof, 4841 " for argument #", index + 1)); 4842 } 4843 } 4844 default: 4845 throw new FormatException( 4846 text("Missing ", kind, " argument")); 4847 } 4848 } 4849 4850 @safe unittest 4851 { 4852 // width/precision 4853 assert(collectExceptionMsg!FormatException(format("%*.d", 5.1, 2)) 4854 == "integer width expected, not double for argument #1"); 4855 assert(collectExceptionMsg!FormatException(format("%-1*.d", 5.1, 2)) 4856 == "integer width expected, not double for argument #1"); 4857 4858 assert(collectExceptionMsg!FormatException(format("%.*d", '5', 2)) 4859 == "integer precision expected, not char for argument #1"); 4860 assert(collectExceptionMsg!FormatException(format("%-1.*d", 4.7, 3)) 4861 == "integer precision expected, not double for argument #1"); 4862 assert(collectExceptionMsg!FormatException(format("%.*d", 5)) 4863 == "Orphan format specifier: %d"); 4864 assert(collectExceptionMsg!FormatException(format("%*.*d", 5)) 4865 == "Missing integer precision argument"); 4866 4867 // separatorCharPos 4868 assert(collectExceptionMsg!FormatException(format("%,?d", 5)) 4869 == "separator character expected, not int for argument #1"); 4870 assert(collectExceptionMsg!FormatException(format("%,?d", '?')) 4871 == "Orphan format specifier: %d"); 4872 assert(collectExceptionMsg!FormatException(format("%.*,*?d", 5)) 4873 == "Missing separator digit width argument"); 4874 } 4875 4876 /* ======================== Unit Tests ====================================== */ 4877 4878 version (StdUnittest) 4879 private void formatTest(T)(T val, string expected, size_t ln = __LINE__, string fn = __FILE__) 4880 { 4881 import core.exception : AssertError; 4882 import std.array : appender; 4883 import std.conv : text; 4884 FormatSpec!char f; 4885 auto w = appender!string(); 4886 formatValue(w, val, f); 4887 enforce!AssertError( 4888 w.data == expected, 4889 text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln); 4890 } 4891 4892 version (StdUnittest) 4893 private void formatTest(T)(string fmt, T val, string expected, size_t ln = __LINE__, string fn = __FILE__) @safe 4894 { 4895 import core.exception : AssertError; 4896 import std.array : appender; 4897 import std.conv : text; 4898 auto w = appender!string(); 4899 formattedWrite(w, fmt, val); 4900 enforce!AssertError( 4901 w.data == expected, 4902 text("expected = `", expected, "`, result = `", w.data, "`"), fn, ln); 4903 } 4904 4905 version (StdUnittest) 4906 private void formatTest(T)(T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) 4907 { 4908 import core.exception : AssertError; 4909 import std.array : appender; 4910 import std.conv : text; 4911 FormatSpec!char f; 4912 auto w = appender!string(); 4913 formatValue(w, val, f); 4914 foreach (cur; expected) 4915 { 4916 if (w.data == cur) return; 4917 } 4918 enforce!AssertError( 4919 false, 4920 text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln); 4921 } 4922 4923 version (StdUnittest) 4924 private void formatTest(T)(string fmt, T val, string[] expected, size_t ln = __LINE__, string fn = __FILE__) @safe 4925 { 4926 import core.exception : AssertError; 4927 import std.array : appender; 4928 import std.conv : text; 4929 auto w = appender!string(); 4930 formattedWrite(w, fmt, val); 4931 foreach (cur; expected) 4932 { 4933 if (w.data == cur) return; 4934 } 4935 enforce!AssertError( 4936 false, 4937 text("expected one of `", expected, "`, result = `", w.data, "`"), fn, ln); 4938 } 4939 4940 @safe /*pure*/ unittest // formatting floating point values is now impure 4941 { 4942 import std.array; 4943 4944 auto stream = appender!string(); 4945 formattedWrite(stream, "%s", 1.1); 4946 assert(stream.data == "1.1", stream.data); 4947 } 4948 4949 @safe pure unittest 4950 { 4951 import std.algorithm; 4952 import std.array; 4953 4954 auto stream = appender!string(); 4955 formattedWrite(stream, "%s", map!"a*a"([2, 3, 5])); 4956 assert(stream.data == "[4, 9, 25]", stream.data); 4957 4958 // Test shared data. 4959 stream = appender!string(); 4960 shared int s = 6; 4961 formattedWrite(stream, "%s", s); 4962 assert(stream.data == "6"); 4963 } 4964 4965 @safe pure unittest 4966 { 4967 import std.array; 4968 auto stream = appender!string(); 4969 formattedWrite(stream, "%u", 42); 4970 assert(stream.data == "42", stream.data); 4971 } 4972 4973 @safe pure unittest 4974 { 4975 // testing raw writes 4976 import std.array; 4977 auto w = appender!(char[])(); 4978 uint a = 0x02030405; 4979 formattedWrite(w, "%+r", a); 4980 assert(w.data.length == 4 && w.data[0] == 2 && w.data[1] == 3 4981 && w.data[2] == 4 && w.data[3] == 5); 4982 w.clear(); 4983 formattedWrite(w, "%-r", a); 4984 assert(w.data.length == 4 && w.data[0] == 5 && w.data[1] == 4 4985 && w.data[2] == 3 && w.data[3] == 2); 4986 } 4987 4988 @safe pure unittest 4989 { 4990 // testing positional parameters 4991 import std.array; 4992 auto w = appender!(char[])(); 4993 formattedWrite(w, 4994 "Numbers %2$s and %1$s are reversed and %1$s%2$s repeated", 4995 42, 0); 4996 assert(w.data == "Numbers 0 and 42 are reversed and 420 repeated", 4997 w.data); 4998 assert(collectExceptionMsg!FormatException(formattedWrite(w, "%1$s, %3$s", 1, 2)) 4999 == "Positional specifier %3$s index exceeds 2"); 5000 5001 w.clear(); 5002 formattedWrite(w, "asd%s", 23); 5003 assert(w.data == "asd23", w.data); 5004 w.clear(); 5005 formattedWrite(w, "%s%s", 23, 45); 5006 assert(w.data == "2345", w.data); 5007 } 5008 5009 @safe unittest 5010 { 5011 import core.stdc..string : strlen; 5012 import std.array : appender; 5013 import std.conv : text, octal; 5014 import core.stdc.stdio : snprintf; 5015 5016 debug(format) printf("std.format.format.unittest\n"); 5017 5018 auto stream = appender!(char[])(); 5019 //goto here; 5020 5021 formattedWrite(stream, 5022 "hello world! %s %s ", true, 57, 1_000_000_000, 'x', " foo"); 5023 assert(stream.data == "hello world! true 57 ", 5024 stream.data); 5025 5026 stream.clear(); 5027 formattedWrite(stream, "%g %A %s", 1.67, -1.28, float.nan); 5028 assert(stream.data == "1.67 -0X1.47AE147AE147BP+0 nan", 5029 stream.data); 5030 stream.clear(); 5031 5032 formattedWrite(stream, "%x %X", 0x1234AF, 0xAFAFAFAF); 5033 assert(stream.data == "1234af AFAFAFAF"); 5034 stream.clear(); 5035 5036 formattedWrite(stream, "%b %o", 0x1234AF, 0xAFAFAFAF); 5037 assert(stream.data == "100100011010010101111 25753727657"); 5038 stream.clear(); 5039 5040 formattedWrite(stream, "%d %s", 0x1234AF, 0xAFAFAFAF); 5041 assert(stream.data == "1193135 2947526575"); 5042 stream.clear(); 5043 5044 // formattedWrite(stream, "%s", 1.2 + 3.4i); 5045 // assert(stream.data == "1.2+3.4i"); 5046 // stream.clear(); 5047 5048 formattedWrite(stream, "%a %A", 1.32, 6.78f); 5049 //formattedWrite(stream, "%x %X", 1.32); 5050 assert(stream.data == "0x1.51eb851eb851fp+0 0X1.B1EB86P+2"); 5051 stream.clear(); 5052 5053 formattedWrite(stream, "%#06.*f",2,12.345); 5054 assert(stream.data == "012.35"); 5055 stream.clear(); 5056 5057 formattedWrite(stream, "%#0*.*f",6,2,12.345); 5058 assert(stream.data == "012.35"); 5059 stream.clear(); 5060 5061 const real constreal = 1; 5062 formattedWrite(stream, "%g",constreal); 5063 assert(stream.data == "1"); 5064 stream.clear(); 5065 5066 formattedWrite(stream, "%7.4g:", 12.678); 5067 assert(stream.data == " 12.68:"); 5068 stream.clear(); 5069 5070 formattedWrite(stream, "%7.4g:", 12.678L); 5071 assert(stream.data == " 12.68:"); 5072 stream.clear(); 5073 5074 formattedWrite(stream, "%04f|%05d|%#05x|%#5x",-4.0,-10,1,1); 5075 assert(stream.data == "-4.000000|-0010|0x001| 0x1", 5076 stream.data); 5077 stream.clear(); 5078 5079 int i; 5080 string s; 5081 5082 i = -10; 5083 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 5084 assert(stream.data == "-10|-10|-10|-10|-10.0000"); 5085 stream.clear(); 5086 5087 i = -5; 5088 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 5089 assert(stream.data == "-5| -5|-05|-5|-5.0000"); 5090 stream.clear(); 5091 5092 i = 0; 5093 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 5094 assert(stream.data == "0| 0|000|0|0.0000"); 5095 stream.clear(); 5096 5097 i = 5; 5098 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 5099 assert(stream.data == "5| 5|005|5|5.0000"); 5100 stream.clear(); 5101 5102 i = 10; 5103 formattedWrite(stream, "%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 5104 assert(stream.data == "10| 10|010|10|10.0000"); 5105 stream.clear(); 5106 5107 formattedWrite(stream, "%.0d", 0); 5108 assert(stream.data == ""); 5109 stream.clear(); 5110 5111 formattedWrite(stream, "%.g", .34); 5112 assert(stream.data == "0.3"); 5113 stream.clear(); 5114 5115 stream.clear(); formattedWrite(stream, "%.0g", .34); 5116 assert(stream.data == "0.3"); 5117 5118 stream.clear(); formattedWrite(stream, "%.2g", .34); 5119 assert(stream.data == "0.34"); 5120 5121 stream.clear(); formattedWrite(stream, "%0.0008f", 1e-08); 5122 assert(stream.data == "0.00000001"); 5123 5124 stream.clear(); formattedWrite(stream, "%0.0008f", 1e-05); 5125 assert(stream.data == "0.00001000"); 5126 5127 //return; 5128 //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); 5129 5130 s = "helloworld"; 5131 string r; 5132 stream.clear(); formattedWrite(stream, "%.2s", s[0 .. 5]); 5133 assert(stream.data == "he"); 5134 stream.clear(); formattedWrite(stream, "%.20s", s[0 .. 5]); 5135 assert(stream.data == "hello"); 5136 stream.clear(); formattedWrite(stream, "%8s", s[0 .. 5]); 5137 assert(stream.data == " hello"); 5138 5139 byte[] arrbyte = new byte[4]; 5140 arrbyte[0] = 100; 5141 arrbyte[1] = -99; 5142 arrbyte[3] = 0; 5143 stream.clear(); formattedWrite(stream, "%s", arrbyte); 5144 assert(stream.data == "[100, -99, 0, 0]", stream.data); 5145 5146 ubyte[] arrubyte = new ubyte[4]; 5147 arrubyte[0] = 100; 5148 arrubyte[1] = 200; 5149 arrubyte[3] = 0; 5150 stream.clear(); formattedWrite(stream, "%s", arrubyte); 5151 assert(stream.data == "[100, 200, 0, 0]", stream.data); 5152 5153 short[] arrshort = new short[4]; 5154 arrshort[0] = 100; 5155 arrshort[1] = -999; 5156 arrshort[3] = 0; 5157 stream.clear(); formattedWrite(stream, "%s", arrshort); 5158 assert(stream.data == "[100, -999, 0, 0]"); 5159 stream.clear(); formattedWrite(stream, "%s",arrshort); 5160 assert(stream.data == "[100, -999, 0, 0]"); 5161 5162 ushort[] arrushort = new ushort[4]; 5163 arrushort[0] = 100; 5164 arrushort[1] = 20_000; 5165 arrushort[3] = 0; 5166 stream.clear(); formattedWrite(stream, "%s", arrushort); 5167 assert(stream.data == "[100, 20000, 0, 0]"); 5168 5169 int[] arrint = new int[4]; 5170 arrint[0] = 100; 5171 arrint[1] = -999; 5172 arrint[3] = 0; 5173 stream.clear(); formattedWrite(stream, "%s", arrint); 5174 assert(stream.data == "[100, -999, 0, 0]"); 5175 stream.clear(); formattedWrite(stream, "%s",arrint); 5176 assert(stream.data == "[100, -999, 0, 0]"); 5177 5178 long[] arrlong = new long[4]; 5179 arrlong[0] = 100; 5180 arrlong[1] = -999; 5181 arrlong[3] = 0; 5182 stream.clear(); formattedWrite(stream, "%s", arrlong); 5183 assert(stream.data == "[100, -999, 0, 0]"); 5184 stream.clear(); formattedWrite(stream, "%s",arrlong); 5185 assert(stream.data == "[100, -999, 0, 0]"); 5186 5187 ulong[] arrulong = new ulong[4]; 5188 arrulong[0] = 100; 5189 arrulong[1] = 999; 5190 arrulong[3] = 0; 5191 stream.clear(); formattedWrite(stream, "%s", arrulong); 5192 assert(stream.data == "[100, 999, 0, 0]"); 5193 5194 string[] arr2 = new string[4]; 5195 arr2[0] = "hello"; 5196 arr2[1] = "world"; 5197 arr2[3] = "foo"; 5198 stream.clear(); formattedWrite(stream, "%s", arr2); 5199 assert(stream.data == `["hello", "world", "", "foo"]`, stream.data); 5200 5201 stream.clear(); formattedWrite(stream, "%.8d", 7); 5202 assert(stream.data == "00000007"); 5203 5204 stream.clear(); formattedWrite(stream, "%.8x", 10); 5205 assert(stream.data == "0000000a"); 5206 5207 stream.clear(); formattedWrite(stream, "%-3d", 7); 5208 assert(stream.data == "7 "); 5209 5210 stream.clear(); formattedWrite(stream, "%*d", -3, 7); 5211 assert(stream.data == "7 "); 5212 5213 stream.clear(); formattedWrite(stream, "%.*d", -3, 7); 5214 //writeln(stream.data); 5215 assert(stream.data == "7"); 5216 5217 stream.clear(); formattedWrite(stream, "%s", "abc"c); 5218 assert(stream.data == "abc"); 5219 stream.clear(); formattedWrite(stream, "%s", "def"w); 5220 assert(stream.data == "def", text(stream.data.length)); 5221 stream.clear(); formattedWrite(stream, "%s", "ghi"d); 5222 assert(stream.data == "ghi"); 5223 5224 here: 5225 @trusted void* deadBeef() { return cast(void*) 0xDEADBEEF; } 5226 stream.clear(); formattedWrite(stream, "%s", deadBeef()); 5227 assert(stream.data == "DEADBEEF", stream.data); 5228 5229 stream.clear(); formattedWrite(stream, "%#x", 0xabcd); 5230 assert(stream.data == "0xabcd"); 5231 stream.clear(); formattedWrite(stream, "%#X", 0xABCD); 5232 assert(stream.data == "0XABCD"); 5233 5234 stream.clear(); formattedWrite(stream, "%#o", octal!12345); 5235 assert(stream.data == "012345"); 5236 stream.clear(); formattedWrite(stream, "%o", 9); 5237 assert(stream.data == "11"); 5238 5239 stream.clear(); formattedWrite(stream, "%+d", 123); 5240 assert(stream.data == "+123"); 5241 stream.clear(); formattedWrite(stream, "%+d", -123); 5242 assert(stream.data == "-123"); 5243 stream.clear(); formattedWrite(stream, "% d", 123); 5244 assert(stream.data == " 123"); 5245 stream.clear(); formattedWrite(stream, "% d", -123); 5246 assert(stream.data == "-123"); 5247 5248 stream.clear(); formattedWrite(stream, "%%"); 5249 assert(stream.data == "%"); 5250 5251 stream.clear(); formattedWrite(stream, "%d", true); 5252 assert(stream.data == "1"); 5253 stream.clear(); formattedWrite(stream, "%d", false); 5254 assert(stream.data == "0"); 5255 5256 stream.clear(); formattedWrite(stream, "%d", 'a'); 5257 assert(stream.data == "97", stream.data); 5258 wchar wc = 'a'; 5259 stream.clear(); formattedWrite(stream, "%d", wc); 5260 assert(stream.data == "97"); 5261 dchar dc = 'a'; 5262 stream.clear(); formattedWrite(stream, "%d", dc); 5263 assert(stream.data == "97"); 5264 5265 byte b = byte.max; 5266 stream.clear(); formattedWrite(stream, "%x", b); 5267 assert(stream.data == "7f"); 5268 stream.clear(); formattedWrite(stream, "%x", ++b); 5269 assert(stream.data == "80"); 5270 stream.clear(); formattedWrite(stream, "%x", ++b); 5271 assert(stream.data == "81"); 5272 5273 short sh = short.max; 5274 stream.clear(); formattedWrite(stream, "%x", sh); 5275 assert(stream.data == "7fff"); 5276 stream.clear(); formattedWrite(stream, "%x", ++sh); 5277 assert(stream.data == "8000"); 5278 stream.clear(); formattedWrite(stream, "%x", ++sh); 5279 assert(stream.data == "8001"); 5280 5281 i = int.max; 5282 stream.clear(); formattedWrite(stream, "%x", i); 5283 assert(stream.data == "7fffffff"); 5284 stream.clear(); formattedWrite(stream, "%x", ++i); 5285 assert(stream.data == "80000000"); 5286 stream.clear(); formattedWrite(stream, "%x", ++i); 5287 assert(stream.data == "80000001"); 5288 5289 stream.clear(); formattedWrite(stream, "%x", 10); 5290 assert(stream.data == "a"); 5291 stream.clear(); formattedWrite(stream, "%X", 10); 5292 assert(stream.data == "A"); 5293 stream.clear(); formattedWrite(stream, "%x", 15); 5294 assert(stream.data == "f"); 5295 stream.clear(); formattedWrite(stream, "%X", 15); 5296 assert(stream.data == "F"); 5297 5298 @trusted void ObjectTest() 5299 { 5300 Object c = null; 5301 stream.clear(); formattedWrite(stream, "%s", c); 5302 assert(stream.data == "null"); 5303 } 5304 ObjectTest(); 5305 5306 enum TestEnum 5307 { 5308 Value1, Value2 5309 } 5310 stream.clear(); formattedWrite(stream, "%s", TestEnum.Value2); 5311 assert(stream.data == "Value2", stream.data); 5312 stream.clear(); formattedWrite(stream, "%s", cast(TestEnum) 5); 5313 assert(stream.data == "cast(TestEnum)5", stream.data); 5314 5315 //immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 5316 //stream.clear(); formattedWrite(stream, "%s", aa.values); 5317 //core.stdc.stdio.fwrite(stream.data.ptr, stream.data.length, 1, stderr); 5318 //assert(stream.data == "[[h,e,l,l,o],[b,e,t,t,y]]"); 5319 //stream.clear(); formattedWrite(stream, "%s", aa); 5320 //assert(stream.data == "[3:[h,e,l,l,o],4:[b,e,t,t,y]]"); 5321 5322 static const dchar[] ds = ['a','b']; 5323 for (int j = 0; j < ds.length; ++j) 5324 { 5325 stream.clear(); formattedWrite(stream, " %d", ds[j]); 5326 if (j == 0) 5327 assert(stream.data == " 97"); 5328 else 5329 assert(stream.data == " 98"); 5330 } 5331 5332 stream.clear(); formattedWrite(stream, "%.-3d", 7); 5333 assert(stream.data == "7", ">" ~ stream.data ~ "<"); 5334 } 5335 5336 @safe unittest 5337 { 5338 import std.array; 5339 import std.stdio; 5340 5341 immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 5342 assert(aa[3] == "hello"); 5343 assert(aa[4] == "betty"); 5344 5345 auto stream = appender!(char[])(); 5346 alias AllNumerics = 5347 AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, 5348 float, double, real); 5349 foreach (T; AllNumerics) 5350 { 5351 T value = 1; 5352 stream.clear(); 5353 formattedWrite(stream, "%s", value); 5354 assert(stream.data == "1"); 5355 } 5356 5357 stream.clear(); 5358 formattedWrite(stream, "%s", aa); 5359 } 5360 5361 @system unittest 5362 { 5363 string s = "hello!124:34.5"; 5364 string a; 5365 int b; 5366 double c; 5367 formattedRead(s, "%s!%s:%s", &a, &b, &c); 5368 assert(a == "hello" && b == 124 && c == 34.5); 5369 } 5370 5371 version (StdUnittest) 5372 private void formatReflectTest(T)(ref T val, string fmt, string formatted, string fn = __FILE__, size_t ln = __LINE__) 5373 { 5374 import core.exception : AssertError; 5375 import std.array : appender; 5376 auto w = appender!string(); 5377 formattedWrite(w, fmt, val); 5378 5379 auto input = w.data; 5380 enforce!AssertError( 5381 input == formatted, 5382 input, fn, ln); 5383 5384 T val2; 5385 formattedRead(input, fmt, &val2); 5386 static if (isAssociativeArray!T) 5387 if (__ctfe) 5388 { 5389 alias aa1 = val; 5390 alias aa2 = val2; 5391 assert(aa1 == aa2); 5392 5393 assert(aa1.length == aa2.length); 5394 5395 assert(aa1.keys == aa2.keys); 5396 5397 assert(aa1.values == aa2.values); 5398 assert(aa1.values.length == aa2.values.length); 5399 foreach (i; 0 .. aa1.values.length) 5400 assert(aa1.values[i] == aa2.values[i]); 5401 5402 foreach (i, key; aa1.keys) 5403 assert(aa1.values[i] == aa1[key]); 5404 foreach (i, key; aa2.keys) 5405 assert(aa2.values[i] == aa2[key]); 5406 return; 5407 } 5408 enforce!AssertError( 5409 val == val2, 5410 input, fn, ln); 5411 } 5412 5413 version (StdUnittest) 5414 private void formatReflectTest(T)(ref T val, string fmt, string[] formatted, string fn = __FILE__, size_t ln = __LINE__) 5415 { 5416 import core.exception : AssertError; 5417 import std.array : appender; 5418 auto w = appender!string(); 5419 formattedWrite(w, fmt, val); 5420 5421 auto input = w.data; 5422 5423 foreach (cur; formatted) 5424 { 5425 if (input == cur) return; 5426 } 5427 enforce!AssertError( 5428 false, 5429 input, 5430 fn, 5431 ln); 5432 5433 T val2; 5434 formattedRead(input, fmt, &val2); 5435 static if (isAssociativeArray!T) 5436 if (__ctfe) 5437 { 5438 alias aa1 = val; 5439 alias aa2 = val2; 5440 assert(aa1 == aa2); 5441 5442 assert(aa1.length == aa2.length); 5443 5444 assert(aa1.keys == aa2.keys); 5445 5446 assert(aa1.values == aa2.values); 5447 assert(aa1.values.length == aa2.values.length); 5448 foreach (i; 0 .. aa1.values.length) 5449 assert(aa1.values[i] == aa2.values[i]); 5450 5451 foreach (i, key; aa1.keys) 5452 assert(aa1.values[i] == aa1[key]); 5453 foreach (i, key; aa2.keys) 5454 assert(aa2.values[i] == aa2[key]); 5455 return; 5456 } 5457 enforce!AssertError( 5458 val == val2, 5459 input, fn, ln); 5460 } 5461 5462 @system unittest 5463 { 5464 void booleanTest() 5465 { 5466 auto b = true; 5467 formatReflectTest(b, "%s", `true`); 5468 formatReflectTest(b, "%b", `1`); 5469 formatReflectTest(b, "%o", `1`); 5470 formatReflectTest(b, "%d", `1`); 5471 formatReflectTest(b, "%u", `1`); 5472 formatReflectTest(b, "%x", `1`); 5473 } 5474 5475 void integerTest() 5476 { 5477 auto n = 127; 5478 formatReflectTest(n, "%s", `127`); 5479 formatReflectTest(n, "%b", `1111111`); 5480 formatReflectTest(n, "%o", `177`); 5481 formatReflectTest(n, "%d", `127`); 5482 formatReflectTest(n, "%u", `127`); 5483 formatReflectTest(n, "%x", `7f`); 5484 } 5485 5486 void floatingTest() 5487 { 5488 auto f = 3.14; 5489 formatReflectTest(f, "%s", `3.14`); 5490 version (MinGW) 5491 formatReflectTest(f, "%e", `3.140000e+000`); 5492 else 5493 formatReflectTest(f, "%e", `3.140000e+00`); 5494 formatReflectTest(f, "%f", `3.140000`); 5495 formatReflectTest(f, "%g", `3.14`); 5496 } 5497 5498 void charTest() 5499 { 5500 auto c = 'a'; 5501 formatReflectTest(c, "%s", `a`); 5502 formatReflectTest(c, "%c", `a`); 5503 formatReflectTest(c, "%b", `1100001`); 5504 formatReflectTest(c, "%o", `141`); 5505 formatReflectTest(c, "%d", `97`); 5506 formatReflectTest(c, "%u", `97`); 5507 formatReflectTest(c, "%x", `61`); 5508 } 5509 5510 void strTest() 5511 { 5512 auto s = "hello"; 5513 formatReflectTest(s, "%s", `hello`); 5514 formatReflectTest(s, "%(%c,%)", `h,e,l,l,o`); 5515 formatReflectTest(s, "%(%s,%)", `'h','e','l','l','o'`); 5516 formatReflectTest(s, "[%(<%c>%| $ %)]", `[<h> $ <e> $ <l> $ <l> $ <o>]`); 5517 } 5518 5519 void daTest() 5520 { 5521 auto a = [1,2,3,4]; 5522 formatReflectTest(a, "%s", `[1, 2, 3, 4]`); 5523 formatReflectTest(a, "[%(%s; %)]", `[1; 2; 3; 4]`); 5524 formatReflectTest(a, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`); 5525 } 5526 5527 void saTest() 5528 { 5529 int[4] sa = [1,2,3,4]; 5530 formatReflectTest(sa, "%s", `[1, 2, 3, 4]`); 5531 formatReflectTest(sa, "[%(%s; %)]", `[1; 2; 3; 4]`); 5532 formatReflectTest(sa, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`); 5533 } 5534 5535 void aaTest() 5536 { 5537 auto aa = [1:"hello", 2:"world"]; 5538 formatReflectTest(aa, "%s", [`[1:"hello", 2:"world"]`, `[2:"world", 1:"hello"]`]); 5539 formatReflectTest(aa, "[%(%s->%s, %)]", [`[1->"hello", 2->"world"]`, `[2->"world", 1->"hello"]`]); 5540 formatReflectTest(aa, "{%([%s=%(%c%)]%|; %)}", [`{[1=hello]; [2=world]}`, `{[2=world]; [1=hello]}`]); 5541 } 5542 5543 import std.exception; 5544 assertCTFEable!( 5545 { 5546 booleanTest(); 5547 integerTest(); 5548 if (!__ctfe) floatingTest(); // snprintf 5549 charTest(); 5550 strTest(); 5551 daTest(); 5552 saTest(); 5553 aaTest(); 5554 return true; 5555 }); 5556 } 5557 5558 //------------------------------------------------------------------------------ 5559 private void skipData(Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 5560 { 5561 import std.ascii : isDigit; 5562 import std.conv : text; 5563 5564 switch (spec.spec) 5565 { 5566 case 'c': input.popFront(); break; 5567 case 'd': 5568 if (input.front == '+' || input.front == '-') input.popFront(); 5569 goto case 'u'; 5570 case 'u': 5571 while (!input.empty && isDigit(input.front)) input.popFront(); 5572 break; 5573 default: 5574 assert(false, 5575 text("Format specifier not understood: %", spec.spec)); 5576 } 5577 } 5578 5579 private template acceptedSpecs(T) 5580 { 5581 static if (isIntegral!T) enum acceptedSpecs = "bdosuxX"; 5582 else static if (isFloatingPoint!T) enum acceptedSpecs = "seEfgG"; 5583 else static if (isSomeChar!T) enum acceptedSpecs = "bcdosuxX"; // integral + 'c' 5584 else enum acceptedSpecs = ""; 5585 } 5586 5587 /** 5588 * Reads a value from the given _input range according to spec 5589 * and returns it as type `T`. 5590 * 5591 * Params: 5592 * T = the type to return 5593 * input = the _input range to read from 5594 * spec = the `FormatSpec` to use when reading from `input` 5595 * Returns: 5596 * A value from `input` of type `T` 5597 * Throws: 5598 * A `FormatException` if `spec` cannot read a type `T` 5599 * See_Also: 5600 * $(REF parse, std, conv) and $(REF to, std, conv) 5601 */ 5602 T unformatValue(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 5603 { 5604 return unformatValueImpl!T(input, spec); 5605 } 5606 5607 /// Booleans 5608 @safe pure unittest 5609 { 5610 auto str = "false"; 5611 auto spec = singleSpec("%s"); 5612 assert(unformatValue!bool(str, spec) == false); 5613 5614 str = "1"; 5615 spec = singleSpec("%d"); 5616 assert(unformatValue!bool(str, spec)); 5617 } 5618 5619 /// Null values 5620 @safe pure unittest 5621 { 5622 auto str = "null"; 5623 auto spec = singleSpec("%s"); 5624 assert(str.unformatValue!(typeof(null))(spec) == null); 5625 } 5626 5627 /// Integrals 5628 @safe pure unittest 5629 { 5630 auto str = "123"; 5631 auto spec = singleSpec("%s"); 5632 assert(str.unformatValue!int(spec) == 123); 5633 5634 str = "ABC"; 5635 spec = singleSpec("%X"); 5636 assert(str.unformatValue!int(spec) == 2748); 5637 5638 str = "11610"; 5639 spec = singleSpec("%o"); 5640 assert(str.unformatValue!int(spec) == 5000); 5641 } 5642 5643 /// Floating point numbers 5644 @safe pure unittest 5645 { 5646 import std.math : approxEqual; 5647 5648 auto str = "123.456"; 5649 auto spec = singleSpec("%s"); 5650 assert(str.unformatValue!double(spec).approxEqual(123.456)); 5651 } 5652 5653 /// Character input ranges 5654 @safe pure unittest 5655 { 5656 auto str = "aaa"; 5657 auto spec = singleSpec("%s"); 5658 assert(str.unformatValue!char(spec) == 'a'); 5659 5660 // Using a numerical format spec reads a Unicode value from a string 5661 str = "65"; 5662 spec = singleSpec("%d"); 5663 assert(str.unformatValue!char(spec) == 'A'); 5664 5665 str = "41"; 5666 spec = singleSpec("%x"); 5667 assert(str.unformatValue!char(spec) == 'A'); 5668 5669 str = "10003"; 5670 spec = singleSpec("%d"); 5671 assert(str.unformatValue!dchar(spec) == '✓'); 5672 } 5673 5674 /// Arrays and static arrays 5675 @safe pure unittest 5676 { 5677 string str = "aaa"; 5678 auto spec = singleSpec("%s"); 5679 assert(str.unformatValue!(dchar[])(spec) == "aaa"d); 5680 5681 str = "aaa"; 5682 spec = singleSpec("%s"); 5683 dchar[3] ret = ['a', 'a', 'a']; 5684 assert(str.unformatValue!(dchar[3])(spec) == ret); 5685 5686 str = "[1, 2, 3, 4]"; 5687 spec = singleSpec("%s"); 5688 assert(str.unformatValue!(int[])(spec) == [1, 2, 3, 4]); 5689 5690 str = "[1, 2, 3, 4]"; 5691 spec = singleSpec("%s"); 5692 int[4] ret2 = [1, 2, 3, 4]; 5693 assert(str.unformatValue!(int[4])(spec) == ret2); 5694 } 5695 5696 /// Associative arrays 5697 @safe pure unittest 5698 { 5699 auto str = `["one": 1, "two": 2]`; 5700 auto spec = singleSpec("%s"); 5701 assert(str.unformatValue!(int[string])(spec) == ["one": 1, "two": 2]); 5702 } 5703 5704 // https://issues.dlang.org/show_bug.cgi?id=7241 5705 @safe pure unittest 5706 { 5707 string input = "a"; 5708 auto spec = FormatSpec!char("%s"); 5709 spec.readUpToNextSpec(input); 5710 auto result = unformatValue!(dchar[1])(input, spec); 5711 assert(result[0] == 'a'); 5712 } 5713 5714 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 5715 if (isInputRange!Range && is(immutable T == immutable bool)) 5716 { 5717 import std.algorithm.searching : find; 5718 import std.conv : parse, text; 5719 5720 if (spec.spec == 's') return parse!T(input); 5721 5722 enforceFmt(find(acceptedSpecs!long, spec.spec).length, 5723 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 5724 5725 return unformatValue!long(input, spec) != 0; 5726 } 5727 5728 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 5729 if (isInputRange!Range && is(T == typeof(null))) 5730 { 5731 import std.conv : parse, text; 5732 enforceFmt(spec.spec == 's', 5733 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 5734 5735 return parse!T(input); 5736 } 5737 5738 /// ditto 5739 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 5740 if (isInputRange!Range && isIntegral!T && !is(T == enum) && isSomeChar!(ElementType!Range)) 5741 { 5742 5743 import std.algorithm.searching : find; 5744 import std.conv : parse, text; 5745 5746 if (spec.spec == 'r') 5747 { 5748 static if (is(immutable ElementEncodingType!Range == immutable char) 5749 || is(immutable ElementEncodingType!Range == immutable byte) 5750 || is(immutable ElementEncodingType!Range == immutable ubyte)) 5751 return rawRead!T(input); 5752 else 5753 throw new FormatException( 5754 "The raw read specifier %r may only be used with narrow strings and ranges of bytes." 5755 ); 5756 } 5757 5758 enforceFmt(find(acceptedSpecs!T, spec.spec).length, 5759 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 5760 5761 enforceFmt(spec.width == 0, "Parsing integers with a width specification is not implemented"); // TODO 5762 5763 immutable uint base = 5764 spec.spec == 'x' || spec.spec == 'X' ? 16 : 5765 spec.spec == 'o' ? 8 : 5766 spec.spec == 'b' ? 2 : 5767 spec.spec == 's' || spec.spec == 'd' || spec.spec == 'u' ? 10 : 0; 5768 assert(base != 0, "base must be not equal to zero"); 5769 5770 return parse!T(input, base); 5771 5772 } 5773 5774 /// ditto 5775 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 5776 if (isFloatingPoint!T && !is(T == enum) && isInputRange!Range 5777 && isSomeChar!(ElementType!Range)&& !is(Range == enum)) 5778 { 5779 import std.algorithm.searching : find; 5780 import std.conv : parse, text; 5781 5782 if (spec.spec == 'r') 5783 { 5784 static if (is(immutable ElementEncodingType!Range == immutable char) 5785 || is(immutable ElementEncodingType!Range == immutable byte) 5786 || is(immutable ElementEncodingType!Range == immutable ubyte)) 5787 return rawRead!T(input); 5788 else 5789 throw new FormatException( 5790 "The raw read specifier %r may only be used with narrow strings and ranges of bytes." 5791 ); 5792 } 5793 5794 enforceFmt(find(acceptedSpecs!T, spec.spec).length, 5795 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 5796 5797 return parse!T(input); 5798 } 5799 5800 /// ditto 5801 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 5802 if (isInputRange!Range && isSomeChar!T && !is(T == enum) && isSomeChar!(ElementType!Range)) 5803 { 5804 import std.algorithm.searching : find; 5805 import std.conv : to, text; 5806 if (spec.spec == 's' || spec.spec == 'c') 5807 { 5808 auto result = to!T(input.front); 5809 input.popFront(); 5810 return result; 5811 } 5812 enforceFmt(find(acceptedSpecs!T, spec.spec).length, 5813 text("Wrong unformat specifier '%", spec.spec , "' for ", T.stringof)); 5814 5815 static if (T.sizeof == 1) 5816 return unformatValue!ubyte(input, spec); 5817 else static if (T.sizeof == 2) 5818 return unformatValue!ushort(input, spec); 5819 else static if (T.sizeof == 4) 5820 return unformatValue!uint(input, spec); 5821 else 5822 static assert(false, T.stringof ~ ".sizeof must be 1, 2, or 4 not " ~ 5823 to!string(T.sizeof)); 5824 } 5825 5826 /// ditto 5827 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char fmt) 5828 if (isInputRange!Range && is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum)) 5829 { 5830 import std.conv : text; 5831 5832 const spec = fmt.spec; 5833 if (spec == '(') 5834 { 5835 return unformatRange!T(input, fmt); 5836 } 5837 enforceFmt(spec == 's', 5838 text("Wrong unformat specifier '%", spec , "' for ", T.stringof)); 5839 5840 static if (isStaticArray!T) 5841 { 5842 T result; 5843 auto app = result[]; 5844 } 5845 else 5846 { 5847 import std.array : appender; 5848 auto app = appender!T(); 5849 } 5850 if (fmt.trailing.empty) 5851 { 5852 for (; !input.empty; input.popFront()) 5853 { 5854 static if (isStaticArray!T) 5855 if (app.empty) 5856 break; 5857 app.put(input.front); 5858 } 5859 } 5860 else 5861 { 5862 immutable end = fmt.trailing.front; 5863 for (; !input.empty && input.front != end; input.popFront()) 5864 { 5865 static if (isStaticArray!T) 5866 if (app.empty) 5867 break; 5868 app.put(input.front); 5869 } 5870 } 5871 static if (isStaticArray!T) 5872 { 5873 enforceFmt(app.empty, "need more input"); 5874 return result; 5875 } 5876 else 5877 return app.data; 5878 } 5879 5880 /// ditto 5881 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char fmt) 5882 if (isInputRange!Range && isArray!T && !is(StringTypeOf!T) && !isAggregateType!T && !is(T == enum)) 5883 { 5884 import std.conv : parse, text; 5885 const spec = fmt.spec; 5886 if (spec == '(') 5887 { 5888 return unformatRange!T(input, fmt); 5889 } 5890 enforceFmt(spec == 's', 5891 text("Wrong unformat specifier '%", spec , "' for ", T.stringof)); 5892 5893 return parse!T(input); 5894 } 5895 5896 /// ditto 5897 private T unformatValueImpl(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char fmt) 5898 if (isInputRange!Range && isAssociativeArray!T && !is(T == enum)) 5899 { 5900 import std.conv : parse, text; 5901 const spec = fmt.spec; 5902 if (spec == '(') 5903 { 5904 return unformatRange!T(input, fmt); 5905 } 5906 enforceFmt(spec == 's', 5907 text("Wrong unformat specifier '%", spec , "' for ", T.stringof)); 5908 5909 return parse!T(input); 5910 } 5911 5912 /** 5913 * Function that performs raw reading. Used by unformatValue 5914 * for integral and float types. 5915 */ 5916 private T rawRead(T, Range)(ref Range input) 5917 if (is(immutable ElementEncodingType!Range == immutable char) 5918 || is(immutable ElementEncodingType!Range == immutable byte) 5919 || is(immutable ElementEncodingType!Range == immutable ubyte)) 5920 { 5921 union X 5922 { 5923 ubyte[T.sizeof] raw; 5924 T typed; 5925 } 5926 X x; 5927 foreach (i; 0 .. T.sizeof) 5928 { 5929 static if (isSomeString!Range) 5930 { 5931 x.raw[i] = input[0]; 5932 input = input[1 .. $]; 5933 } 5934 else 5935 { 5936 // TODO: recheck this 5937 x.raw[i] = input.front; 5938 input.popFront(); 5939 } 5940 } 5941 return x.typed; 5942 } 5943 5944 //debug = unformatRange; 5945 5946 private T unformatRange(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 5947 in 5948 { 5949 const char ss = spec.spec; 5950 assert(ss == '(', "spec.spec must be '(' not " ~ ss); 5951 } 5952 do 5953 { 5954 debug (unformatRange) printf("unformatRange:\n"); 5955 5956 T result; 5957 static if (isStaticArray!T) 5958 { 5959 size_t i; 5960 } 5961 5962 const(Char)[] cont = spec.trailing; 5963 for (size_t j = 0; j < spec.trailing.length; ++j) 5964 { 5965 if (spec.trailing[j] == '%') 5966 { 5967 cont = spec.trailing[0 .. j]; 5968 break; 5969 } 5970 } 5971 debug (unformatRange) printf("\t"); 5972 debug (unformatRange) if (!input.empty) printf("input.front = %c, ", input.front); 5973 debug (unformatRange) printf("cont = %.*s\n", cast(int) cont.length, cont.ptr); 5974 5975 bool checkEnd() 5976 { 5977 return input.empty || !cont.empty && input.front == cont.front; 5978 } 5979 5980 if (!checkEnd()) 5981 { 5982 for (;;) 5983 { 5984 auto fmt = FormatSpec!Char(spec.nested); 5985 fmt.readUpToNextSpec(input); 5986 enforceFmt(!input.empty, "Unexpected end of input when parsing range"); 5987 5988 debug (unformatRange) printf("\t) spec = %c, front = %c ", fmt.spec, input.front); 5989 static if (isStaticArray!T) 5990 { 5991 result[i++] = unformatElement!(typeof(T.init[0]))(input, fmt); 5992 } 5993 else static if (isDynamicArray!T) 5994 { 5995 import std.conv : WideElementType; 5996 result ~= unformatElement!(WideElementType!T)(input, fmt); 5997 } 5998 else static if (isAssociativeArray!T) 5999 { 6000 auto key = unformatElement!(typeof(T.init.keys[0]))(input, fmt); 6001 fmt.readUpToNextSpec(input); // eat key separator 6002 6003 result[key] = unformatElement!(typeof(T.init.values[0]))(input, fmt); 6004 } 6005 debug (unformatRange) { 6006 if (input.empty) printf("-> front = [empty] "); 6007 else printf("-> front = %c ", input.front); 6008 } 6009 6010 static if (isStaticArray!T) 6011 { 6012 debug (unformatRange) printf("i = %u < %u\n", i, T.length); 6013 enforceFmt(i <= T.length, "Too many format specifiers for static array of length %d".format(T.length)); 6014 } 6015 6016 if (spec.sep !is null) 6017 fmt.readUpToNextSpec(input); 6018 auto sep = spec.sep !is null ? spec.sep 6019 : fmt.trailing; 6020 debug (unformatRange) { 6021 if (!sep.empty && !input.empty) printf("-> %c, sep = %.*s\n", input.front, cast(int) sep.length, sep.ptr); 6022 else printf("\n"); 6023 } 6024 6025 if (checkEnd()) 6026 break; 6027 6028 if (!sep.empty && input.front == sep.front) 6029 { 6030 while (!sep.empty) 6031 { 6032 enforceFmt(!input.empty, "Unexpected end of input when parsing range separator"); 6033 enforceFmt(input.front == sep.front, "Unexpected character when parsing range separator"); 6034 input.popFront(); 6035 sep.popFront(); 6036 } 6037 debug (unformatRange) printf("input.front = %c\n", input.front); 6038 } 6039 } 6040 } 6041 static if (isStaticArray!T) 6042 { 6043 enforceFmt(i == T.length, "Too few (%d) format specifiers for static array of length %d".format(i, T.length)); 6044 } 6045 return result; 6046 } 6047 6048 // Undocumented 6049 T unformatElement(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 6050 if (isInputRange!Range) 6051 { 6052 import std.conv : parseElement; 6053 static if (isSomeString!T) 6054 { 6055 if (spec.spec == 's') 6056 { 6057 return parseElement!T(input); 6058 } 6059 } 6060 else static if (isSomeChar!T) 6061 { 6062 if (spec.spec == 's') 6063 { 6064 return parseElement!T(input); 6065 } 6066 } 6067 6068 return unformatValue!T(input, spec); 6069 } 6070 6071 private bool needToSwapEndianess(Char)(scope const ref FormatSpec!Char f) 6072 { 6073 import std.system : endian, Endian; 6074 6075 return endian == Endian.littleEndian && f.flPlus 6076 || endian == Endian.bigEndian && f.flDash; 6077 } 6078 6079 /* ======================== Unit Tests ====================================== */ 6080 6081 @system unittest 6082 { 6083 import std.conv : octal; 6084 6085 int i; 6086 string s; 6087 6088 debug(format) printf("std.format.format.unittest\n"); 6089 6090 s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo"); 6091 assert(s == "hello world! true 57 1000000000x foo"); 6092 6093 s = format("%s %A %s", 1.67, -1.28, float.nan); 6094 assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s); 6095 6096 s = format("%x %X", 0x1234AF, 0xAFAFAFAF); 6097 assert(s == "1234af AFAFAFAF"); 6098 6099 s = format("%b %o", 0x1234AF, 0xAFAFAFAF); 6100 assert(s == "100100011010010101111 25753727657"); 6101 6102 s = format("%d %s", 0x1234AF, 0xAFAFAFAF); 6103 assert(s == "1193135 2947526575"); 6104 } 6105 6106 version (TestComplex) 6107 deprecated 6108 @system unittest 6109 { 6110 string s = format("%s", 1.2 + 3.4i); 6111 assert(s == "1.2+3.4i", s); 6112 } 6113 6114 @system unittest 6115 { 6116 import std.conv : octal; 6117 6118 string s; 6119 int i; 6120 6121 s = format("%#06.*f",2,12.345); 6122 assert(s == "012.35"); 6123 6124 s = format("%#0*.*f",6,2,12.345); 6125 assert(s == "012.35"); 6126 6127 s = format("%7.4g:", 12.678); 6128 assert(s == " 12.68:"); 6129 6130 s = format("%7.4g:", 12.678L); 6131 assert(s == " 12.68:"); 6132 6133 s = format("%04f|%05d|%#05x|%#5x",-4.0,-10,1,1); 6134 assert(s == "-4.000000|-0010|0x001| 0x1"); 6135 6136 i = -10; 6137 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 6138 assert(s == "-10|-10|-10|-10|-10.0000"); 6139 6140 i = -5; 6141 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 6142 assert(s == "-5| -5|-05|-5|-5.0000"); 6143 6144 i = 0; 6145 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 6146 assert(s == "0| 0|000|0|0.0000"); 6147 6148 i = 5; 6149 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 6150 assert(s == "5| 5|005|5|5.0000"); 6151 6152 i = 10; 6153 s = format("%d|%3d|%03d|%1d|%01.4f",i,i,i,i,cast(double) i); 6154 assert(s == "10| 10|010|10|10.0000"); 6155 6156 s = format("%.0d", 0); 6157 assert(s == ""); 6158 6159 s = format("%.g", .34); 6160 assert(s == "0.3"); 6161 6162 s = format("%.0g", .34); 6163 assert(s == "0.3"); 6164 6165 s = format("%.2g", .34); 6166 assert(s == "0.34"); 6167 6168 s = format("%0.0008f", 1e-08); 6169 assert(s == "0.00000001"); 6170 6171 s = format("%0.0008f", 1e-05); 6172 assert(s == "0.00001000"); 6173 6174 s = "helloworld"; 6175 string r; 6176 r = format("%.2s", s[0 .. 5]); 6177 assert(r == "he"); 6178 r = format("%.20s", s[0 .. 5]); 6179 assert(r == "hello"); 6180 r = format("%8s", s[0 .. 5]); 6181 assert(r == " hello"); 6182 6183 byte[] arrbyte = new byte[4]; 6184 arrbyte[0] = 100; 6185 arrbyte[1] = -99; 6186 arrbyte[3] = 0; 6187 r = format("%s", arrbyte); 6188 assert(r == "[100, -99, 0, 0]"); 6189 6190 ubyte[] arrubyte = new ubyte[4]; 6191 arrubyte[0] = 100; 6192 arrubyte[1] = 200; 6193 arrubyte[3] = 0; 6194 r = format("%s", arrubyte); 6195 assert(r == "[100, 200, 0, 0]"); 6196 6197 short[] arrshort = new short[4]; 6198 arrshort[0] = 100; 6199 arrshort[1] = -999; 6200 arrshort[3] = 0; 6201 r = format("%s", arrshort); 6202 assert(r == "[100, -999, 0, 0]"); 6203 6204 ushort[] arrushort = new ushort[4]; 6205 arrushort[0] = 100; 6206 arrushort[1] = 20_000; 6207 arrushort[3] = 0; 6208 r = format("%s", arrushort); 6209 assert(r == "[100, 20000, 0, 0]"); 6210 6211 int[] arrint = new int[4]; 6212 arrint[0] = 100; 6213 arrint[1] = -999; 6214 arrint[3] = 0; 6215 r = format("%s", arrint); 6216 assert(r == "[100, -999, 0, 0]"); 6217 6218 long[] arrlong = new long[4]; 6219 arrlong[0] = 100; 6220 arrlong[1] = -999; 6221 arrlong[3] = 0; 6222 r = format("%s", arrlong); 6223 assert(r == "[100, -999, 0, 0]"); 6224 6225 ulong[] arrulong = new ulong[4]; 6226 arrulong[0] = 100; 6227 arrulong[1] = 999; 6228 arrulong[3] = 0; 6229 r = format("%s", arrulong); 6230 assert(r == "[100, 999, 0, 0]"); 6231 6232 string[] arr2 = new string[4]; 6233 arr2[0] = "hello"; 6234 arr2[1] = "world"; 6235 arr2[3] = "foo"; 6236 r = format("%s", arr2); 6237 assert(r == `["hello", "world", "", "foo"]`); 6238 6239 r = format("%.8d", 7); 6240 assert(r == "00000007"); 6241 r = format("%.8x", 10); 6242 assert(r == "0000000a"); 6243 6244 r = format("%-3d", 7); 6245 assert(r == "7 "); 6246 6247 r = format("%-1*d", 4, 3); 6248 assert(r == "3 "); 6249 6250 r = format("%*d", -3, 7); 6251 assert(r == "7 "); 6252 6253 r = format("%.*d", -3, 7); 6254 assert(r == "7"); 6255 6256 r = format("%-1.*f", 2, 3.1415); 6257 assert(r == "3.14"); 6258 6259 r = format("abc"c); 6260 assert(r == "abc"); 6261 6262 //format() returns the same type as inputted. 6263 wstring wr; 6264 wr = format("def"w); 6265 assert(wr == "def"w); 6266 6267 dstring dr; 6268 dr = format("ghi"d); 6269 assert(dr == "ghi"d); 6270 6271 // Empty static character arrays work as well 6272 const char[0] cempty; 6273 assert(format("test%spath", cempty) == "testpath"); 6274 const wchar[0] wempty; 6275 assert(format("test%spath", wempty) == "testpath"); 6276 const dchar[0] dempty; 6277 assert(format("test%spath", dempty) == "testpath"); 6278 6279 void* p = cast(void*) 0xDEADBEEF; 6280 r = format("%s", p); 6281 assert(r == "DEADBEEF"); 6282 6283 r = format("%#x", 0xabcd); 6284 assert(r == "0xabcd"); 6285 r = format("%#X", 0xABCD); 6286 assert(r == "0XABCD"); 6287 6288 r = format("%#o", octal!12345); 6289 assert(r == "012345"); 6290 r = format("%o", 9); 6291 assert(r == "11"); 6292 r = format("%#o", 0); // https://issues.dlang.org/show_bug.cgi?id=15663 6293 assert(r == "0"); 6294 6295 r = format("%+d", 123); 6296 assert(r == "+123"); 6297 r = format("%+d", -123); 6298 assert(r == "-123"); 6299 r = format("% d", 123); 6300 assert(r == " 123"); 6301 r = format("% d", -123); 6302 assert(r == "-123"); 6303 6304 r = format("%%"); 6305 assert(r == "%"); 6306 6307 r = format("%d", true); 6308 assert(r == "1"); 6309 r = format("%d", false); 6310 assert(r == "0"); 6311 6312 r = format("%d", 'a'); 6313 assert(r == "97"); 6314 wchar wc = 'a'; 6315 r = format("%d", wc); 6316 assert(r == "97"); 6317 dchar dc = 'a'; 6318 r = format("%d", dc); 6319 assert(r == "97"); 6320 6321 byte b = byte.max; 6322 r = format("%x", b); 6323 assert(r == "7f"); 6324 r = format("%x", ++b); 6325 assert(r == "80"); 6326 r = format("%x", ++b); 6327 assert(r == "81"); 6328 6329 short sh = short.max; 6330 r = format("%x", sh); 6331 assert(r == "7fff"); 6332 r = format("%x", ++sh); 6333 assert(r == "8000"); 6334 r = format("%x", ++sh); 6335 assert(r == "8001"); 6336 6337 i = int.max; 6338 r = format("%x", i); 6339 assert(r == "7fffffff"); 6340 r = format("%x", ++i); 6341 assert(r == "80000000"); 6342 r = format("%x", ++i); 6343 assert(r == "80000001"); 6344 6345 r = format("%x", 10); 6346 assert(r == "a"); 6347 r = format("%X", 10); 6348 assert(r == "A"); 6349 r = format("%x", 15); 6350 assert(r == "f"); 6351 r = format("%X", 15); 6352 assert(r == "F"); 6353 6354 Object c = null; 6355 r = format("%s", c); 6356 assert(r == "null"); 6357 6358 enum TestEnum 6359 { 6360 Value1, Value2 6361 } 6362 r = format("%s", TestEnum.Value2); 6363 assert(r == "Value2"); 6364 6365 immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 6366 r = format("%s", aa.values); 6367 assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`); 6368 r = format("%s", aa); 6369 assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`); 6370 6371 static const dchar[] ds = ['a','b']; 6372 for (int j = 0; j < ds.length; ++j) 6373 { 6374 r = format(" %d", ds[j]); 6375 if (j == 0) 6376 assert(r == " 97"); 6377 else 6378 assert(r == " 98"); 6379 } 6380 6381 r = format(">%14d<, %s", 15, [1,2,3]); 6382 assert(r == "> 15<, [1, 2, 3]"); 6383 6384 assert(format("%8s", "bar") == " bar"); 6385 assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4"); 6386 } 6387 6388 // https://issues.dlang.org/show_bug.cgi?id=18205 6389 @safe pure unittest 6390 { 6391 assert("|%8s|".format("abc") == "| abc|"); 6392 assert("|%8s|".format("αβγ") == "| αβγ|"); 6393 assert("|%8s|".format(" ") == "| |"); 6394 assert("|%8s|".format("été"d) == "| été|"); 6395 assert("|%8s|".format("été 2018"w) == "|été 2018|"); 6396 6397 assert("%2s".format("e\u0301"w) == " e\u0301"); 6398 assert("%2s".format("a\u0310\u0337"d) == " a\u0310\u0337"); 6399 } 6400 6401 // https://issues.dlang.org/show_bug.cgi?id=3479 6402 @safe unittest 6403 { 6404 import std.array; 6405 auto stream = appender!(char[])(); 6406 formattedWrite(stream, "%2$.*1$d", 12, 10); 6407 assert(stream.data == "000000000010", stream.data); 6408 } 6409 6410 // https://issues.dlang.org/show_bug.cgi?id=6893 6411 @safe unittest 6412 { 6413 import std.array; 6414 enum E : ulong { A, B, C } 6415 auto stream = appender!(char[])(); 6416 formattedWrite(stream, "%s", E.C); 6417 assert(stream.data == "C"); 6418 } 6419 6420 // Used to check format strings are compatible with argument types 6421 package static const checkFormatException(alias fmt, Args...) = 6422 { 6423 import std.conv : text; 6424 6425 try 6426 { 6427 auto n = .formattedWrite(NoOpSink(), fmt, Args.init); 6428 6429 enforceFmt(n == Args.length, text("Orphan format arguments: args[", n, "..", Args.length, "]")); 6430 } 6431 catch (Exception e) 6432 return (e.msg == ctfpMessage) ? null : e; 6433 return null; 6434 }(); 6435 6436 private void writeAligned(Writer, T, Char)(auto ref Writer w, T s, scope const ref FormatSpec!Char f) 6437 if (isSomeString!T) 6438 { 6439 size_t width; 6440 if (f.width > 0) 6441 { 6442 // check for non-ascii character 6443 import std.algorithm.searching : any; 6444 if (s.any!(a => a > 0x7F)) 6445 { 6446 //TODO: optimize this 6447 import std.uni : graphemeStride; 6448 for (size_t i; i < s.length; i += graphemeStride(s, i)) 6449 ++width; 6450 } 6451 else 6452 width = s.length; 6453 } 6454 else 6455 width = s.length; 6456 6457 if (!f.flDash) 6458 { 6459 // right align 6460 if (f.width > width) 6461 foreach (i ; 0 .. f.width - width) put(w, ' '); 6462 put(w, s); 6463 } 6464 else 6465 { 6466 // left align 6467 put(w, s); 6468 if (f.width > width) 6469 foreach (i ; 0 .. f.width - width) put(w, ' '); 6470 } 6471 } 6472 6473 @safe pure unittest 6474 { 6475 import std.array : appender; 6476 auto w = appender!string(); 6477 auto spec = singleSpec("%s"); 6478 writeAligned(w, "a本Ä", spec); 6479 assert(w.data == "a本Ä", w.data); 6480 } 6481 6482 @safe pure unittest 6483 { 6484 import std.array : appender; 6485 auto w = appender!string(); 6486 auto spec = singleSpec("%10s"); 6487 writeAligned(w, "a本Ä", spec); 6488 assert(w.data == " a本Ä", "|" ~ w.data ~ "|"); 6489 } 6490 6491 @safe pure unittest 6492 { 6493 import std.array : appender; 6494 auto w = appender!string(); 6495 auto spec = singleSpec("%-10s"); 6496 writeAligned(w, "a本Ä", spec); 6497 assert(w.data == "a本Ä ", w.data); 6498 } 6499 6500 /** 6501 * Format arguments into a string. 6502 * 6503 * If the format string is fixed, passing it as a template parameter checks the 6504 * type correctness of the parameters at compile-time. This also can result in 6505 * better performance. 6506 * 6507 * Params: fmt = Format string. For detailed specification, see $(LREF formattedWrite). 6508 * args = Variadic list of arguments to format into returned string. 6509 * 6510 * Throws: 6511 * $(LREF, FormatException) if the number of arguments doesn't match the number 6512 * of format parameters and vice-versa. 6513 */ 6514 typeof(fmt) format(alias fmt, Args...)(Args args) 6515 if (isSomeString!(typeof(fmt))) 6516 { 6517 import std.array : appender; 6518 6519 alias e = checkFormatException!(fmt, Args); 6520 alias Char = Unqual!(ElementEncodingType!(typeof(fmt))); 6521 6522 static assert(!e, e.msg); 6523 auto w = appender!(immutable(Char)[]); 6524 6525 // no need to traverse the string twice during compile time 6526 if (!__ctfe) 6527 { 6528 enum len = guessLength!Char(fmt); 6529 w.reserve(len); 6530 } 6531 else 6532 { 6533 w.reserve(fmt.length); 6534 } 6535 6536 formattedWrite(w, fmt, args); 6537 return w.data; 6538 } 6539 6540 /// Type checking can be done when fmt is known at compile-time: 6541 @safe unittest 6542 { 6543 auto s = format!"%s is %s"("Pi", 3.14); 6544 assert(s == "Pi is 3.14"); 6545 6546 static assert(!__traits(compiles, {s = format!"%l"();})); // missing arg 6547 static assert(!__traits(compiles, {s = format!""(404);})); // surplus arg 6548 static assert(!__traits(compiles, {s = format!"%d"(4.03);})); // incompatible arg 6549 } 6550 6551 // called during compilation to guess the length of the 6552 // result of format 6553 private size_t guessLength(Char, S)(S fmtString) 6554 { 6555 import std.array : appender; 6556 6557 size_t len; 6558 auto output = appender!(immutable(Char)[])(); 6559 auto spec = FormatSpec!Char(fmtString); 6560 while (spec.writeUpToNextSpec(output)) 6561 { 6562 // take a guess 6563 if (spec.width == 0 && (spec.precision == spec.UNSPECIFIED || spec.precision == spec.DYNAMIC)) 6564 { 6565 switch (spec.spec) 6566 { 6567 case 'c': 6568 ++len; 6569 break; 6570 case 'd': 6571 case 'x': 6572 case 'X': 6573 len += 3; 6574 break; 6575 case 'b': 6576 len += 8; 6577 break; 6578 case 'f': 6579 case 'F': 6580 len += 10; 6581 break; 6582 case 's': 6583 case 'e': 6584 case 'E': 6585 case 'g': 6586 case 'G': 6587 len += 12; 6588 break; 6589 default: break; 6590 } 6591 6592 continue; 6593 } 6594 6595 if ((spec.spec == 'e' || spec.spec == 'E' || spec.spec == 'g' || 6596 spec.spec == 'G' || spec.spec == 'f' || spec.spec == 'F') && 6597 spec.precision != spec.UNSPECIFIED && spec.precision != spec.DYNAMIC && 6598 spec.width == 0 6599 ) 6600 { 6601 len += spec.precision + 5; 6602 continue; 6603 } 6604 6605 if (spec.width == spec.precision) 6606 len += spec.width; 6607 else if (spec.width > 0 && spec.width != spec.DYNAMIC && 6608 (spec.precision == spec.UNSPECIFIED || spec.width > spec.precision)) 6609 { 6610 len += spec.width; 6611 } 6612 else if (spec.precision != spec.UNSPECIFIED && spec.precision > spec.width) 6613 len += spec.precision; 6614 } 6615 len += output.data.length; 6616 return len; 6617 } 6618 6619 @safe pure 6620 unittest 6621 { 6622 assert(guessLength!char("%c") == 1); 6623 assert(guessLength!char("%d") == 3); 6624 assert(guessLength!char("%x") == 3); 6625 assert(guessLength!char("%b") == 8); 6626 assert(guessLength!char("%f") == 10); 6627 assert(guessLength!char("%s") == 12); 6628 assert(guessLength!char("%02d") == 2); 6629 assert(guessLength!char("%02d") == 2); 6630 assert(guessLength!char("%4.4d") == 4); 6631 assert(guessLength!char("%2.4f") == 4); 6632 assert(guessLength!char("%02d:%02d:%02d") == 8); 6633 assert(guessLength!char("%0.2f") == 7); 6634 assert(guessLength!char("%0*d") == 0); 6635 } 6636 6637 /// ditto 6638 immutable(Char)[] format(Char, Args...)(in Char[] fmt, Args args) 6639 if (isSomeChar!Char) 6640 { 6641 import std.array : appender; 6642 auto w = appender!(immutable(Char)[]); 6643 auto n = formattedWrite(w, fmt, args); 6644 version (all) 6645 { 6646 // In the future, this check will be removed to increase consistency 6647 // with formattedWrite 6648 import std.conv : text; 6649 enforceFmt(n == args.length, text("Orphan format arguments: args[", n, "..", args.length, "]")); 6650 } 6651 return w.data; 6652 } 6653 6654 @safe pure unittest 6655 { 6656 import core.exception; 6657 import std.exception; 6658 assertCTFEable!( 6659 { 6660 // assert(format(null) == ""); 6661 assert(format("foo") == "foo"); 6662 assert(format("foo%%") == "foo%"); 6663 assert(format("foo%s", 'C') == "fooC"); 6664 assert(format("%s foo", "bar") == "bar foo"); 6665 assert(format("%s foo %s", "bar", "abc") == "bar foo abc"); 6666 assert(format("foo %d", -123) == "foo -123"); 6667 assert(format("foo %d", 123) == "foo 123"); 6668 6669 assertThrown!FormatException(format("foo %s")); 6670 assertThrown!FormatException(format("foo %s", 123, 456)); 6671 6672 assert(format("hel%slo%s%s%s", "world", -138, 'c', true) == 6673 "helworldlo-138ctrue"); 6674 }); 6675 6676 assert(is(typeof(format("happy")) == string)); 6677 assert(is(typeof(format("happy"w)) == wstring)); 6678 assert(is(typeof(format("happy"d)) == dstring)); 6679 } 6680 6681 // https://issues.dlang.org/show_bug.cgi?id=16661 6682 @safe unittest 6683 { 6684 assert(format("%.2f"d, 0.4) == "0.40"); 6685 assert("%02d"d.format(1) == "01"d); 6686 } 6687 6688 /***************************************************** 6689 * Format arguments into buffer $(I buf) which must be large 6690 * enough to hold the result. 6691 * 6692 * Returns: 6693 * The slice of `buf` containing the formatted string. 6694 * 6695 * Throws: 6696 * A `RangeError` if `buf` isn't large enough to hold the 6697 * formatted string. 6698 * 6699 * A $(LREF FormatException) if the length of `args` is different 6700 * than the number of format specifiers in `fmt`. 6701 */ 6702 char[] sformat(alias fmt, Args...)(char[] buf, Args args) 6703 if (isSomeString!(typeof(fmt))) 6704 { 6705 alias e = checkFormatException!(fmt, Args); 6706 static assert(!e, e.msg); 6707 return .sformat(buf, fmt, args); 6708 } 6709 6710 /// ditto 6711 char[] sformat(Char, Args...)(return scope char[] buf, scope const(Char)[] fmt, Args args) 6712 { 6713 import core.exception : RangeError; 6714 import std.utf : encode; 6715 6716 static struct Sink 6717 { 6718 char[] buf; 6719 size_t i; 6720 void put(dchar c) 6721 { 6722 char[4] enc; 6723 auto n = encode(enc, c); 6724 6725 if (buf.length < i + n) 6726 throw new RangeError(__FILE__, __LINE__); 6727 6728 buf[i .. i + n] = enc[0 .. n]; 6729 i += n; 6730 } 6731 void put(scope const(char)[] s) 6732 { 6733 if (buf.length < i + s.length) 6734 throw new RangeError(__FILE__, __LINE__); 6735 6736 buf[i .. i + s.length] = s[]; 6737 i += s.length; 6738 } 6739 void put(scope const(wchar)[] s) 6740 { 6741 for (; !s.empty; s.popFront()) 6742 put(s.front); 6743 } 6744 void put(scope const(dchar)[] s) 6745 { 6746 for (; !s.empty; s.popFront()) 6747 put(s.front); 6748 } 6749 } 6750 auto sink = Sink(buf); 6751 auto n = formattedWrite(sink, fmt, args); 6752 version (all) 6753 { 6754 // In the future, this check will be removed to increase consistency 6755 // with formattedWrite 6756 import std.conv : text; 6757 enforceFmt( 6758 n == args.length, 6759 text("Orphan format arguments: args[", n, " .. ", args.length, "]") 6760 ); 6761 } 6762 return buf[0 .. sink.i]; 6763 } 6764 6765 /// The format string can be checked at compile-time (see $(LREF format) for details): 6766 @system unittest 6767 { 6768 char[10] buf; 6769 6770 assert(buf[].sformat!"foo%s"('C') == "fooC"); 6771 assert(sformat(buf[], "%s foo", "bar") == "bar foo"); 6772 } 6773 6774 @system unittest 6775 { 6776 import core.exception; 6777 6778 debug(string) trustedPrintf("std.string.sformat.unittest\n"); 6779 6780 import std.exception; 6781 assertCTFEable!( 6782 { 6783 char[10] buf; 6784 6785 assert(sformat(buf[], "foo") == "foo"); 6786 assert(sformat(buf[], "foo%%") == "foo%"); 6787 assert(sformat(buf[], "foo%s", 'C') == "fooC"); 6788 assert(sformat(buf[], "%s foo", "bar") == "bar foo"); 6789 assertThrown!RangeError(sformat(buf[], "%s foo %s", "bar", "abc")); 6790 assert(sformat(buf[], "foo %d", -123) == "foo -123"); 6791 assert(sformat(buf[], "foo %d", 123) == "foo 123"); 6792 6793 assertThrown!FormatException(sformat(buf[], "foo %s")); 6794 assertThrown!FormatException(sformat(buf[], "foo %s", 123, 456)); 6795 6796 assert(sformat(buf[], "%s %s %s", "c"c, "w"w, "d"d) == "c w d"); 6797 }); 6798 } 6799 6800 @system unittest // ensure that sformat avoids the GC 6801 { 6802 import core.memory : GC; 6803 const a = ["foo", "bar"]; 6804 const u = GC.stats().usedSize; 6805 char[20] buf; 6806 sformat(buf, "%d", 123); 6807 sformat(buf, "%s", a); 6808 sformat(buf, "%s", 'c'); 6809 assert(u == GC.stats().usedSize); 6810 } 6811 6812 /***************************** 6813 * The .ptr is unsafe because it could be dereferenced and the length of the array may be 0. 6814 * Returns: 6815 * the difference between the starts of the arrays 6816 */ 6817 @trusted private pure nothrow @nogc 6818 ptrdiff_t arrayPtrDiff(T)(const T[] array1, const T[] array2) 6819 { 6820 return array1.ptr - array2.ptr; 6821 } 6822 6823 @safe unittest 6824 { 6825 assertCTFEable!({ 6826 auto tmp = format("%,d", 1000); 6827 assert(tmp == "1,000", "'" ~ tmp ~ "'"); 6828 6829 tmp = format("%,?d", 'z', 1234567); 6830 assert(tmp == "1z234z567", "'" ~ tmp ~ "'"); 6831 6832 tmp = format("%10,?d", 'z', 1234567); 6833 assert(tmp == " 1z234z567", "'" ~ tmp ~ "'"); 6834 6835 tmp = format("%11,2?d", 'z', 1234567); 6836 assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'"); 6837 6838 tmp = format("%11,*?d", 2, 'z', 1234567); 6839 assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'"); 6840 6841 tmp = format("%11,*d", 2, 1234567); 6842 assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'"); 6843 6844 tmp = format("%11,2d", 1234567); 6845 assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'"); 6846 }); 6847 } 6848 6849 @safe unittest 6850 { 6851 auto tmp = format("%,f", 1000.0); 6852 assert(tmp == "1,000.000000", "'" ~ tmp ~ "'"); 6853 6854 tmp = format("%,f", 1234567.891011); 6855 assert(tmp == "1,234,567.891011", "'" ~ tmp ~ "'"); 6856 6857 tmp = format("%,f", -1234567.891011); 6858 assert(tmp == "-1,234,567.891011", "'" ~ tmp ~ "'"); 6859 6860 tmp = format("%,2f", 1234567.891011); 6861 assert(tmp == "1,23,45,67.891011", "'" ~ tmp ~ "'"); 6862 6863 tmp = format("%18,f", 1234567.891011); 6864 assert(tmp == " 1,234,567.891011", "'" ~ tmp ~ "'"); 6865 6866 tmp = format("%18,?f", '.', 1234567.891011); 6867 assert(tmp == " 1.234.567.891011", "'" ~ tmp ~ "'"); 6868 6869 tmp = format("%,?.3f", 'ä', 1234567.891011); 6870 assert(tmp == "1ä234ä567.891", "'" ~ tmp ~ "'"); 6871 6872 tmp = format("%,*?.3f", 1, 'ä', 1234567.891011); 6873 assert(tmp == "1ä2ä3ä4ä5ä6ä7.891", "'" ~ tmp ~ "'"); 6874 6875 tmp = format("%,4?.3f", '_', 1234567.891011); 6876 assert(tmp == "123_4567.891", "'" ~ tmp ~ "'"); 6877 6878 tmp = format("%12,3.3f", 1234.5678); 6879 assert(tmp == " 1,234.568", "'" ~ tmp ~ "'"); 6880 6881 tmp = format("%,e", 3.141592653589793238462); 6882 assert(tmp == "3.141593e+00", "'" ~ tmp ~ "'"); 6883 6884 tmp = format("%15,e", 3.141592653589793238462); 6885 assert(tmp == " 3.141593e+00", "'" ~ tmp ~ "'"); 6886 6887 tmp = format("%15,e", -3.141592653589793238462); 6888 assert(tmp == " -3.141593e+00", "'" ~ tmp ~ "'"); 6889 6890 tmp = format("%.4,*e", 2, 3.141592653589793238462); 6891 assert(tmp == "3.1416e+00", "'" ~ tmp ~ "'"); 6892 6893 tmp = format("%13.4,*e", 2, 3.141592653589793238462); 6894 assert(tmp == " 3.1416e+00", "'" ~ tmp ~ "'"); 6895 6896 tmp = format("%,.0f", 3.14); 6897 assert(tmp == "3", "'" ~ tmp ~ "'"); 6898 6899 tmp = format("%3,g", 1_000_000.123456); 6900 assert(tmp == "1e+06", "'" ~ tmp ~ "'"); 6901 6902 tmp = format("%19,?f", '.', -1234567.891011); 6903 assert(tmp == " -1.234.567.891011", "'" ~ tmp ~ "'"); 6904 } 6905 6906 // Test for multiple indexes 6907 @safe unittest 6908 { 6909 auto tmp = format("%2:5$s", 1, 2, 3, 4, 5); 6910 assert(tmp == "2345", tmp); 6911 } 6912 6913 // https://issues.dlang.org/show_bug.cgi?id=18047 6914 @safe unittest 6915 { 6916 auto cmp = " 123,456"; 6917 assert(cmp.length == 12, format("%d", cmp.length)); 6918 auto tmp = format("%12,d", 123456); 6919 assert(tmp.length == 12, format("%d", tmp.length)); 6920 6921 assert(tmp == cmp, "'" ~ tmp ~ "'"); 6922 } 6923 6924 // https://issues.dlang.org/show_bug.cgi?id=17459 6925 @safe unittest 6926 { 6927 auto cmp = "100"; 6928 auto tmp = format("%0d", 100); 6929 assert(tmp == cmp, tmp); 6930 6931 cmp = "0100"; 6932 tmp = format("%04d", 100); 6933 assert(tmp == cmp, tmp); 6934 6935 cmp = "0,000,000,100"; 6936 tmp = format("%012,3d", 100); 6937 assert(tmp == cmp, tmp); 6938 6939 cmp = "0,000,001,000"; 6940 tmp = format("%012,3d", 1_000); 6941 assert(tmp == cmp, tmp); 6942 6943 cmp = "0,000,100,000"; 6944 tmp = format("%012,3d", 100_000); 6945 assert(tmp == cmp, tmp); 6946 6947 cmp = "0,001,000,000"; 6948 tmp = format("%012,3d", 1_000_000); 6949 assert(tmp == cmp, tmp); 6950 6951 cmp = "0,100,000,000"; 6952 tmp = format("%012,3d", 100_000_000); 6953 assert(tmp == cmp, tmp); 6954 } 6955 6956 // https://issues.dlang.org/show_bug.cgi?id=17459 6957 @safe unittest 6958 { 6959 auto cmp = "100,000"; 6960 auto tmp = format("%06,d", 100_000); 6961 assert(tmp == cmp, tmp); 6962 6963 cmp = "100,000"; 6964 tmp = format("%07,d", 100_000); 6965 assert(tmp == cmp, tmp); 6966 6967 cmp = "0,100,000"; 6968 tmp = format("%08,d", 100_000); 6969 assert(tmp == cmp, tmp); 6970 } 6971 6972 // https://issues.dlang.org/show_bug.cgi?id=20288 6973 @safe unittest 6974 { 6975 string s = format("%,.2f", double.nan); 6976 assert(s == "nan", s); 6977 6978 s = format("%,.2F", double.nan); 6979 assert(s == "NAN", s); 6980 6981 s = format("%,.2f", -double.nan); 6982 assert(s == "-nan", s); 6983 6984 s = format("%,.2F", -double.nan); 6985 assert(s == "-NAN", s); 6986 6987 string g = format("^%13s$", "nan"); 6988 string h = "^ nan$"; 6989 assert(g == h, "\ngot:" ~ g ~ "\nexp:" ~ h); 6990 string a = format("^%13,3.2f$", double.nan); 6991 string b = format("^%13,3.2F$", double.nan); 6992 string c = format("^%13,3.2f$", -double.nan); 6993 string d = format("^%13,3.2F$", -double.nan); 6994 assert(a == "^ nan$", "\ngot:'"~ a ~ "'\nexp:'^ nan$'"); 6995 assert(b == "^ NAN$", "\ngot:'"~ b ~ "'\nexp:'^ NAN$'"); 6996 assert(c == "^ -nan$", "\ngot:'"~ c ~ "'\nexp:'^ -nan$'"); 6997 assert(d == "^ -NAN$", "\ngot:'"~ d ~ "'\nexp:'^ -NAN$'"); 6998 6999 a = format("^%-13,3.2f$", double.nan); 7000 b = format("^%-13,3.2F$", double.nan); 7001 c = format("^%-13,3.2f$", -double.nan); 7002 d = format("^%-13,3.2F$", -double.nan); 7003 assert(a == "^nan $", "\ngot:'"~ a ~ "'\nexp:'^nan $'"); 7004 assert(b == "^NAN $", "\ngot:'"~ b ~ "'\nexp:'^NAN $'"); 7005 assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); 7006 assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); 7007 7008 a = format("^%+13,3.2f$", double.nan); 7009 b = format("^%+13,3.2F$", double.nan); 7010 c = format("^%+13,3.2f$", -double.nan); 7011 d = format("^%+13,3.2F$", -double.nan); 7012 assert(a == "^ +nan$", "\ngot:'"~ a ~ "'\nexp:'^ +nan$'"); 7013 assert(b == "^ +NAN$", "\ngot:'"~ b ~ "'\nexp:'^ +NAN$'"); 7014 assert(c == "^ -nan$", "\ngot:'"~ c ~ "'\nexp:'^ -nan$'"); 7015 assert(d == "^ -NAN$", "\ngot:'"~ d ~ "'\nexp:'^ -NAN$'"); 7016 7017 a = format("^%-+13,3.2f$", double.nan); 7018 b = format("^%-+13,3.2F$", double.nan); 7019 c = format("^%-+13,3.2f$", -double.nan); 7020 d = format("^%-+13,3.2F$", -double.nan); 7021 assert(a == "^+nan $", "\ngot:'"~ a ~ "'\nexp:'^+nan $'"); 7022 assert(b == "^+NAN $", "\ngot:'"~ b ~ "'\nexp:'^+NAN $'"); 7023 assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); 7024 assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); 7025 7026 a = format("^%- 13,3.2f$", double.nan); 7027 b = format("^%- 13,3.2F$", double.nan); 7028 c = format("^%- 13,3.2f$", -double.nan); 7029 d = format("^%- 13,3.2F$", -double.nan); 7030 assert(a == "^ nan $", "\ngot:'"~ a ~ "'\nexp:'^ nan $'"); 7031 assert(b == "^ NAN $", "\ngot:'"~ b ~ "'\nexp:'^ NAN $'"); 7032 assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); 7033 assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); 7034 } 7035 7036 private auto printFloat(T, Char)(return char[] buf, T val, FormatSpec!Char f, 7037 RoundingMode rm = RoundingMode.toNearestTiesToEven) 7038 if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig)) 7039 { 7040 union FloatBits 7041 { 7042 T floatValue; 7043 ulong ulongValue; 7044 } 7045 7046 FloatBits fb; 7047 fb.floatValue = val; 7048 ulong ival = fb.ulongValue; 7049 7050 static if (!is(T == float)) 7051 { 7052 version (DigitalMars) 7053 { 7054 // hack to work around https://issues.dlang.org/show_bug.cgi?id=20363 7055 ival ^= rm; 7056 ival ^= rm; 7057 } 7058 } 7059 7060 import std.math : log2; 7061 enum log2_max_exp = cast(int) log2(T.max_exp); 7062 7063 ulong mnt = ival & ((1L << (T.mant_dig - 1)) - 1); 7064 int exp = (ival >> (T.mant_dig - 1)) & ((1L << (log2_max_exp + 1)) - 1); 7065 enum maxexp = 2 * T.max_exp - 1; 7066 string sgn = (ival >> (T.mant_dig + log2_max_exp)) & 1 ? "-" : ""; 7067 7068 if (sgn == "" && f.flPlus) sgn = "+"; 7069 if (sgn == "" && f.flSpace) sgn = " "; 7070 7071 assert(f.spec == 'a' || f.spec == 'A' || f.spec == 'e' || f.spec == 'E'); 7072 bool is_upper = f.spec == 'A' || f.spec == 'E'; 7073 7074 // special treatment for nan and inf 7075 if (exp == maxexp) 7076 { 7077 import std.algorithm.comparison : max; 7078 7079 size_t length = max(f.width, sgn.length + 3); 7080 char[] result = length <= buf.length ? buf[0 .. length] : new char[length]; 7081 result[] = ' '; 7082 7083 auto offset = f.flDash ? 0 : (result.length - 3); 7084 7085 if (sgn != "") 7086 { 7087 if (f.flDash) ++offset; 7088 result[offset-1] = sgn[0]; 7089 } 7090 7091 result[offset .. offset + 3] = (mnt == 0) ? ( is_upper ? "INF" : "inf" ) : ( is_upper ? "NAN" : "nan" ); 7092 7093 return result; 7094 } 7095 7096 final switch (f.spec) 7097 { 7098 case 'a': case 'A': 7099 return printFloatA(buf, val, f, rm, sgn, exp, mnt, is_upper); 7100 case 'e': case 'E': 7101 return printFloatE(buf, val, f, rm, sgn, exp, mnt, is_upper); 7102 } 7103 } 7104 7105 private auto printFloatA(T, Char)(return char[] buf, T val, FormatSpec!Char f, RoundingMode rm, 7106 string sgn, int exp, ulong mnt, bool is_upper) 7107 if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig)) 7108 { 7109 import std.algorithm.comparison : max; 7110 7111 enum char[16] alpha = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']; 7112 enum char[16] Alpha = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F']; 7113 7114 enum int bias = T.max_exp - 1; 7115 7116 static if (is(T == float)) 7117 { 7118 mnt <<= 1; // make mnt dividable by 4 7119 enum mant_len = T.mant_dig; 7120 } 7121 else 7122 enum mant_len = T.mant_dig - 1; 7123 static assert(mant_len % 4 == 0); 7124 7125 // print full mantissa 7126 char[(mant_len - 1) / 4 + 1] hex_mant; 7127 size_t hex_mant_pos = 0; 7128 size_t pos = mant_len; 7129 7130 while (pos >= 4 && (mnt & ((1L << pos) - 1)) != 0) 7131 { 7132 pos -= 4; 7133 size_t tmp = (mnt >> pos) & 15; 7134 hex_mant[hex_mant_pos++] = is_upper ? Alpha[tmp] : alpha[tmp]; 7135 } 7136 7137 // save integer part 7138 auto first = exp == 0 ? '0' : '1'; 7139 7140 // print exponent 7141 if (exp == 0 && mnt == 0) 7142 exp = 0; // special treatment for 0.0 7143 else if (exp == 0) 7144 exp = 1 - bias; // denormalized number 7145 else 7146 exp -= bias; 7147 7148 auto exp_sgn = exp >= 0 ? '+' : '-'; 7149 if (exp < 0) exp = -exp; 7150 7151 static if (is(T == float)) 7152 enum max_exp_digits = 4; 7153 else 7154 enum max_exp_digits = 5; 7155 7156 char[max_exp_digits] exp_str; 7157 size_t exp_pos = max_exp_digits; 7158 7159 do 7160 { 7161 exp_str[--exp_pos] = '0' + exp%10; 7162 exp /= 10; 7163 } while (exp>0); 7164 7165 exp_str[--exp_pos] = exp_sgn; 7166 7167 // calculate needed buffer width 7168 auto precision = f.precision == f.UNSPECIFIED ? hex_mant_pos : f.precision; 7169 bool dot = precision > 0 || f.flHash; 7170 7171 size_t width = sgn.length + 3 + (dot ? 1 : 0) + precision + 1 + (max_exp_digits - exp_pos); 7172 7173 size_t length = max(width,f.width); 7174 char[] buffer = length <= buf.length ? buf[0 .. length] : new char[length]; 7175 size_t b_pos = 0; 7176 7177 size_t delta = f.width - width; // only used, when f.width > width 7178 7179 // fill buffer 7180 if (!f.flDash && !f.flZero && f.width > width) 7181 { 7182 buffer[b_pos .. b_pos + delta] = ' '; 7183 b_pos += delta; 7184 } 7185 7186 if (sgn != "") buffer[b_pos++] = sgn[0]; 7187 buffer[b_pos++] = '0'; 7188 buffer[b_pos++] = is_upper ? 'X' : 'x'; 7189 7190 if (!f.flDash && f.flZero && f.width > width) 7191 { 7192 buffer[b_pos .. b_pos + delta] = '0'; 7193 b_pos += delta; 7194 } 7195 7196 buffer[b_pos++] = first; 7197 if (dot) buffer[b_pos++] = '.'; 7198 if (precision < hex_mant_pos) 7199 { 7200 buffer[b_pos .. b_pos + precision] = hex_mant[0 .. precision]; 7201 b_pos += precision; 7202 7203 enum roundType { ZERO, LOWER, FIVE, UPPER } 7204 roundType next; 7205 7206 if (hex_mant[precision] == '0') 7207 next = roundType.ZERO; 7208 else if (hex_mant[precision] < '8') 7209 next = roundType.LOWER; 7210 else if (hex_mant[precision] > '8') 7211 next = roundType.UPPER; 7212 else 7213 next = roundType.FIVE; 7214 7215 if (next == roundType.ZERO || next == roundType.FIVE) 7216 { 7217 foreach (i;precision + 1 .. hex_mant_pos) 7218 { 7219 if (hex_mant[i] > '0') 7220 { 7221 next = next == roundType.ZERO ? roundType.LOWER : roundType.UPPER; 7222 break; 7223 } 7224 } 7225 } 7226 7227 bool roundUp = false; 7228 7229 if (rm == RoundingMode.up) 7230 roundUp = next != roundType.ZERO && sgn != "-"; 7231 else if (rm == RoundingMode.down) 7232 roundUp = next != roundType.ZERO && sgn == "-"; 7233 else if (rm == RoundingMode.toZero) 7234 roundUp = false; 7235 else 7236 { 7237 assert(rm == RoundingMode.toNearestTiesToEven || rm == RoundingMode.toNearestTiesAwayFromZero); 7238 roundUp = next == roundType.UPPER; 7239 7240 if (next == roundType.FIVE) 7241 { 7242 // IEEE754 allows for two different ways of implementing roundToNearest: 7243 // 7244 // Round to nearest, ties away from zero 7245 if (rm == RoundingMode.toNearestTiesAwayFromZero) 7246 roundUp = true; 7247 else 7248 { 7249 // Round to nearest, ties to even 7250 auto last = buffer[b_pos-1]; 7251 if (last == '.') last = buffer[b_pos-2]; 7252 roundUp = (last <= '9' && last % 2 != 0) || (last >= '9' && last % 2 == 0); 7253 } 7254 } 7255 } 7256 7257 if (roundUp) 7258 { 7259 foreach_reverse (i;b_pos - precision - 2 .. b_pos) 7260 { 7261 if (buffer[i] == '.') continue; 7262 if (buffer[i] == 'f' || buffer[i] == 'F') 7263 buffer[i] = '0'; 7264 else 7265 { 7266 if (buffer[i] == '9') 7267 buffer[i] = is_upper ? 'A' : 'a'; 7268 else 7269 buffer[i]++; 7270 break; 7271 } 7272 } 7273 } 7274 } 7275 else 7276 { 7277 buffer[b_pos .. b_pos + hex_mant_pos] = hex_mant[0 .. hex_mant_pos]; 7278 buffer[b_pos + hex_mant_pos .. b_pos + precision] = '0'; 7279 b_pos += precision; 7280 } 7281 7282 buffer[b_pos++] = is_upper ? 'P' : 'p'; 7283 buffer[b_pos .. b_pos + (max_exp_digits - exp_pos)] = exp_str[exp_pos .. $]; 7284 b_pos += max_exp_digits - exp_pos; 7285 7286 if (f.flDash && f.width > width) 7287 { 7288 buffer[b_pos .. b_pos + delta] = ' '; 7289 b_pos += delta; 7290 } 7291 7292 return buffer[0 .. b_pos]; 7293 } 7294 7295 @safe unittest 7296 { 7297 auto f = FormatSpec!dchar(""); 7298 f.spec = 'a'; 7299 char[256] buf; 7300 assert(printFloat(buf[], float.nan, f) == "nan"); 7301 assert(printFloat(buf[], -float.nan, f) == "-nan"); 7302 assert(printFloat(buf[], float.infinity, f) == "inf"); 7303 assert(printFloat(buf[], -float.infinity, f) == "-inf"); 7304 assert(printFloat(buf[], 0.0f, f) == "0x0p+0"); 7305 assert(printFloat(buf[], -0.0f, f) == "-0x0p+0"); 7306 7307 assert(printFloat(buf[], double.nan, f) == "nan"); 7308 assert(printFloat(buf[], -double.nan, f) == "-nan"); 7309 assert(printFloat(buf[], double.infinity, f) == "inf"); 7310 assert(printFloat(buf[], -double.infinity, f) == "-inf"); 7311 assert(printFloat(buf[], 0.0, f) == "0x0p+0"); 7312 assert(printFloat(buf[], -0.0, f) == "-0x0p+0"); 7313 7314 import std.math : nextUp; 7315 7316 assert(printFloat(buf[], nextUp(0.0f), f) == "0x0.000002p-126"); 7317 assert(printFloat(buf[], float.epsilon, f) == "0x1p-23"); 7318 assert(printFloat(buf[], float.min_normal, f) == "0x1p-126"); 7319 assert(printFloat(buf[], float.max, f) == "0x1.fffffep+127"); 7320 7321 assert(printFloat(buf[], nextUp(0.0), f) == "0x0.0000000000001p-1022"); 7322 assert(printFloat(buf[], double.epsilon, f) == "0x1p-52"); 7323 assert(printFloat(buf[], double.min_normal, f) == "0x1p-1022"); 7324 assert(printFloat(buf[], double.max, f) == "0x1.fffffffffffffp+1023"); 7325 7326 import std.math : E, PI, PI_2, PI_4, M_1_PI, M_2_PI, M_2_SQRTPI, 7327 LN10, LN2, LOG2, LOG2E, LOG2T, LOG10E, SQRT2, SQRT1_2; 7328 7329 assert(printFloat(buf[], cast(float) E, f) == "0x1.5bf0a8p+1"); 7330 assert(printFloat(buf[], cast(float) PI, f) == "0x1.921fb6p+1"); 7331 assert(printFloat(buf[], cast(float) PI_2, f) == "0x1.921fb6p+0"); 7332 assert(printFloat(buf[], cast(float) PI_4, f) == "0x1.921fb6p-1"); 7333 assert(printFloat(buf[], cast(float) M_1_PI, f) == "0x1.45f306p-2"); 7334 assert(printFloat(buf[], cast(float) M_2_PI, f) == "0x1.45f306p-1"); 7335 assert(printFloat(buf[], cast(float) M_2_SQRTPI, f) == "0x1.20dd76p+0"); 7336 assert(printFloat(buf[], cast(float) LN10, f) == "0x1.26bb1cp+1"); 7337 assert(printFloat(buf[], cast(float) LN2, f) == "0x1.62e43p-1"); 7338 assert(printFloat(buf[], cast(float) LOG2, f) == "0x1.344136p-2"); 7339 assert(printFloat(buf[], cast(float) LOG2E, f) == "0x1.715476p+0"); 7340 assert(printFloat(buf[], cast(float) LOG2T, f) == "0x1.a934fp+1"); 7341 assert(printFloat(buf[], cast(float) LOG10E, f) == "0x1.bcb7b2p-2"); 7342 assert(printFloat(buf[], cast(float) SQRT2, f) == "0x1.6a09e6p+0"); 7343 assert(printFloat(buf[], cast(float) SQRT1_2, f) == "0x1.6a09e6p-1"); 7344 7345 assert(printFloat(buf[], cast(double) E, f) == "0x1.5bf0a8b145769p+1"); 7346 assert(printFloat(buf[], cast(double) PI, f) == "0x1.921fb54442d18p+1"); 7347 assert(printFloat(buf[], cast(double) PI_2, f) == "0x1.921fb54442d18p+0"); 7348 assert(printFloat(buf[], cast(double) PI_4, f) == "0x1.921fb54442d18p-1"); 7349 assert(printFloat(buf[], cast(double) M_1_PI, f) == "0x1.45f306dc9c883p-2"); 7350 assert(printFloat(buf[], cast(double) M_2_PI, f) == "0x1.45f306dc9c883p-1"); 7351 assert(printFloat(buf[], cast(double) M_2_SQRTPI, f) == "0x1.20dd750429b6dp+0"); 7352 assert(printFloat(buf[], cast(double) LN10, f) == "0x1.26bb1bbb55516p+1"); 7353 assert(printFloat(buf[], cast(double) LN2, f) == "0x1.62e42fefa39efp-1"); 7354 assert(printFloat(buf[], cast(double) LOG2, f) == "0x1.34413509f79ffp-2"); 7355 assert(printFloat(buf[], cast(double) LOG2E, f) == "0x1.71547652b82fep+0"); 7356 assert(printFloat(buf[], cast(double) LOG2T, f) == "0x1.a934f0979a371p+1"); 7357 assert(printFloat(buf[], cast(double) LOG10E, f) == "0x1.bcb7b1526e50ep-2"); 7358 assert(printFloat(buf[], cast(double) SQRT2, f) == "0x1.6a09e667f3bcdp+0"); 7359 assert(printFloat(buf[], cast(double) SQRT1_2, f) == "0x1.6a09e667f3bcdp-1"); 7360 7361 } 7362 7363 @safe unittest 7364 { 7365 auto f = FormatSpec!dchar(""); 7366 f.spec = 'a'; 7367 f.precision = 3; 7368 char[32] buf; 7369 7370 assert(printFloat(buf[], 1.0f, f) == "0x1.000p+0"); 7371 assert(printFloat(buf[], 3.3f, f) == "0x1.a66p+1"); 7372 assert(printFloat(buf[], 2.9f, f) == "0x1.733p+1"); 7373 7374 assert(printFloat(buf[], 1.0, f) == "0x1.000p+0"); 7375 assert(printFloat(buf[], 3.3, f) == "0x1.a66p+1"); 7376 assert(printFloat(buf[], 2.9, f) == "0x1.733p+1"); 7377 } 7378 7379 @safe unittest 7380 { 7381 auto f = FormatSpec!dchar(""); 7382 f.spec = 'a'; 7383 f.precision = 0; 7384 char[32] buf; 7385 7386 assert(printFloat(buf[], 1.0f, f) == "0x1p+0"); 7387 assert(printFloat(buf[], 3.3f, f) == "0x2p+1"); 7388 assert(printFloat(buf[], 2.9f, f) == "0x1p+1"); 7389 7390 assert(printFloat(buf[], 1.0, f) == "0x1p+0"); 7391 assert(printFloat(buf[], 3.3, f) == "0x2p+1"); 7392 assert(printFloat(buf[], 2.9, f) == "0x1p+1"); 7393 } 7394 7395 @safe unittest 7396 { 7397 auto f = FormatSpec!dchar(""); 7398 f.spec = 'a'; 7399 f.precision = 0; 7400 f.flHash = true; 7401 char[32] buf; 7402 7403 assert(printFloat(buf[], 1.0f, f) == "0x1.p+0"); 7404 assert(printFloat(buf[], 3.3f, f) == "0x2.p+1"); 7405 assert(printFloat(buf[], 2.9f, f) == "0x1.p+1"); 7406 7407 assert(printFloat(buf[], 1.0, f) == "0x1.p+0"); 7408 assert(printFloat(buf[], 3.3, f) == "0x2.p+1"); 7409 assert(printFloat(buf[], 2.9, f) == "0x1.p+1"); 7410 } 7411 7412 @safe unittest 7413 { 7414 auto f = FormatSpec!dchar(""); 7415 f.spec = 'a'; 7416 f.width = 22; 7417 char[32] buf; 7418 7419 assert(printFloat(buf[], 1.0f, f) == " 0x1p+0"); 7420 assert(printFloat(buf[], 3.3f, f) == " 0x1.a66666p+1"); 7421 assert(printFloat(buf[], 2.9f, f) == " 0x1.733334p+1"); 7422 7423 assert(printFloat(buf[], 1.0, f) == " 0x1p+0"); 7424 assert(printFloat(buf[], 3.3, f) == " 0x1.a666666666666p+1"); 7425 assert(printFloat(buf[], 2.9, f) == " 0x1.7333333333333p+1"); 7426 } 7427 7428 @safe unittest 7429 { 7430 auto f = FormatSpec!dchar(""); 7431 f.spec = 'a'; 7432 f.width = 22; 7433 f.flDash = true; 7434 char[32] buf; 7435 7436 assert(printFloat(buf[], 1.0f, f) == "0x1p+0 "); 7437 assert(printFloat(buf[], 3.3f, f) == "0x1.a66666p+1 "); 7438 assert(printFloat(buf[], 2.9f, f) == "0x1.733334p+1 "); 7439 7440 assert(printFloat(buf[], 1.0, f) == "0x1p+0 "); 7441 assert(printFloat(buf[], 3.3, f) == "0x1.a666666666666p+1 "); 7442 assert(printFloat(buf[], 2.9, f) == "0x1.7333333333333p+1 "); 7443 } 7444 7445 @safe unittest 7446 { 7447 auto f = FormatSpec!dchar(""); 7448 f.spec = 'a'; 7449 f.width = 22; 7450 f.flZero = true; 7451 char[32] buf; 7452 7453 assert(printFloat(buf[], 1.0f, f) == "0x00000000000000001p+0"); 7454 assert(printFloat(buf[], 3.3f, f) == "0x0000000001.a66666p+1"); 7455 assert(printFloat(buf[], 2.9f, f) == "0x0000000001.733334p+1"); 7456 7457 assert(printFloat(buf[], 1.0, f) == "0x00000000000000001p+0"); 7458 assert(printFloat(buf[], 3.3, f) == "0x001.a666666666666p+1"); 7459 assert(printFloat(buf[], 2.9, f) == "0x001.7333333333333p+1"); 7460 } 7461 7462 @safe unittest 7463 { 7464 auto f = FormatSpec!dchar(""); 7465 f.spec = 'a'; 7466 f.width = 22; 7467 f.flPlus = true; 7468 char[32] buf; 7469 7470 assert(printFloat(buf[], 1.0f, f) == " +0x1p+0"); 7471 assert(printFloat(buf[], 3.3f, f) == " +0x1.a66666p+1"); 7472 assert(printFloat(buf[], 2.9f, f) == " +0x1.733334p+1"); 7473 7474 assert(printFloat(buf[], 1.0, f) == " +0x1p+0"); 7475 assert(printFloat(buf[], 3.3, f) == " +0x1.a666666666666p+1"); 7476 assert(printFloat(buf[], 2.9, f) == " +0x1.7333333333333p+1"); 7477 } 7478 7479 @safe unittest 7480 { 7481 auto f = FormatSpec!dchar(""); 7482 f.spec = 'a'; 7483 f.width = 22; 7484 f.flDash = true; 7485 f.flSpace = true; 7486 char[32] buf; 7487 7488 assert(printFloat(buf[], 1.0f, f) == " 0x1p+0 "); 7489 assert(printFloat(buf[], 3.3f, f) == " 0x1.a66666p+1 "); 7490 assert(printFloat(buf[], 2.9f, f) == " 0x1.733334p+1 "); 7491 7492 assert(printFloat(buf[], 1.0, f) == " 0x1p+0 "); 7493 assert(printFloat(buf[], 3.3, f) == " 0x1.a666666666666p+1 "); 7494 assert(printFloat(buf[], 2.9, f) == " 0x1.7333333333333p+1 "); 7495 } 7496 7497 @safe unittest 7498 { 7499 auto f = FormatSpec!dchar(""); 7500 f.spec = 'a'; 7501 f.precision = 1; 7502 char[32] buf; 7503 7504 assert(printFloat(buf[], 0x1.18p0, f, RoundingMode.toNearestTiesAwayFromZero) == "0x1.2p+0"); 7505 assert(printFloat(buf[], 0x1.28p0, f, RoundingMode.toNearestTiesAwayFromZero) == "0x1.3p+0"); 7506 assert(printFloat(buf[], 0x1.1ap0, f, RoundingMode.toNearestTiesAwayFromZero) == "0x1.2p+0"); 7507 assert(printFloat(buf[], 0x1.16p0, f, RoundingMode.toNearestTiesAwayFromZero) == "0x1.1p+0"); 7508 assert(printFloat(buf[], 0x1.10p0, f, RoundingMode.toNearestTiesAwayFromZero) == "0x1.1p+0"); 7509 assert(printFloat(buf[], -0x1.18p0, f, RoundingMode.toNearestTiesAwayFromZero) == "-0x1.2p+0"); 7510 assert(printFloat(buf[], -0x1.28p0, f, RoundingMode.toNearestTiesAwayFromZero) == "-0x1.3p+0"); 7511 assert(printFloat(buf[], -0x1.1ap0, f, RoundingMode.toNearestTiesAwayFromZero) == "-0x1.2p+0"); 7512 assert(printFloat(buf[], -0x1.16p0, f, RoundingMode.toNearestTiesAwayFromZero) == "-0x1.1p+0"); 7513 assert(printFloat(buf[], -0x1.10p0, f, RoundingMode.toNearestTiesAwayFromZero) == "-0x1.1p+0"); 7514 7515 assert(printFloat(buf[], 0x1.18p0, f) == "0x1.2p+0"); 7516 assert(printFloat(buf[], 0x1.28p0, f) == "0x1.2p+0"); 7517 assert(printFloat(buf[], 0x1.1ap0, f) == "0x1.2p+0"); 7518 assert(printFloat(buf[], 0x1.16p0, f) == "0x1.1p+0"); 7519 assert(printFloat(buf[], 0x1.10p0, f) == "0x1.1p+0"); 7520 assert(printFloat(buf[], -0x1.18p0, f) == "-0x1.2p+0"); 7521 assert(printFloat(buf[], -0x1.28p0, f) == "-0x1.2p+0"); 7522 assert(printFloat(buf[], -0x1.1ap0, f) == "-0x1.2p+0"); 7523 assert(printFloat(buf[], -0x1.16p0, f) == "-0x1.1p+0"); 7524 assert(printFloat(buf[], -0x1.10p0, f) == "-0x1.1p+0"); 7525 7526 assert(printFloat(buf[], 0x1.18p0, f, RoundingMode.toZero) == "0x1.1p+0"); 7527 assert(printFloat(buf[], 0x1.28p0, f, RoundingMode.toZero) == "0x1.2p+0"); 7528 assert(printFloat(buf[], 0x1.1ap0, f, RoundingMode.toZero) == "0x1.1p+0"); 7529 assert(printFloat(buf[], 0x1.16p0, f, RoundingMode.toZero) == "0x1.1p+0"); 7530 assert(printFloat(buf[], 0x1.10p0, f, RoundingMode.toZero) == "0x1.1p+0"); 7531 assert(printFloat(buf[], -0x1.18p0, f, RoundingMode.toZero) == "-0x1.1p+0"); 7532 assert(printFloat(buf[], -0x1.28p0, f, RoundingMode.toZero) == "-0x1.2p+0"); 7533 assert(printFloat(buf[], -0x1.1ap0, f, RoundingMode.toZero) == "-0x1.1p+0"); 7534 assert(printFloat(buf[], -0x1.16p0, f, RoundingMode.toZero) == "-0x1.1p+0"); 7535 assert(printFloat(buf[], -0x1.10p0, f, RoundingMode.toZero) == "-0x1.1p+0"); 7536 7537 assert(printFloat(buf[], 0x1.18p0, f, RoundingMode.up) == "0x1.2p+0"); 7538 assert(printFloat(buf[], 0x1.28p0, f, RoundingMode.up) == "0x1.3p+0"); 7539 assert(printFloat(buf[], 0x1.1ap0, f, RoundingMode.up) == "0x1.2p+0"); 7540 assert(printFloat(buf[], 0x1.16p0, f, RoundingMode.up) == "0x1.2p+0"); 7541 assert(printFloat(buf[], 0x1.10p0, f, RoundingMode.up) == "0x1.1p+0"); 7542 assert(printFloat(buf[], -0x1.18p0, f, RoundingMode.up) == "-0x1.1p+0"); 7543 assert(printFloat(buf[], -0x1.28p0, f, RoundingMode.up) == "-0x1.2p+0"); 7544 assert(printFloat(buf[], -0x1.1ap0, f, RoundingMode.up) == "-0x1.1p+0"); 7545 assert(printFloat(buf[], -0x1.16p0, f, RoundingMode.up) == "-0x1.1p+0"); 7546 assert(printFloat(buf[], -0x1.10p0, f, RoundingMode.up) == "-0x1.1p+0"); 7547 7548 assert(printFloat(buf[], 0x1.18p0, f, RoundingMode.down) == "0x1.1p+0"); 7549 assert(printFloat(buf[], 0x1.28p0, f, RoundingMode.down) == "0x1.2p+0"); 7550 assert(printFloat(buf[], 0x1.1ap0, f, RoundingMode.down) == "0x1.1p+0"); 7551 assert(printFloat(buf[], 0x1.16p0, f, RoundingMode.down) == "0x1.1p+0"); 7552 assert(printFloat(buf[], 0x1.10p0, f, RoundingMode.down) == "0x1.1p+0"); 7553 assert(printFloat(buf[], -0x1.18p0, f, RoundingMode.down) == "-0x1.2p+0"); 7554 assert(printFloat(buf[], -0x1.28p0, f, RoundingMode.down) == "-0x1.3p+0"); 7555 assert(printFloat(buf[], -0x1.1ap0, f, RoundingMode.down) == "-0x1.2p+0"); 7556 assert(printFloat(buf[], -0x1.16p0, f, RoundingMode.down) == "-0x1.2p+0"); 7557 assert(printFloat(buf[], -0x1.10p0, f, RoundingMode.down) == "-0x1.1p+0"); 7558 } 7559 7560 // for 100% coverage 7561 @safe unittest 7562 { 7563 auto f = FormatSpec!dchar(""); 7564 f.spec = 'a'; 7565 f.precision = 3; 7566 char[32] buf; 7567 7568 assert(printFloat(buf[], 0x1.19f81p0, f) == "0x1.1a0p+0"); 7569 assert(printFloat(buf[], 0x1.19f01p0, f) == "0x1.19fp+0"); 7570 } 7571 7572 @safe unittest 7573 { 7574 auto f = FormatSpec!dchar(""); 7575 f.spec = 'A'; 7576 f.precision = 3; 7577 char[32] buf; 7578 7579 assert(printFloat(buf[], 0x1.19f81p0, f) == "0X1.1A0P+0"); 7580 assert(printFloat(buf[], 0x1.19f01p0, f) == "0X1.19FP+0"); 7581 } 7582 7583 private auto printFloatE(T, Char)(return char[] buf, T val, FormatSpec!Char f, RoundingMode rm, 7584 string sgn, int exp, ulong mnt, bool is_upper) 7585 if (is(T == float) || is(T == double) || (is(T == real) && T.mant_dig == double.mant_dig)) 7586 { 7587 import std.conv : to; 7588 import std.algorithm.comparison : max; 7589 7590 enum int bias = T.max_exp - 1; 7591 7592 if (f.precision == f.UNSPECIFIED) 7593 f.precision = 6; 7594 7595 // special treatment for 0.0 7596 if (exp == 0 && mnt == 0) 7597 { 7598 size_t length = max(f.width, f.precision + ((f.precision == 0 && !f.flHash) ? 1 : 2) + sgn.length + 4); 7599 char[] result = length <= buf.length ? buf[0 .. length] : new char[length]; 7600 result[] = '0'; 7601 7602 if (f.flDash) 7603 { 7604 if (sgn != "") 7605 result[0] = sgn[0]; 7606 7607 int dot_pos = cast(int) (sgn.length + 1); 7608 if (f.precision > 0 || f.flHash) 7609 result[dot_pos] = '.'; 7610 7611 auto exp_start = dot_pos + ((f.precision > 0 || f.flHash) ? 1 : 0) + f.precision; 7612 if (exp_start + 4 < result.length) 7613 result[exp_start + 4 .. $] = ' '; 7614 7615 result[exp_start] = is_upper?'E':'e'; 7616 result[exp_start + 1] = '+'; 7617 } 7618 else 7619 { 7620 int sign_pos = cast(int) (result.length - 6); 7621 if (f.precision > 0 || f.flHash) 7622 { 7623 int dot_pos = cast(int) (result.length - f.precision - 5); 7624 result[dot_pos] = '.'; 7625 sign_pos = dot_pos - 2; 7626 } 7627 7628 if (f.flZero) 7629 sign_pos = 0; 7630 else if (sign_pos > 0) 7631 result[0 .. sign_pos + (sgn.length == 0 ? 1 : 0)] = ' '; 7632 7633 if (sgn != "") 7634 result[sign_pos] = sgn[0]; 7635 7636 result[$ - 3] = '+'; 7637 result[$ - 4] = is_upper ? 'E' : 'e'; 7638 } 7639 7640 return result; 7641 } 7642 7643 // add leading 1 for normalized values or correct exponent for denormalied values 7644 if (exp != 0) 7645 mnt |= 1L << (T.mant_dig - 1); 7646 else 7647 exp = 1; 7648 exp -= bias; 7649 7650 // estimate the number of bytes needed left and right of the decimal point 7651 // the speed of the algorithm depends on being as accurate as possible with 7652 // this estimate 7653 7654 // Default for the right side is the number of digits given by f.precision plus one for the dot 7655 // plus 6 more for e+... 7656 auto max_right = f.precision + 7; 7657 7658 // If the exponent is <= 0 there is only the sign and one digit left of the dot else 7659 // we have to estimate the number of digits. The factor between exp, which is the number of 7660 // digits in binary system and the searched number is log_2(10). We round this down to 3.32 to 7661 // get a conservative estimate. We need to add 3, because of the sign, the fact, that the 7662 // logarithm is one to small and because we need to round up instead of down, which to!int does. 7663 // And then we might need one more digit in case of a rounding overflow. 7664 auto max_left = exp>0 ? to!int(exp / 3.32) + 4 : 3; 7665 7666 // If the result is not left justified, we may need to add more digits here for getting the 7667 // correct width. 7668 if (!f.flDash) 7669 max_left = max(max_left, f.width - max_right + max_left + 1); 7670 7671 // If the result is left justified, we may need to add more digits to the right. This strongly 7672 // depends, on the exponent, see above. This time, we need to be conservative in the other direction 7673 // for not missing a digit; therefore we round log_2(10) up to 3.33. 7674 if (exp > 0 && f.flDash) 7675 max_right = max(max_right, f.width - to!int(exp / 3.33) - 2); 7676 else if (f.flDash) 7677 max_right = max(max_right, f.width); 7678 7679 7680 size_t length = max_left + max_right; 7681 char[] buffer = length <= buf.length ? buf[0 .. length] : new char[length]; 7682 size_t start = max_left; 7683 size_t left = max_left; 7684 size_t right = max_left; 7685 7686 int final_exp = 0; 7687 7688 enum roundType { ZERO, LOWER, FIVE, UPPER } 7689 roundType next; 7690 7691 // Depending on exp, we will use one of three algorithms: 7692 // 7693 // Algorithm A: For large exponents (exp >= T.mant_dig) 7694 // Algorithm B: For small exponents (exp < T.mant_dig - 61) 7695 // Algorithm C: For exponents close to 0. 7696 // 7697 // Algorithm A: 7698 // The number to print looks like this: mantissa followed by several zeros. 7699 // 7700 // We know, that there is no fractional part, so we can just use integer division, 7701 // consecutivly dividing by 10 and writing down the remainder from right to left. 7702 // Unfortunately the integer is too large to fit in an ulong, so we use something 7703 // like BigInt: An array of ulongs. We only use 60 bits of that ulongs, because 7704 // this simplifies (and speeds up) the division to come. 7705 // 7706 // For the division we use integer division with reminder for each ulong and put 7707 // the reminder of each step in the first 4 bits of ulong of the next step (think of 7708 // long division for the rationale behind this). The final reminder is the next 7709 // digit (from right to left). 7710 // 7711 // This results in the output we would have for the %f specifier. We now adjust this 7712 // for %e: First we calculate the place, where the exponent should be printed, filling 7713 // up with zeros if needed and second we move the leftmost digit one to the left 7714 // and inserting a dot. 7715 // 7716 // After that we decide on the rounding type, using the digits right of the position, 7717 // where the exponent will be printed (currently they are still there, but will be 7718 // overwritten later). 7719 // 7720 // Algorithm B: 7721 // The number to print looks like this: zero dot several zeros followed by the mantissa 7722 // 7723 // We know, that the number has no integer part. The algorithm consecutivly multiplies 7724 // by 10. The integer part (rounded down) after the multiplication is the next digit 7725 // (from left to right). This integer part is removed after each step. 7726 // Again, the number is represented as an array of ulongs, with only 60 bits used of 7727 // every ulong. 7728 // 7729 // For the multiplication we use normal integer multiplication, which can result in digits 7730 // in the uppermost 4 bits. These 4 digits are the carry which is added to the result 7731 // of the next multiplication and finally the last carry is the next digit. 7732 // 7733 // Other than for the %f specifier, this multiplication is splitted into two almost 7734 // identical parts. The first part lasts as long as we find zeros. We need to do this 7735 // to calculate the correct exponent. 7736 // 7737 // The second part will stop, when only zeros remain or when we've got enough digits 7738 // for the requested precision. In the second case, we have to find out, which rounding 7739 // we have. Aside from special cases we do this by calculating one more digit. 7740 // 7741 // Algorithm C: 7742 // This time, we know, that the integral part and the fractional part each fit into a 7743 // ulong. The mantissa might be partially in both parts or completely in the fractional 7744 // part. 7745 // 7746 // We first calculate the integral part by consecutive division by 10. Depending on the 7747 // precision this might result in more digits, than we need. In that case we calculate 7748 // the position of the exponent and the rounding type. 7749 // 7750 // If there is no integral part, we need to find the first non zero digit. We do this by 7751 // consecutive multiplication by 10, saving the first non zero digit followed by a dot. 7752 // 7753 // In either case, we continue filling up with the fractional part until we have enough 7754 // digits. If still necessary, we decide the rounding type, mainly by looking at the 7755 // next digit. 7756 7757 ulong[4] bigbuf; 7758 if (exp >= T.mant_dig) 7759 { 7760 // large number without fractional digits 7761 // 7762 // As this number does not fit in a ulong, we use an array of ulongs. We only use 60 of the 64 bits, 7763 // because this makes it much more easy to implement the division by 10. 7764 int count = exp / 60 + 1; 7765 7766 // saved in big endian format 7767 ulong[] mybig = count <= bigbuf.length ? bigbuf[0 .. count] : new ulong[count]; 7768 7769 // only the first or the first two ulongs contain the mantiassa. The rest are zeros. 7770 int lower = 60 - (exp - T.mant_dig + 1) % 60; 7771 if (lower < T.mant_dig) 7772 { 7773 mybig[0] = mnt >> lower; 7774 mybig[1] = (mnt & ((1L << lower) - 1)) << 60 - lower; 7775 } 7776 else 7777 mybig[0] = (mnt & ((1L << lower) - 1)) << 60 - lower; 7778 7779 // Generation of digits by consecutive division with reminder by 10. 7780 int msu = 0; // Most significant ulong; when it get's zero, we can ignore it further on 7781 while (msu < count - 1 || mybig[$-1] != 0) 7782 { 7783 ulong mod = 0; 7784 foreach (i;msu .. count) 7785 { 7786 mybig[i] |= mod << 60; 7787 mod = mybig[i] % 10; 7788 mybig[i] /= 10; 7789 } 7790 if (mybig[msu] == 0) 7791 ++msu; 7792 7793 buffer[--left] = cast(byte) ('0' + mod); 7794 ++final_exp; 7795 } 7796 --final_exp; 7797 7798 start = left + f.precision + 1; 7799 7800 // we need more zeros for precision 7801 if (right < start) 7802 buffer[right .. start] = '0'; 7803 7804 // move leftmost digit one more left and add dot between 7805 buffer[left - 1] = buffer[left]; 7806 buffer[left] = '.'; 7807 --left; 7808 7809 // rounding type 7810 if (start >= right) 7811 next = roundType.ZERO; 7812 else if (buffer[start] != '0' && buffer[start] != '5') 7813 next = buffer[start] > '5' ? roundType.UPPER : roundType.LOWER; 7814 else 7815 { 7816 next = buffer[start]=='5' ? roundType.FIVE : roundType.ZERO; 7817 foreach (i; start + 1 .. right) 7818 if (buffer[i] > '0') 7819 { 7820 next = next == roundType.FIVE ? roundType.UPPER : roundType.LOWER; 7821 break; 7822 } 7823 } 7824 7825 right = start; 7826 if (f.precision == 0 && !f.flHash) --right; 7827 } 7828 else if (exp + 61 < T.mant_dig) 7829 { 7830 // small number without integer digits 7831 // 7832 // Again this number does not fit in a ulong and we use an array of ulongs. And again we 7833 // only use 60 bits, because this simplifies the multiplication by 10. 7834 int count = (T.mant_dig - exp - 2) / 60 + 1; 7835 7836 // saved in little endian format 7837 ulong[] mybig = count <= bigbuf.length ? bigbuf[0 .. count] : new ulong[count]; 7838 7839 // only the last or the last two ulongs contain the mantiassa. Because of little endian 7840 // format these are the ulongs at index 0 and 1. The rest are zeros. 7841 int upper = 60 - (-exp - 1) % 60; 7842 if (upper < T.mant_dig) 7843 { 7844 mybig[0] = (mnt & ((1L << (T.mant_dig - upper)) - 1)) << 60 - (T.mant_dig - upper); 7845 mybig[1] = mnt >> (T.mant_dig - upper); 7846 } 7847 else 7848 mybig[0] = mnt << (upper - T.mant_dig); 7849 7850 int lsu = 0; // Least significant ulong; when it get's zero, we can ignore it further on 7851 7852 // adding zeros, until we reach first nonzero 7853 while (lsu < count - 1 || mybig[$ - 1]!=0) 7854 { 7855 ulong over = 0; 7856 foreach (i; lsu .. count) 7857 { 7858 mybig[i] = mybig[i] * 10 + over; 7859 over = mybig[i] >> 60; 7860 mybig[i] &= (1L << 60) - 1; 7861 } 7862 if (mybig[lsu] == 0) 7863 ++lsu; 7864 --final_exp; 7865 7866 if (over != 0) 7867 { 7868 buffer[right++] = cast(byte) ('0' + over); 7869 buffer[right++] = '.'; 7870 break; 7871 } 7872 } 7873 7874 if (f.precision == 0 && !f.flHash) --right; 7875 7876 // adding more digits 7877 start = right; 7878 while ((lsu < count - 1 || mybig[$ - 1] != 0) && right - start < f.precision) 7879 { 7880 ulong over = 0; 7881 foreach (i;lsu .. count) 7882 { 7883 mybig[i] = mybig[i] * 10 + over; 7884 over = mybig[i] >> 60; 7885 mybig[i] &= (1L << 60) - 1; 7886 } 7887 if (mybig[lsu] == 0) 7888 ++lsu; 7889 7890 buffer[right++] = cast(byte) ('0' + over); 7891 } 7892 7893 // filling up with zeros to match precision 7894 if (right < start + f.precision) 7895 { 7896 buffer[right .. start + f.precision] = '0'; 7897 right = start + f.precision; 7898 } 7899 7900 // rounding type 7901 if (lsu >= count - 1 && mybig[count - 1] == 0) 7902 next = roundType.ZERO; 7903 else if (lsu == count - 1 && mybig[lsu] == 1L << 59) 7904 next = roundType.FIVE; 7905 else 7906 { 7907 ulong over = 0; 7908 foreach (i;lsu .. count) 7909 { 7910 mybig[i] = mybig[i] * 10 + over; 7911 over = mybig[i] >> 60; 7912 mybig[i] &= (1L << 60) - 1; 7913 } 7914 next = over >= 5 ? roundType.UPPER : roundType.LOWER; 7915 } 7916 } 7917 else 7918 { 7919 // medium sized number, probably with integer and fractional digits 7920 // this is fastest, because both parts fit into a ulong each 7921 ulong int_part = mnt >> (T.mant_dig - 1 - exp); 7922 ulong frac_part = mnt & ((1L << (T.mant_dig - 1 - exp)) - 1); 7923 7924 start = 0; 7925 7926 // could we already decide on the rounding mode in the integer part? 7927 bool found = false; 7928 7929 if (int_part > 0) 7930 { 7931 // integer part, if there is something to print 7932 while (int_part >= 10) 7933 { 7934 buffer[--left] = '0' + (int_part % 10); 7935 int_part /= 10; 7936 ++final_exp; 7937 ++start; 7938 } 7939 7940 buffer[--left] = '.'; 7941 buffer[--left] = cast(byte) ('0' + int_part); 7942 7943 if (right - left > f.precision + 2) 7944 { 7945 auto old_right = right; 7946 right = left + f.precision + 2; 7947 7948 if (buffer[right] == '5' || buffer[right] == '0') 7949 { 7950 next = buffer[right] == '5' ? roundType.FIVE : roundType.ZERO; 7951 if (frac_part != 0) 7952 next = next == roundType.FIVE ? roundType.UPPER : roundType.LOWER; 7953 else 7954 foreach (i;right + 1 .. old_right) 7955 if (buffer[i] > '0') 7956 { 7957 next = next == roundType.FIVE ? roundType.UPPER : roundType.LOWER; 7958 break; 7959 } 7960 } 7961 else 7962 next = buffer[right] > '5' ? roundType.UPPER : roundType.LOWER; 7963 found = true; 7964 } 7965 } 7966 else 7967 { 7968 // fractional part, skipping leading zeros 7969 while (frac_part != 0) 7970 { 7971 --final_exp; 7972 frac_part *= 10; 7973 auto tmp = frac_part >> (T.mant_dig - 1 - exp); 7974 frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1); 7975 if (tmp > 0) 7976 { 7977 buffer[right++] = cast(byte) ('0' + tmp); 7978 buffer[right++] = '.'; 7979 break; 7980 } 7981 } 7982 7983 next = roundType.ZERO; 7984 } 7985 7986 if (f.precision == 0 && !f.flHash) right--; 7987 7988 // the fractional part after the zeros 7989 while (frac_part != 0 && start < f.precision) 7990 { 7991 frac_part *= 10; 7992 buffer[right++] = cast(byte) ('0' + (frac_part >> (T.mant_dig - 1 - exp))); 7993 frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1); 7994 ++start; 7995 } 7996 7997 if (start < f.precision) 7998 { 7999 buffer[right .. right + f.precision - start] = '0'; 8000 right += f.precision - start; 8001 start = f.precision; 8002 } 8003 8004 // rounding mode, if not allready known 8005 if (frac_part != 0 && !found) 8006 { 8007 frac_part *= 10; 8008 auto nextDigit = frac_part >> (T.mant_dig - 1 - exp); 8009 frac_part &= ((1L << (T.mant_dig - 1 - exp)) - 1); 8010 8011 if (nextDigit == 5 && frac_part == 0) 8012 next = roundType.FIVE; 8013 else if (nextDigit >= 5) 8014 next = roundType.UPPER; 8015 else 8016 next = roundType.LOWER; 8017 } 8018 } 8019 8020 // rounding 8021 bool roundUp = false; 8022 if (rm == RoundingMode.up) 8023 roundUp = next != roundType.ZERO && sgn != "-"; 8024 else if (rm == RoundingMode.down) 8025 roundUp = next != roundType.ZERO && sgn == "-"; 8026 else if (rm == RoundingMode.toZero) 8027 roundUp = false; 8028 else 8029 { 8030 assert(rm == RoundingMode.toNearestTiesToEven || rm == RoundingMode.toNearestTiesAwayFromZero); 8031 roundUp = next == roundType.UPPER; 8032 8033 if (next == roundType.FIVE) 8034 { 8035 // IEEE754 allows for two different ways of implementing roundToNearest: 8036 8037 // Round to nearest, ties away from zero 8038 if (rm == RoundingMode.toNearestTiesAwayFromZero) 8039 roundUp = true; 8040 else 8041 { 8042 // Round to nearest, ties to even 8043 auto last = buffer[right-1]; 8044 if (last == '.') last = buffer[right-2]; 8045 roundUp = last % 2 != 0; 8046 } 8047 } 8048 } 8049 8050 if (roundUp) 8051 { 8052 foreach_reverse (i;left .. right) 8053 { 8054 if (buffer[i] == '.') continue; 8055 if (buffer[i] == '9') 8056 buffer[i] = '0'; 8057 else 8058 { 8059 buffer[i]++; 8060 goto printFloat_done; 8061 } 8062 } 8063 8064 // one more digit to the left, so we need to shift everything and increase exponent 8065 buffer[--left] = '1'; 8066 buffer[left + 2] = buffer[left + 1]; 8067 if (f.flHash || f.precision != 0) 8068 buffer[left + 1] = '.'; 8069 right--; 8070 final_exp++; 8071 8072 printFloat_done: 8073 } 8074 8075 // printing exponent 8076 buffer[right++] = is_upper ? 'E' : 'e'; 8077 buffer[right++] = final_exp >= 0 ? '+' : '-'; 8078 8079 if (final_exp < 0) final_exp = -final_exp; 8080 8081 static if (is(T == float)) 8082 enum max_exp_digits = 2; 8083 else 8084 enum max_exp_digits = 3; 8085 8086 char[max_exp_digits] exp_str; 8087 size_t exp_pos = max_exp_digits; 8088 8089 do 8090 { 8091 exp_str[--exp_pos] = '0' + final_exp%10; 8092 final_exp /= 10; 8093 } while (final_exp>0); 8094 if (max_exp_digits - exp_pos == 1) 8095 exp_str[--exp_pos] = '0'; 8096 8097 buffer[right .. right + max_exp_digits - exp_pos] = exp_str[exp_pos .. $]; 8098 right += max_exp_digits - exp_pos; 8099 8100 // sign and padding 8101 bool need_sgn = false; 8102 if (sgn != "") 8103 { 8104 // when padding with zeros we need to postpone adding the sign 8105 if (right - left < f.width && !f.flDash && f.flZero) 8106 need_sgn = true; 8107 else 8108 buffer[--left] = sgn[0]; 8109 } 8110 8111 if (right - left < f.width) 8112 { 8113 if (f.flDash) 8114 { 8115 // padding right 8116 buffer[right .. f.width + left] = ' '; 8117 right = f.width + left; 8118 } 8119 else 8120 { 8121 // padding left 8122 buffer[right - f.width .. left] = f.flZero ? '0' : ' '; 8123 left = right - f.width; 8124 } 8125 } 8126 8127 if (need_sgn) 8128 buffer[left] = sgn[0]; 8129 8130 // without this cast it's getting too slow 8131 return buffer[left .. right]; 8132 } 8133 8134 @safe unittest 8135 { 8136 char[256] buf; 8137 auto f = FormatSpec!dchar(""); 8138 f.spec = 'e'; 8139 assert(printFloat(buf[], float.nan, f) == "nan"); 8140 assert(printFloat(buf[], -float.nan, f) == "-nan"); 8141 assert(printFloat(buf[], float.infinity, f) == "inf"); 8142 assert(printFloat(buf[], -float.infinity, f) == "-inf"); 8143 assert(printFloat(buf[], 0.0f, f) == "0.000000e+00"); 8144 assert(printFloat(buf[], -0.0f, f) == "-0.000000e+00"); 8145 // cast needed due to https://issues.dlang.org/show_bug.cgi?id=20361 8146 assert(printFloat(buf[], cast(float) 1e-40, f) == "9.999946e-41"); 8147 assert(printFloat(buf[], cast(float) -1e-40, f) == "-9.999946e-41"); 8148 assert(printFloat(buf[], 1e-30f, f) == "1.000000e-30"); 8149 assert(printFloat(buf[], -1e-30f, f) == "-1.000000e-30"); 8150 assert(printFloat(buf[], 1e-10f, f) == "1.000000e-10"); 8151 assert(printFloat(buf[], -1e-10f, f) == "-1.000000e-10"); 8152 assert(printFloat(buf[], 0.1f, f) == "1.000000e-01"); 8153 assert(printFloat(buf[], -0.1f, f) == "-1.000000e-01"); 8154 assert(printFloat(buf[], 10.0f, f) == "1.000000e+01"); 8155 assert(printFloat(buf[], -10.0f, f) == "-1.000000e+01"); 8156 assert(printFloat(buf[], 1e30f, f) == "1.000000e+30"); 8157 assert(printFloat(buf[], -1e30f, f) == "-1.000000e+30"); 8158 8159 import std.math : nextUp, nextDown; 8160 assert(printFloat(buf[], nextUp(0.0f), f) == "1.401298e-45"); 8161 assert(printFloat(buf[], nextDown(-0.0f), f) == "-1.401298e-45"); 8162 } 8163 8164 @safe unittest 8165 { 8166 char[256] buf; 8167 auto f = FormatSpec!dchar(""); 8168 f.spec = 'e'; 8169 f.width = 20; 8170 f.precision = 10; 8171 8172 assert(printFloat(buf[], float.nan, f) == " nan"); 8173 assert(printFloat(buf[], -float.nan, f) == " -nan"); 8174 assert(printFloat(buf[], float.infinity, f) == " inf"); 8175 assert(printFloat(buf[], -float.infinity, f) == " -inf"); 8176 assert(printFloat(buf[], 0.0f, f) == " 0.0000000000e+00"); 8177 assert(printFloat(buf[], -0.0f, f) == " -0.0000000000e+00"); 8178 // cast needed due to bug 20361 8179 assert(printFloat(buf[], cast(float) 1e-40, f) == " 9.9999461011e-41"); 8180 assert(printFloat(buf[], cast(float) -1e-40, f) == " -9.9999461011e-41"); 8181 assert(printFloat(buf[], 1e-30f, f) == " 1.0000000032e-30"); 8182 assert(printFloat(buf[], -1e-30f, f) == " -1.0000000032e-30"); 8183 assert(printFloat(buf[], 1e-10f, f) == " 1.0000000134e-10"); 8184 assert(printFloat(buf[], -1e-10f, f) == " -1.0000000134e-10"); 8185 assert(printFloat(buf[], 0.1f, f) == " 1.0000000149e-01"); 8186 assert(printFloat(buf[], -0.1f, f) == " -1.0000000149e-01"); 8187 assert(printFloat(buf[], 10.0f, f) == " 1.0000000000e+01"); 8188 assert(printFloat(buf[], -10.0f, f) == " -1.0000000000e+01"); 8189 assert(printFloat(buf[], 1e30f, f) == " 1.0000000150e+30"); 8190 assert(printFloat(buf[], -1e30f, f) == " -1.0000000150e+30"); 8191 8192 import std.math : nextUp, nextDown; 8193 assert(printFloat(buf[], nextUp(0.0f), f) == " 1.4012984643e-45"); 8194 assert(printFloat(buf[], nextDown(-0.0f), f) == " -1.4012984643e-45"); 8195 } 8196 8197 @safe unittest 8198 { 8199 char[256] buf; 8200 auto f = FormatSpec!dchar(""); 8201 f.spec = 'e'; 8202 f.width = 20; 8203 f.precision = 10; 8204 f.flDash = true; 8205 8206 assert(printFloat(buf[], float.nan, f) == "nan "); 8207 assert(printFloat(buf[], -float.nan, f) == "-nan "); 8208 assert(printFloat(buf[], float.infinity, f) == "inf "); 8209 assert(printFloat(buf[], -float.infinity, f) == "-inf "); 8210 assert(printFloat(buf[], 0.0f, f) == "0.0000000000e+00 "); 8211 assert(printFloat(buf[], -0.0f, f) == "-0.0000000000e+00 "); 8212 // cast needed due to bug 20361 8213 assert(printFloat(buf[], cast(float) 1e-40, f) == "9.9999461011e-41 "); 8214 assert(printFloat(buf[], cast(float) -1e-40, f) == "-9.9999461011e-41 "); 8215 assert(printFloat(buf[], 1e-30f, f) == "1.0000000032e-30 "); 8216 assert(printFloat(buf[], -1e-30f, f) == "-1.0000000032e-30 "); 8217 assert(printFloat(buf[], 1e-10f, f) == "1.0000000134e-10 "); 8218 assert(printFloat(buf[], -1e-10f, f) == "-1.0000000134e-10 "); 8219 assert(printFloat(buf[], 0.1f, f) == "1.0000000149e-01 "); 8220 assert(printFloat(buf[], -0.1f, f) == "-1.0000000149e-01 "); 8221 assert(printFloat(buf[], 10.0f, f) == "1.0000000000e+01 "); 8222 assert(printFloat(buf[], -10.0f, f) == "-1.0000000000e+01 "); 8223 assert(printFloat(buf[], 1e30f, f) == "1.0000000150e+30 "); 8224 assert(printFloat(buf[], -1e30f, f) == "-1.0000000150e+30 "); 8225 8226 import std.math : nextUp, nextDown; 8227 assert(printFloat(buf[], nextUp(0.0f), f) == "1.4012984643e-45 "); 8228 assert(printFloat(buf[], nextDown(-0.0f), f) == "-1.4012984643e-45 "); 8229 } 8230 8231 @safe unittest 8232 { 8233 char[256] buf; 8234 auto f = FormatSpec!dchar(""); 8235 f.spec = 'e'; 8236 f.width = 20; 8237 f.precision = 10; 8238 f.flZero = true; 8239 8240 assert(printFloat(buf[], float.nan, f) == " nan"); 8241 assert(printFloat(buf[], -float.nan, f) == " -nan"); 8242 assert(printFloat(buf[], float.infinity, f) == " inf"); 8243 assert(printFloat(buf[], -float.infinity, f) == " -inf"); 8244 assert(printFloat(buf[], 0.0f, f) == "00000.0000000000e+00"); 8245 assert(printFloat(buf[], -0.0f, f) == "-0000.0000000000e+00"); 8246 // cast needed due to bug 20361 8247 assert(printFloat(buf[], cast(float) 1e-40, f) == "00009.9999461011e-41"); 8248 assert(printFloat(buf[], cast(float) -1e-40, f) == "-0009.9999461011e-41"); 8249 assert(printFloat(buf[], 1e-30f, f) == "00001.0000000032e-30"); 8250 assert(printFloat(buf[], -1e-30f, f) == "-0001.0000000032e-30"); 8251 assert(printFloat(buf[], 1e-10f, f) == "00001.0000000134e-10"); 8252 assert(printFloat(buf[], -1e-10f, f) == "-0001.0000000134e-10"); 8253 assert(printFloat(buf[], 0.1f, f) == "00001.0000000149e-01"); 8254 assert(printFloat(buf[], -0.1f, f) == "-0001.0000000149e-01"); 8255 assert(printFloat(buf[], 10.0f, f) == "00001.0000000000e+01"); 8256 assert(printFloat(buf[], -10.0f, f) == "-0001.0000000000e+01"); 8257 assert(printFloat(buf[], 1e30f, f) == "00001.0000000150e+30"); 8258 assert(printFloat(buf[], -1e30f, f) == "-0001.0000000150e+30"); 8259 8260 import std.math : nextUp, nextDown; 8261 assert(printFloat(buf[], nextUp(0.0f), f) == "00001.4012984643e-45"); 8262 assert(printFloat(buf[], nextDown(-0.0f), f) == "-0001.4012984643e-45"); 8263 } 8264 8265 @safe unittest 8266 { 8267 char[256] buf; 8268 auto f = FormatSpec!dchar(""); 8269 f.spec = 'e'; 8270 f.precision = 1; 8271 8272 assert(printFloat(buf[], 11.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.2e+01"); 8273 assert(printFloat(buf[], 12.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.3e+01"); 8274 assert(printFloat(buf[], 11.7f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.2e+01"); 8275 assert(printFloat(buf[], 11.3f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.1e+01"); 8276 assert(printFloat(buf[], 11.0f, f, RoundingMode.toNearestTiesAwayFromZero) == "1.1e+01"); 8277 assert(printFloat(buf[], -11.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.2e+01"); 8278 assert(printFloat(buf[], -12.5f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.3e+01"); 8279 assert(printFloat(buf[], -11.7f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.2e+01"); 8280 assert(printFloat(buf[], -11.3f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.1e+01"); 8281 assert(printFloat(buf[], -11.0f, f, RoundingMode.toNearestTiesAwayFromZero) == "-1.1e+01"); 8282 8283 assert(printFloat(buf[], 11.5f, f) == "1.2e+01"); 8284 assert(printFloat(buf[], 12.5f, f) == "1.2e+01"); 8285 assert(printFloat(buf[], 11.7f, f) == "1.2e+01"); 8286 assert(printFloat(buf[], 11.3f, f) == "1.1e+01"); 8287 assert(printFloat(buf[], 11.0f, f) == "1.1e+01"); 8288 assert(printFloat(buf[], -11.5f, f) == "-1.2e+01"); 8289 assert(printFloat(buf[], -12.5f, f) == "-1.2e+01"); 8290 assert(printFloat(buf[], -11.7f, f) == "-1.2e+01"); 8291 assert(printFloat(buf[], -11.3f, f) == "-1.1e+01"); 8292 assert(printFloat(buf[], -11.0f, f) == "-1.1e+01"); 8293 8294 assert(printFloat(buf[], 11.5f, f, RoundingMode.toZero) == "1.1e+01"); 8295 assert(printFloat(buf[], 12.5f, f, RoundingMode.toZero) == "1.2e+01"); 8296 assert(printFloat(buf[], 11.7f, f, RoundingMode.toZero) == "1.1e+01"); 8297 assert(printFloat(buf[], 11.3f, f, RoundingMode.toZero) == "1.1e+01"); 8298 assert(printFloat(buf[], 11.0f, f, RoundingMode.toZero) == "1.1e+01"); 8299 assert(printFloat(buf[], -11.5f, f, RoundingMode.toZero) == "-1.1e+01"); 8300 assert(printFloat(buf[], -12.5f, f, RoundingMode.toZero) == "-1.2e+01"); 8301 assert(printFloat(buf[], -11.7f, f, RoundingMode.toZero) == "-1.1e+01"); 8302 assert(printFloat(buf[], -11.3f, f, RoundingMode.toZero) == "-1.1e+01"); 8303 assert(printFloat(buf[], -11.0f, f, RoundingMode.toZero) == "-1.1e+01"); 8304 8305 assert(printFloat(buf[], 11.5f, f, RoundingMode.up) == "1.2e+01"); 8306 assert(printFloat(buf[], 12.5f, f, RoundingMode.up) == "1.3e+01"); 8307 assert(printFloat(buf[], 11.7f, f, RoundingMode.up) == "1.2e+01"); 8308 assert(printFloat(buf[], 11.3f, f, RoundingMode.up) == "1.2e+01"); 8309 assert(printFloat(buf[], 11.0f, f, RoundingMode.up) == "1.1e+01"); 8310 assert(printFloat(buf[], -11.5f, f, RoundingMode.up) == "-1.1e+01"); 8311 assert(printFloat(buf[], -12.5f, f, RoundingMode.up) == "-1.2e+01"); 8312 assert(printFloat(buf[], -11.7f, f, RoundingMode.up) == "-1.1e+01"); 8313 assert(printFloat(buf[], -11.3f, f, RoundingMode.up) == "-1.1e+01"); 8314 assert(printFloat(buf[], -11.0f, f, RoundingMode.up) == "-1.1e+01"); 8315 8316 assert(printFloat(buf[], 11.5f, f, RoundingMode.down) == "1.1e+01"); 8317 assert(printFloat(buf[], 12.5f, f, RoundingMode.down) == "1.2e+01"); 8318 assert(printFloat(buf[], 11.7f, f, RoundingMode.down) == "1.1e+01"); 8319 assert(printFloat(buf[], 11.3f, f, RoundingMode.down) == "1.1e+01"); 8320 assert(printFloat(buf[], 11.0f, f, RoundingMode.down) == "1.1e+01"); 8321 assert(printFloat(buf[], -11.5f, f, RoundingMode.down) == "-1.2e+01"); 8322 assert(printFloat(buf[], -12.5f, f, RoundingMode.down) == "-1.3e+01"); 8323 assert(printFloat(buf[], -11.7f, f, RoundingMode.down) == "-1.2e+01"); 8324 assert(printFloat(buf[], -11.3f, f, RoundingMode.down) == "-1.2e+01"); 8325 assert(printFloat(buf[], -11.0f, f, RoundingMode.down) == "-1.1e+01"); 8326 } 8327 8328 @safe unittest 8329 { 8330 char[256] buf; 8331 auto f = FormatSpec!dchar(""); 8332 f.spec = 'e'; 8333 assert(printFloat(buf[], double.nan, f) == "nan"); 8334 assert(printFloat(buf[], -double.nan, f) == "-nan"); 8335 assert(printFloat(buf[], double.infinity, f) == "inf"); 8336 assert(printFloat(buf[], -double.infinity, f) == "-inf"); 8337 assert(printFloat(buf[], 0.0, f) == "0.000000e+00"); 8338 assert(printFloat(buf[], -0.0, f) == "-0.000000e+00"); 8339 // / 1000 needed due to bug 20361 8340 assert(printFloat(buf[], 1e-307 / 1000, f) == "1.000000e-310"); 8341 assert(printFloat(buf[], -1e-307 / 1000, f) == "-1.000000e-310"); 8342 assert(printFloat(buf[], 1e-30, f) == "1.000000e-30"); 8343 assert(printFloat(buf[], -1e-30, f) == "-1.000000e-30"); 8344 assert(printFloat(buf[], 1e-10, f) == "1.000000e-10"); 8345 assert(printFloat(buf[], -1e-10, f) == "-1.000000e-10"); 8346 assert(printFloat(buf[], 0.1, f) == "1.000000e-01"); 8347 assert(printFloat(buf[], -0.1, f) == "-1.000000e-01"); 8348 assert(printFloat(buf[], 10.0, f) == "1.000000e+01"); 8349 assert(printFloat(buf[], -10.0, f) == "-1.000000e+01"); 8350 assert(printFloat(buf[], 1e300, f) == "1.000000e+300"); 8351 assert(printFloat(buf[], -1e300, f) == "-1.000000e+300"); 8352 8353 import std.math : nextUp, nextDown; 8354 assert(printFloat(buf[], nextUp(0.0), f) == "4.940656e-324"); 8355 assert(printFloat(buf[], nextDown(-0.0), f) == "-4.940656e-324"); 8356 } 8357 8358 @safe unittest 8359 { 8360 char[256] buf; 8361 auto f = FormatSpec!dchar(""); 8362 f.spec = 'e'; 8363 8364 import std.math : nextUp; 8365 8366 double eps = nextUp(0.0); 8367 f.precision = 1000; 8368 assert(printFloat(buf[], eps, f) == 8369 "4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983636163599" 8370 ~"23797965646954457177309266567103559397963987747960107818781263007131903114045278458171678489821036" 8371 ~"88718636056998730723050006387409153564984387312473397273169615140031715385398074126238565591171026" 8372 ~"65855668676818703956031062493194527159149245532930545654440112748012970999954193198940908041656332" 8373 ~"45247571478690147267801593552386115501348035264934720193790268107107491703332226844753335720832431" 8374 ~"93609238289345836806010601150616980975307834227731832924790498252473077637592724787465608477820373" 8375 ~"44696995336470179726777175851256605511991315048911014510378627381672509558373897335989936648099411" 8376 ~"64205702637090279242767544565229087538682506419718265533447265625000000000000000000000000000000000" 8377 ~"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 8378 ~"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" 8379 ~"000000000000000000000e-324"); 8380 8381 f.precision = 50; 8382 assert(printFloat(buf[], double.max, f) == 8383 "1.79769313486231570814527423731704356798070567525845e+308"); 8384 assert(printFloat(buf[], double.epsilon, f) == 8385 "2.22044604925031308084726333618164062500000000000000e-16"); 8386 8387 f.precision = 10; 8388 assert(printFloat(buf[], 1.0/3.0, f) == "3.3333333333e-01"); 8389 assert(printFloat(buf[], 1.0/7.0, f) == "1.4285714286e-01"); 8390 assert(printFloat(buf[], 1.0/9.0, f) == "1.1111111111e-01"); 8391 } 8392 8393 @safe unittest 8394 { 8395 char[256] buf; 8396 auto f = FormatSpec!dchar(""); 8397 f.spec = 'e'; 8398 f.precision = 15; 8399 8400 import std.math : E, PI, PI_2, PI_4, M_1_PI, M_2_PI, M_2_SQRTPI, 8401 LN10, LN2, LOG2, LOG2E, LOG2T, LOG10E, SQRT2, SQRT1_2; 8402 8403 assert(printFloat(buf[], cast(double) E, f) == "2.718281828459045e+00"); 8404 assert(printFloat(buf[], cast(double) PI, f) == "3.141592653589793e+00"); 8405 assert(printFloat(buf[], cast(double) PI_2, f) == "1.570796326794897e+00"); 8406 assert(printFloat(buf[], cast(double) PI_4, f) == "7.853981633974483e-01"); 8407 assert(printFloat(buf[], cast(double) M_1_PI, f) == "3.183098861837907e-01"); 8408 assert(printFloat(buf[], cast(double) M_2_PI, f) == "6.366197723675814e-01"); 8409 assert(printFloat(buf[], cast(double) M_2_SQRTPI, f) == "1.128379167095513e+00"); 8410 assert(printFloat(buf[], cast(double) LN10, f) == "2.302585092994046e+00"); 8411 assert(printFloat(buf[], cast(double) LN2, f) == "6.931471805599453e-01"); 8412 assert(printFloat(buf[], cast(double) LOG2, f) == "3.010299956639812e-01"); 8413 assert(printFloat(buf[], cast(double) LOG2E, f) == "1.442695040888963e+00"); 8414 assert(printFloat(buf[], cast(double) LOG2T, f) == "3.321928094887362e+00"); 8415 assert(printFloat(buf[], cast(double) LOG10E, f) == "4.342944819032518e-01"); 8416 assert(printFloat(buf[], cast(double) SQRT2, f) == "1.414213562373095e+00"); 8417 assert(printFloat(buf[], cast(double) SQRT1_2, f) == "7.071067811865476e-01"); 8418 } 8419 8420 // for 100% coverage 8421 @safe unittest 8422 { 8423 char[256] buf; 8424 auto f = FormatSpec!dchar(""); 8425 f.spec = 'E'; 8426 f.precision = 80; 8427 assert(printFloat(buf[], 5.62776e+12f, f) == 8428 "5.62775982080000000000000000000000000000000000000000000000000000000000000000000000E+12"); 8429 8430 f.precision = 49; 8431 assert(printFloat(buf[], 2.5997869e-12f, f) == 8432 "2.5997869221999758693186777236405760049819946289062E-12"); 8433 8434 f.precision = 6; 8435 assert(printFloat(buf[], -1.1418613e+07f, f) == "-1.141861E+07"); 8436 assert(printFloat(buf[], -1.368281e+07f, f) == "-1.368281E+07"); 8437 8438 f.precision = 0; 8439 assert(printFloat(buf[], 709422.0f, f, RoundingMode.up) == "8E+05"); 8440 8441 f.precision = 1; 8442 assert(printFloat(buf[], -245.666f, f) == "-2.5E+02"); 8443 } 8444 8445 // check no allocations 8446 @system unittest 8447 { 8448 import core.memory; 8449 auto stats = GC.stats; 8450 8451 char[256] buf; 8452 auto f = FormatSpec!dchar(""); 8453 f.spec = 'a'; 8454 assert(printFloat(buf[], float.nan, f) == "nan"); 8455 assert(printFloat(buf[], -float.infinity, f) == "-inf"); 8456 assert(printFloat(buf[], 0.0f, f) == "0x0p+0"); 8457 8458 assert(printFloat(buf[], -double.nan, f) == "-nan"); 8459 assert(printFloat(buf[], double.infinity, f) == "inf"); 8460 assert(printFloat(buf[], -0.0, f) == "-0x0p+0"); 8461 8462 import std.math : nextUp, E; 8463 8464 assert(printFloat(buf[], nextUp(0.0f), f) == "0x0.000002p-126"); 8465 assert(printFloat(buf[], cast(float) E, f) == "0x1.5bf0a8p+1"); 8466 8467 f.spec = 'E'; 8468 f.precision = 80; 8469 assert(printFloat(buf[], 5.62776e+12f, f) == 8470 "5.62775982080000000000000000000000000000000000000000000000000000000000000000000000E+12"); 8471 8472 f.precision = 6; 8473 assert(printFloat(buf[], -1.1418613e+07f, f) == "-1.141861E+07"); 8474 8475 assert(GC.stats.usedSize == stats.usedSize); 8476 }