#include #include namespace clice { namespace { class SelectionBuilder { public: 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()); 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; })); 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(requires { node->isImplicit(); }) { if(node->isImplicit()) { return true; } } auto range = getSourceRange(node); if(range.isInvalid()) { return true; } // range.dump(context.getSourceManager()); // dump(node); return false; } template bool hook(const Node* node, const Callback& callback) { if(isSkippable(node)) { return true; } 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 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; } 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; } 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: /// 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 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); }); } bool TraverseStmt(clang::Stmt* stmt) { return builder.hook(stmt, [&] { return Base::TraverseStmt(stmt); }); } bool TraverseAttr(clang::Attr* attr) { return builder.hook(attr, [&] { return Base::TraverseAttr(attr); }); } /// we don't care about the node without location information, so skip them. bool shouldWalkTypesOfTypeLocs() { return false; } 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); }); } bool TraverseTemplateArgumentLoc(const clang::TemplateArgumentLoc& argument) { return builder.hook(&argument, [&] { return Base::TraverseTemplateArgumentLoc(argument); }); } bool TraverseCXXBaseSpecifier(const clang::CXXBaseSpecifier& base) { return builder.hook(&base, [&] { return Base::TraverseCXXBaseSpecifier(base); }); } bool TraverseConstructorInitializer(clang::CXXCtorInitializer* init) { return builder.hook(init, [&] { return Base::TraverseConstructorInitializer(init); }); } // 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; } 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(std::uint32_t begin, std::uint32_t end, clang::ASTContext& context, clang::syntax::TokenBuffer& tokens) { SelectionBuilder builder(begin, end, context, tokens); auto tree = builder.build(); root = tree.root; llvm::outs() << "----------------------------------------\n"; dump(root, context); } } // namespace clice