diff --git a/.sh b/.sh index 8e384777..da180d78 100755 --- a/.sh +++ b/.sh @@ -3,5 +3,6 @@ cmake -B build -G Ninja \ -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_BUILD_TYPE=Debug \ --DCLICE_TEST=ON +-DCLICE_TEST=ON \ +-DCLICE_EXAMPLES=ON cmake --build build diff --git a/.vscode/launch.json b/.vscode/launch.json index 3dbf4bdb..70e8de98 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,12 +4,24 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + //{ + // "type": "lldb", + // "request": "launch", + // "name": "Launch", + // "program": "${workspaceFolder}/build/clice_test", + // "args": [], + // "cwd": "${workspaceFolder}" + //}, { "type": "lldb", "request": "launch", "name": "Launch", - "program": "${workspaceFolder}/build/clice_test", - "args": [], + "program": "${workspaceFolder}/build/Preamble", + "args": [ + "./main.cpp", + "12", + "11" + ], "cwd": "${workspaceFolder}" }, //{ diff --git a/CMakeLists.txt b/CMakeLists.txt index 6445610a..a669603e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,10 +27,7 @@ set(INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/external/llvm/tools/clang/include" ) -set(LINK_LIBS - nlohmann_json::nlohmann_json - uv - spdlog::spdlog +set(LLVM_LIBS LLVMCore LLVMSupport LLVMIRReader @@ -51,6 +48,13 @@ set(LINK_LIBS clangToolingSyntax ) +set(LINK_LIBS + nlohmann_json::nlohmann_json + uv + spdlog::spdlog + ${LLVM_LIBS} +) + file(GLOB_RECURSE SOURCES "${CMAKE_SOURCE_DIR}/src/*.cpp") list(REMOVE_ITEM SOURCES "${CMAKE_SOURCE_DIR}/src/main.cpp") @@ -72,4 +76,18 @@ if(CLICE_TEST) target_precompile_headers(clice_test PRIVATE ${PCH_HEADER}) target_include_directories(clice_test PRIVATE ${INCLUDE_DIRS}) target_link_libraries(clice_test PRIVATE ${LINK_LIBS} GTest::gtest_main) +endif() + +if(CLICE_EXAMPLES) + macro(add_example target) + add_executable(${target} ${ARGN}) + target_precompile_headers(${target} PRIVATE ${PCH_HEADER}) + target_include_directories(${target} PRIVATE ${INCLUDE_DIRS}) + target_link_libraries(${target} PRIVATE ${LINK_LIBS}) + endmacro() + + add_example(ASTVisitor docs/examples/ASTVisitor.cpp) + add_example(CodeCompletion docs/examples/CodeCompletion.cpp) + add_example(Preamble docs/examples/Preamble.cpp) + add_example(Preprocessor docs/examples/Preprocessor.cpp) endif() \ No newline at end of file diff --git a/clangd.md b/clangd.md index c9ba7d45..3e756503 100644 --- a/clangd.md +++ b/clangd.md @@ -64,6 +64,7 @@ TODO: - 修复 quick fix 的位置问题 - 启发式模版补全(可选) - 控制代码补全的时机,只有光标后面没有字母的时候才补全,不要改中间的词弹补全框,烦死了(可选) +- 如果可变参数类型名太长了,比如`tuple`之类的,则显式成`tuple<...>`然后可以点击展开 希望支持的一些功能: - 可视化宏展开(类似 VS 里面那个功能) diff --git a/docs/examples/ASTVisitor.cpp b/docs/examples/ASTVisitor.cpp new file mode 100644 index 00000000..3a812441 --- /dev/null +++ b/docs/examples/ASTVisitor.cpp @@ -0,0 +1,74 @@ +#include + +class ASTVistor : public clang::RecursiveASTVisitor { +public: + bool VisitDecl(clang::Decl* decl) { + if(clang::NamedDecl* named = llvm::dyn_cast(decl)) { + llvm::outs() << "Decl: " << named->getNameAsString() << "\n"; + } + return true; + } +}; + +int main(int argc, const char** argv) { + assert(argc == 2 && "Usage: Preprocessor "); + llvm::outs() << "running ASTVisitor...\n"; + + auto instance = std::make_unique(); + + clang::DiagnosticIDs* ids = new clang::DiagnosticIDs(); + clang::DiagnosticOptions* diag_opts = new clang::DiagnosticOptions(); + clang::DiagnosticConsumer* consumer = new clang::TextDiagnosticPrinter(llvm::errs(), diag_opts); + clang::DiagnosticsEngine* engine = new clang::DiagnosticsEngine(ids, diag_opts, consumer); + instance->setDiagnostics(engine); + + auto invocation = std::make_shared(); + std::vector args = { + "/home/ykiko/Project/C++/clice/external/llvm/bin/clang++", + "-Xclang", + "-no-round-trip-args", + "-std=c++20", + "-Wno-everything", + argv[1], + "-c", + }; + + invocation = clang::createInvocation(args, {}); + // clang::CompilerInvocation::CreateFromArgs(*invocation, args, instance->getDiagnostics()); + instance->setInvocation(std::move(invocation)); + + if(!instance->createTarget()) { + llvm::errs() << "Failed to create target\n"; + std::terminate(); + } + + if(clang::FileManager* manager = instance->createFileManager()) { + instance->createSourceManager(*manager); + } else { + llvm::errs() << "Failed to create file manager\n"; + std::terminate(); + } + + instance->createPreprocessor(clang::TranslationUnitKind::TU_Complete); + + // ASTContent is necessary for SemanticAnalysis + instance->createASTContext(); + + clang::SyntaxOnlyAction action; + + if(!action.BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) { + llvm::errs() << "Failed to begin source file\n"; + std::terminate(); + } + + if(auto error = action.Execute()) { + llvm::errs() << "Failed to execute action: " << error << "\n"; + std::terminate(); + } + + auto tu = instance->getASTContext().getTranslationUnitDecl(); + ASTVistor visitor; + visitor.TraverseDecl(tu); + + action.EndSourceFile(); +} diff --git a/docs/examples/CodeCompletion.cpp b/docs/examples/CodeCompletion.cpp new file mode 100644 index 00000000..01536620 --- /dev/null +++ b/docs/examples/CodeCompletion.cpp @@ -0,0 +1,166 @@ +#include + +// clang 的 CodeCompletion 提供的功能非常有限,无法直接区分当前的 + +class CodeCompleteConsumer : public clang::CodeCompleteConsumer { +private: + std::shared_ptr Allocator; + clang::CodeCompletionTUInfo CCTUInfo; + +public: + CodeCompleteConsumer() : + clang::CodeCompleteConsumer(clang::CodeCompleteOptions{}), + Allocator(std::make_shared()), CCTUInfo(Allocator) {} + + clang::CodeCompletionAllocator& getAllocator() override { return *Allocator; } + + clang::CodeCompletionTUInfo& getCodeCompletionTUInfo() override { return CCTUInfo; } + + void ProcessCodeCompleteResults(clang::Sema& S, + clang::CodeCompletionContext Context, + clang::CodeCompletionResult* Results, + unsigned NumResults) override { + auto contexts = Context.getVisitedContexts(); + for(auto c: contexts) { + llvm::outs() << " Kind: " << c->getDeclKindName() << "\n"; + } + + llvm::outs() << "code completion results:\n"; + switch(Context.getKind()) { + case clang::CodeCompletionContext::CCC_Attribute: { + } + case clang::CodeCompletionContext::CCC_DotMemberAccess: { + const auto type = Context.getBaseType(); + if(type->isDependentType()) { + + if(const auto dependentType = type->getAs()) { + auto qualifers = dependentType->getQualifier(); + // qualifers->getKind() -> clang::NestedNameSpecifier::SpecifierKind; + // auto t = qualifers->getAsType(); + // TODO: 看是否能根据主模板一路把依赖名替换下去,直到变成非依赖名 + } else if(const auto dependentType = type->getAs()) { + } + } + break; + } + /* ... */ + default: { + llvm::outs() << " Kind: " << Context.getKind() << "\n"; + } + } + + for(unsigned i = 0; i < NumResults; ++i) { + clang::CodeCompletionResult& Result = Results[i]; + + switch(Result.Kind) { + case clang::CodeCompletionResult::RK_Declaration: { + llvm::outs() << " Declaration: "; + llvm::outs() << Result.Declaration->getNameAsString() << "\n"; + break; + } + + case clang::CodeCompletionResult::RK_Keyword: { + llvm::outs() << " Keyword: "; + llvm::outs() << Result.Keyword << "\n"; + break; + } + + case clang::CodeCompletionResult::RK_Macro: { + llvm::outs() << " Macro: "; + llvm::outs() << Result.Macro->getName() << "\n"; + break; + } + + case clang::CodeCompletionResult::RK_Pattern: { + llvm::outs() << " Pattern: "; + llvm::outs() << Result.Pattern->getAsString() << "\n"; + break; + } + } + } + } + + void ProcessOverloadCandidates(clang::Sema& S, + unsigned CurrentArg, + OverloadCandidate* Candidates, + unsigned NumCandidates, + clang::SourceLocation OpenParLoc, + bool Braced) override {} +}; + +int main(int argc, const char** argv) { + assert(argc == 4 && "Usage: CodeCompletion "); + llvm::outs() << "running CodeCompletion...\n"; + + auto instance = std::make_unique(); + + clang::DiagnosticIDs* ids = new clang::DiagnosticIDs(); + clang::DiagnosticOptions* diag_opts = new clang::DiagnosticOptions(); + diag_opts->IgnoreWarnings = true; + clang::DiagnosticConsumer* consumer = new clang::TextDiagnosticPrinter(llvm::errs(), diag_opts); + clang::DiagnosticsEngine* engine = new clang::DiagnosticsEngine(ids, diag_opts, consumer); + instance->setDiagnostics(engine); + + auto invocation = std::make_shared(); + std::vector args = { + "/home/ykiko/Project/C++/clice/external/llvm/bin/clang++", + "-Xclang", + "-no-round-trip-args", + "-std=c++20", + "-Wno-everything", + argv[1], + "-c", + }; + + invocation = clang::createInvocation(args, {}); + + /// NOTICE: + auto& codeCompletionAt = invocation->getFrontendOpts().CodeCompletionAt; + codeCompletionAt.FileName = argv[1]; + codeCompletionAt.Line = std::stoi(argv[2]); + codeCompletionAt.Column = std::stoi(argv[3]); + + instance->setInvocation(std::move(invocation)); + + if(!instance->createTarget()) { + llvm::errs() << "Failed to create target\n"; + std::terminate(); + } + + if(clang::FileManager* manager = instance->createFileManager()) { + instance->createSourceManager(*manager); + } else { + llvm::errs() << "Failed to create file manager\n"; + std::terminate(); + } + + instance->createPreprocessor(clang::TranslationUnitKind::TU_Complete); + + // ASTContent is necessary for SemanticAnalysis + instance->createASTContext(); + + /// NOTICE: + instance->setCodeCompletionConsumer(new CodeCompleteConsumer()); + + clang::SyntaxOnlyAction action; + + if(!action.BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) { + llvm::errs() << "Failed to begin source file\n"; + std::terminate(); + } + + auto& pp = instance->getPreprocessor(); + pp.setTokenWatcher([&pp](const clang::Token& token) { + llvm::outs() << "token: " << pp.getSpelling(token) << " kind: " << token.getName() << "\n"; + }); + + if(auto error = action.Execute()) { + llvm::errs() << "Failed to execute action: " << error << "\n"; + std::terminate(); + } + + auto tu = instance->getASTContext().getTranslationUnitDecl(); + tu->dump(); + + action.EndSourceFile(); +} diff --git a/docs/examples/Module.cpp b/docs/examples/Module.cpp new file mode 100644 index 00000000..34a76016 --- /dev/null +++ b/docs/examples/Module.cpp @@ -0,0 +1,5 @@ +#include + +namespace clice { + +} \ No newline at end of file diff --git a/docs/examples/Preamble.cpp b/docs/examples/Preamble.cpp new file mode 100644 index 00000000..2f6d95e9 --- /dev/null +++ b/docs/examples/Preamble.cpp @@ -0,0 +1,113 @@ +#include +#include + +namespace fs = std::filesystem; + +int main(int argc, const char** argv) { + assert(argc == 2 && "Usage: Preamble "); + llvm::outs() << "running Preamble...\n"; + + auto instance = std::make_unique(); + + clang::DiagnosticIDs* ids = new clang::DiagnosticIDs(); + clang::DiagnosticOptions* diag_opts = new clang::DiagnosticOptions(); + clang::DiagnosticConsumer* consumer = new clang::TextDiagnosticPrinter(llvm::errs(), diag_opts); + clang::DiagnosticsEngine* engine = new clang::DiagnosticsEngine(ids, diag_opts, consumer); + instance->setDiagnostics(engine); + + auto invocation = + std::make_shared(std::make_shared()); + std::vector args = { + "/home/ykiko/Project/C++/clice/external/llvm/bin/clang++", + "-Xclang", + "-no-round-trip-args", + "-std=c++20", + "-Wno-everything", + argv[1], + "-c", + }; + + invocation = clang::createInvocation(args, {}); + + // from filepath: llvm::MemoryBuffer::getFile + // from content: llvm::MemoryBuffer::getMemBuffer(content); + auto tmp = llvm::MemoryBuffer::getFile(argv[1]); + if(auto error = tmp.getError()) { + llvm::errs() << "Failed to get file: " << error.message() << "\n"; + std::terminate(); + } + llvm::MemoryBuffer* buffer = tmp->get(); + + // compute preamble bounds, if MaxLines set to false(0), it means not to limit the number of lines + auto bounds = clang::ComputePreambleBounds({}, *buffer, false); + + auto VFS = llvm::vfs::getRealFileSystem(); + // if(auto error = VFS->setCurrentWorkingDirectory(fs::path(argv[1]).parent_path().string())) { + // llvm::errs() << "Failed to set current working directory: " << error.message() << "\n"; + // } + + // if store the preamble in memory, if not, store it in a file(storagePath) + bool storeInMemory = true; + std::string storagePath = (fs::path(argv[0]).parent_path()).string(); + + // use to collect information in the process of building preamble, such as include files and macros + // TODO: inherit from clang::PreambleCallbacks and collect the information + clang::PreambleCallbacks callbacks = {}; + + // build preamble + auto preamble = clang::PrecompiledPreamble::Build(*invocation, + buffer, + bounds, + *engine, + VFS, + std::make_shared(), + storeInMemory, + "", + callbacks); + + if(auto error = preamble.getError()) { + llvm::errs() << "Failed to build preamble: " << error.message() << "\n"; + std::terminate(); + } + + invocation = clang::createInvocation(args, {}); + // check if the preamble can be reused + if(preamble->CanReuse(*invocation, *buffer, bounds, *VFS)) { + llvm::outs() << "Resued preamble\n"; + // reuse preamble + preamble->AddImplicitPreamble(*invocation, VFS, buffer); + } + + instance->setInvocation(std::move(invocation)); + + if(!instance->createTarget()) { + llvm::errs() << "Failed to create target\n"; + std::terminate(); + } + + if(clang::FileManager* manager = instance->createFileManager()) { + instance->createSourceManager(*manager); + } else { + llvm::errs() << "Failed to create file manager\n"; + std::terminate(); + } + + instance->createPreprocessor(clang::TranslationUnitKind::TU_Complete); + + // ASTContent is necessary for SemanticAnalysis + instance->createASTContext(); + + clang::SyntaxOnlyAction action; + + if(!action.BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) { + llvm::errs() << "Failed to begin source file\n"; + std::terminate(); + } + + if(auto error = action.Execute()) { + llvm::errs() << "Failed to execute action: " << error << "\n"; + std::terminate(); + } + + action.EndSourceFile(); +} diff --git a/docs/examples/Preprocessor.cpp b/docs/examples/Preprocessor.cpp new file mode 100644 index 00000000..71123806 --- /dev/null +++ b/docs/examples/Preprocessor.cpp @@ -0,0 +1,118 @@ +#include +#include +#include + +class PPCallback : public clang::PPCallbacks { +private: + clang::Preprocessor& pp; + clang::SourceManager& sm; + +public: + PPCallback(clang::Preprocessor& pp) : pp(pp), sm(pp.getSourceManager()) {} + + void MacroExpands(const clang::Token& token, + const clang::MacroDefinition& macro, + clang::SourceRange range, + const clang::MacroArgs* args) override { + std::string name = pp.getSpelling(token); + if(name.starts_with("_")) + return; + + clang::MacroInfo* info = macro.getMacroInfo(); + // info->isBuiltinMacro(); + // info->isFunctionLike(); + // info->isObjectLike(); + // info->isVariadic(); + // info->getNumParams(); + // info->params(); + + const int size = args->getNumMacroArguments(); // Expanding macro arguments + for(auto i = 0; i < size; ++i) { + // get first token of first argument of expanding macro + const clang::Token* first = args->getUnexpArgument(i); + // iterate over tokens of first argument of expanding macro + for(auto j = 0; j < args->getArgLength(first); ++j) { + const clang::Token& tok = *(first + j); + // llvm::outs() << "Arg: " << pp.getSpelling(tok) << "\n"; + } + } + + auto expandingRange = sm.getExpansionRange(range); + auto text = clang::Lexer::getSourceText(expandingRange, sm, pp.getLangOpts()); + llvm::outs() << text << "\n"; + } + + void MacroDefined(const clang::Token& token, const clang::MacroDirective* directive) override { + std::string name = pp.getSpelling(token); + if(name.starts_with("_")) + return; + llvm::outs() << "MacroDefined: " << name << "\n"; + } +}; + +int main(int argc, const char** argv) { + assert(argc == 2 && "Usage: Preprocessor "); + llvm::outs() << "running Preprocessor...\n"; + + auto instance = std::make_unique(); + + clang::DiagnosticIDs* ids = new clang::DiagnosticIDs(); + clang::DiagnosticOptions* diag_opts = new clang::DiagnosticOptions(); + clang::DiagnosticConsumer* consumer = new clang::TextDiagnosticPrinter(llvm::errs(), diag_opts); + clang::DiagnosticsEngine* engine = new clang::DiagnosticsEngine(ids, diag_opts, consumer); + instance->setDiagnostics(engine); + + auto invocation = std::make_shared(); + std::vector args = { + "/home/ykiko/Project/C++/clice/external/llvm/bin/clang++", + "-Xclang", + "-no-round-trip-args", + "-std=c++20", + "-Wno-everything", + argv[1], + "-c", + }; + + // invocation = clang::createInvocation(args, {}); + clang::CompilerInvocation::CreateFromArgs(*invocation, args, instance->getDiagnostics()); + instance->setInvocation(std::move(invocation)); + + if(!instance->createTarget()) { + llvm::errs() << "Failed to create target\n"; + std::terminate(); + } + + if(clang::FileManager* manager = instance->createFileManager()) { + instance->createSourceManager(*manager); + } else { + llvm::errs() << "Failed to create file manager\n"; + std::terminate(); + } + + instance->createPreprocessor(clang::TranslationUnitKind::TU_Complete); + + clang::PreprocessOnlyAction action; + + if(!action.BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) { + llvm::errs() << "Failed to begin source file\n"; + std::terminate(); + } + + clang::Preprocessor& pp = instance->getPreprocessor(); + pp.addPPCallbacks(std::make_unique(pp)); + clang::syntax::TokenCollector collector{pp}; + + if(auto error = action.Execute()) { + llvm::errs() << "Failed to execute action: " << error << "\n"; + std::terminate(); + } + + clang::syntax::TokenBuffer buffer = std::move(collector).consume(); + auto tokens = buffer.expandedTokens(); + for(auto& token: tokens) { + llvm::outs() << token.text(pp.getSourceManager()) << "\n"; + } + + // all operations should before action end + action.EndSourceFile(); +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 00000000..23a649c2 --- /dev/null +++ b/main.cpp @@ -0,0 +1,13 @@ + +template +struct X { + static void fooooooooooooooo(int x); + + void bar() { + X x; + // X::bar + } + + static void bar2(); +}; + diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt deleted file mode 100644 index 3bc9e841..00000000 --- a/samples/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -cmake_minimum_required(VERSION 3.22) -project(sample) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -add_executable(sample main.cpp) diff --git a/samples/main.cpp b/samples/main.cpp deleted file mode 100644 index 82059c8f..00000000 --- a/samples/main.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#define EXPAND(...) __VA_ARGS__ -#define GET_PARAM(...) __VA_ARGS__, -#define GET_TYPE_IMPL(...) GET_PARAM __VA_ARGS__ - -#define GET_FST(_1, ...) _1 -#define GET_WRAP(...) GET_FST(__VA_ARGS__) -#define GET_TYPE(x) EXPAND(GET_WRAP(GET_TYPE_IMPL(x))) - -int main() { - GET_TYPE((int)y) x = 1; - GET_TYPE((double)y) y = 1; - return 0; -} diff --git a/tests/Clang/Preamble.cpp b/tests/Clang/Preamble.cpp index 6838e7a9..507ebeb6 100644 --- a/tests/Clang/Preamble.cpp +++ b/tests/Clang/Preamble.cpp @@ -58,15 +58,72 @@ TEST(clice, clang) { instance->createASTContext(); + using namespace clang; + + class Callback : public clang::PPCallbacks { + + public: + Preprocessor& pp; + SourceRange last; + + Callback(Preprocessor& pp) : pp(pp) {} + + /// Called by Preprocessor::HandleMacroExpandedIdentifier when a + /// macro invocation is found. + void MacroExpands(const Token& MacroNameTok, + const MacroDefinition& MD, + SourceRange Range, + const MacroArgs* Args) override { + + auto name = MacroNameTok.getIdentifierInfo()->getName(); + if(name.starts_with("__")) + return; + llvm::outs() << "MacroExpands: " << name; + if(MD.getMacroInfo()->isFunctionLike()) { + llvm::outs() << "("; + int len = Args->getNumMacroArguments(); + for(auto i = 0; i < len; i++) { + auto arg = Args->getUnexpArgument(i); + auto len2 = Args->getArgLength(arg); + for(auto j = 0; j < len2; j++) { + llvm::outs() << pp.getSpelling(*(arg + j)); + } + if(i < len - 1) + llvm::outs() << ", "; + } + llvm::outs() << ")"; + } + llvm::outs() << "\n"; + auto& m = pp.getSourceManager(); + auto x = m.getExpansionRange(Range); + // auto z = m.getImmediateExpansionRange(x.getBegin()); + + auto text = Lexer::getSourceText(x, m, pp.getLangOpts()); + llvm::outs() << text << "\n"; + } + + /// Hook called whenever a macro definition is seen. + void MacroDefined(const Token& MacroNameTok, const MacroDirective* MD) override { + auto name = MacroNameTok.getIdentifierInfo()->getName(); + if(name.starts_with("__")) + return; + // llvm::outs() << "MacroDefined: " << name << "\n"; + } + }; + /// if code completion // instance->setCodeCompletionConsumer(consumer); - clang::SyntaxOnlyAction action; + SyntaxOnlyAction action; if(!action.BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) { llvm::errs() << "Failed to begin source file\n"; std::terminate(); } + // must be after BeginSourceFile + auto& preprocessor = instance->getPreprocessor(); + preprocessor.addPPCallbacks(std::make_unique(preprocessor)); + if(auto error = action.Execute()) { llvm::errs() << "Failed to execute action: " << error << "\n"; std::terminate();