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 #include #include - #include #include @@ -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 &GetChildren(); + std::string string; std::string printf_format; - std::vector 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, 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 #include #include #include @@ -281,31 +282,53 @@ constexpr Definition g_top_level_entries[] = { constexpr Definition g_root = Entry::DefinitionWithChildren( "", 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 &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 &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..2cddf8ff4e900 100644 --- a/lldb/unittests/Core/FormatEntityTest.cpp +++ b/lldb/unittests/Core/FormatEntityTest.cpp @@ -8,15 +8,30 @@ #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; +static Expected 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(); +} + TEST(FormatEntityTest, DefinitionConstructionNameAndType) { Definition d("foo", FormatEntity::Entry::Type::Invalid); @@ -153,10 +168,37 @@ constexpr llvm::StringRef lookupStrings[] = { "${target.file.fullpath}", "${var.dummy-var-to-test-wildcard}"}; -TEST(FormatEntity, LookupAllEntriesInTree) { +TEST(FormatEntityTest, LookupAllEntriesInTree) { for (const llvm::StringRef testString : lookupStrings) { Entry e; EXPECT_TRUE(FormatEntity::Parse(testString, e).Success()) << "Formatting " << testString << " did not succeed"; } } + +TEST(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("")); +}