Add new 'let' command to bind arbitrary values into constants.

Summary:
Add new 'let' command to bind arbitrary values into constants.
These constants can then be used in the matcher expressions.

Reviewers: pcc

CC: cfe-commits

Differential Revision: http://reviews.llvm.org/D3383

llvm-svn: 206984
This commit is contained in:
Samuel Benzaquen 2014-04-23 14:04:52 +00:00
parent 5a7c364343
commit 1f6066c9ac
8 changed files with 237 additions and 78 deletions

View File

@ -54,7 +54,7 @@ struct CollectBoundNodes : MatchFinder::MatchCallback {
}
};
}
} // namespace
bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
unsigned MatchCount = 0;
@ -124,6 +124,15 @@ bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
return true;
}
bool LetQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
if (Value) {
QS.NamedValues[Name] = Value;
} else {
QS.NamedValues.erase(Name);
}
return true;
}
#ifndef _MSC_VER
const QueryKind SetQueryKind<bool>::value;
const QueryKind SetQueryKind<OutputKind>::value;

View File

@ -28,9 +28,10 @@ enum QueryKind {
QK_Invalid,
QK_NoOp,
QK_Help,
QK_Let,
QK_Match,
QK_SetBool,
QK_SetOutputKind
QK_SetOutputKind,
};
class QuerySession;
@ -86,6 +87,17 @@ struct MatchQuery : Query {
static bool classof(const Query *Q) { return Q->Kind == QK_Match; }
};
struct LetQuery : Query {
LetQuery(StringRef Name, const ast_matchers::dynamic::VariantValue &Value)
: Query(QK_Let), Name(Name), Value(Value) {}
bool run(llvm::raw_ostream &OS, QuerySession &QS) const override;
std::string Name;
ast_matchers::dynamic::VariantValue Value;
static bool classof(const Query *Q) { return Q->Kind == QK_Let; }
};
template <typename T> struct SetQueryKind {};
template <> struct SetQueryKind<bool> {

View File

@ -132,12 +132,16 @@ QueryRef QueryParser::endQuery(QueryRef Q) {
return Q;
}
namespace {
enum ParsedQueryKind {
PQK_Invalid,
PQK_NoOp,
PQK_Help,
PQK_Let,
PQK_Match,
PQK_Set
PQK_Set,
PQK_Unlet,
};
enum ParsedQueryVariable {
@ -146,16 +150,52 @@ enum ParsedQueryVariable {
PQV_BindRoot
};
QueryRef makeInvalidQueryFromDiagnostics(const Diagnostics &Diag) {
std::string ErrStr;
llvm::raw_string_ostream OS(ErrStr);
Diag.printToStreamFull(OS);
return new InvalidQuery(OS.str());
}
class QuerySessionSema : public Parser::RegistrySema {
public:
QuerySessionSema(const QuerySession &QS) : QS(QS) {}
ast_matchers::dynamic::VariantValue getNamedValue(StringRef Name) override {
return QS.NamedValues.lookup(Name);
}
private:
const QuerySession &QS;
};
} // namespace
QueryRef QueryParser::completeMatcherExpression() {
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();
}
QueryRef QueryParser::doParse() {
StringRef CommandStr;
ParsedQueryKind QKind = lexOrCompleteWord<ParsedQueryKind>(CommandStr)
.Case("", PQK_NoOp)
.Case("help", PQK_Help)
.Case("m", PQK_Match, /*IsCompletion=*/false)
.Case("let", PQK_Let)
.Case("match", PQK_Match)
.Case("set", PQK_Set)
.Case("unlet", PQK_Unlet)
.Default(PQK_Invalid);
QuerySessionSema S(QS);
switch (QKind) {
case PQK_NoOp:
return new NoOpQuery;
@ -163,29 +203,36 @@ QueryRef QueryParser::doParse() {
case PQK_Help:
return endQuery(new HelpQuery);
case PQK_Match: {
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);
case PQK_Let: {
StringRef Name = lexWord();
if (Name.empty())
return new InvalidQuery("expected variable name");
if (CompletionPos)
return completeMatcherExpression();
Diagnostics Diag;
ast_matchers::dynamic::VariantValue Value;
if (!Parser::parseExpression(StringRef(Begin, End - Begin), &S, &Value,
&Diag)) {
return makeInvalidQueryFromDiagnostics(Diag);
}
return new LetQuery(Name, Value);
}
case PQK_Match: {
if (CompletionPos)
return completeMatcherExpression();
Diagnostics Diag;
Optional<DynTypedMatcher> Matcher = Parser::parseMatcherExpression(
StringRef(Begin, End - Begin), &S, &Diag);
if (!Matcher) {
return makeInvalidQueryFromDiagnostics(Diag);
}
return new MatchQuery(*Matcher);
}
case PQK_Set: {
@ -214,6 +261,15 @@ QueryRef QueryParser::doParse() {
return endQuery(Q);
}
case PQK_Unlet: {
StringRef Name = lexWord();
if (Name.empty())
return new InvalidQuery("expected variable name");
return endQuery(new LetQuery(Name, {}));
}
case PQK_Invalid:
return new InvalidQuery("unknown command: " + CommandStr);
}
@ -221,13 +277,13 @@ QueryRef QueryParser::doParse() {
llvm_unreachable("Invalid query kind");
}
QueryRef QueryParser::parse(StringRef Line) {
return QueryParser(Line).doParse();
QueryRef QueryParser::parse(StringRef Line, const QuerySession &QS) {
return QueryParser(Line, QS).doParse();
}
std::vector<LineEditor::Completion> QueryParser::complete(StringRef Line,
size_t Pos) {
QueryParser P(Line);
std::vector<LineEditor::Completion>
QueryParser::complete(StringRef Line, size_t Pos, const QuerySession &QS) {
QueryParser P(Line, QS);
P.CompletionPos = Line.data() + Pos;
P.doParse();

View File

@ -11,6 +11,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_PARSER_H
#include "Query.h"
#include "QuerySession.h"
#include "llvm/LineEditor/LineEditor.h"
#include <stddef.h>
@ -24,19 +25,20 @@ public:
/// Parse \a Line as a query.
///
/// \return A QueryRef representing the query, which may be an InvalidQuery.
static QueryRef parse(StringRef Line);
static QueryRef parse(StringRef Line, const QuerySession &QS);
/// Compute a list of completions for \a Line assuming a cursor at
/// \param Pos characters past the start of \a Line, ordered from most
/// likely to least likely.
///
/// \return A vector of completions for \a Line.
static std::vector<llvm::LineEditor::Completion> complete(StringRef Line,
size_t Pos);
static std::vector<llvm::LineEditor::Completion>
complete(StringRef Line, size_t Pos, const QuerySession &QS);
private:
QueryParser(StringRef Line)
: Begin(Line.data()), End(Line.data() + Line.size()), CompletionPos(0) {}
QueryParser(StringRef Line, const QuerySession &QS)
: Begin(Line.data()), End(Line.data() + Line.size()), CompletionPos(0),
QS(QS) {}
StringRef lexWord();
@ -45,6 +47,7 @@ private:
QueryRef parseSetBool(bool QuerySession::*Var);
QueryRef parseSetOutputKind();
QueryRef completeMatcherExpression();
QueryRef endQuery(QueryRef Q);
@ -59,6 +62,8 @@ private:
const char *CompletionPos;
std::vector<llvm::LineEditor::Completion> Completions;
const QuerySession &QS;
};
} // namespace query

View File

@ -11,7 +11,9 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_QUERY_QUERY_SESSION_H
#include "Query.h"
#include "clang/ASTMatchers/Dynamic/VariantValue.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringMap.h"
namespace clang {
@ -28,6 +30,7 @@ public:
llvm::ArrayRef<ASTUnit *> ASTs;
OutputKind OutKind;
bool BindRoot;
llvm::StringMap<ast_matchers::dynamic::VariantValue> NamedValues;
};
} // namespace query

View File

@ -95,7 +95,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 = QueryParser::parse(I->c_str());
QueryRef Q = QueryParser::parse(I->c_str(), QS);
if (!Q->run(llvm::outs(), QS))
return 1;
}
@ -112,16 +112,18 @@ int main(int argc, const char **argv) {
std::string Line;
std::getline(Input, Line);
QueryRef Q = QueryParser::parse(Line.c_str());
QueryRef Q = QueryParser::parse(Line.c_str(), QS);
if (!Q->run(llvm::outs(), QS))
return 1;
}
}
} else {
LineEditor LE("clang-query");
LE.setListCompleter(QueryParser::complete);
LE.setListCompleter([&QS](StringRef Line, size_t Pos) {
return QueryParser::complete(Line, Pos, QS);
});
while (llvm::Optional<std::string> Line = LE.readLine()) {
QueryRef Q = QueryParser::parse(*Line);
QueryRef Q = QueryParser::parse(*Line, QS);
Q->run(llvm::outs(), QS);
}
}

View File

@ -8,6 +8,7 @@
//===----------------------------------------------------------------------===//
#include "Query.h"
#include "QueryParser.h"
#include "QuerySession.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/Dynamic/VariantValue.h"
@ -24,20 +25,22 @@ using namespace clang::ast_matchers::dynamic;
using namespace clang::query;
using namespace clang::tooling;
TEST(Query, Basic) {
std::unique_ptr<ASTUnit> FooAST(
buildASTFromCode("void foo1(void) {}\nvoid foo2(void) {}", "foo.cc"));
ASSERT_TRUE(FooAST.get());
std::unique_ptr<ASTUnit> BarAST(
buildASTFromCode("void bar1(void) {}\nvoid bar2(void) {}", "bar.cc"));
ASSERT_TRUE(BarAST.get());
class QueryEngineTest : public ::testing::Test {
protected:
QueryEngineTest() {}
ASTUnit *ASTs[] = { FooAST.get(), BarAST.get() };
std::unique_ptr<ASTUnit> FooAST{
buildASTFromCode("void foo1(void) {}\nvoid foo2(void) {}", "foo.cc")};
std::unique_ptr<ASTUnit> BarAST{
buildASTFromCode("void bar1(void) {}\nvoid bar2(void) {}", "bar.cc")};
ASTUnit *ASTs[2]{FooAST.get(), BarAST.get()};
QuerySession S{ASTs};
std::string Str;
llvm::raw_string_ostream OS(Str);
QuerySession S(ASTs);
llvm::raw_string_ostream OS{Str};
};
TEST_F(QueryEngineTest, Basic) {
DynTypedMatcher FnMatcher = functionDecl();
DynTypedMatcher FooMatcher = functionDecl(hasName("foo1"));
@ -108,3 +111,28 @@ TEST(Query, Basic) {
EXPECT_EQ("Not a valid top-level matcher.\n", OS.str());
}
TEST_F(QueryEngineTest, LetAndMatch) {
EXPECT_TRUE(QueryParser::parse("let x \"foo1\"", S)->run(OS, S));
EXPECT_EQ("", OS.str());
Str.clear();
EXPECT_TRUE(QueryParser::parse("let y hasName(x)", S)->run(OS, S));
EXPECT_EQ("", OS.str());
Str.clear();
EXPECT_TRUE(QueryParser::parse("match functionDecl(y)", S)->run(OS, S));
EXPECT_TRUE(OS.str().find("foo.cc:1:1: note: \"root\" binds here") !=
std::string::npos);
EXPECT_TRUE(OS.str().find("1 match.") != std::string::npos);
Str.clear();
EXPECT_TRUE(QueryParser::parse("unlet x", S)->run(OS, S));
EXPECT_EQ("", OS.str());
Str.clear();
EXPECT_FALSE(QueryParser::parse("let y hasName(x)", S)->run(OS, S));
EXPECT_EQ("1:2: Error parsing argument 1 for matcher hasName.\n"
"1:10: Value not found: x\n", OS.str());
Str.clear();
}

View File

@ -16,95 +16,139 @@
using namespace clang;
using namespace clang::query;
TEST(QueryParser, NoOp) {
QueryRef Q = QueryParser::parse("");
class QueryParserTest : public ::testing::Test {
protected:
QueryParserTest() {}
QueryRef parse(StringRef Code) { return QueryParser::parse(Code, QS); }
QuerySession QS{llvm::ArrayRef<ASTUnit*>()};
};
TEST_F(QueryParserTest, NoOp) {
QueryRef Q = parse("");
EXPECT_TRUE(isa<NoOpQuery>(Q));
Q = QueryParser::parse("\n");
Q = parse("\n");
EXPECT_TRUE(isa<NoOpQuery>(Q));
}
TEST(QueryParser, Invalid) {
QueryRef Q = QueryParser::parse("foo");
TEST_F(QueryParserTest, Invalid) {
QueryRef Q = parse("foo");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("unknown command: foo", cast<InvalidQuery>(Q)->ErrStr);
}
TEST(QueryParser, Help) {
QueryRef Q = QueryParser::parse("help");
TEST_F(QueryParserTest, Help) {
QueryRef Q = parse("help");
ASSERT_TRUE(isa<HelpQuery>(Q));
Q = QueryParser::parse("help me");
Q = parse("help me");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("unexpected extra input: ' me'", cast<InvalidQuery>(Q)->ErrStr);
}
TEST(QueryParser, Set) {
QueryRef Q = QueryParser::parse("set");
TEST_F(QueryParserTest, Set) {
QueryRef Q = parse("set");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected variable name", cast<InvalidQuery>(Q)->ErrStr);
Q = QueryParser::parse("set foo bar");
Q = parse("set foo bar");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("unknown variable: 'foo'", cast<InvalidQuery>(Q)->ErrStr);
Q = QueryParser::parse("set output");
Q = parse("set output");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected 'diag', 'print' or 'dump', got ''",
cast<InvalidQuery>(Q)->ErrStr);
Q = QueryParser::parse("set bind-root true foo");
Q = parse("set bind-root true foo");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("unexpected extra input: ' foo'", cast<InvalidQuery>(Q)->ErrStr);
Q = QueryParser::parse("set output foo");
Q = parse("set output foo");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected 'diag', 'print' or 'dump', got 'foo'",
cast<InvalidQuery>(Q)->ErrStr);
Q = QueryParser::parse("set output dump");
Q = 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 = QueryParser::parse("set bind-root foo");
Q = parse("set bind-root foo");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected 'true' or 'false', got 'foo'",
cast<InvalidQuery>(Q)->ErrStr);
Q = QueryParser::parse("set bind-root true");
Q = 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 = QueryParser::parse("match decl()");
TEST_F(QueryParserTest, Match) {
QueryRef Q = parse("match decl()");
ASSERT_TRUE(isa<MatchQuery>(Q));
EXPECT_TRUE(cast<MatchQuery>(Q)->Matcher.canConvertTo<Decl>());
Q = QueryParser::parse("m stmt()");
Q = parse("m stmt()");
ASSERT_TRUE(isa<MatchQuery>(Q));
EXPECT_TRUE(cast<MatchQuery>(Q)->Matcher.canConvertTo<Stmt>());
}
TEST(QueryParser, Complete) {
TEST_F(QueryParserTest, LetUnlet) {
QueryRef Q = parse("let foo decl()");
ASSERT_TRUE(isa<LetQuery>(Q));
EXPECT_EQ("foo", cast<LetQuery>(Q)->Name);
EXPECT_TRUE(cast<LetQuery>(Q)->Value.isMatcher());
EXPECT_TRUE(cast<LetQuery>(Q)->Value.getMatcher().hasTypedMatcher<Decl>());
Q = parse("let bar \"str\"");
ASSERT_TRUE(isa<LetQuery>(Q));
EXPECT_EQ("bar", cast<LetQuery>(Q)->Name);
EXPECT_TRUE(cast<LetQuery>(Q)->Value.isString());
EXPECT_EQ("str", cast<LetQuery>(Q)->Value.getString());
Q = parse("let");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected variable name", cast<InvalidQuery>(Q)->ErrStr);
Q = parse("unlet x");
ASSERT_TRUE(isa<LetQuery>(Q));
EXPECT_EQ("x", cast<LetQuery>(Q)->Name);
EXPECT_FALSE(cast<LetQuery>(Q)->Value.hasValue());
Q = parse("unlet");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected variable name", cast<InvalidQuery>(Q)->ErrStr);
Q = parse("unlet x bad_data");
ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("unexpected extra input: ' bad_data'",
cast<InvalidQuery>(Q)->ErrStr);
}
TEST_F(QueryParserTest, Complete) {
std::vector<llvm::LineEditor::Completion> Comps =
QueryParser::complete("", 0);
ASSERT_EQ(3u, Comps.size());
QueryParser::complete("", 0, QS);
ASSERT_EQ(5u, 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);
EXPECT_EQ("let ", Comps[1].TypedText);
EXPECT_EQ("let", Comps[1].DisplayText);
EXPECT_EQ("match ", Comps[2].TypedText);
EXPECT_EQ("match", Comps[2].DisplayText);
EXPECT_EQ("set ", Comps[3].TypedText);
EXPECT_EQ("set", Comps[3].DisplayText);
EXPECT_EQ("unlet ", Comps[4].TypedText);
EXPECT_EQ("unlet", Comps[4].DisplayText);
Comps = QueryParser::complete("set o", 5);
Comps = QueryParser::complete("set o", 5, QS);
ASSERT_EQ(1u, Comps.size());
EXPECT_EQ("utput ", Comps[0].TypedText);
EXPECT_EQ("output", Comps[0].DisplayText);
Comps = QueryParser::complete("match while", 11);
Comps = QueryParser::complete("match while", 11, QS);
ASSERT_EQ(1u, Comps.size());
EXPECT_EQ("Stmt(", Comps[0].TypedText);
EXPECT_EQ("Matcher<Stmt> whileStmt(Matcher<WhileStmt>...)",