完成 verilator 的诊断

This commit is contained in:
锦恢 2024-12-10 18:35:29 +08:00
parent b1b302bbfb
commit 7dd4f58da6
8 changed files with 151 additions and 184 deletions

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use ropey::Rope; use ropey::Rope;
use tower_lsp::lsp_types::{Diagnostic, Url}; use tower_lsp::lsp_types::{Diagnostic, Url};
use crate::server::LspServer; use crate::{server::LspServer, utils::command::is_command_valid};
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -35,32 +35,28 @@ pub trait AbstractLinterConfiguration {
/// 提供一次诊断目前基于 uri 中的地址读取真实路径来获取诊断结果 /// 提供一次诊断目前基于 uri 中的地址读取真实路径来获取诊断结果
fn provide_diagnostics(&self, uri: &Url, rope: &Rope, server: &LspServer) -> Option<Vec<Diagnostic>>; fn provide_diagnostics(&self, uri: &Url, rope: &Rope, server: &LspServer) -> Option<Vec<Diagnostic>>;
/// 获取工具在 Windows 下的后缀,比如 iverilog 就是 exe xvlog 就是 bat
fn get_windows_extension(&self) -> &str;
/// 获取 linter 安装路径,内部实现,内部调用 /// 获取 linter 安装路径,内部实现,内部调用
fn get_linter_path(&self) -> &str; fn get_linter_path(&self) -> &str;
/// 获取 可执行文件 的名字,比如
/// - iverilog.exe
/// - iverilog
/// - xvlog.bat
fn get_exe_name(&self) -> String;
/// 获取真实调用路径,比如 /// 获取真实调用路径,比如
/// iverilog.exe 或者 /path/to/verilog.exe /// iverilog.exe 或者 /path/to/verilog.exe
fn get_invoke_name(&self, execute_name: &str) -> String { fn get_invoke_name(&self) -> String {
let suffix = { let exe_name = self.get_exe_name();
let os = std::env::consts::OS;
if os == "windows" {
self.get_windows_extension()
} else {
""
}
};
let linter_path = self.get_linter_path(); let linter_path = self.get_linter_path();
let pathbuf = PathBuf::from_str(linter_path); let pathbuf = PathBuf::from_str(linter_path);
if linter_path.len() == 0 || !pathbuf.as_ref().unwrap().exists() { if linter_path.len() == 0 || !pathbuf.as_ref().unwrap().exists() {
format!("{}{}", execute_name, suffix) exe_name.to_string()
} else { } else {
// 路径存在 // 路径存在
let pathbuf = pathbuf.unwrap(); let pathbuf = pathbuf.unwrap();
let pathbuf = pathbuf.join(format!("{}{}", execute_name, suffix)); let pathbuf = pathbuf.join(exe_name);
let path_string = pathbuf.to_str().unwrap(); let path_string = pathbuf.to_str().unwrap();
path_string.to_string() path_string.to_string()
} }
@ -69,7 +65,15 @@ pub trait AbstractLinterConfiguration {
/// 获取与该工具匹配的诊断器名字与调用函数名。并检查它们是否有效 /// 获取与该工具匹配的诊断器名字与调用函数名。并检查它们是否有效
/// ///
/// 参考链接https://kirigaya.cn/blog/article?seq=284 /// 参考链接https://kirigaya.cn/blog/article?seq=284
fn linter_status(&self) -> LinterStatus; fn linter_status(&self) -> LinterStatus {
let invoke_name = self.get_invoke_name();
let available = is_command_valid(&invoke_name);
LinterStatus {
tool_name: self.get_exe_name(),
available,
invoke_name
}
}
} }
/// rope 的第 line_index 行文字中,第一个和最后一个非空字符在本行的索引 /// rope 的第 line_index 行文字中,第一个和最后一个非空字符在本行的索引

View File

@ -54,7 +54,7 @@ impl AbstractLinterConfiguration for IverilogConfiguration {
) -> Option<Vec<Diagnostic>> { ) -> Option<Vec<Diagnostic>> {
let mut diagnostics = Vec::<Diagnostic>::new(); let mut diagnostics = Vec::<Diagnostic>::new();
let invoke_name = self.get_invoke_name("iverilog"); let invoke_name = self.get_invoke_name();
let pathbuf = uri.to_file_path().unwrap(); let pathbuf = uri.to_file_path().unwrap();
let path_string = pathbuf.to_str().unwrap(); let path_string = pathbuf.to_str().unwrap();
@ -133,31 +133,15 @@ impl AbstractLinterConfiguration for IverilogConfiguration {
Some(diagnostics) Some(diagnostics)
} }
fn get_windows_extension(&self) -> &str {
"exe"
}
fn get_linter_path(&self) -> &str { fn get_linter_path(&self) -> &str {
&self.linter.path &self.linter.path
} }
fn linter_status(&self) -> LinterStatus { fn get_exe_name(&self) -> String {
match self.language_id.as_str() { if std::env::consts::OS == "windows" {
"verilog" => { format!("{}.exe", self.linter.exe_name)
let invoke_name = self.get_invoke_name("iverilog"); } else {
let available = is_command_valid(&invoke_name); self.linter.exe_name.to_string()
LinterStatus {
tool_name: "iverilog".to_string(),
available,
invoke_name
}
}
// 不支持 svlog
// 不支持 vhdl
_ => {
LinterStatus::default()
}
} }
} }
} }

View File

@ -139,19 +139,19 @@ pub fn update_diagnostics_configuration(
"iverilog" => { "iverilog" => {
linter_configuration.iverilog.linter.enabled = true; linter_configuration.iverilog.linter.enabled = true;
linter_configuration.iverilog.linter.path = linter_path.to_string(); linter_configuration.iverilog.linter.path = linter_path.to_string();
let invoke_name = linter_configuration.iverilog.get_invoke_name("iverilog"); let invoke_name = linter_configuration.iverilog.get_invoke_name();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
} }
"verilator" => { "verilator" => {
linter_configuration.verilator.linter.enabled = true; linter_configuration.verilator.linter.enabled = true;
linter_configuration.verilator.linter.path = linter_path.to_string(); linter_configuration.verilator.linter.path = linter_path.to_string();
let invoke_name = linter_configuration.verilator.get_invoke_name("verilator"); let invoke_name = linter_configuration.verilator.get_invoke_name();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
} }
"verible" => { "verible" => {
linter_configuration.verible.linter.enabled = true; linter_configuration.verible.linter.enabled = true;
linter_configuration.verible.linter.path = linter_path.to_string(); linter_configuration.verible.linter.path = linter_path.to_string();
let invoke_name = linter_configuration.verible.get_invoke_name("verible-verilog-syntax"); let invoke_name = linter_configuration.verible.get_invoke_name();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
} }
"modelsim" => { "modelsim" => {
@ -159,9 +159,9 @@ pub fn update_diagnostics_configuration(
linter_configuration.modelsim.linter.path = linter_path.to_string(); linter_configuration.modelsim.linter.path = linter_path.to_string();
let invoke_name = { let invoke_name = {
if language_id == "vhdl" { if language_id == "vhdl" {
linter_configuration.modelsim.get_invoke_name("vcom") linter_configuration.modelsim.get_invoke_name()
} else { } else {
linter_configuration.modelsim.get_invoke_name("vlog") linter_configuration.modelsim.get_invoke_name()
} }
}; };
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
@ -171,9 +171,9 @@ pub fn update_diagnostics_configuration(
linter_configuration.vivado.linter.path = linter_path.to_string(); linter_configuration.vivado.linter.path = linter_path.to_string();
let invoke_name = { let invoke_name = {
if language_id == "vhdl" { if language_id == "vhdl" {
linter_configuration.vivado.get_invoke_name("xvhdl") linter_configuration.vivado.get_invoke_name()
} else { } else {
linter_configuration.vivado.get_invoke_name("xvlog") linter_configuration.vivado.get_invoke_name()
} }
}; };
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);

View File

@ -56,49 +56,15 @@ impl AbstractLinterConfiguration for ModelsimConfiguration {
Some(diagnostics) Some(diagnostics)
} }
fn get_windows_extension(&self) -> &str {
"exe"
}
fn get_linter_path(&self) -> &str { fn get_linter_path(&self) -> &str {
&self.linter.path &self.linter.path
} }
fn linter_status(&self) -> LinterStatus { fn get_exe_name(&self) -> String {
match self.language_id.as_str() { if std::env::consts::OS == "windows" {
"verilog" => { format!("{}.exe", self.linter.exe_name)
let invoke_name = self.get_invoke_name("vlog"); } else {
let available = is_command_valid(&invoke_name); self.linter.exe_name.to_string()
LinterStatus {
tool_name: "vlog".to_string(),
available,
invoke_name
}
}
"systemverilog" => {
let invoke_name = self.get_invoke_name("vlog");
let available = is_command_valid(&invoke_name);
LinterStatus {
tool_name: "vlog".to_string(),
available,
invoke_name
}
}
"vhdl" => {
let invoke_name = self.get_invoke_name("vcom");
let available = is_command_valid(&invoke_name);
LinterStatus {
tool_name: "vcom".to_string(),
available,
invoke_name
}
}
_ => {
LinterStatus::default()
}
} }
} }
} }

View File

@ -142,36 +142,11 @@ impl AbstractLinterConfiguration for VeribleConfiguration {
&self.linter.path &self.linter.path
} }
fn get_windows_extension(&self) -> &str { fn get_exe_name(&self) -> String {
"exe" if std::env::consts::OS == "windows" {
} format!("{}.exe", self.linter.exe_name)
} else {
fn linter_status(&self) -> LinterStatus { self.linter.exe_name.to_string()
match self.language_id.as_str() {
"verilog" => {
let invoke_name = self.get_invoke_name("verible-verilog-syntax");
let available = is_command_valid(&invoke_name);
LinterStatus {
tool_name: "verible-verilog-syntax".to_string(),
available,
invoke_name
}
}
"systemverilog" => {
let invoke_name = self.get_invoke_name("verible-verilog-syntax");
let available = is_command_valid(&invoke_name);
LinterStatus {
tool_name: "verible-verilog-syntax".to_string(),
available,
invoke_name
}
}
// verible 不支持 vhdl
_ => {
LinterStatus::default()
}
} }
} }
} }

View File

@ -1,3 +1,4 @@
use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use regex::Regex; use regex::Regex;
use ropey::Rope; use ropey::Rope;
@ -139,6 +140,97 @@ impl AbstractLinterConfiguration for VerilatorConfiguration {
server: &LspServer server: &LspServer
) -> Option<Vec<Diagnostic>> { ) -> Option<Vec<Diagnostic>> {
let mut diagnostics = Vec::<Diagnostic>::new(); let mut diagnostics = Vec::<Diagnostic>::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()?;
info!("verilator linter: {:?}, output:\n{}", path_string, output_string);
static REGEX_STORE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"%(?P<severity>Error|Warning)(-(?P<warning_type>[A-Z0-9_]+))?: (?P<filepath>[^:]+):(?P<line>\d+):((?P<col>\d+):)? ?(?P<message>.*)").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 {
let captures = match regex.captures(error_tuple.0.as_str()) {
Some(caps) => caps,
None => continue
};
// 因为 verilator 会递归检查,因此报错信息中可能会出现非本文件内的信息
if captures.name("filepath")?.as_str().replace("\\", "/") != path_string {
continue;
}
let line: u32 = captures.name("line")?.as_str().to_string().parse().ok()?;
let col: u32 = captures.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(captures.name("severity")?.as_str());
let message = match severity {
Some(DiagnosticSeverity::ERROR) => captures.name("message")?.as_str().to_string(),
Some(DiagnosticSeverity::WARNING) => format!(
"{}: {}",
captures.name("warning_type")?.as_str(),
captures.name("message")?.as_str()
),
_ => "".to_string(),
};
let message = format!("{}\n\n---\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) Some(diagnostics)
} }
@ -147,36 +239,11 @@ impl AbstractLinterConfiguration for VerilatorConfiguration {
&self.linter.path &self.linter.path
} }
fn get_windows_extension(&self) -> &str { fn get_exe_name(&self) -> String {
"exe" if std::env::consts::OS == "windows" {
} format!("{}.exe", self.linter.exe_name)
} else {
fn linter_status(&self) -> LinterStatus { self.linter.exe_name.to_string()
match self.language_id.as_str() {
"verilog" => {
let invoke_name = self.get_invoke_name("verilator");
let available = is_command_valid(&invoke_name);
LinterStatus {
tool_name: "verilator".to_string(),
available,
invoke_name
}
}
"systemverilog" => {
let invoke_name = self.get_invoke_name("verilator");
let available = is_command_valid(&invoke_name);
LinterStatus {
tool_name: "verilator".to_string(),
available,
invoke_name
}
}
// verilator 不支持 vhdl
_ => {
LinterStatus::default()
}
} }
} }
} }

View File

@ -59,45 +59,11 @@ impl AbstractLinterConfiguration for VivadoConfiguration {
&self.linter.path &self.linter.path
} }
fn get_windows_extension(&self) -> &str { fn get_exe_name(&self) -> String {
"exe" if std::env::consts::OS == "windows" {
} format!("{}.bat", self.linter.exe_name)
} else {
fn linter_status(&self) -> LinterStatus { self.linter.exe_name.to_string()
match self.language_id.as_str() {
"verilog" => {
let invoke_name = self.get_invoke_name("xvlog");
let available = is_command_valid(&invoke_name);
LinterStatus {
tool_name: "xvlog".to_string(),
available,
invoke_name
}
}
"systemverilog" => {
let invoke_name = self.get_invoke_name("xvlog");
let available = is_command_valid(&invoke_name);
LinterStatus {
tool_name: "xvlog".to_string(),
available,
invoke_name
}
}
"vhdl" => {
let invoke_name = self.get_invoke_name("xvhdl");
let available = is_command_valid(&invoke_name);
LinterStatus {
tool_name: "xvhdl".to_string(),
available,
invoke_name
}
}
_ => {
LinterStatus::default()
}
} }
} }
} }

View File

@ -17,7 +17,12 @@ pub fn inlay_hint(server: &LspServer, params: &InlayHintParams) -> Option<Vec<In
let fast_map = server.srcs.hdl_param.path_to_hdl_file.read().unwrap(); let fast_map = server.srcs.hdl_param.path_to_hdl_file.read().unwrap();
let file_id = server.srcs.get_id(&params.text_document.uri); let file_id = server.srcs.get_id(&params.text_document.uri);
let file = server.srcs.get_file(file_id).unwrap(); let file = server.srcs.get_file(file_id);
if file.is_none() {
return None;
}
let file = file.unwrap();
let file = file.read().unwrap(); let file = file.read().unwrap();
let rope = &file.text; let rope = &file.text;
// 先找到 当前 所在的 hdlfile // 先找到 当前 所在的 hdlfile