1 | //===---- QueryParser.cpp - clang-query command parser --------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "QueryParser.h" |
10 | #include "Query.h" |
11 | #include "QuerySession.h" |
12 | #include "clang/ASTMatchers/Dynamic/Parser.h" |
13 | #include "clang/Basic/CharInfo.h" |
14 | #include "llvm/ADT/StringRef.h" |
15 | #include "llvm/ADT/StringSwitch.h" |
16 | #include <optional> |
17 | #include <set> |
18 | |
19 | using namespace llvm; |
20 | using namespace clang::ast_matchers::dynamic; |
21 | |
22 | namespace clang { |
23 | namespace query { |
24 | |
25 | // Lex any amount of whitespace followed by a "word" (any sequence of |
26 | // non-whitespace characters) from the start of region [Begin,End). If no word |
27 | // is found before End, return StringRef(). Begin is adjusted to exclude the |
28 | // lexed region. |
29 | StringRef QueryParser::lexWord() { |
30 | // Don't trim newlines. |
31 | Line = Line.ltrim(Chars: " \t\v\f\r" ); |
32 | |
33 | if (Line.empty()) |
34 | // Even though the Line is empty, it contains a pointer and |
35 | // a (zero) length. The pointer is used in the LexOrCompleteWord |
36 | // code completion. |
37 | return Line; |
38 | |
39 | StringRef Word; |
40 | if (Line.front() == '#') |
41 | Word = Line.substr(Start: 0, N: 1); |
42 | else |
43 | Word = Line.take_until(F: isWhitespace); |
44 | |
45 | Line = Line.drop_front(N: Word.size()); |
46 | return Word; |
47 | } |
48 | |
49 | // This is the StringSwitch-alike used by lexOrCompleteWord below. See that |
50 | // function for details. |
51 | template <typename T> struct QueryParser::LexOrCompleteWord { |
52 | StringRef Word; |
53 | StringSwitch<T> Switch; |
54 | |
55 | QueryParser *P; |
56 | // Set to the completion point offset in Word, or StringRef::npos if |
57 | // completion point not in Word. |
58 | size_t WordCompletionPos; |
59 | |
60 | // Lexes a word and stores it in Word. Returns a LexOrCompleteWord<T> object |
61 | // that can be used like a llvm::StringSwitch<T>, but adds cases as possible |
62 | // completions if the lexed word contains the completion point. |
63 | LexOrCompleteWord(QueryParser *P, StringRef &OutWord) |
64 | : Word(P->lexWord()), Switch(Word), P(P), |
65 | WordCompletionPos(StringRef::npos) { |
66 | OutWord = Word; |
67 | if (P->CompletionPos && P->CompletionPos <= Word.data() + Word.size()) { |
68 | if (P->CompletionPos < Word.data()) |
69 | WordCompletionPos = 0; |
70 | else |
71 | WordCompletionPos = P->CompletionPos - Word.data(); |
72 | } |
73 | } |
74 | |
75 | LexOrCompleteWord &Case(llvm::StringLiteral CaseStr, const T &Value, |
76 | bool IsCompletion = true) { |
77 | |
78 | if (WordCompletionPos == StringRef::npos) |
79 | Switch.Case(CaseStr, Value); |
80 | else if (CaseStr.size() != 0 && IsCompletion && WordCompletionPos <= CaseStr.size() && |
81 | CaseStr.substr(Start: 0, N: WordCompletionPos) == |
82 | Word.substr(Start: 0, N: WordCompletionPos)) |
83 | P->Completions.push_back(x: LineEditor::Completion( |
84 | (CaseStr.substr(Start: WordCompletionPos) + " " ).str(), |
85 | std::string(CaseStr))); |
86 | return *this; |
87 | } |
88 | |
89 | T Default(T Value) { return Switch.Default(Value); } |
90 | }; |
91 | |
92 | QueryRef QueryParser::parseSetBool(bool QuerySession::*Var) { |
93 | StringRef ValStr; |
94 | unsigned Value = LexOrCompleteWord<unsigned>(this, ValStr) |
95 | .Case(CaseStr: "false" , Value: 0) |
96 | .Case(CaseStr: "true" , Value: 1) |
97 | .Default(Value: ~0u); |
98 | if (Value == ~0u) { |
99 | return new InvalidQuery("expected 'true' or 'false', got '" + ValStr + "'" ); |
100 | } |
101 | return new SetQuery<bool>(Var, Value); |
102 | } |
103 | |
104 | template <typename QueryType> QueryRef QueryParser::parseSetOutputKind() { |
105 | StringRef ValStr; |
106 | unsigned OutKind = LexOrCompleteWord<unsigned>(this, ValStr) |
107 | .Case(CaseStr: "diag" , Value: OK_Diag) |
108 | .Case(CaseStr: "print" , Value: OK_Print) |
109 | .Case(CaseStr: "detailed-ast" , Value: OK_DetailedAST) |
110 | .Case(CaseStr: "dump" , Value: OK_DetailedAST) |
111 | .Default(Value: ~0u); |
112 | if (OutKind == ~0u) { |
113 | return new InvalidQuery("expected 'diag', 'print', 'detailed-ast' or " |
114 | "'dump', got '" + |
115 | ValStr + "'" ); |
116 | } |
117 | |
118 | switch (OutKind) { |
119 | case OK_DetailedAST: |
120 | return new QueryType(&QuerySession::DetailedASTOutput); |
121 | case OK_Diag: |
122 | return new QueryType(&QuerySession::DiagOutput); |
123 | case OK_Print: |
124 | return new QueryType(&QuerySession::PrintOutput); |
125 | } |
126 | |
127 | llvm_unreachable("Invalid output kind" ); |
128 | } |
129 | |
130 | QueryRef QueryParser::parseSetTraversalKind(TraversalKind QuerySession::*Var) { |
131 | StringRef ValStr; |
132 | unsigned Value = |
133 | LexOrCompleteWord<unsigned>(this, ValStr) |
134 | .Case(CaseStr: "AsIs" , Value: TK_AsIs) |
135 | .Case(CaseStr: "IgnoreUnlessSpelledInSource" , Value: TK_IgnoreUnlessSpelledInSource) |
136 | .Default(Value: ~0u); |
137 | if (Value == ~0u) { |
138 | return new InvalidQuery("expected traversal kind, got '" + ValStr + "'" ); |
139 | } |
140 | return new SetQuery<TraversalKind>(Var, static_cast<TraversalKind>(Value)); |
141 | } |
142 | |
143 | QueryRef QueryParser::endQuery(QueryRef Q) { |
144 | StringRef = Line; |
145 | StringRef = Extra.ltrim(Chars: " \t\v\f\r" ); |
146 | |
147 | if (ExtraTrimmed.starts_with(Prefix: '\n') || ExtraTrimmed.starts_with(Prefix: "\r\n" )) |
148 | Q->RemainingContent = Extra; |
149 | else { |
150 | StringRef TrailingWord = lexWord(); |
151 | if (TrailingWord.starts_with(Prefix: '#')) { |
152 | Line = Line.drop_until(F: [](char c) { return c == '\n'; }); |
153 | Line = Line.drop_while(F: [](char c) { return c == '\n'; }); |
154 | return endQuery(Q); |
155 | } |
156 | if (!TrailingWord.empty()) { |
157 | return new InvalidQuery("unexpected extra input: '" + Extra + "'" ); |
158 | } |
159 | } |
160 | return Q; |
161 | } |
162 | |
163 | namespace { |
164 | |
165 | enum ParsedQueryKind { |
166 | PQK_Invalid, |
167 | , |
168 | PQK_NoOp, |
169 | PQK_Help, |
170 | PQK_Let, |
171 | PQK_Match, |
172 | PQK_Set, |
173 | PQK_Unlet, |
174 | PQK_Quit, |
175 | PQK_Enable, |
176 | PQK_Disable, |
177 | PQK_File |
178 | }; |
179 | |
180 | enum ParsedQueryVariable { |
181 | PQV_Invalid, |
182 | PQV_Output, |
183 | PQV_BindRoot, |
184 | PQV_PrintMatcher, |
185 | PQV_EnableProfile, |
186 | PQV_Traversal |
187 | }; |
188 | |
189 | QueryRef makeInvalidQueryFromDiagnostics(const Diagnostics &Diag) { |
190 | std::string ErrStr; |
191 | llvm::raw_string_ostream OS(ErrStr); |
192 | Diag.printToStreamFull(OS); |
193 | return new InvalidQuery(OS.str()); |
194 | } |
195 | |
196 | } // namespace |
197 | |
198 | QueryRef QueryParser::completeMatcherExpression() { |
199 | std::vector<MatcherCompletion> Comps = Parser::completeExpression( |
200 | Code&: Line, CompletionOffset: CompletionPos - Line.begin(), S: nullptr, NamedValues: &QS.NamedValues); |
201 | for (auto I = Comps.begin(), E = Comps.end(); I != E; ++I) { |
202 | Completions.push_back(x: LineEditor::Completion(I->TypedText, I->MatcherDecl)); |
203 | } |
204 | return QueryRef(); |
205 | } |
206 | |
207 | QueryRef QueryParser::doParse() { |
208 | StringRef CommandStr; |
209 | ParsedQueryKind QKind = LexOrCompleteWord<ParsedQueryKind>(this, CommandStr) |
210 | .Case(CaseStr: "" , Value: PQK_NoOp) |
211 | .Case(CaseStr: "#" , Value: PQK_Comment, /*IsCompletion=*/false) |
212 | .Case(CaseStr: "help" , Value: PQK_Help) |
213 | .Case(CaseStr: "l" , Value: PQK_Let, /*IsCompletion=*/false) |
214 | .Case(CaseStr: "let" , Value: PQK_Let) |
215 | .Case(CaseStr: "m" , Value: PQK_Match, /*IsCompletion=*/false) |
216 | .Case(CaseStr: "match" , Value: PQK_Match) |
217 | .Case(CaseStr: "q" , Value: PQK_Quit, /*IsCompletion=*/false) |
218 | .Case(CaseStr: "quit" , Value: PQK_Quit) |
219 | .Case(CaseStr: "set" , Value: PQK_Set) |
220 | .Case(CaseStr: "enable" , Value: PQK_Enable) |
221 | .Case(CaseStr: "disable" , Value: PQK_Disable) |
222 | .Case(CaseStr: "unlet" , Value: PQK_Unlet) |
223 | .Case(CaseStr: "f" , Value: PQK_File, /*IsCompletion=*/false) |
224 | .Case(CaseStr: "file" , Value: PQK_File) |
225 | .Default(Value: PQK_Invalid); |
226 | |
227 | switch (QKind) { |
228 | case PQK_Comment: |
229 | case PQK_NoOp: |
230 | Line = Line.drop_until(F: [](char c) { return c == '\n'; }); |
231 | Line = Line.drop_while(F: [](char c) { return c == '\n'; }); |
232 | if (Line.empty()) |
233 | return new NoOpQuery; |
234 | return doParse(); |
235 | |
236 | case PQK_Help: |
237 | return endQuery(Q: new HelpQuery); |
238 | |
239 | case PQK_Quit: |
240 | return endQuery(Q: new QuitQuery); |
241 | |
242 | case PQK_Let: { |
243 | StringRef Name = lexWord(); |
244 | |
245 | if (Name.empty()) |
246 | return new InvalidQuery("expected variable name" ); |
247 | |
248 | if (CompletionPos) |
249 | return completeMatcherExpression(); |
250 | |
251 | Diagnostics Diag; |
252 | ast_matchers::dynamic::VariantValue Value; |
253 | if (!Parser::parseExpression(Code&: Line, S: nullptr, NamedValues: &QS.NamedValues, Value: &Value, |
254 | Error: &Diag)) { |
255 | return makeInvalidQueryFromDiagnostics(Diag); |
256 | } |
257 | |
258 | auto *Q = new LetQuery(Name, Value); |
259 | Q->RemainingContent = Line; |
260 | return Q; |
261 | } |
262 | |
263 | case PQK_Match: { |
264 | if (CompletionPos) |
265 | return completeMatcherExpression(); |
266 | |
267 | Diagnostics Diag; |
268 | auto MatcherSource = Line.ltrim(); |
269 | auto OrigMatcherSource = MatcherSource; |
270 | std::optional<DynTypedMatcher> Matcher = Parser::parseMatcherExpression( |
271 | MatcherCode&: MatcherSource, S: nullptr, NamedValues: &QS.NamedValues, Error: &Diag); |
272 | if (!Matcher) { |
273 | return makeInvalidQueryFromDiagnostics(Diag); |
274 | } |
275 | auto ActualSource = OrigMatcherSource.slice(Start: 0, End: OrigMatcherSource.size() - |
276 | MatcherSource.size()); |
277 | auto *Q = new MatchQuery(ActualSource, *Matcher); |
278 | Q->RemainingContent = MatcherSource; |
279 | return Q; |
280 | } |
281 | |
282 | case PQK_Set: { |
283 | StringRef VarStr; |
284 | ParsedQueryVariable Var = |
285 | LexOrCompleteWord<ParsedQueryVariable>(this, VarStr) |
286 | .Case(CaseStr: "output" , Value: PQV_Output) |
287 | .Case(CaseStr: "bind-root" , Value: PQV_BindRoot) |
288 | .Case(CaseStr: "print-matcher" , Value: PQV_PrintMatcher) |
289 | .Case(CaseStr: "enable-profile" , Value: PQV_EnableProfile) |
290 | .Case(CaseStr: "traversal" , Value: PQV_Traversal) |
291 | .Default(Value: PQV_Invalid); |
292 | if (VarStr.empty()) |
293 | return new InvalidQuery("expected variable name" ); |
294 | if (Var == PQV_Invalid) |
295 | return new InvalidQuery("unknown variable: '" + VarStr + "'" ); |
296 | |
297 | QueryRef Q; |
298 | switch (Var) { |
299 | case PQV_Output: |
300 | Q = parseSetOutputKind<SetExclusiveOutputQuery>(); |
301 | break; |
302 | case PQV_BindRoot: |
303 | Q = parseSetBool(Var: &QuerySession::BindRoot); |
304 | break; |
305 | case PQV_PrintMatcher: |
306 | Q = parseSetBool(Var: &QuerySession::PrintMatcher); |
307 | break; |
308 | case PQV_EnableProfile: |
309 | Q = parseSetBool(Var: &QuerySession::EnableProfile); |
310 | break; |
311 | case PQV_Traversal: |
312 | Q = parseSetTraversalKind(Var: &QuerySession::TK); |
313 | break; |
314 | case PQV_Invalid: |
315 | llvm_unreachable("Invalid query kind" ); |
316 | } |
317 | |
318 | return endQuery(Q); |
319 | } |
320 | case PQK_Enable: |
321 | case PQK_Disable: { |
322 | StringRef VarStr; |
323 | ParsedQueryVariable Var = |
324 | LexOrCompleteWord<ParsedQueryVariable>(this, VarStr) |
325 | .Case(CaseStr: "output" , Value: PQV_Output) |
326 | .Default(Value: PQV_Invalid); |
327 | if (VarStr.empty()) |
328 | return new InvalidQuery("expected variable name" ); |
329 | if (Var == PQV_Invalid) |
330 | return new InvalidQuery("unknown variable: '" + VarStr + "'" ); |
331 | |
332 | QueryRef Q; |
333 | |
334 | if (QKind == PQK_Enable) |
335 | Q = parseSetOutputKind<EnableOutputQuery>(); |
336 | else if (QKind == PQK_Disable) |
337 | Q = parseSetOutputKind<DisableOutputQuery>(); |
338 | else |
339 | llvm_unreachable("Invalid query kind" ); |
340 | return endQuery(Q); |
341 | } |
342 | |
343 | case PQK_Unlet: { |
344 | StringRef Name = lexWord(); |
345 | |
346 | if (Name.empty()) |
347 | return new InvalidQuery("expected variable name" ); |
348 | |
349 | return endQuery(Q: new LetQuery(Name, VariantValue())); |
350 | } |
351 | |
352 | case PQK_File: |
353 | return new FileQuery(Line); |
354 | |
355 | case PQK_Invalid: |
356 | return new InvalidQuery("unknown command: " + CommandStr); |
357 | } |
358 | |
359 | llvm_unreachable("Invalid query kind" ); |
360 | } |
361 | |
362 | QueryRef QueryParser::parse(StringRef Line, const QuerySession &QS) { |
363 | return QueryParser(Line, QS).doParse(); |
364 | } |
365 | |
366 | std::vector<LineEditor::Completion> |
367 | QueryParser::complete(StringRef Line, size_t Pos, const QuerySession &QS) { |
368 | QueryParser P(Line, QS); |
369 | P.CompletionPos = Line.data() + Pos; |
370 | |
371 | P.doParse(); |
372 | return P.Completions; |
373 | } |
374 | |
375 | } // namespace query |
376 | } // namespace clang |
377 | |