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 @@ -30,7 +30,7 @@ `P2286R8 `__,"Formatting ranges" `[format.syn] `_,"Concept ``formattable``",,Mark de Wever,|Complete|, Clang 16 `[format.string.std] `_,"std-format-spec ``type`` debug",,Mark de Wever,|Complete|,Clang 16 -`[format.range] `_,"Formatting for ranges: sequences",,Mark de Wever,|In Progress|, +`[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: ``pair`` and ``tuple``",,Mark de Wever,|Complete|,Clang 16 diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -326,6 +326,7 @@ __format/formatter_tuple.h __format/parser_std_format_spec.h __format/range_default_formatter.h + __format/range_formatter.h __format/unicode.h __functional/binary_function.h __functional/binary_negate.h diff --git a/libcxx/include/__format/buffer.h b/libcxx/include/__format/buffer.h --- a/libcxx/include/__format/buffer.h +++ b/libcxx/include/__format/buffer.h @@ -27,6 +27,11 @@ #include <__iterator/incrementable_traits.h> #include <__iterator/iterator_traits.h> #include <__iterator/wrap_iter.h> +#include <__memory/addressof.h> +#include <__memory/allocate_at_least.h> +#include <__memory/construct_at.h> +#include <__memory/ranges_construct_at.h> +#include <__memory/uninitialized_algorithms.h> #include <__utility/move.h> #include #include @@ -493,6 +498,126 @@ return {_VSTD::move(this->__writer_).__out_it(), this->__size_}; } }; + +# if _LIBCPP_STD_VER > 20 +// A dynamically growing buffer intended to be used for retargeting a context. +// +// P2286 Formatting ranges adds range formatting support. It allows the user to +// specify the minimum width for the entire formatted range. The width of the +// range is not known until the range is formatted. Formatting is done to an +// output_iterator so there's no guarantee it would be possible to add the fill +// to the front of the output. Instead the range is formatted to a temporary +// buffer and that buffer is formatted as a string. +// +// There is an issue with that approach, the format context used in +// std::formatter::format contains the output iterator used as part of its +// type. So using this output iterator means there needs to be a new format +// context and the format arguments need to be retargeted to the new context. +// This retargeting is done by a basic_format_context specialized for the +// __iterator of this container. +template <__fmt_char_type _CharT> +class _LIBCPP_TEMPLATE_VIS __retarget_buffer { +public: + using value_type = _CharT; + + struct __iterator { + using difference_type = ptrdiff_t; + + _LIBCPP_HIDE_FROM_ABI constexpr explicit __iterator(__retarget_buffer& __buffer) + : __buffer_(std::addressof(__buffer)) {} + _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator=(const _CharT& __c) { + __buffer_->push_back(__c); + return *this; + } + _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator=(_CharT&& __c) { + __buffer_->push_back(__c); + return *this; + } + + _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator*() { return *this; } + _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() { return *this; } + _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator++(int) { return *this; } + __retarget_buffer* __buffer_; + }; + + __retarget_buffer(const __retarget_buffer&) = delete; + __retarget_buffer& operator=(const __retarget_buffer&) = delete; + + _LIBCPP_HIDE_FROM_ABI explicit __retarget_buffer(size_t __size_hint) { + allocation_result __result = allocator<_CharT>{}.allocate_at_least(__size_hint); + __ptr_ = __result.ptr; + __capacity_ = __result.count; + } + + _LIBCPP_HIDE_FROM_ABI ~__retarget_buffer() { + ranges::destroy_n(__ptr_, __size_); + allocator<_CharT>{}.deallocate(__ptr_, __capacity_); + } + + _LIBCPP_HIDE_FROM_ABI __iterator __make_output_iterator() { return __iterator{*this}; } + + _LIBCPP_HIDE_FROM_ABI void push_back(_CharT __c) { + std::construct_at(__ptr_ + __size_++, __c); + + if (__size_ == __capacity_) + __grow_buffer(); + } + + template <__fmt_char_type _InCharT> + _LIBCPP_HIDE_FROM_ABI void __copy(basic_string_view<_InCharT> __str) { + size_t __n = __str.size(); + if (__size_ + __n >= __capacity_) + // Push_back requires the buffer to have room for at least one character. + __grow_buffer(__size_ + __n + 1); + + std::uninitialized_copy_n(__str.data(), __n, __ptr_ + __size_); + __size_ += __n; + } + + template <__fmt_char_type _InCharT, class _UnaryOperation> + _LIBCPP_HIDE_FROM_ABI void __transform(const _InCharT* __first, const _InCharT* __last, _UnaryOperation __operation) { + _LIBCPP_ASSERT(__first <= __last, "not a valid range"); + + size_t __n = static_cast(__last - __first); + if (__size_ + __n >= __capacity_) + // Push_back requires the buffer to have room for at least one character. + __grow_buffer(__size_ + __n + 1); + + std::uninitialized_default_construct_n(__ptr_ + __size_, __n); + std::transform(__first, __last, __ptr_ + __size_, std::move(__operation)); + __size_ += __n; + } + + _LIBCPP_HIDE_FROM_ABI void __fill(size_t __n, _CharT __value) { + if (__size_ + __n >= __capacity_) + // Push_back requires the buffer to have room for at least one character. + __grow_buffer(__size_ + __n + 1); + + std::uninitialized_fill_n(__ptr_ + __size_, __n, __value); + __size_ += __n; + } + + _LIBCPP_HIDE_FROM_ABI basic_string_view<_CharT> __view() { return {__ptr_, __size_}; } + +private: + _LIBCPP_HIDE_FROM_ABI void __grow_buffer() { __grow_buffer(__capacity_ * 1.6); } + + _LIBCPP_HIDE_FROM_ABI void __grow_buffer(size_t __capacity) { + _LIBCPP_ASSERT(__capacity > __capacity_, "the buffer must grow"); + allocation_result __result = allocator<_CharT>{}.allocate_at_least(__capacity); + std::uninitialized_move_n(__ptr_, __size_, __result.ptr); + ranges::destroy_n(__ptr_, __size_); + allocator<_CharT>{}.deallocate(__ptr_, __capacity_); + + __ptr_ = __result.ptr; + __capacity_ = __result.count; + } + _CharT* __ptr_; + size_t __capacity_; + size_t __size_{0}; +}; +# endif //_LIBCPP_STD_VER > 20 + } // namespace __format #endif //_LIBCPP_STD_VER > 17 diff --git a/libcxx/include/__format/format_context.h b/libcxx/include/__format/format_context.h --- a/libcxx/include/__format/format_context.h +++ b/libcxx/include/__format/format_context.h @@ -11,13 +11,19 @@ #define _LIBCPP___FORMAT_FORMAT_CONTEXT_H #include <__availability> +#include <__concepts/same_as.h> #include <__config> #include <__format/buffer.h> +#include <__format/format_arg.h> +#include <__format/format_arg_store.h> #include <__format/format_args.h> +#include <__format/format_error.h> #include <__format/format_fwd.h> #include <__iterator/back_insert_iterator.h> #include <__iterator/concepts.h> +#include <__memory/addressof.h> #include <__utility/move.h> +#include <__variant/monostate.h> #include #ifndef _LIBCPP_HAS_NO_LOCALIZATION @@ -138,8 +144,79 @@ : __out_it_(_VSTD::move(__out_it)), __args_(__args) {} #endif }; -_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(basic_format_context); +# if _LIBCPP_STD_VER > 20 +// A specialization for __retarget_buffer +// +// See __retarget_buffer for the motivation for this specialization. +// +// This context holds a reference to the instance of the basic_format_context +// that is retargeted. It converts a formatting argument when it is requested +// during formatting. It is expected that the usage of the arguments is rare so +// the lookups are not expected to be used often. An alternative would be to +// convert all elements during construction. +// +// The elements of the retargets context are only used when an underlying +// formatter uses a locale specific formatting or an formatting argument is +// part for the format spec. For example +// format("{:256:{}}", input, 8); +// Here the width of an element in input is determined dynamically. +// Note when the top-level element has no width the retargeting is not needed. +template +class _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT + basic_format_context::__iterator, _CharT> { +public: + using iterator = typename __format::__retarget_buffer<_CharT>::__iterator; + using char_type = _CharT; + template + using formatter_type = formatter<_Tp, _CharT>; + + template + _LIBCPP_HIDE_FROM_ABI explicit basic_format_context(iterator __out_it, _Context& __ctx) + : __out_it_(std::move(__out_it)), +# ifndef _LIBCPP_HAS_NO_LOCALIZATION + __loc_([](void* __c) { return static_cast<_Context*>(__c)->locale(); }), +# endif + __ctx_(std::addressof(__ctx)), + __arg_([](void* __c, size_t __id) { + return std::visit_format_arg( + [&](auto __arg) -> basic_format_arg { + if constexpr (same_as) + return {}; + else if constexpr (same_as::handle>) + // At the moment it's not possible for formatting to use a re-targeted handle. + // TODO FMT add this when support is needed. + std::__throw_format_error("Re-targeting handle not supported"); + else + return basic_format_arg{ + __format::__determine_arg_t(), + __basic_format_arg_value(__arg)}; + }, + static_cast<_Context*>(__c)->arg(__id)); + }) { + } + + _LIBCPP_HIDE_FROM_ABI basic_format_arg arg(size_t __id) const noexcept { + return __arg_(__ctx_, __id); + } +# ifndef _LIBCPP_HAS_NO_LOCALIZATION + _LIBCPP_HIDE_FROM_ABI _VSTD::locale locale() { return __loc_(__ctx_); } +# endif + _LIBCPP_HIDE_FROM_ABI iterator out() { return std::move(__out_it_); } + _LIBCPP_HIDE_FROM_ABI void advance_to(iterator __it) { __out_it_ = std::move(__it); } + +private: + iterator __out_it_; + +# ifndef _LIBCPP_HAS_NO_LOCALIZATION + std::locale (*__loc_)(void* __ctx); +# endif + + void* __ctx_; + basic_format_arg (*__arg_)(void* __ctx, size_t __id); +}; +# endif //_LIBCPP_STD_VER > 20 +_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(basic_format_context); #endif //_LIBCPP_STD_VER > 17 _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/__format/formatter_output.h b/libcxx/include/__format/formatter_output.h --- a/libcxx/include/__format/formatter_output.h +++ b/libcxx/include/__format/formatter_output.h @@ -102,6 +102,12 @@ if constexpr (_VSTD::same_as>>) { __out_it.__get_container()->__copy(__str); return __out_it; +# if _LIBCPP_STD_VER > 20 + } else if constexpr (_VSTD::same_as::__iterator>) { + __out_it.__buffer_->__copy(__str); + return __out_it; +# endif } else { return std::ranges::copy(__str, _VSTD::move(__out_it)).out; } @@ -132,6 +138,12 @@ if constexpr (_VSTD::same_as>>) { __out_it.__get_container()->__transform(__first, __last, _VSTD::move(__operation)); return __out_it; +# if _LIBCPP_STD_VER > 20 + } else if constexpr (_VSTD::same_as::__iterator>) { + __out_it.__buffer_->__transform(__first, __last, _VSTD::move(__operation)); + return __out_it; +# endif } else { return std::ranges::transform(__first, __last, _VSTD::move(__out_it), __operation).out; } @@ -145,6 +157,11 @@ if constexpr (_VSTD::same_as>>) { __out_it.__get_container()->__fill(__n, __value); return __out_it; +# if _LIBCPP_STD_VER > 20 + } else if constexpr (_VSTD::same_as::__iterator>) { + __out_it.__buffer_->__fill(__n, __value); + return __out_it; +# endif } else { return std::ranges::fill_n(_VSTD::move(__out_it), __n, __value); } diff --git a/libcxx/include/__format/formatter_string.h b/libcxx/include/__format/formatter_string.h --- a/libcxx/include/__format/formatter_string.h +++ b/libcxx/include/__format/formatter_string.h @@ -65,6 +65,12 @@ _LIBCPP_ASSERT(__str, "The basic_format_arg constructor should have " "prevented an invalid pointer."); + __format_spec::__parsed_specifications<_CharT> __specs = _Base::__parser_.__get_parsed_std_specifications(__ctx); +# if _LIBCPP_STD_VER > 20 + if (_Base::__parser_.__type_ == __format_spec::__type::__debug) + return __formatter::__format_escaped_string(basic_string_view<_CharT>{__str}, __ctx.out(), __specs); +# endif + // When using a center or right alignment and the width option the length // of __str must be known to add the padding upfront. This case is handled // by the base class by converting the argument to a basic_string_view. @@ -76,7 +82,6 @@ // now these optimizations aren't implemented. Instead the base class // handles these options. // TODO FMT Implement these improvements. - __format_spec::__parsed_specifications<_CharT> __specs = _Base::__parser_.__get_parsed_std_specifications(__ctx); if (__specs.__has_width() || __specs.__has_precision()) return __formatter::__write_string(basic_string_view<_CharT>{__str}, __ctx.out(), __specs); diff --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h --- a/libcxx/include/__format/parser_std_format_spec.h +++ b/libcxx/include/__format/parser_std_format_spec.h @@ -139,6 +139,7 @@ # if _LIBCPP_STD_VER > 20 inline constexpr __fields __fields_tuple{.__type_ = false, .__allow_colon_in_fill_ = true}; +inline constexpr __fields __fields_range{.__type_ = false, .__allow_colon_in_fill_ = true}; # endif enum class _LIBCPP_ENUM_VIS __alignment : uint8_t { 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 @@ -19,9 +19,11 @@ #include <__config> #include <__format/concepts.h> #include <__format/formatter.h> +#include <__format/range_formatter.h> #include <__ranges/concepts.h> #include <__type_traits/remove_cvref.h> #include <__utility/pair.h> +#include #include _LIBCPP_BEGIN_NAMESPACE_STD @@ -104,7 +106,28 @@ template struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT __range_default_formatter { - __range_default_formatter() = delete; // TODO FMT Implement +private: + using __maybe_const_r = __fmt_maybe_const<_Rp, _CharT>; + range_formatter>, _CharT> __underlying_; + +public: + _LIBCPP_HIDE_FROM_ABI constexpr void set_separator(basic_string_view<_CharT> __separator) { + __underlying_.set_separator(__separator); + } + _LIBCPP_HIDE_FROM_ABI constexpr void + set_brackets(basic_string_view<_CharT> __opening_bracket, basic_string_view<_CharT> __closing_bracket) { + __underlying_.set_brackets(__opening_bracket, __closing_bracket); + } + + 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_r& __range, FormatContext& __ctx) const { + return __underlying_.format(__range, __ctx); + } }; template diff --git a/libcxx/include/__format/range_formatter.h b/libcxx/include/__format/range_formatter.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__format/range_formatter.h @@ -0,0 +1,245 @@ +// -*- 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_RANGE_FORMATTER_H +#define _LIBCPP___FORMAT_RANGE_FORMATTER_H + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +#include <__algorithm/ranges_copy.h> +#include <__availability> +#include <__chrono/statically_widen.h> +#include <__concepts/same_as.h> +#include <__config> +#include <__format/buffer.h> +#include <__format/concepts.h> +#include <__format/format_args.h> +#include <__format/format_context.h> +#include <__format/format_error.h> +#include <__format/formatter.h> +#include <__format/formatter_output.h> +#include <__format/parser_std_format_spec.h> +#include <__iterator/back_insert_iterator.h> +#include <__ranges/concepts.h> +#include <__type_traits/remove_cvref.h> +#include + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 20 + +template + requires same_as, _Tp> && formattable<_Tp, _CharT> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT range_formatter { + _LIBCPP_HIDE_FROM_ABI constexpr void set_separator(basic_string_view<_CharT> __separator) { + __separator_ = __separator; + } + _LIBCPP_HIDE_FROM_ABI constexpr void + set_brackets(basic_string_view<_CharT> __opening_bracket, basic_string_view<_CharT> __closing_bracket) { + __opening_bracket_ = __opening_bracket; + __closing_bracket_ = __closing_bracket; + } + + _LIBCPP_HIDE_FROM_ABI constexpr formatter<_Tp, _CharT>& underlying() { return __underlying_; } + _LIBCPP_HIDE_FROM_ABI constexpr const formatter<_Tp, _CharT>& underlying() const { return __underlying_; } + + template + _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __parse_ctx) { + const _CharT* __begin = __parser_.__parse(__parse_ctx, __format_spec::__fields_range); + const _CharT* __end = __parse_ctx.end(); + if (__begin == __end) + return __begin; + + // The n field overrides a possible m type, therefore delay applying the + // effect of n until the type has been procesed. + bool __clear_brackets = (*__begin == _CharT('n')); + if (__clear_brackets) { + ++__begin; + if (__begin == __end) { + // Since there is no more data, clear the brackets before returning. + set_brackets({}, {}); + return __begin; + } + } + + __parse_type(__begin, __end); + if (__clear_brackets) + set_brackets({}, {}); + if (__begin == __end) + return __begin; + + bool __has_range_underlying_spec = *__begin == _CharT(':'); + if (__parser_.__type_ != __format_spec::__type::__default) { + // [format.range.formatter]/6 + // If the range-type is s or ?s, then there shall be no n option and no + // range-underlying-spec. + if (__clear_brackets) { + if (__parser_.__type_ == __format_spec::__type::__string) + std::__throw_format_error("The n option and type s can't be used together"); + std::__throw_format_error("The n option and type ?s can't be used together"); + } + if (__has_range_underlying_spec) { + if (__parser_.__type_ == __format_spec::__type::__string) + std::__throw_format_error("Type s and an underlying format specification can't be used together"); + std::__throw_format_error("Type ?s and an underlying format specification can't be used together"); + } + } else if (!__has_range_underlying_spec) + std::__set_debug_format(__underlying_); + + if (__has_range_underlying_spec) { + // range-underlying-spec: + // : format-spec + ++__begin; + if (__begin == __end) + return __begin; + + __parse_ctx.advance_to(__begin); + __begin = __underlying_.parse(__parse_ctx); + } + + if (__begin != __end && *__begin != _CharT('}')) + std::__throw_format_error("The format-spec should consume the input or end with a '}'"); + + return __begin; + } + + template + requires formattable, _CharT> && + same_as>, _Tp> + _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator format(_Rp&& __range, _FormatContext& __ctx) const { + __format_spec::__parsed_specifications<_CharT> __specs = __parser_.__get_parsed_std_specifications(__ctx); + + if (!__specs.__has_width()) + return __format_range(__range, __ctx, __specs); + + // The size of the buffer needed is: + // - open bracket characters + // - close bracket character + // - n elements where every element may have a different size + // - (n -1) separators + // The size of the element is hard to predict, knowing the type helps but + // it depends on the format-spec. As an initial estimate we guess 6 + // characters. + // Typically both brackets are 1 character and the separator is 2 + // characters. Which means there will be + // (n - 1) * 2 + 1 + 1 = n * 2 character + // So estimate 8 times the range size as buffer. + __format::__retarget_buffer<_CharT> __buffer{8 * ranges::size(__range)}; + basic_format_context::__iterator, _CharT> __c{ + __buffer.__make_output_iterator(), __ctx}; + + __format_range(__range, __c, __specs); + + return __formatter::__write_string_no_precision(__buffer.__view(), __ctx.out(), __specs); + } + + template + typename _FormatContext::iterator _LIBCPP_HIDE_FROM_ABI + __format_range(_Rp&& __range, _FormatContext& __ctx, __format_spec::__parsed_specifications<_CharT> __specs) const { + if constexpr (same_as<_Tp, _CharT>) { + switch (__specs.__std_.__type_) { + case __format_spec::__type::__string: + case __format_spec::__type::__debug: + return __format_as_string(__range, __ctx, __specs.__std_.__type_ == __format_spec::__type::__debug); + default: + return __format_as_sequence(__range, __ctx); + } + } else + return __format_as_sequence(__range, __ctx); + } + + template + _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator + __format_as_string(_Rp&& __range, _FormatContext& __ctx, bool __debug_format) const { + // When the range is contiguous use a basic_string_view instead to avoid a + // copy of the underlying data. The basic_string_view formatter + // specialization is the "basic" string formatter in libc++. + if constexpr (ranges::contiguous_range<_Rp>) { + std::formatter, _CharT> __formatter; + if (__debug_format) + __formatter.set_debug_format(); + return __formatter.format(basic_string_view<_CharT>{__range.data(), __range.size()}, __ctx); + } else { + std::formatter, _CharT> __formatter; + if (__debug_format) + __formatter.set_debug_format(); + // P2106's from_range has not been implemented yet. Instead use a simple + // copy operation. + // TODO FMT use basic_string's "from_range" constructor. + // return std::formatter, _CharT>{}.format(basic_string<_CharT>{from_range, __range}, __ctx); + basic_string<_CharT> __str; + ranges::copy(__range, back_insert_iterator{__str}); + return __formatter.format(__str, __ctx); + } + } + + template + _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator + __format_as_sequence(_Rp&& __range, _FormatContext& __ctx) const { + __ctx.advance_to(ranges::copy(__opening_bracket_, __ctx.out()).out); + bool __use_separator = false; + for (auto&& __e : __range) { + if (__use_separator) + __ctx.advance_to(ranges::copy(__separator_, __ctx.out()).out); + else + __use_separator = true; + + __ctx.advance_to(__underlying_.format(__e, __ctx)); + } + + return ranges::copy(__closing_bracket_, __ctx.out()).out; + } + + __format_spec::__parser<_CharT> __parser_{.__alignment_ = __format_spec::__alignment::__left}; + +private: + _LIBCPP_HIDE_FROM_ABI constexpr void __parse_type(const _CharT*& __begin, const _CharT* __end) { + switch (*__begin) { + case _CharT('m'): + if constexpr (__fmt_pair_like<_Tp>) { + set_brackets(_LIBCPP_STATICALLY_WIDEN(_CharT, "{"), _LIBCPP_STATICALLY_WIDEN(_CharT, "}")); + set_separator(_LIBCPP_STATICALLY_WIDEN(_CharT, ", ")); + ++__begin; + } else + std::__throw_format_error("The range-format-spec type m requires two elements for a pair or tuple"); + break; + + case _CharT('s'): + if constexpr (same_as<_Tp, _CharT>) { + __parser_.__type_ = __format_spec::__type::__string; + ++__begin; + } else + std::__throw_format_error("The range-format-spec type s requires formatting a character type"); + break; + + case _CharT('?'): + ++__begin; + if (__begin == __end || *__begin != _CharT('s')) + std::__throw_format_error("The format-spec should consume the input or end with a '}'"); + if constexpr (same_as<_Tp, _CharT>) { + __parser_.__type_ = __format_spec::__type::__debug; + ++__begin; + } else + std::__throw_format_error("The range-format-spec type ?s requires formatting a character type"); + } + } + + formatter<_Tp, _CharT> __underlying_; + basic_string_view<_CharT> __separator_ = _LIBCPP_STATICALLY_WIDEN(_CharT, ", "); + basic_string_view<_CharT> __opening_bracket_ = _LIBCPP_STATICALLY_WIDEN(_CharT, "["); + basic_string_view<_CharT> __closing_bracket_ = _LIBCPP_STATICALLY_WIDEN(_CharT, "]"); +}; + +#endif //_LIBCPP_STD_VER > 20 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___FORMAT_RANGE_FORMATTER_H diff --git a/libcxx/include/__memory/allocate_at_least.h b/libcxx/include/__memory/allocate_at_least.h --- a/libcxx/include/__memory/allocate_at_least.h +++ b/libcxx/include/__memory/allocate_at_least.h @@ -25,6 +25,7 @@ _Pointer ptr; size_t count; }; +_LIBCPP_CTAD_SUPPORTED_FOR_TYPE(allocation_result); template [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr diff --git a/libcxx/include/format b/libcxx/include/format --- a/libcxx/include/format +++ b/libcxx/include/format @@ -130,6 +130,11 @@ requires same_as> constexpr range_format format_kind = see below; // since C++23 + // [format.range.formatter], class template range_formatter + template + requires same_as, T> && formattable + class range_formatter; // since C++23 + // [format.range.fmtdef], class template range-default-formatter template struct range-default-formatter; // exposition only, since C++23 @@ -194,6 +199,7 @@ #include <__format/formatter_tuple.h> #include <__format/parser_std_format_spec.h> #include <__format/range_default_formatter.h> +#include <__format/range_formatter.h> #include <__format/unicode.h> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) 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 @@ -870,6 +870,7 @@ module formatter_tuple { private header "__format/formatter_tuple.h" } module parser_std_format_spec { private header "__format/parser_std_format_spec.h" } module range_default_formatter { private header "__format/range_default_formatter.h" } + module range_formatter { private header "__format/range_formatter.h" } module unicode { private header "__format/unicode.h" } } } 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 @@ -358,6 +358,7 @@ #include <__format/formatter_tuple.h> // expected-error@*:* {{use of private header from outside its module: '__format/formatter_tuple.h'}} #include <__format/parser_std_format_spec.h> // expected-error@*:* {{use of private header from outside its module: '__format/parser_std_format_spec.h'}} #include <__format/range_default_formatter.h> // expected-error@*:* {{use of private header from outside its module: '__format/range_default_formatter.h'}} +#include <__format/range_formatter.h> // expected-error@*:* {{use of private header from outside its module: '__format/range_formatter.h'}} #include <__format/unicode.h> // expected-error@*:* {{use of private header from outside its module: '__format/unicode.h'}} #include <__functional/binary_function.h> // expected-error@*:* {{use of private header from outside its module: '__functional/binary_function.h'}} #include <__functional/binary_negate.h> // expected-error@*:* {{use of private header from outside its module: '__functional/binary_negate.h'}} 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 @@ -88,6 +88,7 @@ chrono initializer_list chrono limits chrono locale +chrono new chrono optional chrono ostream chrono ratio @@ -273,6 +274,7 @@ format initializer_list format limits format locale +format new format optional format stdexcept format string 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 @@ -200,11 +200,11 @@ // TODO validate whether the test is correct after the paper has been accepted. template void test_P2286() { - 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>(); + assert_is_formattable, CharT>(); assert_is_not_formattable, CharT>(); assert_is_not_formattable, CharT>(); @@ -220,9 +220,9 @@ assert_is_not_formattable, CharT>(); assert_is_not_formattable, CharT>(); - assert_is_not_formattable, CharT>(); + assert_is_formattable, CharT>(); - assert_is_not_formattable, CharT>(); + assert_is_formattable, CharT>(); assert_is_formattable, CharT>(); assert_is_formattable, CharT>(); diff --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.format.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.format.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.format.pass.cpp @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// 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.{{.+}} + +// + +// template +// requires same_as, T> && formattable +// class range_formatter; + +// 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/utilities/format/format.range/format.range.formatter/format.functions.tests.h b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.tests.h new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.tests.h @@ -0,0 +1,1259 @@ +//===----------------------------------------------------------------------===// +// 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_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FORMATTER_FORMAT_FUNCTIONS_TESTS_H +#define TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FORMATTER_FORMAT_FUNCTIONS_TESTS_H + +#include +#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("{:13?s}"), input); + check(SV(R"("\"Hello'"***)"), SV("{:*<13?s}"), input); + check(SV(R"(_"\"Hello'"__)"), SV("{:_^13?s}"), input); + check(SV(R"(###"\"Hello'")"), SV("{:#>13?s}"), input); + + check(SV(R"("\"Hello'" )"), SV("{:{}?s}"), input, 13); + check(SV(R"("\"Hello'"***)"), SV("{:*<{}?s}"), input, 13); + check(SV(R"(_"\"Hello'"__)"), SV("{:_^{}?s}"), input, 13); + check(SV(R"(###"\"Hello'")"), SV("{:#>{}?s}"), input, 13); + + check_exception("The format-spec range-fill field contains an invalid character", SV("{:} +void test_char(TestFunction check, ExceptionTest check_exception) { + test_char_default( + check, check_exception, std::array{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}); + + // This tests two different implementations in libc++. A basic_string_view + // formatter if the range is contiguous, a basic_string otherwise. + test_char_escaped_string( + check, + check_exception, + std::array{CharT('"'), CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o'), CharT('\'')}); + test_char_escaped_string( + check, + check_exception, + std::list{CharT('"'), CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o'), CharT('\'')}); + + // This tests two different implementations in libc++. A basic_string_view + // formatter if the range is contiguous, a basic_string otherwise. + test_char_string( + check, check_exception, std::array{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}); + test_char_string( + check, check_exception, std::list{CharT('H'), CharT('e'), CharT('l'), CharT('l'), CharT('o')}); +} + +// +// char -> wchar_t +// + +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +template +void test_char_to_wchar(TestFunction check, ExceptionTest check_exception) { + test_char_default(check, check_exception, std::array{'H', 'e', 'l', 'l', 'o'}); + + // 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::array{'H', 'e', 'l', 'l', 'o'}); + check_exception("The range-format-spec type ?s requires formatting a character type", + SV("{:?s}"), + std::array{'H', 'e', 'l', 'l', 'o'}); +} +#endif + +// +// Bool +// + +template +void test_bool(TestFunction check, ExceptionTest check_exception) { + std::array input{true, true, false}; + + 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); +} + +// +// Integral +// + +template +void test_int(TestFunction check, ExceptionTest check_exception, auto&& input) { + check(SV("[1, 2, 42, -42]"), SV("{}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[1, 2, 42, -42] "), SV("{:20}"), input); + check(SV("[1, 2, 42, -42]*****"), SV("{:*<20}"), input); + check(SV("__[1, 2, 42, -42]___"), SV("{:_^20}"), input); + check(SV("#####[1, 2, 42, -42]"), SV("{:#>20}"), input); + + check(SV("[1, 2, 42, -42] "), SV("{:{}}"), input, 20); + check(SV("[1, 2, 42, -42]*****"), SV("{:*<{}}"), input, 20); + check(SV("__[1, 2, 42, -42]___"), SV("{:_^{}}"), input, 20); + check(SV("#####[1, 2, 42, -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("__1, 2, 42, -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("[ 1, 2, 42, -42]"), SV("{::5}"), input); + check(SV("[1****, 2****, 42***, -42**]"), SV("{::*<5}"), input); + check(SV("[__1__, __2__, _42__, _-42_]"), SV("{::_^5}"), input); + check(SV("[::::1, ::::2, :::42, ::-42]"), SV("{:::>5}"), input); + + check(SV("[ 1, 2, 42, -42]"), SV("{::{}}"), input, 5); + check(SV("[1****, 2****, 42***, -42**]"), SV("{::*<{}}"), input, 5); + check(SV("[__1__, __2__, _42__, _-42_]"), SV("{::_^{}}"), input, 5); + check(SV("[::::1, ::::2, :::42, ::-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("[1, 2, 42, -42]"), SV("{::-}"), input); + check(SV("[+1, +2, +42, -42]"), SV("{::+}"), input); + check(SV("[ 1, 2, 42, -42]"), SV("{:: }"), input); + + // *** alternate form *** + check(SV("[0x1, 0x2, 0x2a, -0x2a]"), SV("{::#x}"), input); + + // *** zero-padding *** + check(SV("[00001, 00002, 00042, -0042]"), SV("{::05}"), input); + check(SV("[00001, 00002, 0002a, -002a]"), SV("{::05x}"), input); + check(SV("[0x001, 0x002, 0x02a, -0x2a]"), SV("{::#05x}"), input); + + // *** precision *** + check_exception("The format-spec should consume the input or end with a '}'", SV("{::.}"), input); + + // *** locale-specific form *** + check(SV("[1, 2, 42, -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("^^[::::1, ::::2, :::42, ::-42]^^^"), SV("{:^^33::>5}"), input); + check(SV("^^[::::1, ::::2, :::42, ::-42]^^^"), SV("{:^^{}::>5}"), input, 33); + check(SV("^^[::::1, ::::2, :::42, ::-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) { + test_int(check, check_exception, std::array{1, 2, 42, -42}); + test_int(check, check_exception, std::list{1, 2, 42, -42}); + test_int(check, check_exception, std::vector{1, 2, 42, -42}); + std::array input{1, 2, 42, -42}; + test_int(check, check_exception, std::span{input}); +} + +// +// 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) { + test_floating_point(check, check_exception, std::array{-42.5f, 0.0f, 1.25f, 42.5f}); + test_floating_point(check, check_exception, std::vector{-42.5, 0.0, 1.25, 42.5}); + + std::array input{-42.5l, 0.0l, 1.25l, 42.5l}; + test_floating_point(check, check_exception, std::span{input}); +} + +// +// 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) { + test_pointer(check, check_exception, std::array{nullptr}); + test_pointer(check, check_exception, std::array{static_cast(0)}); + test_pointer(check, check_exception, std::array{static_cast(0)}); +} + +// +// 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) { + test_string(check, check_exception, std::array{CSTR("Hello"), CSTR("world")}); + test_string(check, check_exception, std::array{STR("Hello"), STR("world")}); + test_string(check, check_exception, std::array{SV("Hello"), SV("world")}); +} + +// +// Handle +// + +template +void test_status(TestFunction check, ExceptionTest check_exception) { + std::array input{status::foo, status::bar, status::foobar}; + + 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); +} + +// +// Pair +// + +template +void test_pair_tuple(TestFunction check, ExceptionTest check_exception, auto&& input) { + // [format.range.formatter]/3 + // For range_formatter, the format-spec in a + // range-underlying-spec, if any, is interpreted by formatter. + // + // template + // constexpr typename ParseContext::iterator + // parse(ParseContext& ctx); + // [format.tuple]/7 + // ... if e.set_debug_format() is a valid expression, calls + // e.set_debug_format(). + // So when there is no range-underlying-spec, there is no need to call parse + // thus the char element is not escaped. + // TODO FMT P2733 addresses this issue. + check(SV("[(1, a), (42, *)]"), SV("{}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[(1, a), (42, *)] "), SV("{:22}"), input); + check(SV("[(1, a), (42, *)]*****"), SV("{:*<22}"), input); + check(SV("__[(1, a), (42, *)]___"), SV("{:_^22}"), input); + check(SV("#####[(1, a), (42, *)]"), SV("{:#>22}"), input); + + check(SV("[(1, a), (42, *)] "), SV("{:{}}"), input, 22); + check(SV("[(1, a), (42, *)]*****"), SV("{:*<{}}"), input, 22); + check(SV("__[(1, a), (42, *)]___"), SV("{:_^{}}"), input, 22); + check(SV("#####[(1, a), (42, *)]"), SV("{:#>{}}"), input, 22); + + 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("__(1, a), (42, *)___"), SV("{:_^20n}"), input); + check(SV("__(1, a), (42, *)___"), SV("{:_^20nm}"), input); // m should have no effect + + // *** type *** + check(SV("__{(1, a), (42, *)}___"), SV("{:_^22m}"), 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("[(1, 'a') , (42, '*') ]"), SV("{::11}"), input); + check(SV("[(1, 'a')***, (42, '*')**]"), SV("{::*<11}"), input); + check(SV("[_(1, 'a')__, _(42, '*')_]"), SV("{::_^11}"), input); + check(SV("[###(1, 'a'), ##(42, '*')]"), SV("{::#>11}"), input); + + check(SV("[(1, 'a') , (42, '*') ]"), SV("{::{}}"), input, 11); + check(SV("[(1, 'a')***, (42, '*')**]"), SV("{::*<{}}"), input, 11); + check(SV("[_(1, 'a')__, _(42, '*')_]"), SV("{::_^{}}"), input, 11); + check(SV("[###(1, 'a'), ##(42, '*')]"), SV("{::#>{}}"), input, 11); + + 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("{::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 *** + check(SV("[1: 'a', 42: '*']"), SV("{::m}"), input); + check(SV("[1, 'a', 42, '*']"), SV("{::n}"), input); + for (std::basic_string_view fmt : fmt_invalid_nested_types("")) + check_exception("The format-spec should consume the input or end with a '}'", fmt, input); + + // ***** Both have a format-spec + check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^31:#>11}"), input); + check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^31:#>11}"), input); + check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^{}:#>11}"), input, 31); + check(SV("^^[###(1, 'a'), ##(42, '*')]^^^"), SV("{:^^{}:#>{}}"), input, 31, 11); + + check_exception("Argument index out of bounds", SV("{:^^{}:#>5}"), input); + check_exception("Argument index out of bounds", SV("{:^^{}:#>{}}"), input, 31); + + check(SV("1: 'a', 42: '*'"), SV("{:n:m}"), input); + check(SV("1, 'a', 42, '*'"), SV("{:n:n}"), input); + check(SV("{1: 'a', 42: '*'}"), SV("{:m:m}"), input); + check(SV("{1, 'a', 42, '*'}"), SV("{:m:n}"), input); +} + +template +void test_pair_tuple(TestFunction check, ExceptionTest check_exception) { + test_pair_tuple( + check, check_exception, std::array{std::make_pair(1, CharT('a')), std::make_pair(42, CharT('*'))}); + test_pair_tuple( + check, check_exception, std::array{std::make_tuple(1, CharT('a')), std::make_tuple(42, CharT('*'))}); +} + +// +// Tuple 1 +// + +template +void test_tuple_int(TestFunction check, ExceptionTest check_exception) { + std::array input{std::make_tuple(42), std::make_tuple(99)}; + + check(SV("[(42), (99)]"), SV("{}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[(42), (99)] "), SV("{:17}"), input); + check(SV("[(42), (99)]*****"), SV("{:*<17}"), input); + check(SV("__[(42), (99)]___"), SV("{:_^17}"), input); + check(SV("#####[(42), (99)]"), SV("{:#>17}"), input); + + check(SV("[(42), (99)] "), SV("{:{}}"), input, 17); + check(SV("[(42), (99)]*****"), SV("{:*<{}}"), input, 17); + check(SV("__[(42), (99)]___"), SV("{:_^{}}"), input, 17); + check(SV("#####[(42), (99)]"), SV("{:#>{}}"), input, 17); + + 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), (99)___"), SV("{:_^15n}"), input); + + // *** type *** + check(SV("__{(42), (99)}___"), SV("{:_^17m}"), 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) , (99) ]"), SV("{::7}"), input); + check(SV("[(42)***, (99)***]"), SV("{::*<7}"), input); + check(SV("[_(42)__, _(99)__]"), SV("{::_^7}"), input); + check(SV("[###(42), ###(99)]"), SV("{::#>7}"), input); + + check(SV("[(42) , (99) ]"), SV("{::{}}"), input, 7); + check(SV("[(42)***, (99)***]"), SV("{::*<{}}"), input, 7); + check(SV("[_(42)__, _(99)__]"), SV("{::_^{}}"), input, 7); + check(SV("[###(42), ###(99)]"), SV("{::#>{}}"), input, 7); + + 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("{::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 *** + check(SV("[42, 99]"), SV("{::n}"), input); + for (std::basic_string_view fmt : fmt_invalid_nested_types("")) + check_exception("The format-spec should consume the input or end with a '}'", fmt, input); + + // ***** Both have a format-spec + check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^23:#>7}"), input); + check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^23:#>7}"), input); + check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^{}:#>7}"), input, 23); + check(SV("^^[###(42), ###(99)]^^^"), SV("{:^^{}:#>{}}"), input, 23, 7); + + check_exception("Argument index out of bounds", SV("{:^^{}:#>5}"), input); + check_exception("Argument index out of bounds", SV("{:^^{}:#>{}}"), input, 23); +} + +// +// Tuple 3 +// + +template +void test_tuple_int_int_int(TestFunction check, ExceptionTest check_exception) { + std::array input{std::make_tuple(42, 99, 0), std::make_tuple(1, 10, 100)}; + + check(SV("[(42, 99, 0), (1, 10, 100)]"), SV("{}"), input); + + // ***** underlying has no format-spec + + // *** align-fill & width *** + check(SV("[(42, 99, 0), (1, 10, 100)] "), SV("{:32}"), input); + check(SV("[(42, 99, 0), (1, 10, 100)]*****"), SV("{:*<32}"), input); + check(SV("__[(42, 99, 0), (1, 10, 100)]___"), SV("{:_^32}"), input); + check(SV("#####[(42, 99, 0), (1, 10, 100)]"), SV("{:#>32}"), input); + + check(SV("[(42, 99, 0), (1, 10, 100)] "), SV("{:{}}"), input, 32); + check(SV("[(42, 99, 0), (1, 10, 100)]*****"), SV("{:*<{}}"), input, 32); + check(SV("__[(42, 99, 0), (1, 10, 100)]___"), SV("{:_^{}}"), input, 32); + check(SV("#####[(42, 99, 0), (1, 10, 100)]"), SV("{:#>{}}"), input, 32); + + 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, 99, 0), (1, 10, 100)___"), SV("{:_^30n}"), input); + + // *** type *** + check(SV("__{(42, 99, 0), (1, 10, 100)}___"), SV("{:_^32m}"), 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, 99, 0) , (1, 10, 100) ]"), SV("{::14}"), input); + check(SV("[(42, 99, 0)***, (1, 10, 100)**]"), SV("{::*<14}"), input); + check(SV("[_(42, 99, 0)__, _(1, 10, 100)_]"), SV("{::_^14}"), input); + check(SV("[###(42, 99, 0), ##(1, 10, 100)]"), SV("{::#>14}"), input); + + check(SV("[(42, 99, 0) , (1, 10, 100) ]"), SV("{::{}}"), input, 14); + check(SV("[(42, 99, 0)***, (1, 10, 100)**]"), SV("{::*<{}}"), input, 14); + check(SV("[_(42, 99, 0)__, _(1, 10, 100)_]"), SV("{::_^{}}"), input, 14); + check(SV("[###(42, 99, 0), ##(1, 10, 100)]"), SV("{::#>{}}"), input, 14); + + 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("{::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 *** + check(SV("[42, 99, 0, 1, 10, 100]"), SV("{::n}"), input); + for (std::basic_string_view fmt : fmt_invalid_nested_types("s")) + check_exception("The format-spec should consume the input or end with a '}'", fmt, input); + + // ***** Both have a format-spec + check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^37:#>14}"), input); + check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^37:#>14}"), input); + check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^{}:#>14}"), input, 37); + check(SV("^^[###(42, 99, 0), ##(1, 10, 100)]^^^"), SV("{:^^{}:#>{}}"), input, 37, 14); + + check_exception("Argument index out of bounds", SV("{:^^{}:#>5}"), input); + check_exception("Argument index out of bounds", SV("{:^^{}:#>{}}"), input, 37); +} + +// +// 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 + + test_pair_tuple(check, check_exception); + test_tuple_int(check, check_exception); + test_tuple_int_int_int(check, check_exception); +} + +#endif // TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FORMATTER_FORMAT_FUNCTIONS_TESTS_H diff --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// 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.{{.+}} + +// + +// template +// requires same_as, T> && formattable +// class range_formatter; + +// 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/utilities/format/format.range/format.range.formatter/format.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/format.pass.cpp @@ -0,0 +1,72 @@ +//===----------------------------------------------------------------------===// +// 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.{{.+}} + +// + +// template +// requires same_as, T> && formattable +// class range_formatter + +// template +// typename FormatContext::iterator +// format(const T& ref, 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 "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, std::vector 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::range_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() { + test_format(SV("[1]"), std::vector{1}); + test_format(SV("[0]"), std::vector{0}); +} + +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/utilities/format/format.range/format.range.formatter/parse.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/parse.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/parse.pass.cpp @@ -0,0 +1,72 @@ +//===----------------------------------------------------------------------===// +// 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.{{.+}} + +// + +// template +// requires same_as, T> && formattable +// class range_formatter + +// 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 "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::range_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_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.range/format.range.formatter/set_brackets.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/set_brackets.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/set_brackets.pass.cpp @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// 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.{{.+}} + +// + +// template +// requires same_as, T> && formattable +// class range_formatter + +// constexpr void constexpr void set_brackets(basic_string_view opening, +// basic_string_view closing); + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions test. + +#include + +#include "make_string.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template +constexpr void test_setter() { + std::range_formatter formatter; + formatter.set_brackets(SV("open"), SV("close")); + + // Note there is no direct way to validate this function modified the object. +} + +constexpr bool test() { + test_setter(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_setter(); +#endif + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// 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.{{.+}} + +// + +// template +// requires same_as, T> && formattable +// class range_formatter + +// constexpr void set_separator(basic_string_view sep); + +// Note this tests the basics of this function. It's tested in more detail in +// the format functions test. + +#include + +#include "make_string.h" + +#define SV(S) MAKE_STRING_VIEW(CharT, S) + +template +constexpr void test_setter() { + std::range_formatter formatter; + formatter.set_separator(SV("sep")); + + // Note there is no direct way to validate this function modified the object. +} + +constexpr bool test() { + test_setter(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_setter(); +#endif + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.range/format.range.formatter/underlying.pass.cpp b/libcxx/test/std/utilities/format/format.range/format.range.formatter/underlying.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/utilities/format/format.range/format.range.formatter/underlying.pass.cpp @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// 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.{{.+}} + +// + +// template +// requires same_as, T> && formattable +// class range_formatter + +// constexpr formatter& underlying(); +// constexpr const formatter& underlying() const; + +#include +#include + +#include "test_macros.h" + +template +constexpr void test_underlying() { + { + std::range_formatter formatter; + [[maybe_unused]] std::same_as&> decltype(auto) underlying = formatter.underlying(); + } + { + const std::range_formatter formatter; + [[maybe_unused]] std::same_as&> decltype(auto) underlying = formatter.underlying(); + } +} + +constexpr bool test() { + test_underlying(); +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + test_underlying(); +#endif + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h b/libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h --- a/libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h +++ b/libcxx/test/std/utilities/format/format.tuple/format.functions.tests.h @@ -275,7 +275,7 @@ void test_nested(TestFunction check, ExceptionTest check_exception, Nested&& input) { // [format.formatter.spec]/2 // A debug-enabled specialization of formatter additionally provides a - // public, constexpr, non-static member function set_­debug_­format() + // public, constexpr, non-static member function set_debug_format() // which modifies the state of the formatter to be as if the type of the // std-format-spec parsed by the last call to parse were ?. // pair and tuple are not debug-enabled specializations to the diff --git a/libcxx/test/support/format.functions.common.h b/libcxx/test/support/format.functions.common.h --- a/libcxx/test/support/format.functions.common.h +++ b/libcxx/test/support/format.functions.common.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "make_string.h" @@ -129,4 +130,57 @@ } }; +// Creates format string for the invalid types. +// +// valid contains a list of types that are valid. +// - The type ?s is the only type requiring 2 characters, use S for that type. +// - Whether n is a type or not depends on the context, is is always used. +// +// The return value is a collection of basic_strings, instead of +// basic_string_views since the values are temporaries. +namespace detail { +template +std::basic_string get_colons() { + static std::basic_string result(N, CharT(':')); + return result; +} + +constexpr std::string_view get_format_types() { + return "aAbBcdeEfFgGopsxX" +#if TEST_STD_VER > 20 + "?" +#endif + ; +} + +template +std::vector> fmt_invalid_types(std::string_view valid) { + // std::ranges::to is not available in C++20. + std::vector> result; + std::ranges::copy( + get_format_types() | std::views::filter([&](char type) { return valid.find(type) == std::string_view::npos; }) | + std::views::transform([&](char type) { return std::format(SV("{{{}{}}}"), get_colons(), type); }), + std::back_inserter(result)); + return result; +} + +} // namespace detail + +// Creates format string for the invalid types. +// +// valid contains a list of types that are valid. +// +// The return value is a collection of basic_strings, instead of +// basic_string_views since the values are temporaries. +template +std::vector> fmt_invalid_types(std::string_view valid) { + return detail::fmt_invalid_types(valid); +} + +// Like fmt_invalid_types but when the format spec is for an underlying formatter. +template +std::vector> fmt_invalid_nested_types(std::string_view valid) { + return detail::fmt_invalid_types(valid); +} + #endif // TEST_SUPPORT_FORMAT_FUNCTIONS_COMMON_H