cranelift_isle/
files.rs

1#![expect(missing_docs, reason = "fields mostly self-describing")]
2
3use crate::codegen::Prefix;
4use std::ops::Index;
5use std::path::{Path, PathBuf};
6
7#[derive(Default, Clone, PartialEq, Eq, Debug)]
8pub struct Files {
9    /// Arena of filenames from the input source.
10    ///
11    /// Indexed via `Pos::file`.
12    pub file_names: Vec<String>,
13
14    /// Arena of file source texts.
15    ///
16    /// Indexed via `Pos::file`.
17    pub file_texts: Vec<String>,
18
19    /// Arena of file line maps.
20    ///
21    /// Indexed via `Pos::file`.
22    pub file_line_maps: Vec<LineMap>,
23}
24
25#[derive(Default, Clone, PartialEq, Eq, Debug)]
26pub struct LineMap {
27    /// Mapping from line number to starting byte position.
28    line_ends: Vec<usize>,
29}
30
31impl Index<usize> for LineMap {
32    type Output = usize;
33
34    fn index(&self, index: usize) -> &Self::Output {
35        &self.line_ends[index]
36    }
37}
38
39impl LineMap {
40    pub fn from_str(text: &str) -> Self {
41        let line_ends = text.match_indices('\n').map(|(i, _)| i + 1).collect();
42        Self { line_ends }
43    }
44
45    /// Get the line on which `pos` occurs
46    pub fn line(&self, pos: usize) -> usize {
47        self.line_ends.partition_point(|&end| end <= pos)
48    }
49
50    /// Get the starting byte position of `line`.
51    pub fn get(&self, line: usize) -> Option<&usize> {
52        self.line_ends.get(line)
53    }
54}
55
56impl Files {
57    pub fn from_paths<P: AsRef<Path>>(
58        paths: impl IntoIterator<Item = P>,
59        prefixes: &[crate::codegen::Prefix],
60    ) -> Result<Self, (PathBuf, std::io::Error)> {
61        fn replace_prefixes(prefixes: &[Prefix], path: String) -> String {
62            for Prefix { prefix, name } in prefixes {
63                if path.starts_with(prefix) {
64                    return path.replacen(prefix, name, 1);
65                }
66            }
67            path
68        }
69
70        let mut file_names = Vec::new();
71        let mut file_texts = Vec::new();
72        let mut file_line_maps = Vec::new();
73
74        for path in paths {
75            let path = path.as_ref();
76            let contents =
77                std::fs::read_to_string(path).map_err(|err| (path.to_path_buf(), err))?;
78            let name = replace_prefixes(prefixes, path.display().to_string());
79
80            file_line_maps.push(LineMap::from_str(&contents));
81            file_names.push(name);
82            file_texts.push(contents);
83        }
84
85        Ok(Self {
86            file_names,
87            file_texts,
88            file_line_maps,
89        })
90    }
91
92    pub fn from_names_and_contents(files: impl IntoIterator<Item = (String, String)>) -> Self {
93        let mut file_names = Vec::new();
94        let mut file_texts = Vec::new();
95        let mut file_line_maps = Vec::new();
96
97        for (name, contents) in files {
98            file_line_maps.push(LineMap::from_str(&contents));
99            file_names.push(name);
100            file_texts.push(contents);
101        }
102
103        Self {
104            file_names,
105            file_texts,
106            file_line_maps,
107        }
108    }
109
110    pub fn file_name(&self, file: usize) -> Option<&str> {
111        self.file_names.get(file).map(|x| x.as_str())
112    }
113
114    pub fn file_text(&self, file: usize) -> Option<&str> {
115        self.file_texts.get(file).map(|x| x.as_str())
116    }
117
118    pub fn file_line_map(&self, file: usize) -> Option<&LineMap> {
119        self.file_line_maps.get(file)
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn line_map() {
129        let line_map = LineMap::from_str("");
130        assert_eq!(line_map.line_ends, &[]);
131        assert_eq!(line_map.line(0), 0);
132        assert_eq!(line_map.line(100), 0);
133
134        let line_map = LineMap::from_str("line 0");
135        assert_eq!(line_map.line_ends, &[]);
136        assert_eq!(line_map.line(0), 0);
137        assert_eq!(line_map.line(100), 0);
138
139        let line_map = LineMap::from_str("line 0\nline 1");
140        assert_eq!(line_map.line_ends, &[7]);
141        assert_eq!(line_map.line(0), 0);
142        assert_eq!(line_map.line(100), 1);
143    }
144}