148 lines
5.1 KiB
Rust
148 lines
5.1 KiB
Rust
use std::process::{Command, Stdio};
|
||
|
||
use log::info;
|
||
use regex::Regex;
|
||
use serde::{Deserialize, Serialize};
|
||
use ropey::Rope;
|
||
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url};
|
||
|
||
use crate::{diagnostics::find_non_whitespace_indices, server::LspServer};
|
||
|
||
use super::AbstractLinterConfiguration;
|
||
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
pub struct IverilogConfiguration {
|
||
pub language_id: String,
|
||
pub linter: IverilogLinter
|
||
}
|
||
|
||
#[derive(Debug, Serialize, Deserialize)]
|
||
pub struct IverilogLinter {
|
||
pub name: String,
|
||
pub exe_name: String,
|
||
/// 目前是否启动
|
||
pub enabled: bool,
|
||
pub path: String,
|
||
pub args: Vec<String>,
|
||
}
|
||
|
||
impl IverilogLinter {
|
||
fn new(invoker: &str, args: Vec<String>) -> Self {
|
||
IverilogLinter {
|
||
name: "iverilog".to_string(),
|
||
exe_name: invoker.to_string(),
|
||
enabled: false,
|
||
path: invoker.to_string(),
|
||
args
|
||
}
|
||
}
|
||
}
|
||
|
||
impl AbstractLinterConfiguration for IverilogConfiguration {
|
||
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
|
||
IverilogConfiguration {
|
||
language_id: language_id.to_string(),
|
||
linter: IverilogLinter::new(invoker, args)
|
||
}
|
||
}
|
||
|
||
fn provide_diagnostics(
|
||
&self,
|
||
uri: &Url,
|
||
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()?;
|
||
|
||
info!("iverilog linter: {:?}, output:\n{}", path_string, output_string);
|
||
|
||
// 初始化一个正则捕获器
|
||
static REGEX_STORE: std::sync::OnceLock<Regex> = 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::<usize>() {
|
||
// 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.db.contains_module(unknown_module_name));
|
||
|
||
if server.db.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_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()
|
||
}
|
||
}
|
||
}
|