diff --git a/libcxx/docs/Status/FormatPaper.csv b/libcxx/docs/Status/FormatPaper.csv --- a/libcxx/docs/Status/FormatPaper.csv +++ b/libcxx/docs/Status/FormatPaper.csv @@ -32,6 +32,6 @@ `[format.string.std] `_,"std-format-spec ``type`` debug",,Mark de Wever,|Complete|,Clang 16 `[format.range] `_,"Formatting for ranges: sequences",,Mark de Wever,|Complete|,Clang 16 `[format.range] `_,"Formatting for ranges: associative",,Mark de Wever,, -`[format.range] `_,"Formatting for ranges: container adaptors",,Mark de Wever,, +`[format.range] `_,"Formatting for ranges: container adaptors",,Mark de Wever,|Complete|,Clang 16 `[format.range] `_,"Formatting for ranges: ``pair`` and ``tuple``",,Mark de Wever,|Complete|,Clang 16 `[format.range] `_,"Formatting for ranges: ``vector``",,Mark de Wever,, diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -302,6 +302,7 @@ __filesystem/u8path.h __format/buffer.h __format/concepts.h + __format/container_adaptor.h __format/enable_insertable.h __format/escaped_output_table.h __format/extended_grapheme_cluster_table.h diff --git a/libcxx/include/__format/container_adaptor.h b/libcxx/include/__format/container_adaptor.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__format/container_adaptor.h @@ -0,0 +1,70 @@ +// -*- 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_CONTAINER_ADAPTOR_H +#define _LIBCPP___FORMAT_CONTAINER_ADAPTOR_H + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +#include <__availability> +#include <__config> +#include <__format/concepts.h> +#include <__format/formatter.h> +#include <__format/range_default_formatter.h> +#include +#include + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 20 + +// [container.adaptors.format] only specifies the library should provide the +// formatter specializations, not which header should provide them. +// Since includes a lot of headers, add these headers here instead of +// adding more dependencies like, locale, optinal, string, tuple, etc. to the +// adaptor headers. To use the format functions users already include . + +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT __formatter_container_adaptor { +private: + using __maybe_const_adaptor = __fmt_maybe_const<_Adaptor, _CharT>; + formatter __underlying_; + +public: + template + _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) { + return __underlying_.parse(__ctx); + } + + template + _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator + format(__maybe_const_adaptor& __adaptor, _FormatContext& __ctx) const { + return __underlying_.format(__adaptor.__get_container(), __ctx); + } +}; + +template _Container> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter, _CharT> + : public __formatter_container_adaptor, _CharT> {}; + +template +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter, _CharT> + : public __formatter_container_adaptor, _CharT> {}; + +template _Container> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter, _CharT> + : public __formatter_container_adaptor, _CharT> {}; + +#endif //_LIBCPP_STD_VER > 20 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___FORMAT_CONTAINER_ADAPTOR_H diff --git a/libcxx/include/__format/range_default_formatter.h b/libcxx/include/__format/range_default_formatter.h --- a/libcxx/include/__format/range_default_formatter.h +++ b/libcxx/include/__format/range_default_formatter.h @@ -146,8 +146,6 @@ __range_default_formatter() = delete; // TODO FMT Implement }; -// Dispatcher to select the specialization based on the type of the range. - template requires(format_kind<_Rp> != range_format::disabled && formattable, _CharT>) struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<_Rp, _CharT> diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -178,6 +178,7 @@ #include <__config> #include <__format/buffer.h> #include <__format/concepts.h> +#include <__format/container_adaptor.h> #include <__format/enable_insertable.h> #include <__format/format_arg.h> #include <__format/format_arg_store.h> diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -839,6 +839,7 @@ module __format { module buffer { private header "__format/buffer.h" } module concepts { private header "__format/concepts.h" } + module container_adaptor { private header "__format/container_adaptor.h" } module enable_insertable { private header "__format/enable_insertable.h" } module escaped_output_table { private header "__format/escaped_output_table.h" } module extended_grapheme_cluster_table { private header "__format/extended_grapheme_cluster_table.h" } diff --git a/libcxx/include/queue b/libcxx/include/queue --- a/libcxx/include/queue +++ b/libcxx/include/queue @@ -382,6 +382,8 @@ swap(c, __q.c); } + _LIBCPP_NODISCARD _LIBCPP_HIDE_FROM_ABI const _Container& __get_container() const { return c; } + template friend _LIBCPP_INLINE_VISIBILITY @@ -633,6 +635,8 @@ void swap(priority_queue& __q) _NOEXCEPT_(__is_nothrow_swappable::value && __is_nothrow_swappable::value); + + _LIBCPP_NODISCARD _LIBCPP_HIDE_FROM_ABI const _Container& __get_container() const { return c; } }; #if _LIBCPP_STD_VER >= 17 diff --git a/libcxx/include/stack b/libcxx/include/stack --- a/libcxx/include/stack +++ b/libcxx/include/stack @@ -255,6 +255,8 @@ swap(c, __s.c); } + _LIBCPP_NODISCARD _LIBCPP_HIDE_FROM_ABI const _Container& __get_container() const { return c; } + template friend bool diff --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp --- a/libcxx/test/libcxx/private_headers.verify.cpp +++ b/libcxx/test/libcxx/private_headers.verify.cpp @@ -334,6 +334,7 @@ #include <__filesystem/u8path.h> // expected-error@*:* {{use of private header from outside its module: '__filesystem/u8path.h'}} #include <__format/buffer.h> // expected-error@*:* {{use of private header from outside its module: '__format/buffer.h'}} #include <__format/concepts.h> // expected-error@*:* {{use of private header from outside its module: '__format/concepts.h'}} +#include <__format/container_adaptor.h> // expected-error@*:* {{use of private header from outside its module: '__format/container_adaptor.h'}} #include <__format/enable_insertable.h> // expected-error@*:* {{use of private header from outside its module: '__format/enable_insertable.h'}} #include <__format/escaped_output_table.h> // expected-error@*:* {{use of private header from outside its module: '__format/escaped_output_table.h'}} #include <__format/extended_grapheme_cluster_table.h> // expected-error@*:* {{use of private header from outside its module: '__format/extended_grapheme_cluster_table.h'}} diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv --- a/libcxx/test/libcxx/transitive_includes/cxx03.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv @@ -354,6 +354,8 @@ format limits format locale format optional +format queue +format stack format stdexcept format string format string_view diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv --- a/libcxx/test/libcxx/transitive_includes/cxx11.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv @@ -354,6 +354,8 @@ format limits format locale format optional +format queue +format stack format stdexcept format string format string_view diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv --- a/libcxx/test/libcxx/transitive_includes/cxx14.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv @@ -356,6 +356,8 @@ format limits format locale format optional +format queue +format stack format stdexcept format string format string_view diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv --- a/libcxx/test/libcxx/transitive_includes/cxx17.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv @@ -356,6 +356,8 @@ format limits format locale format optional +format queue +format stack format stdexcept format string format string_view diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv --- a/libcxx/test/libcxx/transitive_includes/cxx20.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv @@ -365,6 +365,8 @@ format limits format locale format optional +format queue +format stack format stdexcept format string format string_view diff --git a/libcxx/test/libcxx/transitive_includes/cxx2b.csv b/libcxx/test/libcxx/transitive_includes/cxx2b.csv --- a/libcxx/test/libcxx/transitive_includes/cxx2b.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx2b.csv @@ -273,6 +273,8 @@ format limits format locale format optional +format queue +format stack format stdexcept format string format string_view diff --git a/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.functions.format.pass.cpp b/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.functions.format.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.functions.format.pass.cpp @@ -0,0 +1,60 @@ +//===----------------------------------------------------------------------===// +// 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, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// TODO FMT Fix this test using GCC, it currently times out. +// UNSUPPORTED: gcc-12 + +// This test requires the dylib support introduced in D92214. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}} +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}} + +// [container.adaptors.format] +// For each of queue, priority_queue, and stack, the library provides the +// following formatter specialization where adaptor-type is the name of the +// template: +// +// template Container, class... U> +// struct formatter, charT> + +// template +// string format(format_string fmt, Args&&... args); +// template +// wstring format(wformat_string fmt, Args&&... args); + +#include +#include + +#include "format.functions.tests.h" +#include "test_format_string.h" +#include "test_macros.h" +#include "assert_macros.h" + +auto test = []( + std::basic_string_view expected, test_format_string fmt, Args&&... args) { + std::basic_string out = std::format(fmt, std::forward(args)...); + TEST_REQUIRE( + out == expected, + test_concat_message("\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = [](std::string_view, std::basic_string_view, Args&&...) { + // After P2216 most exceptions thrown by std::format become ill-formed. + // Therefore this tests does nothing. +}; + +int main(int, char**) { + format_tests(test, test_exception); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + format_tests(test, test_exception); +#endif + + return 0; +} diff --git a/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.functions.tests.h b/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.functions.tests.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.functions.tests.h @@ -0,0 +1,947 @@ +//===----------------------------------------------------------------------===// +// 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 TEST_STD_CONTAINERS_CONTAINER_ADAPTORS_CONTAINER_ADAPTORS_FORMAT_FORMAT_FUNCTIONS_TESTS_H +#define TEST_STD_CONTAINERS_CONTAINER_ADAPTORS_CONTAINER_ADAPTORS_FORMAT_FORMAT_FUNCTIONS_TESTS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "format.functions.common.h" +#include "make_string.h" +#include "platform_support.h" // locale name macros +#include "test_macros.h" + +// +// Char +// + +template +void test_char_default(TestFunction check, ExceptionTest check_exception, auto&& input) { + // Note when no range-underlying-spec is present the char is escaped, + check(SV("['H', 'e', 'l', 'l', 'o']"), SV("{}"), input); + + // when one is present there is no escaping, + check(SV("[H, e, l, l, o]"), SV("{::}"), input); + // unless forced by the type specifier. + check(SV("['H', 'e', 'l', 'l', 'o']"), SV("{::?}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("['H', 'e', 'l', 'l', 'o'] "), SV("{:30}"), input); + check(SV("['H', 'e', 'l', 'l', 'o']*****"), SV("{:*<30}"), input); + check(SV("__['H', 'e', 'l', 'l', 'o']___"), SV("{:_^30}"), input); + check(SV("#####['H', 'e', 'l', 'l', 'o']"), SV("{:#>30}"), input); + + check(SV("['H', 'e', 'l', 'l', 'o'] "), SV("{:{}}"), input, 30); + check(SV("['H', 'e', 'l', 'l', 'o']*****"), SV("{:*<{}}"), input, 30); + check(SV("__['H', 'e', 'l', 'l', 'o']___"), SV("{:_^{}}"), input, 30); + check(SV("#####['H', 'e', 'l', 'l', 'o']"), SV("{:#>{}}"), input, 30); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__'H', 'e', 'l', 'l', 'o'___"), SV("{:_^28n}"), input); + + // *** type *** + check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input); + for (std::basic_string_view fmt : fmt_invalid_types("s")) + check_exception("The format-spec should consume the input or end with a '}'", fmt, input); + + // ***** Only underlying has a format-spec + check(SV("[H , e , l , l , o ]"), SV("{::4}"), input); + check(SV("[H***, e***, l***, l***, o***]"), SV("{::*<4}"), input); + check(SV("[_H__, _e__, _l__, _l__, _o__]"), SV("{::_^4}"), input); + check(SV("[:::H, :::e, :::l, :::l, :::o]"), SV("{:::>4}"), input); + + check(SV("[H , e , l , l , o ]"), SV("{::{}}"), input, 4); + check(SV("[H***, e***, l***, l***, o***]"), SV("{::*<{}}"), input, 4); + check(SV("[_H__, _e__, _l__, _l__, _o__]"), SV("{::_^{}}"), input, 4); + check(SV("[:::H, :::e, :::l, :::l, :::o]"), SV("{:::>{}}"), input, 4); + + check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input); + check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input); + + // *** sign *** + check_exception("A sign field isn't allowed in this format-spec", SV("{::-}"), input); + check_exception("A sign field isn't allowed in this format-spec", SV("{::+}"), input); + check_exception("A sign field isn't allowed in this format-spec", SV("{:: }"), input); + + check(SV("[72, 101, 108, 108, 111]"), SV("{::-d}"), input); + check(SV("[+72, +101, +108, +108, +111]"), SV("{::+d}"), input); + check(SV("[ 72, 101, 108, 108, 111]"), SV("{:: d}"), input); + + // *** alternate form *** + check_exception("An alternate form field isn't allowed in this format-spec", SV("{::#}"), input); + + check(SV("[0x48, 0x65, 0x6c, 0x6c, 0x6f]"), SV("{::#x}"), input); + + // *** zero-padding *** + check_exception("A zero-padding field isn't allowed in this format-spec", SV("{::05}"), input); + + check(SV("[00110, 00145, 00154, 00154, 00157]"), SV("{::05o}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[H, e, l, l, o]"), SV("{::L}"), input); + + // *** type *** + for (std::basic_string_view fmt : fmt_invalid_nested_types("bBcdoxX?")) + check_exception("The format-spec type has a type not supported for a char argument", fmt, input); + + // ***** Both have a format-spec + check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^25::>2}"), input); + check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^{}::>2}"), input, 25); + check(SV("^^[:H, :e, :l, :l, :o]^^^"), SV("{:^^{}::>{}}"), input, 25, 2); + + check_exception("Argument index out of bounds", SV("{:^^{}::>2}"), input); + check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 25); +} + +template +void test_char_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("Hello"), SV("{:s}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("Hello "), SV("{:8s}"), input); + check(SV("Hello***"), SV("{:*<8s}"), input); + check(SV("_Hello__"), SV("{:_^8s}"), input); + check(SV("###Hello"), SV("{:#>8s}"), input); + + check(SV("Hello "), SV("{:{}s}"), input, 8); + check(SV("Hello***"), SV("{:*<{}s}"), input, 8); + check(SV("_Hello__"), SV("{:_^{}s}"), input, 8); + check(SV("###Hello"), SV("{:#>{}s}"), input, 8); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:} fmt : fmt_invalid_nested_types("bBcdoxX?")) + check_exception("The format-spec type has a type not supported for a char argument", fmt, input); + + // ***** Both have a format-spec + check_exception("Type s and an underlying format specification can't be used together", SV("{:5s:5}"), input); +} + +template +void test_char_escaped_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV(R"("Hello")"), SV("{:?s}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV(R"("Hello" )"), SV("{:10?s}"), input); + check(SV(R"("Hello"***)"), SV("{:*<10?s}"), input); + check(SV(R"(_"Hello"__)"), SV("{:_^10?s}"), input); + check(SV(R"(###"Hello")"), SV("{:#>10?s}"), input); + + check(SV(R"("Hello" )"), SV("{:{}?s}"), input, 10); + check(SV(R"("Hello"***)"), SV("{:*<{}?s}"), input, 10); + check(SV(R"(_"Hello"__)"), SV("{:_^{}?s}"), input, 10); + check(SV(R"(###"Hello")"), SV("{:#>{}?s}"), input, 10); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:} +void test_char(TestFunction check, ExceptionTest check_exception) { + // These values are in numeric order when using ASCII, which is used by the priority_queue. + std::array input{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}; + test_char_default(check, check_exception, std::queue{input.begin(), input.end()}); + test_char_default(check, check_exception, std::priority_queue{input.begin(), input.end(), std::greater{}}); + test_char_default(check, check_exception, std::stack{input.begin(), input.end()}); + + test_char_string(check, check_exception, std::queue{input.begin(), input.end()}); + test_char_string(check, check_exception, std::priority_queue{input.begin(), input.end(), std::greater{}}); + test_char_string(check, check_exception, std::stack{input.begin(), input.end()}); + + test_char_escaped_string(check, check_exception, std::queue{input.begin(), input.end()}); + test_char_escaped_string( + check, check_exception, std::priority_queue{input.begin(), input.end(), std::greater{}}); + test_char_escaped_string(check, check_exception, std::stack{input.begin(), input.end()}); +} + +// +// char -> wchar_t +// + +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +template +void test_char_to_wchar(TestFunction check, ExceptionTest check_exception) { + std::array input{'H', 'e', 'l', 'l', 'o'}; + test_char_default(check, check_exception, std::queue{input.begin(), input.end()}); + test_char_default(check, check_exception, std::priority_queue{input.begin(), input.end(), std::greater{}}); + test_char_default(check, check_exception, std::stack{input.begin(), input.end()}); + + // The types s and ?s may only be used when using range_formatter + // where the types T and charT are the same. This means this can't be used for + // range_formatter even when formatter has a + // debug-enabled specialization. + + using CharT = wchar_t; + check_exception("The range-format-spec type s requires formatting a character type", + SV("{:s}"), + std::queue{input.begin(), input.end()}); + check_exception("The range-format-spec type s requires formatting a character type", + SV("{:s}"), + std::priority_queue{input.begin(), input.end()}); + check_exception("The range-format-spec type s requires formatting a character type", + SV("{:s}"), + std::stack{input.begin(), input.end()}); + check_exception("The range-format-spec type ?s requires formatting a character type", + SV("{:?s}"), + std::queue{input.begin(), input.end()}); + check_exception("The range-format-spec type ?s requires formatting a character type", + SV("{:?s}"), + std::priority_queue{input.begin(), input.end()}); + check_exception("The range-format-spec type ?s requires formatting a character type", + SV("{:?s}"), + std::stack{input.begin(), input.end()}); +} +#endif // _LIBCPP_HAS_NO_WIDE_CHARACTERS + +// +// Bool +// + +template +void test_bool(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[true, true, false]"), SV("{}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[true, true, false] "), SV("{:24}"), input); + check(SV("[true, true, false]*****"), SV("{:*<24}"), input); + check(SV("__[true, true, false]___"), SV("{:_^24}"), input); + check(SV("#####[true, true, false]"), SV("{:#>24}"), input); + + check(SV("[true, true, false] "), SV("{:{}}"), input, 24); + check(SV("[true, true, false]*****"), SV("{:*<{}}"), input, 24); + check(SV("__[true, true, false]___"), SV("{:_^{}}"), input, 24); + check(SV("#####[true, true, false]"), SV("{:#>{}}"), input, 24); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__true, true, false___"), SV("{:_^22n}"), input); + + // *** type *** + check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input); + check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input); + check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input); + for (std::basic_string_view fmt : fmt_invalid_types("s")) + check_exception("The format-spec should consume the input or end with a '}'", fmt, input); + + // ***** Only underlying has a format-spec + check(SV("[true , true , false ]"), SV("{::7}"), input); + check(SV("[true***, true***, false**]"), SV("{::*<7}"), input); + check(SV("[_true__, _true__, _false_]"), SV("{::_^7}"), input); + check(SV("[:::true, :::true, ::false]"), SV("{:::>7}"), input); + + check(SV("[true , true , false ]"), SV("{::{}}"), input, 7); + check(SV("[true***, true***, false**]"), SV("{::*<{}}"), input, 7); + check(SV("[_true__, _true__, _false_]"), SV("{::_^{}}"), input, 7); + check(SV("[:::true, :::true, ::false]"), SV("{:::>{}}"), input, 7); + + check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input); + check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input); + + // *** sign *** + check_exception("A sign field isn't allowed in this format-spec", SV("{::-}"), input); + check_exception("A sign field isn't allowed in this format-spec", SV("{::+}"), input); + check_exception("A sign field isn't allowed in this format-spec", SV("{:: }"), input); + + check(SV("[1, 1, 0]"), SV("{::-d}"), input); + check(SV("[+1, +1, +0]"), SV("{::+d}"), input); + check(SV("[ 1, 1, 0]"), SV("{:: d}"), input); + + // *** alternate form *** + check_exception("An alternate form field isn't allowed in this format-spec", SV("{::#}"), input); + + check(SV("[0x1, 0x1, 0x0]"), SV("{::#x}"), input); + + // *** zero-padding *** + check_exception("A zero-padding field isn't allowed in this format-spec", SV("{::05}"), input); + + check(SV("[00001, 00001, 00000]"), SV("{::05o}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[true, true, false]"), SV("{::L}"), input); + + // *** type *** + for (std::basic_string_view fmt : fmt_invalid_nested_types("bBdosxX")) + check_exception("The format-spec type has a type not supported for a bool argument", fmt, input); + + // ***** Both have a format-spec + check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^32::>7}"), input); + check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^{}::>7}"), input, 32); + check(SV("^^[:::true, :::true, ::false]^^^"), SV("{:^^{}::>{}}"), input, 32, 7); + + check_exception("Argument index out of bounds", SV("{:^^{}::>5}"), input); + check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 32); +} + +template +void test_bool(TestFunction check, ExceptionTest check_exception) { + std::array input{true, true, false}; + test_bool(check, check_exception, std::queue{input.begin(), input.end()}); + // TODO FMT Use std::vector after it has been implemented. + test_bool(check, check_exception, std::priority_queue>{input.begin(), input.end()}); + test_bool(check, check_exception, std::stack{input.begin(), input.end()}); +} + +// +// Integral +// + +template +void test_int(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[-42, 1, 2, 42]"), SV("{}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[-42, 1, 2, 42] "), SV("{:20}"), input); + check(SV("[-42, 1, 2, 42]*****"), SV("{:*<20}"), input); + check(SV("__[-42, 1, 2, 42]___"), SV("{:_^20}"), input); + check(SV("#####[-42, 1, 2, 42]"), SV("{:#>20}"), input); + + check(SV("[-42, 1, 2, 42] "), SV("{:{}}"), input, 20); + check(SV("[-42, 1, 2, 42]*****"), SV("{:*<{}}"), input, 20); + check(SV("__[-42, 1, 2, 42]___"), SV("{:_^{}}"), input, 20); + check(SV("#####[-42, 1, 2, 42]"), SV("{:#>{}}"), input, 20); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__-42, 1, 2, 42___"), SV("{:_^18n}"), input); + + // *** type *** + check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input); + check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input); + check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input); + for (std::basic_string_view fmt : fmt_invalid_types("s")) + check_exception("The format-spec should consume the input or end with a '}'", fmt, input); + + // ***** Only underlying has a format-spec + check(SV("[ -42, 1, 2, 42]"), SV("{::5}"), input); + check(SV("[-42**, 1****, 2****, 42***]"), SV("{::*<5}"), input); + check(SV("[_-42_, __1__, __2__, _42__]"), SV("{::_^5}"), input); + check(SV("[::-42, ::::1, ::::2, :::42]"), SV("{:::>5}"), input); + + check(SV("[ -42, 1, 2, 42]"), SV("{::{}}"), input, 5); + check(SV("[-42**, 1****, 2****, 42***]"), SV("{::*<{}}"), input, 5); + check(SV("[_-42_, __1__, __2__, _42__]"), SV("{::_^{}}"), input, 5); + check(SV("[::-42, ::::1, ::::2, :::42]"), SV("{:::>{}}"), input, 5); + + check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input); + check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input); + + // *** sign *** + check(SV("[-42, 1, 2, 42]"), SV("{::-}"), input); + check(SV("[-42, +1, +2, +42]"), SV("{::+}"), input); + check(SV("[-42, 1, 2, 42]"), SV("{:: }"), input); + + // *** alternate form *** + check(SV("[-0x2a, 0x1, 0x2, 0x2a]"), SV("{::#x}"), input); + + // *** zero-padding *** + check(SV("[-0042, 00001, 00002, 00042]"), SV("{::05}"), input); + check(SV("[-002a, 00001, 00002, 0002a]"), SV("{::05x}"), input); + check(SV("[-0x2a, 0x001, 0x002, 0x02a]"), SV("{::#05x}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[-42, 1, 2, 42]"), SV("{::L}"), input); // does nothing in this test, but is accepted. + + // *** type *** + for (std::basic_string_view fmt : fmt_invalid_nested_types("bBcdoxX")) + check_exception("The format-spec type has a type not supported for an integer argument", fmt, input); + + // ***** Both have a format-spec + check(SV("^^[::-42, ::::1, ::::2, :::42]^^^"), SV("{:^^33::>5}"), input); + check(SV("^^[::-42, ::::1, ::::2, :::42]^^^"), SV("{:^^{}::>5}"), input, 33); + check(SV("^^[::-42, ::::1, ::::2, :::42]^^^"), SV("{:^^{}::>{}}"), input, 33, 5); + + check_exception("Argument index out of bounds", SV("{:^^{}::>5}"), input); + check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 33); +} + +template +void test_int(TestFunction check, ExceptionTest check_exception) { + std::array input{-42, 1, 2, 42}; + test_int(check, check_exception, std::queue{input.begin(), input.end()}); + test_int(check, check_exception, std::priority_queue{input.begin(), input.end(), std::greater{}}); + test_int(check, check_exception, std::stack{input.begin(), input.end()}); +} + +// +// Floating point +// + +template +void test_floating_point(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[-42.5, 0, 1.25, 42.5] "), SV("{:27}"), input); + check(SV("[-42.5, 0, 1.25, 42.5]*****"), SV("{:*<27}"), input); + check(SV("__[-42.5, 0, 1.25, 42.5]___"), SV("{:_^27}"), input); + check(SV("#####[-42.5, 0, 1.25, 42.5]"), SV("{:#>27}"), input); + + check(SV("[-42.5, 0, 1.25, 42.5] "), SV("{:{}}"), input, 27); + check(SV("[-42.5, 0, 1.25, 42.5]*****"), SV("{:*<{}}"), input, 27); + check(SV("__[-42.5, 0, 1.25, 42.5]___"), SV("{:_^{}}"), input, 27); + check(SV("#####[-42.5, 0, 1.25, 42.5]"), SV("{:#>{}}"), input, 27); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__-42.5, 0, 1.25, 42.5___"), SV("{:_^25n}"), input); + + // *** type *** + check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input); + check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input); + check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input); + for (std::basic_string_view fmt : fmt_invalid_types("s")) + check_exception("The format-spec should consume the input or end with a '}'", fmt, input); + + // ***** Only underlying has a format-spec + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::5}"), input); + check(SV("[-42.5, 0****, 1.25*, 42.5*]"), SV("{::*<5}"), input); + check(SV("[-42.5, __0__, 1.25_, 42.5_]"), SV("{::_^5}"), input); + check(SV("[-42.5, ::::0, :1.25, :42.5]"), SV("{:::>5}"), input); + + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::{}}"), input, 5); + check(SV("[-42.5, 0****, 1.25*, 42.5*]"), SV("{::*<{}}"), input, 5); + check(SV("[-42.5, __0__, 1.25_, 42.5_]"), SV("{::_^{}}"), input, 5); + check(SV("[-42.5, ::::0, :1.25, :42.5]"), SV("{:::>{}}"), input, 5); + + check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input); + check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input); + + // *** sign *** + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::-}"), input); + check(SV("[-42.5, +0, +1.25, +42.5]"), SV("{::+}"), input); + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{:: }"), input); + + // *** alternate form *** + check(SV("[-42.5, 0., 1.25, 42.5]"), SV("{::#}"), input); + + // *** zero-padding *** + check(SV("[-42.5, 00000, 01.25, 042.5]"), SV("{::05}"), input); + check(SV("[-42.5, 0000., 01.25, 042.5]"), SV("{::#05}"), input); + + // *** precision *** + check(SV("[-42, 0, 1.2, 42]"), SV("{::.2}"), input); + check(SV("[-42.500, 0.000, 1.250, 42.500]"), SV("{::.3f}"), input); + + check(SV("[-42, 0, 1.2, 42]"), SV("{::.{}}"), input, 2); + check(SV("[-42.500, 0.000, 1.250, 42.500]"), SV("{::.{}f}"), input, 3); + + check_exception("The format-spec precision field doesn't contain a value or arg-id", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::L}"), input); // does not require locales present +#ifndef TEST_HAS_NO_LOCALIZATION + std::locale::global(std::locale(LOCALE_fr_FR_UTF_8)); + check(SV("[-42,5, 0, 1,25, 42,5]"), SV("{::L}"), input); + + std::locale::global(std::locale(LOCALE_en_US_UTF_8)); + check(SV("[-42.5, 0, 1.25, 42.5]"), SV("{::L}"), input); + + std::locale::global(std::locale::classic()); +#endif // TEST_HAS_NO_LOCALIZATION + + // *** type *** + for (std::basic_string_view fmt : fmt_invalid_nested_types("aAeEfFgG")) + check_exception("The format-spec type has a type not supported for a floating-point argument", fmt, input); + + // ***** Both have a format-spec + check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^33::>5}"), input); + check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^{}::>5}"), input, 33); + check(SV("^^[-42.5, ::::0, :1.25, :42.5]^^^"), SV("{:^^{}::>{}}"), input, 33, 5); + + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^33::>5.2}"), input); + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>5.2}"), input, 33); + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>{}.2}"), input, 33, 5); + check(SV("^^[::-42, ::::0, ::1.2, :::42]^^^"), SV("{:^^{}::>{}.{}}"), input, 33, 5, 2); + + check_exception("Argument index out of bounds", SV("{:^^{}::>5.2}"), input); + check_exception("Argument index out of bounds", SV("{:^^{}::>{}.2}"), input, 33); + check_exception("Argument index out of bounds", SV("{:^^{}::>{}.{}}"), input, 33, 5); +} + +template +void test_floating_point(TestFunction check, ExceptionTest check_exception) { + std::array input{-42.5l, 0.0l, 1.25l, 42.5l}; + test_floating_point(check, check_exception, std::queue{input.begin(), input.end()}); + test_floating_point(check, check_exception, std::priority_queue{input.begin(), input.end(), std::greater{}}); + test_floating_point(check, check_exception, std::stack{input.begin(), input.end()}); +} + +// +// Pointer +// + +template +void test_pointer(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[0x0]"), SV("{}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[0x0] "), SV("{:10}"), input); + check(SV("[0x0]*****"), SV("{:*<10}"), input); + check(SV("__[0x0]___"), SV("{:_^10}"), input); + check(SV("#####[0x0]"), SV("{:#>10}"), input); + + check(SV("[0x0] "), SV("{:{}}"), input, 10); + check(SV("[0x0]*****"), SV("{:*<{}}"), input, 10); + check(SV("__[0x0]___"), SV("{:_^{}}"), input, 10); + check(SV("#####[0x0]"), SV("{:#>{}}"), input, 10); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("_0x0_"), SV("{:_^5n}"), input); + + // *** type *** + check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input); + check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input); + check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input); + for (std::basic_string_view fmt : fmt_invalid_types("s")) + check_exception("The format-spec should consume the input or end with a '}'", fmt, input); + + // ***** Only underlying has a format-spec + check(SV("[ 0x0]"), SV("{::5}"), input); + check(SV("[0x0**]"), SV("{::*<5}"), input); + check(SV("[_0x0_]"), SV("{::_^5}"), input); + check(SV("[::0x0]"), SV("{:::>5}"), input); + + check(SV("[ 0x0]"), SV("{::{}}"), input, 5); + check(SV("[0x0**]"), SV("{::*<{}}"), input, 5); + check(SV("[_0x0_]"), SV("{::_^{}}"), input, 5); + check(SV("[::0x0]"), SV("{:::>{}}"), input, 5); + + check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input); + check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{::-}"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{::05}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (std::basic_string_view fmt : fmt_invalid_nested_types("p")) + check_exception("The format-spec type has a type not supported for a pointer argument", fmt, input); + + // ***** Both have a format-spec + check(SV("^^[::0x0]^^^"), SV("{:^^12::>5}"), input); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>5}"), input, 12); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>{}}"), input, 12, 5); + + check(SV("^^[::0x0]^^^"), SV("{:^^12::>5}"), input); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>5}"), input, 12); + check(SV("^^[::0x0]^^^"), SV("{:^^{}::>{}}"), input, 12, 5); + + check_exception("Argument index out of bounds", SV("{:^^{}::>5}"), input); + check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 12); +} + +template +void test_pointer(TestFunction check, ExceptionTest check_exception) { + std::array input{static_cast(0)}; + test_pointer(check, check_exception, std::queue{input.begin(), input.end()}); + test_pointer(check, check_exception, std::priority_queue{input.begin(), input.end()}); + test_pointer(check, check_exception, std::stack{input.begin(), input.end()}); +} + +// +// String +// + +template +void test_string(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV(R"(["Hello", "world"])"), SV("{}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV(R"(["Hello", "world"] )"), SV("{:23}"), input); + check(SV(R"(["Hello", "world"]*****)"), SV("{:*<23}"), input); + check(SV(R"(__["Hello", "world"]___)"), SV("{:_^23}"), input); + check(SV(R"(#####["Hello", "world"])"), SV("{:#>23}"), input); + + check(SV(R"(["Hello", "world"] )"), SV("{:{}}"), input, 23); + check(SV(R"(["Hello", "world"]*****)"), SV("{:*<{}}"), input, 23); + check(SV(R"(__["Hello", "world"]___)"), SV("{:_^{}}"), input, 23); + check(SV(R"(#####["Hello", "world"])"), SV("{:#>{}}"), input, 23); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV(R"(_"Hello", "world"_)"), SV("{:_^18n}"), input); + + // *** type *** + check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input); + check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input); + check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input); + for (std::basic_string_view fmt : fmt_invalid_types("s")) + check_exception("The format-spec should consume the input or end with a '}'", fmt, input); + + // ***** Only underlying has a format-spec + check(SV(R"([Hello , world ])"), SV("{::8}"), input); + check(SV(R"([Hello***, world***])"), SV("{::*<8}"), input); + check(SV(R"([_Hello__, _world__])"), SV("{::_^8}"), input); + check(SV(R"([:::Hello, :::world])"), SV("{:::>8}"), input); + + check(SV(R"([Hello , world ])"), SV("{::{}}"), input, 8); + check(SV(R"([Hello***, world***])"), SV("{::*<{}}"), input, 8); + check(SV(R"([_Hello__, _world__])"), SV("{::_^{}}"), input, 8); + check(SV(R"([:::Hello, :::world])"), SV("{:::>{}}"), input, 8); + + check_exception("The format-spec fill field contains an invalid character", SV("{::}<}"), input); + check_exception("The format-spec fill field contains an invalid character", SV("{::{<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{::-}"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{::#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{::05}"), input); + + // *** precision *** + check(SV(R"([Hel, wor])"), SV("{::.3}"), input); + + check(SV(R"([Hel, wor])"), SV("{::.{}}"), input, 3); + + check_exception("The format-spec precision field doesn't contain a value or arg-id", SV("{::.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{::L}"), input); + + // *** type *** + for (std::basic_string_view fmt : fmt_invalid_nested_types("s?")) + check_exception("The format-spec type has a type not supported for a string argument", fmt, input); + + // ***** Both have a format-spec + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8); + + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25); + check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8); + + check_exception("Argument index out of bounds", SV("{:^^{}::>8}"), input); + check_exception("Argument index out of bounds", SV("{:^^{}::>{}}"), input, 25); +} + +template +void test_string(TestFunction check, ExceptionTest check_exception) { + std::array input{STR("Hello"), STR("world")}; + test_string(check, check_exception, std::queue{input.begin(), input.end()}); + test_string(check, check_exception, std::priority_queue{input.begin(), input.end(), std::greater{}}); + test_string(check, check_exception, std::stack{input.begin(), input.end()}); +} + +// +// Handle +// + +template +void test_status(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[0xaaaa, 0x5555, 0xaa55]"), SV("{}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[0xaaaa, 0x5555, 0xaa55] "), SV("{:29}"), input); + check(SV("[0xaaaa, 0x5555, 0xaa55]*****"), SV("{:*<29}"), input); + check(SV("__[0xaaaa, 0x5555, 0xaa55]___"), SV("{:_^29}"), input); + check(SV("#####[0xaaaa, 0x5555, 0xaa55]"), SV("{:#>29}"), input); + + check(SV("[0xaaaa, 0x5555, 0xaa55] "), SV("{:{}}"), input, 29); + check(SV("[0xaaaa, 0x5555, 0xaa55]*****"), SV("{:*<{}}"), input, 29); + check(SV("__[0xaaaa, 0x5555, 0xaa55]___"), SV("{:_^{}}"), input, 29); + check(SV("#####[0xaaaa, 0x5555, 0xaa55]"), SV("{:#>{}}"), input, 29); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:}<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{:{<}"), input); + check_exception("The format-spec range-fill field contains an invalid character", SV("{::<}"), input); + + // *** sign *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:-}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{:+}"), input); + check_exception("The format-spec should consume the input or end with a '}'", SV("{: }"), input); + + // *** alternate form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:#}"), input); + + // *** zero-padding *** + check_exception("A format-spec width field shouldn't have a leading zero", SV("{:0}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:.}"), input); + + // *** locale-specific form *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{:L}"), input); + + // *** n + check(SV("__0xaaaa, 0x5555, 0xaa55___"), SV("{:_^27n}"), input); + + // *** type *** + check_exception("The range-format-spec type m requires two elements for a pair or tuple", SV("{:m}"), input); + check_exception("The range-format-spec type s requires formatting a character type", SV("{:s}"), input); + check_exception("The range-format-spec type ?s requires formatting a character type", SV("{:?s}"), input); + for (std::basic_string_view fmt : fmt_invalid_types("s")) + check_exception("The format-spec should consume the input or end with a '}'", fmt, input); + + // ***** Only underlying has a format-spec + check_exception("The format-spec type has a type not supported for a status argument", SV("{::*<7}"), input); + for (std::basic_string_view fmt : fmt_invalid_nested_types("sxX")) + check_exception("The format-spec type has a type not supported for a status argument", fmt, input); + + check(SV("[0xaaaa, 0x5555, 0xaa55]"), SV("{::x}"), input); + check(SV("[0XAAAA, 0X5555, 0XAA55]"), SV("{::X}"), input); + check(SV("[foo, bar, foobar]"), SV("{::s}"), input); + + // ***** Both have a format-spec + check(SV("^^[0XAAAA, 0X5555, 0XAA55]^^^"), SV("{:^^29:X}"), input); + check(SV("^^[0XAAAA, 0X5555, 0XAA55]^^^"), SV("{:^^{}:X}"), input, 29); + + check_exception("Argument index out of bounds", SV("{:^^{}:X}"), input); +} + +template +void test_status(TestFunction check, ExceptionTest check_exception) { + std::array input{status::foo, status::bar, status::foobar}; + test_status(check, check_exception, std::queue{input.begin(), input.end()}); + test_status(check, check_exception, std::priority_queue{input.begin(), input.end(), std::less{}}); + test_status(check, check_exception, std::stack{input.begin(), input.end()}); +} + +// +// Driver +// + +template +void format_tests(TestFunction check, ExceptionTest check_exception) { + test_char(check, check_exception); +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS + if (std::same_as) // avoid testing twice + test_char_to_wchar(check, check_exception); +#endif + test_bool(check, check_exception); + test_int(check, check_exception); + test_floating_point(check, check_exception); + test_pointer(check, check_exception); + test_string(check, check_exception); + + test_status(check, check_exception); // Has its own handler with its own parser +} + +#endif // TEST_STD_CONTAINERS_CONTAINER_ADAPTORS_CONTAINER_ADAPTORS_FORMAT_FORMAT_FUNCTIONS_TESTS_H diff --git a/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.functions.vformat.pass.cpp b/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.functions.vformat.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.functions.vformat.pass.cpp @@ -0,0 +1,73 @@ +//===----------------------------------------------------------------------===// +// 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, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// TODO FMT Fix this test using GCC, it currently times out. +// UNSUPPORTED: gcc-12 + +// This test requires the dylib support introduced in D92214. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}} +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}} + +// [container.adaptors.format] +// For each of queue, priority_queue, and stack, the library provides the +// following formatter specialization where adaptor-type is the name of the +// template: +// +// template Container, class... U> +// struct formatter, charT> + +// string vformat(string_view fmt, format_args args); +// wstring vformat(wstring_view fmt, wformat_args args); + +#include +#include + +#include "format.functions.tests.h" +#include "test_macros.h" +#include "assert_macros.h" + +auto test = []( + std::basic_string_view expected, std::basic_string_view fmt, Args&&... args) { + std::basic_string out = std::vformat(fmt, std::make_format_args>(args...)); + TEST_REQUIRE( + out == expected, + test_concat_message("\nFormat string ", fmt, "\nExpected output ", expected, "\nActual output ", out, '\n')); +}; + +auto test_exception = + []( + [[maybe_unused]] std::string_view what, + [[maybe_unused]] std::basic_string_view fmt, + [[maybe_unused]] Args&&... args) { +#ifndef TEST_HAS_NO_EXCEPTIONS + try { + TEST_IGNORE_NODISCARD std::vformat(fmt, std::make_format_args>(args...)); + TEST_FAIL(test_concat_message("\nFormat string ", fmt, "\nDidn't throw an exception.\n")); + } catch (const std::format_error& e) { + TEST_LIBCPP_REQUIRE( + e.what() == what, + test_concat_message( + "\nFormat string ", fmt, "\nExpected exception ", what, "\nActual exception ", e.what(), '\n')); + + return; + } + assert(false); +#endif + }; + +int main(int, char**) { + format_tests(test, test_exception); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + format_tests(test, test_exception); +#endif + + return 0; +} diff --git a/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.pass.cpp b/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/container.adaptors/container.adaptors.format/format.pass.cpp @@ -0,0 +1,78 @@ +//===----------------------------------------------------------------------===// +// 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, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// This test requires the dylib support introduced in D92214. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}} +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}} + +// [container.adaptors.format] +// For each of queue, priority_queue, and stack, the library provides the +// following formatter specialization where adaptor-type is the name of the +// template: +// +// template Container, class... U> +// struct formatter, charT> + +// template +// typename FormatContext::iterator +// format(maybe-const-adaptor& r, FormatContext& ctx) const; + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions test. + +#include +#include +#include +#include +#include +#include + +#include "test_format_context.h" +#include "test_macros.h" +#include "make_string.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template +void test_format(StringViewT expected, Arg arg) { + using CharT = typename StringViewT::value_type; + using String = std::basic_string; + using OutIt = std::back_insert_iterator; + using FormatCtxT = std::basic_format_context; + + const std::formatter formatter; + + String result; + OutIt out = std::back_inserter(result); + FormatCtxT format_ctx = test_format_context_create(out, std::make_format_args(arg)); + formatter.format(arg, format_ctx); + assert(result == expected); +} + +template +void test_fmt() { + std::array input{1, 42, 99, 0}; + test_format(SV("[1, 42, 99, 0]"), std::queue{input.begin(), input.end()}); + test_format(SV("[99, 42, 1, 0]"), std::priority_queue{input.begin(), input.end()}); + test_format(SV("[1, 42, 99, 0]"), std::stack{input.begin(), input.end()}); +} + +void test() { + test_fmt(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_fmt(); +#endif +} + +int main(int, char**) { + test(); + + return 0; +} diff --git a/libcxx/test/std/containers/container.adaptors/container.adaptors.format/parse.pass.cpp b/libcxx/test/std/containers/container.adaptors/container.adaptors.format/parse.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/container.adaptors/container.adaptors.format/parse.pass.cpp @@ -0,0 +1,83 @@ +//===----------------------------------------------------------------------===// +// 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, c++20 +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// This test requires the dylib support introduced in D92214. +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{.+}} +// XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx11.{{.+}} + +// [container.adaptors.format] +// For each of queue, priority_queue, and stack, the library provides the +// following formatter specialization where adaptor-type is the name of the +// template: +// +// template Container, class... U> +// struct formatter, charT> + +// template +// constexpr typename ParseContext::iterator +// parse(ParseContext& ctx); + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions test. + +#include +#include +#include +#include +#include + +#include "test_format_context.h" +#include "test_macros.h" +#include "make_string.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template +constexpr void test_parse(StringViewT fmt) { + using CharT = typename StringViewT::value_type; + auto parse_ctx = std::basic_format_parse_context(fmt); + std::formatter formatter; + static_assert(std::semiregular); + + std::same_as auto it = formatter.parse(parse_ctx); + assert(it == fmt.end() - (!fmt.empty() && fmt.back() == '}')); +} + +template +constexpr void test_parse(StringViewT fmt) { + test_parse>(fmt); + test_parse>(fmt); + test_parse>(fmt); +} + +template +constexpr void test_fmt() { + test_parse(SV("")); + test_parse(SV(":d")); + + test_parse(SV("}")); + test_parse(SV(":d}")); +} + +constexpr bool test() { + test_fmt(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_fmt(); +#endif + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp --- a/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formattable/concept.formattable.compile.pass.cpp @@ -195,9 +195,6 @@ } // Tests for P2286 Formatting ranges -// -// The paper hasn't been voted in so currently all formatters are disabled. -// TODO validate whether the test is correct after the paper has been accepted. template void test_P2286() { assert_is_formattable, CharT>(); @@ -216,9 +213,9 @@ assert_is_not_formattable, CharT>(); assert_is_not_formattable, CharT>(); - assert_is_not_formattable, CharT>(); - assert_is_not_formattable, CharT>(); - assert_is_not_formattable, CharT>(); + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); + assert_is_formattable, CharT>(); assert_is_formattable, CharT>();