1use crate::{SyntaxKind, SyntaxKind::*};
24use logos::Logos;
25use rowan::{TextRange, TextSize};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct Token {
30 pub kind: SyntaxKind,
32 pub len: u32,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum LexErrorKind {
39 InvalidDigit { digit: char, radix: u32, token: String },
41 CouldNotLex { content: String },
43 BidiOverride,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct LexError {
50 pub range: TextRange,
52 pub kind: LexErrorKind,
54}
55
56fn comment_block(lex: &mut logos::Lexer<LogosToken>) -> bool {
63 let mut last_asterisk = false;
64 for (index, c) in lex.remainder().char_indices() {
65 if c == '*' {
66 last_asterisk = true;
67 } else if c == '/' && last_asterisk {
68 lex.bump(index + 1);
69 return true;
70 } else if matches!(c, '\u{202A}'..='\u{202E}' | '\u{2066}'..='\u{2069}') {
71 lex.bump(index);
74 return true;
75 } else {
76 last_asterisk = false;
77 }
78 }
79 let remaining = lex.remainder().len();
81 lex.bump(remaining);
82 true
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq, Logos)]
90enum LogosToken {
91 #[regex(r"[ \t\f]+")]
95 Whitespace,
96
97 #[regex(r"\r?\n")]
98 Linebreak,
99
100 #[regex(r"//[^\r\n\u{202A}-\u{202E}\u{2066}-\u{2069}]*")]
102 CommentLine,
103
104 #[token(r"/*", comment_block)]
105 CommentBlock,
106
107 #[regex(r"aleo1[a-z0-9]*")]
113 AddressLiteral,
114
115 #[regex(r"0x[0-9A-Za-z_]+([ui](8|16|32|64|128)|field|group|scalar)?", priority = 3)]
120 #[regex(r"0o[0-9A-Za-z_]+([ui](8|16|32|64|128)|field|group|scalar)?", priority = 3)]
121 #[regex(r"0b[0-9A-Za-z_]+([ui](8|16|32|64|128)|field|group|scalar)?", priority = 3)]
122 #[regex(r"[0-9][0-9A-Za-z_]*([ui](8|16|32|64|128)|field|group|scalar)?")]
123 Integer,
124
125 #[regex(r#""[^"]*""#)]
126 StaticString,
127
128 #[regex(r"'[a-zA-Z][a-zA-Z0-9_]*'")]
130 IdentifierLiteral,
131
132 #[regex(r"group::[a-zA-Z][a-zA-Z0-9_]*")]
142 #[regex(r"signature::[a-zA-Z][a-zA-Z0-9_]*")]
143 #[regex(r"Future::[a-zA-Z][a-zA-Z0-9_]*")]
144 PathSpecial,
145
146 #[regex(r"_[a-zA-Z][a-zA-Z0-9_]*")]
148 IdentIntrinsic,
149
150 #[regex(r"[a-zA-Z][a-zA-Z0-9_]*")]
152 Ident,
153
154 #[token("**=")]
158 PowAssign,
159 #[token("&&=")]
160 AndAssign,
161 #[token("||=")]
162 OrAssign,
163 #[token("<<=")]
164 ShlAssign,
165 #[token(">>=")]
166 ShrAssign,
167
168 #[token("**")]
169 Pow,
170 #[token("&&")]
171 And,
172 #[token("||")]
173 Or,
174 #[token("<<")]
175 Shl,
176 #[token(">>")]
177 Shr,
178 #[token("==")]
179 EqEq,
180 #[token("!=")]
181 NotEq,
182 #[token("<=")]
183 LtEq,
184 #[token(">=")]
185 GtEq,
186 #[token("+=")]
187 AddAssign,
188 #[token("-=")]
189 SubAssign,
190 #[token("*=")]
191 MulAssign,
192 #[token("/=")]
193 DivAssign,
194 #[token("%=")]
195 RemAssign,
196 #[token("&=")]
197 BitAndAssign,
198 #[token("|=")]
199 BitOrAssign,
200 #[token("^=")]
201 BitXorAssign,
202
203 #[token("->")]
204 Arrow,
205 #[token("=>")]
206 FatArrow,
207 #[token("..=")]
208 DotDotEq,
209 #[token("..")]
210 DotDot,
211 #[token("::")]
212 ColonColon,
213
214 #[token("=")]
216 Eq,
217 #[token("!")]
218 Bang,
219 #[token("<")]
220 Lt,
221 #[token(">")]
222 Gt,
223 #[token("+")]
224 Plus,
225 #[token("-")]
226 Minus,
227 #[token("*")]
228 Star,
229 #[token("/")]
230 Slash,
231 #[token("%")]
232 Percent,
233 #[token("&")]
234 Amp,
235 #[token("|")]
236 Pipe,
237 #[token("^")]
238 Caret,
239
240 #[token("(")]
244 LParen,
245 #[token(")")]
246 RParen,
247 #[token("[")]
248 LBracket,
249 #[token("]")]
250 RBracket,
251 #[token("{")]
252 LBrace,
253 #[token("}")]
254 RBrace,
255 #[token(",")]
256 Comma,
257 #[token(".")]
258 Dot,
259 #[token(";")]
260 Semicolon,
261 #[token(":")]
262 Colon,
263 #[token("?")]
264 Question,
265 #[token("_")]
266 Underscore,
267 #[token("@")]
268 At,
269
270 #[regex(r"[\u{202A}-\u{202E}\u{2066}-\u{2069}]")]
276 Bidi,
277}
278
279fn ident_to_kind(s: &str) -> SyntaxKind {
281 match s {
282 "true" => KW_TRUE,
284 "false" => KW_FALSE,
285 "none" => KW_NONE,
286 "address" => KW_ADDRESS,
288 "bool" => KW_BOOL,
289 "field" => KW_FIELD,
290 "group" => KW_GROUP,
291 "scalar" => KW_SCALAR,
292 "signature" => KW_SIGNATURE,
293 "string" => KW_STRING,
294 "record" => KW_RECORD,
295 "dyn" => KW_DYN,
296 "identifier" => KW_IDENTIFIER,
297 "i8" => KW_I8,
298 "i16" => KW_I16,
299 "i32" => KW_I32,
300 "i64" => KW_I64,
301 "i128" => KW_I128,
302 "u8" => KW_U8,
303 "u16" => KW_U16,
304 "u32" => KW_U32,
305 "u64" => KW_U64,
306 "u128" => KW_U128,
307 "if" => KW_IF,
309 "else" => KW_ELSE,
310 "for" => KW_FOR,
311 "in" => KW_IN,
312 "return" => KW_RETURN,
313 "let" => KW_LET,
315 "const" => KW_CONST,
316 "constant" => KW_CONSTANT,
317 "final" => KW_FINAL,
318 "Final" => KW_FINAL_UPPER,
319 "view" => KW_VIEW,
320 "fn" => KW_FN,
321 "Fn" => KW_FN_UPPER,
322 "struct" => KW_STRUCT,
323 "constructor" => KW_CONSTRUCTOR,
324 "interface" => KW_INTERFACE,
325 "program" => KW_PROGRAM,
327 "import" => KW_IMPORT,
328 "mapping" => KW_MAPPING,
329 "storage" => KW_STORAGE,
330 "network" => KW_NETWORK,
331 "aleo" => KW_ALEO,
332 "script" => KW_SCRIPT,
333 "block" => KW_BLOCK,
334 "public" => KW_PUBLIC,
336 "private" => KW_PRIVATE,
337 "as" => KW_AS,
338 "self" => KW_SELF,
339 "assert" => KW_ASSERT,
340 "assert_eq" => KW_ASSERT_EQ,
341 "assert_neq" => KW_ASSERT_NEQ,
342 _ => IDENT,
344 }
345}
346
347pub(crate) fn is_keyword(s: &str) -> bool {
348 ident_to_kind(s).is_keyword()
349}
350
351fn strip_int_suffix(s: &str) -> Option<&str> {
353 let suffixes = ["u128", "i128", "u64", "i64", "u32", "i32", "u16", "i16", "u8", "i8"];
355 for suffix in suffixes {
356 if let Some(prefix) = s.strip_suffix(suffix) {
357 return Some(prefix);
358 }
359 }
360 None
361}
362
363fn validate_integer_digits(text: &str, offset: usize, errors: &mut Vec<LexError>) {
366 let num_part = text
368 .strip_suffix("field")
369 .or_else(|| text.strip_suffix("group"))
370 .or_else(|| text.strip_suffix("scalar"))
371 .or_else(|| strip_int_suffix(text))
372 .unwrap_or(text);
373
374 let (digits, radix, _prefix_len): (&str, u32, usize) = if let Some(s) = num_part.strip_prefix("0x") {
376 (s, 16, 2)
377 } else if let Some(s) = num_part.strip_prefix("0X") {
378 (s, 16, 2)
379 } else if let Some(s) = num_part.strip_prefix("0o") {
380 (s, 8, 2)
381 } else if let Some(s) = num_part.strip_prefix("0O") {
382 (s, 8, 2)
383 } else if let Some(s) = num_part.strip_prefix("0b") {
384 (s, 2, 2)
385 } else if let Some(s) = num_part.strip_prefix("0B") {
386 (s, 2, 2)
387 } else {
388 (num_part, 10, 0)
390 };
391
392 for (_, c) in digits.char_indices() {
394 if c == '_' {
395 continue; }
397 if !c.is_digit(radix) {
398 let error_end = offset + num_part.len();
400 errors.push(LexError {
401 range: TextRange::new(TextSize::new(offset as u32), TextSize::new(error_end as u32)),
402 kind: LexErrorKind::InvalidDigit { digit: c, radix, token: num_part.to_string() },
403 });
404 return; }
406 }
407}
408
409pub fn lex(source: &str) -> (Vec<Token>, Vec<LexError>) {
414 let mut tokens = Vec::new();
415 let mut errors = Vec::new();
416 let mut lexer = LogosToken::lexer(source);
417
418 while let Some(result) = lexer.next() {
419 let span = lexer.span();
420 let len = (span.end - span.start) as u32;
421 let slice = lexer.slice();
422
423 let kind = match result {
424 Ok(token) => match token {
425 LogosToken::Whitespace => WHITESPACE,
427 LogosToken::Linebreak => LINEBREAK,
428 LogosToken::CommentLine => COMMENT_LINE,
429 LogosToken::CommentBlock => {
430 if !slice.ends_with("*/") {
432 let preview_len = slice.len().min(10);
433 let preview = &slice[..preview_len];
434 errors.push(LexError {
435 range: TextRange::new(
436 TextSize::new(span.start as u32),
437 TextSize::new((span.start + 2) as u32), ),
439 kind: LexErrorKind::CouldNotLex { content: preview.to_string() },
440 });
441 }
442 COMMENT_BLOCK
443 }
444
445 LogosToken::AddressLiteral => ADDRESS_LIT,
447 LogosToken::Integer => INTEGER,
448 LogosToken::StaticString => STRING,
449 LogosToken::IdentifierLiteral => IDENT_LIT,
450
451 LogosToken::Ident => ident_to_kind(slice),
453 LogosToken::IdentIntrinsic => IDENT,
454 LogosToken::PathSpecial => IDENT, LogosToken::PowAssign => STAR2_EQ,
458 LogosToken::AndAssign => AMP2_EQ,
459 LogosToken::OrAssign => PIPE2_EQ,
460 LogosToken::ShlAssign => SHL_EQ,
461 LogosToken::ShrAssign => SHR_EQ,
462 LogosToken::Pow => STAR2,
463 LogosToken::And => AMP2,
464 LogosToken::Or => PIPE2,
465 LogosToken::Shl => SHL,
466 LogosToken::Shr => SHR,
467 LogosToken::EqEq => EQ2,
468 LogosToken::NotEq => BANG_EQ,
469 LogosToken::LtEq => LT_EQ,
470 LogosToken::GtEq => GT_EQ,
471 LogosToken::AddAssign => PLUS_EQ,
472 LogosToken::SubAssign => MINUS_EQ,
473 LogosToken::MulAssign => STAR_EQ,
474 LogosToken::DivAssign => SLASH_EQ,
475 LogosToken::RemAssign => PERCENT_EQ,
476 LogosToken::BitAndAssign => AMP_EQ,
477 LogosToken::BitOrAssign => PIPE_EQ,
478 LogosToken::BitXorAssign => CARET_EQ,
479 LogosToken::Arrow => ARROW,
480 LogosToken::FatArrow => FAT_ARROW,
481 LogosToken::DotDotEq => DOT_DOT_EQ,
482 LogosToken::DotDot => DOT_DOT,
483 LogosToken::ColonColon => COLON_COLON,
484
485 LogosToken::Eq => EQ,
487 LogosToken::Bang => BANG,
488 LogosToken::Lt => LT,
489 LogosToken::Gt => GT,
490 LogosToken::Plus => PLUS,
491 LogosToken::Minus => MINUS,
492 LogosToken::Star => STAR,
493 LogosToken::Slash => SLASH,
494 LogosToken::Percent => PERCENT,
495 LogosToken::Amp => AMP,
496 LogosToken::Pipe => PIPE,
497 LogosToken::Caret => CARET,
498
499 LogosToken::LParen => L_PAREN,
501 LogosToken::RParen => R_PAREN,
502 LogosToken::LBracket => L_BRACKET,
503 LogosToken::RBracket => R_BRACKET,
504 LogosToken::LBrace => L_BRACE,
505 LogosToken::RBrace => R_BRACE,
506 LogosToken::Comma => COMMA,
507 LogosToken::Dot => DOT,
508 LogosToken::Semicolon => SEMICOLON,
509 LogosToken::Colon => COLON,
510 LogosToken::Question => QUESTION,
511 LogosToken::Underscore => UNDERSCORE,
512 LogosToken::At => AT,
513
514 LogosToken::Bidi => {
516 errors.push(LexError {
517 range: TextRange::new(TextSize::new(span.start as u32), TextSize::new(span.end as u32)),
518 kind: LexErrorKind::BidiOverride,
519 });
520 ERROR
521 }
522 },
523 Err(()) => {
524 errors.push(LexError {
525 range: TextRange::new(TextSize::new(span.start as u32), TextSize::new(span.end as u32)),
526 kind: LexErrorKind::CouldNotLex { content: slice.to_string() },
527 });
528 ERROR
529 }
530 };
531
532 if kind == INTEGER {
534 validate_integer_digits(slice, span.start, &mut errors);
535 }
536
537 tokens.push(Token { kind, len });
538 }
539
540 tokens.push(Token { kind: EOF, len: 0 });
542
543 (tokens, errors)
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549 use expect_test::{Expect, expect};
550
551 fn check_lex(input: &str, expect: Expect) {
553 let (tokens, _errors) = lex(input);
554 let mut output = String::new();
555 let mut offset = 0usize;
556 for token in &tokens {
557 let text = &input[offset..offset + token.len as usize];
558 output.push_str(&format!("{:?} {:?}\n", token.kind, text));
559 offset += token.len as usize;
560 }
561 expect.assert_eq(&output);
562 }
563
564 fn check_lex_errors(input: &str, expect: Expect) {
566 let (_tokens, errors) = lex(input);
567 let output = errors
568 .iter()
569 .map(|e| format!("{}..{}:{:?}", u32::from(e.range.start()), u32::from(e.range.end()), e.kind))
570 .collect::<Vec<_>>()
571 .join("\n");
572 expect.assert_eq(&output);
573 }
574
575 #[test]
576 fn lex_empty() {
577 check_lex("", expect![[r#"
578 EOF ""
579 "#]]);
580 }
581
582 #[test]
583 fn lex_whitespace() {
584 check_lex(" \t ", expect![[r#"
585 WHITESPACE " \t "
586 EOF ""
587 "#]]);
588 }
589
590 #[test]
591 fn lex_linebreaks() {
592 check_lex("\n\r\n\n", expect![[r#"
593 LINEBREAK "\n"
594 LINEBREAK "\r\n"
595 LINEBREAK "\n"
596 EOF ""
597"#]]);
598 }
599
600 #[test]
601 fn lex_mixed_whitespace() {
602 check_lex(" \n \t\n", expect![[r#"
603 WHITESPACE " "
604 LINEBREAK "\n"
605 WHITESPACE " \t"
606 LINEBREAK "\n"
607 EOF ""
608 "#]]);
609 }
610
611 #[test]
612 fn lex_line_comments() {
613 check_lex("// hello\n// world", expect![[r#"
614 COMMENT_LINE "// hello"
615 LINEBREAK "\n"
616 COMMENT_LINE "// world"
617 EOF ""
618 "#]]);
619 }
620
621 #[test]
622 fn lex_block_comments() {
623 check_lex("/* hello */ /* multi\nline */", expect![[r#"
624 COMMENT_BLOCK "/* hello */"
625 WHITESPACE " "
626 COMMENT_BLOCK "/* multi\nline */"
627 EOF ""
628 "#]]);
629 }
630
631 #[test]
632 fn lex_identifiers() {
633 check_lex("foo Bar _baz x123", expect![[r#"
634 IDENT "foo"
635 WHITESPACE " "
636 IDENT "Bar"
637 WHITESPACE " "
638 IDENT "_baz"
639 WHITESPACE " "
640 IDENT "x123"
641 EOF ""
642 "#]]);
643 }
644
645 #[test]
646 fn lex_keywords() {
647 check_lex("let fn if return true false", expect![[r#"
648 KW_LET "let"
649 WHITESPACE " "
650 KW_FN "fn"
651 WHITESPACE " "
652 KW_IF "if"
653 WHITESPACE " "
654 KW_RETURN "return"
655 WHITESPACE " "
656 KW_TRUE "true"
657 WHITESPACE " "
658 KW_FALSE "false"
659 EOF ""
660 "#]]);
661 }
662
663 #[test]
664 fn lex_type_keywords() {
665 check_lex("u8 u16 u32 u64 u128 i8 i16 i32 i64 i128", expect![[r#"
666 KW_U8 "u8"
667 WHITESPACE " "
668 KW_U16 "u16"
669 WHITESPACE " "
670 KW_U32 "u32"
671 WHITESPACE " "
672 KW_U64 "u64"
673 WHITESPACE " "
674 KW_U128 "u128"
675 WHITESPACE " "
676 KW_I8 "i8"
677 WHITESPACE " "
678 KW_I16 "i16"
679 WHITESPACE " "
680 KW_I32 "i32"
681 WHITESPACE " "
682 KW_I64 "i64"
683 WHITESPACE " "
684 KW_I128 "i128"
685 EOF ""
686 "#]]);
687 }
688
689 #[test]
690 fn lex_more_type_keywords() {
691 check_lex("bool field group scalar address signature string record", expect![[r#"
692 KW_BOOL "bool"
693 WHITESPACE " "
694 KW_FIELD "field"
695 WHITESPACE " "
696 KW_GROUP "group"
697 WHITESPACE " "
698 KW_SCALAR "scalar"
699 WHITESPACE " "
700 KW_ADDRESS "address"
701 WHITESPACE " "
702 KW_SIGNATURE "signature"
703 WHITESPACE " "
704 KW_STRING "string"
705 WHITESPACE " "
706 KW_RECORD "record"
707 EOF ""
708 "#]]);
709 }
710
711 #[test]
712 fn lex_identifier_literal() {
713 check_lex("'foo' 'bar_baz' 'x'", expect![[r#"
714 IDENT_LIT "'foo'"
715 WHITESPACE " "
716 IDENT_LIT "'bar_baz'"
717 WHITESPACE " "
718 IDENT_LIT "'x'"
719 EOF ""
720 "#]]);
721 }
722
723 #[test]
724 fn lex_identifier_keyword() {
725 check_lex("identifier", expect![[r#"
726 KW_IDENTIFIER "identifier"
727 EOF ""
728 "#]]);
729 }
730
731 #[test]
732 fn keyword_predicate_matches_identifier_keywords() {
733 assert!(super::is_keyword("view"));
734 assert!(super::is_keyword("dyn"));
735 assert!(super::is_keyword("in"));
736 assert!(!super::is_keyword("inside"));
737 }
738
739 #[test]
740 fn lex_integers() {
741 check_lex("123 0xFF 0b101 0o77", expect![[r#"
742 INTEGER "123"
743 WHITESPACE " "
744 INTEGER "0xFF"
745 WHITESPACE " "
746 INTEGER "0b101"
747 WHITESPACE " "
748 INTEGER "0o77"
749 EOF ""
750 "#]]);
751 }
752
753 #[test]
754 fn lex_integers_with_underscores() {
755 check_lex("1_000_000 0xFF_FF", expect![[r#"
756 INTEGER "1_000_000"
757 WHITESPACE " "
758 INTEGER "0xFF_FF"
759 EOF ""
760 "#]]);
761 }
762
763 #[test]
764 fn lex_address_literal() {
765 check_lex("aleo1abc123", expect![[r#"
766 ADDRESS_LIT "aleo1abc123"
767 EOF ""
768 "#]]);
769 }
770
771 #[test]
772 fn lex_strings() {
773 check_lex(r#""hello" "world""#, expect![[r#"
774 STRING "\"hello\""
775 WHITESPACE " "
776 STRING "\"world\""
777 EOF ""
778 "#]]);
779 }
780
781 #[test]
782 fn lex_punctuation() {
783 check_lex("( ) [ ] { } , . ; : :: ? -> => _ @", expect![[r#"
784 L_PAREN "("
785 WHITESPACE " "
786 R_PAREN ")"
787 WHITESPACE " "
788 L_BRACKET "["
789 WHITESPACE " "
790 R_BRACKET "]"
791 WHITESPACE " "
792 L_BRACE "{"
793 WHITESPACE " "
794 R_BRACE "}"
795 WHITESPACE " "
796 COMMA ","
797 WHITESPACE " "
798 DOT "."
799 WHITESPACE " "
800 SEMICOLON ";"
801 WHITESPACE " "
802 COLON ":"
803 WHITESPACE " "
804 COLON_COLON "::"
805 WHITESPACE " "
806 QUESTION "?"
807 WHITESPACE " "
808 ARROW "->"
809 WHITESPACE " "
810 FAT_ARROW "=>"
811 WHITESPACE " "
812 UNDERSCORE "_"
813 WHITESPACE " "
814 AT "@"
815 EOF ""
816 "#]]);
817 }
818
819 #[test]
820 fn lex_arithmetic_operators() {
821 check_lex("+ - * / % **", expect![[r#"
822 PLUS "+"
823 WHITESPACE " "
824 MINUS "-"
825 WHITESPACE " "
826 STAR "*"
827 WHITESPACE " "
828 SLASH "/"
829 WHITESPACE " "
830 PERCENT "%"
831 WHITESPACE " "
832 STAR2 "**"
833 EOF ""
834 "#]]);
835 }
836
837 #[test]
838 fn lex_comparison_operators() {
839 check_lex("== != < <= > >=", expect![[r#"
840 EQ2 "=="
841 WHITESPACE " "
842 BANG_EQ "!="
843 WHITESPACE " "
844 LT "<"
845 WHITESPACE " "
846 LT_EQ "<="
847 WHITESPACE " "
848 GT ">"
849 WHITESPACE " "
850 GT_EQ ">="
851 EOF ""
852 "#]]);
853 }
854
855 #[test]
856 fn lex_logical_operators() {
857 check_lex("&& || !", expect![[r#"
858 AMP2 "&&"
859 WHITESPACE " "
860 PIPE2 "||"
861 WHITESPACE " "
862 BANG "!"
863 EOF ""
864 "#]]);
865 }
866
867 #[test]
868 fn lex_bitwise_operators() {
869 check_lex("& | ^ << >>", expect![[r#"
870 AMP "&"
871 WHITESPACE " "
872 PIPE "|"
873 WHITESPACE " "
874 CARET "^"
875 WHITESPACE " "
876 SHL "<<"
877 WHITESPACE " "
878 SHR ">>"
879 EOF ""
880 "#]]);
881 }
882
883 #[test]
884 fn lex_assignment_operators() {
885 check_lex("= += -= *= /= %= **= &&= ||=", expect![[r#"
886 EQ "="
887 WHITESPACE " "
888 PLUS_EQ "+="
889 WHITESPACE " "
890 MINUS_EQ "-="
891 WHITESPACE " "
892 STAR_EQ "*="
893 WHITESPACE " "
894 SLASH_EQ "/="
895 WHITESPACE " "
896 PERCENT_EQ "%="
897 WHITESPACE " "
898 STAR2_EQ "**="
899 WHITESPACE " "
900 AMP2_EQ "&&="
901 WHITESPACE " "
902 PIPE2_EQ "||="
903 EOF ""
904 "#]]);
905 }
906
907 #[test]
908 fn lex_more_assignment_operators() {
909 check_lex("&= |= ^= <<= >>=", expect![[r#"
910 AMP_EQ "&="
911 WHITESPACE " "
912 PIPE_EQ "|="
913 WHITESPACE " "
914 CARET_EQ "^="
915 WHITESPACE " "
916 SHL_EQ "<<="
917 WHITESPACE " "
918 SHR_EQ ">>="
919 EOF ""
920 "#]]);
921 }
922
923 #[test]
924 fn lex_dot_dot() {
925 check_lex("0..10", expect![[r#"
926 INTEGER "0"
927 DOT_DOT ".."
928 INTEGER "10"
929 EOF ""
930 "#]]);
931 }
932
933 #[test]
934 fn lex_simple_expression() {
935 check_lex("x + y * 2", expect![[r#"
936 IDENT "x"
937 WHITESPACE " "
938 PLUS "+"
939 WHITESPACE " "
940 IDENT "y"
941 WHITESPACE " "
942 STAR "*"
943 WHITESPACE " "
944 INTEGER "2"
945 EOF ""
946 "#]]);
947 }
948
949 #[test]
950 fn lex_function_call() {
951 check_lex("foo(a, b)", expect![[r#"
952 IDENT "foo"
953 L_PAREN "("
954 IDENT "a"
955 COMMA ","
956 WHITESPACE " "
957 IDENT "b"
958 R_PAREN ")"
959 EOF ""
960 "#]]);
961 }
962
963 #[test]
964 fn lex_function_definition() {
965 check_lex("fn add(x: u32) -> u32 {", expect![[r#"
966 KW_FN "fn"
967 WHITESPACE " "
968 IDENT "add"
969 L_PAREN "("
970 IDENT "x"
971 COLON ":"
972 WHITESPACE " "
973 KW_U32 "u32"
974 R_PAREN ")"
975 WHITESPACE " "
976 ARROW "->"
977 WHITESPACE " "
978 KW_U32 "u32"
979 WHITESPACE " "
980 L_BRACE "{"
981 EOF ""
982 "#]]);
983 }
984
985 #[test]
986 fn lex_let_statement() {
987 check_lex("let x: u32 = 42;", expect![[r#"
988 KW_LET "let"
989 WHITESPACE " "
990 IDENT "x"
991 COLON ":"
992 WHITESPACE " "
993 KW_U32 "u32"
994 WHITESPACE " "
995 EQ "="
996 WHITESPACE " "
997 INTEGER "42"
998 SEMICOLON ";"
999 EOF ""
1000 "#]]);
1001 }
1002
1003 #[test]
1004 fn lex_typed_integers() {
1005 check_lex("1000u32 42i64 0u8 255u128", expect![[r#"
1007 INTEGER "1000u32"
1008 WHITESPACE " "
1009 INTEGER "42i64"
1010 WHITESPACE " "
1011 INTEGER "0u8"
1012 WHITESPACE " "
1013 INTEGER "255u128"
1014 EOF ""
1015 "#]]);
1016 }
1017
1018 #[test]
1019 fn lex_typed_integers_field() {
1020 check_lex("123field 456group 789scalar", expect![[r#"
1022 INTEGER "123field"
1023 WHITESPACE " "
1024 INTEGER "456group"
1025 WHITESPACE " "
1026 INTEGER "789scalar"
1027 EOF ""
1028 "#]]);
1029 }
1030
1031 #[test]
1032 fn lex_special_paths() {
1033 check_lex("group::GEN signature::verify Future::await", expect![[r#"
1035 IDENT "group::GEN"
1036 WHITESPACE " "
1037 IDENT "signature::verify"
1038 WHITESPACE " "
1039 IDENT "Future::await"
1040 EOF ""
1041 "#]]);
1042 }
1043
1044 #[test]
1045 fn lex_typed_integer_range() {
1046 check_lex("0u8..STOP", expect![[r#"
1048 INTEGER "0u8"
1049 DOT_DOT ".."
1050 IDENT "STOP"
1051 EOF ""
1052 "#]]);
1053 }
1054
1055 #[test]
1056 fn lex_error_unknown_char() {
1057 check_lex_errors("hello $ world", expect![[r#"6..7:CouldNotLex { content: "$" }"#]]);
1058 }
1059
1060 #[test]
1061 fn lex_invalid_hex_digit() {
1062 let (tokens, errors) = lex("0xGAu32");
1063 assert_eq!(tokens.len(), 2); assert!(!errors.is_empty());
1065 assert!(matches!(errors[0].kind, LexErrorKind::InvalidDigit { digit: 'G', radix: 16, .. }));
1066 }
1067
1068 #[test]
1069 fn lex_invalid_octal_digit() {
1070 let (_, errors) = lex("0o9u32");
1071 assert!(!errors.is_empty());
1072 assert!(matches!(errors[0].kind, LexErrorKind::InvalidDigit { digit: '9', radix: 8, .. }));
1073 }
1074
1075 #[test]
1076 fn lex_invalid_binary_digit() {
1077 let (_, errors) = lex("0b2u32");
1078 assert!(!errors.is_empty());
1079 assert!(matches!(errors[0].kind, LexErrorKind::InvalidDigit { digit: '2', radix: 2, .. }));
1080 }
1081
1082 #[test]
1083 fn lex_valid_hex_is_ok() {
1084 let (_, errors) = lex("0xDEADBEEFu64");
1085 assert!(errors.is_empty());
1086 }
1087
1088 #[test]
1089 fn lex_invalid_hex_lowercase() {
1090 let (_, errors) = lex("0xghu32");
1092 assert!(!errors.is_empty());
1093 assert!(matches!(errors[0].kind, LexErrorKind::InvalidDigit { digit: 'g', radix: 16, .. }));
1094 }
1095
1096 #[test]
1097 fn lex_bidi_override_error() {
1098 let (_, errors) = lex("let x\u{202E} = 1;");
1099 assert!(!errors.is_empty());
1100 assert!(matches!(errors[0].kind, LexErrorKind::BidiOverride));
1101 }
1102
1103 #[test]
1104 fn lex_unclosed_block_comment() {
1105 let (tokens, errors) = lex("/* unclosed");
1106 assert!(!errors.is_empty());
1107 assert!(matches!(errors[0].kind, LexErrorKind::CouldNotLex { .. }));
1108 assert!(tokens.iter().any(|t| t.kind == COMMENT_BLOCK));
1110 }
1111
1112 #[test]
1113 fn lex_nested_comment_not_supported() {
1114 let (tokens, errors) = lex("/* outer /* inner */");
1116 assert!(errors.is_empty());
1118 assert!(tokens.iter().any(|t| t.kind == COMMENT_BLOCK));
1123 }
1124
1125 #[test]
1126 fn lex_closed_comment_ok() {
1127 let (_, errors) = lex("/* closed */");
1128 assert!(errors.is_empty());
1129 }
1130}