| // Written in the D programming language. |
| |
| /** |
| This package provides string formatting functionality using |
| `printf` style format strings. |
| |
| $(BOOKTABLE , |
| $(TR $(TH Submodule) $(TH Function Name) $(TH Description)) |
| $(TR |
| $(TD $(I package)) |
| $(TD $(LREF format)) |
| $(TD Converts its arguments according to a format string into a string.) |
| ) |
| $(TR |
| $(TD $(I package)) |
| $(TD $(LREF sformat)) |
| $(TD Converts its arguments according to a format string into a buffer.) |
| ) |
| $(TR |
| $(TD $(I package)) |
| $(TD $(LREF FormatException)) |
| $(TD Signals a problem while formatting.) |
| ) |
| $(TR |
| $(TD $(MREF_ALTTEXT $(D write), std, format, write)) |
| $(TD $(REF_ALTTEXT $(D formattedWrite), formattedWrite, std, format, write)) |
| $(TD Converts its arguments according to a format string and writes |
| the result to an output range.) |
| ) |
| $(TR |
| $(TD $(MREF_ALTTEXT $(D write), std, format, write)) |
| $(TD $(REF_ALTTEXT $(D formatValue), formatValue, std, format, write)) |
| $(TD Formats a value of any type according to a format specifier and |
| writes the result to an output range.) |
| ) |
| $(TR |
| $(TD $(MREF_ALTTEXT $(D read), std, format, read)) |
| $(TD $(REF_ALTTEXT $(D formattedRead), formattedRead, std, format, read)) |
| $(TD Reads an input range according to a format string and stores the read |
| values into its arguments.) |
| ) |
| $(TR |
| $(TD $(MREF_ALTTEXT $(D read), std, format, read)) |
| $(TD $(REF_ALTTEXT $(D unformatValue), unformatValue, std, format, read)) |
| $(TD Reads a value from the given input range and converts it according to |
| a format specifier.) |
| ) |
| $(TR |
| $(TD $(MREF_ALTTEXT $(D spec), std, format, spec)) |
| $(TD $(REF_ALTTEXT $(D FormatSpec), FormatSpec, std, format, spec)) |
| $(TD A general handler for format strings.) |
| ) |
| $(TR |
| $(TD $(MREF_ALTTEXT $(D spec), std, format, spec)) |
| $(TD $(REF_ALTTEXT $(D singleSpec), singleSpec, std, format, spec)) |
| $(TD Helper function that returns a `FormatSpec` for a single format specifier.) |
| )) |
| |
| Limitation: This package does not support localization, but |
| adheres to the rounding mode of the floating point unit, if |
| available. |
| |
| $(SECTION3 Format Strings) |
| |
| The functions contained in this package use $(I format strings). A |
| format string describes the layout of another string for reading or |
| writing purposes. A format string is composed of normal text |
| interspersed with $(I format specifiers). A format specifier starts |
| with a percentage sign $(B '%'), optionally followed by one or more |
| $(I parameters) and ends with a $(I format indicator). A format |
| indicator may be a simple $(I format character) or a $(I compound |
| indicator). |
| |
| $(I Format strings) are composed according to the following grammar: |
| |
| $(PRE |
| $(I FormatString): |
| $(I FormatStringItem) $(I FormatString) |
| $(I FormatStringItem): |
| $(I Character) |
| $(I FormatSpecifier) |
| $(I FormatSpecifier): |
| $(B '%') $(I Parameters) $(I FormatIndicator) |
| |
| $(I FormatIndicator): |
| $(I FormatCharacter) |
| $(I CompoundIndicator) |
| $(I FormatCharacter): |
| $(I see remark below) |
| $(I CompoundIndicator): |
| $(B '$(LPAREN)') $(I FormatString) $(B '%$(RPAREN)') |
| $(B '$(LPAREN)') $(I FormatString) $(B '%|') $(I Delimiter) $(B '%$(RPAREN)') |
| $(I Delimiter) |
| $(I empty) |
| $(I Character) $(I Delimiter) |
| |
| $(I Parameters): |
| $(I Position) $(I Flags) $(I Width) $(I Precision) $(I Separator) |
| $(I Position): |
| $(I empty) |
| $(I Integer) $(B '$') |
| $(I Integer) $(B ':') $(I Integer) $(B '$') |
| $(I Integer) $(B ':') $(B '$') |
| $(I Flags): |
| $(I empty) |
| $(I Flag) $(I Flags) |
| $(I Flag): |
| $(B '-')|$(B '+')|$(B ' ')|$(B '0')|$(B '#')|$(B '=') |
| $(I Width): |
| $(I OptionalPositionalInteger) |
| $(I Precision): |
| $(I empty) |
| $(B '.') $(I OptionalPositionalInteger) |
| $(I Separator): |
| $(I empty) |
| $(B ',') $(I OptionalInteger) |
| $(B ',') $(I OptionalInteger) $(B '?') |
| $(I OptionalInteger): |
| $(I empty) |
| $(I Integer) |
| $(B '*') |
| $(I OptionalPositionalInteger): |
| $(I OptionalInteger) |
| $(B '*') $(I Integer) $(B '$') |
| |
| $(I Character) |
| $(B '%%') |
| $(I AnyCharacterExceptPercent) |
| $(I Integer): |
| $(I NonZeroDigit) $(I Digits) |
| $(I Digits): |
| $(I empty) |
| $(I Digit) $(I Digits) |
| $(I NonZeroDigit): |
| $(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9') |
| $(I Digit): |
| $(B '0')|$(B '1')|$(B '2')|$(B '3')|$(B '4')|$(B '5')|$(B '6')|$(B '7')|$(B '8')|$(B '9') |
| ) |
| |
| Note: $(I FormatCharacter) is unspecified. It can be any character |
| that has no other purpose in this grammar, but it is |
| recommended to assign (lower- and uppercase) letters. |
| |
| Note: The $(I Parameters) of a $(I CompoundIndicator) are currently |
| limited to a $(B '-') flag. |
| |
| $(SECTION4 Format Indicator) |
| |
| The $(I format indicator) can either be a single character or an |
| expression surrounded by $(B %\() and $(B %\)). It specifies the |
| basic manner in which a value will be formatted and is the minimum |
| requirement to format a value. |
| |
| The following characters can be used as $(I format characters): |
| |
| $(BOOKTABLE , |
| $(TR $(TH FormatCharacter) $(TH Semantics)) |
| $(TR $(TD $(B 's')) |
| $(TD To be formatted in a human readable format. |
| Can be used with all types.)) |
| $(TR $(TD $(B 'c')) |
| $(TD To be formatted as a character.)) |
| $(TR $(TD $(B 'd')) |
| $(TD To be formatted as a signed decimal integer.)) |
| $(TR $(TD $(B 'u')) |
| $(TD To be formatted as a decimal image of the underlying bit representation.)) |
| $(TR $(TD $(B 'b')) |
| $(TD To be formatted as a binary image of the underlying bit representation.)) |
| $(TR $(TD $(B 'o')) |
| $(TD To be formatted as an octal image of the underlying bit representation.)) |
| $(TR $(TD $(B 'x') / $(B 'X')) |
| $(TD To be formatted as a hexadecimal image of the underlying bit representation.)) |
| $(TR $(TD $(B 'e') / $(B 'E')) |
| $(TD To be formatted as a real number in decimal scientific notation.)) |
| $(TR $(TD $(B 'f') / $(B 'F')) |
| $(TD To be formatted as a real number in decimal natural notation.)) |
| $(TR $(TD $(B 'g') / $(B 'G')) |
| $(TD To be formatted as a real number in decimal short notation. |
| Depending on the number, a scientific notation or |
| a natural notation is used.)) |
| $(TR $(TD $(B 'a') / $(B 'A')) |
| $(TD To be formatted as a real number in hexadecimal scientific notation.)) |
| $(TR $(TD $(B 'r')) |
| $(TD To be formatted as raw bytes. |
| The output may not be printable and depends on endianness.)) |
| ) |
| |
| The $(I compound indicator) can be used to describe compound types |
| like arrays or structs in more detail. A compound type is enclosed |
| within $(B '%\(') and $(B '%\)'). The enclosed sub-format string is |
| applied to individual elements. The trailing portion of the |
| sub-format string following the specifier for the element is |
| interpreted as the delimiter, and is therefore omitted following the |
| last element. The $(B '%|') specifier may be used to explicitly |
| indicate the start of the delimiter, so that the preceding portion of |
| the string will be included following the last element. |
| |
| The $(I format string) inside of the $(I compound indicator) should |
| contain exactly one $(I format specifier) (two in case of associative |
| arrays), which specifies the formatting mode of the elements of the |
| compound type. This $(I format specifier) can be a $(I compound |
| indicator) itself. |
| |
| Note: Inside a $(I compound indicator), strings and characters are |
| escaped automatically. To avoid this behavior, use `"%-$(LPAREN)"` |
| instead of `"%$(LPAREN)"`. |
| |
| $(SECTION4 Flags) |
| |
| There are several flags that affect the outcome of the formatting. |
| |
| $(BOOKTABLE , |
| $(TR $(TH Flag) $(TH Semantics)) |
| $(TR $(TD $(B '-')) |
| $(TD When the formatted result is shorter then the value |
| given by the width parameter, the output is right |
| justified. With the $(B '-') flag this is changed |
| to left justification. |
| |
| There are two exceptions where the $(B '-') flag has a |
| different meaning: (1) with $(B 'r') it denotes to use little |
| endian and (2) in case of a compound indicator it means that |
| no special handling of the members is applied.)) |
| $(TR $(TD $(B '=')) |
| $(TD When the formatted result is shorter then the value |
| given by the width parameter, the output is centered. |
| If the central position is not possible it is moved slightly |
| to the right. In this case, if $(B '-') flag is present in |
| addition to the $(B '=') flag, it is moved slightly to the left.)) |
| $(TR $(TD $(B '+') / $(B ' ')) |
| $(TD Applies to numerical values. By default, positive numbers are not |
| formatted to include the `+` sign. With one of these two flags present, |
| positive numbers are preceded by a plus sign or a space. |
| When both flags are present, a plus sign is used. |
| |
| In case of $(B 'r'), a big endian format is used.)) |
| $(TR $(TD $(B '0')) |
| $(TD Is applied to numerical values that are printed right justified. |
| If the zero flag is present, the space left to the number is |
| filled with zeros instead of spaces.)) |
| $(TR $(TD $(B '#')) |
| $(TD Denotes that an alternative output must be used. This depends on the type |
| to be formatted and the $(I format character) used. See the |
| sections below for more information.)) |
| ) |
| |
| $(SECTION4 Width$(COMMA) Precision and Separator) |
| |
| The $(I width) parameter specifies the minimum width of the result. |
| |
| The meaning of $(I precision) depends on the format indicator. For |
| integers it denotes the minimum number of digits printed, for |
| real numbers it denotes the number of fractional digits and for |
| strings and compound types it denotes the maximum number of elements |
| that are included in the output. |
| |
| A $(I separator) is used for formatting numbers. If it is specified, |
| the output is divided into chunks of three digits, separated by a $(B |
| ','). The number of digits in a chunk can be given explicitly by |
| providing a number or a $(B '*') after the $(B ','). |
| |
| In all three cases the number of digits can be replaced by a $(B |
| '*'). In this scenario, the next argument is used as the number of |
| digits. If the argument is a negative number, the $(I precision) and |
| $(I separator) parameters are considered unspecified. For $(I width), |
| the absolute value is used and the $(B '-') flag is set. |
| |
| The $(I separator) can also be followed by a $(B '?'). In that case, |
| an additional argument is used to specify the symbol that should be |
| used to separate the chunks. |
| |
| $(SECTION4 Position) |
| |
| By default, the arguments are processed in the provided order. With |
| the $(I position) parameter it is possible to address arguments |
| directly. It is also possible to denote a series of arguments with |
| two numbers separated by $(B ':'), that are all processed in the same |
| way. The second number can be omitted. In that case the series ends |
| with the last argument. |
| |
| It's also possible to use positional arguments for $(I width), $(I |
| precision) and $(I separator) by adding a number and a $(B |
| '$(DOLLAR)') after the $(B '*'). |
| |
| $(SECTION4 Types) |
| |
| This section describes the result of combining types with format |
| characters. It is organized in 2 subsections: a list of general |
| information regarding the formatting of types in the presence of |
| format characters and a table that contains details for every |
| available combination of type and format character. |
| |
| When formatting types, the following rules apply: |
| |
| $(UL |
| $(LI If the format character is upper case, the resulting string will |
| be formatted using upper case letters.) |
| $(LI The default precision for floating point numbers is 6 digits.) |
| $(LI Rounding of floating point numbers adheres to the rounding mode |
| of the floating point unit, if available.) |
| $(LI The floating point values `NaN` and `Infinity` are formatted as |
| `nan` and `inf`, possibly preceded by $(B '+') or $(B '-') sign.) |
| $(LI Formatting reals is only supported for 64 bit reals and 80 bit reals. |
| All other reals are cast to double before they are formatted. This will |
| cause the result to be `inf` for very large numbers.) |
| $(LI Characters and strings formatted with the $(B 's') format character |
| inside of compound types are surrounded by single and double quotes |
| and unprintable characters are escaped. To avoid this, a $(B '-') |
| flag can be specified for the compound specifier |
| $(LPAREN)e.g. `"%-$(LPAREN)%s%$(RPAREN)"` instead of `"%$(LPAREN)%s%$(RPAREN)"` $(RPAREN).) |
| $(LI Structs, unions, classes and interfaces are formatted by calling a |
| `toString` method if available. |
| See $(MREF_ALTTEXT $(D module std.format.write), std, format, write) for more |
| details.) |
| $(LI Only part of these combinations can be used for reading. See |
| $(MREF_ALTTEXT $(D module std.format.read), std, format, read) for more |
| detailed information.) |
| ) |
| |
| This table contains descriptions for every possible combination of |
| type and format character: |
| |
| $(BOOKTABLE , |
| $(TR $(THMINWIDTH Type) $(THMINWIDTH Format Character) $(TH Formatted as...)) |
| $(TR $(MULTIROW_CELL 1, `null`) |
| $(TD $(B 's')) |
| $(TD `null`) |
| ) |
| $(TR $(MULTIROW_CELL 3, `bool`) |
| $(TD $(B 's')) |
| $(TD `false` or `true`) |
| ) |
| $(TR $(TD $(B 'b'), $(B 'd'), $(B 'o'), $(B 'u'), $(B 'x'), $(B 'X')) |
| $(TD As the integrals 0 or 1 with the same format character. |
| |
| $(I Please note, that $(B 'o') and $(B 'x') with $(B '#') flag |
| might produce unexpected results due to special handling of |
| the value 0.)) |
| ) |
| $(TR $(TD $(B 'r')) |
| $(TD `\0` or `\1`) |
| ) |
| $(TR $(MULTIROW_CELL 4, $(I Integral)) |
| $(TD $(B 's'), $(B 'd')) |
| $(TD A signed decimal number. The $(B '#') flag is ignored.) |
| ) |
| $(TR $(TD $(B 'b'), $(B 'o'), $(B 'u'), $(B 'x'), $(B 'X')) |
| $(TD An unsigned binary, decimal, octal or hexadecimal number. |
| |
| In case of $(B 'o') and $(B 'x'), the $(B '#') flag |
| denotes that the number must be preceded by `0` and `0x`, with |
| the exception of the value 0, where this does not apply. For |
| $(B 'b') and $(B 'u') the $(B '#') flag has no effect.) |
| ) |
| $(TR $(TD $(B 'e'), $(B 'E'), $(B 'f'), $(B 'F'), $(B 'g'), $(B 'G'), $(B 'a'), $(B 'A')) |
| $(TD As a floating point value with the same specifier. |
| |
| Default precision is large enough to add all digits |
| of the integral value. |
| |
| In case of ($B 'a') and $(B 'A'), the integral digit can be |
| any hexadecimal digit. |
| ) |
| ) |
| $(TR $(TD $(B 'r')) |
| $(TD Characters taken directly from the binary representation.) |
| ) |
| $(TR $(MULTIROW_CELL 5, $(I Floating Point)) |
| $(TD $(B 'e'), $(B 'E')) |
| $(TD Scientific notation: Exactly one integral digit followed by a dot |
| and fractional digits, followed by the exponent. |
| The exponent is formatted as $(B 'e') followed by |
| a $(B '+') or $(B '-') sign, followed by at least |
| two digits. |
| |
| When there are no fractional digits and the $(B '#') flag |
| is $(I not) present, the dot is omitted.) |
| ) |
| $(TR $(TD $(B 'f'), $(B 'F')) |
| $(TD Natural notation: Integral digits followed by a dot and |
| fractional digits. |
| |
| When there are no fractional digits and the $(B '#') flag |
| is $(I not) present, the dot is omitted. |
| |
| $(I Please note: the difference between $(B 'f') and $(B 'F') |
| is only visible for `NaN` and `Infinity`.)) |
| ) |
| $(TR $(TD $(B 's'), $(B 'g'), $(B 'G')) |
| $(TD Short notation: If the absolute value is larger than `10 ^^ precision` |
| or smaller than `0.0001`, the scientific notation is used. |
| If not, the natural notation is applied. |
| |
| In both cases $(I precision) denotes the count of all digits, including |
| the integral digits. Trailing zeros (including a trailing dot) are removed. |
| |
| If $(B '#') flag is present, trailing zeros are not removed.) |
| ) |
| $(TR $(TD $(B 'a'), $(B 'A')) |
| $(TD Hexadecimal scientific notation: `0x` followed by `1` |
| (or `0` in case of value zero or denormalized number) |
| followed by a dot, fractional digits in hexadecimal |
| notation and an exponent. The exponent is build by `p`, |
| followed by a sign and the exponent in $(I decimal) notation. |
| |
| When there are no fractional digits and the $(B '#') flag |
| is $(I not) present, the dot is omitted.) |
| ) |
| $(TR $(TD $(B 'r')) |
| $(TD Characters taken directly from the binary representation.) |
| ) |
| $(TR $(MULTIROW_CELL 3, $(I Character)) |
| $(TD $(B 's'), $(B 'c')) |
| $(TD As the character. |
| |
| Inside of a compound indicator $(B 's') is treated differently: The |
| character is surrounded by single quotes and non printable |
| characters are escaped. This can be avoided by preceding |
| the compound indicator with a $(B '-') flag |
| $(LPAREN)e.g. `"%-$(LPAREN)%s%$(RPAREN)"`$(RPAREN).) |
| ) |
| $(TR $(TD $(B 'b'), $(B 'd'), $(B 'o'), $(B 'u'), $(B 'x'), $(B 'X')) |
| $(TD As the integral that represents the character.) |
| ) |
| $(TR $(TD $(B 'r')) |
| $(TD Characters taken directly from the binary representation.) |
| ) |
| $(TR $(MULTIROW_CELL 3, $(I String)) |
| $(TD $(B 's')) |
| $(TD The sequence of characters that form the string. |
| |
| Inside of a compound indicator the string is surrounded by double quotes |
| and non printable characters are escaped. This can be avoided |
| by preceding the compound indicator with a $(B '-') flag |
| $(LPAREN)e.g. `"%-$(LPAREN)%s%$(RPAREN)"`$(RPAREN).) |
| ) |
| $(TR $(TD $(B 'r')) |
| $(TD The sequence of characters, each formatted with $(B 'r').) |
| ) |
| $(TR $(TD compound) |
| $(TD As an array of characters.) |
| ) |
| $(TR $(MULTIROW_CELL 3, $(I Array)) |
| $(TD $(B 's')) |
| $(TD When the elements are characters, the array is formatted as |
| a string. In all other cases the array is surrounded by square brackets |
| and the elements are separated by a comma and a space. If the elements |
| are strings, they are surrounded by double quotes and non |
| printable characters are escaped.) |
| ) |
| $(TR $(TD $(B 'r')) |
| $(TD The sequence of the elements, each formatted with $(B 'r').) |
| ) |
| $(TR $(TD compound) |
| $(TD The sequence of the elements, each formatted according to the specifications |
| given inside of the compound specifier.) |
| ) |
| $(TR $(MULTIROW_CELL 2, $(I Associative Array)) |
| $(TD $(B 's')) |
| $(TD As a sequence of the elements in unpredictable order. The output is |
| surrounded by square brackets. The elements are separated by a |
| comma and a space. The elements are formatted as `key:value`.) |
| ) |
| $(TR $(TD compound) |
| $(TD As a sequence of the elements in unpredictable order. Each element |
| is formatted according to the specifications given inside of the |
| compound specifier. The first specifier is used for formatting |
| the key and the second specifier is used for formatting the value. |
| The order can be changed with positional arguments. For example |
| `"%(%2$s (%1$s), %)"` will write the value, followed by the key in |
| parenthesis.) |
| ) |
| $(TR $(MULTIROW_CELL 2, $(I Enum)) |
| $(TD $(B 's')) |
| $(TD The name of the value. If the name is not available, the base value |
| is used, preceeded by a cast.) |
| ) |
| $(TR $(TD All, but $(B 's')) |
| $(TD Enums can be formatted with all format characters that can be used |
| with the base value. In that case they are formatted like the base value.) |
| ) |
| $(TR $(MULTIROW_CELL 3, $(I Input Range)) |
| $(TD $(B 's')) |
| $(TD When the elements of the range are characters, they are written like a string. |
| In all other cases, the elements are enclosed by square brackets and separated |
| by a comma and a space.) |
| ) |
| $(TR $(TD $(B 'r')) |
| $(TD The sequence of the elements, each formatted with $(B 'r').) |
| ) |
| $(TR $(TD compound) |
| $(TD The sequence of the elements, each formatted according to the specifications |
| given inside of the compound specifier.) |
| ) |
| $(TR $(MULTIROW_CELL 1, $(I Struct)) |
| $(TD $(B 's')) |
| $(TD When the struct has neither an applicable `toString` |
| nor is an input range, it is formatted as follows: |
| `StructType(field1, field2, ...)`.) |
| ) |
| $(TR $(MULTIROW_CELL 1, $(I Class)) |
| $(TD $(B 's')) |
| $(TD When the class has neither an applicable `toString` |
| nor is an input range, it is formatted as the |
| fully qualified name of the class.) |
| ) |
| $(TR $(MULTIROW_CELL 1, $(I Union)) |
| $(TD $(B 's')) |
| $(TD When the union has neither an applicable `toString` |
| nor is an input range, it is formatted as its base name.) |
| ) |
| $(TR $(MULTIROW_CELL 2, $(I Pointer)) |
| $(TD $(B 's')) |
| $(TD A null pointer is formatted as 'null'. All other pointers are |
| formatted as hexadecimal numbers with the format character $(B 'X').) |
| ) |
| $(TR $(TD $(B 'x'), $(B 'X')) |
| $(TD Formatted as a hexadecimal number.) |
| ) |
| $(TR $(MULTIROW_CELL 3, $(I SIMD vector)) |
| $(TD $(B 's')) |
| $(TD The array is surrounded by square brackets |
| and the elements are separated by a comma and a space.) |
| ) |
| $(TR $(TD $(B 'r')) |
| $(TD The sequence of the elements, each formatted with $(B 'r').) |
| ) |
| $(TR $(TD compound) |
| $(TD The sequence of the elements, each formatted according to the specifications |
| given inside of the compound specifier.) |
| ) |
| $(TR $(MULTIROW_CELL 1, $(I Delegate)) |
| $(TD $(B 's'), $(B 'r'), compound) |
| $(TD As the `.stringof` of this delegate treated as a string. |
| |
| $(I Please note: The implementation is currently buggy |
| and its use is discouraged.)) |
| ) |
| ) |
| |
| Copyright: Copyright The D Language Foundation 2000-2021. |
| |
| Macros: |
| SUBREF = $(REF_ALTTEXT $2, $2, std, format, $1)$(NBSP) |
| MULTIROW_CELL = <td rowspan="$1">$+</td> |
| THMINWIDTH = <th scope="col" width="20%">$0</th> |
| |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). |
| |
| Authors: $(HTTP walterbright.com, Walter Bright), $(HTTP erdani.com, |
| Andrei Alexandrescu), and Kenji Hara |
| |
| Source: $(PHOBOSSRC std/format.d) |
| */ |
| module std.format; |
| |
| /// Simple use: |
| @safe unittest |
| { |
| // Easiest way is to use `%s` everywhere: |
| assert(format("I got %s %s for %s euros.", 30, "eggs", 5.27) == "I got 30 eggs for 5.27 euros."); |
| |
| // Other format characters provide more control: |
| assert(format("I got %b %(%X%) for %f euros.", 30, "eggs", 5.27) == "I got 11110 65676773 for 5.270000 euros."); |
| } |
| |
| /// Compound specifiers allow formatting arrays and other compound types: |
| @safe unittest |
| { |
| /* |
| The trailing end of the sub-format string following the specifier for |
| each item is interpreted as the array delimiter, and is therefore |
| omitted following the last array item: |
| */ |
| assert(format("My items are %(%s %).", [1,2,3]) == "My items are 1 2 3."); |
| assert(format("My items are %(%s, %).", [1,2,3]) == "My items are 1, 2, 3."); |
| |
| /* |
| The "%|" delimiter specifier may be used to indicate where the |
| delimiter begins, so that the portion of the format string prior to |
| it will be retained in the last array element: |
| */ |
| assert(format("My items are %(-%s-%|, %).", [1,2,3]) == "My items are -1-, -2-, -3-."); |
| |
| /* |
| These compound format specifiers may be nested in the case of a |
| nested array argument: |
| */ |
| auto mat = [[1, 2, 3], |
| [4, 5, 6], |
| [7, 8, 9]]; |
| |
| assert(format("%(%(%d %) - %)", mat), "1 2 3 - 4 5 6 - 7 8 9"); |
| assert(format("[%(%(%d %) - %)]", mat), "[1 2 3 - 4 5 6 - 7 8 9]"); |
| assert(format("[%([%(%d %)]%| - %)]", mat), "[1 2 3] - [4 5 6] - [7 8 9]"); |
| |
| /* |
| Strings and characters are escaped automatically inside compound |
| format specifiers. To avoid this behavior, use "%-(" instead of "%(": |
| */ |
| assert(format("My friends are %s.", ["John", "Nancy"]) == `My friends are ["John", "Nancy"].`); |
| assert(format("My friends are %(%s, %).", ["John", "Nancy"]) == `My friends are "John", "Nancy".`); |
| assert(format("My friends are %-(%s, %).", ["John", "Nancy"]) == `My friends are John, Nancy.`); |
| } |
| |
| /// Using parameters: |
| @safe unittest |
| { |
| // Flags can be used to influence to outcome: |
| assert(format("%g != %+#g", 3.14, 3.14) == "3.14 != +3.14000"); |
| |
| // Width and precision help to arrange the formatted result: |
| assert(format(">%10.2f<", 1234.56789) == "> 1234.57<"); |
| |
| // Numbers can be grouped: |
| assert(format("%,4d", int.max) == "21,4748,3647"); |
| |
| // It's possible to specify the position of an argument: |
| assert(format("%3$s %1$s", 3, 17, 5) == "5 3"); |
| } |
| |
| /// Providing parameters as arguments: |
| @safe unittest |
| { |
| // Width as argument |
| assert(format(">%*s<", 10, "abc") == "> abc<"); |
| |
| // Precision as argument |
| assert(format(">%.*f<", 5, 123.2) == ">123.20000<"); |
| |
| // Grouping as argument |
| assert(format("%,*d", 1, int.max) == "2,1,4,7,4,8,3,6,4,7"); |
| |
| // Grouping separator as argument |
| assert(format("%,3?d", '_', int.max) == "2_147_483_647"); |
| |
| // All at once |
| assert(format("%*.*,*?d", 20, 15, 6, '/', int.max) == " 000/002147/483647"); |
| } |
| |
| public import std.format.read; |
| public import std.format.spec; |
| public import std.format.write; |
| |
| import std.exception : enforce; |
| import std.range.primitives : isInputRange; |
| import std.traits : CharTypeOf, isSomeChar, isSomeString, StringTypeOf; |
| import std.format.internal.write : hasToString; |
| |
| /** |
| Signals an issue encountered while formatting. |
| */ |
| class FormatException : Exception |
| { |
| /// Generic constructor. |
| @safe @nogc pure nothrow |
| this() |
| { |
| super("format error"); |
| } |
| |
| /** |
| Creates a new instance of `FormatException`. |
| |
| Params: |
| msg = message of the exception |
| fn = file name of the file where the exception was created (optional) |
| ln = line number of the file where the exception was created (optional) |
| next = for internal use, should always be null (optional) |
| */ |
| @safe @nogc pure nothrow |
| this(string msg, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) |
| { |
| super(msg, fn, ln, next); |
| } |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.exception : assertThrown; |
| |
| assertThrown!FormatException(format("%d", "foo")); |
| } |
| |
| package alias enforceFmt = enforce!FormatException; |
| |
| // @@@DEPRECATED_[2.107.0]@@@ |
| deprecated("formatElement was accidentally made public and will be removed in 2.107.0") |
| void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) |
| if (is(StringTypeOf!T) && !hasToString!(T, Char) && !is(T == enum)) |
| { |
| import std.format.internal.write : fe = formatElement; |
| |
| fe(w, val, f); |
| } |
| |
| // @@@DEPRECATED_[2.107.0]@@@ |
| deprecated("formatElement was accidentally made public and will be removed in 2.107.0") |
| void formatElement(Writer, T, Char)(auto ref Writer w, T val, scope const ref FormatSpec!Char f) |
| if (is(CharTypeOf!T) && !is(T == enum)) |
| { |
| import std.format.internal.write : fe = formatElement; |
| |
| fe(w, val, f); |
| } |
| |
| // @@@DEPRECATED_[2.107.0]@@@ |
| deprecated("formatElement was accidentally made public and will be removed in 2.107.0") |
| void formatElement(Writer, T, Char)(auto ref Writer w, auto ref T val, scope const ref FormatSpec!Char f) |
| if ((!is(StringTypeOf!T) || hasToString!(T, Char)) && !is(CharTypeOf!T) || is(T == enum)) |
| { |
| import std.format.internal.write : fe = formatElement; |
| |
| fe(w, val, f); |
| } |
| |
| // Like NullSink, but toString() isn't even called at all. Used to test the format string. |
| package struct NoOpSink |
| { |
| void put(E)(scope const E) pure @safe @nogc nothrow {} |
| } |
| |
| // @@@DEPRECATED_[2.107.0]@@@ |
| deprecated("unformatElement was accidentally made public and will be removed in 2.107.0") |
| T unformatElement(T, Range, Char)(ref Range input, scope const ref FormatSpec!Char spec) |
| if (isInputRange!Range) |
| { |
| import std.format.internal.read : ue = unformatElement; |
| |
| return ue(input, spec); |
| } |
| |
| // Used to check format strings are compatible with argument types |
| package(std) enum checkFormatException(alias fmt, Args...) = |
| { |
| import std.conv : text; |
| |
| try |
| { |
| auto n = .formattedWrite(NoOpSink(), fmt, Args.init); |
| |
| enforceFmt(n == Args.length, text("Orphan format arguments: args[", n, "..", Args.length, "]")); |
| } |
| catch (Exception e) |
| return e.msg; |
| return null; |
| }(); |
| |
| /** |
| Converts its arguments according to a format string into a string. |
| |
| The second version of `format` takes the format string as template |
| argument. In this case, it is checked for consistency at |
| compile-time and produces slightly faster code, because the length of |
| the output buffer can be estimated in advance. |
| |
| Params: |
| fmt = a $(MREF_ALTTEXT format string, std,format) |
| args = a variadic list of arguments to be formatted |
| Char = character type of `fmt` |
| Args = a variadic list of types of the arguments |
| |
| Returns: |
| The formatted string. |
| |
| Throws: |
| A $(LREF FormatException) if formatting did not succeed. |
| |
| See_Also: |
| $(LREF sformat) for a variant, that tries to avoid garbage collection. |
| */ |
| immutable(Char)[] format(Char, Args...)(in Char[] fmt, Args args) |
| if (isSomeChar!Char) |
| { |
| import std.array : appender; |
| |
| auto w = appender!(immutable(Char)[]); |
| auto n = formattedWrite(w, fmt, args); |
| version (all) |
| { |
| // In the future, this check will be removed to increase consistency |
| // with formattedWrite |
| import std.conv : text; |
| enforceFmt(n == args.length, text("Orphan format arguments: args[", n, "..", args.length, "]")); |
| } |
| return w.data; |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| assert(format("Here are %d %s.", 3, "apples") == "Here are 3 apples."); |
| |
| assert("Increase: %7.2f %%".format(17.4285) == "Increase: 17.43 %"); |
| } |
| |
| @safe pure unittest |
| { |
| import std.exception : assertCTFEable, assertThrown; |
| |
| assertCTFEable!( |
| { |
| assert(format("foo") == "foo"); |
| assert(format("foo%%") == "foo%"); |
| assert(format("foo%s", 'C') == "fooC"); |
| assert(format("%s foo", "bar") == "bar foo"); |
| assert(format("%s foo %s", "bar", "abc") == "bar foo abc"); |
| assert(format("foo %d", -123) == "foo -123"); |
| assert(format("foo %d", 123) == "foo 123"); |
| |
| assertThrown!FormatException(format("foo %s")); |
| assertThrown!FormatException(format("foo %s", 123, 456)); |
| |
| assert(format("hel%slo%s%s%s", "world", -138, 'c', true) == "helworldlo-138ctrue"); |
| }); |
| |
| assert(is(typeof(format("happy")) == string)); |
| assert(is(typeof(format("happy"w)) == wstring)); |
| assert(is(typeof(format("happy"d)) == dstring)); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=16661 |
| @safe pure unittest |
| { |
| assert(format("%.2f"d, 0.4) == "0.40"); |
| assert("%02d"d.format(1) == "01"d); |
| } |
| |
| @safe unittest |
| { |
| int i; |
| string s; |
| |
| s = format("hello world! %s %s %s%s%s", true, 57, 1_000_000_000, 'x', " foo"); |
| assert(s == "hello world! true 57 1000000000x foo"); |
| |
| s = format("%s %A %s", 1.67, -1.28, float.nan); |
| assert(s == "1.67 -0X1.47AE147AE147BP+0 nan", s); |
| |
| s = format("%x %X", 0x1234AF, 0xAFAFAFAF); |
| assert(s == "1234af AFAFAFAF"); |
| |
| s = format("%b %o", 0x1234AF, 0xAFAFAFAF); |
| assert(s == "100100011010010101111 25753727657"); |
| |
| s = format("%d %s", 0x1234AF, 0xAFAFAFAF); |
| assert(s == "1193135 2947526575"); |
| } |
| |
| @safe unittest |
| { |
| import std.conv : octal; |
| |
| string s; |
| int i; |
| |
| s = format("%#06.*f", 2, 12.345); |
| assert(s == "012.35"); |
| |
| s = format("%#0*.*f", 6, 2, 12.345); |
| assert(s == "012.35"); |
| |
| s = format("%7.4g:", 12.678); |
| assert(s == " 12.68:"); |
| |
| s = format("%7.4g:", 12.678L); |
| assert(s == " 12.68:"); |
| |
| s = format("%04f|%05d|%#05x|%#5x", -4.0, -10, 1, 1); |
| assert(s == "-4.000000|-0010|0x001| 0x1"); |
| |
| i = -10; |
| s = format("%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); |
| assert(s == "-10|-10|-10|-10|-10.0000"); |
| |
| i = -5; |
| s = format("%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); |
| assert(s == "-5| -5|-05|-5|-5.0000"); |
| |
| i = 0; |
| s = format("%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); |
| assert(s == "0| 0|000|0|0.0000"); |
| |
| i = 5; |
| s = format("%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); |
| assert(s == "5| 5|005|5|5.0000"); |
| |
| i = 10; |
| s = format("%d|%3d|%03d|%1d|%01.4f", i, i, i, i, cast(double) i); |
| assert(s == "10| 10|010|10|10.0000"); |
| |
| s = format("%.0d", 0); |
| assert(s == "0"); |
| |
| s = format("%.g", .34); |
| assert(s == "0.3"); |
| |
| s = format("%.0g", .34); |
| assert(s == "0.3"); |
| |
| s = format("%.2g", .34); |
| assert(s == "0.34"); |
| |
| s = format("%0.0008f", 1e-08); |
| assert(s == "0.00000001"); |
| |
| s = format("%0.0008f", 1e-05); |
| assert(s == "0.00001000"); |
| |
| s = "helloworld"; |
| string r; |
| r = format("%.2s", s[0 .. 5]); |
| assert(r == "he"); |
| r = format("%.20s", s[0 .. 5]); |
| assert(r == "hello"); |
| r = format("%8s", s[0 .. 5]); |
| assert(r == " hello"); |
| |
| byte[] arrbyte = new byte[4]; |
| arrbyte[0] = 100; |
| arrbyte[1] = -99; |
| arrbyte[3] = 0; |
| r = format("%s", arrbyte); |
| assert(r == "[100, -99, 0, 0]"); |
| |
| ubyte[] arrubyte = new ubyte[4]; |
| arrubyte[0] = 100; |
| arrubyte[1] = 200; |
| arrubyte[3] = 0; |
| r = format("%s", arrubyte); |
| assert(r == "[100, 200, 0, 0]"); |
| |
| short[] arrshort = new short[4]; |
| arrshort[0] = 100; |
| arrshort[1] = -999; |
| arrshort[3] = 0; |
| r = format("%s", arrshort); |
| assert(r == "[100, -999, 0, 0]"); |
| |
| ushort[] arrushort = new ushort[4]; |
| arrushort[0] = 100; |
| arrushort[1] = 20_000; |
| arrushort[3] = 0; |
| r = format("%s", arrushort); |
| assert(r == "[100, 20000, 0, 0]"); |
| |
| int[] arrint = new int[4]; |
| arrint[0] = 100; |
| arrint[1] = -999; |
| arrint[3] = 0; |
| r = format("%s", arrint); |
| assert(r == "[100, -999, 0, 0]"); |
| |
| long[] arrlong = new long[4]; |
| arrlong[0] = 100; |
| arrlong[1] = -999; |
| arrlong[3] = 0; |
| r = format("%s", arrlong); |
| assert(r == "[100, -999, 0, 0]"); |
| |
| ulong[] arrulong = new ulong[4]; |
| arrulong[0] = 100; |
| arrulong[1] = 999; |
| arrulong[3] = 0; |
| r = format("%s", arrulong); |
| assert(r == "[100, 999, 0, 0]"); |
| |
| string[] arr2 = new string[4]; |
| arr2[0] = "hello"; |
| arr2[1] = "world"; |
| arr2[3] = "foo"; |
| r = format("%s", arr2); |
| assert(r == `["hello", "world", "", "foo"]`); |
| |
| r = format("%.8d", 7); |
| assert(r == "00000007"); |
| r = format("%.8x", 10); |
| assert(r == "0000000a"); |
| |
| r = format("%-3d", 7); |
| assert(r == "7 "); |
| |
| r = format("%-1*d", 4, 3); |
| assert(r == "3 "); |
| |
| r = format("%*d", -3, 7); |
| assert(r == "7 "); |
| |
| r = format("%.*d", -3, 7); |
| assert(r == "7"); |
| |
| r = format("%-1.*f", 2, 3.1415); |
| assert(r == "3.14"); |
| |
| r = format("abc"c); |
| assert(r == "abc"); |
| |
| //format() returns the same type as inputted. |
| wstring wr; |
| wr = format("def"w); |
| assert(wr == "def"w); |
| |
| dstring dr; |
| dr = format("ghi"d); |
| assert(dr == "ghi"d); |
| |
| // Empty static character arrays work as well |
| const char[0] cempty; |
| assert(format("test%spath", cempty) == "testpath"); |
| const wchar[0] wempty; |
| assert(format("test%spath", wempty) == "testpath"); |
| const dchar[0] dempty; |
| assert(format("test%spath", dempty) == "testpath"); |
| |
| void* p = () @trusted { return cast(void*) 0xDEADBEEF; } (); |
| r = format("%s", p); |
| assert(r == "DEADBEEF"); |
| |
| r = format("%#x", 0xabcd); |
| assert(r == "0xabcd"); |
| r = format("%#X", 0xABCD); |
| assert(r == "0XABCD"); |
| |
| r = format("%#o", octal!12345); |
| assert(r == "012345"); |
| r = format("%o", 9); |
| assert(r == "11"); |
| r = format("%#o", 0); // https://issues.dlang.org/show_bug.cgi?id=15663 |
| assert(r == "0"); |
| |
| r = format("%+d", 123); |
| assert(r == "+123"); |
| r = format("%+d", -123); |
| assert(r == "-123"); |
| r = format("% d", 123); |
| assert(r == " 123"); |
| r = format("% d", -123); |
| assert(r == "-123"); |
| |
| r = format("%%"); |
| assert(r == "%"); |
| |
| r = format("%d", true); |
| assert(r == "1"); |
| r = format("%d", false); |
| assert(r == "0"); |
| |
| r = format("%d", 'a'); |
| assert(r == "97"); |
| wchar wc = 'a'; |
| r = format("%d", wc); |
| assert(r == "97"); |
| dchar dc = 'a'; |
| r = format("%d", dc); |
| assert(r == "97"); |
| |
| byte b = byte.max; |
| r = format("%x", b); |
| assert(r == "7f"); |
| r = format("%x", ++b); |
| assert(r == "80"); |
| r = format("%x", ++b); |
| assert(r == "81"); |
| |
| short sh = short.max; |
| r = format("%x", sh); |
| assert(r == "7fff"); |
| r = format("%x", ++sh); |
| assert(r == "8000"); |
| r = format("%x", ++sh); |
| assert(r == "8001"); |
| |
| i = int.max; |
| r = format("%x", i); |
| assert(r == "7fffffff"); |
| r = format("%x", ++i); |
| assert(r == "80000000"); |
| r = format("%x", ++i); |
| assert(r == "80000001"); |
| |
| r = format("%x", 10); |
| assert(r == "a"); |
| r = format("%X", 10); |
| assert(r == "A"); |
| r = format("%x", 15); |
| assert(r == "f"); |
| r = format("%X", 15); |
| assert(r == "F"); |
| |
| Object c = null; |
| r = () @trusted { return format("%s", c); } (); |
| assert(r == "null"); |
| |
| enum TestEnum |
| { |
| Value1, Value2 |
| } |
| r = format("%s", TestEnum.Value2); |
| assert(r == "Value2"); |
| |
| immutable(char[5])[int] aa = ([3:"hello", 4:"betty"]); |
| r = () @trusted { return format("%s", aa.values); } (); |
| assert(r == `["hello", "betty"]` || r == `["betty", "hello"]`); |
| r = format("%s", aa); |
| assert(r == `[3:"hello", 4:"betty"]` || r == `[4:"betty", 3:"hello"]`); |
| |
| static const dchar[] ds = ['a','b']; |
| for (int j = 0; j < ds.length; ++j) |
| { |
| r = format(" %d", ds[j]); |
| if (j == 0) |
| assert(r == " 97"); |
| else |
| assert(r == " 98"); |
| } |
| |
| r = format(">%14d<, %s", 15, [1,2,3]); |
| assert(r == "> 15<, [1, 2, 3]"); |
| |
| assert(format("%8s", "bar") == " bar"); |
| assert(format("%8s", "b\u00e9ll\u00f4") == " b\u00e9ll\u00f4"); |
| } |
| |
| @safe unittest |
| { |
| import std.exception : assertCTFEable; |
| |
| assertCTFEable!( |
| { |
| auto tmp = format("%,d", 1000); |
| assert(tmp == "1,000", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%,?d", 'z', 1234567); |
| assert(tmp == "1z234z567", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%10,?d", 'z', 1234567); |
| assert(tmp == " 1z234z567", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%11,2?d", 'z', 1234567); |
| assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%11,*?d", 2, 'z', 1234567); |
| assert(tmp == " 1z23z45z67", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%11,*d", 2, 1234567); |
| assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%11,2d", 1234567); |
| assert(tmp == " 1,23,45,67", "'" ~ tmp ~ "'"); |
| }); |
| } |
| |
| @safe unittest |
| { |
| auto tmp = format("%,f", 1000.0); |
| assert(tmp == "1,000.000000", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%,f", 1234567.891011); |
| assert(tmp == "1,234,567.891011", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%,f", -1234567.891011); |
| assert(tmp == "-1,234,567.891011", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%,2f", 1234567.891011); |
| assert(tmp == "1,23,45,67.891011", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%18,f", 1234567.891011); |
| assert(tmp == " 1,234,567.891011", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%18,?f", '.', 1234567.891011); |
| assert(tmp == " 1.234.567.891011", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%,?.3f", 'ä', 1234567.891011); |
| assert(tmp == "1ä234ä567.891", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%,*?.3f", 1, 'ä', 1234567.891011); |
| assert(tmp == "1ä2ä3ä4ä5ä6ä7.891", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%,4?.3f", '_', 1234567.891011); |
| assert(tmp == "123_4567.891", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%12,3.3f", 1234.5678); |
| assert(tmp == " 1,234.568", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%,e", 3.141592653589793238462); |
| assert(tmp == "3.141593e+00", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%15,e", 3.141592653589793238462); |
| assert(tmp == " 3.141593e+00", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%15,e", -3.141592653589793238462); |
| assert(tmp == " -3.141593e+00", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%.4,*e", 2, 3.141592653589793238462); |
| assert(tmp == "3.1416e+00", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%13.4,*e", 2, 3.141592653589793238462); |
| assert(tmp == " 3.1416e+00", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%,.0f", 3.14); |
| assert(tmp == "3", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%3,g", 1_000_000.123456); |
| assert(tmp == "1e+06", "'" ~ tmp ~ "'"); |
| |
| tmp = format("%19,?f", '.', -1234567.891011); |
| assert(tmp == " -1.234.567.891011", "'" ~ tmp ~ "'"); |
| } |
| |
| // Test for multiple indexes |
| @safe unittest |
| { |
| auto tmp = format("%2:5$s", 1, 2, 3, 4, 5); |
| assert(tmp == "2345", tmp); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=18047 |
| @safe unittest |
| { |
| auto cmp = " 123,456"; |
| assert(cmp.length == 12, format("%d", cmp.length)); |
| auto tmp = format("%12,d", 123456); |
| assert(tmp.length == 12, format("%d", tmp.length)); |
| |
| assert(tmp == cmp, "'" ~ tmp ~ "'"); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=17459 |
| @safe unittest |
| { |
| auto cmp = "100"; |
| auto tmp = format("%0d", 100); |
| assert(tmp == cmp, tmp); |
| |
| cmp = "0100"; |
| tmp = format("%04d", 100); |
| assert(tmp == cmp, tmp); |
| |
| cmp = "0,000,000,100"; |
| tmp = format("%012,3d", 100); |
| assert(tmp == cmp, tmp); |
| |
| cmp = "0,000,001,000"; |
| tmp = format("%012,3d", 1_000); |
| assert(tmp == cmp, tmp); |
| |
| cmp = "0,000,100,000"; |
| tmp = format("%012,3d", 100_000); |
| assert(tmp == cmp, tmp); |
| |
| cmp = "0,001,000,000"; |
| tmp = format("%012,3d", 1_000_000); |
| assert(tmp == cmp, tmp); |
| |
| cmp = "0,100,000,000"; |
| tmp = format("%012,3d", 100_000_000); |
| assert(tmp == cmp, tmp); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=17459 |
| @safe unittest |
| { |
| auto cmp = "100,000"; |
| auto tmp = format("%06,d", 100_000); |
| assert(tmp == cmp, tmp); |
| |
| cmp = "100,000"; |
| tmp = format("%07,d", 100_000); |
| assert(tmp == cmp, tmp); |
| |
| cmp = "0,100,000"; |
| tmp = format("%08,d", 100_000); |
| assert(tmp == cmp, tmp); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=20288 |
| @safe unittest |
| { |
| string s = format("%,.2f", double.nan); |
| assert(s == "nan", s); |
| |
| s = format("%,.2F", double.nan); |
| assert(s == "NAN", s); |
| |
| s = format("%,.2f", -double.nan); |
| assert(s == "-nan", s); |
| |
| s = format("%,.2F", -double.nan); |
| assert(s == "-NAN", s); |
| |
| string g = format("^%13s$", "nan"); |
| string h = "^ nan$"; |
| assert(g == h, "\ngot:" ~ g ~ "\nexp:" ~ h); |
| string a = format("^%13,3.2f$", double.nan); |
| string b = format("^%13,3.2F$", double.nan); |
| string c = format("^%13,3.2f$", -double.nan); |
| string d = format("^%13,3.2F$", -double.nan); |
| assert(a == "^ nan$", "\ngot:'"~ a ~ "'\nexp:'^ nan$'"); |
| assert(b == "^ NAN$", "\ngot:'"~ b ~ "'\nexp:'^ NAN$'"); |
| assert(c == "^ -nan$", "\ngot:'"~ c ~ "'\nexp:'^ -nan$'"); |
| assert(d == "^ -NAN$", "\ngot:'"~ d ~ "'\nexp:'^ -NAN$'"); |
| |
| a = format("^%-13,3.2f$", double.nan); |
| b = format("^%-13,3.2F$", double.nan); |
| c = format("^%-13,3.2f$", -double.nan); |
| d = format("^%-13,3.2F$", -double.nan); |
| assert(a == "^nan $", "\ngot:'"~ a ~ "'\nexp:'^nan $'"); |
| assert(b == "^NAN $", "\ngot:'"~ b ~ "'\nexp:'^NAN $'"); |
| assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); |
| assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); |
| |
| a = format("^%+13,3.2f$", double.nan); |
| b = format("^%+13,3.2F$", double.nan); |
| c = format("^%+13,3.2f$", -double.nan); |
| d = format("^%+13,3.2F$", -double.nan); |
| assert(a == "^ +nan$", "\ngot:'"~ a ~ "'\nexp:'^ +nan$'"); |
| assert(b == "^ +NAN$", "\ngot:'"~ b ~ "'\nexp:'^ +NAN$'"); |
| assert(c == "^ -nan$", "\ngot:'"~ c ~ "'\nexp:'^ -nan$'"); |
| assert(d == "^ -NAN$", "\ngot:'"~ d ~ "'\nexp:'^ -NAN$'"); |
| |
| a = format("^%-+13,3.2f$", double.nan); |
| b = format("^%-+13,3.2F$", double.nan); |
| c = format("^%-+13,3.2f$", -double.nan); |
| d = format("^%-+13,3.2F$", -double.nan); |
| assert(a == "^+nan $", "\ngot:'"~ a ~ "'\nexp:'^+nan $'"); |
| assert(b == "^+NAN $", "\ngot:'"~ b ~ "'\nexp:'^+NAN $'"); |
| assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); |
| assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); |
| |
| a = format("^%- 13,3.2f$", double.nan); |
| b = format("^%- 13,3.2F$", double.nan); |
| c = format("^%- 13,3.2f$", -double.nan); |
| d = format("^%- 13,3.2F$", -double.nan); |
| assert(a == "^ nan $", "\ngot:'"~ a ~ "'\nexp:'^ nan $'"); |
| assert(b == "^ NAN $", "\ngot:'"~ b ~ "'\nexp:'^ NAN $'"); |
| assert(c == "^-nan $", "\ngot:'"~ c ~ "'\nexp:'^-nan $'"); |
| assert(d == "^-NAN $", "\ngot:'"~ d ~ "'\nexp:'^-NAN $'"); |
| } |
| |
| @safe unittest |
| { |
| struct S |
| { |
| int a; |
| |
| void toString(void delegate(const(char)[]) sink, string fmt) |
| { |
| auto spec = singleSpec(fmt); |
| sink.formatValue(a, spec); |
| } |
| } |
| |
| S s = S(1); |
| auto result = () @trusted { return format!"%5,3d"(s); } (); |
| assert(result == " 1"); |
| } |
| |
| /// ditto |
| typeof(fmt) format(alias fmt, Args...)(Args args) |
| if (isSomeString!(typeof(fmt))) |
| { |
| import std.array : appender; |
| import std.range.primitives : ElementEncodingType; |
| import std.traits : Unqual; |
| |
| alias e = checkFormatException!(fmt, Args); |
| alias Char = Unqual!(ElementEncodingType!(typeof(fmt))); |
| |
| static assert(!e, e); |
| auto w = appender!(immutable(Char)[]); |
| |
| // no need to traverse the string twice during compile time |
| if (!__ctfe) |
| { |
| enum len = guessLength!Char(fmt); |
| w.reserve(len); |
| } |
| else |
| { |
| w.reserve(fmt.length); |
| } |
| |
| formattedWrite(w, fmt, args); |
| return w.data; |
| } |
| |
| /// The format string can be checked at compile-time: |
| @safe pure unittest |
| { |
| auto s = format!"%s is %s"("Pi", 3.14); |
| assert(s == "Pi is 3.14"); |
| |
| // This line doesn't compile, because 3.14 cannot be formatted with %d: |
| // s = format!"%s is %d"("Pi", 3.14); |
| } |
| |
| @safe pure unittest |
| { |
| string s; |
| static assert(!__traits(compiles, {s = format!"%l"();})); // missing arg |
| static assert(!__traits(compiles, {s = format!""(404);})); // surplus arg |
| static assert(!__traits(compiles, {s = format!"%d"(4.03);})); // incompatible arg |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=17381 |
| @safe pure unittest |
| { |
| static assert(!__traits(compiles, format!"%s"(1.5, 2))); |
| static assert(!__traits(compiles, format!"%f"(1.5, 2))); |
| static assert(!__traits(compiles, format!"%s"(1.5L, 2))); |
| static assert(!__traits(compiles, format!"%f"(1.5L, 2))); |
| } |
| |
| // called during compilation to guess the length of the |
| // result of format |
| private size_t guessLength(Char, S)(S fmtString) |
| { |
| import std.array : appender; |
| |
| size_t len; |
| auto output = appender!(immutable(Char)[])(); |
| auto spec = FormatSpec!Char(fmtString); |
| while (spec.writeUpToNextSpec(output)) |
| { |
| // take a guess |
| if (spec.width == 0 && (spec.precision == spec.UNSPECIFIED || spec.precision == spec.DYNAMIC)) |
| { |
| switch (spec.spec) |
| { |
| case 'c': |
| ++len; |
| break; |
| case 'd': |
| case 'x': |
| case 'X': |
| len += 3; |
| break; |
| case 'b': |
| len += 8; |
| break; |
| case 'f': |
| case 'F': |
| len += 10; |
| break; |
| case 's': |
| case 'e': |
| case 'E': |
| case 'g': |
| case 'G': |
| len += 12; |
| break; |
| default: break; |
| } |
| |
| continue; |
| } |
| |
| if ((spec.spec == 'e' || spec.spec == 'E' || spec.spec == 'g' || |
| spec.spec == 'G' || spec.spec == 'f' || spec.spec == 'F') && |
| spec.precision != spec.UNSPECIFIED && spec.precision != spec.DYNAMIC && |
| spec.width == 0 |
| ) |
| { |
| len += spec.precision + 5; |
| continue; |
| } |
| |
| if (spec.width == spec.precision) |
| len += spec.width; |
| else if (spec.width > 0 && spec.width != spec.DYNAMIC && |
| (spec.precision == spec.UNSPECIFIED || spec.width > spec.precision)) |
| { |
| len += spec.width; |
| } |
| else if (spec.precision != spec.UNSPECIFIED && spec.precision > spec.width) |
| len += spec.precision; |
| } |
| len += output.data.length; |
| return len; |
| } |
| |
| @safe pure |
| unittest |
| { |
| assert(guessLength!char("%c") == 1); |
| assert(guessLength!char("%d") == 3); |
| assert(guessLength!char("%x") == 3); |
| assert(guessLength!char("%b") == 8); |
| assert(guessLength!char("%f") == 10); |
| assert(guessLength!char("%s") == 12); |
| assert(guessLength!char("%02d") == 2); |
| assert(guessLength!char("%02d") == 2); |
| assert(guessLength!char("%4.4d") == 4); |
| assert(guessLength!char("%2.4f") == 4); |
| assert(guessLength!char("%02d:%02d:%02d") == 8); |
| assert(guessLength!char("%0.2f") == 7); |
| assert(guessLength!char("%0*d") == 0); |
| } |
| |
| /** |
| Converts its arguments according to a format string into a buffer. |
| The buffer has to be large enough to hold the formatted string. |
| |
| The second version of `sformat` takes the format string as a template |
| argument. In this case, it is checked for consistency at |
| compile-time. |
| |
| Params: |
| buf = the buffer where the formatted string should go |
| fmt = a $(MREF_ALTTEXT format string, std,format) |
| args = a variadic list of arguments to be formatted |
| Char = character type of `fmt` |
| Args = a variadic list of types of the arguments |
| |
| Returns: |
| A slice of `buf` containing the formatted string. |
| |
| Throws: |
| A $(REF_ALTTEXT RangeError, RangeError, core, exception) if `buf` |
| isn't large enough to hold the formatted string |
| and a $(LREF FormatException) if formatting did not succeed. |
| |
| Note: |
| In theory this function should be `@nogc`. But with the current |
| implementation there are some cases where allocations occur: |
| |
| $(UL |
| $(LI An exception is thrown.) |
| $(LI A custom `toString` function of a compound type allocates.)) |
| */ |
| char[] sformat(Char, Args...)(return scope char[] buf, scope const(Char)[] fmt, Args args) |
| { |
| import core.exception : RangeError; |
| import std.range.primitives; |
| import std.utf : encode; |
| |
| static struct Sink |
| { |
| char[] buf; |
| size_t i; |
| void put(dchar c) |
| { |
| char[4] enc; |
| auto n = encode(enc, c); |
| |
| if (buf.length < i + n) |
| throw new RangeError(__FILE__, __LINE__); |
| |
| buf[i .. i + n] = enc[0 .. n]; |
| i += n; |
| } |
| void put(scope const(char)[] s) |
| { |
| if (buf.length < i + s.length) |
| throw new RangeError(__FILE__, __LINE__); |
| |
| buf[i .. i + s.length] = s[]; |
| i += s.length; |
| } |
| void put(scope const(wchar)[] s) |
| { |
| for (; !s.empty; s.popFront()) |
| put(s.front); |
| } |
| void put(scope const(dchar)[] s) |
| { |
| for (; !s.empty; s.popFront()) |
| put(s.front); |
| } |
| } |
| auto sink = Sink(buf); |
| auto n = formattedWrite(sink, fmt, args); |
| version (all) |
| { |
| // In the future, this check will be removed to increase consistency |
| // with formattedWrite |
| import std.conv : text; |
| enforceFmt( |
| n == args.length, |
| text("Orphan format arguments: args[", n, " .. ", args.length, "]") |
| ); |
| } |
| return buf[0 .. sink.i]; |
| } |
| |
| /// ditto |
| char[] sformat(alias fmt, Args...)(char[] buf, Args args) |
| if (isSomeString!(typeof(fmt))) |
| { |
| alias e = checkFormatException!(fmt, Args); |
| static assert(!e, e); |
| return .sformat(buf, fmt, args); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| char[20] buf; |
| assert(sformat(buf[], "Here are %d %s.", 3, "apples") == "Here are 3 apples."); |
| |
| assert(buf[].sformat("Increase: %7.2f %%", 17.4285) == "Increase: 17.43 %"); |
| } |
| |
| /// The format string can be checked at compile-time: |
| @safe pure unittest |
| { |
| char[20] buf; |
| |
| assert(sformat!"Here are %d %s."(buf[], 3, "apples") == "Here are 3 apples."); |
| |
| // This line doesn't compile, because 3.14 cannot be formatted with %d: |
| // writeln(sformat!"Here are %d %s."(buf[], 3.14, "apples")); |
| } |
| |
| // checking, what is implicitly and explicitly stated in the public unittest |
| @safe unittest |
| { |
| import std.exception : assertThrown; |
| |
| char[20] buf; |
| assertThrown!FormatException(sformat(buf[], "Here are %d %s.", 3.14, "apples")); |
| assert(!__traits(compiles, sformat!"Here are %d %s."(buf[], 3.14, "apples"))); |
| } |
| |
| @safe unittest |
| { |
| import core.exception : RangeError; |
| import std.exception : assertCTFEable, assertThrown; |
| |
| assertCTFEable!( |
| { |
| char[10] buf; |
| |
| assert(sformat(buf[], "foo") == "foo"); |
| assert(sformat(buf[], "foo%%") == "foo%"); |
| assert(sformat(buf[], "foo%s", 'C') == "fooC"); |
| assert(sformat(buf[], "%s foo", "bar") == "bar foo"); |
| () @trusted { |
| assertThrown!RangeError(sformat(buf[], "%s foo %s", "bar", "abc")); |
| } (); |
| assert(sformat(buf[], "foo %d", -123) == "foo -123"); |
| assert(sformat(buf[], "foo %d", 123) == "foo 123"); |
| |
| assertThrown!FormatException(sformat(buf[], "foo %s")); |
| assertThrown!FormatException(sformat(buf[], "foo %s", 123, 456)); |
| |
| assert(sformat(buf[], "%s %s %s", "c"c, "w"w, "d"d) == "c w d"); |
| }); |
| } |
| |
| @safe unittest // ensure that sformat avoids the GC |
| { |
| import core.memory : GC; |
| |
| const a = ["foo", "bar"]; |
| const u = () @trusted { return GC.stats().usedSize; } (); |
| char[20] buf; |
| sformat(buf, "%d", 123); |
| sformat(buf, "%s", a); |
| sformat(buf, "%s", 'c'); |
| const v = () @trusted { return GC.stats().usedSize; } (); |
| assert(u == v); |
| } |
| |
| version (StdUnittest) |
| private void formatReflectTest(T)(ref T val, string fmt, string formatted, string fn = __FILE__, size_t ln = __LINE__) |
| { |
| formatReflectTest(val, fmt, [formatted], fn, ln); |
| } |
| |
| version (StdUnittest) |
| private void formatReflectTest(T)(ref T val, string fmt, string[] formatted, string fn = __FILE__, size_t ln = __LINE__) |
| { |
| import core.exception : AssertError; |
| import std.algorithm.searching : canFind; |
| import std.array : appender; |
| import std.math.operations : isClose; |
| import std.traits : FloatingPointTypeOf; |
| |
| auto w = appender!string(); |
| formattedWrite(w, fmt, val); |
| |
| auto input = w.data; |
| enforce!AssertError(formatted.canFind(input), input, fn, ln); |
| |
| T val2; |
| formattedRead(input, fmt, val2); |
| |
| static if (is(FloatingPointTypeOf!T)) |
| enforce!AssertError(isClose(val, val2), input, fn, ln); |
| else |
| enforce!AssertError(val == val2, input, fn, ln); |
| } |
| |
| @safe unittest |
| { |
| void booleanTest() |
| { |
| auto b = true; |
| formatReflectTest(b, "%s", `true`); |
| formatReflectTest(b, "%b", `1`); |
| formatReflectTest(b, "%o", `1`); |
| formatReflectTest(b, "%d", `1`); |
| formatReflectTest(b, "%u", `1`); |
| formatReflectTest(b, "%x", `1`); |
| } |
| |
| void integerTest() |
| { |
| auto n = 127; |
| formatReflectTest(n, "%s", `127`); |
| formatReflectTest(n, "%b", `1111111`); |
| formatReflectTest(n, "%o", `177`); |
| formatReflectTest(n, "%d", `127`); |
| formatReflectTest(n, "%u", `127`); |
| formatReflectTest(n, "%x", `7f`); |
| } |
| |
| void floatingTest() |
| { |
| auto f = 3.14; |
| formatReflectTest(f, "%s", `3.14`); |
| formatReflectTest(f, "%e", `3.140000e+00`); |
| formatReflectTest(f, "%f", `3.140000`); |
| formatReflectTest(f, "%g", `3.14`); |
| } |
| |
| void charTest() |
| { |
| auto c = 'a'; |
| formatReflectTest(c, "%s", `a`); |
| formatReflectTest(c, "%c", `a`); |
| formatReflectTest(c, "%b", `1100001`); |
| formatReflectTest(c, "%o", `141`); |
| formatReflectTest(c, "%d", `97`); |
| formatReflectTest(c, "%u", `97`); |
| formatReflectTest(c, "%x", `61`); |
| } |
| |
| void strTest() |
| { |
| auto s = "hello"; |
| formatReflectTest(s, "%s", `hello`); |
| formatReflectTest(s, "%(%c,%)", `h,e,l,l,o`); |
| formatReflectTest(s, "%(%s,%)", `'h','e','l','l','o'`); |
| formatReflectTest(s, "[%(<%c>%| $ %)]", `[<h> $ <e> $ <l> $ <l> $ <o>]`); |
| } |
| |
| void daTest() |
| { |
| auto a = [1,2,3,4]; |
| formatReflectTest(a, "%s", `[1, 2, 3, 4]`); |
| formatReflectTest(a, "[%(%s; %)]", `[1; 2; 3; 4]`); |
| formatReflectTest(a, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`); |
| } |
| |
| void saTest() |
| { |
| int[4] sa = [1,2,3,4]; |
| formatReflectTest(sa, "%s", `[1, 2, 3, 4]`); |
| formatReflectTest(sa, "[%(%s; %)]", `[1; 2; 3; 4]`); |
| formatReflectTest(sa, "[%(<%s>%| $ %)]", `[<1> $ <2> $ <3> $ <4>]`); |
| } |
| |
| void aaTest() |
| { |
| auto aa = [1:"hello", 2:"world"]; |
| formatReflectTest(aa, "%s", [`[1:"hello", 2:"world"]`, `[2:"world", 1:"hello"]`]); |
| formatReflectTest(aa, "[%(%s->%s, %)]", [`[1->"hello", 2->"world"]`, `[2->"world", 1->"hello"]`]); |
| formatReflectTest(aa, "{%([%s=%(%c%)]%|; %)}", [`{[1=hello]; [2=world]}`, `{[2=world]; [1=hello]}`]); |
| } |
| |
| import std.exception : assertCTFEable; |
| |
| assertCTFEable!( |
| { |
| booleanTest(); |
| integerTest(); |
| floatingTest(); |
| charTest(); |
| strTest(); |
| daTest(); |
| saTest(); |
| aaTest(); |
| }); |
| } |