Skip to content

Commit 5e9401c

Browse files
authored
[clang-query] Load queries and matchers from file during REPL cycle (#90603)
The clang-query tool has the ability to execute or pre-load queries from a file when the tool is launched, but doesn't have the ability to do the same from the interactive REPL prompt. Because the prompt also doesn't seem to allow multi-line matchers, this can make prototyping and iterating on more complicated matchers difficult. Supporting a dynamic load at REPL time allows the cost of reading the compilation database and building the AST to be imposed just once, and allows faster prototyping.
1 parent 19a62fb commit 5e9401c

File tree

12 files changed

+80
-21
lines changed

12 files changed

+80
-21
lines changed

clang-tools-extra/clang-query/Query.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "Query.h"
10+
#include "QueryParser.h"
1011
#include "QuerySession.h"
1112
#include "clang/AST/ASTDumper.h"
1213
#include "clang/ASTMatchers/ASTMatchFinder.h"
@@ -281,5 +282,26 @@ const QueryKind SetQueryKind<bool>::value;
281282
const QueryKind SetQueryKind<OutputKind>::value;
282283
#endif
283284

285+
bool FileQuery::run(llvm::raw_ostream &OS, QuerySession &QS) const {
286+
auto Buffer = llvm::MemoryBuffer::getFile(StringRef{File}.trim());
287+
if (!Buffer) {
288+
if (Prefix.has_value())
289+
llvm::errs() << *Prefix << ": ";
290+
llvm::errs() << "cannot open " << File << ": "
291+
<< Buffer.getError().message() << "\n";
292+
return false;
293+
}
294+
295+
StringRef FileContentRef(Buffer.get()->getBuffer());
296+
297+
while (!FileContentRef.empty()) {
298+
QueryRef Q = QueryParser::parse(FileContentRef, QS);
299+
if (!Q->run(llvm::outs(), QS))
300+
return false;
301+
FileContentRef = Q->RemainingContent;
302+
}
303+
return true;
304+
}
305+
284306
} // namespace query
285307
} // namespace clang

clang-tools-extra/clang-query/Query.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ enum QueryKind {
3030
QK_SetTraversalKind,
3131
QK_EnableOutputKind,
3232
QK_DisableOutputKind,
33-
QK_Quit
33+
QK_Quit,
34+
QK_File
3435
};
3536

3637
class QuerySession;
@@ -188,6 +189,21 @@ struct DisableOutputQuery : SetNonExclusiveOutputQuery {
188189
}
189190
};
190191

192+
struct FileQuery : Query {
193+
FileQuery(StringRef File, StringRef Prefix = StringRef())
194+
: Query(QK_File), File(File),
195+
Prefix(!Prefix.empty() ? std::optional<std::string>(Prefix)
196+
: std::nullopt) {}
197+
198+
bool run(llvm::raw_ostream &OS, QuerySession &QS) const override;
199+
200+
static bool classof(const Query *Q) { return Q->Kind == QK_File; }
201+
202+
private:
203+
std::string File;
204+
std::optional<std::string> Prefix;
205+
};
206+
191207
} // namespace query
192208
} // namespace clang
193209

clang-tools-extra/clang-query/QueryParser.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ enum ParsedQueryKind {
183183
PQK_Unlet,
184184
PQK_Quit,
185185
PQK_Enable,
186-
PQK_Disable
186+
PQK_Disable,
187+
PQK_File
187188
};
188189

189190
enum ParsedQueryVariable {
@@ -222,12 +223,14 @@ QueryRef QueryParser::doParse() {
222223
.Case("let", PQK_Let)
223224
.Case("m", PQK_Match, /*IsCompletion=*/false)
224225
.Case("match", PQK_Match)
225-
.Case("q", PQK_Quit, /*IsCompletion=*/false)
226+
.Case("q", PQK_Quit, /*IsCompletion=*/false)
226227
.Case("quit", PQK_Quit)
227228
.Case("set", PQK_Set)
228229
.Case("enable", PQK_Enable)
229230
.Case("disable", PQK_Disable)
230231
.Case("unlet", PQK_Unlet)
232+
.Case("f", PQK_File, /*IsCompletion=*/false)
233+
.Case("file", PQK_File)
231234
.Default(PQK_Invalid);
232235

233236
switch (QKind) {
@@ -351,6 +354,9 @@ QueryRef QueryParser::doParse() {
351354
return endQuery(new LetQuery(Name, VariantValue()));
352355
}
353356

357+
case PQK_File:
358+
return new FileQuery(Line);
359+
354360
case PQK_Invalid:
355361
return new InvalidQuery("unknown command: " + CommandStr);
356362
}

clang-tools-extra/clang-query/tool/ClangQuery.cpp

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,22 +74,8 @@ static cl::opt<std::string> PreloadFile(
7474

7575
bool runCommandsInFile(const char *ExeName, std::string const &FileName,
7676
QuerySession &QS) {
77-
auto Buffer = llvm::MemoryBuffer::getFile(FileName);
78-
if (!Buffer) {
79-
llvm::errs() << ExeName << ": cannot open " << FileName << ": "
80-
<< Buffer.getError().message() << "\n";
81-
return true;
82-
}
83-
84-
StringRef FileContentRef(Buffer.get()->getBuffer());
85-
86-
while (!FileContentRef.empty()) {
87-
QueryRef Q = QueryParser::parse(FileContentRef, QS);
88-
if (!Q->run(llvm::outs(), QS))
89-
return true;
90-
FileContentRef = Q->RemainingContent;
91-
}
92-
return false;
77+
FileQuery Query(FileName, ExeName);
78+
return !Query.run(llvm::errs(), QS);
9379
}
9480

9581
int main(int argc, const char **argv) {

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ Improvements to clang-doc
9090
Improvements to clang-query
9191
---------------------------
9292

93-
The improvements are...
93+
- Added the `file` command to dynamically load a list of commands and matchers
94+
from an external file, allowing the cost of reading the compilation database
95+
and building the AST to be imposed just once for faster prototyping.
9496

9597
Improvements to clang-rename
9698
----------------------------
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# This file intentionally has no queries
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
f DIRECTORY/runtime_file.script
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
set bind-root false
2+
3+
l func functionDecl(hasName("bar"))
4+
m func.bind("f")
5+
m varDecl().bind("v")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// RUN: not clang-query -c foo -c bar %s -- | FileCheck %s
22
// RUN: not clang-query -f %S/Inputs/foo.script %s -- | FileCheck %s
33
// RUN: not clang-query -f %S/Inputs/nonexistent.script %s -- 2>&1 | FileCheck --check-prefix=CHECK-NONEXISTENT %s
4+
// RUN: not clang-query -c 'file %S/Inputs/nonexistent.script' %s -- 2>&1 | FileCheck --check-prefix=CHECK-NONEXISTENT-FILEQUERY %s
45
// RUN: not clang-query -c foo -f foo %s -- 2>&1 | FileCheck --check-prefix=CHECK-BOTH %s
56

67
// CHECK: unknown command: foo
78
// CHECK-NOT: unknown command: bar
89

910
// CHECK-NONEXISTENT: cannot open {{.*}}nonexistent.script
11+
// CHECK-NONEXISTENT-FILEQUERY: cannot open {{.*}}nonexistent.script
1012
// CHECK-BOTH: cannot specify both -c and -f
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// RUN: clang-query -c 'file %S/Inputs/empty.script' %s --
2+
// COM: no output expected; nothing to CHECK
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// RUN: rm -rf %/t
2+
// RUN: mkdir %/t
3+
// RUN: cp %/S/Inputs/file.script %/t/file.script
4+
// RUN: cp %/S/Inputs/runtime_file.script %/t/runtime_file.script
5+
// Need to embed the correct temp path in the actual JSON-RPC requests.
6+
// RUN: sed -e "s|DIRECTORY|%/t|" %/t/file.script > %/t/file.script.temp
7+
8+
// RUN: clang-query -c 'file %/t/file.script.temp' %s -- | FileCheck %s
9+
10+
// CHECK: file-query.c:11:1: note: "f" binds here
11+
void bar(void) {}
12+
13+
// CHECK: file-query.c:14:1: note: "v" binds here
14+
int baz{1};

clang-tools-extra/unittests/clang-query/QueryParserTest.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ TEST_F(QueryParserTest, Comment) {
197197
TEST_F(QueryParserTest, Complete) {
198198
std::vector<llvm::LineEditor::Completion> Comps =
199199
QueryParser::complete("", 0, QS);
200-
ASSERT_EQ(8u, Comps.size());
200+
ASSERT_EQ(9u, Comps.size());
201201
EXPECT_EQ("help ", Comps[0].TypedText);
202202
EXPECT_EQ("help", Comps[0].DisplayText);
203203
EXPECT_EQ("let ", Comps[1].TypedText);
@@ -214,6 +214,8 @@ TEST_F(QueryParserTest, Complete) {
214214
EXPECT_EQ("disable", Comps[6].DisplayText);
215215
EXPECT_EQ("unlet ", Comps[7].TypedText);
216216
EXPECT_EQ("unlet", Comps[7].DisplayText);
217+
EXPECT_EQ("file ", Comps[8].TypedText);
218+
EXPECT_EQ("file", Comps[8].DisplayText);
217219

218220
Comps = QueryParser::complete("set o", 5, QS);
219221
ASSERT_EQ(1u, Comps.size());

0 commit comments

Comments
 (0)