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