Index: clangd/index/dex/dexp/Dexp.cpp =================================================================== --- clangd/index/dex/dexp/Dexp.cpp +++ clangd/index/dex/dexp/Dexp.cpp @@ -25,7 +25,7 @@ using clang::clangd::loadIndex; using clang::clangd::Symbol; using clang::clangd::SymbolIndex; -using llvm::StringRef; +using namespace llvm; namespace { @@ -52,51 +52,42 @@ llvm::outs() << llvm::formatv("{0} took {1:ms+n}.\n", Name, Duration); } -void fuzzyFind(llvm::StringRef UnqualifiedName, const SymbolIndex &Index) { - FuzzyFindRequest Request; - Request.Limit = 10; - Request.Query = UnqualifiedName; - // FIXME(kbobyrev): Print symbol final scores to see the distribution. - static const auto OutputFormat = "{0,-4} | {1,-40} | {2,-25}\n"; - llvm::outs() << llvm::formatv(OutputFormat, "Rank", "Symbol ID", - "Symbol Name"); - size_t Rank = 0; - Index.fuzzyFind(Request, [&](const Symbol &Sym) { - llvm::outs() << llvm::formatv(OutputFormat, Rank++, Sym.ID.str(), Sym.Name); - }); -} - -static const std::string HelpMessage = R"(dexp commands: - -> find Name - -Constructs fuzzy find request given unqualified symbol name and returns top 10 -symbols retrieved from index. - -> lookup SymbolID - -Retrieves symbol names given USR. -)"; - -void help() { llvm::outs() << HelpMessage; } - -void lookup(StringRef USR, const SymbolIndex &Index) { - llvm::DenseSet IDs{clang::clangd::SymbolID{USR}}; - clang::clangd::LookupRequest Request{IDs}; - bool FoundSymbol = false; - Index.lookup(Request, [&](const Symbol &Sym) { - if (!FoundSymbol) - FoundSymbol = true; - llvm::outs() << SymbolToYAML(Sym); - }); - if (!FoundSymbol) - llvm::outs() << "not found\n"; -} +// REPL commands inherit from Command and contain their options as members. +// Creating a Command populates parser options, parseAndRun() resets them. +class Command { + // By resetting the parser options, we lost the standard -help flag. + cl::opt> Help{ + "help", cl::desc("Display available options"), cl::ValueDisallowed, + cl::cat(cl::GeneralCategory)}; + virtual void run() = 0; + +protected: + const SymbolIndex *Index; + +public: + virtual ~Command() = default; + virtual void parseAndRun(ArrayRef Argv, const char *Overview, + const SymbolIndex &Index) { + std::string ParseErrs; + llvm::raw_string_ostream OS(ParseErrs); + bool Ok = + cl::ParseCommandLineOptions(Argv.size(), Argv.data(), Overview, &OS); + if (Help.getNumOccurrences() > 0) { + // Avoid printing parse errors in this case. + // (Well, in theory. A bunch get printed to llvm::errs() regardless!) + cl::PrintHelpMessage(); + } else { + outs() << OS.str(); + if (Ok) { + this->Index = &Index; + reportTime(Argv[0], [&] { run(); }); + } + } + cl::ResetCommandLineParser(); // must do this before opts are destroyed. + } +}; -// FIXME(kbobyrev): Make this an actual REPL: probably use LLVM Command Line -// library for parsing flags and arguments. -// FIXME(kbobyrev): Ideas for commands: -// * symbol lookup: print out symbol in YAML format given SymbolID +// FIXME(kbobyrev): Ideas for more commands: // * find symbol references: print set of reference locations // * load/swap/reload index: this would make it possible to get rid of llvm::cl // usages in the tool driver and actually use llvm::cl library in the REPL. @@ -105,39 +96,86 @@ // * show number of tokens of each kind // * print out tokens with the most dense posting lists // * print out tokens with least dense posting lists -void dispatch(StringRef Request, const SymbolIndex &Index) { - llvm::SmallVector Arguments; - Request.split(Arguments, ' '); - if (Arguments.empty()) { - llvm::outs() << "Request can not be empty.\n"; - help(); - return; - } - if (Arguments.front() == "find") { - if (Arguments.size() != 2) { - llvm::outs() << "find request must specify unqualified symbol name.\n"; - return; +class FuzzyFind : public Command { + cl::opt Query{ + "query", + cl::Positional, + cl::Required, + cl::desc("Query string to be fuzzy-matched"), + }; + cl::opt Scopes{ + "scopes", + cl::desc("Allowed symbol scopes (comma-separated list)"), + }; + cl::opt Limit{ + "limit", + cl::init(10), + cl::desc("Max results to display"), + }; + + void run() override { + FuzzyFindRequest Request; + Request.Limit = Limit; + Request.Query = Query; + if (Scopes.getNumOccurrences() > 0) { + llvm::SmallVector Scopes; + StringRef(this->Scopes).split(Scopes, ','); + Request.Scopes = {Scopes.begin(), Scopes.end()}; } - reportTime("fuzzy find request", - [&]() { fuzzyFind(Arguments.back(), Index); }); - } else if (Arguments.front() == "lookup") { - if (Arguments.size() != 2) { - llvm::outs() << "lookup request must specify symbol ID .\n"; + // FIXME(kbobyrev): Print symbol final scores to see the distribution. + static const auto OutputFormat = "{0,-4} | {1,-40} | {2,-25}\n"; + llvm::outs() << llvm::formatv(OutputFormat, "Rank", "Symbol ID", + "Symbol Name"); + size_t Rank = 0; + Index->fuzzyFind(Request, [&](const Symbol &Sym) { + llvm::outs() << llvm::formatv(OutputFormat, Rank++, Sym.ID.str(), + Sym.Name); + }); + } +}; + +class Lookup : public Command { + cl::opt ID{ + "id", + cl::Positional, + cl::Required, + cl::desc("Symbol ID to look up (hex)"), + }; + + void run() override { + auto Raw = fromHex(ID); + if (Raw.size() != clang::clangd::SymbolID::RawSize) { + llvm::outs() << "invalid SymbolID\n"; return; } - reportTime("lookup request", [&]() { lookup(Arguments.back(), Index); }); - } else if (Arguments.front() == "help") { - help(); - } else { - llvm::outs() << "Unknown command. Try 'help'\n"; + + clang::clangd::LookupRequest Request; + Request.IDs = {clang::clangd::SymbolID::fromRaw(Raw)}; + bool FoundSymbol = false; + Index->lookup(Request, [&](const Symbol &Sym) { + FoundSymbol = true; + llvm::outs() << SymbolToYAML(Sym); + }); + if (!FoundSymbol) + llvm::outs() << "not found\n"; } -} +}; + +struct { + const char *Name; + const char *Description; + std::function()> Implementation; +} CommandInfo[] = { + {"find", "Search for symbols with fuzzyFind", llvm::make_unique}, + {"lookup", "Dump symbol details by ID", llvm::make_unique}, +}; } // namespace int main(int argc, const char *argv[]) { llvm::cl::ParseCommandLineOptions(argc, argv, Overview); + llvm::cl::ResetCommandLineParser(); // We reuse it for REPL commands. llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); std::unique_ptr Index; @@ -154,8 +192,35 @@ llvm::LineEditor LE("dexp"); - while (llvm::Optional Request = LE.readLine()) - dispatch(Request.getValue(), *Index); + while (llvm::Optional Request = LE.readLine()) { + // Split on spaces and add required null-termination. + std::replace(Request->begin(), Request->end(), ' ', '\0'); + SmallVector Args; + StringRef(*Request).split(Args, '\0', /*MaxSplit=*/-1, /*KeepEmpty=*/false); + if (Args.empty()) + continue; + if (Args.front() == "help") { + outs() << "dexp - Index explorer\nCommands:\n"; + for (const auto &C : CommandInfo) + outs() << llvm::formatv("{0,16} - {1}\n", C.Name, C.Description); + outs() << "Get detailed command help with e.g. `find -help`.\n"; + continue; + } + SmallVector FakeArgv; + for (StringRef S : Args) + FakeArgv.push_back(S.data()); // Terminated by separator or end of string. + + bool Recognized = false; + for (const auto &Cmd : CommandInfo) { + if (Cmd.Name == Args.front()) { + Recognized = true; + Cmd.Implementation()->parseAndRun(FakeArgv, Cmd.Description, *Index); + break; + } + } + if (!Recognized) + outs() << "Unknown command. Try 'help'.\n"; + } return 0; } Index: test/CMakeLists.txt =================================================================== --- test/CMakeLists.txt +++ test/CMakeLists.txt @@ -56,6 +56,7 @@ # These individual tools have no tests, add them here to make them compile # together with check-clang-tools, so that we won't break them in the future. clangd-indexer + dexp # Unit tests ExtraToolsUnitTests