diff --git a/libcxx/docs/Cxx2aStatusPaperStatus.csv b/libcxx/docs/Cxx2aStatusPaperStatus.csv --- a/libcxx/docs/Cxx2aStatusPaperStatus.csv +++ b/libcxx/docs/Cxx2aStatusPaperStatus.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|","13.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 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -108,6 +108,7 @@ __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 --- /dev/null +++ b/libcxx/include/__format/formatter_bool.h @@ -0,0 +1,151 @@ +// -*- 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/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_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +// TODO FMT Remove this once we require compilers with proper C++20 support. +// If the compiler has no concepts support, the format header will be disabled. +// Without concepts support enable_if needs to be used and that too much effort +// to support compilers with partial C++20 support. +#if !defined(_LIBCPP_HAS_NO_CONCEPTS) && \ + !defined(_LIBCPP_HAS_NO_BUILTIN_IS_CONSTANT_EVALUATED) + +namespace __format_spec { + +template +class _LIBCPP_TEMPLATE_VIS __parser_bool : public __parser_integral<_CharT> { +public: + using _Base = __parser_integral<_CharT>; + + _LIBCPP_INLINE_VISIBILITY constexpr auto parse(auto& __parse_ctx) + -> decltype(__parse_ctx.begin()) { + auto __it = _Base::parse(__parse_ctx); + + 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_INLINE_VISIBILITY 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) && !defined(_LIBCPP_HAS_NO_BUILTIN_IS_CONSTANT_EVALUATED) + +#endif //_LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___FORMAT_FORMATTER_BOOL_H diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -283,6 +283,7 @@ #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> @@ -394,28 +395,6 @@ // 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_INLINE_VISIBILITY - auto parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) { - // TODO FMT Implement - return __parse_ctx.begin(); - } - - _LIBCPP_INLINE_VISIBILITY - 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. template struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter diff --git a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.bool.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.bool.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.bool.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.bool.pass.cpp @@ -54,8 +54,8 @@ static_assert(std::is_swappable_v); - test(STR("1"), STR("}"), true); - test(STR("0"), STR("}"), false); + test(STR("true"), STR("}"), true); + test(STR("false"), STR("}"), false); } int main(int, char**) { 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 --- 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 @@ -68,8 +68,8 @@ 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 --- a/libcxx/test/std/utilities/format/format.functions/format_to.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/format_to.pass.cpp @@ -69,8 +69,8 @@ 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 --- 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 @@ -226,6 +226,55 @@ } } +#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("yes"), loc, STR("{:L}"), true); + + 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); +#endif +} + template void test_integer() { std::locale loc = std::locale(std::locale(), new numpunct()); @@ -556,6 +605,7 @@ template void test() { + test_bool(); test_integer(); } 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 @@ -396,6 +396,246 @@ test_string(world, universe); test_string_unicode(); } +template +void test_bool() { + + // *** align-fill & width *** + test(STR("answer is 'true '"), STR("answer is '{:7}'"), true); + test(STR("answer is ' true'"), STR("answer is '{:>7}'"), true); + test(STR("answer is 'true '"), STR("answer is '{:<7}'"), true); + test(STR("answer is ' true '"), STR("answer is '{:^7}'"), true); + + test(STR("answer is 'false '"), STR("answer is '{:8s}'"), false); + test(STR("answer is ' false'"), STR("answer is '{:>8s}'"), false); + test(STR("answer is 'false '"), STR("answer is '{:<8s}'"), false); + test(STR("answer is ' false '"), STR("answer is '{:^8s}'"), false); + + test(STR("answer is '---true'"), STR("answer is '{:->7}'"), true); + test(STR("answer is 'true---'"), STR("answer is '{:-<7}'"), true); + test(STR("answer is '-true--'"), STR("answer is '{:-^7}'"), true); + + test(STR("answer is '---false'"), STR("answer is '{:->8s}'"), false); + test(STR("answer is 'false---'"), STR("answer is '{:-<8s}'"), false); + test(STR("answer is '-false--'"), STR("answer is '{:-^8s}'"), false); + + // *** Sign *** + test_exception("A sign field isn't allowed in this format-spec", STR("{:-}"), + true); + test_exception("A sign field isn't allowed in this format-spec", STR("{:+}"), + true); + test_exception("A sign field isn't allowed in this format-spec", STR("{: }"), + true); + + test_exception("A sign field isn't allowed in this format-spec", STR("{:-s}"), + true); + test_exception("A sign field isn't allowed in this format-spec", STR("{:+s}"), + true); + test_exception("A sign field isn't allowed in this format-spec", STR("{: s}"), + true); + + // *** alternate form *** + test_exception("An alternate form field isn't allowed in this format-spec", + STR("{:#}"), true); + test_exception("An alternate form field isn't allowed in this format-spec", + STR("{:#s}"), true); + + // *** zero-padding *** + test_exception("A zero-padding field isn't allowed in this format-spec", + STR("{:0}"), true); + test_exception("A zero-padding field isn't allowed in this format-spec", + STR("{:0s}"), true); + + // *** precision *** + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.}"), true); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.0}"), true); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.42}"), true); + + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.s}"), true); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.0s}"), true); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.42s}"), true); + + // *** locale-specific form *** + // See locale-specific_form.pass.cpp + + // *** type *** + for (const auto& fmt : invalid_types("bBcdosxX")) + test_exception( + "The format-spec type has a type not supported for a bool argument", + fmt, true); +} + +template +void test_bool_as_char() { + // *** align-fill & width *** + test(STR("answer is '\1 '"), STR("answer is '{:6c}'"), true); + test(STR("answer is ' \1'"), STR("answer is '{:>6c}'"), true); + test(STR("answer is '\1 '"), STR("answer is '{:<6c}'"), true); + test(STR("answer is ' \1 '"), STR("answer is '{:^6c}'"), true); + + test(STR("answer is '-----\1'"), STR("answer is '{:->6c}'"), true); + test(STR("answer is '\1-----'"), STR("answer is '{:-<6c}'"), true); + test(STR("answer is '--\1---'"), STR("answer is '{:-^6c}'"), true); + + if constexpr (std::same_as) { + // Helper to embed a \0 in the output string. + auto str = [](std::string r) { + std::replace(r.begin(), r.end(), '\1', '\0'); + return r; + }; + test(str("answer is '\1 '"), STR("answer is '{:6c}'"), false); + test(str("answer is ' \1'"), STR("answer is '{:>6c}'"), false); + test(str("answer is '\1 '"), STR("answer is '{:<6c}'"), false); + test(str("answer is ' \1 '"), STR("answer is '{:^6c}'"), false); + + test(str("answer is '-----\1'"), STR("answer is '{:->6c}'"), false); + test(str("answer is '\1-----'"), STR("answer is '{:-<6c}'"), false); + test(str("answer is '--\1---'"), STR("answer is '{:-^6c}'"), false); + } else if constexpr (std::same_as) { + auto str = [](std::wstring r) { + std::replace(r.begin(), r.end(), L'\1', L'\0'); + return r; + }; + test(str(L"answer is '\1 '"), STR("answer is '{:6c}'"), false); + test(str(L"answer is ' \1'"), STR("answer is '{:>6c}'"), false); + test(str(L"answer is '\1 '"), STR("answer is '{:<6c}'"), false); + test(str(L"answer is ' \1 '"), STR("answer is '{:^6c}'"), false); + + test(str(L"answer is '-----\1'"), STR("answer is '{:->6c}'"), false); + test(str(L"answer is '\1-----'"), STR("answer is '{:-<6c}'"), false); + test(str(L"answer is '--\1---'"), STR("answer is '{:-^6c}'"), false); + } + + // *** Sign *** + test_exception("A sign field isn't allowed in this format-spec", STR("{:-c}"), + true); + test_exception("A sign field isn't allowed in this format-spec", STR("{:+c}"), + true); + test_exception("A sign field isn't allowed in this format-spec", STR("{: c}"), + true); + + // *** alternate form *** + test_exception("An alternate form field isn't allowed in this format-spec", + STR("{:#c}"), true); + + // *** zero-padding *** + test_exception("A zero-padding field isn't allowed in this format-spec", + STR("{:0c}"), true); + + // *** precision *** + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.c}"), true); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.0c}"), true); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.42c}"), true); + + // *** locale-specific form *** + // Note it has no effect but it's allowed. + test(STR("answer is '*'"), STR("answer is '{:Lc}'"), '*'); + + // *** type *** + for (const auto& fmt : invalid_types("bBcdosxX")) + test_exception( + "The format-spec type has a type not supported for a bool argument", + fmt, true); +} + +template +void test_bool_as_integer() { + // *** align-fill & width *** + test(STR("answer is '1'"), STR("answer is '{:<1d}'"), true); + test(STR("answer is '1 '"), STR("answer is '{:<2d}'"), true); + test(STR("answer is '0 '"), STR("answer is '{:<2d}'"), false); + + test(STR("answer is ' 1'"), STR("answer is '{:6d}'"), true); + test(STR("answer is ' 1'"), STR("answer is '{:>6d}'"), true); + test(STR("answer is '1 '"), STR("answer is '{:<6d}'"), true); + test(STR("answer is ' 1 '"), STR("answer is '{:^6d}'"), true); + + test(STR("answer is '*****0'"), STR("answer is '{:*>6d}'"), false); + test(STR("answer is '0*****'"), STR("answer is '{:*<6d}'"), false); + test(STR("answer is '**0***'"), STR("answer is '{:*^6d}'"), false); + + // Test whether zero padding is ignored + test(STR("answer is ' 1'"), STR("answer is '{:>06d}'"), true); + test(STR("answer is '1 '"), STR("answer is '{:<06d}'"), true); + test(STR("answer is ' 1 '"), STR("answer is '{:^06d}'"), true); + + // *** Sign *** + test(STR("answer is 1"), STR("answer is {:d}"), true); + test(STR("answer is 0"), STR("answer is {:-d}"), false); + test(STR("answer is +1"), STR("answer is {:+d}"), true); + test(STR("answer is 0"), STR("answer is {: d}"), false); + + // *** alternate form *** + test(STR("answer is +1"), STR("answer is {:+#d}"), true); + test(STR("answer is +1"), STR("answer is {:+b}"), true); + test(STR("answer is +0b1"), STR("answer is {:+#b}"), true); + test(STR("answer is +0B1"), STR("answer is {:+#B}"), true); + test(STR("answer is +1"), STR("answer is {:+o}"), true); + test(STR("answer is +01"), STR("answer is {:+#o}"), true); + test(STR("answer is +1"), STR("answer is {:+x}"), true); + test(STR("answer is +0x1"), STR("answer is {:+#x}"), true); + test(STR("answer is +1"), STR("answer is {:+X}"), true); + test(STR("answer is +0X1"), STR("answer is {:+#X}"), true); + + test(STR("answer is 0"), STR("answer is {:#d}"), false); + test(STR("answer is 0"), STR("answer is {:b}"), false); + test(STR("answer is 0b0"), STR("answer is {:#b}"), false); + test(STR("answer is 0B0"), STR("answer is {:#B}"), false); + test(STR("answer is 0"), STR("answer is {:o}"), false); + test(STR("answer is 0"), STR("answer is {:#o}"), false); + test(STR("answer is 0"), STR("answer is {:x}"), false); + test(STR("answer is 0x0"), STR("answer is {:#x}"), false); + test(STR("answer is 0"), STR("answer is {:X}"), false); + test(STR("answer is 0X0"), STR("answer is {:#X}"), false); + + // *** zero-padding & width *** + test(STR("answer is +00000000001"), STR("answer is {:+#012d}"), true); + test(STR("answer is +00000000001"), STR("answer is {:+012b}"), true); + test(STR("answer is +0b000000001"), STR("answer is {:+#012b}"), true); + test(STR("answer is +0B000000001"), STR("answer is {:+#012B}"), true); + test(STR("answer is +00000000001"), STR("answer is {:+012o}"), true); + test(STR("answer is +00000000001"), STR("answer is {:+#012o}"), true); + test(STR("answer is +00000000001"), STR("answer is {:+012x}"), true); + test(STR("answer is +0x000000001"), STR("answer is {:+#012x}"), true); + test(STR("answer is +00000000001"), STR("answer is {:+012X}"), true); + test(STR("answer is +0X000000001"), STR("answer is {:+#012X}"), true); + + test(STR("answer is 000000000000"), STR("answer is {:#012d}"), false); + test(STR("answer is 000000000000"), STR("answer is {:012b}"), false); + test(STR("answer is 0b0000000000"), STR("answer is {:#012b}"), false); + test(STR("answer is 0B0000000000"), STR("answer is {:#012B}"), false); + test(STR("answer is 000000000000"), STR("answer is {:012o}"), false); + test(STR("answer is 000000000000"), STR("answer is {:#012o}"), false); + test(STR("answer is 000000000000"), STR("answer is {:012x}"), false); + test(STR("answer is 0x0000000000"), STR("answer is {:#012x}"), false); + test(STR("answer is 000000000000"), STR("answer is {:012X}"), false); + test(STR("answer is 0X0000000000"), STR("answer is {:#012X}"), false); + + // *** precision *** + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.}"), true); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.0}"), true); + test_exception("A precision field isn't allowed in this format-spec", + STR("{:.42}"), true); + + // *** locale-specific form *** + // See locale-specific_form.pass.cpp + + // *** type *** + for (const auto& fmt : invalid_types("bBcdosxX")) + test_exception( + "The format-spec type has a type not supported for a bool argument", + fmt, true); +} template void test_integer_as_integer() { @@ -728,8 +968,8 @@ test(STR("}"), STR("}}")); // *** Test argument ID *** - test(STR("hello 01"), STR("hello {0:}{1:}"), false, true); - test(STR("hello 10"), STR("hello {1:}{0:}"), false, true); + test(STR("hello false true"), STR("hello {0:} {1:}"), false, true); + test(STR("hello true false"), STR("hello {1:} {0:}"), false, true); // ** Test invalid format strings *** test_exception("The format string terminates while parsing a replacement " @@ -788,7 +1028,11 @@ test_string(); // *** Test Boolean format argument *** - test(STR("hello 01"), STR("hello {}{}"), false, true); + test(STR("hello false true"), STR("hello {} {}"), false, true); + + test_bool(); + test_bool_as_char(); + test_bool_as_integer(); // *** Test signed integral format argument *** test(STR("hello 42"), STR("hello {}"), static_cast(42)); 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 --- 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 @@ -80,8 +80,8 @@ 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 --- a/libcxx/test/std/utilities/format/format.functions/vformat_to.pass.cpp +++ b/libcxx/test/std/utilities/format/format.functions/vformat_to.pass.cpp @@ -83,8 +83,8 @@ 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.string/format.string.std/std_format_spec_bool.pass.cpp b/libcxx/test/std/utilities/format/format.string/format.string.std/std_format_spec_bool.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.string/format.string.std/std_format_spec_bool.pass.cpp @@ -0,0 +1,366 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-no-concepts + +// + +// Tests the parsing of the format string as specified in [format.string.std]. +// It validates whether the std-format-spec is valid for a boolean type. + +#include +#include +#ifndef _LIBCPP_HAS_NO_LOCALIZATION +#include +#endif + +#include "concepts_precision.h" +#include "test_macros.h" +#include "make_string.h" + +#define CSTR(S) MAKE_CSTRING(CharT, S) + +using namespace std::__format_spec; + +template +using parser = __parser_bool; + +#include "test_exception.h" // requires parser + +template +struct Expected { + CharT fill; + _Flags::_Alignment alignment; + _Flags::_Sign sign; + bool alternate_form; + bool zero_padding; + uint32_t width; + bool width_as_arg; + bool locale_specific_form; + _Flags::_Type type; +}; + +template +constexpr void test(Expected expected, size_t size, + std::basic_string_view fmt) { + // Initialize parser with sufficient arguments to avoid the parsing to fail + // due to insufficient arguments. + std::basic_format_parse_context parse_ctx(fmt, + std::__format::__number_max); + auto begin = parse_ctx.begin(); + auto end = parse_ctx.end(); + parser parser; + auto it = parser.parse(parse_ctx); + + assert(begin == parse_ctx.begin()); + assert(end == parse_ctx.end()); + + assert(begin + size == it); + assert(parser.__fill == expected.fill); + assert(parser.__alignment == expected.alignment); + assert(parser.__sign == expected.sign); + assert(parser.__alternate_form == expected.alternate_form); + assert(parser.__zero_padding == expected.zero_padding); + assert(parser.__width == expected.width); + assert(parser.__width_as_arg == expected.width_as_arg); + assert(parser.__locale_specific_form == expected.locale_specific_form); + assert(parser.__type == expected.type); +} + +template +constexpr void test(Expected expected, size_t size, const CharT* f) { + // The format-spec is valid if completely consumed or terminates at a '}'. + // The valid inputs all end with a '}'. The test is executed twice: + // - first with the terminating '}', + // - second consuming the entire input. + std::basic_string_view fmt{f}; + assert(fmt.back() == CharT('}') && "Pre-condition failure"); + + test(expected, size, fmt); + fmt.remove_suffix(1); + test(expected, size, fmt); +} + +template +constexpr void test_as_string() { + // *** Align-fill *** + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__string}, + 0, CSTR("}")); + test({CharT('-'), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__string}, + 2, CSTR("-<}")); + test({CharT(' '), _Flags::_Alignment::__center, _Flags::_Sign::__default, + false, false, 0, false, false, _Flags::_Type::__string}, + 1, CSTR("^}")); + test({CharT('+'), _Flags::_Alignment::__right, _Flags::_Sign::__default, + false, false, 0, false, false, _Flags::_Type::__string}, + 2, CSTR("+>}")); + + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__string}, + 1, CSTR("s}")); + test({CharT('-'), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__string}, + 3, CSTR("-s}")); + + // *** 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({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 1, false, false, _Flags::_Type::__string}, + 1, CSTR("1}")); + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 42, false, false, _Flags::_Type::__string}, + 2, CSTR("42}")); + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 2'147'483'647, false, false, _Flags::_Type::__string}, + 10, CSTR("2147483647}")); + + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 1, false, false, _Flags::_Type::__string}, + 2, CSTR("1s}")); + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 42, false, false, _Flags::_Type::__string}, + 3, CSTR("42s}")); + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 2'147'483'647, false, false, _Flags::_Type::__string}, + 11, CSTR("2147483647s}")); + + // *** Precision *** + test_exception("A precision field isn't allowed in this format-spec", + CSTR(".")); + test_exception("A precision field isn't allowed in this format-spec", + CSTR(".1")); + + // *** Locale-specific form *** + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, true, _Flags::_Type::__string}, + 1, CSTR("L}")); + + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, true, _Flags::_Type::__string}, + 2, CSTR("Ls}")); +} + +template +constexpr void test_as_char() { + // *** Align-fill *** + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__char}, + 1, CSTR("c}")); + test({CharT('-'), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__char}, + 3, CSTR("-c}")); + + // *** 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({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 1, false, false, _Flags::_Type::__char}, + 2, CSTR("1c}")); + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 42, false, false, _Flags::_Type::__char}, + 3, CSTR("42c}")); + static_assert(std::__format::__number_max == 2'147'483'647, + "Update the assert and the test."); + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 2'147'483'647, false, false, _Flags::_Type::__char}, + 11, CSTR("2147483647c}")); + + // *** Precision *** + test_exception("A precision field isn't allowed in this format-spec", + CSTR(".")); + test_exception("A precision field isn't allowed in this format-spec", + CSTR(".1")); + + // *** Locale-specific form *** + test({CharT(' '), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, true, _Flags::_Type::__char}, + 2, CSTR("Lc}")); +} + +template +constexpr void test_as_integer() { + // *** Align-fill *** + test({CharT(' '), _Flags::_Alignment::__right, _Flags::_Sign::__default, + false, false, 0, false, false, _Flags::_Type::__decimal}, + 1, CSTR("d}")); + test({CharT('-'), _Flags::_Alignment::__left, _Flags::_Sign::__default, false, + false, 0, false, false, _Flags::_Type::__decimal}, + 3, CSTR("- +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_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(); +#ifndef _LIBCPP_HAS_NO_CHAR8_T + test(); +#endif +#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS + test(); + test(); +#endif + return true; +} + +int main(int, char**) { +#ifndef _WIN32 + // Make sure the parsers match the expectations. The layout of the + // subobjects is chosen to minimize the size required. + static_assert(sizeof(parser) == 2 * sizeof(uint32_t)); + static_assert( + sizeof(parser) == + (sizeof(wchar_t) <= 2 ? 2 * sizeof(uint32_t) : 3 * sizeof(uint32_t))); +#ifndef _LIBCPP_HAS_NO_CHAR8_T + static_assert(sizeof(parser) == 2 * sizeof(uint32_t)); +#endif +#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS + static_assert(sizeof(parser) == 2 * sizeof(uint32_t)); + static_assert(sizeof(parser) == 3 * sizeof(uint32_t)); +#endif +#endif + + test(); + static_assert(test()); + + return 0; +}