209 lines
7.7 KiB
Rust
209 lines
7.7 KiB
Rust
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<String>,
|
|
}
|
|
|
|
impl VerilatorLinter {
|
|
fn new(invoker: &str, args: Vec<String>) -> 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<DiagnosticSeverity> {
|
|
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<String>) -> 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<Vec<Diagnostic>> {
|
|
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()?;
|
|
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<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 {
|
|
// 对于 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<String> {
|
|
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
|
|
} |