some update.
This commit is contained in:
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -18,7 +18,7 @@
|
||||
"name": "Launch",
|
||||
"program": "${workspaceFolder}/build/ASTVisitor",
|
||||
"args": [
|
||||
"/home/ykiko/Project/C++/clice/main.cpp",
|
||||
"/home/ykiko/C++/clice/main.cpp",
|
||||
],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
project(clice)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_CXX_FLAGS "-isystem/usr/include/c++/13 -isystem/usr/include/x86_64-linux-gnu/c++/13 ${CMAKE_CXX_FLAGS_RELEASE} -g -O0 -fno-rtti")
|
||||
|
||||
set(CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/external/llvm/lib/cmake")
|
||||
find_package(LLVM REQUIRED CONFIG)
|
||||
find_package(Clang REQUIRED CONFIG)
|
||||
|
||||
message(STATUS "LLVM_INCLUDE_DIRS: ${LLVM_INCLUDE_DIRS}")
|
||||
|
||||
set(PCH_HEADER "${CMAKE_SOURCE_DIR}/include/Clang/Clang.h")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_RELEASE} -w -g -O0 -fno-rtti")
|
||||
|
||||
set(JSON_BuildTests OFF CACHE INTERNAL "")
|
||||
set(LLVM_ENABLE_PROJECTS "clang")
|
||||
set(LLVM_TARGETS_TO_BUILD "X86")
|
||||
set(LLVM_USE_LINKER "lld")
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
set(LLVM_ABI_BREAKING_CHECKS "FORCE_OFF")
|
||||
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/external/llvm-project/llvm")
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/external/json")
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/external/libuv")
|
||||
add_subdirectory("${CMAKE_SOURCE_DIR}/external/spdlog")
|
||||
|
||||
set(INCLUDE_DIRS
|
||||
"${CMAKE_SOURCE_DIR}/include"
|
||||
"${CMAKE_SOURCE_DIR}/external/llvm-project/clang/lib/Sema"
|
||||
"${CMAKE_SOURCE_DIR}/external/json/include"
|
||||
"${CMAKE_SOURCE_DIR}/external/libuv/include"
|
||||
"${CMAKE_SOURCE_DIR}/external/spdlog/include"
|
||||
"${CMAKE_SOURCE_DIR}/external/llvm/include"
|
||||
"${CMAKE_SOURCE_DIR}/external/llvm/tools/clang/include"
|
||||
)
|
||||
|
||||
set(PCH_HEADER "${CMAKE_SOURCE_DIR}/include/Clang/Clang.h")
|
||||
|
||||
set(LLVM_LIBS
|
||||
LLVMCore
|
||||
LLVMSupport
|
||||
@@ -46,6 +46,7 @@ set(LLVM_LIBS
|
||||
clangToolingInclusions
|
||||
clangToolingInclusionsStdlib
|
||||
clangToolingSyntax
|
||||
LLVMFrontendOpenMP
|
||||
)
|
||||
|
||||
set(LINK_LIBS
|
||||
@@ -90,4 +91,5 @@ if(CLICE_EXAMPLES)
|
||||
add_example(CodeCompletion docs/examples/CodeCompletion.cpp)
|
||||
add_example(Preamble docs/examples/Preamble.cpp)
|
||||
add_example(Preprocessor docs/examples/Preprocessor.cpp)
|
||||
add_example(TreeTransform docs/examples/TreeTransform.cpp)
|
||||
endif()
|
||||
@@ -24,6 +24,7 @@ FIXME:
|
||||
- no-self-contained: https://github.com/clangd/clangd/issues/45
|
||||
- 提供更好的模板代码补全(需要在索引文件里面记录模板实例化),https://github.com/clangd/clangd/issues/443,然后补全的时候获取`.`号前面的表达式类型,之后再这里查找
|
||||
- 支持模块:https://github.com/clangd/clangd/issues/1293
|
||||
- https://github.com/clangd/clangd/issues/123 优化头文查找
|
||||
## 性能优化
|
||||
|
||||
TODO: 寻找核心优化点
|
||||
|
||||
@@ -77,4 +77,35 @@
|
||||
2. Semantic Tokens 等基于当前 AST 的操作,则是遍历 AST 渲染 Token 即可。
|
||||
3. 剩下很多的,例如 Find References 等等等查询功能,都是在已经索引好的文件中进行查询,不需要对语法树进行什么改动。
|
||||
|
||||
###
|
||||
### DependentNameResolver
|
||||
|
||||
对于一个`DependentNameType`我们要分情况处理
|
||||
|
||||
最一般的`DependentNameType`可以用如下形式的正则表达式处理 `typename T<...>[::T2|::template T2<...>]*`
|
||||
|
||||
首先我们要讨论希望化简到什么结果,例如对于
|
||||
|
||||
```cpp
|
||||
typename std::vector<std::vector<T>>::reference
|
||||
```
|
||||
|
||||
我们希望得到
|
||||
|
||||
```cpp
|
||||
std::vector<T>&
|
||||
```
|
||||
|
||||
对于
|
||||
|
||||
```cpp
|
||||
typename std::vector<std::vector<T>>::value_type::reference
|
||||
```
|
||||
|
||||
我们希望得到
|
||||
|
||||
```cpp
|
||||
T&
|
||||
```
|
||||
|
||||
无论如何我们需要递归进行处理
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Clang/Clang.h>
|
||||
#include <clang/Sema/Lookup.h>
|
||||
#include <clang/Sema/Template.h>
|
||||
|
||||
class DependentShaper {
|
||||
clang::ASTContext& context;
|
||||
@@ -33,6 +34,15 @@ public:
|
||||
}
|
||||
|
||||
clang::ElaboratedType* elaboratedType = nullptr;
|
||||
clang::MultiLevelTemplateArgumentList list;
|
||||
list.addOuterTemplateArguments(originalArguments);
|
||||
llvm::outs() << "-------------------------------------------------\n";
|
||||
clang::Sema::CodeSynthesisContext InstContext;
|
||||
// InstContext.Kind = clang::Sema::CodeSynthesisContext::TemplateInstantiation;
|
||||
// InstContext.Entity = nullptr;
|
||||
// InstContext.TemplateArgs = TemplateArgs;
|
||||
// sema.CodeSynthesisContexts.back().dump();
|
||||
auto result = sema.SubstType(input, list, {}, {});
|
||||
|
||||
if(auto type = llvm::dyn_cast<clang::LValueReferenceType>(input)) {
|
||||
auto pointee = type->getPointeeType();
|
||||
@@ -147,6 +157,455 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
namespace clang {
|
||||
class DependentNameResolver {
|
||||
public:
|
||||
Sema& S;
|
||||
ASTContext& Ctx;
|
||||
clang::NamedDecl* CurrentDecl;
|
||||
|
||||
public:
|
||||
DependentNameResolver(ASTContext& Ctx, Sema& S) : Ctx(Ctx), S(S) {}
|
||||
|
||||
std::vector<TemplateArgument> resolve(llvm::ArrayRef<TemplateArgument> arguments) {
|
||||
std::vector<TemplateArgument> result;
|
||||
for(auto arg: arguments) {
|
||||
if(arg.getKind() == TemplateArgument::ArgKind::Type) {
|
||||
if(auto type = llvm::dyn_cast<TemplateTypeParmType>(arg.getAsType())) {
|
||||
const TemplateTypeParmDecl* param = type->getDecl();
|
||||
if(param->hasDefaultArgument()) {
|
||||
result.push_back(param->getDefaultArgument().getArgument());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push_back(arg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QualType resolve(QualType T) {
|
||||
if(!T->isDependentType()) {
|
||||
return T;
|
||||
}
|
||||
|
||||
while(true) {
|
||||
if(auto DNT = T->getAs<DependentNameType>()) {
|
||||
T = resolve(DNT);
|
||||
} else if(auto DTST = T->getAs<DependentTemplateSpecializationType>()) {
|
||||
T = resolve(DTST);
|
||||
} else if(auto LRT = T->getAs<LValueReferenceType>()) {
|
||||
return Ctx.getLValueReferenceType(resolve(LRT->getPointeeType()));
|
||||
} else {
|
||||
return T;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// resolve a dependent name type, e.g. `typename std::vector<T>::reference`
|
||||
QualType resolve(const DependentNameType* DNT) {
|
||||
// e.g. when DNT is `typename std::vector<T>::reference`
|
||||
// - qualifier: std::vector<T>
|
||||
// - identifier: reference
|
||||
return resolve(DNT->getQualifier(), DNT->getIdentifier());
|
||||
}
|
||||
|
||||
/// resolve a dependent template specialization type.
|
||||
QualType resolve(const DependentTemplateSpecializationType* DTST) {
|
||||
// e.g. when DTST is `typename std::allocator_traits<Alloc>::template rebind_alloc<T>`.
|
||||
// - qualifier: std::allocator_traits<Alloc>
|
||||
// - identifier: rebind_alloc
|
||||
// - template_arguments: <T>
|
||||
return resolve(DTST->getQualifier(), DTST->getIdentifier(), DTST->template_arguments());
|
||||
}
|
||||
|
||||
QualType resolve(const NestedNameSpecifier* TST,
|
||||
const IdentifierInfo* II,
|
||||
ArrayRef<TemplateArgument> arguments = {}) {
|
||||
|
||||
switch(TST->getKind()) {
|
||||
case NestedNameSpecifier::SpecifierKind::Identifier: {
|
||||
llvm::outs() << "\n------------------ Identifier -----------------------\n";
|
||||
// when the kind of TST is Identifier
|
||||
// e.g. std::vector<std::vector<T>>::value_type::
|
||||
// resolve it recursively
|
||||
return resolve(resolve(TST->getPrefix(), TST->getAsIdentifier()), II, arguments);
|
||||
}
|
||||
|
||||
case NestedNameSpecifier::SpecifierKind::TypeSpec: {
|
||||
llvm::outs() << "\n------------------ TypeSpec -----------------------\n";
|
||||
TST->dump();
|
||||
llvm::outs() << " " << II->getName() << "\n";
|
||||
// when the kind of TST is TypeSpec, e.g. std::vector<T>::
|
||||
return resolve(QualType(TST->getAsType(), 0), II, arguments);
|
||||
}
|
||||
|
||||
case NestedNameSpecifier::SpecifierKind::TypeSpecWithTemplate: {
|
||||
llvm::outs() << "------------------ TypeSpecWithTemplate -----------------------\n";
|
||||
// when the kind of TST is TypeSpecWithTemplate, e.g. std::vector<T>::template name<U>::
|
||||
TST->dump();
|
||||
return resolve(QualType(TST->getAsType(), 0), II, arguments);
|
||||
}
|
||||
|
||||
default: {
|
||||
llvm::outs() << "\n------------------ Unknown -----------------------\n";
|
||||
TST->dump();
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QualType substitute(ClassTemplateDecl* CTD,
|
||||
const IdentifierInfo* II,
|
||||
ArrayRef<TemplateArgument> arguments = {}) {
|
||||
Sema::CodeSynthesisContext context;
|
||||
|
||||
auto args = resolve(arguments);
|
||||
|
||||
context.Entity = CTD;
|
||||
context.TemplateArgs = args.data();
|
||||
context.Kind = Sema::CodeSynthesisContext::TemplateInstantiation;
|
||||
S.pushCodeSynthesisContext(context);
|
||||
|
||||
MultiLevelTemplateArgumentList list;
|
||||
|
||||
auto recordDecl = CTD->getTemplatedDecl();
|
||||
auto member = recordDecl->lookup(II).front();
|
||||
|
||||
QualType type;
|
||||
|
||||
if(auto TAD = llvm::dyn_cast<TypeAliasDecl>(member)) {
|
||||
type = TAD->getUnderlyingType();
|
||||
} else if(auto TD = llvm::dyn_cast<TypedefDecl>(member)) {
|
||||
type = TD->getUnderlyingType();
|
||||
} else if(auto TATD = llvm::dyn_cast<TypeAliasTemplateDecl>(member)) {
|
||||
auto args2 = resolve(arguments);
|
||||
|
||||
context.Entity = TATD;
|
||||
context.TemplateArgs = args2.data();
|
||||
context.Kind = Sema::CodeSynthesisContext::TypeAliasTemplateInstantiation;
|
||||
S.pushCodeSynthesisContext(context);
|
||||
|
||||
MultiLevelTemplateArgumentList list;
|
||||
list.addOuterTemplateArguments(TATD, args2, false);
|
||||
|
||||
auto TAD = TATD->getTemplatedDecl();
|
||||
|
||||
type = TAD->getUnderlyingType();
|
||||
} else if(auto CTD = llvm::dyn_cast<ClassTemplateDecl>(member)) {
|
||||
return substitute(CTD, II, arguments);
|
||||
} else {
|
||||
member->dump();
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
list.addOuterTemplateArguments(CTD, args, true);
|
||||
return S.SubstType(type, list, {}, {});
|
||||
}
|
||||
|
||||
/// typename A<U1>::template B<U2>::template C<U3>::type::
|
||||
QualType resolve(const DependentTemplateSpecializationType* DTST, const IdentifierInfo* II) {
|
||||
// auto prefix;
|
||||
MultiLevelTemplateArgumentList list;
|
||||
while(true) {
|
||||
// list.addOuterTemplateArguments()
|
||||
}
|
||||
}
|
||||
|
||||
QualType resolve(QualType T, const IdentifierInfo* II, ArrayRef<TemplateArgument> arguments = {}) {
|
||||
if(!T->isDependentType() && arguments.size() == 0) {
|
||||
// TODO:
|
||||
}
|
||||
llvm::outs() << "\n";
|
||||
T.dump();
|
||||
Sema::CodeSynthesisContext context;
|
||||
|
||||
if(auto TTPT = T->getAs<TemplateTypeParmType>()) {
|
||||
llvm::outs() << "\n-------------------------------------------------\n";
|
||||
T->dump();
|
||||
llvm::outs() << " \n" << II->getName();
|
||||
|
||||
// e.g. when T is `T`
|
||||
// - index: 0
|
||||
} else if(auto TST = T->getAs<TemplateSpecializationType>()) {
|
||||
auto TemplateName = TST->getTemplateName();
|
||||
auto TemplateDecl = TemplateName.getAsTemplateDecl();
|
||||
auto TemplatedDecl = TemplateDecl->getTemplatedDecl();
|
||||
|
||||
if(auto CTD = llvm::dyn_cast<ClassTemplateDecl>(TemplateDecl)) {
|
||||
return substitute(CTD, II, TST->template_arguments());
|
||||
} else {
|
||||
TemplateDecl->dump();
|
||||
std::terminate();
|
||||
}
|
||||
auto TemplateArgs = TST->template_arguments();
|
||||
} else if(auto DTST = T->getAs<DependentTemplateSpecializationType>()) {
|
||||
auto TST = DTST->getQualifier()->getAsType()->getAs<TemplateSpecializationType>();
|
||||
auto TemplateName = TST->getTemplateName();
|
||||
auto TemplateDecl = TemplateName.getAsTemplateDecl();
|
||||
auto TemplatedDecl = TemplateDecl->getTemplatedDecl();
|
||||
|
||||
if(auto CTD = llvm::dyn_cast<ClassTemplateDecl>(TemplateDecl)) {
|
||||
auto args = resolve(TST->template_arguments());
|
||||
|
||||
context.Entity = CTD;
|
||||
context.TemplateArgs = args.data();
|
||||
context.Kind = Sema::CodeSynthesisContext::TemplateInstantiation;
|
||||
S.pushCodeSynthesisContext(context);
|
||||
|
||||
MultiLevelTemplateArgumentList list;
|
||||
list.addOuterTemplateArguments(CTD, args, true);
|
||||
|
||||
auto recordDecl = CTD->getTemplatedDecl();
|
||||
auto member = recordDecl->lookup(DTST->getIdentifier()).front();
|
||||
|
||||
if(auto CTD2 = llvm::dyn_cast<ClassTemplateDecl>(member)) {
|
||||
auto args2 = resolve(DTST->template_arguments());
|
||||
|
||||
context.Entity = CTD2;
|
||||
context.TemplateArgs = args2.data();
|
||||
context.Kind = Sema::CodeSynthesisContext::TemplateInstantiation;
|
||||
S.pushCodeSynthesisContext(context);
|
||||
|
||||
MultiLevelTemplateArgumentList list;
|
||||
list.addOuterTemplateArguments(CTD2, args2, true);
|
||||
list.addOuterTemplateArguments(CTD, args, true);
|
||||
|
||||
auto CRD = CTD2->getTemplatedDecl();
|
||||
return S.SubstType(
|
||||
llvm::dyn_cast<TypeAliasDecl>(CRD->lookup(II).front())->getUnderlyingType(),
|
||||
list,
|
||||
{},
|
||||
{});
|
||||
|
||||
} else if(auto TATD = llvm::dyn_cast<TypeAliasTemplateDecl>(member)) {
|
||||
auto args2 = resolve(arguments);
|
||||
|
||||
context.Entity = TATD;
|
||||
context.TemplateArgs = args2.data();
|
||||
context.Kind = Sema::CodeSynthesisContext::TypeAliasTemplateInstantiation;
|
||||
S.pushCodeSynthesisContext(context);
|
||||
|
||||
MultiLevelTemplateArgumentList list;
|
||||
list.addOuterTemplateArguments(TATD, args2, false);
|
||||
list.addOuterTemplateArguments(CTD, args, true);
|
||||
|
||||
auto TAD = TATD->getTemplatedDecl();
|
||||
return S.SubstType(TAD->getUnderlyingType(), list, {}, {});
|
||||
} else {
|
||||
member->dump();
|
||||
std::terminate();
|
||||
}
|
||||
} else {
|
||||
T->dump();
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// S.SubstType()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class DependentNameResolverV2 {
|
||||
public:
|
||||
Sema& S;
|
||||
ASTContext& Ctx;
|
||||
|
||||
std::vector<std::pair<Decl*, std::vector<TemplateArgument>>*> arguments;
|
||||
|
||||
public:
|
||||
DependentNameResolverV2(ASTContext& Ctx, Sema& S) : Ctx(Ctx), S(S) {}
|
||||
|
||||
std::vector<TemplateArgument> resolve(llvm::ArrayRef<TemplateArgument> arguments) {
|
||||
std::vector<TemplateArgument> result;
|
||||
for(auto arg: arguments) {
|
||||
if(arg.getKind() == TemplateArgument::ArgKind::Type) {
|
||||
if(auto type = llvm::dyn_cast<TemplateTypeParmType>(arg.getAsType())) {
|
||||
const TemplateTypeParmDecl* param = type->getDecl();
|
||||
|
||||
if(param && param->hasDefaultArgument()) {
|
||||
result.push_back(param->getDefaultArgument().getArgument());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push_back(arg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QualType dealias(QualType type) {
|
||||
if(auto DNT = type->getAs<TemplateSpecializationType>()) {
|
||||
return QualType(DNT, 0);
|
||||
} else if(auto DTST = type->getAs<DependentTemplateSpecializationType>()) {
|
||||
auto NNS = NestedNameSpecifier::Create(
|
||||
Ctx,
|
||||
nullptr,
|
||||
false,
|
||||
dealias(QualType(DTST->getQualifier()->getAsType(), 0)).getTypePtr());
|
||||
|
||||
return Ctx.getDependentTemplateSpecializationType(DTST->getKeyword(),
|
||||
NNS,
|
||||
DTST->getIdentifier(),
|
||||
resolve(DTST->template_arguments()));
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
QualType resolve(QualType type) {
|
||||
|
||||
while(true) {
|
||||
// llvm::outs() << "--------------------------------------------------------------------\n";
|
||||
// type.dump();
|
||||
|
||||
MultiLevelTemplateArgumentList list;
|
||||
if(auto DNT = type->getAs<DependentNameType>()) {
|
||||
type = resolve(resolve(DNT->getQualifier(), DNT->getIdentifier()));
|
||||
for(auto begin = arguments.rbegin(), end = arguments.rend(); begin != end; ++begin) {
|
||||
list.addOuterTemplateArguments((*begin)->first, (*begin)->second, true);
|
||||
}
|
||||
type = S.SubstType(dealias(type), list, {}, {});
|
||||
arguments.clear();
|
||||
|
||||
} else if(auto DTST = type->getAs<DependentTemplateSpecializationType>()) {
|
||||
auto ND = resolve(DTST->getQualifier(), DTST->getIdentifier());
|
||||
if(auto TATD = llvm::dyn_cast<TypeAliasTemplateDecl>(ND)) {
|
||||
auto args = resolve(DTST->template_arguments());
|
||||
|
||||
Sema::CodeSynthesisContext context;
|
||||
context.Entity = TATD;
|
||||
context.Kind = Sema::CodeSynthesisContext::TypeAliasTemplateInstantiation;
|
||||
context.TemplateArgs = args.data();
|
||||
S.pushCodeSynthesisContext(context);
|
||||
|
||||
list.addOuterTemplateArguments(TATD, args, true);
|
||||
for(auto begin = arguments.rbegin(), end = arguments.rend(); begin != end; ++begin) {
|
||||
list.addOuterTemplateArguments((*begin)->first, (*begin)->second, true);
|
||||
}
|
||||
|
||||
// llvm::outs() << "before:
|
||||
// ----------------------------------------------------------------\n";
|
||||
// TATD->getTemplatedDecl()->getUnderlyingType().dump();
|
||||
type = dealias(TATD->getTemplatedDecl()->getUnderlyingType());
|
||||
// llvm::outs() << "arguments:
|
||||
// -------------------------------------------------------------\n"; list.dump();
|
||||
type = S.SubstType(type, list, {}, {});
|
||||
// type.dump();
|
||||
arguments.clear();
|
||||
|
||||
} else {
|
||||
ND->dump();
|
||||
std::terminate();
|
||||
}
|
||||
// return resolve(DTST);
|
||||
} else if(auto LRT = type->getAs<LValueReferenceType>()) {
|
||||
type = Ctx.getLValueReferenceType(resolve(LRT->getPointeeType()));
|
||||
} else {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QualType resolve(NamedDecl* ND) {
|
||||
if(auto TD = llvm::dyn_cast<TypedefDecl>(ND)) {
|
||||
return TD->getUnderlyingType();
|
||||
} else if(auto TAD = llvm::dyn_cast<TypeAliasDecl>(ND)) {
|
||||
return TAD->getUnderlyingType();
|
||||
} else {
|
||||
ND->dump();
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
|
||||
NamedDecl* resolve(const NestedNameSpecifier* NNS, const IdentifierInfo* II) {
|
||||
switch(NNS->getKind()) {
|
||||
// prefix is an identifier, e.g. <...>::name::
|
||||
case NestedNameSpecifier::SpecifierKind::Identifier: {
|
||||
return lookup(resolve(resolve(NNS->getPrefix(), NNS->getAsIdentifier())), II);
|
||||
}
|
||||
|
||||
// prefix is a type, e.g. <...>::typename name::
|
||||
case NestedNameSpecifier::SpecifierKind::TypeSpec:
|
||||
case NestedNameSpecifier::SpecifierKind::TypeSpecWithTemplate: {
|
||||
return lookup(QualType(NNS->getAsType(), 0), II);
|
||||
}
|
||||
|
||||
default: {
|
||||
NNS->dump();
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NamedDecl* lookup(QualType Type, const IdentifierInfo* Name) {
|
||||
NamedDecl* TemplateDecl;
|
||||
ArrayRef<TemplateArgument> arguments;
|
||||
|
||||
llvm::outs() << "--------------------------------------------------------------------\n";
|
||||
Type.dump();
|
||||
|
||||
if(auto TTPT = Type->getAs<TemplateTypeParmType>()) {
|
||||
Type->dump();
|
||||
std::terminate();
|
||||
} else if(auto TST = Type->getAs<TemplateSpecializationType>()) {
|
||||
auto TemplateName = TST->getTemplateName();
|
||||
TemplateDecl = TemplateName.getAsTemplateDecl();
|
||||
arguments = TST->template_arguments();
|
||||
} else if(auto DTST = Type->getAs<DependentTemplateSpecializationType>()) {
|
||||
TemplateDecl = resolve(DTST->getQualifier(), DTST->getIdentifier());
|
||||
arguments = DTST->template_arguments();
|
||||
} else if(auto RT = Type->getAs<RecordType>()) {
|
||||
return RT->getDecl()->lookup(Name).front();
|
||||
} else {
|
||||
Type->dump();
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
this->arguments.push_back(
|
||||
new std::pair<Decl*, std::vector<TemplateArgument>>{TemplateDecl, resolve(arguments)});
|
||||
|
||||
NamedDecl* result;
|
||||
|
||||
Sema::CodeSynthesisContext context;
|
||||
context.Entity = TemplateDecl;
|
||||
context.Kind = Sema::CodeSynthesisContext::TemplateInstantiation;
|
||||
context.TemplateArgs = this->arguments.back()->second.data();
|
||||
S.pushCodeSynthesisContext(context);
|
||||
|
||||
if(auto CTD = llvm::dyn_cast<ClassTemplateDecl>(TemplateDecl)) {
|
||||
llvm::outs() << "--------------------------------------------------------------------\n";
|
||||
llvm::SmallVector<ClassTemplatePartialSpecializationDecl*> paritals;
|
||||
CTD->getPartialSpecializations(paritals);
|
||||
|
||||
for(auto partial: paritals) {
|
||||
partial->getInjectedSpecializationType().dump();
|
||||
}
|
||||
llvm::outs() << "--------------------------------------------------------------------\n";
|
||||
// CTD->findPartialSpecialization()
|
||||
auto partial = CTD->findPartialSpecialization(Type);
|
||||
if(partial) {
|
||||
result = partial->lookup(Name).front();
|
||||
}
|
||||
|
||||
if(!result) {
|
||||
result = CTD->getTemplatedDecl()->lookup(Name).front();
|
||||
}
|
||||
} else if(auto TATD = llvm::dyn_cast<TypeAliasTemplateDecl>(TemplateDecl)) {
|
||||
result = lookup(TATD->getTemplatedDecl()->getUnderlyingType(), Name);
|
||||
}
|
||||
|
||||
if(result == nullptr) {
|
||||
Type.dump();
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace clang
|
||||
|
||||
class ASTVistor : public clang::RecursiveASTVisitor<ASTVistor> {
|
||||
private:
|
||||
clang::Preprocessor& preprocessor;
|
||||
@@ -165,15 +624,13 @@ public:
|
||||
|
||||
bool VisitTypeAliasDecl(clang::TypeAliasDecl* decl) {
|
||||
auto& sm = context.getSourceManager();
|
||||
if(sm.isInMainFile(decl->getLocation()) && decl->getName() == "type") {
|
||||
if(sm.isInMainFile(decl->getLocation()) && decl->getName() == "result") {
|
||||
auto type = decl->getUnderlyingType();
|
||||
type.dump();
|
||||
llvm::outs() << "----------------------------------------------------------------\n";
|
||||
if(auto templateType = type->getAs<clang::DependentNameType>()) {
|
||||
DependentShaper shaper{context};
|
||||
auto result = shaper.simplify(templateType);
|
||||
result->dump();
|
||||
}
|
||||
llvm::outs() << "--------------------------------- Result ------------------------------\n";
|
||||
clang::DependentNameResolverV2 shaper{context, sema};
|
||||
auto result = shaper.resolve(type);
|
||||
result.dump();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -194,7 +651,7 @@ int main(int argc, const char** argv) {
|
||||
|
||||
auto invocation = std::make_shared<clang::CompilerInvocation>();
|
||||
std::vector<const char*> args = {
|
||||
"/home/ykiko/Project/C++/clice/external/llvm/bin/clang++",
|
||||
"/usr/local/bin/clang++",
|
||||
"-Xclang",
|
||||
"-no-round-trip-args",
|
||||
"-std=c++20",
|
||||
|
||||
@@ -29,67 +29,73 @@ public:
|
||||
clang::CodeCompletionContext Context,
|
||||
clang::CodeCompletionResult* Results,
|
||||
unsigned NumResults) override {
|
||||
auto contexts = Context.getVisitedContexts();
|
||||
for(auto c: contexts) {
|
||||
// llvm::outs() << " Kind: " << c->getDeclKindName() << "\n";
|
||||
// for(auto d: c->decls()) {
|
||||
// // d->dump();
|
||||
// }
|
||||
}
|
||||
|
||||
llvm::outs() << "code completion results:\n";
|
||||
switch(Context.getKind()) {
|
||||
case clang::CodeCompletionContext::CCC_Attribute: {
|
||||
}
|
||||
case clang::CodeCompletionContext::CCC_DotMemberAccess: {
|
||||
const auto type = Context.getBaseType();
|
||||
if(type->isDependentType()) {
|
||||
|
||||
if(const auto dependentType = type->getAs<clang::DependentNameType>()) {
|
||||
auto qualifers = dependentType->getQualifier();
|
||||
// qualifers->getKind() -> clang::NestedNameSpecifier::SpecifierKind;
|
||||
// auto t = qualifers->getAsType();
|
||||
// TODO: 看是否能根据主模板一路把依赖名替换下去,直到变成非依赖名
|
||||
} else if(const auto dependentType = type->getAs<clang::TemplateSpecializationType>()) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* ... */
|
||||
default: {
|
||||
llvm::outs() << " Kind: " << Context.getKind() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned i = 0; i < NumResults; ++i) {
|
||||
clang::CodeCompletionResult& Result = Results[i];
|
||||
|
||||
switch(Result.Kind) {
|
||||
case clang::CodeCompletionResult::RK_Declaration: {
|
||||
llvm::outs() << " Declaration: ";
|
||||
llvm::outs() << Result.Declaration->getNameAsString() << "\n";
|
||||
break;
|
||||
}
|
||||
|
||||
case clang::CodeCompletionResult::RK_Keyword: {
|
||||
llvm::outs() << " Keyword: ";
|
||||
llvm::outs() << Result.Keyword << "\n";
|
||||
break;
|
||||
}
|
||||
|
||||
case clang::CodeCompletionResult::RK_Macro: {
|
||||
llvm::outs() << " Macro: ";
|
||||
llvm::outs() << Result.Macro->getName() << "\n";
|
||||
break;
|
||||
}
|
||||
|
||||
case clang::CodeCompletionResult::RK_Pattern: {
|
||||
llvm::outs() << " Pattern: ";
|
||||
llvm::outs() << Result.Pattern->getAsString() << "\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
llvm::outs() << NumResults << " code completion results\n";
|
||||
auto kind = Context.getKind();
|
||||
if(kind == clang::CodeCompletionContext::CCC_DotMemberAccess) {
|
||||
auto type = Context.getBaseType();
|
||||
type->dump();
|
||||
}
|
||||
// auto contexts = Context.getVisitedContexts();
|
||||
// for(auto c: contexts) {
|
||||
// // llvm::outs() << " Kind: " << c->getDeclKindName() << "\n";
|
||||
// // for(auto d: c->decls()) {
|
||||
// // // d->dump();
|
||||
// // }
|
||||
// }
|
||||
//
|
||||
// llvm::outs() << "code completion results:\n";
|
||||
// switch(Context.getKind()) {
|
||||
// case clang::CodeCompletionContext::CCC_Attribute: {
|
||||
// }
|
||||
// case clang::CodeCompletionContext::CCC_DotMemberAccess: {
|
||||
// const auto type = Context.getBaseType();
|
||||
// if(type->isDependentType()) {
|
||||
//
|
||||
// if(const auto dependentType = type->getAs<clang::DependentNameType>()) {
|
||||
// auto qualifers = dependentType->getQualifier();
|
||||
// // qualifers->getKind() -> clang::NestedNameSpecifier::SpecifierKind;
|
||||
// // auto t = qualifers->getAsType();
|
||||
// // TODO: 看是否能根据主模板一路把依赖名替换下去,直到变成非依赖名
|
||||
// } else if(const auto dependentType = type->getAs<clang::TemplateSpecializationType>()) {
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// /* ... */
|
||||
// default: {
|
||||
// llvm::outs() << " Kind: " << Context.getKind() << "\n";
|
||||
// }
|
||||
//}
|
||||
//
|
||||
// for(unsigned i = 0; i < NumResults; ++i) {
|
||||
// clang::CodeCompletionResult& Result = Results[i];
|
||||
//
|
||||
// switch(Result.Kind) {
|
||||
// case clang::CodeCompletionResult::RK_Declaration: {
|
||||
// llvm::outs() << " Declaration: ";
|
||||
// llvm::outs() << Result.Declaration->getNameAsString() << "\n";
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// case clang::CodeCompletionResult::RK_Keyword: {
|
||||
// llvm::outs() << " Keyword: ";
|
||||
// llvm::outs() << Result.Keyword << "\n";
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// case clang::CodeCompletionResult::RK_Macro: {
|
||||
// llvm::outs() << " Macro: ";
|
||||
// llvm::outs() << Result.Macro->getName() << "\n";
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// case clang::CodeCompletionResult::RK_Pattern: {
|
||||
// llvm::outs() << " Pattern: ";
|
||||
// llvm::outs() << Result.Pattern->getAsString() << "\n";
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
void ProcessOverloadCandidates(clang::Sema& S,
|
||||
@@ -109,13 +115,13 @@ int main(int argc, const char** argv) {
|
||||
clang::DiagnosticIDs* ids = new clang::DiagnosticIDs();
|
||||
clang::DiagnosticOptions* diag_opts = new clang::DiagnosticOptions();
|
||||
diag_opts->IgnoreWarnings = true;
|
||||
clang::DiagnosticConsumer* consumer = new clang::TextDiagnosticPrinter(llvm::errs(), diag_opts);
|
||||
clang::DiagnosticConsumer* consumer = new clang::IgnoringDiagConsumer();
|
||||
clang::DiagnosticsEngine* engine = new clang::DiagnosticsEngine(ids, diag_opts, consumer);
|
||||
instance->setDiagnostics(engine);
|
||||
|
||||
auto invocation = std::make_shared<clang::CompilerInvocation>();
|
||||
std::vector<const char*> args = {
|
||||
"/home/ykiko/Project/C++/clice/external/llvm/bin/clang++",
|
||||
"/usr/local/bin/clang++",
|
||||
"-Xclang",
|
||||
"-no-round-trip-args",
|
||||
"-std=c++20",
|
||||
|
||||
130
docs/examples/TreeTransform.cpp
Normal file
130
docs/examples/TreeTransform.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include <Clang/Clang.h>
|
||||
#include <clang/Sema/Lookup.h>
|
||||
#include <clang/Sema/Template.h>
|
||||
#include <TreeTransform.h>
|
||||
#include <clang/Sema/SemaConsumer.h>
|
||||
|
||||
namespace clang {
|
||||
class MyTreeTransform : public TreeTransform<MyTreeTransform> {
|
||||
ASTContext& Context;
|
||||
|
||||
public:
|
||||
MyTreeTransform(Sema& SemaRef, clang::ASTContext& Context) :
|
||||
TreeTransform<MyTreeTransform>(SemaRef), Context(Context) {}
|
||||
|
||||
ExprResult TransformBinaryOperator(BinaryOperator* E) {
|
||||
llvm::outs() << "Transforming BinaryOperator\n";
|
||||
E->dump();
|
||||
auto LHS = TransformExpr(E->getLHS());
|
||||
auto RHS = TransformExpr(E->getRHS());
|
||||
return SemaRef.BuildBinOp(SemaRef.getCurScope(),
|
||||
E->getOperatorLoc(),
|
||||
E->getOpcode(),
|
||||
RHS.get(),
|
||||
LHS.get());
|
||||
}
|
||||
|
||||
ExprResult RebuildBinaryOperator(SourceLocation OpLoc, BinaryOperatorKind Opc, Expr* LHS, Expr* RHS) {
|
||||
llvm::outs() << "-----------------------------" << "\n";
|
||||
return SemaRef.BuildBinOp(SemaRef.getCurScope(), OpLoc, Opc, RHS, LHS);
|
||||
}
|
||||
};
|
||||
|
||||
class ASTVisitor : public RecursiveASTVisitor<ASTVisitor> {
|
||||
clang::ASTContext& Context;
|
||||
clang::Sema& SemaRef;
|
||||
|
||||
public:
|
||||
|
||||
public:
|
||||
ASTVisitor(clang::ASTContext& Context, clang::Sema& SemaRef) : Context(Context), SemaRef(SemaRef) {}
|
||||
|
||||
bool VisitVarDecl(VarDecl* decl) {
|
||||
llvm::outs() << "Visiting BinaryOperator\n";
|
||||
clang::MyTreeTransform transformer{SemaRef, Context};
|
||||
decl->dump();
|
||||
auto result = transformer.TransformExpr(decl->getInit());
|
||||
decl->setInit(result.get());
|
||||
decl->dump();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class MySemaConsumer : public clang::SemaConsumer {
|
||||
clang::CompilerInstance& CI;
|
||||
|
||||
public:
|
||||
MySemaConsumer(clang::CompilerInstance& CI) : CI(CI) {}
|
||||
|
||||
void HandleTranslationUnit(clang::ASTContext& Context) override {
|
||||
clang::Sema& SemaRef = CI.getSema();
|
||||
clang::MyTreeTransform Transformer(SemaRef, Context);
|
||||
llvm::outs() << "------------------------------\n";
|
||||
auto TU = Context.getTranslationUnitDecl();
|
||||
clang::ASTVisitor visitor{Context, SemaRef};
|
||||
visitor.TraverseDecl(TU);
|
||||
}
|
||||
};
|
||||
|
||||
class MyFrontendAction : public clang::ASTFrontendAction {
|
||||
public:
|
||||
std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance& CI,
|
||||
llvm::StringRef) override {
|
||||
return std::make_unique<MySemaConsumer>(CI);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace clang
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
assert(argc == 2 && "Usage: Preprocessor <source-file>");
|
||||
llvm::outs() << "running ASTVisitor...\n";
|
||||
|
||||
auto instance = std::make_unique<clang::CompilerInstance>();
|
||||
|
||||
clang::DiagnosticIDs* ids = new clang::DiagnosticIDs();
|
||||
clang::DiagnosticOptions* diag_opts = new clang::DiagnosticOptions();
|
||||
clang::DiagnosticConsumer* consumer = new clang::IgnoringDiagConsumer();
|
||||
clang::DiagnosticsEngine* engine = new clang::DiagnosticsEngine(ids, diag_opts, consumer);
|
||||
instance->setDiagnostics(engine);
|
||||
|
||||
auto invocation = std::make_shared<clang::CompilerInvocation>();
|
||||
std::vector<const char*> args = {
|
||||
"/usr/local/bin/clang++",
|
||||
"-Xclang",
|
||||
"-no-round-trip-args",
|
||||
"-std=c++20",
|
||||
argv[1],
|
||||
};
|
||||
|
||||
invocation = clang::createInvocation(args, {});
|
||||
// clang::CompilerInvocation::CreateFromArgs(*invocation, args, instance->getDiagnostics());
|
||||
instance->setInvocation(std::move(invocation));
|
||||
|
||||
if(!instance->createTarget()) {
|
||||
llvm::errs() << "Failed to create target\n";
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// instance->setASTConsumer(std::make_unique<clang::MyASTConsumer>(*instance));
|
||||
|
||||
clang::MyFrontendAction action;
|
||||
|
||||
if(!action.BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) {
|
||||
llvm::errs() << "Failed to begin source file\n";
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
if(auto error = action.Execute()) {
|
||||
llvm::errs() << "Failed to execute action: " << error << "\n";
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
auto tu = instance->getASTContext().getTranslationUnitDecl();
|
||||
|
||||
// tu->dump();
|
||||
// ASTVistor visitor{instance->getPreprocessor(), buffer, instance->getASTContext(),
|
||||
// instance->getSema()}; visitor.TraverseDecl(tu);
|
||||
|
||||
action.EndSourceFile();
|
||||
};
|
||||
@@ -1,65 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <Clang/Clang.h>
|
||||
|
||||
namespace clice {
|
||||
|
||||
class DependentShaper {
|
||||
clang::ASTContext& context;
|
||||
|
||||
public:
|
||||
DependentShaper(clang::ASTContext& context) : context(context) {}
|
||||
|
||||
const clang::NamedDecl* simplify(const clang::TemplateSpecializationType* templateType,
|
||||
const clang::IdentifierInfo* identifier) {
|
||||
// get the template decl of the template specialization
|
||||
// e.g. vector<int> -> vector
|
||||
|
||||
// get the template arguments of the template specialization
|
||||
// e.g. vector<int> -> int
|
||||
auto templateDecl = templateType->getTemplateName().getAsTemplateDecl();
|
||||
if(auto decl = llvm::dyn_cast<clang::ClassTemplateDecl>(templateDecl)) {
|
||||
// get the record decl of the template decl
|
||||
clang::CXXRecordDecl* recordDecl = decl->getTemplatedDecl();
|
||||
// lookup the identifier in the record decl
|
||||
auto result = recordDecl->lookup(identifier);
|
||||
assert(result.size() == 1 && "multiple results found");
|
||||
return result.front();
|
||||
|
||||
} else if(auto decl = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(templateDecl)) {
|
||||
}
|
||||
}
|
||||
|
||||
const clang::NamedDecl* simplify(const clang::NestedNameSpecifier* specifier,
|
||||
const clang::IdentifierInfo* identifier) {
|
||||
switch(specifier->getKind()) {
|
||||
case clang::NestedNameSpecifier::TypeSpec: {
|
||||
auto node = specifier->getAsType();
|
||||
|
||||
if(auto type = node->getAs<clang::TemplateTypeParmType>()) {
|
||||
// represent a direct dependent name, e.g. typename T::^ name
|
||||
// and can not be further simplified
|
||||
} else if(auto type = node->getAs<clang::TemplateSpecializationType>()) {
|
||||
// represent a dependent name that is a template specialization
|
||||
// e.g. typename vector<int>::^ name, and can be further simplified
|
||||
return simplify(type, identifier);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case clang::NestedNameSpecifier::TypeSpecWithTemplate: {
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const clang::NamedDecl* simplify(const clang::DependentNameType* type) {
|
||||
return simplify(type->getQualifier(), type->getIdentifier());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace clice
|
||||
0
include/Clang/SelectionTree.h
Normal file
0
include/Clang/SelectionTree.h
Normal file
@@ -20,14 +20,14 @@ public:
|
||||
requires std::is_invocable_v<Fn>
|
||||
async(Fn&& fn) : callback(std::forward<Fn>(fn)) {}
|
||||
|
||||
bool await_ready(this const async& self) noexcept { return false; }
|
||||
bool await_ready() noexcept { return false; }
|
||||
|
||||
void await_suspend(this async& self, std::coroutine_handle<> handle) noexcept {
|
||||
self.handle = handle;
|
||||
self.req.data = &self;
|
||||
void await_suspend(std::coroutine_handle<> handle) noexcept {
|
||||
handle = handle;
|
||||
req.data = this;
|
||||
uv_queue_work(
|
||||
uv_default_loop(),
|
||||
&self.req,
|
||||
&this->req,
|
||||
[](uv_work_t* req) {
|
||||
auto& self = *static_cast<async*>(req->data);
|
||||
self.result = self.callback();
|
||||
@@ -41,9 +41,9 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
decltype(auto) await_resume(this async& self) noexcept {
|
||||
assert(self.result.has_value());
|
||||
return std::move(*self.result);
|
||||
decltype(auto) await_resume() noexcept {
|
||||
// assert(self.result.has_value());
|
||||
return std::move(*this->result);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCLICE_TEST=ON \
|
||||
-DCLICE_EXAMPLES=ON
|
||||
|
||||
5
scripts/clone.sh
Executable file
5
scripts/clone.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
cd external
|
||||
git clone https://github.com/google/googletest.git --depth=1
|
||||
git clone https://github.com/nlohmann/json.git --depth=1
|
||||
git clone https://github.com/libuv/libuv.git --depth=1
|
||||
git clone https://github.com/gabime/spdlog.git --depth=1
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <Clang/Directive.h>
|
||||
#include <vector>
|
||||
|
||||
namespace clice {
|
||||
|
||||
@@ -14,7 +15,8 @@ public:
|
||||
clang::OptionalFileEntryRef File,
|
||||
StringRef SearchPath,
|
||||
StringRef RelativePath,
|
||||
const clang::Module* Imported,
|
||||
const clang::Module* SuggestedModule,
|
||||
bool ModuleImported,
|
||||
clang::SrcMgr::CharacteristicKind FileType) override {}
|
||||
|
||||
void moduleImport(clang::SourceLocation ImportLoc,
|
||||
|
||||
@@ -107,7 +107,7 @@ public:
|
||||
if(lastLocation != location) {
|
||||
// if not, update the last location and find the token
|
||||
lastLocation = location;
|
||||
auto token = buffer.spelledTokenAt(location);
|
||||
auto token = buffer.spelledTokenContaining(location);
|
||||
if(token) {
|
||||
// if there is a corresponding token, update the token
|
||||
auto iter = tokenMap.find(token);
|
||||
|
||||
Reference in New Issue
Block a user