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.starts_with(Prefix: '\n') || extraTrimmed.starts_with(Prefix: "\r\n" )) |
95 | queryRef->remainingContent = extra; |
96 | else { |
97 | llvm::StringRef trailingWord = lexWord(); |
98 | if (trailingWord.starts_with(Prefix: '#')) { |
99 | line = line.drop_until(F: [](char c) { return c == '\n'; }); |
100 | line = line.drop_while(F: [](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 | , |
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 | code&: line, completionOffset: completionPos - line.begin(), matcherRegistry: qs.getRegistryData(), |
134 | namedValues: &qs.namedValues); |
135 | for (const auto &comp : comps) { |
136 | completions.emplace_back(args: comp.typedText, args: 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(caseStr: "" , value: ParsedQueryKind::NoOp) |
147 | .Case(caseStr: "#" , value: ParsedQueryKind::Comment, /*isCompletion=*/false) |
148 | .Case(caseStr: "help" , value: ParsedQueryKind::Help) |
149 | .Case(caseStr: "m" , value: ParsedQueryKind::Match, /*isCompletion=*/false) |
150 | .Case(caseStr: "match" , value: ParsedQueryKind::Match) |
151 | .Case(caseStr: "q" , value: ParsedQueryKind::Quit, /*IsCompletion=*/isCompletion: false) |
152 | .Case(caseStr: "quit" , value: ParsedQueryKind::Quit) |
153 | .Default(value: ParsedQueryKind::Invalid); |
154 | |
155 | switch (qKind) { |
156 | case ParsedQueryKind::Comment: |
157 | case ParsedQueryKind::NoOp: |
158 | line = line.drop_until(F: [](char c) { return c == '\n'; }); |
159 | line = line.drop_while(F: [](char c) { return c == '\n'; }); |
160 | if (line.empty()) |
161 | return new NoOpQuery; |
162 | return doParse(); |
163 | |
164 | case ParsedQueryKind::Help: |
165 | return endQuery(queryRef: new HelpQuery); |
166 | |
167 | case ParsedQueryKind::Quit: |
168 | return endQuery(queryRef: 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 | matcherCode&: matcherSource, matcherRegistry: qs.getRegistryData(), namedValues: &qs.namedValues, error: &diag); |
181 | if (!matcher) { |
182 | return makeInvalidQueryFromDiagnostics(diag); |
183 | } |
184 | auto actualSource = origMatcherSource.substr(Start: 0, N: 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 | |