diff --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv --- a/libcxx/docs/Status/Cxx20Issues.csv +++ b/libcxx/docs/Status/Cxx20Issues.csv @@ -205,7 +205,7 @@ "`3242 `__","``std::format``\ : missing rules for ``arg-id``\ in ``width``\ and ``precision``\ ","Prague","|Complete|","Clang 13" "`3243 `__","``std::format``\ and negative zeroes","Prague","","" "`3247 `__","``ranges::iter_move``\ should perform ADL-only lookup of ``iter_move``\ ","Prague","","" -"`3248 `__","``std::format``\ ``#b``\ , ``#B``\ , ``#o``\ , ``#x``\ , and ``#X``\ presentation types misformat negative numbers","Prague","","" +"`3248 `__","``std::format``\ ``#b``\ , ``#B``\ , ``#o``\ , ``#x``\ , and ``#X``\ presentation types misformat negative numbers","Prague","|Complete|","13.0" "`3250 `__","``std::format``\ : ``#``\ (alternate form) for NaN and inf","Prague","","" "`3251 `__","Are ``std::format``\ alignment specifiers applied to string arguments?","Prague","","" "`3252 `__","Parse locale's aware modifiers for commands are not consistent with POSIX spec","Prague","","" diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -129,7 +129,7 @@ "`P1644 `__","LWG","Add wait/notify to atomic","Cologne","","" "`P1650 `__","LWG","Output std::chrono::days with 'd' suffix","Cologne","","" "`P1651 `__","LWG","bind_front should not unwrap reference_wrapper","Cologne","|Complete|","13.0" -"`P1652 `__","LWG","Printf corner cases in std::format","Cologne","","" +"`P1652 `__","LWG","Printf corner cases in std::format","Cologne","|In Progress|","" "`P1661 `__","LWG","Remove dedicated precalculated hash lookup interface","Cologne","|Nothing To Do|","" "`P1754 `__","LWG","Rename concepts to standard_case for C++20, while we still can","Cologne","|In Progress|","" "","","","","","" diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -108,6 +108,8 @@ __format/format_parse_context.h __format/format_string.h __format/formatter.h + __format/formatter_integer.h + __format/formatter_integral.h __format/formatter_string.h __format/parser_std_format_spec.h __function_like.h diff --git a/libcxx/include/__format/formatter.h b/libcxx/include/__format/formatter.h --- a/libcxx/include/__format/formatter.h +++ b/libcxx/include/__format/formatter.h @@ -141,6 +141,30 @@ return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after, __fill); } +/** + * @overload + * + * Uses a transformation operation before writing an element. + * + * TODO FMT Fill will probably change to support Unicode grapheme cluster. + */ +template +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY auto +__write(__output_iterator auto __out_it, const _CharT* __first, + const _CharT* __last, size_t __size, _UnaryOperation __op, + size_t __width, _Fill __fill, + __format_spec::_Flags::_Alignment __alignment) -> decltype(__out_it) { + + _LIBCPP_ASSERT(__first <= __last, "Not a valid range"); + _LIBCPP_ASSERT(__size < __width, "Precondition failure"); + + __padding_size_result __padding = + __padding_size(__size, __width, __alignment); + __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before, __fill); + __out_it = _VSTD::transform(__first, __last, _VSTD::move(__out_it), __op); + return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after, __fill); +} + /** * Writes Unicode input to the output with the required padding. * diff --git a/libcxx/include/__format/formatter_integer.h b/libcxx/include/__format/formatter_integer.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__format/formatter_integer.h @@ -0,0 +1,170 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___FORMAT_FORMATTER_INTEGER_H +#define _LIBCPP___FORMAT_FORMATTER_INTEGER_H + +#include <__availability> +#include <__config> +#include <__format/format_error.h> +#include <__format/format_fwd.h> +#include <__format/formatter.h> +#include <__format/formatter_integral.h> +#include <__format/parser_std_format_spec.h> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +// TODO FMT Remove this once we require compilers with proper C++20 support. +// If the compiler has no concepts support, the format header will be disabled. +// Without concepts support enable_if needs to be used and that too much effort +// to support compilers with partial C++20 support. +#if !defined(_LIBCPP_HAS_NO_CONCEPTS) && \ + !defined(_LIBCPP_HAS_NO_BUILTIN_IS_CONSTANT_EVALUATED) + +namespace __format_spec { + +template +class _LIBCPP_TEMPLATE_VIS __parser_integer : public __parser_integral<_CharT> { +public: + using _Base = __parser_integral<_CharT>; + + _LIBCPP_INLINE_VISIBILITY constexpr auto parse(auto& __parse_ctx) + -> decltype(__parse_ctx.begin()) { + auto __it = _Base::parse(__parse_ctx); + + this->__handle_integer(); + switch (this->__type) { + case _Flags::_Type::__default: + this->__type = _Flags::_Type::__decimal; + break; + + case _Flags::_Type::__binary_lower_case: + case _Flags::_Type::__binary_upper_case: + case _Flags::_Type::__char: + case _Flags::_Type::__octal: + case _Flags::_Type::__decimal: + case _Flags::_Type::__hexadecimal_lower_case: + case _Flags::_Type::__hexadecimal_upper_case: + break; + + default: + __throw_format_error("The format-spec type has a type not supported for " + "an integer argument"); + } + return __it; + } +}; + +template +using __formatter_integer = __formatter_integral<__parser_integer<_CharT>>; + +} // namespace __format_spec + +// [format.formatter.spec]/2.3 +// For each charT, for each cv-unqualified arithmetic type ArithmeticT other +// than char, wchar_t, char8_t, char16_t, or char32_t, a specialization + +// Signed integral types. +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter + : public __format_spec::__formatter_integer<_CharT> {}; +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter + : public __format_spec::__formatter_integer<_CharT> {}; +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter + : public __format_spec::__formatter_integer<_CharT> {}; +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter + : public __format_spec::__formatter_integer<_CharT> {}; +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter + : public __format_spec::__formatter_integer<_CharT> {}; +#ifndef _LIBCPP_HAS_NO_INT128 +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter<__int128_t, _CharT> + : public __format_spec::__formatter_integer<_CharT> { + using _Base = __format_spec::__formatter_integer<_CharT>; + + _LIBCPP_INLINE_VISIBILITY auto format(__int128_t __value, auto& __ctx) + -> decltype(__ctx.out()) { + // TODO FMT Implement full 128 bit support. + using _To = long long; + if (__value < numeric_limits<_To>::min() || + __value > numeric_limits<_To>::max()) + __throw_format_error("128 bit value is outside of implemented range"); + + return _Base::format(static_cast<_To>(__value), __ctx); + } +}; +#endif + +// Unsigned integral types. +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter + : public __format_spec::__formatter_integer<_CharT> {}; +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter + : public __format_spec::__formatter_integer<_CharT> {}; +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter + : public __format_spec::__formatter_integer<_CharT> {}; +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter + : public __format_spec::__formatter_integer<_CharT> {}; +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter + : public __format_spec::__formatter_integer<_CharT> {}; +#ifndef _LIBCPP_HAS_NO_INT128 +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + formatter<__uint128_t, _CharT> + : public __format_spec::__formatter_integer<_CharT> { + using _Base = __format_spec::__formatter_integer<_CharT>; + + _LIBCPP_INLINE_VISIBILITY auto format(__uint128_t __value, auto& __ctx) + -> decltype(__ctx.out()) { + // TODO FMT Implement full 128 bit support. + using _To = unsigned long long; + if (__value < numeric_limits<_To>::min() || + __value > numeric_limits<_To>::max()) + __throw_format_error("128 bit value is outside of implemented range"); + + return _Base::format(static_cast<_To>(__value), __ctx); + } +}; +#endif + +#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) && !defined(_LIBCPP_HAS_NO_BUILTIN_IS_CONSTANT_EVALUATED) + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___FORMAT_FORMATTER_INTEGER_H diff --git a/libcxx/include/__format/formatter_integral.h b/libcxx/include/__format/formatter_integral.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__format/formatter_integral.h @@ -0,0 +1,561 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___FORMAT_FORMATTER_INTEGRAL_H +#define _LIBCPP___FORMAT_FORMATTER_INTEGRAL_H + +#include <__config> +#include <__format/format_error.h> +#include <__format/format_fwd.h> +#include <__format/formatter.h> +#include <__format/parser_std_format_spec.h> +#include +#include +#include +#include +#include +#include + +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +#include +#endif + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +// TODO FMT Remove this once we require compilers with proper C++20 support. +// If the compiler has no concepts support, the format header will be disabled. +// Without concepts support enable_if needs to be used and that too much effort +// to support compilers with partial C++20 support. +#if !defined(_LIBCPP_HAS_NO_CONCEPTS) && \ + !defined(_LIBCPP_HAS_NO_BUILTIN_IS_CONSTANT_EVALUATED) + +/** + * Integral formatting classes. + * + * There are two types used here: + * * C++-type, the type as used in C++. + * * format-type, the output type specified in the std-format-spec. + * + * Design of the integral formatters consists of several layers. + * * @ref __parser_integral The basic std-format-spec parser for all integral + * classes. This parser does the basic sanity checks. It also contains some + * helper functions that are nice to have available for all parsers. + * * A C++-type specific parser. These parsers must derive from + * @ref __parser_integral. Their task is to validate whether the parsed + * std-format-spec is valid for the C++-type and selected format-type. After + * validation they need to make sure all members are properly set. For + * example, when the alignment hasn't changed it needs to set the proper + * default alignment for the format-type. The following parsers are available: + * - @ref __parser_integer + * - @ref __parser_char + * - @ref __parser_bool + * * A general formatter for all integral types @ref __formatter_integral. This + * formatter can handle all formatting of integers and characters. The class + * derives from the proper formatter. + * Note the boolean string format-type isn't supported in this class. + * * A typedef C++-type group combing the @ref __formatter_integral with a + * parser: + * * @ref __formatter_integer + * * @ref __formatter_char + * * @ref __formatter_bool + * * Then every C++-type has its own formatter specializations. They inherit + * from the C++-type group typedef. Most specializations need nothing else. + * Others need some additional specializations in this class. For example the + * 128-bit integral formatters use the 64-bit version to do the formatting. + */ +namespace __format_spec { + +template +class _LIBCPP_TEMPLATE_VIS __parser_integral + : public __parser_std<_CharT, __parser_no_precision, __parser_sign, + __parser_alternate_form, __parser_zero_padding, + __parser_locale_specific_form> { +public: + /** + * Handles the post-parsing updates for the integer types. + * + * Updates the zero-padding and alignment for integer types. + * + * [format.string.std]/13 + * If the 0 character and an align option both appear, the 0 character is + * ignored. + * + * For the formatter a @ref __default alignment means zero-padding. Update + * the alignment based on parsed format string. + */ + _LIBCPP_INLINE_VISIBILITY constexpr void __handle_integer() noexcept { + this->__zero_padding &= this->__alignment == _Flags::_Alignment::__default; + if (!this->__zero_padding && + this->__alignment == _Flags::_Alignment::__default) + this->__alignment = _Flags::_Alignment::__right; + } + + /** + * Handles the post-parsing updates for the character types. + * + * Sets the alignment and validates the format flags set for a character type. + * + * At the moment the validation for a character and a Boolean behave the + * same, but this may change in the future. + */ + _LIBCPP_INLINE_VISIBILITY constexpr void __handle_char() { __handle_bool(); } + + /** + * Handles the post-parsing updates for the Boolean types. + * + * Sets the alignment and validates the format flags set for a Boolean type. + */ + _LIBCPP_INLINE_VISIBILITY constexpr void __handle_bool() { + if (this->__sign != _Flags::_Sign::__default) + __throw_format_error("A sign field isn't allowed in this format-spec"); + + if (this->__alternate_form) + __throw_format_error( + "An alternate form field isn't allowed in this format-spec"); + + if (this->__zero_padding) + __throw_format_error( + "A zero-padding field isn't allowed in this format-spec"); + + if (this->__alignment == _Flags::_Alignment::__default) + this->__alignment = _Flags::_Alignment::__left; + } +}; + +/** Wrapper around @ref to_chars, returning the output pointer. */ +template +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY char* +__to_buffer(char* __first, char* __last, _Tp __value, int __base) { + // TODO FMT Evaluate code overhead due to not calling the internal function + // directly. (Should be zero overhead.) + to_chars_result __r = _VSTD::to_chars(__first, __last, __value, __base); + _LIBCPP_ASSERT(__r.ec == errc(0), "Internal buffer too small"); + return __r.ptr; +} + +/** + * Helper to determine the buffer size to output a integer in Base @em x. + * + * There are several overloads for the supported bases. The function uses the + * base as template argument so it can be used in a constant expression. + */ +template +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY constexpr size_t +__buffer_size() noexcept requires(_Base == 2) { + return numeric_limits<_Tp>::digits // The number of binary digits. + + 2 // Reserve space for the '0[Bb]' prefix. + + 1; // Reserve space for the sign. +} + +template +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY constexpr size_t +__buffer_size() noexcept requires(_Base == 8) { + return numeric_limits<_Tp>::digits // The number of binary digits. + / 3 // Adjust to octal. + + 1 // Turn floor to ceil. + + 1 // Reserve space for the '0' prefix. + + 1; // Reserve space for the sign. +} + +template +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY constexpr size_t +__buffer_size() noexcept requires(_Base == 10) { + return numeric_limits<_Tp>::digits10 // The floored value. + + 1 // Turn floor to ceil. + + 1; // Reserve space for the sign. +} + +template +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY constexpr size_t +__buffer_size() noexcept requires(_Base == 16) { + return numeric_limits<_Tp>::digits // The number of binary digits. + / 4 // Adjust to hexadecimal. + + 2 // Reserve space for the '0[Xx]' prefix. + + 1; // Reserve space for the sign. +} + +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY inline char* +__insert_sign(char* __buf, bool __negative, _Flags::_Sign __sign) { + if (__negative) + *__buf++ = '-'; + else + switch (__sign) { + case _Flags::_Sign::__default: + case _Flags::_Sign::__minus: + // No sign added. + break; + case _Flags::_Sign::__plus: + *__buf++ = '+'; + break; + case _Flags::_Sign::__space: + *__buf++ = ' '; + break; + } + + return __buf; +} + +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY constexpr char __hex_to_upper(char c) { + switch (c) { + case 'a': + return 'A'; + case 'b': + return 'B'; + case 'c': + return 'C'; + case 'd': + return 'D'; + case 'e': + return 'E'; + case 'f': + return 'F'; + } + return c; +} + +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +/** + * Determines the required grouping based on the size of the input. + * + * The grouping's last element will be repeated. For simplicity this repeating + * is unwrapped based on the length of the input. (When the input is short some + * groups are not processed.) + * + * @returns The size of the groups to write. This means the number of + * separator characters written is size() - 1. + * + * @note Since zero-sized groups cause issues they are silently ignored. + * + * @note The grouping field of the locale is always a @c std::string, + * regardless whether the @c std::numpunct's type is @c char or @c wchar_t. + */ +[[nodiscard]] _LIBCPP_INLINE_VISIBILITY inline string +__determine_grouping(ptrdiff_t __size, const string& __grouping) { + _LIBCPP_ASSERT(!__grouping.empty() && __size > __grouping[0], + "The slow grouping formatting is used while there will be no " + "separators written"); + string __r; + auto __end = __grouping.end() - 1; + auto __ptr = __grouping.begin(); + + while (true) { + __size -= *__ptr; + if (__size > 0) + __r.push_back(*__ptr); + else { + // __size <= 0 so the value pushed will be <= *__ptr. + __r.push_back(*__ptr + __size); + return __r; + } + + // Proceed to the next group. + if (__ptr != __end) { + do { + ++__ptr; + // Skip grouping with a width of 0. + } while (*__ptr == 0 && __ptr != __end); + } + } + + _LIBCPP_UNREACHABLE(); +} +#endif + +template +class _LIBCPP_TEMPLATE_VIS __formatter_integral : public _Parser { +public: + using _CharT = typename _Parser::char_type; + + template + _LIBCPP_INLINE_VISIBILITY auto format(_Tp __value, auto& __ctx) + -> decltype(__ctx.out()) { + if (this->__width_needs_substitution()) + this->__substitute_width_arg_id(__ctx.arg(this->__width)); + + if (this->__type == _Flags::_Type::__char) + return __format_char(__value, __ctx); + + if constexpr (unsigned_integral<_Tp>) + return __format_unsigned_integral(__value, __ctx); + else { + // Depending on the std-format-spec string the sign and the value + // might not be outputted together: + // - alternate form may insert a prefix string. + // - zero-padding may insert additional '0' characters. + // Therefore the value is processed as a positive unsigned value. + // The function @ref __insert_sign will a '-' when the value was negative. + auto __r = __to_unsigned_like(__value); + if (__value < 0) { + __negative = true; + __r = __complement(__r); + } + return __format_unsigned_integral(__r, __ctx); + } + } + +private: + /** Generic formatting for format-type c. */ + [[nodiscard]] _LIBCPP_INLINE_VISIBILITY auto + __format_char(integral auto __value, auto& __ctx) -> decltype(__ctx.out()) { + if (this->__alignment == _Flags::_Alignment::__default) + this->__alignment = _Flags::_Alignment::__right; + + using _Tp = decltype(__value); + if constexpr (!same_as<_CharT, _Tp>) { + // cmp_less and cmp_greater can't be used for character types. + if constexpr (signed_integral<_CharT> == signed_integral<_Tp>) { + if (__value < numeric_limits<_CharT>::min() || + __value > numeric_limits<_CharT>::max()) + __throw_format_error( + "Integral value outside the range of the char type"); + } else if constexpr (signed_integral<_CharT>) { + // _CharT is signed _Tp is unsigned + if (__value > + static_cast>(numeric_limits<_CharT>::max())) + __throw_format_error( + "Integral value outside the range of the char type"); + } else { + // _CharT is unsigned _Tp is signed + if (__value < 0 || static_cast>(__value) > + numeric_limits<_CharT>::max()) + __throw_format_error( + "Integral value outside the range of the char type"); + } + } + + const auto __c = static_cast<_CharT>(__value); + return __write(_VSTD::addressof(__c), _VSTD::addressof(__c) + 1, + __ctx.out()); + } + + /** + * Generic formatting for format-type bBdoxX. + * + * This small wrapper allocates a buffer with the required size. Then calls + * the real formatter with the buffer and the prefix for the base. + */ + [[nodiscard]] _LIBCPP_INLINE_VISIBILITY auto + __format_unsigned_integral(unsigned_integral auto __value, auto& __ctx) + -> decltype(__ctx.out()) { + switch (this->__type) { + case _Flags::_Type::__binary_lower_case: { + array()> __array; + return __format_unsigned_integral(__array.begin(), __array.end(), __value, + 2, __ctx, "0b"); + } + case _Flags::_Type::__binary_upper_case: { + array()> __array; + return __format_unsigned_integral(__array.begin(), __array.end(), __value, + 2, __ctx, "0B"); + } + case _Flags::_Type::__octal: { + // Octal is special; if __value == 0 there's no prefix. + array()> __array; + if (__value == 0) + return __format_unsigned_integral(__array.begin(), __array.end(), + __value, 8, __ctx, nullptr); + else + return __format_unsigned_integral(__array.begin(), __array.end(), + __value, 8, __ctx, "0"); + } + case _Flags::_Type::__decimal: { + array()> __array; + return __format_unsigned_integral(__array.begin(), __array.end(), __value, + 10, __ctx, nullptr); + } + case _Flags::_Type::__hexadecimal_lower_case: { + array()> __array; + return __format_unsigned_integral(__array.begin(), __array.end(), __value, + 16, __ctx, "0x"); + } + case _Flags::_Type::__hexadecimal_upper_case: { + array()> __array; + return __format_unsigned_integral(__array.begin(), __array.end(), __value, + 16, __ctx, "0X"); + } + default: + _LIBCPP_ASSERT(false, "The parser should have validated the type"); + _LIBCPP_UNREACHABLE(); + } + } + + template + requires(same_as || same_as) + [[nodiscard]] _LIBCPP_INLINE_VISIBILITY + auto __write(const _Tp* __first, const _Tp* __last, auto __out_it) + -> decltype(__out_it) { + + unsigned __size = __last - __first; + if (this->__type != _Flags::_Type::__hexadecimal_upper_case) [[likely]] { + if (__size >= this->__width) + return _VSTD::copy(__first, __last, _VSTD::move(__out_it)); + + return __formatter::__write(_VSTD::move(__out_it), __first, __last, + __size, this->__width, this->__fill, + this->__alignment); + } + + // this->__type == _Flags::_Type::__hexadecimal_upper_case + // This means all characters in the range [a-f] need to be changed to their + // uppercase representation. The transformation is done as transformation + // in the output routine instead of before. This avoids another pass over + // the data. + if (__size >= this->__width) + return _VSTD::transform(__first, __last, _VSTD::move(__out_it), + __hex_to_upper); + + return __formatter::__write(_VSTD::move(__out_it), __first, __last, __size, + __hex_to_upper, this->__width, this->__fill, + this->__alignment); + } + + [[nodiscard]] _LIBCPP_INLINE_VISIBILITY auto __format_unsigned_integral( + char* __begin, char* __end, unsigned_integral auto __value, int __base, + auto& __ctx, const char* __prefix) -> decltype(__ctx.out()) { + char* __first = __insert_sign(__begin, __negative, this->__sign); + if (this->__alternate_form && __prefix) + while (*__prefix) + *__first++ = *__prefix++; + + char* __last = __to_buffer(__first, __end, __value, __base); +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + if (this->__locale_specific_form) { + const auto& __np = use_facet>(__ctx.locale()); + string __grouping = __np.grouping(); + ptrdiff_t __size = __last - __first; + // Writing the grouped form has more overhead than the normal output + // routines. If there will be no separators written the locale-specific + // form is identical to the normal routine. Test whether to grouped form + // is required. + if (!__grouping.empty() && __size > __grouping[0]) + return __format_grouping(__ctx.out(), __begin, __first, __last, + __determine_grouping(__size, __grouping), + __np.thousands_sep()); + } +#endif + auto __out_it = __ctx.out(); + if (this->__alignment != _Flags::_Alignment::__default) + __first = __begin; + else { + // __buf contains [sign][prefix]data + // ^ location of __first + // The zero padding is done like: + // - Write [sign][prefix] + // - Write data right aligned with '0' as fill character. + __out_it = _VSTD::copy(__begin, __first, _VSTD::move(__out_it)); + this->__alignment = _Flags::_Alignment::__right; + this->__fill = _CharT('0'); + unsigned __size = __first - __begin; + this->__width -= _VSTD::min(__size, this->__width); + } + + return __write(__first, __last, _VSTD::move(__out_it)); + } + +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + /** Format's the locale-specific form's groupings. */ + template + [[nodiscard]] _LIBCPP_INLINE_VISIBILITY _OutIt + __format_grouping(_OutIt __out_it, const char* __begin, const char* __first, + const char* __last, string&& __grouping, _CharT __sep) { + + // TODO FMT This function duplicates some functionality of the normal + // output routines. Evaluate whether these parts can be efficiently + // combined with the existing routines. + + unsigned __size = (__first - __begin) + // [sign][prefix] + (__last - __first) + // data + (__grouping.size() - 1); // number of separator characters + + __formatter::__padding_size_result __padding = {0, 0}; + if (this->__alignment == _Flags::_Alignment::__default) { + // Write [sign][prefix]. + __out_it = _VSTD::copy(__begin, __first, _VSTD::move(__out_it)); + + if (this->__width > __size) { + // Write zero padding. + __padding.__before = this->__width - __size; + __out_it = _VSTD::fill_n(_VSTD::move(__out_it), this->__width - __size, + _CharT('0')); + } + } else { + if (this->__width > __size) { + // Determine padding and write padding. + __padding = __formatter::__padding_size(__size, this->__width, + this->__alignment); + + __out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before, + this->__fill); + } + // Write [sign][prefix]. + __out_it = _VSTD::copy(__begin, __first, _VSTD::move(__out_it)); + } + + auto __r = __grouping.rbegin(); + auto __e = __grouping.rend() - 1; + _LIBCPP_ASSERT(__r != __e, "The slow grouping formatting is used while " + "there will be no separators written."); + // The output is divided in small groups of numbers to write: + // - A group before the first separator. + // - A separator and a group, repeated for the number of separators. + // - A group after the last separator. + // This loop achieves that process by testing the termination condition + // midway in the loop. + // + // TODO FMT This loop evaluates the loop invariant `this->__type != + // _Flags::_Type::__hexadecimal_upper_case` for every iteration. (This test + // happens in the __write call.) Benchmark whether making two loops and + // hoisting the invariant is worth the effort. + while (true) { + if (this->__type == _Flags::_Type::__hexadecimal_upper_case) { + __last = __first + *__r; + __out_it = _VSTD::transform(__first, __last, _VSTD::move(__out_it), + __hex_to_upper); + __first = __last; + } else { + __out_it = _VSTD::copy_n(__first, *__r, _VSTD::move(__out_it)); + __first += *__r; + } + + if (__r == __e) + break; + + ++__r; + *__out_it++ = __sep; + } + + return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after, + this->__fill); + } +#endif + +private: + bool __negative{false}; +}; + +} // namespace __format_spec + +#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) && !defined(_LIBCPP_HAS_NO_BUILTIN_IS_CONSTANT_EVALUATED) + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___FORMAT_FORMATTER_INTEGRAL_H diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -279,9 +279,11 @@ #include <__format/format_args.h> #include <__format/format_context.h> #include <__format/format_error.h> +#include <__format/format_fwd.h> #include <__format/format_parse_context.h> #include <__format/format_string.h> #include <__format/formatter.h> +#include <__format/formatter_integer.h> #include <__format/formatter_string.h> #include <__format/parser_std_format_spec.h> #include <__variant/monostate.h> @@ -390,9 +392,6 @@ template _LIBCPP_HIDDEN auto __handle_format(_Uv __value, auto& __ctx) -> decltype(__ctx.out()) -#ifndef _LIBCPP_HAS_NO_INT128 - requires(!same_as<_Uv, __int128_t> && !same_as<_Uv, __uint128_t>) -#endif { // TODO FMT Implement using formatting arguments // TODO FMT Improve PoC since using std::to_string is inefficient. @@ -405,20 +404,6 @@ *__out_it++ = __str[__i]; return __out_it; } -#ifndef _LIBCPP_HAS_NO_INT128 - template - _LIBCPP_HIDDEN auto __handle_format(_Uv __value, auto& __ctx) - -> decltype(__ctx.out()) requires(same_as<_Uv, __int128_t> || - same_as<_Uv, __uint128_t>) { - using _To = conditional_t, long long, unsigned long long>; - // TODO FMT Implement full 128 bit support. - if (__value < numeric_limits<_To>::min() || - __value > numeric_limits<_To>::max()) - __throw_format_error("128 bit value is outside of implemented range"); - - return __handle_format(static_cast<_To>(__value), __ctx); - } -#endif }; } // namespace __format @@ -461,50 +446,6 @@ } }; -// Signed integral types. -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_arithmetic {}; -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_arithmetic {}; -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_arithmetic {}; -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_arithmetic {}; -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_arithmetic {}; -#ifndef _LIBCPP_HAS_NO_INT128 -template -struct _LIBCPP_TEMPLATE_VIS formatter<__int128_t, _CharT> - : public __format::__formatter_arithmetic<__int128_t, _CharT> {}; -#endif - -// Unsigned integral types. -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_arithmetic {}; -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_arithmetic {}; -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_arithmetic {}; -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_arithmetic {}; -template -struct _LIBCPP_TEMPLATE_VIS formatter - : public __format::__formatter_arithmetic {}; -#ifndef _LIBCPP_HAS_NO_INT128 -template -struct _LIBCPP_TEMPLATE_VIS formatter<__uint128_t, _CharT> - : public __format::__formatter_arithmetic<__uint128_t, _CharT> {}; -#endif - // Floating point types. template struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -403,6 +403,8 @@ module format_parse_context { header "__format/format_parse_context.h" } module format_string { header "__format/format_string.h" } module formatter { header "__format/formatter.h" } + module formatter_integer { header "__format/formatter_integer.h" } + module formatter_integral { header "__format/formatter_integral.h" } module formatter_string { header "__format/formatter_string.h" } module parser_std_format_spec { header "__format/parser_std_format_spec.h" } } diff --git a/libcxx/test/std/utilities/format/format.functions/locale-specific_form.pass.cpp b/libcxx/test/std/utilities/format/format.functions/locale-specific_form.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.functions/locale-specific_form.pass.cpp @@ -0,0 +1,567 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: libcpp-has-no-localization + +// REQUIRES: locale.en_US.UTF-8 + +// + +// This test the locale-specific form for these formatting functions: +// +// // [format.functions], formatting functions +// template +// string format(string_view fmt, const Args&... args); +// template +// wstring format(wstring_view fmt, const Args&... args); +// template +// string format(const locale& loc, string_view fmt, const Args&... args); +// template +// wstring format(const locale& loc, wstring_view fmt, const Args&... args); +// +// string vformat(string_view fmt, format_args args); +// wstring vformat(wstring_view fmt, wformat_args args); +// string vformat(const locale& loc, string_view fmt, format_args args); +// wstring vformat(const locale& loc, wstring_view fmt, wformat_args args); +// +// template +// Out format_to(Out out, string_view fmt, const Args&... args); +// template +// Out format_to(Out out, wstring_view fmt, const Args&... args); +// template +// Out format_to(Out out, const locale& loc, string_view fmt, const Args&... args); +// template +// Out format_to(Out out, const locale& loc, wstring_view fmt, const Args&... args); +// +// template +// Out vformat_to(Out out, string_view fmt, +// format_args_t, char> args); +// template +// Out vformat_to(Out out, wstring_view fmt, +// format_args_t, wchar_t> args); +// template +// Out vformat_to(Out out, const locale& loc, string_view fmt, +// format_args_t, char> args); +// template +// Out vformat_to(Out out, const locale& loc, wstring_view fmt, +// format_args_t, wchar_t> args); +// +// template struct format_to_n_result { +// Out out; +// iter_difference_t size; +// }; +// +// template +// format_to_n_result format_to_n(Out out, iter_difference_t n, +// string_view fmt, const Args&... args); +// template +// format_to_n_result format_to_n(Out out, iter_difference_t n, +// wstring_view fmt, const Args&... args); +// template +// format_to_n_result format_to_n(Out out, iter_difference_t n, +// const locale& loc, string_view fmt, +// const Args&... args); +// template +// format_to_n_result format_to_n(Out out, iter_difference_t n, +// const locale& loc, wstring_view fmt, +// const Args&... args); +// +// template +// size_t formatted_size(string_view fmt, const Args&... args); +// template +// size_t formatted_size(wstring_view fmt, const Args&... args); +// template +// size_t formatted_size(const locale& loc, string_view fmt, const Args&... args); +// template +// size_t formatted_size(const locale& loc, wstring_view fmt, const Args&... args); + +#include +#include +#include +#include + +#include "test_macros.h" +#include "make_string.h" +#include "platform_support.h" // locale name macros + +#define STR(S) MAKE_STRING(CharT, S) + +template +struct numpunct; + +template <> +struct numpunct : std::numpunct { + string_type do_truename() const override { return "yes"; } + string_type do_falsename() const override { return "no"; } + + std::string do_grouping() const override { return "\1\2\3\2\1"; }; + char do_thousands_sep() const override { return '_'; } +}; + +template <> +struct numpunct : std::numpunct { + string_type do_truename() const override { return L"yes"; } + string_type do_falsename() const override { return L"no"; } + + std::string do_grouping() const override { return "\1\2\3\2\1"; }; + wchar_t do_thousands_sep() const override { return L'_'; } +}; + +template +void test(std::basic_string expected, std::basic_string fmt, + const Args&... args) { + // *** format *** + { + std::basic_string out = std::format(fmt, args...); + if constexpr (std::same_as) + if (out != expected) + std::cerr << "\nFormat string " << fmt << "\nExpected output " + << expected << "\nActual output " << out << '\n'; + assert(out == expected); + } + // *** vformat *** + { + std::basic_string out = std::vformat( + fmt, std::make_format_args>, CharT>>( + args...)); + assert(out == expected); + } + // *** format_to *** + { + std::basic_string out(expected.size(), CharT(' ')); + auto it = std::format_to(out.begin(), fmt, args...); + assert(it == out.end()); + assert(out == expected); + } + // *** vformat_to *** + { + std::basic_string out(expected.size(), CharT(' ')); + auto it = std::vformat_to( + out.begin(), fmt, + std::make_format_args::iterator, CharT>>(args...)); + assert(it == out.end()); + assert(out == expected); + } + // *** format_to_n *** + { + std::basic_string out; + std::format_to_n_result result = + std::format_to_n(std::back_inserter(out), 1000, fmt, args...); + using diff_type = decltype(result.size); + diff_type formatted_size = std::formatted_size(fmt, args...); + diff_type size = std::min(1000, formatted_size); + + assert(result.size == formatted_size); + assert(out == expected.substr(0, size)); + } + // *** formatted_size *** + { + size_t size = std::formatted_size(fmt, args...); + assert(size == expected.size()); + } +} + +template +void test(std::basic_string expected, std::locale loc, + std::basic_string fmt, const Args&... args) { + // *** format *** + { + std::basic_string out = std::format(loc, fmt, args...); + if constexpr (std::same_as) + if (out != expected) + std::cerr << "\nFormat string " << fmt << "\nExpected output " + << expected << "\nActual output " << out << '\n'; + assert(out == expected); + } + // *** vformat *** + { + std::basic_string out = std::vformat( + loc, fmt, + std::make_format_args>, CharT>>( + args...)); + assert(out == expected); + } + // *** format_to *** + { + std::basic_string out(expected.size(), CharT(' ')); + auto it = std::format_to(out.begin(), loc, fmt, args...); + assert(it == out.end()); + assert(out == expected); + } + // *** vformat_to *** + { + std::basic_string out(expected.size(), CharT(' ')); + auto it = std::vformat_to( + out.begin(), loc, fmt, + std::make_format_args::iterator, CharT>>(args...)); + assert(it == out.end()); + assert(out == expected); + } + // *** format_to_n *** + { + std::basic_string out; + std::format_to_n_result result = + std::format_to_n(std::back_inserter(out), 1000, loc, fmt, args...); + using diff_type = decltype(result.size); + diff_type formatted_size = std::formatted_size(loc, fmt, args...); + diff_type size = std::min(1000, formatted_size); + + assert(result.size == formatted_size); + assert(out == expected.substr(0, size)); + } + // *** formatted_size *** + { + size_t size = std::formatted_size(loc, fmt, args...); + assert(size == expected.size()); + } +} + +template +void test_integer() { + std::locale loc = std::locale(std::locale(), new numpunct()); + std::locale en_US = std::locale(LOCALE_en_US_UTF_8); + + // *** Decimal *** + std::locale::global(en_US); + test(STR("0"), STR("{:L}"), 0); + test(STR("1"), STR("{:L}"), 1); + test(STR("10"), STR("{:L}"), 10); + test(STR("100"), STR("{:L}"), 100); + test(STR("1,000"), STR("{:L}"), 1'000); + test(STR("10,000"), STR("{:L}"), 10'000); + test(STR("100,000"), STR("{:L}"), 100'000); + test(STR("1,000,000"), STR("{:L}"), 1'000'000); + test(STR("10,000,000"), STR("{:L}"), 10'000'000); + test(STR("100,000,000"), STR("{:L}"), 100'000'000); + test(STR("1,000,000,000"), STR("{:L}"), 1'000'000'000); + + test(STR("-1"), STR("{:L}"), -1); + test(STR("-10"), STR("{:L}"), -10); + test(STR("-100"), STR("{:L}"), -100); + test(STR("-1,000"), STR("{:L}"), -1'000); + test(STR("-10,000"), STR("{:L}"), -10'000); + test(STR("-100,000"), STR("{:L}"), -100'000); + test(STR("-1,000,000"), STR("{:L}"), -1'000'000); + test(STR("-10,000,000"), STR("{:L}"), -10'000'000); + test(STR("-100,000,000"), STR("{:L}"), -100'000'000); + test(STR("-1,000,000,000"), STR("{:L}"), -1'000'000'000); + + std::locale::global(loc); + test(STR("0"), STR("{:L}"), 0); + test(STR("1"), STR("{:L}"), 1); + test(STR("1_0"), STR("{:L}"), 10); + test(STR("10_0"), STR("{:L}"), 100); + test(STR("1_00_0"), STR("{:L}"), 1'000); + test(STR("10_00_0"), STR("{:L}"), 10'000); + test(STR("100_00_0"), STR("{:L}"), 100'000); + test(STR("1_000_00_0"), STR("{:L}"), 1'000'000); + test(STR("10_000_00_0"), STR("{:L}"), 10'000'000); + test(STR("1_00_000_00_0"), STR("{:L}"), 100'000'000); + test(STR("1_0_00_000_00_0"), STR("{:L}"), 1'000'000'000); + + test(STR("-1"), STR("{:L}"), -1); + test(STR("-1_0"), STR("{:L}"), -10); + test(STR("-10_0"), STR("{:L}"), -100); + test(STR("-1_00_0"), STR("{:L}"), -1'000); + test(STR("-10_00_0"), STR("{:L}"), -10'000); + test(STR("-100_00_0"), STR("{:L}"), -100'000); + test(STR("-1_000_00_0"), STR("{:L}"), -1'000'000); + test(STR("-10_000_00_0"), STR("{:L}"), -10'000'000); + test(STR("-1_00_000_00_0"), STR("{:L}"), -100'000'000); + test(STR("-1_0_00_000_00_0"), STR("{:L}"), -1'000'000'000); + + test(STR("0"), en_US, STR("{:L}"), 0); + test(STR("1"), en_US, STR("{:L}"), 1); + test(STR("10"), en_US, STR("{:L}"), 10); + test(STR("100"), en_US, STR("{:L}"), 100); + test(STR("1,000"), en_US, STR("{:L}"), 1'000); + test(STR("10,000"), en_US, STR("{:L}"), 10'000); + test(STR("100,000"), en_US, STR("{:L}"), 100'000); + test(STR("1,000,000"), en_US, STR("{:L}"), 1'000'000); + test(STR("10,000,000"), en_US, STR("{:L}"), 10'000'000); + test(STR("100,000,000"), en_US, STR("{:L}"), 100'000'000); + test(STR("1,000,000,000"), en_US, STR("{:L}"), 1'000'000'000); + + test(STR("-1"), en_US, STR("{:L}"), -1); + test(STR("-10"), en_US, STR("{:L}"), -10); + test(STR("-100"), en_US, STR("{:L}"), -100); + test(STR("-1,000"), en_US, STR("{:L}"), -1'000); + test(STR("-10,000"), en_US, STR("{:L}"), -10'000); + test(STR("-100,000"), en_US, STR("{:L}"), -100'000); + test(STR("-1,000,000"), en_US, STR("{:L}"), -1'000'000); + test(STR("-10,000,000"), en_US, STR("{:L}"), -10'000'000); + test(STR("-100,000,000"), en_US, STR("{:L}"), -100'000'000); + test(STR("-1,000,000,000"), en_US, STR("{:L}"), -1'000'000'000); + + std::locale::global(en_US); + test(STR("0"), loc, STR("{:L}"), 0); + test(STR("1"), loc, STR("{:L}"), 1); + test(STR("1_0"), loc, STR("{:L}"), 10); + test(STR("10_0"), loc, STR("{:L}"), 100); + test(STR("1_00_0"), loc, STR("{:L}"), 1'000); + test(STR("10_00_0"), loc, STR("{:L}"), 10'000); + test(STR("100_00_0"), loc, STR("{:L}"), 100'000); + test(STR("1_000_00_0"), loc, STR("{:L}"), 1'000'000); + test(STR("10_000_00_0"), loc, STR("{:L}"), 10'000'000); + test(STR("1_00_000_00_0"), loc, STR("{:L}"), 100'000'000); + test(STR("1_0_00_000_00_0"), loc, STR("{:L}"), 1'000'000'000); + + test(STR("-1"), loc, STR("{:L}"), -1); + test(STR("-1_0"), loc, STR("{:L}"), -10); + test(STR("-10_0"), loc, STR("{:L}"), -100); + test(STR("-1_00_0"), loc, STR("{:L}"), -1'000); + test(STR("-10_00_0"), loc, STR("{:L}"), -10'000); + test(STR("-100_00_0"), loc, STR("{:L}"), -100'000); + test(STR("-1_000_00_0"), loc, STR("{:L}"), -1'000'000); + test(STR("-10_000_00_0"), loc, STR("{:L}"), -10'000'000); + test(STR("-1_00_000_00_0"), loc, STR("{:L}"), -100'000'000); + test(STR("-1_0_00_000_00_0"), loc, STR("{:L}"), -1'000'000'000); + + // *** Binary *** + std::locale::global(en_US); + test(STR("0"), STR("{:Lb}"), 0b0); + test(STR("1"), STR("{:Lb}"), 0b1); + test(STR("1,000,000,000"), STR("{:Lb}"), 0b1'000'000'000); + + test(STR("0b0"), STR("{:#Lb}"), 0b0); + test(STR("0b1"), STR("{:#Lb}"), 0b1); + test(STR("0b1,000,000,000"), STR("{:#Lb}"), 0b1'000'000'000); + + test(STR("-1"), STR("{:LB}"), -0b1); + test(STR("-1,000,000,000"), STR("{:LB}"), -0b1'000'000'000); + + test(STR("-0B1"), STR("{:#LB}"), -0b1); + test(STR("-0B1,000,000,000"), STR("{:#LB}"), -0b1'000'000'000); + + std::locale::global(loc); + test(STR("0"), STR("{:Lb}"), 0b0); + test(STR("1"), STR("{:Lb}"), 0b1); + test(STR("1_0_00_000_00_0"), STR("{:Lb}"), 0b1'000'000'000); + + test(STR("0b0"), STR("{:#Lb}"), 0b0); + test(STR("0b1"), STR("{:#Lb}"), 0b1); + test(STR("0b1_0_00_000_00_0"), STR("{:#Lb}"), 0b1'000'000'000); + + test(STR("-1"), STR("{:LB}"), -0b1); + test(STR("-1_0_00_000_00_0"), STR("{:LB}"), -0b1'000'000'000); + + test(STR("-0B1"), STR("{:#LB}"), -0b1); + test(STR("-0B1_0_00_000_00_0"), STR("{:#LB}"), -0b1'000'000'000); + + test(STR("0"), en_US, STR("{:Lb}"), 0b0); + test(STR("1"), en_US, STR("{:Lb}"), 0b1); + test(STR("1,000,000,000"), en_US, STR("{:Lb}"), 0b1'000'000'000); + + test(STR("0b0"), en_US, STR("{:#Lb}"), 0b0); + test(STR("0b1"), en_US, STR("{:#Lb}"), 0b1); + test(STR("0b1,000,000,000"), en_US, STR("{:#Lb}"), 0b1'000'000'000); + + test(STR("-1"), en_US, STR("{:LB}"), -0b1); + test(STR("-1,000,000,000"), en_US, STR("{:LB}"), -0b1'000'000'000); + + test(STR("-0B1"), en_US, STR("{:#LB}"), -0b1); + test(STR("-0B1,000,000,000"), en_US, STR("{:#LB}"), -0b1'000'000'000); + + std::locale::global(en_US); + test(STR("0"), loc, STR("{:Lb}"), 0b0); + test(STR("1"), loc, STR("{:Lb}"), 0b1); + test(STR("1_0_00_000_00_0"), loc, STR("{:Lb}"), 0b1'000'000'000); + + test(STR("0b0"), loc, STR("{:#Lb}"), 0b0); + test(STR("0b1"), loc, STR("{:#Lb}"), 0b1); + test(STR("0b1_0_00_000_00_0"), loc, STR("{:#Lb}"), 0b1'000'000'000); + + test(STR("-1"), loc, STR("{:LB}"), -0b1); + test(STR("-1_0_00_000_00_0"), loc, STR("{:LB}"), -0b1'000'000'000); + + test(STR("-0B1"), loc, STR("{:#LB}"), -0b1); + test(STR("-0B1_0_00_000_00_0"), loc, STR("{:#LB}"), -0b1'000'000'000); + + // *** Octal *** + std::locale::global(en_US); + test(STR("0"), STR("{:Lo}"), 00); + test(STR("1"), STR("{:Lo}"), 01); + test(STR("1,000,000,000"), STR("{:Lo}"), 01'000'000'000); + + test(STR("0"), STR("{:#Lo}"), 00); + test(STR("01"), STR("{:#Lo}"), 01); + test(STR("01,000,000,000"), STR("{:#Lo}"), 01'000'000'000); + + test(STR("-1"), STR("{:Lo}"), -01); + test(STR("-1,000,000,000"), STR("{:Lo}"), -01'000'000'000); + + test(STR("-01"), STR("{:#Lo}"), -01); + test(STR("-01,000,000,000"), STR("{:#Lo}"), -01'000'000'000); + + std::locale::global(loc); + test(STR("0"), STR("{:Lo}"), 00); + test(STR("1"), STR("{:Lo}"), 01); + test(STR("1_0_00_000_00_0"), STR("{:Lo}"), 01'000'000'000); + + test(STR("0"), STR("{:#Lo}"), 00); + test(STR("01"), STR("{:#Lo}"), 01); + test(STR("01_0_00_000_00_0"), STR("{:#Lo}"), 01'000'000'000); + + test(STR("-1"), STR("{:Lo}"), -01); + test(STR("-1_0_00_000_00_0"), STR("{:Lo}"), -01'000'000'000); + + test(STR("-01"), STR("{:#Lo}"), -01); + test(STR("-01_0_00_000_00_0"), STR("{:#Lo}"), -01'000'000'000); + + test(STR("0"), en_US, STR("{:Lo}"), 00); + test(STR("1"), en_US, STR("{:Lo}"), 01); + test(STR("1,000,000,000"), en_US, STR("{:Lo}"), 01'000'000'000); + + test(STR("0"), en_US, STR("{:#Lo}"), 00); + test(STR("01"), en_US, STR("{:#Lo}"), 01); + test(STR("01,000,000,000"), en_US, STR("{:#Lo}"), 01'000'000'000); + + test(STR("-1"), en_US, STR("{:Lo}"), -01); + test(STR("-1,000,000,000"), en_US, STR("{:Lo}"), -01'000'000'000); + + test(STR("-01"), en_US, STR("{:#Lo}"), -01); + test(STR("-01,000,000,000"), en_US, STR("{:#Lo}"), -01'000'000'000); + + std::locale::global(en_US); + test(STR("0"), loc, STR("{:Lo}"), 00); + test(STR("1"), loc, STR("{:Lo}"), 01); + test(STR("1_0_00_000_00_0"), loc, STR("{:Lo}"), 01'000'000'000); + + test(STR("0"), loc, STR("{:#Lo}"), 00); + test(STR("01"), loc, STR("{:#Lo}"), 01); + test(STR("01_0_00_000_00_0"), loc, STR("{:#Lo}"), 01'000'000'000); + + test(STR("-1"), loc, STR("{:Lo}"), -01); + test(STR("-1_0_00_000_00_0"), loc, STR("{:Lo}"), -01'000'000'000); + + test(STR("-01"), loc, STR("{:#Lo}"), -01); + test(STR("-01_0_00_000_00_0"), loc, STR("{:#Lo}"), -01'000'000'000); + + // *** Hexadecimal *** + std::locale::global(en_US); + test(STR("0"), STR("{:Lx}"), 0x0); + test(STR("1"), STR("{:Lx}"), 0x1); + test(STR("1,000,000,000"), STR("{:Lx}"), 0x1'000'000'000); + + test(STR("0x0"), STR("{:#Lx}"), 0x0); + test(STR("0x1"), STR("{:#Lx}"), 0x1); + test(STR("0x1,000,000,000"), STR("{:#Lx}"), 0x1'000'000'000); + + test(STR("-1"), STR("{:LX}"), -0x1); + test(STR("-1,000,000,000"), STR("{:LX}"), -0x1'000'000'000); + + test(STR("-0X1"), STR("{:#LX}"), -0x1); + test(STR("-0X1,000,000,000"), STR("{:#LX}"), -0x1'000'000'000); + + std::locale::global(loc); + test(STR("0"), STR("{:Lx}"), 0x0); + test(STR("1"), STR("{:Lx}"), 0x1); + test(STR("1_0_00_000_00_0"), STR("{:Lx}"), 0x1'000'000'000); + + test(STR("0x0"), STR("{:#Lx}"), 0x0); + test(STR("0x1"), STR("{:#Lx}"), 0x1); + test(STR("0x1_0_00_000_00_0"), STR("{:#Lx}"), 0x1'000'000'000); + + test(STR("-1"), STR("{:LX}"), -0x1); + test(STR("-1_0_00_000_00_0"), STR("{:LX}"), -0x1'000'000'000); + + test(STR("-0X1"), STR("{:#LX}"), -0x1); + test(STR("-0X1_0_00_000_00_0"), STR("{:#LX}"), -0x1'000'000'000); + + test(STR("0"), en_US, STR("{:Lx}"), 0x0); + test(STR("1"), en_US, STR("{:Lx}"), 0x1); + test(STR("1,000,000,000"), en_US, STR("{:Lx}"), 0x1'000'000'000); + + test(STR("0x0"), en_US, STR("{:#Lx}"), 0x0); + test(STR("0x1"), en_US, STR("{:#Lx}"), 0x1); + test(STR("0x1,000,000,000"), en_US, STR("{:#Lx}"), 0x1'000'000'000); + + test(STR("-1"), en_US, STR("{:LX}"), -0x1); + test(STR("-1,000,000,000"), en_US, STR("{:LX}"), -0x1'000'000'000); + + test(STR("-0X1"), en_US, STR("{:#LX}"), -0x1); + test(STR("-0X1,000,000,000"), en_US, STR("{:#LX}"), -0x1'000'000'000); + + std::locale::global(en_US); + test(STR("0"), loc, STR("{:Lx}"), 0x0); + test(STR("1"), loc, STR("{:Lx}"), 0x1); + test(STR("1_0_00_000_00_0"), loc, STR("{:Lx}"), 0x1'000'000'000); + + test(STR("0x0"), loc, STR("{:#Lx}"), 0x0); + test(STR("0x1"), loc, STR("{:#Lx}"), 0x1); + test(STR("0x1_0_00_000_00_0"), loc, STR("{:#Lx}"), 0x1'000'000'000); + + test(STR("-1"), loc, STR("{:LX}"), -0x1); + test(STR("-1_0_00_000_00_0"), loc, STR("{:LX}"), -0x1'000'000'000); + + test(STR("-0X1"), loc, STR("{:#LX}"), -0x1); + test(STR("-0X1_0_00_000_00_0"), loc, STR("{:#LX}"), -0x1'000'000'000); + + // *** align-fill & width *** + test(STR("4_2"), loc, STR("{:L}"), 42); + + test(STR(" 4_2"), loc, STR("{:6L}"), 42); + test(STR("4_2 "), loc, STR("{:<6L}"), 42); + test(STR(" 4_2 "), loc, STR("{:^6L}"), 42); + test(STR(" 4_2"), loc, STR("{:>6L}"), 42); + + test(STR("4_2***"), loc, STR("{:*<6L}"), 42); + test(STR("*4_2**"), loc, STR("{:*^6L}"), 42); + test(STR("***4_2"), loc, STR("{:*>6L}"), 42); + + test(STR("4_a*****"), loc, STR("{:*<8Lx}"), 0x4a); + test(STR("**4_a***"), loc, STR("{:*^8Lx}"), 0x4a); + test(STR("*****4_a"), loc, STR("{:*>8Lx}"), 0x4a); + + test(STR("0x4_a***"), loc, STR("{:*<#8Lx}"), 0x4a); + test(STR("*0x4_a**"), loc, STR("{:*^#8Lx}"), 0x4a); + test(STR("***0x4_a"), loc, STR("{:*>#8Lx}"), 0x4a); + + test(STR("4_A*****"), loc, STR("{:*<8LX}"), 0x4a); + test(STR("**4_A***"), loc, STR("{:*^8LX}"), 0x4a); + test(STR("*****4_A"), loc, STR("{:*>8LX}"), 0x4a); + + test(STR("0X4_A***"), loc, STR("{:*<#8LX}"), 0x4a); + test(STR("*0X4_A**"), loc, STR("{:*^#8LX}"), 0x4a); + test(STR("***0X4_A"), loc, STR("{:*>#8LX}"), 0x4a); + + // Test whether zero padding is ignored + test(STR("4_2 "), loc, STR("{:<06L}"), 42); + test(STR(" 4_2 "), loc, STR("{:^06L}"), 42); + test(STR(" 4_2"), loc, STR("{:>06L}"), 42); + + // *** zero-padding & width *** + test(STR(" 4_2"), loc, STR("{:6L}"), 42); + test(STR("0004_2"), loc, STR("{:06L}"), 42); + test(STR("-004_2"), loc, STR("{:06L}"), -42); + + test(STR("000004_a"), loc, STR("{:08Lx}"), 0x4a); + test(STR("0x0004_a"), loc, STR("{:#08Lx}"), 0x4a); + test(STR("0X0004_A"), loc, STR("{:#08LX}"), 0x4a); + + test(STR("-00004_a"), loc, STR("{:08Lx}"), -0x4a); + test(STR("-0x004_a"), loc, STR("{:#08Lx}"), -0x4a); + test(STR("-0X004_A"), loc, STR("{:#08LX}"), -0x4a); +} + +template +void test() { + test_integer(); +} + +int main(int, char**) { + test(); + test(); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.functions/tests.inc b/libcxx/test/std/utilities/format/format.functions/tests.inc --- a/libcxx/test/std/utilities/format/format.functions/tests.inc +++ b/libcxx/test/std/utilities/format/format.functions/tests.inc @@ -5,7 +5,9 @@ // //===----------------------------------------------------------------------===// -// Contains the test cases for the format functions without locale. +// Contains the test cases for the format functions. The locale-specific form +// aren't tested in these test cases. They're tested in +// locale-specific_form.pass.cpp. #define STR(S) MAKE_STRING(CharT, S) @@ -247,6 +249,330 @@ test_string_unicode(); } +template +void test_integer_as_integer() { + // *** align-fill & width *** + test(STR("answer is '42'"), STR("answer is '{:<1}'"), I(42)); + test(STR("answer is '42'"), STR("answer is '{:<2}'"), I(42)); + test(STR("answer is '42 '"), STR("answer is '{:<3}'"), I(42)); + + test(STR("answer is ' 42'"), STR("answer is '{:7}'"), I(42)); + test(STR("answer is ' 42'"), STR("answer is '{:>7}'"), I(42)); + test(STR("answer is '42 '"), STR("answer is '{:<7}'"), I(42)); + test(STR("answer is ' 42 '"), STR("answer is '{:^7}'"), I(42)); + + test(STR("answer is '*****42'"), STR("answer is '{:*>7}'"), I(42)); + test(STR("answer is '42*****'"), STR("answer is '{:*<7}'"), I(42)); + test(STR("answer is '**42***'"), STR("answer is '{:*^7}'"), I(42)); + + // Test whether zero padding is ignored + test(STR("answer is ' 42'"), STR("answer is '{:>07}'"), I(42)); + test(STR("answer is '42 '"), STR("answer is '{:<07}'"), I(42)); + test(STR("answer is ' 42 '"), STR("answer is '{:^07}'"), I(42)); + + // *** Sign *** + if constexpr (std::signed_integral) + test(STR("answer is -42"), STR("answer is {}"), I(-42)); + test(STR("answer is 0"), STR("answer is {}"), I(0)); + test(STR("answer is 42"), STR("answer is {}"), I(42)); + + if constexpr (std::signed_integral) + test(STR("answer is -42"), STR("answer is {:-}"), I(-42)); + test(STR("answer is 0"), STR("answer is {:-}"), I(0)); + test(STR("answer is 42"), STR("answer is {:-}"), I(42)); + + if constexpr (std::signed_integral) + test(STR("answer is -42"), STR("answer is {:+}"), I(-42)); + test(STR("answer is +0"), STR("answer is {:+}"), I(0)); + test(STR("answer is +42"), STR("answer is {:+}"), I(42)); + + if constexpr (std::signed_integral) + test(STR("answer is -42"), STR("answer is {: }"), I(-42)); + test(STR("answer is 0"), STR("answer is {: }"), I(0)); + test(STR("answer is 42"), STR("answer is {: }"), I(42)); + + // *** alternate form *** + if constexpr (std::signed_integral) { + test(STR("answer is -42"), STR("answer is {:#}"), I(-42)); + test(STR("answer is -42"), STR("answer is {:#d}"), I(-42)); + test(STR("answer is -101010"), STR("answer is {:b}"), I(-42)); + test(STR("answer is -0b101010"), STR("answer is {:#b}"), I(-42)); + test(STR("answer is -0B101010"), STR("answer is {:#B}"), I(-42)); + test(STR("answer is -52"), STR("answer is {:o}"), I(-42)); + test(STR("answer is -052"), STR("answer is {:#o}"), I(-42)); + test(STR("answer is -2a"), STR("answer is {:x}"), I(-42)); + test(STR("answer is -0x2a"), STR("answer is {:#x}"), I(-42)); + test(STR("answer is -2A"), STR("answer is {:X}"), I(-42)); + test(STR("answer is -0X2A"), STR("answer is {:#X}"), I(-42)); + } + test(STR("answer is 0"), STR("answer is {:#}"), I(0)); + test(STR("answer is 0"), STR("answer is {:#d}"), I(0)); + test(STR("answer is 0"), STR("answer is {:b}"), I(0)); + test(STR("answer is 0b0"), STR("answer is {:#b}"), I(0)); + test(STR("answer is 0B0"), STR("answer is {:#B}"), I(0)); + test(STR("answer is 0"), STR("answer is {:o}"), I(0)); + test(STR("answer is 0"), STR("answer is {:#o}"), I(0)); + test(STR("answer is 0"), STR("answer is {:x}"), I(0)); + test(STR("answer is 0x0"), STR("answer is {:#x}"), I(0)); + test(STR("answer is 0"), STR("answer is {:X}"), I(0)); + test(STR("answer is 0X0"), STR("answer is {:#X}"), I(0)); + + test(STR("answer is +42"), STR("answer is {:+#}"), I(42)); + test(STR("answer is +42"), STR("answer is {:+#d}"), I(42)); + test(STR("answer is +101010"), STR("answer is {:+b}"), I(42)); + test(STR("answer is +0b101010"), STR("answer is {:+#b}"), I(42)); + test(STR("answer is +0B101010"), STR("answer is {:+#B}"), I(42)); + test(STR("answer is +52"), STR("answer is {:+o}"), I(42)); + test(STR("answer is +052"), STR("answer is {:+#o}"), I(42)); + test(STR("answer is +2a"), STR("answer is {:+x}"), I(42)); + test(STR("answer is +0x2a"), STR("answer is {:+#x}"), I(42)); + test(STR("answer is +2A"), STR("answer is {:+X}"), I(42)); + test(STR("answer is +0X2A"), STR("answer is {:+#X}"), I(42)); + + // *** zero-padding & width *** + if constexpr (std::signed_integral) { + test(STR("answer is -00000000042"), STR("answer is {:#012}"), I(-42)); + test(STR("answer is -00000000042"), STR("answer is {:#012d}"), I(-42)); + test(STR("answer is -00000101010"), STR("answer is {:012b}"), I(-42)); + test(STR("answer is -0b000101010"), STR("answer is {:#012b}"), I(-42)); + test(STR("answer is -0B000101010"), STR("answer is {:#012B}"), I(-42)); + test(STR("answer is -00000000052"), STR("answer is {:012o}"), I(-42)); + test(STR("answer is -00000000052"), STR("answer is {:#012o}"), I(-42)); + test(STR("answer is -0000000002a"), STR("answer is {:012x}"), I(-42)); + test(STR("answer is -0x00000002a"), STR("answer is {:#012x}"), I(-42)); + test(STR("answer is -0000000002A"), STR("answer is {:012X}"), I(-42)); + test(STR("answer is -0X00000002A"), STR("answer is {:#012X}"), I(-42)); + } + + test(STR("answer is 000000000000"), STR("answer is {:#012}"), I(0)); + test(STR("answer is 000000000000"), STR("answer is {:#012d}"), I(0)); + test(STR("answer is 000000000000"), STR("answer is {:012b}"), I(0)); + test(STR("answer is 0b0000000000"), STR("answer is {:#012b}"), I(0)); + test(STR("answer is 0B0000000000"), STR("answer is {:#012B}"), I(0)); + test(STR("answer is 000000000000"), STR("answer is {:012o}"), I(0)); + test(STR("answer is 000000000000"), STR("answer is {:#012o}"), I(0)); + test(STR("answer is 000000000000"), STR("answer is {:012x}"), I(0)); + test(STR("answer is 0x0000000000"), STR("answer is {:#012x}"), I(0)); + test(STR("answer is 000000000000"), STR("answer is {:012X}"), I(0)); + test(STR("answer is 0X0000000000"), STR("answer is {:#012X}"), I(0)); + + test(STR("answer is +00000000042"), STR("answer is {:+#012}"), I(42)); + test(STR("answer is +00000000042"), STR("answer is {:+#012d}"), I(42)); + test(STR("answer is +00000101010"), STR("answer is {:+012b}"), I(42)); + test(STR("answer is +0b000101010"), STR("answer is {:+#012b}"), I(42)); + test(STR("answer is +0B000101010"), STR("answer is {:+#012B}"), I(42)); + test(STR("answer is +00000000052"), STR("answer is {:+012o}"), I(42)); + test(STR("answer is +00000000052"), STR("answer is {:+#012o}"), I(42)); + test(STR("answer is +0000000002a"), STR("answer is {:+012x}"), I(42)); + test(STR("answer is +0x00000002a"), STR("answer is {:+#012x}"), I(42)); + test(STR("answer is +0000000002A"), STR("answer is {:+012X}"), I(42)); + test(STR("answer is +0X00000002A"), STR("answer is {:+#012X}"), I(42)); + + // *** precision *** + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.}"), I(0)); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.0}"), I(0)); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.42}"), I(0)); + + // *** locale-specific form *** + // See locale-specific_form.pass.cpp + + // *** type *** + for (const auto& fmt : invalid_types("bBcdoxX")) + test_exception( + "The format-spec type has a type not supported for an integer argument", + fmt, 42); +} + +template +void test_integer_as_char() { + // *** align-fill & width *** + // TODO FMT Validate whether this should be left aligned + test(STR("answer is ' *'"), STR("answer is '{:6c}'"), I(42)); + test(STR("answer is ' *'"), STR("answer is '{:>6c}'"), I(42)); + test(STR("answer is '* '"), STR("answer is '{:<6c}'"), I(42)); + test(STR("answer is ' * '"), STR("answer is '{:^6c}'"), I(42)); + + test(STR("answer is '-----*'"), STR("answer is '{:->6c}'"), I(42)); + test(STR("answer is '*-----'"), STR("answer is '{:-<6c}'"), I(42)); + test(STR("answer is '--*---'"), STR("answer is '{:-^6c}'"), I(42)); + + // Test whether zero padding is ignored + test(STR("answer is ' *'"), STR("answer is '{:>06c}'"), I(42)); + test(STR("answer is '* '"), STR("answer is '{:<06c}'"), I(42)); + test(STR("answer is ' * '"), STR("answer is '{:^06c}'"), I(42)); + + // *** Sign *** + test(STR("answer is *"), STR("answer is {:c}"), I(42)); + test(STR("answer is *"), STR("answer is {:-c}"), I(42)); + test(STR("answer is *"), STR("answer is {:+c}"), I(42)); + test(STR("answer is *"), STR("answer is {: c}"), I(42)); + + // *** alternate form *** + test(STR("answer is *"), STR("answer is {:#c}"), I(42)); + test(STR("answer is *"), STR("answer is {:-#c}"), I(42)); + test(STR("answer is *"), STR("answer is {:+#c}"), I(42)); + test(STR("answer is *"), STR("answer is {: #c}"), I(42)); + + // *** zero-padding & width *** + test(STR("answer is ' *'"), STR("answer is '{:+#012c}'"), I(42)); + + // *** precision *** + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.c}"), I(0)); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.0c}"), I(0)); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.42c}"), I(0)); + + // *** locale-specific form *** + // Note it has no effect but it's allowed. + test(STR("answer is '*'"), STR("answer is '{:Lc}'"), I(42)); + + // *** type *** + for (const auto& fmt : invalid_types("bBcdoxX")) + test_exception( + "The format-spec type has a type not supported for an integer argument", + fmt, I(42)); + + // *** Validate range *** + // TODO FMT Update test after adding 128-bit support. + if constexpr (sizeof(I) <= sizeof(long long)) { + // The code has some duplications to keep the if statement readable. + if constexpr (std::signed_integral) { + if constexpr (std::signed_integral && sizeof(I) > sizeof(CharT)) { + test_exception("Integral value outside the range of the char type", + STR("{:c}"), std::numeric_limits::min()); + test_exception("Integral value outside the range of the char type", + STR("{:c}"), std::numeric_limits::max()); + } else if constexpr (std::unsigned_integral && + sizeof(I) >= sizeof(CharT)) { + test_exception("Integral value outside the range of the char type", + STR("{:c}"), std::numeric_limits::max()); + } + } else if constexpr (sizeof(I) > sizeof(CharT)) { + test_exception("Integral value outside the range of the char type", + STR("{:c}"), std::numeric_limits::max()); + } + } +} + +template +void test_integer() { + test_integer_as_integer(); + test_integer_as_char(); +} + +template +void test_signed_integer() { + test_integer(); + test_integer(); + test_integer(); + test_integer(); + test_integer(); +#ifndef _LIBCPP_HAS_NO_INT128 + test_integer<__int128_t, CharT>(); +#endif + // *** test the minma and maxima *** + test(STR("-0b10000000"), STR("{:#b}"), std::numeric_limits::min()); + test(STR("-0200"), STR("{:#o}"), std::numeric_limits::min()); + test(STR("-128"), STR("{:#}"), std::numeric_limits::min()); + test(STR("-0x80"), STR("{:#x}"), std::numeric_limits::min()); + + test(STR("-0b1000000000000000"), STR("{:#b}"), + std::numeric_limits::min()); + test(STR("-0100000"), STR("{:#o}"), std::numeric_limits::min()); + test(STR("-32768"), STR("{:#}"), std::numeric_limits::min()); + test(STR("-0x8000"), STR("{:#x}"), std::numeric_limits::min()); + + test(STR("-0b10000000000000000000000000000000"), STR("{:#b}"), + std::numeric_limits::min()); + test(STR("-020000000000"), STR("{:#o}"), std::numeric_limits::min()); + test(STR("-2147483648"), STR("{:#}"), std::numeric_limits::min()); + test(STR("-0x80000000"), STR("{:#x}"), std::numeric_limits::min()); + + test(STR("-0b100000000000000000000000000000000000000000000000000000000000000" + "0"), + STR("{:#b}"), std::numeric_limits::min()); + test(STR("-01000000000000000000000"), STR("{:#o}"), + std::numeric_limits::min()); + test(STR("-9223372036854775808"), STR("{:#}"), + std::numeric_limits::min()); + test(STR("-0x8000000000000000"), STR("{:#x}"), + std::numeric_limits::min()); + + test(STR("0b1111111"), STR("{:#b}"), std::numeric_limits::max()); + test(STR("0177"), STR("{:#o}"), std::numeric_limits::max()); + test(STR("127"), STR("{:#}"), std::numeric_limits::max()); + test(STR("0x7f"), STR("{:#x}"), std::numeric_limits::max()); + + test(STR("0b111111111111111"), STR("{:#b}"), + std::numeric_limits::max()); + test(STR("077777"), STR("{:#o}"), std::numeric_limits::max()); + test(STR("32767"), STR("{:#}"), std::numeric_limits::max()); + test(STR("0x7fff"), STR("{:#x}"), std::numeric_limits::max()); + + test(STR("0b1111111111111111111111111111111"), STR("{:#b}"), + std::numeric_limits::max()); + test(STR("017777777777"), STR("{:#o}"), std::numeric_limits::max()); + test(STR("2147483647"), STR("{:#}"), std::numeric_limits::max()); + test(STR("0x7fffffff"), STR("{:#x}"), std::numeric_limits::max()); + + test(STR("0b111111111111111111111111111111111111111111111111111111111111111"), + STR("{:#b}"), std::numeric_limits::max()); + test(STR("0777777777777777777777"), STR("{:#o}"), + std::numeric_limits::max()); + test(STR("9223372036854775807"), STR("{:#}"), + std::numeric_limits::max()); + test(STR("0x7fffffffffffffff"), STR("{:#x}"), + std::numeric_limits::max()); + + // TODO FMT Add __int128_t test after implementing full range. +} + +template +void test_unsigned_integer() { + test_integer(); + test_integer(); + test_integer(); + test_integer(); + test_integer(); +#ifndef _LIBCPP_HAS_NO_INT128 + test_integer<__uint128_t, CharT>(); +#endif + // *** test the maxima *** + test(STR("0b11111111"), STR("{:#b}"), std::numeric_limits::max()); + test(STR("0377"), STR("{:#o}"), std::numeric_limits::max()); + test(STR("255"), STR("{:#}"), std::numeric_limits::max()); + test(STR("0xff"), STR("{:#x}"), std::numeric_limits::max()); + + test(STR("0b1111111111111111"), STR("{:#b}"), + std::numeric_limits::max()); + test(STR("0177777"), STR("{:#o}"), std::numeric_limits::max()); + test(STR("65535"), STR("{:#}"), std::numeric_limits::max()); + test(STR("0xffff"), STR("{:#x}"), std::numeric_limits::max()); + + test(STR("0b11111111111111111111111111111111"), STR("{:#b}"), + std::numeric_limits::max()); + test(STR("037777777777"), STR("{:#o}"), std::numeric_limits::max()); + test(STR("4294967295"), STR("{:#}"), std::numeric_limits::max()); + test(STR("0xffffffff"), STR("{:#x}"), std::numeric_limits::max()); + + test( + STR("0b1111111111111111111111111111111111111111111111111111111111111111"), + STR("{:#b}"), std::numeric_limits::max()); + test(STR("01777777777777777777777"), STR("{:#o}"), + std::numeric_limits::max()); + test(STR("18446744073709551615"), STR("{:#}"), + std::numeric_limits::max()); + test(STR("0xffffffffffffffff"), STR("{:#x}"), + std::numeric_limits::max()); + + // TODO FMT Add __uint128_t test after implementing full range. +} + template void test() { // *** Test escaping *** @@ -340,6 +666,7 @@ static_cast<__int128_t>(std::numeric_limits::max()) + 1); } #endif + test_signed_integer(); // ** Test unsigned integral format argument *** test(STR("hello 42"), STR("hello {}"), static_cast(42)); @@ -363,6 +690,7 @@ 1); } #endif + test_unsigned_integer(); // *** Test floating point format argument *** test(STR("hello 42.000000"), STR("hello {}"), static_cast(42)); diff --git a/libcxx/test/std/utilities/format/format.string/format.string.std/concepts_precision.h b/libcxx/test/std/utilities/format/format.string/format.string.std/concepts_precision.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.string/format.string.std/concepts_precision.h @@ -0,0 +1,21 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LIBCXX_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_CONCEPTS_PRECISION_H +#define LIBCXX_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_CONCEPTS_PRECISION_H + +template +concept has_precision = requires(T parser) { + parser.__precision; +}; + +template +concept has_precision_as_arg = requires(T parser) { + parser.__precision_as_arg; +}; + +#endif // LIBCXX_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_CONCEPTS_PRECISION_H diff --git a/libcxx/test/std/utilities/format/format.string/format.string.std/std_format_spec_integer.pass.cpp b/libcxx/test/std/utilities/format/format.string/format.string.std/std_format_spec_integer.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.string/format.string.std/std_format_spec_integer.pass.cpp @@ -0,0 +1,242 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-no-concepts + +// + +// Tests the parsing of the format string as specified in [format.string.std]. +// It validates whether the std-format-spec is valid for a string type. + +#include +#include +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +#include +#endif + +#include "concepts_precision.h" +#include "test_macros.h" +#include "make_string.h" + +#define CSTR(S) MAKE_CSTRING(CharT, S) + +using namespace std::__format_spec; + +template +using parser = __parser_integer; + +#include "test_exception.h" // requires parser + +template +struct Expected { + CharT fill; + _Flags::_Alignment alignment; + _Flags::_Sign sign; + bool alternate_form; + bool zero_padding; + uint32_t width; + bool width_as_arg; + bool locale_specific_form; + _Flags::_Type type; +}; + +template +constexpr void test(Expected expected, size_t size, + std::basic_string_view fmt) { + // Initialize parser with sufficient arguments to avoid the parsing to fail + // due to insufficient arguments. + std::basic_format_parse_context parse_ctx(fmt, + std::__format::__number_max); + auto begin = parse_ctx.begin(); + auto end = parse_ctx.end(); + parser parser; + auto it = parser.parse(parse_ctx); + + assert(begin == parse_ctx.begin()); + assert(end == parse_ctx.end()); + + assert(begin + size == it); + assert(parser.__fill == expected.fill); + assert(parser.__alignment == expected.alignment); + assert(parser.__sign == expected.sign); + assert(parser.__alternate_form == expected.alternate_form); + assert(parser.__zero_padding == expected.zero_padding); + assert(parser.__width == expected.width); + assert(parser.__width_as_arg == expected.width_as_arg); + assert(parser.__locale_specific_form == expected.locale_specific_form); + assert(parser.__type == expected.type); +} + +template +constexpr void test(Expected expected, size_t size, const CharT* f) { + // The format-spec is valid if completely consumed or terminates at a '}'. + // The valid inputs all end with a '}'. The test is executed twice: + // - first with the terminating '}', + // - second consuming the entire input. + std::basic_string_view fmt{f}; + assert(fmt.back() == CharT('}') && "Pre-condition failure"); + + test(expected, size, fmt); + fmt.remove_suffix(1); + test(expected, size, fmt); +} + +template +constexpr void test() { + parser parser; + + assert(parser.__fill == CharT(' ')); + assert(parser.__alignment == _Flags::_Alignment::__default); + assert(parser.__sign == _Flags::_Sign::__default); + assert(parser.__alternate_form == false); + assert(parser.__zero_padding == false); + assert(parser.__width == 0); + assert(parser.__width_as_arg == false); + static_assert(!has_precision); + static_assert(!has_precision_as_arg); + assert(parser.__locale_specific_form == false); + assert(parser.__type == _Flags::_Type::__default); + + // *** Align-fill *** + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, + false, false, 0, false, false, _Flags::_Type::__decimal}, + 0, CSTR("}")); + test({CharT(' '), _Flags::_Alignment::__center, _Flags::_Sign::__default, + false, false, 0, false, false, _Flags::_Type::__decimal}, + 1, CSTR("^}")); + test({CharT('-'), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__decimal}, + 2, CSTR("-<}")); + test({CharT('+'), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__decimal}, + 2, CSTR("+<}")); + + // *** Sign *** + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__minus, false, + false, 0, false, false, _Flags::_Type::__decimal}, + 1, CSTR("-}")); + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__plus, false, + false, 0, false, false, _Flags::_Type::__decimal}, + 1, CSTR("+}")); + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__space, false, + false, 0, false, false, _Flags::_Type::__decimal}, + 1, CSTR(" }")); + + // *** Alternate form *** + // TODO FMT The standard seems to indicate right alignment. + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, true, + false, 0, false, false, _Flags::_Type::__decimal}, + 1, CSTR("#}")); + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, true, + false, 0, false, false, _Flags::_Type::__decimal}, + 2, CSTR("#d}")); + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, true, + false, 0, false, false, _Flags::_Type::__char}, + 2, CSTR("#c}")); + + // *** Zero padding *** + // TODO FMT What to do with zero-padding without a width? + // [format.string.std]/13 + // A zero (0) character preceding the width field pads the field with + // leading zeros (following any indication of sign or base) to the field + // width, except when applied to an infinity or NaN. + // Obviously it makes no sense, but should it be allowed or is it a format + // errror? + test({CharT(' '), _Flags::_Alignment::__default, _Flags::_Sign::__default, + false, true, 0, false, false, _Flags::_Type::__decimal}, + 1, CSTR("0}")); + test({CharT(' '), _Flags::_Alignment::__center, _Flags::_Sign::__default, + false, false, 0, false, false, _Flags::_Type::__decimal}, + 2, CSTR("^0}")); + + // *** Width *** + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, + false, false, 1, false, false, _Flags::_Type::__decimal}, + 1, CSTR("1}")); + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, + false, false, 42, false, false, _Flags::_Type::__decimal}, + 2, CSTR("42}")); + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, + false, false, 2'147'483'647, false, false, _Flags::_Type::__decimal}, + 10, CSTR("2147483647}")); + + // *** Precision *** + test_exception("A precision field isn't allowed in this format-spec", + CSTR(".")); + test_exception("A precision field isn't allowed in this format-spec", + CSTR(".1")); + + // *** Locale-specific form *** + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, + false, false, 0, false, true, _Flags::_Type::__decimal}, + 1, CSTR("L}")); + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, + false, false, 0, false, true, _Flags::_Type::__decimal}, + 2, CSTR("Ld}")); + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, + false, false, 0, false, true, _Flags::_Type::__char}, + 2, CSTR("Lc}")); + + // *** Type *** + { + const char* expected = + "The format-spec type has a type not supported for an integer argument"; + test_exception(expected, CSTR("A}")); + test_exception(expected, CSTR("E}")); + test_exception(expected, CSTR("F}")); + test_exception(expected, CSTR("G}")); + test_exception(expected, CSTR("a}")); + test_exception(expected, CSTR("e}")); + test_exception(expected, CSTR("f}")); + test_exception(expected, CSTR("g}")); + test_exception(expected, CSTR("p}")); + test_exception(expected, CSTR("s}")); + } + + // **** General *** + test_exception("The format-spec should consume the input or end with a '}'", + CSTR("ss")); +} + +constexpr bool test() { + test(); + test(); +#ifndef _LIBCPP_HAS_NO_CHAR8_T + test(); +#endif +#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS + test(); + test(); +#endif + return true; +} + +int main(int, char**) { +#ifndef _WIN32 + // Make sure the parsers match the expectations. The layout of the + // subobjects is chosen to minimize the size required. + static_assert(sizeof(parser) == 2 * sizeof(uint32_t)); + static_assert( + sizeof(parser) == + (sizeof(wchar_t) <= 2 ? 2 * sizeof(uint32_t) : 3 * sizeof(uint32_t))); +#ifndef _LIBCPP_HAS_NO_CHAR8_T + static_assert(sizeof(parser) == 2 * sizeof(uint32_t)); +#endif +#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS + static_assert(sizeof(parser) == 2 * sizeof(uint32_t)); + static_assert(sizeof(parser) == 3 * sizeof(uint32_t)); +#endif +#endif + + test(); + static_assert(test()); + + return 0; +}