Skip to content

Commit

Permalink
[libc++][format] Adds string formatter.
Browse files Browse the repository at this point in the history
Implements the formatter for all string types.
[format.formatter.spec]/2.2
For each charT, the string type specializations
```
  template<> struct formatter<charT*, charT>;
  template<> struct formatter<const charT*, charT>;
  template<size_t N> struct formatter<const charT[N], charT>;
  template<class traits, class Allocator>
    struct formatter<basic_string<charT, traits, Allocator>, charT>;
  template<class traits>
    struct formatter<basic_string_view<charT, traits>, charT>;
```
This removes the stub implemented in D96664.

Implements parts of:
- P0645 Text Formatting
- P1868 width: clarifying units of width and precision in std::format

Reviewed By: #libc, ldionne, vitaut

Differential Revision: https://reviews.llvm.org/D103425
  • Loading branch information
mordante committed Oct 7, 2021
1 parent 9072183 commit d550930
Show file tree
Hide file tree
Showing 15 changed files with 624 additions and 93 deletions.
1 change: 1 addition & 0 deletions libcxx/include/CMakeLists.txt
Expand Up @@ -140,6 +140,7 @@ set(files
__format/format_parse_context.h
__format/format_string.h
__format/formatter.h
__format/formatter_string.h
__format/parser_std_format_spec.h
__function_like.h
__functional_base
Expand Down
5 changes: 0 additions & 5 deletions libcxx/include/__format/format_string.h
Expand Up @@ -20,9 +20,6 @@
#pragma GCC system_header
#endif

_LIBCPP_PUSH_MACROS
#include <__undef_macros>

_LIBCPP_BEGIN_NAMESPACE_STD

#if _LIBCPP_STD_VER > 17
Expand Down Expand Up @@ -169,6 +166,4 @@ __parse_arg_id(const _CharT* __begin, const _CharT* __end, auto& __parse_ctx) {

_LIBCPP_END_NAMESPACE_STD

_LIBCPP_POP_MACROS

#endif // _LIBCPP___FORMAT_FORMAT_STRING_H
136 changes: 136 additions & 0 deletions libcxx/include/__format/formatter.h
Expand Up @@ -10,10 +10,16 @@
#ifndef _LIBCPP___FORMAT_FORMATTER_H
#define _LIBCPP___FORMAT_FORMATTER_H

#include <__algorithm/copy.h>
#include <__algorithm/fill_n.h>
#include <__availability>
#include <__config>
#include <__format/format_error.h>
#include <__format/format_fwd.h>
#include <__format/format_string.h>
#include <__format/parser_std_format_spec.h>
#include <concepts>
#include <string_view>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
Expand Down Expand Up @@ -54,6 +60,136 @@ struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter {
}
};

namespace __formatter {

/** The character types that formatters are specialized for. */
template <class _CharT>
concept __char_type = same_as<_CharT, char> || same_as<_CharT, wchar_t>;

struct _LIBCPP_TEMPLATE_VIS __padding_size_result {
size_t __before;
size_t __after;
};

_LIBCPP_HIDE_FROM_ABI constexpr __padding_size_result
__padding_size(size_t __size, size_t __width,
__format_spec::_Flags::_Alignment __align) {
_LIBCPP_ASSERT(__width > __size,
"Don't call this function when no padding is required");
_LIBCPP_ASSERT(
__align != __format_spec::_Flags::_Alignment::__default,
"Caller should adjust the default to the value required by the type");

size_t __fill = __width - __size;
switch (__align) {
case __format_spec::_Flags::_Alignment::__default:
_LIBCPP_UNREACHABLE();

case __format_spec::_Flags::_Alignment::__left:
return {0, __fill};

case __format_spec::_Flags::_Alignment::__center: {
// The extra padding is divided per [format.string.std]/3
// __before = floor(__fill, 2);
// __after = ceil(__fill, 2);
size_t __before = __fill / 2;
size_t __after = __fill - __before;
return {__before, __after};
}
case __format_spec::_Flags::_Alignment::__right:
return {__fill, 0};
}
_LIBCPP_UNREACHABLE();
}

/**
* Writes the input to the output with the required padding.
*
* Since the output column width is specified the function can be used for
* ASCII and Unicode input.
*
* @pre [@a __first, @a __last) is a valid range.
* @pre @a __size <= @a __width. Using this function when this pre-condition
* doesn't hold incurs an unwanted overhead.
*
* @param __out_it The output iterator to write to.
* @param __first Pointer to the first element to write.
* @param __last Pointer beyond the last element to write.
* @param __size The (estimated) output column width. When the elements
* to be written are ASCII the following condition holds
* @a __size == @a __last - @a __first.
* @param __width The number of output columns to write.
* @param __fill The character used for the alignment of the output.
* TODO FMT Will probably change to support Unicode grapheme
* cluster.
* @param __alignment The requested alignment.
*
* @returns An iterator pointing beyond the last element written.
*
* @note The type of the elements in range [@a __first, @a __last) can differ
* from the type of @a __fill. Integer output uses @c std::to_chars for its
* conversion, which means the [@a __first, @a __last) always contains elements
* of the type @c char.
*/
template <class _CharT, class _Fill>
_LIBCPP_HIDE_FROM_ABI auto
__write(output_iterator<const _CharT&> auto __out_it, const _CharT* __first,
const _CharT* __last, size_t __size, size_t __width, _Fill __fill,
__format_spec::_Flags::_Alignment __alignment) -> decltype(__out_it) {

_LIBCPP_ASSERT(__first <= __last, "Not a valid range");
_LIBCPP_ASSERT(__size < __width, "Precondition failure");

__padding_size_result __padding =
__padding_size(__size, __width, __alignment);
__out_it = _VSTD::fill_n(_VSTD::move(__out_it), __padding.__before, __fill);
__out_it = _VSTD::copy(__first, __last, _VSTD::move(__out_it));
return _VSTD::fill_n(_VSTD::move(__out_it), __padding.__after, __fill);
}

/**
* Writes Unicode input to the output with the required padding.
*
* This function does almost the same as the @ref __write function, but handles
* the width estimation of the Unicode input.
*
* @param __str The range [@a __first, @a __last).
* @param __precision The width to truncate the input string to, use @c -1 for
* no limit.
*/
template <class _CharT, class _Fill>
_LIBCPP_HIDE_FROM_ABI auto
__write_unicode(output_iterator<const _CharT&> auto __out_it,
basic_string_view<_CharT> __str, ptrdiff_t __width,
ptrdiff_t __precision, _Fill __fill,
__format_spec::_Flags::_Alignment __alignment)
-> decltype(__out_it) {

// This value changes when there Unicode column width limits the output
// size.
auto __last = __str.end();
if (__width != 0 || __precision != -1) {
__format_spec::__string_alignment<_CharT> __format_traits =
__format_spec::__get_string_alignment(__str.begin(), __str.end(),
__width, __precision);

if (__format_traits.__align)
return __write(_VSTD::move(__out_it), __str.begin(),
__format_traits.__last, __format_traits.__size, __width,
__fill, __alignment);

// No alignment required update the output based on the precision.
// This might be the same as __str.end().
__last = __format_traits.__last;
}

// Copy the input to the output. The output size might be limited by the
// precision.
return _VSTD::copy(__str.begin(), __last, _VSTD::move(__out_it));
}

} // namespace __formatter

#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS)

#endif //_LIBCPP_STD_VER > 17
Expand Down
154 changes: 154 additions & 0 deletions libcxx/include/__format/formatter_string.h
@@ -0,0 +1,154 @@
// -*- 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_FORMATTER_STRING_H
#define _LIBCPP___FORMAT_FORMATTER_STRING_H

#include <__config>
#include <__format/format_error.h>
#include <__format/format_fwd.h>
#include <__format/format_string.h>
#include <__format/formatter.h>
#include <__format/parser_std_format_spec.h>
#include <algorithm>
#include <string_view>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif

_LIBCPP_PUSH_MACROS
#include <__undef_macros>

_LIBCPP_BEGIN_NAMESPACE_STD

#if _LIBCPP_STD_VER > 17

// TODO FMT Remove this once we require compilers with proper C++20 support.
// If the compiler has no concepts support, the format header will be disabled.
// Without concepts support enable_if needs to be used and that too much effort
// to support compilers with partial C++20 support.
#if !defined(_LIBCPP_HAS_NO_CONCEPTS)

namespace __format_spec {

template <__formatter::__char_type _CharT>
class _LIBCPP_TEMPLATE_VIS __formatter_string : public __parser_string<_CharT> {
public:
_LIBCPP_HIDE_FROM_ABI auto format(basic_string_view<_CharT> __str,
auto& __ctx) -> decltype(__ctx.out()) {

_LIBCPP_ASSERT(this->__alignment != _Flags::_Alignment::__default,
"The parser should not use these defaults");

if (this->__width_needs_substitution())
this->__substitute_width_arg_id(__ctx.arg(this->__width));

if (this->__precision_needs_substitution())
this->__substitute_precision_arg_id(__ctx.arg(this->__precision));

return __formatter::__write_unicode(
__ctx.out(), __str, this->__width,
this->__has_precision_field() ? this->__precision : -1, this->__fill,
this->__alignment);
}
};

} //namespace __format_spec

// [format.formatter.spec]/2.2 For each charT, the string type specializations

// Formatter const char*.
template <class _CharT>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
formatter<const _CharT*, _CharT>
: public __format_spec::__formatter_string<_CharT> {
using _Base = __format_spec::__formatter_string<_CharT>;

_LIBCPP_HIDE_FROM_ABI auto format(const _CharT* __str, auto& __ctx)
-> decltype(__ctx.out()) {
_LIBCPP_ASSERT(__str, "The basic_format_arg constructor should have "
"prevented an invalid pointer.");

// 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.
//
// When using left alignment and the width option the padding is added
// after outputting __str so the length can be determined while outputting
// __str. The same holds true for the precision, during outputting __str it
// can be validated whether the precision threshold has been reached. For
// now these optimizations aren't implemented. Instead the base class
// handles these options.
// TODO FMT Implement these improvements.
if (this->__has_width_field() || this->__has_precision_field())
return _Base::format(__str, __ctx);

// No formatting required, copy the string to the output.
auto __out_it = __ctx.out();
while (*__str)
*__out_it++ = *__str++;
return __out_it;
}
};

// Formatter char*.
template <class _CharT>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
formatter<_CharT*, _CharT> : public formatter<const _CharT*, _CharT> {
using _Base = formatter<const _CharT*, _CharT>;

_LIBCPP_HIDE_FROM_ABI auto format(_CharT* __str, auto& __ctx)
-> decltype(__ctx.out()) {
return _Base::format(__str, __ctx);
}
};

// Formatter const char[].
template <class _CharT, size_t _Size>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
formatter<const _CharT[_Size], _CharT>
: public __format_spec::__formatter_string<_CharT> {
using _Base = __format_spec::__formatter_string<_CharT>;

_LIBCPP_HIDE_FROM_ABI auto format(const _CharT __str[_Size], auto& __ctx)
-> decltype(__ctx.out()) {
return _Base::format(_VSTD::basic_string_view<_CharT>(__str, _Size), __ctx);
}
};

// Formatter std::string.
template <class _CharT, class _Traits, class _Allocator>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
formatter<basic_string<_CharT, _Traits, _Allocator>, _CharT>
: public __format_spec::__formatter_string<_CharT> {
using _Base = __format_spec::__formatter_string<_CharT>;

_LIBCPP_HIDE_FROM_ABI auto
format(const basic_string<_CharT, _Traits, _Allocator>& __str, auto& __ctx)
-> decltype(__ctx.out()) {
return _Base::format(_VSTD::basic_string_view<_CharT>(__str), __ctx);
}
};

// Formatter std::string_view.
template <class _CharT, class _Traits>
struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT
formatter<basic_string_view<_CharT, _Traits>, _CharT>
: public __format_spec::__formatter_string<_CharT> {};

#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS)

#endif //_LIBCPP_STD_VER > 17

_LIBCPP_END_NAMESPACE_STD

_LIBCPP_POP_MACROS

#endif // _LIBCPP___FORMAT_FORMATTER_STRING_H
1 change: 1 addition & 0 deletions libcxx/include/__format/parser_std_format_spec.h
Expand Up @@ -238,6 +238,7 @@ __substitute_arg_id(basic_format_arg<_Context> __arg) {
static_cast<_CT>(__format::__number_max))
__throw_format_error("A format-spec arg-id replacement exceeds "
"the maximum supported value");

return __arg;
} else if constexpr (same_as<_Type, monostate>)
__throw_format_error("Argument index out of bounds");
Expand Down

0 comments on commit d550930

Please sign in to comment.