mirror of
https://github.com/dlang/phobos.git
synced 2025-04-27 21:51:40 +03:00
1832 lines
58 KiB
D
1832 lines
58 KiB
D
// 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 than the value
|
|
given by the width parameter, the output is left
|
|
justified. Without the $(B '-') flag, the output remains
|
|
right justified.
|
|
|
|
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 than 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/package.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");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=23245
|
|
@safe unittest
|
|
{
|
|
static struct S
|
|
{
|
|
string toString() { return "S"; }
|
|
}
|
|
|
|
S[1] s;
|
|
assert(format("%s", s) == "[S]");
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=23246
|
|
@safe unittest
|
|
{
|
|
static struct S
|
|
{
|
|
string toString() { return "S"; }
|
|
}
|
|
|
|
S[int] s = [0 : S()];
|
|
assert(format("%s", s) == "[0:S]");
|
|
}
|
|
|
|
/// 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(char c)
|
|
{
|
|
if (buf.length <= i)
|
|
throw new RangeError(__FILE__, __LINE__);
|
|
|
|
buf[i] = c;
|
|
i += 1;
|
|
}
|
|
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);
|
|
}
|
|
|
|
@safe unittest // https://issues.dlang.org/show_bug.cgi?id=23488
|
|
{
|
|
static struct R
|
|
{
|
|
string s = "Ü";
|
|
bool empty() { return s.length == 0; }
|
|
char front() { return s[0]; }
|
|
void popFront() { s = s[1 .. $]; }
|
|
}
|
|
char[2] buf;
|
|
assert(sformat(buf, "%s", R()) == "Ü");
|
|
}
|
|
|
|
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();
|
|
});
|
|
}
|