1mod error;
26mod token;
27
28pub use error::LexError;
29pub use token::Token;
30
31pub const KEYWORDS: [&str; 32] = [
37 "let", "mut", "const", "fn", "return", "if", "else", "while", "for", "in", "break", "continue",
38 "match", "type", "record", "trait", "impl", "import", "module", "pub", "async", "await",
39 "extern", "unsafe", "true", "false", "as", "is", "where", "with", "self", "Self",
40];
41
42use nova_diagnostics::{FileId, Span, Spanned};
43
44pub fn lex(source: &str, file: FileId) -> (Vec<Spanned<Token>>, Vec<LexError>) {
49 let mut lexer = Lexer::new(source, file);
50 lexer.tokenize()
51}
52
53pub fn lex_with_comments(
58 source: &str,
59 file: FileId,
60) -> (Vec<Spanned<Token>>, Vec<Comment>, Vec<LexError>) {
61 let mut lexer = Lexer::new(source, file);
62 let (tokens, errors) = lexer.tokenize();
63 (tokens, lexer.comments, errors)
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub struct Comment {
69 pub kind: CommentKind,
70 pub span: Span,
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub enum CommentKind {
78 Line,
80 Block,
82}
83
84struct InterpFrame {
86 open: usize,
89 braces: usize,
92}
93
94pub struct Lexer<'src> {
97 source: &'src str,
98 file: FileId,
99 pos: usize,
101 interp: Vec<InterpFrame>,
104 in_string: bool,
106 comments: Vec<Comment>,
108}
109
110impl<'src> Lexer<'src> {
111 pub fn new(source: &'src str, file: FileId) -> Self {
112 Self {
113 source,
114 file,
115 pos: 0,
116 interp: Vec::new(),
117 in_string: false,
118 comments: Vec::new(),
119 }
120 }
121
122 pub fn tokenize(&mut self) -> (Vec<Spanned<Token>>, Vec<LexError>) {
123 let mut tokens = Vec::new();
124 let mut errors = Vec::new();
125
126 while self.pos < self.source.len() {
127 match self.next_token() {
128 Ok(Some(tok)) => tokens.push(tok),
129 Ok(None) => {}
130 Err(e) => errors.push(e),
131 }
132 }
133
134 if let Some(frame) = self.interp.last() {
138 let end = (frame.open + 2).min(self.source.len());
139 errors.push(LexError::UnterminatedInterpolation(
140 self.span(frame.open, end),
141 ));
142 }
143
144 tokens.push(Spanned::new(
145 Token::Eof,
146 Span::point(self.source.len() as u32, self.file),
147 ));
148
149 (tokens, errors)
150 }
151
152 fn span(&self, start: usize, end: usize) -> Span {
153 Span::new(start as u32, end as u32, self.file)
154 }
155
156 fn skip_trivia(&mut self) -> Result<(), LexError> {
163 let b = self.source.as_bytes();
164 loop {
165 while self.pos < b.len() {
166 match b[self.pos] {
167 b' ' | b'\t' | b'\n' | b'\r' => self.pos += 1,
168 _ => break,
169 }
170 }
171 if self.pos + 1 < b.len() && b[self.pos] == b'/' && b[self.pos + 1] == b'/' {
172 let doc = b.get(self.pos + 2) == Some(&b'/') && b.get(self.pos + 3) != Some(&b'/');
173 if doc {
174 break;
175 }
176 let start = self.pos;
177 while self.pos < b.len() && b[self.pos] != b'\n' {
178 self.pos += 1;
179 }
180 let end = if self.pos > start && b[self.pos - 1] == b'\r' {
181 self.pos - 1
182 } else {
183 self.pos
184 };
185 self.comments.push(Comment {
186 kind: CommentKind::Line,
187 span: self.span(start, end),
188 });
189 continue;
190 }
191 if self.pos + 1 < b.len() && b[self.pos] == b'/' && b[self.pos + 1] == b'*' {
192 let start = self.pos;
193 self.pos += 2;
194 while self.pos + 1 < b.len() && !(b[self.pos] == b'*' && b[self.pos + 1] == b'/') {
195 self.pos += 1;
196 }
197 if self.pos + 1 >= b.len() {
198 self.pos = b.len();
199 return Err(LexError::UnterminatedBlockComment(
200 self.span(start, b.len()),
201 ));
202 }
203 self.pos += 2;
204 self.comments.push(Comment {
205 kind: CommentKind::Block,
206 span: self.span(start, self.pos),
207 });
208 continue;
209 }
210 break;
211 }
212 Ok(())
213 }
214
215 fn next_token(&mut self) -> Result<Option<Spanned<Token>>, LexError> {
216 if self.in_string {
219 if self.pos >= self.source.len() {
220 return Ok(None);
221 }
222 return self.lex_string_content();
223 }
224
225 self.skip_trivia()?;
230
231 if self.pos >= self.source.len() {
232 return Ok(None);
233 }
234
235 if self.source.as_bytes()[self.pos] == b'"' {
237 return self.lex_string_start();
238 }
239
240 if self.pos + 1 < self.source.len()
242 && self.source.as_bytes()[self.pos] == b'r'
243 && (self.source.as_bytes()[self.pos + 1] == b'"'
244 || self.source.as_bytes()[self.pos + 1] == b'#')
245 {
246 return self.lex_raw_string();
247 }
248
249 self.lex_logos()
251 }
252
253 fn lex_string_start(&mut self) -> Result<Option<Spanned<Token>>, LexError> {
254 let start = self.pos;
255 self.pos += 1; self.in_string = true;
257 Ok(Some(Spanned::new(
259 Token::StrStart,
260 self.span(start, self.pos),
261 )))
262 }
263
264 fn lex_string_content(&mut self) -> Result<Option<Spanned<Token>>, LexError> {
265 let src = self.source.as_bytes();
266 let start = self.pos;
267
268 if src[self.pos] == b'"' {
270 self.pos += 1;
271 self.in_string = false;
272 return Ok(Some(Spanned::new(
273 Token::StrEnd,
274 self.span(start, self.pos),
275 )));
276 }
277
278 if self.pos + 1 < src.len() && src[self.pos] == b'$' && src[self.pos + 1] == b'{' {
280 self.pos += 2;
281 self.in_string = false; self.interp.push(InterpFrame {
283 open: start,
284 braces: 0,
285 });
286 return Ok(Some(Spanned::new(
287 Token::InterpOpen,
288 self.span(start, self.pos),
289 )));
290 }
291
292 let mut buf = String::new();
294 while self.pos < src.len() && src[self.pos] != b'"' {
295 if self.pos + 1 < src.len() && src[self.pos] == b'$' && src[self.pos + 1] == b'{' {
297 break;
298 }
299 if src[self.pos] == b'\\' {
301 self.pos += 1;
302 if self.pos >= src.len() {
303 return Err(LexError::UnterminatedString(self.span(start, self.pos)));
304 }
305 match src[self.pos] {
306 b'n' => {
307 buf.push('\n');
308 self.pos += 1;
309 }
310 b't' => {
311 buf.push('\t');
312 self.pos += 1;
313 }
314 b'r' => {
315 buf.push('\r');
316 self.pos += 1;
317 }
318 b'"' => {
319 buf.push('"');
320 self.pos += 1;
321 }
322 b'\\' => {
323 buf.push('\\');
324 self.pos += 1;
325 }
326 b'0' => {
327 buf.push('\0');
328 self.pos += 1;
329 }
330 b'u' => {
331 self.pos += 1;
332 if self.pos < src.len() && src[self.pos] == b'{' {
333 self.pos += 1;
334 let hex_start = self.pos;
335 while self.pos < src.len() && src[self.pos] != b'}' {
336 self.pos += 1;
337 }
338 let hex = &self.source[hex_start..self.pos];
339 if self.pos < src.len() {
340 self.pos += 1;
341 } match u32::from_str_radix(hex, 16).ok().and_then(char::from_u32) {
343 Some(c) => buf.push(c),
344 None => {
345 return Err(LexError::InvalidUnicodeEscape(
346 self.span(start, self.pos),
347 ))
348 }
349 }
350 } else {
351 return Err(LexError::InvalidEscapeSequence(
352 'u',
353 self.span(start, self.pos),
354 ));
355 }
356 }
357 c => {
358 return Err(LexError::InvalidEscapeSequence(
359 c as char,
360 self.span(start, self.pos),
361 ));
362 }
363 }
364 } else {
365 let ch_len = utf8_char_width(src[self.pos]);
367 let slice = &self.source[self.pos..self.pos + ch_len];
368 buf.push_str(slice);
369 self.pos += ch_len;
370 }
371 }
372
373 if self.pos >= src.len() && !buf.is_empty() {
374 return Err(LexError::UnterminatedString(self.span(start, self.pos)));
375 }
376
377 let end = self.pos;
378 Ok(Some(Spanned::new(
379 Token::StrPart(buf),
380 self.span(start, end),
381 )))
382 }
383
384 fn lex_raw_string(&mut self) -> Result<Option<Spanned<Token>>, LexError> {
385 let start = self.pos;
386 self.pos += 1; let mut hash_count = 0usize;
388 let src = self.source.as_bytes();
389
390 while self.pos < src.len() && src[self.pos] == b'#' {
391 hash_count += 1;
392 self.pos += 1;
393 }
394
395 if self.pos >= src.len() || src[self.pos] != b'"' {
396 return Err(LexError::InvalidNumberLiteral(self.span(start, self.pos)));
397 }
398 self.pos += 1; let mut content = String::new();
401 loop {
402 if self.pos >= src.len() {
403 return Err(LexError::UnterminatedString(self.span(start, self.pos)));
404 }
405 if src[self.pos] == b'"' {
406 let mut end_hashes = 0;
408 let mut look = self.pos + 1;
409 while look < src.len() && src[look] == b'#' && end_hashes < hash_count {
410 end_hashes += 1;
411 look += 1;
412 }
413 if end_hashes == hash_count {
414 self.pos = look;
415 break;
416 }
417 }
418 let ch_len = utf8_char_width(src[self.pos]);
419 content.push_str(&self.source[self.pos..self.pos + ch_len]);
420 self.pos += ch_len;
421 }
422
423 Ok(Some(Spanned::new(
424 Token::RawStr(content),
425 self.span(start, self.pos),
426 )))
427 }
428
429 fn maybe_close_interp(&mut self, start: usize) -> Spanned<Token> {
436 let span = self.span(start, self.pos);
437 match self.interp.last_mut() {
438 Some(frame) if frame.braces > 0 => {
439 frame.braces -= 1;
440 Spanned::new(Token::RBrace, span)
441 }
442 Some(_) => {
443 self.interp.pop();
444 self.in_string = true;
445 Spanned::new(Token::InterpClose, span)
446 }
447 None => Spanned::new(Token::RBrace, span),
448 }
449 }
450
451 fn lex_logos(&mut self) -> Result<Option<Spanned<Token>>, LexError> {
452 use logos::Logos;
453 let remaining = &self.source[self.pos..];
455 let mut lex = RawToken::lexer(remaining);
456
457 match lex.next() {
458 None => {
459 self.pos = self.source.len();
460 Ok(None)
461 }
462 Some(Ok(raw)) => {
463 let tok_span = lex.span();
468 let tok_start = self.pos + tok_span.start;
469 let tok_end = self.pos + tok_span.end;
470 self.pos = tok_end;
471 let span = self.span(tok_start, tok_end);
472
473 match raw {
477 RawToken::LBrace => {
478 if let Some(frame) = self.interp.last_mut() {
479 frame.braces += 1;
480 }
481 }
482 RawToken::RBrace => return Ok(Some(self.maybe_close_interp(tok_start))),
483 _ => {}
484 }
485
486 let tok = raw_to_token(raw, lex.slice());
487 Ok(Some(Spanned::new(tok, span)))
488 }
489 Some(Err(())) => {
490 let err_offset = lex.span().start;
493 let ch = remaining[err_offset..].chars().next().unwrap_or('?');
494 let err_start = self.pos + err_offset;
495 self.pos = err_start + ch.len_utf8();
496 Err(LexError::UnexpectedCharacter(
497 ch,
498 self.span(err_start, self.pos),
499 ))
500 }
501 }
502 }
503}
504
505fn utf8_char_width(b: u8) -> usize {
506 match b {
507 0x00..=0x7F => 1,
508 0xC0..=0xDF => 2,
509 0xE0..=0xEF => 3,
510 0xF0..=0xFF => 4,
511 _ => 1,
512 }
513}
514
515fn raw_to_token(raw: RawToken, slice: &str) -> Token {
517 match raw {
518 RawToken::Let => Token::Let,
519 RawToken::Mut => Token::Mut,
520 RawToken::Const => Token::Const,
521 RawToken::Fn => Token::Fn,
522 RawToken::Return => Token::Return,
523 RawToken::If => Token::If,
524 RawToken::Else => Token::Else,
525 RawToken::While => Token::While,
526 RawToken::For => Token::For,
527 RawToken::In => Token::In,
528 RawToken::Break => Token::Break,
529 RawToken::Continue => Token::Continue,
530 RawToken::Match => Token::Match,
531 RawToken::Type => Token::Type,
532 RawToken::Record => Token::Record,
533 RawToken::Trait => Token::Trait,
534 RawToken::Impl => Token::Impl,
535 RawToken::Import => Token::Import,
536 RawToken::Module => Token::Module,
537 RawToken::Pub => Token::Pub,
538 RawToken::Async => Token::Async,
539 RawToken::Await => Token::Await,
540 RawToken::Extern => Token::Extern,
541 RawToken::Unsafe => Token::Unsafe,
542 RawToken::True => Token::True,
543 RawToken::False => Token::False,
544 RawToken::As => Token::As,
545 RawToken::Is => Token::Is,
546 RawToken::Where => Token::Where,
547 RawToken::With => Token::With,
548 RawToken::SelfLower => Token::SelfLower,
549 RawToken::SelfUpper => Token::SelfUpper,
550 RawToken::LParen => Token::LParen,
551 RawToken::RParen => Token::RParen,
552 RawToken::LBracket => Token::LBracket,
553 RawToken::RBracket => Token::RBracket,
554 RawToken::LBrace => Token::LBrace,
555 RawToken::RBrace => Token::RBrace, RawToken::Comma => Token::Comma,
557 RawToken::Semicolon => Token::Semicolon,
558 RawToken::Colon => Token::Colon,
559 RawToken::ColonColon => Token::ColonColon,
560 RawToken::Arrow => Token::Arrow,
561 RawToken::FatArrow => Token::FatArrow,
562 RawToken::Dot => Token::Dot,
563 RawToken::DotDot => Token::DotDot,
564 RawToken::DotDotEq => Token::DotDotEq,
565 RawToken::At => Token::At,
566 RawToken::Question => Token::Question,
567 RawToken::Eq => Token::Eq,
568 RawToken::EqEq => Token::EqEq,
569 RawToken::BangEq => Token::BangEq,
570 RawToken::Lt => Token::Lt,
571 RawToken::LtEq => Token::LtEq,
572 RawToken::Gt => Token::Gt,
573 RawToken::GtEq => Token::GtEq,
574 RawToken::AmpAmp => Token::AmpAmp,
575 RawToken::PipePipe => Token::PipePipe,
576 RawToken::Bang => Token::Bang,
577 RawToken::Plus => Token::Plus,
578 RawToken::Minus => Token::Minus,
579 RawToken::Star => Token::Star,
580 RawToken::Slash => Token::Slash,
581 RawToken::Percent => Token::Percent,
582 RawToken::PlusEq => Token::PlusEq,
583 RawToken::MinusEq => Token::MinusEq,
584 RawToken::StarEq => Token::StarEq,
585 RawToken::SlashEq => Token::SlashEq,
586 RawToken::PercentEq => Token::PercentEq,
587 RawToken::Amp => Token::Amp,
588 RawToken::Pipe => Token::Pipe,
589 RawToken::Caret => Token::Caret,
590 RawToken::Tilde => Token::Tilde,
591 RawToken::LtLt => Token::LtLt,
592 RawToken::GtGt => Token::GtGt,
593 RawToken::AmpEq => Token::AmpEq,
594 RawToken::PipeEq => Token::PipeEq,
595 RawToken::CaretEq => Token::CaretEq,
596 RawToken::LtLtEq => Token::LtLtEq,
597 RawToken::GtGtEq => Token::GtGtEq,
598 RawToken::Float(f) => Token::Float(f),
599 RawToken::Int(n) => Token::Int(n),
600 RawToken::Char(c) => Token::Char(c),
601 RawToken::DocComment => Token::DocComment(slice[3..].trim_end().to_owned()),
604 RawToken::Ident => Token::Ident(slice.to_owned()),
605 }
606}
607
608#[derive(logos::Logos, Debug, Clone, PartialEq)]
613#[logos(skip r"[ \t\n\r]+")]
614#[logos(skip r"//[^\n]*")] #[logos(skip r"/\*([^*]|\*[^/])*\*/")] enum RawToken {
617 #[token("let")]
619 Let,
620 #[token("mut")]
621 Mut,
622 #[token("const")]
623 Const,
624 #[token("fn")]
625 Fn,
626 #[token("return")]
627 Return,
628 #[token("if")]
629 If,
630 #[token("else")]
631 Else,
632 #[token("while")]
633 While,
634 #[token("for")]
635 For,
636 #[token("in")]
637 In,
638 #[token("break")]
639 Break,
640 #[token("continue")]
641 Continue,
642 #[token("match")]
643 Match,
644 #[token("type")]
645 Type,
646 #[token("record")]
647 Record,
648 #[token("trait")]
649 Trait,
650 #[token("impl")]
651 Impl,
652 #[token("import")]
653 Import,
654 #[token("module")]
655 Module,
656 #[token("pub")]
657 Pub,
658 #[token("async")]
659 Async,
660 #[token("await")]
661 Await,
662 #[token("extern")]
663 Extern,
664 #[token("unsafe")]
665 Unsafe,
666 #[token("true")]
667 True,
668 #[token("false")]
669 False,
670 #[token("as")]
671 As,
672 #[token("is")]
673 Is,
674 #[token("where")]
675 Where,
676 #[token("with")]
677 With,
678 #[token("self")]
679 SelfLower,
680 #[token("Self")]
681 SelfUpper,
682
683 #[token("::")]
685 ColonColon,
686 #[token("->")]
687 Arrow,
688 #[token("=>")]
689 FatArrow,
690 #[token("..")]
691 DotDot,
692 #[token("..=")]
693 DotDotEq,
694 #[token("==")]
695 EqEq,
696 #[token("!=")]
697 BangEq,
698 #[token("<=")]
699 LtEq,
700 #[token(">=")]
701 GtEq,
702 #[token("&&")]
703 AmpAmp,
704 #[token("||")]
705 PipePipe,
706 #[token("+=")]
707 PlusEq,
708 #[token("-=")]
709 MinusEq,
710 #[token("*=")]
711 StarEq,
712 #[token("/=")]
713 SlashEq,
714 #[token("%=")]
715 PercentEq,
716 #[token("&=")]
717 AmpEq,
718 #[token("|=")]
719 PipeEq,
720 #[token("^=")]
721 CaretEq,
722 #[token("<<=")]
723 LtLtEq,
724 #[token(">>=")]
725 GtGtEq,
726 #[token("<<")]
727 LtLt,
728 #[token(">>")]
729 GtGt,
730
731 #[token("(")]
733 LParen,
734 #[token(")")]
735 RParen,
736 #[token("[")]
737 LBracket,
738 #[token("]")]
739 RBracket,
740 #[token("{")]
741 LBrace,
742 #[token("}")]
743 RBrace,
744 #[token(",")]
745 Comma,
746 #[token(";")]
747 Semicolon,
748 #[token(":")]
749 Colon,
750 #[token(".")]
751 Dot,
752 #[token("@")]
753 At,
754 #[token("?")]
755 Question,
756 #[token("=")]
757 Eq,
758 #[token("<")]
759 Lt,
760 #[token(">")]
761 Gt,
762 #[token("!")]
763 Bang,
764 #[token("+")]
765 Plus,
766 #[token("-")]
767 Minus,
768 #[token("*")]
769 Star,
770 #[token("/")]
771 Slash,
772 #[token("%")]
773 Percent,
774 #[token("&")]
775 Amp,
776 #[token("|")]
777 Pipe,
778 #[token("^")]
779 Caret,
780 #[token("~")]
781 Tilde,
782
783 #[regex(r"[0-9][0-9_]*\.[0-9][0-9_]*([eE][+-]?[0-9]+)?", lex_float)]
785 #[regex(r"[0-9][0-9_]*[eE][+-]?[0-9]+", lex_float)]
786 Float(f64),
787
788 #[regex(r"0x[0-9a-fA-F][0-9a-fA-F_]*", lex_hex_int)]
790 #[regex(r"0b[01][01_]*", lex_bin_int)]
791 #[regex(r"0o[0-7][0-7_]*", lex_oct_int)]
792 #[regex(r"[0-9][0-9_]*", lex_dec_int)]
793 Int(i64),
794
795 #[regex(r"'([^'\\]|\\.)'", lex_char)]
797 Char(char),
798
799 #[regex(r"///[^\n]*")]
801 DocComment,
802
803 #[regex(r"[a-zA-Z_][a-zA-Z0-9_]*")]
805 Ident,
806}
807
808fn lex_hex_int(lex: &mut logos::Lexer<RawToken>) -> Option<i64> {
809 let s = lex.slice()[2..].replace('_', "");
810 i64::from_str_radix(&s, 16).ok()
811}
812
813fn lex_bin_int(lex: &mut logos::Lexer<RawToken>) -> Option<i64> {
814 let s = lex.slice()[2..].replace('_', "");
815 i64::from_str_radix(&s, 2).ok()
816}
817
818fn lex_oct_int(lex: &mut logos::Lexer<RawToken>) -> Option<i64> {
819 let s = lex.slice()[2..].replace('_', "");
820 i64::from_str_radix(&s, 8).ok()
821}
822
823fn lex_dec_int(lex: &mut logos::Lexer<RawToken>) -> Option<i64> {
824 lex.slice().replace('_', "").parse().ok()
825}
826
827fn lex_float(lex: &mut logos::Lexer<RawToken>) -> Option<f64> {
828 lex.slice().replace('_', "").parse().ok()
829}
830
831fn lex_char(lex: &mut logos::Lexer<RawToken>) -> Option<char> {
832 let s = lex.slice();
833 let inner = &s[1..s.len() - 1];
835 if inner.starts_with('\\') {
836 match inner.as_bytes().get(1)? {
837 b'n' => Some('\n'),
838 b't' => Some('\t'),
839 b'r' => Some('\r'),
840 b'\\' => Some('\\'),
841 b'\'' => Some('\''),
842 b'0' => Some('\0'),
843 _ => None,
844 }
845 } else {
846 inner.chars().next()
847 }
848}