1use core::ops::Range;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct Spans<'a> {
14 source: &'a str,
16}
17
18impl<'a> Spans<'a> {
19 #[must_use]
21 pub const fn new(source: &'a str) -> Self {
22 Self { source }
23 }
24
25 #[must_use]
30 pub fn validate_span(&self, span: &str) -> bool {
31 let source_start = self.source.as_ptr() as usize;
32 let source_end = source_start + self.source.len();
33
34 let span_start = span.as_ptr() as usize;
35 let span_end = span_start + span.len();
36
37 span_start >= source_start && span_end <= source_end
38 }
39
40 #[must_use]
42 pub fn span_offset(&self, span: &str) -> Option<usize> {
43 let source_start = self.source.as_ptr() as usize;
44 let span_start = span.as_ptr() as usize;
45
46 if self.validate_span(span) {
47 Some(span_start - source_start)
48 } else {
49 None
50 }
51 }
52
53 #[must_use]
55 pub fn span_line(&self, span: &str) -> Option<usize> {
56 let offset = self.span_offset(span)?;
57 Some(self.source[..offset].chars().filter(|&c| c == '\n').count() + 1)
58 }
59
60 #[must_use]
62 pub fn span_column(&self, span: &str) -> Option<usize> {
63 let offset = self.span_offset(span)?;
64 let line_start = self.source[..offset].rfind('\n').map_or(0, |pos| pos + 1);
65
66 Some(self.source[line_start..offset].chars().count() + 1)
67 }
68
69 #[must_use]
71 pub fn substring(&self, range: Range<usize>) -> Option<&'a str> {
72 self.source.get(range)
73 }
74}