init project.

This commit is contained in:
ykiko
2024-06-30 14:08:54 +08:00
parent b8e2946fe6
commit c46ea4ac19
15 changed files with 753 additions and 0 deletions

4
.gitignore vendored
View File

@@ -30,3 +30,7 @@
*.exe
*.out
*.app
.cache/
build/
external/

6
.sh Executable file
View File

@@ -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

16
.vscode/launch.json vendored Normal file
View File

@@ -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}"
}
]
}

54
CMakeLists.txt Normal file
View File

@@ -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
)

108
clangd.md Normal file
View File

@@ -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 源码的依赖,比如 jsonhashmap 这种,提供方便换成第三方容器的接口,方便测试性能
一些讨论:
- 使用 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<std::unique_ptr<FileData>>`,它同时有一个`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。

78
clice.md Normal file
View File

@@ -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 等等等查询功能,都是在已经索引好的文件中进行查询,不需要对语法树进行什么改动。

8
include/Clang/Clang.h Normal file
View File

@@ -0,0 +1,8 @@
#include "clang/AST/RecursiveASTVisitor.h"
#include <clang/Basic/Diagnostic.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/FrontendActions.h>
#include <clang/Frontend/TextDiagnosticPrinter.h>
#include <clang/Sema/Sema.h>
#include <clang/Tooling/CompilationDatabase.h>
#include <clang/Tooling/Syntax/Tokens.h>

0
include/Clang/Compiler.h Normal file
View File

View File

@@ -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

9
include/LSP/Server.h Normal file
View File

@@ -0,0 +1,9 @@
namespace clice {
/// core class responsible for starting the server
class Server {
public:
int run();
};
} // namespace clice

View File

@@ -0,0 +1,234 @@
#include <Clang/Clang.h>
namespace tooling = clang::tooling;
int line = 0;
int column = 0;
std::unique_ptr<tooling::CompilationDatabase> datebase;
class CodeCompleteConsumer : public clang::CodeCompleteConsumer {
public:
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
clang::CodeCompletionTUInfo CCTUInfo;
CodeCompleteConsumer()
: clang::CodeCompleteConsumer(clang::CodeCompleteOptions{}),
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
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<clang::DependentNameType>();
// 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<tooling::CompileCommand> {
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<clang::DiagnosticIDs> 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<const char *> 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<char> 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<clang::CompilerInstance> instance =
std::make_unique<clang::CompilerInstance>();
auto invocation = createInvocation(path, compile_commands);
instance->setInvocation(
std::make_shared<clang::CompilerInvocation>(*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<Visitor> {
bool VisitTranslationUnitDecl(clang::TranslationUnitDecl *tu) {
tu->dump();
return true;
}
bool VisitCXXMethodDecl(clang::CXXMethodDecl *decl) { return true; }
};

2
src/Clang/Compiler.cpp Normal file
View File

@@ -0,0 +1,2 @@

View File

@@ -0,0 +1,176 @@
#include <Clang/Clang.h>
namespace tooling = clang::tooling;
namespace {
std::unique_ptr<tooling::CompilationDatabase> datebase;
auto GetCommands(std::string_view path, std::string_view compile_commands_path)
-> std::vector<tooling::CompileCommand> {
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<clang::DiagnosticIDs> 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<const char *> 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<char> 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<clang::CompilerInstance> instance =
std::make_unique<clang::CompilerInstance>();
auto invocation = createInvocation(path, compile_commands);
instance->setInvocation(
std::make_shared<clang::CompilerInvocation>(*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<Visitor> {
bool VisitTranslationUnitDecl(clang::TranslationUnitDecl *tu) {
tu->dump();
return true;
}
bool VisitCXXMethodDecl(clang::CXXMethodDecl *decl) { return true; }
};
} // namespace

11
src/LSP/Server.cpp Normal file
View File

@@ -0,0 +1,11 @@
#include <uv.h>
#include <LSP/Server.h>
namespace clice {
int Server::run() {
uv_loop_t* loop = uv_default_loop();
return 0;
}
} // namespace clice

0
src/main.cpp Normal file
View File