1 // Written in the D programming language. 2 3 /** 4 This package provides string formatting functionality using 5 `printf` style format strings. 6 7 $(BOOKTABLE , 8 $(TR $(TH Submodule) $(TH Function Name) $(TH Description)) 9 $(TR 10 $(TD $(I package)) 11 $(TD $(LREF format)) 12 $(TD Converts its arguments according to a format string into a string.) 13 ) 14 $(TR 15 $(TD $(I package)) 16 $(TD $(LREF sformat)) 17 $(TD Converts its arguments according to a format string into a buffer.) 18 ) 19 $(TR 20 $(TD $(I package)) 21 $(TD $(LREF FormatException)) 22 $(TD Signals a problem while formatting.) 23 ) 24 $(TR 25 $(TD $(MREF_ALTTEXT $(D write), std, format, write)) 26 $(TD $(REF_ALTTEXT $(D formattedWrite), formattedWrite, std, format, write)) 27 $(TD Converts its arguments according to a format string and writes 28 the result to an output range.) 29 ) 30 $(TR 31 $(TD $(MREF_ALTTEXT $(D write), std, format, write)) 32 $(TD $(REF_ALTTEXT $(D formatValue), formatValue, std, format, write)) 33 $(TD Formats a value of any type according to a format specifier and 34 writes the result to an output range.) 35 ) 36 $(TR 37 $(TD $(MREF_ALTTEXT $(D read), std, format, read)) 38 $(TD $(REF_ALTTEXT $(D formattedRead), formattedRead, std, format, read)) 39 $(TD Reads an input range according to a format string and stores the read 40 values into its arguments.) 41 ) 42 $(TR 43 $(TD $(MREF_ALTTEXT $(D read), std, format, read)) 44 $(TD $(REF_ALTTEXT $(D unformatValue), unformatValue, std, format, read)) 45 $(TD Reads a value from the given input range and converts it according to 46 a format specifier.) 47 ) 48 $(TR 49 $(TD $(MREF_ALTTEXT $(D spec), std, format, spec)) 50 $(TD $(REF_ALTTEXT $(D FormatSpec), FormatSpec, std, format, spec)) 51 $(TD A general handler for format strings.) 52 ) 53 $(TR 54 $(TD $(MREF_ALTTEXT $(D spec), std, format, spec)) 55 $(TD $(REF_ALTTEXT $(D singleSpec), singleSpec, std, format, spec)) 56 $(TD Helper function that returns a `FormatSpec` for a single format specifier.) 57 )) 58 59 Limitation: This package does not support localization, but 60 adheres to the rounding mode of the floating point unit, if 61 available. 62 63 $(H3 $(LNAME2 format-strings, Format Strings)) 64 65 The functions contained in this package use $(I format strings). A 66 format string describes the layout of another string for reading or 67 writing purposes. A format string is composed of normal text 68 interspersed with $(I format specifiers). A format specifier starts 69 with a percentage sign $(B '%'), optionally followed by one or more 70 $(I parameters) and ends with a $(I format indicator). A format 71 indicator may be a simple $(I format character) or a $(I compound 72 indicator). 73 74 $(I Format strings) are composed according to the following grammar: 75 76 $(PRE 77 $(I FormatString): 78 $(I FormatStringItem) $(I FormatString) 79 $(I FormatStringItem): 80 $(I Character) 81 $(I FormatSpecifier) 82 $(I FormatSpecifier): 83 $(B '%') $(I Parameters) $(I FormatIndicator) 84 85 $(I FormatIndicator): 86 $(I FormatCharacter) 87 $(I CompoundIndicator) 88 $(I FormatCharacter): 89 $(I see remark below) 90 $(I CompoundIndicator): 91 $(B '$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)') 92 $(B '$(LPAREN)') $(I FormatString) $(B '%|') $(I Delimiter) $(B '%$(RPAREN)') 93 $(I Delimiter) 94 $(I empty) 95 $(I Character) $(I Delimiter) 96 97 $(I Parameters): 98 $(I Position) $(I Flags) $(I Width) $(I Precision) $(I Separator) 99 $(I Position): 100 $(I empty) 101 $(I Integer) $(B '$') 102 $(I Integer) $(B ':') $(I Integer) $(B '$') 103 $(I Integer) $(B ':') $(B '$') 104 $(I Flags): 105 $(I empty) 106 $(I Flag) $(I Flags) 107 $(I Flag): 108 $(B '-')|$(B '+')|$(B ' ')|$(B '0')|$(B '#')|$(B '=') 109 $(I Width): 110 $(I OptionalPositionalInteger) 111 $(I Precision): 112 $(I empty) 113 $(B '.') $(I OptionalPositionalInteger) 114 $(I Separator): 115 $(I empty) 116 $(B ',') $(I OptionalInteger) 117 $(B ',') $(I OptionalInteger) $(B '?') 118 $(I OptionalInteger): 119 $(I empty) 120 $(I Integer) 121 $(B '*') 122 $(I OptionalPositionalInteger): 123 $(I OptionalInteger) 124 $(B '*') $(I Integer) $(B '$') 125 126 $(I Character) 127 $(B '%%') 128 $(I AnyCharacterExceptPercent) 129 $(I Integer): 130 $(I NonZeroDigit) $(I Digits) 131 $(I Digits): 132 $(I empty) 133 $(I Digit) $(I Digits) 134 $(I NonZeroDigit): 135 $(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9') 136 $(I Digit): 137 $(B '0')|$(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9') 138 ) 139 140 Note: $(I FormatCharacter) is unspecified. It can be any character 141 that has no other purpose in this grammar, but it is 142 recommended to assign (lower- and uppercase) letters. 143 144 Note: The $(I Parameters) of a $(I CompoundIndicator) are currently 145 limited to a $(B '-') flag. 146 147 $(H4 $(LNAME2 format-indicator, Format Indicator)) 148 149 The $(I format indicator) can either be a single character or an 150 expression surrounded by $(B %\() and $(B %\)). It specifies the 151 basic manner in which a value will be formatted and is the minimum 152 requirement to format a value. 153 154 The following characters can be used as $(I format characters): 155 156 $(BOOKTABLE , 157 $(TR $(TH FormatCharacter) $(TH Semantics)) 158 $(TR $(TD $(B 's')) 159 $(TD To be formatted in a human readable format. 160 Can be used with all types.)) 161 $(TR $(TD $(B 'c')) 162 $(TD To be formatted as a character.)) 163 $(TR $(TD $(B 'd')) 164 $(TD To be formatted as a signed decimal integer.)) 165 $(TR $(TD $(B 'u')) 166 $(TD To be formatted as a decimal image of the underlying bit representation.)) 167 $(TR $(TD $(B 'b')) 168 $(TD To be formatted as a binary image of the underlying bit representation.)) 169 $(TR $(TD $(B 'o')) 170 $(TD To be formatted as an octal image of the underlying bit representation.)) 171 $(TR $(TD $(B 'x') / $(B 'X')) 172 $(TD To be formatted as a hexadecimal image of the underlying bit representation.)) 173 $(TR $(TD $(B 'e') / $(B 'E')) 174 $(TD To be formatted as a real number in decimal scientific notation.)) 175 $(TR $(TD $(B 'f') / $(B 'F')) 176 $(TD To be formatted as a real number in decimal natural notation.)) 177 $(TR $(TD $(B 'g') / $(B 'G')) 178 $(TD To be formatted as a real number in decimal short notation. 179 Depending on the number, a scientific notation or 180 a natural notation is used.)) 181 $(TR $(TD $(B 'a') / $(B 'A')) 182 $(TD To be formatted as a real number in hexadecimal scientific notation.)) 183 $(TR $(TD $(B 'r')) 184 $(TD To be formatted as raw bytes. 185 The output may not be printable and depends on endianness.)) 186 ) 187 188 The $(I compound indicator) can be used to describe compound types 189 like arrays or structs in more detail. A compound type is enclosed 190 within $(B '%\(') and $(B '%\)'). The enclosed sub-format string is 191 applied to individual elements. The trailing portion of the 192 sub-format string following the specifier for the element is 193 interpreted as the delimiter, and is therefore omitted following the 194 last element. The $(B '%|') specifier may be used to explicitly 195 indicate the start of the delimiter, so that the preceding portion of 196 the string will be included following the last element. 197 198 The $(I format string) inside of the $(I compound indicator) should 199 contain exactly one $(I format specifier) (two in case of associative 200 arrays), which specifies the formatting mode of the elements of the 201 compound type. This $(I format specifier) can be a $(I compound 202 indicator) itself. 203 204 Note: Inside a $(I compound indicator), strings and characters are 205 escaped automatically. To avoid this behavior, use `"%-$(LPAREN)"` 206 instead of `"%$(LPAREN)"`. 207 208 $(H4 $(LNAME2 flags, Flags)) 209 210 There are several flags that affect the outcome of the formatting. 211 212 $(BOOKTABLE , 213 $(TR $(TH Flag) $(TH Semantics)) 214 $(TR $(TD $(B '-')) 215 $(TD When the formatted result is shorter than the value 216 given by the width parameter, the output is left 217 justified. Without the $(B '-') flag, the output remains 218 right justified. 219 220 There are two exceptions where the $(B '-') flag has a 221 different meaning: (1) with $(B 'r') it denotes to use little 222 endian and (2) in case of a compound indicator it means that 223 no special handling of the members is applied.)) 224 $(TR $(TD $(B '=')) 225 $(TD When the formatted result is shorter than the value 226 given by the width parameter, the output is centered. 227 If the central position is not possible it is moved slightly 228 to the right. In this case, if $(B '-') flag is present in 229 addition to the $(B '=') flag, it is moved slightly to the left.)) 230 $(TR $(TD $(B '+') / $(B ' ')) 231 $(TD Applies to numerical values. By default, positive numbers are not 232 formatted to include the `+` sign. With one of these two flags present, 233 positive numbers are preceded by a plus sign or a space. 234 When both flags are present, a plus sign is used. 235 236 In case of $(B 'r'), a big endian format is used.)) 237 $(TR $(TD $(B '0')) 238 $(TD Is applied to numerical values that are printed right justified. 239 If the zero flag is present, the space left to the number is 240 filled with zeros instead of spaces.)) 241 $(TR $(TD $(B '#')) 242 $(TD Denotes that an alternative output must be used. This depends on the type 243 to be formatted and the $(I format character) used. See the 244 sections below for more information.)) 245 ) 246 247 $(H4 $(LNAME2 width-precision-separator, Width, Precision and Separator)) 248 249 The $(I width) parameter specifies the minimum width of the result. 250 251 The meaning of $(I precision) depends on the format indicator. For 252 integers it denotes the minimum number of digits printed, for 253 real numbers it denotes the number of fractional digits and for 254 strings and compound types it denotes the maximum number of elements 255 that are included in the output. 256 257 A $(I separator) is used for formatting numbers. If it is specified, 258 the output is divided into chunks of three digits, separated by a $(B 259 ','). The number of digits in a chunk can be given explicitly by 260 providing a number or a $(B '*') after the $(B ','). 261 262 In all three cases the number of digits can be replaced by a $(B 263 '*'). In this scenario, the next argument is used as the number of 264 digits. If the argument is a negative number, the $(I precision) and 265 $(I separator) parameters are considered unspecified. For $(I width), 266 the absolute value is used and the $(B '-') flag is set. 267 268 The $(I separator) can also be followed by a $(B '?'). In that case, 269 an additional argument is used to specify the symbol that should be 270 used to separate the chunks. 271 272 $(H4 $(LNAME2 position, Position)) 273 274 By default, the arguments are processed in the provided order. With 275 the $(I position) parameter it is possible to address arguments 276 directly. It is also possible to denote a series of arguments with 277 two numbers separated by $(B ':'), that are all processed in the same 278 way. The second number can be omitted. In that case the series ends 279 with the last argument. 280 281 It's also possible to use positional arguments for $(I width), $(I 282 precision) and $(I separator) by adding a number and a $(B 283 '$(DOLLAR)') after the $(B '*'). 284 285 $(H4 $(LNAME2 types, Types)) 286 287 This section describes the result of combining types with format 288 characters. It is organized in 2 subsections: a list of general 289 information regarding the formatting of types in the presence of 290 format characters and a table that contains details for every 291 available combination of type and format character. 292 293 When formatting types, the following rules apply: 294 295 $(UL 296 $(LI If the format character is upper case, the resulting string will 297 be formatted using upper case letters.) 298 $(LI The default precision for floating point numbers is 6 digits.) 299 $(LI Rounding of floating point numbers adheres to the rounding mode 300 of the floating point unit, if available.) 301 $(LI The floating point values `NaN` and `Infinity` are formatted as 302 `nan` and `inf`, possibly preceded by $(B '+') or $(B '-') sign.) 303 $(LI Formatting reals is only supported for 64 bit reals and 80 bit reals. 304 All other reals are cast to double before they are formatted. This will 305 cause the result to be `inf` for very large numbers.) 306 $(LI Characters and strings formatted with the $(B 's') format character 307 inside of compound types are surrounded by single and double quotes 308 and unprintable characters are escaped. To avoid this, a $(B '-') 309 flag can be specified for the compound specifier 310 $(LPAREN)e.g. `"%-$(LPAREN)%s%$(RPAREN)"` instead of `"%$(LPAREN)%s%$(RPAREN)"` $(RPAREN).) 311 $(LI Structs, unions, classes and interfaces are formatted by calling a 312 `toString` method if available. 313 See $(MREF_ALTTEXT $(D module std.format.write), std, format, write) for more 314 details.) 315 $(LI Only part of these combinations can be used for reading. See 316 $(MREF_ALTTEXT $(D module std.format.read), std, format, read) for more 317 detailed information.) 318 ) 319 320 This table contains descriptions for every possible combination of 321 type and format character: 322 323 $(BOOKTABLE , 324 $(TR $(THMINWIDTH Type) $(THMINWIDTH Format Character) $(TH Formatted as...)) 325 $(TR $(MULTIROW_CELL 1, `null`) 326 $(TD $(B 's')) 327 $(TD `null`) 328 ) 329 $(TR $(MULTIROW_CELL 3, `bool`) 330 $(TD $(B 's')) 331 $(TD `false` or `true`) 332 ) 333 $(TR $(TD $(B 'b'), $(B 'd'), $(B 'o'), $(B 'u'), $(B 'x'), $(B 'X')) 334 $(TD As the integrals 0 or 1 with the same format character. 335 336 $(I Please note, that $(B 'o') and $(B 'x') with $(B '#') flag 337 might produce unexpected results due to special handling of 338 the value 0.)) 339 ) 340 $(TR $(TD $(B 'r')) 341 $(TD `\0` or `\1`) 342 ) 343 $(TR $(MULTIROW_CELL 4, $(I Integral)) 344 $(TD $(B 's'), $(B 'd')) 345 $(TD A signed decimal number. The $(B '#') flag is ignored.) 346 ) 347 $(TR $(TD $(B 'b'), $(B 'o'), $(B 'u'), $(B 'x'), $(B 'X')) 348 $(TD An unsigned binary, decimal, octal or hexadecimal number. 349 350 In case of $(B 'o') and $(B 'x'), the $(B '#') flag 351 denotes that the number must be preceded by `0` and `0x`, with 352 the exception of the value 0, where this does not apply. For 353 $(B 'b') and $(B 'u') the $(B '#') flag has no effect.) 354 ) 355 $(TR $(TD $(B 'e'), $(B 'E'), $(B 'f'), $(B 'F'), $(B 'g'), $(B 'G'), $(B 'a'), $(B 'A')) 356 $(TD As a floating point value with the same specifier. 357 358 Default precision is large enough to add all digits 359 of the integral value. 360 361 In case of $(B 'a') and $(B 'A'), the integral digit can be 362 any hexadecimal digit. 363 ) 364 ) 365 $(TR $(TD $(B 'r')) 366 $(TD Characters taken directly from the binary representation.) 367 ) 368 $(TR $(MULTIROW_CELL 5, $(I Floating Point)) 369 $(TD $(B 'e'), $(B 'E')) 370 $(TD Scientific notation: Exactly one integral digit followed by a dot 371 and fractional digits, followed by the exponent. 372 The exponent is formatted as $(B 'e') followed by 373 a $(B '+') or $(B '-') sign, followed by at least 374 two digits. 375 376 When there are no fractional digits and the $(B '#') flag 377 is $(I not) present, the dot is omitted.) 378 ) 379 $(TR $(TD $(B 'f'), $(B 'F')) 380 $(TD Natural notation: Integral digits followed by a dot and 381 fractional digits. 382 383 When there are no fractional digits and the $(B '#') flag 384 is $(I not) present, the dot is omitted. 385 386 $(I Please note: the difference between $(B 'f') and $(B 'F') 387 is only visible for `NaN` and `Infinity`.)) 388 ) 389 $(TR $(TD $(B 's'), $(B 'g'), $(B 'G')) 390 $(TD Short notation: If the absolute value is larger than `10 ^^ precision` 391 or smaller than `0.0001`, the scientific notation is used. 392 If not, the natural notation is applied. 393 394 In both cases $(I precision) denotes the count of all digits, including 395 the integral digits. Trailing zeros (including a trailing dot) are removed. 396 397 If $(B '#') flag is present, trailing zeros are not removed.) 398 ) 399 $(TR $(TD $(B 'a'), $(B 'A')) 400 $(TD Hexadecimal scientific notation: `0x` followed by `1` 401 (or `0` in case of value zero or denormalized number) 402 followed by a dot, fractional digits in hexadecimal 403 notation and an exponent. The exponent is build by `p`, 404 followed by a sign and the exponent in $(I decimal) notation. 405 406 When there are no fractional digits and the $(B '#') flag 407 is $(I not) present, the dot is omitted.) 408 ) 409 $(TR $(TD $(B 'r')) 410 $(TD Characters taken directly from the binary representation.) 411 ) 412 $(TR $(MULTIROW_CELL 3, $(I Character)) 413 $(TD $(B 's'), $(B 'c')) 414 $(TD As the character. 415 416 Inside of a compound indicator $(B 's') is treated differently: The 417 character is surrounded by single quotes and non printable 418 characters are escaped. This can be avoided by preceding 419 the compound indicator with a $(B '-') flag 420 $(LPAREN)e.g. `"%-$(LPAREN)%s%$(RPAREN)"`$(RPAREN).) 421 ) 422 $(TR $(TD $(B 'b'), $(B 'd'), $(B 'o'), $(B 'u'), $(B 'x'), $(B 'X')) 423 $(TD As the integral that represents the character.) 424 ) 425 $(TR $(TD $(B 'r')) 426 $(TD Characters taken directly from the binary representation.) 427 ) 428 $(TR $(MULTIROW_CELL 3, $(I String)) 429 $(TD $(B 's')) 430 $(TD The sequence of characters that form the string. 431 432 Inside of a compound indicator the string is surrounded by double quotes 433 and non printable characters are escaped. This can be avoided 434 by preceding the compound indicator with a $(B '-') flag 435 $(LPAREN)e.g. `"%-$(LPAREN)%s%$(RPAREN)"`$(RPAREN).) 436 ) 437 $(TR $(TD $(B 'r')) 438 $(TD The sequence of characters, each formatted with $(B 'r').) 439 ) 440 $(TR $(TD compound) 441 $(TD As an array of characters.) 442 ) 443 $(TR $(MULTIROW_CELL 3, $(I Array)) 444 $(TD $(B 's')) 445 $(TD When the elements are characters, the array is formatted as 446 a string. In all other cases the array is surrounded by square brackets 447 and the elements are separated by a comma and a space. If the elements 448 are strings, they are surrounded by double quotes and non 449 printable characters are escaped.) 450 ) 451 $(TR $(TD $(B 'r')) 452 $(TD The sequence of the elements, each formatted with $(B 'r').) 453 ) 454 $(TR $(TD compound) 455 $(TD The sequence of the elements, each formatted according to the specifications 456 given inside of the compound specifier.) 457 ) 458 $(TR $(MULTIROW_CELL 2, $(I Associative Array)) 459 $(TD $(B 's')) 460 $(TD As a sequence of the elements in unpredictable order. The output is 461 surrounded by square brackets. The elements are separated by a 462 comma and a space. The elements are formatted as `key:value`.) 463 ) 464 $(TR $(TD compound) 465 $(TD As a sequence of the elements in unpredictable order. Each element 466 is formatted according to the specifications given inside of the 467 compound specifier. The first specifier is used for formatting 468 the key and the second specifier is used for formatting the value. 469 The order can be changed with positional arguments. For example 470 `"%(%2$s (%1$s), %)"` will write the value, followed by the key in 471 parenthesis.) 472 ) 473 $(TR $(MULTIROW_CELL 2, $(I Enum)) 474 $(TD $(B 's')) 475 $(TD The name of the value. If the name is not available, the base value 476 is used, preceeded by a cast.) 477 ) 478 $(TR $(TD All, but $(B 's')) 479 $(TD Enums can be formatted with all format characters that can be used 480 with the base value. In that case they are formatted like the base value.) 481 ) 482 $(TR $(MULTIROW_CELL 3, $(I Input Range)) 483 $(TD $(B 's')) 484 $(TD When the elements of the range are characters, they are written like a string. 485 In all other cases, the elements are enclosed by square brackets and separated 486 by a comma and a space.) 487 ) 488 $(TR $(TD $(B 'r')) 489 $(TD The sequence of the elements, each formatted with $(B 'r').) 490 ) 491 $(TR $(TD compound) 492 $(TD The sequence of the elements, each formatted according to the specifications 493 given inside of the compound specifier.) 494 ) 495 $(TR $(MULTIROW_CELL 1, $(I Struct)) 496 $(TD $(B 's')) 497 $(TD When the struct has neither an applicable `toString` 498 nor is an input range, it is formatted as follows: 499 `StructType(field1, field2, ...)`.) 500 ) 501 $(TR $(MULTIROW_CELL 1, $(I Class)) 502 $(TD $(B 's')) 503 $(TD When the class has neither an applicable `toString` 504 nor is an input range, it is formatted as the 505 fully qualified name of the class.) 506 ) 507 $(TR $(MULTIROW_CELL 1, $(I Union)) 508 $(TD $(B 's')) 509 $(TD When the union has neither an applicable `toString` 510 nor is an input range, it is formatted as its base name.) 511 ) 512 $(TR $(MULTIROW_CELL 2, $(I Pointer)) 513 $(TD $(B 's')) 514 $(TD A null pointer is formatted as 'null'. All other pointers are 515 formatted as hexadecimal numbers with the format character $(B 'X').) 516 ) 517 $(TR $(TD $(B 'x'), $(B 'X')) 518 $(TD Formatted as a hexadecimal number.) 519 ) 520 $(TR $(MULTIROW_CELL 3, $(I SIMD vector)) 521 $(TD $(B 's')) 522 $(TD The array is surrounded by square brackets 523 and the elements are separated by a comma and a space.) 524 ) 525 $(TR $(TD $(B 'r')) 526 $(TD The sequence of the elements, each formatted with $(B 'r').) 527 ) 528 $(TR $(TD compound) 529 $(TD The sequence of the elements, each formatted according to the specifications 530 given inside of the compound specifier.) 531 ) 532 $(TR $(MULTIROW_CELL 1, $(I Delegate)) 533 $(TD $(B 's'), $(B 'r'), compound) 534 $(TD As the `.stringof` of this delegate treated as a string. 535 536 $(I Please note: The implementation is currently buggy 537 and its use is discouraged.)) 538 ) 539 ) 540 541 Copyright: Copyright The D Language Foundation 2000-2021. 542 543 Macros: 544 SUBREF = $(REF_ALTTEXT $2, $2, std, format, $1)$(NBSP) 545 MULTIROW_CELL = <td rowspan="$1">$+</td> 546 THMINWIDTH = <th scope="col" width="20%">$0</th> 547 548 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 549 550 Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, 551 Andrei Alexandrescu), and Kenji Hara 552 553 Source: $(PHOBOSSRC std/format/package.d) 554 */ 555 module std.format; 556 557 /// Simple use: 558 @safe unittest 559 { 560 // Easiest way is to use `%s` everywhere: 561 assert(format("I got %s %s for %s euros.", 30, "eggs", 5.27) == "I got 30 eggs for 5.27 euros."); 562 563 // Other format characters provide more control: 564 assert(format("I got %b %(%X%) for %f euros.", 30, "eggs", 5.27) == "I got 11110 65676773 for 5.270000 euros."); 565 } 566 567 /// Compound specifiers allow formatting arrays and other compound types: 568 @safe unittest 569 { 570 /* 571 The trailing end of the sub-format string following the specifier for 572 each item is interpreted as the array delimiter, and is therefore 573 omitted following the last array item: 574 */ 575 assert(format("My items are %(%s %).", [1,2,3]) == "My items are 1 2 3."); 576 assert(format("My items are %(%s, %).", [1,2,3]) == "My items are 1, 2, 3."); 577 578 /* 579 The "%|" delimiter specifier may be used to indicate where the 580 delimiter begins, so that the portion of the format string prior to 581 it will be retained in the last array element: 582 */ 583 assert(format("My items are %(-%s-%|, %).", [1,2,3]) == "My items are -1-, -2-, -3-."); 584 585 /* 586 These compound format specifiers may be nested in the case of a 587 nested array argument: 588 */ 589 auto mat = [[1, 2, 3], 590 [4, 5, 6], 591 [7, 8, 9]]; 592 593 assert(format("%(%(%d %) - %)", mat), "1 2 3 - 4 5 6 - 7 8 9"); 594 assert(format("[%(%(%d %) - %)]", mat), "[1 2 3 - 4 5 6 - 7 8 9]"); 595 assert(format("[%([%(%d %)]%| - %)]", mat), "[1 2 3] - [4 5 6] - [7 8 9]"); 596 597 /* 598 Strings and characters are escaped automatically inside compound 599 format specifiers. To avoid this behavior, use "%-(" instead of "%(": 600 */ 601 assert(format("My friends are %s.", ["John", "Nancy"]) == `My friends are ["John", "Nancy"].`); 602 assert(format("My friends are %(%s, %).", ["John", "Nancy"]) == `My friends are "John", "Nancy".`); 603 assert(format("My friends are %-(%s, %).", ["John", "Nancy"]) == `My friends are John, Nancy.`); 604 } 605 606 /// Using parameters: 607 @safe unittest 608 { 609 // Flags can be used to influence to outcome: 610 assert(format("%g != %+#g", 3.14, 3.14) == "3.14 != +3.14000"); 611 612 // Width and precision help to arrange the formatted result: 613 assert(format(">%10.2f<", 1234.56789) == "> 1234.57<"); 614 615 // Numbers can be grouped: 616 assert(format("%,4d", int.max) == "21,4748,3647"); 617 618 // It's possible to specify the position of an argument: 619 assert(format("%3$s %1$s", 3, 17, 5) == "5 3"); 620 } 621 622 /// Providing parameters as arguments: 623 @safe unittest 624 { 625 // Width as argument 626 assert(format(">%*s<", 10, "abc") == "> abc<"); 627 628 // Precision as argument 629 assert(format(">%.*f<", 5, 123.2) == ">123.20000<"); 630 631 // Grouping as argument 632 assert(format("%,*d", 1, int.max) == "2,1,4,7,4,8,3,6,4,7"); 633 634 // Grouping separator as argument 635 assert(format("%,3?d", '_', int.max) == "2_147_483_647"); 636 637 // All at once 638 assert(format("%*.*,*?d", 20, 15, 6, '/', int.max) == " 000/002147/483647"); 639 } 640 641 public import std.format.read; 642 public import std.format.spec; 643 public import std.format.write; 644 645 import std.exception : enforce; 646 import std.range.primitives : isInputRange; 647 import std.traits : CharTypeOf, isSomeChar, isSomeString, StringTypeOf; 648 import std.format.internal.write : hasToString; 649 650 /** 651 Signals an issue encountered while formatting. 652 */ 653 class FormatException : Exception 654 { 655 /// Generic constructor. 656 @safe @nogc pure nothrow 657 this() 658 { 659 super("format error"); 660 } 661 662 /** 663 Creates a new instance of `FormatException`. 664 665 Params: 666 msg = message of the exception 667 fn = file name of the file where the exception was created (optional) 668 ln = line number of the file where the exception was created (optional) 669 next = for internal use, should always be null (optional) 670 */ 671 @safe @nogc pure nothrow 672 this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) 673 { 674 super(msg, fn, ln, next); 675 } 676 } 677 678 /// 679 @safe unittest 680 { 681 import std.exception : assertThrown; 682 683 assertThrown!FormatException(format("%d", "foo")); 684 } 685 686 package alias enforceFmt = enforce!FormatException; 687 688 // @@@DEPRECATED_[2.107.0]@@@ 689 deprecated("formatElement was accidentally made public and will be removed in 2.107.0") 690 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 691 if (is(StringTypeOf!T) && !hasToString!(T, Char) && !is(T == enum)) 692 { 693 import std.format.internal.write : fe = formatElement; 694 695 fe(w, val, f); 696 } 697 698 // @@@DEPRECATED_[2.107.0]@@@ 699 deprecated("formatElement was accidentally made public and will be removed in 2.107.0") 700 void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) 701 if (is(CharTypeOf!T) && !is(T == enum)) 702 { 703 import std.format.internal.write : fe = formatElement; 704 705 fe(w, val, f); 706 } 707 708 // @@@DEPRECATED_[2.107.0]@@@ 709 deprecated("formatElement was accidentally made public and will be removed in 2.107.0") 710 void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f) 711 if ((!is(StringTypeOf!T) || hasToString!(T, Char)) && !is(CharTypeOf!T) || is(T == enum)) 712 { 713 import std.format.internal.write : fe = formatElement; 714 715 fe(w, val, f); 716 } 717 718 // Like NullSink, but toString() isn't even called at all. Used to test the format string. 719 package struct NoOpSink 720 { 721 void put(E)(scope const E) pure @safe @nogc nothrow {} 722 } 723 724 // @@@DEPRECATED_[2.107.0]@@@ 725 deprecated("unformatElement was accidentally made public and will be removed in 2.107.0") 726 T unformatElement(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) 727 if (isInputRange!Range) 728 { 729 import std.format.internal.read : ue = unformatElement; 730 731 return ue(input, spec); 732 } 733 734 // Used to check format strings are compatible with argument types 735 package(std) enum checkFormatException(alias fmt, Args...) = 736 { 737 import std.conv : text; 738 739 try 740 { 741 auto n = .formattedWrite(NoOpSink(), fmt, Args.init); 742 743 enforceFmt(n == Args.length, text("Orphan format arguments: args[", n, "..", Args.length, "]")); 744 } 745 catch (Exception e) 746 return e.msg; 747 return null; 748 }(); 749 750 /** 751 Converts its arguments according to a format string into a string. 752 753 The second version of `format` takes the format string as template 754 argument. In this case, it is checked for consistency at 755 compile-time and produces slightly faster code, because the length of 756 the output buffer can be estimated in advance. 757 758 Params: 759 fmt = a $(MREF_ALTTEXT format string, std,format) 760 args = a variadic list of arguments to be formatted 761 Char = character type of `fmt` 762 Args = a variadic list of types of the arguments 763 764 Returns: 765 The formatted string. 766 767 Throws: 768 A $(LREF FormatException) if formatting did not succeed. 769 770 See_Also: 771 $(LREF sformat) for a variant, that tries to avoid garbage collection. 772 */ 773 immutable(Char)[] format(Char, Args...)(in Char[] fmt, Args args) 774 if (isSomeChar!Char) 775 { 776 import std.array : appender; 777 778 auto w = appender!(immutable(Char)[]); 779 auto n = formattedWrite(w, fmt, args); 780 version (all) 781 { 782 // In the future, this check will be removed to increase consistency 783 // with formattedWrite 784 import std.conv : text; 785 enforceFmt(n == args.length, text("Orphan format arguments: args[", n, "..", args.length, "]")); 786 } 787 return w.data; 788 } 789 790 /// 791 @safe pure unittest 792 { 793 assert(format("Here are %d %s.", 3, "apples") == "Here are 3 apples."); 794 795 assert("Increase: %7.2f %%".format(17.4285) == "Increase: 17.43 %"); 796 } 797 798 @safe pure unittest 799 { 800 import std.exception : assertCTFEable, assertThrown; 801 802 assertCTFEable!( 803 { 804 assert(format("foo") == "foo"); 805 assert(format("foo%%") == "foo%"); 806 assert(format("foo%s", 'C') == "fooC"); 807 assert(format("%s foo", "bar") == "bar foo"); 808 assert(format("%s foo %s", "bar", "abc") == "bar foo abc"); 809 assert(format("foo %d", -123) == "foo -123"); 810 assert(format("foo %d", 123) == "foo 123"); 811 812 assertThrown!FormatException(format("foo %s")); 813 assertThrown!FormatException(format("foo %s", 123, 456)); 814 815 assert(format("hel%slo%s%s%s", "world", -138, 'c', true) == "helworldlo-138ctrue"); 816 }); 817 818 assert(is(typeof(format("happy")) == string)); 819 assert(is(typeof(format("happy"w)) == wstring)); 820 assert(is(typeof(format("happy"d)) == dstring)); 821 } 822 823 // https://issues.dlang.org/show_bug.cgi?id=16661 824 @safe pure unittest 825 { 826 assert(format("%.2f"d, 0.4) == "0.40"); 827 assert("%02d"d.format(1) == "01"d); 828 } 829 830 @safe unittest 831 { 832 int i; 833 string s; 834 835 s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo"); 836 assert(s == "hello world! true 57 1000000000x foo"); 837 838 s = format("%s %A %s", 1.67, -1.28, float.nan); 839 assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s); 840 841 s = format("%x %X", 0x1234AF, 0xAFAFAFAF); 842 assert(s == "1234af AFAFAFAF"); 843 844 s = format("%b %o", 0x1234AF, 0xAFAFAFAF); 845 assert(s == "100100011010010101111 25753727657"); 846 847 s = format("%d %s", 0x1234AF, 0xAFAFAFAF); 848 assert(s == "1193135 2947526575"); 849 } 850 851 @safe unittest 852 { 853 import std.conv : octal; 854 855 string s; 856 int i; 857 858 s = format("%#06.*f", 2, 12.345); 859 assert(s == "012.35"); 860 861 s = format("%#0*.*f", 6, 2, 12.345); 862 assert(s == "012.35"); 863 864 s = format("%7.4g:", 12.678); 865 assert(s == " 12.68:"); 866 867 s = format("%7.4g:", 12.678L); 868 assert(s == " 12.68:"); 869 870 s = format("%04f|%05d|%#05x|%#5x", -4.0, -10, 1, 1); 871 assert(s == "-4.000000|-0010|0x001| 0x1"); 872 873 i = -10; 874 s = format("%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 875 assert(s == "-10|-10|-10|-10|-10.0000"); 876 877 i = -5; 878 s = format("%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 879 assert(s == "-5| -5|-05|-5|-5.0000"); 880 881 i = 0; 882 s = format("%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 883 assert(s == "0| 0|000|0|0.0000"); 884 885 i = 5; 886 s = format("%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 887 assert(s == "5| 5|005|5|5.0000"); 888 889 i = 10; 890 s = format("%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); 891 assert(s == "10| 10|010|10|10.0000"); 892 893 s = format("%.0d", 0); 894 assert(s == "0"); 895 896 s = format("%.g", .34); 897 assert(s == "0.3"); 898 899 s = format("%.0g", .34); 900 assert(s == "0.3"); 901 902 s = format("%.2g", .34); 903 assert(s == "0.34"); 904 905 s = format("%0.0008f", 1e-08); 906 assert(s == "0.00000001"); 907 908 s = format("%0.0008f", 1e-05); 909 assert(s == "0.00001000"); 910 911 s = "helloworld"; 912 string r; 913 r = format("%.2s", s[0 .. 5]); 914 assert(r == "he"); 915 r = format("%.20s", s[0 .. 5]); 916 assert(r == "hello"); 917 r = format("%8s", s[0 .. 5]); 918 assert(r == " hello"); 919 920 byte[] arrbyte = new byte[4]; 921 arrbyte[0] = 100; 922 arrbyte[1] = -99; 923 arrbyte[3] = 0; 924 r = format("%s", arrbyte); 925 assert(r == "[100, -99, 0, 0]"); 926 927 ubyte[] arrubyte = new ubyte[4]; 928 arrubyte[0] = 100; 929 arrubyte[1] = 200; 930 arrubyte[3] = 0; 931 r = format("%s", arrubyte); 932 assert(r == "[100, 200, 0, 0]"); 933 934 short[] arrshort = new short[4]; 935 arrshort[0] = 100; 936 arrshort[1] = -999; 937 arrshort[3] = 0; 938 r = format("%s", arrshort); 939 assert(r == "[100, -999, 0, 0]"); 940 941 ushort[] arrushort = new ushort[4]; 942 arrushort[0] = 100; 943 arrushort[1] = 20_000; 944 arrushort[3] = 0; 945 r = format("%s", arrushort); 946 assert(r == "[100, 20000, 0, 0]"); 947 948 int[] arrint = new int[4]; 949 arrint[0] = 100; 950 arrint[1] = -999; 951 arrint[3] = 0; 952 r = format("%s", arrint); 953 assert(r == "[100, -999, 0, 0]"); 954 955 long[] arrlong = new long[4]; 956 arrlong[0] = 100; 957 arrlong[1] = -999; 958 arrlong[3] = 0; 959 r = format("%s", arrlong); 960 assert(r == "[100, -999, 0, 0]"); 961 962 ulong[] arrulong = new ulong[4]; 963 arrulong[0] = 100; 964 arrulong[1] = 999; 965 arrulong[3] = 0; 966 r = format("%s", arrulong); 967 assert(r == "[100, 999, 0, 0]"); 968 969 string[] arr2 = new string[4]; 970 arr2[0] = "hello"; 971 arr2[1] = "world"; 972 arr2[3] = "foo"; 973 r = format("%s", arr2); 974 assert(r == `["hello", "world", "", "foo"]`); 975 976 r = format("%.8d", 7); 977 assert(r == "00000007"); 978 r = format("%.8x", 10); 979 assert(r == "0000000a"); 980 981 r = format("%-3d", 7); 982 assert(r == "7 "); 983 984 r = format("%-1*d", 4, 3); 985 assert(r == "3 "); 986 987 r = format("%*d", -3, 7); 988 assert(r == "7 "); 989 990 r = format("%.*d", -3, 7); 991 assert(r == "7"); 992 993 r = format("%-1.*f", 2, 3.1415); 994 assert(r == "3.14"); 995 996 r = format("abc"c); 997 assert(r == "abc"); 998 999 //format() returns the same type as inputted. 1000 wstring wr; 1001 wr = format("def"w); 1002 assert(wr == "def"w); 1003 1004 dstring dr; 1005 dr = format("ghi"d); 1006 assert(dr == "ghi"d); 1007 1008 // Empty static character arrays work as well 1009 const char[0] cempty; 1010 assert(format("test%spath", cempty) == "testpath"); 1011 const wchar[0] wempty; 1012 assert(format("test%spath", wempty) == "testpath"); 1013 const dchar[0] dempty; 1014 assert(format("test%spath", dempty) == "testpath"); 1015 1016 void* p = () @trusted { return cast(void*) 0xDEADBEEF; } (); 1017 r = format("%s", p); 1018 assert(r == "DEADBEEF"); 1019 1020 r = format("%#x", 0xabcd); 1021 assert(r == "0xabcd"); 1022 r = format("%#X", 0xABCD); 1023 assert(r == "0XABCD"); 1024 1025 r = format("%#o", octal!12345); 1026 assert(r == "012345"); 1027 r = format("%o", 9); 1028 assert(r == "11"); 1029 r = format("%#o", 0); // https://issues.dlang.org/show_bug.cgi?id=15663 1030 assert(r == "0"); 1031 1032 r = format("%+d", 123); 1033 assert(r == "+123"); 1034 r = format("%+d", -123); 1035 assert(r == "-123"); 1036 r = format("% d", 123); 1037 assert(r == " 123"); 1038 r = format("% d", -123); 1039 assert(r == "-123"); 1040 1041 r = format("%%"); 1042 assert(r == "%"); 1043 1044 r = format("%d", true); 1045 assert(r == "1"); 1046 r = format("%d", false); 1047 assert(r == "0"); 1048 1049 r = format("%d", 'a'); 1050 assert(r == "97"); 1051 wchar wc = 'a'; 1052 r = format("%d", wc); 1053 assert(r == "97"); 1054 dchar dc = 'a'; 1055 r = format("%d", dc); 1056 assert(r == "97"); 1057 1058 byte b = byte.max; 1059 r = format("%x", b); 1060 assert(r == "7f"); 1061 r = format("%x", ++b); 1062 assert(r == "80"); 1063 r = format("%x", ++b); 1064 assert(r == "81"); 1065 1066 short sh = short.max; 1067 r = format("%x", sh); 1068 assert(r == "7fff"); 1069 r = format("%x", ++sh); 1070 assert(r == "8000"); 1071 r = format("%x", ++sh); 1072 assert(r == "8001"); 1073 1074 i = int.max; 1075 r = format("%x", i); 1076 assert(r == "7fffffff"); 1077 r = format("%x", ++i); 1078 assert(r == "80000000"); 1079 r = format("%x", ++i); 1080 assert(r == "80000001"); 1081 1082 r = format("%x", 10); 1083 assert(r == "a"); 1084 r = format("%X", 10); 1085 assert(r == "A"); 1086 r = format("%x", 15); 1087 assert(r == "f"); 1088 r = format("%X", 15); 1089 assert(r == "F"); 1090 1091 Object c = null; 1092 r = () @trusted { return format("%s", c); } (); 1093 assert(r == "null"); 1094 1095 enum TestEnum 1096 { 1097 Value1, Value2 1098 } 1099 r = format("%s", TestEnum.Value2); 1100 assert(r == "Value2"); 1101 1102 immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); 1103 r = () @trusted { return format("%s", aa.values); } (); 1104 assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`); 1105 r = format("%s", aa); 1106 assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`); 1107 1108 static const dchar[] ds = ['a','b']; 1109 for (int j = 0; j < ds.length; ++j) 1110 { 1111 r = format(" %d", ds[j]); 1112 if (j == 0) 1113 assert(r == " 97"); 1114 else 1115 assert(r == " 98"); 1116 } 1117 1118 r = format(">%14d<, %s", 15, [1,2,3]); 1119 assert(r == "> 15<, [1, 2, 3]"); 1120 1121 assert(format("%8s", "bar") == " bar"); 1122 assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4"); 1123 } 1124 1125 @safe unittest 1126 { 1127 import std.exception : assertCTFEable; 1128 1129 assertCTFEable!( 1130 { 1131 auto tmp = format("%,d", 1000); 1132 assert(tmp == "1,000", "'" ~ tmp ~ "'"); 1133 1134 tmp = format("%,?d", 'z', 1234567); 1135 assert(tmp == "1z234z567", "'" ~ tmp ~ "'"); 1136 1137 tmp = format("%10,?d", 'z', 1234567); 1138 assert(tmp == " 1z234z567", "'" ~ tmp ~ "'"); 1139 1140 tmp = format("%11,2?d", 'z', 1234567); 1141 assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'"); 1142 1143 tmp = format("%11,*?d", 2, 'z', 1234567); 1144 assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'"); 1145 1146 tmp = format("%11,*d", 2, 1234567); 1147 assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'"); 1148 1149 tmp = format("%11,2d", 1234567); 1150 assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'"); 1151 }); 1152 } 1153 1154 @safe unittest 1155 { 1156 auto tmp = format("%,f", 1000.0); 1157 assert(tmp == "1,000.000000", "'" ~ tmp ~ "'"); 1158 1159 tmp = format("%,f", 1234567.891011); 1160 assert(tmp == "1,234,567.891011", "'" ~ tmp ~ "'"); 1161 1162 tmp = format("%,f", -1234567.891011); 1163 assert(tmp == "-1,234,567.891011", "'" ~ tmp ~ "'"); 1164 1165 tmp = format("%,2f", 1234567.891011); 1166 assert(tmp == "1,23,45,67.891011", "'" ~ tmp ~ "'"); 1167 1168 tmp = format("%18,f", 1234567.891011); 1169 assert(tmp == " 1,234,567.891011", "'" ~ tmp ~ "'"); 1170 1171 tmp = format("%18,?f", '.', 1234567.891011); 1172 assert(tmp == " 1.234.567.891011", "'" ~ tmp ~ "'"); 1173 1174 tmp = format("%,?.3f", 'ä', 1234567.891011); 1175 assert(tmp == "1ä234ä567.891", "'" ~ tmp ~ "'"); 1176 1177 tmp = format("%,*?.3f", 1, 'ä', 1234567.891011); 1178 assert(tmp == "1ä2ä3ä4ä5ä6ä7.891", "'" ~ tmp ~ "'"); 1179 1180 tmp = format("%,4?.3f", '_', 1234567.891011); 1181 assert(tmp == "123_4567.891", "'" ~ tmp ~ "'"); 1182 1183 tmp = format("%12,3.3f", 1234.5678); 1184 assert(tmp == " 1,234.568", "'" ~ tmp ~ "'"); 1185 1186 tmp = format("%,e", 3.141592653589793238462); 1187 assert(tmp == "3.141593e+00", "'" ~ tmp ~ "'"); 1188 1189 tmp = format("%15,e", 3.141592653589793238462); 1190 assert(tmp == " 3.141593e+00", "'" ~ tmp ~ "'"); 1191 1192 tmp = format("%15,e", -3.141592653589793238462); 1193 assert(tmp == " -3.141593e+00", "'" ~ tmp ~ "'"); 1194 1195 tmp = format("%.4,*e", 2, 3.141592653589793238462); 1196 assert(tmp == "3.1416e+00", "'" ~ tmp ~ "'"); 1197 1198 tmp = format("%13.4,*e", 2, 3.141592653589793238462); 1199 assert(tmp == " 3.1416e+00", "'" ~ tmp ~ "'"); 1200 1201 tmp = format("%,.0f", 3.14); 1202 assert(tmp == "3", "'" ~ tmp ~ "'"); 1203 1204 tmp = format("%3,g", 1_000_000.123456); 1205 assert(tmp == "1e+06", "'" ~ tmp ~ "'"); 1206 1207 tmp = format("%19,?f", '.', -1234567.891011); 1208 assert(tmp == " -1.234.567.891011", "'" ~ tmp ~ "'"); 1209 } 1210 1211 // Test for multiple indexes 1212 @safe unittest 1213 { 1214 auto tmp = format("%2:5$s", 1, 2, 3, 4, 5); 1215 assert(tmp == "2345", tmp); 1216 } 1217 1218 // https://issues.dlang.org/show_bug.cgi?id=18047 1219 @safe unittest 1220 { 1221 auto cmp = " 123,456"; 1222 assert(cmp.length == 12, format("%d", cmp.length)); 1223 auto tmp = format("%12,d", 123456); 1224 assert(tmp.length == 12, format("%d", tmp.length)); 1225 1226 assert(tmp == cmp, "'" ~ tmp ~ "'"); 1227 } 1228 1229 // https://issues.dlang.org/show_bug.cgi?id=17459 1230 @safe unittest 1231 { 1232 auto cmp = "100"; 1233 auto tmp = format("%0d", 100); 1234 assert(tmp == cmp, tmp); 1235 1236 cmp = "0100"; 1237 tmp = format("%04d", 100); 1238 assert(tmp == cmp, tmp); 1239 1240 cmp = "0,000,000,100"; 1241 tmp = format("%012,3d", 100); 1242 assert(tmp == cmp, tmp); 1243 1244 cmp = "0,000,001,000"; 1245 tmp = format("%012,3d", 1_000); 1246 assert(tmp == cmp, tmp); 1247 1248 cmp = "0,000,100,000"; 1249 tmp = format("%012,3d", 100_000); 1250 assert(tmp == cmp, tmp); 1251 1252 cmp = "0,001,000,000"; 1253 tmp = format("%012,3d", 1_000_000); 1254 assert(tmp == cmp, tmp); 1255 1256 cmp = "0,100,000,000"; 1257 tmp = format("%012,3d", 100_000_000); 1258 assert(tmp == cmp, tmp); 1259 } 1260 1261 // https://issues.dlang.org/show_bug.cgi?id=17459 1262 @safe unittest 1263 { 1264 auto cmp = "100,000"; 1265 auto tmp = format("%06,d", 100_000); 1266 assert(tmp == cmp, tmp); 1267 1268 cmp = "100,000"; 1269 tmp = format("%07,d", 100_000); 1270 assert(tmp == cmp, tmp); 1271 1272 cmp = "0,100,000"; 1273 tmp = format("%08,d", 100_000); 1274 assert(tmp == cmp, tmp); 1275 } 1276 1277 // https://issues.dlang.org/show_bug.cgi?id=20288 1278 @safe unittest 1279 { 1280 string s = format("%,.2f", double.nan); 1281 assert(s == "nan", s); 1282 1283 s = format("%,.2F", double.nan); 1284 assert(s == "NAN", s); 1285 1286 s = format("%,.2f", -double.nan); 1287 assert(s == "-nan", s); 1288 1289 s = format("%,.2F", -double.nan); 1290 assert(s == "-NAN", s); 1291 1292 string g = format("^%13s$", "nan"); 1293 string h = "^ nan$"; 1294 assert(g == h, "\ngot:" ~ g ~ "\nexp:" ~ h); 1295 string a = format("^%13,3.2f$", double.nan); 1296 string b = format("^%13,3.2F$", double.nan); 1297 string c = format("^%13,3.2f$", -double.nan); 1298 string d = format("^%13,3.2F$", -double.nan); 1299 assert(a == "^ nan$", "\ngot:'"~ a ~ "'\nexp:'^ nan$'"); 1300 assert(b == "^ NAN$", "\ngot:'"~ b ~ "'\nexp:'^ NAN$'"); 1301 assert(c == "^ -nan$", "\ngot:'"~ c ~ "'\nexp:'^ -nan$'"); 1302 assert(d == "^ -NAN$", "\ngot:'"~ d ~ "'\nexp:'^ -NAN$'"); 1303 1304 a = format("^%-13,3.2f$", double.nan); 1305 b = format("^%-13,3.2F$", double.nan); 1306 c = format("^%-13,3.2f$", -double.nan); 1307 d = format("^%-13,3.2F$", -double.nan); 1308 assert(a == "^nan $", "\ngot:'"~ a ~ "'\nexp:'^nan $'"); 1309 assert(b == "^NAN $", "\ngot:'"~ b ~ "'\nexp:'^NAN $'"); 1310 assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); 1311 assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); 1312 1313 a = format("^%+13,3.2f$", double.nan); 1314 b = format("^%+13,3.2F$", double.nan); 1315 c = format("^%+13,3.2f$", -double.nan); 1316 d = format("^%+13,3.2F$", -double.nan); 1317 assert(a == "^ +nan$", "\ngot:'"~ a ~ "'\nexp:'^ +nan$'"); 1318 assert(b == "^ +NAN$", "\ngot:'"~ b ~ "'\nexp:'^ +NAN$'"); 1319 assert(c == "^ -nan$", "\ngot:'"~ c ~ "'\nexp:'^ -nan$'"); 1320 assert(d == "^ -NAN$", "\ngot:'"~ d ~ "'\nexp:'^ -NAN$'"); 1321 1322 a = format("^%-+13,3.2f$", double.nan); 1323 b = format("^%-+13,3.2F$", double.nan); 1324 c = format("^%-+13,3.2f$", -double.nan); 1325 d = format("^%-+13,3.2F$", -double.nan); 1326 assert(a == "^+nan $", "\ngot:'"~ a ~ "'\nexp:'^+nan $'"); 1327 assert(b == "^+NAN $", "\ngot:'"~ b ~ "'\nexp:'^+NAN $'"); 1328 assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); 1329 assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); 1330 1331 a = format("^%- 13,3.2f$", double.nan); 1332 b = format("^%- 13,3.2F$", double.nan); 1333 c = format("^%- 13,3.2f$", -double.nan); 1334 d = format("^%- 13,3.2F$", -double.nan); 1335 assert(a == "^ nan $", "\ngot:'"~ a ~ "'\nexp:'^ nan $'"); 1336 assert(b == "^ NAN $", "\ngot:'"~ b ~ "'\nexp:'^ NAN $'"); 1337 assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); 1338 assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); 1339 } 1340 1341 @safe unittest 1342 { 1343 struct S 1344 { 1345 int a; 1346 1347 void toString(void delegate(const(char)[]) sink, string fmt) 1348 { 1349 auto spec = singleSpec(fmt); 1350 sink.formatValue(a, spec); 1351 } 1352 } 1353 1354 S s = S(1); 1355 auto result = () @trusted { return format!"%5,3d"(s); } (); 1356 assert(result == " 1"); 1357 } 1358 1359 // https://issues.dlang.org/show_bug.cgi?id=23245 1360 @safe unittest 1361 { 1362 static struct S 1363 { 1364 string toString() { return "S"; } 1365 } 1366 1367 S[1] s; 1368 assert(format("%s", s) == "[S]"); 1369 } 1370 1371 // https://issues.dlang.org/show_bug.cgi?id=23246 1372 @safe unittest 1373 { 1374 static struct S 1375 { 1376 string toString() { return "S"; } 1377 } 1378 1379 S[int] s = [0 : S()]; 1380 assert(format("%s", s) == "[0:S]"); 1381 } 1382 1383 /// ditto 1384 typeof(fmt) format(alias fmt, Args...)(Args args) 1385 if (isSomeString!(typeof(fmt))) 1386 { 1387 import std.array : appender; 1388 import std.range.primitives : ElementEncodingType; 1389 import std.traits : Unqual; 1390 1391 alias e = checkFormatException!(fmt, Args); 1392 alias Char = Unqual!(ElementEncodingType!(typeof(fmt))); 1393 1394 static assert(!e, e); 1395 auto w = appender!(immutable(Char)[]); 1396 1397 // no need to traverse the string twice during compile time 1398 if (!__ctfe) 1399 { 1400 enum len = guessLength!Char(fmt); 1401 w.reserve(len); 1402 } 1403 else 1404 { 1405 w.reserve(fmt.length); 1406 } 1407 1408 formattedWrite(w, fmt, args); 1409 return w.data; 1410 } 1411 1412 /// The format string can be checked at compile-time: 1413 @safe pure unittest 1414 { 1415 auto s = format!"%s is %s"("Pi", 3.14); 1416 assert(s == "Pi is 3.14"); 1417 1418 // This line doesn't compile, because 3.14 cannot be formatted with %d: 1419 // s = format!"%s is %d"("Pi", 3.14); 1420 } 1421 1422 @safe pure unittest 1423 { 1424 string s; 1425 static assert(!__traits(compiles, {s = format!"%l"();})); // missing arg 1426 static assert(!__traits(compiles, {s = format!""(404);})); // surplus arg 1427 static assert(!__traits(compiles, {s = format!"%d"(4.03);})); // incompatible arg 1428 } 1429 1430 // https://issues.dlang.org/show_bug.cgi?id=17381 1431 @safe pure unittest 1432 { 1433 static assert(!__traits(compiles, format!"%s"(1.5, 2))); 1434 static assert(!__traits(compiles, format!"%f"(1.5, 2))); 1435 static assert(!__traits(compiles, format!"%s"(1.5L, 2))); 1436 static assert(!__traits(compiles, format!"%f"(1.5L, 2))); 1437 } 1438 1439 // called during compilation to guess the length of the 1440 // result of format 1441 private size_t guessLength(Char, S)(S fmtString) 1442 { 1443 import std.array : appender; 1444 1445 size_t len; 1446 auto output = appender!(immutable(Char)[])(); 1447 auto spec = FormatSpec!Char(fmtString); 1448 while (spec.writeUpToNextSpec(output)) 1449 { 1450 // take a guess 1451 if (spec.width == 0 && (spec.precision == spec.UNSPECIFIED || spec.precision == spec.DYNAMIC)) 1452 { 1453 switch (spec.spec) 1454 { 1455 case 'c': 1456 ++len; 1457 break; 1458 case 'd': 1459 case 'x': 1460 case 'X': 1461 len += 3; 1462 break; 1463 case 'b': 1464 len += 8; 1465 break; 1466 case 'f': 1467 case 'F': 1468 len += 10; 1469 break; 1470 case 's': 1471 case 'e': 1472 case 'E': 1473 case 'g': 1474 case 'G': 1475 len += 12; 1476 break; 1477 default: break; 1478 } 1479 1480 continue; 1481 } 1482 1483 if ((spec.spec == 'e' || spec.spec == 'E' || spec.spec == 'g' || 1484 spec.spec == 'G' || spec.spec == 'f' || spec.spec == 'F') && 1485 spec.precision != spec.UNSPECIFIED && spec.precision != spec.DYNAMIC && 1486 spec.width == 0 1487 ) 1488 { 1489 len += spec.precision + 5; 1490 continue; 1491 } 1492 1493 if (spec.width == spec.precision) 1494 len += spec.width; 1495 else if (spec.width > 0 && spec.width != spec.DYNAMIC && 1496 (spec.precision == spec.UNSPECIFIED || spec.width > spec.precision)) 1497 { 1498 len += spec.width; 1499 } 1500 else if (spec.precision != spec.UNSPECIFIED && spec.precision > spec.width) 1501 len += spec.precision; 1502 } 1503 len += output.data.length; 1504 return len; 1505 } 1506 1507 @safe pure 1508 unittest 1509 { 1510 assert(guessLength!char("%c") == 1); 1511 assert(guessLength!char("%d") == 3); 1512 assert(guessLength!char("%x") == 3); 1513 assert(guessLength!char("%b") == 8); 1514 assert(guessLength!char("%f") == 10); 1515 assert(guessLength!char("%s") == 12); 1516 assert(guessLength!char("%02d") == 2); 1517 assert(guessLength!char("%02d") == 2); 1518 assert(guessLength!char("%4.4d") == 4); 1519 assert(guessLength!char("%2.4f") == 4); 1520 assert(guessLength!char("%02d:%02d:%02d") == 8); 1521 assert(guessLength!char("%0.2f") == 7); 1522 assert(guessLength!char("%0*d") == 0); 1523 } 1524 1525 /** 1526 Converts its arguments according to a format string into a buffer. 1527 The buffer has to be large enough to hold the formatted string. 1528 1529 The second version of `sformat` takes the format string as a template 1530 argument. In this case, it is checked for consistency at 1531 compile-time. 1532 1533 Params: 1534 buf = the buffer where the formatted string should go 1535 fmt = a $(MREF_ALTTEXT format string, std,format) 1536 args = a variadic list of arguments to be formatted 1537 Char = character type of `fmt` 1538 Args = a variadic list of types of the arguments 1539 1540 Returns: 1541 A slice of `buf` containing the formatted string. 1542 1543 Throws: 1544 A $(REF_ALTTEXT RangeError, RangeError, core, exception) if `buf` 1545 isn't large enough to hold the formatted string 1546 and a $(LREF FormatException) if formatting did not succeed. 1547 1548 Note: 1549 In theory this function should be `@nogc`. But with the current 1550 implementation there are some cases where allocations occur: 1551 1552 $(UL 1553 $(LI An exception is thrown.) 1554 $(LI A custom `toString` function of a compound type allocates.)) 1555 */ 1556 char[] sformat(Char, Args...)(return scope char[] buf, scope const(Char)[] fmt, Args args) 1557 { 1558 import core.exception : RangeError; 1559 import std.range.primitives; 1560 import std.utf : encode; 1561 1562 static struct Sink 1563 { 1564 char[] buf; 1565 size_t i; 1566 void put(char c) 1567 { 1568 if (buf.length <= i) 1569 throw new RangeError(__FILE__, __LINE__); 1570 1571 buf[i] = c; 1572 i += 1; 1573 } 1574 void put(dchar c) 1575 { 1576 char[4] enc; 1577 auto n = encode(enc, c); 1578 1579 if (buf.length < i + n) 1580 throw new RangeError(__FILE__, __LINE__); 1581 1582 buf[i .. i + n] = enc[0 .. n]; 1583 i += n; 1584 } 1585 void put(scope const(char)[] s) 1586 { 1587 if (buf.length < i + s.length) 1588 throw new RangeError(__FILE__, __LINE__); 1589 1590 buf[i .. i + s.length] = s[]; 1591 i += s.length; 1592 } 1593 void put(scope const(wchar)[] s) 1594 { 1595 for (; !s.empty; s.popFront()) 1596 put(s.front); 1597 } 1598 void put(scope const(dchar)[] s) 1599 { 1600 for (; !s.empty; s.popFront()) 1601 put(s.front); 1602 } 1603 } 1604 auto sink = Sink(buf); 1605 auto n = formattedWrite(sink, fmt, args); 1606 version (all) 1607 { 1608 // In the future, this check will be removed to increase consistency 1609 // with formattedWrite 1610 import std.conv : text; 1611 enforceFmt( 1612 n == args.length, 1613 text("Orphan format arguments: args[", n, " .. ", args.length, "]") 1614 ); 1615 } 1616 return buf[0 .. sink.i]; 1617 } 1618 1619 /// ditto 1620 char[] sformat(alias fmt, Args...)(char[] buf, Args args) 1621 if (isSomeString!(typeof(fmt))) 1622 { 1623 alias e = checkFormatException!(fmt, Args); 1624 static assert(!e, e); 1625 return .sformat(buf, fmt, args); 1626 } 1627 1628 /// 1629 @safe pure unittest 1630 { 1631 char[20] buf; 1632 assert(sformat(buf[], "Here are %d %s.", 3, "apples") == "Here are 3 apples."); 1633 1634 assert(buf[].sformat("Increase: %7.2f %%", 17.4285) == "Increase: 17.43 %"); 1635 } 1636 1637 /// The format string can be checked at compile-time: 1638 @safe pure unittest 1639 { 1640 char[20] buf; 1641 1642 assert(sformat!"Here are %d %s."(buf[], 3, "apples") == "Here are 3 apples."); 1643 1644 // This line doesn't compile, because 3.14 cannot be formatted with %d: 1645 // writeln(sformat!"Here are %d %s."(buf[], 3.14, "apples")); 1646 } 1647 1648 // checking, what is implicitly and explicitly stated in the public unittest 1649 @safe unittest 1650 { 1651 import std.exception : assertThrown; 1652 1653 char[20] buf; 1654 assertThrown!FormatException(sformat(buf[], "Here are %d %s.", 3.14, "apples")); 1655 assert(!__traits(compiles, sformat!"Here are %d %s."(buf[], 3.14, "apples"))); 1656 } 1657 1658 @safe unittest 1659 { 1660 import core.exception : RangeError; 1661 import std.exception : assertCTFEable, assertThrown; 1662 1663 assertCTFEable!( 1664 { 1665 char[10] buf; 1666 1667 assert(sformat(buf[], "foo") == "foo"); 1668 assert(sformat(buf[], "foo%%") == "foo%"); 1669 assert(sformat(buf[], "foo%s", 'C') == "fooC"); 1670 assert(sformat(buf[], "%s foo", "bar") == "bar foo"); 1671 () @trusted { 1672 assertThrown!RangeError(sformat(buf[], "%s foo %s", "bar", "abc")); 1673 } (); 1674 assert(sformat(buf[], "foo %d", -123) == "foo -123"); 1675 assert(sformat(buf[], "foo %d", 123) == "foo 123"); 1676 1677 assertThrown!FormatException(sformat(buf[], "foo %s")); 1678 assertThrown!FormatException(sformat(buf[], "foo %s", 123, 456)); 1679 1680 assert(sformat(buf[], "%s %s %s", "c"c, "w"w, "d"d) == "c w d"); 1681 }); 1682 } 1683 1684 @safe unittest // ensure that sformat avoids the GC 1685 { 1686 import core.memory : GC; 1687 1688 const a = ["foo", "bar"]; 1689 const u = () @trusted { return GC.stats().usedSize; } (); 1690 char[20] buf; 1691 sformat(buf, "%d", 123); 1692 sformat(buf, "%s", a); 1693 sformat(buf, "%s", 'c'); 1694 const v = () @trusted { return GC.stats().usedSize; } (); 1695 assert(u == v); 1696 } 1697 1698 @safe unittest // https://issues.dlang.org/show_bug.cgi?id=23488 1699 { 1700 static struct R 1701 { 1702 string s = "Ü"; 1703 bool empty() { return s.length == 0; } 1704 char front() { return s[0]; } 1705 void popFront() { s = s[1 .. $]; } 1706 } 1707 char[2] buf; 1708 assert(sformat(buf, "%s", R()) == "Ü"); 1709 } 1710 1711 version (StdUnittest) 1712 private void formatReflectTest(T)(ref T val, string fmt, string formatted, string fn = __FILE__, size_t ln = __LINE__) 1713 { 1714 formatReflectTest(val, fmt, [formatted], fn, ln); 1715 } 1716 1717 version (StdUnittest) 1718 private void formatReflectTest(T)(ref T val, string fmt, string[] formatted, string fn = __FILE__, size_t ln = __LINE__) 1719 { 1720 import core.exception : AssertError; 1721 import std.algorithm.searching : canFind; 1722 import std.array : appender; 1723 import std.math.operations : isClose; 1724 import std.traits : FloatingPointTypeOf; 1725 1726 auto w = appender!string(); 1727 formattedWrite(w, fmt, val); 1728 1729 auto input = w.data; 1730 enforce!AssertError(formatted.canFind(input), input, fn, ln); 1731 1732 T val2; 1733 formattedRead(input, fmt, val2); 1734 1735 static if (is(FloatingPointTypeOf!T)) 1736 enforce!AssertError(isClose(val, val2), input, fn, ln); 1737 else 1738 enforce!AssertError(val == val2, input, fn, ln); 1739 } 1740 1741 @safe unittest 1742 { 1743 void booleanTest() 1744 { 1745 auto b = true; 1746 formatReflectTest(b, "%s", `true`); 1747 formatReflectTest(b, "%b", `1`); 1748 formatReflectTest(b, "%o", `1`); 1749 formatReflectTest(b, "%d", `1`); 1750 formatReflectTest(b, "%u", `1`); 1751 formatReflectTest(b, "%x", `1`); 1752 } 1753 1754 void integerTest() 1755 { 1756 auto n = 127; 1757 formatReflectTest(n, "%s", `127`); 1758 formatReflectTest(n, "%b", `1111111`); 1759 formatReflectTest(n, "%o", `177`); 1760 formatReflectTest(n, "%d", `127`); 1761 formatReflectTest(n, "%u", `127`); 1762 formatReflectTest(n, "%x", `7f`); 1763 } 1764 1765 void floatingTest() 1766 { 1767 auto f = 3.14; 1768 formatReflectTest(f, "%s", `3.14`); 1769 formatReflectTest(f, "%e", `3.140000e+00`); 1770 formatReflectTest(f, "%f", `3.140000`); 1771 formatReflectTest(f, "%g", `3.14`); 1772 } 1773 1774 void charTest() 1775 { 1776 auto c = 'a'; 1777 formatReflectTest(c, "%s", `a`); 1778 formatReflectTest(c, "%c", `a`); 1779 formatReflectTest(c, "%b", `1100001`); 1780 formatReflectTest(c, "%o", `141`); 1781 formatReflectTest(c, "%d", `97`); 1782 formatReflectTest(c, "%u", `97`); 1783 formatReflectTest(c, "%x", `61`); 1784 } 1785 1786 void strTest() 1787 { 1788 auto s = "hello"; 1789 formatReflectTest(s, "%s", `hello`); 1790 formatReflectTest(s, "%(%c,%)", `h,e,l,l,o`); 1791 formatReflectTest(s, "%(%s,%)", `'h','e','l','l','o'`); 1792 formatReflectTest(s, "[%(<%c>%| $ %)]", `[<h> $ <e> $ <l> $ <l> $ <o>]`); 1793 } 1794 1795 void daTest() 1796 { 1797 auto a = [1,2,3,4]; 1798 formatReflectTest(a, "%s", `[1, 2, 3, 4]`); 1799 formatReflectTest(a, "[%(%s; %)]", `[1; 2; 3; 4]`); 1800 formatReflectTest(a, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`); 1801 } 1802 1803 void saTest() 1804 { 1805 int[4] sa = [1,2,3,4]; 1806 formatReflectTest(sa, "%s", `[1, 2, 3, 4]`); 1807 formatReflectTest(sa, "[%(%s; %)]", `[1; 2; 3; 4]`); 1808 formatReflectTest(sa, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`); 1809 } 1810 1811 void aaTest() 1812 { 1813 auto aa = [1:"hello", 2:"world"]; 1814 formatReflectTest(aa, "%s", [`[1:"hello", 2:"world"]`, `[2:"world", 1:"hello"]`]); 1815 formatReflectTest(aa, "[%(%s->%s, %)]", [`[1->"hello", 2->"world"]`, `[2->"world", 1->"hello"]`]); 1816 formatReflectTest(aa, "{%([%s=%(%c%)]%|; %)}", [`{[1=hello]; [2=world]}`, `{[2=world]; [1=hello]}`]); 1817 } 1818 1819 import std.exception : assertCTFEable; 1820 1821 assertCTFEable!( 1822 { 1823 booleanTest(); 1824 integerTest(); 1825 floatingTest(); 1826 charTest(); 1827 strTest(); 1828 daTest(); 1829 saTest(); 1830 aaTest(); 1831 }); 1832 }