cranelift_isle/
files.rs

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