diff --git a/.vscode/launch.json b/.vscode/launch.json index fa238403..b853bc52 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,68 +9,27 @@ "request": "launch", "name": "clice_socket", "program": "${workspaceFolder}/build/bin/clice", - "args": ["--config=/home/ykiko/C++/clice2/docs/clice.toml"] - }, - { - "type": "lldb", - "request": "attach", - "name": "clice_attach", - "program": "clice" + "args": [ + "--config={workspaceFolder}/docs/clice.toml" + ] }, { "type": "lldb", "request": "launch", - "name": "PCM", + "name": "clice_test", "program": "${workspaceFolder}/build/bin/clice-tests", "args": [ - "--test-dir=/home/ykiko/C++/clice2/tests", - "--gtest_filter=clice.PCM" + "--test-dir={workspaceFolder}/tests", + "${input:filter}" ], - "cwd": "${workspaceFolder}" - }, + } + ], + "inputs": [ { - "type": "lldb", - "request": "launch", - "name": "TemplateResolver", - "program": "${workspaceFolder}/build/bin/clice-tests", - "args": [ - "--test-dir=/home/ykiko/C++/clice2/tests", - "--gtest_filter=TemplateResolver.DefaultArgument" - ], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Index", - "program": "${workspaceFolder}/build/bin/clice-tests", - "args": [ - "--test-dir=/home/ykiko/C++/clice2/tests", - "--gtest_filter=Index.VarTemplate*" - ], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "ASTVisitor", - "program": "${workspaceFolder}/build/bin/clice-tests", - "args": [ - "--test-dir=/home/ykiko/C++/clice2/tests", - "--gtest_filter=clice.ASTVisitor*" - ], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "CodeCompletion", - "program": "${workspaceFolder}/build/bin/clice-tests", - "args": [ - "--test-dir=/home/ykiko/C++/clice2/tests", - "--gtest_filter=CodeCompletion.*" - ], - "cwd": "${workspaceFolder}" + "id": "filter", + "type": "promptString", + "description": "Filter for test names", + "default": "" } ] } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e4050c2..5d4eb5dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ endif() # build clice core part as library file(GLOB_RECURSE CLICE_CORE_SOURCES + "${CMAKE_SOURCE_DIR}/src/Basic/*.cpp" "${CMAKE_SOURCE_DIR}/src/Compiler/*.cpp" "${CMAKE_SOURCE_DIR}/src/Index/*.cpp" "${CMAKE_SOURCE_DIR}/src/Feature/*.cpp" diff --git a/include/Basic/Document.h b/include/Basic/Document.h new file mode 100644 index 00000000..7c28d769 --- /dev/null +++ b/include/Basic/Document.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Location.h" + +namespace clice::proto { + +struct TextDocumentItem { + /// The text document's URI. + DocumentUri uri; + + /// The text document's language identifier. + string languageId; + + /// The version number of this document (it will strictly increase after each + /// change, including undo/redo). + uinteger version; + + /// The content of the opened text document. + string text; +}; + +} // namespace clice::proto diff --git a/include/Basic/URI.h b/include/Basic/URI.h index 2a030a58..10205029 100644 --- a/include/Basic/URI.h +++ b/include/Basic/URI.h @@ -1,5 +1,36 @@ +#pragma once + +#include +#include + namespace clice { +class URI { +public: + URI(llvm::StringRef scheme, llvm::StringRef authority, llvm::StringRef path) : + m_scheme(scheme), m_authority(authority), m_body(path) {} - -} \ No newline at end of file + URI(const URI&) = default; + + bool operator== (const URI&) const = default; + + /// Returns decoded scheme e.g. "https" + llvm::StringRef scheme() const { return m_scheme; } + + /// Returns decoded authority e.g. "reviews.lvm.org" + llvm::StringRef authority() const { return m_authority; } + + /// Returns decoded body e.g. "/D41946" + llvm::StringRef body() const { return m_body; } + + static llvm::Expected parse(llvm::StringRef content); + + static std::string resolve(llvm::StringRef content); + +private: + std::string m_scheme; + std::string m_authority; + std::string m_body; +}; + +} // namespace clice diff --git a/include/Compiler/Command.h b/include/Compiler/Command.h new file mode 100644 index 00000000..1f3453f2 --- /dev/null +++ b/include/Compiler/Command.h @@ -0,0 +1,37 @@ +#pragma once + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Allocator.h" +#include "clang/Tooling/CompilationDatabase.h" + +namespace clice { + +class CommandManager { +public: + /// FIXME: support multiple compilation databases. + CommandManager(llvm::StringRef path); + + llvm::ArrayRef lookup(llvm::StringRef file); + + // TODO: add a function for scaning module dependencies. + + struct Data { + std::uint64_t index; + std::uint64_t size; + }; + +private: + std::vector args; + llvm::BumpPtrAllocator allocator; + + llvm::StringMap cache; + + /// module name -> file path + llvm::StringMap moduleMap; + + std::unique_ptr CDB; +}; + +} // namespace clice diff --git a/include/Server/LSPServer.h b/include/Server/LSPServer.h new file mode 100644 index 00000000..1417a072 --- /dev/null +++ b/include/Server/LSPServer.h @@ -0,0 +1,91 @@ +#include "Basic/URI.h" +#include "Server.h" +#include "Compiler/Compiler.h" + +namespace clice { + +namespace proto { + +struct DidOpenTextDocumentParams { + /// The document that was opened. + TextDocumentItem textDocument; +}; + +} // namespace proto + +class TranslationUnit { + using Callback = llvm::unique_function; + +public: + void addFile(llvm::StringRef uri, llvm::StringRef content) { + /// 1. build PCH or PCM + /// 2. build AST + } + +private: + bool isBuilding = false; + std::string path; + std::unique_ptr compiler; + llvm::SmallVector callbacks; +}; + +class FileManager { +public: + +private: + llvm::StringMap files; +}; + +class LSPServer : public Server { +public: + LSPServer(const config::ServerOption& option) : Server(option) { + addMethod("initialize", &LSPServer::onInitialize); + addNotification("initialized", &LSPServer::onInitialized); + addNotification("textDocument/didOpen", &LSPServer::onDidOpen); + } + + template + void addMethod(std::string_view method, R (LSPServer::*handler)(const Params&)) { + methods[method] = [this, handler](json::Value params) { + return json::serialize((this->*handler)(json::deserialize(params))); + }; + } + + template + void addNotification(std::string_view method, void (LSPServer::*handler)(const Params&)) { + notifications[method] = [this, handler](json::Value params) { + (this->*handler)(json::deserialize(params)); + }; + } + + void run() { + auto loop = [&]() { + if(hasMessage()) { + auto& json = peek(); + if(json.kind() == json::Value::Object) { + auto object = json.getAsObject(); + dispatch(std::move(json)); + } else { + llvm::errs() << "Strange message.\n"; + consume(); + } + } + }; + Server::run(loop); + } + + void dispatch(json::Value request); + +public: + auto onInitialize(const proto::InitializeParams& params) -> proto::InitializeResult; + + void onInitialized(const proto::InitializedParams& params); + + void onDidOpen(const proto::DidOpenTextDocumentParams& params); + +private: + llvm::StringMap> methods; + llvm::StringMap> notifications; +}; + +} // namespace clice diff --git a/include/Server/Server.h b/include/Server/Server.h index 1f7aa993..69858c0a 100644 --- a/include/Server/Server.h +++ b/include/Server/Server.h @@ -1,7 +1,7 @@ #pragma once #include "Server/Config.h" -#include "Basic/Basic.h" +#include "Basic/Document.h" #include "Support/JSON.h" #include "llvm/ADT/FunctionExtras.h" @@ -69,6 +69,9 @@ struct InitializeResult { }; struct InitializedParams {}; + + + } // namespace clice::proto namespace clice { @@ -94,10 +97,12 @@ public: } json::Value& peek() { + assert(!messages.empty()); return messages.front(); } void consume() { + assert(!messages.empty()); messages.erase(messages.begin()); } diff --git a/include/Support/URI.h b/include/Support/URI.h deleted file mode 100644 index 10205029..00000000 --- a/include/Support/URI.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include -#include - -namespace clice { - -class URI { -public: - URI(llvm::StringRef scheme, llvm::StringRef authority, llvm::StringRef path) : - m_scheme(scheme), m_authority(authority), m_body(path) {} - - URI(const URI&) = default; - - bool operator== (const URI&) const = default; - - /// Returns decoded scheme e.g. "https" - llvm::StringRef scheme() const { return m_scheme; } - - /// Returns decoded authority e.g. "reviews.lvm.org" - llvm::StringRef authority() const { return m_authority; } - - /// Returns decoded body e.g. "/D41946" - llvm::StringRef body() const { return m_body; } - - static llvm::Expected parse(llvm::StringRef content); - - static std::string resolve(llvm::StringRef content); - -private: - std::string m_scheme; - std::string m_authority; - std::string m_body; -}; - -} // namespace clice diff --git a/src/Basic/URI.cpp b/src/Basic/URI.cpp index 9cf87203..10e5933e 100644 --- a/src/Basic/URI.cpp +++ b/src/Basic/URI.cpp @@ -1 +1,79 @@ -#include "Basic/URI.h" \ No newline at end of file +#include + +#include +#include + +namespace clice { + +/// returns true if the scheme is valid according to RFC 3986. +bool isValidScheme(llvm::StringRef scheme) { + if(scheme.empty()) { + return false; + } + + if(!llvm::isAlpha(scheme[0])) { + return false; + } + + return llvm::all_of(llvm::drop_begin(scheme), [](char C) { + return llvm::isAlnum(C) || C == '+' || C == '.' || C == '-'; + }); +} + +/// decodes a string according to percent-encoding, e.g., "a%20b" -> "a b". +static std::string decodePercent(llvm::StringRef content) { + std::string result; + for(auto iter = content.begin(), sent = content.end(); iter != sent; ++iter) { + auto c = *iter; + if(c == '%' && iter + 2 < sent) { + auto m = *(iter + 1); + auto n = *(iter + 2); + if(llvm::isHexDigit(m) && llvm::isHexDigit(n)) { + result += llvm::hexFromNibbles(m, n); + iter += 2; + continue; + } + } + result += c; + } + return result; +} + +llvm::Expected URI::parse(llvm::StringRef content) { + URI result("", "", ""); + llvm::StringRef uri = content; + auto pos = uri.find(':'); + if(pos == llvm::StringRef::npos) { + return error("scheme is missing in URI: {}", content); + } else { + result.m_scheme = uri.substr(0, pos); + if(!isValidScheme(result.m_scheme)) { + return error("invalid scheme in URI: {}", content); + } + uri = uri.substr(pos + 1); + } + + if(uri.consume_front("//")) { + pos = uri.find('/'); + result.m_authority = uri.substr(0, pos); + uri = uri.substr(pos); + } + + result.m_body = decodePercent(uri); + + return result; +} + +std::string URI::resolve(llvm::StringRef content) { + auto uri = parse(content); + if(!uri) { + std::terminate(); + } + llvm::SmallString<128> result; + if(auto err = fs::real_path(uri->body(), result)) { + std::terminate(); + } + return result.str().str(); +} + +} // namespace clice diff --git a/src/Compiler/Command.cpp b/src/Compiler/Command.cpp new file mode 100644 index 00000000..8a28ee93 --- /dev/null +++ b/src/Compiler/Command.cpp @@ -0,0 +1,57 @@ +#include "Compiler/Command.h" +#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" + +namespace clice { + +namespace clice {} + +CommandManager::CommandManager(llvm::StringRef path) { + std::string error; + CDB = clang::tooling::CompilationDatabase::loadFromDirectory(path, error); + if(!CDB) { + llvm::errs() << "Failed to load compilation database: " << error << "\n"; + return; + } + + /// FIXME: module support + // using namespace clang::tooling::dependencies; + // DependencyScanningService service(ScanningMode::DependencyDirectivesScan, + // ScanningOutputFormat::P1689); + // DependencyScanningTool tool(service); + // + // for(auto& command: CDB->getAllCompileCommands()) { + // auto rule = tool.getP1689ModuleDependencyFile(command, command.Directory); + // if(rule) { + // moduleMap[rule->Provides->ModuleName] = command.Filename; + // } else { + // llvm::errs() << std::format("Failed to scan module dependencies for {}, Because: + // {}\n", + // command.Filename, + // llvm::toString(rule.takeError())); + // } + //} +} + +llvm::ArrayRef CommandManager::lookup(llvm::StringRef file) { + auto iter = cache.find(file); + if(iter != cache.end()) { + return llvm::ArrayRef(args).slice(iter->second.index, iter->second.size); + } + + auto commands = CDB->getCompileCommands(file); + assert(commands.size() == 1); + auto& command = commands[0]; + + std::size_t index = args.size(); + for(auto& arg: command.CommandLine) { + auto data = allocator.Allocate(arg.size() + 1); + std::copy(arg.begin(), arg.end(), data); + data[arg.size()] = '\0'; + args.push_back(data); + } + + cache.try_emplace(file, Data{index, args.size() - index}); + return llvm::ArrayRef(args).slice(index, args.size() - index); +} + +} // namespace clice diff --git a/src/Server/LSPServer.cpp b/src/Server/LSPServer.cpp new file mode 100644 index 00000000..6365b1e6 --- /dev/null +++ b/src/Server/LSPServer.cpp @@ -0,0 +1,26 @@ +#include "Server/LSPServer.h" + +namespace clice { + +void LSPServer::dispatch(json::Value request) { + assert(request.kind() == json::Value::Object); + auto object = request.getAsObject(); + + if(auto method = object->get("method")) { + assert(method->kind() == json::Value::String); + auto name = method->getAsString(); + if(auto iter = methods.find(*name); iter != methods.end()) { + response(std::move(*object->get("id")), + iter->second(std::move(*object->get("params")))); + consume(); + } else if(auto iter = notifications.find(*name); iter != notifications.end()) { + iter->second(std::move(*object->get("params"))); + consume(); + } else { + llvm::errs() << "Unknown method: " << *name << "\n"; + consume(); + } + } +} + +} // namespace clice diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index ba991233..1017825d 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -59,15 +59,20 @@ Server::Server(const config::ServerOption& option) { }); static auto on_alloc = +[](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { - static llvm::SmallString<4096> buffer; + /// This function is called synchronously before `on_read`. See the implementation of + /// `uv__read` in libuv/src/unix/stream.c. So it is safe to use a static buffer here. + static llvm::SmallString<65536> buffer; buffer.resize_for_overwrite(suggested_size); buf->base = buffer.data(); buf->len = suggested_size; }; static auto on_read = [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { + /// We have at most one connection and use default event loop. So there is no data race + /// risk. It is safe to use a static buffer here. + /// FIXME: use a more efficient data structure. + static MessageBuffer messageBuffer; if(nread > 0) { - static MessageBuffer messageBuffer; messageBuffer.append({buf->base, static_cast(nread)}); if(auto message = messageBuffer.peek(); !message.empty()) { auto& server = *static_cast(stream->data); diff --git a/src/Server/Server2.cpp b/src/Server/Server2.cpp deleted file mode 100644 index 0388289d..00000000 --- a/src/Server/Server2.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "llvm/ADT/SmallVector.h" -#include -#include -#include -#include -#include -#include - -namespace clice { - -uv_loop_t* loop = uv_default_loop(); - -template -struct awaiter { - - awaiter(Func&& func) : func(std::forward(func)) {} - - bool await_ready() noexcept { - return false; - } - - void await_suspend(std::coroutine_handle<> h) noexcept { - request.data = this; - uv_queue_work( - loop, - &request, - [](uv_work_t* req) { - auto* self = static_cast(req->data); - self->result = self->func(); - }, - [](uv_work_t* req, int status) { - auto* self = static_cast(req->data); - self->coro.resume(); - }); - } - - decltype(auto) await_resume() { - return std::move(*result); - } - - Func func; - uv_work_t request; - std::coroutine_handle<> coro; - std::optional result; -}; - -template -struct Result { - template - void return_value(T&& value) noexcept {} -}; - -template <> -struct Result { - void return_void() noexcept {} -}; - -template -struct Task { - struct promise_type; - - using handle = std::coroutine_handle; - - struct promise_type : Result { - auto get_return_object() noexcept { - return Task{handle::from_promise(*this)}; - } - - std::suspend_always initial_suspend() noexcept { - return {}; - } - - auto final_suspend() noexcept { - struct final_awaiter { - std::coroutine_handle<> caller; - - bool await_ready() noexcept { - return false; - } - - void await_suspend(std::coroutine_handle<> h) noexcept { - h.destroy(); - caller.resume(); - } - - void await_resume() noexcept {} - }; - - return final_awaiter{caller}; - } - - void unhandled_exception() noexcept { - std::terminate(); - } - - std::coroutine_handle<> caller; - }; - - bool await_ready() noexcept { - return false; - } - - void await_suspend(std::coroutine_handle<> caller) noexcept { - h.promise().caller = caller; - h.resume(); - } - - decltype(auto) await_resume() {} - - handle h; -}; - -template -auto run(Func&& func) { - return awaiter{std::forward(func)}; -} - -Task<> test() { - co_await run([] { - printf("Hello from thread pool.\n"); - return 1; - }); - printf("Thread pool work completed.\n"); -} - -Task<> test2() { - co_await test(); -} - -} // namespace clice - diff --git a/src/Support/URI.cpp b/src/Support/URI.cpp deleted file mode 100644 index 47fa09f5..00000000 --- a/src/Support/URI.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include -#include -#include -namespace clice { - -/// returns true if the scheme is valid according to RFC 3986. -bool isValidScheme(llvm::StringRef scheme) { - if(scheme.empty()) { - return false; - } - - if(!llvm::isAlpha(scheme[0])) { - return false; - } - - return llvm::all_of(llvm::drop_begin(scheme), [](char C) { - return llvm::isAlnum(C) || C == '+' || C == '.' || C == '-'; - }); -} - -/// decodes a string according to percent-encoding, e.g., "a%20b" -> "a b". -static std::string decodePercent(llvm::StringRef content) { - std::string result; - for(auto iter = content.begin(), sent = content.end(); iter != sent; ++iter) { - auto c = *iter; - if(c == '%' && iter + 2 < sent) { - auto m = *(iter + 1); - auto n = *(iter + 2); - if(llvm::isHexDigit(m) && llvm::isHexDigit(n)) { - result += llvm::hexFromNibbles(m, n); - iter += 2; - continue; - } - } - result += c; - } - return result; -} - -llvm::Expected URI::parse(llvm::StringRef content) { - URI result("", "", ""); - llvm::StringRef uri = content; - auto pos = uri.find(':'); - if(pos == llvm::StringRef::npos) { - return error("scheme is missing in URI: {}", content); - } else { - result.m_scheme = uri.substr(0, pos); - if(!isValidScheme(result.m_scheme)) { - return error("invalid scheme in URI: {}", content); - } - uri = uri.substr(pos + 1); - } - - if(uri.consume_front("//")) { - pos = uri.find('/'); - result.m_authority = uri.substr(0, pos); - uri = uri.substr(pos); - } - - result.m_body = decodePercent(uri); - - return result; -} - -std::string URI::resolve(llvm::StringRef content) { - auto uri = parse(content); - if(!uri) { - std::terminate(); - } - llvm::SmallString<128> result; - if(auto err = fs::real_path(uri->body(), result)) { - std::terminate(); - } - return result.str().str(); -} - -} // namespace clice diff --git a/src/main.cpp b/src/main.cpp index bc6c2e23..05ce9a07 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,7 @@ +#include "Basic/URI.h" #include "Server/Server.h" #include "llvm/Support/CommandLine.h" -namespace clice {} - using namespace clice; int main(int argc, const char** argv) { @@ -18,28 +17,7 @@ int main(int argc, const char** argv) { config::parse(argv[0], config); - Server server(config::server()); - auto loop = [&server]() { - if(server.hasMessage()) { - auto& json = server.peek(); - assert(json.kind() == json::Value::Object); - auto object = json.getAsObject(); - - if(auto method = object->get("method")) { - assert(method->kind() == json::Value::String); - auto name = method->getAsString(); - if(name == "initialize") { - assert(object->get("params")); - auto params = - json::deserialize(*object->get("params")); - llvm::outs() << "Initialize: " << params.workspaceFolders[0].uri << "\n"; - server.response(std::move(*object->get("id")), - json::serialize(proto::InitializeResult{})); - server.consume(); - } - } - } - }; - server.run(loop); return 0; } + + diff --git a/tests/ASTVisitor/test.cpp b/tests/ASTVisitor/test.cpp index e69de29b..f43bfcc7 100644 --- a/tests/ASTVisitor/test.cpp +++ b/tests/ASTVisitor/test.cpp @@ -0,0 +1,16 @@ +#include + +template +std::enable_if_t, int> foo() {} + +template +std::enable_if_t, int> foo() {} + +int foo(); + +int main() { + foo(); + foo(); + foo(); + return 0; +} diff --git a/tests/compile_commands.json b/tests/compile_commands.json new file mode 100644 index 00000000..a2370836 --- /dev/null +++ b/tests/compile_commands.json @@ -0,0 +1,12 @@ +[ + { + "directory": "/path/to/project", + "command": "clang++ -Iinclude -std=c++17 -o file.o -c file.cpp", + "file": "file.cpp" + }, + { + "directory": "/path/to/project", + "command": "clang++ -Iinclude -std=c++17 -o another_file.o -c another_file.cpp", + "file": "another_file.cpp" + } +] \ No newline at end of file diff --git a/unittests/AST/Command.cpp b/unittests/AST/Command.cpp new file mode 100644 index 00000000..03f63457 --- /dev/null +++ b/unittests/AST/Command.cpp @@ -0,0 +1,15 @@ +#include "../Test.h" +#include "Compiler/Command.h" + +namespace { + +TEST(clice, CommandManager) { + clice::CommandManager manager("/home/ykiko/C++/clice2/tests"); + auto args = manager.lookup("another_file.cpp"); + for(auto arg: args) { + llvm::outs() << arg << " "; + } + llvm::outs() << "\n"; +} + +} // namespace diff --git a/unittests/Support/URI.cpp b/unittests/Support/URI.cpp index e494ab5c..4a1fc148 100644 --- a/unittests/Support/URI.cpp +++ b/unittests/Support/URI.cpp @@ -1,5 +1,5 @@ #include -#include +#include namespace {