diff --git a/clang-tools-extra/clang-query/Query.h b/clang-tools-extra/clang-query/Query.h --- a/clang-tools-extra/clang-query/Query.h +++ b/clang-tools-extra/clang-query/Query.h @@ -26,6 +26,7 @@ QK_Help, QK_Let, QK_Match, + QK_Replace, QK_SetBool, QK_SetOutputKind, QK_EnableOutputKind, @@ -98,6 +99,19 @@ static bool classof(const Query *Q) { return Q->Kind == QK_Match; } }; +/// Query for "replace MATCHER REPLACEMENT" +struct ReplaceQuery : Query { + ReplaceQuery(StringRef ReplacementSource, const ast_matchers::dynamic::DynTypedMatcher &Matcher) + : Query(QK_Replace), Matcher(Matcher), ReplacementSource(ReplacementSource) {} + bool run(llvm::raw_ostream &OS, QuerySession &QS) const override; + + ast_matchers::dynamic::DynTypedMatcher Matcher; + + StringRef ReplacementSource; + + static bool classof(const Query *Q) { return Q->Kind == QK_Replace; } +}; + struct LetQuery : Query { LetQuery(StringRef Name, const ast_matchers::dynamic::VariantValue &Value) : Query(QK_Let), Name(Name), Value(Value) {} diff --git a/clang-tools-extra/clang-query/Query.cpp b/clang-tools-extra/clang-query/Query.cpp --- a/clang-tools-extra/clang-query/Query.cpp +++ b/clang-tools-extra/clang-query/Query.cpp @@ -9,10 +9,21 @@ #include "Query.h" #include "QuerySession.h" #include "clang/AST/ASTDumper.h" +#include "clang/AST/ExprConcepts.h" #include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/CharInfo.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/TextDiagnostic.h" +#include "clang/Sema/DeclSpec.h" +#include "clang/Tooling/Core/Diagnostic.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/DiagnosticsYaml.h" +#include "clang/Tooling/ReplacementsYaml.h" +#include "llvm/ADT/None.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/YAMLTraits.h" using namespace clang::ast_matchers; using namespace clang::ast_matchers::dynamic; @@ -162,6 +173,179 @@ return true; } +namespace { + +class ReplacementParser { + public: + ReplacementParser(StringRef ReplacementPattern, const ast_matchers::internal::BoundNodesMap::IDToNodeMap& BoundNodes, SourceManager& SourceManager): + ReplacementPattern(ReplacementPattern), BoundNodes(BoundNodes), SM(SourceManager) { + Result.reserve(ReplacementPattern.size()); + } + + Optional Parse() { + while (!StreamFinished() && !Failed) { + char Char = ReadChar(); + + if (Char == '$') { + if (StreamFinished()) { + Fail("$ should be either be escaped ($$) or be a part of a lookup instruction ($(name))"); + continue; + } + if (GetCurrentChar() == '$') { + // Skip the second $. + ReadChar(); + Result.append(1, Char); + continue; + } + Result.append(LookUpValueForBoundName(ParseBoundName())); + } else { + Result.append(1, Char); + } + } + + if (Failed) + return llvm::None; + return Result; + } + + std::string getError() { + return Error; + } + + private: + char GetCurrentChar() { + return ReplacementPattern[CurrentPos]; + } + + char ReadChar() { + assert(!StreamFinished()); + char CurrentChar = GetCurrentChar(); + ++CurrentPos; + return CurrentChar; + } + + bool StreamFinished() { + return CurrentPos == ReplacementPattern.size(); + } + + // Read $(name) pattern after initial $ is read. + StringRef ParseBoundName() { + assert(ReadChar() == '('); + + size_t StartPos = CurrentPos; + + while (true) { + if (StreamFinished()) { + Fail("Replacement pattern has ended without closing the lookup instruction"); + return ""; + } + char Char = ReadChar(); + if (Char == ')') + return ReplacementPattern.slice(StartPos, CurrentPos - 1); + if (!isAlphanumeric(Char)) { + Fail("Invalid char encountered inside lookup instruction: " + std::string(1, Char) + ", should be alphanumeric"); + return ""; + } + } + } + + std::string LookUpValueForBoundName(StringRef Name) { + auto it = BoundNodes.find(Name); + if (it == BoundNodes.end()) { + Fail("No bound node for name '" + Name.str() + "' found"); + return ""; + } + SourceRange SR = it->second.getSourceRange(); + return Lexer::getSourceText(SM.getExpansionRange(SR), SM, LangOptions()).str(); + } + + void Fail(std::string Error) { + if (Failed) + return; + Failed = true; + Error = Error; + } + + StringRef ReplacementPattern; + const ast_matchers::internal::BoundNodesMap::IDToNodeMap BoundNodes; + SourceManager& SM; + + size_t CurrentPos = 0; + bool Failed = false; + std::string Error; + + std::string Result; +}; + +} // namespace + +bool ReplaceQuery::run(llvm::raw_ostream& OS, QuerySession &QS) const { + fprintf(stderr, "### Running replacement query\n"); + tooling::Replacements Replacements; + + size_t SuccessfulReplacements = 0; + size_t FailedReplacements = 0; + + llvm::ExitOnError ExitOnError; + + for (auto &AST : QS.ASTs) { + fprintf(stderr, "### Processing AST\n"); + MatchFinder Finder; + std::vector Matches; + DynTypedMatcher MaybeBoundMatcher = Matcher; + if (QS.BindRoot) { + llvm::Optional M = Matcher.tryBind("root"); + if (M) + MaybeBoundMatcher = *M; + } + CollectBoundNodes Collect(Matches); + if (!Finder.addDynamicMatcher(MaybeBoundMatcher, &Collect)) { + OS << "Not a valid top-level matcher.\n"; + return false; + } + Finder.matchAST(AST->getASTContext()); + + fprintf(stderr, "### Processing %d matches\n", Matches.size()); + + for (auto MI = Matches.begin(), ME = Matches.end(); MI != ME; ++MI) { + // TODO: Allow node-to-be-replaced to be configurable. + auto NodeToBeReplacedIt = MI->getMap().find("root"); + if (NodeToBeReplacedIt == MI->getMap().end()) { + OS << "Failed to find node 'root' in the match"; + ++FailedReplacements; + continue; + } + + fprintf(stderr, "### Starting to parse RS %s\n", ReplacementSource.str().c_str()); + ReplacementParser Parser(ReplacementSource, MI->getMap(), AST->getSourceManager()); + auto ParseResult = Parser.Parse(); + fprintf(stderr, "### Parsing finished\n"); + + if (!ParseResult) { + OS << "Failed to apply replacement: " + Parser.getError(); + ++FailedReplacements; + continue; + } + + ExitOnError(Replacements.add(tooling::Replacement(AST->getSourceManager(), &NodeToBeReplacedIt->second, ParseResult.getValue()))); + ++SuccessfulReplacements; + } + } + OS << SuccessfulReplacements << " matches generated a replacements, " << FailedReplacements << " matches failed to generate a replacement"; + + tooling::TranslationUnitDiagnostics TUD; + for (const tooling::Replacement& Replacement: Replacements) { + tooling::Diagnostic Diag; + ExitOnError(Diag.Message.Fix[Replacement.getFilePath()].add(Replacement)); + TUD.Diagnostics.insert(TUD.Diagnostics.end(), Diag); + } + + llvm::yaml::Output YAML(OS); + YAML << TUD; + + return true; +} + bool LetQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const { if (Value) { QS.NamedValues[Name] = Value; diff --git a/clang-tools-extra/clang-query/QueryParser.cpp b/clang-tools-extra/clang-query/QueryParser.cpp --- a/clang-tools-extra/clang-query/QueryParser.cpp +++ b/clang-tools-extra/clang-query/QueryParser.cpp @@ -161,6 +161,7 @@ PQK_Let, PQK_Match, PQK_Set, + PQK_Replace, PQK_Unlet, PQK_Quit, PQK_Enable, @@ -202,6 +203,8 @@ .Case("let", PQK_Let) .Case("m", PQK_Match, /*IsCompletion=*/false) .Case("match", PQK_Match) + .Case("r", PQK_Replace, /*IsCompletion=*/false) + .Case("replace", PQK_Replace) .Case("q", PQK_Quit, /*IsCompletion=*/false) .Case("quit", PQK_Quit) .Case("set", PQK_Set) @@ -265,6 +268,20 @@ return Q; } + case PQK_Replace: { + Diagnostics Diag; + auto MatcherAndReplacementSource = Line.split(";"); + auto MatcherSource = MatcherAndReplacementSource.first.ltrim(); + Optional Matcher = Parser::parseMatcherExpression( + MatcherSource, nullptr, &QS.NamedValues, &Diag); + if (!Matcher) { + fprintf(stderr, "### Failed to construct matcher"); + return makeInvalidQueryFromDiagnostics(Diag); + } + auto *Q = new ReplaceQuery(MatcherAndReplacementSource.second.trim(), *Matcher); + return Q; + } + case PQK_Set: { StringRef VarStr; ParsedQueryVariable Var =