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,21 @@ 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,9 +9,20 @@ #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/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" using namespace clang::ast_matchers; @@ -147,7 +158,8 @@ const ASTContext &Ctx = AST->getASTContext(); const SourceManager &SM = Ctx.getSourceManager(); ASTDumper Dumper(OS, &Ctx.getCommentCommandTraits(), &SM, - SM.getDiagnostics().getShowColors(), Ctx.getPrintingPolicy()); + SM.getDiagnostics().getShowColors(), + Ctx.getPrintingPolicy()); Dumper.Visit(BI->second); OS << "\n"; } @@ -162,6 +174,186 @@ 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 @@ -78,7 +78,8 @@ if (WordCompletionPos == StringRef::npos) Switch.Case(CaseStr, Value); - else if (CaseStr.size() != 0 && IsCompletion && WordCompletionPos <= CaseStr.size() && + else if (CaseStr.size() != 0 && IsCompletion && + WordCompletionPos <= CaseStr.size() && CaseStr.substr(0, WordCompletionPos) == Word.substr(0, WordCompletionPos)) P->Completions.push_back(LineEditor::Completion( @@ -161,6 +162,7 @@ PQK_Let, PQK_Match, PQK_Set, + PQK_Replace, PQK_Unlet, PQK_Quit, PQK_Enable, @@ -202,7 +204,9 @@ .Case("let", PQK_Let) .Case("m", PQK_Match, /*IsCompletion=*/false) .Case("match", PQK_Match) - .Case("q", PQK_Quit, /*IsCompletion=*/false) + .Case("r", PQK_Replace, /*IsCompletion=*/false) + .Case("replace", PQK_Replace) + .Case("q", PQK_Quit, /*IsCompletion=*/false) .Case("quit", PQK_Quit) .Case("set", PQK_Set) .Case("enable", PQK_Enable) @@ -265,6 +269,21 @@ 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 =