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);
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
166pub enum File {
167 FileId(u16),
169 Synthetic(u16),
171}
172
173impl File {
174 pub fn fresh() -> Self {
180 let id = NEXT_FILE_ID.fetch_add(1, Ordering::Relaxed);
181 Self::FileId(id)
182 }
183
184 pub const fn from_raw(raw: u16) -> Self {
186 Self::FileId(raw)
187 }
188
189 pub const fn into_raw(self) -> u16 {
191 match self {
192 Self::FileId(id) | Self::Synthetic(id) => id,
193 }
194 }
195}
196
197pub type SourceId = File;
199
200pub struct Source {
202 file: File,
203 text: Arc<str>,
204 line_starts: OnceLock<Arc<[Offset]>>,
205}
206
207impl Source {
208 pub fn new(text: impl Into<Arc<str>>) -> Arc<Self> {
210 Self::with_file(File::fresh(), text)
211 }
212
213 pub fn synthetic(text: impl Into<Arc<str>>) -> Arc<Self> {
215 let id = NEXT_FILE_ID.fetch_add(1, Ordering::Relaxed);
216 Self::with_file(File::Synthetic(id), text)
217 }
218
219 pub fn with_file(file: File, text: impl Into<Arc<str>>) -> Arc<Self> {
221 let text = text.into();
222 Arc::new(Self {
223 file,
224 text,
225 line_starts: OnceLock::new(),
226 })
227 }
228
229 pub fn file(&self) -> File {
231 self.file
232 }
233
234 pub fn text(&self) -> &str {
236 &self.text
237 }
238
239 pub fn line_column(&self, offset: Offset) -> Option<LineColumn> {
244 self.text.get(..offset)?;
245 let line_starts = self.line_starts.get_or_init(|| {
246 std::iter::once(0)
247 .chain(
248 self.text
249 .bytes()
250 .enumerate()
251 .filter_map(|(offset, byte)| (byte == b'\n').then_some(offset + 1)),
252 )
253 .collect::<Vec<_>>()
254 .into()
255 });
256 let line_index = line_starts
257 .partition_point(|line_start| *line_start <= offset)
258 .checked_sub(1)?;
259 let line_start = line_starts[line_index];
260 let column = self.text.get(line_start..offset)?.chars().count() + 1;
261 Some(LineColumn {
262 line: line_index + 1,
263 column,
264 })
265 }
266}
267
268impl Debug for Source {
269 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
270 formatter
271 .debug_struct("Source")
272 .field("file", &self.file)
273 .field("bytes", &self.text.len())
274 .finish_non_exhaustive()
275 }
276}
277
278#[derive(Clone)]
280pub struct Span {
281 source: Arc<Source>,
282 range: Option<Range<Offset>>,
283}
284
285impl Span {
286 pub fn new(source: Arc<Source>, range: Option<Range<Offset>>) -> Self {
288 Self {
289 source,
290 range: range.filter(|range| range.start <= range.end),
291 }
292 }
293
294 pub fn ranged(source: Arc<Source>, range: Range<Offset>) -> Self {
296 Self::new(source, Some(range))
297 }
298
299 pub fn unlocated(source: Arc<Source>) -> Self {
301 Self {
302 source,
303 range: None,
304 }
305 }
306
307 pub fn for_text(text: impl Into<Arc<str>>) -> Self {
309 let source = Source::synthetic(text);
310 Self::ranged(Arc::clone(&source), 0..source.text().len())
311 }
312
313 pub fn file(&self) -> File {
315 self.source.file()
316 }
317
318 pub fn source(&self) -> &Arc<Source> {
320 &self.source
321 }
322
323 pub fn source_id(&self) -> File {
325 self.file()
326 }
327
328 pub fn range(&self) -> Option<Range<Offset>> {
330 self.range.clone()
331 }
332
333 pub fn with_source(mut self, source: Arc<Source>) -> Self {
335 self.source = source;
336 self
337 }
338
339 pub fn with_text(self, text: impl Into<Arc<str>>) -> Self {
345 let text = text.into();
346 if self.text() == Some(text.as_ref()) {
347 return self;
348 }
349 let source = Source::with_file(self.file(), text);
350 Self::ranged(Arc::clone(&source), 0..source.text().len())
351 }
352
353 pub fn text(&self) -> Option<&str> {
355 self.source.text().get(self.range.as_ref()?.clone())
356 }
357 pub fn at(&self, offset: Offset) -> Self {
359 if self.range.is_none() {
360 return Self::unlocated(Arc::clone(&self.source));
361 }
362 Self {
363 source: Arc::clone(&self.source),
364 range: Some(offset..offset),
365 }
366 }
367
368 pub fn subspan(&self, start: Offset, end: Offset) -> Self {
373 let Some(outer) = &self.range else {
374 return Self::unlocated(Arc::clone(&self.source));
375 };
376 let Some(start) = outer.start.checked_add(start) else {
377 return Self::unlocated(Arc::clone(&self.source));
378 };
379 let Some(end) = outer.start.checked_add(end) else {
380 return Self::unlocated(Arc::clone(&self.source));
381 };
382 if start > end || end > outer.end {
383 return Self::unlocated(Arc::clone(&self.source));
384 }
385 Self {
386 source: Arc::clone(&self.source),
387 range: Some(start..end),
388 }
389 }
390
391 pub fn start(&self) -> Option<Offset> {
393 self.bounds().map(|(start, _)| start)
394 }
395
396 pub fn end(&self) -> Option<Offset> {
398 self.bounds().map(|(_, end)| end)
399 }
400
401 pub fn bounds(&self) -> Option<(Offset, Offset)> {
403 self.range.as_ref().map(|range| (range.start, range.end))
404 }
405
406 pub fn line_column_range(&self) -> Option<LineColumnRange> {
411 let (start, end) = self.bounds()?;
412 Some(LineColumnRange {
413 start: self.source.line_column(start)?,
414 end: self.source.line_column(end)?,
415 })
416 }
417
418 pub fn len(&self) -> Option<usize> {
420 self.bounds().map(|(start, end)| end - start)
421 }
422
423 pub fn is_empty(&self) -> bool {
426 self.len().unwrap_or(0) == 0
427 }
428
429 pub fn shares_source(&self, other: &Self) -> bool {
431 Arc::ptr_eq(&self.source, &other.source)
432 }
433
434 pub fn contains(&self, offset: Offset) -> bool {
442 self.bounds()
443 .is_some_and(|(start, end)| start <= offset && offset < end)
444 }
445
446 pub fn touches(&self, offset: Offset) -> bool {
453 self.bounds()
454 .is_some_and(|(start, end)| start <= offset && offset <= end)
455 }
456
457 pub fn shifted(&self, edit: &TextEdit) -> Self {
470 match self.bounds() {
471 None => Self::unlocated(Arc::clone(&self.source)),
472 Some((start, end)) => Self {
473 source: Arc::clone(&self.source),
474 range: Some(edit.shift(start)..edit.shift(end)),
475 },
476 }
477 }
478
479 pub fn resized(&self, len: usize) -> Self {
482 let Some(start) = self.start() else {
483 return Self::unlocated(Arc::clone(&self.source));
484 };
485 Self {
486 source: Arc::clone(&self.source),
487 range: Some(start..start.saturating_add(len)),
488 }
489 }
490}
491
492impl Span {
493 pub fn join(&self, other: Self) -> Option<Self> {
496 if !self.shares_source(&other) {
497 return None;
498 }
499 match (self.bounds(), other.bounds()) {
500 (None, _) => Some(other),
501 (_, None) => Some(self.clone()),
502 (Some((start, end)), Some((start2, end2))) => {
503 let start = start.min(start2);
504 let end = end.max(end2);
505 Some(Self {
506 source: Arc::clone(&self.source),
507 range: Some(start..end),
508 })
509 }
510 }
511 }
512
513 pub fn meet(&self, other: Self) -> Option<Self> {
516 if !self.shares_source(&other) {
517 return None;
518 }
519 match (self.bounds(), other.bounds()) {
520 (Some((start, end)), Some((start2, end2))) => {
521 let start = start.max(start2);
522 let end = end.min(end2);
523 if start < end {
524 Some(Self {
525 source: Arc::clone(&self.source),
526 range: Some(start..end),
527 })
528 } else {
529 None
530 }
531 }
532 _ => None,
533 }
534 }
535
536 pub fn join_all<I, S>(spans: I) -> Option<Self>
539 where
540 I: IntoIterator<Item = S>,
541 S: Into<Option<Self>>,
542 {
543 let mut spans = spans.into_iter().filter_map(Into::into);
544 let first = spans.next()?;
545 spans.try_fold(first, |left, right| left.join(right))
546 }
547}
548
549impl PartialEq for Span {
550 fn eq(&self, other: &Self) -> bool {
551 self.shares_source(other) && self.range == other.range
552 }
553}
554
555impl Eq for Span {}
556
557impl Hash for Span {
558 fn hash<H: Hasher>(&self, state: &mut H) {
559 Arc::as_ptr(&self.source).hash(state);
560 self.range.hash(state);
561 }
562}
563
564impl Debug for Span {
565 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
566 let mut span = formatter.debug_struct("Span");
567 span.field("file", &self.file())
568 .field("range", &self.line_column_range())
569 .field("bytes", &self.range);
570 if let Some(text) = self.text() {
571 span.field("text", &text);
572 }
573 span.finish()
574 }
575}
576
577impl Display for Span {
578 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
579 match self.line_column_range() {
580 Some(range) => Display::fmt(&range, formatter),
581 None => formatter.write_str("unlocated"),
582 }
583 }
584}
585
586pub trait Spanned {
588 fn span(&self) -> Span {
595 self.try_span()
596 .expect("syntax must have one shared source snapshot")
597 }
598
599 fn try_span(&self) -> Option<Span> {
602 Some(self.span())
603 }
604
605 fn expect_span(&self) -> Span {
611 self.span()
612 }
613}
614
615impl Spanned for Span {
616 fn span(&self) -> Span {
617 self.clone()
618 }
619}
620
621impl<T: Spanned + ?Sized> Spanned for &T {
622 fn span(&self) -> Span {
623 (*self).span()
624 }
625
626 fn try_span(&self) -> Option<Span> {
627 (*self).try_span()
628 }
629}
630
631impl<T: Spanned + ?Sized> Spanned for Box<T> {
632 fn span(&self) -> Span {
633 self.as_ref().span()
634 }
635
636 fn try_span(&self) -> Option<Span> {
637 self.as_ref().try_span()
638 }
639}
640
641impl<T: Spanned> Spanned for Option<T> {
642 fn try_span(&self) -> Option<Span> {
643 self.as_ref().and_then(Spanned::try_span)
644 }
645}
646
647impl<T: Spanned> Spanned for Vec<T> {
648 fn try_span(&self) -> Option<Span> {
649 Span::join_all(self.iter().map(Spanned::try_span))
650 }
651}
652
653impl<A: Spanned, B: Spanned> Spanned for (A, B) {
654 fn try_span(&self) -> Option<Span> {
655 self.0.try_span()?.join(self.1.try_span()?)
656 }
657}
658
659impl<T: Spanned> Spanned for &[T] {
660 fn try_span(&self) -> Option<Span> {
661 Span::join_all(self.iter().map(Spanned::try_span))
662 }
663}
664
665#[cfg(test)]
666mod source_tests {
667 use std::sync::Arc;
668
669 use super::*;
670
671 #[test]
672 fn spans_resolve_text_from_one_shared_source() {
673 let source = Source::new("quotient ring");
674 let quotient = Span::ranged(Arc::clone(&source), 0..8);
675 let ring = Span::ranged(Arc::clone(&source), 9..13);
676
677 assert_eq!(quotient.text(), Some("quotient"));
678 assert_eq!(ring.text(), Some("ring"));
679 assert!(Arc::ptr_eq(quotient.source(), ring.source()));
680 }
681
682 #[test]
683 fn joining_different_sources_returns_none() {
684 let left = Span::for_text("x");
685 let right = Span::for_text("y");
686
687 assert_eq!(left.join(right), None);
688 }
689
690 #[test]
691 fn an_unlocated_span_still_knows_its_source() {
692 let source = Source::new("x + y");
693 let span = Span::unlocated(Arc::clone(&source));
694
695 assert_eq!(span.file(), source.file());
696 assert_eq!(span.range(), None);
697 assert_eq!(span.text(), None);
698 }
699
700 #[test]
701 fn source_snapshots_with_the_same_file_do_not_alias() {
702 let file = File::from_raw(7);
703 let before = Span::ranged(Source::with_file(file, "x"), 0..1);
704 let after = Span::ranged(Source::with_file(file, "y"), 0..1);
705
706 assert_ne!(before, after);
707 assert_eq!(before.join(after), None);
708 }
709
710 #[test]
711 fn shifting_preserves_source_identity() {
712 let source = Source::new("ring");
713 let span = Span::ranged(Arc::clone(&source), 0..4);
714 let shifted = span.shifted(&TextEdit::insertion(0, "graded "));
715
716 assert!(Arc::ptr_eq(&source, shifted.source()));
717 assert_eq!(shifted.range(), Some(7..11));
718 }
719
720 #[test]
721 fn line_columns_are_one_based_and_count_characters() {
722 let source = Source::new("aα\nβc");
723
724 assert_eq!(
725 source.line_column(0),
726 Some(LineColumn { line: 1, column: 1 }),
727 );
728 assert_eq!(
729 source.line_column("aα\n".len()),
730 Some(LineColumn { line: 2, column: 1 }),
731 );
732 assert_eq!(
733 source.line_column("aα\nβ".len()),
734 Some(LineColumn { line: 2, column: 2 }),
735 );
736 assert_eq!(source.line_column(2), None);
737 }
738
739 #[test]
740 fn spans_format_as_half_open_line_column_ranges() {
741 let source = Source::new("one\ntwo");
742 let range = Span::ranged(Arc::clone(&source), 2..6);
743 let point = range.at(4);
744
745 assert_eq!(range.to_string(), "1:3-2:3");
746 assert_eq!(point.to_string(), "2:1");
747 }
748}