xref: /llvm-project/clang-tools-extra/clang-query/QueryParser.cpp (revision 03f06b97106b84569db8e0277e70e44bb3b1e9b7)
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(" \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(0, 1);
42   else
43     Word = Line.take_until(isWhitespace);
44 
45   Line = Line.drop_front(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(0, WordCompletionPos) ==
82                  Word.substr(0, WordCompletionPos))
83       P->Completions.push_back(LineEditor::Completion(
84           (CaseStr.substr(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("false", 0)
96                        .Case("true", 1)
97                        .Default(~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("diag", OK_Diag)
108                          .Case("print", OK_Print)
109                          .Case("detailed-ast", OK_DetailedAST)
110                          .Case("dump", OK_DetailedAST)
111                          .Default(~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("AsIs", TK_AsIs)
135           .Case("IgnoreUnlessSpelledInSource", TK_IgnoreUnlessSpelledInSource)
136           .Default(~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 Extra = Line;
145   StringRef ExtraTrimmed = Extra.ltrim(" \t\v\f\r");
146 
147   if (ExtraTrimmed.starts_with('\n') || ExtraTrimmed.starts_with("\r\n"))
148     Q->RemainingContent = Extra;
149   else {
150     StringRef TrailingWord = lexWord();
151     if (TrailingWord.starts_with('#')) {
152       Line = Line.drop_until([](char c) { return c == '\n'; });
153       Line = Line.drop_while([](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   PQK_Comment,
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       Line, CompletionPos - Line.begin(), nullptr, &QS.NamedValues);
201   for (auto I = Comps.begin(), E = Comps.end(); I != E; ++I) {
202     Completions.push_back(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("", PQK_NoOp)
211                               .Case("#", PQK_Comment, /*IsCompletion=*/false)
212                               .Case("help", PQK_Help)
213                               .Case("l", PQK_Let, /*IsCompletion=*/false)
214                               .Case("let", PQK_Let)
215                               .Case("m", PQK_Match, /*IsCompletion=*/false)
216                               .Case("match", PQK_Match)
217                               .Case("q", PQK_Quit, /*IsCompletion=*/false)
218                               .Case("quit", PQK_Quit)
219                               .Case("set", PQK_Set)
220                               .Case("enable", PQK_Enable)
221                               .Case("disable", PQK_Disable)
222                               .Case("unlet", PQK_Unlet)
223                               .Case("f", PQK_File, /*IsCompletion=*/false)
224                               .Case("file", PQK_File)
225                               .Default(PQK_Invalid);
226 
227   switch (QKind) {
228   case PQK_Comment:
229   case PQK_NoOp:
230     Line = Line.drop_until([](char c) { return c == '\n'; });
231     Line = Line.drop_while([](char c) { return c == '\n'; });
232     if (Line.empty())
233       return new NoOpQuery;
234     return doParse();
235 
236   case PQK_Help:
237     return endQuery(new HelpQuery);
238 
239   case PQK_Quit:
240     return endQuery(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(Line, nullptr, &QS.NamedValues, &Value,
254                                  &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         MatcherSource, nullptr, &QS.NamedValues, &Diag);
272     if (!Matcher) {
273       return makeInvalidQueryFromDiagnostics(Diag);
274     }
275     auto ActualSource = OrigMatcherSource.slice(0, 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("output", PQV_Output)
287             .Case("bind-root", PQV_BindRoot)
288             .Case("print-matcher", PQV_PrintMatcher)
289             .Case("enable-profile", PQV_EnableProfile)
290             .Case("traversal", PQV_Traversal)
291             .Default(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(&QuerySession::BindRoot);
304       break;
305     case PQV_PrintMatcher:
306       Q = parseSetBool(&QuerySession::PrintMatcher);
307       break;
308     case PQV_EnableProfile:
309       Q = parseSetBool(&QuerySession::EnableProfile);
310       break;
311     case PQV_Traversal:
312       Q = parseSetTraversalKind(&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("output", PQV_Output)
326             .Default(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(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