完成诊断器架构重新设计

This commit is contained in:
锦恢 2024-12-06 23:24:10 +08:00
parent 7824b74c9a
commit e726fffd99
16 changed files with 553 additions and 77 deletions

View File

@ -35,5 +35,14 @@
"\t}",
"}",
]
},
"new": {
"scope": "rust",
"prefix": "new",
"body": [
"pub fn new($1) -> Self {",
"\t$2",
"}"
]
}
}

66
src/diagnostics/common.rs Normal file
View File

@ -0,0 +1,66 @@
use std::{path::PathBuf, str::FromStr};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct LinterStatus {
pub tool_name: String,
pub available: bool,
pub invoke_name: String
}
impl Default for LinterStatus {
fn default() -> Self {
LinterStatus {
tool_name: "".to_string(),
available: false,
invoke_name: "".to_string()
}
}
}
pub trait AbstractLinterConfiguration {
fn new(language_id: &str) -> Self;
/// 提供一次诊断
fn provide_diagnostics(&self);
/// 获取工具在 Windows 下的后缀,比如 iverilog 就是 exe xvlog 就是 bat
fn get_windows_extension(&self) -> &str;
fn get_linter_path(&self) -> &str;
/// 获取真实调用路径,比如
///
/// iverilog.exe 或者 /path/to/verilog.exe
fn get_invoke_name(&self, execute_name: &str) -> String {
let suffix = {
let os = std::env::consts::OS;
if os == "windows" {
self.get_windows_extension()
} else {
""
}
};
let linter_path = self.get_linter_path();
let pathbuf = PathBuf::from_str(linter_path);
if linter_path.len() == 0 || !pathbuf.as_ref().unwrap().exists() {
format!("{}{}", execute_name, suffix)
} else {
// 路径存在
let pathbuf = pathbuf.unwrap();
let pathbuf = pathbuf.join(format!("{}{}", execute_name, suffix));
let path_string = pathbuf.to_str().unwrap();
path_string.to_string()
}
}
/// 获取与该工具匹配的诊断器名字与调用函数名。并检查它们是否有效
///
/// 参考链接https://kirigaya.cn/blog/article?seq=284
fn linter_status(&self) -> LinterStatus;
}

View File

@ -0,0 +1,74 @@
use std::{path::PathBuf, str::FromStr};
use serde::{Deserialize, Serialize};
use crate::utils::command::is_command_valid;
use super::{AbstractLinterConfiguration, LinterStatus};
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct IverilogConfiguration {
pub language_id: String,
pub linter: IverilogLinter
}
#[derive(Debug, Serialize, Deserialize)]
pub struct IverilogLinter {
pub name: String,
/// 目前是否启动
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl Default for IverilogLinter {
fn default() -> Self {
Self {
name: "iverilog".to_string(),
enabled: false,
path: "iverilog".to_string(),
args: Vec::new(),
}
}
}
impl AbstractLinterConfiguration for IverilogConfiguration {
fn new(language_id: &str) -> Self {
IverilogConfiguration {
language_id: language_id.to_string(),
linter: IverilogLinter::default()
}
}
fn provide_diagnostics(&self) {
}
fn get_windows_extension(&self) -> &str {
"exe"
}
fn get_linter_path(&self) -> &str {
&self.linter.path
}
fn linter_status(&self) -> LinterStatus {
match self.language_id.as_str() {
"verilog" => {
let invoke_name = self.get_invoke_name("iverilog");
let available = is_command_valid(&invoke_name);
LinterStatus {
tool_name: "iverilog".to_string(),
available,
invoke_name
}
}
// 不支持 svlog
// 不支持 vhdl
_ => {
LinterStatus::default()
}
}
}
}

View File

@ -1,16 +1,19 @@
use std::fs::OpenOptions;
use crate::{server::{LspConfiguration, LspServer}, utils::get_language_id_by_uri};
use log::info;
use ropey::Rope;
use serde::Deserialize;
use tower_lsp::lsp_types::*;
pub mod common;
pub mod iverilog;
pub mod verible;
pub mod verilator;
pub mod vivado;
pub mod modelsim;
pub use common::*;
pub use iverilog::*;
pub use verible::*;
pub use verilator::*;
pub use vivado::*;
@ -19,6 +22,10 @@ pub use modelsim::*;
/// description
/// 诊断功能需要提供两套函数,一套函数用于从给定路径读取文件并给出诊断结果;一套用于从 lsp 的文件缓冲区直接读取文本然后给出诊断结果。
/// 前者用于扫描整个项目使用,后者在用户实时修改代码时,给出实时的诊断信息。
///
/// 诊断模块的每一个子诊断器都需要实现如下的函数
/// - provide_diagnostics: 提供一次诊断
/// -
/// 获取诊断核心函数
pub fn provide_diagnostics(
@ -55,7 +62,7 @@ pub fn provide_diagnostics(
info!("verilator linter enter");
} else if linter_configuration.verible.linter.enabled {
info!("verilator linter enter");
info!("verible linter enter");
} else if linter_configuration.modelsim.linter.enabled {
info!("modelsim linter enter");
@ -103,27 +110,37 @@ pub fn update_diagnostics_configuration(
linter_configuration.modelsim.linter.enabled = false;
linter_configuration.vivado.linter.enabled = false;
if linter_configuration.verilator.linter.name == linter_name {
linter_configuration.verilator.linter.enabled = true;
linter_configuration.verilator.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.verilator.linter.path);
} else if linter_configuration.verible.linter.name == linter_name {
linter_configuration.verible.linter.enabled = true;
linter_configuration.verible.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.verible.linter.path);
} else if linter_configuration.modelsim.linter.name == linter_name {
linter_configuration.modelsim.linter.enabled = true;
linter_configuration.modelsim.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.modelsim.linter.path);
} else if linter_configuration.vivado.linter.name == linter_name {
linter_configuration.vivado.linter.enabled = true;
linter_configuration.vivado.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.vivado.linter.path);
match linter_name {
"iverilog" => {
linter_configuration.iverilog.linter.enabled = true;
linter_configuration.iverilog.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.iverilog.linter.path);
}
"verilator" => {
linter_configuration.verilator.linter.enabled = true;
linter_configuration.verilator.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.verilator.linter.path);
}
"verible" => {
linter_configuration.verible.linter.enabled = true;
linter_configuration.verible.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.verible.linter.path);
}
"modelsim" => {
linter_configuration.modelsim.linter.enabled = true;
linter_configuration.modelsim.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.modelsim.linter.path);
}
"vivado" => {
linter_configuration.vivado.linter.enabled = true;
linter_configuration.vivado.linter.path = linter_path.to_string();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, linter_configuration.vivado.linter.path);
}
_ => {
info!("未找到匹配的诊断器: {}", linter_name);
}
}
}
@ -141,6 +158,8 @@ pub enum DigitalLinterMode {
#[derive(Debug, Deserialize)]
#[derive(serde::Serialize)]
pub struct DigitalLinterConfiguration {
// iverilog 相关的工具配置
pub iverilog: IverilogConfiguration,
// verible 相关的工具配置
pub verible: VeribleConfiguration,
// verilator 相关的工具配置
@ -151,13 +170,14 @@ pub struct DigitalLinterConfiguration {
pub vivado: VivadoConfiguration
}
impl Default for DigitalLinterConfiguration {
fn default() -> Self {
impl DigitalLinterConfiguration {
pub fn new(language_id: &str) -> Self {
DigitalLinterConfiguration {
verible: VeribleConfiguration::default(),
verilator: VerilatorConfiguration::default(),
modelsim: ModelsimConfiguration::default(),
vivado: VivadoConfiguration::default()
iverilog: IverilogConfiguration::new(language_id),
verible: VeribleConfiguration::new(language_id),
verilator: VerilatorConfiguration::new(language_id),
modelsim: ModelsimConfiguration::new(language_id),
vivado: VivadoConfiguration::new(language_id)
}
}
}
}

View File

@ -1,15 +1,19 @@
use std::{path::PathBuf, str::FromStr};
use serde::{Deserialize, Serialize};
use crate::utils::command::is_command_valid;
use super::{AbstractLinterConfiguration, LinterStatus};
#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct ModelsimConfiguration {
pub linter: ModelsimLinter,
pub format: ModelsimFormat,
pub language_id: String,
pub linter: ModelsimLinter
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct ModelsimLinter {
pub name: String,
/// 目前是否启动
@ -18,19 +22,12 @@ pub struct ModelsimLinter {
pub args: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct ModelsimFormat {
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl Default for ModelsimLinter {
fn default() -> Self {
Self {
name: "modelsim".to_string(),
enabled: true,
enabled: false,
path: "Modelsim-verilog-syntax".to_string(),
args: Vec::new(),
}
@ -38,16 +35,61 @@ impl Default for ModelsimLinter {
}
impl Default for ModelsimFormat {
fn default() -> Self {
Self {
enabled: true,
path: "Modelsim-verilog-format".to_string(),
args: Vec::new(),
impl AbstractLinterConfiguration for ModelsimConfiguration {
fn new(language_id: &str) -> Self {
ModelsimConfiguration {
language_id: language_id.to_string(),
linter: ModelsimLinter::default()
}
}
}
fn provide_diagnostics(&self) {
pub fn provide_diagnostics() {
}
fn get_windows_extension(&self) -> &str {
"exe"
}
fn get_linter_path(&self) -> &str {
&self.linter.path
}
fn linter_status(&self) -> LinterStatus {
match self.language_id.as_str() {
"verilog" => {
let invoke_name = self.get_invoke_name("vlog");
let available = is_command_valid(&invoke_name);
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

@ -4,16 +4,20 @@ use ropey::Rope;
use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*;
use crate::utils::command::is_command_valid;
use super::{AbstractLinterConfiguration, LinterStatus};
#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VeribleConfiguration {
pub language_id: String,
pub linter: VeribleLinter,
pub format: VeribleFormat,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VeribleLinter {
pub name: String,
/// 目前是否启动
@ -23,7 +27,6 @@ pub struct VeribleLinter {
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VeribleFormat {
pub enabled: bool,
pub path: String,
@ -34,7 +37,7 @@ impl Default for VeribleLinter {
fn default() -> Self {
Self {
name: "verible".to_string(),
enabled: true,
enabled: false,
path: "verible-verilog-syntax".to_string(),
args: Vec::new(),
}
@ -45,16 +48,14 @@ impl Default for VeribleLinter {
impl Default for VeribleFormat {
fn default() -> Self {
Self {
enabled: true,
enabled: false,
path: "verible-verilog-format".to_string(),
args: Vec::new(),
}
}
}
pub fn provide_diagnostics() {
}
/// syntax checking using verible-verilog-syntax
@ -112,3 +113,55 @@ fn verible_syntax(
None
}
}
impl AbstractLinterConfiguration for VeribleConfiguration {
fn new(language_id: &str) -> Self {
VeribleConfiguration {
language_id: language_id.to_string(),
linter: VeribleLinter::default(),
format: VeribleFormat::default()
}
}
fn provide_diagnostics(&self) {
}
fn get_linter_path(&self) -> &str {
&self.linter.path
}
fn get_windows_extension(&self) -> &str {
"exe"
}
fn linter_status(&self) -> LinterStatus {
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

@ -5,14 +5,17 @@ use std::path::PathBuf;
use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*;
use crate::utils::command::is_command_valid;
use super::{AbstractLinterConfiguration, LinterStatus};
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct VerilatorConfiguration {
pub language_id: String,
pub linter: VerilatorLinter,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VerilatorLinter {
pub name: String,
/// 目前是否启动
@ -25,7 +28,7 @@ impl Default for VerilatorLinter {
fn default() -> Self {
Self {
name: "verilator".to_string(),
enabled: true,
enabled: false,
path: "verilator".to_string(),
args: vec![
"--lint-only".to_string(),
@ -36,10 +39,6 @@ impl Default for VerilatorLinter {
}
}
pub fn provide_diagnostics() {
}
/// convert captured severity string to DiagnosticSeverity
fn verilator_severity(severity: &str) -> Option<DiagnosticSeverity> {
@ -123,3 +122,54 @@ fn verilator_syntax(
None
}
}
impl AbstractLinterConfiguration for VerilatorConfiguration {
fn new(language_id: &str) -> Self {
VerilatorConfiguration {
language_id: language_id.to_string(),
linter: VerilatorLinter::default()
}
}
fn provide_diagnostics(&self) {
}
fn get_linter_path(&self) -> &str {
&self.linter.path
}
fn get_windows_extension(&self) -> &str {
"exe"
}
fn linter_status(&self) -> LinterStatus {
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

@ -1,13 +1,16 @@
use serde::{Deserialize, Serialize};
use crate::utils::command::is_command_valid;
use super::{AbstractLinterConfiguration, LinterStatus};
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct VivadoConfiguration {
pub language_id: String,
pub linter: VivadoLinter,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VivadoLinter {
pub name: String,
/// 目前是否启动
@ -20,7 +23,7 @@ impl Default for VivadoLinter {
fn default() -> Self {
Self {
name: "vivado".to_string(),
enabled: true,
enabled: false,
path: "Vivado".to_string(),
args: vec![
"--lint-only".to_string(),
@ -31,6 +34,61 @@ impl Default for VivadoLinter {
}
}
pub fn provide_diagnostics() {
impl AbstractLinterConfiguration for VivadoConfiguration {
fn new(language_id: &str) -> Self {
VivadoConfiguration {
language_id: language_id.to_string(),
linter: VivadoLinter::default()
}
}
fn provide_diagnostics(&self) {
}
fn get_linter_path(&self) -> &str {
&self.linter.path
}
fn get_windows_extension(&self) -> &str {
"exe"
}
fn linter_status(&self) -> LinterStatus {
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

@ -6,7 +6,8 @@ use request::{
fast::DoFastApi,
fast::SyncFastApi,
config::UpdateConfigurationApi,
primitives::DoPrimitivesJudgeApi
primitives::DoPrimitivesJudgeApi,
linter::LinterStatusApi
};
use log::info;
@ -57,6 +58,7 @@ async fn main() {
.custom_method("api/do-primitives-judge", DoPrimitivesJudgeApi)
.custom_method("api/update-configuration", UpdateConfigurationApi)
.custom_method("api/sync-fast", SyncFastApi)
.custom_method("api/linter-status", LinterStatusApi)
.finish();
Server::new(stdin, stdout, socket)

View File

@ -14,7 +14,6 @@ pub struct UpdateConfigurationApi;
#[serde(rename_all = "camelCase")]
pub struct UpdateConfigurationParams {
configs: Vec<UpdateConfigurationItem>,
#[serde(rename = "configType")]
config_type: String
}

94
src/request/linter.rs Normal file
View File

@ -0,0 +1,94 @@
use std::{borrow::Cow, sync::Arc};
use std::future;
use log::info;
use serde::{Deserialize, Serialize};
use tower_lsp::jsonrpc::Result;
use crate::diagnostics::{AbstractLinterConfiguration, LinterStatus};
use crate::server::Backend;
#[derive(Clone)]
pub struct LinterStatusApi;
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct LinterStatusParams {
language_id: String,
linter_name: String,
linter_path: String
}
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (LinterStatusParams, ), Result<LinterStatus>> for LinterStatusApi {
type Future = future::Ready<Result<LinterStatus>>;
fn invoke(&self, _server: &'a Arc<Backend>, _params: (LinterStatusParams, )) -> Self::Future {
let request_param = _params.0;
let language_id = request_param.language_id;
let linter_name = request_param.linter_name;
let linter_path = request_param.linter_path;
let linter_status = get_linter_status(
&_server,
&language_id,
&linter_name,
&linter_path
);
future::ready(linter_status)
}
}
fn get_linter_status(
backend: &Arc<Backend>,
language_id: &str,
linter_name: &str,
#[allow(unused)]
linter_path: &str
) -> Result<LinterStatus> {
let configuration = backend.server.configuration.read().unwrap();
// 获取对应语言的配置项目
let configuration = match language_id {
"verilog" => &configuration.vlog_linter_configuration,
"systemverilog" => &configuration.svlog_linter_configuration,
"vhdl" => &configuration.vhdl_linter_configuration,
_ => {
return Err(tower_lsp::jsonrpc::Error {
code: tower_lsp::jsonrpc::ErrorCode::InvalidRequest,
message: Cow::Owned(format!("无效的语言 ID {}", language_id)),
data: None
});
}
};
// 再根据 linter name 进行分类讨论
match linter_name {
"iverilog" => {
Ok(configuration.iverilog.linter_status())
}
"vivado" => {
Ok(configuration.vivado.linter_status())
}
"modelsim" => {
Ok(configuration.modelsim.linter_status())
}
"verible" => {
Ok(configuration.verible.linter_status())
}
"verilator" => {
Ok(configuration.verilator.linter_status())
}
_ => {
return Err(tower_lsp::jsonrpc::Error {
code: tower_lsp::jsonrpc::ErrorCode::InvalidRequest,
message: Cow::Owned(format!("无效的语言 ID {}", language_id)),
data: None
});
}
}
}

View File

@ -2,4 +2,5 @@ pub mod notification;
pub mod config;
pub mod primitives;
pub mod fast;
pub mod test;
pub mod test;
pub mod linter;

View File

@ -26,7 +26,7 @@ pub struct LspServer {
pub vhdl_keyword_completiom_items: Vec<CompletionItem>,
/// 相关的配置项目
pub configuration: Arc<RwLock<LspConfiguration>>,
#[allow(unused)]
pub log_handle: Mutex<Option<LoggerHandle>>,
}
@ -118,9 +118,9 @@ impl Default for LspConfiguration {
include_dirs: Vec::new(),
source_dirs: Vec::new(),
linter_mode: DigitalLinterMode::FULL,
vlog_linter_configuration: DigitalLinterConfiguration::default(),
vhdl_linter_configuration: DigitalLinterConfiguration::default(),
svlog_linter_configuration: DigitalLinterConfiguration::default(),
vlog_linter_configuration: DigitalLinterConfiguration::new("verilog"),
vhdl_linter_configuration: DigitalLinterConfiguration::new("vhdl"),
svlog_linter_configuration: DigitalLinterConfiguration::new("systemverilog"),
log_level: LogLevel::Info,
}
}

View File

@ -667,14 +667,12 @@ pub fn sv_parser_pipeline(
return;
}
info!("debug, parse: {:?}", path);
let syntax_tree = recovery_sv_parse_with_retry(
doc,
uri,
last_change_range,
include_dirs
);
info!("finish parse {:?}", path);
// 更新 scope tree
let mut scope_tree = match &syntax_tree {

9
src/utils/command.rs Normal file
View File

@ -0,0 +1,9 @@
use std::process::Command;
pub fn is_command_valid(command: &str) -> bool {
// 尝试执行命令
match Command::new(command).output() {
Ok(output) => output.status.success(),
Err(_) => false,
}
}

View File

@ -6,6 +6,7 @@ use ropey::RopeSlice;
use tower_lsp::lsp_types::*;
use vhdl_lang::{ast::ObjectClass, AnyEntKind, Concurrent, Object, Overloaded, SrcPos, Type};
pub mod command;
pub mod fast;
pub mod file;
pub use file::*;