[libcxx-commits] [libcxx] a04a6ce - [libc++][format] Adds parser std-format-spec.

Mark de Wever via libcxx-commits libcxx-commits at lists.llvm.org
Tue Sep 21 09:30:02 PDT 2021


Author: Mark de Wever
Date: 2021-09-21T18:29:58+02:00
New Revision: a04a6ce7726b51c4f503c8de899362bee40d4e04

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

LOG: [libc++][format] Adds parser std-format-spec.

This implements the generic std.format.spec framework for all types.

The Unicode support will be added in a separate patch.

Implements parts of:
- P0645 Text Formatting

Completes:
- LWG-3242 std::format: missing rules for arg-id in width and precision
- P1892 Extended locale-specific presentation specifiers for std::format

Reviewed By: #libc, ldionne, vitaut

Differential Revision: https://reviews.llvm.org/D103368

Added: 
    libcxx/include/__format/parser_std_format_spec.h
    libcxx/test/libcxx/diagnostics/detail.headers/format/parser_std_format_spec.module.verify.cpp
    libcxx/test/libcxx/utilities/format/format.string/format.string.std/concepts_precision.h
    libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integral.pass.cpp
    libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_string.pass.cpp
    libcxx/test/libcxx/utilities/format/format.string/format.string.std/test_exception.h

Modified: 
    libcxx/docs/Status/Cxx20Issues.csv
    libcxx/docs/Status/Cxx20Papers.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__format/formatter.h
    libcxx/include/format
    libcxx/include/module.modulemap

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv
index 67729a3ccbe19..62a0cd9da8001 100644
--- a/libcxx/docs/Status/Cxx20Issues.csv
+++ b/libcxx/docs/Status/Cxx20Issues.csv
@@ -202,7 +202,7 @@
 "`3233 <https://wg21.link/LWG3233>`__","Broken requirements for ``shared_ptr``\  converting constructors","Prague","",""
 "`3237 <https://wg21.link/LWG3237>`__","LWG 3038 and 3190 have inconsistent PRs","Prague","",""
 "`3238 <https://wg21.link/LWG3238>`__","Insufficiently-defined behavior of ``std::function``\  deduction guides","Prague","",""
-"`3242 <https://wg21.link/LWG3242>`__","``std::format``\ : missing rules for ``arg-id``\  in ``width``\  and ``precision``\ ","Prague","",""
+"`3242 <https://wg21.link/LWG3242>`__","``std::format``\ : missing rules for ``arg-id``\  in ``width``\  and ``precision``\ ","Prague","|Complete|","Clang 14"
 "`3243 <https://wg21.link/LWG3243>`__","``std::format``\  and negative zeroes","Prague","",""
 "`3247 <https://wg21.link/LWG3247>`__","``ranges::iter_move``\  should perform ADL-only lookup of ``iter_move``\ ","Prague","",""
 "`3248 <https://wg21.link/LWG3248>`__","``std::format``\  ``#b``\ , ``#B``\ , ``#o``\ , ``#x``\ , and ``#X``\   presentation types misformat negative numbers","Prague","",""

diff  --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv
index a95a11fae61e6..b5224ea9ef386 100644
--- a/libcxx/docs/Status/Cxx20Papers.csv
+++ b/libcxx/docs/Status/Cxx20Papers.csv
@@ -157,7 +157,7 @@
 "`P1871 <https://wg21.link/P1871>`__","LWG","Should concepts be enabled or disabled?","Belfast","* *",""
 "`P1872 <https://wg21.link/P1872>`__","LWG","span should have size_type, not index_type","Belfast","|Complete|","10.0"
 "`P1878 <https://wg21.link/P1878>`__","LWG","Constraining Readable Types","Belfast","* *",""
-"`P1892 <https://wg21.link/P1892>`__","LWG","Extended locale-specific presentation specifiers for std::format","Belfast","* *",""
+"`P1892 <https://wg21.link/P1892>`__","LWG","Extended locale-specific presentation specifiers for std::format","Belfast","|Complete|","14.0"
 "`P1902 <https://wg21.link/P1902>`__","LWG","Missing feature-test macros 2018-2019","Belfast","* *",""
 "`P1959 <https://wg21.link/P1959>`__","LWG","Remove std::weak_equality and std::strong_equality","Belfast","* *",""
 "`P1960 <https://wg21.link/P1960>`__","LWG","NB Comment Changes Reviewed by SG1","Belfast","* *",""
@@ -199,4 +199,4 @@
 "`P2216R3 <https://wg21.link/P2216R3>`__","LWG",std::format improvements,"June 2021","",""
 "`P2281R1 <https://wg21.link/P2281R1>`__","LWG",Clarifying range adaptor objects,"June 2021","",""
 "`P2328R1 <https://wg21.link/P2328R1>`__","LWG",join_view should join all views of ranges,"June 2021","",""
-"`P2367R0 <https://wg21.link/P2367R0>`__","LWG",Remove misuses of list-initialization from Clause 24,"June 2021","",""
\ No newline at end of file
+"`P2367R0 <https://wg21.link/P2367R0>`__","LWG",Remove misuses of list-initialization from Clause 24,"June 2021","",""

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 34cc9a52e52ff..68ddbd48c74bd 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -138,6 +138,7 @@ set(files
   __format/format_parse_context.h
   __format/format_string.h
   __format/formatter.h
+  __format/parser_std_format_spec.h
   __function_like.h
   __functional_base
   __functional/binary_function.h

diff  --git a/libcxx/include/__format/formatter.h b/libcxx/include/__format/formatter.h
index 2aec2f4e28466..c471bcae57e63 100644
--- a/libcxx/include/__format/formatter.h
+++ b/libcxx/include/__format/formatter.h
@@ -13,6 +13,7 @@
 #include <__availability>
 #include <__config>
 #include <__format/format_error.h>
+#include <__format/parser_std_format_spec.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #pragma GCC system_header

diff  --git a/libcxx/include/__format/parser_std_format_spec.h b/libcxx/include/__format/parser_std_format_spec.h
new file mode 100644
index 0000000000000..a383a1ff50b48
--- /dev/null
+++ b/libcxx/include/__format/parser_std_format_spec.h
@@ -0,0 +1,722 @@
+// -*- 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_PARSER_STD_FORMAT_SPEC_H
+#define _LIBCPP___FORMAT_PARSER_STD_FORMAT_SPEC_H
+
+#include <__config>
+#include <__debug>
+#include <__format/format_arg.h>
+#include <__format/format_error.h>
+#include <__format/format_string.h>
+#include <__variant/monostate.h>
+#include <concepts>
+#include <cstdint>
+#include <type_traits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_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 {
+
+/**
+ * Contains the flags for the std-format-spec.
+ *
+ * Some format-options can only be used for specific C++types and may depend on
+ * the selected format-type.
+ * * The C++type filtering can be done using the proper policies for
+ *   @ref __parser_std.
+ * * The format-type filtering needs to be done post parsing in the parser
+ *   derived from @ref __parser_std.
+ */
+class _LIBCPP_TYPE_VIS _Flags {
+public:
+  enum class _LIBCPP_ENUM_VIS _Alignment : uint8_t {
+    /**
+     * No alignment is set in the format string.
+     *
+     * Zero-padding is ignored when an alignment is selected.
+     * The default alignment depends on the selected format-type.
+     */
+    __default,
+    __left,
+    __center,
+    __right
+  };
+  enum class _LIBCPP_ENUM_VIS _Sign : uint8_t {
+    /**
+     * No sign is set in the format string.
+     *
+     * The sign isn't allowed for certain format-types. By using this value
+     * it's possible to detect whether or not the user explicitly set the sign
+     * flag. For formatting purposes it behaves the same as @ref __minus.
+     */
+    __default,
+    __minus,
+    __plus,
+    __space
+  };
+
+  _Alignment __alignment : 2 {_Alignment::__default};
+  _Sign __sign : 2 {_Sign::__default};
+  uint8_t __alternate_form : 1 {false};
+  uint8_t __zero_padding : 1 {false};
+  uint8_t __locale_specific_form : 1 {false};
+
+  enum class _LIBCPP_ENUM_VIS _Type : uint8_t {
+    __default,
+    __string,
+    __binary_lower_case,
+    __binary_upper_case,
+    __octal,
+    __decimal,
+    __hexadecimal_lower_case,
+    __hexadecimal_upper_case,
+    __pointer,
+    __char,
+    __float_hexadecimal_lower_case,
+    __float_hexadecimal_upper_case,
+    __scientific_lower_case,
+    __scientific_upper_case,
+    __fixed_lower_case,
+    __fixed_upper_case,
+    __general_lower_case,
+    __general_upper_case
+  };
+
+  _Type __type{_Type::__default};
+};
+
+namespace __detail {
+template <class _CharT>
+_LIBCPP_HIDE_FROM_ABI constexpr bool
+__parse_alignment(_CharT __c, _Flags& __flags) noexcept {
+  switch (__c) {
+  case _CharT('<'):
+    __flags.__alignment = _Flags::_Alignment::__left;
+    return true;
+
+  case _CharT('^'):
+    __flags.__alignment = _Flags::_Alignment::__center;
+    return true;
+
+  case _CharT('>'):
+    __flags.__alignment = _Flags::_Alignment::__right;
+    return true;
+  }
+  return false;
+}
+} // namespace __detail
+
+template <class _CharT>
+class _LIBCPP_TEMPLATE_VIS __parser_fill_align {
+public:
+  // TODO FMT The standard doesn't specify this character is a Unicode
+  // character. Validate what fmt and MSVC have implemented.
+  _CharT __fill{_CharT(' ')};
+
+protected:
+  _LIBCPP_HIDE_FROM_ABI constexpr const _CharT*
+  __parse(const _CharT* __begin, const _CharT* __end, _Flags& __flags) {
+    _LIBCPP_ASSERT(__begin != __end,
+                   "When called with an empty input the function will cause "
+                   "undefined behavior by evaluating data not in the input");
+    if (__begin + 1 != __end) {
+      if (__detail::__parse_alignment(*(__begin + 1), __flags)) {
+        if (*__begin == _CharT('{') || *__begin == _CharT('}'))
+          __throw_format_error(
+              "The format-spec fill field contains an invalid character");
+        __fill = *__begin;
+        return __begin + 2;
+      }
+    }
+
+    if (__detail::__parse_alignment(*__begin, __flags))
+      return __begin + 1;
+
+    return __begin;
+  }
+};
+
+template <class _CharT>
+_LIBCPP_HIDE_FROM_ABI constexpr const _CharT*
+__parse_sign(const _CharT* __begin, _Flags& __flags) noexcept {
+  switch (*__begin) {
+  case _CharT('-'):
+    __flags.__sign = _Flags::_Sign::__minus;
+    break;
+  case _CharT('+'):
+    __flags.__sign = _Flags::_Sign::__plus;
+    break;
+  case _CharT(' '):
+    __flags.__sign = _Flags::_Sign::__space;
+    break;
+  default:
+    return __begin;
+  }
+  return __begin + 1;
+}
+
+template <class _CharT>
+_LIBCPP_HIDE_FROM_ABI constexpr const _CharT*
+__parse_alternate_form(const _CharT* __begin, _Flags& __flags) noexcept {
+  if (*__begin == _CharT('#')) {
+    __flags.__alternate_form = true;
+    ++__begin;
+  }
+
+  return __begin;
+}
+
+template <class _CharT>
+_LIBCPP_HIDE_FROM_ABI constexpr const _CharT*
+__parse_zero_padding(const _CharT* __begin, _Flags& __flags) noexcept {
+  if (*__begin == _CharT('0')) {
+    __flags.__zero_padding = true;
+    ++__begin;
+  }
+
+  return __begin;
+}
+
+template <class _CharT>
+_LIBCPP_HIDE_FROM_ABI constexpr __format::__parse_number_result< _CharT>
+__parse_arg_id(const _CharT* __begin, const _CharT* __end, auto& __parse_ctx) {
+  // This function is a wrapper to call the real parser. But it does the
+  // validation for the pre-conditions and post-conditions.
+  if (__begin == __end)
+    __throw_format_error("End of input while parsing format-spec arg-id");
+
+  __format::__parse_number_result __r =
+      __format::__parse_arg_id(__begin, __end, __parse_ctx);
+
+  if (__r.__ptr == __end || *__r.__ptr != _CharT('}'))
+    __throw_format_error("A format-spec arg-id should terminate at a '}'");
+
+  ++__r.__ptr;
+  return __r;
+}
+
+template <class _Context>
+_LIBCPP_HIDE_FROM_ABI constexpr uint32_t
+__substitute_arg_id(basic_format_arg<_Context> __arg) {
+  return visit_format_arg(
+      [](auto __arg) -> uint32_t {
+        using _Type = decltype(__arg);
+        if constexpr (integral<_Type>) {
+          if constexpr (signed_integral<_Type>) {
+            if (__arg < 0)
+              __throw_format_error("A format-spec arg-id replacement shouldn't "
+                                   "have a negative value");
+          }
+
+          using _CT = common_type_t<_Type, decltype(__format::__number_max)>;
+          if (static_cast<_CT>(__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");
+        else
+          __throw_format_error("A format-spec arg-id replacement argument "
+                               "isn't an integral type");
+      },
+      __arg);
+}
+
+class _LIBCPP_TYPE_VIS __parser_width {
+public:
+  /** Contains a width or an arg-id. */
+  uint32_t __width : 31 {0};
+  /** Determines whether the value stored is a width or an arg-id. */
+  uint32_t __width_as_arg : 1 {0};
+
+protected:
+  /**
+   * Does the supplied std-format-spec contain a width field?
+   *
+   * When the field isn't present there's no padding required. This can be used
+   * to optimize the formatting.
+   */
+  constexpr bool __has_width_field() const noexcept {
+    return __width_as_arg || __width;
+  }
+
+  /**
+   * Does the supplied width field contain an arg-id?
+   *
+   * If @c true the formatter needs to call @ref __substitute_width_arg_id.
+   */
+  constexpr bool __width_needs_substitution() const noexcept {
+    return __width_as_arg;
+  }
+
+  template <class _CharT>
+  _LIBCPP_HIDE_FROM_ABI constexpr const _CharT*
+  __parse(const _CharT* __begin, const _CharT* __end, auto& __parse_ctx) {
+    if (*__begin == _CharT('0'))
+      __throw_format_error(
+          "A format-spec width field shouldn't have a leading zero");
+
+    if (*__begin == _CharT('{')) {
+      __format::__parse_number_result __r =
+          __parse_arg_id(++__begin, __end, __parse_ctx);
+      __width = __r.__value;
+      __width_as_arg = 1;
+      return __r.__ptr;
+    }
+
+    if (*__begin < _CharT('0') || *__begin > _CharT('9'))
+      return __begin;
+
+    __format::__parse_number_result __r =
+        __format::__parse_number(__begin, __end);
+    __width = __r.__value;
+    _LIBCPP_ASSERT(__width != 0,
+                   "A zero value isn't allowed and should be impossible, "
+                   "due to validations in this function");
+    return __r.__ptr;
+  }
+
+  void _LIBCPP_HIDE_FROM_ABI constexpr __substitute_width_arg_id(auto __arg) {
+    _LIBCPP_ASSERT(__width_as_arg == 1,
+                   "Substitute width called when no substitution is required");
+
+    // The clearing of the flag isn't required but looks better when debugging
+    // the code.
+    __width_as_arg = 0;
+    __width = __substitute_arg_id(__arg);
+    if (__width == 0)
+      __throw_format_error(
+          "A format-spec width field replacement should have a positive value");
+  }
+};
+
+class _LIBCPP_TYPE_VIS __parser_precision {
+public:
+  /** Contains a precision or an arg-id. */
+  uint32_t __precision : 31 {__format::__number_max};
+  /**
+   * Determines whether the value stored is a precision or an arg-id.
+   *
+   * @note Since @ref __precision == @ref __format::__number_max is a valid
+   * value, the default value contains an arg-id of INT32_MAX. (This number of
+   * arguments isn't supported by compilers.)  This is used to detect whether
+   * the std-format-spec contains a precision field.
+   */
+  uint32_t __precision_as_arg : 1 {1};
+
+protected:
+  /**
+   * Does the supplied std-format-spec contain a precision field?
+   *
+   * When the field isn't present there's no truncating required. This can be
+   * used to optimize the formatting.
+   */
+  constexpr bool __has_precision_field() const noexcept {
+
+    return __precision_as_arg == 0 ||             // Contains a value?
+           __precision != __format::__number_max; // The arg-id is valid?
+  }
+
+  /**
+   * Does the supplied precision field contain an arg-id?
+   *
+   * If @c true the formatter needs to call @ref __substitute_precision_arg_id.
+   */
+  constexpr bool __precision_needs_substitution() const noexcept {
+    return __precision_as_arg && __precision != __format::__number_max;
+  }
+
+  template <class _CharT>
+  _LIBCPP_HIDE_FROM_ABI constexpr const _CharT*
+  __parse(const _CharT* __begin, const _CharT* __end, auto& __parse_ctx) {
+    if (*__begin != _CharT('.'))
+      return __begin;
+
+    ++__begin;
+    if (__begin == __end)
+      __throw_format_error("End of input while parsing format-spec precision");
+
+    if (*__begin == _CharT('0')) {
+      ++__begin;
+      if (__begin != __end && *__begin >= '0' && *__begin <= '9')
+        __throw_format_error(
+            "A format-spec precision field shouldn't have a leading zero");
+
+      __precision = 0;
+      __precision_as_arg = 0;
+      return __begin;
+    }
+
+    if (*__begin == _CharT('{')) {
+      __format::__parse_number_result __arg_id =
+          __parse_arg_id(++__begin, __end, __parse_ctx);
+      _LIBCPP_ASSERT(__arg_id.__value != __format::__number_max,
+                     "Unsupported number of arguments, since this number of "
+                     "arguments is used a special value");
+      __precision = __arg_id.__value;
+      return __arg_id.__ptr;
+    }
+
+    if (*__begin < _CharT('0') || *__begin > _CharT('9'))
+      __throw_format_error(
+          "The format-spec precision field doesn't contain a value or arg-id");
+
+    __format::__parse_number_result __r =
+        __format::__parse_number(__begin, __end);
+    __precision = __r.__value;
+    __precision_as_arg = 0;
+    return __r.__ptr;
+  }
+
+  void _LIBCPP_HIDE_FROM_ABI constexpr __substitute_precision_arg_id(
+      auto __arg) {
+    _LIBCPP_ASSERT(
+        __precision_as_arg == 1 && __precision != __format::__number_max,
+        "Substitute precision called when no substitution is required");
+
+    // The clearing of the flag isn't required but looks better when debugging
+    // the code.
+    __precision_as_arg = 0;
+    __precision = __substitute_arg_id(__arg);
+  }
+};
+
+template <class _CharT>
+_LIBCPP_HIDE_FROM_ABI constexpr const _CharT*
+__parse_locale_specific_form(const _CharT* __begin, _Flags& __flags) noexcept {
+  if (*__begin == _CharT('L')) {
+    __flags.__locale_specific_form = true;
+    ++__begin;
+  }
+
+  return __begin;
+}
+
+template <class _CharT>
+_LIBCPP_HIDE_FROM_ABI constexpr const _CharT*
+__parse_type(const _CharT* __begin, _Flags& __flags) {
+
+  // Determines the type. It does not validate whether the selected type is
+  // valid. Most formatters have optional fields that are only allowed for
+  // certain types. These parsers need to do validation after the type has
+  // been parsed. So its easier to implement the validation for all types in
+  // the specific parse function.
+  switch (*__begin) {
+  case 'A':
+    __flags.__type = _Flags::_Type::__float_hexadecimal_upper_case;
+    break;
+  case 'B':
+    __flags.__type = _Flags::_Type::__binary_upper_case;
+    break;
+  case 'E':
+    __flags.__type = _Flags::_Type::__scientific_upper_case;
+    break;
+  case 'F':
+    __flags.__type = _Flags::_Type::__fixed_upper_case;
+    break;
+  case 'G':
+    __flags.__type = _Flags::_Type::__general_upper_case;
+    break;
+  case 'X':
+    __flags.__type = _Flags::_Type::__hexadecimal_upper_case;
+    break;
+  case 'a':
+    __flags.__type = _Flags::_Type::__float_hexadecimal_lower_case;
+    break;
+  case 'b':
+    __flags.__type = _Flags::_Type::__binary_lower_case;
+    break;
+  case 'c':
+    __flags.__type = _Flags::_Type::__char;
+    break;
+  case 'd':
+    __flags.__type = _Flags::_Type::__decimal;
+    break;
+  case 'e':
+    __flags.__type = _Flags::_Type::__scientific_lower_case;
+    break;
+  case 'f':
+    __flags.__type = _Flags::_Type::__fixed_lower_case;
+    break;
+  case 'g':
+    __flags.__type = _Flags::_Type::__general_lower_case;
+    break;
+  case 'o':
+    __flags.__type = _Flags::_Type::__octal;
+    break;
+  case 'p':
+    __flags.__type = _Flags::_Type::__pointer;
+    break;
+  case 's':
+    __flags.__type = _Flags::_Type::__string;
+    break;
+  case 'x':
+    __flags.__type = _Flags::_Type::__hexadecimal_lower_case;
+    break;
+  default:
+    return __begin;
+  }
+  return ++__begin;
+}
+
+/**
+ * The parser for the std-format-spec.
+ *
+ * [format.string.std]/1 specifies the std-format-spec:
+ *   fill-and-align sign # 0 width precision L type
+ *
+ * All these fields are optional. Whether these fields can be used depend on:
+ * - The type supplied to the format string.
+ *   E.g. A string never uses the sign field so the field may not be set.
+ *   This constrain is validated by the parsers in this file.
+ * - The supplied value for the optional type field.
+ *   E.g. A int formatted as decimal uses the sign field.
+ *   When formatted as a char the sign field may no longer be set.
+ *   This constrain isn't validated by the parsers in this file.
+ *
+ * The base classes are ordered to minimize the amount of padding.
+ *
+ * This implements the parser for the string types.
+ */
+template <class _CharT>
+class _LIBCPP_TEMPLATE_VIS __parser_string
+    : public __parser_width,              // provides __width(|as_arg)
+      public __parser_precision,          // provides __precision(|as_arg)
+      public __parser_fill_align<_CharT>, // provides __fill and uses __flags
+      public _Flags                       // provides __flags
+{
+public:
+  using char_type = _CharT;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __parser_string() {
+    this->__alignment = _Flags::_Alignment::__left;
+  }
+
+  /**
+   * The low-level std-format-spec parse function.
+   *
+   * @pre __begin points at the beginning of the std-format-spec. This means
+   * directly after the ':'.
+   * @pre The std-format-spec parses the entire input, or the first unmatched
+   * character is a '}'.
+   *
+   * @returns The iterator pointing at the last parsed character.
+   */
+  _LIBCPP_HIDE_FROM_ABI constexpr auto parse(auto& __parse_ctx)
+      -> decltype(__parse_ctx.begin()) {
+    auto __it = __parse(__parse_ctx);
+    __process_display_type();
+    return __it;
+  }
+
+private:
+  /**
+   * Parses the std-format-spec.
+   *
+   * @throws __throw_format_error When @a __parse_ctx contains an ill-formed
+   *                               std-format-spec.
+   *
+   * @returns An iterator to the end of input or point at the closing '}'.
+   */
+  _LIBCPP_HIDE_FROM_ABI constexpr auto __parse(auto& __parse_ctx)
+      -> decltype(__parse_ctx.begin()) {
+
+    auto __begin = __parse_ctx.begin();
+    auto __end = __parse_ctx.end();
+    if (__begin == __end)
+      return __begin;
+
+    __begin = __parser_fill_align<_CharT>::__parse(__begin, __end,
+                                                   static_cast<_Flags&>(*this));
+    if (__begin == __end)
+      return __begin;
+
+    __begin = __parser_width::__parse(__begin, __end, __parse_ctx);
+    if (__begin == __end)
+      return __begin;
+
+    __begin = __parser_precision::__parse(__begin, __end, __parse_ctx);
+    if (__begin == __end)
+      return __begin;
+
+    __begin = __parse_type(__begin, static_cast<_Flags&>(*this));
+
+    if (__begin != __end && *__begin != _CharT('}'))
+      __throw_format_error(
+          "The format-spec should consume the input or end with a '}'");
+
+    return __begin;
+  }
+
+  /** Processes the parsed std-format-spec based on the parsed display type. */
+  void _LIBCPP_HIDE_FROM_ABI constexpr __process_display_type() {
+    switch (this->__type) {
+    case _Flags::_Type::__default:
+    case _Flags::_Type::__string:
+      break;
+
+    default:
+      __throw_format_error("The format-spec type has a type not supported for "
+                           "a string argument");
+    }
+  }
+};
+
+/**
+ * The parser for the std-format-spec.
+ *
+ * This implements the parser for the integral types. This includes the
+ * character type and boolean type.
+ *
+ * See @ref __parser_string.
+ */
+template <class _CharT>
+class _LIBCPP_TEMPLATE_VIS __parser_integral
+    : public __parser_width,              // provides __width(|as_arg)
+      public __parser_fill_align<_CharT>, // provides __fill and uses __flags
+      public _Flags                       // provides __flags
+{
+public:
+  using char_type = _CharT;
+
+  // TODO FMT This class probably doesn't need public member functions after
+  // format.string.std/std_format_spec_integral.pass.cpp has been retired.
+
+  /**
+   * The low-level std-format-spec parse function.
+   *
+   * @pre __begin points at the beginning of the std-format-spec. This means
+   * directly after the ':'.
+   * @pre The std-format-spec parses the entire input, or the first unmatched
+   * character is a '}'.
+   *
+   * @returns The iterator pointing at the last parsed character.
+   */
+  _LIBCPP_HIDE_FROM_ABI constexpr auto parse(auto& __parse_ctx)
+      -> decltype(__parse_ctx.begin()) {
+    auto __begin = __parse_ctx.begin();
+    auto __end = __parse_ctx.end();
+    if (__begin == __end)
+      return __begin;
+
+    __begin = __parser_fill_align<_CharT>::__parse(__begin, __end,
+                                                   static_cast<_Flags&>(*this));
+    if (__begin == __end)
+      return __begin;
+
+    __begin = __parse_sign(__begin, static_cast<_Flags&>(*this));
+    if (__begin == __end)
+      return __begin;
+
+    __begin = __parse_alternate_form(__begin, static_cast<_Flags&>(*this));
+    if (__begin == __end)
+      return __begin;
+
+    __begin = __parse_zero_padding(__begin, static_cast<_Flags&>(*this));
+    if (__begin == __end)
+      return __begin;
+
+    __begin = __parser_width::__parse(__begin, __end, __parse_ctx);
+    if (__begin == __end)
+      return __begin;
+
+    __begin =
+        __parse_locale_specific_form(__begin, static_cast<_Flags&>(*this));
+    if (__begin == __end)
+      return __begin;
+
+    __begin = __parse_type(__begin, static_cast<_Flags&>(*this));
+
+    if (__begin != __end && *__begin != _CharT('}'))
+      __throw_format_error(
+          "The format-spec should consume the input or end with a '}'");
+
+    return __begin;
+  }
+
+protected:
+  /**
+   * Handles the post-parsing updates for the integer types.
+   *
+   * Updates the zero-padding and alignment for integer types.
+   *
+   * [format.string.std]/13
+   *   If the 0 character and an align option both appear, the 0 character is
+   *   ignored.
+   *
+   * For the formatter a @ref __default alignment means zero-padding. Update
+   * the alignment based on parsed format string.
+   */
+  _LIBCPP_HIDE_FROM_ABI constexpr void __handle_integer() noexcept {
+    this->__zero_padding &= this->__alignment == _Flags::_Alignment::__default;
+    if (!this->__zero_padding &&
+        this->__alignment == _Flags::_Alignment::__default)
+      this->__alignment = _Flags::_Alignment::__right;
+  }
+
+  /**
+   * Handles the post-parsing updates for the character types.
+   *
+   * Sets the alignment and validates the format flags set for a character type.
+   *
+   * At the moment the validation for a character and a Boolean behave the
+   * same, but this may change in the future.
+   * Specifically at the moment the locale-specific form is allowed for the
+   * char output type, but it has no effect on the output.
+   */
+  _LIBCPP_HIDE_FROM_ABI constexpr void __handle_char() { __handle_bool(); }
+
+  /**
+   * Handles the post-parsing updates for the Boolean types.
+   *
+   * Sets the alignment and validates the format flags set for a Boolean type.
+   */
+  _LIBCPP_HIDE_FROM_ABI constexpr void __handle_bool() {
+    if (this->__sign != _Flags::_Sign::__default)
+      __throw_format_error("A sign field isn't allowed in this format-spec");
+
+    if (this->__alternate_form)
+      __throw_format_error(
+          "An alternate form field isn't allowed in this format-spec");
+
+    if (this->__zero_padding)
+      __throw_format_error(
+          "A zero-padding field isn't allowed in this format-spec");
+
+    if (this->__alignment == _Flags::_Alignment::__default)
+      this->__alignment = _Flags::_Alignment::__left;
+  }
+};
+
+// TODO FMT Add a parser for floating-point values.
+// TODO FMT Add a parser for pointer values.
+
+} // namespace __format_spec
+
+# endif // !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+#endif //_LIBCPP_STD_VER > 17
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___FORMAT_PARSER_STD_FORMAT_SPEC_H

diff  --git a/libcxx/include/format b/libcxx/include/format
index 25350f7bfb26f..dcedc32536267 100644
--- a/libcxx/include/format
+++ b/libcxx/include/format
@@ -277,6 +277,7 @@ 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 <__variant/monostate.h>
 #include <array>

diff  --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index e49d78a725c24..7a0d6731c0041 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -436,18 +436,19 @@ module std [system] {
     export *
 
     module __format {
-      module format_arg           { private header "__format/format_arg.h"           }
-      module format_args          { private header "__format/format_args.h"          }
+      module format_arg             { private header "__format/format_arg.h"           }
+      module format_args            { private header "__format/format_args.h"          }
       module format_context {
         private header "__format/format_context.h"
         export optional
         export locale
       }
-      module format_error         { private header "__format/format_error.h"         }
-      module format_fwd           { private header "__format/format_fwd.h"           }
-      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 format_error           { private header "__format/format_error.h"           }
+      module format_fwd             { private header "__format/format_fwd.h"             }
+      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 parser_std_format_spec { private header "__format/parser_std_format_spec.h" }
     }
   }
   module forward_list {

diff  --git a/libcxx/test/libcxx/diagnostics/detail.headers/format/parser_std_format_spec.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/format/parser_std_format_spec.module.verify.cpp
new file mode 100644
index 0000000000000..e846e765e89bd
--- /dev/null
+++ b/libcxx/test/libcxx/diagnostics/detail.headers/format/parser_std_format_spec.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/parser_std_format_spec.h'}}
+#include <__format/parser_std_format_spec.h>

diff  --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/concepts_precision.h b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/concepts_precision.h
new file mode 100644
index 0000000000000..86c9f0638804f
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/concepts_precision.h
@@ -0,0 +1,21 @@
+//===----------------------------------------------------------------------===//
+// 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 LIBCXX_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_CONCEPTS_PRECISION_H
+#define LIBCXX_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_CONCEPTS_PRECISION_H
+
+template <class T>
+concept has_precision = requires(T parser) {
+  parser.__precision;
+};
+
+template <class T>
+concept has_precision_as_arg = requires(T parser) {
+  parser.__precision_as_arg;
+};
+
+#endif // LIBCXX_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_CONCEPTS_PRECISION_H

diff  --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integral.pass.cpp b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integral.pass.cpp
new file mode 100644
index 0000000000000..3ccc8da8fd493
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_integral.pass.cpp
@@ -0,0 +1,313 @@
+//===----------------------------------------------------------------------===//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// <format>
+
+// Tests the parsing of the format string as specified in [format.string.std].
+// It validates whether the std-format-spec is valid for a string type.
+
+// TODO FMT This test should removed once the integer parser is implemented.
+// The integral specific fields are all tested for the integer, making this
+// test redundant.
+
+#include <format>
+#include <cassert>
+#ifndef _LIBCPP_HAS_NO_LOCALIZATION
+# include <iostream>
+#endif
+
+#include "concepts_precision.h"
+#include "test_macros.h"
+#include "make_string.h"
+#include "test_exception.h"
+
+#define CSTR(S) MAKE_CSTRING(CharT, S)
+
+using namespace std::__format_spec;
+
+template <class CharT>
+using Parser = __parser_integral<CharT>;
+
+template <class CharT>
+struct Expected {
+  CharT fill = CharT(' ');
+  _Flags::_Alignment alignment = _Flags::_Alignment::__default;
+  _Flags::_Sign sign = _Flags::_Sign::__default;
+  bool alternate_form = false;
+  bool zero_padding = false;
+  uint32_t width = 0;
+  bool width_as_arg = false;
+  bool locale_specific_form = false;
+  _Flags::_Type type = _Flags::_Type::__default;
+};
+
+template <class CharT>
+constexpr void test(Expected<CharT> expected, size_t size,
+                    std::basic_string_view<CharT> fmt) {
+  // Initialize parser with sufficient arguments to avoid the parsing to fail
+  // due to insufficient arguments.
+  std::basic_format_parse_context<CharT> parse_ctx(fmt,
+                                                   std::__format::__number_max);
+  auto begin = parse_ctx.begin();
+  auto end = parse_ctx.end();
+  Parser<CharT> parser;
+  auto it = parser.parse(parse_ctx);
+
+  assert(begin == parse_ctx.begin());
+  assert(end == parse_ctx.end());
+
+  assert(begin + size == it);
+  assert(parser.__fill == expected.fill);
+  assert(parser.__alignment == expected.alignment);
+  assert(parser.__sign == expected.sign);
+  assert(parser.__alternate_form == expected.alternate_form);
+  assert(parser.__zero_padding == expected.zero_padding);
+  assert(parser.__width == expected.width);
+  assert(parser.__width_as_arg == expected.width_as_arg);
+  assert(parser.__locale_specific_form == expected.locale_specific_form);
+  assert(parser.__type == expected.type);
+}
+
+template <class CharT>
+constexpr void test(Expected<CharT> expected, size_t size, const CharT* f) {
+  // The format-spec is valid if completely consumed or terminates at a '}'.
+  // The valid inputs all end with a '}'. The test is executed twice:
+  // - first with the terminating '}',
+  // - second consuming the entire input.
+  std::basic_string_view<CharT> fmt{f};
+  assert(fmt.back() == CharT('}') && "Pre-condition failure");
+
+  test(expected, size, fmt);
+  fmt.remove_suffix(1);
+  test(expected, size, fmt);
+}
+
+template <class CharT>
+constexpr void test() {
+  Parser<CharT> parser;
+
+  assert(parser.__fill == CharT(' '));
+  assert(parser.__alignment == _Flags::_Alignment::__default);
+  assert(parser.__sign == _Flags::_Sign::__default);
+  assert(parser.__alternate_form == false);
+  assert(parser.__zero_padding == false);
+  assert(parser.__width == 0);
+  assert(parser.__width_as_arg == false);
+  static_assert(!has_precision<decltype(parser)>);
+  static_assert(!has_precision_as_arg<decltype(parser)>);
+  assert(parser.__locale_specific_form == false);
+  assert(parser.__type == _Flags::_Type::__default);
+
+  test({}, 0, CSTR("}"));
+
+  // *** Align-fill ***
+  test({.alignment = _Flags::_Alignment::__left}, 1, CSTR("<}"));
+  test({.alignment = _Flags::_Alignment::__center}, 1, "^}");
+  test({.alignment = _Flags::_Alignment::__right}, 1, ">}");
+
+  test({.fill = CharT('L'), .alignment = _Flags::_Alignment::__left}, 2,
+       CSTR("L<}"));
+  test({.fill = CharT('#'), .alignment = _Flags::_Alignment::__center}, 2,
+       CSTR("#^}"));
+  test({.fill = CharT('0'), .alignment = _Flags::_Alignment::__right}, 2,
+       CSTR("0>}"));
+
+  test_exception<Parser<CharT>>(
+      "The format-spec fill field contains an invalid character", CSTR("{<"));
+  test_exception<Parser<CharT>>(
+      "The format-spec fill field contains an invalid character", CSTR("}<"));
+
+  // *** Sign ***
+  test({.sign = _Flags::_Sign::__minus}, 1, CSTR("-}"));
+  test({.sign = _Flags::_Sign::__plus}, 1, CSTR("+}"));
+  test({.sign = _Flags::_Sign::__space}, 1, CSTR(" }"));
+
+  // *** Alternate form ***
+  test({.alternate_form = true}, 1, CSTR("#}"));
+
+  // *** Zero padding ***
+  // TODO FMT What to do with zero-padding without a width?
+  // [format.string.std]/13
+  //   A zero (0) character preceding the width field pads the field with
+  //   leading zeros (following any indication of sign or base) to the field
+  //   width, except when applied to an infinity or NaN.
+  // Obviously it makes no sense, but should it be allowed or is it a format
+  // errror?
+  test({.zero_padding = true}, 1, CSTR("0}"));
+  test({.alignment = _Flags::_Alignment::__center, .zero_padding = true}, 2,
+       CSTR("^0}"));
+
+  // *** Width ***
+  test({.width = 0, .width_as_arg = false}, 0, CSTR("}"));
+  test({.width = 1, .width_as_arg = false}, 1, CSTR("1}"));
+  test({.width = 10, .width_as_arg = false}, 2, CSTR("10}"));
+  test({.width = 1000, .width_as_arg = false}, 4, CSTR("1000}"));
+  test({.width = 1000000, .width_as_arg = false}, 7, CSTR("1000000}"));
+
+  test({.width = 0, .width_as_arg = true}, 2, CSTR("{}}"));
+  test({.width = 0, .width_as_arg = true}, 3, CSTR("{0}}"));
+  test({.width = 1, .width_as_arg = true}, 3, CSTR("{1}}"));
+
+  test_exception<Parser<CharT>>(
+      "A format-spec width field shouldn't have a leading zero", CSTR("00"));
+
+  static_assert(std::__format::__number_max == 2'147'483'647,
+                "Update the assert and the test.");
+  test({.width = 2'147'483'647, .width_as_arg = false}, 10,
+       CSTR("2147483647}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large", CSTR("2147483648"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large", CSTR("5000000000"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large", CSTR("10000000000"));
+
+  test_exception<Parser<CharT>>("End of input while parsing format-spec arg-id",
+                                CSTR("{"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{0"));
+  test_exception<Parser<CharT>>(
+      "The arg-id of the format-spec starts with an invalid character",
+      CSTR("{a"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{1"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{9"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{9:"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{9a"));
+
+  static_assert(std::__format::__number_max == 2'147'483'647,
+                "Update the assert and the test.");
+  // Note the static_assert tests whether the arg-id is valid.
+  // Therefore the following should be true arg-id < __format::__number_max.
+  test({.width = 2'147'483'646, .width_as_arg = true}, 12,
+       CSTR("{2147483646}}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR("{2147483648}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR("{5000000000}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR("{10000000000}"));
+
+  // *** Precision ***
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR("."));
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR(".1"));
+
+  // *** Locale-specific form ***
+  test({.locale_specific_form = true}, 1, CSTR("L}"));
+  test({.locale_specific_form = true, .type = _Flags::_Type::__decimal}, 2,
+       CSTR("Ld}"));
+  test({.locale_specific_form = true, .type = _Flags::_Type::__char}, 2,
+       CSTR("Lc}"));
+
+  // *** Type ***
+
+  {
+    const char* not_a_type =
+        "The format-spec should consume the input or end with a '}'";
+
+    test({.type = _Flags::_Type::__float_hexadecimal_upper_case}, 1,
+         CSTR("A}"));
+    test({.type = _Flags::_Type::__binary_upper_case}, 1, CSTR("B}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("C}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("D}"));
+    test({.type = _Flags::_Type::__scientific_upper_case}, 1, CSTR("E}"));
+    test({.type = _Flags::_Type::__fixed_upper_case}, 1, CSTR("F}"));
+    test({.type = _Flags::_Type::__general_upper_case}, 1, CSTR("G}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("H}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("I}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("J}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("K}"));
+    test({.locale_specific_form = true}, 1, CSTR("L}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("M}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("N}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("O}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("P}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("Q}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("R}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("S}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("T}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("U}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("V}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("W}"));
+    test({.type = _Flags::_Type::__hexadecimal_upper_case}, 1, CSTR("X}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("Y}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("Z}"));
+
+    test({.type = _Flags::_Type::__float_hexadecimal_lower_case}, 1,
+         CSTR("a}"));
+    test({.type = _Flags::_Type::__binary_lower_case}, 1, CSTR("b}"));
+    test({.type = _Flags::_Type::__char}, 1, CSTR("c}"));
+    test({.type = _Flags::_Type::__decimal}, 1, CSTR("d}"));
+    test({.type = _Flags::_Type::__scientific_lower_case}, 1, CSTR("e}"));
+    test({.type = _Flags::_Type::__fixed_lower_case}, 1, CSTR("f}"));
+    test({.type = _Flags::_Type::__general_lower_case}, 1, CSTR("g}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("h}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("i}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("j}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("k}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("l}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("m}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("n}"));
+    test({.type = _Flags::_Type::__octal}, 1, CSTR("o}"));
+    test({.type = _Flags::_Type::__pointer}, 1, CSTR("p}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("q}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("r}"));
+    test({.type = _Flags::_Type::__string}, 1, CSTR("s}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("t}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("u}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("v}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("w}"));
+    test({.type = _Flags::_Type::__hexadecimal_lower_case}, 1, CSTR("x}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("y}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("z}"));
+  }
+  // **** General ***
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR("ss"));
+}
+
+constexpr bool test() {
+  test<char>();
+  test<wchar_t>();
+#ifndef _LIBCPP_HAS_NO_CHAR8_T
+  test<char8_t>();
+#endif
+#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS
+  test<char16_t>();
+  test<char32_t>();
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+#ifndef _WIN32
+  // Make sure the parsers match the expectations. The layout of the
+  // subobjects is chosen to minimize the size required.
+  static_assert(sizeof(Parser<char>) == 2 * sizeof(uint32_t));
+  static_assert(
+      sizeof(Parser<wchar_t>) ==
+      (sizeof(wchar_t) <= 2 ? 2 * sizeof(uint32_t) : 3 * sizeof(uint32_t)));
+#endif
+
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_string.pass.cpp b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_string.pass.cpp
new file mode 100644
index 0000000000000..53753d03d791a
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/std_format_spec_string.pass.cpp
@@ -0,0 +1,380 @@
+//===----------------------------------------------------------------------===//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// UNSUPPORTED: libcpp-no-concepts
+// UNSUPPORTED: libcpp-has-no-incomplete-format
+
+// <format>
+
+// Tests the parsing of the format string as specified in [format.string.std].
+// It validates whether the std-format-spec is valid for a string type.
+
+#include <format>
+#include <cassert>
+#ifndef _LIBCPP_HAS_NO_LOCALIZATION
+# include <iostream>
+#endif
+
+#include "test_macros.h"
+#include "make_string.h"
+#include "test_exception.h"
+
+#define CSTR(S) MAKE_CSTRING(CharT, S)
+
+using namespace std::__format_spec;
+
+template <class CharT>
+using Parser = __parser_string<CharT>;
+
+template <class CharT>
+struct Expected {
+  CharT fill = CharT(' ');
+  _Flags::_Alignment alignment = _Flags::_Alignment::__left;
+  uint32_t width = 0;
+  bool width_as_arg = false;
+  uint32_t precision = std::__format::__number_max;
+  bool precision_as_arg = true;
+  _Flags::_Type type = _Flags::_Type::__default;
+};
+
+template <class CharT>
+constexpr void test(Expected<CharT> expected, size_t size,
+                    std::basic_string_view<CharT> fmt) {
+  // Initialize parser with sufficient arguments to avoid the parsing to fail
+  // due to insufficient arguments.
+  std::basic_format_parse_context<CharT> parse_ctx(fmt,
+                                                   std::__format::__number_max);
+  auto begin = parse_ctx.begin();
+  auto end = parse_ctx.end();
+  Parser<CharT> parser;
+  auto it = parser.parse(parse_ctx);
+
+  assert(begin == parse_ctx.begin());
+  assert(end == parse_ctx.end());
+
+  assert(begin + size == it);
+  assert(parser.__fill == expected.fill);
+  assert(parser.__alignment == expected.alignment);
+  assert(parser.__sign == _Flags::_Sign::__default);
+  assert(parser.__alternate_form == false);
+  assert(parser.__zero_padding == false);
+  assert(parser.__width == expected.width);
+  assert(parser.__width_as_arg == expected.width_as_arg);
+  assert(parser.__precision == expected.precision);
+  assert(parser.__precision_as_arg == expected.precision_as_arg);
+  assert(parser.__locale_specific_form == false);
+  assert(parser.__type == expected.type);
+}
+
+template <class CharT>
+constexpr void test(Expected<CharT> expected, size_t size, const CharT* f) {
+  // The format-spec is valid if completely consumed or terminates at a '}'.
+  // The valid inputs all end with a '}'. The test is executed twice:
+  // - first with the terminating '}',
+  // - second consuming the entire input.
+  std::basic_string_view<CharT> fmt{f};
+  assert(fmt.back() == CharT('}') && "Pre-condition failure");
+
+  test(expected, size, fmt);
+  fmt.remove_suffix(1);
+  test(expected, size, fmt);
+}
+
+template <class CharT>
+constexpr void test() {
+  Parser<CharT> parser;
+
+  assert(parser.__fill == CharT(' '));
+  assert(parser.__alignment == _Flags::_Alignment::__left);
+  assert(parser.__sign == _Flags::_Sign::__default);
+  assert(parser.__alternate_form == false);
+  assert(parser.__zero_padding == false);
+  assert(parser.__width == 0);
+  assert(parser.__width_as_arg == false);
+  assert(parser.__precision == std::__format::__number_max);
+  assert(parser.__precision_as_arg == true);
+  assert(parser.__locale_specific_form == false);
+  assert(parser.__type == _Flags::_Type::__default);
+
+  test({}, 0, CSTR("}"));
+
+  // *** Align-fill ***
+  test({.alignment = _Flags::_Alignment::__left}, 1, CSTR("<}"));
+  test({.alignment = _Flags::_Alignment::__center}, 1, "^}");
+  test({.alignment = _Flags::_Alignment::__right}, 1, ">}");
+
+  test({.fill = CharT('L'), .alignment = _Flags::_Alignment::__left}, 2,
+       CSTR("L<}"));
+  test({.fill = CharT('#'), .alignment = _Flags::_Alignment::__center}, 2,
+       CSTR("#^}"));
+  test({.fill = CharT('0'), .alignment = _Flags::_Alignment::__right}, 2,
+       CSTR("0>}"));
+
+  test_exception<Parser<CharT>>(
+      "The format-spec fill field contains an invalid character", CSTR("{<"));
+  test_exception<Parser<CharT>>(
+      "The format-spec fill field contains an invalid character", CSTR("}<"));
+
+  // *** Sign ***
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR("+"));
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR("-"));
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR(" "));
+
+  // *** Alternate form ***
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR("#"));
+
+  // *** Zero padding ***
+  test_exception<Parser<CharT>>(
+      "A format-spec width field shouldn't have a leading zero", CSTR("0"));
+
+  // *** Width ***
+  test({.width = 0, .width_as_arg = false}, 0, CSTR("}"));
+  test({.width = 1, .width_as_arg = false}, 1, CSTR("1}"));
+  test({.width = 10, .width_as_arg = false}, 2, CSTR("10}"));
+  test({.width = 1000, .width_as_arg = false}, 4, CSTR("1000}"));
+  test({.width = 1000000, .width_as_arg = false}, 7, CSTR("1000000}"));
+
+  test({.width = 0, .width_as_arg = true}, 2, CSTR("{}}"));
+  test({.width = 0, .width_as_arg = true}, 3, CSTR("{0}}"));
+  test({.width = 1, .width_as_arg = true}, 3, CSTR("{1}}"));
+
+  test_exception<Parser<CharT>>(
+      "A format-spec width field shouldn't have a leading zero", CSTR("00"));
+
+  static_assert(std::__format::__number_max == 2'147'483'647,
+                "Update the assert and the test.");
+  test({.width = 2'147'483'647, .width_as_arg = false}, 10,
+       CSTR("2147483647}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large", CSTR("2147483648"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large", CSTR("5000000000"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large", CSTR("10000000000"));
+
+  test_exception<Parser<CharT>>("End of input while parsing format-spec arg-id",
+                                CSTR("{"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{0"));
+  test_exception<Parser<CharT>>(
+      "The arg-id of the format-spec starts with an invalid character",
+      CSTR("{a"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{1"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{9"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{9:"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR("{9a"));
+
+  static_assert(std::__format::__number_max == 2'147'483'647,
+                "Update the assert and the test.");
+  // Note the static_assert tests whether the arg-id is valid.
+  // Therefore the following should be true arg-id < __format::__number_max.
+  test({.width = 2'147'483'646, .width_as_arg = true}, 12,
+       CSTR("{2147483646}}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR("{2147483648}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR("{5000000000}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR("{10000000000}"));
+
+  // *** Precision ***
+  test({.precision = 0, .precision_as_arg = false}, 2, CSTR(".0}"));
+  test({.precision = 1, .precision_as_arg = false}, 2, CSTR(".1}"));
+  test({.precision = 10, .precision_as_arg = false}, 3, CSTR(".10}"));
+  test({.precision = 1000, .precision_as_arg = false}, 5, CSTR(".1000}"));
+  test({.precision = 1000000, .precision_as_arg = false}, 8, CSTR(".1000000}"));
+
+  test({.precision = 0, .precision_as_arg = true}, 3, CSTR(".{}}"));
+  test({.precision = 0, .precision_as_arg = true}, 4, CSTR(".{0}}"));
+  test({.precision = 1, .precision_as_arg = true}, 4, CSTR(".{1}}"));
+
+  test_exception<Parser<CharT>>(
+      "A format-spec precision field shouldn't have a leading zero",
+      CSTR(".00"));
+  test_exception<Parser<CharT>>(
+      "A format-spec precision field shouldn't have a leading zero",
+      CSTR(".01"));
+  test_exception<Parser<CharT>>(
+      "The format-spec precision field doesn't contain a value or arg-id",
+      CSTR(".a"));
+  test_exception<Parser<CharT>>(
+      "The format-spec precision field doesn't contain a value or arg-id",
+      CSTR(".:"));
+
+  static_assert(std::__format::__number_max == 2'147'483'647,
+                "Update the assert and the test.");
+  test({.precision = 2'147'483'647, .precision_as_arg = false}, 11,
+       CSTR(".2147483647}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large", CSTR(".2147483648"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large", CSTR(".5000000000"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR(".10000000000"));
+
+  test_exception<Parser<CharT>>("End of input while parsing format-spec arg-id",
+                                CSTR(".{"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR(".{0"));
+  test_exception<Parser<CharT>>(
+      "The arg-id of the format-spec starts with an invalid character",
+      CSTR(".{a"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR(".{1"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR(".{9"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR(".{9:"));
+  test_exception<Parser<CharT>>(
+      "A format-spec arg-id should terminate at a '}'", CSTR(".{9a"));
+
+  static_assert(std::__format::__number_max == 2'147'483'647,
+                "Update the assert and the test.");
+  // Note the static_assert tests whether the arg-id is valid.
+  // Therefore the following should be true arg-id < __format::__number_max.
+  test({.precision = 2'147'483'646, .precision_as_arg = true}, 13,
+       CSTR(".{2147483646}}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR(".{2147483648}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR(".{5000000000}"));
+  test_exception<Parser<CharT>>(
+      "The numeric value of the format-spec is too large",
+      CSTR(".{10000000000}"));
+
+  // *** Width & Precision ***
+  test({.width = 1,
+        .width_as_arg = false,
+        .precision = 0,
+        .precision_as_arg = false},
+       3, CSTR("1.0}"));
+  test({.width = 0,
+        .width_as_arg = true,
+        .precision = 1,
+        .precision_as_arg = true},
+       5, CSTR("{}.{}}"));
+  test({.width = 10,
+        .width_as_arg = true,
+        .precision = 9,
+        .precision_as_arg = true},
+       8, CSTR("{10}.{9}}"));
+
+  // *** Locale-specific form ***
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR("L"));
+
+  // *** Type ***
+
+  {
+    const char* unsuported_type =
+        "The format-spec type has a type not supported for a string argument";
+    const char* not_a_type =
+        "The format-spec should consume the input or end with a '}'";
+
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("A}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("B}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("C}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("D}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("E}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("F}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("G}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("H}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("I}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("J}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("K}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("L}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("M}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("N}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("O}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("P}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("Q}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("R}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("S}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("T}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("U}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("V}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("W}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("X}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("Y}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("Z}"));
+
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("a}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("b}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("c}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("d}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("e}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("f}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("g}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("h}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("i}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("j}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("k}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("l}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("m}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("n}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("o}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("p}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("q}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("r}"));
+    test({.type = _Flags::_Type::__string}, 1, CSTR("s}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("t}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("u}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("v}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("w}"));
+    test_exception<Parser<CharT>>(unsuported_type, CSTR("x}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("y}"));
+    test_exception<Parser<CharT>>(not_a_type, CSTR("z}"));
+  }
+  // **** General ***
+  test_exception<Parser<CharT>>(
+      "The format-spec should consume the input or end with a '}'", CSTR("ss"));
+}
+
+constexpr bool test() {
+  test<char>();
+  test<wchar_t>();
+#ifndef _LIBCPP_HAS_NO_CHAR8_T
+  test<char8_t>();
+#endif
+#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS
+  test<char16_t>();
+  test<char32_t>();
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+#ifndef _WIN32
+  // Make sure the parsers match the expectations. The layout of the
+  // subobjects is chosen to minimize the size required.
+  LIBCPP_STATIC_ASSERT(sizeof(Parser<char>) == 3 * sizeof(uint32_t));
+  LIBCPP_STATIC_ASSERT(
+      sizeof(Parser<wchar_t>) ==
+      (sizeof(wchar_t) <= 2 ? 3 * sizeof(uint32_t) : 4 * sizeof(uint32_t)));
+#endif
+
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/libcxx/utilities/format/format.string/format.string.std/test_exception.h b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/test_exception.h
new file mode 100644
index 0000000000000..80e45c5024b2c
--- /dev/null
+++ b/libcxx/test/libcxx/utilities/format/format.string/format.string.std/test_exception.h
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+// 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_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_TEST_EXCEPTION_H
+#define _LIBCPP_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_TEST_EXCEPTION_H
+
+#include <format>
+#include <string_view>
+
+namespace detail {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+template <class Parser, class CharT>
+void test_exception(std::string_view what, const CharT* fmt) {
+  try {
+    std::basic_format_parse_context<CharT> parse_ctx(fmt);
+    (void)Parser{}.parse(parse_ctx);
+    assert(false);
+  } catch (std::format_error& e) {
+    LIBCPP_ASSERT(e.what() == what);
+    return;
+  }
+
+  assert(false);
+}
+#endif
+} // namespace detail
+
+/**
+ *  Wrapper for the exception tests.
+ *
+ *  When using the real function directly during in a constexpr test and add
+ *  the `std::is_constant_evaluated()` test there the compilation fails. This
+ *  happens since assert calls the non-constexpr function '__assert_fail'.
+ *  Solve this issue with an layer of indirection.
+ */
+template <class Parser, class CharT>
+constexpr void test_exception(std::string_view what, const CharT* fmt) {
+#ifndef TEST_HAS_NO_EXCEPTIONS
+  if (!std::is_constant_evaluated())
+    detail::test_exception<Parser>(what, fmt);
+#else
+  (void)what;
+  (void)fmt;
+#endif
+}
+
+#endif // _LIBCPP_TEST_STD_UTILITIES_FORMAT_FORMAT_STRING_FORMAT_STRING_STD_TEST_EXCEPTION_H


        


More information about the libcxx-commits mailing list