1use crate::tokens::{Span, StringPart, Token, TokenKind};
6
7#[allow(dead_code)]
9#[allow(missing_docs)]
10pub struct LexerModeStack {
11 pub stack: Vec<LexerMode>,
13}
14impl LexerModeStack {
15 #[allow(dead_code)]
17 pub fn new() -> Self {
18 LexerModeStack {
19 stack: vec![LexerMode::Normal],
20 }
21 }
22 #[allow(dead_code)]
24 pub fn push(&mut self, mode: LexerMode) {
25 self.stack.push(mode);
26 }
27 #[allow(dead_code)]
29 pub fn pop(&mut self) -> Option<LexerMode> {
30 if self.stack.len() > 1 {
31 self.stack.pop()
32 } else {
33 None
34 }
35 }
36 #[allow(dead_code)]
38 pub fn current(&self) -> &LexerMode {
39 self.stack.last().unwrap_or(&LexerMode::Normal)
40 }
41 #[allow(dead_code)]
43 pub fn depth(&self) -> usize {
44 self.stack.len()
45 }
46}
47pub struct Lexer {
59 input: Vec<char>,
61 pos: usize,
63 line: usize,
65 column: usize,
67}
68impl Lexer {
69 pub fn new(input: &str) -> Self {
71 Self {
72 input: input.chars().collect(),
73 pos: 0,
74 line: 1,
75 column: 1,
76 }
77 }
78 fn peek(&self) -> Option<char> {
80 self.input.get(self.pos).copied()
81 }
82 fn peek_ahead(&self, offset: usize) -> Option<char> {
84 self.input.get(self.pos + offset).copied()
85 }
86 fn advance(&mut self) -> Option<char> {
88 let ch = self.peek()?;
89 self.pos += 1;
90 if ch == '\n' {
91 self.line += 1;
92 self.column = 1;
93 } else {
94 self.column += 1;
95 }
96 Some(ch)
97 }
98 fn skip_whitespace(&mut self) {
100 while let Some(ch) = self.peek() {
101 if ch.is_whitespace() {
102 self.advance();
103 } else if ch == '-' && self.peek_ahead(1) == Some('-') {
104 if self.peek_ahead(2) == Some('-') {
105 break;
106 }
107 self.advance();
108 self.advance();
109 while let Some(ch) = self.peek() {
110 if ch == '\n' {
111 break;
112 }
113 self.advance();
114 }
115 } else if ch == '/' && self.peek_ahead(1) == Some('-') {
116 if self.peek_ahead(2) == Some('-') {
117 break;
118 }
119 self.advance();
120 self.advance();
121 let mut depth = 1;
122 while depth > 0 {
123 match self.peek() {
124 Some('/') if self.peek_ahead(1) == Some('-') => {
125 self.advance();
126 self.advance();
127 depth += 1;
128 }
129 Some('-') if self.peek_ahead(1) == Some('/') => {
130 self.advance();
131 self.advance();
132 depth -= 1;
133 }
134 Some(_) => {
135 self.advance();
136 }
137 None => break,
138 }
139 }
140 } else {
141 break;
142 }
143 }
144 }
145 fn lex_line_doc_comment(&mut self, start: usize, start_line: usize, start_col: usize) -> Token {
147 self.advance();
148 self.advance();
149 self.advance();
150 if self.peek() == Some(' ') {
151 self.advance();
152 }
153 let mut s = String::new();
154 while let Some(ch) = self.peek() {
155 if ch == '\n' {
156 break;
157 }
158 s.push(ch);
159 self.advance();
160 }
161 Token::new(
162 TokenKind::DocComment(s),
163 Span::new(start, self.pos, start_line, start_col),
164 )
165 }
166 fn lex_block_doc_comment(
168 &mut self,
169 start: usize,
170 start_line: usize,
171 start_col: usize,
172 ) -> Token {
173 self.advance();
174 self.advance();
175 self.advance();
176 if self.peek() == Some(' ') {
177 self.advance();
178 }
179 let mut s = String::new();
180 let mut depth = 1;
181 while depth > 0 {
182 match self.peek() {
183 Some('/') if self.peek_ahead(1) == Some('-') => {
184 self.advance();
185 self.advance();
186 depth += 1;
187 if depth > 1 {
188 s.push_str("/-");
189 }
190 }
191 Some('-') if self.peek_ahead(1) == Some('/') => {
192 depth -= 1;
193 if depth > 0 {
194 s.push_str("-/");
195 }
196 self.advance();
197 self.advance();
198 }
199 Some(ch) => {
200 s.push(ch);
201 self.advance();
202 }
203 None => break,
204 }
205 }
206 let s = s.trim_end().to_string();
207 Token::new(
208 TokenKind::DocComment(s),
209 Span::new(start, self.pos, start_line, start_col),
210 )
211 }
212 fn lex_ident(&mut self, start: usize, start_line: usize, start_col: usize) -> Token {
216 let mut s = String::new();
217 if self.peek() == Some('_')
218 && !self
219 .peek_ahead(1)
220 .is_some_and(|c| c.is_alphanumeric() || c == '_')
221 {
222 self.advance();
223 return Token::new(
224 TokenKind::Underscore,
225 Span::new(start, self.pos, start_line, start_col),
226 );
227 }
228 while let Some(ch) = self.peek() {
229 if ch.is_alphanumeric() || ch == '_' || ch == '\'' {
230 s.push(ch);
231 self.advance();
232 } else {
233 break;
234 }
235 }
236 let kind = match s.as_str() {
237 "axiom" => TokenKind::Axiom,
238 "definition" | "def" => TokenKind::Definition,
239 "theorem" => TokenKind::Theorem,
240 "lemma" => TokenKind::Lemma,
241 "opaque" => TokenKind::Opaque,
242 "inductive" => TokenKind::Inductive,
243 "structure" => TokenKind::Structure,
244 "class" => TokenKind::Class,
245 "instance" => TokenKind::Instance,
246 "namespace" => TokenKind::Namespace,
247 "section" => TokenKind::Section,
248 "variable" => TokenKind::Variable,
249 "variables" => TokenKind::Variables,
250 "parameter" => TokenKind::Parameter,
251 "parameters" => TokenKind::Parameters,
252 "constant" => TokenKind::Constant,
253 "constants" => TokenKind::Constants,
254 "end" => TokenKind::End,
255 "import" => TokenKind::Import,
256 "export" => TokenKind::Export,
257 "open" => TokenKind::Open,
258 "attribute" => TokenKind::Attribute,
259 "return" => TokenKind::Return,
260 "Type" => TokenKind::Type,
261 "Prop" => TokenKind::Prop,
262 "Sort" => TokenKind::Sort,
263 "fun" => TokenKind::Fun,
264 "forall" => TokenKind::Forall,
265 "let" => TokenKind::Let,
266 "in" => TokenKind::In,
267 "if" => TokenKind::If,
268 "then" => TokenKind::Then,
269 "else" => TokenKind::Else,
270 "match" => TokenKind::Match,
271 "with" => TokenKind::With,
272 "do" => TokenKind::Do,
273 "have" => TokenKind::Have,
274 "suffices" => TokenKind::Suffices,
275 "show" => TokenKind::Show,
276 "where" => TokenKind::Where,
277 "from" => TokenKind::From,
278 "by" => TokenKind::By,
279 "exists" => TokenKind::Exists,
280 _ => TokenKind::Ident(s),
281 };
282 Token::new(kind, Span::new(start, self.pos, start_line, start_col))
283 }
284 fn lex_hex_number(&mut self, start: usize, start_line: usize, start_col: usize) -> Token {
286 let mut num = 0u64;
287 let mut has_digits = false;
288 while let Some(ch) = self.peek() {
289 if let Some(d) = ch.to_digit(16) {
290 num = num.wrapping_mul(16).wrapping_add(d as u64);
291 has_digits = true;
292 self.advance();
293 } else if ch == '_' {
294 self.advance();
295 } else {
296 break;
297 }
298 }
299 if !has_digits {
300 return Token::new(
301 TokenKind::Error("expected hex digits after 0x".to_string()),
302 Span::new(start, self.pos, start_line, start_col),
303 );
304 }
305 Token::new(
306 TokenKind::Nat(num),
307 Span::new(start, self.pos, start_line, start_col),
308 )
309 }
310 fn lex_bin_number(&mut self, start: usize, start_line: usize, start_col: usize) -> Token {
312 let mut num = 0u64;
313 let mut has_digits = false;
314 while let Some(ch) = self.peek() {
315 match ch {
316 '0' => {
317 num = num.wrapping_mul(2);
318 has_digits = true;
319 self.advance();
320 }
321 '1' => {
322 num = num.wrapping_mul(2).wrapping_add(1);
323 has_digits = true;
324 self.advance();
325 }
326 '_' => {
327 self.advance();
328 }
329 _ => break,
330 }
331 }
332 if !has_digits {
333 return Token::new(
334 TokenKind::Error("expected binary digits after 0b".to_string()),
335 Span::new(start, self.pos, start_line, start_col),
336 );
337 }
338 Token::new(
339 TokenKind::Nat(num),
340 Span::new(start, self.pos, start_line, start_col),
341 )
342 }
343 fn lex_oct_number(&mut self, start: usize, start_line: usize, start_col: usize) -> Token {
345 let mut num = 0u64;
346 let mut has_digits = false;
347 while let Some(ch) = self.peek() {
348 if let Some(d) = ch.to_digit(8) {
349 num = num.wrapping_mul(8).wrapping_add(d as u64);
350 has_digits = true;
351 self.advance();
352 } else if ch == '_' {
353 self.advance();
354 } else {
355 break;
356 }
357 }
358 if !has_digits {
359 return Token::new(
360 TokenKind::Error("expected octal digits after 0o".to_string()),
361 Span::new(start, self.pos, start_line, start_col),
362 );
363 }
364 Token::new(
365 TokenKind::Nat(num),
366 Span::new(start, self.pos, start_line, start_col),
367 )
368 }
369 fn lex_number(&mut self, start: usize, start_line: usize, start_col: usize) -> Token {
371 if self.peek() == Some('0') {
372 match self.peek_ahead(1) {
373 Some('x') | Some('X') => {
374 self.advance();
375 self.advance();
376 return self.lex_hex_number(start, start_line, start_col);
377 }
378 Some('b') | Some('B') => {
379 self.advance();
380 self.advance();
381 return self.lex_bin_number(start, start_line, start_col);
382 }
383 Some('o') | Some('O') => {
384 self.advance();
385 self.advance();
386 return self.lex_oct_number(start, start_line, start_col);
387 }
388 _ => {}
389 }
390 }
391 let mut int_str = String::new();
392 while let Some(ch) = self.peek() {
393 if ch.is_ascii_digit() {
394 int_str.push(ch);
395 self.advance();
396 } else if ch == '_' && self.peek_ahead(1).is_some_and(|c| c.is_ascii_digit()) {
397 self.advance();
398 } else {
399 break;
400 }
401 }
402 let is_float =
403 self.peek() == Some('.') && self.peek_ahead(1).is_some_and(|c| c.is_ascii_digit());
404 if is_float {
405 self.advance();
406 let mut frac_str = String::new();
407 frac_str.push_str(&int_str);
408 frac_str.push('.');
409 while let Some(ch) = self.peek() {
410 if ch.is_ascii_digit() {
411 frac_str.push(ch);
412 self.advance();
413 } else {
414 break;
415 }
416 }
417 if self.peek() == Some('e') || self.peek() == Some('E') {
418 frac_str.push('e');
419 self.advance();
420 if self.peek() == Some('+') || self.peek() == Some('-') {
421 frac_str.push(self.advance().expect("peek confirmed '+' or '-' exists"));
422 }
423 while let Some(ch) = self.peek() {
424 if ch.is_ascii_digit() {
425 frac_str.push(ch);
426 self.advance();
427 } else {
428 break;
429 }
430 }
431 }
432 let val: f64 = frac_str.parse().unwrap_or(0.0);
433 return Token::new(
434 TokenKind::Float(val),
435 Span::new(start, self.pos, start_line, start_col),
436 );
437 }
438 if self.peek() == Some('e') || self.peek() == Some('E') {
439 let mut exp_str = String::new();
440 exp_str.push_str(&int_str);
441 exp_str.push('e');
442 self.advance();
443 if self.peek() == Some('+') || self.peek() == Some('-') {
444 exp_str.push(self.advance().expect("peek confirmed '+' or '-' exists"));
445 }
446 while let Some(ch) = self.peek() {
447 if ch.is_ascii_digit() {
448 exp_str.push(ch);
449 self.advance();
450 } else {
451 break;
452 }
453 }
454 let val: f64 = exp_str.parse().unwrap_or(0.0);
455 return Token::new(
456 TokenKind::Float(val),
457 Span::new(start, self.pos, start_line, start_col),
458 );
459 }
460 let num: u64 = int_str.parse().unwrap_or(0);
461 Token::new(
462 TokenKind::Nat(num),
463 Span::new(start, self.pos, start_line, start_col),
464 )
465 }
466 fn parse_escape_char(&mut self) -> Option<char> {
469 match self.peek() {
470 Some('n') => {
471 self.advance();
472 Some('\n')
473 }
474 Some('t') => {
475 self.advance();
476 Some('\t')
477 }
478 Some('r') => {
479 self.advance();
480 Some('\r')
481 }
482 Some('\\') => {
483 self.advance();
484 Some('\\')
485 }
486 Some('"') => {
487 self.advance();
488 Some('"')
489 }
490 Some('\'') => {
491 self.advance();
492 Some('\'')
493 }
494 Some('0') => {
495 self.advance();
496 Some('\0')
497 }
498 Some('x') => {
499 self.advance();
500 let hi = self.advance().and_then(|c| c.to_digit(16))?;
501 let lo = self.advance().and_then(|c| c.to_digit(16))?;
502 let val = (hi * 16 + lo) as u8;
503 Some(val as char)
504 }
505 Some('u') => {
506 self.advance();
507 if self.peek() != Some('{') {
508 return None;
509 }
510 self.advance();
511 let mut code = 0u32;
512 let mut has_digit = false;
513 while let Some(ch) = self.peek() {
514 if ch == '}' {
515 self.advance();
516 break;
517 }
518 let d = ch.to_digit(16)?;
519 code = code * 16 + d;
520 has_digit = true;
521 self.advance();
522 }
523 if !has_digit {
524 return None;
525 }
526 char::from_u32(code)
527 }
528 Some(ch) => {
529 self.advance();
530 Some(ch)
531 }
532 None => None,
533 }
534 }
535 fn lex_string(&mut self, start: usize, start_line: usize, start_col: usize) -> Token {
537 self.advance();
538 let mut s = String::new();
539 while let Some(ch) = self.peek() {
540 if ch == '"' {
541 self.advance();
542 return Token::new(
543 TokenKind::String(s),
544 Span::new(start, self.pos, start_line, start_col),
545 );
546 } else if ch == '\\' {
547 self.advance();
548 if let Some(escaped) = self.parse_escape_char() {
549 s.push(escaped);
550 } else {
551 s.push('?');
552 }
553 } else {
554 s.push(ch);
555 self.advance();
556 }
557 }
558 Token::new(
559 TokenKind::Error("unterminated string".to_string()),
560 Span::new(start, self.pos, start_line, start_col),
561 )
562 }
563 fn lex_char(&mut self, start: usize, start_line: usize, start_col: usize) -> Token {
565 self.advance();
566 let ch = if self.peek() == Some('\\') {
567 self.advance();
568 match self.parse_escape_char() {
569 Some(c) => c,
570 None => {
571 return Token::new(
572 TokenKind::Error("invalid escape in char literal".to_string()),
573 Span::new(start, self.pos, start_line, start_col),
574 );
575 }
576 }
577 } else {
578 match self.advance() {
579 Some(c) => c,
580 None => {
581 return Token::new(
582 TokenKind::Error("unterminated char literal".to_string()),
583 Span::new(start, self.pos, start_line, start_col),
584 );
585 }
586 }
587 };
588 if self.peek() == Some('\'') {
589 self.advance();
590 Token::new(
591 TokenKind::Char(ch),
592 Span::new(start, self.pos, start_line, start_col),
593 )
594 } else {
595 Token::new(
596 TokenKind::Error("unterminated char literal".to_string()),
597 Span::new(start, self.pos, start_line, start_col),
598 )
599 }
600 }
601 fn lex_interpolated_string(
604 &mut self,
605 start: usize,
606 start_line: usize,
607 start_col: usize,
608 ) -> Token {
609 self.advance();
610 self.advance();
611 self.advance();
612 let mut parts: Vec<StringPart> = Vec::new();
613 let mut current_lit = String::new();
614 while let Some(ch) = self.peek() {
615 if ch == '"' {
616 self.advance();
617 if !current_lit.is_empty() {
618 parts.push(StringPart::Literal(current_lit));
619 }
620 return Token::new(
621 TokenKind::InterpolatedString(parts),
622 Span::new(start, self.pos, start_line, start_col),
623 );
624 } else if ch == '{' {
625 self.advance();
626 if !current_lit.is_empty() {
627 parts.push(StringPart::Literal(current_lit.clone()));
628 current_lit.clear();
629 }
630 let mut interp_tokens = Vec::new();
631 let mut depth = 1;
632 loop {
633 let tok = self.next_token_raw();
634 match &tok.kind {
635 TokenKind::LBrace => depth += 1,
636 TokenKind::RBrace => {
637 depth -= 1;
638 if depth == 0 {
639 break;
640 }
641 }
642 TokenKind::Eof => break,
643 _ => {}
644 }
645 interp_tokens.push(tok);
646 }
647 parts.push(StringPart::Interpolation(interp_tokens));
648 } else if ch == '\\' {
649 self.advance();
650 if let Some(escaped) = self.parse_escape_char() {
651 current_lit.push(escaped);
652 } else {
653 current_lit.push('?');
654 }
655 } else {
656 current_lit.push(ch);
657 self.advance();
658 }
659 }
660 Token::new(
661 TokenKind::Error("unterminated interpolated string".to_string()),
662 Span::new(start, self.pos, start_line, start_col),
663 )
664 }
665 fn next_token_raw(&mut self) -> Token {
668 self.skip_whitespace();
669 let start = self.pos;
670 let start_line = self.line;
671 let start_col = self.column;
672 let Some(ch) = self.peek() else {
673 return Token::new(
674 TokenKind::Eof,
675 Span::new(start, start, start_line, start_col),
676 );
677 };
678 if ch.is_alphabetic() || ch == '_' {
679 return self.lex_ident(start, start_line, start_col);
680 }
681 if ch.is_ascii_digit() {
682 return self.lex_number(start, start_line, start_col);
683 }
684 if ch == '"' {
685 return self.lex_string(start, start_line, start_col);
686 }
687 self.advance();
688 let kind = match ch {
689 '(' => TokenKind::LParen,
690 ')' => TokenKind::RParen,
691 '{' => TokenKind::LBrace,
692 '}' => TokenKind::RBrace,
693 '[' => TokenKind::LBracket,
694 ']' => TokenKind::RBracket,
695 ',' => TokenKind::Comma,
696 ';' => TokenKind::Semicolon,
697 '.' if self.peek() == Some('.') => {
698 self.advance();
699 TokenKind::DotDot
700 }
701 '.' => TokenKind::Dot,
702 '|' if self.peek() == Some('|') => {
703 self.advance();
704 TokenKind::OrOr
705 }
706 '|' => TokenKind::Bar,
707 '@' => TokenKind::At,
708 '#' => TokenKind::Hash,
709 '+' => TokenKind::Plus,
710 '-' if self.peek() == Some('>') => {
711 self.advance();
712 TokenKind::Arrow
713 }
714 '-' => TokenKind::Minus,
715 '>' if self.peek() == Some('=') => {
716 self.advance();
717 TokenKind::Ge
718 }
719 '>' => TokenKind::Gt,
720 '*' => TokenKind::Star,
721 '/' => TokenKind::Slash,
722 '%' => TokenKind::Percent,
723 '^' => TokenKind::Caret,
724 '_' => TokenKind::Underscore,
725 '?' => TokenKind::Question,
726 '!' if self.peek() == Some('=') => {
727 self.advance();
728 TokenKind::BangEq
729 }
730 '!' => TokenKind::Bang,
731 '&' if self.peek() == Some('&') => {
732 self.advance();
733 TokenKind::AndAnd
734 }
735 ':' if self.peek() == Some('=') => {
736 self.advance();
737 TokenKind::Assign
738 }
739 ':' => TokenKind::Colon,
740 '=' if self.peek() == Some('>') => {
741 self.advance();
742 TokenKind::FatArrow
743 }
744 '=' => TokenKind::Eq,
745 '<' if self.peek() == Some('-') => {
746 self.advance();
747 TokenKind::LeftArrow
748 }
749 '<' if self.peek() == Some('=') => {
750 self.advance();
751 TokenKind::Le
752 }
753 '<' => TokenKind::Lt,
754 '\u{2190}' => TokenKind::LeftArrow,
755 '\u{2264}' | '\u{2A7D}' => TokenKind::Le,
756 '\u{2265}' | '\u{2A7E}' => TokenKind::Ge,
757 '\u{2260}' => TokenKind::Ne,
758 '\u{2192}' => TokenKind::Arrow,
759 '\u{21D2}' => TokenKind::FatArrow,
760 '\u{2227}' => TokenKind::And,
761 '\u{2228}' => TokenKind::Or,
762 '\u{00AC}' => TokenKind::Not,
763 '\u{2194}' => TokenKind::Iff,
764 '\u{2200}' => TokenKind::Forall,
765 '\u{2203}' => TokenKind::Exists,
766 '\u{03BB}' => TokenKind::Fun,
767 '\u{27E8}' => TokenKind::LAngle,
768 '\u{27E9}' => TokenKind::RAngle,
769 _ => TokenKind::Error(format!("unexpected character: {}", ch)),
770 };
771 Token::new(kind, Span::new(start, self.pos, start_line, start_col))
772 }
773 pub fn next_token(&mut self) -> Token {
775 self.skip_whitespace();
776 let start = self.pos;
777 let start_line = self.line;
778 let start_col = self.column;
779 let Some(ch) = self.peek() else {
780 return Token::new(
781 TokenKind::Eof,
782 Span::new(start, start, start_line, start_col),
783 );
784 };
785 if ch == '/' && self.peek_ahead(1) == Some('-') && self.peek_ahead(2) == Some('-') {
786 return self.lex_block_doc_comment(start, start_line, start_col);
787 }
788 if ch == '-' && self.peek_ahead(1) == Some('-') && self.peek_ahead(2) == Some('-') {
789 return self.lex_line_doc_comment(start, start_line, start_col);
790 }
791 if ch == 's' && self.peek_ahead(1) == Some('!') && self.peek_ahead(2) == Some('"') {
792 return self.lex_interpolated_string(start, start_line, start_col);
793 }
794 if ch.is_alphabetic() || ch == '_' {
795 return self.lex_ident(start, start_line, start_col);
796 }
797 if ch.is_ascii_digit() {
798 return self.lex_number(start, start_line, start_col);
799 }
800 if ch == '"' {
801 return self.lex_string(start, start_line, start_col);
802 }
803 if ch == '\'' {
804 let is_char_lit = if self.peek_ahead(1) == Some('\\') {
805 true
806 } else {
807 self.peek_ahead(2) == Some('\'')
808 };
809 if is_char_lit {
810 return self.lex_char(start, start_line, start_col);
811 }
812 }
813 self.advance();
814 let kind = match ch {
815 '(' => TokenKind::LParen,
816 ')' => TokenKind::RParen,
817 '{' => TokenKind::LBrace,
818 '}' => TokenKind::RBrace,
819 '[' => TokenKind::LBracket,
820 ']' => TokenKind::RBracket,
821 ',' => TokenKind::Comma,
822 ';' => TokenKind::Semicolon,
823 '.' if self.peek() == Some('.') => {
824 self.advance();
825 TokenKind::DotDot
826 }
827 '.' => TokenKind::Dot,
828 '|' if self.peek() == Some('|') => {
829 self.advance();
830 TokenKind::OrOr
831 }
832 '|' => TokenKind::Bar,
833 '@' => TokenKind::At,
834 '#' => TokenKind::Hash,
835 '+' => TokenKind::Plus,
836 '-' if self.peek() == Some('>') => {
837 self.advance();
838 TokenKind::Arrow
839 }
840 '-' => TokenKind::Minus,
841 '>' if self.peek() == Some('=') => {
842 self.advance();
843 TokenKind::Ge
844 }
845 '>' => TokenKind::Gt,
846 '*' => TokenKind::Star,
847 '/' => TokenKind::Slash,
848 '%' => TokenKind::Percent,
849 '^' => TokenKind::Caret,
850 '_' => TokenKind::Underscore,
851 '?' => TokenKind::Question,
852 '\'' => TokenKind::Error("unexpected tick '".to_string()),
853 '!' if self.peek() == Some('=') => {
854 self.advance();
855 TokenKind::BangEq
856 }
857 '!' => TokenKind::Bang,
858 '&' if self.peek() == Some('&') => {
859 self.advance();
860 TokenKind::AndAnd
861 }
862 ':' if self.peek() == Some('=') => {
863 self.advance();
864 TokenKind::Assign
865 }
866 ':' => TokenKind::Colon,
867 '=' if self.peek() == Some('>') => {
868 self.advance();
869 TokenKind::FatArrow
870 }
871 '=' => TokenKind::Eq,
872 '<' if self.peek() == Some('-') => {
873 self.advance();
874 TokenKind::LeftArrow
875 }
876 '<' if self.peek() == Some('=') => {
877 self.advance();
878 TokenKind::Le
879 }
880 '<' => TokenKind::Lt,
881 '\u{2190}' => TokenKind::LeftArrow,
882 '\u{2264}' | '\u{2A7D}' => TokenKind::Le,
883 '\u{2265}' | '\u{2A7E}' => TokenKind::Ge,
884 '\u{2260}' => TokenKind::Ne,
885 '\u{2192}' => TokenKind::Arrow,
886 '\u{21D2}' => TokenKind::FatArrow,
887 '\u{2227}' => TokenKind::And,
888 '\u{2228}' => TokenKind::Or,
889 '\u{00AC}' => TokenKind::Not,
890 '\u{2194}' => TokenKind::Iff,
891 '\u{2200}' => TokenKind::Forall,
892 '\u{2203}' => TokenKind::Exists,
893 '\u{03BB}' => TokenKind::Fun,
894 '\u{27E8}' => TokenKind::LAngle,
895 '\u{27E9}' => TokenKind::RAngle,
896 _ => TokenKind::Error(format!("unexpected character: {}", ch)),
897 };
898 Token::new(kind, Span::new(start, self.pos, start_line, start_col))
899 }
900 pub fn tokenize(&mut self) -> Vec<Token> {
902 let mut tokens = Vec::new();
903 loop {
904 let tok = self.next_token();
905 let is_eof = matches!(tok.kind, TokenKind::Eof);
906 tokens.push(tok);
907 if is_eof {
908 break;
909 }
910 }
911 tokens
912 }
913}
914#[allow(dead_code)]
916#[allow(missing_docs)]
917pub struct Scanner<'a> {
918 src: &'a str,
920 pos: usize,
922 line: usize,
924 col: usize,
926}
927impl<'a> Scanner<'a> {
928 #[allow(dead_code)]
930 pub fn new(src: &'a str) -> Self {
931 Scanner {
932 src,
933 pos: 0,
934 line: 1,
935 col: 1,
936 }
937 }
938 #[allow(dead_code)]
940 pub fn peek(&self) -> Option<char> {
941 self.src[self.pos..].chars().next()
942 }
943 #[allow(dead_code)]
945 #[allow(clippy::should_implement_trait)]
946 pub fn next(&mut self) -> Option<char> {
947 let c = self.src[self.pos..].chars().next()?;
948 self.pos += c.len_utf8();
949 if c == '\n' {
950 self.line += 1;
951 self.col = 1;
952 } else {
953 self.col += 1;
954 }
955 Some(c)
956 }
957 #[allow(dead_code)]
959 pub fn is_eof(&self) -> bool {
960 self.pos >= self.src.len()
961 }
962 #[allow(dead_code)]
964 pub fn position(&self) -> usize {
965 self.pos
966 }
967 #[allow(dead_code)]
969 pub fn line(&self) -> usize {
970 self.line
971 }
972 #[allow(dead_code)]
974 pub fn col(&self) -> usize {
975 self.col
976 }
977 #[allow(dead_code)]
979 pub fn skip_whitespace(&mut self) {
980 while self.peek().map(|c| c.is_whitespace()).unwrap_or(false) {
981 self.next();
982 }
983 }
984 #[allow(dead_code)]
986 pub fn consume_while<F: Fn(char) -> bool>(&mut self, pred: F) -> &'a str {
987 let start = self.pos;
988 while self.peek().is_some_and(&pred) {
989 self.next();
990 }
991 &self.src[start..self.pos]
992 }
993 #[allow(dead_code)]
995 pub fn remaining(&self) -> &'a str {
996 &self.src[self.pos..]
997 }
998}
999#[allow(dead_code)]
1001#[allow(missing_docs)]
1002#[derive(Debug, Clone, Copy)]
1003pub struct LexerPos {
1004 pub offset: usize,
1006 pub line: usize,
1008 pub col: usize,
1010}
1011#[allow(dead_code)]
1013#[allow(missing_docs)]
1014pub struct LexerRuleTable {
1015 pub rules: Vec<LexerRule>,
1017}
1018impl LexerRuleTable {
1019 #[allow(dead_code)]
1021 pub fn new() -> Self {
1022 LexerRuleTable { rules: Vec::new() }
1023 }
1024 #[allow(dead_code)]
1026 pub fn add(&mut self, rule: LexerRule) {
1027 self.rules.push(rule);
1028 }
1029 #[allow(dead_code)]
1031 pub fn find_rule(&self, c: char) -> Option<&LexerRule> {
1032 self.rules.iter().find(|r| r.start.matches(c))
1033 }
1034 #[allow(dead_code)]
1036 pub fn len(&self) -> usize {
1037 self.rules.len()
1038 }
1039 #[allow(dead_code)]
1041 pub fn is_empty(&self) -> bool {
1042 self.rules.is_empty()
1043 }
1044}
1045#[allow(dead_code)]
1047#[allow(missing_docs)]
1048#[derive(Debug, Clone, PartialEq, Eq)]
1049pub enum TokenCategory {
1050 Ident,
1052 Keyword,
1054 Literal,
1056 Operator,
1058 Delimiter,
1060 Trivia,
1062 Eof,
1064}
1065#[allow(dead_code)]
1067#[allow(missing_docs)]
1068#[derive(Debug, Clone)]
1069pub struct TokenDiff {
1070 pub only_left: usize,
1072 pub only_right: usize,
1074 pub matching: usize,
1076}
1077#[allow(dead_code)]
1079#[allow(missing_docs)]
1080#[derive(Debug, Default)]
1081pub struct LexSummary {
1082 pub token_count: usize,
1084 pub ident_count: usize,
1086 pub keyword_count: usize,
1088 pub operator_count: usize,
1090 pub literal_count: usize,
1092 pub comment_count: usize,
1094 pub whitespace_count: usize,
1096}
1097impl LexSummary {
1098 #[allow(dead_code)]
1100 pub fn new() -> Self {
1101 LexSummary::default()
1102 }
1103 #[allow(dead_code)]
1105 pub fn format(&self) -> String {
1106 format!(
1107 "tokens={} idents={} keywords={} ops={} literals={} comments={} ws={}",
1108 self.token_count,
1109 self.ident_count,
1110 self.keyword_count,
1111 self.operator_count,
1112 self.literal_count,
1113 self.comment_count,
1114 self.whitespace_count
1115 )
1116 }
1117}
1118#[allow(dead_code)]
1120#[allow(missing_docs)]
1121pub struct KeywordTrie {
1122 children: std::collections::HashMap<char, KeywordTrie>,
1124 pub is_keyword: bool,
1126}
1127impl KeywordTrie {
1128 #[allow(dead_code)]
1130 pub fn new() -> Self {
1131 KeywordTrie {
1132 children: std::collections::HashMap::new(),
1133 is_keyword: false,
1134 }
1135 }
1136 #[allow(dead_code)]
1138 pub fn insert(&mut self, keyword: &str) {
1139 let mut node = self;
1140 for c in keyword.chars() {
1141 node = node.children.entry(c).or_default();
1142 }
1143 node.is_keyword = true;
1144 }
1145 #[allow(dead_code)]
1147 pub fn contains(&self, s: &str) -> bool {
1148 let mut node = self;
1149 for c in s.chars() {
1150 match node.children.get(&c) {
1151 Some(child) => node = child,
1152 None => return false,
1153 }
1154 }
1155 node.is_keyword
1156 }
1157}
1158#[allow(dead_code)]
1160#[allow(missing_docs)]
1161#[derive(Debug, Clone)]
1162pub struct LexerRule {
1163 pub kind: String,
1165 pub start: CharClass,
1167 pub cont: CharClass,
1169}
1170impl LexerRule {
1171 #[allow(dead_code)]
1173 pub fn new(kind: &str, start: CharClass, cont: CharClass) -> Self {
1174 LexerRule {
1175 kind: kind.to_string(),
1176 start,
1177 cont,
1178 }
1179 }
1180}
1181#[allow(dead_code)]
1183#[allow(missing_docs)]
1184#[derive(Debug, Default)]
1185pub struct TriviaAccumulator {
1186 pub text: String,
1188 pub has_newlines: bool,
1190}
1191impl TriviaAccumulator {
1192 #[allow(dead_code)]
1194 pub fn new() -> Self {
1195 TriviaAccumulator {
1196 text: String::new(),
1197 has_newlines: false,
1198 }
1199 }
1200 #[allow(dead_code)]
1202 pub fn push(&mut self, s: &str) {
1203 if s.contains('\n') {
1204 self.has_newlines = true;
1205 }
1206 self.text.push_str(s);
1207 }
1208 #[allow(dead_code)]
1210 pub fn clear(&mut self) {
1211 self.text.clear();
1212 self.has_newlines = false;
1213 }
1214}
1215#[allow(dead_code)]
1217#[allow(missing_docs)]
1218pub struct CharFreqTable {
1219 pub counts: [u32; 128],
1221}
1222impl CharFreqTable {
1223 #[allow(dead_code)]
1225 #[allow(clippy::should_implement_trait)]
1226 pub fn from_str(src: &str) -> Self {
1227 let mut counts = [0u32; 128];
1228 for c in src.chars() {
1229 if (c as usize) < 128 {
1230 counts[c as usize] += 1;
1231 }
1232 }
1233 CharFreqTable { counts }
1234 }
1235 #[allow(dead_code)]
1237 pub fn count(&self, c: char) -> u32 {
1238 if (c as usize) < 128 {
1239 self.counts[c as usize]
1240 } else {
1241 0
1242 }
1243 }
1244}
1245#[allow(dead_code)]
1247#[allow(missing_docs)]
1248#[derive(Debug, Clone)]
1249pub struct RawToken {
1250 pub kind: String,
1252 pub text: String,
1254 pub start: usize,
1256 pub end: usize,
1258 pub line: usize,
1260 pub col: usize,
1262}
1263#[allow(dead_code)]
1265#[allow(missing_docs)]
1266pub struct FilteredTokenStream {
1267 pub tokens: Vec<RawToken>,
1269 pos: usize,
1271}
1272impl FilteredTokenStream {
1273 #[allow(dead_code)]
1275 pub fn new(tokens: Vec<RawToken>) -> Self {
1276 let tokens = tokens.into_iter().filter(|t| t.kind != "WS").collect();
1277 FilteredTokenStream { tokens, pos: 0 }
1278 }
1279 #[allow(dead_code)]
1281 pub fn peek(&self) -> Option<&RawToken> {
1282 self.tokens.get(self.pos)
1283 }
1284 #[allow(dead_code)]
1286 #[allow(clippy::should_implement_trait)]
1287 pub fn next(&mut self) -> Option<&RawToken> {
1288 let tok = self.tokens.get(self.pos)?;
1289 self.pos += 1;
1290 Some(tok)
1291 }
1292 #[allow(dead_code)]
1294 pub fn is_eof(&self) -> bool {
1295 self.pos >= self.tokens.len()
1296 }
1297 #[allow(dead_code)]
1299 pub fn remaining(&self) -> usize {
1300 self.tokens.len().saturating_sub(self.pos)
1301 }
1302 #[allow(dead_code)]
1304 pub fn position(&self) -> usize {
1305 self.pos
1306 }
1307}
1308#[allow(dead_code)]
1310#[allow(missing_docs)]
1311#[derive(Debug, Clone)]
1312pub enum CharClass {
1313 Alpha,
1315 Digit,
1317 AlphaNum,
1319 Whitespace,
1321 Exact(char),
1323 OneOf(Vec<char>),
1325 NoneOf(Vec<char>),
1327}
1328impl CharClass {
1329 #[allow(dead_code)]
1331 pub fn matches(&self, c: char) -> bool {
1332 match self {
1333 CharClass::Alpha => c.is_ascii_alphabetic(),
1334 CharClass::Digit => c.is_ascii_digit(),
1335 CharClass::AlphaNum => c.is_ascii_alphanumeric(),
1336 CharClass::Whitespace => c.is_whitespace(),
1337 CharClass::Exact(x) => c == *x,
1338 CharClass::OneOf(set) => set.contains(&c),
1339 CharClass::NoneOf(set) => !set.contains(&c),
1340 }
1341 }
1342}
1343#[allow(dead_code)]
1345#[allow(missing_docs)]
1346#[derive(Debug, Clone, PartialEq, Eq)]
1347pub enum LexerMode {
1348 Normal,
1350 StringLit,
1352 Comment,
1354 Tactic,
1356}
1357#[allow(dead_code)]
1359#[allow(missing_docs)]
1360#[derive(Debug, Clone, PartialEq, Eq)]
1361pub enum LexerState {
1362 Start,
1364 Ident,
1366 Number,
1368 Operator,
1370 StringStart,
1372 LineComment,
1374 Done,
1376}
1377#[allow(dead_code)]
1379#[allow(missing_docs)]
1380pub struct LineMap {
1381 pub line_starts: Vec<usize>,
1383}
1384impl LineMap {
1385 #[allow(dead_code)]
1387 #[allow(clippy::should_implement_trait)]
1388 pub fn from_str(src: &str) -> Self {
1389 let mut line_starts = vec![0];
1390 for (i, c) in src.char_indices() {
1391 if c == '\n' {
1392 line_starts.push(i + 1);
1393 }
1394 }
1395 LineMap { line_starts }
1396 }
1397 #[allow(dead_code)]
1399 pub fn line_for_offset(&self, offset: usize) -> usize {
1400 match self.line_starts.binary_search(&offset) {
1401 Ok(idx) => idx + 1,
1402 Err(idx) => idx,
1403 }
1404 }
1405 #[allow(dead_code)]
1407 pub fn col_for_offset(&self, offset: usize) -> usize {
1408 let line = self.line_for_offset(offset);
1409 let line_start = self.line_starts.get(line - 1).copied().unwrap_or(0);
1410 offset - line_start + 1
1411 }
1412 #[allow(dead_code)]
1414 pub fn line_count(&self) -> usize {
1415 self.line_starts.len()
1416 }
1417}