// { dg-do run { target c++23 } } // { dg-options "-fexec-charset=UTF-8" } // { dg-timeout-factor 2 } #include #include #include #include #include #include template 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 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 bool is_range_formatter_spec_for(CharT const* spec, Rg&& rg) { using V = std::remove_cvref_t>; std::range_formatter fmt; std::basic_format_parse_context pc(spec); try { (void)fmt.parse(pc); return true; } catch (const std::format_error&) { return false; } } #define WIDEN_(C, S) ::std::__format::_Widen(S, L##S) #define WIDEN(S) WIDEN_(CharT, S) void test_format_string() { // only CharT value types are supported VERIFY( !is_range_formatter_spec_for(L"s", std::vector()) ); VERIFY( !is_format_string_for(L"{:s}", std::vector()) ); VERIFY( !is_range_formatter_spec_for(L"s", std::vector()) ); VERIFY( !is_format_string_for(L"{:s}", std::vector()) ); VERIFY( !is_range_formatter_spec_for("s", std::vector()) ); VERIFY( !is_format_string_for("{:s}", std::vector()) ); // invalid format stringss VERIFY( !is_range_formatter_spec_for("?", std::vector()) ); VERIFY( !is_format_string_for("{:?}", std::vector()) ); VERIFY( !is_range_formatter_spec_for("ns", std::vector()) ); VERIFY( !is_format_string_for("{:ns}", std::vector()) ); VERIFY( !is_range_formatter_spec_for("s:", std::vector()) ); VERIFY( !is_format_string_for("{:s:}", std::vector()) ); // precision is not supported, even for s VERIFY( !is_range_formatter_spec_for(".10s", std::vector()) ); VERIFY( !is_format_string_for("{:.10s}", std::vector()) ); VERIFY( !is_format_string_for("{:.{}s}", std::vector(), 10) ); // width needs to be integer type VERIFY( !is_format_string_for("{:{}s}", std::vector(), 1.0f) ); } template void test_output() { using CharT = std::ranges::range_value_t; auto makeRange = [](std::basic_string& s) { return Range(s.data(), s.data() + s.size()); }; std::basic_string res; size_t size = 0; std::basic_string s1 = WIDEN("abcd"); res = std::format(WIDEN("{}"), makeRange(s1)); VERIFY( res == WIDEN("['a', 'b', 'c', 'd']") ); res = std::format(WIDEN("{::}"), makeRange(s1)); VERIFY( res == WIDEN("[a, b, c, d]") ); res = std::format(WIDEN("{:s}"), makeRange(s1)); VERIFY( res == WIDEN("abcd") ); res = std::format(WIDEN("{:?s}"), makeRange(s1)); VERIFY( res == WIDEN(R"("abcd")") ); res = std::format(WIDEN("{:3s}"), makeRange(s1)); VERIFY( res == WIDEN("abcd") ); res = std::format(WIDEN("{:7s}"), makeRange(s1)); VERIFY( res == WIDEN("abcd ") ); res = std::format(WIDEN("{:{}s}"), makeRange(s1), 7); VERIFY( res == WIDEN("abcd ") ); res = std::format(WIDEN("{1:{0}s}"), 7, makeRange(s1)); VERIFY( res == WIDEN("abcd ") ); res = std::format(WIDEN("{:*>6s}"), makeRange(s1)); VERIFY( res == WIDEN("**abcd") ); res = std::format(WIDEN("{:-<5s}"), makeRange(s1)); VERIFY( res == WIDEN("abcd-") ); res = std::format(WIDEN("{:=^8s}"), makeRange(s1)); VERIFY( res == WIDEN("==abcd==") ); std::basic_string s2(512, static_cast('a')); res = std::format(WIDEN("{:=^8s}"), makeRange(s2)); VERIFY( res == s2 ); size = std::formatted_size(WIDEN("{:s}"), makeRange(s1)); VERIFY( size == 4 ); size = std::formatted_size(WIDEN("{:3s}"), makeRange(s1)); VERIFY( size == 4 ); size = std::formatted_size(WIDEN("{:7s}"), makeRange(s1)); VERIFY( size == 7 ); size = std::formatted_size(WIDEN("{:s}"), makeRange(s2)); VERIFY( size == 512 ); } template struct cstr_view { cstr_view() = default; explicit cstr_view(CharT* f, CharT* l) : ptr(f) { VERIFY(!*l); } struct sentinel { friend constexpr bool operator==(CharT const* ptr, sentinel) noexcept { return !*ptr; } }; constexpr CharT* begin() const noexcept { return ptr; }; static constexpr sentinel end() noexcept { return {}; } private: CharT* ptr = ""; }; template void test_outputs() { using namespace __gnu_test; test_output>(); test_output>(); test_output>(); test_output>(); test_output>(); test_output>(); test_output>(); test_output>(); test_output>(); test_output>(); test_output>(); test_output>(); static_assert(!std::formattable, CharT>); static_assert(!std::formattable, CharT>); } void test_nested() { std::string_view s1 = "str1"; std::string_view s2 = "str2"; std::vector vs; vs.emplace_back(s1); vs.emplace_back(s2); VERIFY( std::format("{}", vs) == R"(["str1", "str2"])" ); VERIFY( std::format("{:}", vs) == R"(["str1", "str2"])" ); VERIFY( std::format("{::?}", vs) == R"(["str1", "str2"])" ); VERIFY( std::format("{::}", vs) == R"([str1, str2])" ); std::vector> vv; vv.emplace_back(s1.begin(), s1.end()); vv.emplace_back(s2.begin(), s2.end()); std::string_view escaped = R"([['s', 't', 'r', '1'], ['s', 't', 'r', '2']])"; VERIFY( std::format("{}", vv) == escaped ); VERIFY( std::format("{:}", vv) == escaped ); VERIFY( std::format("{::}", vv) == escaped ); VERIFY( std::format("{:::?}", vv) == escaped ); VERIFY( std::format("{:::}", vv) == R"([[s, t, r, 1], [s, t, r, 2]])" ); VERIFY( std::format("{::s}", vv) == R"([str1, str2])" ); VERIFY( std::format("{::?s}", vv) == R"(["str1", "str2"])" ); } bool strip_quotes(std::string_view& v) { if (!v.starts_with('"') || !v.ends_with('"')) return false; v.remove_prefix(1); v.remove_suffix(1); return true; } 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; } 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 in += in; // width is 96, size is 480 in += in; // width is 192, size is 960 std::forward_list lc(std::from_range, in); resv = res = std::format("{:s}", lc); VERIFY( resv == in ); resv = res = std::format("{:*>10s}", lc); VERIFY( resv == in ); resv = res = std::format("{:*>240s}", lc); VERIFY( strip_prefix(resv, 48, '*') ); VERIFY( resv == in ); resv = res = std::format("{:?s}", lc); VERIFY( strip_quotes(resv) ); VERIFY( resv == in ); resv = res = std::format("{:*>10?s}", lc); VERIFY( strip_quotes(resv) ); VERIFY( resv == in ); resv = res = std::format("{:*>240?s}", lc); VERIFY( strip_prefix(resv, 46, '*') ); VERIFY( strip_quotes(resv) ); VERIFY( resv == in ); // width is 5, size is 15 in = "\u2160\u2161\u2162\u2163\u2164"; in += in; // width is 10, size is 30 in += in; // width is 20, size is 60 in += in; // width is 40, size is 120 in += in; // width is 80, size is 240 in += in; // width is 160, size is 480 lc.assign_range(in); resv = res = std::format("{:s}", lc); VERIFY( resv == in ); resv = res = std::format("{:*>10s}", lc); VERIFY( resv == in ); resv = res = std::format("{:*>200s}", lc); VERIFY( strip_prefix(resv, 40, '*') ); VERIFY( resv == in ); resv = res = std::format("{:?s}", lc); VERIFY( strip_quotes(resv) ); VERIFY( resv == in ); resv = res = std::format("{:*>10?s}", lc); VERIFY( strip_quotes(resv) ); VERIFY( resv == in ); resv = res = std::format("{:*>200?s}", lc); VERIFY( strip_prefix(resv, 38, '*') ); VERIFY( strip_quotes(resv) ); VERIFY( resv == in ); } void test_escaping() { std::string res; std::string_view resv; const std::string_view input = "\t\n\r\\\"" "\u008a" // Cc, Control, Line Tabulation Set, "\u00ad" // Cf, Format, Soft Hyphen "\u1d3d" // Lm, Modifier letter, Modifier Letter Capital Ou "\u00a0" // Zs, Space Separator, No-Break Space (NBSP) "\u2029" // Zp, Paragraph Separator, Paragraph Separator "\U0001f984" // So, Other Symbol, Unicorn Face ; const std::string_view output = R"(\t\n\r\\\")" R"(\u{8a})" R"(\u{ad})" "\u1d3d" R"(\u{a0})" R"(\u{2029})" "\U0001f984"; std::forward_list lc(std::from_range, input); resv = res = std::format("{:s}", lc); VERIFY( resv == input ); resv = res = std::format("{:?s}", lc); VERIFY( strip_quotes(resv) ); VERIFY( resv == output ); // width is 5, size is 15 std::string in = "\u2160\u2161\u2162\u2163\u2164"; in += in; // width is 10, size is 30 in += in; // width is 20, size is 60 in += in; // width is 40, size is 120 in += in; // width is 80, size is 240 in += in; // width is 160, size is 480 std::string_view inv = in; // last charcter is incomplete lc.assign_range(inv.substr(0, 479)); // non-debug format, chars copied as is resv = res = std::format("{:s}", lc); VERIFY( resv == inv.substr(0, 479) ); // debug-format, incomplete code-point sequence is esaped resv = res = std::format("{:?s}", lc); VERIFY( strip_quotes(resv) ); VERIFY( resv.substr(0, 477) == inv.substr(0, 477) ); resv.remove_prefix(477); VERIFY( resv == R"(\x{e2}\x{85})" ); } int main() { test_format_string(); test_outputs(); test_outputs(); test_nested(); test_padding(); test_escaping(); }