Skip to content

Commit

Permalink
Add range_formatter (#4642)
Browse files Browse the repository at this point in the history
Co-authored-by: Stephan T. Lavavej <stl@nuwen.net>
  • Loading branch information
JMazurkiewicz and StephanTLavavej committed May 30, 2024
1 parent cce32f4 commit 0d32e40
Show file tree
Hide file tree
Showing 6 changed files with 773 additions and 6 deletions.
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>
_NODISCARD constexpr const _CharT* _Parse_range_specs(
const _CharT* _Begin, const _CharT* const _End, _Range_specs_setter<_CharT>& _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 (const _CharT _Maybe_type = *_Begin) {
case '?':
if (++_Begin == _End || *_Begin != 's') {
_Throw_format_error("Invalid range-type '?'; was '?s' intended?");
}
[[fallthrough]];
case 'm':
case 's':
_Callbacks._On_type(_Maybe_type);
++_Begin;
break;
}

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;
}

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

_NODISCARD 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>) {
set_brackets(_STATICALLY_WIDEN(_CharT, "{"), _STATICALLY_WIDEN(_CharT, "}"));
set_separator(_STATICALLY_WIDEN(_CharT, ", "));
_Underlying.set_brackets({}, {});
_Underlying.set_separator(_STATICALLY_WIDEN(_CharT, ": "));
} else {
_Throw_format_error("Range-type 'm' requires type T to be a pair or a 2-element tuple.");
}
[[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>) {
if (_Specs._No_brackets) {
_Throw_format_error("Range-types 's' and '?s' cannot be combined with the 'n' option.");
} else if (_Has_underlying_spec) {
_Throw_format_error("Range-types 's' and '?s' cannot be combined with a range-underlying-spec.");
}
} else {
_Throw_format_error("Range-types 's' and '?s' require type T to be charT.");
}

break;
}

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(
back_insert_iterator{_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, const bool _Debug) const {
if constexpr (_RANGES contiguous_range<_Range>) {
const auto _Size = _STD _To_unsigned_like(_RANGES distance(_Rng));

if (!_STD in_range<size_t>(_Size)) [[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

0 comments on commit 0d32e40

Please sign in to comment.