xref: /llvm-project/mlir/lib/Query/QueryParser.cpp (revision 5287a9b3456fe7aefa24c8da95ef265b8dba875b)
1 //===---- QueryParser.cpp - mlir-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 "llvm/ADT/StringSwitch.h"
11 
12 namespace mlir::query {
13 
14 // Lex any amount of whitespace followed by a "word" (any sequence of
15 // non-whitespace characters) from the start of region [begin,end).  If no word
16 // is found before end, return StringRef(). begin is adjusted to exclude the
17 // lexed region.
18 llvm::StringRef QueryParser::lexWord() {
19   // Don't trim newlines.
20   line = line.ltrim(" \t\v\f\r");
21 
22   if (line.empty())
23     // Even though the line is empty, it contains a pointer and
24     // a (zero) length. The pointer is used in the LexOrCompleteWord
25     // code completion.
26     return line;
27 
28   llvm::StringRef word;
29   if (line.front() == '#') {
30     word = line.substr(0, 1);
31   } else {
32     word = line.take_until([](char c) {
33       // Don't trim newlines.
34       return llvm::StringRef(" \t\v\f\r").contains(c);
35     });
36   }
37 
38   line = line.drop_front(word.size());
39   return word;
40 }
41 
42 // This is the StringSwitch-alike used by LexOrCompleteWord below. See that
43 // function for details.
44 template <typename T>
45 struct QueryParser::LexOrCompleteWord {
46   llvm::StringRef word;
47   llvm::StringSwitch<T> stringSwitch;
48 
49   QueryParser *queryParser;
50   // Set to the completion point offset in word, or StringRef::npos if
51   // completion point not in word.
52   size_t wordCompletionPos;
53 
54   // Lexes a word and stores it in word. Returns a LexOrCompleteword<T> object
55   // that can be used like a llvm::StringSwitch<T>, but adds cases as possible
56   // completions if the lexed word contains the completion point.
57   LexOrCompleteWord(QueryParser *queryParser, llvm::StringRef &outWord)
58       : word(queryParser->lexWord()), stringSwitch(word),
59         queryParser(queryParser), wordCompletionPos(llvm::StringRef::npos) {
60     outWord = word;
61     if (queryParser->completionPos &&
62         queryParser->completionPos <= word.data() + word.size()) {
63       if (queryParser->completionPos < word.data())
64         wordCompletionPos = 0;
65       else
66         wordCompletionPos = queryParser->completionPos - word.data();
67     }
68   }
69 
70   LexOrCompleteWord &Case(llvm::StringLiteral caseStr, const T &value,
71                           bool isCompletion = true) {
72 
73     if (wordCompletionPos == llvm::StringRef::npos)
74       stringSwitch.Case(caseStr, value);
75     else if (!caseStr.empty() && isCompletion &&
76              wordCompletionPos <= caseStr.size() &&
77              caseStr.substr(0, wordCompletionPos) ==
78                  word.substr(0, wordCompletionPos)) {
79 
80       queryParser->completions.emplace_back(
81           (caseStr.substr(wordCompletionPos) + " ").str(),
82           std::string(caseStr));
83     }
84     return *this;
85   }
86 
87   T Default(T value) { return stringSwitch.Default(value); }
88 };
89 
90 QueryRef QueryParser::endQuery(QueryRef queryRef) {
91   llvm::StringRef extra = line;
92   llvm::StringRef extraTrimmed = extra.ltrim(" \t\v\f\r");
93 
94   if (extraTrimmed.starts_with('\n') || extraTrimmed.starts_with("\r\n"))
95     queryRef->remainingContent = extra;
96   else {
97     llvm::StringRef trailingWord = lexWord();
98     if (trailingWord.starts_with('#')) {
99       line = line.drop_until([](char c) { return c == '\n'; });
100       line = line.drop_while([](char c) { return c == '\n'; });
101       return endQuery(queryRef);
102     }
103     if (!trailingWord.empty()) {
104       return new InvalidQuery("unexpected extra input: '" + extra + "'");
105     }
106   }
107   return queryRef;
108 }
109 
110 namespace {
111 
112 enum class ParsedQueryKind {
113   Invalid,
114   Comment,
115   NoOp,
116   Help,
117   Match,
118   Quit,
119 };
120 
121 QueryRef
122 makeInvalidQueryFromDiagnostics(const matcher::internal::Diagnostics &diag) {
123   std::string errStr;
124   llvm::raw_string_ostream os(errStr);
125   diag.print(os);
126   return new InvalidQuery(errStr);
127 }
128 } // namespace
129 
130 QueryRef QueryParser::completeMatcherExpression() {
131   std::vector<matcher::MatcherCompletion> comps =
132       matcher::internal::Parser::completeExpression(
133           line, completionPos - line.begin(), qs.getRegistryData(),
134           &qs.namedValues);
135   for (const auto &comp : comps) {
136     completions.emplace_back(comp.typedText, comp.matcherDecl);
137   }
138   return QueryRef();
139 }
140 
141 QueryRef QueryParser::doParse() {
142 
143   llvm::StringRef commandStr;
144   ParsedQueryKind qKind =
145       LexOrCompleteWord<ParsedQueryKind>(this, commandStr)
146           .Case("", ParsedQueryKind::NoOp)
147           .Case("#", ParsedQueryKind::Comment, /*isCompletion=*/false)
148           .Case("help", ParsedQueryKind::Help)
149           .Case("m", ParsedQueryKind::Match, /*isCompletion=*/false)
150           .Case("match", ParsedQueryKind::Match)
151           .Case("q", ParsedQueryKind::Quit, /*IsCompletion=*/false)
152           .Case("quit", ParsedQueryKind::Quit)
153           .Default(ParsedQueryKind::Invalid);
154 
155   switch (qKind) {
156   case ParsedQueryKind::Comment:
157   case ParsedQueryKind::NoOp:
158     line = line.drop_until([](char c) { return c == '\n'; });
159     line = line.drop_while([](char c) { return c == '\n'; });
160     if (line.empty())
161       return new NoOpQuery;
162     return doParse();
163 
164   case ParsedQueryKind::Help:
165     return endQuery(new HelpQuery);
166 
167   case ParsedQueryKind::Quit:
168     return endQuery(new QuitQuery);
169 
170   case ParsedQueryKind::Match: {
171     if (completionPos) {
172       return completeMatcherExpression();
173     }
174 
175     matcher::internal::Diagnostics diag;
176     auto matcherSource = line.ltrim();
177     auto origMatcherSource = matcherSource;
178     std::optional<matcher::DynMatcher> matcher =
179         matcher::internal::Parser::parseMatcherExpression(
180             matcherSource, qs.getRegistryData(), &qs.namedValues, &diag);
181     if (!matcher) {
182       return makeInvalidQueryFromDiagnostics(diag);
183     }
184     auto actualSource = origMatcherSource.substr(0, origMatcherSource.size() -
185                                                         matcherSource.size());
186     QueryRef query = new MatchQuery(actualSource, *matcher);
187     query->remainingContent = matcherSource;
188     return query;
189   }
190 
191   case ParsedQueryKind::Invalid:
192     return new InvalidQuery("unknown command: " + commandStr);
193   }
194 
195   llvm_unreachable("Invalid query kind");
196 }
197 
198 QueryRef QueryParser::parse(llvm::StringRef line, const QuerySession &qs) {
199   return QueryParser(line, qs).doParse();
200 }
201 
202 std::vector<llvm::LineEditor::Completion>
203 QueryParser::complete(llvm::StringRef line, size_t pos,
204                       const QuerySession &qs) {
205   QueryParser queryParser(line, qs);
206   queryParser.completionPos = line.data() + pos;
207 
208   queryParser.doParse();
209   return queryParser.completions;
210 }
211 
212 } // namespace mlir::query
213