Add completion to the query parser, and hook it up to clang-query.

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

llvm-svn: 200604
This commit is contained in:
Peter Collingbourne 2014-02-01 01:42:46 +00:00
parent c31176da02
commit d9a0f254bc
4 changed files with 201 additions and 62 deletions

View File

@ -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 *&Begin, const char *End) {
}
}
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 QuerySession::*Var, StringRef ValStr) {
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(StringRef ValStr) {
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

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}