MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
lexer.cpp
Go to the documentation of this file.
1#include "mim/ast/lexer.h"
2
3#include <charconv>
4
5#include <limits>
6
7#include "mim/def.h" // Idx::bitwidth2size is all the IR a lexer needs
8
9namespace mim::ast {
10
11namespace utf8 = fe::utf8;
12using Tag = Tok::Tag;
13
14namespace {
15
16/// As World::lit_idx_mod but at token level: a @p mod of `0` means 2^64 and wraps nothing.
17Tok idx_tok(Loc loc, u64 mod, u64 val) { return {loc, mod, mod == 0 ? val : val % mod}; }
18
19std::optional<char> unescape(char32_t c) {
20 // clang-format off
21 switch (c) {
22 case '\'': return '\'';
23 case '\\': return '\\';
24 case '"': return '"';
25 case '0': return '\0';
26 case 'a': return '\a';
27 case 'b': return '\b';
28 case 'f': return '\f';
29 case 'n': return '\n';
30 case 'r': return '\r';
31 case 't': return '\t';
32 case 'v': return '\v';
33 default: return {};
34 }
35 // clang-format on
36}
37
38bool is_id_head(char32_t c) { return c == '_' || utf8::isalpha(c); }
39bool is_id_tail(char32_t c) { return c == '_' || utf8::isalnum(c); }
40
41/// `std::nullopt` on overflow.
42std::optional<u64> parse_u64(std::string_view sv, int base) {
43 u64 res = 0;
44 auto ec = std::from_chars(sv.data(), sv.data() + sv.size(), res, base).ec;
45 if (ec == std::errc::result_out_of_range) return {};
46 return res;
47}
48
49/// Saturates on overflow, where the widest literal is what Mim means.
50u64 to_u64(std::string_view sv, int base) { return parse_u64(sv, base).value_or(std::numeric_limits<u64>::max()); }
51
52/// `std::nullopt` if @p sv is out of range.
53std::optional<f64> to_f64(std::string_view sv, int base) {
54 f64 res = 0.;
55 auto ec = std::from_chars(sv.data(), sv.data() + sv.size(), res,
56 base == 16 ? std::chars_format::hex : std::chars_format::general)
57 .ec;
58 if (ec == std::errc::result_out_of_range) return {};
59 return res;
60}
61
62} // namespace
63
64Lexer::Lexer(Driver& driver, std::string_view buf, const fe::Src* src, std::ostream* md)
65 : Super(buf, src)
66 , driver_(driver)
67 , md_(md) {
68 md_pos_ = pos();
69
70 if (start_md())
71 emit_md(true);
72 else
73 md_open();
74}
75
77 while (true) {
78 start();
79
80 if (accept(utf8::EoF)) {
81 if (fenced_) {
82 md_flush();
83 if (md_nls_ == 0) md_emit("\n"); // the buffer may end without one
84 md_close();
85 }
86 return tok(Tag::EoF);
87 }
88 if (accept(utf8::isspace)) continue;
89 if (recover_utf8()) continue;
90
91 // clang-format off
92 // delimiters
93 if (accept( '(')) return tok(Tag::D_paren_l);
94 if (accept( ')')) return tok(Tag::D_paren_r);
95 if (accept( '[')) return tok(Tag::D_brckt_l);
96 if (accept( ']')) return tok(Tag::D_brckt_r);
97 if (accept( '{')) return tok(Tag::D_brace_l);
98 if (accept( '}')) return tok(Tag::D_brace_r);
99 if (accept(U'«')) return tok(Tag::D_quote_l);
100 if (accept(U'»')) return tok(Tag::D_quote_r);
101 if (accept(U'‹')) return tok(Tag::D_angle_l);
102 if (accept(U'›')) return tok(Tag::D_angle_r);
103 // further tokens
104 if (accept( '+')) return tok(Tag::T_add);
105 if (accept( '-')) {
106 if (accept('>')) return tok(Tag::T_arrow_r);
107 return tok(Tag::T_sub);
108 }
109 if (accept(U'→')) return tok(Tag::T_arrow_r);
110 if (accept(U'←')) return tok(Tag::T_arrow_l);
111 if (accept( '@')) return tok(Tag::T_at);
112 if (accept( '=')) {
113 if (accept('>')) return tok(Tag::T_fat_arrow);
114 if (accept('=')) return tok(Tag::T_eq);
115 return tok(Tag::T_assign);
116 }
117 if (accept( '!')) {
118 if (accept('=')) return tok(Tag::T_ne);
119 error().e(loc_, "expected `=` after `!`");
120 continue;
121 }
122 if (accept( '<')) {
123 if (accept('<')) return tok(Tag::T_shl);
124 if (accept('=')) return tok(Tag::T_le);
125 if (accept('-')) return tok(Tag::T_arrow_l);
126 return tok(Tag::T_lt);
127 }
128 if (accept( '>')) {
129 if (accept('>')) return tok(Tag::T_shr);
130 if (accept('=')) return tok(Tag::T_ge);
131 return tok(Tag::T_gt);
132 }
133 if (accept(U'⊥')) return tok(Tag::T_bot);
134 if (accept(U'⊤')) return tok(Tag::T_top);
135 if (accept(U'□')) return tok(Tag::T_box);
136 if (accept( ',')) return tok(Tag::T_comma);
137 if (accept( '$')) return tok(Tag::T_dollar);
138 if (accept( '#')) return tok(Tag::T_extract);
139 if (accept(U'λ')) return tok(Tag::T_lm);
140 if (accept( '%')) return tok(Tag::T_rem);
141 if (accept( '|')) return tok(Tag::T_pipe);
142 if (accept( ';')) return tok(Tag::T_semicolon);
143 if (accept(U'★')) return tok(Tag::T_star);
144 if (accept( '*')) return tok(Tag::T_star);
145 if (accept( ':')) return tok(Tag::T_colon);
146 if (accept(U'∪')) return tok(Tag::T_union);
147 // clang-format on
148
149 if (accept('.')) {
150 if (accept(utf8::isdigit)) {
151 lex_digits();
152 lex_exp();
153 return {loc_, lex_f64(view(), 10)};
154 }
155
156 return tok(Tag::T_dot);
157 }
158
159 if (accept('`')) {
160 if (accept(utf8::any('+', '-', '*', '/', '%'))) return {loc_, Tag::M_id, sym()};
161 if (accept('<')) {
162 accept(utf8::any('<', '='));
163 return {loc_, Tag::M_id, sym()};
164 }
165 if (accept('>')) {
166 accept(utf8::any('>', '='));
167 return {loc_, Tag::M_id, sym()};
168 }
169 if (accept(utf8::any('=', '!')) && accept('=')) return {loc_, Tag::M_id, sym()};
170 error().e(loc_, "expected one of `+`, `-`, `*`, `/`, `%`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `<<`, `>>` "
171 "after the escape hatch");
172 continue;
173 }
174
175 if (accept('\'')) {
176 if (ahead(0) == '\'' && ahead(1) == '\'') {
177 next(), next();
178 error().e(loc_, "character constant must be escaped: `\\'`");
179 return {loc_, char8_t('\'')};
180 }
181 if (accept('\'')) {
182 error().e(loc_, "empty character literal");
183 return {loc_, char8_t('\0')};
184 }
185 auto c = lex_char();
186 if (accept('\'')) return {loc_, c};
187 // Like C, a malformed literal extends to its closing quote or the end of the line so that its tail doesn't
188 // cascade.
189 while (ahead() != '\'' && !eol())
190 if (next() == '\\' && !eol()) next();
191 if (accept('\''))
192 error().e(loc_, "character literal must contain exactly one character");
193 else
194 error().e(loc_, "unterminated character literal");
195 return {loc_, c};
196 }
197
198 if (accept('\"')) return lex_str();
199
200 if (lex_id()) {
201 auto s = sym();
202 if (auto tag = driver().keys().find(s)) return {loc_, *tag};
203 return {loc_, Tag::M_id, s};
204 }
205
206 if (utf8::isdigit(ahead())) return lex_lit();
207
208 if (start_md()) {
209 emit_md();
210 continue;
211 }
212
213 // comments
214 if (accept('/')) {
215 if (accept('*')) {
216 eat_comments();
217 continue;
218 }
219 if (accept('/')) {
220 accept_while([](char32_t c) { return c != '\n'; });
221 continue;
222 }
223
224 return tok(Tag::T_div);
225 }
226
227 recover_char();
228 }
229}
230
231bool Lexer::lex_id() {
232 if (accept(is_id_head)) {
233 accept_while(is_id_tail);
234 return true;
235 }
236 return false;
237}
238
239std::string Lexer::escape(std::string_view str) {
240 std::string res;
241 for (auto c : str) {
242 // clang-format off
243 switch (c) {
244 case '\\': res += "\\\\"; break;
245 case '\"': res += "\\\""; break;
246 case '\0': res += "\\0"; break;
247 case '\a': res += "\\a"; break;
248 case '\b': res += "\\b"; break;
249 case '\f': res += "\\f"; break;
250 case '\n': res += "\\n"; break;
251 case '\r': res += "\\r"; break;
252 case '\t': res += "\\t"; break;
253 case '\v': res += "\\v"; break;
254 default: res += c;
255 }
256 // clang-format on
257 }
258 return res;
259}
260
261bool Lexer::is_id(std::string_view str) {
262 size_t i = 0;
263 if (!is_id_head(utf8::decode(str, i))) return false;
264 while (i != str.size())
265 if (!is_id_tail(utf8::decode(str, i))) return false;
266 return true;
267}
268
269// clang-format off
270Tok Lexer::lex_lit() {
271 int base = 10;
272
273 // prefix starting with '0'
274 if (accept('0')) {
275 if (accept(utf8::any('b', 'B'))) base = 2;
276 else if (accept(utf8::any('o', 'O'))) base = 8;
277 else if (accept(utf8::any('x', 'X'))) base = 16;
278 }
279
280 // Everything the prefix does not cover; std::from_chars wants a hexadecimal float without its `0x`.
281 auto begin = loc_.end.off;
282 auto body = [&](uint32_t end) { return buf_.substr(begin, end - begin); };
283
284 lex_digits(base);
285 auto end = loc_.end.off;
286 if (base != 10 && end == begin && ahead() != '.')
287 error().e(loc_, "expected digits after `0{}`", base == 16 ? 'x' : base == 8 ? 'o' : 'b');
288
289 if (accept(utf8::any('i', 'I'))) {
290 auto val = to_u64(body(end), base);
291 auto i = loc_.end.off;
292 lex_digits();
293 auto width = to_u64(buf_.substr(i, loc_.end.off - i), 10);
294 if (width == 0 || width > 64) {
295 error().e(loc_, "bit width of an Idx literal must be within 1 and 64");
296 width = 64;
297 }
298 return Tok{loc_, Idx::bitwidth2size(width), val};
299 }
300
301 if (base == 10) {
302 if (utf8::isrange(ahead(), U'₀', U'₉')) {
303 auto i = to_u64(body(end), 10);
304 std::string mod;
305 while (utf8::isrange(ahead(), U'₀', U'₉')) mod += char(next() - U'₀' + '0');
306 return idx_tok(loc_, to_u64(mod, 10), i);
307 } else if (accept('_')) {
308 auto i = to_u64(body(end), 10);
309 auto m = loc_.end.off;
310 if (accept(utf8::isdigit)) {
311 lex_digits(10);
312 return idx_tok(loc_, to_u64(buf_.substr(m, loc_.end.off - m), 10), i);
313 } else {
314 error().e(loc_, "stray underscore in Idx literal; size is missing");
315 return Tok{loc_, i};
316 }
317 }
318 }
319
320 bool is_float = false;
321 if (base == 10 || base == 16) {
322 // parse fractional part
323 if (accept('.')) {
324 is_float = true;
325 lex_digits(base);
326 }
327
328 bool has_exp = lex_exp(base);
329 if (base == 16 && is_float && !has_exp) error().e(loc_, "hexadecimal floating constants require an exponent");
330 is_float |= has_exp;
331 }
332
333 if (is_float) return Tok{loc_, lex_f64(body(loc_.end.off), base)};
334
335 auto val = parse_u64(body(end), base);
336 if (!val) error().w(loc_, "integer literal saturates to `⊤:Nat`");
337 return Tok{loc_, val.value_or(std::numeric_limits<u64>::max())};
338}
339
340void Lexer::lex_digits(int base /*= 10*/) {
341 switch (base) {
342 // clang-format off
343 case 2: accept_while(utf8::isbdigit); break;
344 case 8: accept_while(utf8::isodigit); break;
345 case 10: accept_while(utf8::isdigit); break;
346 case 16: accept_while(utf8::isxdigit); break;
347 // clang-format on
348 default: std::unreachable();
349 }
350}
351
352f64 Lexer::lex_f64(std::string_view sv, int base) {
353 auto res = to_f64(sv, base);
354 if (!res) error().e(loc_, "floating-point literal is out of range");
355 return res.value_or(0.);
356}
357
358bool Lexer::lex_exp(int base /*= 10*/) {
359 if (accept(base == 10 ? utf8::any('e', 'E') : utf8::any('p', 'P'))) {
360 accept(utf8::any('+', '-'));
361 if (!utf8::isdigit(ahead())) error().e(loc_, "exponent has no digits");
362 lex_digits();
363 return true;
364 }
365 return false;
366}
367// clang-format on
368
369char8_t Lexer::lex_char() {
370 if (accept('\\')) {
371 auto c = ahead();
372 if (auto u = unescape(c)) return next(), *u;
373 if (c != utf8::EoF) {
374 error().e(loc_.anew_end(), "invalid escape character `\\{}`", utf8::Char32(c));
375 next();
376 }
377 return '\0';
378 }
379
380 auto c = next();
381 if (utf8::isascii(c)) return char8_t(c);
382 error().e(loc_, "invalid character `{}`", utf8::Char32(c));
383 return '\0';
384}
385
386/// The body is a slice of Lexer::buf_ unless an escape made it diverge - see Lexer::unquote.
387Tok Lexer::lex_str() {
388 auto begin = loc_.end.off; // just past the opening `"`
389 bool esc = false;
390
391 while (true) {
392 if (accept('\"')) return {loc_, Tag::L_str, sym_str(begin, loc_.end.off - 1, esc)};
393
394 if (ahead() == utf8::EoF) {
395 error().e(loc_, "unterminated string literal");
396 return {loc_, Tag::L_str, sym_str(begin, loc_.end.off, esc)};
397 }
398
399 if (accept('\\')) {
400 esc = true;
401 if (ahead() != utf8::EoF) next();
402 } else if (auto c = next(); !utf8::isascii(c)) {
403 error().e(loc_, "invalid character `{}`", utf8::Char32(c));
404 }
405 }
406}
407
408Sym Lexer::sym_str(uint32_t begin, uint32_t end, bool esc) {
409 auto body = buf_.substr(begin, end - begin);
410 return driver().sym(esc ? std::string_view(unquote(body, begin)) : body);
411}
412
413std::string Lexer::unquote(std::string_view body, uint32_t begin) {
414 std::string res;
415 res.reserve(body.size());
416
417 for (size_t i = 0, e = body.size(); i != e; ++i) {
418 auto c = body[i];
419 if (c == '\\' && i + 1 != e) {
420 if (auto u = unescape(body[++i])) {
421 res += *u;
422 } else {
423 auto loc = Loc(src_, Pos(begin + u32(i) - 1), Pos(begin + u32(i) + 1));
424 error().e(loc, "invalid escape character `\\{}`", body[i]);
425 }
426 } else {
427 res += c;
428 }
429 }
430
431 return res;
432}
433
434void Lexer::eat_comments() {
435 while (true) {
436 accept_while([](char32_t c) { return c != '*'; });
437 if (accept(utf8::EoF)) {
438 error().e(loc_, "unterminated multi-line comment");
439 return;
440 }
441 next();
442 if (accept('/')) break;
443 }
444}
445
446void Lexer::emit_md(bool start_of_file) {
447 if (!start_of_file) {
448 md_flush();
449 md_close();
450 md_blank();
451 }
452
453 do {
454 for (int i = 0; i != 3; ++i)
455 next();
456 accept(' ');
457 md_skip();
458
459 accept_while([](char32_t c) { return c != '\n'; });
460 accept('\n');
461 md_flush();
462 } while (start_md());
463
464 if (ahead() != utf8::EoF) md_open();
465}
466
467} // namespace mim::ast
Driver & driver()
fe::Lexer's default diagnostics go to its Driver::error.
Definition lexer.h:25
static bool is_id(std::string_view str)
Does str match the id production - the same rule Lexer::lex_id applies to the input?
Definition lexer.cpp:261
static std::string escape(std::string_view str)
Inverse of Lexer::lex_char: renders str as the body of a Mim string literal.
Definition lexer.cpp:239
Lexer(Driver &driver, const fe::Src &src, std::ostream *md=nullptr)
Creates a lexer to read *.mim files (see Lexical Structure).
Definition lexer.h:19
Definition ast.h:16
Tok::Tag Tag
Definition bind.cpp:11
uint32_t u32
Definition types.h:27
uint64_t u64
Definition types.h:27