[clang-tools-extra] r200604 - Add completion to the query parser, and hook it up to clang-query.

Peter Collingbourne peter at pcc.me.uk
Fri Jan 31 17:42:46 PST 2014


Author: pcc
Date: Fri Jan 31 19:42:46 2014
New Revision: 200604

URL: http://llvm.org/viewvc/llvm-project?rev=200604&view=rev
Log:
Add completion to the query parser, and hook it up to clang-query.

Differential Revision: http://llvm-reviews.chandlerc.com/D2263

Modified:
    clang-tools-extra/trunk/clang-query/QueryParser.cpp
    clang-tools-extra/trunk/clang-query/QueryParser.h
    clang-tools-extra/trunk/clang-query/tool/ClangQuery.cpp
    clang-tools-extra/trunk/unittests/clang-query/QueryParserTest.cpp

Modified: clang-tools-extra/trunk/clang-query/QueryParser.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-query/QueryParser.cpp?rev=200604&r1=200603&r2=200604&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-query/QueryParser.cpp (original)
+++ clang-tools-extra/trunk/clang-query/QueryParser.cpp Fri Jan 31 19:42:46 2014
@@ -15,6 +15,8 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/StringSwitch.h"
 
+#include <set>
+
 using namespace llvm;
 using namespace clang::ast_matchers::dynamic;
 
@@ -25,10 +27,10 @@ namespace query {
 // 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 StringRef LexWord(const char *&Be
   }
 }
 
-static QueryRef ParseSetBool(bool QuerySession::*Var, StringRef ValStr) {
-  unsigned Value = StringSwitch<unsigned>(ValStr)
+// This is the StringSwitch-alike used by lexOrCompleteWord below. See that
+// function for details.
+template <typename T> struct QueryParser::LexOrCompleteWord {
+  StringSwitch<T> 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 <unsigned N>
+  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<T> object
+// that can be used like a llvm::StringSwitch<T>, but adds cases as possible
+// completions if the lexed word contains the completion point.
+template <typename T>
+QueryParser::LexOrCompleteWord<T>
+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<T>(this, Word, WordCompletionPos);
+}
+
+QueryRef QueryParser::parseSetBool(bool QuerySession::*Var) {
+  StringRef ValStr;
+  unsigned Value = lexOrCompleteWord<unsigned>(ValStr)
                       .Case("false", 0)
                       .Case("true", 1)
                       .Default(~0u);
@@ -57,8 +111,9 @@ static QueryRef ParseSetBool(bool QueryS
   return new SetQuery<bool>(Var, Value);
 }
 
-static QueryRef ParseSetOutputKind(StringRef ValStr) {
-  unsigned OutKind = StringSwitch<unsigned>(ValStr)
+QueryRef QueryParser::parseSetOutputKind() {
+  StringRef ValStr;
+  unsigned OutKind = lexOrCompleteWord<unsigned>(ValStr)
                          .Case("diag", OK_Diag)
                          .Case("print", OK_Print)
                          .Case("dump", OK_Dump)
@@ -70,9 +125,9 @@ static QueryRef ParseSetOutputKind(Strin
   return new SetQuery<OutputKind>(&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 @@ enum ParsedQueryVariable {
   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<ParsedQueryKind>(CommandStr)
+QueryRef QueryParser::doParse() {
+  StringRef CommandStr;
+  ParsedQueryKind QKind = lexOrCompleteWord<ParsedQueryKind>(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 @@ QueryRef ParseQuery(StringRef Line) {
     return new NoOpQuery;
 
   case PQK_Help:
-    return EndQuery(Begin, End, new HelpQuery);
+    return endQuery(new HelpQuery);
 
   case PQK_Match: {
-    Diagnostics Diag;
-    Optional<DynTypedMatcher> 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<MatcherCompletion> Comps = Parser::completeExpression(
+          StringRef(Begin, End - Begin), CompletionPos - Begin);
+      for (std::vector<MatcherCompletion>::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<DynTypedMatcher> 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<ParsedQueryVariable>(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<ParsedQueryVariable>(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 @@ QueryRef ParseQuery(StringRef Line) {
   llvm_unreachable("Invalid query kind");
 }
 
+QueryRef QueryParser::parse(StringRef Line) {
+  return QueryParser(Line).doParse();
+}
+
+std::vector<LineEditor::Completion> QueryParser::complete(StringRef Line,
+                                                          size_t Pos) {
+  QueryParser P(Line);
+  P.CompletionPos = Line.data() + Pos;
+
+  P.doParse();
+  return P.Completions;
+}
+
 } // namespace query
 } // namespace clang

Modified: clang-tools-extra/trunk/clang-query/QueryParser.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-query/QueryParser.h?rev=200604&r1=200603&r2=200604&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-query/QueryParser.h (original)
+++ clang-tools-extra/trunk/clang-query/QueryParser.h Fri Jan 31 19:42:46 2014
@@ -12,14 +12,55 @@
 
 #include "Query.h"
 
+#include <stddef.h>
+#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<llvm::LineEditor::Completion> complete(StringRef Line,
+                                                            size_t Pos);
+
+private:
+  QueryParser(StringRef Line)
+      : Begin(Line.data()), End(Line.data() + Line.size()), CompletionPos(0) {}
+
+  StringRef lexWord();
+
+  template <typename T> struct LexOrCompleteWord;
+  template <typename T> LexOrCompleteWord<T> 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<llvm::LineEditor::Completion> Completions;
+};
 
 } // namespace query
 } // namespace clang

Modified: clang-tools-extra/trunk/clang-query/tool/ClangQuery.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-query/tool/ClangQuery.cpp?rev=200604&r1=200603&r2=200604&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-query/tool/ClangQuery.cpp (original)
+++ clang-tools-extra/trunk/clang-query/tool/ClangQuery.cpp Fri Jan 31 19:42:46 2014
@@ -96,7 +96,7 @@ int main(int argc, const char **argv) {
     for (cl::list<std::string>::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 @@ int main(int argc, const char **argv) {
         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<std::string> Line = LE.readLine()) {
-      QueryRef Q = ParseQuery(*Line);
+      QueryRef Q = QueryParser::parse(*Line);
       Q->run(llvm::outs(), QS);
     }
   }

Modified: clang-tools-extra/trunk/unittests/clang-query/QueryParserTest.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-query/QueryParserTest.cpp?rev=200604&r1=200603&r2=200604&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-query/QueryParserTest.cpp (original)
+++ clang-tools-extra/trunk/unittests/clang-query/QueryParserTest.cpp Fri Jan 31 19:42:46 2014
@@ -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<NoOpQuery>(Q));
 
-  Q = ParseQuery("\n");
+  Q = QueryParser::parse("\n");
   EXPECT_TRUE(isa<NoOpQuery>(Q));
 }
 
 TEST(QueryParser, Invalid) {
-  QueryRef Q = ParseQuery("foo");
+  QueryRef Q = QueryParser::parse("foo");
   ASSERT_TRUE(isa<InvalidQuery>(Q));
   EXPECT_EQ("unknown command: foo", cast<InvalidQuery>(Q)->ErrStr);
 }
 
 TEST(QueryParser, Help) {
-  QueryRef Q = ParseQuery("help");
+  QueryRef Q = QueryParser::parse("help");
   ASSERT_TRUE(isa<HelpQuery>(Q));
 
-  Q = ParseQuery("help me");
+  Q = QueryParser::parse("help me");
   ASSERT_TRUE(isa<InvalidQuery>(Q));
   EXPECT_EQ("unexpected extra input: ' me'", cast<InvalidQuery>(Q)->ErrStr);
 }
 
 TEST(QueryParser, Set) {
-  QueryRef Q = ParseQuery("set");
+  QueryRef Q = QueryParser::parse("set");
   ASSERT_TRUE(isa<InvalidQuery>(Q));
   EXPECT_EQ("expected variable name", cast<InvalidQuery>(Q)->ErrStr);
 
-  Q = ParseQuery("set foo bar");
+  Q = QueryParser::parse("set foo bar");
   ASSERT_TRUE(isa<InvalidQuery>(Q));
   EXPECT_EQ("unknown variable: 'foo'", cast<InvalidQuery>(Q)->ErrStr);
 
-  Q = ParseQuery("set output");
+  Q = QueryParser::parse("set output");
   ASSERT_TRUE(isa<InvalidQuery>(Q));
-  EXPECT_EQ("expected variable value", cast<InvalidQuery>(Q)->ErrStr);
+  EXPECT_EQ("expected 'diag', 'print' or 'dump', got ''",
+            cast<InvalidQuery>(Q)->ErrStr);
 
-  Q = ParseQuery("set bind-root true foo");
+  Q = QueryParser::parse("set bind-root true foo");
   ASSERT_TRUE(isa<InvalidQuery>(Q));
   EXPECT_EQ("unexpected extra input: ' foo'", cast<InvalidQuery>(Q)->ErrStr);
 
-  Q = ParseQuery("set output foo");
+  Q = QueryParser::parse("set output foo");
   ASSERT_TRUE(isa<InvalidQuery>(Q));
   EXPECT_EQ("expected 'diag', 'print' or 'dump', got 'foo'",
             cast<InvalidQuery>(Q)->ErrStr);
 
-  Q = ParseQuery("set output dump");
+  Q = QueryParser::parse("set output dump");
   ASSERT_TRUE(isa<SetQuery<OutputKind> >(Q));
   EXPECT_EQ(&QuerySession::OutKind, cast<SetQuery<OutputKind> >(Q)->Var);
   EXPECT_EQ(OK_Dump, cast<SetQuery<OutputKind> >(Q)->Value);
 
-  Q = ParseQuery("set bind-root foo");
+  Q = QueryParser::parse("set bind-root foo");
   ASSERT_TRUE(isa<InvalidQuery>(Q));
   EXPECT_EQ("expected 'true' or 'false', got 'foo'",
             cast<InvalidQuery>(Q)->ErrStr);
 
-  Q = ParseQuery("set bind-root true");
+  Q = QueryParser::parse("set bind-root true");
   ASSERT_TRUE(isa<SetQuery<bool> >(Q));
   EXPECT_EQ(&QuerySession::BindRoot, cast<SetQuery<bool> >(Q)->Var);
   EXPECT_EQ(true, cast<SetQuery<bool> >(Q)->Value);
 }
 
 TEST(QueryParser, Match) {
-  QueryRef Q = ParseQuery("match decl()");
+  QueryRef Q = QueryParser::parse("match decl()");
   ASSERT_TRUE(isa<MatchQuery>(Q));
   EXPECT_TRUE(cast<MatchQuery>(Q)->Matcher.canConvertTo<Decl>());
 
-  Q = ParseQuery("m stmt()");
+  Q = QueryParser::parse("m stmt()");
   ASSERT_TRUE(isa<MatchQuery>(Q));
   EXPECT_TRUE(cast<MatchQuery>(Q)->Matcher.canConvertTo<Stmt>());
 }
+
+TEST(QueryParser, Complete) {
+  std::vector<llvm::LineEditor::Completion> 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<Stmt> whileStmt(Matcher<WhileStmt>...)",
+            Comps[0].DisplayText);
+}





More information about the cfe-commits mailing list