[llvm] [Support] Add format object for interleaved ranges (PR #135517)

via llvm-commits llvm-commits at lists.llvm.org
Sat Apr 12 20:35:10 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-support

Author: Jakub Kuderski (kuhar)

<details>
<summary>Changes</summary>

Add two new format functions for printing ranges: `interleaved` and `interleaved_array`.

This is meant to improve the ergonomics of printing ranges. Before this patch, we have to either use `llvm::interleave` or write a for loop by hand. For example:

Before:
```c++
ArrayRef<Type> types = ...;
ArrayRef<Values> values = ...;
LLVM_DEBUG({
  llvm::dbgs() << "Types: ";
  llvm::interleave_comma(llvm::dbgs(), types);
  llvm::dbgs() << "\n";
  llvm::dbgs() << "Values: [";
  llvm::interleave_comma(llvm::dbgs(), values);
  llvm::dbgs() << "]\n";
}):
```

After:
```c++
ArrayRef<Type> types = ...;
ArrayRef<Values> values = ...;
LLVM_DEBUG(llvm::dbgs() << "Types: " << interleaved(types) << "\n");
LLVM_DEBUG(llvm::dbgs() << "Values: " << interleaved_array(values) << "\n");
```

The separator and the prefix/suffix strings are customizable.

---
Full diff: https://github.com/llvm/llvm-project/pull/135517.diff


5 Files Affected:

- (modified) llvm/include/llvm/Support/Format.h (+85) 
- (modified) llvm/include/llvm/Support/raw_ostream.h (+5-1) 
- (modified) llvm/lib/Support/raw_ostream.cpp (+10-2) 
- (modified) llvm/unittests/Support/CMakeLists.txt (+2-1) 
- (added) llvm/unittests/Support/InterleavedRangeTest.cpp (+70) 


``````````diff
diff --git a/llvm/include/llvm/Support/Format.h b/llvm/include/llvm/Support/Format.h
index 89b6ae35ba5de..f6c6e449aae6b 100644
--- a/llvm/include/llvm/Support/Format.h
+++ b/llvm/include/llvm/Support/Format.h
@@ -26,6 +26,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/DataTypes.h"
+#include "llvm/Support/raw_ostream.h"
 #include <cassert>
 #include <cstdio>
 #include <optional>
@@ -255,6 +256,90 @@ format_bytes_with_ascii(ArrayRef<uint8_t> Bytes,
                         ByteGroupSize, Upper, true);
 }
 
+/// The base class for printing interleaved ranges. Provides a virtual function
+/// to be used by the '<<' operator in raw_ostream. This class is not intended
+/// to be copied or used in polymorphic containers.
+class InterleavedRangeBase {
+protected:
+  ~InterleavedRangeBase() = default; // Disallow polymorphic deletion.
+  InterleavedRangeBase() = default;
+  InterleavedRangeBase(const InterleavedRangeBase &) = delete; // Disallow copy.
+  virtual void home(); // Out of line virtual method to anchor the vtable.
+
+  /// Appends the interleaved range to the output stream `OS`.
+  virtual void printInterleaved(raw_ostream &OS) const = 0;
+
+  friend class raw_ostream;
+};
+
+/// The concrete class template for interleaved ranges. Supports specifying the
+/// separator and, optionally, the prefix and suffix to be printed surrounding
+/// the range.
+/// Uses the operator '<<' of the range element type for printing. The range
+/// type itself does not have to have an '<<' operator defined.
+template <typename Range>
+class InterleavedRange final : public InterleavedRangeBase {
+  const Range &TheRange;
+  StringRef Separator;
+  StringRef Prefix;
+  StringRef Suffix;
+
+  virtual void printInterleaved(raw_ostream &OS) const override {
+    if (!Prefix.empty())
+      OS << Prefix;
+    llvm::interleave(TheRange, OS, Separator);
+    if (!Suffix.empty())
+      OS << Suffix;
+  }
+
+public:
+  InterleavedRange(const Range &R, StringRef Separator, StringRef Prefix,
+                   StringRef Suffix)
+      : TheRange(R), Separator(Separator), Prefix(Prefix), Suffix(Suffix) {}
+
+  std::string str() const {
+    std::string Result;
+    raw_string_ostream Stream(Result);
+    Stream << *this;
+    Stream.flush();
+    return Result;
+  }
+
+  operator std::string() const { return str(); }
+};
+
+/// interleaved - Output range \p R as a sequence of interleaved elements.
+/// Requires the range element type to be printable using
+/// `raw_ostream& operator<<`. The \p Separator and \p Prefix / \p Suffix can be
+/// customized. Examples:
+/// ```c++
+///   SmallVector<int> Vals = {1, 2, 3};
+///   OS << interleaved(Vals);                 // ==> "1, 2, 3"
+///   OS << interleaved(Vals, ";");            // ==> "1;2;3"
+///   OS << interleaved(Vals, " ", "{", "}");  // ==> "{1 2 3}"
+/// ```
+template <typename Range>
+InterleavedRange<Range> interleaved(const Range &R, StringRef Separator = ", ",
+                                    StringRef Prefix = "",
+                                    StringRef Suffix = "") {
+  return {R, Separator, Prefix, Suffix};
+}
+
+/// interleaved - Output range \p R as an array of interleaved elements.
+/// Requires the range element type to be printable using
+/// `raw_ostream& operator<<`. The \p Separator can be customized. Examples:
+/// ```c++
+///   SmallVector<int> Vals = {1, 2, 3};
+///   OS << interleaved_array(Vals);       // ==> "[1, 2, 3]"
+///   OS << interleaved_array(Vals, ";");  // ==> "[1;2;3]"
+///   OS << interleaved_array(Vals, " ");  // ==> "[1 2 3]"
+/// ```
+template <typename Range>
+InterleavedRange<Range> interleaved_array(const Range &R,
+                                          StringRef Separator = ", ") {
+  return {R, Separator, "[", "]"};
+}
+
 } // end namespace llvm
 
 #endif
diff --git a/llvm/include/llvm/Support/raw_ostream.h b/llvm/include/llvm/Support/raw_ostream.h
index d3b411590e7fd..f6b2268a94927 100644
--- a/llvm/include/llvm/Support/raw_ostream.h
+++ b/llvm/include/llvm/Support/raw_ostream.h
@@ -29,12 +29,13 @@
 namespace llvm {
 
 class Duration;
+template <class T> class [[nodiscard]] Expected;
 class formatv_object_base;
 class format_object_base;
 class FormattedString;
 class FormattedNumber;
 class FormattedBytes;
-template <class T> class [[nodiscard]] Expected;
+class InterleavedRangeBase;
 
 namespace sys {
 namespace fs {
@@ -318,6 +319,9 @@ class raw_ostream {
   // Formatted output, see the format_bytes() function in Support/Format.h.
   raw_ostream &operator<<(const FormattedBytes &);
 
+  // Formatted output, see the interleaved() function in Support/Format.h.
+  raw_ostream &operator<<(const InterleavedRangeBase &);
+
   /// indent - Insert 'NumSpaces' spaces.
   raw_ostream &indent(unsigned NumSpaces);
 
diff --git a/llvm/lib/Support/raw_ostream.cpp b/llvm/lib/Support/raw_ostream.cpp
index e75ddc66b7d16..58d5568cdcea1 100644
--- a/llvm/lib/Support/raw_ostream.cpp
+++ b/llvm/lib/Support/raw_ostream.cpp
@@ -471,6 +471,12 @@ raw_ostream &raw_ostream::operator<<(const FormattedBytes &FB) {
   return *this;
 }
 
+raw_ostream &
+raw_ostream::operator<<(const InterleavedRangeBase &InterleavedRange) {
+  InterleavedRange.printInterleaved(*this);
+  return *this;
+}
+
 template <char C>
 static raw_ostream &write_padding(raw_ostream &OS, unsigned NumChars) {
   static const char Chars[] = {C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C,
@@ -555,8 +561,10 @@ void raw_ostream::anchor() {}
 //===----------------------------------------------------------------------===//
 
 // Out of line virtual method.
-void format_object_base::home() {
-}
+void format_object_base::home() {}
+
+// Out of line virtual method.
+void InterleavedRangeBase::home() {}
 
 //===----------------------------------------------------------------------===//
 //  raw_fd_ostream
diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt
index 6c4e7cb689b20..4a12a928af119 100644
--- a/llvm/unittests/Support/CMakeLists.txt
+++ b/llvm/unittests/Support/CMakeLists.txt
@@ -49,6 +49,7 @@ add_llvm_unittest(SupportTests
   HashBuilderTest.cpp
   IndexedAccessorTest.cpp
   InstructionCostTest.cpp
+  InterleavedRangeTest.cpp
   JSONTest.cpp
   KnownBitsTest.cpp
   LEB128Test.cpp
@@ -61,7 +62,7 @@ add_llvm_unittest(SupportTests
   MemoryBufferRefTest.cpp
   MemoryBufferTest.cpp
   MemoryTest.cpp
-  MustacheTest.cpp      
+  MustacheTest.cpp
   ModRefTest.cpp
   NativeFormatTests.cpp
   OptimizedStructLayoutTest.cpp
diff --git a/llvm/unittests/Support/InterleavedRangeTest.cpp b/llvm/unittests/Support/InterleavedRangeTest.cpp
new file mode 100644
index 0000000000000..53acf36040b67
--- /dev/null
+++ b/llvm/unittests/Support/InterleavedRangeTest.cpp
@@ -0,0 +1,70 @@
+//===- InterleavedRangeTest.cpp - Unit tests for interleaved format -------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/Format.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/raw_ostream.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+TEST(InterleavedRangeTest, VectorInt) {
+  SmallVector<int> V = {0, 1, 2, 3};
+
+  // First, make sure that the raw print API works as expected.
+  std::string Buff;
+  raw_string_ostream OS(Buff);
+  OS << interleaved(V);
+  EXPECT_EQ("0, 1, 2, 3", Buff);
+  Buff.clear();
+  OS << interleaved_array(V);
+  EXPECT_EQ("[0, 1, 2, 3]", Buff);
+
+  // In the rest of the tests, use `.str()` for convenience.
+  EXPECT_EQ("0, 1, 2, 3", interleaved(V).str());
+  EXPECT_EQ("{{0,1,2,3}}", interleaved(V, ",", "{{", "}}").str());
+  EXPECT_EQ("[0, 1, 2, 3]", interleaved_array(V).str());
+  EXPECT_EQ("[0;1;2;3]", interleaved_array(V, ";").str());
+  EXPECT_EQ("0;1;2;3", interleaved(V, ";").str());
+}
+
+TEST(InterleavedRangeTest, VectorIntEmpty) {
+  SmallVector<int> V = {};
+  EXPECT_EQ("", interleaved(V).str());
+  EXPECT_EQ("{{}}", interleaved(V, ",", "{{", "}}").str());
+  EXPECT_EQ("[]", interleaved_array(V).str());
+  EXPECT_EQ("", interleaved(V, ";").str());
+}
+
+TEST(InterleavedRangeTest, VectorIntOneElem) {
+  SmallVector<int> V = {42};
+  EXPECT_EQ("42", interleaved(V).str());
+  EXPECT_EQ("{{42}}", interleaved(V, ",", "{{", "}}").str());
+  EXPECT_EQ("[42]", interleaved_array(V).str());
+  EXPECT_EQ("42", interleaved(V, ";").str());
+}
+
+struct CustomPrint {
+  int N;
+  friend raw_ostream &operator<<(raw_ostream &OS, const CustomPrint &CP) {
+    OS << "$$" << CP.N << "##";
+    return OS;
+  }
+};
+
+TEST(InterleavedRangeTest, CustomPrint) {
+  CustomPrint V[] = {{3}, {4}, {5}};
+  EXPECT_EQ("$$3##, $$4##, $$5##", interleaved(V).str());
+  EXPECT_EQ("{{$$3##;$$4##;$$5##}}", interleaved(V, ";", "{{", "}}").str());
+  EXPECT_EQ("[$$3##, $$4##, $$5##]", interleaved_array(V).str());
+}
+
+} // namespace

``````````

</details>


https://github.com/llvm/llvm-project/pull/135517


More information about the llvm-commits mailing list