https://github.com/JDevlieghere updated 
https://github.com/llvm/llvm-project/pull/137751

>From b6618a2ecfe20880e323177f9683f51135b83fd6 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jo...@devlieghere.com>
Date: Mon, 28 Apr 2025 20:51:34 -0700
Subject: [PATCH] [lldb] Support alternatives for scope format entries

This PR implements support for specifying multiple alternatives for
scope format entries. Scopes are used to enclose things that should only
be printed when everything in the scope resolves.

For example, the following scope only resolves if both
`${line.file.basename}` and `${line.number}` resolve.
`

```
{ at ${line.file.basename}:${line.number}}
```

However, the current implementation doesn't let you specify what to
print when they don't resolve. This PR adds support for specifying
multiple alternative scopes, which are evaluated left-to-right.

For example:

```
{ at ${line.file.basename}:${line.number}| in ${function.name}| <unknown 
location>}
```

This will resolve to:

 - ` at ${line.file.basename}:${line.number}` if the corresponding variables 
resolve.
 - Otherwise, this resolves to ` in ${function.name}` if `${function.name}` 
resolves.
 - Otherwise, this resolves to ` <unknown location>` which always resolves.

This PR makes the `|` character a special character within a scope, but
allows it to be escaped.

I ended up with this approach because it fit quite nicely in the
existing architecture of the format entries and by limiting the
functionality to scopes, it sidesteps some complexity, like dealing with
recursion.
---
 lldb/include/lldb/Core/FormatEntity.h    | 27 +++++---
 lldb/source/Core/FormatEntity.cpp        | 87 +++++++++++++++++-------
 lldb/unittests/Core/FormatEntityTest.cpp | 59 ++++++++++++++--
 3 files changed, 135 insertions(+), 38 deletions(-)

diff --git a/lldb/include/lldb/Core/FormatEntity.h 
b/lldb/include/lldb/Core/FormatEntity.h
index 97065afe19bfe..6acf6fbe43239 100644
--- a/lldb/include/lldb/Core/FormatEntity.h
+++ b/lldb/include/lldb/Core/FormatEntity.h
@@ -11,10 +11,10 @@
 
 #include "lldb/lldb-enumerations.h"
 #include "lldb/lldb-types.h"
+#include "llvm/ADT/SmallVector.h"
 #include <algorithm>
 #include <cstddef>
 #include <cstdint>
-
 #include <string>
 #include <vector>
 
@@ -158,9 +158,7 @@ struct Entry {
   }
 
   Entry(Type t = Type::Invalid, const char *s = nullptr,
-        const char *f = nullptr)
-      : string(s ? s : ""), printf_format(f ? f : ""), type(t) {}
-
+        const char *f = nullptr);
   Entry(llvm::StringRef s);
   Entry(char ch);
 
@@ -170,15 +168,19 @@ struct Entry {
 
   void AppendText(const char *cstr);
 
-  void AppendEntry(const Entry &&entry) { children.push_back(entry); }
+  void AppendEntry(const Entry &&entry);
+
+  void StartAlternative();
 
   void Clear() {
     string.clear();
     printf_format.clear();
-    children.clear();
+    children_stack.clear();
+    children_stack.emplace_back();
     type = Type::Invalid;
     fmt = lldb::eFormatDefault;
     number = 0;
+    level = 0;
     deref = false;
   }
 
@@ -191,7 +193,7 @@ struct Entry {
       return false;
     if (printf_format != rhs.printf_format)
       return false;
-    if (children != rhs.children)
+    if (children_stack != rhs.children_stack)
       return false;
     if (type != rhs.type)
       return false;
@@ -202,9 +204,18 @@ struct Entry {
     return true;
   }
 
+  std::vector<Entry> &GetChildren();
+
   std::string string;
   std::string printf_format;
-  std::vector<Entry> children;
+
+  /// A stack of children entries, used by Scope entries to provide alterantive
+  /// children. All other entries have a stack of size 1.
+  /// @{
+  llvm::SmallVector<std::vector<Entry>, 1> children_stack;
+  size_t level = 0;
+  /// @}
+
   Type type;
   lldb::Format fmt = lldb::eFormatDefault;
   lldb::addr_t number = 0;
diff --git a/lldb/source/Core/FormatEntity.cpp 
b/lldb/source/Core/FormatEntity.cpp
index 40e247a64b78c..031f0e57f132e 100644
--- a/lldb/source/Core/FormatEntity.cpp
+++ b/lldb/source/Core/FormatEntity.cpp
@@ -60,6 +60,7 @@
 #include "llvm/Support/Regex.h"
 #include "llvm/TargetParser/Triple.h"
 
+#include <cassert>
 #include <cctype>
 #include <cinttypes>
 #include <cstdio>
@@ -281,31 +282,53 @@ constexpr Definition g_top_level_entries[] = {
 constexpr Definition g_root = Entry::DefinitionWithChildren(
     "<root>", EntryType::Root, g_top_level_entries);
 
+FormatEntity::Entry::Entry(Type t, const char *s, const char *f)
+    : string(s ? s : ""), printf_format(f ? f : ""), children_stack({{}}),
+      type(t) {}
+
 FormatEntity::Entry::Entry(llvm::StringRef s)
-    : string(s.data(), s.size()), printf_format(), children(),
-      type(Type::String) {}
+    : string(s.data(), s.size()), children_stack({{}}), type(Type::String) {}
 
 FormatEntity::Entry::Entry(char ch)
-    : string(1, ch), printf_format(), children(), type(Type::String) {}
+    : string(1, ch), printf_format(), children_stack({{}}), type(Type::String) 
{
+}
+
+std::vector<Entry> &FormatEntity::Entry::GetChildren() {
+  assert(level < children_stack.size());
+  return children_stack[level];
+}
 
 void FormatEntity::Entry::AppendChar(char ch) {
-  if (children.empty() || children.back().type != Entry::Type::String)
-    children.push_back(Entry(ch));
+  auto &entries = GetChildren();
+  if (entries.empty() || entries.back().type != Entry::Type::String)
+    entries.push_back(Entry(ch));
   else
-    children.back().string.append(1, ch);
+    entries.back().string.append(1, ch);
 }
 
 void FormatEntity::Entry::AppendText(const llvm::StringRef &s) {
-  if (children.empty() || children.back().type != Entry::Type::String)
-    children.push_back(Entry(s));
+  auto &entries = GetChildren();
+  if (entries.empty() || entries.back().type != Entry::Type::String)
+    entries.push_back(Entry(s));
   else
-    children.back().string.append(s.data(), s.size());
+    entries.back().string.append(s.data(), s.size());
 }
 
 void FormatEntity::Entry::AppendText(const char *cstr) {
   return AppendText(llvm::StringRef(cstr));
 }
 
+void FormatEntity::Entry::AppendEntry(const Entry &&entry) {
+  auto &entries = GetChildren();
+  entries.push_back(entry);
+}
+
+void FormatEntity::Entry::StartAlternative() {
+  assert(type == Entry::Type::Scope);
+  children_stack.emplace_back();
+  level++;
+}
+
 #define ENUM_TO_CSTR(eee)                                                      
\
   case FormatEntity::Entry::Type::eee:                                         
\
     return #eee
@@ -405,8 +428,9 @@ void FormatEntity::Entry::Dump(Stream &s, int depth) const {
   if (deref)
     s.Printf("deref = true, ");
   s.EOL();
-  for (const auto &child : children) {
-    child.Dump(s, depth + 1);
+  for (const auto &children : children_stack) {
+    for (const auto &child : children)
+      child.Dump(s, depth + 1);
   }
 }
 
@@ -1308,7 +1332,7 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
     return true;
 
   case Entry::Type::Root:
-    for (const auto &child : entry.children) {
+    for (const auto &child : entry.children_stack[0]) {
       if (!Format(child, s, sc, exe_ctx, addr, valobj, function_changed,
                   initial_function)) {
         return false; // If any item of root fails, then the formatting fails
@@ -1322,19 +1346,26 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
 
   case Entry::Type::Scope: {
     StreamString scope_stream;
-    bool success = false;
-    for (const auto &child : entry.children) {
-      success = Format(child, scope_stream, sc, exe_ctx, addr, valobj,
-                       function_changed, initial_function);
-      if (!success)
-        break;
+    auto format_children = [&](const std::vector<Entry> &children) {
+      scope_stream.Clear();
+      for (const auto &child : children) {
+        if (!Format(child, scope_stream, sc, exe_ctx, addr, valobj,
+                    function_changed, initial_function))
+          return false;
+      }
+      return true;
+    };
+
+    for (auto &children : entry.children_stack) {
+      if (format_children(children)) {
+        s.Write(scope_stream.GetString().data(),
+                scope_stream.GetString().size());
+        return true;
+      }
     }
-    // Only if all items in a scope succeed, then do we print the output into
-    // the main stream
-    if (success)
-      s.Write(scope_stream.GetString().data(), 
scope_stream.GetString().size());
-  }
+
     return true; // Scopes always successfully print themselves
+  }
 
   case Entry::Type::Variable:
   case Entry::Type::VariableSynthetic:
@@ -2124,7 +2155,7 @@ static Status ParseInternal(llvm::StringRef &format, 
Entry &parent_entry,
                             uint32_t depth) {
   Status error;
   while (!format.empty() && error.Success()) {
-    const size_t non_special_chars = format.find_first_of("${}\\");
+    const size_t non_special_chars = format.find_first_of("${}\\|");
 
     if (non_special_chars == llvm::StringRef::npos) {
       // No special characters, just string bytes so add them and we are done
@@ -2161,6 +2192,14 @@ static Status ParseInternal(llvm::StringRef &format, 
Entry &parent_entry,
                 .drop_front(); // Skip the '}' as we are at the end of the 
scope
       return error;
 
+    case '|':
+      format = format.drop_front(); // Skip the '|'
+      if (parent_entry.type == Entry::Type::Scope)
+        parent_entry.StartAlternative();
+      else
+        parent_entry.AppendChar('|');
+      break;
+
     case '\\': {
       format = format.drop_front(); // Skip the '\' character
       if (format.empty()) {
diff --git a/lldb/unittests/Core/FormatEntityTest.cpp 
b/lldb/unittests/Core/FormatEntityTest.cpp
index 5983c9de99ef7..7e1ff6fb1fa87 100644
--- a/lldb/unittests/Core/FormatEntityTest.cpp
+++ b/lldb/unittests/Core/FormatEntityTest.cpp
@@ -8,16 +8,36 @@
 
 #include "lldb/Core/FormatEntity.h"
 #include "lldb/Utility/Status.h"
-
+#include "lldb/Utility/StreamString.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Testing/Support/Error.h"
 #include "gtest/gtest.h"
 
 using namespace lldb_private;
+using namespace llvm;
 
 using Definition = FormatEntity::Entry::Definition;
 using Entry = FormatEntity::Entry;
 
-TEST(FormatEntityTest, DefinitionConstructionNameAndType) {
+namespace {
+class FormatEntityTest : public ::testing::Test {
+public:
+  Expected<std::string> Format(StringRef format_str) {
+    StreamString stream;
+    FormatEntity::Entry format;
+    Status status = FormatEntity::Parse(format_str, format);
+    if (status.Fail())
+      return status.ToError();
+
+    FormatEntity::Format(format, stream, nullptr, nullptr, nullptr, nullptr,
+                         false, false);
+    return stream.GetString().str();
+  }
+};
+} // namespace
+
+TEST_F(FormatEntityTest, DefinitionConstructionNameAndType) {
   Definition d("foo", FormatEntity::Entry::Type::Invalid);
 
   EXPECT_STREQ(d.name, "foo");
@@ -29,7 +49,7 @@ TEST(FormatEntityTest, DefinitionConstructionNameAndType) {
   EXPECT_FALSE(d.keep_separator);
 }
 
-TEST(FormatEntityTest, DefinitionConstructionNameAndString) {
+TEST_F(FormatEntityTest, DefinitionConstructionNameAndString) {
   Definition d("foo", "string");
 
   EXPECT_STREQ(d.name, "foo");
@@ -41,7 +61,7 @@ TEST(FormatEntityTest, DefinitionConstructionNameAndString) {
   EXPECT_FALSE(d.keep_separator);
 }
 
-TEST(FormatEntityTest, DefinitionConstructionNameTypeData) {
+TEST_F(FormatEntityTest, DefinitionConstructionNameTypeData) {
   Definition d("foo", FormatEntity::Entry::Type::Invalid, 33);
 
   EXPECT_STREQ(d.name, "foo");
@@ -53,7 +73,7 @@ TEST(FormatEntityTest, DefinitionConstructionNameTypeData) {
   EXPECT_FALSE(d.keep_separator);
 }
 
-TEST(FormatEntityTest, DefinitionConstructionNameTypeChildren) {
+TEST_F(FormatEntityTest, DefinitionConstructionNameTypeChildren) {
   Definition d("foo", FormatEntity::Entry::Type::Invalid, 33);
   Definition parent("parent", FormatEntity::Entry::Type::Invalid, 1, &d);
   EXPECT_STREQ(parent.name, "parent");
@@ -153,10 +173,37 @@ constexpr llvm::StringRef lookupStrings[] = {
     "${target.file.fullpath}",
     "${var.dummy-var-to-test-wildcard}"};
 
-TEST(FormatEntity, LookupAllEntriesInTree) {
+TEST_F(FormatEntityTest, LookupAllEntriesInTree) {
   for (const llvm::StringRef testString : lookupStrings) {
     Entry e;
     EXPECT_TRUE(FormatEntity::Parse(testString, e).Success())
         << "Formatting " << testString << " did not succeed";
   }
 }
+
+TEST_F(FormatEntityTest, Scope) {
+  // Scope with  one alternative.
+  EXPECT_THAT_EXPECTED(Format("{${frame.pc}|foo}"), HasValue("foo"));
+
+  // Scope with multiple alternatives.
+  EXPECT_THAT_EXPECTED(Format("{${frame.pc}|${function.name}|foo}"),
+                       HasValue("foo"));
+
+  // Escaped pipe inside a scope.
+  EXPECT_THAT_EXPECTED(Format("{foo\\|bar}"), HasValue("foo|bar"));
+
+  // Unescaped pipe outside a scope.
+  EXPECT_THAT_EXPECTED(Format("foo|bar"), HasValue("foo|bar"));
+
+  // Nested scopes. Note that scopes always resolve.
+  EXPECT_THAT_EXPECTED(Format("{{${frame.pc}|foo}|{bar}}"), HasValue("foo"));
+  EXPECT_THAT_EXPECTED(Format("{{${frame.pc}}|{bar}}"), HasValue(""));
+
+  // Pipe between scopes.
+  EXPECT_THAT_EXPECTED(Format("{foo}|{bar}"), HasValue("foo|bar"));
+  EXPECT_THAT_EXPECTED(Format("{foo}||{bar}"), HasValue("foo||bar"));
+
+  // Empty space between pipes.
+  EXPECT_THAT_EXPECTED(Format("{{foo}||{bar}}"), HasValue("foo"));
+  EXPECT_THAT_EXPECTED(Format("{${frame.pc}||{bar}}"), HasValue(""));
+}

_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to