diff --git a/libcxx/include/__format/format_arg.h b/libcxx/include/__format/format_arg.h --- a/libcxx/include/__format/format_arg.h +++ b/libcxx/include/__format/format_arg.h @@ -14,7 +14,9 @@ #include <__config> #include <__format/format_error.h> #include <__format/format_fwd.h> +#include <__format/format_parse_context.h> #include <__functional_base> +#include <__memory/addressof.h> #include <__variant/monostate.h> #include #include @@ -57,7 +59,8 @@ __long_double, __const_char_type_ptr, __string_view, - __ptr + __ptr, + __handle }; } // namespace __format @@ -101,6 +104,8 @@ return _VSTD::invoke(_VSTD::forward<_Visitor>(__vis), __arg.__string_view); case __format::__arg_t::__ptr: return _VSTD::invoke(_VSTD::forward<_Visitor>(__vis), __arg.__ptr); + case __format::__arg_t::__handle: + return _VSTD::invoke(_VSTD::forward<_Visitor>(__vis), __arg.__handle); } _LIBCPP_UNREACHABLE(); } @@ -108,8 +113,7 @@ template class _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT basic_format_arg { public: - // TODO FMT Define the handle class. - class handle; + class _LIBCPP_TEMPLATE_VIS handle; _LIBCPP_HIDE_FROM_ABI basic_format_arg() noexcept : __type_{__format::__arg_t::__none} {} @@ -158,7 +162,7 @@ const char_type* __const_char_type_ptr; basic_string_view __string_view; const void* __ptr; - // TODO FMT Add the handle. + handle __handle; }; __format::__arg_t __type_; @@ -245,6 +249,34 @@ template requires is_void_v<_Tp> _LIBCPP_HIDE_FROM_ABI explicit basic_format_arg(_Tp* __p) noexcept : __ptr(__p), __type_(__format::__arg_t::__ptr) {} + + template + _LIBCPP_HIDE_FROM_ABI explicit basic_format_arg(const _Tp& __v) noexcept + : __handle(__v), __type_(__format::__arg_t::__handle) {} +}; + +template +class _LIBCPP_TEMPLATE_VIS basic_format_arg<_Context>::handle { + friend class basic_format_arg<_Context>; + +public: + _LIBCPP_HIDE_FROM_ABI + void format(basic_format_parse_context& __parse_ctx, _Context& __ctx) const { + __format_(__parse_ctx, __ctx, __ptr_); + } + +private: + const void* __ptr_; + void (*__format_)(basic_format_parse_context&, _Context&, const void*); + + template + _LIBCPP_HIDE_FROM_ABI explicit handle(const _Tp& __v) noexcept + : __ptr_(_VSTD::addressof(__v)), + __format_([](basic_format_parse_context& __parse_ctx, _Context& __ctx, const void* __ptr) { + typename _Context::template formatter_type<_Tp> __f; + __parse_ctx.advance_to(__f.parse(__parse_ctx)); + __ctx.advance_to(__f.format(*static_cast(__ptr), __ctx)); + }) {} }; #endif // !defined(_LIBCPP_HAS_NO_CONCEPTS) diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -369,6 +369,8 @@ [&](auto __arg) { if constexpr (same_as) __throw_format_error("Argument index out of bounds"); + else if constexpr (same_as::handle>) + __arg.format(__parse_ctx, __ctx); else { formatter __formatter; __parse_ctx.advance_to(__formatter.parse(__parse_ctx)); diff --git a/libcxx/test/libcxx/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp b/libcxx/test/libcxx/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp --- a/libcxx/test/libcxx/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp +++ b/libcxx/test/libcxx/utilities/format/format.arguments/format.arg/visit_format_arg.pass.cpp @@ -8,6 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // This test requires the dylib support introduced in D92214. // XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{9|10|11|12|13|14|15}} diff --git a/libcxx/test/libcxx/utilities/format/format.arguments/format.args/get.pass.cpp b/libcxx/test/libcxx/utilities/format/format.arguments/format.args/get.pass.cpp --- a/libcxx/test/libcxx/utilities/format/format.arguments/format.args/get.pass.cpp +++ b/libcxx/test/libcxx/utilities/format/format.arguments/format.args/get.pass.cpp @@ -8,6 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // This test requires the dylib support introduced in D92214. // XFAIL: use_system_cxx_lib && target={{.+}}-apple-macosx10.{{9|10|11|12|13|14|15}} diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/class.pass.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/class.pass.cpp --- a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/class.pass.cpp +++ b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/class.pass.cpp @@ -8,6 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.sh.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.sh.cpp --- a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.sh.cpp +++ b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.sh.cpp @@ -9,6 +9,8 @@ // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-incomplete-format // UNSUPPORTED: libcpp-has-no-wide-characters +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // Validate it works regardless of the signedness of `char`. // RUN: %{cxx} %{flags} %{compile_flags} -fsigned-char -fsyntax-only %s diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg/operator_bool.pass.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg/operator_bool.pass.cpp --- a/libcxx/test/std/utilities/format/format.arguments/format.arg/operator_bool.pass.cpp +++ b/libcxx/test/std/utilities/format/format.arguments/format.arg/operator_bool.pass.cpp @@ -8,6 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // diff --git a/libcxx/test/std/utilities/format/format.arguments/format.args/ctor.pass.cpp b/libcxx/test/std/utilities/format/format.arguments/format.args/ctor.pass.cpp --- a/libcxx/test/std/utilities/format/format.arguments/format.args/ctor.pass.cpp +++ b/libcxx/test/std/utilities/format/format.arguments/format.args/ctor.pass.cpp @@ -8,6 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/arg.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/arg.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/arg.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/arg.pass.cpp @@ -8,6 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/ctor.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/ctor.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/ctor.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/ctor.pass.cpp @@ -9,6 +9,8 @@ // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-localization // UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // REQUIRES: locale.en_US.UTF-8 // REQUIRES: locale.fr_FR.UTF-8 diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/locale.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/locale.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/locale.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.context/locale.pass.cpp @@ -9,6 +9,8 @@ // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-localization // UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // REQUIRES: locale.en_US.UTF-8 // REQUIRES: locale.fr_FR.UTF-8 diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.handle.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.handle.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.handle.pass.cpp @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-no-concepts +// UNSUPPORTED: libcpp-has-no-incomplete-format + +// + +// A user defined formatter using +// template +// class basic_format_arg::handle + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" + +enum class color { black, red, gold }; +const char* color_names[] = {"black", "red", "gold"}; + +template <> +struct std::formatter : std::formatter { + auto format(color c, format_context& ctx) { + return formatter::format(color_names[static_cast(c)], ctx); + } +}; + +void test(std::string expected, std::string_view fmt, color arg) { + auto parse_ctx = std::format_parse_context(fmt); + std::formatter formatter; + static_assert(std::semiregular); + + auto it = formatter.parse(parse_ctx); + assert(it == fmt.end() - (!fmt.empty() && fmt.back() == '}')); + + std::string result; + auto out = std::back_inserter(result); + using FormatCtxT = std::basic_format_context; + + auto format_ctx = std::__format_context_create(out, std::make_format_args(arg)); + formatter.format(arg, format_ctx); + assert(result == expected); +} + +void test_termination_condition(std::string expected, std::string f, color arg) { + // 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::string_view fmt{f}; + assert(fmt.back() == '}' && "Pre-condition failure"); + + test(expected, fmt, arg); + fmt.remove_suffix(1); + test(expected, fmt, arg); +} + +int main(int, char**) { + test_termination_condition("black", "}", color::black); + test_termination_condition("red", "}", color::red); + test_termination_condition("gold", "}", color::gold); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.char.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.char.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.char.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.char.pass.cpp @@ -8,6 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.signed_integral.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.signed_integral.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.signed_integral.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.signed_integral.pass.cpp @@ -8,6 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // diff --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.unsigned_integral.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.unsigned_integral.pass.cpp --- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.unsigned_integral.pass.cpp +++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.unsigned_integral.pass.cpp @@ -8,6 +8,8 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: libcpp-no-concepts // UNSUPPORTED: libcpp-has-no-incomplete-format +// TODO FMT Evaluate gcc-11 status +// UNSUPPORTED: gcc-11 // diff --git a/libcxx/test/std/utilities/format/format.functions/format_tests.h b/libcxx/test/std/utilities/format/format.functions/format_tests.h --- a/libcxx/test/std/utilities/format/format.functions/format_tests.h +++ b/libcxx/test/std/utilities/format/format.functions/format_tests.h @@ -10,9 +10,12 @@ #include -#include "make_string.h" - +#include +#include #include +#include + +#include "make_string.h" // In this file the following template types are used: // TestFunction must be callable as check(expected-result, string-to-format, args-to-format...) @@ -39,6 +42,93 @@ template using context_t = typename context::type; +// A user-defined type used to test the handle formatter. +enum class status : uint16_t { foo = 0xAAAA, bar = 0x5555, foobar = 0xAA55 }; + +// The formatter for a user-defined type used to test the handle formatter. +template +struct std::formatter { + int type = 0; + + constexpr auto parse(auto& parse_ctx) -> decltype(parse_ctx.begin()) { + auto begin = parse_ctx.begin(); + auto end = parse_ctx.end(); + if (begin == end) + return begin; + + switch (*begin) { + case CharT('x'): + break; + case CharT('X'): + type = 1; + break; + case CharT('s'): + type = 2; + break; + case CharT('}'): + return begin; + default: + throw_format_error("The format-spec type has a type not supported for a status argument"); + } + + ++begin; + if (begin != end && *begin != CharT('}')) + throw_format_error("The format-spec should consume the input or end with a '}'"); + + return begin; + } + + auto format(status s, auto& ctx) -> decltype(ctx.out()) { + const char* names[] = {"foo", "bar", "foobar"}; + char buffer[6]; + const char* begin; + const char* end; + switch (type) { + case 0: + begin = buffer; + buffer[0] = '0'; + buffer[1] = 'x'; + end = std::to_chars(&buffer[2], std::end(buffer), static_cast(s), 16).ptr; + break; + + case 1: + begin = buffer; + buffer[0] = '0'; + buffer[1] = 'X'; + end = std::to_chars(&buffer[2], std::end(buffer), static_cast(s), 16).ptr; + std::transform(static_cast(&buffer[2]), end, &buffer[2], [](char c) { return std::toupper(c); }); + break; + + case 2: + switch (s) { + case status::foo: + begin = names[0]; + break; + case status::bar: + begin = names[1]; + break; + case status::foobar: + begin = names[2]; + break; + } + end = begin + strlen(begin); + break; + } + + return std::copy(begin, end, ctx.out()); + } + +private: + void throw_format_error(const char* s) { +#ifndef _LIBCPP_NO_EXCEPTIONS + throw std::format_error(s); +#else + (void)s; + std::abort(); +#endif + } +}; + template std::vector> invalid_types(std::string valid) { std::vector> result; @@ -2505,6 +2595,29 @@ check_exception("The format-spec type has a type not supported for a pointer argument", fmt, P(nullptr)); } +template +void format_test_handle(TestFunction check, ExceptionTest check_exception) { + // *** Valid permuatations *** + check(STR("answer is '0xaaaa'"), STR("answer is '{}'"), status::foo); + check(STR("answer is '0xaaaa'"), STR("answer is '{:x}'"), status::foo); + check(STR("answer is '0XAAAA'"), STR("answer is '{:X}'"), status::foo); + check(STR("answer is 'foo'"), STR("answer is '{:s}'"), status::foo); + + check(STR("answer is '0x5555'"), STR("answer is '{}'"), status::bar); + check(STR("answer is '0x5555'"), STR("answer is '{:x}'"), status::bar); + check(STR("answer is '0X5555'"), STR("answer is '{:X}'"), status::bar); + check(STR("answer is 'bar'"), STR("answer is '{:s}'"), status::bar); + + check(STR("answer is '0xaa55'"), STR("answer is '{}'"), status::foobar); + check(STR("answer is '0xaa55'"), STR("answer is '{:x}'"), status::foobar); + check(STR("answer is '0XAA55'"), STR("answer is '{:X}'"), status::foobar); + check(STR("answer is 'foobar'"), STR("answer is '{:s}'"), status::foobar); + + // *** type *** + for (const auto& fmt : invalid_types("xXs")) + check_exception("The format-spec type has a type not supported for a status argument", fmt, status::foo); +} + template void format_test_pointer(TestFunction check, ExceptionTest check_exception) { format_test_pointer(check, check_exception); @@ -2645,6 +2758,9 @@ check(STR("hello 0x42"), STR("hello {}"), reinterpret_cast(0x42)); check(STR("hello 0x42"), STR("hello {}"), reinterpret_cast(0x42)); format_test_pointer(check, check_exception); + + // *** Test handle formatter argument *** + format_test_handle(check, check_exception); } #ifndef TEST_HAS_NO_WIDE_CHARACTERS