Files
clice/src/Feature/SemanticTokens.cpp
2024-09-15 13:50:49 +08:00

437 lines
14 KiB
C++

#include "AST/ParsedAST.h"
#include "Feature/SemanticTokens.h"
namespace clice::feature {
namespace {
struct SemanticToken {
clang::SourceLocation begin;
std::size_t length;
protocol::SemanticTokenType type;
std::uint32_t modifiers = 0;
SemanticToken& addModifier(protocol::SemanticTokenModifier modifier) {
modifiers |= 1 << static_cast<std::uint32_t>(modifier);
return *this;
}
};
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) : AST(parsedAST), filename(filename) {}
SemanticToken& addToken(protocol::SemanticTokenType type, clang::SourceLocation begin, std::size_t length) {
result.emplace_back(begin, length, type, 0);
return result.back();
}
SemanticToken& addToken(protocol::SemanticTokenType type, clang::SourceLocation loc) {
auto token = AST.tokenBuffer.spelledTokenContaining(loc);
if(token) {
return addToken(type, token->location(), token->length());
}
}
// FIXME: source range can be a multi-line range, split it into multiple tokens
// SemanticToken& addToken(protocol::SemanticTokenType type, clang::SourceRange range) {
// SemanticToken token;
// token.begin = range.getBegin();
// token.length =
// AST.sourceManager.getFileOffset(range.getEnd()) - AST.sourceManager.getFileOffset(range.getBegin());
// token.type = type;
// result.emplace_back(std::move(token));
// return result.back();
//}
void addAngle(clang::SourceLocation left, clang::SourceLocation right) {}
std::vector<SemanticToken> build();
private:
const ParsedAST& AST;
llvm::StringRef filename;
std::vector<SemanticToken> result;
};
/// Collect highlight information from AST.
class HighlightCollector : public clang::RecursiveASTVisitor<HighlightCollector> {
public:
HighlightCollector(const ParsedAST& AST, HighlightBuilder& builder) : AST(AST), builder(builder) {}
// Traverse(TranslationUnitDecl) {
// for(auto decl: node->decls()) {
// // FIXME: some decls are located in their parents' file
// // e.g. `ClassTemplateSpecializationDecl`, find and exclude them
// node->getLexicalDeclContext();
// // if(sourceManager.isInFileID(decl->getLocation(), fileID)) {
// // TraverseDecl(decl);
// // }
// }
// return true;
// }
#define Traverse(NAME) bool Traverse##NAME(clang::NAME* node)
#define WalkUpFrom(NAME) bool WalkUpFrom##NAME(clang::NAME* node)
#define VISIT(NAME) bool Visit##NAME(clang::NAME* node)
#define VISIT_TYPE(NAME) bool Visit##NAME(clang::NAME node)
// WalkUpFrom(NamespaceDecl) {}
VISIT(ImportDecl) {}
VISIT(NamespaceDecl) {
builder.addToken(protocol::SemanticTokenType::Namespace, node->getLocation());
return true;
}
VISIT(DeclaratorDecl) {
for(unsigned i = 0; i < node->getNumTemplateParameterLists(); ++i) {
if(auto params = node->getTemplateParameterList(i)) {
builder.addAngle(params->getLAngleLoc(), params->getRAngleLoc());
}
}
return true;
}
VISIT(TagDecl) {
for(unsigned i = 0; i < node->getNumTemplateParameterLists(); ++i) {
if(auto params = node->getTemplateParameterList(i)) {
builder.addAngle(params->getLAngleLoc(), params->getRAngleLoc());
}
}
return true;
}
VISIT(FunctionDecl) {
if(auto args = node->getTemplateSpecializationArgsAsWritten()) {
builder.addAngle(args->getLAngleLoc(), args->getRAngleLoc());
}
builder.addToken(protocol::SemanticTokenType::Function, node->getLocation());
return true;
}
VISIT(TemplateDecl) {
if(auto params = node->getTemplateParameters()) {
builder.addAngle(params->getLAngleLoc(), params->getRAngleLoc());
}
return true;
}
VISIT(ClassTemplateSpecializationDecl) {
if(auto args = node->getTemplateArgsAsWritten()) {
builder.addAngle(args->getLAngleLoc(), args->getRAngleLoc());
}
return true;
}
VISIT(ClassTemplatePartialSpecializationDecl) {
if(auto params = node->getTemplateParameters()) {
builder.addAngle(params->getLAngleLoc(), params->getRAngleLoc());
}
return true;
}
VISIT(VarTemplateSpecializationDecl) {
if(auto args = node->getTemplateArgsAsWritten()) {
builder.addAngle(args->LAngleLoc, args->RAngleLoc);
}
return true;
}
VISIT(VarTemplatePartialSpecializationDecl) {
if(auto params = node->getTemplateParameters()) {
builder.addAngle(params->getLAngleLoc(), params->getRAngleLoc());
}
return true;
}
VISIT(CXXNamedCastExpr) {
builder.addAngle(node->getAngleBrackets().getBegin(), node->getAngleBrackets().getEnd());
return true;
}
VISIT(OverloadExpr) {
builder.addAngle(node->getLAngleLoc(), node->getRAngleLoc());
return true;
}
VISIT(CXXDependentScopeMemberExpr) {
builder.addAngle(node->getLAngleLoc(), node->getRAngleLoc());
return true;
}
VISIT(DependentScopeDeclRefExpr) {
builder.addAngle(node->getLAngleLoc(), node->getRAngleLoc());
return true;
}
VISIT_TYPE(DependentNameTypeLoc) {
// DependentNameType: `typename T::type`
// ^~~~ highlight this
builder.addToken(protocol::SemanticTokenType::Type, node.getNameLoc());
return true;
}
VISIT_TYPE(TemplateSpecializationTypeLoc) {
node.dump();
builder.addAngle(node.getLAngleLoc(), node.getRAngleLoc());
return true;
}
VISIT_TYPE(DependentTemplateSpecializationTypeLoc) {
builder.addAngle(node.getLAngleLoc(), node.getRAngleLoc());
return true;
}
bool TraverseNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc loc) {
if(clang::NestedNameSpecifier* NNS = loc.getNestedNameSpecifier()) {
if(NNS->getKind() == clang::NestedNameSpecifier::Identifier) {
// NestedNameSpecifier: `T::type::`
// ^~~~ highlight this
builder.addToken(protocol::SemanticTokenType::Type, loc.getLocalBeginLoc());
}
}
return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(loc);
}
private:
const ParsedAST& AST;
HighlightBuilder& builder;
};
std::vector<SemanticToken> HighlightBuilder::build() {
auto fileID = AST.getFileID(filename);
auto tokens = AST.spelledTokens(fileID);
// highlight from tokens.
for(auto& token: tokens) {
protocol::SemanticTokenType type = protocol::SemanticTokenType::LAST_TYPE;
switch(token.kind()) {
case clang::tok::TokenKind::numeric_constant: {
type = protocol::SemanticTokenType::Number;
break;
}
case clang::tok::char_constant:
case clang::tok::wide_char_constant:
case clang::tok::utf8_char_constant:
case clang::tok::utf16_char_constant:
case clang::tok::utf32_char_constant: {
type = protocol::SemanticTokenType::Character;
break;
}
case clang::tok::string_literal:
case clang::tok::wide_string_literal:
case clang::tok::utf8_string_literal:
case clang::tok::utf16_string_literal:
case clang::tok::utf32_string_literal: {
type = protocol::SemanticTokenType::String;
break;
}
default: {
if(isKeyword(token.kind(), token.text(AST.sourceManager), AST.context.getLangOpts())) {
type = protocol::SemanticTokenType::Keyword;
break;
}
}
}
if(type != protocol::SemanticTokenType::LAST_TYPE) {
addToken(type, token.location(), token.length());
}
}
// TODO: highlight from preprocessor.
// highlight from AST.
HighlightCollector collector(AST, *this);
collector.TraverseTranslationUnitDecl(AST.context.getTranslationUnitDecl());
// AST.context.getTranslationUnitDecl()->dump();
llvm::sort(result, [](const SemanticToken& lhs, const SemanticToken& rhs) {
return lhs.begin < rhs.begin;
});
return std::move(result);
}
} // namespace
protocol::SemanticTokens semanticTokens(const ParsedAST& AST, llvm::StringRef filename) {
HighlightBuilder builder(AST, filename);
std::vector<SemanticToken> tokens = builder.build();
// for(auto& token: tokens) {
// spdlog::info("{}", token.dump(AST.sourceManager));
// }
protocol::SemanticTokens result;
unsigned int last_line = 0;
unsigned int last_column = 0;
/// FXIME: resolve position encoding
for(auto& token: tokens) {
auto line = AST.sourceManager.getPresumedLineNumber(token.begin) - 1;
auto column = AST.sourceManager.getPresumedColumnNumber(token.begin) - 1;
result.data.push_back(line - last_line);
result.data.push_back(line == last_line ? column - last_column : column);
result.data.push_back(token.length);
result.data.push_back(token.type);
result.data.push_back(token.modifiers);
last_line = line;
last_column = column;
}
return result;
};
} // namespace clice::feature