Index: clang-query/CMakeLists.txt =================================================================== --- clang-query/CMakeLists.txt +++ clang-query/CMakeLists.txt @@ -6,6 +6,9 @@ add_clang_library(clangQuery Query.cpp QueryParser.cpp + QueryReplace.cpp + QueryReplace.h + LINK_LIBS clangAST @@ -16,3 +19,4 @@ ) add_subdirectory(tool) +add_subdirectory(replace-tool) Index: clang-query/QueryReplace.h =================================================================== --- /dev/null +++ clang-query/QueryReplace.h @@ -0,0 +1,58 @@ +//===--- QueryReplace.h - clang-query-replace ----------------------*- C++-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERYREPLACE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERYREPLACE_H + +#include "clang/AST/ASTConsumer.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" +#include "clang/Tooling/RefactoringCallbacks.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include +#include +#include + +namespace clang { +namespace query { +/// \brief Describes an AST query and replace operation. +struct QueryReplaceSpec { + static llvm::Expected parseFromYAML(llvm::StringRef Spec); + /// \brief clang-query style matcher expression + std::string Query; + + /// \brief The node bound to this identifier will be replaced by the + /// result of evaluating the template below. + std::string FromId; + + /// \brief Replacement template. ${identifier} will be replaced by the text of + /// the node bound to the identifier, and $$ by a literal '$'. + std::string ToTemplate; +}; + +class QueryReplaceTool { +public: + QueryReplaceTool( + std::map &FileToReplaces); + llvm::Error addOperation(QueryReplaceSpec &spec); + std::unique_ptr newASTConsumer(); + +private: + struct ReplaceOp { + ast_matchers::dynamic::DynTypedMatcher Matcher; + std::unique_ptr Callback; + }; + std::vector Operations; + + tooling::ASTMatchRefactorer Refactorer; +}; +} // namespace query +} // namespace clang +#endif Index: clang-query/QueryReplace.cpp =================================================================== --- /dev/null +++ clang-query/QueryReplace.cpp @@ -0,0 +1,88 @@ +//===--- QueryReplace.cpp - clang-query-replace --------------------------*- C++ +//-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "QueryReplace.h" +#include "QueryParser.h" +#include "QuerySession.h" + +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTTypeTraits.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/ASTMatchers/Dynamic/Parser.h" +#include "clang/Lex/Lexer.h" +#include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/RefactoringCallbacks.h" +#include "clang/Tooling/Tooling.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/YAMLTraits.h" + +using clang::query::QueryReplaceSpec; +using clang::ast_matchers::MatchFinder; +using clang::ast_type_traits::DynTypedNode; +using clang::tooling::ReplaceNodeWithTemplate; +using llvm::Error; +using llvm::Expected; +using llvm::StringError; +using llvm::StringRef; + +namespace llvm { +namespace yaml { +template <> struct MappingTraits { + static void mapping(IO &IO, clang::query::QueryReplaceSpec &Op) { + IO.mapRequired("Query", Op.Query); + IO.mapRequired("FromId", Op.FromId); + IO.mapRequired("ToTemplate", Op.ToTemplate); + } +}; +} // namespace yaml +} // namespace llvm + +namespace clang { +namespace query { + +Expected QueryReplaceSpec::parseFromYAML(StringRef Spec) { + llvm::yaml::Input Input(Spec); + QueryReplaceSpec Op; + Input >> Op; + if (Input.error()) + return llvm::errorCodeToError(Input.error()); + return Op; +} + +QueryReplaceTool::QueryReplaceTool( + std::map &FileToReplaces) + : Refactorer(FileToReplaces) {} + +Error QueryReplaceTool::addOperation(clang::query::QueryReplaceSpec &Spec) { + ast_matchers::dynamic::Diagnostics Diag; + // FIXME: Support query sessions (for interactive use). + auto Matcher = ast_matchers::dynamic::Parser::parseMatcherExpression( + Spec.Query, nullptr, nullptr, &Diag); + if (!Matcher) { + return llvm::make_error( + StringRef("Cannot parse Query:") + Diag.toStringFull(), + std::make_error_code(std::errc::bad_message)); + } + auto Operation = + ReplaceNodeWithTemplate::create(Spec.FromId, Spec.ToTemplate); + if (!Operation) + return Operation.takeError(); + Operations.emplace_back(ReplaceOp{Matcher.getValue(), std::move(*Operation)}); + + Refactorer.addDynamicMatcher(Operations.back().Matcher, + Operations.back().Callback.get()); + return Error::success(); +} + +std::unique_ptr QueryReplaceTool::newASTConsumer() { + return Refactorer.newASTConsumer(); +} +} // namespace query +} // namespace clang Index: clang-query/replace-tool/CMakeLists.txt =================================================================== --- /dev/null +++ clang-query/replace-tool/CMakeLists.txt @@ -0,0 +1,14 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) + +add_clang_executable(clang-query-replace ClangQueryReplace.cpp) +target_link_libraries(clang-query-replace + clangAST + clangASTMatchers + clangBasic + clangDynamicASTMatchers + clangFrontend + clangQuery + clangTooling + ) + +install(TARGETS clang-query-replace RUNTIME DESTINATION bin) \ No newline at end of file Index: clang-query/replace-tool/ClangQueryReplace.cpp =================================================================== --- /dev/null +++ clang-query/replace-tool/ClangQueryReplace.cpp @@ -0,0 +1,85 @@ +//===---- ClangQueryReplace.cpp - clang-query-replace tool ----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This tool automatically modifies source files based on AST matchers. +// +// A given AST matcher is applied to the source, and a bound node in +// this matcher is replaced by the result of substituting the text +// corresponding to other bound nodes into a string template. +// +// For example: +// clang-query-replace --spec < +#include +#include + +using namespace clang; +using namespace clang::query; +using namespace llvm; + +using std::error_code; +using clang::tooling::CommonOptionsParser; + +static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage); +static cl::OptionCategory ClangQueryCategory("clang-query-replace options"); + +static cl::list Commands("spec", + cl::desc("YAML action specification"), + cl::value_desc("spec"), + cl::cat(ClangQueryCategory)); + +int main(int argc, const char **argv) { + llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); + + CommonOptionsParser OptionsParser(argc, argv, ClangQueryCategory); + if (Commands.empty()) { + llvm::errs() << "Must specify at least one --spec \n"; + return 1; + } + + tooling::RefactoringTool Refactor(OptionsParser.getCompilations(), + OptionsParser.getSourcePathList()); + + QueryReplaceTool Tool(Refactor.getReplacements()); + for (auto &SpecYaml : Commands) { + + llvm::Expected Spec = + QueryReplaceSpec::parseFromYAML(SpecYaml); + if (llvm::Error Error = Spec.takeError()) + llvm::report_fatal_error(std::move(Error), false); + if (llvm::Error Error = Tool.addOperation(*Spec)) + llvm::report_fatal_error(std::move(Error), false); + } + + return Refactor.runAndSave(tooling::newFrontendActionFactory(&Tool).get()); +} Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -44,6 +44,7 @@ clang-include-fixer clang-move clang-query + clang-query-replace clang-rename clang-reorder-fields clang-tidy Index: test/clang-query/replacement.c =================================================================== --- /dev/null +++ test/clang-query/replacement.c @@ -0,0 +1,5 @@ +// RUN: cp %s %t.c +// RUN: clang-query-replace --spec '{Query: "varDecl(hasName(\"version\"), hasInitializer(integerLiteral().bind(\"init\"))).bind(\"decl\")", FromId: "decl", ToTemplate: "string version = \"$$-${init}\""}' %t.c -- +// RUN: FileCheck --match-full-lines %s < %t.c +int version = 4; +// CHECK: string version = "$-4";