sourcemap_resolver/
extractor.rs

1use regex::Regex;
2use std::cmp::Ordering;
3use std::collections::VecDeque;
4use std::ops::Range;
5use std::path::PathBuf;
6
7#[derive(Debug)]
8pub struct ExtractResult {
9    pub range: Range<u32>,
10    pub path: PathBuf,
11    pub line: u32,
12    pub column: Option<u32>,
13}
14
15struct Pattern {
16    window: usize,
17    regex: Regex,
18}
19
20pub struct Extractor {
21    patterns: Vec<Pattern>,
22    window: usize,
23    lines: VecDeque<(String, Option<ExtractResult>)>,
24}
25
26impl Default for Extractor {
27    fn default() -> Self {
28        let patterns: Vec<Pattern> = vec![
29            // "path", 10
30            Pattern {
31                window: 1,
32                regex: Regex::new(r###""?(?<path>[^ "\n]+)"?, (?:line )?(?<line>[0-9]+)"###)
33                    .unwrap(),
34            },
35            // path:10:10
36            Pattern {
37                window: 1,
38                regex: Regex::new(r"(?<path>[^: \[\]]+):(?<line>[0-9]+)(?::(?<column>[0-9]+))?")
39                    .unwrap(),
40            },
41            // File: path Line: 10
42            Pattern {
43                window: 1,
44                regex: Regex::new(r"File: (?<path>[^: \[\]]+) Line: (?<line>[0-9]+)").unwrap(),
45            },
46            // line 10 in file
47            // 'path'
48            Pattern {
49                window: 2,
50                regex: Regex::new(r"line (?<line>[0-9]+) in file\s*\n\s*'(?<path>[^']+)'").unwrap(),
51            },
52        ];
53        let window = patterns.iter().map(|x| x.window).max().unwrap_or(1);
54
55        Self {
56            patterns,
57            window,
58            lines: VecDeque::new(),
59        }
60    }
61}
62
63impl Extractor {
64    pub fn new() -> Self {
65        Self::default()
66    }
67
68    pub fn push_line(&mut self, line: &str) -> Option<(String, Option<ExtractResult>)> {
69        let mut ret = None;
70
71        self.lines.push_back((line.to_string(), None));
72
73        match self.lines.len().cmp(&self.window) {
74            Ordering::Greater => ret = self.lines.pop_front(),
75            Ordering::Less => return None,
76            _ => (),
77        }
78
79        let text = self
80            .lines
81            .iter()
82            .map(|x| x.0.clone())
83            .collect::<Vec<_>>()
84            .join("\n");
85
86        for pattern in self.patterns.iter() {
87            if let Some(caps) = pattern.regex.captures(&text) {
88                let start = caps.get(0).unwrap().start();
89                let end = caps.get(0).unwrap().end();
90                let path = caps.name("path").unwrap().as_str().to_string();
91                let line = caps.name("line").unwrap().as_str().parse::<u32>().unwrap();
92                let column = caps
93                    .name("column")
94                    .map(|x| x.as_str().parse::<u32>().unwrap());
95
96                let mut line_start = 0;
97                let mut line_end = 0;
98                for (text, extract) in &mut self.lines {
99                    line_end += text.len() + 1;
100
101                    if line_start <= end && end < line_end {
102                        let start = start.saturating_sub(line_start) as u32;
103                        let end = (end - line_start) as u32;
104                        *extract = Some(ExtractResult {
105                            range: Range { start, end },
106                            path: PathBuf::from(path.clone()),
107                            line,
108                            column,
109                        });
110                    }
111
112                    line_start = line_end;
113                }
114            }
115        }
116
117        ret
118    }
119
120    pub fn end(&mut self) -> Vec<(String, Option<ExtractResult>)> {
121        self.lines.drain(0..).collect()
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    fn extract(text: &str) -> Vec<(String, Option<ExtractResult>)> {
130        let mut extractor = Extractor::new();
131        let mut ret = Vec::new();
132        for line in text.lines() {
133            if let Some(x) = extractor.push_line(line) {
134                ret.push(x);
135            }
136        }
137        ret.append(&mut extractor.end());
138        ret
139    }
140
141    #[test]
142    fn vcs() {
143        let src = r##"
144test.sv, 31
145"##;
146
147        let ret = extract(&src);
148        assert_eq!(ret.len(), 2);
149        assert_eq!(ret[1].0, "test.sv, 31");
150        assert_eq!(ret[1].1.as_ref().unwrap().range, 0..11);
151        assert_eq!(ret[1].1.as_ref().unwrap().path.to_string_lossy(), "test.sv");
152        assert_eq!(ret[1].1.as_ref().unwrap().line, 31);
153        assert_eq!(ret[1].1.as_ref().unwrap().column, None);
154
155        let src = r##"
156"test.sv", 10: test1.unnamed$$_1: started at 0s failed at 0s
157"##;
158
159        let ret = extract(src);
160        assert_eq!(ret.len(), 2);
161        assert_eq!(
162            ret[1].0,
163            "\"test.sv\", 10: test1.unnamed$$_1: started at 0s failed at 0s"
164        );
165        assert_eq!(ret[1].1.as_ref().unwrap().range, 0..13);
166        assert_eq!(ret[1].1.as_ref().unwrap().path.to_string_lossy(), "test.sv");
167        assert_eq!(ret[1].1.as_ref().unwrap().line, 10);
168        assert_eq!(ret[1].1.as_ref().unwrap().column, None);
169
170        let src = r##"
171$finish called from file "test.sv", line 12.
172"##;
173
174        let ret = extract(&src);
175        assert_eq!(ret.len(), 2);
176        assert_eq!(ret[1].0, "$finish called from file \"test.sv\", line 12.");
177        assert_eq!(ret[1].1.as_ref().unwrap().range, 25..43);
178        assert_eq!(ret[1].1.as_ref().unwrap().path.to_string_lossy(), "test.sv");
179        assert_eq!(ret[1].1.as_ref().unwrap().line, 12);
180        assert_eq!(ret[1].1.as_ref().unwrap().column, None);
181    }
182
183    #[test]
184    fn vivado() {
185        let src = r##"
186Time: 0 ps  Iteration: 0  Process: /test1/Initial7_0  Scope: test1  File: test.sv Line: 9
187"##;
188
189        let ret = extract(src);
190        assert_eq!(ret.len(), 2);
191        assert_eq!(
192            ret[1].0,
193            "Time: 0 ps  Iteration: 0  Process: /test1/Initial7_0  Scope: test1  File: test.sv Line: 9"
194        );
195        assert_eq!(ret[1].1.as_ref().unwrap().range, 68..89);
196        assert_eq!(ret[1].1.as_ref().unwrap().path.to_string_lossy(), "test.sv");
197        assert_eq!(ret[1].1.as_ref().unwrap().line, 9);
198        assert_eq!(ret[1].1.as_ref().unwrap().column, None);
199
200        let src = r##"
201ERROR: [VRFC 10-4982] syntax error near 'endmodule' [test.sv:23]
202"##;
203
204        let ret = extract(src);
205        assert_eq!(ret.len(), 2);
206        assert_eq!(
207            ret[1].0,
208            "ERROR: [VRFC 10-4982] syntax error near 'endmodule' [test.sv:23]"
209        );
210        assert_eq!(ret[1].1.as_ref().unwrap().range, 53..63);
211        assert_eq!(ret[1].1.as_ref().unwrap().path.to_string_lossy(), "test.sv");
212        assert_eq!(ret[1].1.as_ref().unwrap().line, 23);
213        assert_eq!(ret[1].1.as_ref().unwrap().column, None);
214    }
215
216    #[test]
217    fn verilator() {
218        let src = r##"
219%Error: test.sv:11: Verilog $stop
220"##;
221
222        let ret = extract(src);
223        assert_eq!(ret.len(), 2);
224        assert_eq!(ret[1].0, "%Error: test.sv:11: Verilog $stop");
225        assert_eq!(ret[1].1.as_ref().unwrap().range, 8..18);
226        assert_eq!(ret[1].1.as_ref().unwrap().path.to_string_lossy(), "test.sv");
227        assert_eq!(ret[1].1.as_ref().unwrap().line, 11);
228        assert_eq!(ret[1].1.as_ref().unwrap().column, None);
229
230        let src = r##"
231%Error: test.sv:23:1: syntax error, unexpected endmodule
232"##;
233
234        let ret = extract(src);
235        assert_eq!(ret.len(), 2);
236        assert_eq!(
237            ret[1].0,
238            "%Error: test.sv:23:1: syntax error, unexpected endmodule"
239        );
240        assert_eq!(ret[1].1.as_ref().unwrap().range, 8..20);
241        assert_eq!(ret[1].1.as_ref().unwrap().path.to_string_lossy(), "test.sv");
242        assert_eq!(ret[1].1.as_ref().unwrap().line, 23);
243        assert_eq!(ret[1].1.as_ref().unwrap().column, Some(1));
244    }
245
246    #[test]
247    fn design_compiler() {
248        let src = r##"
249Inferred memory devices in process
250	in routine ModuleA line 10 in file
251		'test.sv'.
252"##;
253
254        let ret = extract(src);
255        assert_eq!(ret.len(), 4);
256        assert_eq!(ret[3].0, "\t\t'test.sv'.");
257        assert_eq!(ret[3].1.as_ref().unwrap().range, 0..11);
258        assert_eq!(ret[3].1.as_ref().unwrap().path.to_string_lossy(), "test.sv");
259        assert_eq!(ret[3].1.as_ref().unwrap().line, 10);
260        assert_eq!(ret[3].1.as_ref().unwrap().column, None);
261
262        let src = r##"
263Warning:  test.sv:67: DEFAULT branch of CASE statement cannot be reached. (ELAB-311)
264"##;
265
266        let ret = extract(src);
267        assert_eq!(ret.len(), 2);
268        assert_eq!(
269            ret[1].0,
270            "Warning:  test.sv:67: DEFAULT branch of CASE statement cannot be reached. (ELAB-311)"
271        );
272        assert_eq!(ret[1].1.as_ref().unwrap().range, 10..20);
273        assert_eq!(ret[1].1.as_ref().unwrap().path.to_string_lossy(), "test.sv");
274        assert_eq!(ret[1].1.as_ref().unwrap().line, 67);
275        assert_eq!(ret[1].1.as_ref().unwrap().column, None);
276    }
277
278    #[test]
279    fn formality() {
280        let src = r##"
281Warning: INITIAL statements are not supported. (File: test.sv Line: 22)  (FMR_VLOG-101)
282"##;
283
284        let ret = extract(src);
285        assert_eq!(ret.len(), 2);
286        assert_eq!(
287            ret[1].0,
288            "Warning: INITIAL statements are not supported. (File: test.sv Line: 22)  (FMR_VLOG-101)"
289        );
290        assert_eq!(ret[1].1.as_ref().unwrap().range, 48..70);
291        assert_eq!(ret[1].1.as_ref().unwrap().path.to_string_lossy(), "test.sv");
292        assert_eq!(ret[1].1.as_ref().unwrap().line, 22);
293        assert_eq!(ret[1].1.as_ref().unwrap().column, None);
294    }
295}