Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add range_formatter #4642

Merged
merged 37 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4dde166
Add `range_formatter`
JMazurkiewicz Apr 28, 2024
2e4fbbd
Rearrange entries in `expected_results.txt`
JMazurkiewicz Apr 30, 2024
f3e0e5e
Use `_Is_two_tuple` and remove `_Pair_like_with_formatter`
JMazurkiewicz Apr 30, 2024
2102ddd
Use `size_t` instead of `_String_view::size_type`
JMazurkiewicz Apr 30, 2024
2363bde
Remove `_String_view` alias
JMazurkiewicz Apr 30, 2024
5d2d147
Rename `_String_formatter` to `_String_view_formatter`
JMazurkiewicz Apr 30, 2024
bfabe6e
Add `if constexpr` to verify if `max(SignedLike) > max(size_t)`
JMazurkiewicz Apr 30, 2024
af78d8e
Fix condition `_Size >= _Size_t_max` => `_Size > _Size_t_max`
JMazurkiewicz Apr 30, 2024
adc5f8d
Fix parsing of `range-fill-and-align`
JMazurkiewicz May 2, 2024
783cd70
Merge remote-tracking branch 'upstream/main' into format/ranges
JMazurkiewicz May 2, 2024
753a515
Fix formatting
JMazurkiewicz May 2, 2024
2c5a252
`check_setters` -> `check_other_functions`
JMazurkiewicz May 14, 2024
ac8b925
Fix `range_formatter` for arbitrary format context
JMazurkiewicz May 14, 2024
24cdb13
Remove unnecessary `if` from `_Parse_range_specs`
JMazurkiewicz May 15, 2024
58c123f
Cleanup `check_range_formatter_with_arbitrary_context`
JMazurkiewicz May 15, 2024
e993011
Remove unnecessary call to `set_separator` from `range_formatter::parse`
JMazurkiewicz May 15, 2024
a0cc0c3
Fix order of actions in `range_formatter::parse`
JMazurkiewicz May 15, 2024
5e00dba
Merge remote-tracking branch 'upstream/main' into format/ranges
JMazurkiewicz May 25, 2024
f163372
Use `_Measure_display_width`
JMazurkiewicz May 26, 2024
c931c77
Address #4636
JMazurkiewicz May 26, 2024
8f60263
Add `_EXPORT_STD`
JMazurkiewicz May 27, 2024
05408d2
Always `break`, even for the last `case`.
StephanTLavavej May 29, 2024
875f9ce
Use `back_insert_iterator` CTAD.
StephanTLavavej May 29, 2024
60c261e
Flip test, avoid dead code with `if constexpr { } else { }`.
StephanTLavavej May 29, 2024
2132330
Mark `underlying()` as `_NODISCARD`.
StephanTLavavej May 29, 2024
b6c4d5e
Add `const`.
StephanTLavavej May 29, 2024
ee614c0
Simplify with `in_range`.
StephanTLavavej May 29, 2024
6f245a8
Improve messages, flip and split tests.
StephanTLavavej May 29, 2024
8a79bcc
Simplify `_Parse_range_specs` to always take `_Range_specs_setter<_Ch…
StephanTLavavej May 29, 2024
5027c57
`Debugable` => `Debuggable`
StephanTLavavej May 29, 2024
cd9efc2
Work around MSVC compiler bug.
StephanTLavavej May 29, 2024
d7c5a0b
Include more headers.
StephanTLavavej May 29, 2024
e0a73f2
Construct `format_error` with braces.
StephanTLavavej May 29, 2024
bba1416
Drop duplicated line.
StephanTLavavej May 29, 2024
690ae85
Order vs. chaos.
StephanTLavavej May 29, 2024
bd4704a
Fix missing `set_separator` for 'm', and test it.
StephanTLavavej May 29, 2024
b71b448
Reduce compiler memory consumption for the instantiation test.
StephanTLavavej May 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
253 changes: 251 additions & 2 deletions stl/inc/format
Original file line number Diff line number Diff line change
Expand Up @@ -3974,7 +3974,7 @@ _NODISCARD int _Measure_display_width(const basic_string_view<_CharT> _Value) {
enum class _Fmt_tuple_type : uint8_t { _None, _Key_value, _No_brackets };

template <class _CharT>
struct _Fill_align_and_width_specs { // used by pair, tuple, thread::id, and stacktrace_entry formatters
struct _Fill_align_and_width_specs {
int _Width = -1;
int _Dynamic_width_index = -1;
_Fmt_align _Alignment = _Fmt_align::_None;
Expand Down Expand Up @@ -4308,8 +4308,10 @@ public:
_Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Arg_id);
}

private:
protected:
_Fill_align_and_width_specs<_CharT>& _Specs;

private:
basic_format_parse_context<_CharT>& _Parse_ctx;

_NODISCARD static constexpr int _Verify_dynamic_arg_index_in_range(const size_t _Idx) {
Expand Down Expand Up @@ -4365,6 +4367,253 @@ public:
private:
_Fill_align_and_width_specs<_CharT> _Specs;
};

template <class _CharT>
struct _Range_specs : _Fill_align_and_width_specs<_CharT> {
bool _No_brackets = false;
char _Type = '\0';
};

template <class _CharT>
class _Range_specs_setter : public _Fill_align_and_width_specs_setter<_CharT> {
public:
constexpr explicit _Range_specs_setter(
_Range_specs<_CharT>& _Specs_, basic_format_parse_context<_CharT>& _Parse_ctx_)
: _Fill_align_and_width_specs_setter<_CharT>(_Specs_, _Parse_ctx_) {}

constexpr void _On_no_brackets() {
static_cast<_Range_specs<_CharT>&>(this->_Specs)._No_brackets = true;
}

constexpr void _On_type(const _CharT _Type) {
_STL_INTERNAL_CHECK(_Type == 'm' || _Type == 's' || _Type == '?');
static_cast<_Range_specs<_CharT>&>(this->_Specs)._Type = static_cast<char>(_Type);
}
};

template <class _CharT, class _CallbacksType>
_NODISCARD constexpr const _CharT* _Parse_range_specs(
const _CharT* _Begin, const _CharT* _End, _CallbacksType&& _Callbacks) {
if (_Begin == _End || *_Begin == '}' || *_Begin == ':') {
return _Begin;
}

_Begin = _Parse_align(_Begin, _End, _Callbacks);
if (_Begin == _End) {
return _Begin;
}

_Begin = _Parse_width(_Begin, _End, _Callbacks);
if (_Begin == _End) {
return _Begin;
}

if (*_Begin == 'n') {
_Callbacks._On_no_brackets();
if (++_Begin == _End) {
return _Begin;
}
}

switch (_CharT _Maybe_type = *_Begin) {
case '?':
if (++_Begin == _End || *_Begin != 's') {
_Throw_format_error("Invalid range-type '?'; did you mean '?s'?");
}
[[fallthrough]];
case 'm':
case 's':
_Callbacks._On_type(_Maybe_type);
++_Begin;
}

return _Begin;
}

_EXPORT_STD template <class _Ty, class _CharT = char>
requires same_as<remove_cvref_t<_Ty>, _Ty> && formattable<_Ty, _CharT>
class range_formatter {
private:
formatter<_Ty, _CharT> _Underlying;
basic_string_view<_CharT> _Separator = _STATICALLY_WIDEN(_CharT, ", ");
basic_string_view<_CharT> _Opening_bracket = _STATICALLY_WIDEN(_CharT, "[");
basic_string_view<_CharT> _Closing_bracket = _STATICALLY_WIDEN(_CharT, "]");
_Range_specs<_CharT> _Specs;

public:
constexpr void set_separator(basic_string_view<_CharT> _Sep) noexcept {
_Separator = _Sep;
}

constexpr void set_brackets(basic_string_view<_CharT> _Opening, basic_string_view<_CharT> _Closing) noexcept {
_Opening_bracket = _Opening;
_Closing_bracket = _Closing;
}

constexpr formatter<_Ty, _CharT>& underlying() noexcept {
return _Underlying;
}

constexpr const formatter<_Ty, _CharT>& underlying() const noexcept {
return _Underlying;
}

template <class _ParseContext>
constexpr _ParseContext::iterator parse(_ParseContext& _Ctx) {
_Range_specs_setter<_CharT> _Callback{_Specs, _Ctx};
auto _It = _STD _Parse_range_specs(_Ctx._Unchecked_begin(), _Ctx._Unchecked_end(), _Callback);

_Ctx.advance_to(_Ctx.begin() + (_It - _Ctx._Unchecked_begin()));
bool _Has_underlying_spec = false;
if (_It != _Ctx._Unchecked_end()) {
if (*_It == ':') {
_Has_underlying_spec = true;
_Ctx.advance_to(_Ctx.begin() + 1);
} else if (*_It != '}') {
_Throw_format_error("Invalid range-format-spec.");
}
}

_It = _Underlying.parse(_Ctx)._Unwrapped();
if (_It != _Ctx._Unchecked_end() && *_It != '}') {
_Throw_format_error("Missing '}' in format string.");
}

switch (_Specs._Type) {
case 'm':
if constexpr (!_Is_two_tuple<_Ty>) {
_Throw_format_error("Range-type 'm' requires type T to be specialization of pair or tuple such that "
"tuple_size_v<T> is 2.");

} else {
set_brackets(_STATICALLY_WIDEN(_CharT, "{"), _STATICALLY_WIDEN(_CharT, "}"));
_Underlying.set_brackets({}, {});
_Underlying.set_separator(_STATICALLY_WIDEN(_CharT, ": "));
}
[[fallthrough]];

case '\0':
if constexpr (requires { _Underlying.set_debug_format(); }) {
if (!_Has_underlying_spec) {
_Underlying.set_debug_format();
}
}
break;

case 's':
case '?':
if constexpr (!same_as<_Ty, _CharT>) {
_Throw_format_error("Range-types 's' and '?s' requires type T to be charT.");
}

if (_Specs._No_brackets || _Has_underlying_spec) {
_Throw_format_error(
"Range-types 's' and '?s' cannot be combined with 'n' option and range-underlying-spec.");
}
}

if (_Specs._No_brackets) {
set_brackets({}, {});
}

return _Ctx.begin() + (_It - _Ctx._Unchecked_begin());
}

template <_RANGES input_range _Range, class _FormatContext>
requires formattable<_RANGES range_reference_t<_Range>, _CharT>
&& same_as<remove_cvref_t<_RANGES range_reference_t<_Range>>, _Ty>
_FormatContext::iterator format(_Range&& _Rng, _FormatContext& _Ctx) const {
return _Format(_STD forward<_Range>(_Rng), _Ctx);
}

private:
template <_RANGES input_range _Range, class _FormatContext>
_FormatContext::iterator _Format(_Range&&, _FormatContext&) const {
_Throw_format_error("Unsupported 'basic_format_context'.");
}

template <_RANGES input_range _Range, class _FormatContext>
requires _Is_specialization_v<typename _FormatContext::iterator, back_insert_iterator>
&& derived_from<typename _FormatContext::iterator::container_type, _Fmt_buffer<_CharT>>
_FormatContext::iterator _Format(_Range&& _Rng, _FormatContext& _Ctx) const {
auto _Format_specs = _Specs;
if (_Specs._Dynamic_width_index >= 0) {
_Format_specs._Width =
_STD _Get_dynamic_specs<_Width_checker>(_Ctx.arg(static_cast<size_t>(_Specs._Dynamic_width_index)));
}

basic_string<_CharT> _Buffer;
{
_Fmt_iterator_buffer<back_insert_iterator<basic_string<_CharT>>, _CharT> _Fmt_buf(
_STD back_inserter(_Buffer));
using _Inserter = back_insert_iterator<_Fmt_buffer<_CharT>>;
auto _Nested_context = basic_format_context<_Inserter, _CharT>::_Make_from(
_Inserter{_Fmt_buf}, _Ctx._Get_args(), _Ctx._Get_lazy_locale());

if constexpr (same_as<_Ty, _CharT>) {
if (_Specs._Type == 's' || _Specs._Type == '?') {
_Format_as_string(_STD forward<_Range>(_Rng), _Nested_context, _Specs._Type == '?');
} else {
_Format_as_sequence(_STD forward<_Range>(_Rng), _Nested_context);
}
} else {
_Format_as_sequence(_STD forward<_Range>(_Rng), _Nested_context);
}
}

const int _Width = _Measure_display_width<_CharT>(_Buffer);
return _STD _Write_aligned(
_Ctx.out(), _Width, _Format_specs, _Fmt_align::_Left, [&](_FormatContext::iterator _Out) {
return _STD _Fmt_write(_STD move(_Out), basic_string_view{_Buffer});
});
}

template <_RANGES input_range _Range, class _FormatContext>
void _Format_as_sequence(_Range&& _Rng, _FormatContext& _Ctx) const {
_Ctx.advance_to(_STD _Fmt_write(_Ctx.out(), _Opening_bracket));
bool _Separate = false;
for (auto&& _Elem : _Rng) {
if (_Separate) {
_Ctx.advance_to(_STD _Fmt_write(_Ctx.out(), _Separator));
}

_Separate = true;
_Ctx.advance_to(_Underlying.format(_Elem, _Ctx));
}

(void) _STD _Fmt_write(_Ctx.out(), _Closing_bracket);
}

template <_RANGES input_range _Range, class _FormatContext>
void _Format_as_string(_Range&& _Rng, _FormatContext& _Ctx, bool _Debug) const {
if constexpr (_RANGES contiguous_range<_Range>) {
const auto _Size = _STD _To_unsigned_like(_RANGES distance(_Rng));

if constexpr (const auto _Size_t_max = (numeric_limits<size_t>::max)();
(numeric_limits<decltype(_Size)>::max)() > _Size_t_max) {
if (_Size > _Size_t_max) [[unlikely]] {
_Throw_format_error("Formatted range is too long.");
}
}

formatter<basic_string_view<_CharT>, _CharT> _String_view_formatter;
if (_Debug) {
_String_view_formatter.set_debug_format();
}

const basic_string_view<_CharT> _Str(_STD to_address(_RANGES begin(_Rng)), static_cast<size_t>(_Size));
_String_view_formatter.format(_Str, _Ctx);
} else {
using _String = basic_string<_CharT>;
formatter<_String, _CharT> _String_formatter;
if (_Debug) {
_String_formatter.set_debug_format();
}

_String_formatter.format(_String{from_range, _Rng}, _Ctx);
}
}
};
#endif // _HAS_CXX23
_STD_END

Expand Down
6 changes: 2 additions & 4 deletions tests/libcxx/expected_results.txt
Original file line number Diff line number Diff line change
Expand Up @@ -287,10 +287,6 @@ std/utilities/format/format.range/format.range.formatter/format.functions.format
std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp FAIL
std/utilities/format/format.range/format.range.formatter/format.pass.cpp FAIL
std/utilities/format/format.range/format.range.formatter/parse.pass.cpp FAIL
std/utilities/format/format.range/format.range.formatter/set_brackets.pass.cpp FAIL
std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp FAIL
std/utilities/format/format.range/format.range.formatter/underlying.pass.cpp FAIL
std/utilities/format/types.compile.pass.cpp FAIL

# P2363R5 Extending Associative Containers With The Remaining Heterogeneous Overloads
std/language.support/support.limits/support.limits.general/map.version.compile.pass.cpp FAIL
Expand Down Expand Up @@ -1124,6 +1120,8 @@ std/utilities/format/format.formatter/format.formatter.spec/formatter.pointer.pa
std/utilities/format/format.formatter/format.formatter.spec/formatter.signed_integral.pass.cpp FAIL
std/utilities/format/format.formatter/format.formatter.spec/formatter.string.pass.cpp FAIL
std/utilities/format/format.formatter/format.formatter.spec/formatter.unsigned_integral.pass.cpp FAIL
std/utilities/format/format.range/format.range.formatter/set_brackets.pass.cpp FAIL
std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp FAIL
std/utilities/format/format.tuple/format.pass.cpp FAIL

# Not analyzed. Apparent false positives from static analysis where it thinks that array indexing is out of bounds.
Expand Down
5 changes: 5 additions & 0 deletions tests/std/include/range_algorithm_support.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,11 @@ namespace test {
using RebindAsMoveOnly =
range<Category, Element, IsSized, Diff, IsCommon, Eq, Proxy, IsView, Copyability::move_only>;

template <class OtherElement>
using RebindElement = range<Category, OtherElement, IsSized, Diff, IsCommon, Eq, Proxy, IsView, Copy>;

static constexpr ProxyRef proxy_ref = Proxy;

using detail::range_base<Element, Copy>::range_base;

[[nodiscard]] constexpr I begin() const noexcept {
Expand Down
1 change: 1 addition & 0 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ tests\P2286R8_text_formatting_escaping
tests\P2286R8_text_formatting_escaping_legacy_text_encoding
tests\P2286R8_text_formatting_escaping_utf8
tests\P2286R8_text_formatting_formattable
tests\P2286R8_text_formatting_range_formatter
tests\P2286R8_text_formatting_tuple
tests\P2286R8_text_formatting_vector_bool_reference
tests\P2302R4_ranges_alg_contains
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\usual_latest_matrix.lst
Loading