miden_core/debuginfo/
source_manager.rs

1use alloc::{
2    boxed::Box,
3    collections::BTreeMap,
4    string::{String, ToString},
5    sync::Arc,
6    vec::Vec,
7};
8use core::{error::Error, fmt::Debug};
9
10use super::*;
11
12// SOURCE ID
13// ================================================================================================
14
15/// A [SourceId] represents the index/identifier associated with a unique source file in a
16/// [SourceManager] implementation.
17#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct SourceId(u32);
19
20impl Default for SourceId {
21    fn default() -> Self {
22        Self::UNKNOWN
23    }
24}
25
26impl SourceId {
27    pub const UNKNOWN: Self = Self(u32::MAX);
28
29    /// Create a new [SourceId] from a `u32` value, but assert if the value is reserved
30    pub fn new(id: u32) -> Self {
31        assert_ne!(id, u32::MAX, "u32::MAX is a reserved value for SourceId::default()/UNKNOWN");
32
33        Self(id)
34    }
35
36    /// Create a new [SourceId] from a raw `u32` value
37    #[inline(always)]
38    pub const fn new_unchecked(id: u32) -> Self {
39        Self(id)
40    }
41
42    #[inline(always)]
43    pub const fn to_usize(self) -> usize {
44        self.0 as usize
45    }
46
47    #[inline(always)]
48    pub const fn to_u32(self) -> u32 {
49        self.0
50    }
51
52    pub const fn is_unknown(&self) -> bool {
53        self.0 == u32::MAX
54    }
55}
56
57impl TryFrom<usize> for SourceId {
58    type Error = ();
59
60    #[inline]
61    fn try_from(id: usize) -> Result<Self, Self::Error> {
62        match u32::try_from(id) {
63            Ok(n) if n < u32::MAX => Ok(Self(n)),
64            _ => Err(()),
65        }
66    }
67}
68
69// SOURCE MANAGER
70// ================================================================================================
71
72/// The set of errors which may be raised by a [SourceManager]
73#[derive(Debug, thiserror::Error)]
74pub enum SourceManagerError {
75    /// A [SourceId] was provided to a [SourceManager] which was allocated by a different
76    /// [SourceManager]
77    #[error("attempted to use an invalid source id")]
78    InvalidSourceId,
79    /// An attempt was made to read content using invalid byte indices
80    #[error("attempted to read content out of bounds")]
81    InvalidBounds,
82    /// Custom error variant for implementors of the trait.
83    #[error("{error_msg}")]
84    Custom {
85        error_msg: Box<str>,
86        // thiserror will return this when calling Error::source on SourceManagerError.
87        source: Option<Box<dyn Error + Send + Sync + 'static>>,
88    },
89}
90
91impl SourceManagerError {
92    pub fn custom(message: String) -> Self {
93        Self::Custom { error_msg: message.into(), source: None }
94    }
95
96    pub fn custom_with_source(message: String, source: impl Error + Send + Sync + 'static) -> Self {
97        Self::Custom {
98            error_msg: message.into(),
99            source: Some(Box::new(source)),
100        }
101    }
102}
103
104pub trait SourceManager: Debug {
105    /// Returns true if `file` is managed by this source manager
106    fn is_manager_of(&self, file: &SourceFile) -> bool {
107        match self.get(file.id()) {
108            Ok(found) => core::ptr::addr_eq(Arc::as_ptr(&found), file),
109            Err(_) => false,
110        }
111    }
112    /// Copies `file` into this source manager (if not already managed by this manager).
113    ///
114    /// The returned source file is guaranteed to be owned by this manager.
115    fn copy_into(&self, file: &SourceFile) -> Arc<SourceFile> {
116        if let Ok(found) = self.get(file.id()) {
117            if core::ptr::addr_eq(Arc::as_ptr(&found), file) {
118                return found;
119            }
120        }
121        self.load_from_raw_parts(file.name(), file.content().clone())
122    }
123    /// Load the given `content` into this [SourceManager] with `name`
124    fn load(&self, name: &str, content: String) -> Arc<SourceFile> {
125        let name = Arc::from(name.to_string().into_boxed_str());
126        let content = SourceContent::new(Arc::clone(&name), content.into_boxed_str());
127        self.load_from_raw_parts(name, content)
128    }
129    /// Load content into this [SourceManager] from raw [SourceFile] components
130    fn load_from_raw_parts(&self, name: Arc<str>, content: SourceContent) -> Arc<SourceFile>;
131    /// Get the [SourceFile] corresponding to `id`
132    fn get(&self, id: SourceId) -> Result<Arc<SourceFile>, SourceManagerError>;
133    /// Get the most recent [SourceFile] whose path is `path`
134    fn get_by_path(&self, path: &str) -> Option<Arc<SourceFile>> {
135        self.find(path).and_then(|id| self.get(id).ok())
136    }
137    /// Search for a source file named `name`, and return its [SourceId] if found.
138    fn find(&self, name: &str) -> Option<SourceId>;
139    /// Convert a [FileLineCol] to an equivalent [SourceSpan], if the referenced file is available
140    fn file_line_col_to_span(&self, loc: FileLineCol) -> Option<SourceSpan>;
141    /// Convert a [SourceSpan] to an equivalent [FileLineCol], if the span is valid
142    fn file_line_col(&self, span: SourceSpan) -> Result<FileLineCol, SourceManagerError>;
143    /// Convert a [Location] to an equivalent [SourceSpan], if the referenced file is available
144    fn location_to_span(&self, loc: Location) -> Option<SourceSpan>;
145    /// Convert a [SourceSpan] to an equivalent [Location], if the span is valid
146    fn location(&self, span: SourceSpan) -> Result<Location, SourceManagerError>;
147    /// Get the source associated with `id` as a string slice
148    fn source(&self, id: SourceId) -> Result<&str, SourceManagerError>;
149    /// Get the source corresponding to `span` as a string slice
150    fn source_slice(&self, span: SourceSpan) -> Result<&str, SourceManagerError>;
151}
152
153impl<T: ?Sized + SourceManager> SourceManager for Arc<T> {
154    #[inline(always)]
155    fn is_manager_of(&self, file: &SourceFile) -> bool {
156        (**self).is_manager_of(file)
157    }
158    #[inline(always)]
159    fn copy_into(&self, file: &SourceFile) -> Arc<SourceFile> {
160        (**self).copy_into(file)
161    }
162    #[inline(always)]
163    fn load(&self, name: &str, content: String) -> Arc<SourceFile> {
164        (**self).load(name, content)
165    }
166    #[inline(always)]
167    fn load_from_raw_parts(&self, name: Arc<str>, content: SourceContent) -> Arc<SourceFile> {
168        (**self).load_from_raw_parts(name, content)
169    }
170    #[inline(always)]
171    fn get(&self, id: SourceId) -> Result<Arc<SourceFile>, SourceManagerError> {
172        (**self).get(id)
173    }
174    #[inline(always)]
175    fn get_by_path(&self, path: &str) -> Option<Arc<SourceFile>> {
176        (**self).get_by_path(path)
177    }
178    #[inline(always)]
179    fn find(&self, name: &str) -> Option<SourceId> {
180        (**self).find(name)
181    }
182    #[inline(always)]
183    fn file_line_col_to_span(&self, loc: FileLineCol) -> Option<SourceSpan> {
184        (**self).file_line_col_to_span(loc)
185    }
186    #[inline(always)]
187    fn file_line_col(&self, span: SourceSpan) -> Result<FileLineCol, SourceManagerError> {
188        (**self).file_line_col(span)
189    }
190    #[inline(always)]
191    fn location_to_span(&self, loc: Location) -> Option<SourceSpan> {
192        (**self).location_to_span(loc)
193    }
194    #[inline(always)]
195    fn location(&self, span: SourceSpan) -> Result<Location, SourceManagerError> {
196        (**self).location(span)
197    }
198    #[inline(always)]
199    fn source(&self, id: SourceId) -> Result<&str, SourceManagerError> {
200        (**self).source(id)
201    }
202    #[inline(always)]
203    fn source_slice(&self, span: SourceSpan) -> Result<&str, SourceManagerError> {
204        (**self).source_slice(span)
205    }
206}
207
208#[cfg(feature = "std")]
209pub trait SourceManagerExt: SourceManager {
210    /// Load the content of `path` into this [SourceManager], using the given path as the source
211    /// name.
212    fn load_file(&self, path: &std::path::Path) -> Result<Arc<SourceFile>, SourceManagerError> {
213        let name = path.to_string_lossy();
214        if let Some(existing) = self.get_by_path(name.as_ref()) {
215            return Ok(existing);
216        }
217
218        let name = Arc::from(name.into_owned().into_boxed_str());
219        let content = std::fs::read_to_string(path)
220            .map(|s| SourceContent::new(Arc::clone(&name), s.into_boxed_str()))
221            .map_err(|source| {
222                SourceManagerError::custom_with_source(
223                    format!("failed to load filed at `{}`", path.display()),
224                    source,
225                )
226            })?;
227
228        Ok(self.load_from_raw_parts(name, content))
229    }
230}
231
232#[cfg(feature = "std")]
233impl<T: ?Sized + SourceManager> SourceManagerExt for T {}
234
235// DEFAULT SOURCE MANAGER
236// ================================================================================================
237
238use crate::utils::sync::RwLock;
239
240#[derive(Debug, Default)]
241pub struct DefaultSourceManager(RwLock<DefaultSourceManagerImpl>);
242impl Clone for DefaultSourceManager {
243    fn clone(&self) -> Self {
244        let manager = self.0.read();
245        Self(RwLock::new(manager.clone()))
246    }
247}
248
249#[derive(Debug, Default, Clone)]
250struct DefaultSourceManagerImpl {
251    files: Vec<Arc<SourceFile>>,
252    names: BTreeMap<Arc<str>, SourceId>,
253}
254
255impl DefaultSourceManagerImpl {
256    fn insert(&mut self, name: Arc<str>, content: SourceContent) -> Arc<SourceFile> {
257        // If we have previously inserted the same content with `name`, return the previously
258        // inserted source id
259        if let Some(file) = self.names.get(&name).copied().and_then(|id| {
260            let file = &self.files[id.to_usize()];
261            if file.as_str() == content.as_str() {
262                Some(Arc::clone(file))
263            } else {
264                None
265            }
266        }) {
267            return file;
268        }
269        let id = SourceId::try_from(self.files.len())
270            .expect("system limit: source manager has exhausted its supply of source ids");
271        let file = Arc::new(SourceFile::from_raw_parts(id, content));
272        self.files.push(Arc::clone(&file));
273        self.names.insert(Arc::clone(&name), id);
274        file
275    }
276
277    fn get(&self, id: SourceId) -> Result<Arc<SourceFile>, SourceManagerError> {
278        self.files
279            .get(id.to_usize())
280            .cloned()
281            .ok_or(SourceManagerError::InvalidSourceId)
282    }
283
284    fn get_by_path(&self, path: &str) -> Option<Arc<SourceFile>> {
285        self.find(path).and_then(|id| self.get(id).ok())
286    }
287
288    fn find(&self, name: &str) -> Option<SourceId> {
289        self.names.get(name).copied()
290    }
291
292    fn file_line_col_to_span(&self, loc: FileLineCol) -> Option<SourceSpan> {
293        let file = self
294            .names
295            .get(&loc.path)
296            .copied()
297            .and_then(|id| self.files.get(id.to_usize()))?;
298        file.line_column_to_span(loc.line, loc.column)
299    }
300
301    fn file_line_col(&self, span: SourceSpan) -> Result<FileLineCol, SourceManagerError> {
302        self.files
303            .get(span.source_id().to_usize())
304            .ok_or(SourceManagerError::InvalidSourceId)
305            .map(|file| file.location(span))
306    }
307
308    fn location_to_span(&self, loc: Location) -> Option<SourceSpan> {
309        let file = self
310            .names
311            .get(&loc.path)
312            .copied()
313            .and_then(|id| self.files.get(id.to_usize()))?;
314
315        let max_len = ByteIndex::from(file.as_str().len() as u32);
316        if loc.start >= max_len || loc.end > max_len {
317            return None;
318        }
319
320        Some(SourceSpan::new(file.id(), loc.start..loc.end))
321    }
322
323    fn location(&self, span: SourceSpan) -> Result<Location, SourceManagerError> {
324        self.files
325            .get(span.source_id().to_usize())
326            .ok_or(SourceManagerError::InvalidSourceId)
327            .map(|file| Location::new(file.name(), span.start(), span.end()))
328    }
329}
330
331impl SourceManager for DefaultSourceManager {
332    fn load_from_raw_parts(&self, name: Arc<str>, content: SourceContent) -> Arc<SourceFile> {
333        let mut manager = self.0.write();
334        manager.insert(name, content)
335    }
336
337    fn get(&self, id: SourceId) -> Result<Arc<SourceFile>, SourceManagerError> {
338        let manager = self.0.read();
339        manager.get(id)
340    }
341
342    fn get_by_path(&self, path: &str) -> Option<Arc<SourceFile>> {
343        let manager = self.0.read();
344        manager.get_by_path(path)
345    }
346
347    fn find(&self, name: &str) -> Option<SourceId> {
348        let manager = self.0.read();
349        manager.find(name)
350    }
351
352    fn file_line_col_to_span(&self, loc: FileLineCol) -> Option<SourceSpan> {
353        let manager = self.0.read();
354        manager.file_line_col_to_span(loc)
355    }
356
357    fn file_line_col(&self, span: SourceSpan) -> Result<FileLineCol, SourceManagerError> {
358        let manager = self.0.read();
359        manager.file_line_col(span)
360    }
361
362    fn location_to_span(&self, loc: Location) -> Option<SourceSpan> {
363        let manager = self.0.read();
364        manager.location_to_span(loc)
365    }
366
367    fn location(&self, span: SourceSpan) -> Result<Location, SourceManagerError> {
368        let manager = self.0.read();
369        manager.location(span)
370    }
371
372    fn source(&self, id: SourceId) -> Result<&str, SourceManagerError> {
373        let manager = self.0.read();
374        let ptr = manager
375            .files
376            .get(id.to_usize())
377            .ok_or(SourceManagerError::InvalidSourceId)
378            .map(|file| file.as_str() as *const str)?;
379        drop(manager);
380        // SAFETY: Because the lifetime of the returned reference is bound to the manager, and
381        // because we can only ever add files, not modify/remove them, this is safe. Exclusive
382        // access to the manager does _not_ mean exclusive access to the contents of previously
383        // added source files
384        Ok(unsafe { &*ptr })
385    }
386
387    fn source_slice(&self, span: SourceSpan) -> Result<&str, SourceManagerError> {
388        self.source(span.source_id())?
389            .get(span.into_slice_index())
390            .ok_or(SourceManagerError::InvalidBounds)
391    }
392}
393
394#[cfg(test)]
395mod error_assertions {
396    use super::*;
397
398    /// Asserts at compile time that the passed error has Send + Sync + 'static bounds.
399    fn _assert_error_is_send_sync_static<E: core::error::Error + Send + Sync + 'static>(_: E) {}
400
401    fn _assert_source_manager_error_bounds(err: SourceManagerError) {
402        _assert_error_is_send_sync_static(err);
403    }
404}