[libc++][format] Adds string formatter.
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
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -20,9 +20,6 @@
|
||||
#pragma GCC system_header
|
||||
#endif
|
||||
|
||||
_LIBCPP_PUSH_MACROS
|
||||
#include <__undef_macros>
|
||||
|
||||
_LIBCPP_BEGIN_NAMESPACE_STD
|
||||
|
||||
#if _LIBCPP_STD_VER > 17
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -54,6 +60,136 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
154
libcxx/include/__format/formatter_string.h
Normal file
154
libcxx/include/__format/formatter_string.h
Normal file
@@ -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
|
||||
@@ -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");
|
||||
|
||||
@@ -277,8 +277,9 @@ namespace std {
|
||||
#include <__format/format_error.h>
|
||||
#include <__format/format_parse_context.h>
|
||||
#include <__format/format_string.h>
|
||||
#include <__format/parser_std_format_spec.h>
|
||||
#include <__format/formatter.h>
|
||||
#include <__format/formatter_string.h>
|
||||
#include <__format/parser_std_format_spec.h>
|
||||
#include <__variant/monostate.h>
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
@@ -354,43 +355,6 @@ struct _LIBCPP_TEMPLATE_VIS __formatter_char {
|
||||
}
|
||||
};
|
||||
|
||||
template <class _CharT>
|
||||
struct _LIBCPP_TEMPLATE_VIS __formatter_c_string {
|
||||
_LIBCPP_HIDE_FROM_ABI
|
||||
auto parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) {
|
||||
// TODO FMT Implement this function.
|
||||
return __parse_ctx.begin();
|
||||
}
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI
|
||||
auto format(const _CharT* __str, auto& __ctx) -> decltype(__ctx.out()) {
|
||||
// TODO FMT Implement the parsed formatting arguments.
|
||||
auto __out_it = __ctx.out();
|
||||
while (*__str)
|
||||
*__out_it++ = *__str++;
|
||||
return __out_it;
|
||||
}
|
||||
};
|
||||
|
||||
template <class _CharT>
|
||||
struct _LIBCPP_TEMPLATE_VIS __formatter_string {
|
||||
_LIBCPP_HIDE_FROM_ABI
|
||||
auto parse(auto& __parse_ctx) -> decltype(__parse_ctx.begin()) {
|
||||
// TODO FMT Implement this function.
|
||||
return __parse_ctx.begin();
|
||||
}
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI
|
||||
auto format(basic_string_view<_CharT> __str, auto& __ctx)
|
||||
-> decltype(__ctx.out()) {
|
||||
// TODO FMT Implement the parsed formatting arguments.
|
||||
auto __out_it = __ctx.out();
|
||||
for (const auto __c : __str)
|
||||
*__out_it++ = __c;
|
||||
return __out_it;
|
||||
}
|
||||
};
|
||||
|
||||
template <class _Tp, class _CharT>
|
||||
requires(is_arithmetic_v<_Tp> &&
|
||||
!same_as<_Tp, bool>) struct _LIBCPP_HIDE_FROM_ABI
|
||||
@@ -470,54 +434,6 @@ template <>
|
||||
struct _LIBCPP_TEMPLATE_VIS formatter<wchar_t, wchar_t>
|
||||
: public __format::__formatter_char<wchar_t, wchar_t> {};
|
||||
|
||||
// [format.formatter.spec]/2.2 For each charT, the string type specializations
|
||||
|
||||
template <class _CharT>
|
||||
struct _LIBCPP_TEMPLATE_VIS formatter<_CharT*, _CharT>
|
||||
: public __format::__formatter_c_string<_CharT> {
|
||||
using _Base = __format::__formatter_c_string<_CharT>;
|
||||
|
||||
_LIBCPP_HIDE_FROM_ABI auto format(_CharT* __str, auto& __ctx)
|
||||
-> decltype(__ctx.out()) {
|
||||
_LIBCPP_ASSERT(__str, "The basic_format_arg constructor should have "
|
||||
"prevented an invalid pointer");
|
||||
return _Base::format(__str, __ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <class _CharT>
|
||||
struct _LIBCPP_TEMPLATE_VIS formatter<const _CharT*, _CharT>
|
||||
: public __format::__formatter_c_string<_CharT> {};
|
||||
|
||||
template <class _CharT, size_t _Size>
|
||||
struct _LIBCPP_TEMPLATE_VIS formatter<const _CharT[_Size], _CharT>
|
||||
: public __format::__formatter_string<_CharT> {
|
||||
using _Base = __format::__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);
|
||||
}
|
||||
};
|
||||
|
||||
template <class _CharT, class _Traits, class _Allocator>
|
||||
struct _LIBCPP_TEMPLATE_VIS
|
||||
formatter<basic_string<_CharT, _Traits, _Allocator>, _CharT>
|
||||
: public __format::__formatter_string<_CharT> {
|
||||
using _Base = __format::__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);
|
||||
}
|
||||
};
|
||||
|
||||
template <class _CharT, class _Traits>
|
||||
struct _LIBCPP_TEMPLATE_VIS
|
||||
formatter<basic_string_view<_CharT, _Traits>, _CharT>
|
||||
: public __format::__formatter_string<_CharT> {};
|
||||
|
||||
// [format.formatter.spec]/2.3
|
||||
// For each charT, for each cv-unqualified arithmetic type ArithmeticT other
|
||||
// than char, wchar_t, char8_t, char16_t, or char32_t, a specialization
|
||||
|
||||
@@ -450,6 +450,7 @@ module std [system] {
|
||||
module format_parse_context { private header "__format/format_parse_context.h" }
|
||||
module format_string { private header "__format/format_string.h" }
|
||||
module formatter { private header "__format/formatter.h" }
|
||||
module formatter_string { private header "__format/formatter_string.h" }
|
||||
module parser_std_format_spec { private header "__format/parser_std_format_spec.h" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// -*- 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// REQUIRES: modules-build
|
||||
|
||||
// WARNING: This test was generated by 'generate_private_header_tests.py'
|
||||
// and should not be edited manually.
|
||||
|
||||
// expected-error@*:* {{use of private header from outside its module: '__format/formatter_string.h'}}
|
||||
#include <__format/formatter_string.h>
|
||||
@@ -72,6 +72,29 @@ void test_char_pointer() {
|
||||
|
||||
std::basic_string<CharT> s(CSTR("abc\0abc"), 7);
|
||||
test_termination_condition<ArgumentT>(STR("abc"), STR("}"), s.c_str());
|
||||
|
||||
test_termination_condition<ArgumentT>(STR("world"), STR("}"), CSTR("world"));
|
||||
test_termination_condition<ArgumentT>(STR("world"), STR("_>}"),
|
||||
CSTR("world"));
|
||||
|
||||
test_termination_condition<ArgumentT>(STR(" world"), STR(">8}"),
|
||||
CSTR("world"));
|
||||
test_termination_condition<ArgumentT>(STR("___world"), STR("_>8}"),
|
||||
CSTR("world"));
|
||||
test_termination_condition<ArgumentT>(STR("_world__"), STR("_^8}"),
|
||||
CSTR("world"));
|
||||
test_termination_condition<ArgumentT>(STR("world___"), STR("_<8}"),
|
||||
CSTR("world"));
|
||||
|
||||
test_termination_condition<ArgumentT>(STR("world"), STR(".5}"),
|
||||
CSTR("world"));
|
||||
test_termination_condition<ArgumentT>(STR("unive"), STR(".5}"),
|
||||
CSTR("universe"));
|
||||
|
||||
test_termination_condition<ArgumentT>(STR("%world%"), STR("%^7.7}"),
|
||||
CSTR("world"));
|
||||
test_termination_condition<ArgumentT>(STR("univers"), STR("%^7.7}"),
|
||||
CSTR("universe"));
|
||||
}
|
||||
|
||||
int main(int, char**) {
|
||||
|
||||
@@ -96,6 +96,20 @@ void test_array() {
|
||||
|
||||
std::basic_string<CharT> s(CSTR("abc\0abc"), 7);
|
||||
test_helper_wrapper<"abc\0abc">(s, STR("}"));
|
||||
|
||||
test_helper_wrapper<"world">(STR("world"), STR("}"));
|
||||
test_helper_wrapper<"world">(STR("world"), STR("_>}"));
|
||||
|
||||
test_helper_wrapper<"world">(STR(" world"), STR(">8}"));
|
||||
test_helper_wrapper<"world">(STR("___world"), STR("_>8}"));
|
||||
test_helper_wrapper<"world">(STR("_world__"), STR("_^8}"));
|
||||
test_helper_wrapper<"world">(STR("world___"), STR("_<8}"));
|
||||
|
||||
test_helper_wrapper<"world">(STR("world"), STR(".5}"));
|
||||
test_helper_wrapper<"universe">(STR("unive"), STR(".5}"));
|
||||
|
||||
test_helper_wrapper<"world">(STR("%world%"), STR("%^7.7}"));
|
||||
test_helper_wrapper<"universe">(STR("univers"), STR("%^7.7}"));
|
||||
}
|
||||
|
||||
int main(int, char**) {
|
||||
|
||||
@@ -83,6 +83,30 @@ void test_string_type() {
|
||||
|
||||
std::basic_string<CharT> s(CSTR("abc\0abc"), 7);
|
||||
test_termination_condition<T, ArgumentT>(s, STR("}"), s);
|
||||
|
||||
test_termination_condition<T, ArgumentT>(STR("world"), STR("}"),
|
||||
STR("world"));
|
||||
test_termination_condition<T, ArgumentT>(STR("world"), STR("_>}"),
|
||||
STR("world"));
|
||||
|
||||
test_termination_condition<T, ArgumentT>(STR(" world"), STR(">8}"),
|
||||
STR("world"));
|
||||
test_termination_condition<T, ArgumentT>(STR("___world"), STR("_>8}"),
|
||||
STR("world"));
|
||||
test_termination_condition<T, ArgumentT>(STR("_world__"), STR("_^8}"),
|
||||
STR("world"));
|
||||
test_termination_condition<T, ArgumentT>(STR("world___"), STR("_<8}"),
|
||||
STR("world"));
|
||||
|
||||
test_termination_condition<T, ArgumentT>(STR("world"), STR(".5}"),
|
||||
STR("world"));
|
||||
test_termination_condition<T, ArgumentT>(STR("unive"), STR(".5}"),
|
||||
STR("universe"));
|
||||
|
||||
test_termination_condition<T, ArgumentT>(STR("%world%"), STR("%^7.7}"),
|
||||
STR("world"));
|
||||
test_termination_condition<T, ArgumentT>(STR("univers"), STR("%^7.7}"),
|
||||
STR("universe"));
|
||||
}
|
||||
|
||||
template <class CharT>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#ifndef _LIBCPP_HAS_NO_LOCALIZATION
|
||||
#include <iostream>
|
||||
#endif
|
||||
#include <vector>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "format_tests.h"
|
||||
|
||||
@@ -10,10 +10,256 @@
|
||||
|
||||
#include "make_string.h"
|
||||
|
||||
#define STR(S) MAKE_STRING(CharT, S)
|
||||
|
||||
// In this file the following template types are used:
|
||||
// TestFunction must be callable as check(expected-result, string-to-format, args-to-format...)
|
||||
// ExceptionTest must be callable as check_exception(expected-exception, string-to-format, args-to-format...)
|
||||
|
||||
#define STR(S) MAKE_STRING(CharT, S)
|
||||
|
||||
template <class CharT>
|
||||
std::vector<std::basic_string<CharT>> invalid_types(std::string valid) {
|
||||
std::vector<std::basic_string<CharT>> result;
|
||||
|
||||
#define CASE(T) \
|
||||
case #T[0]: \
|
||||
result.push_back(STR("Invalid formatter type {:" #T "}")); \
|
||||
break;
|
||||
|
||||
for (auto type : "aAbBcdeEfFgGopsxX") {
|
||||
if (valid.find(type) != std::string::npos)
|
||||
continue;
|
||||
|
||||
switch (type) {
|
||||
CASE(a)
|
||||
CASE(A)
|
||||
CASE(b)
|
||||
CASE(B)
|
||||
CASE(c)
|
||||
CASE(d)
|
||||
CASE(e)
|
||||
CASE(E)
|
||||
CASE(f)
|
||||
CASE(F)
|
||||
CASE(g)
|
||||
CASE(G)
|
||||
CASE(o)
|
||||
CASE(p)
|
||||
CASE(s)
|
||||
CASE(x)
|
||||
CASE(X)
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
assert(false && "Add the type to the list of cases.");
|
||||
}
|
||||
}
|
||||
#undef CASE
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class CharT, class T, class TestFunction, class ExceptionTest>
|
||||
void format_test_string(T world, T universe, TestFunction check,
|
||||
ExceptionTest check_exception) {
|
||||
|
||||
// *** Valid input tests ***
|
||||
// Unsed argument is ignored. TODO FMT what does the Standard mandate?
|
||||
check(STR("hello world"), STR("hello {}"), world, universe);
|
||||
check(STR("hello world and universe"), STR("hello {} and {}"), world,
|
||||
universe);
|
||||
check(STR("hello world"), STR("hello {0}"), world, universe);
|
||||
check(STR("hello universe"), STR("hello {1}"), world, universe);
|
||||
check(STR("hello universe and world"), STR("hello {1} and {0}"), world,
|
||||
universe);
|
||||
|
||||
check(STR("hello world"), STR("hello {:_>}"), world);
|
||||
check(STR("hello world"), STR("hello {:>8}"), world);
|
||||
check(STR("hello ___world"), STR("hello {:_>8}"), world);
|
||||
check(STR("hello _world__"), STR("hello {:_^8}"), world);
|
||||
check(STR("hello world___"), STR("hello {:_<8}"), world);
|
||||
|
||||
check(STR("hello >>>world"), STR("hello {:>>8}"), world);
|
||||
check(STR("hello <<<world"), STR("hello {:<>8}"), world);
|
||||
check(STR("hello ^^^world"), STR("hello {:^>8}"), world);
|
||||
|
||||
check(STR("hello $world"), STR("hello {:$>{}}"), world, 6);
|
||||
check(STR("hello $world"), STR("hello {0:$>{1}}"), world, 6);
|
||||
check(STR("hello $world"), STR("hello {1:$>{0}}"), 6, world);
|
||||
|
||||
check(STR("hello world"), STR("hello {:.5}"), world);
|
||||
check(STR("hello unive"), STR("hello {:.5}"), universe);
|
||||
|
||||
check(STR("hello univer"), STR("hello {:.{}}"), universe, 6);
|
||||
check(STR("hello univer"), STR("hello {0:.{1}}"), universe, 6);
|
||||
check(STR("hello univer"), STR("hello {1:.{0}}"), 6, universe);
|
||||
|
||||
check(STR("hello %world%"), STR("hello {:%^7.7}"), world);
|
||||
check(STR("hello univers"), STR("hello {:%^7.7}"), universe);
|
||||
check(STR("hello %world%"), STR("hello {:%^{}.{}}"), world, 7, 7);
|
||||
check(STR("hello %world%"), STR("hello {0:%^{1}.{2}}"), world, 7, 7);
|
||||
check(STR("hello %world%"), STR("hello {0:%^{2}.{1}}"), world, 7, 7);
|
||||
check(STR("hello %world%"), STR("hello {1:%^{0}.{2}}"), 7, world, 7);
|
||||
|
||||
check(STR("hello world"), STR("hello {:_>s}"), world);
|
||||
check(STR("hello $world"), STR("hello {:$>{}s}"), world, 6);
|
||||
check(STR("hello world"), STR("hello {:.5s}"), world);
|
||||
check(STR("hello univer"), STR("hello {:.{}s}"), universe, 6);
|
||||
check(STR("hello %world%"), STR("hello {:%^7.7s}"), world);
|
||||
|
||||
check(STR("hello #####uni"), STR("hello {:#>8.3s}"), universe);
|
||||
check(STR("hello ##uni###"), STR("hello {:#^8.3s}"), universe);
|
||||
check(STR("hello uni#####"), STR("hello {:#<8.3s}"), universe);
|
||||
|
||||
// *** sign ***
|
||||
check_exception("The format-spec should consume the input or end with a '}'",
|
||||
STR("hello {:-}"), world);
|
||||
|
||||
// *** alternate form ***
|
||||
check_exception("The format-spec should consume the input or end with a '}'",
|
||||
STR("hello {:#}"), world);
|
||||
|
||||
// *** zero-padding ***
|
||||
check_exception("A format-spec width field shouldn't have a leading zero",
|
||||
STR("hello {:0}"), world);
|
||||
|
||||
// *** width ***
|
||||
#if _LIBCPP_VERSION
|
||||
// This limit isn't specified in the Standard.
|
||||
static_assert(std::__format::__number_max == 2'147'483'647,
|
||||
"Update the assert and the test.");
|
||||
check_exception("The numeric value of the format-spec is too large",
|
||||
STR("{:2147483648}"), world);
|
||||
check_exception("The numeric value of the format-spec is too large",
|
||||
STR("{:5000000000}"), world);
|
||||
check_exception("The numeric value of the format-spec is too large",
|
||||
STR("{:10000000000}"), world);
|
||||
#endif
|
||||
|
||||
check_exception(
|
||||
"A format-spec width field replacement should have a positive value",
|
||||
STR("hello {:{}}"), world, 0);
|
||||
check_exception(
|
||||
"A format-spec arg-id replacement shouldn't have a negative value",
|
||||
STR("hello {:{}}"), world, -1);
|
||||
check_exception(
|
||||
"A format-spec arg-id replacement exceeds the maximum supported value",
|
||||
STR("hello {:{}}"), world, -1u);
|
||||
check_exception("Argument index out of bounds", STR("hello {:{}}"), world);
|
||||
check_exception(
|
||||
"A format-spec arg-id replacement argument isn't an integral type",
|
||||
STR("hello {:{}}"), world, universe);
|
||||
check_exception(
|
||||
"Using manual argument numbering in automatic argument numbering mode",
|
||||
STR("hello {:{0}}"), world, 1);
|
||||
check_exception(
|
||||
"Using automatic argument numbering in manual argument numbering mode",
|
||||
STR("hello {0:{}}"), world, 1);
|
||||
|
||||
// *** precision ***
|
||||
check_exception("A format-spec precision field shouldn't have a leading zero",
|
||||
STR("hello {:.01}"), world);
|
||||
|
||||
#if _LIBCPP_VERSION
|
||||
// This limit isn't specified in the Standard.
|
||||
static_assert(std::__format::__number_max == 2'147'483'647,
|
||||
"Update the assert and the test.");
|
||||
check_exception("The numeric value of the format-spec is too large",
|
||||
STR("{:.2147483648}"), world);
|
||||
check_exception("The numeric value of the format-spec is too large",
|
||||
STR("{:.5000000000}"), world);
|
||||
check_exception("The numeric value of the format-spec is too large",
|
||||
STR("{:.10000000000}"), world);
|
||||
#endif
|
||||
|
||||
// Precision 0 allowed, but not useful for string arguments.
|
||||
check(STR("hello "), STR("hello {:.{}}"), world, 0);
|
||||
check_exception(
|
||||
"A format-spec arg-id replacement shouldn't have a negative value",
|
||||
STR("hello {:.{}}"), world, -1);
|
||||
check_exception(
|
||||
"A format-spec arg-id replacement exceeds the maximum supported value",
|
||||
STR("hello {:.{}}"), world, -1u);
|
||||
check_exception("Argument index out of bounds", STR("hello {:.{}}"), world);
|
||||
check_exception(
|
||||
"A format-spec arg-id replacement argument isn't an integral type",
|
||||
STR("hello {:.{}}"), world, universe);
|
||||
check_exception(
|
||||
"Using manual argument numbering in automatic argument numbering mode",
|
||||
STR("hello {:.{0}}"), world, 1);
|
||||
check_exception(
|
||||
"Using automatic argument numbering in manual argument numbering mode",
|
||||
STR("hello {0:.{}}"), world, 1);
|
||||
|
||||
// *** locale-specific form ***
|
||||
check_exception("The format-spec should consume the input or end with a '}'",
|
||||
STR("hello {:L}"), world);
|
||||
|
||||
// *** type ***
|
||||
for (const auto& fmt : invalid_types<CharT>("s"))
|
||||
check_exception(
|
||||
"The format-spec type has a type not supported for a string argument",
|
||||
fmt, world);
|
||||
}
|
||||
|
||||
template <class CharT, class TestFunction>
|
||||
void format_test_string_unicode(TestFunction check) {
|
||||
#ifndef _LIBCPP_HAS_NO_UNICODE
|
||||
// ß requires one column
|
||||
check(STR("aßc"), STR("{}"), STR("aßc"));
|
||||
|
||||
check(STR("aßc"), STR("{:.3}"), STR("aßc"));
|
||||
check(STR("aß"), STR("{:.2}"), STR("aßc"));
|
||||
check(STR("a"), STR("{:.1}"), STR("aßc"));
|
||||
|
||||
check(STR("aßc"), STR("{:3.3}"), STR("aßc"));
|
||||
check(STR("aß"), STR("{:2.2}"), STR("aßc"));
|
||||
check(STR("a"), STR("{:1.1}"), STR("aßc"));
|
||||
|
||||
check(STR("aßc---"), STR("{:-<6}"), STR("aßc"));
|
||||
check(STR("-aßc--"), STR("{:-^6}"), STR("aßc"));
|
||||
check(STR("---aßc"), STR("{:->6}"), STR("aßc"));
|
||||
|
||||
// \u1000 requires two columns
|
||||
check(STR("a\u1110c"), STR("{}"), STR("a\u1110c"));
|
||||
|
||||
check(STR("a\u1100c"), STR("{:.4}"), STR("a\u1100c"));
|
||||
check(STR("a\u1100"), STR("{:.3}"), STR("a\u1100c"));
|
||||
check(STR("a"), STR("{:.2}"), STR("a\u1100c"));
|
||||
check(STR("a"), STR("{:.1}"), STR("a\u1100c"));
|
||||
|
||||
check(STR("a\u1100c"), STR("{:-<4.4}"), STR("a\u1100c"));
|
||||
check(STR("a\u1100"), STR("{:-<3.3}"), STR("a\u1100c"));
|
||||
check(STR("a-"), STR("{:-<2.2}"), STR("a\u1100c"));
|
||||
check(STR("a"), STR("{:-<1.1}"), STR("a\u1100c"));
|
||||
|
||||
check(STR("a\u1110c---"), STR("{:-<7}"), STR("a\u1110c"));
|
||||
check(STR("-a\u1110c--"), STR("{:-^7}"), STR("a\u1110c"));
|
||||
check(STR("---a\u1110c"), STR("{:->7}"), STR("a\u1110c"));
|
||||
#else
|
||||
(void)check;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <class CharT, class TestFunction, class ExceptionTest>
|
||||
void format_string_tests(TestFunction check, ExceptionTest check_exception) {
|
||||
std::basic_string<CharT> world = STR("world");
|
||||
std::basic_string<CharT> universe = STR("universe");
|
||||
|
||||
// Testing the char const[] is a bit tricky due to array to pointer decay.
|
||||
// Since there are separate tests in format.formatter.spec the array is not
|
||||
// tested here.
|
||||
format_test_string<CharT>(world.c_str(), universe.c_str(), check,
|
||||
check_exception);
|
||||
format_test_string<CharT>(const_cast<CharT*>(world.c_str()),
|
||||
const_cast<CharT*>(universe.c_str()), check,
|
||||
check_exception);
|
||||
format_test_string<CharT>(std::basic_string_view<CharT>(world),
|
||||
std::basic_string_view<CharT>(universe), check,
|
||||
check_exception);
|
||||
format_test_string<CharT>(world, universe, check, check_exception);
|
||||
format_test_string_unicode<CharT>(check);
|
||||
}
|
||||
|
||||
template <class CharT, class TestFunction, class ExceptionTest>
|
||||
void format_tests(TestFunction check, ExceptionTest check_exception) {
|
||||
// *** Test escaping ***
|
||||
@@ -71,6 +317,7 @@ void format_tests(TestFunction check, ExceptionTest check_exception) {
|
||||
std::basic_string_view<CharT> data = buffer;
|
||||
check(STR("hello world"), STR("hello {}"), data);
|
||||
}
|
||||
format_string_tests<CharT>(check, check_exception);
|
||||
|
||||
// *** Test Boolean format argument ***
|
||||
check(STR("hello 01"), STR("hello {}{}"), false, true);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <format>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "format_tests.h"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <format>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
#include "test_macros.h"
|
||||
#include "format_tests.h"
|
||||
|
||||
Reference in New Issue
Block a user