miden_diagnostics/
source.rs

1use std::convert::Into;
2use std::num::NonZeroU32;
3use std::ops::Range;
4
5use super::*;
6
7/// A handle that points to a file in the codemap.
8#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct SourceId(pub(crate) NonZeroU32);
10impl SourceId {
11    pub(crate) const UNKNOWN_SOURCE_ID: u32 = u32::max_value();
12
13    pub const UNKNOWN: Self = Self(unsafe { NonZeroU32::new_unchecked(Self::UNKNOWN_SOURCE_ID) });
14
15    pub(crate) fn new(index: u32) -> Self {
16        assert!(index > 0);
17        assert!(index < Self::UNKNOWN_SOURCE_ID);
18        Self(NonZeroU32::new(index).unwrap())
19    }
20
21    #[inline]
22    pub(crate) fn get(self) -> u32 {
23        self.0.get()
24    }
25}
26
27/// The representation of a source file in the database.
28#[derive(Debug, Clone)]
29pub struct SourceFile {
30    id: SourceId,
31    name: FileName,
32    source: String,
33    line_starts: Vec<ByteIndex>,
34    parent: Option<SourceSpan>,
35}
36impl SourceFile {
37    pub(crate) fn new(
38        id: SourceId,
39        name: FileName,
40        source: String,
41        parent: Option<SourceSpan>,
42    ) -> Self {
43        let line_starts = codespan_reporting::files::line_starts(source.as_str())
44            .map(|i| ByteIndex::from(i as u32))
45            .collect();
46
47        Self {
48            id,
49            name,
50            source,
51            line_starts,
52            parent,
53        }
54    }
55
56    /// Returns the [FileName] associated with this [SourceFile]
57    pub fn name(&self) -> &FileName {
58        &self.name
59    }
60
61    /// Returns the [SourceId] associated with this [SourceFile]
62    pub fn id(&self) -> SourceId {
63        self.id
64    }
65
66    /// Returns the parent [SourceSpan] for this [SourceFile]
67    pub fn parent(&self) -> Option<SourceSpan> {
68        self.parent
69    }
70
71    /// Computes the [ByteIndex] at which the line corresponding to `line_index` begins
72    pub fn line_start(&self, line_index: LineIndex) -> Result<ByteIndex, Error> {
73        use std::cmp::Ordering;
74
75        match line_index.cmp(&self.last_line_index()) {
76            Ordering::Less => Ok(self.line_starts[line_index.to_usize()]),
77            Ordering::Equal => Ok(self.source_span().end_index()),
78            Ordering::Greater => Err(Error::LineTooLarge {
79                given: line_index.to_usize(),
80                max: self.last_line_index().to_usize(),
81            }),
82        }
83    }
84
85    /// Returns the [LineIndex] of the last line in this file
86    pub fn last_line_index(&self) -> LineIndex {
87        LineIndex::from(self.line_starts.len() as RawIndex)
88    }
89
90    pub(crate) fn line_span(&self, line_index: LineIndex) -> Result<codespan::Span, Error> {
91        let line_start = self.line_start(line_index)?;
92        let next_line_start = self.line_start(line_index + LineOffset::from(1))?;
93
94        Ok(codespan::Span::new(line_start, next_line_start))
95    }
96
97    pub(crate) fn line_index(&self, byte_index: ByteIndex) -> LineIndex {
98        match self.line_starts.binary_search(&byte_index) {
99            // Found the start of a line
100            Ok(line) => LineIndex::from(line as u32),
101            Err(next_line) => LineIndex::from(next_line as u32 - 1),
102        }
103    }
104
105    pub(crate) fn line_column_to_span(
106        &self,
107        line_index: LineIndex,
108        column_index: ColumnIndex,
109    ) -> Result<codespan::Span, Error> {
110        let column_index = column_index.to_usize();
111        let line_span = self.line_span(line_index)?;
112        let line_src = self
113            .source
114            .as_str()
115            .get(line_span.start().to_usize()..line_span.end().to_usize())
116            .unwrap();
117        if line_src.len() < column_index {
118            let base = line_span.start().to_usize();
119            return Err(Error::IndexTooLarge {
120                given: base + column_index,
121                max: base + line_src.len(),
122            });
123        }
124        let (pre, _) = line_src.split_at(column_index);
125        let start = line_span.start();
126        let offset = ByteOffset::from_str_len(pre);
127        Ok(codespan::Span::new(start + offset, start + offset))
128    }
129
130    /// Returns a [Location] corresponding to the given byte index in this file.
131    pub fn location<I: Into<ByteIndex>>(&self, byte_index: I) -> Result<Location, Error> {
132        let byte_index = byte_index.into();
133        let line_index = self.line_index(byte_index);
134        let line_start_index = self
135            .line_start(line_index)
136            .map_err(|_| Error::IndexTooLarge {
137                given: byte_index.to_usize(),
138                max: self.source().len() - 1,
139            })?;
140        let line_src = self
141            .source
142            .as_str()
143            .get(line_start_index.to_usize()..byte_index.to_usize())
144            .ok_or_else(|| {
145                let given = byte_index.to_usize();
146                let max = self.source().len() - 1;
147                if given >= max {
148                    Error::IndexTooLarge { given, max }
149                } else {
150                    Error::InvalidCharBoundary { given }
151                }
152            })?;
153
154        Ok(Location {
155            line: line_index,
156            column: ColumnIndex::from(line_src.chars().count() as u32),
157        })
158    }
159
160    /// Returns the underlying content of this file as a string slice
161    #[inline(always)]
162    pub fn source(&self) -> &str {
163        self.source.as_str()
164    }
165
166    /// Returns a [SourceSpan] covering all of the content in this file
167    pub fn source_span(&self) -> SourceSpan {
168        SourceSpan {
169            source_id: self.id,
170            start: ByteIndex(0),
171            end: ByteIndex(self.source.len() as u32),
172        }
173    }
174
175    /// Returns a subset of the underlying content of this file as a string slice
176    ///
177    /// The given range corresponds to character indices in the underlying content.
178    pub fn source_slice(&self, span: impl Into<Range<usize>>) -> Result<&str, Error> {
179        let span = span.into();
180        let start = span.start;
181        let end = span.end;
182
183        self.source().get(start..end).ok_or_else(|| {
184            let max = self.source().len() - 1;
185            Error::IndexTooLarge {
186                given: if start > max { start } else { end },
187                max,
188            }
189        })
190    }
191}