Skip to content

Commit

Permalink
Allow newlines in AST Matchers in clang-query files
Browse files Browse the repository at this point in the history
Reviewers: aaron.ballman

Subscribers: cfe-commits

Tags: #clang

Differential Revision: https://reviews.llvm.org/D71842
  • Loading branch information
steveire committed Dec 26, 2019
1 parent 04926e6 commit 522ee29
Show file tree
Hide file tree
Showing 8 changed files with 377 additions and 76 deletions.
21 changes: 18 additions & 3 deletions clang-tools-extra/clang-query/Query.cpp
Expand Up @@ -101,9 +101,24 @@ bool MatchQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
Finder.matchAST(AST->getASTContext());

if (QS.PrintMatcher) {
std::string prefixText = "Matcher: ";
OS << "\n " << prefixText << Source << "\n";
OS << " " << std::string(prefixText.size() + Source.size(), '=') << '\n';
SmallVector<StringRef, 4> Lines;
Source.split(Lines, "\n");
auto FirstLine = Lines[0];
Lines.erase(Lines.begin(), Lines.begin() + 1);
while (!Lines.empty() && Lines.back().empty()) {
Lines.resize(Lines.size() - 1);
}
unsigned MaxLength = FirstLine.size();
std::string PrefixText = "Matcher: ";
OS << "\n " << PrefixText << FirstLine;

for (auto Line : Lines) {
OS << "\n" << std::string(PrefixText.size() + 2, ' ') << Line;
MaxLength = std::max<int>(MaxLength, Line.rtrim().size());
}

OS << "\n"
<< " " << std::string(PrefixText.size() + MaxLength, '=') << "\n\n";
}

for (auto MI = Matches.begin(), ME = Matches.end(); MI != ME; ++MI) {
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clang-query/Query.h
Expand Up @@ -44,6 +44,7 @@ struct Query : llvm::RefCountedBase<Query> {
/// \return false if an error occurs, otherwise return true.
virtual bool run(llvm::raw_ostream &OS, QuerySession &QS) const = 0;

StringRef RemainingContent;
const QueryKind Kind;
};

Expand Down
54 changes: 42 additions & 12 deletions clang-tools-extra/clang-query/QueryParser.cpp
Expand Up @@ -26,20 +26,23 @@ namespace query {
// is found before End, return StringRef(). Begin is adjusted to exclude the
// lexed region.
StringRef QueryParser::lexWord() {
Line = Line.ltrim();
Line = Line.drop_while([this](char c) {
// Don't trim newlines.
return StringRef(" \t\v\f\r").contains(c);
});

if (Line.empty())
// Even though the Line is empty, it contains a pointer and
// a (zero) length. The pointer is used in the LexOrCompleteWord
// code completion.
return Line;

if (Line.front() == '#') {
Line = {};
return StringRef();
}
StringRef Word;
if (Line.front() == '#')
Word = Line.substr(0, 1);
else
Word = Line.take_until(isWhitespace);

StringRef Word = Line.take_until(isWhitespace);
Line = Line.drop_front(Word.size());
return Word;
}
Expand Down Expand Up @@ -125,9 +128,25 @@ template <typename QueryType> QueryRef QueryParser::parseSetOutputKind() {
}

QueryRef QueryParser::endQuery(QueryRef Q) {
const StringRef Extra = Line;
if (!lexWord().empty())
return new InvalidQuery("unexpected extra input: '" + Extra + "'");
StringRef Extra = Line;
StringRef ExtraTrimmed = Extra.drop_while(
[](char c) { return StringRef(" \t\v\f\r").contains(c); });

if ((!ExtraTrimmed.empty() && ExtraTrimmed[0] == '\n') ||
(ExtraTrimmed.size() >= 2 && ExtraTrimmed[0] == '\r' &&
ExtraTrimmed[1] == '\n'))
Q->RemainingContent = Extra;
else {
StringRef TrailingWord = lexWord();
if (!TrailingWord.empty() && TrailingWord.front() == '#') {
Line = Line.drop_until([](char c) { return c == '\n'; });
Line = Line.drop_while([](char c) { return c == '\n'; });
return endQuery(Q);
}
if (!TrailingWord.empty()) {
return new InvalidQuery("unexpected extra input: '" + Extra + "'");
}
}
return Q;
}

Expand Down Expand Up @@ -193,7 +212,11 @@ QueryRef QueryParser::doParse() {
switch (QKind) {
case PQK_Comment:
case PQK_NoOp:
return new NoOpQuery;
Line = Line.drop_until([](char c) { return c == '\n'; });
Line = Line.drop_while([](char c) { return c == '\n'; });
if (Line.empty())
return new NoOpQuery;
return doParse();

case PQK_Help:
return endQuery(new HelpQuery);
Expand All @@ -217,7 +240,9 @@ QueryRef QueryParser::doParse() {
return makeInvalidQueryFromDiagnostics(Diag);
}

return new LetQuery(Name, Value);
auto *Q = new LetQuery(Name, Value);
Q->RemainingContent = Line;
return Q;
}

case PQK_Match: {
Expand All @@ -226,12 +251,17 @@ QueryRef QueryParser::doParse() {

Diagnostics Diag;
auto MatcherSource = Line.trim();
auto OrigMatcherSource = MatcherSource;
Optional<DynTypedMatcher> Matcher = Parser::parseMatcherExpression(
MatcherSource, nullptr, &QS.NamedValues, &Diag);
if (!Matcher) {
return makeInvalidQueryFromDiagnostics(Diag);
}
return new MatchQuery(MatcherSource, *Matcher);
auto ActualSource = OrigMatcherSource.slice(0, OrigMatcherSource.size() -
MatcherSource.size());
auto *Q = new MatchQuery(ActualSource, *Matcher);
Q->RemainingContent = MatcherSource;
return Q;
}

case PQK_Set: {
Expand Down
11 changes: 7 additions & 4 deletions clang-tools-extra/clang-query/tool/ClangQuery.cpp
Expand Up @@ -69,13 +69,16 @@ bool runCommandsInFile(const char *ExeName, std::string const &FileName,
llvm::errs() << ExeName << ": cannot open " << FileName << "\n";
return 1;
}
while (Input.good()) {
std::string Line;
std::getline(Input, Line);

QueryRef Q = QueryParser::parse(Line, QS);
std::string FileContent((std::istreambuf_iterator<char>(Input)),
std::istreambuf_iterator<char>());

StringRef FileContentRef(FileContent);
while (!FileContentRef.empty()) {
QueryRef Q = QueryParser::parse(FileContentRef, QS);
if (!Q->run(llvm::outs(), QS))
return true;
FileContentRef = Q->RemainingContent;
}
return false;
}
Expand Down
101 changes: 101 additions & 0 deletions clang-tools-extra/unittests/clang-query/QueryParserTest.cpp
Expand Up @@ -230,3 +230,104 @@ TEST_F(QueryParserTest, Complete) {
EXPECT_EQ("et ", Comps[0].TypedText);
EXPECT_EQ("let", Comps[0].DisplayText);
}

TEST_F(QueryParserTest, Multiline) {

// Single string with multiple commands
QueryRef Q = parse(R"matcher(
set bind-root false
set output dump
)matcher");

ASSERT_TRUE(isa<SetQuery<bool>>(Q));

Q = parse(Q->RemainingContent);
ASSERT_TRUE(isa<SetExclusiveOutputQuery>(Q));

// Missing newline
Q = parse(R"matcher(
set bind-root false set output dump
)matcher");

ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("unexpected extra input: ' set output dump\n '",
cast<InvalidQuery>(Q)->ErrStr);

// Commands which do their own parsing
Q = parse(R"matcher(
let fn functionDecl(hasName("foo"))
match callExpr(callee(functionDecl()))
)matcher");

ASSERT_TRUE(isa<LetQuery>(Q));

Q = parse(Q->RemainingContent);
ASSERT_TRUE(isa<MatchQuery>(Q));

// Multi-line matcher
Q = parse(R"matcher(
match callExpr(callee(
functionDecl().bind("fn")
))
)matcher");

ASSERT_TRUE(isa<MatchQuery>(Q));

// Comment locations
Q = parse(R"matcher(
#nospacecomment
# Leading comment
match callExpr ( # Trailing comment
# Comment alone on line
callee(
functionDecl(
).bind(
"fn"
)
)) # Comment trailing close
# Comment after match
)matcher");

ASSERT_TRUE(isa<MatchQuery>(Q));

// \r\n
Q = parse("set bind-root false\r\nset output dump");

ASSERT_TRUE(isa<SetQuery<bool>>(Q));

Q = parse(Q->RemainingContent);
ASSERT_TRUE(isa<SetExclusiveOutputQuery>(Q));

// Leading and trailing space in lines
Q = parse(" set bind-root false \r\n set output dump ");

ASSERT_TRUE(isa<SetQuery<bool>>(Q));

Q = parse(Q->RemainingContent);
ASSERT_TRUE(isa<SetExclusiveOutputQuery>(Q));

// Incomplete commands
Q = parse("set\nbind-root false");

ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected variable name", cast<InvalidQuery>(Q)->ErrStr);

Q = parse("set bind-root\nfalse");

ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("expected 'true' or 'false', got ''",
cast<InvalidQuery>(Q)->ErrStr);

Q = parse(R"matcher(
match callExpr
(
)
)matcher");

ASSERT_TRUE(isa<InvalidQuery>(Q));
EXPECT_EQ("1:9: Error parsing matcher. Found token <NewLine> "
"while looking for '('.",
cast<InvalidQuery>(Q)->ErrStr);
}
24 changes: 11 additions & 13 deletions clang/include/clang/ASTMatchers/Dynamic/Parser.h
Expand Up @@ -164,16 +164,14 @@ class Parser {
/// description of the error.
/// The caller takes ownership of the DynTypedMatcher object returned.
static llvm::Optional<DynTypedMatcher>
parseMatcherExpression(StringRef MatcherCode, Sema *S,
const NamedValueMap *NamedValues,
Diagnostics *Error);
parseMatcherExpression(StringRef &MatcherCode, Sema *S,
const NamedValueMap *NamedValues, Diagnostics *Error);
static llvm::Optional<DynTypedMatcher>
parseMatcherExpression(StringRef MatcherCode, Sema *S,
Diagnostics *Error) {
parseMatcherExpression(StringRef &MatcherCode, Sema *S, Diagnostics *Error) {
return parseMatcherExpression(MatcherCode, S, nullptr, Error);
}
static llvm::Optional<DynTypedMatcher>
parseMatcherExpression(StringRef MatcherCode, Diagnostics *Error) {
parseMatcherExpression(StringRef &MatcherCode, Diagnostics *Error) {
return parseMatcherExpression(MatcherCode, nullptr, Error);
}

Expand All @@ -189,14 +187,14 @@ class Parser {
/// \param NamedValues A map of precomputed named values. This provides
/// the dictionary for the <NamedValue> rule of the grammar.
/// If null, it is ignored.
static bool parseExpression(StringRef Code, Sema *S,
static bool parseExpression(StringRef &Code, Sema *S,
const NamedValueMap *NamedValues,
VariantValue *Value, Diagnostics *Error);
static bool parseExpression(StringRef Code, Sema *S,
VariantValue *Value, Diagnostics *Error) {
static bool parseExpression(StringRef &Code, Sema *S, VariantValue *Value,
Diagnostics *Error) {
return parseExpression(Code, S, nullptr, Value, Error);
}
static bool parseExpression(StringRef Code, VariantValue *Value,
static bool parseExpression(StringRef &Code, VariantValue *Value,
Diagnostics *Error) {
return parseExpression(Code, nullptr, Value, Error);
}
Expand All @@ -213,14 +211,14 @@ class Parser {
/// \return The list of completions, which may be empty if there are no
/// available completions or if an error occurred.
static std::vector<MatcherCompletion>
completeExpression(StringRef Code, unsigned CompletionOffset, Sema *S,
completeExpression(StringRef &Code, unsigned CompletionOffset, Sema *S,
const NamedValueMap *NamedValues);
static std::vector<MatcherCompletion>
completeExpression(StringRef Code, unsigned CompletionOffset, Sema *S) {
completeExpression(StringRef &Code, unsigned CompletionOffset, Sema *S) {
return completeExpression(Code, CompletionOffset, S, nullptr);
}
static std::vector<MatcherCompletion>
completeExpression(StringRef Code, unsigned CompletionOffset) {
completeExpression(StringRef &Code, unsigned CompletionOffset) {
return completeExpression(Code, CompletionOffset, nullptr);
}

Expand Down

0 comments on commit 522ee29

Please sign in to comment.