1use logicaffeine_base::Interner;
38use crate::lexicon::{self, Aspect, Definiteness, Lexicon, Time};
39use crate::token::{BlockType, CalendarUnit, FocusKind, MeasureKind, Span, Token, TokenType};
40
41#[derive(Debug, Clone, PartialEq)]
49pub enum LineToken {
50 Indent,
52 Dedent,
54 Newline,
56 Content { text: String, start: usize, end: usize },
58}
59
60pub struct LineLexer<'a> {
63 source: &'a str,
64 bytes: &'a [u8],
65 indent_stack: Vec<usize>,
66 pending_dedents: usize,
67 position: usize,
68 has_pending_content: bool,
70 pending_content_start: usize,
71 pending_content_end: usize,
72 pending_content_text: String,
73 finished_lines: bool,
75 emitted_indent: bool,
77 escape_body_ranges: Vec<(usize, usize)>,
79}
80
81impl<'a> LineLexer<'a> {
82 pub fn new(source: &'a str) -> Self {
83 Self {
84 source,
85 bytes: source.as_bytes(),
86 indent_stack: vec![0],
87 pending_dedents: 0,
88 position: 0,
89 has_pending_content: false,
90 pending_content_start: 0,
91 pending_content_end: 0,
92 pending_content_text: String::new(),
93 finished_lines: false,
94 emitted_indent: false,
95 escape_body_ranges: Vec::new(),
96 }
97 }
98
99 pub fn with_escape_ranges(source: &'a str, escape_body_ranges: Vec<(usize, usize)>) -> Self {
100 Self {
101 source,
102 bytes: source.as_bytes(),
103 indent_stack: vec![0],
104 pending_dedents: 0,
105 position: 0,
106 has_pending_content: false,
107 pending_content_start: 0,
108 pending_content_end: 0,
109 pending_content_text: String::new(),
110 finished_lines: false,
111 emitted_indent: false,
112 escape_body_ranges,
113 }
114 }
115
116 fn is_in_escape_body(&self, pos: usize) -> bool {
118 self.escape_body_ranges.iter().any(|(start, end)| pos >= *start && pos < *end)
119 }
120
121 fn measure_indent(&self, line_start: usize) -> (usize, usize) {
124 let mut indent = 0;
125 let mut pos = line_start;
126
127 while pos < self.bytes.len() {
128 match self.bytes[pos] {
129 b' ' => {
130 indent += 1;
131 pos += 1;
132 }
133 b'\t' => {
134 indent += 4; pos += 1;
136 }
137 _ => break,
138 }
139 }
140
141 (indent, pos)
142 }
143
144 fn read_line_content(&self, content_start: usize) -> (String, usize, usize, usize) {
147 let mut pos = content_start;
148
149 while pos < self.bytes.len() && self.bytes[pos] != b'\n' {
151 pos += 1;
152 }
153
154 let content_end = pos;
155 let text = self.source[content_start..content_end].trim_end().to_string();
156
157 let next_line_start = if pos < self.bytes.len() && self.bytes[pos] == b'\n' {
159 pos + 1
160 } else {
161 pos
162 };
163
164 (text, content_start, content_end, next_line_start)
165 }
166
167 fn is_blank_line(&self, line_start: usize) -> bool {
169 let mut pos = line_start;
170 while pos < self.bytes.len() {
171 match self.bytes[pos] {
172 b' ' | b'\t' => pos += 1,
173 b'\n' => return true,
174 _ => return false,
175 }
176 }
177 true }
179
180 fn process_next_line(&mut self) -> bool {
183 while self.position < self.bytes.len() && self.is_blank_line(self.position) {
185 while self.position < self.bytes.len() && self.bytes[self.position] != b'\n' {
187 self.position += 1;
188 }
189 if self.position < self.bytes.len() {
190 self.position += 1; }
192 }
193
194 if self.position >= self.bytes.len() {
196 self.finished_lines = true;
197 if self.indent_stack.len() > 1 {
199 self.pending_dedents = self.indent_stack.len() - 1;
200 self.indent_stack.truncate(1);
201 }
202 return self.pending_dedents > 0;
203 }
204
205 let (line_indent, content_start) = self.measure_indent(self.position);
207
208 let (text, start, end, next_pos) = self.read_line_content(content_start);
210
211 if text.is_empty() {
213 self.position = next_pos;
214 return self.process_next_line();
215 }
216
217 let current_indent = *self.indent_stack.last().unwrap();
218
219 if line_indent > current_indent {
221 self.indent_stack.push(line_indent);
223 self.emitted_indent = true;
224 self.has_pending_content = true;
226 self.pending_content_text = text;
227 self.pending_content_start = start;
228 self.pending_content_end = end;
229 self.position = next_pos;
230 return true;
232 } else if line_indent < current_indent {
233 while self.indent_stack.len() > 1 {
235 let top = *self.indent_stack.last().unwrap();
236 if line_indent < top {
237 self.indent_stack.pop();
238 self.pending_dedents += 1;
239 } else {
240 break;
241 }
242 }
243 self.has_pending_content = true;
245 self.pending_content_text = text;
246 self.pending_content_start = start;
247 self.pending_content_end = end;
248 self.position = next_pos;
249 return true;
250 } else {
251 self.has_pending_content = true;
253 self.pending_content_text = text;
254 self.pending_content_start = start;
255 self.pending_content_end = end;
256 self.position = next_pos;
257 return true;
258 }
259 }
260}
261
262impl<'a> Iterator for LineLexer<'a> {
263 type Item = LineToken;
264
265 fn next(&mut self) -> Option<LineToken> {
266 if self.pending_dedents > 0 {
268 self.pending_dedents -= 1;
269 return Some(LineToken::Dedent);
270 }
271
272 if self.has_pending_content {
274 self.has_pending_content = false;
275 let text = std::mem::take(&mut self.pending_content_text);
276 let start = self.pending_content_start;
277 let end = self.pending_content_end;
278 return Some(LineToken::Content { text, start, end });
279 }
280
281 if !self.finished_lines {
287 let had_indent = self.indent_stack.len();
288 if self.process_next_line() {
289 if self.indent_stack.len() > had_indent {
291 return Some(LineToken::Indent);
292 }
293 if self.pending_dedents > 0 {
295 self.pending_dedents -= 1;
296 return Some(LineToken::Dedent);
297 }
298 if self.has_pending_content {
300 self.has_pending_content = false;
301 let text = std::mem::take(&mut self.pending_content_text);
302 let start = self.pending_content_start;
303 let end = self.pending_content_end;
304 return Some(LineToken::Content { text, start, end });
305 }
306 } else if self.pending_dedents > 0 {
307 self.pending_dedents -= 1;
309 return Some(LineToken::Dedent);
310 }
311 }
312
313 if self.pending_dedents > 0 {
315 self.pending_dedents -= 1;
316 return Some(LineToken::Dedent);
317 }
318
319 None
320 }
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
328pub enum LexerMode {
329 #[default]
330 Declarative, Imperative, }
333
334pub struct Lexer<'a> {
335 words: Vec<WordItem>,
336 pos: usize,
337 lexicon: Lexicon,
338 interner: &'a mut Interner,
339 input_len: usize,
340 in_let_context: bool,
341 mode: LexerMode,
342 source: String,
343 escape_body_ranges: Vec<(usize, usize)>,
345}
346
347struct WordItem {
348 word: String,
349 trailing_punct: Option<char>,
350 start: usize,
351 end: usize,
352 punct_pos: Option<usize>,
353}
354
355impl<'a> Lexer<'a> {
356 pub fn new(input: &str, interner: &'a mut Interner) -> Self {
380 let escape_ranges = Self::find_escape_block_ranges(input);
381 let escape_body_ranges: Vec<(usize, usize)> = escape_ranges.iter()
382 .map(|(_, end, content_start, _)| (*content_start, *end))
383 .collect();
384 let words = Self::split_into_words(input, &escape_ranges);
385 let input_len = input.len();
386
387 Lexer {
388 words,
389 pos: 0,
390 lexicon: Lexicon::new(),
391 interner,
392 input_len,
393 in_let_context: false,
394 mode: LexerMode::Declarative,
395 source: input.to_string(),
396 escape_body_ranges,
397 }
398 }
399
400 fn find_escape_block_ranges(source: &str) -> Vec<(usize, usize, usize, String)> {
405 let mut ranges = Vec::new();
406 let lines: Vec<&str> = source.split('\n').collect();
407 let mut line_starts: Vec<usize> = Vec::with_capacity(lines.len());
408 let mut pos = 0;
409 for line in &lines {
410 line_starts.push(pos);
411 pos += line.len() + 1; }
413
414 let mut i = 0;
415 while i < lines.len() {
416 let trimmed = lines[i].trim();
417 let lower = trimmed.to_lowercase();
421 if lower == "escape to rust:" ||
422 lower.ends_with(" escape to rust:") ||
423 (lower.starts_with("escape to ") && lower.ends_with(':'))
424 {
425 let header_indent = Self::measure_indent_static(lines[i]);
427 i += 1;
428
429 let mut body_start_line = i;
431 while body_start_line < lines.len() && lines[body_start_line].trim().is_empty() {
432 body_start_line += 1;
433 }
434
435 if body_start_line >= lines.len() {
436 continue;
438 }
439
440 let base_indent = Self::measure_indent_static(lines[body_start_line]);
441 if base_indent <= header_indent {
442 continue;
444 }
445
446 let body_byte_start = line_starts[body_start_line];
448 let mut body_end_line = body_start_line;
449 let mut code_lines: Vec<String> = Vec::new();
450
451 let mut j = body_start_line;
452 while j < lines.len() {
453 let line = lines[j];
454 if line.trim().is_empty() {
455 code_lines.push(String::new());
457 body_end_line = j;
458 j += 1;
459 continue;
460 }
461 let line_indent = Self::measure_indent_static(line);
462 if line_indent < base_indent {
463 break;
464 }
465 let stripped = Self::strip_indent(line, base_indent);
467 code_lines.push(stripped);
468 body_end_line = j;
469 j += 1;
470 }
471
472 while code_lines.last().map_or(false, |l| l.is_empty()) {
474 code_lines.pop();
475 }
476
477 if !code_lines.is_empty() {
478 let body_byte_end = if body_end_line + 1 < lines.len() {
479 line_starts[body_end_line + 1]
480 } else {
481 source.len()
482 };
483 let content_start = body_byte_start + Self::leading_whitespace_bytes(lines[body_start_line]);
485 let raw_code = code_lines.join("\n");
486 ranges.push((body_byte_start, body_byte_end, content_start, raw_code));
487 }
488
489 i = j;
490 } else {
491 i += 1;
492 }
493 }
494
495 ranges
496 }
497
498 fn leading_whitespace_bytes(line: &str) -> usize {
500 let mut count = 0;
501 for c in line.chars() {
502 match c {
503 ' ' | '\t' => count += c.len_utf8(),
504 _ => break,
505 }
506 }
507 count
508 }
509
510 fn measure_indent_static(line: &str) -> usize {
512 let mut indent = 0;
513 for c in line.chars() {
514 match c {
515 ' ' => indent += 1,
516 '\t' => indent += 4,
517 _ => break,
518 }
519 }
520 indent
521 }
522
523 fn strip_indent(line: &str, count: usize) -> String {
525 let mut stripped = 0;
526 let mut byte_pos = 0;
527 for (i, c) in line.char_indices() {
528 if stripped >= count {
529 byte_pos = i;
530 break;
531 }
532 match c {
533 ' ' => { stripped += 1; byte_pos = i + 1; }
534 '\t' => { stripped += 4; byte_pos = i + 1; }
535 _ => { byte_pos = i; break; }
536 }
537 }
538 if stripped < count {
539 byte_pos = line.len();
540 }
541 line[byte_pos..].to_string()
542 }
543
544 fn split_into_words(input: &str, escape_ranges: &[(usize, usize, usize, String)]) -> Vec<WordItem> {
545 let mut items = Vec::new();
546 let mut current_word = String::new();
547 let mut word_start = 0;
548 let chars: Vec<char> = input.chars().collect();
549 let mut char_idx = 0;
550 let mut skip_count = 0;
551 let mut skip_to_byte: Option<usize> = None;
553
554 for (i, c) in input.char_indices() {
555 if skip_count > 0 {
556 skip_count -= 1;
557 char_idx += 1;
558 continue;
559 }
560 if let Some(end) = skip_to_byte {
562 if i < end {
563 char_idx += 1;
564 continue;
565 }
566 skip_to_byte = None;
567 word_start = i;
568 }
569 if let Some((_, end, content_start, raw_code)) = escape_ranges.iter().find(|(s, _, _, _)| i == *s) {
571 if !current_word.is_empty() {
573 items.push(WordItem {
574 word: std::mem::take(&mut current_word),
575 trailing_punct: None,
576 start: word_start,
577 end: i,
578 punct_pos: None,
579 });
580 }
581 items.push(WordItem {
584 word: format!("\x00ESC:{}", raw_code),
585 trailing_punct: None,
586 start: *content_start,
587 end: *end,
588 punct_pos: None,
589 });
590 skip_to_byte = Some(*end);
591 word_start = *end;
592 char_idx += 1;
593 continue;
594 }
595 let next_pos = i + c.len_utf8();
596 match c {
597 ' ' | '\t' | '\n' | '\r' => {
598 if !current_word.is_empty() {
599 items.push(WordItem {
600 word: std::mem::take(&mut current_word),
601 trailing_punct: None,
602 start: word_start,
603 end: i,
604 punct_pos: None,
605 });
606 }
607 word_start = next_pos;
608 }
609 '.' => {
610 let prev_is_digit = !current_word.is_empty()
612 && current_word.chars().last().map_or(false, |ch| ch.is_ascii_digit());
613 let next_is_digit = char_idx + 1 < chars.len()
614 && chars[char_idx + 1].is_ascii_digit();
615
616 if prev_is_digit && next_is_digit {
617 current_word.push(c);
619 } else {
620 if !current_word.is_empty() {
622 items.push(WordItem {
623 word: std::mem::take(&mut current_word),
624 trailing_punct: Some(c),
625 start: word_start,
626 end: i,
627 punct_pos: Some(i),
628 });
629 } else {
630 items.push(WordItem {
631 word: String::new(),
632 trailing_punct: Some(c),
633 start: i,
634 end: next_pos,
635 punct_pos: Some(i),
636 });
637 }
638 word_start = next_pos;
639 }
640 }
641 '#' => {
642 if char_idx + 1 < chars.len() && chars[char_idx + 1] == '#' {
644 if !current_word.is_empty() {
647 items.push(WordItem {
648 word: std::mem::take(&mut current_word),
649 trailing_punct: None,
650 start: word_start,
651 end: i,
652 punct_pos: None,
653 });
654 }
655 let header_start = i;
657 let mut j = char_idx + 2;
658 while j < chars.len() && (chars[j] == ' ' || chars[j] == '\t') {
659 j += 1;
660 }
661 let mut block_word = String::from("##");
663 while j < chars.len() && chars[j].is_alphabetic() {
664 block_word.push(chars[j]);
665 j += 1;
666 }
667 if block_word.len() > 2 {
668 items.push(WordItem {
669 word: block_word,
670 trailing_punct: None,
671 start: header_start,
672 end: header_start + (j - char_idx),
673 punct_pos: None,
674 });
675 }
676 skip_count = j - char_idx - 1;
677 word_start = header_start + (j - char_idx);
678 } else {
679 let mut look_ahead = char_idx + 1;
683 while look_ahead < chars.len() && chars[look_ahead] != '\n' {
684 skip_count += 1;
685 look_ahead += 1;
686 }
687 if !current_word.is_empty() {
688 items.push(WordItem {
689 word: std::mem::take(&mut current_word),
690 trailing_punct: None,
691 start: word_start,
692 end: i,
693 punct_pos: None,
694 });
695 }
696 word_start = look_ahead + 1; }
698 }
699 '"' => {
701 if !current_word.is_empty() {
703 items.push(WordItem {
704 word: std::mem::take(&mut current_word),
705 trailing_punct: None,
706 start: word_start,
707 end: i,
708 punct_pos: None,
709 });
710 }
711
712 if char_idx + 2 < chars.len() && chars[char_idx + 1] == '"' && chars[char_idx + 2] == '"' {
714 let string_start = i;
715 let mut j = char_idx + 3; if j < chars.len() && chars[j] == '\n' {
718 j += 1;
719 }
720 let mut raw_content = String::new();
721 while j < chars.len() {
723 if j + 2 < chars.len() && chars[j] == '"' && chars[j + 1] == '"' && chars[j + 2] == '"' {
724 break;
725 }
726 raw_content.push(chars[j]);
727 j += 1;
728 }
729 if raw_content.ends_with('\n') {
731 raw_content.pop();
732 }
733 let dedented = Self::dedent_triple_quote(&raw_content);
735 let end_pos = if j + 2 < chars.len() { j + 3 } else { chars.len() };
736 items.push(WordItem {
737 word: format!("\x00STR:{}", dedented),
738 trailing_punct: None,
739 start: string_start,
740 end: end_pos,
741 punct_pos: None,
742 });
743 if j + 2 < chars.len() {
745 skip_count = (j + 2) - char_idx;
746 } else {
747 skip_count = chars.len() - 1 - char_idx;
748 }
749 word_start = end_pos;
750 } else {
751 let string_start = i;
753 let mut j = char_idx + 1;
754 let mut string_content = String::new();
755 while j < chars.len() && chars[j] != '"' {
756 if chars[j] == '\\' && j + 1 < chars.len() {
757 j += 1;
759 if j < chars.len() {
760 string_content.push(chars[j]);
761 }
762 } else {
763 string_content.push(chars[j]);
764 }
765 j += 1;
766 }
767
768 items.push(WordItem {
771 word: format!("\x00STR:{}", string_content),
772 trailing_punct: None,
773 start: string_start,
774 end: if j < chars.len() { j + 1 } else { j },
775 punct_pos: None,
776 });
777
778 if j < chars.len() {
780 skip_count = j - char_idx;
781 } else {
782 skip_count = j - char_idx - 1;
783 }
784 word_start = if j < chars.len() { j + 1 } else { j };
785 }
786 }
787 '`' => {
789 if !current_word.is_empty() {
791 items.push(WordItem {
792 word: std::mem::take(&mut current_word),
793 trailing_punct: None,
794 start: word_start,
795 end: i,
796 punct_pos: None,
797 });
798 }
799
800 let char_start = i;
802 let mut j = char_idx + 1;
803 let mut char_content = String::new();
804
805 if j < chars.len() {
806 if chars[j] == '\\' && j + 1 < chars.len() {
807 j += 1;
809 let escaped_char = match chars[j] {
810 'n' => '\n',
811 't' => '\t',
812 'r' => '\r',
813 '\\' => '\\',
814 '`' => '`',
815 '0' => '\0',
816 c => c,
817 };
818 char_content.push(escaped_char);
819 j += 1;
820 } else if chars[j] != '`' {
821 char_content.push(chars[j]);
823 j += 1;
824 }
825 }
826
827 if j < chars.len() && chars[j] == '`' {
829 j += 1; }
831
832 items.push(WordItem {
834 word: format!("\x00CHAR:{}", char_content),
835 trailing_punct: None,
836 start: char_start,
837 end: if j <= chars.len() { char_start + (j - char_idx) } else { char_start + 1 },
838 punct_pos: None,
839 });
840
841 if j > char_idx + 1 {
842 skip_count = j - char_idx - 1;
843 }
844 word_start = char_start + (j - char_idx);
845 }
846 '-' if char_idx + 1 < chars.len() && chars[char_idx + 1] == '>' => {
848 if !current_word.is_empty() {
850 items.push(WordItem {
851 word: std::mem::take(&mut current_word),
852 trailing_punct: None,
853 start: word_start,
854 end: i,
855 punct_pos: None,
856 });
857 }
858 items.push(WordItem {
860 word: "->".to_string(),
861 trailing_punct: None,
862 start: i,
863 end: i + 2,
864 punct_pos: None,
865 });
866 skip_count = 1; word_start = i + 2;
868 }
869 '<' if char_idx + 1 < chars.len() && chars[char_idx + 1] == '=' => {
871 if !current_word.is_empty() {
872 items.push(WordItem {
873 word: std::mem::take(&mut current_word),
874 trailing_punct: None,
875 start: word_start,
876 end: i,
877 punct_pos: None,
878 });
879 }
880 items.push(WordItem {
881 word: "<=".to_string(),
882 trailing_punct: None,
883 start: i,
884 end: i + 2,
885 punct_pos: None,
886 });
887 skip_count = 1;
888 word_start = i + 2;
889 }
890 '>' if char_idx + 1 < chars.len() && chars[char_idx + 1] == '=' => {
892 if !current_word.is_empty() {
893 items.push(WordItem {
894 word: std::mem::take(&mut current_word),
895 trailing_punct: None,
896 start: word_start,
897 end: i,
898 punct_pos: None,
899 });
900 }
901 items.push(WordItem {
902 word: ">=".to_string(),
903 trailing_punct: None,
904 start: i,
905 end: i + 2,
906 punct_pos: None,
907 });
908 skip_count = 1;
909 word_start = i + 2;
910 }
911 '=' if char_idx + 1 < chars.len() && chars[char_idx + 1] == '=' => {
913 if !current_word.is_empty() {
914 items.push(WordItem {
915 word: std::mem::take(&mut current_word),
916 trailing_punct: None,
917 start: word_start,
918 end: i,
919 punct_pos: None,
920 });
921 }
922 items.push(WordItem {
923 word: "==".to_string(),
924 trailing_punct: None,
925 start: i,
926 end: i + 2,
927 punct_pos: None,
928 });
929 skip_count = 1;
930 word_start = i + 2;
931 }
932 '!' if char_idx + 1 < chars.len() && chars[char_idx + 1] == '=' => {
934 if !current_word.is_empty() {
935 items.push(WordItem {
936 word: std::mem::take(&mut current_word),
937 trailing_punct: None,
938 start: word_start,
939 end: i,
940 punct_pos: None,
941 });
942 }
943 items.push(WordItem {
944 word: "!=".to_string(),
945 trailing_punct: None,
946 start: i,
947 end: i + 2,
948 punct_pos: None,
949 });
950 skip_count = 1;
951 word_start = i + 2;
952 }
953 '-' if Self::is_date_hyphen(¤t_word, &chars, char_idx) => {
955 current_word.push(c);
957 }
958 ':' if Self::is_time_colon(¤t_word, &chars, char_idx) => {
960 current_word.push(c);
962 }
963 '+' | '-' if Self::is_exponent_sign(¤t_word, &chars, char_idx) => {
965 current_word.push(c);
966 }
967 '(' | ')' | '[' | ']' | ',' | '?' | '!' | ':' | '+' | '-' | '*' | '/' | '%' | '<' | '>' | '=' => {
968 if !current_word.is_empty() {
969 items.push(WordItem {
970 word: std::mem::take(&mut current_word),
971 trailing_punct: Some(c),
972 start: word_start,
973 end: i,
974 punct_pos: Some(i),
975 });
976 } else {
977 items.push(WordItem {
978 word: String::new(),
979 trailing_punct: Some(c),
980 start: i,
981 end: next_pos,
982 punct_pos: Some(i),
983 });
984 }
985 word_start = next_pos;
986 }
987 '\'' => {
988 let remaining: String = chars[char_idx + 1..].iter().collect();
990 let remaining_lower = remaining.to_lowercase();
991
992 if remaining_lower.starts_with("t ") || remaining_lower.starts_with("t.") ||
993 remaining_lower.starts_with("t,") || remaining_lower == "t" ||
994 (char_idx + 1 < chars.len() && chars[char_idx + 1] == 't' &&
995 (char_idx + 2 >= chars.len() || !chars[char_idx + 2].is_alphabetic())) {
996 let word_lower = current_word.to_lowercase();
998 if word_lower == "don" || word_lower == "doesn" || word_lower == "didn" {
999 let base = if word_lower == "don" { "do" }
1001 else if word_lower == "doesn" { "does" }
1002 else { "did" };
1003 items.push(WordItem {
1004 word: base.to_string(),
1005 trailing_punct: None,
1006 start: word_start,
1007 end: i,
1008 punct_pos: None,
1009 });
1010 items.push(WordItem {
1011 word: "not".to_string(),
1012 trailing_punct: None,
1013 start: i,
1014 end: i + 2,
1015 punct_pos: None,
1016 });
1017 current_word.clear();
1018 word_start = next_pos + 1;
1019 skip_count = 1;
1020 } else if word_lower == "won" {
1021 items.push(WordItem {
1023 word: "will".to_string(),
1024 trailing_punct: None,
1025 start: word_start,
1026 end: i,
1027 punct_pos: None,
1028 });
1029 items.push(WordItem {
1030 word: "not".to_string(),
1031 trailing_punct: None,
1032 start: i,
1033 end: i + 2,
1034 punct_pos: None,
1035 });
1036 current_word.clear();
1037 word_start = next_pos + 1;
1038 skip_count = 1;
1039 } else if word_lower == "can" {
1040 items.push(WordItem {
1042 word: "cannot".to_string(),
1043 trailing_punct: None,
1044 start: word_start,
1045 end: i + 2,
1046 punct_pos: None,
1047 });
1048 current_word.clear();
1049 word_start = next_pos + 1;
1050 skip_count = 1;
1051 } else {
1052 if !current_word.is_empty() {
1054 items.push(WordItem {
1055 word: std::mem::take(&mut current_word),
1056 trailing_punct: Some('\''),
1057 start: word_start,
1058 end: i,
1059 punct_pos: Some(i),
1060 });
1061 }
1062 word_start = next_pos;
1063 }
1064 } else {
1065 if !current_word.is_empty() {
1067 items.push(WordItem {
1068 word: std::mem::take(&mut current_word),
1069 trailing_punct: Some('\''),
1070 start: word_start,
1071 end: i,
1072 punct_pos: Some(i),
1073 });
1074 }
1075 word_start = next_pos;
1076 }
1077 }
1078 c if c.is_alphabetic() || c.is_ascii_digit() || (c == '.' && !current_word.is_empty() && current_word.chars().all(|ch| ch.is_ascii_digit())) || c == '_' => {
1079 if current_word.is_empty() {
1080 word_start = i;
1081 }
1082 current_word.push(c);
1083 }
1084 _ => {
1085 word_start = next_pos;
1086 }
1087 }
1088 char_idx += 1;
1089 }
1090
1091 if !current_word.is_empty() {
1092 items.push(WordItem {
1093 word: current_word,
1094 trailing_punct: None,
1095 start: word_start,
1096 end: input.len(),
1097 punct_pos: None,
1098 });
1099 }
1100
1101 items
1102 }
1103
1104 fn peek_word(&self, offset: usize) -> Option<&str> {
1105 self.words.get(self.pos + offset).map(|w| w.word.as_str())
1106 }
1107
1108 fn prev_token_is_determiner(&self) -> bool {
1110 if self.pos == 0 { return false; }
1111 if let Some(prev) = self.words.get(self.pos - 1) {
1112 matches!(prev.word.to_lowercase().as_str(),
1113 "every" | "each" | "some" | "all" | "any" | "no" | "the" | "a" | "an")
1114 } else {
1115 false
1116 }
1117 }
1118
1119 fn peek_sequence(&self, expected: &[&str]) -> bool {
1120 for (i, &exp) in expected.iter().enumerate() {
1121 match self.peek_word(i + 1) {
1122 Some(w) if w.to_lowercase() == exp => continue,
1123 _ => return false,
1124 }
1125 }
1126 true
1127 }
1128
1129 fn consume_words(&mut self, count: usize) {
1130 self.pos += count;
1131 }
1132
1133 pub fn tokenize(&mut self) -> Vec<Token> {
1144 let mut tokens = Vec::new();
1145
1146 while self.pos < self.words.len() {
1147 let item = &self.words[self.pos];
1148 let word = item.word.clone();
1149 let trailing_punct = item.trailing_punct;
1150 let word_start = item.start;
1151 let word_end = item.end;
1152 let punct_pos = item.punct_pos;
1153
1154 if word.is_empty() {
1155 if let Some(punct) = trailing_punct {
1156 let kind = match punct {
1157 '(' => TokenType::LParen,
1158 ')' => TokenType::RParen,
1159 '[' => TokenType::LBracket,
1160 ']' => TokenType::RBracket,
1161 ',' => TokenType::Comma,
1162 ':' => TokenType::Colon,
1163 '.' | '?' => {
1164 self.in_let_context = false;
1165 TokenType::Period
1166 }
1167 '!' => TokenType::Exclamation,
1168 '+' => TokenType::Plus,
1169 '-' => TokenType::Minus,
1170 '*' => TokenType::Star,
1171 '/' => TokenType::Slash,
1172 '%' => TokenType::Percent,
1173 '<' => TokenType::Lt,
1174 '>' => TokenType::Gt,
1175 '=' => TokenType::Assign,
1176 _ => {
1177 self.pos += 1;
1178 continue;
1179 }
1180 };
1181 let lexeme = self.interner.intern(&punct.to_string());
1182 let span = Span::new(word_start, word_end);
1183 tokens.push(Token::new(kind, lexeme, span));
1184 }
1185 self.pos += 1;
1186 continue;
1187 }
1188
1189 if word.starts_with("\x00STR:") {
1191 let content = &word[5..]; let span = Span::new(word_start, word_end);
1193 if Self::has_unescaped_brace(content) {
1194 let sym = self.interner.intern(content);
1195 tokens.push(Token::new(TokenType::InterpolatedString(sym), sym, span));
1196 } else {
1197 let normalized = content.replace("{{", "{").replace("}}", "}");
1199 let sym = self.interner.intern(&normalized);
1200 tokens.push(Token::new(TokenType::StringLiteral(sym), sym, span));
1201 }
1202 self.pos += 1;
1203 continue;
1204 }
1205
1206 if word.starts_with("\x00CHAR:") {
1208 let content = &word[6..]; let sym = self.interner.intern(content);
1210 let span = Span::new(word_start, word_end);
1211 tokens.push(Token::new(TokenType::CharLiteral(sym), sym, span));
1212 self.pos += 1;
1213 continue;
1214 }
1215
1216 if word.starts_with("\x00ESC:") {
1218 let content = &word[5..]; let sym = self.interner.intern(content);
1220 let span = Span::new(word_start, word_end);
1221 tokens.push(Token::new(TokenType::EscapeBlock(sym), sym, span));
1222 self.pos += 1;
1223 continue;
1224 }
1225
1226 let kind = self.classify_with_lookahead(&word);
1227 let lexeme = self.interner.intern(&word);
1228 let span = Span::new(word_start, word_end);
1229 tokens.push(Token::new(kind, lexeme, span));
1230
1231 if let Some(punct) = trailing_punct {
1232 if punct == '\'' {
1233 if let Some(next_item) = self.words.get(self.pos + 1) {
1234 if next_item.word.to_lowercase() == "s" {
1235 let poss_lexeme = self.interner.intern("'s");
1236 let poss_start = punct_pos.unwrap_or(word_end);
1237 let poss_end = next_item.end;
1238 tokens.push(Token::new(TokenType::Possessive, poss_lexeme, Span::new(poss_start, poss_end)));
1239 self.pos += 1;
1240 if let Some(s_punct) = next_item.trailing_punct {
1241 let kind = match s_punct {
1242 '(' => TokenType::LParen,
1243 ')' => TokenType::RParen,
1244 '[' => TokenType::LBracket,
1245 ']' => TokenType::RBracket,
1246 ',' => TokenType::Comma,
1247 ':' => TokenType::Colon,
1248 '.' | '?' => TokenType::Period,
1249 '!' => TokenType::Exclamation,
1250 '+' => TokenType::Plus,
1251 '-' => TokenType::Minus,
1252 '*' => TokenType::Star,
1253 '/' => TokenType::Slash,
1254 '%' => TokenType::Percent,
1255 '<' => TokenType::Lt,
1256 '>' => TokenType::Gt,
1257 '=' => TokenType::Assign,
1258 _ => {
1259 self.pos += 1;
1260 continue;
1261 }
1262 };
1263 let s_punct_pos = next_item.punct_pos.unwrap_or(next_item.end);
1264 let lexeme = self.interner.intern(&s_punct.to_string());
1265 tokens.push(Token::new(kind, lexeme, Span::new(s_punct_pos, s_punct_pos + 1)));
1266 }
1267 self.pos += 1;
1268 continue;
1269 }
1270 }
1271 self.pos += 1;
1272 continue;
1273 }
1274
1275 let kind = match punct {
1276 '(' => TokenType::LParen,
1277 ')' => TokenType::RParen,
1278 '[' => TokenType::LBracket,
1279 ']' => TokenType::RBracket,
1280 ',' => TokenType::Comma,
1281 ':' => TokenType::Colon,
1282 '.' | '?' => {
1283 self.in_let_context = false;
1284 TokenType::Period
1285 }
1286 '!' => TokenType::Exclamation,
1287 '+' => TokenType::Plus,
1288 '-' => TokenType::Minus,
1289 '*' => TokenType::Star,
1290 '/' => TokenType::Slash,
1291 '%' => TokenType::Percent,
1292 '<' => TokenType::Lt,
1293 '>' => TokenType::Gt,
1294 '=' => TokenType::Assign,
1295 _ => {
1296 self.pos += 1;
1297 continue;
1298 }
1299 };
1300 let p_start = punct_pos.unwrap_or(word_end);
1301 let lexeme = self.interner.intern(&punct.to_string());
1302 tokens.push(Token::new(kind, lexeme, Span::new(p_start, p_start + 1)));
1303 }
1304
1305 self.pos += 1;
1306 }
1307
1308 let eof_lexeme = self.interner.intern("");
1309 let eof_span = Span::new(self.input_len, self.input_len);
1310 tokens.push(Token::new(TokenType::EOF, eof_lexeme, eof_span));
1311
1312 self.insert_indentation_tokens(tokens)
1313 }
1314
1315 fn insert_indentation_tokens(&mut self, tokens: Vec<Token>) -> Vec<Token> {
1320 let mut result = Vec::new();
1321 let empty_sym = self.interner.intern("");
1322
1323 let line_lexer = LineLexer::new(&self.source);
1325 let line_tokens: Vec<LineToken> = line_lexer.collect();
1326
1327 let mut structural_events: Vec<(usize, bool)> = Vec::new(); let mut pending_indents = 0usize;
1331 let mut pending_dedents = 0usize;
1332
1333 for line_token in &line_tokens {
1334 match line_token {
1335 LineToken::Indent => {
1336 pending_indents += 1;
1337 }
1338 LineToken::Dedent => {
1339 pending_dedents += 1;
1340 }
1341 LineToken::Content { start, .. } => {
1342 for _ in 0..pending_dedents {
1344 structural_events.push((*start, false)); }
1346 pending_dedents = 0;
1347
1348 for _ in 0..pending_indents {
1350 structural_events.push((*start, true)); }
1352 pending_indents = 0;
1353 }
1354 LineToken::Newline => {}
1355 }
1356 }
1357
1358 for _ in 0..pending_dedents {
1360 structural_events.push((self.input_len, false));
1361 }
1362
1363 if !self.escape_body_ranges.is_empty() {
1368 let mut filtered = Vec::new();
1372 for &(pos, is_indent) in &structural_events {
1373 let is_inside_escape_body = self.escape_body_ranges.iter().any(|(start, end)| {
1374 pos > *start && pos < *end
1376 });
1377 if !is_inside_escape_body {
1378 filtered.push((pos, is_indent));
1379 }
1380 }
1381 structural_events = filtered;
1382 }
1383
1384 {
1388 let string_spans: Vec<(usize, usize)> = tokens.iter()
1389 .filter(|t| matches!(t.kind, TokenType::StringLiteral(_) | TokenType::InterpolatedString(_)))
1390 .filter(|t| t.span.end - t.span.start > 6) .map(|t| (t.span.start, t.span.end))
1392 .collect();
1393 if !string_spans.is_empty() {
1394 structural_events.retain(|&(pos, _)| {
1395 !string_spans.iter().any(|(start, end)| pos > *start && pos < *end)
1396 });
1397 }
1398 }
1399
1400 structural_events.sort_by(|a, b| {
1402 if a.0 != b.0 {
1403 a.0.cmp(&b.0)
1404 } else {
1405 a.1.cmp(&b.1)
1407 }
1408 });
1409
1410 let mut event_idx = 0;
1415 let mut last_colon_pos: Option<usize> = None;
1416
1417 for token in tokens.iter() {
1418 let token_start = token.span.start;
1419
1420 while event_idx < structural_events.len() {
1422 let (event_pos, is_indent) = structural_events[event_idx];
1423
1424 if event_pos <= token_start {
1426 let span = if is_indent {
1427 Span::new(last_colon_pos.unwrap_or(event_pos), last_colon_pos.unwrap_or(event_pos))
1429 } else {
1430 Span::new(event_pos, event_pos)
1431 };
1432 let kind = if is_indent { TokenType::Indent } else { TokenType::Dedent };
1433 result.push(Token::new(kind, empty_sym, span));
1434 event_idx += 1;
1435 } else {
1436 break;
1437 }
1438 }
1439
1440 result.push(token.clone());
1441
1442 if token.kind == TokenType::Colon && self.is_end_of_line(token.span.end) {
1444 last_colon_pos = Some(token.span.end);
1445 }
1446 }
1447
1448 while event_idx < structural_events.len() {
1450 let (event_pos, is_indent) = structural_events[event_idx];
1451 let span = Span::new(event_pos, event_pos);
1452 let kind = if is_indent { TokenType::Indent } else { TokenType::Dedent };
1453 result.push(Token::new(kind, empty_sym, span));
1454 event_idx += 1;
1455 }
1456
1457 let eof_pos = result.iter().position(|t| t.kind == TokenType::EOF);
1459 if let Some(pos) = eof_pos {
1460 let eof = result.remove(pos);
1461 result.push(eof);
1462 }
1463
1464 result
1465 }
1466
1467 fn is_end_of_line(&self, from_pos: usize) -> bool {
1469 let bytes = self.source.as_bytes();
1470 let mut pos = from_pos;
1471 while pos < bytes.len() {
1472 match bytes[pos] {
1473 b' ' | b'\t' => pos += 1,
1474 b'\n' => return true,
1475 _ => return false,
1476 }
1477 }
1478 true }
1480
1481 fn measure_next_line_indent(&self, from_pos: usize) -> Option<usize> {
1482 let bytes = self.source.as_bytes();
1483 let mut pos = from_pos;
1484
1485 while pos < bytes.len() && bytes[pos] != b'\n' {
1486 pos += 1;
1487 }
1488
1489 if pos >= bytes.len() {
1490 return None;
1491 }
1492
1493 pos += 1;
1494
1495 let mut indent = 0;
1496 while pos < bytes.len() {
1497 match bytes[pos] {
1498 b' ' => indent += 1,
1499 b'\t' => indent += 4,
1500 b'\n' => {
1501 indent = 0;
1502 }
1503 _ => break,
1504 }
1505 pos += 1;
1506 }
1507
1508 if pos >= bytes.len() {
1509 return None;
1510 }
1511
1512 Some(indent)
1513 }
1514
1515 fn word_to_number(word: &str) -> Option<u32> {
1516 lexicon::word_to_number(&word.to_lowercase())
1517 }
1518
1519 fn is_date_hyphen(current_word: &str, chars: &[char], char_idx: usize) -> bool {
1525 let word_chars: Vec<char> = current_word.chars().collect();
1527
1528 if word_chars.len() == 4 && word_chars.iter().all(|c| c.is_ascii_digit()) {
1530 if char_idx + 5 < chars.len()
1532 && chars[char_idx + 1].is_ascii_digit()
1533 && chars[char_idx + 2].is_ascii_digit()
1534 && chars[char_idx + 3] == '-'
1535 && chars[char_idx + 4].is_ascii_digit()
1536 && chars[char_idx + 5].is_ascii_digit()
1537 {
1538 return true;
1539 }
1540 }
1541
1542 if word_chars.len() == 7
1544 && word_chars[0..4].iter().all(|c| c.is_ascii_digit())
1545 && word_chars[4] == '-'
1546 && word_chars[5..7].iter().all(|c| c.is_ascii_digit())
1547 {
1548 if char_idx + 2 < chars.len()
1550 && chars[char_idx + 1].is_ascii_digit()
1551 && chars[char_idx + 2].is_ascii_digit()
1552 {
1553 let next_not_digit = char_idx + 3 >= chars.len()
1555 || !chars[char_idx + 3].is_ascii_digit();
1556 if next_not_digit {
1557 return true;
1558 }
1559 }
1560 }
1561
1562 false
1563 }
1564
1565 fn is_time_colon(current_word: &str, chars: &[char], char_idx: usize) -> bool {
1571 let word_chars: Vec<char> = current_word.chars().collect();
1573 if word_chars.is_empty() || word_chars.len() > 2 {
1574 return false;
1575 }
1576 if !word_chars.iter().all(|c| c.is_ascii_digit()) {
1577 return false;
1578 }
1579
1580 if char_idx + 4 < chars.len()
1582 && chars[char_idx + 1].is_ascii_digit()
1583 && chars[char_idx + 2].is_ascii_digit()
1584 {
1585 let next_two: String = chars[char_idx + 3..char_idx + 5].iter().collect();
1587 let lower = next_two.to_lowercase();
1588 if lower == "am" || lower == "pm" {
1589 let after_suffix = char_idx + 5 >= chars.len()
1591 || !chars[char_idx + 5].is_alphabetic();
1592 if after_suffix {
1593 return true;
1594 }
1595 }
1596 }
1597
1598 false
1599 }
1600
1601 fn has_unescaped_brace(content: &str) -> bool {
1604 let bytes = content.as_bytes();
1605 let mut i = 0;
1606 while i < bytes.len() {
1607 if bytes[i] == b'{' {
1608 if i + 1 < bytes.len() && bytes[i + 1] == b'{' {
1609 i += 2;
1610 } else {
1611 return true;
1612 }
1613 } else {
1614 i += 1;
1615 }
1616 }
1617 false
1618 }
1619
1620 fn is_exponent_sign(current_word: &str, chars: &[char], char_idx: usize) -> bool {
1626 if !current_word.ends_with('e') && !current_word.ends_with('E') {
1628 return false;
1629 }
1630 let before_e = ¤t_word[..current_word.len() - 1];
1632 if before_e.is_empty() || !before_e.chars().next().unwrap().is_ascii_digit() {
1633 return false;
1634 }
1635 char_idx + 1 < chars.len() && chars[char_idx + 1].is_ascii_digit()
1637 }
1638
1639 fn dedent_triple_quote(raw: &str) -> String {
1642 let lines: Vec<&str> = raw.lines().collect();
1643 if lines.is_empty() {
1644 return String::new();
1645 }
1646 let min_indent = lines.iter()
1648 .filter(|l| !l.trim().is_empty())
1649 .map(|l| l.len() - l.trim_start().len())
1650 .min()
1651 .unwrap_or(0);
1652 lines.iter()
1654 .map(|l| {
1655 if l.len() >= min_indent {
1656 &l[min_indent..]
1657 } else {
1658 l.trim()
1659 }
1660 })
1661 .collect::<Vec<_>>()
1662 .join("\n")
1663 }
1664
1665 fn is_numeric_literal(word: &str) -> bool {
1666 if word.is_empty() {
1667 return false;
1668 }
1669 let chars: Vec<char> = word.chars().collect();
1670 let first = chars[0];
1671 if first.is_ascii_digit() {
1672 return true;
1674 }
1675 if let Some(underscore_pos) = word.rfind('_') {
1678 let before_underscore = &word[..underscore_pos];
1679 let after_underscore = &word[underscore_pos + 1..];
1680 let is_math_symbol = matches!(
1682 before_underscore.to_lowercase().as_str(),
1683 "aleph" | "omega" | "beth"
1684 );
1685 if is_math_symbol
1686 && !after_underscore.is_empty()
1687 && after_underscore.chars().all(|c| c.is_ascii_digit())
1688 {
1689 return true;
1690 }
1691 }
1692 false
1693 }
1694
1695 fn parse_duration_literal(word: &str) -> Option<(i64, &str)> {
1708 if word.is_empty() || !word.chars().next()?.is_ascii_digit() {
1709 return None;
1710 }
1711
1712 const SUFFIXES: &[(&str, i64)] = &[
1714 ("ns", 1),
1715 ("μs", 1_000),
1716 ("us", 1_000),
1717 ("ms", 1_000_000),
1718 ("sec", 1_000_000_000),
1719 ("s", 1_000_000_000),
1720 ("min", 60_000_000_000),
1721 ("hr", 3_600_000_000_000),
1722 ("h", 3_600_000_000_000),
1723 ];
1724
1725 for (suffix, multiplier) in SUFFIXES {
1727 if word.ends_with(suffix) {
1728 let num_part = &word[..word.len() - suffix.len()];
1729 let cleaned: String = num_part.chars().filter(|c| *c != '_').collect();
1731 if let Ok(n) = cleaned.parse::<i64>() {
1732 return Some((n.saturating_mul(*multiplier), *suffix));
1733 }
1734 }
1735 }
1736
1737 None
1738 }
1739
1740 fn parse_date_literal(word: &str) -> Option<i32> {
1745 if word.len() != 10 {
1747 return None;
1748 }
1749
1750 let bytes = word.as_bytes();
1751
1752 if bytes[4] != b'-' || bytes[7] != b'-' {
1754 return None;
1755 }
1756
1757 let year: i32 = word[0..4].parse().ok()?;
1759 let month: u32 = word[5..7].parse().ok()?;
1760 let day: u32 = word[8..10].parse().ok()?;
1761
1762 if month < 1 || month > 12 || day < 1 || day > 31 {
1764 return None;
1765 }
1766
1767 let y = if month <= 2 { year - 1 } else { year };
1770 let era = if y >= 0 { y / 400 } else { (y - 399) / 400 };
1771 let yoe = (y - era * 400) as u32;
1772 let m = month;
1773 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + day - 1;
1774 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
1775 let days = era * 146097 + doe as i32 - 719468;
1776
1777 Some(days)
1778 }
1779
1780 fn parse_time_literal(word: &str) -> Option<i64> {
1789 let lower = word.to_lowercase();
1790
1791 if lower == "noon" {
1793 return Some(12i64 * 3600 * 1_000_000_000);
1794 }
1795 if lower == "midnight" {
1796 return Some(0);
1797 }
1798
1799 let is_pm = lower.ends_with("pm");
1801 let is_am = lower.ends_with("am");
1802
1803 if !is_pm && !is_am {
1804 return None;
1805 }
1806
1807 let time_part = &lower[..lower.len() - 2];
1809
1810 let (hour, minute): (i64, i64) = if let Some(colon_idx) = time_part.find(':') {
1812 let hour_str = &time_part[..colon_idx];
1813 let min_str = &time_part[colon_idx + 1..];
1814 let h: i64 = hour_str.parse().ok()?;
1815 let m: i64 = min_str.parse().ok()?;
1816 (h, m)
1817 } else {
1818 let h: i64 = time_part.parse().ok()?;
1820 (h, 0)
1821 };
1822
1823 if hour < 1 || hour > 12 || minute < 0 || minute > 59 {
1825 return None;
1826 }
1827
1828 let hour_24 = if is_am {
1830 if hour == 12 { 0 } else { hour } } else {
1832 if hour == 12 { 12 } else { hour + 12 } };
1834
1835 let nanos = (hour_24 * 3600 + minute * 60) * 1_000_000_000;
1837 Some(nanos)
1838 }
1839
1840 fn classify_with_lookahead(&mut self, word: &str) -> TokenType {
1841 if word.starts_with("##") {
1843 let block_name = &word[2..];
1844 let block_type = match block_name.to_lowercase().as_str() {
1845 "theorem" => BlockType::Theorem,
1846 "main" => BlockType::Main,
1847 "definition" => BlockType::Definition,
1848 "proof" => BlockType::Proof,
1849 "example" => BlockType::Example,
1850 "logic" => BlockType::Logic,
1851 "note" => BlockType::Note,
1852 "to" => BlockType::Function, "a" | "an" => BlockType::TypeDef, "policy" => BlockType::Policy, "requires" => BlockType::Requires, "hardware" => BlockType::Hardware, "property" => BlockType::Property, "no" => BlockType::No, _ => BlockType::Note, };
1861
1862 self.mode = match block_type {
1864 BlockType::Main | BlockType::Function => LexerMode::Imperative,
1865 _ => LexerMode::Declarative,
1866 };
1867
1868 return TokenType::BlockHeader { block_type };
1869 }
1870
1871 let lower = word.to_lowercase();
1872
1873 if lower == "each" && self.peek_sequence(&["other"]) {
1874 self.consume_words(1);
1875 return TokenType::Reciprocal;
1876 }
1877
1878 if lower == "to" {
1879 if let Some(next) = self.peek_word(1) {
1880 if self.is_verb_like(next) {
1881 return TokenType::To;
1882 }
1883 }
1884 let sym = self.interner.intern("to");
1885 return TokenType::Preposition(sym);
1886 }
1887
1888 if lower == "at" {
1889 if let Some(next) = self.peek_word(1) {
1890 let next_lower = next.to_lowercase();
1891 if next_lower == "least" {
1892 if let Some(num_word) = self.peek_word(2) {
1893 if let Some(n) = Self::word_to_number(num_word) {
1894 self.consume_words(2);
1895 return TokenType::AtLeast(n);
1896 }
1897 }
1898 }
1899 if next_lower == "most" {
1900 if let Some(num_word) = self.peek_word(2) {
1901 if let Some(n) = Self::word_to_number(num_word) {
1902 self.consume_words(2);
1903 return TokenType::AtMost(n);
1904 }
1905 }
1906 }
1907 }
1908 }
1909
1910 if let Some(n) = Self::word_to_number(&lower) {
1911 return TokenType::Cardinal(n);
1912 }
1913
1914 if let Some((nanos, unit)) = Self::parse_duration_literal(word) {
1916 let unit_sym = self.interner.intern(unit);
1917 return TokenType::DurationLiteral {
1918 nanos,
1919 original_unit: unit_sym,
1920 };
1921 }
1922
1923 if let Some(days) = Self::parse_date_literal(word) {
1925 return TokenType::DateLiteral { days };
1926 }
1927
1928 if let Some(nanos_from_midnight) = Self::parse_time_literal(word) {
1930 return TokenType::TimeLiteral { nanos_from_midnight };
1931 }
1932
1933 if Self::is_numeric_literal(word) {
1934 let sym = self.interner.intern(word);
1935 return TokenType::Number(sym);
1936 }
1937
1938 if lower == "if" && self.peek_sequence(&["and", "only", "if"]) {
1939 self.consume_words(3);
1940 return TokenType::Iff;
1941 }
1942
1943 if lower == "is" {
1944 if self.peek_sequence(&["equal", "to"]) {
1945 self.consume_words(2);
1946 return TokenType::Identity;
1947 }
1948 if self.peek_sequence(&["identical", "to"]) {
1949 self.consume_words(2);
1950 return TokenType::Identity;
1951 }
1952 }
1953
1954 if (lower == "a" || lower == "an") && word.chars().next().unwrap().is_uppercase() {
1955 if let Some(next) = self.peek_word(1) {
1958 let next_lower = next.to_lowercase();
1959 let next_starts_lowercase = next.chars().next().map(|c| c.is_lowercase()).unwrap_or(false);
1960
1961 if matches!(next_lower.as_str(), "if" | "and" | "or" | "implies" | "iff") {
1963 let sym = self.interner.intern(word);
1964 return TokenType::ProperName(sym);
1965 }
1966
1967 let is_verb = self.lexicon.lookup_verb(&next_lower).is_some()
1972 && !lexicon::is_disambiguation_not_verb(&next_lower);
1973 let is_gerund = next_lower.ends_with("ing");
1974 let is_also_noun_or_adj = self.is_noun_like(&next_lower) || self.is_adjective_like(&next_lower);
1975 if is_verb && !is_gerund && !is_also_noun_or_adj {
1976 let sym = self.interner.intern(word);
1977 return TokenType::ProperName(sym);
1978 }
1979
1980 if let Some(third) = self.peek_word(2) {
1983 let third_lower = third.to_lowercase();
1984 if third_lower == "is" || third_lower == "are" || third_lower == "has" {
1986 return TokenType::Article(Definiteness::Indefinite);
1987 }
1988 }
1989
1990 let is_content_word = self.is_noun_like(&next_lower) || self.is_adjective_like(&next_lower);
1994 if is_content_word || next_starts_lowercase {
1995 return TokenType::Article(Definiteness::Indefinite);
1996 }
1997 }
1998 let sym = self.interner.intern(word);
1999 return TokenType::ProperName(sym);
2000 }
2001
2002 self.classify_word(word)
2003 }
2004
2005 fn is_noun_like(&self, word: &str) -> bool {
2006 if lexicon::is_noun_pattern(word) || lexicon::is_common_noun(word) {
2007 return true;
2008 }
2009 if word.ends_with("er") || word.ends_with("ian") || word.ends_with("ist") {
2010 return true;
2011 }
2012 false
2013 }
2014
2015 fn is_adjective_like(&self, word: &str) -> bool {
2016 lexicon::is_adjective(word) || lexicon::is_non_intersective(word)
2017 }
2018
2019 fn classify_word(&mut self, word: &str) -> TokenType {
2020 let lower = word.to_lowercase();
2021 let first_char = word.chars().next().unwrap();
2022
2023 if lower == "that" {
2026 if let Some(next) = self.peek_word(1) {
2027 let next_lower = next.to_lowercase();
2028 if self.is_noun_like(&next_lower) || self.is_adjective_like(&next_lower) {
2029 return TokenType::Article(Definiteness::Distal);
2030 }
2031 }
2032 }
2033
2034 if word == "->" {
2036 return TokenType::Arrow;
2037 }
2038
2039 if word == "<=" {
2041 return TokenType::LtEq;
2042 }
2043 if word == ">=" {
2044 return TokenType::GtEq;
2045 }
2046 if word == "==" {
2047 return TokenType::EqEq;
2048 }
2049 if word == "!=" {
2050 return TokenType::NotEq;
2051 }
2052 if word == "<" {
2053 return TokenType::Lt;
2054 }
2055 if word == ">" {
2056 return TokenType::Gt;
2057 }
2058 if word == "=" {
2060 return TokenType::Assign;
2061 }
2062
2063 if let Some(kind) = lexicon::lookup_keyword(&lower) {
2064 return kind;
2065 }
2066
2067 if let Some(kind) = lexicon::lookup_pronoun(&lower) {
2068 return kind;
2069 }
2070
2071 if let Some(def) = lexicon::lookup_article(&lower) {
2072 return TokenType::Article(def);
2073 }
2074
2075 if let Some(time) = lexicon::lookup_auxiliary(&lower) {
2076 return TokenType::Auxiliary(time);
2077 }
2078
2079 match lower.as_str() {
2081 "call" => return TokenType::Call,
2082 "in" if self.mode == LexerMode::Imperative => return TokenType::In,
2083 "inside" if self.mode == LexerMode::Imperative => return TokenType::Inside,
2085 "at" if self.mode == LexerMode::Imperative => return TokenType::At,
2087 "into" if self.mode == LexerMode::Imperative => return TokenType::Into,
2089 "before" => return TokenType::Before,
2091 _ => {}
2092 }
2093
2094 if lexicon::is_preposition(&lower) {
2095 let sym = self.interner.intern(&lower);
2096 return TokenType::Preposition(sym);
2097 }
2098
2099 match lower.as_str() {
2100 "equals" => return TokenType::Equals,
2101 "item" => return TokenType::Item,
2102 "items" => return TokenType::Items,
2103 "mut" if self.mode == LexerMode::Imperative => return TokenType::Mut,
2105 "let" => {
2106 self.in_let_context = true;
2107 return TokenType::Let;
2108 }
2109 "set" => {
2110 if self.peek_word(1).map_or(false, |w| w.to_lowercase() == "of") {
2113 } else if self.mode == LexerMode::Imperative {
2115 return TokenType::Set;
2117 } else {
2118 for offset in 2..=5 {
2121 if self.peek_word(offset).map_or(false, |w| w.to_lowercase() == "to") {
2122 return TokenType::Set;
2123 }
2124 }
2125 }
2126 }
2127 "return" => return TokenType::Return,
2128 "break" => return TokenType::Break,
2129 "xor" => return TokenType::Xor,
2130 "shifted" => return TokenType::Shifted,
2131 "be" if self.in_let_context => {
2132 self.in_let_context = false;
2133 return TokenType::Be;
2134 }
2135 "while" => return TokenType::While,
2136 "assert" => return TokenType::Assert,
2137 "trust" => return TokenType::Trust,
2138 "check" => return TokenType::Check,
2139 "given" if self.mode == LexerMode::Declarative => return TokenType::Given,
2141 "prove" if self.mode == LexerMode::Declarative => return TokenType::Prove,
2142 "auto" if self.mode == LexerMode::Declarative => return TokenType::Auto,
2143 "listen" if self.mode == LexerMode::Imperative => return TokenType::Listen,
2145 "connect" if self.mode == LexerMode::Imperative => return TokenType::NetConnect,
2146 "sleep" if self.mode == LexerMode::Imperative => return TokenType::Sleep,
2147 "sync" if self.mode == LexerMode::Imperative => return TokenType::Sync,
2149 "mount" if self.mode == LexerMode::Imperative => return TokenType::Mount,
2151 "persistent" => return TokenType::Persistent, "combined" if self.mode == LexerMode::Imperative => return TokenType::Combined,
2153 "launch" if self.mode == LexerMode::Imperative => return TokenType::Launch,
2157 "task" if self.mode == LexerMode::Imperative => return TokenType::Task,
2158 "pipe" if self.mode == LexerMode::Imperative => return TokenType::Pipe,
2159 "receive" if self.mode == LexerMode::Imperative => return TokenType::Receive,
2160 "stop" if self.mode == LexerMode::Imperative => return TokenType::Stop,
2161 "try" if self.mode == LexerMode::Imperative => return TokenType::Try,
2162 "into" if self.mode == LexerMode::Imperative => return TokenType::Into,
2163 "native" => return TokenType::Native,
2164 "escape" if self.mode == LexerMode::Imperative => return TokenType::Escape,
2165 "from" => return TokenType::From,
2166 "otherwise" => return TokenType::Otherwise,
2167 "else" => return TokenType::Else,
2169 "elif" => return TokenType::Elif,
2170 "either" if self.mode == LexerMode::Declarative => return TokenType::Either,
2172 "inspect" if self.mode == LexerMode::Imperative => return TokenType::Inspect,
2174 "new" if self.mode == LexerMode::Imperative => return TokenType::New,
2176 "give" if self.mode == LexerMode::Imperative => return TokenType::Give,
2179 "show" if self.mode == LexerMode::Imperative => return TokenType::Show,
2180 "push" if self.mode == LexerMode::Imperative => return TokenType::Push,
2182 "pop" if self.mode == LexerMode::Imperative => return TokenType::Pop,
2183 "copy" if self.mode == LexerMode::Imperative => return TokenType::Copy,
2184 "through" if self.mode == LexerMode::Imperative => return TokenType::Through,
2185 "length" if self.mode == LexerMode::Imperative => return TokenType::Length,
2186 "at" if self.mode == LexerMode::Imperative => return TokenType::At,
2187 "add" if self.mode == LexerMode::Imperative => return TokenType::Add,
2189 "remove" if self.mode == LexerMode::Imperative => return TokenType::Remove,
2190 "contains" if self.mode == LexerMode::Imperative => return TokenType::Contains,
2191 "union" if self.mode == LexerMode::Imperative => return TokenType::Union,
2192 "intersection" if self.mode == LexerMode::Imperative => return TokenType::Intersection,
2193 "inside" if self.mode == LexerMode::Imperative => return TokenType::Inside,
2195 "zone" if self.mode == LexerMode::Imperative => return TokenType::Zone,
2196 "called" if self.mode == LexerMode::Imperative => return TokenType::Called,
2197 "size" if self.mode == LexerMode::Imperative => return TokenType::Size,
2198 "mapped" if self.mode == LexerMode::Imperative => return TokenType::Mapped,
2199 "attempt" if self.mode == LexerMode::Imperative => return TokenType::Attempt,
2201 "following" if self.mode == LexerMode::Imperative => return TokenType::Following,
2202 "simultaneously" if self.mode == LexerMode::Imperative => return TokenType::Simultaneously,
2203 "read" if self.mode == LexerMode::Imperative => return TokenType::Read,
2205 "write" if self.mode == LexerMode::Imperative => return TokenType::Write,
2206 "console" if self.mode == LexerMode::Imperative => return TokenType::Console,
2207 "file" if self.mode == LexerMode::Imperative => return TokenType::File,
2208 "spawn" if self.mode == LexerMode::Imperative => return TokenType::Spawn,
2210 "send" if self.mode == LexerMode::Imperative => return TokenType::Send,
2211 "await" if self.mode == LexerMode::Imperative => return TokenType::Await,
2212 "portable" => return TokenType::Portable,
2214 "manifest" if self.mode == LexerMode::Imperative => return TokenType::Manifest,
2216 "chunk" if self.mode == LexerMode::Imperative => return TokenType::Chunk,
2217 "shared" => return TokenType::Shared, "merge" if self.mode == LexerMode::Imperative => return TokenType::Merge,
2220 "increase" if self.mode == LexerMode::Imperative => return TokenType::Increase,
2221 "decrease" if self.mode == LexerMode::Imperative => return TokenType::Decrease,
2223 "append" if self.mode == LexerMode::Imperative => return TokenType::Append,
2224 "resolve" if self.mode == LexerMode::Imperative => return TokenType::Resolve,
2225 "values" if self.mode == LexerMode::Imperative => return TokenType::Values,
2226 "tally" => return TokenType::Tally,
2228 "sharedset" => return TokenType::SharedSet,
2229 "sharedsequence" => return TokenType::SharedSequence,
2230 "collaborativesequence" => return TokenType::CollaborativeSequence,
2231 "sharedmap" => return TokenType::SharedMap,
2232 "divergent" => return TokenType::Divergent,
2233 "removewins" => return TokenType::RemoveWins,
2234 "addwins" => return TokenType::AddWins,
2235 "yata" => return TokenType::YATA,
2236 "day" | "days" => return TokenType::CalendarUnit(CalendarUnit::Day),
2238 "week" | "weeks" => return TokenType::CalendarUnit(CalendarUnit::Week),
2239 "month" | "months" => return TokenType::CalendarUnit(CalendarUnit::Month),
2240 "year" | "years" => return TokenType::CalendarUnit(CalendarUnit::Year),
2241 "ago" => return TokenType::Ago,
2243 "hence" => return TokenType::Hence,
2244 "if" => return TokenType::If,
2245 "only" => return TokenType::Focus(FocusKind::Only),
2246 "even" => return TokenType::Focus(FocusKind::Even),
2247 "just" if self.peek_word(1).map_or(false, |w| {
2248 !self.is_verb_like(w) || w.to_lowercase() == "john" || w.chars().next().map_or(false, |c| c.is_uppercase())
2249 }) => return TokenType::Focus(FocusKind::Just),
2250 "much" => return TokenType::Measure(MeasureKind::Much),
2251 "little" => return TokenType::Measure(MeasureKind::Little),
2252 _ => {}
2253 }
2254
2255 if lexicon::is_scopal_adverb(&lower) {
2256 let sym = self.interner.intern(&Self::capitalize(&lower));
2257 return TokenType::ScopalAdverb(sym);
2258 }
2259
2260 if lexicon::is_temporal_adverb(&lower) {
2261 let sym = self.interner.intern(&Self::capitalize(&lower));
2262 return TokenType::TemporalAdverb(sym);
2263 }
2264
2265 if lexicon::is_non_intersective(&lower) {
2266 let sym = self.interner.intern(&Self::capitalize(&lower));
2267 return TokenType::NonIntersectiveAdjective(sym);
2268 }
2269
2270 if lexicon::is_adverb(&lower) {
2271 let sym = self.interner.intern(&Self::capitalize(&lower));
2272 return TokenType::Adverb(sym);
2273 }
2274 if lower.ends_with("ly") && !lexicon::is_not_adverb(&lower) && lower.len() > 4 {
2275 let sym = self.interner.intern(&Self::capitalize(&lower));
2276 return TokenType::Adverb(sym);
2277 }
2278
2279 if let Some(base) = self.try_parse_superlative(&lower) {
2280 let sym = self.interner.intern(&base);
2281 return TokenType::Superlative(sym);
2282 }
2283
2284 let irregular_comparative = match lower.as_str() {
2286 "less" => Some("Little"),
2287 "more" => Some("Much"),
2288 "better" => Some("Good"),
2289 "worse" => Some("Bad"),
2290 _ => None,
2291 };
2292 if let Some(base) = irregular_comparative {
2293 let sym = self.interner.intern(base);
2294 return TokenType::Comparative(sym);
2295 }
2296
2297 if let Some(base) = self.try_parse_comparative(&lower) {
2298 let sym = self.interner.intern(&base);
2299 return TokenType::Comparative(sym);
2300 }
2301
2302 if lexicon::is_performative(&lower) {
2303 let after_determiner = self.prev_token_is_determiner();
2308 if !lexicon::is_common_noun(&lower) || !after_determiner {
2309 let sym = self.interner.intern(&Self::capitalize(&lower));
2310 return TokenType::Performative(sym);
2311 }
2312 }
2314
2315 if lexicon::is_base_verb_early(&lower) {
2316 let after_determiner = self.prev_token_is_determiner();
2320 if !lexicon::is_common_noun(&lower) || !after_determiner {
2321 let sym = self.interner.intern(&Self::capitalize(&lower));
2322 let class = lexicon::lookup_verb_class(&lower);
2323 return TokenType::Verb {
2324 lemma: sym,
2325 time: Time::Present,
2326 aspect: Aspect::Simple,
2327 class,
2328 };
2329 }
2330 }
2332
2333 if lower.ends_with("ing") && lower.len() > 4 {
2336 if let Some(entry) = self.lexicon.lookup_verb(&lower) {
2337 let sym = self.interner.intern(&entry.lemma);
2338 return TokenType::Verb {
2339 lemma: sym,
2340 time: entry.time,
2341 aspect: entry.aspect,
2342 class: entry.class,
2343 };
2344 }
2345 }
2346
2347 if first_char.is_uppercase() {
2348 if let Some(next) = self.peek_word(1) {
2355 let next_lower = next.to_lowercase();
2356 let is_followed_by_verb = self.lexicon.lookup_verb(&next_lower).is_some()
2358 || matches!(next_lower.as_str(), "is" | "are" | "was" | "were" | "has" | "have" | "had");
2359
2360 if is_followed_by_verb {
2361 if let Some(analysis) = lexicon::analyze_word(&lower) {
2363 match analysis {
2364 lexicon::WordAnalysis::Noun(meta) if meta.number == lexicon::Number::Plural => {
2365 let sym = self.interner.intern(&lower);
2367 return TokenType::Noun(sym);
2368 }
2369 lexicon::WordAnalysis::DerivedNoun { number: lexicon::Number::Plural, .. } => {
2370 let sym = self.interner.intern(&lower);
2372 return TokenType::Noun(sym);
2373 }
2374 _ => {
2375 }
2378 }
2379 }
2380 }
2381 }
2382
2383 let sym = self.interner.intern(word);
2384 return TokenType::ProperName(sym);
2385 }
2386
2387 let verb_entry = self.lexicon.lookup_verb(&lower);
2388 let is_noun = lexicon::is_common_noun(&lower);
2389 let is_adj = self.is_adjective_like(&lower);
2390 let is_disambiguated = lexicon::is_disambiguation_not_verb(&lower);
2391
2392 if verb_entry.is_some() && (is_noun || is_adj) && !is_disambiguated {
2394 let entry = verb_entry.unwrap();
2395 let verb_token = TokenType::Verb {
2396 lemma: self.interner.intern(&entry.lemma),
2397 time: entry.time,
2398 aspect: entry.aspect,
2399 class: entry.class,
2400 };
2401
2402 let mut alternatives = Vec::new();
2403 if is_noun {
2404 alternatives.push(TokenType::Noun(self.interner.intern(word)));
2405 }
2406 if is_adj {
2407 alternatives.push(TokenType::Adjective(self.interner.intern(word)));
2408 }
2409
2410 return TokenType::Ambiguous {
2411 primary: Box::new(verb_token),
2412 alternatives,
2413 };
2414 }
2415
2416 if let Some(_) = &verb_entry {
2418 if is_disambiguated {
2419 let sym = self.interner.intern(word);
2420 if is_noun {
2421 return TokenType::Noun(sym);
2422 }
2423 return TokenType::Adjective(sym);
2424 }
2425 }
2426
2427 if let Some(entry) = verb_entry {
2429 let sym = self.interner.intern(&entry.lemma);
2430 return TokenType::Verb {
2431 lemma: sym,
2432 time: entry.time,
2433 aspect: entry.aspect,
2434 class: entry.class,
2435 };
2436 }
2437
2438 if is_noun {
2440 let sym = self.interner.intern(word);
2441 return TokenType::Noun(sym);
2442 }
2443
2444 if lexicon::is_base_verb(&lower) {
2445 let sym = self.interner.intern(&Self::capitalize(&lower));
2446 let class = lexicon::lookup_verb_class(&lower);
2447 return TokenType::Verb {
2448 lemma: sym,
2449 time: Time::Present,
2450 aspect: Aspect::Simple,
2451 class,
2452 };
2453 }
2454
2455 if lower.ends_with("ian")
2456 || lower.ends_with("er")
2457 || lower == "logic"
2458 || lower == "time"
2459 || lower == "men"
2460 || lower == "book"
2461 || lower == "house"
2462 || lower == "code"
2463 || lower == "user"
2464 {
2465 let sym = self.interner.intern(word);
2466 return TokenType::Noun(sym);
2467 }
2468
2469 if lexicon::is_particle(&lower) {
2470 let sym = self.interner.intern(&lower);
2471 return TokenType::Particle(sym);
2472 }
2473
2474 let sym = self.interner.intern(word);
2475 TokenType::Adjective(sym)
2476 }
2477
2478 fn capitalize(s: &str) -> String {
2479 let mut chars = s.chars();
2480 match chars.next() {
2481 None => String::new(),
2482 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
2483 }
2484 }
2485
2486 pub fn is_collective_verb(lemma: &str) -> bool {
2487 lexicon::is_collective_verb(&lemma.to_lowercase())
2488 }
2489
2490 pub fn is_mixed_verb(lemma: &str) -> bool {
2491 lexicon::is_mixed_verb(&lemma.to_lowercase())
2492 }
2493
2494 pub fn is_distributive_verb(lemma: &str) -> bool {
2495 lexicon::is_distributive_verb(&lemma.to_lowercase())
2496 }
2497
2498 pub fn is_intensional_predicate(lemma: &str) -> bool {
2499 lexicon::is_intensional_predicate(&lemma.to_lowercase())
2500 }
2501
2502 pub fn is_opaque_verb(lemma: &str) -> bool {
2503 lexicon::is_opaque_verb(&lemma.to_lowercase())
2504 }
2505
2506 pub fn is_ditransitive_verb(lemma: &str) -> bool {
2507 lexicon::is_ditransitive_verb(&lemma.to_lowercase())
2508 }
2509
2510 fn is_verb_like(&self, word: &str) -> bool {
2511 let lower = word.to_lowercase();
2512 if lexicon::is_infinitive_verb(&lower) {
2513 return true;
2514 }
2515 if let Some(entry) = self.lexicon.lookup_verb(&lower) {
2516 return entry.lemma.len() > 0;
2517 }
2518 false
2519 }
2520
2521 pub fn is_subject_control_verb(lemma: &str) -> bool {
2522 lexicon::is_subject_control_verb(&lemma.to_lowercase())
2523 }
2524
2525 pub fn is_raising_verb(lemma: &str) -> bool {
2526 lexicon::is_raising_verb(&lemma.to_lowercase())
2527 }
2528
2529 pub fn is_object_control_verb(lemma: &str) -> bool {
2530 lexicon::is_object_control_verb(&lemma.to_lowercase())
2531 }
2532
2533 pub fn is_weather_verb(lemma: &str) -> bool {
2534 matches!(
2535 lemma.to_lowercase().as_str(),
2536 "rain" | "snow" | "hail" | "thunder" | "pour"
2537 )
2538 }
2539
2540 fn try_parse_superlative(&self, word: &str) -> Option<String> {
2541 if !word.ends_with("est") || word.len() < 5 {
2542 return None;
2543 }
2544
2545 let base = &word[..word.len() - 3];
2546
2547 if base.len() >= 2 {
2548 let chars: Vec<char> = base.chars().collect();
2549 let last = chars[chars.len() - 1];
2550 let second_last = chars[chars.len() - 2];
2551 if last == second_last && !"aeiou".contains(last) {
2552 let stem = &base[..base.len() - 1];
2553 if lexicon::is_gradable_adjective(stem) {
2554 return Some(Self::capitalize(stem));
2555 }
2556 }
2557 }
2558
2559 if base.ends_with("i") {
2560 let stem = format!("{}y", &base[..base.len() - 1]);
2561 if lexicon::is_gradable_adjective(&stem) {
2562 return Some(Self::capitalize(&stem));
2563 }
2564 }
2565
2566 if lexicon::is_gradable_adjective(base) {
2567 return Some(Self::capitalize(base));
2568 }
2569
2570 None
2571 }
2572
2573 fn try_parse_comparative(&self, word: &str) -> Option<String> {
2574 if !word.ends_with("er") || word.len() < 4 {
2575 return None;
2576 }
2577
2578 let base = &word[..word.len() - 2];
2579
2580 if base.len() >= 2 {
2581 let chars: Vec<char> = base.chars().collect();
2582 let last = chars[chars.len() - 1];
2583 let second_last = chars[chars.len() - 2];
2584 if last == second_last && !"aeiou".contains(last) {
2585 let stem = &base[..base.len() - 1];
2586 if lexicon::is_gradable_adjective(stem) {
2587 return Some(Self::capitalize(stem));
2588 }
2589 }
2590 }
2591
2592 if base.ends_with("i") {
2593 let stem = format!("{}y", &base[..base.len() - 1]);
2594 if lexicon::is_gradable_adjective(&stem) {
2595 return Some(Self::capitalize(&stem));
2596 }
2597 }
2598
2599 if lexicon::is_gradable_adjective(base) {
2600 return Some(Self::capitalize(base));
2601 }
2602
2603 None
2604 }
2605}
2606
2607#[cfg(test)]
2608mod tests {
2609 use super::*;
2610
2611 #[test]
2612 fn lexer_handles_apostrophe() {
2613 let mut interner = Interner::new();
2614 let mut lexer = Lexer::new("it's raining", &mut interner);
2615 let tokens = lexer.tokenize();
2616 assert!(!tokens.is_empty());
2617 }
2618
2619 #[test]
2620 fn lexer_handles_question_mark() {
2621 let mut interner = Interner::new();
2622 let mut lexer = Lexer::new("Is it raining?", &mut interner);
2623 let tokens = lexer.tokenize();
2624 assert!(!tokens.is_empty());
2625 }
2626
2627 #[test]
2628 fn ring_is_not_verb() {
2629 let mut interner = Interner::new();
2630 let mut lexer = Lexer::new("ring", &mut interner);
2631 let tokens = lexer.tokenize();
2632 assert!(matches!(tokens[0].kind, TokenType::Noun(_)));
2633 }
2634
2635 #[test]
2636 fn debug_that_token() {
2637 let mut interner = Interner::new();
2638 let mut lexer = Lexer::new("The cat that runs", &mut interner);
2639 let tokens = lexer.tokenize();
2640 for (i, t) in tokens.iter().enumerate() {
2641 let lex = interner.resolve(t.lexeme);
2642 eprintln!("Token[{}]: {:?} -> {:?}", i, lex, t.kind);
2643 }
2644 let that_token = tokens.iter().find(|t| interner.resolve(t.lexeme) == "that");
2645 if let Some(t) = that_token {
2646 let check = std::mem::discriminant(&t.kind) == std::mem::discriminant(&TokenType::That);
2648 eprintln!("Discriminant check for That: {}", check);
2649 assert!(matches!(t.kind, TokenType::That), "'that' should be TokenType::That, got {:?}", t.kind);
2650 } else {
2651 panic!("No 'that' token found");
2652 }
2653 }
2654
2655 #[test]
2656 fn bus_is_not_verb() {
2657 let mut interner = Interner::new();
2658 let mut lexer = Lexer::new("bus", &mut interner);
2659 let tokens = lexer.tokenize();
2660 assert!(matches!(tokens[0].kind, TokenType::Noun(_)));
2661 }
2662
2663 #[test]
2664 fn lowercase_a_is_article() {
2665 let mut interner = Interner::new();
2666 let mut lexer = Lexer::new("a car", &mut interner);
2667 let tokens = lexer.tokenize();
2668 for (i, t) in tokens.iter().enumerate() {
2669 let lex = interner.resolve(t.lexeme);
2670 eprintln!("Token[{}]: {:?} -> {:?}", i, lex, t.kind);
2671 }
2672 assert_eq!(tokens[0].kind, TokenType::Article(Definiteness::Indefinite));
2673 assert!(matches!(tokens[1].kind, TokenType::Noun(_)), "Expected Noun, got {:?}", tokens[1].kind);
2674 }
2675
2676 #[test]
2677 fn open_is_ambiguous() {
2678 let mut interner = Interner::new();
2679 let mut lexer = Lexer::new("open", &mut interner);
2680 let tokens = lexer.tokenize();
2681
2682 if let TokenType::Ambiguous { primary, alternatives } = &tokens[0].kind {
2683 assert!(matches!(**primary, TokenType::Verb { .. }), "Primary should be Verb");
2684 assert!(alternatives.iter().any(|t| matches!(t, TokenType::Adjective(_))),
2685 "Should have Adjective alternative");
2686 } else {
2687 panic!("Expected Ambiguous token for 'open', got {:?}", tokens[0].kind);
2688 }
2689 }
2690
2691 #[test]
2692 fn basic_tokenization() {
2693 let mut interner = Interner::new();
2694 let mut lexer = Lexer::new("All men are mortal.", &mut interner);
2695 let tokens = lexer.tokenize();
2696 assert_eq!(tokens[0].kind, TokenType::All);
2697 assert!(matches!(tokens[1].kind, TokenType::Noun(_)));
2698 assert_eq!(tokens[2].kind, TokenType::Are);
2699 }
2700
2701 #[test]
2702 fn iff_tokenizes_as_single_token() {
2703 let mut interner = Interner::new();
2704 let mut lexer = Lexer::new("A if and only if B", &mut interner);
2705 let tokens = lexer.tokenize();
2706 assert!(
2707 tokens.iter().any(|t| t.kind == TokenType::Iff),
2708 "should contain Iff token: got {:?}",
2709 tokens
2710 );
2711 }
2712
2713 #[test]
2714 fn is_equal_to_tokenizes_as_identity() {
2715 let mut interner = Interner::new();
2716 let mut lexer = Lexer::new("Socrates is equal to Socrates", &mut interner);
2717 let tokens = lexer.tokenize();
2718 assert!(
2719 tokens.iter().any(|t| t.kind == TokenType::Identity),
2720 "should contain Identity token: got {:?}",
2721 tokens
2722 );
2723 }
2724
2725 #[test]
2726 fn is_identical_to_tokenizes_as_identity() {
2727 let mut interner = Interner::new();
2728 let mut lexer = Lexer::new("Clark is identical to Superman", &mut interner);
2729 let tokens = lexer.tokenize();
2730 assert!(
2731 tokens.iter().any(|t| t.kind == TokenType::Identity),
2732 "should contain Identity token: got {:?}",
2733 tokens
2734 );
2735 }
2736
2737 #[test]
2738 fn itself_tokenizes_as_reflexive() {
2739 let mut interner = Interner::new();
2740 let mut lexer = Lexer::new("John loves itself", &mut interner);
2741 let tokens = lexer.tokenize();
2742 assert!(
2743 tokens.iter().any(|t| t.kind == TokenType::Reflexive),
2744 "should contain Reflexive token: got {:?}",
2745 tokens
2746 );
2747 }
2748
2749 #[test]
2750 fn himself_tokenizes_as_reflexive() {
2751 let mut interner = Interner::new();
2752 let mut lexer = Lexer::new("John sees himself", &mut interner);
2753 let tokens = lexer.tokenize();
2754 assert!(
2755 tokens.iter().any(|t| t.kind == TokenType::Reflexive),
2756 "should contain Reflexive token: got {:?}",
2757 tokens
2758 );
2759 }
2760
2761 #[test]
2762 fn to_stay_tokenizes_correctly() {
2763 let mut interner = Interner::new();
2764 let mut lexer = Lexer::new("to stay", &mut interner);
2765 let tokens = lexer.tokenize();
2766 assert!(
2767 tokens.iter().any(|t| t.kind == TokenType::To),
2768 "should contain To token: got {:?}",
2769 tokens
2770 );
2771 assert!(
2772 tokens.iter().any(|t| matches!(t.kind, TokenType::Verb { .. })),
2773 "should contain Verb token for stay: got {:?}",
2774 tokens
2775 );
2776 }
2777
2778 #[test]
2779 fn possessive_apostrophe_s() {
2780 let mut interner = Interner::new();
2781 let mut lexer = Lexer::new("John's dog", &mut interner);
2782 let tokens = lexer.tokenize();
2783 assert!(
2784 tokens.iter().any(|t| t.kind == TokenType::Possessive),
2785 "should contain Possessive token: got {:?}",
2786 tokens
2787 );
2788 assert!(
2789 tokens.iter().any(|t| matches!(&t.kind, TokenType::ProperName(_))),
2790 "should have John as proper name: got {:?}",
2791 tokens
2792 );
2793 }
2794
2795 #[test]
2796 fn lexer_produces_valid_spans() {
2797 let input = "All men are mortal.";
2798 let mut interner = Interner::new();
2799 let mut lexer = Lexer::new(input, &mut interner);
2800 let tokens = lexer.tokenize();
2801
2802 assert_eq!(tokens[0].span.start, 0);
2804 assert_eq!(tokens[0].span.end, 3);
2805 assert_eq!(&input[tokens[0].span.start..tokens[0].span.end], "All");
2806
2807 assert_eq!(tokens[1].span.start, 4);
2809 assert_eq!(tokens[1].span.end, 7);
2810 assert_eq!(&input[tokens[1].span.start..tokens[1].span.end], "men");
2811
2812 assert_eq!(tokens[2].span.start, 8);
2814 assert_eq!(tokens[2].span.end, 11);
2815 assert_eq!(&input[tokens[2].span.start..tokens[2].span.end], "are");
2816
2817 assert_eq!(tokens[3].span.start, 12);
2819 assert_eq!(tokens[3].span.end, 18);
2820 assert_eq!(&input[tokens[3].span.start..tokens[3].span.end], "mortal");
2821
2822 assert_eq!(tokens[4].span.start, 18);
2824 assert_eq!(tokens[4].span.end, 19);
2825
2826 assert_eq!(tokens[5].span.start, input.len());
2828 assert_eq!(tokens[5].kind, TokenType::EOF);
2829 }
2830
2831 #[test]
2832 fn triple_quote_produces_string_token() {
2833 let mut interner = Interner::new();
2834 let source = "## Main\nLet msg be \"\"\"\n Hello\n World\n\"\"\".\nShow msg.";
2835 let mut lexer = Lexer::new(source, &mut interner);
2836 let tokens = lexer.tokenize();
2837 for (i, t) in tokens.iter().enumerate() {
2839 let lex = interner.resolve(t.lexeme);
2840 eprintln!("Token[{}]: {:?} lex={:?} span={}..{}", i, t.kind, lex, t.span.start, t.span.end);
2841 }
2842 let str_token = tokens.iter().find(|t| matches!(t.kind, TokenType::StringLiteral(_) | TokenType::InterpolatedString(_)));
2844 assert!(str_token.is_some(), "Should have a string token. Tokens: {:?}", tokens.iter().map(|t| format!("{:?}", t.kind)).collect::<Vec<_>>());
2845 if let Some(tok) = str_token {
2846 let content = interner.resolve(tok.lexeme);
2847 eprintln!("Triple-quote content: {:?}", content);
2848 assert!(content.contains("Hello"), "Should contain Hello, got: {:?}", content);
2849 }
2850 }
2851}