11namespace utf8 = fe::utf8;
17Tok idx_tok(Loc loc, u64 mod, u64 val) {
return {loc, mod, mod == 0 ? val : val % mod}; }
19std::optional<char> unescape(
char32_t c) {
22 case '\'':
return '\'';
23 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';
38bool is_id_head(
char32_t c) {
return c ==
'_' || utf8::isalpha(c); }
39bool is_id_tail(
char32_t c) {
return c ==
'_' || utf8::isalnum(c); }
42std::optional<u64> parse_u64(std::string_view sv,
int base) {
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 {};
50u64 to_u64(std::string_view sv,
int base) {
return parse_u64(sv, base).value_or(std::numeric_limits<u64>::max()); }
53std::optional<f64> to_f64(std::string_view sv,
int base) {
55 auto ec = std::from_chars(sv.data(), sv.data() + sv.size(), res,
56 base == 16 ? std::chars_format::hex : std::chars_format::general)
58 if (ec == std::errc::result_out_of_range)
return {};
64Lexer::Lexer(Driver& driver, std::string_view buf,
const fe::Src* src, std::ostream* md)
80 if (accept(utf8::EoF)) {
83 if (md_nls_ == 0) md_emit(
"\n");
88 if (accept(utf8::isspace))
continue;
89 if (recover_utf8())
continue;
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);
104 if (accept(
'+'))
return tok(Tag::T_add);
106 if (accept(
'>'))
return tok(Tag::T_arrow_r);
107 return tok(Tag::T_sub);
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);
113 if (accept(
'>'))
return tok(Tag::T_fat_arrow);
114 if (accept(
'='))
return tok(Tag::T_eq);
115 return tok(Tag::T_assign);
118 if (accept(
'='))
return tok(Tag::T_ne);
119 error().e(loc_,
"expected `=` after `!`");
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);
129 if (accept(
'>'))
return tok(Tag::T_shr);
130 if (accept(
'='))
return tok(Tag::T_ge);
131 return tok(Tag::T_gt);
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);
150 if (accept(utf8::isdigit)) {
153 return {loc_, lex_f64(view(), 10)};
156 return tok(Tag::T_dot);
160 if (accept(utf8::any(
'+',
'-',
'*',
'/',
'%')))
return {loc_, Tag::M_id, sym()};
162 accept(utf8::any(
'<',
'='));
163 return {loc_, Tag::M_id, sym()};
166 accept(utf8::any(
'>',
'='));
167 return {loc_, Tag::M_id, sym()};
169 if (accept(utf8::any(
'=',
'!')) && accept(
'='))
return {loc_, Tag::M_id, sym()};
170 error().e(loc_,
"expected one of `+`, `-`, `*`, `/`, `%`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `<<`, `>>` "
171 "after the escape hatch");
176 if (ahead(0) ==
'\'' && ahead(1) ==
'\'') {
178 error().e(loc_,
"character constant must be escaped: `\\'`");
179 return {loc_, char8_t(
'\'')};
182 error().e(loc_,
"empty character literal");
183 return {loc_, char8_t(
'\0')};
186 if (accept(
'\''))
return {loc_, c};
189 while (ahead() !=
'\'' && !eol())
190 if (next() ==
'\\' && !eol()) next();
192 error().e(loc_,
"character literal must contain exactly one character");
194 error().e(loc_,
"unterminated character literal");
198 if (accept(
'\"'))
return lex_str();
202 if (
auto tag =
driver().keys().find(s))
return {loc_, *tag};
203 return {loc_, Tag::M_id, s};
206 if (utf8::isdigit(ahead()))
return lex_lit();
220 accept_while([](
char32_t c) {
return c !=
'\n'; });
224 return tok(Tag::T_div);
231bool Lexer::lex_id() {
232 if (accept(is_id_head)) {
233 accept_while(is_id_tail);
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;
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;
270Tok Lexer::lex_lit() {
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;
281 auto begin = loc_.end.off;
282 auto body = [&](uint32_t end) {
return buf_.substr(begin, end - begin); };
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');
289 if (accept(utf8::any(
'i',
'I'))) {
290 auto val = to_u64(body(end), base);
291 auto i = loc_.end.off;
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");
298 return Tok{loc_, Idx::bitwidth2size(width), val};
302 if (utf8::isrange(ahead(), U
'₀', U
'₉')) {
303 auto i = to_u64(body(end), 10);
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)) {
312 return idx_tok(loc_, to_u64(buf_.substr(m, loc_.end.off - m), 10), i);
314 error().e(loc_,
"stray underscore in Idx literal; size is missing");
320 bool is_float =
false;
321 if (base == 10 || base == 16) {
328 bool has_exp = lex_exp(base);
329 if (base == 16 && is_float && !has_exp)
error().e(loc_,
"hexadecimal floating constants require an exponent");
333 if (is_float)
return Tok{loc_, lex_f64(body(loc_.end.off), base)};
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())};
340void Lexer::lex_digits(
int base ) {
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;
348 default: std::unreachable();
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.);
358bool Lexer::lex_exp(
int base ) {
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");
369char8_t Lexer::lex_char() {
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));
381 if (utf8::isascii(c))
return char8_t(c);
382 error().e(loc_,
"invalid character `{}`", utf8::Char32(c));
387Tok Lexer::lex_str() {
388 auto begin = loc_.end.off;
392 if (accept(
'\"'))
return {loc_, Tag::L_str, sym_str(begin, loc_.end.off - 1, esc)};
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)};
401 if (ahead() != utf8::EoF) next();
402 }
else if (
auto c = next(); !utf8::isascii(c)) {
403 error().e(loc_,
"invalid character `{}`", utf8::Char32(c));
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);
413std::string Lexer::unquote(std::string_view body, uint32_t begin) {
415 res.reserve(body.size());
417 for (
size_t i = 0, e = body.size(); i != e; ++i) {
419 if (c ==
'\\' && i + 1 != e) {
420 if (
auto u = unescape(body[++i])) {
423 auto loc = Loc(src_,
Pos(begin +
u32(i) - 1),
Pos(begin +
u32(i) + 1));
424 error().e(loc,
"invalid escape character `\\{}`", body[i]);
434void Lexer::eat_comments() {
436 accept_while([](
char32_t c) {
return c !=
'*'; });
437 if (accept(utf8::EoF)) {
438 error().e(loc_,
"unterminated multi-line comment");
442 if (accept(
'/'))
break;
446void Lexer::emit_md(
bool start_of_file) {
447 if (!start_of_file) {
454 for (
int i = 0; i != 3; ++i)
459 accept_while([](
char32_t c) {
return c !=
'\n'; });
462 }
while (start_md());
464 if (ahead() != utf8::EoF) md_open();
Driver & driver()
fe::Lexer's default diagnostics go to its Driver::error.
static bool is_id(std::string_view str)
Does str match the id production - the same rule Lexer::lex_id applies to the input?
static std::string escape(std::string_view str)
Inverse of Lexer::lex_char: renders str as the body of a Mim string literal.
Lexer(Driver &driver, const fe::Src &src, std::ostream *md=nullptr)
Creates a lexer to read *.mim files (see Lexical Structure).