Index: clang-query/QueryParser.h =================================================================== --- clang-query/QueryParser.h +++ clang-query/QueryParser.h @@ -12,14 +12,55 @@ #include "Query.h" +#include +#include "llvm/LineEditor/LineEditor.h" + namespace clang { namespace query { -/// \brief Parse \p Line. -/// -/// \return A reference to the parsed query object, which may be an -/// \c InvalidQuery if a parse error occurs. -QueryRef ParseQuery(StringRef Line); +class QuerySession; + +class QueryParser { +public: + /// Parse \param Line as a query. + /// + /// \return A QueryRef representing the query, which may be an InvalidQuery. + static QueryRef parse(StringRef Line); + + /// Compute a list of completions for \param Line assuming a cursor at + /// \param Pos characters past the start of \param Line, ordered from most + /// likely to least likely. + /// + /// \return A vector of completions for \param Line. + static std::vector complete(StringRef Line, + size_t Pos); + +private: + QueryParser(StringRef Line) + : Begin(Line.data()), End(Line.data() + Line.size()), CompletionPos(0) {} + + StringRef lexWord(); + + template struct LexOrCompleteWord; + template LexOrCompleteWord lexOrCompleteWord(StringRef &Str); + + QueryRef parseSetBool(bool QuerySession::*Var); + QueryRef parseSetOutputKind(); + + QueryRef endQuery(QueryRef Q); + + /// \brief Parse [\p Begin,\p End). + /// + /// \return A reference to the parsed query object, which may be an + /// \c InvalidQuery if a parse error occurs. + QueryRef doParse(); + + const char *Begin; + const char *End; + + const char *CompletionPos; + std::vector Completions; +}; } // namespace query } // namespace clang Index: clang-query/QueryParser.cpp =================================================================== --- clang-query/QueryParser.cpp +++ clang-query/QueryParser.cpp @@ -15,6 +15,8 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSwitch.h" +#include + using namespace llvm; using namespace clang::ast_matchers::dynamic; @@ -25,10 +27,10 @@ // non-whitespace characters) from the start of region [Begin,End). If no word // is found before End, return StringRef(). Begin is adjusted to exclude the // lexed region. -static StringRef LexWord(const char *&Begin, const char *End) { +StringRef QueryParser::lexWord() { while (true) { if (Begin == End) - return StringRef(); + return StringRef(Begin, 0); if (!isWhitespace(*Begin)) break; @@ -46,8 +48,60 @@ } } -static QueryRef ParseSetBool(bool QuerySession::*Var, StringRef ValStr) { - unsigned Value = StringSwitch(ValStr) +// This is the StringSwitch-alike used by lexOrCompleteWord below. See that +// function for details. +template struct QueryParser::LexOrCompleteWord { + StringSwitch Switch; + + QueryParser *P; + StringRef Word; + // Set to the completion point offset in Word, or StringRef::npos if + // completion point not in Word. + size_t WordCompletionPos; + + LexOrCompleteWord(QueryParser *P, StringRef Word, size_t WCP) + : Switch(Word), P(P), Word(Word), WordCompletionPos(WCP) {} + + template + LexOrCompleteWord &Case(const char (&S)[N], const T &Value, + bool IsCompletion = true) { + StringRef CaseStr(S, N - 1); + + if (WordCompletionPos == StringRef::npos) + Switch.Case(S, Value); + else if (N != 1 && IsCompletion && WordCompletionPos <= CaseStr.size() && + CaseStr.substr(0, WordCompletionPos) == + Word.substr(0, WordCompletionPos)) + P->Completions.push_back(LineEditor::Completion( + (CaseStr.substr(WordCompletionPos) + " ").str(), CaseStr)); + return *this; + } + + T Default(const T& Value) const { + return Switch.Default(Value); + } +}; + +// Lexes a word and stores it in Word. Returns a LexOrCompleteWord object +// that can be used like a llvm::StringSwitch, but adds cases as possible +// completions if the lexed word contains the completion point. +template +QueryParser::LexOrCompleteWord +QueryParser::lexOrCompleteWord(StringRef &Word) { + Word = lexWord(); + size_t WordCompletionPos = StringRef::npos; + if (CompletionPos && CompletionPos <= Word.data() + Word.size()) { + if (CompletionPos < Word.data()) + WordCompletionPos = 0; + else + WordCompletionPos = CompletionPos - Word.data(); + } + return LexOrCompleteWord(this, Word, WordCompletionPos); +} + +QueryRef QueryParser::parseSetBool(bool QuerySession::*Var) { + StringRef ValStr; + unsigned Value = lexOrCompleteWord(ValStr) .Case("false", 0) .Case("true", 1) .Default(~0u); @@ -57,8 +111,9 @@ return new SetQuery(Var, Value); } -static QueryRef ParseSetOutputKind(StringRef ValStr) { - unsigned OutKind = StringSwitch(ValStr) +QueryRef QueryParser::parseSetOutputKind() { + StringRef ValStr; + unsigned OutKind = lexOrCompleteWord(ValStr) .Case("diag", OK_Diag) .Case("print", OK_Print) .Case("dump", OK_Dump) @@ -70,9 +125,9 @@ return new SetQuery(&QuerySession::OutKind, OutputKind(OutKind)); } -static QueryRef EndQuery(const char *Begin, const char *End, QueryRef Q) { +QueryRef QueryParser::endQuery(QueryRef Q) { const char *Extra = Begin; - if (!LexWord(Begin, End).empty()) + if (!lexWord().empty()) return new InvalidQuery("unexpected extra input: '" + StringRef(Extra, End - Extra) + "'"); return Q; @@ -92,15 +147,12 @@ PQV_BindRoot }; -QueryRef ParseQuery(StringRef Line) { - const char *Begin = Line.data(); - const char *End = Line.data() + Line.size(); - - StringRef CommandStr = LexWord(Begin, End); - ParsedQueryKind QKind = StringSwitch(CommandStr) +QueryRef QueryParser::doParse() { + StringRef CommandStr; + ParsedQueryKind QKind = lexOrCompleteWord(CommandStr) .Case("", PQK_NoOp) .Case("help", PQK_Help) - .Case("m", PQK_Match) + .Case("m", PQK_Match, /*IsCompletion=*/false) .Case("match", PQK_Match) .Case("set", PQK_Set) .Default(PQK_Invalid); @@ -110,50 +162,57 @@ return new NoOpQuery; case PQK_Help: - return EndQuery(Begin, End, new HelpQuery); + return endQuery(new HelpQuery); case PQK_Match: { - Diagnostics Diag; - Optional Matcher = - Parser::parseMatcherExpression(StringRef(Begin, End - Begin), &Diag); - if (!Matcher) { - std::string ErrStr; - llvm::raw_string_ostream OS(ErrStr); - Diag.printToStreamFull(OS); - return new InvalidQuery(OS.str()); + if (CompletionPos) { + std::vector Comps = Parser::completeExpression( + StringRef(Begin, End - Begin), CompletionPos - Begin); + for (std::vector::iterator I = Comps.begin(), + E = Comps.end(); + I != E; ++I) { + Completions.push_back( + LineEditor::Completion(I->TypedText, I->MatcherDecl)); + } + return QueryRef(); + } else { + Diagnostics Diag; + Optional Matcher = + Parser::parseMatcherExpression(StringRef(Begin, End - Begin), &Diag); + if (!Matcher) { + std::string ErrStr; + llvm::raw_string_ostream OS(ErrStr); + Diag.printToStreamFull(OS); + return new InvalidQuery(OS.str()); + } + return new MatchQuery(*Matcher); } - return new MatchQuery(*Matcher); } case PQK_Set: { - StringRef VarStr = LexWord(Begin, End); + StringRef VarStr; + ParsedQueryVariable Var = lexOrCompleteWord(VarStr) + .Case("output", PQV_Output) + .Case("bind-root", PQV_BindRoot) + .Default(PQV_Invalid); if (VarStr.empty()) return new InvalidQuery("expected variable name"); - - ParsedQueryVariable Var = StringSwitch(VarStr) - .Case("output", PQV_Output) - .Case("bind-root", PQV_BindRoot) - .Default(PQV_Invalid); if (Var == PQV_Invalid) return new InvalidQuery("unknown variable: '" + VarStr + "'"); - StringRef ValStr = LexWord(Begin, End); - if (ValStr.empty()) - return new InvalidQuery("expected variable value"); - QueryRef Q; switch (Var) { case PQV_Output: - Q = ParseSetOutputKind(ValStr); + Q = parseSetOutputKind(); break; case PQV_BindRoot: - Q = ParseSetBool(&QuerySession::BindRoot, ValStr); + Q = parseSetBool(&QuerySession::BindRoot); break; case PQV_Invalid: llvm_unreachable("Invalid query kind"); } - return EndQuery(Begin, End, Q); + return endQuery(Q); } case PQK_Invalid: @@ -163,5 +222,18 @@ llvm_unreachable("Invalid query kind"); } +QueryRef QueryParser::parse(StringRef Line) { + return QueryParser(Line).doParse(); +} + +std::vector QueryParser::complete(StringRef Line, + size_t Pos) { + QueryParser P(Line); + P.CompletionPos = Line.data() + Pos; + + P.doParse(); + return P.Completions; +} + } // namespace query } // namespace clang Index: clang-query/tool/ClangQuery.cpp =================================================================== --- clang-query/tool/ClangQuery.cpp +++ clang-query/tool/ClangQuery.cpp @@ -96,7 +96,7 @@ for (cl::list::iterator I = Commands.begin(), E = Commands.end(); I != E; ++I) { - QueryRef Q = ParseQuery(I->c_str()); + QueryRef Q = QueryParser::parse(I->c_str()); if (!Q->run(llvm::outs(), QS)) return 1; } @@ -113,15 +113,16 @@ std::string Line; std::getline(Input, Line); - QueryRef Q = ParseQuery(Line.c_str()); + QueryRef Q = QueryParser::parse(Line.c_str()); if (!Q->run(llvm::outs(), QS)) return 1; } } } else { LineEditor LE("clang-query"); + LE.setListCompleter(QueryParser::complete); while (llvm::Optional Line = LE.readLine()) { - QueryRef Q = ParseQuery(*Line); + QueryRef Q = QueryParser::parse(*Line); Q->run(llvm::outs(), QS); } } Index: unittests/clang-query/QueryParserTest.cpp =================================================================== --- unittests/clang-query/QueryParserTest.cpp +++ unittests/clang-query/QueryParserTest.cpp @@ -11,77 +11,102 @@ #include "Query.h" #include "QuerySession.h" #include "gtest/gtest.h" +#include "llvm/LineEditor/LineEditor.h" using namespace clang; using namespace clang::query; TEST(QueryParser, NoOp) { - QueryRef Q = ParseQuery(""); + QueryRef Q = QueryParser::parse(""); EXPECT_TRUE(isa(Q)); - Q = ParseQuery("\n"); + Q = QueryParser::parse("\n"); EXPECT_TRUE(isa(Q)); } TEST(QueryParser, Invalid) { - QueryRef Q = ParseQuery("foo"); + QueryRef Q = QueryParser::parse("foo"); ASSERT_TRUE(isa(Q)); EXPECT_EQ("unknown command: foo", cast(Q)->ErrStr); } TEST(QueryParser, Help) { - QueryRef Q = ParseQuery("help"); + QueryRef Q = QueryParser::parse("help"); ASSERT_TRUE(isa(Q)); - Q = ParseQuery("help me"); + Q = QueryParser::parse("help me"); ASSERT_TRUE(isa(Q)); EXPECT_EQ("unexpected extra input: ' me'", cast(Q)->ErrStr); } TEST(QueryParser, Set) { - QueryRef Q = ParseQuery("set"); + QueryRef Q = QueryParser::parse("set"); ASSERT_TRUE(isa(Q)); EXPECT_EQ("expected variable name", cast(Q)->ErrStr); - Q = ParseQuery("set foo bar"); + Q = QueryParser::parse("set foo bar"); ASSERT_TRUE(isa(Q)); EXPECT_EQ("unknown variable: 'foo'", cast(Q)->ErrStr); - Q = ParseQuery("set output"); + Q = QueryParser::parse("set output"); ASSERT_TRUE(isa(Q)); - EXPECT_EQ("expected variable value", cast(Q)->ErrStr); + EXPECT_EQ("expected 'diag', 'print' or 'dump', got ''", + cast(Q)->ErrStr); - Q = ParseQuery("set bind-root true foo"); + Q = QueryParser::parse("set bind-root true foo"); ASSERT_TRUE(isa(Q)); EXPECT_EQ("unexpected extra input: ' foo'", cast(Q)->ErrStr); - Q = ParseQuery("set output foo"); + Q = QueryParser::parse("set output foo"); ASSERT_TRUE(isa(Q)); EXPECT_EQ("expected 'diag', 'print' or 'dump', got 'foo'", cast(Q)->ErrStr); - Q = ParseQuery("set output dump"); + Q = QueryParser::parse("set output dump"); ASSERT_TRUE(isa >(Q)); EXPECT_EQ(&QuerySession::OutKind, cast >(Q)->Var); EXPECT_EQ(OK_Dump, cast >(Q)->Value); - Q = ParseQuery("set bind-root foo"); + Q = QueryParser::parse("set bind-root foo"); ASSERT_TRUE(isa(Q)); EXPECT_EQ("expected 'true' or 'false', got 'foo'", cast(Q)->ErrStr); - Q = ParseQuery("set bind-root true"); + Q = QueryParser::parse("set bind-root true"); ASSERT_TRUE(isa >(Q)); EXPECT_EQ(&QuerySession::BindRoot, cast >(Q)->Var); EXPECT_EQ(true, cast >(Q)->Value); } TEST(QueryParser, Match) { - QueryRef Q = ParseQuery("match decl()"); + QueryRef Q = QueryParser::parse("match decl()"); ASSERT_TRUE(isa(Q)); EXPECT_TRUE(cast(Q)->Matcher.canConvertTo()); - Q = ParseQuery("m stmt()"); + Q = QueryParser::parse("m stmt()"); ASSERT_TRUE(isa(Q)); EXPECT_TRUE(cast(Q)->Matcher.canConvertTo()); } + +TEST(QueryParser, Complete) { + std::vector Comps = + QueryParser::complete("", 0); + ASSERT_EQ(3u, Comps.size()); + EXPECT_EQ("help ", Comps[0].TypedText); + EXPECT_EQ("help", Comps[0].DisplayText); + EXPECT_EQ("match ", Comps[1].TypedText); + EXPECT_EQ("match", Comps[1].DisplayText); + EXPECT_EQ("set ", Comps[2].TypedText); + EXPECT_EQ("set", Comps[2].DisplayText); + + Comps = QueryParser::complete("set o", 5); + ASSERT_EQ(1u, Comps.size()); + EXPECT_EQ("utput ", Comps[0].TypedText); + EXPECT_EQ("output", Comps[0].DisplayText); + + Comps = QueryParser::complete("match while", 11); + ASSERT_EQ(1u, Comps.size()); + EXPECT_EQ("Stmt(", Comps[0].TypedText); + EXPECT_EQ("Matcher whileStmt(Matcher...)", + Comps[0].DisplayText); +}