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(_) => {
265 let msg = alloc::format!("invalid {} literal '{}{}'", prefix, prefix, digits);
266 self.record_error(msg);
267 0.0
268 }
269 };
270 if self.peek() == Some('n') {
272 self.advance();
273 let mut lit = String::from(prefix);
274 lit.push_str(&digits);
275 return Token::BigIntLit(lit);
276 }
277 return Token::Num(val);
278 }
279 let mut has_dot = false;
280 let mut has_exp = false;
281 while let Some(ch) = self.peek() {
282 if ch.is_ascii_digit() {
283 s.push(ch);
284 self.advance();
285 } else if ch == '_' {
286 self.advance();
289 } else if ch == '.' && !has_dot && !has_exp {
290 s.push(ch);
291 has_dot = true;
292 self.advance();
293 } else if (ch == 'e' || ch == 'E') && !has_exp {
294 s.push(ch);
295 has_exp = true;
296 self.advance();
297 if matches!(self.peek(), Some('+') | Some('-')) {
298 if let Some(c) = self.advance() {
299 s.push(c);
300 }
301 }
302 } else {
303 break;
304 }
305 }
306 if self.peek() == Some('n') && !has_dot && !has_exp {
308 self.advance();
309 return Token::BigIntLit(s);
310 }
311 match s.parse::<f64>() {
312 Ok(v) => Token::Num(v),
313 Err(_) => {
314 self.record_error(alloc::format!("invalid numeric literal '{}'", s));
316 Token::Num(0.0)
317 }
318 }
319 }
320
321 fn read_string(&mut self, quote: char) -> Token {
322 self.advance(); let mut s = String::new();
324 let mut closed = false;
325 while let Some(ch) = self.peek() {
326 if ch == quote {
327 self.advance();
328 closed = true;
329 break;
330 }
331 if ch == '\\' {
332 self.advance();
333 if let Some(esc) = self.advance() {
334 if let Some(c) = unescape(esc, self) {
335 s.push(c);
336 }
337 }
343 } else {
344 s.push(ch);
345 self.advance();
346 }
347 }
348 if !closed {
349 self.record_error(alloc::format!("unterminated string literal ({}...)", quote));
351 }
352 Token::Str(s)
353 }
354
355 fn read_template(&mut self) -> Token {
356 self.advance(); let mut parts = Vec::new();
358 let mut cur = String::new();
359 let mut raw_start = self.pos;
360 while let Some(ch) = self.peek() {
361 if ch == '`' {
362 let raw: String = self.chars[raw_start..self.pos].iter().collect();
363 self.advance();
364 parts.push(TemplatePart::Quasi(core::mem::take(&mut cur), raw));
365 return Token::Template(parts);
366 }
367 if ch == '\\' {
368 self.advance();
369 if let Some(esc) = self.advance() {
370 if let Some(c) = unescape(esc, self) {
371 cur.push(c);
372 }
373 }
374 continue;
375 }
376 if ch == '$' && self.peek_at(1) == Some('{') {
377 let raw: String = self.chars[raw_start..self.pos].iter().collect();
378 self.advance(); self.advance(); parts.push(TemplatePart::Quasi(core::mem::take(&mut cur), raw));
381 parts.push(TemplatePart::Expr(self.scan_interpolation_expr()));
382 raw_start = self.pos;
383 continue;
384 }
385 cur.push(ch);
386 self.advance();
387 }
388 self.record_error(String::from("unterminated template literal"));
391 let raw: String = self.chars[raw_start..self.pos].iter().collect();
392 parts.push(TemplatePart::Quasi(cur, raw));
393 Token::Template(parts)
394 }
395
396 fn scan_interpolation_expr(&mut self) -> String {
405 let mut src = String::new();
406 let mut depth = 1i32;
407 while let Some(ch) = self.peek() {
408 match ch {
409 '{' => {
410 depth += 1;
411 src.push(ch);
412 self.advance();
413 }
414 '}' => {
415 depth -= 1;
416 self.advance();
417 if depth == 0 {
418 break;
419 }
420 src.push(ch);
421 }
422 '\'' | '"' => {
423 let quote = ch;
424 src.push(ch);
425 self.advance();
426 while let Some(c2) = self.peek() {
427 src.push(c2);
428 self.advance();
429 if c2 == '\\' {
430 if let Some(c3) = self.peek() {
431 src.push(c3);
432 self.advance();
433 }
434 continue;
435 }
436 if c2 == quote {
437 break;
438 }
439 }
440 }
441 '`' => {
442 src.push(ch);
443 self.advance();
444 while let Some(c2) = self.peek() {
445 src.push(c2);
446 self.advance();
447 if c2 == '\\' {
448 if let Some(c3) = self.peek() {
449 src.push(c3);
450 self.advance();
451 }
452 continue;
453 }
454 if c2 == '`' {
455 break;
456 }
457 }
458 }
459 _ => {
460 src.push(ch);
461 self.advance();
462 }
463 }
464 }
465 src
466 }
467
468 pub fn next_token(&mut self) -> Token {
469 let t = self.scan();
470 self.prev = Some(t.clone());
471 t
472 }
473
474 fn scan(&mut self) -> Token {
475 self.skip_trivia();
476 let ch = match self.peek() {
477 Some(c) => c,
478 None => return Token::Eof,
479 };
480 if ch.is_ascii_alphabetic() || ch == '_' || ch == '$' || ch == '#' {
481 return self.read_identifier_or_keyword();
482 }
483 if ch.is_ascii_digit()
484 || (ch == '.' && self.peek_at(1).map(|c| c.is_ascii_digit()).unwrap_or(false))
485 {
486 return self.read_number();
487 }
488 if ch == '"' || ch == '\'' {
489 return self.read_string(ch);
490 }
491 if ch == '`' {
492 return self.read_template();
493 }
494 if ch == '/' && self.regex_allowed() {
496 if let Some(rx) = self.try_read_regex() {
497 return rx;
498 }
499 }
500 for sym in SYMBOLS {
502 if self.match_str(sym) {
503 if *sym == "?." && self.peek_at(2).is_some_and(|c| c.is_ascii_digit()) {
512 continue;
513 }
514 self.pos += sym.chars().count();
515 return Token::Sym(String::from(*sym));
516 }
517 }
518 self.advance();
520 self.scan()
521 }
522
523 fn regex_allowed(&self) -> bool {
525 match &self.prev {
526 None | Some(Token::Eof) => true,
527 Some(Token::Num(_))
528 | Some(Token::BigIntLit(_))
529 | Some(Token::Str(_))
530 | Some(Token::Ident(_))
531 | Some(Token::Template(_))
532 | Some(Token::Regex(_, _)) => false,
533 Some(Token::Keyword(k)) => !matches!(
534 k.as_str(),
535 "this" | "super" | "true" | "false" | "null" | "undefined"
536 ),
537 Some(Token::Sym(s)) => !matches!(s.as_str(), ")" | "]"),
539 }
540 }
541
542 fn try_read_regex(&mut self) -> Option<Token> {
544 let start = self.pos;
545 self.advance(); let mut pat = String::new();
547 let mut in_class = false;
548 loop {
549 match self.peek() {
550 None | Some('\n') => {
551 self.pos = start;
552 return None;
553 }
554 Some('\\') => {
555 pat.push('\\');
556 self.advance();
557 if let Some(c) = self.peek() {
558 pat.push(c);
559 self.advance();
560 }
561 }
562 Some('[') => {
563 in_class = true;
564 pat.push('[');
565 self.advance();
566 }
567 Some(']') => {
568 in_class = false;
569 pat.push(']');
570 self.advance();
571 }
572 Some('/') if !in_class => {
573 self.advance();
574 break;
575 }
576 Some(c) => {
577 pat.push(c);
578 self.advance();
579 }
580 }
581 }
582 if pat.is_empty() {
584 self.pos = start;
585 return None;
586 }
587 let mut flags = String::new();
588 while let Some(c) = self.peek() {
589 if c.is_ascii_alphabetic() {
590 flags.push(c);
591 self.advance();
592 } else {
593 break;
594 }
595 }
596 Some(Token::Regex(pat, flags))
597 }
598
599 fn match_str(&self, s: &str) -> bool {
600 for (i, c) in s.chars().enumerate() {
601 if self.peek_at(i) != Some(c) {
602 return false;
603 }
604 }
605 true
606 }
607}
608
609fn unescape(esc: char, lexer: &mut Lexer) -> Option<char> {
613 match esc {
614 '\n' => return None,
615 '\r' => {
616 if lexer.peek() == Some('\n') {
617 lexer.advance();
618 }
619 return None;
620 }
621 _ => {}
622 }
623 Some(match esc {
624 'n' => '\n',
625 't' => '\t',
626 'r' => '\r',
627 'b' => '\u{8}',
628 'f' => '\u{c}',
629 'v' => '\u{b}',
630 '0' => '\0',
631 'u' => {
632 let mut hex = String::new();
634 if lexer.peek() == Some('{') {
635 lexer.advance();
636 while let Some(c) = lexer.peek() {
637 if c == '}' {
638 lexer.advance();
639 break;
640 }
641 hex.push(c);
642 lexer.advance();
643 }
644 } else {
645 for _ in 0..4 {
646 if let Some(c) = lexer.peek() {
647 if c.is_ascii_hexdigit() {
648 hex.push(c);
649 lexer.advance();
650 } else {
651 break;
652 }
653 }
654 }
655 }
656 u32::from_str_radix(&hex, 16)
657 .ok()
658 .and_then(char::from_u32)
659 .unwrap_or('\u{fffd}')
660 }
661 'x' => {
662 let mut hex = String::new();
663 for _ in 0..2 {
664 if let Some(c) = lexer.peek() {
665 if c.is_ascii_hexdigit() {
666 hex.push(c);
667 lexer.advance();
668 } else {
669 break;
670 }
671 }
672 }
673 u32::from_str_radix(&hex, 16)
674 .ok()
675 .and_then(char::from_u32)
676 .unwrap_or('\u{fffd}')
677 }
678 other => other,
679 })
680}