diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv index 18acdf922437f..a4dd75c5ca039 100644 --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -171,7 +171,7 @@ "`P1460 `__","LWG","Mandating the Standard Library: Clause 20 - Utilities library","Prague","* *","" "`P1739 `__","LWG","Avoid template bloat for safe_ranges in combination with ""subrange-y"" view adaptors","Prague","* *","" "`P1831 `__","LWG","Deprecating volatile: library","Prague","* *","" -"`P1868 `__","LWG","width: clarifying units of width and precision in std::format","Prague","|In Progress|","" +"`P1868 `__","LWG","width: clarifying units of width and precision in std::format","Prague","|Complete|","14.0" "`P1908 `__","CWG","Reserving Attribute Namespaces for Future Use","Prague","* *","" "`P1937 `__","CWG","Fixing inconsistencies between constexpr and consteval functions","Prague","* *","" "`P1956 `__","LWG","On the names of low-level bit manipulation functions","Prague","|Complete|","12.0" diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index 568f06bda4812..3460f72f80000 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -140,6 +140,7 @@ set(files __format/format_parse_context.h __format/format_string.h __format/formatter.h + __format/formatter_bool.h __format/formatter_char.h __format/formatter_integer.h __format/formatter_integral.h diff --git a/libcxx/include/__format/formatter_bool.h b/libcxx/include/__format/formatter_bool.h new file mode 100644 index 0000000000000..8d9a1d26a4e22 --- /dev/null +++ b/libcxx/include/__format/formatter_bool.h @@ -0,0 +1,145 @@ +// -*- 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_BOOL_H +#define _LIBCPP___FORMAT_FORMATTER_BOOL_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 + +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +#include +#endif + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_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_bool : 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::__string; + [[fallthrough]]; + case _Flags::_Type::__string: + this->__handle_bool(); + break; + + case _Flags::_Type::__char: + this->__handle_char(); + break; + + 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; + + default: + __throw_format_error( + "The format-spec type has a type not supported for a bool argument"); + } + + return __it; + } +}; + +template +struct _LIBCPP_TEMPLATE_VIS __bool_strings; + +template <> +struct _LIBCPP_TEMPLATE_VIS __bool_strings { + static constexpr string_view __true{"true"}; + static constexpr string_view __false{"false"}; +}; + +template <> +struct _LIBCPP_TEMPLATE_VIS __bool_strings { + static constexpr wstring_view __true{L"true"}; + static constexpr wstring_view __false{L"false"}; +}; + +template +using __formatter_bool = __formatter_integral<__parser_bool<_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 + +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter + : public __format_spec::__formatter_bool<_CharT> { + using _Base = __format_spec::__formatter_bool<_CharT>; + + _LIBCPP_HIDE_FROM_ABI auto format(bool __value, auto& __ctx) + -> decltype(__ctx.out()) { + if (this->__type != __format_spec::_Flags::_Type::__string) + return _Base::format(static_cast(__value), __ctx); + + if (this->__width_needs_substitution()) + this->__substitute_width_arg_id(__ctx.arg(this->__width)); + +#ifndef _LIBCPP_HAS_NO_LOCALIZATION + if (this->__locale_specific_form) { + const auto& __np = use_facet>(__ctx.locale()); + basic_string<_CharT> __str = __value ? __np.truename() : __np.falsename(); + return __formatter::__write_unicode( + __ctx.out(), basic_string_view<_CharT>{__str}, this->__width, -1, + this->__fill, this->__alignment); + } +#endif + basic_string_view<_CharT> __str = + __value ? __format_spec::__bool_strings<_CharT>::__true + : __format_spec::__bool_strings<_CharT>::__false; + + // The output only uses ASCII so every character is one column. + unsigned __size = __str.size(); + if (__size >= this->__width) + return _VSTD::copy(__str.begin(), __str.end(), __ctx.out()); + + return __formatter::__write(__ctx.out(), __str.begin(), __str.end(), __size, + this->__width, this->__fill, this->__alignment); + } +}; + +#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___FORMAT_FORMATTER_BOOL_H diff --git a/libcxx/include/format b/libcxx/include/format index d4e24b6c30fcf..91160151489aa 100644 --- a/libcxx/include/format +++ b/libcxx/include/format @@ -279,6 +279,7 @@ namespace std { #include <__format/format_parse_context.h> #include <__format/format_string.h> #include <__format/formatter.h> +#include <__format/formatter_bool.h> #include <__format/formatter_char.h> #include <__format/formatter_integer.h> #include <__format/formatter_string.h> @@ -389,28 +390,6 @@ private: // These specializations are helper stubs and not proper formatters. // TODO FMT Implement the proper formatter specializations. -// [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 - -// Boolean. -template -struct _LIBCPP_TEMPLATE_VIS formatter { - _LIBCPP_HIDE_FROM_ABI - auto parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) { - // TODO FMT Implement - return __parse_ctx.begin(); - } - - _LIBCPP_HIDE_FROM_ABI - auto format(bool __b, auto& __ctx) -> decltype(__ctx.out()) { - // TODO FMT Implement using formatting arguments - auto __out_it = __ctx.out(); - *__out_it++ = _CharT('0') + __b; - return __out_it; - } -}; - // 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 index 4f107738313fe..5abe2431300a7 100644 --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -450,6 +450,7 @@ module std [system] { 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_bool { private header "__format/formatter_bool.h" } module formatter_char { private header "__format/formatter_char.h" } module formatter_integer { private header "__format/formatter_integer.h" } module formatter_integral { private header "__format/formatter_integral.h" } diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_bool.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_bool.module.verify.cpp new file mode 100644 index 0000000000000..9cd9dfa37a16f --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_bool.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_bool.h'}} +#include <__format/formatter_bool.h> diff --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_bool.pass.cpp b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_bool.pass.cpp new file mode 100644 index 0000000000000..c6fdbee325320 --- /dev/null +++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_bool.pass.cpp @@ -0,0 +1,452 @@ +//===----------------------------------------------------------------------===// +// 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-incomplete-format + +// + +// Tests the parsing of the format string as specified in [format.string.std]. +// It validates whether the std-format-spec is valid for a boolean type. + +#include +#include +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +# include +#endif + +#include "concepts_precision.h" +#include "test_macros.h" +#include "make_string.h" +#include "test_exception.h" + +#define CSTR(S) MAKE_CSTRING(CharT, S) + +using namespace std::__format_spec; + +template +using Parser = __parser_bool; + +template +struct Expected { + CharT fill = CharT(' '); + _Flags::_Alignment alignment = _Flags::_Alignment::__left; + _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::__string; +}; + +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_as_string() { + + test({}, 1, CSTR("s}")); + + // *** Align-fill *** + test({.alignment = _Flags::_Alignment::__left}, 1, CSTR("<}")); + test({.alignment = _Flags::_Alignment::__center}, 1, "^}"); + test({.alignment = _Flags::_Alignment::__right}, 1, ">}"); + + test({.alignment = _Flags::_Alignment::__left}, 2, CSTR("s}"); + + test({.fill = CharT('L'), .alignment = _Flags::_Alignment::__left}, 2, + CSTR("L<}")); + test({.fill = CharT('#'), .alignment = _Flags::_Alignment::__center}, 2, + CSTR("#^}")); + test({.fill = CharT('0'), .alignment = _Flags::_Alignment::__right}, 2, + CSTR("0>}")); + + test({.fill = CharT('L'), .alignment = _Flags::_Alignment::__left}, 3, + CSTR("Ls}")); + + // *** Sign *** + test_exception>( + "A sign field isn't allowed in this format-spec", CSTR("-}")); + test_exception>( + "A sign field isn't allowed in this format-spec", CSTR("-s}")); + + // *** Alternate form *** + test_exception>( + "An alternate form field isn't allowed in this format-spec", CSTR("#}")); + test_exception>( + "An alternate form field isn't allowed in this format-spec", CSTR("#s}")); + + // *** Zero padding *** + test_exception>( + "A zero-padding field isn't allowed in this format-spec", CSTR("0}")); + test_exception>( + "A zero-padding field isn't allowed in this format-spec", CSTR("0s}")); + + // *** Width *** + test({.width = 0, .width_as_arg = false}, 0, CSTR("}")); + test({.width = 1, .width_as_arg = false}, 1, CSTR("1}")); + test({.width = 10, .width_as_arg = false}, 2, CSTR("10}")); + test({.width = 1000, .width_as_arg = false}, 4, CSTR("1000}")); + test({.width = 1000000, .width_as_arg = false}, 7, CSTR("1000000}")); + + test({.width = 0, .width_as_arg = true}, 2, CSTR("{}}")); + test({.width = 0, .width_as_arg = true}, 3, CSTR("{0}}")); + test({.width = 1, .width_as_arg = true}, 3, CSTR("{1}}")); + + test_exception>( + "A format-spec width field shouldn't have a leading zero", CSTR("00")); + + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + test({.width = 2'147'483'647, .width_as_arg = false}, 10, + CSTR("2147483647}")); + test_exception>( + "The numeric value of the format-spec is too large", CSTR("2147483648")); + test_exception>( + "The numeric value of the format-spec is too large", CSTR("5000000000")); + test_exception>( + "The numeric value of the format-spec is too large", CSTR("10000000000")); + + test_exception>("End of input while parsing format-spec arg-id", + CSTR("{")); + test_exception>( + "A format-spec arg-id should terminate at a '}'", CSTR("{0")); + test_exception>( + "The arg-id of the format-spec starts with an invalid character", + CSTR("{a")); + test_exception>( + "A format-spec arg-id should terminate at a '}'", CSTR("{1")); + test_exception>( + "A format-spec arg-id should terminate at a '}'", CSTR("{9")); + test_exception>( + "A format-spec arg-id should terminate at a '}'", CSTR("{9:")); + test_exception>( + "A format-spec arg-id should terminate at a '}'", CSTR("{9a")); + + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + // Note the static_assert tests whether the arg-id is valid. + // Therefore the following should be true arg-id < __format::__number_max. + test({.width = 2'147'483'646, .width_as_arg = true}, 12, + CSTR("{2147483646}}")); + test_exception>( + "The numeric value of the format-spec is too large", + CSTR("{2147483648}")); + test_exception>( + "The numeric value of the format-spec is too large", + CSTR("{5000000000}")); + test_exception>( + "The numeric value of the format-spec is too large", + CSTR("{10000000000}")); + + // *** Precision *** + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR(".")); + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR(".1")); + + // *** Locale-specific form *** + test({.locale_specific_form = true}, 1, CSTR("L}")); + test({.locale_specific_form = true}, 2, CSTR("Ls}")); +} + +template +constexpr void test_as_char() { + + test({.type = _Flags::_Type::__char}, 1, CSTR("c}")); + + // *** Align-fill *** + test({.alignment = _Flags::_Alignment::__left, .type = _Flags::_Type::__char}, + 2, CSTR("c}"); + + test({.fill = CharT('L'), + .alignment = _Flags::_Alignment::__left, + .type = _Flags::_Type::__char}, + 3, CSTR("Lc}")); + + // *** Sign *** + test_exception>( + "A sign field isn't allowed in this format-spec", CSTR("-c}")); + + // *** Alternate form *** + test_exception>( + "An alternate form field isn't allowed in this format-spec", CSTR("#c}")); + + // *** Zero padding *** + test_exception>( + "A zero-padding field isn't allowed in this format-spec", CSTR("0c}")); + + // *** Width *** + test({.width = 0, .width_as_arg = false, .type = _Flags::_Type::__char}, 1, + CSTR("c}")); + test({.width = 1, .width_as_arg = false, .type = _Flags::_Type::__char}, 2, + CSTR("1c}")); + test({.width = 10, .width_as_arg = false, .type = _Flags::_Type::__char}, 3, + CSTR("10c}")); + test({.width = 1000, .width_as_arg = false, .type = _Flags::_Type::__char}, 5, + CSTR("1000c}")); + test({.width = 1000000, .width_as_arg = false, .type = _Flags::_Type::__char}, + 8, CSTR("1000000c}")); + + test({.width = 0, .width_as_arg = true, .type = _Flags::_Type::__char}, 3, + CSTR("{}c}")); + test({.width = 0, .width_as_arg = true, .type = _Flags::_Type::__char}, 4, + CSTR("{0}c}")); + test({.width = 1, .width_as_arg = true, .type = _Flags::_Type::__char}, 4, + CSTR("{1}c}")); + + // *** Precision *** + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR(".")); + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR(".1")); + + // *** Locale-specific form *** + test({.locale_specific_form = true, .type = _Flags::_Type::__char}, 2, + CSTR("Lc}")); +} + +template +constexpr void test_as_integer() { + + test({.alignment = _Flags::_Alignment::__right, + .type = _Flags::_Type::__decimal}, + 1, CSTR("d}")); + + // *** Align-fill *** + test({.alignment = _Flags::_Alignment::__left, + .type = _Flags::_Type::__decimal}, + 2, CSTR("d}"); + + test({.fill = CharT('L'), + .alignment = _Flags::_Alignment::__left, + .type = _Flags::_Type::__decimal}, + 3, CSTR("Ld}")); + + // *** Sign *** + test({.alignment = _Flags::_Alignment::__right, + .sign = _Flags::_Sign::__minus, + .type = _Flags::_Type::__decimal}, + 2, CSTR("-d}")); + test({.alignment = _Flags::_Alignment::__right, + .sign = _Flags::_Sign::__plus, + .type = _Flags::_Type::__decimal}, + 2, CSTR("+d}")); + test({.alignment = _Flags::_Alignment::__right, + .sign = _Flags::_Sign::__space, + .type = _Flags::_Type::__decimal}, + 2, CSTR(" d}")); + + // *** Alternate form *** + test({.alignment = _Flags::_Alignment::__right, + .alternate_form = true, + .type = _Flags::_Type::__decimal}, + 2, CSTR("#d}")); + + // *** Zero padding *** + test({.alignment = _Flags::_Alignment::__default, + .zero_padding = true, + .type = _Flags::_Type::__decimal}, + 2, CSTR("0d}")); + test({.alignment = _Flags::_Alignment::__center, + .type = _Flags::_Type::__decimal}, + 3, CSTR("^0d}")); + + // *** Width *** + test({.alignment = _Flags::_Alignment::__right, + .width = 0, + .width_as_arg = false, + .type = _Flags::_Type::__decimal}, + 1, CSTR("d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 1, + .width_as_arg = false, + .type = _Flags::_Type::__decimal}, + 2, CSTR("1d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 10, + .width_as_arg = false, + .type = _Flags::_Type::__decimal}, + 3, CSTR("10d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 1000, + .width_as_arg = false, + .type = _Flags::_Type::__decimal}, + 5, CSTR("1000d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 1000000, + .width_as_arg = false, + .type = _Flags::_Type::__decimal}, + 8, CSTR("1000000d}")); + + test({.alignment = _Flags::_Alignment::__right, + .width = 0, + .width_as_arg = true, + .type = _Flags::_Type::__decimal}, + 3, CSTR("{}d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 0, + .width_as_arg = true, + .type = _Flags::_Type::__decimal}, + 4, CSTR("{0}d}")); + test({.alignment = _Flags::_Alignment::__right, + .width = 1, + .width_as_arg = true, + .type = _Flags::_Type::__decimal}, + 4, CSTR("{1}d}")); + + // *** Precision *** + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR(".")); + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR(".1")); + + // *** Locale-specific form *** + test({.alignment = _Flags::_Alignment::__right, + .locale_specific_form = true, + .type = _Flags::_Type::__decimal}, + 2, CSTR("Ld}")); +} + +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); + + test({}, 0, CSTR("}")); + + test_as_string(); + test_as_char(); + test_as_integer(); + + // *** Type *** + { + const char* expected = + "The format-spec type has a type not supported for a bool 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}")); + } + + // **** General *** + test_exception>( + "The format-spec should consume the input or end with a '}'", CSTR("ss")); +} + +constexpr bool test() { + test(); + test(); + + 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))); +#endif + + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.bool.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.bool.pass.cpp index 9a62d04be37f4..41b6f1246945f 100644 --- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.bool.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.bool.pass.cpp @@ -67,8 +67,8 @@ void test_termination_condition(StringT expected, StringT f, bool arg) { template void test_boolean() { - test_termination_condition(STR("1"), STR("}"), true); - test_termination_condition(STR("0"), STR("}"), false); + test_termination_condition(STR("true"), STR("}"), true); + test_termination_condition(STR("false"), STR("}"), false); } int main(int, char**) { diff --git a/libcxx/test/std/utilities/format/format.functions/format_tests.h b/libcxx/test/std/utilities/format/format.functions/format_tests.h index 1478288b8c976..3da2edfbf066b 100644 --- a/libcxx/test/std/utilities/format/format.functions/format_tests.h +++ b/libcxx/test/std/utilities/format/format.functions/format_tests.h @@ -15,6 +15,7 @@ // ExceptionTest must be callable as check_exception(expected-exception, string-to-format, args-to-format...) #define STR(S) MAKE_STRING(CharT, S) +#define CSTR(S) MAKE_CSTRING(CharT, S) template std::vector> invalid_types(std::string valid) { @@ -260,6 +261,238 @@ void format_string_tests(TestFunction check, ExceptionTest check_exception) { format_test_string_unicode(check); } +template +void format_test_bool(TestFunction check, ExceptionTest check_exception) { + + // *** align-fill & width *** + check(STR("answer is 'true '"), STR("answer is '{:7}'"), true); + check(STR("answer is ' true'"), STR("answer is '{:>7}'"), true); + check(STR("answer is 'true '"), STR("answer is '{:<7}'"), true); + check(STR("answer is ' true '"), STR("answer is '{:^7}'"), true); + + check(STR("answer is 'false '"), STR("answer is '{:8s}'"), false); + check(STR("answer is ' false'"), STR("answer is '{:>8s}'"), false); + check(STR("answer is 'false '"), STR("answer is '{:<8s}'"), false); + check(STR("answer is ' false '"), STR("answer is '{:^8s}'"), false); + + check(STR("answer is '---true'"), STR("answer is '{:->7}'"), true); + check(STR("answer is 'true---'"), STR("answer is '{:-<7}'"), true); + check(STR("answer is '-true--'"), STR("answer is '{:-^7}'"), true); + + check(STR("answer is '---false'"), STR("answer is '{:->8s}'"), false); + check(STR("answer is 'false---'"), STR("answer is '{:-<8s}'"), false); + check(STR("answer is '-false--'"), STR("answer is '{:-^8s}'"), false); + + // *** Sign *** + check_exception("A sign field isn't allowed in this format-spec", STR("{:-}"), + true); + check_exception("A sign field isn't allowed in this format-spec", STR("{:+}"), + true); + check_exception("A sign field isn't allowed in this format-spec", STR("{: }"), + true); + + check_exception("A sign field isn't allowed in this format-spec", + STR("{:-s}"), true); + check_exception("A sign field isn't allowed in this format-spec", + STR("{:+s}"), true); + check_exception("A sign field isn't allowed in this format-spec", + STR("{: s}"), true); + + // *** alternate form *** + check_exception("An alternate form field isn't allowed in this format-spec", + STR("{:#}"), true); + check_exception("An alternate form field isn't allowed in this format-spec", + STR("{:#s}"), true); + + // *** zero-padding *** + check_exception("A zero-padding field isn't allowed in this format-spec", + STR("{:0}"), true); + check_exception("A zero-padding field isn't allowed in this format-spec", + STR("{:0s}"), true); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.}"), true); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.0}"), true); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.42}"), true); + + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.s}"), true); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.0s}"), true); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.42s}"), true); + + // *** locale-specific form *** + // See locale-specific_form.pass.cpp + + // *** type *** + for (const auto& fmt : invalid_types("bBcdosxX")) + check_exception( + "The format-spec type has a type not supported for a bool argument", + fmt, true); +} + +template +void format_test_bool_as_char(TestFunction check, + ExceptionTest check_exception) { + // *** align-fill & width *** + check(STR("answer is '\1 '"), STR("answer is '{:6c}'"), true); + check(STR("answer is ' \1'"), STR("answer is '{:>6c}'"), true); + check(STR("answer is '\1 '"), STR("answer is '{:<6c}'"), true); + check(STR("answer is ' \1 '"), STR("answer is '{:^6c}'"), true); + + check(STR("answer is '-----\1'"), STR("answer is '{:->6c}'"), true); + check(STR("answer is '\1-----'"), STR("answer is '{:-<6c}'"), true); + check(STR("answer is '--\1---'"), STR("answer is '{:-^6c}'"), true); + + check(std::basic_string(CSTR("answer is '\0 '"), 18), + STR("answer is '{:6c}'"), false); + check(std::basic_string(CSTR("answer is '\0 '"), 18), + STR("answer is '{:6c}'"), false); + check(std::basic_string(CSTR("answer is ' \0'"), 18), + STR("answer is '{:>6c}'"), false); + check(std::basic_string(CSTR("answer is '\0 '"), 18), + STR("answer is '{:<6c}'"), false); + check(std::basic_string(CSTR("answer is ' \0 '"), 18), + STR("answer is '{:^6c}'"), false); + + check(std::basic_string(CSTR("answer is '-----\0'"), 18), + STR("answer is '{:->6c}'"), false); + check(std::basic_string(CSTR("answer is '\0-----'"), 18), + STR("answer is '{:-<6c}'"), false); + check(std::basic_string(CSTR("answer is '--\0---'"), 18), + STR("answer is '{:-^6c}'"), false); + + // *** Sign *** + check_exception("A sign field isn't allowed in this format-spec", + STR("{:-c}"), true); + check_exception("A sign field isn't allowed in this format-spec", + STR("{:+c}"), true); + check_exception("A sign field isn't allowed in this format-spec", + STR("{: c}"), true); + + // *** alternate form *** + check_exception("An alternate form field isn't allowed in this format-spec", + STR("{:#c}"), true); + + // *** zero-padding *** + check_exception("A zero-padding field isn't allowed in this format-spec", + STR("{:0c}"), true); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.c}"), true); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.0c}"), true); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.42c}"), true); + + // *** locale-specific form *** + // Note it has no effect but it's allowed. + check(STR("answer is '*'"), STR("answer is '{:Lc}'"), '*'); + + // *** type *** + for (const auto& fmt : invalid_types("bBcdosxX")) + check_exception( + "The format-spec type has a type not supported for a bool argument", + fmt, true); +} + +template +void format_test_bool_as_integer(TestFunction check, + ExceptionTest check_exception) { + // *** align-fill & width *** + check(STR("answer is '1'"), STR("answer is '{:<1d}'"), true); + check(STR("answer is '1 '"), STR("answer is '{:<2d}'"), true); + check(STR("answer is '0 '"), STR("answer is '{:<2d}'"), false); + + check(STR("answer is ' 1'"), STR("answer is '{:6d}'"), true); + check(STR("answer is ' 1'"), STR("answer is '{:>6d}'"), true); + check(STR("answer is '1 '"), STR("answer is '{:<6d}'"), true); + check(STR("answer is ' 1 '"), STR("answer is '{:^6d}'"), true); + + check(STR("answer is '*****0'"), STR("answer is '{:*>6d}'"), false); + check(STR("answer is '0*****'"), STR("answer is '{:*<6d}'"), false); + check(STR("answer is '**0***'"), STR("answer is '{:*^6d}'"), false); + + // Test whether zero padding is ignored + check(STR("answer is ' 1'"), STR("answer is '{:>06d}'"), true); + check(STR("answer is '1 '"), STR("answer is '{:<06d}'"), true); + check(STR("answer is ' 1 '"), STR("answer is '{:^06d}'"), true); + + // *** Sign *** + check(STR("answer is 1"), STR("answer is {:d}"), true); + check(STR("answer is 0"), STR("answer is {:-d}"), false); + check(STR("answer is +1"), STR("answer is {:+d}"), true); + check(STR("answer is 0"), STR("answer is {: d}"), false); + + // *** alternate form *** + check(STR("answer is +1"), STR("answer is {:+#d}"), true); + check(STR("answer is +1"), STR("answer is {:+b}"), true); + check(STR("answer is +0b1"), STR("answer is {:+#b}"), true); + check(STR("answer is +0B1"), STR("answer is {:+#B}"), true); + check(STR("answer is +1"), STR("answer is {:+o}"), true); + check(STR("answer is +01"), STR("answer is {:+#o}"), true); + check(STR("answer is +1"), STR("answer is {:+x}"), true); + check(STR("answer is +0x1"), STR("answer is {:+#x}"), true); + check(STR("answer is +1"), STR("answer is {:+X}"), true); + check(STR("answer is +0X1"), STR("answer is {:+#X}"), true); + + check(STR("answer is 0"), STR("answer is {:#d}"), false); + check(STR("answer is 0"), STR("answer is {:b}"), false); + check(STR("answer is 0b0"), STR("answer is {:#b}"), false); + check(STR("answer is 0B0"), STR("answer is {:#B}"), false); + check(STR("answer is 0"), STR("answer is {:o}"), false); + check(STR("answer is 0"), STR("answer is {:#o}"), false); + check(STR("answer is 0"), STR("answer is {:x}"), false); + check(STR("answer is 0x0"), STR("answer is {:#x}"), false); + check(STR("answer is 0"), STR("answer is {:X}"), false); + check(STR("answer is 0X0"), STR("answer is {:#X}"), false); + + // *** zero-padding & width *** + check(STR("answer is +00000000001"), STR("answer is {:+#012d}"), true); + check(STR("answer is +00000000001"), STR("answer is {:+012b}"), true); + check(STR("answer is +0b000000001"), STR("answer is {:+#012b}"), true); + check(STR("answer is +0B000000001"), STR("answer is {:+#012B}"), true); + check(STR("answer is +00000000001"), STR("answer is {:+012o}"), true); + check(STR("answer is +00000000001"), STR("answer is {:+#012o}"), true); + check(STR("answer is +00000000001"), STR("answer is {:+012x}"), true); + check(STR("answer is +0x000000001"), STR("answer is {:+#012x}"), true); + check(STR("answer is +00000000001"), STR("answer is {:+012X}"), true); + check(STR("answer is +0X000000001"), STR("answer is {:+#012X}"), true); + + check(STR("answer is 000000000000"), STR("answer is {:#012d}"), false); + check(STR("answer is 000000000000"), STR("answer is {:012b}"), false); + check(STR("answer is 0b0000000000"), STR("answer is {:#012b}"), false); + check(STR("answer is 0B0000000000"), STR("answer is {:#012B}"), false); + check(STR("answer is 000000000000"), STR("answer is {:012o}"), false); + check(STR("answer is 000000000000"), STR("answer is {:#012o}"), false); + check(STR("answer is 000000000000"), STR("answer is {:012x}"), false); + check(STR("answer is 0x0000000000"), STR("answer is {:#012x}"), false); + check(STR("answer is 000000000000"), STR("answer is {:012X}"), false); + check(STR("answer is 0X0000000000"), STR("answer is {:#012X}"), false); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.}"), true); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.0}"), true); + check_exception("The format-spec should consume the input or end with a '}'", + STR("{:.42}"), true); + + // *** locale-specific form *** + // See locale-specific_form.pass.cpp + + // *** type *** + for (const auto& fmt : invalid_types("bBcdosxX")) + check_exception( + "The format-spec type has a type not supported for a bool argument", + fmt, true); +} + template void format_test_integer_as_integer(TestFunction check, ExceptionTest check_exception) { @@ -743,8 +976,8 @@ void format_tests(TestFunction check, ExceptionTest check_exception) { check(STR("}"), STR("}}")); // *** Test argument ID *** - check(STR("hello 01"), STR("hello {0:}{1:}"), false, true); - check(STR("hello 10"), STR("hello {1:}{0:}"), false, true); + check(STR("hello false true"), STR("hello {0:} {1:}"), false, true); + check(STR("hello true false"), STR("hello {1:} {0:}"), false, true); // ** Test invalid format strings *** check_exception("The format string terminates at a '{'", STR("{")); @@ -799,7 +1032,11 @@ void format_tests(TestFunction check, ExceptionTest check_exception) { format_string_tests(check, check_exception); // *** Test Boolean format argument *** - check(STR("hello 01"), STR("hello {}{}"), false, true); + check(STR("hello false true"), STR("hello {} {}"), false, true); + + format_test_bool(check, check_exception); + format_test_bool_as_char(check, check_exception); + format_test_bool_as_integer(check, check_exception); // *** Test signed integral format argument *** check(STR("hello 42"), STR("hello {}"), static_cast(42)); diff --git a/libcxx/test/std/utilities/format/format.functions/format_to.locale.pass.cpp b/libcxx/test/std/utilities/format/format.functions/format_to.locale.pass.cpp index 9598bea7076da..461f541c73750 100644 --- a/libcxx/test/std/utilities/format/format.functions/format_to.locale.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/format_to.locale.pass.cpp @@ -56,8 +56,8 @@ auto test = [](std::basic_string expected, CharT out[4096]; CharT* it = std::format_to(out, std::locale(), fmt, args...); assert(std::distance(out, it) == int(expected.size())); - *it = '\0'; - assert(out == expected); + // Convert to std::string since output contains '\0' for boolean tests. + assert(std::basic_string(out, it) == expected); } }; diff --git a/libcxx/test/std/utilities/format/format.functions/format_to.pass.cpp b/libcxx/test/std/utilities/format/format.functions/format_to.pass.cpp index 795587cb4f993..fc988beaca97e 100644 --- a/libcxx/test/std/utilities/format/format.functions/format_to.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/format_to.pass.cpp @@ -57,8 +57,8 @@ auto test = [](std::basic_string expected, CharT out[4096]; CharT* it = std::format_to(out, fmt, args...); assert(std::distance(out, it) == int(expected.size())); - *it = '\0'; - assert(out == expected); + // Convert to std::string since output contains '\0' for boolean tests. + assert(std::basic_string(out, it) == expected); } }; 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 index b32f22a5020b2..eda372310ac8d 100644 --- 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 @@ -227,6 +227,56 @@ void test(std::basic_string expected, std::locale loc, } } +#ifndef _LIBCPP_HAS_NO_UNICODE +template +struct numpunct_unicode; + +template <> +struct numpunct_unicode : std::numpunct { + string_type do_truename() const override { return "gültig"; } + string_type do_falsename() const override { return "ungültig"; } +}; + +template <> +struct numpunct_unicode : std::numpunct { + string_type do_truename() const override { return L"gültig"; } + string_type do_falsename() const override { return L"ungültig"; } +}; +#endif + +template +void test_bool() { + std::locale loc = std::locale(std::locale(), new numpunct()); + + std::locale::global(std::locale(LOCALE_en_US_UTF_8)); + assert(std::locale().name() == LOCALE_en_US_UTF_8); + test(STR("true"), STR("{:L}"), true); + test(STR("false"), STR("{:L}"), false); + + test(STR("yes"), loc, STR("{:L}"), true); + test(STR("no"), loc, STR("{:L}"), false); + + std::locale::global(loc); + test(STR("yes"), STR("{:L}"), true); + test(STR("no"), STR("{:L}"), false); + + test(STR("true"), std::locale(LOCALE_en_US_UTF_8), STR("{:L}"), true); + test(STR("false"), std::locale(LOCALE_en_US_UTF_8), STR("{:L}"), false); + +#ifndef _LIBCPP_HAS_NO_UNICODE + std::locale loc_unicode = + std::locale(std::locale(), new numpunct_unicode()); + + test(STR("gültig"), loc_unicode, STR("{:L}"), true); + test(STR("ungültig"), loc_unicode, STR("{:L}"), false); + + test(STR("gültig "), loc_unicode, STR("{:9L}"), true); + test(STR("gültig!!!"), loc_unicode, STR("{:!<9L}"), true); + test(STR("_gültig__"), loc_unicode, STR("{:_^9L}"), true); + test(STR(" gültig"), loc_unicode, STR("{:>9L}"), true); +#endif +} + template void test_integer() { std::locale loc = std::locale(std::locale(), new numpunct()); @@ -557,6 +607,7 @@ void test_integer() { template void test() { + test_bool(); test_integer(); } diff --git a/libcxx/test/std/utilities/format/format.functions/vformat_to.locale.pass.cpp b/libcxx/test/std/utilities/format/format.functions/vformat_to.locale.pass.cpp index 6569934bec767..a006a2b686c08 100644 --- a/libcxx/test/std/utilities/format/format.functions/vformat_to.locale.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/vformat_to.locale.pass.cpp @@ -68,8 +68,8 @@ auto test = [](std::basic_string expected, std::make_format_args>( args...)); assert(std::distance(out, it) == int(expected.size())); - *it = '\0'; - assert(out == expected); + // Convert to std::string since output contains '\0' for boolean tests. + assert(std::basic_string(out, it) == expected); } }; diff --git a/libcxx/test/std/utilities/format/format.functions/vformat_to.pass.cpp b/libcxx/test/std/utilities/format/format.functions/vformat_to.pass.cpp index 179776a7f0760..1c243e68d493e 100644 --- a/libcxx/test/std/utilities/format/format.functions/vformat_to.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/vformat_to.pass.cpp @@ -71,8 +71,8 @@ auto test = [](std::basic_string expected, std::make_format_args>( args...)); assert(std::distance(out, it) == int(expected.size())); - *it = '\0'; - assert(out == expected); + // Convert to std::string since output contains '\0' for boolean tests. + assert(std::basic_string(out, it) == expected); } };