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