[libc++][format] Add basic_format_parse_context.
authorMark de Wever <koraq@xs4all.nl>
Tue, 2 Feb 2021 17:10:33 +0000 (18:10 +0100)
committerMark de Wever <koraq@xs4all.nl>
Thu, 11 Feb 2021 16:57:54 +0000 (17:57 +0100)
Implements parts of:
- P0645 Text Formatting

Depends on D92214

Reland with changes:
The format header will only be compiled if the compiler used has support
for concepts. This should fix the issues with the initial version.

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

libcxx/include/__config
libcxx/include/format
libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/advance_to.pass.cpp [new file with mode: 0644]
libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/begin.pass.cpp [new file with mode: 0644]
libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/check_arg_id.pass.cpp [new file with mode: 0644]
libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/ctor.pass.cpp [new file with mode: 0644]
libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/end.pass.cpp [new file with mode: 0644]
libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/next_arg_id.pass.cpp [new file with mode: 0644]
libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/types.compile.pass.cpp [new file with mode: 0644]

index cd1f4da..102dee1 100644 (file)
@@ -536,6 +536,10 @@ typedef __char32_t char32_t;
 #define _LIBCPP_HAS_NO_VARIABLE_TEMPLATES
 #endif
 
+#if !defined(__cpp_concepts) || __cpp_concepts < 201907L
+#define _LIBCPP_HAS_NO_CONCEPTS
+#endif
+
 #if !defined(_LIBCPP_HAS_NO_ASAN) && !defined(__SANITIZE_ADDRESS__)
 #define _LIBCPP_HAS_NO_ASAN
 #endif
index 833423a..9e41120 100644 (file)
@@ -19,14 +19,51 @@ namespace std {
     explicit format_error(const string& what_arg);
     explicit format_error(const char* what_arg);
   };
+
+  // [format.parse.ctx], class template basic_format_parse_context
+  template<class charT>
+  class basic_format_parse_context {
+  public:
+    using char_type = charT;
+    using const_iterator = typename basic_string_view<charT>::const_iterator;
+    using iterator = const_iterator;
+
+  private:
+    iterator begin_;                                    // exposition only
+    iterator end_;                                      // exposition only
+    enum indexing { unknown, manual, automatic };       // exposition only
+    indexing indexing_;                                 // exposition only
+    size_t next_arg_id_;                                // exposition only
+    size_t num_args_;                                   // exposition only
+
+  public:
+    constexpr explicit basic_format_parse_context(basic_string_view<charT> fmt,
+                                                  size_t num_args = 0) noexcept;
+    basic_format_parse_context(const basic_format_parse_context&) = delete;
+    basic_format_parse_context& operator=(const basic_format_parse_context&) = delete;
+
+    constexpr const_iterator begin() const noexcept;
+    constexpr const_iterator end() const noexcept;
+    constexpr void advance_to(const_iterator it);
+
+    constexpr size_t next_arg_id();
+    constexpr void check_arg_id(size_t id);
+  };
+  using format_parse_context = basic_format_parse_context<char>;
+  using wformat_parse_context = basic_format_parse_context<wchar_t>;
 }
 
 */
 
 #include <__config>
 #include <stdexcept>
+#include <string_view>
 #include <version>
 
+#ifdef _LIBCPP_NO_EXCEPTIONS
+#include <cstdlib>
+#endif
+
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
 #endif
@@ -38,6 +75,12 @@ _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.
+#ifndef _LIBCPP_HAS_NO_CONCEPTS
+
 class _LIBCPP_EXCEPTION_ABI format_error : public runtime_error {
 public:
   _LIBCPP_INLINE_VISIBILITY explicit format_error(const string& __s)
@@ -47,6 +90,89 @@ public:
   virtual ~format_error() noexcept;
 };
 
+_LIBCPP_NORETURN inline _LIBCPP_INLINE_VISIBILITY void
+__throw_format_error(const char* __s) {
+#ifndef _LIBCPP_NO_EXCEPTIONS
+  throw format_error(__s);
+#else
+  (void)__s;
+  _VSTD::abort();
+#endif
+}
+
+template <class _CharT>
+class _LIBCPP_TEMPLATE_VIS basic_format_parse_context {
+public:
+  using char_type = _CharT;
+  using const_iterator =
+      typename _VSTD::basic_string_view<_CharT>::const_iterator;
+  using iterator = const_iterator;
+
+public:
+  _LIBCPP_INLINE_VISIBILITY
+  constexpr explicit basic_format_parse_context(
+      _VSTD::basic_string_view<_CharT> __fmt, size_t __num_args = 0) noexcept
+      : __begin_(__fmt.begin()),
+        __end_(__fmt.end()),
+        __indexing_(__unknown),
+        __next_arg_id_(0),
+        __num_args_(__num_args) {}
+
+  basic_format_parse_context(const basic_format_parse_context&) = delete;
+  basic_format_parse_context&
+  operator=(const basic_format_parse_context&) = delete;
+
+  _LIBCPP_INLINE_VISIBILITY constexpr const_iterator begin() const noexcept {
+    return __begin_;
+  }
+  _LIBCPP_INLINE_VISIBILITY constexpr const_iterator end() const noexcept {
+    return __end_;
+  }
+  _LIBCPP_INLINE_VISIBILITY constexpr void advance_to(const_iterator __it) {
+    __begin_ = __it;
+  }
+
+  _LIBCPP_INLINE_VISIBILITY constexpr size_t next_arg_id() {
+    if (__indexing_ == __manual)
+      __throw_format_error("Using automatic argument numbering in manual "
+                           "argument numbering mode");
+
+    if (__indexing_ == __unknown)
+      __indexing_ = __automatic;
+    return __next_arg_id_++;
+  }
+  _LIBCPP_INLINE_VISIBILITY constexpr void check_arg_id(size_t __id) {
+    if (__indexing_ == __automatic)
+      __throw_format_error("Using manual argument numbering in automatic "
+                           "argument numbering mode");
+
+    if (__indexing_ == __unknown)
+      __indexing_ = __manual;
+
+    // Throws an exception to make the expression a non core constant
+    // expression as required by:
+    // [format.parse.ctx]/11
+    //   Remarks: Call expressions where id >= num_args_ are not core constant
+    //   expressions ([expr.const]).
+    // Note: the Throws clause [format.parse.ctx]/10 doesn't specify the
+    // behavior when id >= num_args_.
+    if (_VSTD::is_constant_evaluated() && __id >= __num_args_)
+      __throw_format_error("Argument index outside the valid range");
+  }
+
+private:
+  iterator __begin_;
+  iterator __end_;
+  enum _Indexing { __unknown, __manual, __automatic };
+  _Indexing __indexing_;
+  size_t __next_arg_id_;
+  size_t __num_args_;
+};
+
+using format_parse_context = basic_format_parse_context<char>;
+using wformat_parse_context = basic_format_parse_context<wchar_t>;
+
+#endif //_LIBCPP_HAS_NO_CONCEPTS
 #endif //_LIBCPP_STD_VER > 17
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/advance_to.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/advance_to.pass.cpp
new file mode 100644 (file)
index 0000000..c91d1b8
--- /dev/null
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+// 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
+
+// <format>
+
+// constexpr void advance_to(const_iterator it);
+
+#include <format>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+constexpr void test(const CharT* fmt) {
+  {
+    std::basic_format_parse_context<CharT> context(fmt);
+
+    context.advance_to(context.begin() + 1);
+    assert(context.begin() == &fmt[1]);
+
+    context.advance_to(context.begin() + 1);
+    assert(context.begin() == &fmt[2]);
+
+    context.advance_to(context.begin() + 1);
+    assert(context.begin() == context.end());
+  }
+  {
+    std::basic_string_view view{fmt};
+    std::basic_format_parse_context context(view);
+
+    context.advance_to(context.begin() + 1);
+    assert(context.begin() == view.begin() + 1);
+
+    context.advance_to(context.begin() + 1);
+    assert(context.begin() == view.begin() + 2);
+
+    context.advance_to(context.begin() + 1);
+    assert(context.begin() == context.end());
+  }
+}
+
+constexpr bool test() {
+  test("abc");
+  test(L"abc");
+#ifndef _LIBCPP_NO_HAS_CHAR8_T
+  test(u8"abc");
+#endif
+#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS
+  test(u"abc");
+  test(U"abc");
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/begin.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/begin.pass.cpp
new file mode 100644 (file)
index 0000000..b08129b
--- /dev/null
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+// 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
+
+// <format>
+
+// constexpr begin() const noexcept;
+
+#include <format>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+constexpr void test(const CharT* fmt) {
+  {
+    std::basic_format_parse_context<CharT> context(fmt);
+    assert(context.begin() == &fmt[0]);
+    ASSERT_NOEXCEPT(context.begin());
+  }
+  {
+    std::basic_string_view view{fmt};
+    std::basic_format_parse_context context(view);
+    assert(context.begin() == view.begin());
+    ASSERT_NOEXCEPT(context.begin());
+  }
+}
+
+constexpr bool test() {
+  test("abc");
+  test(L"abc");
+#ifndef _LIBCPP_NO_HAS_CHAR8_T
+  test(u8"abc");
+#endif
+#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS
+  test(u"abc");
+  test(U"abc");
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/check_arg_id.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/check_arg_id.pass.cpp
new file mode 100644 (file)
index 0000000..734dc41
--- /dev/null
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+// 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: no-exceptions
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: with_system_cxx_lib=macosx10.15
+// XFAIL: with_system_cxx_lib=macosx10.14
+// XFAIL: with_system_cxx_lib=macosx10.13
+// XFAIL: with_system_cxx_lib=macosx10.12
+// XFAIL: with_system_cxx_lib=macosx10.11
+// XFAIL: with_system_cxx_lib=macosx10.10
+// XFAIL: with_system_cxx_lib=macosx10.9
+
+// <format>
+
+// constexpr void check_arg_id(size_t id);
+
+#include <format>
+#include <cassert>
+
+#include "test_macros.h"
+
+constexpr bool test() {
+  std::format_parse_context context("", 10);
+  for (size_t i = 0; i < 10; ++i)
+    context.check_arg_id(i);
+
+  return true;
+}
+
+void test_exception() {
+  [] {
+    std::format_parse_context context("", 1);
+    context.next_arg_id();
+    try {
+      context.check_arg_id(0);
+      assert(false);
+    } catch (const std::format_error& e) {
+      assert(strcmp(e.what(), "Using manual argument numbering in automatic "
+                              "argument numbering mode") == 0);
+      return;
+    }
+    assert(false);
+  }();
+
+  auto test_arg = [](size_t num_args) {
+    std::format_parse_context context("", num_args);
+    // Out of bounds access is valid if !std::is_constant_evaluated()
+    for (size_t i = 0; i <= num_args; ++i)
+      context.check_arg_id(i);
+  };
+  for (size_t i = 0; i < 10; ++i)
+    test_arg(i);
+}
+
+int main(int, char**) {
+  test();
+  test_exception();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/ctor.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/ctor.pass.cpp
new file mode 100644 (file)
index 0000000..5a4a046
--- /dev/null
@@ -0,0 +1,77 @@
+//===----------------------------------------------------------------------===//
+// 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
+
+// <format>
+
+// constexpr explicit
+// basic_format_parse_context(basic_string_view<charT> fmt,
+//                            size_t num_args = 0) noexcept
+
+#include <format>
+#include <cassert>
+#include <type_traits>
+
+#include "test_macros.h"
+
+template <class CharT>
+constexpr void test(const CharT* fmt) {
+  // Validate the constructor is explicit.
+  static_assert(
+      !std::is_convertible_v<std::basic_string_view<CharT>,
+                             std::basic_format_parse_context<CharT> >);
+  static_assert(
+      !std::is_copy_constructible_v<std::basic_format_parse_context<CharT> >);
+  static_assert(
+      !std::is_copy_assignable_v<std::basic_format_parse_context<CharT> >);
+  // The move operations are implicitly deleted due to the
+  // deleted copy operations.
+  static_assert(
+      !std::is_move_constructible_v<std::basic_format_parse_context<CharT> >);
+  static_assert(
+      !std::is_move_assignable_v<std::basic_format_parse_context<CharT> >);
+
+  ASSERT_NOEXCEPT(
+      std::basic_format_parse_context{std::basic_string_view<CharT>{}});
+  ASSERT_NOEXCEPT(
+      std::basic_format_parse_context{std::basic_string_view<CharT>{}, 42});
+
+  {
+    std::basic_format_parse_context<CharT> context(fmt);
+    assert(context.begin() == &fmt[0]);
+    assert(context.end() == &fmt[3]);
+  }
+  {
+    std::basic_string_view view{fmt};
+    std::basic_format_parse_context context(view);
+    assert(context.begin() == view.begin());
+    assert(context.end() == view.end());
+  }
+}
+
+constexpr bool test() {
+  test("abc");
+  test(L"abc");
+#ifndef _LIBCPP_NO_HAS_CHAR8_T
+  test(u8"abc");
+#endif
+#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS
+  test(u"abc");
+  test(U"abc");
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/end.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/end.pass.cpp
new file mode 100644 (file)
index 0000000..323939a
--- /dev/null
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+// 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
+
+// <format>
+
+// constexpr end() const noexcept;
+
+#include <format>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <class CharT>
+constexpr void test(const CharT* fmt) {
+  {
+    std::basic_format_parse_context<CharT> context(fmt);
+    assert(context.end() == &fmt[3]);
+    ASSERT_NOEXCEPT(context.end());
+  }
+  {
+    std::basic_string_view view{fmt};
+    std::basic_format_parse_context context(view);
+    assert(context.end() == view.end());
+    ASSERT_NOEXCEPT(context.end());
+  }
+}
+
+constexpr bool test() {
+  test("abc");
+  test(L"abc");
+#ifndef _LIBCPP_NO_HAS_CHAR8_T
+  test(u8"abc");
+#endif
+#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS
+  test(u"abc");
+  test(U"abc");
+#endif
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/next_arg_id.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/next_arg_id.pass.cpp
new file mode 100644 (file)
index 0000000..77197ad
--- /dev/null
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+// 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: no-exceptions
+
+// This test requires the dylib support introduced in D92214.
+// XFAIL: with_system_cxx_lib=macosx10.15
+// XFAIL: with_system_cxx_lib=macosx10.14
+// XFAIL: with_system_cxx_lib=macosx10.13
+// XFAIL: with_system_cxx_lib=macosx10.12
+// XFAIL: with_system_cxx_lib=macosx10.11
+// XFAIL: with_system_cxx_lib=macosx10.10
+// XFAIL: with_system_cxx_lib=macosx10.9
+
+// <format>
+
+// constexpr size_t next_arg_id();
+
+#include <format>
+#include <cassert>
+
+#include "test_macros.h"
+
+constexpr bool test() {
+  std::format_parse_context context("");
+  for (size_t i = 0; i < 10; ++i)
+    assert(i == context.next_arg_id());
+
+  return true;
+}
+
+void test_exception() {
+  std::format_parse_context context("", 1);
+  context.check_arg_id(0);
+
+  try {
+    context.next_arg_id();
+    assert(false);
+  } catch (const std::format_error& e) {
+    assert(strcmp(e.what(), "Using automatic argument numbering in manual "
+                            "argument numbering mode") == 0);
+    return;
+  }
+  assert(false);
+}
+
+int main(int, char**) {
+  test();
+  test_exception();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/types.compile.pass.cpp b/libcxx/test/std/utilities/format/format.formatter/format.parse.ctx/types.compile.pass.cpp
new file mode 100644 (file)
index 0000000..c9301c5
--- /dev/null
@@ -0,0 +1,63 @@
+//===----------------------------------------------------------------------===//
+// 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
+
+// <format>
+
+// Class typedefs:
+// template<class charT>
+// class basic_format_parse_context {
+// public:
+//   using char_type = charT;
+//   using const_iterator = typename basic_string_view<charT>::const_iterator;
+//   using iterator = const_iterator;
+// }
+//
+// Namespace std typedefs:
+// using format_parse_context = basic_format_parse_context<char>;
+// using wformat_parse_context = basic_format_parse_context<wchar_t>;
+
+#include <format>
+#include <type_traits>
+
+#include "test_macros.h"
+
+template <class CharT>
+constexpr void test() {
+  static_assert(
+      std::is_same_v<typename std::basic_format_parse_context<CharT>::char_type,
+                     CharT>);
+  static_assert(std::is_same_v<
+                typename std::basic_format_parse_context<CharT>::const_iterator,
+                typename std::basic_string_view<CharT>::const_iterator>);
+  static_assert(
+      std::is_same_v<
+          typename std::basic_format_parse_context<CharT>::iterator,
+          typename std::basic_format_parse_context<CharT>::const_iterator>);
+}
+
+constexpr void test() {
+  test<char>();
+  test<wchar_t>();
+#ifndef _LIBCPP_NO_HAS_CHAR8_T
+  test<char8_t>();
+#endif
+#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS
+  test<char16_t>();
+  test<char32_t>();
+#endif
+}
+
+static_assert(std::is_same_v<std::format_parse_context,
+                             std::basic_format_parse_context<char> >);
+static_assert(std::is_same_v<std::wformat_parse_context,
+                             std::basic_format_parse_context<wchar_t> >);
+
+// Required for MSVC internal test runner compatibility.
+int main(int, char**) { return 0; }