| //===---- QueryParser.cpp - clang-query command parser --------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "QueryParser.h" |
| #include "Query.h" |
| #include "QuerySession.h" |
| #include "clang/ASTMatchers/Dynamic/Parser.h" |
| #include "clang/Basic/CharInfo.h" |
| #include "clang/Tooling/NodeIntrospection.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/ADT/StringSwitch.h" |
| #include <set> |
| |
| using namespace llvm; |
| using namespace clang::ast_matchers::dynamic; |
| |
| namespace clang { |
| namespace query { |
| |
| // Lex any amount of whitespace followed by a "word" (any sequence of |
| // non-whitespace characters) from the start of region [Begin,End). If no word |
| // is found before End, return StringRef(). Begin is adjusted to exclude the |
| // lexed region. |
| StringRef QueryParser::lexWord() { |
| Line = Line.drop_while([](char c) { |
| // Don't trim newlines. |
| return StringRef(" \t\v\f\r").contains(c); |
| }); |
| |
| if (Line.empty()) |
| // Even though the Line is empty, it contains a pointer and |
| // a (zero) length. The pointer is used in the LexOrCompleteWord |
| // code completion. |
| return Line; |
| |
| StringRef Word; |
| if (Line.front() == '#') |
| Word = Line.substr(0, 1); |
| else |
| Word = Line.take_until(isWhitespace); |
| |
| Line = Line.drop_front(Word.size()); |
| return Word; |
| } |
| |
| // This is the StringSwitch-alike used by lexOrCompleteWord below. See that |
| // function for details. |
| template <typename T> struct QueryParser::LexOrCompleteWord { |
| StringRef Word; |
| StringSwitch<T> Switch; |
| |
| QueryParser *P; |
| // Set to the completion point offset in Word, or StringRef::npos if |
| // completion point not in Word. |
| size_t WordCompletionPos; |
| |
| // Lexes a word and stores it in Word. Returns a LexOrCompleteWord<T> object |
| // that can be used like a llvm::StringSwitch<T>, but adds cases as possible |
| // completions if the lexed word contains the completion point. |
| LexOrCompleteWord(QueryParser *P, StringRef &OutWord) |
| : Word(P->lexWord()), Switch(Word), P(P), |
| WordCompletionPos(StringRef::npos) { |
| OutWord = Word; |
| if (P->CompletionPos && P->CompletionPos <= Word.data() + Word.size()) { |
| if (P->CompletionPos < Word.data()) |
| WordCompletionPos = 0; |
| else |
| WordCompletionPos = P->CompletionPos - Word.data(); |
| } |
| } |
| |
| LexOrCompleteWord &Case(llvm::StringLiteral CaseStr, const T &Value, |
| bool IsCompletion = true) { |
| |
| if (WordCompletionPos == StringRef::npos) |
| Switch.Case(CaseStr, Value); |
| else if (CaseStr.size() != 0 && IsCompletion && WordCompletionPos <= CaseStr.size() && |
| CaseStr.substr(0, WordCompletionPos) == |
| Word.substr(0, WordCompletionPos)) |
| P->Completions.push_back(LineEditor::Completion( |
| (CaseStr.substr(WordCompletionPos) + " ").str(), |
| std::string(CaseStr))); |
| return *this; |
| } |
| |
| T Default(T Value) { return Switch.Default(Value); } |
| }; |
| |
| QueryRef QueryParser::parseSetBool(bool QuerySession::*Var) { |
| StringRef ValStr; |
| unsigned Value = LexOrCompleteWord<unsigned>(this, ValStr) |
| .Case("false", 0) |
| .Case("true", 1) |
| .Default(~0u); |
| if (Value == ~0u) { |
| return new InvalidQuery("expected 'true' or 'false', got '" + ValStr + "'"); |
| } |
| return new SetQuery<bool>(Var, Value); |
| } |
| |
| template <typename QueryType> QueryRef QueryParser::parseSetOutputKind() { |
| StringRef ValStr; |
| bool HasIntrospection = tooling::NodeIntrospection::hasIntrospectionSupport(); |
| unsigned OutKind = |
| LexOrCompleteWord<unsigned>(this, ValStr) |
| .Case("diag", OK_Diag) |
| .Case("print", OK_Print) |
| .Case("detailed-ast", OK_DetailedAST) |
| .Case("srcloc", OK_SrcLoc, /*IsCompletion=*/HasIntrospection) |
| .Case("dump", OK_DetailedAST) |
| .Default(~0u); |
| if (OutKind == ~0u) { |
| return new InvalidQuery("expected 'diag', 'print', 'detailed-ast'" + |
| StringRef(HasIntrospection ? ", 'srcloc'" : "") + |
| " or 'dump', got '" + ValStr + "'"); |
| } |
| |
| switch (OutKind) { |
| case OK_DetailedAST: |
| return new QueryType(&QuerySession::DetailedASTOutput); |
| case OK_Diag: |
| return new QueryType(&QuerySession::DiagOutput); |
| case OK_Print: |
| return new QueryType(&QuerySession::PrintOutput); |
| case OK_SrcLoc: |
| if (HasIntrospection) |
| return new QueryType(&QuerySession::SrcLocOutput); |
| return new InvalidQuery("'srcloc' output support is not available."); |
| } |
| |
| llvm_unreachable("Invalid output kind"); |
| } |
| |
| QueryRef QueryParser::parseSetTraversalKind(TraversalKind QuerySession::*Var) { |
| StringRef ValStr; |
| unsigned Value = |
| LexOrCompleteWord<unsigned>(this, ValStr) |
| .Case("AsIs", TK_AsIs) |
| .Case("IgnoreUnlessSpelledInSource", TK_IgnoreUnlessSpelledInSource) |
| .Default(~0u); |
| if (Value == ~0u) { |
| return new InvalidQuery("expected traversal kind, got '" + ValStr + "'"); |
| } |
| return new SetQuery<TraversalKind>(Var, static_cast<TraversalKind>(Value)); |
| } |
| |
| QueryRef QueryParser::endQuery(QueryRef Q) { |
| StringRef Extra = Line; |
| StringRef ExtraTrimmed = Extra.drop_while( |
| [](char c) { return StringRef(" \t\v\f\r").contains(c); }); |
| |
| if ((!ExtraTrimmed.empty() && ExtraTrimmed[0] == '\n') || |
| (ExtraTrimmed.size() >= 2 && ExtraTrimmed[0] == '\r' && |
| ExtraTrimmed[1] == '\n')) |
| Q->RemainingContent = Extra; |
| else { |
| StringRef TrailingWord = lexWord(); |
| if (!TrailingWord.empty() && TrailingWord.front() == '#') { |
| Line = Line.drop_until([](char c) { return c == '\n'; }); |
| Line = Line.drop_while([](char c) { return c == '\n'; }); |
| return endQuery(Q); |
| } |
| if (!TrailingWord.empty()) { |
| return new InvalidQuery("unexpected extra input: '" + Extra + "'"); |
| } |
| } |
| return Q; |
| } |
| |
| namespace { |
| |
| enum ParsedQueryKind { |
| PQK_Invalid, |
| PQK_Comment, |
| PQK_NoOp, |
| PQK_Help, |
| PQK_Let, |
| PQK_Match, |
| PQK_Set, |
| PQK_Unlet, |
| PQK_Quit, |
| PQK_Enable, |
| PQK_Disable |
| }; |
| |
| enum ParsedQueryVariable { |
| PQV_Invalid, |
| PQV_Output, |
| PQV_BindRoot, |
| PQV_PrintMatcher, |
| PQV_Traversal |
| }; |
| |
| QueryRef makeInvalidQueryFromDiagnostics(const Diagnostics &Diag) { |
| std::string ErrStr; |
| llvm::raw_string_ostream OS(ErrStr); |
| Diag.printToStreamFull(OS); |
| return new InvalidQuery(OS.str()); |
| } |
| |
| } // namespace |
| |
| QueryRef QueryParser::completeMatcherExpression() { |
| std::vector<MatcherCompletion> Comps = Parser::completeExpression( |
| Line, CompletionPos - Line.begin(), nullptr, &QS.NamedValues); |
| for (auto I = Comps.begin(), E = Comps.end(); I != E; ++I) { |
| Completions.push_back(LineEditor::Completion(I->TypedText, I->MatcherDecl)); |
| } |
| return QueryRef(); |
| } |
| |
| QueryRef QueryParser::doParse() { |
| StringRef CommandStr; |
| ParsedQueryKind QKind = LexOrCompleteWord<ParsedQueryKind>(this, CommandStr) |
| .Case("", PQK_NoOp) |
| .Case("#", PQK_Comment, /*IsCompletion=*/false) |
| .Case("help", PQK_Help) |
| .Case("l", PQK_Let, /*IsCompletion=*/false) |
| .Case("let", PQK_Let) |
| .Case("m", PQK_Match, /*IsCompletion=*/false) |
| .Case("match", PQK_Match) |
| .Case("q", PQK_Quit, /*IsCompletion=*/false) |
| .Case("quit", PQK_Quit) |
| .Case("set", PQK_Set) |
| .Case("enable", PQK_Enable) |
| .Case("disable", PQK_Disable) |
| .Case("unlet", PQK_Unlet) |
| .Default(PQK_Invalid); |
| |
| switch (QKind) { |
| case PQK_Comment: |
| case PQK_NoOp: |
| Line = Line.drop_until([](char c) { return c == '\n'; }); |
| Line = Line.drop_while([](char c) { return c == '\n'; }); |
| if (Line.empty()) |
| return new NoOpQuery; |
| return doParse(); |
| |
| case PQK_Help: |
| return endQuery(new HelpQuery); |
| |
| case PQK_Quit: |
| return endQuery(new QuitQuery); |
| |
| case PQK_Let: { |
| StringRef Name = lexWord(); |
| |
| if (Name.empty()) |
| return new InvalidQuery("expected variable name"); |
| |
| if (CompletionPos) |
| return completeMatcherExpression(); |
| |
| Diagnostics Diag; |
| ast_matchers::dynamic::VariantValue Value; |
| if (!Parser::parseExpression(Line, nullptr, &QS.NamedValues, &Value, |
| &Diag)) { |
| return makeInvalidQueryFromDiagnostics(Diag); |
| } |
| |
| auto *Q = new LetQuery(Name, Value); |
| Q->RemainingContent = Line; |
| return Q; |
| } |
| |
| case PQK_Match: { |
| if (CompletionPos) |
| return completeMatcherExpression(); |
| |
| Diagnostics Diag; |
| auto MatcherSource = Line.ltrim(); |
| auto OrigMatcherSource = MatcherSource; |
| Optional<DynTypedMatcher> Matcher = Parser::parseMatcherExpression( |
| MatcherSource, nullptr, &QS.NamedValues, &Diag); |
| if (!Matcher) { |
| return makeInvalidQueryFromDiagnostics(Diag); |
| } |
| auto ActualSource = OrigMatcherSource.slice(0, OrigMatcherSource.size() - |
| MatcherSource.size()); |
| auto *Q = new MatchQuery(ActualSource, *Matcher); |
| Q->RemainingContent = MatcherSource; |
| return Q; |
| } |
| |
| case PQK_Set: { |
| StringRef VarStr; |
| ParsedQueryVariable Var = |
| LexOrCompleteWord<ParsedQueryVariable>(this, VarStr) |
| .Case("output", PQV_Output) |
| .Case("bind-root", PQV_BindRoot) |
| .Case("print-matcher", PQV_PrintMatcher) |
| .Case("traversal", PQV_Traversal) |
| .Default(PQV_Invalid); |
| if (VarStr.empty()) |
| return new InvalidQuery("expected variable name"); |
| if (Var == PQV_Invalid) |
| return new InvalidQuery("unknown variable: '" + VarStr + "'"); |
| |
| QueryRef Q; |
| switch (Var) { |
| case PQV_Output: |
| Q = parseSetOutputKind<SetExclusiveOutputQuery>(); |
| break; |
| case PQV_BindRoot: |
| Q = parseSetBool(&QuerySession::BindRoot); |
| break; |
| case PQV_PrintMatcher: |
| Q = parseSetBool(&QuerySession::PrintMatcher); |
| break; |
| case PQV_Traversal: |
| Q = parseSetTraversalKind(&QuerySession::TK); |
| break; |
| case PQV_Invalid: |
| llvm_unreachable("Invalid query kind"); |
| } |
| |
| return endQuery(Q); |
| } |
| case PQK_Enable: |
| case PQK_Disable: { |
| StringRef VarStr; |
| ParsedQueryVariable Var = |
| LexOrCompleteWord<ParsedQueryVariable>(this, VarStr) |
| .Case("output", PQV_Output) |
| .Default(PQV_Invalid); |
| if (VarStr.empty()) |
| return new InvalidQuery("expected variable name"); |
| if (Var == PQV_Invalid) |
| return new InvalidQuery("unknown variable: '" + VarStr + "'"); |
| |
| QueryRef Q; |
| |
| if (QKind == PQK_Enable) |
| Q = parseSetOutputKind<EnableOutputQuery>(); |
| else if (QKind == PQK_Disable) |
| Q = parseSetOutputKind<DisableOutputQuery>(); |
| else |
| llvm_unreachable("Invalid query kind"); |
| return endQuery(Q); |
| } |
| |
| case PQK_Unlet: { |
| StringRef Name = lexWord(); |
| |
| if (Name.empty()) |
| return new InvalidQuery("expected variable name"); |
| |
| return endQuery(new LetQuery(Name, VariantValue())); |
| } |
| |
| case PQK_Invalid: |
| return new InvalidQuery("unknown command: " + CommandStr); |
| } |
| |
| llvm_unreachable("Invalid query kind"); |
| } |
| |
| QueryRef QueryParser::parse(StringRef Line, const QuerySession &QS) { |
| return QueryParser(Line, QS).doParse(); |
| } |
| |
| std::vector<LineEditor::Completion> |
| QueryParser::complete(StringRef Line, size_t Pos, const QuerySession &QS) { |
| QueryParser P(Line, QS); |
| P.CompletionPos = Line.data() + Pos; |
| |
| P.doParse(); |
| return P.Completions; |
| } |
| |
| } // namespace query |
| } // namespace clang |