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 Pattern {
31 window: 1,
32 regex: Regex::new(r###""?(?<path>[^ "\n]+)"?, (?:line )?(?<line>[0-9]+)"###)
33 .unwrap(),
34 },
35 Pattern {
37 window: 1,
38 regex: Regex::new(r"(?<path>[^: \[\]]+):(?<line>[0-9]+)(?::(?<column>[0-9]+))?")
39 .unwrap(),
40 },
41 Pattern {
43 window: 1,
44 regex: Regex::new(r"File: (?<path>[^: \[\]]+) Line: (?<line>[0-9]+)").unwrap(),
45 },
46 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}