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 14" "`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|","14.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 @@ -110,6 +110,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 @@ -147,6 +147,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 +_LIBCPP_HIDE_FROM_ABI 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) + +namespace __format_spec { + +template +class _LIBCPP_TEMPLATE_VIS __parser_integer : public __parser_integral<_CharT> { +public: + _LIBCPP_HIDE_FROM_ABI constexpr auto parse(auto& __parse_ctx) + -> decltype(__parse_ctx.begin()) { + auto __it = __parser_integral<_CharT>::__parse(__parse_ctx); + + switch (this->__type) { + case _Flags::_Type::__default: + this->__type = _Flags::_Type::__decimal; + [[fallthrough]]; + + case _Flags::_Type::__binary_lower_case: + case _Flags::_Type::__binary_upper_case: + case _Flags::_Type::__octal: + case _Flags::_Type::__decimal: + case _Flags::_Type::__hexadecimal_lower_case: + case _Flags::_Type::__hexadecimal_upper_case: + this->__handle_integer(); + break; + + case _Flags::_Type::__char: + this->__handle_char(); + 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_HIDE_FROM_ABI 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_HIDE_FROM_ABI 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) + +#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,502 @@ +// -*- 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 <__algorithm/copy.h> +#include <__algorithm/copy_n.h> +#include <__algorithm/fill_n.h> +#include <__algorithm/transform.h> +#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) + +/** + * 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 combining 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. + */ +namespace __format_spec { + +/** Wrapper around @ref to_chars, returning the output pointer. */ +template +_LIBCPP_HIDE_FROM_ABI 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 +_LIBCPP_HIDE_FROM_ABI 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 +_LIBCPP_HIDE_FROM_ABI 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 +_LIBCPP_HIDE_FROM_ABI 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 +_LIBCPP_HIDE_FROM_ABI 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. +} + +_LIBCPP_HIDE_FROM_ABI 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; +} + +_LIBCPP_HIDE_FROM_ABI 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; +} + +/** + * 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. + */ +_LIBCPP_HIDE_FROM_ABI 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(); +} + +template +requires __formatter::__char_type +class _LIBCPP_TEMPLATE_VIS __formatter_integral : public _Parser { +public: + using _CharT = typename _Parser::char_type; + + template + _LIBCPP_HIDE_FROM_ABI 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_as_char(__value, __ctx); + + if constexpr (unsigned_integral<_Tp>) + return __format_unsigned_integral(__value, false, __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); + bool __negative = __value < 0; + if (__negative) + __r = __complement(__r); + + return __format_unsigned_integral(__r, __negative, __ctx); + } + } + +private: + /** Generic formatting for format-type c. */ + _LIBCPP_HIDE_FROM_ABI auto __format_as_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. + */ + _LIBCPP_HIDE_FROM_ABI auto + __format_unsigned_integral(unsigned_integral auto __value, bool __negative, + 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, + __negative, 2, __ctx, "0b"); + } + case _Flags::_Type::__binary_upper_case: { + array()> __array; + return __format_unsigned_integral(__array.begin(), __array.end(), __value, + __negative, 2, __ctx, "0B"); + } + case _Flags::_Type::__octal: { + // Octal is special; if __value == 0 there's no prefix. + array()> __array; + return __format_unsigned_integral(__array.begin(), __array.end(), __value, + __negative, 8, __ctx, + __value != 0 ? "0" : nullptr); + } + case _Flags::_Type::__decimal: { + array()> __array; + return __format_unsigned_integral(__array.begin(), __array.end(), __value, + __negative, 10, __ctx, nullptr); + } + case _Flags::_Type::__hexadecimal_lower_case: { + array()> __array; + return __format_unsigned_integral(__array.begin(), __array.end(), __value, + __negative, 16, __ctx, "0x"); + } + case _Flags::_Type::__hexadecimal_upper_case: { + array()> __array; + return __format_unsigned_integral(__array.begin(), __array.end(), __value, + __negative, 16, __ctx, "0X"); + } + default: + _LIBCPP_ASSERT(false, "The parser should have validated the type"); + _LIBCPP_UNREACHABLE(); + } + } + + template + requires(same_as || same_as) _LIBCPP_HIDE_FROM_ABI + 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. + // TODO FMT See whether it's possible to do this transformation during the + // conversion. (This probably requires changing std::to_chars' alphabet.) + 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); + } + + _LIBCPP_HIDE_FROM_ABI auto + __format_unsigned_integral(char* __begin, char* __end, + unsigned_integral auto __value, bool __negative, + 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 + _LIBCPP_HIDE_FROM_ABI _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 +}; + +} // namespace __format_spec + +#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___FORMAT_FORMATTER_INTEGRAL_H diff --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h --- a/libcxx/include/__format/parser_std_format_spec.h +++ b/libcxx/include/__format/parser_std_format_spec.h @@ -602,9 +602,7 @@ public: using char_type = _CharT; - // TODO FMT This class probably doesn't need public member functions after - // format.string.std/std_format_spec_integral.pass.cpp has been retired. - +protected: /** * The low-level std-format-spec parse function. * @@ -615,7 +613,7 @@ * * @returns The iterator pointing at the last parsed character. */ - auto _LIBCPP_HIDE_FROM_ABI constexpr parse(auto& __parse_ctx) + auto _LIBCPP_HIDE_FROM_ABI constexpr __parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) { auto __begin = __parse_ctx.begin(); auto __end = __parse_ctx.end(); @@ -657,7 +655,6 @@ return __begin; } -protected: /** * Handles the post-parsing updates for the integer types. * diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -270,9 +270,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> @@ -386,9 +388,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. @@ -401,20 +400,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 @@ -457,50 +442,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. // TODO FMT There are no replacements for the floating point stubs due to not // having floating point support in std::to_chars yet. These stubs aren't diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -410,6 +410,8 @@ module format_parse_context { private header "__format/format_parse_context.h" } module format_string { private header "__format/format_string.h" } module formatter { private header "__format/formatter.h" } + module formatter_integer { private header "__format/formatter_integer.h" } + module formatter_integral { private header "__format/formatter_integral.h" } module formatter_string { private header "__format/formatter_string.h" } module parser_std_format_spec { private header "__format/parser_std_format_spec.h" } } diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_integer.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_integer.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_integer.module.verify.cpp @@ -0,0 +1,16 @@ +// -*- 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__format/formatter_integer.h'}} +#include <__format/formatter_integer.h> diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_integral.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_integral.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_integral.module.verify.cpp @@ -0,0 +1,16 @@ +// -*- 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__format/formatter_integral.h'}} +#include <__format/formatter_integral.h> diff --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integral.pass.cpp b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integer.pass.cpp rename from libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integral.pass.cpp rename to libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integer.pass.cpp --- a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integral.pass.cpp +++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integer.pass.cpp @@ -14,10 +14,6 @@ // 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. -// TODO FMT This test should removed once the integer parser is implemented. -// The integral specific fields are all tested for the integer, making this -// test redundant. - #include #include #ifndef _LIBCPP_HAS_NO_LOCALIZATION @@ -33,21 +29,21 @@ using namespace std::__format_spec; template -using parser = __parser_integral; +using parser = __parser_integer; #include "test_exception.h" // requires parser template struct Expected { CharT fill = CharT(' '); - _Flags::_Alignment alignment = _Flags::_Alignment::__default; + _Flags::_Alignment alignment = _Flags::_Alignment::__right; _Flags::_Sign sign = _Flags::_Sign::__default; bool alternate_form = false; bool zero_padding = false; uint32_t width = 0; bool width_as_arg = false; bool locale_specific_form = false; - _Flags::_Type type = _Flags::_Type::__default; + _Flags::_Type type = _Flags::_Type::__decimal; }; template @@ -108,6 +104,9 @@ assert(parser.__type == _Flags::_Type::__default); test({}, 0, CSTR("}")); + test({}, 1, CSTR("d}")); + test({.alignment = _Flags::_Alignment::__left, .type = _Flags::_Type::__char}, + 1, CSTR("c}")); // *** Align-fill *** test({.alignment = _Flags::_Alignment::__left}, 1, CSTR("<}")); @@ -131,8 +130,19 @@ test({.sign = _Flags::_Sign::__plus}, 1, CSTR("+}")); test({.sign = _Flags::_Sign::__space}, 1, CSTR(" }")); + test({.sign = _Flags::_Sign::__minus}, 2, CSTR("-d}")); + test({.sign = _Flags::_Sign::__plus}, 2, CSTR("+d}")); + test({.sign = _Flags::_Sign::__space}, 2, CSTR(" d}")); + + test_exception("A sign field isn't allowed in this format-spec", CSTR("-c")); + test_exception("A sign field isn't allowed in this format-spec", CSTR("+c")); + test_exception("A sign field isn't allowed in this format-spec", CSTR(" c")); + // *** Alternate form *** test({.alternate_form = true}, 1, CSTR("#}")); + test({.alternate_form = true}, 2, CSTR("#d}")); + test_exception("An alternate form field isn't allowed in this format-spec", + CSTR("#c")); // *** Zero padding *** // TODO FMT What to do with zero-padding without a width? @@ -142,9 +152,32 @@ // 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({.zero_padding = true}, 1, CSTR("0}")); - test({.alignment = _Flags::_Alignment::__center, .zero_padding = true}, 2, + test({.alignment = _Flags::_Alignment::__default, .zero_padding = true}, 1, + CSTR("0}")); + test({.alignment = _Flags::_Alignment::__left, .zero_padding = false}, 2, + CSTR("<0}")); + test({.alignment = _Flags::_Alignment::__center, .zero_padding = false}, 2, CSTR("^0}")); + test({.alignment = _Flags::_Alignment::__right, .zero_padding = false}, 2, + CSTR(">0}")); + + test({.alignment = _Flags::_Alignment::__default, .zero_padding = true}, 2, + CSTR("0d}")); + test({.alignment = _Flags::_Alignment::__left, .zero_padding = false}, 3, + CSTR("<0d}")); + test({.alignment = _Flags::_Alignment::__center, .zero_padding = false}, 3, + CSTR("^0d}")); + test({.alignment = _Flags::_Alignment::__right, .zero_padding = false}, 3, + CSTR(">0d}")); + + test_exception("A zero-padding field isn't allowed in this format-spec", + CSTR("0c")); + test_exception("A zero-padding field isn't allowed in this format-spec", + CSTR("<0c")); + test_exception("A zero-padding field isn't allowed in this format-spec", + CSTR("^0c")); + test_exception("A zero-padding field isn't allowed in this format-spec", + CSTR(">0c")); // *** Width *** test({.width = 0, .width_as_arg = false}, 0, CSTR("}")); @@ -202,25 +235,27 @@ // *** Locale-specific form *** test({.locale_specific_form = true}, 1, CSTR("L}")); - test({.locale_specific_form = true, .type = _Flags::_Type::__decimal}, 2, - CSTR("Ld}")); - test({.locale_specific_form = true, .type = _Flags::_Type::__char}, 2, - CSTR("Lc}")); + test({.locale_specific_form = true}, 2, CSTR("Ld}")); + // Note the flag is allowed, but has no effect. + test({.alignment = _Flags::_Alignment::__left, + .locale_specific_form = true, + .type = _Flags::_Type::__char}, + 2, CSTR("Lc}")); // *** Type *** - { + const char* unsuported_type = + "The format-spec type has a type not supported for an integer argument"; const char* not_a_type = "The format-spec should consume the input or end with a '}'"; - test({.type = _Flags::_Type::__float_hexadecimal_upper_case}, 1, - CSTR("A}")); + test_exception(unsuported_type, CSTR("A}")); test({.type = _Flags::_Type::__binary_upper_case}, 1, CSTR("B}")); test_exception(not_a_type, CSTR("C}")); test_exception(not_a_type, CSTR("D}")); - test({.type = _Flags::_Type::__scientific_upper_case}, 1, CSTR("E}")); - test({.type = _Flags::_Type::__fixed_upper_case}, 1, CSTR("F}")); - test({.type = _Flags::_Type::__general_upper_case}, 1, CSTR("G}")); + test_exception(unsuported_type, CSTR("E}")); + test_exception(unsuported_type, CSTR("F}")); + test_exception(unsuported_type, CSTR("G}")); test_exception(not_a_type, CSTR("H}")); test_exception(not_a_type, CSTR("I}")); test_exception(not_a_type, CSTR("J}")); @@ -241,14 +276,15 @@ test_exception(not_a_type, CSTR("Y}")); test_exception(not_a_type, CSTR("Z}")); - test({.type = _Flags::_Type::__float_hexadecimal_lower_case}, 1, - CSTR("a}")); + test_exception(unsuported_type, CSTR("a}")); test({.type = _Flags::_Type::__binary_lower_case}, 1, CSTR("b}")); - test({.type = _Flags::_Type::__char}, 1, CSTR("c}")); + test({.alignment = _Flags::_Alignment::__left, + .type = _Flags::_Type::__char}, + 1, CSTR("c}")); test({.type = _Flags::_Type::__decimal}, 1, CSTR("d}")); - test({.type = _Flags::_Type::__scientific_lower_case}, 1, CSTR("e}")); - test({.type = _Flags::_Type::__fixed_lower_case}, 1, CSTR("f}")); - test({.type = _Flags::_Type::__general_lower_case}, 1, CSTR("g}")); + test_exception(unsuported_type, CSTR("e}")); + test_exception(unsuported_type, CSTR("f}")); + test_exception(unsuported_type, CSTR("g}")); test_exception(not_a_type, CSTR("h}")); test_exception(not_a_type, CSTR("i}")); test_exception(not_a_type, CSTR("j}")); @@ -257,10 +293,10 @@ test_exception(not_a_type, CSTR("m}")); test_exception(not_a_type, CSTR("n}")); test({.type = _Flags::_Type::__octal}, 1, CSTR("o}")); - test({.type = _Flags::_Type::__pointer}, 1, CSTR("p}")); + test_exception(unsuported_type, CSTR("p}")); test_exception(not_a_type, CSTR("q}")); test_exception(not_a_type, CSTR("r}")); - test({.type = _Flags::_Type::__string}, 1, CSTR("s}")); + test_exception(unsuported_type, CSTR("s}")); test_exception(not_a_type, CSTR("t}")); test_exception(not_a_type, CSTR("u}")); test_exception(not_a_type, CSTR("v}")); @@ -277,13 +313,6 @@ constexpr bool test() { test(); test(); -#ifndef _LIBCPP_HAS_NO_CHAR8_T - test(); -#endif -#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS - test(); - test(); -#endif return true; } 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,568 @@ +//===----------------------------------------------------------------------===// +// 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 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// 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) @@ -252,6 +254,326 @@ 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("The format-spec should consume the input or end with a '}'", + STR("{:.}"), I(0)); + test_exception("The format-spec should consume the input or end with a '}'", + STR("{:.0}"), I(0)); + test_exception("The format-spec should consume the input or end with a '}'", + 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 *** + 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)); + + // *** Sign *** + test(STR("answer is *"), STR("answer is {:c}"), I(42)); + test_exception("A sign field isn't allowed in this format-spec", + STR("answer is {:-c}"), I(42)); + test_exception("A sign field isn't allowed in this format-spec", + STR("answer is {:+c}"), I(42)); + test_exception("A sign field isn't allowed in this format-spec", + STR("answer is {: c}"), I(42)); + + // *** alternate form *** + test_exception("An alternate form field isn't allowed in this format-spec", + STR("answer is {:#c}"), I(42)); + + // *** zero-padding & width *** + test_exception("A zero-padding field isn't allowed in this format-spec", + STR("answer is {:01c}"), I(42)); + + // *** precision *** + test_exception("The format-spec should consume the input or end with a '}'", + STR("{:.c}"), I(0)); + test_exception("The format-spec should consume the input or end with a '}'", + STR("{:.0c}"), I(0)); + test_exception("The format-spec should consume the input or end with a '}'", + 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 *** @@ -341,6 +663,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)); @@ -364,6 +687,7 @@ 1); } #endif + test_unsigned_integer(); // *** Test floating point format argument *** // TODO FMT Enable after floating-point support has been enabled