diff --git a/include/Compiler/Selection.h b/include/Compiler/Selection.h index ae2ebeb5..2352b963 100644 --- a/include/Compiler/Selection.h +++ b/include/Compiler/Selection.h @@ -13,10 +13,21 @@ namespace clice { class SelectionTree { public: - SelectionTree(clang::ASTContext& context, - const clang::syntax::TokenBuffer& tokens, - clang::SourceLocation begin, - clang::SourceLocation end); + struct Node { + Node* parent; + clang::DynTypedNode node; + llvm::SmallVector children; + }; + + SelectionTree() = default; + + SelectionTree(std::uint32_t begin, + std::uint32_t end, + clang::ASTContext& context, + clang::syntax::TokenBuffer& tokens); + + Node* root; + std::deque storage; }; } // namespace clice diff --git a/src/Compiler/Selection.cpp b/src/Compiler/Selection.cpp index 2130c324..198dd023 100644 --- a/src/Compiler/Selection.cpp +++ b/src/Compiler/Selection.cpp @@ -1,4 +1,8 @@ -#include "Compiler/Selection.h" + + +#include + +#include namespace clice { @@ -6,33 +10,61 @@ namespace { class SelectionBuilder { public: - SelectionBuilder(clang::SourceRange input, clang::ASTContext& context) : input(input), context(context) {} + SelectionBuilder(std::uint32_t begin, + std::uint32_t end, + clang::ASTContext& context, + clang::syntax::TokenBuffer& buffer) : context(context), buffer(buffer) { + // The location in clang AST is token-based, of course. Because the parser + // processes tokens from the lexer. So we need to find boundary tokens at first. + auto& sm = context.getSourceManager(); // FIXME: support other file. + auto tokens = buffer.spelledTokens(sm.getMainFileID()); - void push() {} + left = std::to_address(std::partition_point(tokens.begin(), tokens.end(), [&](const auto& token) { + // int xxxx = 3; + // ^^^^^^ + // expect to find the first token whose end location is greater than or equal to `begin`. + return sm.getFileOffset(token.endLocation()) < begin; + })); - void pop() {} + rigth = std::to_address(std::partition_point(tokens.rbegin(), tokens.rend(), [&](const auto& token) { + // int xxxx = 3; + // ^^^^^^ + // expect to find the first token whose start location is less than or equal to `end`. + return sm.getFileOffset(token.location()) > end; + })); + + if(left == tokens.end() || rigth == tokens.end()) { + std::terminate(); + return; + } + } + + template + auto getSourceRange(const Node* node) -> clang::SourceRange { + if constexpr(std::is_base_of_v) { + return node->getRange(); + } else { + return node->getSourceRange(); + } + } template bool isSkippable(const Node* node) { - if constexpr(std::is_same_v) { - if(llvm::dyn_cast(node)) { - return false; + if constexpr(requires { node->isImplicit(); }) { + if(node->isImplicit()) { + return true; } } - clang::SourceRange range; - if constexpr(std::is_base_of_v) { - range = node->getRange(); - } else { - range = node->getSourceRange(); - } - + auto range = getSourceRange(node); if(range.isInvalid()) { return true; } - range.dump(context.getSourceManager()); - return input.getBegin() > range.getEnd() || input.getEnd() < range.getBegin(); + // range.dump(context.getSourceManager()); + // dump(node); + + return false; } template @@ -41,35 +73,97 @@ public: return true; } - return callback(); + storage.emplace_back(SelectionTree::Node{nullptr, clang::DynTypedNode::create(*node)}); + auto range = getSourceRange(node); + + llvm::outs() << "-----------------------------------------\n"; + range.dump(context.getSourceManager()); + clang::SourceRange(left->location(), rigth->location()).dump(context.getSourceManager()); + + // FIXME: currently we only consider fully nested case. + // consider supporting partially nested case. + + if(range.getBegin() < left->location() && range.getEnd() > rigth->location()) { + // if the source range of node contains the boundary tokens, its + // children may be selected. so traverse them recursively. + llvm::outs() << "select\n"; + stack.emplace(&storage.back()); + bool ret = callback(); + return ret; + } + + // if the boundary tokens contain the source range of node, it means + // the node is selected. store the father node and skip its children. + if(left->location() <= range.getBegin() && rigth->location() >= range.getEnd()) { + if(!stack.empty()) { + llvm::outs() << "selected\n"; + stack.top()->children.push_back(&storage.back()); + } + return true; + } + + return true; } + template + void dump(const Node* node) { + if constexpr(requires { node->dump(); }) { + node->dump(); + } + + if constexpr(std::is_same_v) { + const clang::NestedNameSpecifierLoc& NNSL = *node; + NNSL.getNestedNameSpecifier()->dump(); + llvm::outs() << "\n"; + } + + if constexpr(std::is_same_v) { + const clang::Attr& attr = *node; + attr.getScopeLoc().dump(context.getSourceManager()); + attr.printPretty(llvm::outs(), context.getPrintingPolicy()); + llvm::outs() << "\n"; + } + } + + using Node = SelectionTree::Node; + + SelectionTree build(); + private: - clang::SourceRange input; + /// the two boundary tokens. + const clang::syntax::Token* left; + const clang::syntax::Token* rigth; clang::ASTContext& context; + clang::syntax::TokenBuffer& buffer; + /// father nodes stack. + std::stack stack; + std::deque storage; }; +/*/ + +/*/ class SelectionCollector : public clang::RecursiveASTVisitor { public: SelectionCollector(SelectionBuilder& builder) : builder(builder) {} using Base = clang::RecursiveASTVisitor; - bool TraverseStmt(clang::Stmt* stmt) { - return builder.hook(stmt, [&] { - return Base::TraverseStmt(stmt); + bool TraverseDecl(clang::Decl* decl) { + /// `TranslationUnitDecl` has invalid location information. + /// So we process it separately. + if(llvm::isa_and_nonnull(decl)) { + return Base::TraverseDecl(decl); + } + + return builder.hook(decl, [&] { + return Base::TraverseDecl(decl); }); } - /// we don't care about the type without location information. - /// so just skip all its children. - bool TraverseType(clang::QualType type) { - return true; - } - - bool TraverseTypeLoc(clang::TypeLoc loc) { - return builder.hook(&loc, [&] { - return Base::TraverseTypeLoc(loc); + bool TraverseStmt(clang::Stmt* stmt) { + return builder.hook(stmt, [&] { + return Base::TraverseStmt(stmt); }); } @@ -79,18 +173,31 @@ public: }); } - bool TraverseDecl(clang::Decl* decl) { - return builder.hook(decl, [&] { - return Base::TraverseDecl(decl); - }); + /// we don't care about the node without location information, so skip them. + bool shouldWalkTypesOfTypeLocs() { + return false; } - /// we don't care about the name without location information. - /// so just skip all its children. - bool TraverseNestedNameSpecifier(clang::NestedNameSpecifier* NNS) { + bool TraverseType(clang::QualType) { return true; } + bool TraverseNestedNameSpecifier(clang::NestedNameSpecifier*) { + return true; + } + + bool TraverseTypeLoc(clang::TypeLoc loc) { + /// clang currently doesn't record any information for `QualifiedTypeLoc`. + /// It has same location with its inner type. So we just ignore it. + if(auto QTL = loc.getAs()) { + return TraverseTypeLoc(QTL.getUnqualifiedLoc()); + } + + return builder.hook(&loc, [&] { + return Base::TraverseTypeLoc(loc); + }); + } + bool TraverseNestedNameSpecifierLoc(clang::NestedNameSpecifierLoc NNS) { return builder.hook(&NNS, [&] { return Base::TraverseNestedNameSpecifierLoc(NNS); @@ -115,6 +222,12 @@ public: }); } + // bool TraverseDeclarationNameInfo(clang::DeclarationNameInfo info) { + // return builder.hook(&info, [&] { + // return Base::TraverseDeclarationNameInfo(info); + // }); + // } + // FIXME: figure out concept in clang AST. bool TraverseConceptReference(clang::ConceptReference* concept_) { return true; @@ -124,17 +237,37 @@ private: SelectionBuilder& builder; }; +SelectionTree SelectionBuilder::build() { + SelectionCollector collector(*this); + collector.TraverseAST(context); + + SelectionTree tree; + tree.root = stack.empty() ? nullptr : stack.top(); + tree.storage = std::move(storage); + return tree; +} + +void dump(const SelectionTree::Node* node, clang::ASTContext& context) { + if(node) { + node->node.dump(llvm::outs(), context); + for(auto child: node->children) { + dump(child, context); + } + } +} + } // namespace -SelectionTree::SelectionTree(clang::ASTContext& context, - const clang::syntax::TokenBuffer& tokens, - clang::SourceLocation begin, - clang::SourceLocation end) { +SelectionTree::SelectionTree(std::uint32_t begin, + std::uint32_t end, + clang::ASTContext& context, + clang::syntax::TokenBuffer& tokens) { - SelectionBuilder builder({begin, end}, context); - SelectionCollector collector(builder); - collector.TraverseAST(context); - // context.getTranslationUnitDecl()->dump(); + SelectionBuilder builder(begin, end, context, tokens); + auto tree = builder.build(); + root = tree.root; + llvm::outs() << "----------------------------------------\n"; + dump(root, context); } } // namespace clice diff --git a/tests/AST/Selection.cpp b/tests/AST/Selection.cpp index 53278154..8e6ad52e 100644 --- a/tests/AST/Selection.cpp +++ b/tests/AST/Selection.cpp @@ -18,9 +18,9 @@ TEST(clice, SelectionTree) { auto AST = ParsedAST::build("main.cpp", content, compileArgs); auto id = AST->getFileID("main.cpp"); auto& sm = AST->context.getSourceManager(); - auto begin = sm.translateLineCol(id, 1, 5); - auto end = sm.translateLineCol(id, 1, 8); - SelectionTree tree(AST->context, AST->tokenBuffer, begin, end); + auto begin = sm.translateLineCol(id, 2, 6); + auto end = sm.translateLineCol(id, 2, 12); + SelectionTree tree(sm.getFileOffset(begin), sm.getFileOffset(end), AST->context, AST->tokenBuffer); }); } diff --git a/tests/Source/SelectionTree/test.cpp b/tests/Source/SelectionTree/test.cpp index 676a651f..5ae9c524 100644 --- a/tests/Source/SelectionTree/test.cpp +++ b/tests/Source/SelectionTree/test.cpp @@ -1 +1,3 @@ -int foo = 122; \ No newline at end of file +struct X { + int xxxxxxx; +};