1use std::borrow::Borrow;
25use std::collections::HashSet;
26use std::fmt;
27use std::ops::Deref;
28use std::sync::Arc;
29
30use crate::discovery::Language;
31use crate::ir::{ByteRange, IrNode, Shape};
32
33#[derive(Debug, Clone, Eq)]
42pub struct Lexeme(Arc<str>);
43
44impl Lexeme {
45 #[must_use]
47 pub fn as_str(&self) -> &str {
48 &self.0
49 }
50}
51
52impl PartialEq for Lexeme {
53 fn eq(&self, other: &Self) -> bool {
54 Arc::ptr_eq(&self.0, &other.0) || self.0 == other.0
57 }
58}
59
60impl std::hash::Hash for Lexeme {
61 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
62 self.0.hash(state);
64 }
65}
66
67impl Deref for Lexeme {
68 type Target = str;
69
70 fn deref(&self) -> &str {
71 &self.0
72 }
73}
74
75impl AsRef<str> for Lexeme {
76 fn as_ref(&self) -> &str {
77 &self.0
78 }
79}
80
81impl Borrow<str> for Lexeme {
82 fn borrow(&self) -> &str {
83 &self.0
84 }
85}
86
87impl From<&str> for Lexeme {
88 fn from(text: &str) -> Self {
89 Self(Arc::from(text))
90 }
91}
92
93impl PartialEq<str> for Lexeme {
94 fn eq(&self, other: &str) -> bool {
95 &*self.0 == other
96 }
97}
98
99impl PartialEq<&str> for Lexeme {
100 fn eq(&self, other: &&str) -> bool {
101 &*self.0 == *other
102 }
103}
104
105impl fmt::Display for Lexeme {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 f.write_str(&self.0)
108 }
109}
110
111#[derive(Debug, Default)]
117pub struct LexemeInterner {
118 known: HashSet<Lexeme>,
119}
120
121impl LexemeInterner {
122 #[must_use]
124 pub fn new() -> Self {
125 Self::default()
126 }
127
128 pub fn intern(&mut self, text: &str) -> Lexeme {
130 if let Some(found) = self.known.get(text) {
131 return found.clone();
132 }
133 let lexeme = Lexeme::from(text);
134 self.known.insert(lexeme.clone());
135 lexeme
136 }
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum LiteralKind {
142 Integer,
144 Float,
146 String,
148 Char,
150 Bool,
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub enum TokenKind {
157 Identifier,
159 Keyword,
161 Literal(LiteralKind),
163 Lifetime,
165 Punctuation,
167 Unknown,
169}
170
171impl TokenKind {
172 #[must_use]
177 pub const fn tag(self) -> u8 {
178 match self {
179 Self::Identifier => 1,
180 Self::Keyword => 2,
181 Self::Literal(_) => 3,
182 Self::Punctuation => 4,
183 Self::Lifetime => 5,
184 Self::Unknown => 6,
185 }
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub struct SourceSpan {
196 pub start_byte: usize,
198 pub end_byte: usize,
200 pub start_line: u32,
202 pub start_column: u32,
204}
205
206#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct Token {
209 pub kind: TokenKind,
211 pub text: Lexeme,
213 pub span: SourceSpan,
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum DiagnosticKind {
220 UnterminatedString,
222 UnterminatedChar,
224 UnterminatedBlockComment,
226 UnexpectedCharacter,
228 UnmatchedDelimiter,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
234pub struct Diagnostic {
235 pub kind: DiagnosticKind,
237 pub span: SourceSpan,
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub enum UnitKind {
244 Function,
246 Method,
248 Impl,
250 Record,
252 Closure,
254}
255
256impl UnitKind {
257 #[must_use]
259 pub const fn name(self) -> &'static str {
260 match self {
261 Self::Function => "function",
262 Self::Method => "method",
263 Self::Impl => "impl",
264 Self::Record => "record",
265 Self::Closure => "closure",
266 }
267 }
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
272pub struct Unit {
273 pub kind: UnitKind,
275 pub name: Option<String>,
277 pub token_start: usize,
279 pub token_end: usize,
281 pub span: SourceSpan,
283}
284
285#[must_use]
295pub fn tokens_in_range(tokens: &[Token], token_start: usize, token_end: usize) -> &[Token] {
296 let start = token_start.min(tokens.len());
297 let end = token_end.min(tokens.len()).max(start);
298 &tokens[start..end]
299}
300
301#[derive(Debug, Clone)]
303pub struct LexedFile {
304 pub language: Language,
306 pub frontend_version: &'static str,
309 pub tokens: Vec<Token>,
311 pub units: Vec<Unit>,
313 pub diagnostics: Vec<Diagnostic>,
315}
316
317#[derive(Debug, Clone)]
323pub struct AssembledIr {
324 pub tokens: Vec<Token>,
326 pub error_ranges: Vec<ByteRange>,
328 pub depth_truncated: bool,
330}
331
332#[derive(Debug)]
342pub struct IrAssembly<'s> {
343 source: &'s str,
344 line_starts: Vec<usize>,
346 interner: LexemeInterner,
347 tokens: Vec<Token>,
348 token_starts: Vec<usize>,
351 error_ranges: Vec<ByteRange>,
352 depth_truncated: bool,
353}
354
355impl<'s> IrAssembly<'s> {
356 #[must_use]
358 pub fn new(source: &'s str) -> Self {
359 let mut line_starts = vec![0];
360 for (index, byte) in source.bytes().enumerate() {
361 if byte == b'\n' {
362 line_starts.push(index + 1);
363 }
364 }
365 Self {
366 source,
367 line_starts,
368 interner: LexemeInterner::new(),
369 tokens: Vec::new(),
370 token_starts: Vec::new(),
371 error_ranges: Vec::new(),
372 depth_truncated: false,
373 }
374 }
375
376 #[must_use]
378 pub const fn source(&self) -> &'s str {
379 self.source
380 }
381
382 pub fn intern(&mut self, text: &str) -> Lexeme {
384 self.interner.intern(text)
385 }
386
387 #[must_use]
389 pub fn line_column(&self, byte: usize) -> (u32, u32) {
390 let line_index = self
391 .line_starts
392 .partition_point(|&start| start <= byte)
393 .saturating_sub(1);
394 let line_start = self.line_starts.get(line_index).copied().unwrap_or(0);
395 let column_chars = self
396 .source
397 .get(line_start..byte)
398 .map_or(0, |prefix| prefix.chars().count());
399 (
400 u32::try_from(line_index + 1).unwrap_or(u32::MAX),
401 u32::try_from(column_chars + 1).unwrap_or(u32::MAX),
402 )
403 }
404
405 #[must_use]
407 pub fn span(&self, start_byte: usize, end_byte: usize) -> SourceSpan {
408 let (start_line, start_column) = self.line_column(start_byte);
409 SourceSpan {
410 start_byte,
411 end_byte,
412 start_line,
413 start_column,
414 }
415 }
416
417 pub fn push_token(&mut self, kind: TokenKind, text: &str, start_byte: usize, end_byte: usize) {
422 let span = self.span(start_byte, end_byte);
423 self.push_spanned_token(kind, text, span);
424 }
425
426 pub fn push_spanned_token(&mut self, kind: TokenKind, text: &str, span: SourceSpan) {
431 let text = self.interner.intern(text);
432 self.token_starts.push(span.start_byte);
433 self.tokens.push(Token { kind, text, span });
434 }
435
436 #[must_use]
439 pub const fn token_count(&self) -> usize {
440 self.tokens.len()
441 }
442
443 #[must_use]
445 pub fn token_index_at(&self, byte: usize) -> usize {
446 self.token_starts.partition_point(|&start| start < byte)
447 }
448
449 #[must_use]
451 pub fn token_bounds(&self, range: ByteRange) -> (usize, usize) {
452 (
453 self.token_index_at(range.start),
454 self.token_index_at(range.end),
455 )
456 }
457
458 pub fn record_error_range(&mut self, range: ByteRange) {
460 self.error_ranges.push(range);
461 }
462
463 #[must_use]
465 pub const fn depth_truncated(&self) -> bool {
466 self.depth_truncated
467 }
468
469 pub fn truncate_at_depth(&mut self, range: ByteRange) -> IrNode {
476 self.depth_truncated = true;
477 self.record_error_range(range);
478 self.error_node(range)
479 }
480
481 #[must_use]
483 pub fn error_node(&self, range: ByteRange) -> IrNode {
484 let (token_start, token_end) = self.token_bounds(range);
485 IrNode {
486 shape: Shape::Error,
487 name: None,
488 token_start,
489 token_end,
490 range,
491 children: Vec::new(),
492 }
493 }
494
495 #[must_use]
497 pub fn finish(mut self) -> AssembledIr {
498 self.error_ranges
499 .sort_unstable_by_key(|range| (range.start, range.end));
500 self.error_ranges.dedup();
501 AssembledIr {
502 tokens: self.tokens,
503 error_ranges: self.error_ranges,
504 depth_truncated: self.depth_truncated,
505 }
506 }
507}
508
509pub trait Frontend {
511 fn language(&self) -> Language;
513
514 fn frontend_version(&self) -> &'static str;
516
517 fn lex(&self, source: &str) -> LexedFile;
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn kind_tags_are_distinct_and_stable() {
527 let tags = [
528 TokenKind::Identifier.tag(),
529 TokenKind::Keyword.tag(),
530 TokenKind::Literal(LiteralKind::Integer).tag(),
531 TokenKind::Punctuation.tag(),
532 TokenKind::Lifetime.tag(),
533 TokenKind::Unknown.tag(),
534 ];
535 let mut sorted = tags.to_vec();
536 sorted.sort_unstable();
537 sorted.dedup();
538 assert_eq!(sorted.len(), tags.len(), "tags must be distinct");
539 assert_eq!(
541 TokenKind::Literal(LiteralKind::Integer).tag(),
542 TokenKind::Literal(LiteralKind::String).tag()
543 );
544 }
545
546 #[test]
551 fn the_shared_clamp_covers_the_tokens_each_call_site_clamped_to() {
552 let token = |text: &str| Token {
553 kind: TokenKind::Identifier,
554 text: text.into(),
555 span: SourceSpan {
556 start_byte: 0,
557 end_byte: 1,
558 start_line: 1,
559 start_column: 1,
560 },
561 };
562 let tokens = [token("a"), token("b"), token("c")];
563 let previous = |start: usize, end: usize| {
565 let start = start.min(tokens.len());
566 let end = end.min(tokens.len()).max(start);
567 &tokens[start..end]
568 };
569
570 for (start, end) in [
571 (0, 3),
572 (1, 2),
573 (2, 2),
574 (0, 9),
575 (5, 9),
576 (3, 3),
577 (2, 1),
578 (9, 1),
579 ] {
580 assert_eq!(
581 tokens_in_range(&tokens, start, end),
582 previous(start, end),
583 "range {start}..{end}"
584 );
585 }
586 assert!(
587 tokens_in_range(&tokens, 5, 9).is_empty(),
588 "{:?}",
589 tokens_in_range(&tokens, 5, 9)
590 );
591 assert!(
592 tokens_in_range(&tokens, 2, 1).is_empty(),
593 "{:?}",
594 tokens_in_range(&tokens, 2, 1)
595 );
596 assert!(
597 tokens_in_range(&[], 0, 4).is_empty(),
598 "{:?}",
599 tokens_in_range(&[], 0, 4)
600 );
601 assert_eq!(tokens_in_range(&tokens, 1, 9).len(), 2);
602 }
603
604 #[test]
605 fn interning_shares_one_allocation_per_text() {
606 let mut interner = LexemeInterner::new();
607 let a = interner.intern("alpha");
608 let b = interner.intern("alpha");
609 let c = interner.intern("beta");
610 assert!(Arc::ptr_eq(&a.0, &b.0), "same text must share storage");
611 assert!(!Arc::ptr_eq(&a.0, &c.0));
612 assert_eq!(a, b);
613 assert_ne!(a, c);
614 }
615
616 #[test]
617 fn lexeme_equality_and_hash_follow_content_across_interners() {
618 let a = LexemeInterner::new().intern("shared");
619 let b = LexemeInterner::new().intern("shared");
620 assert!(
621 !Arc::ptr_eq(&a.0, &b.0),
622 "distinct interners allocate separately"
623 );
624 assert_eq!(a, b, "equality is by content, not by pointer");
625 let set: HashSet<Lexeme> = [a].into();
626 assert!(set.contains("shared"), "str lookups must hash consistently");
627 }
628
629 #[test]
630 fn lexeme_compares_against_plain_strings() {
631 let lexeme = Lexeme::from("fn");
632 assert_eq!(lexeme, "fn");
633 assert_eq!(lexeme.as_str(), "fn");
634 assert_eq!(lexeme.to_string(), "fn");
635 assert_eq!(lexeme.as_bytes(), b"fn");
636 }
637
638 #[test]
639 fn assembly_columns_count_characters_not_bytes() {
640 let source = "let é = 1;\nlet b = 2;\n";
641 let assembly = IrAssembly::new(source);
642 let e_acute = source.find('é').unwrap_or_default();
643 assert_eq!(assembly.line_column(0), (1, 1));
644 assert_eq!(assembly.line_column(e_acute), (1, 5));
645 assert_eq!(assembly.line_column(e_acute + 'é'.len_utf8()), (1, 6));
648 let second_line = source.find("let b").unwrap_or_default();
649 assert_eq!(assembly.line_column(second_line), (2, 1));
650 }
651
652 #[test]
653 fn assembly_maps_byte_ranges_onto_token_indices() {
654 let source = "a bb ccc";
655 let mut assembly = IrAssembly::new(source);
656 for (start, end) in [(0, 1), (2, 4), (5, 8)] {
657 let text = source.get(start..end).unwrap_or_default();
658 assembly.push_token(TokenKind::Identifier, text, start, end);
659 }
660 assert_eq!(assembly.token_count(), 3);
661 assert_eq!(assembly.token_index_at(1), 1);
663 assert_eq!(
664 assembly.token_bounds(ByteRange { start: 2, end: 8 }),
665 (1, 3)
666 );
667 assert_eq!(
668 assembly.token_bounds(ByteRange {
669 start: 0,
670 end: source.len()
671 }),
672 (0, 3)
673 );
674 }
675
676 #[test]
677 fn assembly_records_a_depth_limited_subtree_as_an_error_leaf() {
678 let source = "a bb ccc";
679 let mut assembly = IrAssembly::new(source);
680 for (start, end) in [(0, 1), (2, 4), (5, 8)] {
681 let text = source.get(start..end).unwrap_or_default();
682 assembly.push_token(TokenKind::Identifier, text, start, end);
683 }
684 assert!(!assembly.depth_truncated());
685 let omitted = ByteRange { start: 2, end: 8 };
686 let leaf = assembly.truncate_at_depth(omitted);
687 assert_eq!(leaf.shape, Shape::Error);
688 assert_eq!(leaf.name, None);
689 assert_eq!((leaf.token_start, leaf.token_end), (1, 3));
690 assert_eq!(leaf.range, omitted);
691 assert!(leaf.children.is_empty(), "children: {:?}", leaf.children);
692 assert!(assembly.depth_truncated());
693 let assembled = assembly.finish();
694 assert!(assembled.depth_truncated);
695 assert_eq!(assembled.error_ranges, vec![omitted]);
696 }
697
698 #[test]
699 fn assembly_finishes_with_ordered_unique_error_ranges() {
700 let mut assembly = IrAssembly::new("abc");
701 for range in [(2, 3), (0, 1), (2, 3), (0, 2)] {
702 assembly.record_error_range(ByteRange {
703 start: range.0,
704 end: range.1,
705 });
706 }
707 let assembled = assembly.finish();
708 assert_eq!(
709 assembled.error_ranges,
710 vec![
711 ByteRange { start: 0, end: 1 },
712 ByteRange { start: 0, end: 2 },
713 ByteRange { start: 2, end: 3 },
714 ]
715 );
716 assert!(!assembled.depth_truncated);
717 }
718
719 #[test]
720 fn assembly_keeps_spans_established_elsewhere() {
721 let mut assembly = IrAssembly::new("fn a() {}");
724 let span = SourceSpan {
725 start_byte: 900,
726 end_byte: 901,
727 start_line: 42,
728 start_column: 7,
729 };
730 assembly.push_spanned_token(TokenKind::Punctuation, "}", span);
731 let assembled = assembly.finish();
732 assert_eq!(assembled.tokens.len(), 1);
733 assert_eq!(assembled.tokens[0].span, span);
734 assert_eq!(assembled.tokens[0].text, "}");
735 }
736
737 #[test]
738 fn unit_kind_names_are_stable() {
739 assert_eq!(UnitKind::Function.name(), "function");
740 assert_eq!(UnitKind::Method.name(), "method");
741 assert_eq!(UnitKind::Impl.name(), "impl");
742 assert_eq!(UnitKind::Closure.name(), "closure");
743 }
744}