diff --git a/include/AST/ParsedAST.h b/include/AST/ParsedAST.h index b8ad3182..66287f3f 100644 --- a/include/AST/ParsedAST.h +++ b/include/AST/ParsedAST.h @@ -19,6 +19,18 @@ struct ParsedAST { llvm::StringRef content, std::vector& args, Preamble* preamble = nullptr); + + clang::FileID getFileID(llvm::StringRef filename) const { + auto entry = fileManager.getFileRef(filename); + if(!entry) { + // TODO: + } + return sourceManager.translateFile(entry.get()); + } + + llvm::ArrayRef spelledTokens(clang::FileID fileID) const { + return tokenBuffer.spelledTokens(fileID); + } }; } // namespace clice diff --git a/include/Server/Scheduler.h b/include/Server/Scheduler.h index 981e71e0..a12dd1c8 100644 --- a/include/Server/Scheduler.h +++ b/include/Server/Scheduler.h @@ -1,3 +1,5 @@ +#pragma once + #include #include #include @@ -17,7 +19,7 @@ private: }; public: - void dispatch(std::string_view method, json::Value value); + void dispatch(json::Value id, std::string_view method, json::Value value); private: llvm::StringMap files; diff --git a/src/Feature/SemanticTokens.cpp b/src/Feature/SemanticTokens.cpp index b233e0fd..963f284f 100644 --- a/src/Feature/SemanticTokens.cpp +++ b/src/Feature/SemanticTokens.cpp @@ -10,14 +10,181 @@ namespace { #define VISIT(NAME) bool Visit##NAME(clang::NAME* node) #define VISIT_TYPE(NAME) bool Visit##NAME(clang::NAME node) -class SemanticToken {}; +struct SemanticToken { + std::uint32_t modifiers = 0; + clang::SourceRange range; + protocol::SemanticTokenType type; +}; -protocol::SemanticTokens semanticTokens(const ParsedAST& AST, llvm::StringRef filename) { - auto entry = AST.fileManager.getFileRef(filename); - auto fileID = AST.sourceManager.translateFile(entry.get()); - auto tokens = AST.tokenBuffer.spelledTokens(fileID); +static bool isKeyword(clang::tok::TokenKind kind, llvm::StringRef text, const clang::LangOptions& option) { + switch(kind) { + case clang::tok::kw_void: + case clang::tok::kw_int: + case clang::tok::kw_char: + case clang::tok::kw_long: + case clang::tok::kw_short: + case clang::tok::kw_signed: + case clang::tok::kw_unsigned: + case clang::tok::kw_float: + case clang::tok::kw_double: + case clang::tok::kw_const: + case clang::tok::kw_volatile: + case clang::tok::kw_auto: + case clang::tok::kw_static: + case clang::tok::kw_register: + case clang::tok::kw_extern: + case clang::tok::kw_if: + case clang::tok::kw_else: + case clang::tok::kw_switch: + case clang::tok::kw_case: + case clang::tok::kw_default: + case clang::tok::kw_do: + case clang::tok::kw_for: + case clang::tok::kw_while: + case clang::tok::kw_break: + case clang::tok::kw_continue: + case clang::tok::kw_goto: + case clang::tok::kw_return: + case clang::tok::kw_struct: + case clang::tok::kw_union: + case clang::tok::kw_enum: + case clang::tok::kw_typedef: + case clang::tok::kw_sizeof: { + return true; + } - + case clang::tok::amp: return option.CPlusPlus && text == "bitand"; + case clang::tok::ampamp: return option.CPlusPlus && text == "and"; + case clang::tok::ampequal: return option.CPlusPlus && text == "and_eq"; + case clang::tok::pipe: return option.CPlusPlus && text == "bitor"; + case clang::tok::pipepipe: return option.CPlusPlus && text == "or"; + case clang::tok::pipeequal: return option.CPlusPlus && text == "or_eq"; + case clang::tok::caret: return option.CPlusPlus && text == "xor"; + case clang::tok::caretequal: return option.CPlusPlus && text == "xor_eq"; + case clang::tok::exclaim: return option.CPlusPlus && text == "not"; + case clang::tok::exclaimequal: return option.CPlusPlus && text == "not_eq"; + case clang::tok::tilde: return option.CPlusPlus && text == "compl"; + + case clang::tok::kw_asm: + case clang::tok::kw_wchar_t: + case clang::tok::kw_try: + case clang::tok::kw_throw: + case clang::tok::kw_catch: + case clang::tok::kw_typeid: + case clang::tok::kw_this: + case clang::tok::kw_friend: + case clang::tok::kw_mutable: + case clang::tok::kw_explicit: + case clang::tok::kw_virtual: + case clang::tok::kw_operator: + case clang::tok::kw_class: + case clang::tok::kw_public: + case clang::tok::kw_protected: + case clang::tok::kw_private: + case clang::tok::kw_using: + case clang::tok::kw_namespace: + case clang::tok::kw_template: + case clang::tok::kw_typename: + case clang::tok::kw_export: + case clang::tok::kw_const_cast: + case clang::tok::kw_static_cast: + case clang::tok::kw_dynamic_cast: + case clang::tok::kw_reinterpret_cast: + case clang::tok::kw_new: + case clang::tok::kw_delete: { + return option.CPlusPlus; + } + + case clang::tok::kw_restrict: + case clang::tok::kw__Bool: + case clang::tok::kw__Complex: + case clang::tok::kw__Imaginary: { + return option.C99; + } + + case clang::tok::kw_inline: { + return option.CPlusPlus || option.C99; + } + + case clang::tok::kw__Alignas: + case clang::tok::kw__Alignof: + case clang::tok::kw__Atomic: + case clang::tok::kw__Generic: + case clang::tok::kw__Noreturn: + case clang::tok::kw__Static_assert: + case clang::tok::kw__Thread_local: { + return option.C11; + } + + case clang::tok::kw_typeof: + case clang::tok::kw_typeof_unqual: + case clang::tok::kw__BitInt: + case clang::tok::kw__Decimal32: + case clang::tok::kw__Decimal64: + case clang::tok::kw__Decimal128: { + return option.C23; + } + + case clang::tok::kw_bool: + case clang::tok::kw_true: + case clang::tok::kw_false: { + return option.CPlusPlus || option.C23; + } + + case clang::tok::kw_char16_t: + case clang::tok::kw_char32_t: + case clang::tok::kw_noexcept: + case clang::tok::kw_decltype: { + return option.CPlusPlus11; + } + + case clang::tok::kw_nullptr: + case clang::tok::kw_alignas: + case clang::tok::kw_alignof: + case clang::tok::kw_thread_local: + case clang::tok::kw_static_assert: + case clang::tok::kw_constexpr: { + return option.CPlusPlus11 || option.C23; + } + + case clang::tok::kw_char8_t: + case clang::tok::kw_import: + case clang::tok::kw_module: + case clang::tok::kw_constinit: + case clang::tok::kw_consteval: + case clang::tok::kw_concept: + case clang::tok::kw_requires: + case clang::tok::kw_co_await: + case clang::tok::kw_co_yield: + case clang::tok::kw_co_return: { + return option.CPlusPlus20; + } + } + return false; +} + +class HighlightBuilder { +public: + HighlightBuilder(const ParsedAST& parsedAST, llvm::StringRef filename) { + auto fileID = parsedAST.getFileID(filename); + auto tokens = parsedAST.spelledTokens(fileID); + for(auto& token: tokens) { + SemanticToken semanticToken; + semanticToken.range = clang::SourceRange(token.location(), token.endLocation()); + if(isKeyword(token.kind(), token.text(parsedAST.sourceManager), parsedAST.context.getLangOpts())) { + semanticToken.type = protocol::SemanticTokenType::Keyword; + } else { + semanticToken.type = protocol::SemanticTokenType::Number; + } + semanticToken.modifiers = 0; + result.emplace_back(std::move(semanticToken)); + } + } + + std::vector build() { return std::move(result); } + +private: + std::vector result; }; class Highlighter : public clang::RecursiveASTVisitor { @@ -172,4 +339,30 @@ private: } // namespace +protocol::SemanticTokens semanticTokens(const ParsedAST& AST, llvm::StringRef filename) { + HighlightBuilder builder(AST, filename); + std::vector tokens = builder.build(); + + protocol::SemanticTokens result; + unsigned int last_line = 0; + unsigned int last_column = 0; + /// FXIME: resolve position encoding + for(std::size_t index = 0; index < tokens.size(); ++index) { + auto& token = tokens[index]; + auto line = AST.sourceManager.getPresumedLineNumber(token.range.getBegin()) - 1; + auto column = AST.sourceManager.getPresumedColumnNumber(token.range.getBegin()) - 1; + auto length = AST.sourceManager.getPresumedColumnNumber(token.range.getEnd()) - column; + result.data.push_back(line - last_line); + result.data.push_back(column > last_column ? column - last_column : column); + result.data.push_back(length); + result.data.push_back(token.type); + result.data.push_back(token.modifiers); + + last_line = line; + last_column = column; + } + + return result; +}; + } // namespace clice::feature diff --git a/src/Server/Scheduler.cpp b/src/Server/Scheduler.cpp index c41cca7e..c2fabdf4 100644 --- a/src/Server/Scheduler.cpp +++ b/src/Server/Scheduler.cpp @@ -1,10 +1,12 @@ #include #include #include +#include +#include namespace clice { -void Scheduler::dispatch(std::string_view method, json::Value value) { +void Scheduler::dispatch(json::Value id, std::string_view method, json::Value value) { if(method == "textDocument/didOpen") { auto params = json::deserialize(value); std::vector compileArgs = { @@ -20,7 +22,8 @@ void Scheduler::dispatch(std::string_view method, json::Value value) { } else if(method == "textDocument/semanticTokens/full") { auto params = json::deserialize(value); auto AST = files[params.textDocument.uri].ast.get(); - + auto semanticTokens = feature::semanticTokens(*AST, "main.cpp"); + Server::instance.response(std::move(id), json::serialize(semanticTokens)); } else { spdlog::error("unknown method: {}", method); } diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index 0962a575..0c0ee5e0 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -139,7 +139,12 @@ void Server::handleMessage(std::string_view message) { if(auto handler = handlers.find(method); handler != handlers.end()) { handler->second(*id, *params); } else { - scheduler.dispatch(method, *params); + // FIXME: notify do not have a ID. + if(id) { + scheduler.dispatch(std::move(*id), method, *params); + } else { + scheduler.dispatch(nullptr, method, *params); + } } } @@ -158,12 +163,15 @@ void Server::response(json::Value id, json::Value result) { s = "Content-Length: " + std::to_string(s.size()) + "\r\n\r\n" + s; - static uv_buf_t buf = uv_buf_init(s.data(), s.size()); + // FIXME: use more flexible way to do this. + static uv_buf_t buf; + buf = uv_buf_init(s.data(), s.size()); static uv_write_t req; auto state = uv_write(&req, (uv_stream_t*)&stdout_pipe, &buf, 1, NULL); if(state < 0) { spdlog::error("Error writing to stdout: {}", uv_strerror(state)); } + spdlog::info("Response: {}", s); } } // namespace clice diff --git a/tests/Resolver.cpp b/tests/Resolver.cpp index 277c5bc0..06763765 100644 --- a/tests/Resolver.cpp +++ b/tests/Resolver.cpp @@ -388,40 +388,40 @@ struct test { ASSERT_EQ(T->getDecl()->getName(), "X"); } -TEST(DependentNameResolver, std_vector) { - const char* code = R"( -#include - -template -struct A {}; - -template -struct test { - using result = typename std::vector>::reference; -}; -)"; - - Visitor visitor(code); - clang::QualType result = visitor.test(); - // result->dump(); -} - -TEST(DependentNameResolver, std_list) { - const char* code = R"( -#include - -template -struct A {}; - -template -struct test { - using result = typename std::list>::reference; -}; -)"; - - Visitor visitor(code); - clang::QualType result = visitor.test(); -} +// TEST(DependentNameResolver, std_vector) { +// const char* code = R"( +// #include +// +// template +// struct A {}; +// +// template +// struct test { +// using result = typename std::vector>::reference; +// }; +//)"; +// +// Visitor visitor(code); +// clang::QualType result = visitor.test(); +// // result->dump(); +// } +// +// TEST(DependentNameResolver, std_list) { +// const char* code = R"( +// #include +// +// template +// struct A {}; +// +// template +// struct test { +// using result = typename std::list>::reference; +// }; +//)"; +// +// Visitor visitor(code); +// clang::QualType result = visitor.test(); +// } } // namespace diff --git a/tests/test.cpp b/tests/test.cpp new file mode 100644 index 00000000..f6f199ce --- /dev/null +++ b/tests/test.cpp @@ -0,0 +1,33 @@ +#include +#include + +namespace { + +using namespace clang; + +TEST(test, test) { + std::vector compileArgs = { + "clang++", + "-std=c++20", + "main.cpp", + "-resource-dir=/home/ykiko/C++/clice2/build/lib/clang/20", + }; + + const char* code = R"( +int x [[maybe_unused]] = 0; +)"; + + auto AST = clice::ParsedAST::build("main.cpp", code, compileArgs); + auto fileID = AST->getFileID("main.cpp"); + auto tokens = AST->spelledTokens(fileID); + + // ClassVisitor visitor(&AST->context); + // visitor.TraverseDecl(AST->context.getTranslationUnitDecl()); + + for(auto& token: tokens) { + llvm::outs() << clang::tok::getTokenName(token.kind()) << ": " << token.text(AST->sourceManager) << "\n"; + } +} + +} // namespace +