From b1b302bbfb0310d7528875f2cd7b54b971813364 Mon Sep 17 00:00:00 2001 From: LSTM-Kirigaya <1193466151@qq.com> Date: Mon, 9 Dec 2024 23:07:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20iverilog=20=E7=9A=84?= =?UTF-8?q?=E8=AF=8A=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/diagnostics/common.rs | 23 +++++++++- src/diagnostics/iverilog.rs | 85 +++++++++++++++++++++++++++++++----- src/diagnostics/mod.rs | 37 +++++++++------- src/diagnostics/modelsim.rs | 10 ++--- src/diagnostics/verible.rs | 9 ++-- src/diagnostics/verilator.rs | 8 ++-- src/diagnostics/vivado.rs | 9 ++-- src/sources.rs | 18 +++++++- 8 files changed, 156 insertions(+), 43 deletions(-) diff --git a/src/diagnostics/common.rs b/src/diagnostics/common.rs index b66f7d4..6def168 100644 --- a/src/diagnostics/common.rs +++ b/src/diagnostics/common.rs @@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize}; use ropey::Rope; use tower_lsp::lsp_types::{Diagnostic, Url}; +use crate::server::LspServer; + #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct LinterStatus { @@ -31,7 +33,7 @@ pub trait AbstractLinterConfiguration { fn new(language_id: &str, invoker: &str, args: Vec) -> Self; /// 提供一次诊断目前基于 uri 中的地址读取真实路径来获取诊断结果 - fn provide_diagnostics(&self, uri: &Url, rope: &Rope) -> Vec; + fn provide_diagnostics(&self, uri: &Url, rope: &Rope, server: &LspServer) -> Option>; /// 获取工具在 Windows 下的后缀,比如 iverilog 就是 exe , xvlog 就是 bat fn get_windows_extension(&self) -> &str; @@ -68,4 +70,23 @@ pub trait AbstractLinterConfiguration { /// /// 参考链接:https://kirigaya.cn/blog/article?seq=284 fn linter_status(&self) -> LinterStatus; +} + +/// rope 的第 line_index 行文字中,第一个和最后一个非空字符在本行的索引 +pub fn find_non_whitespace_indices(rope: &Rope, line_index: usize) -> Option<(usize, usize)> { + let line_text = rope.line(line_index); + let mut first_non_whitespace_index = None; + let mut last_non_whitespace_index = None; + for (i, c) in line_text.chars().enumerate() { + if !c.is_whitespace() { + if first_non_whitespace_index.is_none() { + first_non_whitespace_index = Some(i); + } + last_non_whitespace_index = Some(i); + } + } + match (first_non_whitespace_index, last_non_whitespace_index) { + (Some(first), Some(last)) => Some((first, last)), + _ => None, + } } \ No newline at end of file diff --git a/src/diagnostics/iverilog.rs b/src/diagnostics/iverilog.rs index cac8bff..936bac3 100644 --- a/src/diagnostics/iverilog.rs +++ b/src/diagnostics/iverilog.rs @@ -1,10 +1,12 @@ use std::process::{Command, Stdio}; +use log::info; +use regex::Regex; use serde::{Deserialize, Serialize}; use ropey::Rope; -use tower_lsp::lsp_types::{Diagnostic, Url}; +use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url}; -use crate::utils::command::is_command_valid; +use crate::{diagnostics::find_non_whitespace_indices, server::LspServer, sources::LSPSupport, utils::command::is_command_valid}; use super::{AbstractLinterConfiguration, LinterStatus}; @@ -47,25 +49,88 @@ impl AbstractLinterConfiguration for IverilogConfiguration { fn provide_diagnostics( &self, uri: &Url, - #[allow(unused)] - rope: &Rope - ) -> Vec { + rope: &Rope, + server: &LspServer + ) -> Option> { let mut diagnostics = Vec::::new(); let invoke_name = self.get_invoke_name("iverilog"); let pathbuf = uri.to_file_path().unwrap(); let path_string = pathbuf.to_str().unwrap(); - let mut process = Command::new(invoke_name) + let child = Command::new(&invoke_name) .stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) - .arg("-t null") + .args(&self.linter.args) .arg(path_string) - .spawn(); + .spawn() + .ok()?; - - diagnostics + let output = child.wait_with_output().ok()?; + let output_string = String::from_utf8(output.stderr).ok()?; + + info!("iverilog linter: {:?}, output:\n{}", path_string, output_string); + + // 初始化一个正则捕获器 + static REGEX_STORE: std::sync::OnceLock = std::sync::OnceLock::new(); + // {filename}:{errorno}: {error tag}(:{error description}) + let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"^(.*?)\s*:\s*(\d+)\s*:\s*(\w+)\s*(?::\s*(.*))?$").unwrap() }); + + for error_line in output_string.lines() { + + let captures = match regex.captures(error_line) { + Some(cap) => cap, + None => continue + }; + + let error_no = captures.get(2).unwrap().as_str().trim(); + let error_tag = captures.get(3).unwrap().as_str().trim(); + let error_description = captures.get(4).map_or("", |m| m.as_str()).trim(); + + let error_no = match error_no.parse::() { + // Icarus Verilog 在报告错误和警告时,行数是从 1 开始计数的。 + Ok(no) => no - 1, + Err(_) => 0 + }; + + if let Some((start_char, end_char)) = find_non_whitespace_indices(rope, error_no) { + let range = Range { + start: Position { line: error_no as u32, character: start_char as u32 }, + end: Position { line: error_no as u32, character: end_char as u32 } + }; + + // 特殊处理错误信息,比如 + // /home/dide/project/Digital-Test/DIDEtemp/user/sim/FFT/FFT_IFFT_tb.v:81: error: Unknown module type: FFT_IFFT + // 找不到模块 XXX,此时 error_description 就是 Unknown module type: 找不到的模块名,去 fast 里寻找 + if error_description.starts_with("Unknown module type") { + let mut groups = error_description.split(":"); + if let Some(unknown_module_name) = groups.nth(1) { + let unknown_module_name = unknown_module_name.trim(); + info!("包含 {} ? {}", unknown_module_name, server.srcs.contains_module(unknown_module_name)); + + if server.srcs.contains_module(unknown_module_name) { + continue; + } + } + } + + let diagnostic = Diagnostic { + range, + code: None, + severity: Some(DiagnosticSeverity::ERROR), + source: Some("Digital IDE: iverilog".to_string()), + message: format!("{} {}", error_tag, error_description), + related_information: None, + tags: None, + code_description: None, + data: None + }; + diagnostics.push(diagnostic); + } + } + + Some(diagnostics) } fn get_windows_extension(&self) -> &str { diff --git a/src/diagnostics/mod.rs b/src/diagnostics/mod.rs index 5cd8514..3d5c98f 100644 --- a/src/diagnostics/mod.rs +++ b/src/diagnostics/mod.rs @@ -27,12 +27,18 @@ pub use modelsim::*; /// - provide_diagnostics: 提供一次诊断 /// - /// 获取诊断核心函数 +/// - uri: 当前正在诊断的文件的 uri +/// - rope: 当前正在诊断的文件在后端增量更新的文本内容 +/// - files: 所有 hdl 文件,方便后续进行联合诊断使用 +/// - configuration: 前端关于 lsp 相关的配置 +/// - server: 服务实例 pub fn provide_diagnostics( uri: Url, rope: &Rope, #[allow(unused_variables)] files: Vec, configuration: &LspConfiguration, + server: &LspServer ) -> PublishDiagnosticsParams { let mut diagnostics = Vec::::new(); let language_id = get_language_id_by_uri(&uri); @@ -60,33 +66,33 @@ pub fn provide_diagnostics( match linter_configuration { config if config.iverilog.linter.enabled => { info!("iverilog linter enter"); - diagnostics.append( - &mut config.iverilog.provide_diagnostics(&uri, rope) - ); + if let Some(diag) = &mut config.iverilog.provide_diagnostics(&uri, rope, server) { + diagnostics.append(diag); + } } config if config.verilator.linter.enabled => { info!("verilator linter enter"); - diagnostics.append( - &mut config.verilator.provide_diagnostics(&uri, rope) - ); + if let Some(diag) = &mut config.verilator.provide_diagnostics(&uri, rope, server) { + diagnostics.append(diag); + } } config if config.verible.linter.enabled => { info!("verible linter enter"); - diagnostics.append( - &mut config.verible.provide_diagnostics(&uri, rope) - ); + if let Some(diag) = &mut config.verible.provide_diagnostics(&uri, rope, server) { + diagnostics.append(diag); + } } config if config.modelsim.linter.enabled => { info!("modelsim linter enter"); - diagnostics.append( - &mut config.modelsim.provide_diagnostics(&uri, rope) - ); + if let Some(diag) = &mut config.modelsim.provide_diagnostics(&uri, rope, server) { + diagnostics.append(diag); + } } config if config.vivado.linter.enabled => { info!("vivado linter enter"); - diagnostics.append( - &mut config.vivado.provide_diagnostics(&uri, rope) - ); + if let Some(diag) = &mut config.vivado.provide_diagnostics(&uri, rope, server) { + diagnostics.append(diag); + } } _ => {} } @@ -123,6 +129,7 @@ pub fn update_diagnostics_configuration( let linter_configuration = linter_configuration.unwrap(); + linter_configuration.iverilog.linter.enabled = false; linter_configuration.verilator.linter.enabled = false; linter_configuration.verible.linter.enabled = false; linter_configuration.modelsim.linter.enabled = false; diff --git a/src/diagnostics/modelsim.rs b/src/diagnostics/modelsim.rs index 9ce5a91..1bd5876 100644 --- a/src/diagnostics/modelsim.rs +++ b/src/diagnostics/modelsim.rs @@ -1,7 +1,7 @@ #[allow(unused)] use log::info; use serde::{Deserialize, Serialize}; -use crate::utils::command::is_command_valid; +use crate::{server::LspServer, utils::command::is_command_valid}; use super::{AbstractLinterConfiguration, LinterStatus}; use ropey::Rope; use tower_lsp::lsp_types::{Diagnostic, Url}; @@ -48,12 +48,12 @@ impl AbstractLinterConfiguration for ModelsimConfiguration { fn provide_diagnostics( &self, uri: &Url, - #[allow(unused)] - rope: &Rope - ) -> Vec { + rope: &Rope, + server: &LspServer + ) -> Option> { let mut diagnostics = Vec::::new(); - diagnostics + Some(diagnostics) } fn get_windows_extension(&self) -> &str { diff --git a/src/diagnostics/verible.rs b/src/diagnostics/verible.rs index d169ab7..0b00ece 100644 --- a/src/diagnostics/verible.rs +++ b/src/diagnostics/verible.rs @@ -4,7 +4,7 @@ use ropey::Rope; use std::process::{Command, Stdio}; use tower_lsp::lsp_types::*; -use crate::utils::command::is_command_valid; +use crate::{server::LspServer, utils::command::is_command_valid}; use super::{AbstractLinterConfiguration, LinterStatus}; @@ -130,11 +130,12 @@ impl AbstractLinterConfiguration for VeribleConfiguration { &self, uri: &Url, #[allow(unused)] - rope: &Rope - ) -> Vec { + rope: &Rope, + server: &LspServer + ) -> Option> { let mut diagnostics = Vec::::new(); - diagnostics + Some(diagnostics) } fn get_linter_path(&self) -> &str { diff --git a/src/diagnostics/verilator.rs b/src/diagnostics/verilator.rs index 72f5a3f..563c2a3 100644 --- a/src/diagnostics/verilator.rs +++ b/src/diagnostics/verilator.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use std::process::{Command, Stdio}; use tower_lsp::lsp_types::*; +use crate::server::LspServer; use crate::utils::command::is_command_valid; use super::{AbstractLinterConfiguration, LinterStatus}; @@ -134,11 +135,12 @@ impl AbstractLinterConfiguration for VerilatorConfiguration { &self, uri: &Url, #[allow(unused)] - rope: &Rope - ) -> Vec { + rope: &Rope, + server: &LspServer + ) -> Option> { let mut diagnostics = Vec::::new(); - diagnostics + Some(diagnostics) } fn get_linter_path(&self) -> &str { diff --git a/src/diagnostics/vivado.rs b/src/diagnostics/vivado.rs index e26df89..256c65c 100644 --- a/src/diagnostics/vivado.rs +++ b/src/diagnostics/vivado.rs @@ -1,7 +1,7 @@ #[allow(unused)] use log::info; use serde::{Deserialize, Serialize}; -use crate::utils::command::is_command_valid; +use crate::{server::LspServer, utils::command::is_command_valid}; use ropey::Rope; use tower_lsp::lsp_types::{Diagnostic, Url}; @@ -47,11 +47,12 @@ impl AbstractLinterConfiguration for VivadoConfiguration { &self, uri: &Url, #[allow(unused)] - rope: &Rope - ) -> Vec { + rope: &Rope, + server: &LspServer + ) -> Option> { let mut diagnostics = Vec::::new(); - diagnostics + Some(diagnostics) } fn get_linter_path(&self) -> &str { diff --git a/src/sources.rs b/src/sources.rs index 582d980..d9e745c 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -61,7 +61,8 @@ impl LspServer { uri, &file.text, urls, - &self.configuration.read().unwrap() + &self.configuration.read().unwrap(), + &self ); diagnostics } else { @@ -113,6 +114,7 @@ impl LspServer { &file.text, urls, &self.configuration.read().unwrap(), + &self ) } else { PublishDiagnosticsParams { @@ -491,6 +493,20 @@ impl Sources { } None } + + /// 同时从 fast 和 primitives 里面找 module + /// 在内部判断一个 module 是否存在时会用到 + /// - name: 模块的名字 + pub fn contains_module(&self, name: &str) -> bool { + if let Some(_) = self.hdl_param.find_module_by_name(name) { + return true; + } + + // 从 primitives 里面寻找 + let primitive_text = self.primitive_text.clone(); + let primitive_map = primitive_text.name_to_text.read().unwrap(); + primitive_map.contains_key(name) + } }