MLIR 24.0.0git
Lexer.cpp
Go to the documentation of this file.
1//===- Lexer.cpp - MLIR Lexer Implementation ------------------------------===//
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// This file implements the lexer for the MLIR textual form.
10//
11//===----------------------------------------------------------------------===//
12
13#include "Lexer.h"
14#include "Token.h"
16#include "mlir/IR/Diagnostics.h"
17#include "mlir/IR/Location.h"
18#include "mlir/IR/MLIRContext.h"
19#include "mlir/Support/LLVM.h"
20#include "llvm/ADT/STLExtras.h"
21#include "llvm/ADT/StringExtras.h"
22#include "llvm/ADT/StringSwitch.h"
23#include "llvm/Support/ErrorHandling.h"
24#include "llvm/Support/SourceMgr.h"
25#include <cassert>
26#include <cctype>
27
28using namespace mlir;
29
30// Returns true if 'c' is an allowable punctuation character: [$._-]
31// Returns false otherwise.
32static bool isPunct(char c) {
33 return c == '$' || c == '.' || c == '_' || c == '-';
34}
35
36Lexer::Lexer(const llvm::SourceMgr &sourceMgr, MLIRContext *context,
37 AsmParserCodeCompleteContext *codeCompleteContext)
38 : sourceMgr(sourceMgr), context(context), codeCompleteLoc(nullptr) {
39 auto bufferID = sourceMgr.getMainFileID();
40
41 // Check to see if the main buffer contains the last buffer, and if so the
42 // last buffer should be used as main file for parsing.
43 if (sourceMgr.getNumBuffers() > 1) {
44 unsigned lastFileID = sourceMgr.getNumBuffers();
45 const llvm::MemoryBuffer *main = sourceMgr.getMemoryBuffer(bufferID);
46 const llvm::MemoryBuffer *last = sourceMgr.getMemoryBuffer(lastFileID);
47 if (main->getBufferStart() <= last->getBufferStart() &&
48 main->getBufferEnd() >= last->getBufferEnd()) {
49 bufferID = lastFileID;
50 }
51 }
52 curBuffer = sourceMgr.getMemoryBuffer(bufferID)->getBuffer();
53 curPtr = curBuffer.begin();
54
55 // Set the code completion location if it was provided.
56 if (codeCompleteContext)
57 codeCompleteLoc = codeCompleteContext->getCodeCompleteLoc().getPointer();
58}
59
60/// Encode the specified source location information into an attribute for
61/// attachment to the IR.
63 auto &sourceMgr = getSourceMgr();
64 unsigned mainFileID = sourceMgr.getMainFileID();
65
66 auto [lineNo, column] = sourceMgr.getLineAndColumn(loc);
67 auto *buffer = sourceMgr.getMemoryBuffer(mainFileID);
68
69 return FileLineColLoc::get(context, buffer->getBufferIdentifier(), lineNo,
70 column);
71}
72
73/// emitError - Emit an error message and return an Token::error token.
74Token Lexer::emitError(const char *loc, const Twine &message) {
75 mlir::emitError(getEncodedSourceLocation(SMLoc::getFromPointer(loc)),
76 message);
77 return formToken(Token::error, loc);
78}
79
81 const char *curBufferEnd = curBuffer.end();
82 while (true) {
83 const char *tokStart = curPtr;
84
85 // Check to see if the current token is at the code completion location.
86 if (tokStart == codeCompleteLoc)
87 return formToken(Token::code_complete, tokStart);
88
89 if (tokStart == curBufferEnd)
90 return formToken(Token::eof, tokStart);
91
92 // Lex the next token.
93 switch (*curPtr++) {
94 default:
95 // Handle bare identifiers.
96 if (isalpha(curPtr[-1]))
97 return lexBareIdentifierOrKeyword(tokStart);
98
99 // Unknown character, emit an error.
100 return emitError(tokStart, "unexpected character");
101
102 case ' ':
103 case '\t':
104 case '\n':
105 case '\r':
106 // Handle whitespace.
107 continue;
108
109 case '_':
110 // Handle bare identifiers.
111 return lexBareIdentifierOrKeyword(tokStart);
112
113 case 0:
114 // A nul character in the source is skipped like whitespace.
115 continue;
116
117 case ':':
118 return formToken(Token::colon, tokStart);
119 case ',':
120 return formToken(Token::comma, tokStart);
121 case '.':
122 return lexEllipsis(tokStart);
123 case '(':
124 return formToken(Token::l_paren, tokStart);
125 case ')':
126 return formToken(Token::r_paren, tokStart);
127 case '{':
128 if (peek() == '-' && peek(1) == '#') {
129 curPtr += 2;
130 return formToken(Token::file_metadata_begin, tokStart);
131 }
132 return formToken(Token::l_brace, tokStart);
133 case '}':
134 return formToken(Token::r_brace, tokStart);
135 case '[':
136 return formToken(Token::l_square, tokStart);
137 case ']':
138 return formToken(Token::r_square, tokStart);
139 case '<':
140 return formToken(Token::less, tokStart);
141 case '>':
142 return formToken(Token::greater, tokStart);
143 case '=':
144 return formToken(Token::equal, tokStart);
145
146 case '+':
147 return formToken(Token::plus, tokStart);
148 case '*':
149 return formToken(Token::star, tokStart);
150 case '-':
151 if (peek() == '>') {
152 ++curPtr;
153 return formToken(Token::arrow, tokStart);
154 }
155 return formToken(Token::minus, tokStart);
156
157 case '?':
158 return formToken(Token::question, tokStart);
159
160 case '|':
161 return formToken(Token::vertical_bar, tokStart);
162
163 case '/':
164 if (peek() == '/') {
165 skipComment();
166 continue;
167 }
168 return formToken(Token::slash, tokStart);
169
170 case '@':
171 return lexAtIdentifier(tokStart);
172
173 case '#':
174 if (peek() == '-' && peek(1) == '}') {
175 curPtr += 2;
176 return formToken(Token::file_metadata_end, tokStart);
177 }
178 [[fallthrough]];
179 case '!':
180 case '^':
181 case '%':
182 return lexPrefixedIdentifier(tokStart);
183 case '"':
184 return lexString(tokStart);
185
186 case '0':
187 case '1':
188 case '2':
189 case '3':
190 case '4':
191 case '5':
192 case '6':
193 case '7':
194 case '8':
195 case '9':
196 return lexNumber(tokStart);
197 }
198 }
199}
200
201/// Lex an '@foo' identifier.
202///
203/// symbol-ref-id ::= `@` (bare-id | string-literal)
204///
205Token Lexer::lexAtIdentifier(const char *tokStart) {
206 char cur = peek();
207
208 // Try to parse a string literal, if present.
209 if (cur == '"') {
210 ++curPtr;
211 Token stringIdentifier = lexString(curPtr);
212 if (stringIdentifier.is(Token::error))
213 return stringIdentifier;
214 return formToken(Token::at_identifier, tokStart);
215 }
216
217 // Otherwise, these always start with a letter or underscore.
218 if (!isalpha(cur) && cur != '_')
219 return emitError(curPtr,
220 "@ identifier expected to start with letter or '_'");
221
222 ++curPtr;
223 while (isalpha(peek()) || isdigit(peek()) || peek() == '_' || peek() == '$' ||
224 peek() == '.')
225 ++curPtr;
226 return formToken(Token::at_identifier, tokStart);
227}
228
229/// Lex a bare identifier or keyword that starts with a letter.
230///
231/// bare-id ::= (letter|[_]) (letter|digit|[_$.])*
232/// integer-type ::= `[su]?i[1-9][0-9]*`
233///
234Token Lexer::lexBareIdentifierOrKeyword(const char *tokStart) {
235 // Match the rest of the identifier regex: [0-9a-zA-Z_.$]*
236 while (isalpha(peek()) || isdigit(peek()) || peek() == '_' || peek() == '$' ||
237 peek() == '.')
238 ++curPtr;
239
240 // Check to see if this identifier is a keyword.
241 StringRef spelling(tokStart, curPtr - tokStart);
242
243 auto isAllDigit = [](StringRef str) {
244 return llvm::all_of(str, llvm::isDigit);
245 };
246
247 // Check for i123, si456, ui789.
248 if ((spelling.size() > 1 && tokStart[0] == 'i' &&
249 isAllDigit(spelling.drop_front())) ||
250 ((spelling.size() > 2 && tokStart[1] == 'i' &&
251 (tokStart[0] == 's' || tokStart[0] == 'u')) &&
252 isAllDigit(spelling.drop_front(2))))
253 return Token(Token::inttype, spelling);
254
256#define TOK_KEYWORD(SPELLING) .Case(#SPELLING, Token::kw_##SPELLING)
257#include "TokenKinds.def"
258 .Default(Token::bare_identifier);
259
260 return Token(kind, spelling);
261}
262
263/// Skip a comment line, starting with a '//'.
264///
265/// TODO: add a regex for comments here and to the spec.
266///
267void Lexer::skipComment() {
268 // Advance over the second '/' in a '//' comment.
269 assert(peek() == '/');
270 ++curPtr;
271
272 const char *curBufferEnd = curBuffer.end();
273 while (true) {
274 if (curPtr == curBufferEnd)
275 return;
276
277 switch (*curPtr++) {
278 case '\n':
279 case '\r':
280 // Newline is end of comment.
281 return;
282 default:
283 // Skip over other characters.
284 break;
285 }
286 }
287}
288
289/// Lex an ellipsis.
290///
291/// ellipsis ::= '...'
292///
293Token Lexer::lexEllipsis(const char *tokStart) {
294 assert(curPtr[-1] == '.');
295
296 if (peek() != '.' || peek(1) != '.')
297 return emitError(curPtr, "expected three consecutive dots for an ellipsis");
298
299 curPtr += 2;
300 return formToken(Token::ellipsis, tokStart);
301}
302
303/// Lex a number literal.
304///
305/// integer-literal ::= digit+ | `0x` hex_digit+
306/// float-literal ::= [-+]?[0-9]+[.][0-9]*([eE][-+]?[0-9]+)?
307///
308Token Lexer::lexNumber(const char *tokStart) {
309 assert(isdigit(curPtr[-1]));
310
311 // Handle the hexadecimal case.
312 if (curPtr[-1] == '0' && peek() == 'x') {
313 // If we see stuff like 0xi32, this is a literal `0` followed by an
314 // identifier `xi32`, stop after `0`.
315 if (!isxdigit(peek(1)))
316 return formToken(Token::integer, tokStart);
317
318 curPtr += 2;
319 while (isxdigit(peek()))
320 ++curPtr;
321
322 return formToken(Token::integer, tokStart);
323 }
324
325 // Handle the normal decimal case.
326 while (isdigit(peek()))
327 ++curPtr;
328
329 if (peek() != '.')
330 return formToken(Token::integer, tokStart);
331 ++curPtr;
332
333 // Skip over [0-9]*([eE][-+]?[0-9]+)?
334 while (isdigit(peek()))
335 ++curPtr;
336
337 if (peek() == 'e' || peek() == 'E') {
338 if (isdigit(static_cast<unsigned char>(peek(1))) ||
339 ((peek(1) == '-' || peek(1) == '+') &&
340 isdigit(static_cast<unsigned char>(peek(2))))) {
341 curPtr += 2;
342 while (isdigit(peek()))
343 ++curPtr;
344 }
345 }
346 return formToken(Token::floatliteral, tokStart);
347}
348
349/// Lex an identifier that starts with a prefix followed by suffix-id.
350///
351/// attribute-id ::= `#` suffix-id
352/// ssa-id ::= '%' suffix-id
353/// block-id ::= '^' suffix-id
354/// type-id ::= '!' suffix-id
355/// suffix-id ::= digit+ | (letter|id-punct) (letter|id-punct|digit)*
356/// id-punct ::= `$` | `.` | `_` | `-`
357///
358Token Lexer::lexPrefixedIdentifier(const char *tokStart) {
359 Token::Kind kind;
360 StringRef errorKind;
361 switch (*tokStart) {
362 case '#':
363 kind = Token::hash_identifier;
364 errorKind = "invalid attribute name";
365 break;
366 case '%':
367 kind = Token::percent_identifier;
368 errorKind = "invalid SSA name";
369 break;
370 case '^':
371 kind = Token::caret_identifier;
372 errorKind = "invalid block name";
373 break;
374 case '!':
375 kind = Token::exclamation_identifier;
376 errorKind = "invalid type identifier";
377 break;
378 default:
379 llvm_unreachable("invalid caller");
380 }
381
382 // Parse suffix-id.
383 if (isdigit(peek())) {
384 // If suffix-id starts with a digit, the rest must be digits.
385 while (isdigit(peek()))
386 ++curPtr;
387 } else if (isalpha(peek()) || isPunct(peek())) {
388 do {
389 ++curPtr;
390 } while (isalpha(peek()) || isdigit(peek()) || isPunct(peek()));
391 } else if (curPtr == codeCompleteLoc) {
392 return formToken(Token::code_complete, tokStart);
393 } else {
394 return emitError(curPtr - 1, errorKind);
395 }
396
397 // Check for a code completion within the identifier.
398 if (codeCompleteLoc && codeCompleteLoc >= tokStart &&
399 codeCompleteLoc <= curPtr) {
400 return Token(Token::code_complete,
401 StringRef(tokStart, codeCompleteLoc - tokStart));
402 }
403
404 return formToken(kind, tokStart);
405}
406
407/// Lex a string literal.
408///
409/// string-literal ::= '"' [^"\n\f\v\r]* '"'
410///
411/// TODO: define escaping rules.
412Token Lexer::lexString(const char *tokStart) {
413 assert(curPtr[-1] == '"');
414
415 const char *curBufferEnd = curBuffer.end();
416 while (true) {
417 // Check to see if there is a code completion location within the string. In
418 // these cases we generate a completion location and place the currently
419 // lexed string within the token. This allows for the parser to use the
420 // partially lexed string when computing the completion results.
421 if (curPtr == codeCompleteLoc)
422 return formToken(Token::code_complete, tokStart);
423
424 if (curPtr == curBufferEnd)
425 return emitError(curPtr, "expected '\"' in string literal");
426
427 // A nul character in the string is included like any other character.
428 switch (*curPtr++) {
429 case '"':
430 return formToken(Token::string, tokStart);
431 case '\n':
432 case '\v':
433 case '\f':
434 return emitError(curPtr - 1, "expected '\"' in string literal");
435 case '\\':
436 // Handle explicitly a few escapes.
437 if (peek() == '"' || peek() == '\\' || peek() == 'n' || peek() == 't')
438 ++curPtr;
439 else if (llvm::isHexDigit(peek()) && llvm::isHexDigit(peek(1)))
440 // Support \xx for two hex digits.
441 curPtr += 2;
442 else
443 return emitError(curPtr - 1, "unknown escape in string literal");
444 continue;
445
446 default:
447 continue;
448 }
449 }
450}
static bool isPunct(char c)
Definition Lexer.cpp:32
This class provides an abstract interface into the parser for hooking in code completion events.
static FileLineColLoc get(StringAttr filename, unsigned line, unsigned column)
Definition Location.cpp:161
Token lexToken()
Definition Lexer.cpp:80
Location getEncodedSourceLocation(SMLoc loc)
Encode the specified source location information into a Location object for attachment to the IR or e...
Definition Lexer.cpp:62
const llvm::SourceMgr & getSourceMgr()
Definition Lexer.h:28
Lexer(const llvm::SourceMgr &sourceMgr, MLIRContext *context, AsmParserCodeCompleteContext *codeCompleteContext)
Definition Lexer.cpp:36
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:71
This represents a token in the MLIR syntax.
Definition Token.h:20
bool is(Kind k) const
Definition Token.h:38
Include the generated interface declarations.
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
llvm::StringSwitch< T, R > StringSwitch
Definition LLVM.h:144