You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
373 lines
11 KiB
373 lines
11 KiB
//===---- 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 "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;
|
|
unsigned OutKind = LexOrCompleteWord<unsigned>(this, ValStr)
|
|
.Case("diag", OK_Diag)
|
|
.Case("print", OK_Print)
|
|
.Case("detailed-ast", OK_DetailedAST)
|
|
.Case("dump", OK_DetailedAST)
|
|
.Default(~0u);
|
|
if (OutKind == ~0u) {
|
|
return new InvalidQuery(
|
|
"expected 'diag', 'print', 'detailed-ast' 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);
|
|
}
|
|
|
|
llvm_unreachable("Invalid output kind");
|
|
}
|
|
|
|
QueryRef QueryParser::parseSetTraversalKind(
|
|
ast_type_traits::TraversalKind QuerySession::*Var) {
|
|
StringRef ValStr;
|
|
unsigned Value =
|
|
LexOrCompleteWord<unsigned>(this, ValStr)
|
|
.Case("AsIs", ast_type_traits::TK_AsIs)
|
|
.Case("IgnoreUnlessSpelledInSource",
|
|
ast_type_traits::TK_IgnoreUnlessSpelledInSource)
|
|
.Default(~0u);
|
|
if (Value == ~0u) {
|
|
return new InvalidQuery("expected traversal kind, got '" + ValStr + "'");
|
|
}
|
|
return new SetQuery<ast_type_traits::TraversalKind>(
|
|
Var, static_cast<ast_type_traits::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
|