use log::info; use serde::{Deserialize, Serialize}; use regex::Regex; use ropey::Rope; use std::process::{Command, Stdio}; use tower_lsp::lsp_types::*; use crate::server::LspServer; use super::AbstractLinterConfiguration; #[derive(Debug, Serialize, Deserialize)] pub struct VerilatorConfiguration { pub language_id: String, pub linter: VerilatorLinter, } #[derive(Debug, Serialize, Deserialize)] pub struct VerilatorLinter { pub name: String, pub exe_name: String, /// 目前是否启动 pub enabled: bool, pub path: String, pub args: Vec, } impl VerilatorLinter { fn new(invoker: &str, args: Vec) -> Self { Self { name: "verilator".to_string(), exe_name: invoker.to_string(), enabled: false, path: invoker.to_string(), args, } } } /// convert captured severity string to DiagnosticSeverity fn verilator_severity(severity: &str) -> Option { match severity { "Error" => Some(DiagnosticSeverity::ERROR), s if s.starts_with("Warning") => Some(DiagnosticSeverity::WARNING), // NOTE: afaik, verilator doesn't have an info or hint severity _ => Some(DiagnosticSeverity::INFORMATION), } } impl AbstractLinterConfiguration for VerilatorConfiguration { fn new(language_id: &str, invoker: &str, args: Vec) -> Self { VerilatorConfiguration { language_id: language_id.to_string(), linter: VerilatorLinter::new(invoker, args) } } fn provide_diagnostics( &self, uri: &Url, #[allow(unused)] rope: &Rope, server: &LspServer ) -> Option> { let mut diagnostics = Vec::::new(); let invoke_name = self.get_invoke_name(); let pathbuf = uri.to_file_path().unwrap(); let path_string = pathbuf.to_str().unwrap(); let child = Command::new(&invoke_name) .stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) .args(&self.linter.args) .arg(path_string) .spawn() .ok()?; let output = child.wait_with_output().ok()?; let output_string = String::from_utf8(output.stderr).ok()?; let lint_level = server.db.get_lsp_configuration_string_value("digital-ide.function.lsp.linter.linter-level").unwrap_or("error".to_string()); info!("verilator linter: {:?}, output:\n{}", path_string, output_string); static REGEX_STORE: std::sync::OnceLock = std::sync::OnceLock::new(); let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"%(?PError|Warning)(-(?P[A-Z0-9_]+))?: (?P[^:]+):(?P\d+):((?P\d+):)? ?(?P.*)").unwrap() }); // verilator 错误输出样例 // %Warning-IMPLICIT: library/Apply/Comm/FDE/AGC/AGC.v:23:12: Signal definition not found, creating implicitly: 'r_rms' // : ... Suggested alternative: 'ref_rms' // 23 | assign r_rms = (reference * reference); // | ^~~~~ // ... Use "/* verilator lint_off IMPLICIT */" and lint_on around source to disable this message. // %Error: Exiting due to 5 warning(s) // 先将上面的输出处理为一整块一整块的数组,每块分解为两个部分:第一行(用于解析行号和列号)和后面几行(具体错误地址) let mut error_tuples = Vec::<(String, String)>::new(); let mut current_error_tuple: Option<(&str, Vec<&str>)> = None; for error_line in output_string.lines() { if error_line.starts_with("%") { if let Some((first, ref description)) = current_error_tuple { error_tuples.push((first.to_string(), description.join("\n"))); } current_error_tuple = Some((error_line, Vec::<&str>::new())); } else { if let Some((_, ref mut description)) = current_error_tuple { description.push(error_line); } else { continue; } } } for error_tuple in error_tuples { // 对于 NOTFOUNDMODULE 进行过滤 // 如果存在于 fast 中,则直接跳过 if let Some(module_name) = match_not_module_found(error_tuple.0.as_str()) { if server.db.contains_module(&module_name) { continue; } } // 使用模板进行解析 let caps = match regex.captures(error_tuple.0.as_str()) { Some(caps) => caps, None => continue }; // 因为 verilator 会递归检查,因此报错信息中可能会出现非本文件内的信息 if caps.name("filepath")?.as_str().replace("\\", "/") != path_string { continue; } let line: u32 = caps.name("line")?.as_str().to_string().parse().ok()?; let col: u32 = caps.name("col").map_or("1", |m| m.as_str()).parse().ok()?; // verilator 的诊断索引都是 one index 的 let pos = Position::new(line - 1, col - 1); let severity = verilator_severity(caps.name("severity")?.as_str()); let message = match severity { Some(DiagnosticSeverity::ERROR) => { caps.name("message")?.as_str().to_string() }, Some(DiagnosticSeverity::WARNING) => { // 如果诊断等级为 error ,也就是只显示错误,那么直接跳过 if lint_level == "error" { continue; } format!( "{}: {}", caps.name("warning_type")?.as_str(), caps.name("message")?.as_str() ) }, _ => "".to_string(), }; let message = format!("{}\n\n{}", message, error_tuple.1); let diagnostic = Diagnostic { range: Range::new(pos, pos), code: None, severity, source: Some("Digital IDE: verilator".to_string()), message, related_information: None, tags: None, code_description: None, data: None }; diagnostics.push(diagnostic); } Some(diagnostics) } fn get_linter_path(&self) -> &str { &self.linter.path } fn get_exe_name(&self) -> String { if std::env::consts::OS == "windows" { format!("{}.exe", self.linter.exe_name) } else { self.linter.exe_name.to_string() } } } /// %Warning-DECLFILENAME: /home/dide/project/Digital-Test/DIDEtemp/user/sim/FFT/FFT_IFFT_tb.v:82:5: Filename 'FFT_IFFT_tb' does not match NOTFOUNDMODULE name: 'FFT_IFFT' /// 匹配最后一个 module 的 name fn match_not_module_found(error_line: &str) -> Option { if let Some(start_index) = error_line.find("NOTFOUNDMODULE") { let mut strings = "".to_string(); for char in error_line.chars().skip(start_index).skip_while(|x| x.to_string() != "'") { strings.push(char); } info!("strings: {:?}", strings); if strings.starts_with("'") && strings.ends_with("'") { strings = strings[1 .. strings.len() - 1].to_string(); } return Some(strings); } None }