diff --git a/.gitignore b/.gitignore index 259148fa..7f19681b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ *.exe *.out *.app + +.cache/ +build/ +external/ \ No newline at end of file diff --git a/.sh b/.sh new file mode 100755 index 00000000..3ce53603 --- /dev/null +++ b/.sh @@ -0,0 +1,6 @@ +cmake -B build -G Ninja \ +-DCMAKE_C_COMPILER_LAUNCHER=ccache \ +-DCMAKE_CXX_COMPILER=clang++ \ +-DCMAKE_C_COMPILER=clang \ +-DCMAKE_BUILD_TYPE=Debug +cmake --build build diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..9ce69e7a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/build/libtooling", + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..751ee028 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.22) +project(libtooling) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_RELEASE} -g -O0 -fno-rtti ") + +set(CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/external/llvm/lib/cmake") +find_package(LLVM REQUIRED CONFIG) +find_package(Clang REQUIRED CONFIG) + +separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) +add_definitions(${LLVM_DEFINITIONS_LIST}) + +set(PCH_HEADER "${CMAKE_SOURCE_DIR}/include/Clang/Clang.h") + +add_subdirectory(external/libuv) +add_subdirectory(external/simdjson) + +add_executable(libtooling src/main.cpp) + +target_include_directories(libtooling PRIVATE "${CMAKE_SOURCE_DIR}/include") +target_include_directories(libtooling PRIVATE ${LLVM_INCLUDE_DIRS}) +target_include_directories(libtooling PRIVATE ${CLANG_INCLUDE_DIRS}) + +llvm_map_components_to_libnames(llvm_libs support core irreader) + +message(STATUS "LLVM_INCLUDE_DIRS: ${LLVM_INCLUDE_DIRS}") +message(STATUS "LLVM_LIBS: ${llvm_libs}") + +target_precompile_headers(libtooling PRIVATE ${PCH_HEADER}) + +target_link_libraries(libtooling PRIVATE uv) +target_link_libraries(libtooling PRIVATE simdjson) +target_link_libraries(libtooling PRIVATE ${llvm_libs}) + +target_link_libraries(libtooling PRIVATE + LLVMTargetParser + clangAST + clangASTMatchers + clangBasic + clangDriver + clangFormat + clangFrontend + clangIndex + clangLex + clangSema + clangSerialization + clangTooling + clangToolingCore + clangToolingInclusions + clangToolingInclusionsStdlib + clangToolingSyntax +) \ No newline at end of file diff --git a/clangd.md b/clangd.md new file mode 100644 index 00000000..c95bb708 --- /dev/null +++ b/clangd.md @@ -0,0 +1,108 @@ +首先分析一下项目架构,clice 本身是一个非常简单的模型,就是一个 client,实现了 [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) 的 client。比如在 vscode 里面用的时候,对方发消息,然后我们回复指定内容即可。 + +那这个项目的难点主要在哪? + +## 和庞大的 Clang 源码进行交互 + +目前进度: +- 成功基于 CompilerInstance 创建了简单的 CodeCompletionConsumer,可以进行简单的代码补全查询 +- 了解到进行语义高亮的原理,先把所有 Tokens 记录下来,然后遍历 AST 的时候再根据语义信息(可以用 Location 作为 Key 来索引)去高亮,语义分析的时候 Tokens 肯定全有了,但是暂时不知道如何拿到 Tokens +- 成功拿到 Tokens,原来是 CodeCompletion 和 Tokens 不能同时用。在使用 Action 进行 Execute 后,可以从 CompilerInstance 里面拿到整个编译单元的 AST,遍历 AST(使用 RecursiveASTVisitor),然后再根据这个反向去渲染 Token 即可。 +- 已经知道是由 PrecompiledPreamble 负责构建预编译头文件,clangd 里面的 Preamble(Preamble.cpp) 也都是指这个,但是。一个源文件编译最多依赖一个 pch,依赖关系是线性的,但是一个 module 可以依赖多个其它的 module,依赖关系是有向无环图。 + + +TODO: +- 了解是什么 Preamble +- 了解 module 的工作机制 +- 了解 FrontendAction 的使用 +- 详细了解 Clang 前端的工作流程 +- 了解启发式代码补全的原理 +- 了解 AST 是如何被加载到内存中的 + +FIXME: +- 为每个 Token 都提供语义高亮: https://github.com/clangd/clangd/issues/1115 +- no-self-contained: https://github.com/clangd/clangd/issues/45 + +## 性能优化 + +TODO: 寻找核心优化点 +- 尽可能减少对 LLVM 源码的依赖,比如 json,hashmap 这种,提供方便换成第三方容器的接口,方便测试性能 + +一些讨论: +- 使用 LRU 缓存来优化语法树的查找与储存,参考 [Rust analyzer](https://github.com/rust-lang/rust-analyzer/pull/1382),目前 clangd 使用嵌套 vector 来处理这个问题 [ASTNode](https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/clangd/Protocol.h#L2017). +- [持久化储存](https://github.com/rust-lang/rust-analyzer/issues/4712),指定缓存目录 +- 做加法而不是做减法,默认只提供非常少的功能,以减轻内存使用负担,通过主动开启选项来提供更多功能 + +## 编制索引与缓存查询 + +TODO: +- 了解索引工作的实际调度过程 +- 了解 AST 是以何种形式被储存到磁盘中的 + +一些讨论: +- [支持离线索引](https://github.com/clangd/clangd/issues/587) +- 另外请见 https://discourse.llvm.org/t/using-background-and-static-indexes-simultaneously-for-large-codebases/3706/7 + +## 详细的支持功能列表 + +需要具体到哪些功能要做 + +比较大的特色是支持 module,这个需要重点支持,同时需要进一步阅读 LSProtocol 的文档,看看还有哪些功能需要支持。PCH 相关 ...。 + +一些可能有帮助的内容: +- 内联类型提示: https://github.com/clangd/clangd/issues/1535 +- 生成函数实现(补全父类虚函数和未实现的成员函数): https://github.com/clangd/clangd/issues/445 +- 更详细的 Token 语义提示:https://github.com/clangd/clangd/issues/1115 + + +一些必须要做的选项: +- 支持补全的时候不补全函数模板的<> +- 支持补全的时候不补全函数变量,模板变量,concept的占位符 +- 支持 markdown 注释渲染 +- 修复 quick fix 的位置问题 +- 启发式模版补全(可选) +- 控制代码补全的时机,只有光标后面没有字母的时候才补全,不要改中间的词弹补全框,烦死了(可选) + +希望支持的一些功能: +- 可视化宏展开(类似 VS 里面那个功能) +- 插件系统,支持用户编写一些简单的脚步来扩展功能(代码生成 .etc) + +# clangd 源码阅读(从 ClangMain 开始一点一点阅读) + + +## 第一阶段 + +`ClangMain(tool/ClangMain.cpp)` 里面没什么重要的内容,主要是 LLVM 的初始化和设置一些命令行 Option,主要调用了 `ClangServerLSP(ClangServerLSP.h)` 的 `run` 函数,这个 `run` 函数本身主要调用了 `Transport` 的 `loop` 函数,即开启服务器的事件循环 + +`Transport(Transport.h)` 本身是一个抽象类,通过不同的子类来实现,可以发送不同格式的消息。在 `ClangMain` 里面根据不同的情况使用不同的 `Transport` 子类,默认是 `JSONTransport(JSONTransport.cpp)`,大部分方法都没什么好看的,就是设置一些协议格式,然后发送消息(似乎是通过标准输入输出流进行通信,不过这个不重要),我们主要看 `loop` 函数。 + +`loop` 函数主要调用 `handleMessage` 这个私有方法来处理消息,这个私有方法则主要是把最后的处理交给 `MessageHandler`,`MessageHandler` 是 `Transport` 的一个成员抽象类,`ClangServerLSP` 实现了一个 `MessageHandler(ClangServerLSP.cpp-176)` 用于处理消息,对应的成员是 `MsgHandler`,在构造函数中初始化。下面主要看这个 `MsgHandler` 的处理逻辑。 + +`MsgHandler` 主要有三个函数 `onNotify`,`onCall` 和 `onReply`。分别表示 响应通知,响应请求和响应回复。三个函数的逻辑大体是相似的,都是 RPC 调用,根据函数名调用对应的函数,然后返回执行结果(找不到就报错)。接下来就主要看 `Handlers` 这个成员,他负责储存所有的处理函数。 + +`Handlers` 的类型是 `LSPBinder::RawHandlers`,在 `ClangServerLSP` 的构造函数中把 `Handlers` 这个成员绑定给了 `Bind`(`LSPBinder` 类型),`LSPBinder` 的 `method`,`notification` 和 `command` 成员函数的作用是,分别往对应的 `handler` 里面注册函数。在 `ClangServerLSP` 的构造函数中注册了 `ClangdLSPServer::onInitialize` 这个函数,可以猜测,实际的初始化工作都是在这里完成的,接下来我们主要看 `onInitialize` 这个函数的逻辑。 + +这里执行了很多初始化的逻辑,暂时没有细看,不知道都是干嘛用的。注册成员函数是在 `ClangdLSPServer` 的 `bindMethods` 方法中完成的,它注册了所有需要用到的方法,第一阶段的阅读到这里暂时结束,接下来要针对每一个模块看了。 + +## 第二阶段 + +接下来主要看`ClangServer(ClangServer.h)`这个类型,`ClangServerLSP`里面的绝大多数触发函数,只是对这个类对应函数的简单包装。先重点看一下它的`BackgroundIdx`和`TUScheduler`这两个成员。前者负责对文件进行索引,把结果储存到磁盘上。后者负责管理和加载 AST 到内存中,便于后续的操作。 + +一切的一切都从`ClangServer::addDocument`这个函数开始,处理客户端发过来的数据,然后调用`TUScheduler`的`update`函数,来加载到内存中。之后再调用`BackgroundIdx`的`boostRelated`函数,储存对应的索引文件。接下来先分析`TUScheduler`这个类。所有的 AST 都储存在 Files 这个成员变量里,它的类型是`llvm::StringMap>`,它同时有一个`ASTCache`类型的成员变量`IdleASTs`,LRU 储存一些 AST,用于快速查找。 + +`TUScheduler`的`update`函数主要创建了一个`ASTWorkerHandle`,然后调用它的`update`函数来处理这个事情。其实就是发起一个异步任务,更新对应的 AST 内容。值得注意的是 update 函数似乎都只是更新相关文件的状态,而具体的 AST 或者 Preamble 的创建则都是在第一次使用的时候,即 runWithAST 和 runWithPreamble 中。 + +TODO: 查看 runWithAST 和 runWithPreamble 的具体实现,PreambleThread 负责构建 PCH + +Clang 的 AST 的实际解析发生在 ParsedAST.cpp 的 build 函数,ParsedAST 本身会储存一个 CompilerInstance 实例。 + +!!! 原来 CodeCompletion 和 TokenCollector 不能同时获取,这也就意味着如果需要完成代码补全和语义高亮似乎需要多次遍历 AST ...... + +## 注意事项 + +注意:FeatureModule 这个东西没啥用,感觉是个废弃的功能,之后记得扔了。 + +## 一些已经解决的问题 + +SemanticHighlight:通过 SynatxOnlyAction 和 TokenCollector 可以拿到所有 Tokens,之后再遍历一遍语法树,分别处理每个语法树元素的高亮即可(可以通过 Location 查询 Token)。另外要注意,关键字高亮优先级最大,记得处理遍历语法树时候未处理完的 Token。 + diff --git a/clice.md b/clice.md new file mode 100644 index 00000000..76c37954 --- /dev/null +++ b/clice.md @@ -0,0 +1,78 @@ +`clangd.md`侧重于对 clangd 源码的分析,仅仅是分析里面的关键部分,作为我们编写代码的参考。但是最终我们是要有我们自己的架构的,所以有必要自顶向下的对项目模块进行一下规划。 + +整体上来看,clice 的模型很简单,只是一个实现了 LSP 协议的 Server,所以一般的服务器模型也适用于它。 + +## Overview + +首先整个服务器底层需要有线程池和事件循环来处理事件(异步逻辑),具体有哪些事件后文会详细讨论。我们打算使用 C++20 来编写,所以可以使用协程来简化异步代码的编写。相比于 clangd 中的回调函数套回调函数,这可以大大提高代码可维护性。 + +## Event + +现在我们要讨论有哪些事件需要处理,这里就要根据 LSP 来具体分析了。 + +### Server Lifecycle + +这里主要是处理和服务器生命周期相关的消息,比如初始化,关闭,等等。这些消息需要最优先处理。由于 cLang 上游代码时不时可能会崩溃,所以重启对于 clice 来说是比较常见的。 + +>理想情况是对线程进行隔离,一个线程的编译器挂了不影响其它的线程,这个还需要进一步研究。 + +值得注意的是 clice 希望额外支持插件功能,所以需要利用 LSP 中的 registerCapability 这个消息格式。 + +### Text Document Synchronization + +这个是和文档同步相关的消息,比如文档打开,关闭,修改等等。这个消息是最频繁的,所以需要尽可能的优化。 + +>目前 clangd 在一个文件打开的时候就会在后台发起编译这个文件的预编译头的任务,具体的策略需要进一步研究。 + + +### Language Features + +首先对 LSP 支持的功能进行概览:LSP 3.17 currently: + +- Goto Declaration:跳转到声明 +- Goto Definition:跳转到定义 +- Goto Type Definition:跳转到类型定义 +- Goto Implementation:跳转到实现 +- Find References:查找所有引用 +- Prepare Call Hierarchy:没搞懂 +- Call Hierarchy Incoming Calls:没搞懂 +- Call Hierarchy Outgoing Calls:没搞懂 +- Prepare Type Hierarchy:没搞懂 +- Type Hierarchy Supertypes:没搞懂 +- Type Hierarchy Subtypes:没搞懂 +- Document Highlights:没搞懂 +- Document Link:没搞懂 +- Document Link Resolve:没搞懂 +- Hover:悬停提示 +- Code Lens:没搞懂 +- Code Lens Refresh Request:没搞懂 +- Folding Range:把某段代码折叠起来 +- Selection Range:没搞懂 +- Document Symbols:没搞懂 +- Semantic Tokens:用于语义高亮 +- Inline Value:没搞懂 +- Inline Value Refresh:没搞懂 +- Inlay Hint:用于内嵌提示,比如函数参数或者`auto`的类型 +- Inlay Hint Resolve:没搞懂 +- Inlay Hint Refresh:刷新内嵌提示 +- Monikers:没搞懂 +- Completion:代码补全 +- Completion Item Resolve:解决重载函数的代码补全 +- PublishDiagnostics Notification:发出诊断信息 +- Pull Diagnostics:没搞懂 +- Signature Help Request:请求函数签名信息 +- Code Action:重构等操作(还有那个 quick fix) +- Code Action Resolve:没搞懂 +- Document Color:没搞懂 +- Color Presentation:没搞懂 +- Document Formatting:格式化 +- Document Range Formatting:只格式化某个部分 +- Document on Type Formatting:没搞懂 +- Rename:重命名 +- Prepare Rename:解决重命名 +- Linked Editing Range:没搞懂 + +这些任务从最终实现的角度来说可以主要分成三种: +1. CodeCompletion 这个需要利用 CodeCompletionConsumer 调用 Clang 提供的接口来实现,然而我们实际上可以做一些更加复杂的分析(clangd 目前没有做)。比如判断当前的是不是在 Template 语境下从而决定补全`sizeof`的时候要不要补全`...`。在补全成员的时候,似乎我们也可以获取`expr.f`中的父对象的类型,从而根据它的类型来做一些补全。有待进一步研究。 +2. Semantic Tokens 等基于当前 AST 的操作,则是遍历 AST 渲染 Token 即可。 +3. 剩下很多的,例如 Find References 等等等查询功能,都是在已经索引好的文件中进行查询,不需要对语法树进行什么改动。 \ No newline at end of file diff --git a/include/Clang/Clang.h b/include/Clang/Clang.h new file mode 100644 index 00000000..19600e8d --- /dev/null +++ b/include/Clang/Clang.h @@ -0,0 +1,8 @@ +#include "clang/AST/RecursiveASTVisitor.h" +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/include/Clang/Compiler.h b/include/Clang/Compiler.h new file mode 100644 index 00000000..e69de29b diff --git a/include/LSP/SemanticTokens.h b/include/LSP/SemanticTokens.h new file mode 100644 index 00000000..9c2f8377 --- /dev/null +++ b/include/LSP/SemanticTokens.h @@ -0,0 +1,47 @@ + +namespace clice { + +// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens +// The protocol defines a set of token types and modifiers but clients are +// allowed to extend these and announce the values they support in the +// corresponding client capability. +enum class SemanticTokenTypes { + Namespace, + Type, + Class, + Enum, + Interface, + Struct, + TypeParameter, + Parameter, + Variable, + Property, + EnumMember, + Event, + Function, + Method, + Macro, + Keyword, + Modifier, + Comment, + String, + Number, + Regexp, + Operator, + Decorator // @since 3.17.0 +}; + +enum SemanticTokenModifiers { + Declaration, + Definition, + Readonly, + Static, + Deprecated, + Abstract, + Async, + Modification, + Documentation, + DefaultLibrary +}; + +} // namespace clice diff --git a/include/LSP/Server.h b/include/LSP/Server.h new file mode 100644 index 00000000..e9ba041e --- /dev/null +++ b/include/LSP/Server.h @@ -0,0 +1,9 @@ +namespace clice { + +/// core class responsible for starting the server +class Server { +public: + int run(); +}; + +} // namespace clice \ No newline at end of file diff --git a/src/Clang/CodeCompletion.cpp b/src/Clang/CodeCompletion.cpp new file mode 100644 index 00000000..3fd2d244 --- /dev/null +++ b/src/Clang/CodeCompletion.cpp @@ -0,0 +1,234 @@ +#include + +namespace tooling = clang::tooling; +int line = 0; +int column = 0; +std::unique_ptr datebase; + +class CodeCompleteConsumer : public clang::CodeCompleteConsumer { +public: + std::shared_ptr Allocator; + clang::CodeCompletionTUInfo CCTUInfo; + + CodeCompleteConsumer() + : clang::CodeCompleteConsumer(clang::CodeCompleteOptions{}), + Allocator(std::make_shared()), + CCTUInfo(Allocator) {} + + void ProcessCodeCompleteResults(clang::Sema &S, + clang::CodeCompletionContext Context, + clang::CodeCompletionResult *Results, + unsigned NumResults) override { + + auto type = Context.getBaseType(); + type.dump(); + if (type->isDependentType()) { + const auto dependentType = type->getAs(); + // TODO: improve + } + + 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; + } + } + } + } + + virtual clang::CodeCompletionAllocator &getAllocator() override { + return *Allocator; + } + + virtual clang::CodeCompletionTUInfo &getCodeCompletionTUInfo() override { + return CCTUInfo; + } +}; + +auto GetCommands(std::string_view path, std::string_view compile_commands_path) + -> std::vector { + if (!datebase) { + std::string error; + datebase = tooling::CompilationDatabase::loadFromDirectory( + compile_commands_path, error); + + if (!datebase) { + llvm::errs() << "Failed to load compilation database. " << error << "\n"; + std::terminate(); + } + } + + return datebase->getCompileCommands(path); +} + +auto createDiagnostic() { + clang::DiagnosticOptions DiagOpts; + + clang::TextDiagnosticPrinter *DiagClient = + new clang::TextDiagnosticPrinter(llvm::errs(), &DiagOpts); + + llvm::IntrusiveRefCntPtr DiagID = + new clang::DiagnosticIDs(); + + return clang::DiagnosticsEngine(DiagID, &DiagOpts, DiagClient); +} + +auto createInvocation(std::string_view path, + std::string_view compile_commands) { + auto commands = GetCommands(path, compile_commands); + llvm::ArrayRef command = commands[0].CommandLine; + + std::vector args = {command.front().c_str(), "-Xclang", + "-no-round-trip-args"}; + + for (auto &arg : command.drop_front()) { + args.push_back(arg.c_str()); + } + + static auto engine = createDiagnostic(); + auto invocation = clang::createInvocation(args); + + // set input file + auto &inputs = invocation->getFrontendOpts().Inputs; + inputs.push_back( + clang::FrontendInputFile(path, clang::InputKind{clang::Language::CXX})); + + // set code completion + auto &completionAt = invocation->getFrontendOpts().CodeCompletionAt; + completionAt.FileName = path.data(); + completionAt.Line = line; + completionAt.Column = column; + + return invocation; +} + +struct DiagnosticConsumer : clang::DiagnosticConsumer { + void BeginSourceFile(const clang::LangOptions &LangOpts, + const clang::Preprocessor *PP) override {} + + void EndSourceFile() override {} + + void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) override { + if (DiagLevel == clang::DiagnosticsEngine::Level::Note) { + return; + } + + llvm::errs() << "Diagnostic: "; + llvm::SmallVector buf; + Info.FormatDiagnostic(buf); + llvm::errs().write(buf.data(), buf.size()); + llvm::errs() << "\n"; + } +}; + +auto createInstance(std::string_view path, std::string_view compile_commands) { + std::unique_ptr instance = + std::make_unique(); + + auto invocation = createInvocation(path, compile_commands); + instance->setInvocation( + std::make_shared(*invocation)); + + instance->createDiagnostics(new DiagnosticConsumer(), true); + + if (!instance->createTarget()) { + llvm::errs() << "Failed to create target\n"; + std::terminate(); + } + + if (auto manager = instance->createFileManager()) { + instance->createSourceManager(*manager); + } else { + llvm::errs() << "Failed to create file manager\n"; + std::terminate(); + } + + instance->createPreprocessor(clang::TranslationUnitKind::TU_Complete); + + instance->createASTContext(); + + instance->setCodeCompletionConsumer(new CodeCompleteConsumer()); + + return instance; +} + +class AST { + clang::FrontendAction *action; + clang::CompilerInstance *instance; + +private: + AST() = default; + +public: + AST(const AST &) = delete; + + AST(AST &&other) noexcept : action(other.action), instance(other.instance) { + other.action = nullptr; + other.instance = nullptr; + } + + ~AST() { + if (action) { + action->EndSourceFile(); + delete action; + delete instance; + } + } + + static AST create(std::string_view path, std::string_view compile_commands) { + AST ast; + + ast.instance = createInstance(path, compile_commands).release(); + ast.action = new clang::SyntaxOnlyAction(); + + const auto &input = ast.instance->getFrontendOpts().Inputs[0]; + + if (!ast.action->BeginSourceFile(*ast.instance, input)) { + llvm::errs() << "Failed to begin source file\n"; + std::terminate(); + } + + if (llvm::Error error = ast.action->Execute()) { + llvm::errs() << "Failed to execute action: " << error << "\n"; + std::terminate(); + } + + return ast; + } + + auto &getASTContext() { return instance->getASTContext(); } + + auto &getSourceManager() { return instance->getSourceManager(); } +}; + +struct Visitor : clang::RecursiveASTVisitor { + bool VisitTranslationUnitDecl(clang::TranslationUnitDecl *tu) { + tu->dump(); + return true; + } + + bool VisitCXXMethodDecl(clang::CXXMethodDecl *decl) { return true; } +}; diff --git a/src/Clang/Compiler.cpp b/src/Clang/Compiler.cpp new file mode 100644 index 00000000..139597f9 --- /dev/null +++ b/src/Clang/Compiler.cpp @@ -0,0 +1,2 @@ + + diff --git a/src/Clang/SemanticTokens.cpp b/src/Clang/SemanticTokens.cpp new file mode 100644 index 00000000..bbd12fa4 --- /dev/null +++ b/src/Clang/SemanticTokens.cpp @@ -0,0 +1,176 @@ +#include + +namespace tooling = clang::tooling; +namespace { + +std::unique_ptr datebase; + +auto GetCommands(std::string_view path, std::string_view compile_commands_path) + -> std::vector { + if (!datebase) { + std::string error; + datebase = tooling::CompilationDatabase::loadFromDirectory( + compile_commands_path, error); + + if (!datebase) { + llvm::errs() << "Failed to load compilation database. " << error << "\n"; + std::terminate(); + } + } + + return datebase->getCompileCommands(path); +} + +auto createDiagnostic() { + clang::DiagnosticOptions DiagOpts; + + clang::TextDiagnosticPrinter *DiagClient = + new clang::TextDiagnosticPrinter(llvm::errs(), &DiagOpts); + + llvm::IntrusiveRefCntPtr DiagID = + new clang::DiagnosticIDs(); + + return clang::DiagnosticsEngine(DiagID, &DiagOpts, DiagClient); +} + +auto createInvocation(std::string_view path, + std::string_view compile_commands) { + auto commands = GetCommands(path, compile_commands); + llvm::ArrayRef command = commands[0].CommandLine; + + std::vector args = {command.front().c_str(), "-Xclang", + "-no-round-trip-args"}; + + for (auto &arg : command.drop_front()) { + args.push_back(arg.c_str()); + } + + static auto engine = createDiagnostic(); + auto invocation = clang::createInvocation(args); + + // set input file + auto &inputs = invocation->getFrontendOpts().Inputs; + inputs.push_back( + clang::FrontendInputFile(path, clang::InputKind{clang::Language::CXX})); + + return invocation; +} + +struct DiagnosticConsumer : clang::DiagnosticConsumer { + void BeginSourceFile(const clang::LangOptions &LangOpts, + const clang::Preprocessor *PP) override {} + + void EndSourceFile() override {} + + void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel, + const clang::Diagnostic &Info) override { + if (DiagLevel == clang::DiagnosticsEngine::Level::Note) { + return; + } + + llvm::errs() << "Diagnostic: "; + llvm::SmallVector buf; + Info.FormatDiagnostic(buf); + llvm::errs().write(buf.data(), buf.size()); + llvm::errs() << "\n"; + } +}; + +auto createInstance(std::string_view path, std::string_view compile_commands) { + std::unique_ptr instance = + std::make_unique(); + + auto invocation = createInvocation(path, compile_commands); + instance->setInvocation( + std::make_shared(*invocation)); + + instance->createDiagnostics(new DiagnosticConsumer(), true); + + if (!instance->createTarget()) { + llvm::errs() << "Failed to create target\n"; + std::terminate(); + } + + if (auto manager = instance->createFileManager()) { + instance->createSourceManager(*manager); + } else { + llvm::errs() << "Failed to create file manager\n"; + std::terminate(); + } + + instance->createPreprocessor(clang::TranslationUnitKind::TU_Complete); + + instance->createASTContext(); + + return instance; +} + +class AST { + clang::FrontendAction *action; + clang::CompilerInstance *instance; + clang::syntax::TokenBuffer *tokens; + +private: + AST() = default; + +public: + AST(const AST &) = delete; + + AST(AST &&other) noexcept + : action(other.action), instance(other.instance), tokens(other.tokens) { + other.action = nullptr; + other.instance = nullptr; + other.tokens = nullptr; + } + + ~AST() { + if (action) { + action->EndSourceFile(); + delete action; + delete instance; + delete tokens; + } + } + + static AST create(std::string_view path, std::string_view compile_commands) { + AST ast; + + ast.instance = createInstance(path, compile_commands).release(); + ast.action = new clang::SyntaxOnlyAction(); + + const auto &input = ast.instance->getFrontendOpts().Inputs[0]; + + if (!ast.action->BeginSourceFile(*ast.instance, input)) { + llvm::errs() << "Failed to begin source file\n"; + std::terminate(); + } + + clang::syntax::TokenCollector collector = {ast.instance->getPreprocessor()}; + + if (llvm::Error error = ast.action->Execute()) { + llvm::errs() << "Failed to execute action: " << error << "\n"; + std::terminate(); + } + + ast.tokens = new auto(std::move(collector).consume()); + + return ast; + } + + auto &getASTContext() { return instance->getASTContext(); } + + auto &getSourceManager() { return instance->getSourceManager(); } + + auto getTokens() { return tokens->expandedTokens(); } +}; + +struct Visitor : clang::RecursiveASTVisitor { + bool VisitTranslationUnitDecl(clang::TranslationUnitDecl *tu) { + tu->dump(); + return true; + } + + bool VisitCXXMethodDecl(clang::CXXMethodDecl *decl) { return true; } +}; + +} // namespace \ No newline at end of file diff --git a/src/LSP/Server.cpp b/src/LSP/Server.cpp new file mode 100644 index 00000000..1e5ad235 --- /dev/null +++ b/src/LSP/Server.cpp @@ -0,0 +1,11 @@ +#include +#include + +namespace clice { + +int Server::run() { + uv_loop_t* loop = uv_default_loop(); + return 0; +} + +} // namespace clice \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000..e69de29b