Skip to main content

source_lang/
file.rs

1//! One stored source: its name, its text, and its place in the global space.
2
3use alloc::boxed::Box;
4
5use span_lang::Span;
6
7/// A single source held by a [`SourceMap`](crate::SourceMap): a display name, the
8/// owned source text, and the half-open [`Span`] the text occupies in the map's
9/// global position space.
10///
11/// The map owns the text so that everything above it can borrow `&str` for the
12/// life of the map without re-reading or copying — a line index, a lexer, a
13/// diagnostic renderer all read through this borrow. The [`span`](SourceFile::span)
14/// is the file's footprint in the shared coordinate space: its `start` is the
15/// global offset of the first byte, and a local offset within the file plus that
16/// `start` is the corresponding global position.
17///
18/// Construction is internal; a `SourceFile` only ever comes from
19/// [`SourceMap::add`](crate::SourceMap::add) or
20/// [`SourceMap::source`](crate::SourceMap::source), so its span is always
21/// consistent with the map that produced it.
22///
23/// # Examples
24///
25/// ```
26/// use source_lang::SourceMap;
27///
28/// let mut map = SourceMap::new();
29/// let id = map.add("greeting.txt", "hello").expect("fits");
30/// let file = map.source(id).expect("just added");
31///
32/// assert_eq!(file.name(), "greeting.txt");
33/// assert_eq!(file.text(), "hello");
34/// assert_eq!(file.span().len(), 5);
35/// ```
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct SourceFile {
38    name: Box<str>,
39    text: Box<str>,
40    span: Span,
41}
42
43impl SourceFile {
44    /// Assembles a stored source. Internal: the span must match the slot the map
45    /// assigned, so only the map constructs one.
46    #[inline]
47    pub(crate) const fn new(name: Box<str>, text: Box<str>, span: Span) -> Self {
48        Self { name, text, span }
49    }
50
51    /// Returns the source's display name — the path or label it was added under.
52    ///
53    /// This is purely a label for diagnostics; the map does not interpret it, so
54    /// two sources may share a name and still be distinct entries.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use source_lang::SourceMap;
60    ///
61    /// let mut map = SourceMap::new();
62    /// let id = map.add("src/main.rs", "fn main() {}").expect("fits");
63    /// assert_eq!(map.source(id).unwrap().name(), "src/main.rs");
64    /// ```
65    #[inline]
66    #[must_use]
67    pub fn name(&self) -> &str {
68        &self.name
69    }
70
71    /// Returns the source text, borrowed for the life of the map.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use source_lang::SourceMap;
77    ///
78    /// let mut map = SourceMap::new();
79    /// let id = map.add("note.txt", "first line\nsecond line").expect("fits");
80    /// let text = map.source(id).unwrap().text();
81    /// assert_eq!(text.lines().count(), 2);
82    /// ```
83    #[inline]
84    #[must_use]
85    pub fn text(&self) -> &str {
86        &self.text
87    }
88
89    /// Returns the file's half-open range in the map's global position space.
90    ///
91    /// `span().start()` is the global offset of the file's first byte; the file
92    /// covers `start..start + text().len()`. Subtracting `start` from a global
93    /// position that falls in this range gives the local offset into
94    /// [`text`](SourceFile::text) — which is exactly what
95    /// [`SourceMap::locate`](crate::SourceMap::locate) returns.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use source_lang::SourceMap;
101    ///
102    /// let mut map = SourceMap::new();
103    /// let first = map.add("a.txt", "abc").expect("fits");   // global 0..3
104    /// let second = map.add("b.txt", "de").expect("fits");   // global 3..5
105    ///
106    /// assert_eq!(map.source(first).unwrap().span().start().to_u32(), 0);
107    /// assert_eq!(map.source(second).unwrap().span().start().to_u32(), 3);
108    /// ```
109    #[inline]
110    #[must_use]
111    pub const fn span(&self) -> Span {
112        self.span
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    extern crate alloc;
119    use alloc::boxed::Box;
120
121    use super::*;
122
123    fn boxed(s: &str) -> Box<str> {
124        Box::from(s)
125    }
126
127    #[test]
128    fn test_accessors_return_stored_values() {
129        let file = SourceFile::new(boxed("name"), boxed("body"), Span::new(4, 8));
130        assert_eq!(file.name(), "name");
131        assert_eq!(file.text(), "body");
132        assert_eq!(file.span(), Span::new(4, 8));
133    }
134
135    #[test]
136    fn test_empty_text_has_zero_width_span() {
137        let file = SourceFile::new(boxed("empty"), boxed(""), Span::empty(12));
138        assert_eq!(file.text(), "");
139        assert!(file.span().is_empty());
140    }
141}