diff --git a/include/Server/Async.h b/include/Server/Async.h index ecea251b..2910ad3e 100644 --- a/include/Server/Async.h +++ b/include/Server/Async.h @@ -27,22 +27,38 @@ struct promise; namespace clice::async { +extern uv_loop_t* loop; + +template +T& uv_cast(U* u) { + assert(u->data && "uv_cast: invalid uv handle"); + return *static_cast(u->data); +} + +using Callback = llvm::unique_function(json::Value)>; + +void start_server(Callback callback); + +void start_server(Callback callback, const char* ip, unsigned int port); + +void write(json::Value id, json::Value result); + template -struct Result { +struct result { union { Value value; }; - Result() {} + result() {} - ~Result() {} + ~result() {} bool await_ready() noexcept { return false; } decltype(auto) await_resume() noexcept { - return value; + return std::move(value); } template @@ -52,7 +68,7 @@ struct Result { }; template <> -struct Result { +struct result { bool await_ready() noexcept { return false; } @@ -62,14 +78,6 @@ struct Result { void return_void() noexcept {} }; -template -T& uv_cast(U* u) { - assert(u->data && "uv_cast: invalid uv handle"); - return *static_cast(u->data); -} - -inline uv_loop_t* loop = uv_default_loop(); - /// Schedule a coroutine to run in the event loop. inline void schedule(std::coroutine_handle<> handle) { assert(handle && "schedule: invalid coroutine handle"); @@ -96,7 +104,7 @@ auto schedule_task(Task&& task) { static_assert(!std::is_reference_v, "return type must not be a reference"); - struct Awaiter : Result { + struct Awaiter : result { Func func; std::coroutine_handle<> caller; @@ -115,7 +123,7 @@ auto schedule_task(Task&& task) { } }, [](uv_work_t* work, int status) { - auto awaiter = uv_cast(work); + auto& awaiter = uv_cast(work); awaiter.caller.resume(); delete work; }); @@ -155,26 +163,11 @@ inline auto sleep(std::chrono::milliseconds ms) { return Awaiter{ms}; } -struct FinalAwaiter { - std::coroutine_handle<> caller; - - bool await_ready() noexcept { - return false; - } - - void await_suspend(std::coroutine_handle<> self) noexcept { - self.destroy(); - /// If this coroutine is a top-level coroutine, its caller is empty. - if(!caller) { - return; - } - - /// Schedule the caller to run in the event loop. - schedule(caller); - } - - void await_resume() noexcept {} -}; +template +int run(Ps&&... ps) { + (schedule(std::forward(ps)), ...); + return uv_run(uv_default_loop(), UV_RUN_DEFAULT); +} } // namespace clice::async @@ -183,7 +176,7 @@ namespace clice { template class promise { public: - struct promise_type : async::Result { + struct promise_type : async::result { std::coroutine_handle<> caller; auto get_return_object() { @@ -199,7 +192,28 @@ public: } auto final_suspend() noexcept { - return async::FinalAwaiter{.caller = caller}; + struct FinalAwaiter { + std::coroutine_handle<> caller; + + bool await_ready() noexcept { + return false; + } + + void await_suspend(std::coroutine_handle<> self) noexcept { + self.destroy(); + /// If this coroutine is a top-level coroutine, its caller is empty. + if(!caller) { + return; + } + + /// Schedule the caller to run in the event loop. + async::schedule(caller); + } + + void await_resume() noexcept {} + }; + + return FinalAwaiter{.caller = caller}; } }; @@ -234,68 +248,5 @@ private: coroutine_handle h; }; -template -class Future { -public: - bool await_ready() { - return false; - } - - void setReady() { - isReady = true; - if(isReady) { - for(auto handle: handles) { - handle.resume(); - } - handles.clear(); - } - } - - void await_suspend(std::coroutine_handle<> handle) { - handles.emplace_back(handle); - if(isReady) { - for(auto handle: handles) { - handle.resume(); - } - handles.clear(); - } - } - - void await_resume() {} - -private: - bool isReady = false; - llvm::SmallVector, 6> handles; -}; - -template -int run(Ps&&... ps) { - (schedule(std::forward(ps)), ...); - return uv_run(uv_default_loop(), UV_RUN_DEFAULT); -} - -struct Writer { - void write(json::Value id, json::Value result); - -public: - void* handle; -}; - -struct Server { -public: - using Callback = llvm::unique_function(json::Value, Writer&)>; - - Server(Callback callback); - - Server(Callback callback, const char* ip, unsigned int port); - - void run() { - uv_run(async::loop, UV_RUN_DEFAULT); - } - -public: - Writer writer; - Callback callback; -}; - } // namespace clice + diff --git a/include/Server/Server.h b/include/Server/Server.h index c965ed20..130b5dcb 100644 --- a/include/Server/Server.h +++ b/include/Server/Server.h @@ -5,6 +5,7 @@ #include "Basic/Document.h" #include "Compiler/Compiler.h" #include "Support/JSON.h" +#include "Support/FileSystem.h" #include "Feature/Lookup.h" #include "Feature/DocumentHighlight.h" @@ -122,54 +123,53 @@ struct DidCloseTextDocumentParams { namespace clice { -class CacheManager { +class Server { public: - promise buildPCH(std::string file, std::string content); + Server() { + addMethod("initialize", &Server::onInitialize); + addMethod("initialized", &Server::onInitialized); + addMethod("shutdown", &Server::onShutdown); + addMethod("exit", &Server::onExit); -private: - std::string outDir; -}; + addMethod("textDocument/didOpen", &Server::onDidOpen); + addMethod("textDocument/didChange", &Server::onDidChange); + addMethod("textDocument/didSave", &Server::onDidSave); + addMethod("textDocument/didClose", &Server::onDidClose); -class LSPServer { -public: - LSPServer() { - addMethod("initialize", &LSPServer::onInitialize); - addMethod("initialized", &LSPServer::onInitialized); - addMethod("shutdown", &LSPServer::onShutdown); - addMethod("exit", &LSPServer::onExit); - - addMethod("textDocument/didOpen", &LSPServer::onDidOpen); - addMethod("textDocument/didChange", &LSPServer::onDidChange); - addMethod("textDocument/didSave", &LSPServer::onDidSave); - addMethod("textDocument/didClose", &LSPServer::onDidClose); - - addMethod("textDocument/declaration", &LSPServer::onGotoDeclaration); - addMethod("textDocument/definition", &LSPServer::onGotoDefinition); - addMethod("textDocument/typeDefinition", &LSPServer::onGotoTypeDefinition); - addMethod("textDocument/implementation", &LSPServer::onGotoImplementation); - addMethod("textDocument/references", &LSPServer::onFindReferences); - addMethod("textDocument/callHierarchy/prepare", &LSPServer::onPrepareCallHierarchy); - addMethod("textDocument/callHierarchy/incomingCalls", &LSPServer::onIncomingCall); - addMethod("textDocument/callHierarchy/outgoingCalls", &LSPServer::onOutgoingCall); - addMethod("textDocument/typeHierarchy/prepare", &LSPServer::onPrepareTypeHierarchy); - addMethod("textDocument/typeHierarchy/supertypes", &LSPServer::onSupertypes); - addMethod("textDocument/typeHierarchy/subtypes", &LSPServer::onSubtypes); - addMethod("textDocument/documentHighlight", &LSPServer::onDocumentHighlight); - addMethod("textDocument/documentLink", &LSPServer::onDocumentLink); - addMethod("textDocument/hover", &LSPServer::onHover); - addMethod("textDocument/codeLens", &LSPServer::onCodeLens); - addMethod("textDocument/foldingRange", &LSPServer::onFoldingRange); - addMethod("textDocument/documentSymbol", &LSPServer::onDocumentSymbol); - addMethod("textDocument/semanticTokens", &LSPServer::onSemanticTokens); - addMethod("textDocument/inlayHint", &LSPServer::onInlayHint); - addMethod("textDocument/completion", &LSPServer::onCodeCompletion); - addMethod("textDocument/signatureHelp", &LSPServer::onSignatureHelp); - addMethod("textDocument/codeAction", &LSPServer::onCodeAction); - addMethod("textDocument/formatting", &LSPServer::onFormatting); - addMethod("textDocument/rangeFormatting", &LSPServer::onRangeFormatting); + addMethod("textDocument/declaration", &Server::onGotoDeclaration); + addMethod("textDocument/definition", &Server::onGotoDefinition); + addMethod("textDocument/typeDefinition", &Server::onGotoTypeDefinition); + addMethod("textDocument/implementation", &Server::onGotoImplementation); + addMethod("textDocument/references", &Server::onFindReferences); + addMethod("textDocument/callHierarchy/prepare", &Server::onPrepareCallHierarchy); + addMethod("textDocument/callHierarchy/incomingCalls", &Server::onIncomingCall); + addMethod("textDocument/callHierarchy/outgoingCalls", &Server::onOutgoingCall); + addMethod("textDocument/typeHierarchy/prepare", &Server::onPrepareTypeHierarchy); + addMethod("textDocument/typeHierarchy/supertypes", &Server::onSupertypes); + addMethod("textDocument/typeHierarchy/subtypes", &Server::onSubtypes); + addMethod("textDocument/documentHighlight", &Server::onDocumentHighlight); + addMethod("textDocument/documentLink", &Server::onDocumentLink); + addMethod("textDocument/hover", &Server::onHover); + addMethod("textDocument/codeLens", &Server::onCodeLens); + addMethod("textDocument/foldingRange", &Server::onFoldingRange); + addMethod("textDocument/documentSymbol", &Server::onDocumentSymbol); + addMethod("textDocument/semanticTokens", &Server::onSemanticTokens); + addMethod("textDocument/inlayHint", &Server::onInlayHint); + addMethod("textDocument/completion", &Server::onCodeCompletion); + addMethod("textDocument/signatureHelp", &Server::onSignatureHelp); + addMethod("textDocument/codeAction", &Server::onCodeAction); + addMethod("textDocument/formatting", &Server::onFormatting); + addMethod("textDocument/rangeFormatting", &Server::onRangeFormatting); } - promise dispatch(json::Value value, Writer& writer); + promise dispatch(json::Value value); + + void run(int argc, const char** argv) { + auto loop = [this](json::Value value) -> promise { + co_await dispatch(std::move(value)); + }; + async::start_server(loop, "127.0.0.1", 50051); + } private: using onRequest = llvm::unique_function(json::Value, json::Value)>; @@ -177,7 +177,7 @@ private: template void addMethod(llvm::StringRef name, - promise (LSPServer::*method)(json::Value, const Param&)) { + promise (Server::*method)(json::Value, const Param&)) { requests.try_emplace(name, [this, method](json::Value id, json::Value value) -> promise { co_await (this->*method)(std::move(id), @@ -186,7 +186,7 @@ private: } template - void addMethod(llvm::StringRef name, promise (LSPServer::*method)(const Param&)) { + void addMethod(llvm::StringRef name, promise (Server::*method)(const Param&)) { notifications.try_emplace(name, [this, method](json::Value value) -> promise { co_await (this->*method)(json::deserialize(value)); }); @@ -275,7 +275,73 @@ private: const proto::DocumentRangeFormattingParams& params); private: - Writer* writer; + /// Information of building precompiled header. + struct PCH { + /// The path of this PCH. + std::string path; + /// The source file path. + std::string sourcePath; + /// The header part of source file used to build this PCH. + std::string preamble; + /// The arguments used to build this PCH. + std::string arguments; + /// All files involved in building this PCH(excluding the source file). + std::vector deps; + + /// FIXME: use asyncronous file system API. + bool needUpdate(llvm::StringRef sourceContent) { + /// Check whether the header part changed. + if(sourceContent.substr(0, preamble.size()) != preamble) { + return true; + } + + /// Check timestamp of all files involved in building this PCH. + fs::file_status build; + if(auto error = fs::status(path, build)) { + llvm::errs() << "Error: " << error.message() << "\n"; + std::terminate(); + } + + /// TODO: check whether deps changed through comparing timestamps. + return false; + } + }; + + /// Information of building precompiled module. + struct PCM {}; + + promise updatePCH() { + co_return; + } + + promise updatePCM() { + co_return; + } + + promise buildAST(llvm::StringRef filepath, llvm::StringRef content) { + llvm::SmallString<128> path = filepath; + + /// FIXME: lookup from CDB file and adjust and remove unnecessary arguments. + llvm::SmallVector args = { + "clang++", + "-std=c++20", + path.c_str(), + "-resource-dir", + "/home/ykiko/C++/clice2/build/lib/clang/20", + }; + + /// through arguments to judge is it a module. + bool isModule = false; + co_await (isModule ? updatePCM() : updatePCH()); + + auto compiler = co_await async::schedule_task([=]() { + std::unique_ptr compiler = std::make_unique(path, content, args); + compiler->buildAST(); + return compiler; + }); + } + +private: llvm::StringMap requests; llvm::StringMap notifications; }; diff --git a/src/Server/Async.cpp b/src/Server/Async.cpp index dfd3794e..c21d1067 100644 --- a/src/Server/Async.cpp +++ b/src/Server/Async.cpp @@ -2,11 +2,17 @@ #include "llvm/ADT/SmallString.h" -namespace clice { +namespace clice::async { + +using Callback = llvm::unique_function(json::Value)>; +uv_loop_t* loop = uv_default_loop(); namespace { -static void onAlloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { +Callback callback = {}; +uv_stream_t* writer = {}; + +void on_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { /// 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; @@ -45,7 +51,7 @@ private: llvm::SmallString<4096> buffer; }; -void onRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { +void 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. @@ -54,9 +60,8 @@ void onRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { if(nread > 0) { buffer.append({buf->base, static_cast(nread)}); if(auto message = buffer.peek(); !message.empty()) { - auto& server = *static_cast(stream->data); if(auto json = json::parse(message)) { - async::schedule(server.callback(std::move(*json), server.writer)); + async::schedule(callback(std::move(*json))); buffer.consume(); } else { llvm::errs() << "JSON PARSE ERROR " << json.takeError() << "\n"; @@ -72,34 +77,28 @@ void onRead(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { } // namespace -Server::Server(Callback callback) : callback(std::move(callback)) { +void start_server(Callback callback) { static uv_pipe_t in; static uv_pipe_t out; - writer.handle = &out; + async::callback = std::move(callback); + writer = reinterpret_cast(&out); - uv_pipe_init(async::loop, &in, 0); - uv_pipe_init(async::loop, &out, 0); + int r = uv_read_start((uv_stream_t*)&in, async::on_alloc, async::on_read); - in.data = this; - out.data = this; - - int r = uv_read_start((uv_stream_t*)&in, onAlloc, onRead); + uv_run(async::loop, UV_RUN_DEFAULT); } -Server::Server(Callback callback, const char* ip, unsigned int port) : - callback(std::move(callback)) { +void start_server(Callback callback, const char* ip, unsigned int port) { static uv_tcp_t server; static uv_tcp_t client; - writer.handle = &client; + async::callback = std::move(callback); + writer = reinterpret_cast(&client); uv_tcp_init(async::loop, &server); uv_tcp_init(async::loop, &client); - server.data = this; - client.data = this; - struct sockaddr_in addr; uv_ip4_addr(ip, port, &addr); @@ -112,14 +111,16 @@ Server::Server(Callback callback, const char* ip, unsigned int port) : if(uv_accept(server, (uv_stream_t*)&client) == 0) { llvm::errs() << "Client connected.\n"; - uv_read_start((uv_stream_t*)&client, onAlloc, onRead); + uv_read_start((uv_stream_t*)&client, async::on_alloc, async::on_read); } else { uv_close((uv_handle_t*)&client, NULL); } }); + + uv_run(async::loop, UV_RUN_DEFAULT); } -void Writer::write(json::Value id, json::Value result) { +void write(json::Value id, json::Value result) { json::Value response = json::Object{ {"jsonrpc", "2.0" }, {"id", id }, @@ -156,11 +157,11 @@ void Writer::write(json::Value id, json::Value result) { delete req; }; - int r = uv_write(req, static_cast(handle), bufs, 2, on_write); + int r = uv_write(req, writer, bufs, 2, on_write); if(r) { llvm::errs() << "Write error: " << uv_strerror(r) << "\n"; } } -} // namespace clice +} // namespace clice::async diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index 5dc97f7d..e39568f9 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -3,34 +3,29 @@ namespace clice { -promise CacheManager::buildPCH(std::string file, std::string content) { - - co_return; -} - -promise LSPServer::onInitialize(json::Value id, const proto::InitializeParams& params) { +promise Server::onInitialize(json::Value id, const proto::InitializeParams& params) { llvm::outs() << "onInitialize\n"; - writer->write(std::move(id), json::serialize(proto::InitializeResult())); + async::write(std::move(id), json::serialize(proto::InitializeResult())); async::sleep(std::chrono::seconds(10)); co_return; } -promise LSPServer::onInitialized(const proto::InitializedParams& params) { +promise Server::onInitialized(const proto::InitializedParams& params) { llvm::outs() << "onInitialized\n"; co_return; } -promise LSPServer::onExit(const proto::None&) { +promise Server::onExit(const proto::None&) { llvm::outs() << "onExit\n"; co_return; } -promise LSPServer::onShutdown(json::Value id, const proto::None&) { +promise Server::onShutdown(json::Value id, const proto::None&) { llvm::outs() << "onShutdown\n"; co_return; } -promise LSPServer::onDidOpen(const proto::DidOpenTextDocumentParams& params) { +promise Server::onDidOpen(const proto::DidOpenTextDocumentParams& params) { llvm::outs() << "onDidOpen: " << params.textDocument.uri << "\n"; auto path = URI::resolve(params.textDocument.uri); llvm::StringRef content = params.textDocument.text; @@ -60,22 +55,140 @@ promise LSPServer::onDidOpen(const proto::DidOpenTextDocumentParams& param co_return; } -promise LSPServer::dispatch(json::Value value, Writer& writer) { - this->writer = &writer; +promise Server::onDidChange(const proto::DidChangeTextDocumentParams& document) { + co_return; +} + +promise Server::onDidSave(const proto::DidSaveTextDocumentParams& document) { + co_return; +} + +promise Server::onDidClose(const proto::DidCloseTextDocumentParams& document) { + co_return; +} + +promise Server::onGotoDeclaration(json::Value id, const proto::DeclarationParams& params) { + co_return; +} + +promise Server::onGotoDefinition(json::Value id, const proto::DefinitionParams& params) { + co_return; +} + +promise Server::onGotoTypeDefinition(json::Value id, + const proto::TypeDefinitionParams& params) { + co_return; +} + +promise Server::onGotoImplementation(json::Value id, + const proto::ImplementationParams& params) { + co_return; +} + +promise Server::onFindReferences(json::Value id, const proto::ReferenceParams& params) { + co_return; +} + +promise Server::onPrepareCallHierarchy(json::Value id, + const proto::CallHierarchyPrepareParams& params) { + co_return; +} + +promise Server::onIncomingCall(json::Value id, + const proto::CallHierarchyIncomingCallsParams& params) { + co_return; +} + +promise Server::onOutgoingCall(json::Value id, + const proto::CallHierarchyOutgoingCallsParams& params) { + co_return; +} + +promise Server::onPrepareTypeHierarchy(json::Value id, + const proto::TypeHierarchyPrepareParams& params) { + co_return; +} + +promise Server::onSupertypes(json::Value id, + const proto::TypeHierarchySupertypesParams& params) { + co_return; +} + +promise Server::onSubtypes(json::Value id, const proto::TypeHierarchySubtypesParams& params) { + co_return; +} + +promise Server::onDocumentHighlight(json::Value id, + const proto::DocumentHighlightParams& params) { + co_return; +} + +promise Server::onDocumentLink(json::Value id, const proto::DocumentLinkParams& params) { + co_return; +} + +promise Server::onHover(json::Value id, const proto::HoverParams& params) { + co_return; +} + +promise Server::onCodeLens(json::Value id, const proto::CodeLensParams& params) { + co_return; +} + +promise Server::onFoldingRange(json::Value id, const proto::FoldingRangeParams& params) { + co_return; +} + +promise Server::onDocumentSymbol(json::Value id, const proto::DocumentSymbolParams& params) { + co_return; +} + +promise Server::onSemanticTokens(json::Value id, const proto::SemanticTokensParams& params) { + co_return; +} + +promise Server::onInlayHint(json::Value id, const proto::InlayHintParams& params) { + co_return; +} + +promise Server::onCodeCompletion(json::Value id, const proto::CompletionParams& params) { + co_return; +} + +promise Server::onSignatureHelp(json::Value id, const proto::SignatureHelpParams& params) { + co_return; +} + +promise Server::onCodeAction(json::Value id, const proto::CodeActionParams& params) { + co_return; +} + +promise Server::onFormatting(json::Value id, const proto::DocumentFormattingParams& params) { + co_return; +} + +promise Server::onRangeFormatting(json::Value id, + const proto::DocumentRangeFormattingParams& params) { + co_return; +} + +promise Server::dispatch(json::Value value) { assert(value.kind() == json::Value::Object); auto object = value.getAsObject(); assert(object && "value is not an object"); if(auto method = object->get("method")) { auto name = *method->getAsString(); + auto params = object->get("params"); if(auto id = object->get("id")) { if(auto iter = requests.find(name); iter != requests.end()) { - co_await iter->second(std::move(*id), std::move(*object->get("params"))); + co_await iter->second(std::move(*id), + params ? std::move(*params) : json::Value(nullptr)); } else { llvm::errs() << "Unknown request: " << name << "\n"; } } else { if(auto iter = notifications.find(name); iter != notifications.end()) { - co_await iter->second(std::move(*object->get("params"))); + co_await iter->second(params ? std::move(*params) : json::Value(nullptr)); } else { llvm::errs() << "Unknown notification: " << name << "\n"; } diff --git a/src/main.cpp b/src/main.cpp index 3f7bef86..a66a47c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,10 @@ -#include "Basic/Basic.h" #include "Server/Server.h" using namespace clice; int main(int argc, const char** argv) { - LSPServer LSP; - auto loop = [&LSP](json::Value value, Writer& writer) -> promise { - co_await LSP.dispatch(std::move(value), writer); - }; - Server server(loop, "127.0.0.1", 50051); - server.run(); + Server server; + server.run(argc, argv); return 0; }