1use std::fmt::{Debug, Display, Formatter};
2use std::hash::{Hash, Hasher};
3use std::ops::Range;
4use std::sync::{
5 Arc, OnceLock,
6 atomic::{AtomicU16, Ordering},
7};
8
9pub type Offset = usize;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
22pub struct LineColumn {
28 pub line: usize,
30 pub column: usize,
32}
33
34impl Display for LineColumn {
35 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
36 write!(formatter, "{}:{}", self.line, self.column)
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub struct LineColumnRange {
46 pub start: LineColumn,
48 pub end: LineColumn,
50}
51
52impl Display for LineColumnRange {
53 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
54 if self.start == self.end {
55 Display::fmt(&self.start, formatter)
56 } else {
57 write!(formatter, "{}-{}", self.start, self.end)
58 }
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
73pub enum TextEdit {
74 Insertion {
76 at: Offset,
78 text: String,
80 },
81 Deletion {
83 start: Offset,
85 end: Offset,
87 },
88}
89
90impl TextEdit {
91 pub fn insertion(at: Offset, text: impl Into<String>) -> Self {
93 Self::Insertion {
94 at,
95 text: text.into(),
96 }
97 }
98
99 pub fn deletion(start: Offset, end: Offset) -> Self {
105 assert!(start <= end, "deletion start must not exceed its end");
106 Self::Deletion { start, end }
107 }
108
109 fn shift(&self, offset: Offset) -> Offset {
121 match self {
122 Self::Insertion { at, text } => {
123 if offset >= *at {
124 offset + text.len()
125 } else {
126 offset
127 }
128 }
129 Self::Deletion { start, end } => {
130 if offset <= *start {
131 offset
132 } else if offset <= *end {
133 *start
135 } else {
136 offset - (end - start)
137 }
138 }
139 }
140 }
141
142 pub fn apply(&self, source: &str) -> Option<String> {
145 let (cut, resumes) = match self {
146 Self::Insertion { at, .. } => (*at, *at),
147 Self::Deletion { start, end } => (*start, *end),
148 };
149 let mut edited = String::from(source.get(..cut)?);
150 if let Self::Insertion { text, .. } = self {
151 edited.push_str(text);
152 }
153 edited.push_str(source.get(resumes..)?);
154 Some(edited)
155 }
156}
157
158static NEXT_FILE_ID: AtomicU16 = AtomicU16::new(1);
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
167pub enum File {
168 FileId(u16),
170 Synthetic(u16),
172}
173
174impl File {
175 pub fn fresh() -> Self {
181 let id = NEXT_FILE_ID.fetch_add(1, Ordering::Relaxed);
182 Self::FileId(id)
183 }
184
185 pub const fn from_raw(raw: u16) -> Self {
187 Self::FileId(raw)
188 }
189
190 pub const fn into_raw(self) -> u16 {
192 match self {
193 Self::FileId(id) | Self::Synthetic(id) => id,
194 }
195 }
196}
197
198pub type SourceId = File;
200
201pub struct Source {
203 file: File,
205 text: Arc<str>,
207 line_starts: OnceLock<Box<[Offset]>>,
209}
210
211impl Source {
212 pub fn new(text: impl Into<Arc<str>>) -> Arc<Self> {
214 Self::with_file(File::fresh(), text)
215 }
216
217 pub fn synthetic(text: impl Into<Arc<str>>) -> Arc<Self> {
219 let id = NEXT_FILE_ID.fetch_add(1, Ordering::Relaxed);
220 Self::with_file(File::Synthetic(id), text)
221 }
222
223 pub fn with_file(file: File, text: impl Into<Arc<str>>) -> Arc<Self> {
225 let text = text.into();
226 Arc::new(Self {
227 file,
228 text,
229 line_starts: OnceLock::new(),
230 })
231 }
232
233 pub fn file(&self) -> File {
235 self.file
236 }
237
238 pub fn text(&self) -> &str {
240 &self.text
241 }
242
243 pub fn line_column(&self, offset: Offset) -> Option<LineColumn> {
248 self.text.get(..offset)?;
249 let line_starts = self.line_starts.get_or_init(|| {
250 std::iter::once(0)
251 .chain(
252 self.text
253 .bytes()
254 .enumerate()
255 .filter_map(|(offset, byte)| (byte == b'\n').then_some(offset + 1)),
256 )
257 .collect::<Vec<_>>()
258 .into_boxed_slice()
259 });
260 let line_index = line_starts
261 .partition_point(|line_start| *line_start <= offset)
262 .checked_sub(1)?;
263 let line_start = line_starts[line_index];
264 let column = self.text.get(line_start..offset)?.chars().count() + 1;
265 Some(LineColumn {
266 line: line_index + 1,
267 column,
268 })
269 }
270}
271
272impl Debug for Source {
273 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
274 formatter
275 .debug_struct("Source")
276 .field("file", &self.file)
277 .field("bytes", &self.text.len())
278 .finish_non_exhaustive()
279 }
280}
281
282#[derive(Clone)]
284pub struct Span {
285 source: Arc<Source>,
286 range: Option<Range<Offset>>,
287}
288
289impl Span {
290 pub fn new(source: Arc<Source>, range: Option<Range<Offset>>) -> Self {
292 Self {
293 source,
294 range: range.filter(|range| range.start <= range.end),
295 }
296 }
297
298 pub fn ranged(source: Arc<Source>, range: Range<Offset>) -> Self {
300 Self::new(source, Some(range))
301 }
302
303 pub fn unlocated(source: Arc<Source>) -> Self {
305 Self {
306 source,
307 range: None,
308 }
309 }
310
311 pub fn for_text(text: impl Into<Arc<str>>) -> Self {
313 let source = Source::synthetic(text);
314 Self::ranged(Arc::clone(&source), 0..source.text().len())
315 }
316
317 pub fn file(&self) -> File {
319 self.source.file()
320 }
321
322 pub fn source(&self) -> &Arc<Source> {
324 &self.source
325 }
326
327 pub fn source_id(&self) -> File {
329 self.file()
330 }
331
332 pub fn range(&self) -> Option<Range<Offset>> {
334 self.range.clone()
335 }
336
337 pub fn with_source(mut self, source: Arc<Source>) -> Self {
339 self.source = source;
340 self
341 }
342
343 pub fn with_text(self, text: impl Into<Arc<str>>) -> Self {
349 let text = text.into();
350 if self.text() == Some(text.as_ref()) {
351 return self;
352 }
353 let source = Source::with_file(self.file(), text);
354 Self::ranged(Arc::clone(&source), 0..source.text().len())
355 }
356
357 pub fn text(&self) -> Option<&str> {
359 self.source.text().get(self.range.as_ref()?.clone())
360 }
361 pub fn at(&self, offset: Offset) -> Self {
363 if self.range.is_none() {
364 return Self::unlocated(Arc::clone(&self.source));
365 }
366 Self {
367 source: Arc::clone(&self.source),
368 range: Some(offset..offset),
369 }
370 }
371
372 pub fn subspan(&self, start: Offset, end: Offset) -> Self {
377 let Some(outer) = &self.range else {
378 return Self::unlocated(Arc::clone(&self.source));
379 };
380 let Some(start) = outer.start.checked_add(start) else {
381 return Self::unlocated(Arc::clone(&self.source));
382 };
383 let Some(end) = outer.start.checked_add(end) else {
384 return Self::unlocated(Arc::clone(&self.source));
385 };
386 if start > end || end > outer.end {
387 return Self::unlocated(Arc::clone(&self.source));
388 }
389 Self {
390 source: Arc::clone(&self.source),
391 range: Some(start..end),
392 }
393 }
394
395 pub fn start(&self) -> Option<Offset> {
397 self.bounds().map(|(start, _)| start)
398 }
399
400 pub fn end(&self) -> Option<Offset> {
402 self.bounds().map(|(_, end)| end)
403 }
404
405 pub fn bounds(&self) -> Option<(Offset, Offset)> {
407 self.range.as_ref().map(|range| (range.start, range.end))
408 }
409
410 pub fn line_column_range(&self) -> Option<LineColumnRange> {
415 let (start, end) = self.bounds()?;
416 Some(LineColumnRange {
417 start: self.source.line_column(start)?,
418 end: self.source.line_column(end)?,
419 })
420 }
421
422 pub fn len(&self) -> Option<usize> {
424 self.bounds().map(|(start, end)| end - start)
425 }
426
427 pub fn is_empty(&self) -> bool {
430 self.len().unwrap_or(0) == 0
431 }
432
433 pub fn shares_source(&self, other: &Self) -> bool {
435 Arc::ptr_eq(&self.source, &other.source)
436 }
437
438 pub fn contains(&self, offset: Offset) -> bool {
446 self.bounds()
447 .is_some_and(|(start, end)| start <= offset && offset < end)
448 }
449
450 pub fn touches(&self, offset: Offset) -> bool {
457 self.bounds()
458 .is_some_and(|(start, end)| start <= offset && offset <= end)
459 }
460
461 pub fn shifted(&self, edit: &TextEdit) -> Self {
474 match self.bounds() {
475 None => Self::unlocated(Arc::clone(&self.source)),
476 Some((start, end)) => Self {
477 source: Arc::clone(&self.source),
478 range: Some(edit.shift(start)..edit.shift(end)),
479 },
480 }
481 }
482
483 pub fn resized(&self, len: usize) -> Self {
486 let Some(start) = self.start() else {
487 return Self::unlocated(Arc::clone(&self.source));
488 };
489 Self {
490 source: Arc::clone(&self.source),
491 range: Some(start..start.saturating_add(len)),
492 }
493 }
494}
495
496impl Span {
497 pub fn join(&self, other: Self) -> Option<Self> {
500 if !self.shares_source(&other) {
501 return None;
502 }
503 match (self.bounds(), other.bounds()) {
504 (None, _) => Some(other),
505 (_, None) => Some(self.clone()),
506 (Some((start, end)), Some((start2, end2))) => {
507 let start = start.min(start2);
508 let end = end.max(end2);
509 Some(Self {
510 source: Arc::clone(&self.source),
511 range: Some(start..end),
512 })
513 }
514 }
515 }
516
517 pub fn meet(&self, other: Self) -> Option<Self> {
520 if !self.shares_source(&other) {
521 return None;
522 }
523 match (self.bounds(), other.bounds()) {
524 (Some((start, end)), Some((start2, end2))) => {
525 let start = start.max(start2);
526 let end = end.min(end2);
527 if start < end {
528 Some(Self {
529 source: Arc::clone(&self.source),
530 range: Some(start..end),
531 })
532 } else {
533 None
534 }
535 }
536 _ => None,
537 }
538 }
539
540 pub fn join_all<I, S>(spans: I) -> Option<Self>
543 where
544 I: IntoIterator<Item = S>,
545 S: Into<Option<Self>>,
546 {
547 let mut spans = spans.into_iter().filter_map(Into::into);
548 let first = spans.next()?;
549 spans.try_fold(first, |left, right| left.join(right))
550 }
551}
552
553impl PartialEq for Span {
554 fn eq(&self, other: &Self) -> bool {
555 self.shares_source(other) && self.range == other.range
556 }
557}
558
559impl Eq for Span {}
560
561impl Hash for Span {
562 fn hash<H: Hasher>(&self, state: &mut H) {
563 Arc::as_ptr(&self.source).hash(state);
564 self.range.hash(state);
565 }
566}
567
568impl Debug for Span {
569 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
570 let mut span = formatter.debug_struct("Span");
571 span.field("file", &self.file())
572 .field("range", &self.line_column_range())
573 .field("bytes", &self.range);
574 if let Some(text) = self.text() {
575 span.field("text", &text);
576 }
577 span.finish()
578 }
579}
580
581impl Display for Span {
582 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
583 match self.line_column_range() {
584 Some(range) => Display::fmt(&range, formatter),
585 None => formatter.write_str("unlocated"),
586 }
587 }
588}
589
590pub trait Spanned {
592 fn span(&self) -> Span {
599 self.try_span()
600 .expect("syntax must have one shared source snapshot")
601 }
602
603 fn try_span(&self) -> Option<Span> {
606 Some(self.span())
607 }
608
609 fn expect_span(&self) -> Span {
615 self.span()
616 }
617}
618
619impl Spanned for Span {
620 fn span(&self) -> Span {
621 self.clone()
622 }
623}
624
625impl<T: Spanned + ?Sized> Spanned for &T {
626 fn span(&self) -> Span {
627 (*self).span()
628 }
629
630 fn try_span(&self) -> Option<Span> {
631 (*self).try_span()
632 }
633}
634
635impl<T: Spanned + ?Sized> Spanned for Box<T> {
636 fn span(&self) -> Span {
637 self.as_ref().span()
638 }
639
640 fn try_span(&self) -> Option<Span> {
641 self.as_ref().try_span()
642 }
643}
644
645impl<T: Spanned> Spanned for Option<T> {
646 fn try_span(&self) -> Option<Span> {
647 self.as_ref().and_then(Spanned::try_span)
648 }
649}
650
651impl<T: Spanned> Spanned for Vec<T> {
652 fn try_span(&self) -> Option<Span> {
653 Span::join_all(self.iter().map(Spanned::try_span))
654 }
655}
656
657impl<A: Spanned, B: Spanned> Spanned for (A, B) {
658 fn try_span(&self) -> Option<Span> {
659 self.0.try_span()?.join(self.1.try_span()?)
660 }
661}
662
663impl<T: Spanned> Spanned for &[T] {
664 fn try_span(&self) -> Option<Span> {
665 Span::join_all(self.iter().map(Spanned::try_span))
666 }
667}
668
669#[cfg(test)]
670mod source_tests {
671 use std::sync::Arc;
672
673 use super::*;
674
675 #[test]
676 fn spans_resolve_text_from_one_shared_source() {
677 let source = Source::new("quotient ring");
678 let quotient = Span::ranged(Arc::clone(&source), 0..8);
679 let ring = Span::ranged(Arc::clone(&source), 9..13);
680
681 assert_eq!(quotient.text(), Some("quotient"));
682 assert_eq!(ring.text(), Some("ring"));
683 assert!(Arc::ptr_eq(quotient.source(), ring.source()));
684 }
685
686 #[test]
687 fn joining_different_sources_returns_none() {
688 let left = Span::for_text("x");
689 let right = Span::for_text("y");
690
691 assert_eq!(left.join(right), None);
692 }
693
694 #[test]
695 fn an_unlocated_span_still_knows_its_source() {
696 let source = Source::new("x + y");
697 let span = Span::unlocated(Arc::clone(&source));
698
699 assert_eq!(span.file(), source.file());
700 assert_eq!(span.range(), None);
701 assert_eq!(span.text(), None);
702 }
703
704 #[test]
705 fn source_snapshots_with_the_same_file_do_not_alias() {
706 let file = File::from_raw(7);
707 let before = Span::ranged(Source::with_file(file, "x"), 0..1);
708 let after = Span::ranged(Source::with_file(file, "y"), 0..1);
709
710 assert_ne!(before, after);
711 assert_eq!(before.join(after), None);
712 }
713
714 #[test]
715 fn shifting_preserves_source_identity() {
716 let source = Source::new("ring");
717 let span = Span::ranged(Arc::clone(&source), 0..4);
718 let shifted = span.shifted(&TextEdit::insertion(0, "graded "));
719
720 assert!(Arc::ptr_eq(&source, shifted.source()));
721 assert_eq!(shifted.range(), Some(7..11));
722 }
723
724 #[test]
725 fn line_columns_are_one_based_and_count_characters() {
726 let source = Source::new("aα\nβc");
727
728 assert_eq!(
729 source.line_column(0),
730 Some(LineColumn { line: 1, column: 1 }),
731 );
732 assert_eq!(
733 source.line_column("aα\n".len()),
734 Some(LineColumn { line: 2, column: 1 }),
735 );
736 assert_eq!(
737 source.line_column("aα\nβ".len()),
738 Some(LineColumn { line: 2, column: 2 }),
739 );
740 assert_eq!(source.line_column(2), None);
741 }
742
743 #[test]
744 fn spans_format_as_half_open_line_column_ranges() {
745 let source = Source::new("one\ntwo");
746 let range = Span::ranged(Arc::clone(&source), 2..6);
747 let point = range.at(4);
748
749 assert_eq!(range.to_string(), "1:3-2:3");
750 assert_eq!(point.to_string(), "2:1");
751 }
752}