| 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 | |