Skip to content

Commit 0d32e40

Browse files
Add range_formatter (#4642)
Co-authored-by: Stephan T. Lavavej <stl@nuwen.net>
1 parent cce32f4 commit 0d32e40

File tree

6 files changed

+773
-6
lines changed

6 files changed

+773
-6
lines changed

stl/inc/format

Lines changed: 251 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3974,7 +3974,7 @@ _NODISCARD int _Measure_display_width(const basic_string_view<_CharT> _Value) {
39743974
enum class _Fmt_tuple_type : uint8_t { _None, _Key_value, _No_brackets };
39753975

39763976
template <class _CharT>
3977-
struct _Fill_align_and_width_specs { // used by pair, tuple, thread::id, and stacktrace_entry formatters
3977+
struct _Fill_align_and_width_specs {
39783978
int _Width = -1;
39793979
int _Dynamic_width_index = -1;
39803980
_Fmt_align _Alignment = _Fmt_align::_None;
@@ -4308,8 +4308,10 @@ public:
43084308
_Specs._Dynamic_width_index = _Verify_dynamic_arg_index_in_range(_Arg_id);
43094309
}
43104310

4311-
private:
4311+
protected:
43124312
_Fill_align_and_width_specs<_CharT>& _Specs;
4313+
4314+
private:
43134315
basic_format_parse_context<_CharT>& _Parse_ctx;
43144316

43154317
_NODISCARD static constexpr int _Verify_dynamic_arg_index_in_range(const size_t _Idx) {
@@ -4365,6 +4367,253 @@ public:
43654367
private:
43664368
_Fill_align_and_width_specs<_CharT> _Specs;
43674369
};
4370+
4371+
template <class _CharT>
4372+
struct _Range_specs : _Fill_align_and_width_specs<_CharT> {
4373+
bool _No_brackets = false;
4374+
char _Type = '\0';
4375+
};
4376+
4377+
template <class _CharT>
4378+
class _Range_specs_setter : public _Fill_align_and_width_specs_setter<_CharT> {
4379+
public:
4380+
constexpr explicit _Range_specs_setter(
4381+
_Range_specs<_CharT>& _Specs_, basic_format_parse_context<_CharT>& _Parse_ctx_)
4382+
: _Fill_align_and_width_specs_setter<_CharT>(_Specs_, _Parse_ctx_) {}
4383+
4384+
constexpr void _On_no_brackets() {
4385+
static_cast<_Range_specs<_CharT>&>(this->_Specs)._No_brackets = true;
4386+
}
4387+
4388+
constexpr void _On_type(const _CharT _Type) {
4389+
_STL_INTERNAL_CHECK(_Type == 'm' || _Type == 's' || _Type == '?');
4390+
static_cast<_Range_specs<_CharT>&>(this->_Specs)._Type = static_cast<char>(_Type);
4391+
}
4392+
};
4393+
4394+
template <class _CharT>
4395+
_NODISCARD constexpr const _CharT* _Parse_range_specs(
4396+
const _CharT* _Begin, const _CharT* const _End, _Range_specs_setter<_CharT>& _Callbacks) {
4397+
if (_Begin == _End || *_Begin == '}' || *_Begin == ':') {
4398+
return _Begin;
4399+
}
4400+
4401+
_Begin = _Parse_align(_Begin, _End, _Callbacks);
4402+
if (_Begin == _End) {
4403+
return _Begin;
4404+
}
4405+
4406+
_Begin = _Parse_width(_Begin, _End, _Callbacks);
4407+
if (_Begin == _End) {
4408+
return _Begin;
4409+
}
4410+
4411+
if (*_Begin == 'n') {
4412+
_Callbacks._On_no_brackets();
4413+
if (++_Begin == _End) {
4414+
return _Begin;
4415+
}
4416+
}
4417+
4418+
switch (const _CharT _Maybe_type = *_Begin) {
4419+
case '?':
4420+
if (++_Begin == _End || *_Begin != 's') {
4421+
_Throw_format_error("Invalid range-type '?'; was '?s' intended?");
4422+
}
4423+
[[fallthrough]];
4424+
case 'm':
4425+
case 's':
4426+
_Callbacks._On_type(_Maybe_type);
4427+
++_Begin;
4428+
break;
4429+
}
4430+
4431+
return _Begin;
4432+
}
4433+
4434+
_EXPORT_STD template <class _Ty, class _CharT = char>
4435+
requires same_as<remove_cvref_t<_Ty>, _Ty> && formattable<_Ty, _CharT>
4436+
class range_formatter {
4437+
private:
4438+
formatter<_Ty, _CharT> _Underlying;
4439+
basic_string_view<_CharT> _Separator = _STATICALLY_WIDEN(_CharT, ", ");
4440+
basic_string_view<_CharT> _Opening_bracket = _STATICALLY_WIDEN(_CharT, "[");
4441+
basic_string_view<_CharT> _Closing_bracket = _STATICALLY_WIDEN(_CharT, "]");
4442+
_Range_specs<_CharT> _Specs;
4443+
4444+
public:
4445+
constexpr void set_separator(basic_string_view<_CharT> _Sep) noexcept {
4446+
_Separator = _Sep;
4447+
}
4448+
4449+
constexpr void set_brackets(basic_string_view<_CharT> _Opening, basic_string_view<_CharT> _Closing) noexcept {
4450+
_Opening_bracket = _Opening;
4451+
_Closing_bracket = _Closing;
4452+
}
4453+
4454+
_NODISCARD constexpr formatter<_Ty, _CharT>& underlying() noexcept {
4455+
return _Underlying;
4456+
}
4457+
4458+
_NODISCARD constexpr const formatter<_Ty, _CharT>& underlying() const noexcept {
4459+
return _Underlying;
4460+
}
4461+
4462+
template <class _ParseContext>
4463+
constexpr _ParseContext::iterator parse(_ParseContext& _Ctx) {
4464+
_Range_specs_setter<_CharT> _Callback{_Specs, _Ctx};
4465+
auto _It = _STD _Parse_range_specs(_Ctx._Unchecked_begin(), _Ctx._Unchecked_end(), _Callback);
4466+
4467+
_Ctx.advance_to(_Ctx.begin() + (_It - _Ctx._Unchecked_begin()));
4468+
bool _Has_underlying_spec = false;
4469+
if (_It != _Ctx._Unchecked_end()) {
4470+
if (*_It == ':') {
4471+
_Has_underlying_spec = true;
4472+
_Ctx.advance_to(_Ctx.begin() + 1);
4473+
} else if (*_It != '}') {
4474+
_Throw_format_error("Invalid range-format-spec.");
4475+
}
4476+
}
4477+
4478+
_It = _Underlying.parse(_Ctx)._Unwrapped();
4479+
if (_It != _Ctx._Unchecked_end() && *_It != '}') {
4480+
_Throw_format_error("Missing '}' in format string.");
4481+
}
4482+
4483+
switch (_Specs._Type) {
4484+
case 'm':
4485+
if constexpr (_Is_two_tuple<_Ty>) {
4486+
set_brackets(_STATICALLY_WIDEN(_CharT, "{"), _STATICALLY_WIDEN(_CharT, "}"));
4487+
set_separator(_STATICALLY_WIDEN(_CharT, ", "));
4488+
_Underlying.set_brackets({}, {});
4489+
_Underlying.set_separator(_STATICALLY_WIDEN(_CharT, ": "));
4490+
} else {
4491+
_Throw_format_error("Range-type 'm' requires type T to be a pair or a 2-element tuple.");
4492+
}
4493+
[[fallthrough]];
4494+
4495+
case '\0':
4496+
if constexpr (requires { _Underlying.set_debug_format(); }) {
4497+
if (!_Has_underlying_spec) {
4498+
_Underlying.set_debug_format();
4499+
}
4500+
}
4501+
break;
4502+
4503+
case 's':
4504+
case '?':
4505+
if constexpr (same_as<_Ty, _CharT>) {
4506+
if (_Specs._No_brackets) {
4507+
_Throw_format_error("Range-types 's' and '?s' cannot be combined with the 'n' option.");
4508+
} else if (_Has_underlying_spec) {
4509+
_Throw_format_error("Range-types 's' and '?s' cannot be combined with a range-underlying-spec.");
4510+
}
4511+
} else {
4512+
_Throw_format_error("Range-types 's' and '?s' require type T to be charT.");
4513+
}
4514+
4515+
break;
4516+
}
4517+
4518+
if (_Specs._No_brackets) {
4519+
set_brackets({}, {});
4520+
}
4521+
4522+
return _Ctx.begin() + (_It - _Ctx._Unchecked_begin());
4523+
}
4524+
4525+
template <_RANGES input_range _Range, class _FormatContext>
4526+
requires formattable<_RANGES range_reference_t<_Range>, _CharT>
4527+
&& same_as<remove_cvref_t<_RANGES range_reference_t<_Range>>, _Ty>
4528+
_FormatContext::iterator format(_Range&& _Rng, _FormatContext& _Ctx) const {
4529+
return _Format(_STD forward<_Range>(_Rng), _Ctx);
4530+
}
4531+
4532+
private:
4533+
template <_RANGES input_range _Range, class _FormatContext>
4534+
_FormatContext::iterator _Format(_Range&&, _FormatContext&) const {
4535+
_Throw_format_error("Unsupported 'basic_format_context'.");
4536+
}
4537+
4538+
template <_RANGES input_range _Range, class _FormatContext>
4539+
requires _Is_specialization_v<typename _FormatContext::iterator, back_insert_iterator>
4540+
&& derived_from<typename _FormatContext::iterator::container_type, _Fmt_buffer<_CharT>>
4541+
_FormatContext::iterator _Format(_Range&& _Rng, _FormatContext& _Ctx) const {
4542+
auto _Format_specs = _Specs;
4543+
if (_Specs._Dynamic_width_index >= 0) {
4544+
_Format_specs._Width =
4545+
_STD _Get_dynamic_specs<_Width_checker>(_Ctx.arg(static_cast<size_t>(_Specs._Dynamic_width_index)));
4546+
}
4547+
4548+
basic_string<_CharT> _Buffer;
4549+
{
4550+
_Fmt_iterator_buffer<back_insert_iterator<basic_string<_CharT>>, _CharT> _Fmt_buf(
4551+
back_insert_iterator{_Buffer});
4552+
using _Inserter = back_insert_iterator<_Fmt_buffer<_CharT>>;
4553+
auto _Nested_context = basic_format_context<_Inserter, _CharT>::_Make_from(
4554+
_Inserter{_Fmt_buf}, _Ctx._Get_args(), _Ctx._Get_lazy_locale());
4555+
4556+
if constexpr (same_as<_Ty, _CharT>) {
4557+
if (_Specs._Type == 's' || _Specs._Type == '?') {
4558+
_Format_as_string(_STD forward<_Range>(_Rng), _Nested_context, _Specs._Type == '?');
4559+
} else {
4560+
_Format_as_sequence(_STD forward<_Range>(_Rng), _Nested_context);
4561+
}
4562+
} else {
4563+
_Format_as_sequence(_STD forward<_Range>(_Rng), _Nested_context);
4564+
}
4565+
}
4566+
4567+
const int _Width = _Measure_display_width<_CharT>(_Buffer);
4568+
return _STD _Write_aligned(
4569+
_Ctx.out(), _Width, _Format_specs, _Fmt_align::_Left, [&](_FormatContext::iterator _Out) {
4570+
return _STD _Fmt_write(_STD move(_Out), basic_string_view{_Buffer});
4571+
});
4572+
}
4573+
4574+
template <_RANGES input_range _Range, class _FormatContext>
4575+
void _Format_as_sequence(_Range&& _Rng, _FormatContext& _Ctx) const {
4576+
_Ctx.advance_to(_STD _Fmt_write(_Ctx.out(), _Opening_bracket));
4577+
bool _Separate = false;
4578+
for (auto&& _Elem : _Rng) {
4579+
if (_Separate) {
4580+
_Ctx.advance_to(_STD _Fmt_write(_Ctx.out(), _Separator));
4581+
}
4582+
4583+
_Separate = true;
4584+
_Ctx.advance_to(_Underlying.format(_Elem, _Ctx));
4585+
}
4586+
4587+
(void) _STD _Fmt_write(_Ctx.out(), _Closing_bracket);
4588+
}
4589+
4590+
template <_RANGES input_range _Range, class _FormatContext>
4591+
void _Format_as_string(_Range&& _Rng, _FormatContext& _Ctx, const bool _Debug) const {
4592+
if constexpr (_RANGES contiguous_range<_Range>) {
4593+
const auto _Size = _STD _To_unsigned_like(_RANGES distance(_Rng));
4594+
4595+
if (!_STD in_range<size_t>(_Size)) [[unlikely]] {
4596+
_Throw_format_error("Formatted range is too long.");
4597+
}
4598+
4599+
formatter<basic_string_view<_CharT>, _CharT> _String_view_formatter;
4600+
if (_Debug) {
4601+
_String_view_formatter.set_debug_format();
4602+
}
4603+
4604+
const basic_string_view<_CharT> _Str(_STD to_address(_RANGES begin(_Rng)), static_cast<size_t>(_Size));
4605+
_String_view_formatter.format(_Str, _Ctx);
4606+
} else {
4607+
using _String = basic_string<_CharT>;
4608+
formatter<_String, _CharT> _String_formatter;
4609+
if (_Debug) {
4610+
_String_formatter.set_debug_format();
4611+
}
4612+
4613+
_String_formatter.format(_String{from_range, _Rng}, _Ctx);
4614+
}
4615+
}
4616+
};
43684617
#endif // _HAS_CXX23
43694618
_STD_END
43704619

tests/libcxx/expected_results.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,6 @@ std/utilities/format/format.range/format.range.formatter/format.functions.format
287287
std/utilities/format/format.range/format.range.formatter/format.functions.vformat.pass.cpp FAIL
288288
std/utilities/format/format.range/format.range.formatter/format.pass.cpp FAIL
289289
std/utilities/format/format.range/format.range.formatter/parse.pass.cpp FAIL
290-
std/utilities/format/format.range/format.range.formatter/set_brackets.pass.cpp FAIL
291-
std/utilities/format/format.range/format.range.formatter/set_separator.pass.cpp FAIL
292-
std/utilities/format/format.range/format.range.formatter/underlying.pass.cpp FAIL
293-
std/utilities/format/types.compile.pass.cpp FAIL
294290

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

11291127
# Not analyzed. Apparent false positives from static analysis where it thinks that array indexing is out of bounds.

tests/std/include/range_algorithm_support.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,11 @@ namespace test {
878878
using RebindAsMoveOnly =
879879
range<Category, Element, IsSized, Diff, IsCommon, Eq, Proxy, IsView, Copyability::move_only>;
880880

881+
template <class OtherElement>
882+
using RebindElement = range<Category, OtherElement, IsSized, Diff, IsCommon, Eq, Proxy, IsView, Copy>;
883+
884+
static constexpr ProxyRef proxy_ref = Proxy;
885+
881886
using detail::range_base<Element, Copy>::range_base;
882887

883888
[[nodiscard]] constexpr I begin() const noexcept {

tests/std/test.lst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,7 @@ tests\P2286R8_text_formatting_escaping
618618
tests\P2286R8_text_formatting_escaping_legacy_text_encoding
619619
tests\P2286R8_text_formatting_escaping_utf8
620620
tests\P2286R8_text_formatting_formattable
621+
tests\P2286R8_text_formatting_range_formatter
621622
tests\P2286R8_text_formatting_tuple
622623
tests\P2286R8_text_formatting_vector_bool_reference
623624
tests\P2302R4_ranges_alg_contains
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
RUNALL_INCLUDE ..\usual_latest_matrix.lst

0 commit comments

Comments
 (0)