1use alloc::string::String;
9use alloc::vec::Vec;
10
11#[derive(Debug, Clone, PartialEq)]
14pub enum TemplatePart {
15 Quasi(String, String),
18 Expr(String),
19}
20
21#[derive(Debug, Clone, PartialEq)]
22pub enum Token {
23 Keyword(String),
24 Ident(String),
25 Num(f64),
26 BigIntLit(String),
28 Str(String),
29 Template(Vec<TemplatePart>),
32 Sym(String),
34 Regex(String, String),
36 Eof,
37}
38
39const KEYWORDS: &[&str] = &[
41 "var",
42 "let",
43 "const",
44 "if",
45 "else",
46 "function",
47 "return",
48 "true",
49 "false",
50 "null",
51 "undefined",
52 "this",
53 "new",
54 "typeof",
55 "void",
56 "delete",
57 "instanceof",
58 "in",
59 "of",
60 "while",
61 "do",
62 "for",
63 "break",
64 "continue",
65 "throw",
66 "try",
67 "catch",
68 "finally",
69 "class",
70 "extends",
71 "super",
72 "switch",
73 "case",
74 "default",
75 "async",
76 "await",
77 "yield",
78 "import",
79 "export",
80];
81
82const SYMBOLS: &[&str] = &[
84 ">>>=", "===", "!==", "**=", ">>>", "<<=", ">>=", "&&=", "||=", "??=", "...", "==", "!=", "<=",
85 ">=", "&&", "||", "??", "?.", "**", "++", "--", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=",
86 "<<", ">>", "=>", "+", "-", "*", "/", "%", "=", "<", ">", "!", "~", "&", "|", "^", "?", ":",
87 ".", ",", ";", "(", ")", "{", "}", "[", "]",
88];
89
90#[derive(Debug, Clone, PartialEq)]
98pub struct LexError {
99 pub message: String,
100 pub pos: usize,
102}
103
104const MAX_LEX_ERRORS: usize = 64;
106
107pub struct Lexer {
108 chars: Vec<char>,
109 pos: usize,
110 prev: Option<Token>,
112 errors: Vec<LexError>,
114 error_count: usize,
116}
117
118impl Lexer {
119 pub fn new(source: &str) -> Self {
120 Self {
121 chars: source.chars().collect(),
122 pos: 0,
123 prev: None,
124 errors: Vec::new(),
125 error_count: 0,
126 }
127 }
128
129 pub fn errors(&self) -> &[LexError] {
131 &self.errors
132 }
133 pub fn error_count(&self) -> usize {
135 self.error_count
136 }
137 pub fn take_errors(&mut self) -> Vec<LexError> {
139 core::mem::take(&mut self.errors)
140 }
141
142 fn record_error(&mut self, message: String) {
143 self.error_count += 1;
144 if self.errors.len() < MAX_LEX_ERRORS {
145 self.errors.push(LexError {
146 message,
147 pos: self.pos,
148 });
149 }
150 }
151
152 fn peek(&self) -> Option<char> {
153 self.chars.get(self.pos).copied()
154 }
155 fn peek_at(&self, off: usize) -> Option<char> {
156 self.chars.get(self.pos + off).copied()
157 }
158 fn advance(&mut self) -> Option<char> {
159 let ch = self.peek();
160 if ch.is_some() {
161 self.pos += 1;
162 }
163 ch
164 }
165
166 fn skip_trivia(&mut self) {
168 loop {
169 match self.peek() {
170 Some(c) if c.is_whitespace() => {
171 self.advance();
172 }
173 Some('/') if self.peek_at(1) == Some('/') => {
174 while let Some(c) = self.peek() {
175 if c == '\n' {
176 break;
177 }
178 self.advance();
179 }
180 }
181 Some('/') if self.peek_at(1) == Some('*') => {
182 self.advance();
183 self.advance();
184 let mut closed = false;
185 while let Some(c) = self.peek() {
186 if c == '*' && self.peek_at(1) == Some('/') {
187 self.advance();
188 self.advance();
189 closed = true;
190 break;
191 }
192 self.advance();
193 }
194 if !closed {
195 self.record_error(String::from("unterminated block comment"));
198 }
199 }
200 _ => break,
201 }
202 }
203 }
204
205 fn read_identifier_or_keyword(&mut self) -> Token {
206 let mut s = String::new();
207 if self.peek() == Some('#') {
212 s.push('#');
213 self.advance();
214 }
215 while let Some(ch) = self.peek() {
216 if ch.is_ascii_alphanumeric() || ch == '_' || ch == '$' {
217 s.push(ch);
218 self.advance();
219 } else {
220 break;
221 }
222 }
223 if KEYWORDS.contains(&s.as_str()) {
224 Token::Keyword(s)
225 } else {
226 Token::Ident(s)
227 }
228 }
229
230 fn read_number(&mut self) -> Token {
231 let mut s = String::new();
232 let radix_prefix = if self.peek() == Some('0') {
237 match self.peek_at(1) {
238 Some('x') | Some('X') => Some((16u32, "0x")),
239 Some('o') | Some('O') => Some((8u32, "0o")),
240 Some('b') | Some('B') => Some((2u32, "0b")),
241 _ => None,
242 }
243 } else {
244 None
245 };
246 if let Some((radix, prefix)) = radix_prefix {
247 self.advance();
248 self.advance();
249 let mut digits = String::new();
250 while let Some(ch) = self.peek() {
251 if ch.is_digit(radix) {
252 digits.push(ch);
253 self.advance();
254 } else if ch == '_' {
255 self.advance();
256 } else {
257 break;
258 }
259 }
260 let val = match u64::from_str_radix(&digits, radix) {
263 Ok(v) => v as f64,
264 Err(_) if !digits.is_empty() => digits
267 .chars()
268 .filter_map(|c| c.to_digit(radix))
269 .fold(0.0f64, |acc, d| acc * radix as f64 + d as f64),
270 Err(_) => {
271 let msg = alloc::format!("invalid {} literal '{}{}'", prefix, prefix, digits);
272 self.record_error(msg);
273 0.0
274 }
275 };
276 if self.peek() == Some('n') {
278 self.advance();
279 let mut lit = String::from(prefix);
280 lit.push_str(&digits);
281 return Token::BigIntLit(lit);
282 }
283 return Token::Num(val);
284 }
285 let mut has_dot = false;
286 let mut has_exp = false;
287 while let Some(ch) = self.peek() {
288 if ch.is_ascii_digit() {
289 s.push(ch);
290 self.advance();
291 } else if ch == '_' {
292 self.advance();
295 } else if ch == '.' && !has_dot && !has_exp {
296 s.push(ch);
297 has_dot = true;
298 self.advance();
299 } else if (ch == 'e' || ch == 'E') && !has_exp {
300 s.push(ch);
301 has_exp = true;
302 self.advance();
303 if matches!(self.peek(), Some('+') | Some('-')) {
304 if let Some(c) = self.advance() {
305 s.push(c);
306 }
307 }
308 } else {
309 break;
310 }
311 }
312 if self.peek() == Some('n') && !has_dot && !has_exp {
314 self.advance();
315 return Token::BigIntLit(s);
316 }
317 match s.parse::<f64>() {
318 Ok(v) => Token::Num(v),
319 Err(_) => {
320 self.record_error(alloc::format!("invalid numeric literal '{}'", s));
322 Token::Num(0.0)
323 }
324 }
325 }
326
327 fn read_string(&mut self, quote: char) -> Token {
328 self.advance(); let mut s = String::new();
330 let mut closed = false;
331 while let Some(ch) = self.peek() {
332 if ch == quote {
333 self.advance();
334 closed = true;
335 break;
336 }
337 if ch == '\\' {
338 self.advance();
339 if let Some(esc) = self.advance() {
340 if let Some(c) = unescape(esc, self) {
341 s.push(c);
342 }
343 }
349 } else {
350 s.push(ch);
351 self.advance();
352 }
353 }
354 if !closed {
355 self.record_error(alloc::format!("unterminated string literal ({}...)", quote));
357 }
358 Token::Str(s)
359 }
360
361 fn read_template(&mut self) -> Token {
362 self.advance(); let mut parts = Vec::new();
364 let mut cur = String::new();
365 let mut raw_start = self.pos;
366 while let Some(ch) = self.peek() {
367 if ch == '`' {
368 let raw: String = self.chars[raw_start..self.pos].iter().collect();
369 self.advance();
370 parts.push(TemplatePart::Quasi(core::mem::take(&mut cur), raw));
371 return Token::Template(parts);
372 }
373 if ch == '\\' {
374 self.advance();
375 if let Some(esc) = self.advance() {
376 if let Some(c) = unescape(esc, self) {
377 cur.push(c);
378 }
379 }
380 continue;
381 }
382 if ch == '$' && self.peek_at(1) == Some('{') {
383 let raw: String = self.chars[raw_start..self.pos].iter().collect();
384 self.advance(); self.advance(); parts.push(TemplatePart::Quasi(core::mem::take(&mut cur), raw));
387 parts.push(TemplatePart::Expr(self.scan_interpolation_expr()));
388 raw_start = self.pos;
389 continue;
390 }
391 cur.push(ch);
392 self.advance();
393 }
394 self.record_error(String::from("unterminated template literal"));
397 let raw: String = self.chars[raw_start..self.pos].iter().collect();
398 parts.push(TemplatePart::Quasi(cur, raw));
399 Token::Template(parts)
400 }
401
402 fn scan_interpolation_expr(&mut self) -> String {
411 let mut src = String::new();
412 let mut depth = 1i32;
413 while let Some(ch) = self.peek() {
414 match ch {
415 '{' => {
416 depth += 1;
417 src.push(ch);
418 self.advance();
419 }
420 '}' => {
421 depth -= 1;
422 self.advance();
423 if depth == 0 {
424 break;
425 }
426 src.push(ch);
427 }
428 '\'' | '"' => {
429 let quote = ch;
430 src.push(ch);
431 self.advance();
432 while let Some(c2) = self.peek() {
433 src.push(c2);
434 self.advance();
435 if c2 == '\\' {
436 if let Some(c3) = self.peek() {
437 src.push(c3);
438 self.advance();
439 }
440 continue;
441 }
442 if c2 == quote {
443 break;
444 }
445 }
446 }
447 '`' => {
448 src.push(ch);
449 self.advance();
450 while let Some(c2) = self.peek() {
451 src.push(c2);
452 self.advance();
453 if c2 == '\\' {
454 if let Some(c3) = self.peek() {
455 src.push(c3);
456 self.advance();
457 }
458 continue;
459 }
460 if c2 == '`' {
461 break;
462 }
463 }
464 }
465 _ => {
466 src.push(ch);
467 self.advance();
468 }
469 }
470 }
471 src
472 }
473
474 pub fn next_token(&mut self) -> Token {
475 let t = self.scan();
476 self.prev = Some(t.clone());
477 t
478 }
479
480 fn scan(&mut self) -> Token {
481 self.skip_trivia();
482 let ch = match self.peek() {
483 Some(c) => c,
484 None => return Token::Eof,
485 };
486 if ch.is_ascii_alphabetic() || ch == '_' || ch == '$' || ch == '#' {
487 return self.read_identifier_or_keyword();
488 }
489 if ch.is_ascii_digit()
490 || (ch == '.' && self.peek_at(1).map(|c| c.is_ascii_digit()).unwrap_or(false))
491 {
492 return self.read_number();
493 }
494 if ch == '"' || ch == '\'' {
495 return self.read_string(ch);
496 }
497 if ch == '`' {
498 return self.read_template();
499 }
500 if ch == '/' && self.regex_allowed() {
502 if let Some(rx) = self.try_read_regex() {
503 return rx;
504 }
505 }
506 for sym in SYMBOLS {
508 if self.match_str(sym) {
509 if *sym == "?." && self.peek_at(2).is_some_and(|c| c.is_ascii_digit()) {
518 continue;
519 }
520 self.pos += sym.chars().count();
521 return Token::Sym(String::from(*sym));
522 }
523 }
524 self.advance();
526 self.scan()
527 }
528
529 fn regex_allowed(&self) -> bool {
531 match &self.prev {
532 None | Some(Token::Eof) => true,
533 Some(Token::Num(_))
534 | Some(Token::BigIntLit(_))
535 | Some(Token::Str(_))
536 | Some(Token::Ident(_))
537 | Some(Token::Template(_))
538 | Some(Token::Regex(_, _)) => false,
539 Some(Token::Keyword(k)) => !matches!(
540 k.as_str(),
541 "this" | "super" | "true" | "false" | "null" | "undefined"
542 ),
543 Some(Token::Sym(s)) => !matches!(s.as_str(), ")" | "]"),
545 }
546 }
547
548 fn try_read_regex(&mut self) -> Option<Token> {
550 let start = self.pos;
551 self.advance(); let mut pat = String::new();
553 let mut in_class = false;
554 loop {
555 match self.peek() {
556 None | Some('\n') => {
557 self.pos = start;
558 return None;
559 }
560 Some('\\') => {
561 pat.push('\\');
562 self.advance();
563 if let Some(c) = self.peek() {
564 pat.push(c);
565 self.advance();
566 }
567 }
568 Some('[') => {
569 in_class = true;
570 pat.push('[');
571 self.advance();
572 }
573 Some(']') => {
574 in_class = false;
575 pat.push(']');
576 self.advance();
577 }
578 Some('/') if !in_class => {
579 self.advance();
580 break;
581 }
582 Some(c) => {
583 pat.push(c);
584 self.advance();
585 }
586 }
587 }
588 if pat.is_empty() {
590 self.pos = start;
591 return None;
592 }
593 let mut flags = String::new();
594 while let Some(c) = self.peek() {
595 if c.is_ascii_alphabetic() {
596 flags.push(c);
597 self.advance();
598 } else {
599 break;
600 }
601 }
602 Some(Token::Regex(pat, flags))
603 }
604
605 fn match_str(&self, s: &str) -> bool {
606 for (i, c) in s.chars().enumerate() {
607 if self.peek_at(i) != Some(c) {
608 return false;
609 }
610 }
611 true
612 }
613}
614
615fn unescape(esc: char, lexer: &mut Lexer) -> Option<char> {
619 match esc {
620 '\n' => return None,
621 '\r' => {
622 if lexer.peek() == Some('\n') {
623 lexer.advance();
624 }
625 return None;
626 }
627 _ => {}
628 }
629 Some(match esc {
630 'n' => '\n',
631 't' => '\t',
632 'r' => '\r',
633 'b' => '\u{8}',
634 'f' => '\u{c}',
635 'v' => '\u{b}',
636 '0' => '\0',
637 'u' => {
638 let mut hex = String::new();
640 if lexer.peek() == Some('{') {
641 lexer.advance();
642 while let Some(c) = lexer.peek() {
643 if c == '}' {
644 lexer.advance();
645 break;
646 }
647 hex.push(c);
648 lexer.advance();
649 }
650 } else {
651 for _ in 0..4 {
652 if let Some(c) = lexer.peek() {
653 if c.is_ascii_hexdigit() {
654 hex.push(c);
655 lexer.advance();
656 } else {
657 break;
658 }
659 }
660 }
661 }
662 u32::from_str_radix(&hex, 16)
663 .ok()
664 .and_then(char::from_u32)
665 .unwrap_or('\u{fffd}')
666 }
667 'x' => {
668 let mut hex = String::new();
669 for _ in 0..2 {
670 if let Some(c) = lexer.peek() {
671 if c.is_ascii_hexdigit() {
672 hex.push(c);
673 lexer.advance();
674 } else {
675 break;
676 }
677 }
678 }
679 u32::from_str_radix(&hex, 16)
680 .ok()
681 .and_then(char::from_u32)
682 .unwrap_or('\u{fffd}')
683 }
684 other => other,
685 })
686}