[libcxx-commits] [libcxx] d550930 - [libc++][format] Adds string formatter.

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Thu Oct 7 08:03:09 PDT 2021


Author: Mark de Wever
Date: 2021-10-07T17:03:04+02:00
New Revision: d550930afcbb84740681c219ab13efd133143f88

URL: https://github.com/llvm/llvm-project/commit/d550930afcbb84740681c219ab13efd133143f88
DIFF: https://github.com/llvm/llvm-project/commit/d550930afcbb84740681c219ab13efd133143f88.diff

LOG: [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

Added: 
    libcxx/include/__format/formatter_string.h
    libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_string.module.verify.cpp

Modified: 
    libcxx/include/CMakeLists.txt
    libcxx/include/__format/format_string.h
    libcxx/include/__format/formatter.h
    libcxx/include/__format/parser_std_format_spec.h
    libcxx/include/format
    libcxx/include/module.modulemap
    libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.c_string.pass.cpp
    libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.const_char_array.pass.cpp
    libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.string.pass.cpp
    libcxx/test/std/utilities/format/format.functions/format.pass.cpp
    libcxx/test/std/utilities/format/format.functions/format_tests.h
    libcxx/test/std/utilities/format/format.functions/formatted_size.pass.cpp
    libcxx/test/std/utilities/format/format.functions/vformat.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index e046d16f0d789..a884521dfaf2a 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -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

diff  --git a/libcxx/include/__format/format_string.h b/libcxx/include/__format/format_string.h
index 864053f64cde8..885e572fc068c 100644
--- a/libcxx/include/__format/format_string.h
+++ b/libcxx/include/__format/format_string.h
@@ -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

diff  --git a/libcxx/include/__format/formatter.h b/libcxx/include/__format/formatter.h
index c471bcae57e63..9c7cca0b574d5 100644
--- a/libcxx/include/__format/formatter.h
+++ b/libcxx/include/__format/formatter.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 @@ 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 
diff er
+ * 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, ptr
diff _t __width,
+                ptr
diff _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

diff  --git a/libcxx/include/__format/formatter_string.h b/libcxx/include/__format/formatter_string.h
new file mode 100644
index 0000000000000..45d1147e445da
--- /dev/null
+++ b/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

diff  --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h
index aab565e63e9e8..3d9d7c72b879f 100644
--- a/libcxx/include/__format/parser_std_format_spec.h
+++ b/libcxx/include/__format/parser_std_format_spec.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");

diff  --git a/libcxx/include/format b/libcxx/include/format
index dcedc32536267..a869bbe1d6f99 100644
--- a/libcxx/include/format
+++ b/libcxx/include/format
@@ -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

diff  --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index f85e9e31f88d2..85b093be2e7e1 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -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" }
     }
   }

diff  --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_string.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_string.module.verify.cpp
new file mode 100644
index 0000000000000..fac1465d028e9
--- /dev/null
+++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/formatter_string.module.verify.cpp
@@ -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>

diff  --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.c_string.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.c_string.pass.cpp
index 7ae578a17bdda..ab729141ef543 100644
--- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.c_string.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.c_string.pass.cpp
@@ -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**) {

diff  --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.const_char_array.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.const_char_array.pass.cpp
index 608504759047a..b2228043b5ed6 100644
--- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.const_char_array.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.const_char_array.pass.cpp
@@ -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**) {

diff  --git a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.string.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.string.pass.cpp
index 7bd1d1c631175..6a6de7e64cb0e 100644
--- a/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.string.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.formatter/format.context/format.formatter.spec/formatter.string.pass.cpp
@@ -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>

diff  --git a/libcxx/test/std/utilities/format/format.functions/format.pass.cpp b/libcxx/test/std/utilities/format/format.functions/format.pass.cpp
index acf775cf0531f..392a412680caa 100644
--- a/libcxx/test/std/utilities/format/format.functions/format.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.functions/format.pass.cpp
@@ -27,6 +27,7 @@
 #ifndef _LIBCPP_HAS_NO_LOCALIZATION
 #include <iostream>
 #endif
+#include <vector>
 
 #include "test_macros.h"
 #include "format_tests.h"

diff  --git a/libcxx/test/std/utilities/format/format.functions/format_tests.h b/libcxx/test/std/utilities/format/format.functions/format_tests.h
index 770c0df629090..8c61a3a59d39f 100644
--- a/libcxx/test/std/utilities/format/format.functions/format_tests.h
+++ b/libcxx/test/std/utilities/format/format.functions/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);

diff  --git a/libcxx/test/std/utilities/format/format.functions/formatted_size.pass.cpp b/libcxx/test/std/utilities/format/format.functions/formatted_size.pass.cpp
index 732a942c52405..3ba377656934d 100644
--- a/libcxx/test/std/utilities/format/format.functions/formatted_size.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.functions/formatted_size.pass.cpp
@@ -20,6 +20,7 @@
 
 #include <format>
 #include <cassert>
+#include <vector>
 
 #include "test_macros.h"
 #include "format_tests.h"

diff  --git a/libcxx/test/std/utilities/format/format.functions/vformat.pass.cpp b/libcxx/test/std/utilities/format/format.functions/vformat.pass.cpp
index cb3bd20fb1690..b1e7025bbcd15 100644
--- a/libcxx/test/std/utilities/format/format.functions/vformat.pass.cpp
+++ b/libcxx/test/std/utilities/format/format.functions/vformat.pass.cpp
@@ -18,6 +18,7 @@
 
 #include <format>
 #include <cassert>
+#include <vector>
 
 #include "test_macros.h"
 #include "format_tests.h"


        


More information about the libcxx-commits mailing list