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