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(Chars: " \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(Start: 0, N: 1); |
31 | } else { |
32 | word = line.take_until(F: [](char c) { |
33 | // Don't trim newlines. |
34 | return llvm::StringRef(" \t\v\f\r" ).contains(C: c); |
35 | }); |
36 | } |
37 | |
38 | line = line.drop_front(N: 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(Start: 0, N: wordCompletionPos) == |
78 | word.substr(Start: 0, N: wordCompletionPos)) { |
79 | |
80 | queryParser->completions.emplace_back( |
81 | args: (caseStr.substr(Start: wordCompletionPos) + " " ).str(), |
82 | args: 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 = line; |
92 | llvm::StringRef = extra.ltrim(Chars: " \t\v\f\r" ); |
93 | |
94 | if ((!extraTrimmed.empty() && extraTrimmed[0] == '\n') || |
95 | (extraTrimmed.size() >= 2 && extraTrimmed[0] == '\r' && |
96 | extraTrimmed[1] == '\n')) |
97 | queryRef->remainingContent = extra; |
98 | else { |
99 | llvm::StringRef trailingWord = lexWord(); |
100 | if (!trailingWord.empty() && trailingWord.front() == '#') { |
101 | line = line.drop_until(F: [](char c) { return c == '\n'; }); |
102 | line = line.drop_while(F: [](char c) { return c == '\n'; }); |
103 | return endQuery(queryRef); |
104 | } |
105 | if (!trailingWord.empty()) { |
106 | return new InvalidQuery("unexpected extra input: '" + extra + "'" ); |
107 | } |
108 | } |
109 | return queryRef; |
110 | } |
111 | |
112 | namespace { |
113 | |
114 | enum class ParsedQueryKind { |
115 | Invalid, |
116 | , |
117 | NoOp, |
118 | Help, |
119 | Match, |
120 | Quit, |
121 | }; |
122 | |
123 | QueryRef |
124 | makeInvalidQueryFromDiagnostics(const matcher::internal::Diagnostics &diag) { |
125 | std::string errStr; |
126 | llvm::raw_string_ostream os(errStr); |
127 | diag.print(os); |
128 | return new InvalidQuery(os.str()); |
129 | } |
130 | } // namespace |
131 | |
132 | QueryRef QueryParser::completeMatcherExpression() { |
133 | std::vector<matcher::MatcherCompletion> comps = |
134 | matcher::internal::Parser::completeExpression( |
135 | code&: line, completionOffset: completionPos - line.begin(), matcherRegistry: qs.getRegistryData(), |
136 | namedValues: &qs.namedValues); |
137 | for (const auto &comp : comps) { |
138 | completions.emplace_back(args: comp.typedText, args: comp.matcherDecl); |
139 | } |
140 | return QueryRef(); |
141 | } |
142 | |
143 | QueryRef QueryParser::doParse() { |
144 | |
145 | llvm::StringRef commandStr; |
146 | ParsedQueryKind qKind = |
147 | LexOrCompleteWord<ParsedQueryKind>(this, commandStr) |
148 | .Case(caseStr: "" , value: ParsedQueryKind::NoOp) |
149 | .Case(caseStr: "#" , value: ParsedQueryKind::Comment, /*isCompletion=*/false) |
150 | .Case(caseStr: "help" , value: ParsedQueryKind::Help) |
151 | .Case(caseStr: "m" , value: ParsedQueryKind::Match, /*isCompletion=*/false) |
152 | .Case(caseStr: "match" , value: ParsedQueryKind::Match) |
153 | .Case(caseStr: "q" , value: ParsedQueryKind::Quit, /*IsCompletion=*/isCompletion: false) |
154 | .Case(caseStr: "quit" , value: ParsedQueryKind::Quit) |
155 | .Default(value: ParsedQueryKind::Invalid); |
156 | |
157 | switch (qKind) { |
158 | case ParsedQueryKind::Comment: |
159 | case ParsedQueryKind::NoOp: |
160 | line = line.drop_until(F: [](char c) { return c == '\n'; }); |
161 | line = line.drop_while(F: [](char c) { return c == '\n'; }); |
162 | if (line.empty()) |
163 | return new NoOpQuery; |
164 | return doParse(); |
165 | |
166 | case ParsedQueryKind::Help: |
167 | return endQuery(queryRef: new HelpQuery); |
168 | |
169 | case ParsedQueryKind::Quit: |
170 | return endQuery(queryRef: new QuitQuery); |
171 | |
172 | case ParsedQueryKind::Match: { |
173 | if (completionPos) { |
174 | return completeMatcherExpression(); |
175 | } |
176 | |
177 | matcher::internal::Diagnostics diag; |
178 | auto matcherSource = line.ltrim(); |
179 | auto origMatcherSource = matcherSource; |
180 | std::optional<matcher::DynMatcher> matcher = |
181 | matcher::internal::Parser::parseMatcherExpression( |
182 | matcherCode&: matcherSource, matcherRegistry: qs.getRegistryData(), namedValues: &qs.namedValues, error: &diag); |
183 | if (!matcher) { |
184 | return makeInvalidQueryFromDiagnostics(diag); |
185 | } |
186 | auto actualSource = origMatcherSource.slice(Start: 0, End: origMatcherSource.size() - |
187 | matcherSource.size()); |
188 | QueryRef query = new MatchQuery(actualSource, *matcher); |
189 | query->remainingContent = matcherSource; |
190 | return query; |
191 | } |
192 | |
193 | case ParsedQueryKind::Invalid: |
194 | return new InvalidQuery("unknown command: " + commandStr); |
195 | } |
196 | |
197 | llvm_unreachable("Invalid query kind" ); |
198 | } |
199 | |
200 | QueryRef QueryParser::parse(llvm::StringRef line, const QuerySession &qs) { |
201 | return QueryParser(line, qs).doParse(); |
202 | } |
203 | |
204 | std::vector<llvm::LineEditor::Completion> |
205 | QueryParser::complete(llvm::StringRef line, size_t pos, |
206 | const QuerySession &qs) { |
207 | QueryParser queryParser(line, qs); |
208 | queryParser.completionPos = line.data() + pos; |
209 | |
210 | queryParser.doParse(); |
211 | return queryParser.completions; |
212 | } |
213 | |
214 | } // namespace mlir::query |
215 | |