span/
lib.rs

1//! Utilities to represent spans inside a file
2
3use std::{fmt, fmt::Debug, ops::Deref, str};
4
5pub mod source;
6#[doc(hidden)]
7pub use source::SourceMap;
8
9/// Represents a span in the [`SourceMap`], bounded by an offset and a len
10///
11/// # About offset
12/// The offset of this Span is an absolute offset inside the [`SourceMap`]
13/// It doesn't represent the offset relative to the file it was created from.
14/// This is done to avoid havind an additional field to identify the source file.
15///
16/// To get the base offset of a [`Span`] you can use the
17/// [`SourceMap::get_base_offset_of_span`] function
18#[derive(Clone, Copy, Default, PartialEq)]
19pub struct Span {
20    /// Offset of the span
21    pub offset: usize,
22    /// Length of the span
23    pub len: usize
24}
25
26impl Debug for Span {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        let Span { offset, len } = self;
29        write!(f, "Span {{ {offset}, {len} }}")
30    }
31}
32
33/// Represents a [`Span`] in a file, bounded by
34/// it's start line and col, plus it's end line and col
35#[derive(Debug, Clone, Copy, Default, PartialEq)]
36pub struct FilePosition {
37    pub start_line: usize,
38    pub start_col: usize,
39    pub end_line: usize,
40    pub end_col: usize,
41}
42
43impl fmt::Display for FilePosition {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        let FilePosition {
46            start_line,
47            start_col,
48            ..
49        } = self;
50        write!(f, "[{start_line}:{start_col}]")
51    }
52}
53
54impl Span {
55
56    pub const fn dummy() -> Span { Span { offset: 0, len: 0 } }
57
58    /// Joins two spans together.
59    /// Returns the smallest Span that covers both.
60    #[must_use]
61    pub const fn join(&self, other: &Span) -> Span {
62        let (left, right) = if self.offset < other.offset {
63            (self, other)
64        } else {
65            (other, self)
66        };
67        Span {
68            offset: left.offset,
69            len: right.end_offset() - left.offset,
70        }
71    }
72    /// Slices the given string with this span
73    ///
74    /// `base_offset` is the base offset of the corresponding
75    /// [`SourceFile`](source::SourceFile) for this `Span`
76    ///
77    /// You can also use the [`SourceMap::slice`] function
78    ///
79    /// See: [about offset](Self#about-offset)
80    ///
81    /// # Examples
82    ///
83    /// Simple example with a single source file
84    /// ```
85    /// use span::Span;
86    ///
87    /// let src = "abcdefg";
88    /// let span = Span {
89    ///     offset: 1,
90    ///     len: 5,
91    /// };
92    /// let slice = span.slice(0, src);
93    /// assert_eq!("bcdef", slice);
94    /// ```
95    ///
96    /// Example with multiple source files
97    /// ```
98    /// use span::{Span, source::SourceMap};
99    ///
100    /// let src1 = "abcdefghi";
101    /// let src2 = "abcdefhij";
102    ///
103    /// let mut source = SourceMap::default();
104    /// let offset1 = source.add_file_annon(src1.into()).offset();
105    /// let offset2 = source.add_file_annon(src2.into()).offset();
106    ///
107    /// let span1 = Span {
108    ///     offset: offset1 + 3,
109    ///     len: 4,
110    /// };
111    ///
112    /// let span2 = Span {
113    ///     offset: offset2 + 4,
114    ///     len: 2,
115    /// };
116    ///
117    /// let slice1 = span1.slice(offset1, src1);
118    /// let slice2 = span2.slice(offset2, src1);
119    ///
120    /// assert_eq!(slice1, "defg");
121    /// assert_eq!(slice2, "ef");
122    ///
123    /// ```
124    #[must_use]
125    #[inline]
126    pub fn slice<'a>(&self, base_offset: usize, src: &'a str) -> &'a str {
127        let start = self.offset - base_offset;
128        let end = start + self.len;
129        &src[start..end]
130    }
131    /// Gets the [file position](FilePosition) of this span in the given string slice
132    ///
133    /// `base_offset` is the base offset of the corresponding
134    /// [`SourceFile`](source::SourceFile) for this `Span`
135    ///
136    /// You can also use the [`SourceMap::file_position`] function
137    ///
138    /// See: [about offset](Self#about-offset)
139    #[must_use]
140    pub fn file_position(&self, base_offset: usize, src: &str) -> FilePosition {
141        let mut fpos = FilePosition {
142            start_line: 1,
143            start_col: 0,
144            end_line: 0,
145            end_col: 0,
146        };
147
148        let start = self.offset - base_offset;
149
150        for c in src[..start].chars() {
151            if c == '\n' {
152                fpos.start_col = 0;
153                fpos.start_line += 1;
154            }
155            fpos.start_col += 1;
156        }
157
158        fpos.end_line = fpos.start_line;
159        fpos.end_col = fpos.start_col;
160
161        let end = start + self.len;
162        for c in src[start..end].chars() {
163            if c == '\n' {
164                fpos.end_col = 0;
165                fpos.end_line += 1;
166            }
167            fpos.end_col += 1;
168        }
169
170        fpos
171    }
172    /// Returns the end offset of the span. This is, the
173    /// offset of the span plus it's length
174    #[must_use]
175    #[inline]
176    pub const fn end_offset(&self) -> usize { self.offset + self.len }
177}
178
179impl fmt::Display for Span {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        write!(f, "[{}:{}]", self.offset, self.offset + self.len)
182    }
183}
184
185#[derive(Debug)]
186pub struct Spanned<T> {
187    pub val: T,
188    pub span: Span,
189}
190
191impl<T> Deref for Spanned<T> {
192    type Target = T;
193
194    fn deref(&self) -> &Self::Target { &self.val }
195}
196
197impl<T: Clone> Clone for Spanned<T> {
198    fn clone(&self) -> Self {
199        Self {
200            val: self.val.clone(),
201            span: self.span,
202        }
203    }
204}
205
206impl<T: PartialEq> PartialEq for Spanned<T> {
207    fn eq(&self, other: &Self) -> bool { self.val == other.val }
208}