1use alloc::{boxed::Box, sync::Arc, vec::Vec};
2use core::{fmt, num::NonZeroU32, ops::Range};
3
4use super::{FileLineCol, SourceId, SourceSpan};
5
6#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct SourceFile {
12 id: SourceId,
14 content: SourceContent,
16}
17
18#[cfg(feature = "diagnostics")]
19impl miette::SourceCode for SourceFile {
20 fn read_span<'a>(
21 &'a self,
22 span: &miette::SourceSpan,
23 context_lines_before: usize,
24 context_lines_after: usize,
25 ) -> Result<alloc::boxed::Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
26 let mut start =
27 u32::try_from(span.offset()).map_err(|_| miette::MietteError::OutOfBounds)?;
28 let len = u32::try_from(span.len()).map_err(|_| miette::MietteError::OutOfBounds)?;
29 let mut end = start.checked_add(len).ok_or(miette::MietteError::OutOfBounds)?;
30 if context_lines_before > 0 {
31 let line_index = self.content.line_index(start.into());
32 let start_line_index = line_index.saturating_sub(context_lines_before as u32);
33 start = self.content.line_start(start_line_index).map(|idx| idx.to_u32()).unwrap_or(0);
34 }
35 if context_lines_after > 0 {
36 let line_index = self.content.line_index(end.into());
37 let end_line_index = line_index
38 .checked_add(context_lines_after as u32)
39 .ok_or(miette::MietteError::OutOfBounds)?;
40 end = self
41 .content
42 .line_range(end_line_index)
43 .map(|range| range.end.to_u32())
44 .unwrap_or_else(|| self.content.source_range().end.to_u32());
45 }
46 Ok(Box::new(ScopedSourceFileRef {
47 file: self,
48 span: miette::SourceSpan::new((start as usize).into(), end.abs_diff(start) as usize),
49 }))
50 }
51}
52
53impl SourceFile {
54 pub fn new(id: SourceId, path: impl Into<Arc<str>>, content: impl Into<Box<str>>) -> Self {
56 let path = path.into();
57 let content = SourceContent::new(path, content.into());
58 Self { id, content }
59 }
60
61 pub(super) fn from_raw_parts(id: SourceId, content: SourceContent) -> Self {
62 Self { id, content }
63 }
64
65 pub const fn id(&self) -> SourceId {
67 self.id
68 }
69
70 pub fn name(&self) -> Arc<str> {
72 self.content.name()
73 }
74
75 #[cfg(feature = "std")]
77 #[inline]
78 pub fn path(&self) -> &std::path::Path {
79 self.content.path()
80 }
81
82 pub fn content(&self) -> &SourceContent {
84 &self.content
85 }
86
87 pub fn line_count(&self) -> usize {
89 self.content.last_line_index().to_usize() + 1
90 }
91
92 pub fn len(&self) -> usize {
94 self.content.len()
95 }
96
97 pub fn is_empty(&self) -> bool {
99 self.content.is_empty()
100 }
101
102 #[inline(always)]
104 pub fn as_str(&self) -> &str {
105 self.content.as_str()
106 }
107
108 #[inline(always)]
110 pub fn as_bytes(&self) -> &[u8] {
111 self.content.as_bytes()
112 }
113
114 #[inline]
116 pub fn source_span(&self) -> SourceSpan {
117 let range = self.content.source_range();
118 SourceSpan::new(self.id, range.start.0..range.end.0)
119 }
120
121 #[inline(always)]
127 pub fn source_slice(&self, span: impl Into<Range<usize>>) -> Option<&str> {
128 self.content.source_slice(span)
129 }
130
131 pub fn slice(self: &Arc<Self>, span: impl Into<Range<u32>>) -> SourceFileRef {
133 SourceFileRef::new(Arc::clone(self), span)
134 }
135
136 pub fn line_column_to_span(&self, line: u32, column: u32) -> Option<SourceSpan> {
140 let line_index = LineIndex::from(line.saturating_sub(1));
141 let column_index = ColumnIndex::from(column.saturating_sub(1));
142 let offset = self.content.line_column_to_offset(line_index, column_index)?;
143 Some(SourceSpan::at(self.id, offset.0))
144 }
145
146 pub fn location(&self, span: SourceSpan) -> FileLineCol {
148 assert_eq!(span.source_id(), self.id, "mismatched source ids");
149
150 self.content
151 .location(ByteIndex(span.into_range().start))
152 .expect("invalid source span: starting byte is out of bounds")
153 }
154}
155
156impl AsRef<str> for SourceFile {
157 #[inline(always)]
158 fn as_ref(&self) -> &str {
159 self.as_str()
160 }
161}
162
163impl AsRef<[u8]> for SourceFile {
164 #[inline(always)]
165 fn as_ref(&self) -> &[u8] {
166 self.as_bytes()
167 }
168}
169
170#[cfg(feature = "std")]
171impl AsRef<std::path::Path> for SourceFile {
172 #[inline(always)]
173 fn as_ref(&self) -> &std::path::Path {
174 self.path()
175 }
176}
177
178#[derive(Debug, Clone)]
188pub struct SourceFileRef {
189 file: Arc<SourceFile>,
190 span: SourceSpan,
191}
192
193impl SourceFileRef {
194 pub fn new(file: Arc<SourceFile>, span: impl Into<Range<u32>>) -> Self {
199 let span = span.into();
200 let end = core::cmp::min(span.end, file.len() as u32);
201 let span = SourceSpan::new(file.id(), span.start..end);
202 Self { file, span }
203 }
204
205 pub fn source_file(&self) -> Arc<SourceFile> {
207 self.file.clone()
208 }
209
210 #[cfg(feature = "std")]
212 pub fn path(&self) -> &std::path::Path {
213 self.file.path()
214 }
215
216 pub fn name(&self) -> &str {
218 self.file.content.path.as_ref()
219 }
220
221 pub const fn span(&self) -> SourceSpan {
223 self.span
224 }
225
226 pub fn as_str(&self) -> &str {
228 self.file.source_slice(self.span).unwrap()
229 }
230
231 #[inline]
233 pub fn as_bytes(&self) -> &[u8] {
234 self.as_str().as_bytes()
235 }
236
237 pub fn len(&self) -> usize {
240 self.span.len()
241 }
242
243 pub fn is_empty(&self) -> bool {
245 self.len() == 0
246 }
247}
248
249impl Eq for SourceFileRef {}
250
251impl PartialEq for SourceFileRef {
252 fn eq(&self, other: &Self) -> bool {
253 self.as_str() == other.as_str()
254 }
255}
256
257impl Ord for SourceFileRef {
258 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
259 self.as_str().cmp(other.as_str())
260 }
261}
262
263impl PartialOrd for SourceFileRef {
264 #[inline(always)]
265 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
266 Some(self.cmp(other))
267 }
268}
269
270impl core::hash::Hash for SourceFileRef {
271 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
272 self.span.hash(state);
273 self.as_str().hash(state);
274 }
275}
276
277impl AsRef<str> for SourceFileRef {
278 #[inline(always)]
279 fn as_ref(&self) -> &str {
280 self.as_str()
281 }
282}
283
284impl AsRef<[u8]> for SourceFileRef {
285 #[inline(always)]
286 fn as_ref(&self) -> &[u8] {
287 self.as_bytes()
288 }
289}
290
291#[cfg(feature = "diagnostics")]
292impl From<&SourceFileRef> for miette::SourceSpan {
293 fn from(source: &SourceFileRef) -> Self {
294 source.span.into()
295 }
296}
297
298#[cfg(feature = "diagnostics")]
300struct ScopedSourceFileRef<'a> {
301 file: &'a SourceFile,
302 span: miette::SourceSpan,
303}
304
305#[cfg(feature = "diagnostics")]
306impl<'a> miette::SpanContents<'a> for ScopedSourceFileRef<'a> {
307 #[inline]
308 fn data(&self) -> &'a [u8] {
309 let start = self.span.offset();
310 let end = start + self.span.len();
311 &self.file.as_bytes()[start..end]
312 }
313
314 #[inline]
315 fn span(&self) -> &miette::SourceSpan {
316 &self.span
317 }
318
319 fn line(&self) -> usize {
320 let offset = self.span.offset() as u32;
321 self.file.content.line_index(offset.into()).to_usize()
322 }
323
324 fn column(&self) -> usize {
325 let start = self.span.offset() as u32;
326 let end = start + self.span.len() as u32;
327 let span = SourceSpan::new(self.file.id(), start..end);
328 let loc = self.file.location(span);
329 loc.column.saturating_sub(1) as usize
330 }
331
332 #[inline]
333 fn line_count(&self) -> usize {
334 self.file.line_count()
335 }
336
337 #[inline]
338 fn name(&self) -> Option<&str> {
339 Some(self.file.content.path.as_ref())
340 }
341
342 #[inline]
343 fn language(&self) -> Option<&str> {
344 None
345 }
346}
347
348#[cfg(feature = "diagnostics")]
349impl miette::SourceCode for SourceFileRef {
350 #[inline]
351 fn read_span<'a>(
352 &'a self,
353 span: &miette::SourceSpan,
354 context_lines_before: usize,
355 context_lines_after: usize,
356 ) -> Result<alloc::boxed::Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
357 self.file.read_span(span, context_lines_before, context_lines_after)
358 }
359}
360
361#[derive(Clone)]
370pub struct SourceContent {
371 path: Arc<str>,
373 content: Box<str>,
375 line_starts: Box<[ByteIndex]>,
377}
378
379impl fmt::Debug for SourceContent {
380 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
381 f.debug_struct("SourceContent")
382 .field("path", &self.path)
383 .field("size_in_bytes", &self.content.len())
384 .field("line_count", &self.line_starts.len())
385 .field("content", &self.content)
386 .finish()
387 }
388}
389
390impl Eq for SourceContent {}
391
392impl PartialEq for SourceContent {
393 #[inline]
394 fn eq(&self, other: &Self) -> bool {
395 self.path == other.path && self.content == other.content
396 }
397}
398
399impl Ord for SourceContent {
400 #[inline]
401 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
402 self.path.cmp(&other.path).then_with(|| self.content.cmp(&other.content))
403 }
404}
405
406impl PartialOrd for SourceContent {
407 #[inline]
408 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
409 Some(self.cmp(other))
410 }
411}
412
413impl core::hash::Hash for SourceContent {
414 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
415 self.path.hash(state);
416 self.content.hash(state);
417 }
418}
419
420impl SourceContent {
421 pub fn new(path: Arc<str>, content: Box<str>) -> Self {
427 let bytes = content.as_bytes();
428
429 assert!(
430 bytes.len() < u32::MAX as usize,
431 "unsupported source file: current maximum supported length in bytes is 2^32"
432 );
433
434 let line_starts = core::iter::once(ByteIndex(0))
435 .chain(memchr::memchr_iter(b'\n', content.as_bytes()).filter_map(|mut offset| {
436 let mut preceding_escapes = 0;
438 let line_start = offset + 1;
439 while let Some(prev_offset) = offset.checked_sub(1) {
440 if bytes[prev_offset] == b'\\' {
441 offset = prev_offset;
442 preceding_escapes += 1;
443 continue;
444 }
445 break;
446 }
447
448 let is_escaped = preceding_escapes > 0 && preceding_escapes % 2 != 0;
450 if is_escaped {
451 None
452 } else {
453 Some(ByteIndex(line_start as u32))
454 }
455 }))
456 .collect::<Vec<_>>()
457 .into_boxed_slice();
458
459 Self { path, content, line_starts }
460 }
461
462 pub fn name(&self) -> Arc<str> {
464 self.path.clone()
465 }
466
467 #[cfg(feature = "std")]
469 #[inline]
470 pub fn path(&self) -> &std::path::Path {
471 std::path::Path::new(self.path.as_ref())
472 }
473
474 #[inline(always)]
476 pub fn as_str(&self) -> &str {
477 self.content.as_ref()
478 }
479
480 #[inline(always)]
482 pub fn as_bytes(&self) -> &[u8] {
483 self.content.as_bytes()
484 }
485
486 #[inline(always)]
488 pub fn len(&self) -> usize {
489 self.content.len()
490 }
491
492 #[inline(always)]
494 pub fn is_empty(&self) -> bool {
495 self.content.is_empty()
496 }
497
498 #[inline]
500 pub fn source_range(&self) -> Range<ByteIndex> {
501 ByteIndex(0)..ByteIndex(self.content.len() as u32)
502 }
503
504 #[inline(always)]
510 pub fn source_slice(&self, span: impl Into<Range<usize>>) -> Option<&str> {
511 self.as_str().get(span.into())
512 }
513
514 pub fn line_start(&self, line_index: LineIndex) -> Option<ByteIndex> {
518 self.line_starts.get(line_index.to_usize()).copied()
519 }
520
521 #[inline]
523 pub fn last_line_index(&self) -> LineIndex {
524 LineIndex(self.line_starts.len() as u32)
525 }
526
527 pub fn line_range(&self, line_index: LineIndex) -> Option<Range<ByteIndex>> {
529 let line_start = self.line_start(line_index)?;
530 match self.line_start(line_index + 1) {
531 Some(line_end) => Some(line_start..line_end),
532 None => Some(line_start..ByteIndex(self.content.len() as u32)),
533 }
534 }
535
536 pub fn line_index(&self, byte_index: ByteIndex) -> LineIndex {
538 match self.line_starts.binary_search(&byte_index) {
539 Ok(line) => LineIndex(line as u32),
540 Err(next_line) => LineIndex(next_line as u32 - 1),
541 }
542 }
543
544 pub fn line_column_to_offset(
548 &self,
549 line_index: LineIndex,
550 column_index: ColumnIndex,
551 ) -> Option<ByteIndex> {
552 let column_index = column_index.to_usize();
553 let line_span = self.line_range(line_index)?;
554 let line_src = self
555 .content
556 .get(line_span.start.to_usize()..line_span.end.to_usize())
557 .expect("invalid line boundaries: invalid utf-8");
558 if line_src.len() < column_index {
559 return None;
560 }
561 let (pre, _) = line_src.split_at(column_index);
562 let start = line_span.start;
563 Some(start + ByteOffset::from_str_len(pre))
564 }
565
566 pub fn location(&self, byte_index: ByteIndex) -> Option<FileLineCol> {
569 let line_index = self.line_index(byte_index);
570 let line_start_index = self.line_start(line_index)?;
571 let line_src = self.content.get(line_start_index.to_usize()..byte_index.to_usize())?;
572 let column_index = ColumnIndex::from(line_src.chars().count() as u32);
573 Some(FileLineCol {
574 path: self.path.clone(),
575 line: line_index.number().get(),
576 column: column_index.number().get(),
577 })
578 }
579}
580
581#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
586pub struct ByteIndex(u32);
587impl ByteIndex {
588 pub const fn new(index: u32) -> Self {
590 Self(index)
591 }
592
593 #[inline(always)]
595 pub const fn to_usize(self) -> usize {
596 self.0 as usize
597 }
598
599 #[inline(always)]
601 pub const fn to_u32(self) -> u32 {
602 self.0
603 }
604}
605impl core::ops::Add<ByteOffset> for ByteIndex {
606 type Output = ByteIndex;
607
608 fn add(self, rhs: ByteOffset) -> Self {
609 Self((self.0 as i64 + rhs.0) as u32)
610 }
611}
612impl core::ops::Add<u32> for ByteIndex {
613 type Output = ByteIndex;
614
615 fn add(self, rhs: u32) -> Self {
616 Self(self.0 + rhs)
617 }
618}
619impl core::ops::AddAssign<ByteOffset> for ByteIndex {
620 fn add_assign(&mut self, rhs: ByteOffset) {
621 *self = *self + rhs;
622 }
623}
624impl core::ops::AddAssign<u32> for ByteIndex {
625 fn add_assign(&mut self, rhs: u32) {
626 self.0 += rhs;
627 }
628}
629impl core::ops::Sub<ByteOffset> for ByteIndex {
630 type Output = ByteIndex;
631
632 fn sub(self, rhs: ByteOffset) -> Self {
633 Self((self.0 as i64 - rhs.0) as u32)
634 }
635}
636impl core::ops::Sub<u32> for ByteIndex {
637 type Output = ByteIndex;
638
639 fn sub(self, rhs: u32) -> Self {
640 Self(self.0 - rhs)
641 }
642}
643impl core::ops::SubAssign<ByteOffset> for ByteIndex {
644 fn sub_assign(&mut self, rhs: ByteOffset) {
645 *self = *self - rhs;
646 }
647}
648impl core::ops::SubAssign<u32> for ByteIndex {
649 fn sub_assign(&mut self, rhs: u32) {
650 self.0 -= rhs;
651 }
652}
653impl From<u32> for ByteIndex {
654 fn from(index: u32) -> Self {
655 Self(index)
656 }
657}
658impl From<ByteIndex> for u32 {
659 fn from(index: ByteIndex) -> Self {
660 index.0
661 }
662}
663
664#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
666pub struct ByteOffset(i64);
667impl ByteOffset {
668 pub fn from_char_len(c: char) -> ByteOffset {
670 Self(c.len_utf8() as i64)
671 }
672
673 pub fn from_str_len(s: &str) -> ByteOffset {
675 Self(s.len() as i64)
676 }
677}
678impl core::ops::Add for ByteOffset {
679 type Output = ByteOffset;
680
681 fn add(self, rhs: Self) -> Self {
682 Self(self.0 + rhs.0)
683 }
684}
685impl core::ops::AddAssign for ByteOffset {
686 fn add_assign(&mut self, rhs: Self) {
687 self.0 += rhs.0;
688 }
689}
690impl core::ops::Sub for ByteOffset {
691 type Output = ByteOffset;
692
693 fn sub(self, rhs: Self) -> Self {
694 Self(self.0 - rhs.0)
695 }
696}
697impl core::ops::SubAssign for ByteOffset {
698 fn sub_assign(&mut self, rhs: Self) {
699 self.0 -= rhs.0;
700 }
701}
702
703#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
705pub struct LineIndex(u32);
706impl LineIndex {
707 pub const fn number(self) -> NonZeroU32 {
709 unsafe { NonZeroU32::new_unchecked(self.0 + 1) }
710 }
711
712 #[inline(always)]
714 pub const fn to_usize(self) -> usize {
715 self.0 as usize
716 }
717
718 pub fn checked_add(self, offset: u32) -> Option<Self> {
720 self.0.checked_add(offset).map(Self)
721 }
722
723 pub fn checked_sub(self, offset: u32) -> Option<Self> {
725 self.0.checked_sub(offset).map(Self)
726 }
727
728 pub const fn saturating_add(self, offset: u32) -> Self {
730 Self(self.0.saturating_add(offset))
731 }
732
733 pub const fn saturating_sub(self, offset: u32) -> Self {
735 Self(self.0.saturating_sub(offset))
736 }
737}
738impl From<u32> for LineIndex {
739 fn from(index: u32) -> Self {
740 Self(index)
741 }
742}
743impl core::ops::Add<u32> for LineIndex {
744 type Output = LineIndex;
745
746 fn add(self, rhs: u32) -> Self {
747 Self(self.0 + rhs)
748 }
749}
750
751#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
753pub struct ColumnIndex(u32);
754impl ColumnIndex {
755 pub const fn number(self) -> NonZeroU32 {
757 unsafe { NonZeroU32::new_unchecked(self.0 + 1) }
758 }
759
760 #[inline(always)]
762 pub const fn to_usize(self) -> usize {
763 self.0 as usize
764 }
765}
766impl From<u32> for ColumnIndex {
767 fn from(index: u32) -> Self {
768 Self(index)
769 }
770}