altimin created this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

DO NOT SUBMIT: Early prototype


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D76607

Files:
  clang-tools-extra/clang-query/Query.cpp
  clang-tools-extra/clang-query/Query.h
  clang-tools-extra/clang-query/QueryParser.cpp

Index: clang-tools-extra/clang-query/QueryParser.cpp
===================================================================
--- clang-tools-extra/clang-query/QueryParser.cpp
+++ 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<DynTypedMatcher> 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 =
Index: clang-tools-extra/clang-query/Query.h
===================================================================
--- clang-tools-extra/clang-query/Query.h
+++ 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) {}
Index: clang-tools-extra/clang-query/Query.cpp
===================================================================
--- clang-tools-extra/clang-query/Query.cpp
+++ 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<std::string> 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<BoundNodes> Matches;
+    DynTypedMatcher MaybeBoundMatcher = Matcher;
+    if (QS.BindRoot) {
+      llvm::Optional<DynTypedMatcher> 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;
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
  • [PATCH] D76607: [clang-que... Alexander Timin via Phabricator via cfe-commits

Reply via email to