Index: lldb/source/Commands/CommandObjectRegexCommand.h =================================================================== --- lldb/source/Commands/CommandObjectRegexCommand.h +++ lldb/source/Commands/CommandObjectRegexCommand.h @@ -39,6 +39,14 @@ protected: bool DoExecute(llvm::StringRef command, CommandReturnObject &result) override; + /// Substitute variables of the format %1 in the input string. + /// + /// The replacements vector is expected to be the result of a regex match, so + /// %1 will be matched with replacements[1] and not replacements[0]. + static void SubstituteVariables( + std::string &str, + const llvm::SmallVectorImpl &replacements); + struct Entry { RegularExpression regex; std::string command; Index: lldb/source/Commands/CommandObjectRegexCommand.cpp =================================================================== --- lldb/source/Commands/CommandObjectRegexCommand.cpp +++ lldb/source/Commands/CommandObjectRegexCommand.cpp @@ -9,6 +9,7 @@ #include "CommandObjectRegexCommand.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" +#include using namespace lldb; using namespace lldb_private; @@ -25,28 +26,42 @@ // Destructor CommandObjectRegexCommand::~CommandObjectRegexCommand() = default; +void CommandObjectRegexCommand::SubstituteVariables( + std::string &str, + const llvm::SmallVectorImpl &replacements) { + const char pattern = '%'; + + // Walk the string left to right by jumping between positions with a '%'. + size_t pos = str.find(pattern); + while (pos != std::string::npos) { + // Parse the number following '%'. + const size_t idx = std::atoi(str.c_str() + pos + 1); + + // If the '%' is not followed by a number, or the number is greater than + // the number of replacements, look for the next '%'. + if (idx == 0 || idx >= replacements.size()) { + pos = str.find(pattern, pos + 1); + continue; + } + + // Replace the % and subsequent number by the corresponding replacement + // before looking for the next '%'. + const std::string replacement(replacements[idx]); + const size_t pattern_size = 1 + static_cast(log10(idx) + 1); + str.replace(pos, pattern_size, replacement); + pos = str.find(pattern, pos + replacement.size()); + } +} + bool CommandObjectRegexCommand::DoExecute(llvm::StringRef command, CommandReturnObject &result) { EntryCollection::const_iterator pos, end = m_entries.end(); for (pos = m_entries.begin(); pos != end; ++pos) { llvm::SmallVector matches; if (pos->regex.Execute(command, &matches)) { - std::string new_command(pos->command); - char percent_var[8]; - size_t idx, percent_var_idx; - for (uint32_t match_idx = 1; match_idx <= m_max_matches; ++match_idx) { - if (match_idx < matches.size()) { - const std::string match_str = matches[match_idx].str(); - const int percent_var_len = - ::snprintf(percent_var, sizeof(percent_var), "%%%u", match_idx); - for (idx = 0; (percent_var_idx = new_command.find( - percent_var, idx)) != std::string::npos;) { - new_command.erase(percent_var_idx, percent_var_len); - new_command.insert(percent_var_idx, match_str); - idx = percent_var_idx + match_str.size(); - } - } - } + std::string new_command = pos->command; + SubstituteVariables(new_command, matches); + // Interpret the new command and return this as the result! if (m_interpreter.GetExpandRegexAliases()) result.GetOutputStream().Printf("%s\n", new_command.c_str()); Index: lldb/unittests/Interpreter/CMakeLists.txt =================================================================== --- lldb/unittests/Interpreter/CMakeLists.txt +++ lldb/unittests/Interpreter/CMakeLists.txt @@ -4,6 +4,7 @@ TestOptionArgParser.cpp TestOptionValue.cpp TestOptionValueFileColonLine.cpp + TestRegexCommand.cpp LINK_LIBS lldbCore Index: lldb/unittests/Interpreter/TestRegexCommand.cpp =================================================================== --- /dev/null +++ lldb/unittests/Interpreter/TestRegexCommand.cpp @@ -0,0 +1,51 @@ +//===-- TestRegexCommand.cpp ----------------------------------------------===// +// +// 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 "Commands/CommandObjectRegexCommand.h" + +#include "gtest/gtest.h" + +using namespace lldb_private; +using namespace lldb; + +namespace { +class TestRegexCommand : public CommandObjectRegexCommand { +public: + static std::string + Substitute(std::string input, + const llvm::SmallVectorImpl &replacements) { + SubstituteVariables(input, replacements); + return input; + } +}; +} // namespace + +TEST(RegexCommandTest, SubstituteVariables) { + const llvm::SmallVector substitutions = {"UNUSED", "foo", + "bar", "baz"}; + + EXPECT_EQ(TestRegexCommand::Substitute("%1", substitutions), "foo"); + EXPECT_EQ(TestRegexCommand::Substitute("%2", substitutions), "bar"); + EXPECT_EQ(TestRegexCommand::Substitute("%3", substitutions), "baz"); + EXPECT_EQ(TestRegexCommand::Substitute("%1%2%3", substitutions), "foobarbaz"); + EXPECT_EQ(TestRegexCommand::Substitute("%1%2%3%4", substitutions), + "foobarbaz%4"); + EXPECT_EQ(TestRegexCommand::Substitute("#%1#%2#%3#", substitutions), + "#foo#bar#baz#"); + EXPECT_EQ(TestRegexCommand::Substitute("%11", substitutions), "%11"); +} + +TEST(RegexCommandTest, SubstituteVariablesNoRecursion) { + const llvm::SmallVector substitutions = {"UNUSED", "%2", + "%3", "%4"}; + + EXPECT_EQ(TestRegexCommand::Substitute("%1", substitutions), "%2"); + EXPECT_EQ(TestRegexCommand::Substitute("%2", substitutions), "%3"); + EXPECT_EQ(TestRegexCommand::Substitute("%3", substitutions), "%4"); + EXPECT_EQ(TestRegexCommand::Substitute("%1%2%3", substitutions), "%2%3%4"); +}