diff --git a/.gitignore b/.gitignore index 505cde37..1794a3c3 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,4 @@ .cache/ build* temp* -deps/ +deps/clang-extra/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c2515be..c6ac2804 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,7 +93,7 @@ if(CLICE_ENABLE_TEST) target_link_libraries(clice_test PRIVATE gtest_main) target_clang(clice_test) - file(GLOB_RECURSE AST_SRC_FILES "${CMAKE_SOURCE_DIR}/src/AST/*.cpp" "${CMAKE_SOURCE_DIR}/src/Feature/*.cpp") + file(GLOB_RECURSE AST_SRC_FILES "${CMAKE_SOURCE_DIR}/src/AST/*.cpp" "${CMAKE_SOURCE_DIR}/src/Feature/*.cpp" "${CMAKE_SOURCE_DIR}/src/Support/*.cpp") file(GLOB_RECURSE TEST_SRC_FILES "${CMAKE_SOURCE_DIR}/tests/*/*.cpp") target_sources(clice_test PRIVATE ${AST_SRC_FILES} ${TEST_SRC_FILES}) endif() diff --git a/docs/contribution.md b/docs/contribution.md new file mode 100644 index 00000000..e8b8b57a --- /dev/null +++ b/docs/contribution.md @@ -0,0 +1 @@ +Comment style: lowercase at the beginning. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/roadmap.md b/docs/roadmap.md index 10972418..ffe7747b 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,5 +1,22 @@ -- implement basic server and client -- implement SemanticTokens -- implement CodeCompletion +- collect information from preprocessor, figure out what is the proper to store these information, e.g. macros, pragmas, directives, comments, etc. + +- schedule files, ... + - use `TreeTransform` to simplify `DependentNameResolver` -- refactor `clang/lib/SemaComplete.cpp` \ No newline at end of file + +- refactor `clang/lib/SemaComplete.cpp` + +给 AST Features 分个类 + + + +- Go to Declaration - readonly +- Go to Definition - readonly + +跳转到被引用实体的声明或者定义。有些实体不区分声明和定义,比如宏,别名和命名空间声明,此时这两个函数效果是一样的。由于 C++ 允许多次向前声明,所以 Go to Declaration 可能返回多个结果。但是定义只有一个,所以 Go to Definition 一般只会返回一个结果。有些时候当我们浏览子类的虚函数实现的时候,想要跳转到父类的实现,此时可以对该函数上的 `virtual`, `override` 或者 `final` 使用上述命令。 + +- Go to type definition +跳转到类型定义,该调用会直接跳转到变量/字段的类型定义 + +- Go to implementation +常用于跳转到虚函数实现,部分情况下。特别的,如果 clice 能发现你这个类型是一个 CRTP 类型,那么它也可以进行对应的跳转。 diff --git a/include/AST/Directive.h b/include/AST/Directive.h index 52fe57e9..ea8bbf74 100644 --- a/include/AST/Directive.h +++ b/include/AST/Directive.h @@ -2,13 +2,43 @@ namespace clice { -// TODO: +/// Represents a full conditional preprocessing block, from #if to #endif. +struct IfBlock { + /// Represents a single conditional branch (e.g., #if, #ifdef, #elif). + struct Branch { + /// Location of the directive. + /// NOTE: The `#` is a separate token and not necessarily adjacent. + clang::SourceLocation location; + + /// Location of the condition expression. + clang::SourceLocation condition; + + /// Evaluated value of the condition. + clang::PPCallbacks::ConditionValueKind value; + }; + + /// Initial #if, #ifdef, or #ifndef directive. + Branch if_; + + /// #elif, #elifdef, or #elifndef directives. + std::vector elifs; + + /// Location of the #else directive, if present. + clang::SourceLocation elseLoc; + + /// Location of the #endif directive. + clang::SourceLocation endifLoc; +}; struct Directive { + /// map from the location of if/ifdef/ifndef to the corresponding if block. + llvm::DenseMap ifBlocks; +}; + +struct Directives { clang::SourceManager& sourceManager; - llvm::StringSet<> includes; - llvm::StringMap> comments; - + llvm::DenseMap x; + clang::CommentHandler* handler(); std::unique_ptr callback(); }; diff --git a/include/AST/ParsedAST.h b/include/AST/ParsedAST.h index 66287f3f..a7b2e934 100644 --- a/include/AST/ParsedAST.h +++ b/include/AST/ParsedAST.h @@ -11,7 +11,7 @@ struct ParsedAST { clang::FileManager& fileManager; clang::SourceManager& sourceManager; clang::syntax::TokenBuffer tokenBuffer; - std::unique_ptr directive; + std::unique_ptr directive; std::unique_ptr action; std::unique_ptr instance; diff --git a/include/AST/Selection.h b/include/AST/Selection.h index c6b86a2e..196c295e 100644 --- a/include/AST/Selection.h +++ b/include/AST/Selection.h @@ -4,4 +4,6 @@ namespace clice { class Selection {}; +clang::Decl* test(clang::syntax::Token* token, ParsedAST& AST, llvm::StringRef filename); + } // namespace clice diff --git a/include/Protocol/SemanticTokens.def b/include/Protocol/SemanticTokens.def index a48926fa..f20e8b3a 100644 --- a/include/Protocol/SemanticTokens.def +++ b/include/Protocol/SemanticTokens.def @@ -25,7 +25,7 @@ SEMANTIC_TOKEN_TYPE(Keyword, "keyword") SEMANTIC_TOKEN_TYPE(Builtin, "builtin") /// preprocessor directive (e.g. `#include`, `#define`, `#ifdef`). -SEMANTIC_TOKEN_TYPE(Directive, "directive") +SEMANTIC_TOKEN_TYPE(Directives, "directive") /// header file (e.g. `"foo.h"` and ``). SEMANTIC_TOKEN_TYPE(Header, "header") diff --git a/include/Support/Error.h b/include/Support/Error.h new file mode 100644 index 00000000..2a511b57 --- /dev/null +++ b/include/Support/Error.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +namespace clice { + +template +llvm::Error error(const char* fmt, Args&&... args) { + return llvm::make_error(llvm::formatv(fmt, std::forward(args)...).str(), + llvm::inconvertibleErrorCode()); +} + +} // namespace clice diff --git a/include/Support/URI.h b/include/Support/URI.h new file mode 100644 index 00000000..f4c9e79c --- /dev/null +++ b/include/Support/URI.h @@ -0,0 +1,34 @@ +#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 text); + +private: + std::string m_scheme; + std::string m_authority; + std::string m_body; +}; + +} // namespace clice diff --git a/src/AST/Directive.cpp b/src/AST/Directive.cpp index 2cacb381..0245206f 100644 --- a/src/AST/Directive.cpp +++ b/src/AST/Directive.cpp @@ -1,29 +1,36 @@ -#include "AST/Directive.h" +#include #include + namespace clice { struct CommentHandler : public clang::CommentHandler { - Directive& directive; + Directives& directive; - CommentHandler(Directive& directive) : directive(directive) {} + CommentHandler(Directives& directive) : directive(directive) {} virtual bool HandleComment(clang::Preprocessor& preproc, clang::SourceRange Comment) { // directive.comments.push_back(Comment); + auto start = directive.sourceManager.getCharacterData(Comment.getBegin()); + auto end = directive.sourceManager.getCharacterData(Comment.getEnd()); + llvm::outs() << "Comment: " << llvm::StringRef(start, end - start) << "\n"; return false; } }; struct PPCallback : clang::PPCallbacks { - PPCallback(Directive& directive) : directive(directive) {} + PPCallback(Directives& directive) : directive(directive) {} - Directive& directive; + Directives& directive; + clang::FileID currentID; void FileChanged(clang::SourceLocation loc, clang::PPCallbacks::FileChangeReason reason, clang::SrcMgr::CharacteristicKind fileType, - clang::FileID id) override { - llvm::outs() << "FileChanged, reason: " << enum_name(reason) << "\n"; - loc.dump(directive.sourceManager); + clang::FileID) override { + // llvm::outs() << directive.sourceManager.getFileEntryForID(id)->tryGetRealPathName(); + if(reason == clang::PPCallbacks::FileChangeReason::EnterFile) { + currentID = directive.sourceManager.getFileID(loc); + } } void InclusionDirective(clang::SourceLocation HashLoc, @@ -51,7 +58,7 @@ struct PPCallback : clang::PPCallbacks { void If(clang::SourceLocation Loc, clang::SourceRange ConditionRange, clang::PPCallbacks::ConditionValueKind ConditionValue) override { - // llvm::outs() << "If\n"; + llvm::outs() << "If\n"; } void Elif(clang::SourceLocation loc, @@ -87,8 +94,8 @@ struct PPCallback : clang::PPCallbacks { void MacroDefined(const clang::Token& MacroNameTok, const clang::MacroDirective* MD) override {} }; -clang::CommentHandler* Directive::handler() { return new CommentHandler(*this); } +clang::CommentHandler* Directives::handler() { return new CommentHandler(*this); } -std::unique_ptr Directive::callback() { return std::make_unique(*this); } +std::unique_ptr Directives::callback() { return std::make_unique(*this); } } // namespace clice diff --git a/src/AST/ParsedAST.cpp b/src/AST/ParsedAST.cpp index b25600c9..4b6ee4a7 100644 --- a/src/AST/ParsedAST.cpp +++ b/src/AST/ParsedAST.cpp @@ -1,4 +1,4 @@ -#include "AST/ParsedAST.h" +#include #include "clang/Lex/PreprocessorOptions.h" namespace clice { @@ -65,7 +65,7 @@ std::unique_ptr ParsedAST::build(llvm::StringRef filename, auto& preproc = instance->getPreprocessor(); clang::syntax::TokenCollector collector(preproc); - auto directive = std::make_unique(instance->getSourceManager()); + auto directive = std::make_unique(instance->getSourceManager()); preproc.addCommentHandler(directive->handler()); preproc.addPPCallbacks(directive->callback()); diff --git a/src/Support/URI.cpp b/src/Support/URI.cpp new file mode 100644 index 00000000..97b9ec66 --- /dev/null +++ b/src/Support/URI.cpp @@ -0,0 +1,64 @@ +#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; +} + +} // namespace clice diff --git a/tests/Resolver.cpp b/tests/AST/Resolver.cpp similarity index 100% rename from tests/Resolver.cpp rename to tests/AST/Resolver.cpp index 06763765..b203510f 100644 --- a/tests/Resolver.cpp +++ b/tests/AST/Resolver.cpp @@ -1,10 +1,10 @@ -#include #include - -using namespace clice; +#include namespace { +using namespace clice; + std::vector compileArgs = { "clang++", "-std=c++20", diff --git a/tests/Feature/SemanticTokens.cpp b/tests/Feature/SemanticTokens.cpp index 137f5b61..55e9a995 100644 --- a/tests/Feature/SemanticTokens.cpp +++ b/tests/Feature/SemanticTokens.cpp @@ -21,9 +21,12 @@ TEST(test, test) { }; #include - + const char* code = R"( -#include +// 123 +#if x +#endif +// 333 )"; auto AST = clice::ParsedAST::build("main.cpp", code, compileArgs); diff --git a/tests/JSON.cpp b/tests/Support/JSON.cpp similarity index 100% rename from tests/JSON.cpp rename to tests/Support/JSON.cpp diff --git a/tests/Support/URI.cpp b/tests/Support/URI.cpp new file mode 100644 index 00000000..8a664171 --- /dev/null +++ b/tests/Support/URI.cpp @@ -0,0 +1,45 @@ +#include +#include + +namespace { + +using namespace clice; + +TEST(URITest, ConstructorAndAccessors) { + URI uri("https", "reviews.lvm.org", "/D41946"); + + EXPECT_EQ(uri.scheme(), "https"); + EXPECT_EQ(uri.authority(), "reviews.lvm.org"); + EXPECT_EQ(uri.body(), "/D41946"); +} + +TEST(URITest, CopyConstructor) { + URI uri1("https", "reviews.lvm.org", "/D41946"); + URI uri2(uri1); + + EXPECT_EQ(uri2.scheme(), "https"); + EXPECT_EQ(uri2.authority(), "reviews.lvm.org"); + EXPECT_EQ(uri2.body(), "/D41946"); +} + +TEST(URITest, EqualityOperator) { + URI uri1("https", "reviews.lvm.org", "/D41946"); + URI uri2("https", "reviews.lvm.org", "/D41946"); + URI uri3("http", "example.com", "/index.html"); + + EXPECT_TRUE(uri1 == uri2); + EXPECT_FALSE(uri1 == uri3); +} + +TEST(URITest, ParseFunction) { + auto expectedUri = URI::parse("https://reviews.lvm.org/D41946"); + ASSERT_TRUE(static_cast(expectedUri)); + + URI uri = expectedUri.get(); + EXPECT_EQ(uri.scheme(), "https"); + EXPECT_EQ(uri.authority(), "reviews.lvm.org"); + EXPECT_EQ(uri.body(), "/D41946"); +} + +} // namespace +