| // { dg-do run { target c++23 } } | |
| // { dg-options "-fexec-charset=UTF-8 -fconstexpr-ops-limit=5000000000" } | |
| // { dg-timeout-factor 2 } | |
| #include <array> | |
| #include <format> | |
| #include <list> | |
| #include <ranges> | |
| #include <span> | |
| #include <string> | |
| #include <testsuite_hooks.h> | |
| #include <testsuite_iterators.h> | |
| #include <vector> | |
| #ifdef __glibcxx_constexpr_format | |
| # define CONSTEXPR constexpr | |
| #else | |
| # define CONSTEXPR | |
| #endif | |
| struct NotFormattable | |
| {}; | |
| static_assert(!std::formattable<std::vector<NotFormattable>, char>); | |
| static_assert(!std::formattable<std::span<NotFormattable>, wchar_t>); | |
| template<typename... Args> | |
| CONSTEXPR bool | |
| is_format_string_for(const char* str, Args&&... args) | |
| { | |
| try { | |
| (void) std::vformat(str, std::make_format_args(args...)); | |
| return true; | |
| } catch (const std::format_error&) { | |
| return false; | |
| } | |
| } | |
| template<typename... Args> | |
| CONSTEXPR bool | |
| is_format_string_for(const wchar_t* str, Args&&... args) | |
| { | |
| try { | |
| (void) std::vformat(str, std::make_wformat_args(args...)); | |
| return true; | |
| } catch (const std::format_error&) { | |
| return false; | |
| } | |
| } | |
| template<typename Rg, typename CharT> | |
| CONSTEXPR bool | |
| is_range_formatter_spec_for(CharT const* spec, Rg&& rg) | |
| { | |
| using V = std::remove_cvref_t<std::ranges::range_reference_t<Rg>>; | |
| std::range_formatter<V, CharT> fmt; | |
| std::basic_format_parse_context<CharT> pc(spec); | |
| try { | |
| (void)fmt.parse(pc); | |
| return true; | |
| } catch (const std::format_error&) { | |
| return false; | |
| } | |
| } | |
| CONSTEXPR void | |
| test_format_string() | |
| { | |
| // invalid format spec 'p' | |
| VERIFY( !is_range_formatter_spec_for("p", std::vector<int>()) ); | |
| VERIFY( !is_format_string_for("{:p}", std::vector<int>()) ); | |
| VERIFY( !is_range_formatter_spec_for("np", std::vector<int>()) ); | |
| VERIFY( !is_format_string_for("{:np}", std::vector<int>()) ); | |
| // width needs to be integer type | |
| VERIFY( !is_format_string_for("{:{}}", std::vector<int>(), 1.0f) ); | |
| // element format needs to be valid | |
| VERIFY( !is_range_formatter_spec_for(":p", std::vector<int>()) ); | |
| VERIFY( !is_format_string_for("{::p}", std::vector<int>()) ); | |
| VERIFY( !is_range_formatter_spec_for("n:p", std::vector<int>()) ); | |
| VERIFY( !is_format_string_for("{:n:p}", std::vector<int>()) ); | |
| } | |
| #define WIDEN_(C, S) ::std::__format::_Widen<C>(S, L##S) | |
| #define WIDEN(S) WIDEN_(CharT, S) | |
| template<typename CharT, typename Range, typename Storage> | |
| CONSTEXPR void | |
| test_output() | |
| { | |
| using Sv = std::basic_string_view<CharT>; | |
| using T = std::ranges::range_value_t<Range>; | |
| auto makeRange = [](Storage& s) -> Range { | |
| if constexpr (std::is_same_v<std::remove_cvref_t<Range>, Storage>) | |
| return s; | |
| else | |
| return Range(std::ranges::data(s), | |
| std::ranges::data(s) + std::ranges::size(s)); | |
| }; | |
| std::basic_string<CharT> res; | |
| size_t size = 0; | |
| Storage v1{1, 2, 3}; | |
| res = std::format(WIDEN("{}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("[1, 2, 3]") ); | |
| res = std::format(WIDEN("{:}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("[1, 2, 3]") ); | |
| res = std::format(WIDEN("{:n}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("1, 2, 3") ); | |
| res = std::format(WIDEN("{:3}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("[1, 2, 3]") ); | |
| res = std::format(WIDEN("{:10}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("[1, 2, 3] ") ); | |
| res = std::format(WIDEN("{:{}}"), makeRange(v1), 10); | |
| VERIFY( res == WIDEN("[1, 2, 3] ") ); | |
| res = std::format(WIDEN("{1:{0}}"), 10, makeRange(v1)); | |
| VERIFY( res == WIDEN("[1, 2, 3] ") ); | |
| res = std::format(WIDEN("{:10n}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("1, 2, 3 ") ); | |
| res = std::format(WIDEN("{:*<11}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("[1, 2, 3]**") ); | |
| res = std::format(WIDEN("{:->12}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("---[1, 2, 3]") ); | |
| res = std::format(WIDEN("{:=^13}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("==[1, 2, 3]==") ); | |
| res = std::format(WIDEN("{:=^13n}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("===1, 2, 3===") ); | |
| res = std::format(WIDEN("{::#x}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("[0x1, 0x2, 0x3]") ); | |
| res = std::format(WIDEN("{:|^25n:#05x}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("|||0x001, 0x002, 0x003|||") ); | |
| // ':' is start of the format string for element | |
| res = std::format(WIDEN("{::^+04}"), makeRange(v1)); | |
| VERIFY( res == WIDEN("[ +1 , +2 , +3 ]") ); | |
| size = std::formatted_size(WIDEN("{:}"), makeRange(v1)); | |
| VERIFY( size == Sv(WIDEN("[1, 2, 3]")).size() ); | |
| size = std::formatted_size(WIDEN("{:3}"), makeRange(v1)); | |
| VERIFY( size == Sv(WIDEN("[1, 2, 3]")).size() ); | |
| size = std::formatted_size(WIDEN("{:10}"), makeRange(v1)); | |
| VERIFY( size == 10 ); | |
| size = std::formatted_size(WIDEN("{:|^25n:#05x}"), makeRange(v1)); | |
| VERIFY( size == 25 ); | |
| } | |
| template<typename Cont> | |
| CONSTEXPR void | |
| test_output_cont() | |
| { | |
| test_output<char, Cont&, Cont>(); | |
| test_output<wchar_t, Cont const&, Cont>(); | |
| } | |
| template<typename View> | |
| CONSTEXPR void | |
| test_output_view() | |
| { | |
| test_output<char, View, int[3]>(); | |
| test_output<wchar_t, View, int[3]>(); | |
| } | |
| CONSTEXPR void | |
| test_outputs() | |
| { | |
| using namespace __gnu_test; | |
| test_output_cont<std::vector<int>>(); | |
| if (!std::is_constant_evaluated()) | |
| test_output_cont<std::list<int>>(); | |
| test_output_cont<std::array<int, 3>>(); | |
| test_output_view<std::span<int>>(); | |
| test_output_view<std::ranges::subrange<int*>>(); | |
| test_output_view<test_forward_range<int>>(); | |
| test_output_view<test_input_range<int>>(); | |
| test_output_view<test_input_range_nocopy<int>>(); | |
| test_output_view<std::span<const int>>(); | |
| test_output_view<std::ranges::subrange<const int*>>(); | |
| test_output_view<test_forward_range<const int>>(); | |
| } | |
| CONSTEXPR void | |
| test_nested() | |
| { | |
| std::vector<std::vector<int>> v | |
| { | |
| {1, 2}, | |
| {11, 12} | |
| }; | |
| std::string res = std::format("{}", v); | |
| VERIFY( res == "[[1, 2], [11, 12]]" ); | |
| res = std::format("{:+^18:n:02}", v); | |
| VERIFY( res == "+[01, 02, 11, 12]+" ); | |
| } | |
| CONSTEXPR bool | |
| strip_quote(std::string_view& v) | |
| { | |
| if (!v.starts_with('"')) | |
| return false; | |
| v.remove_prefix(1); | |
| return true; | |
| } | |
| CONSTEXPR bool | |
| strip_prefix(std::string_view& v, std::string_view expected, bool quoted = false) | |
| { | |
| if (quoted && !strip_quote(v)) | |
| return false; | |
| if (!v.starts_with(expected)) | |
| return false; | |
| v.remove_prefix(expected.size()); | |
| if (quoted && !strip_quote(v)) | |
| return false; | |
| return true; | |
| } | |
| CONSTEXPR bool | |
| strip_squares(std::string_view& v) | |
| { | |
| if (!v.starts_with('[') || !v.ends_with(']')) | |
| return false; | |
| v.remove_prefix(1); | |
| v.remove_suffix(1); | |
| return true; | |
| } | |
| CONSTEXPR bool | |
| strip_prefix(std::string_view& v, size_t n, char c) | |
| { | |
| size_t pos = v.find_first_not_of(c); | |
| if (pos == std::string_view::npos) | |
| pos = v.size(); | |
| if (pos != n) | |
| return false; | |
| v.remove_prefix(n); | |
| return true; | |
| } | |
| CONSTEXPR void | |
| test_padding() | |
| { | |
| std::string res; | |
| std::string_view resv; | |
| // width is 3, size is 15 | |
| std::string in = "o\u0302\u0323i\u0302\u0323u\u0302\u0323"; | |
| in += in; // width is 6, size is 30 | |
| in += in; // width is 12, size is 60 | |
| in += in; // width is 24, size is 120 | |
| in += in; // width is 48, size is 240 | |
| // width is 192, size is 960 | |
| std::vector<std::string> const vs{in, in, in, in}; | |
| auto const check_elems = [=](std::string_view& v, bool quoted) | |
| { | |
| VERIFY( strip_prefix(v, in, quoted) ); | |
| VERIFY( strip_prefix(v, ", ", false) ); | |
| VERIFY( strip_prefix(v, in, quoted) ); | |
| VERIFY( strip_prefix(v, ", ", false) ); | |
| VERIFY( strip_prefix(v, in, quoted) ); | |
| VERIFY( strip_prefix(v, ", ", false) ); | |
| VERIFY( strip_prefix(v, in, quoted) ); | |
| return v.empty(); | |
| }; | |
| resv = res = std::format("{}", vs); | |
| VERIFY( strip_squares(resv) ); | |
| VERIFY( check_elems(resv, true) ); | |
| resv = res = std::format("{:n}", vs); | |
| VERIFY( check_elems(resv, true) ); | |
| resv = res = std::format("{::}", vs); | |
| VERIFY( strip_squares(resv) ); | |
| VERIFY( check_elems(resv, false) ); | |
| resv = res = std::format("{:n:}", vs); | |
| VERIFY( check_elems(resv, false) ); | |
| resv = res = std::format("{:*>10}", vs); | |
| VERIFY( strip_squares(resv) ); | |
| VERIFY( check_elems(resv, true) ); | |
| resv = res = std::format("{:*>10n}", vs); | |
| VERIFY( check_elems(resv, true) ); | |
| resv = res = std::format("{:*>10:}", vs); | |
| VERIFY( strip_squares(resv) ); | |
| VERIFY( check_elems(resv, false) ); | |
| resv = res = std::format("{:*>10n:}", vs); | |
| VERIFY( check_elems(resv, false) ); | |
| resv = res = std::format("{:*>256}", vs); | |
| VERIFY( strip_prefix(resv, 48, '*') ); | |
| VERIFY( strip_squares(resv) ); | |
| VERIFY( check_elems(resv, true) ); | |
| resv = res = std::format("{:*>256n}", vs); | |
| VERIFY( strip_prefix(resv, 50, '*') ); | |
| VERIFY( check_elems(resv, true) ); | |
| resv = res = std::format("{:*>240}", vs); | |
| VERIFY( strip_prefix(resv, 32, '*') ); | |
| VERIFY( strip_squares(resv) ); | |
| VERIFY( check_elems(resv, true) ); | |
| resv = res = std::format("{:*>240n}", vs); | |
| VERIFY( strip_prefix(resv, 34, '*') ); | |
| VERIFY( check_elems(resv, true) ); | |
| resv = res = std::format("{:*>240:}", vs); | |
| VERIFY( strip_prefix(resv, 40, '*') ); | |
| VERIFY( strip_squares(resv) ); | |
| VERIFY( check_elems(resv, false) ); | |
| resv = res = std::format("{:*>240n:}", vs); | |
| VERIFY( strip_prefix(resv, 42, '*') ); | |
| VERIFY( check_elems(resv, false) ); | |
| } | |
| CONSTEXPR bool | |
| test_all() | |
| { | |
| test_format_string(); | |
| test_outputs(); | |
| test_nested(); | |
| test_padding(); | |
| return true; | |
| } | |
| #ifdef __glibcxx_constexpr_format | |
| static_assert(test_all()); | |
| #endif | |
| int main() | |
| { | |
| test_all(); | |
| } |