370 lines
13 KiB
Rust
370 lines
13 KiB
Rust
use std::borrow::Cow;
|
||
use std::str::FromStr;
|
||
use std::{fs, future};
|
||
use std::path::{Path, PathBuf};
|
||
use std::sync::Arc;
|
||
use log::info;
|
||
use ropey::Rope;
|
||
use serde::{Deserialize, Serialize};
|
||
use tower_lsp::jsonrpc::Result;
|
||
use tower_lsp::lsp_types::*;
|
||
use vhdl_lang::Project;
|
||
|
||
use crate::core::cache_storage::CacheResult;
|
||
use crate::core::hdlparam::FastHdlparam;
|
||
use crate::core::sv_parser::make_fast_from_syntaxtree;
|
||
|
||
use crate::core::vhdl_parser::make_fast_from_units;
|
||
use crate::definition::VhdlProject;
|
||
use crate::{core, utils::*};
|
||
use crate::server::Backend;
|
||
use crate::sources::recovery_sv_parse_with_retry;
|
||
|
||
|
||
#[derive(Deserialize, Serialize, Debug)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct DoFastApiRequestParams {
|
||
path: String,
|
||
file_type: String,
|
||
tool_chain: String
|
||
}
|
||
|
||
|
||
#[derive(Clone)]
|
||
pub struct DoFastApi;
|
||
|
||
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (DoFastApiRequestParams, ), Result<FastHdlparam>> for DoFastApi {
|
||
type Future = future::Ready<Result<FastHdlparam>>;
|
||
fn invoke(&self, _server: &'a Arc<Backend>, _params: (DoFastApiRequestParams, )) -> Self::Future {
|
||
let request_param = _params.0;
|
||
let path = request_param.path;
|
||
let file_type = request_param.file_type;
|
||
let tool_chain = request_param.tool_chain;
|
||
let hdlparam = do_fast(path, file_type, tool_chain, _server);
|
||
future::ready(hdlparam)
|
||
}
|
||
}
|
||
|
||
|
||
#[derive(Deserialize, Serialize, Debug)]
|
||
#[serde(rename_all = "camelCase")]
|
||
pub struct SyncFastApiRequestParams {
|
||
path: String,
|
||
file_type: String,
|
||
tool_chain: String
|
||
}
|
||
|
||
#[derive(Clone)]
|
||
pub struct SyncFastApi;
|
||
|
||
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (SyncFastApiRequestParams, ), Result<FastHdlparam>> for SyncFastApi {
|
||
type Future = future::Ready<Result<FastHdlparam>>;
|
||
fn invoke(&self, _server: &'a Arc<Backend>, _params: (SyncFastApiRequestParams, )) -> Self::Future {
|
||
let request_param = _params.0;
|
||
let path = request_param.path;
|
||
let file_type = request_param.file_type;
|
||
let tool_chain = request_param.tool_chain;
|
||
let hdlparam = sync_fast(path, file_type, tool_chain, _server);
|
||
future::ready(hdlparam)
|
||
}
|
||
}
|
||
|
||
fn make_textdocumenitem_from_path(path_buf: &PathBuf) -> Option<TextDocumentItem> {
|
||
if let Ok(url) = Url::from_file_path(path_buf) {
|
||
if let Ok(text) = fs::read_to_string(path_buf) {
|
||
let language_id = get_language_id_by_uri(&url);
|
||
return Some(TextDocumentItem::new(url, language_id, -1, text));
|
||
}
|
||
}
|
||
None
|
||
}
|
||
|
||
/// 前端交互接口: do_fast,输入文件路径,计算出对应的 fast 结构
|
||
pub fn do_fast(
|
||
path: String,
|
||
file_type: String,
|
||
tool_chain: String,
|
||
backend: &Arc<Backend>
|
||
) -> Result<FastHdlparam> {
|
||
info!("parse fast {:?}, type: {:?}, toolchain: {:?}", path, file_type, tool_chain);
|
||
|
||
// 根据 file_type 和 tool_chain 计算正确的 path
|
||
let path = {
|
||
if file_type == "ip" && tool_chain == "xilinx" {
|
||
let pathbuf = PathBuf::from_str(&path).unwrap();
|
||
let basename = pathbuf.file_name().unwrap().to_str().unwrap();
|
||
format!("{}/synth/{}.vhd", path, basename)
|
||
} else {
|
||
path
|
||
}
|
||
};
|
||
|
||
let language_id = get_language_id_by_path_str(&path);
|
||
|
||
let parse_fast_by_language_id = |language_id: &str| {
|
||
match language_id {
|
||
"vhdl" => {
|
||
do_vhdl_fast(
|
||
&path,
|
||
&file_type,
|
||
&tool_chain,
|
||
backend
|
||
)
|
||
}
|
||
|
||
"verilog" | "systemverilog" => {
|
||
do_sv_fast(
|
||
&path,
|
||
&file_type,
|
||
&tool_chain,
|
||
backend
|
||
)
|
||
}
|
||
|
||
_ => Err(tower_lsp::jsonrpc::Error {
|
||
code: tower_lsp::jsonrpc::ErrorCode::InvalidRequest,
|
||
message: Cow::Owned(format!("invalid file: {path}, expect vhdl, verilog or system verilog!")),
|
||
data: None
|
||
})
|
||
}
|
||
};
|
||
|
||
let path_buf = PathBuf::from_str(&path).unwrap();
|
||
// 做缓存优化
|
||
let cache = &backend.server.cache;
|
||
match cache.try_get_fast_cache(&path_buf) {
|
||
// 找到缓存,直接返回
|
||
CacheResult::Ok(fast) => {
|
||
return Ok(fast);
|
||
}
|
||
|
||
// cache 没找到,那么就需要计算并存入
|
||
CacheResult::CacheNotFound => {
|
||
match parse_fast_by_language_id(&language_id) {
|
||
Ok(fast) => {
|
||
cache.update_cache(&path_buf, fast.clone());
|
||
return Ok(fast);
|
||
},
|
||
Err(err) => Err(err)
|
||
}
|
||
}
|
||
|
||
// 不需要缓存的文件正常进行 fast 计算即可
|
||
_ => {
|
||
parse_fast_by_language_id(&language_id)
|
||
}
|
||
}
|
||
}
|
||
|
||
fn do_sv_fast(
|
||
path: &str,
|
||
#[allow(unused)]
|
||
file_type: &str,
|
||
#[allow(unused)]
|
||
tool_chain: &str,
|
||
backend: &Arc<Backend>
|
||
) -> Result<FastHdlparam> {
|
||
let path_buf = PathBuf::from(&path);
|
||
|
||
// 从 pathbuf 中读取文本并作为 TextDocumentItem 打开
|
||
let doc = match make_textdocumenitem_from_path(&path_buf) {
|
||
Some(doc) => doc,
|
||
None => {
|
||
let api_error = tower_lsp::jsonrpc::Error {
|
||
code: tower_lsp::jsonrpc::ErrorCode::InvalidParams,
|
||
message: Cow::Owned(format!("cannot make doc from path : {path}")),
|
||
data: None
|
||
};
|
||
|
||
return Err(api_error);
|
||
}
|
||
};
|
||
|
||
let uri = doc.uri;
|
||
let text = Rope::from(doc.text);
|
||
// fast 解析不需要 include
|
||
let includes: Vec<PathBuf> = Vec::new();
|
||
|
||
let parse_result = recovery_sv_parse_with_retry(
|
||
&text,
|
||
&uri,
|
||
&None,
|
||
&includes
|
||
);
|
||
|
||
let sources = &backend.server.srcs;
|
||
if let Some(syntax_tree) = parse_result {
|
||
if let Ok(mut fast) = make_fast_from_syntaxtree(&syntax_tree, &path_buf) {
|
||
fast.file_type = file_type.to_string();
|
||
let hdl_param = sources.hdl_param.clone();
|
||
hdl_param.update_fast(path.to_string(), fast.clone());
|
||
return Ok(fast);
|
||
}
|
||
}
|
||
|
||
Err(tower_lsp::jsonrpc::Error {
|
||
code: tower_lsp::jsonrpc::ErrorCode::ParseError,
|
||
message: Cow::Owned(format!("error happen when parse {path} [do_sv_fast]")),
|
||
data: None
|
||
})
|
||
}
|
||
|
||
fn do_vhdl_fast(
|
||
path: &str,
|
||
#[allow(unused)]
|
||
file_type: &str,
|
||
#[allow(unused)]
|
||
tool_chain: &str,
|
||
backend: &Arc<Backend>
|
||
) -> Result<FastHdlparam> {
|
||
let sources = &backend.server.srcs;
|
||
let pathbuf = PathBuf::from_str(path).unwrap();
|
||
let hdl_param = sources.hdl_param.clone();
|
||
let vhdl_project = sources.vhdl_project.clone();
|
||
|
||
// TODO: 支持对于 synth 下的 vhdl 文件的解析,从而提供更加丰富的 IP 支持
|
||
if file_type == "ip" {
|
||
match tool_chain {
|
||
// 此时的 pathbuf 类似 {ip_name}/synth/{ip_name}.vhd
|
||
"xilinx" => {
|
||
// 如果 ip 描述文件存在,则解析它,否则,创建空的写入
|
||
// IP 描述文件一般都不会很大,所以不需要缓存
|
||
if !pathbuf.exists() {
|
||
let ip_name = pathbuf.file_name().unwrap().to_str().unwrap();
|
||
let ip_name = ip_name.strip_suffix(".vhd").unwrap();
|
||
let fake_content = vec![
|
||
core::hdlparam::Module {
|
||
name: ip_name.to_string(),
|
||
arch_name: "".to_string(),
|
||
params: vec![],
|
||
ports: vec![],
|
||
instances: vec![],
|
||
range: core::hdlparam::Range::default()
|
||
}
|
||
];
|
||
let ip_fast = core::hdlparam::FastHdlparam {
|
||
fast_macro: core::hdlparam::Macro {
|
||
includes: vec![],
|
||
defines: vec![],
|
||
errors: vec![],
|
||
invalid: vec![]
|
||
},
|
||
file_type: "ip".to_string(),
|
||
content: fake_content
|
||
};
|
||
hdl_param.update_fast(path.to_string(), ip_fast.clone());
|
||
return Ok(ip_fast);
|
||
} else if let Some(vhdl_project) = &mut *vhdl_project.write().unwrap() {
|
||
vhdl_project.config_file_strs.push(format!("{:?}", pathbuf));
|
||
let mut messages = Vec::new();
|
||
let config_str = format!(
|
||
r#"
|
||
[libraries]
|
||
digital_lsp.files = [{}]
|
||
"#, vhdl_project.config_file_strs.join(",")
|
||
);
|
||
let config = vhdl_lang::Config::from_str(&config_str, Path::new(""));
|
||
if let Ok(mut config) = config {
|
||
config.append(&vhdl_project.std_config, &mut messages);
|
||
let mut project = Project::from_config(config, &mut messages);
|
||
project.analyse();
|
||
*vhdl_project = VhdlProject { project, std_config: vhdl_project.std_config.clone(), config_file_strs: vhdl_project.config_file_strs.clone() };
|
||
}
|
||
|
||
let arch_and_entity = vhdl_project.project.get_analyzed_units(&pathbuf);
|
||
|
||
if let Some(mut fast) = make_fast_from_units(arch_and_entity) {
|
||
fast.file_type = file_type.to_string();
|
||
// IP 不需要内部的 instance,其实现是加密的,只需要暴露 module 的接口即可
|
||
// 所以此处需要清空所有的 module 中的 instance
|
||
for module in &mut fast.content {
|
||
module.instances.clear();
|
||
}
|
||
hdl_param.update_fast(path.to_string(), fast.clone());
|
||
return Ok(fast);
|
||
}
|
||
} else {
|
||
return Err(tower_lsp::jsonrpc::Error {
|
||
code: tower_lsp::jsonrpc::ErrorCode::ParseError,
|
||
message: Cow::Owned(format!("error happen when parse {path} in [do_vhdl_fast]")),
|
||
data: None
|
||
});
|
||
}
|
||
},
|
||
_ => {}
|
||
}
|
||
}
|
||
|
||
// 没有特殊情况,则正常解析并写入
|
||
if let Some(vhdl_project) = &mut *vhdl_project.write().unwrap() {
|
||
vhdl_project.config_file_strs.push(format!("{:?}", pathbuf));
|
||
let mut messages = Vec::new();
|
||
let config_str = format!(
|
||
r#"
|
||
[libraries]
|
||
digital_lsp.files = [{}]
|
||
"#, vhdl_project.config_file_strs.join(",")
|
||
);
|
||
let config = vhdl_lang::Config::from_str(&config_str, Path::new(""));
|
||
if let Ok(mut config) = config {
|
||
config.append(&vhdl_project.std_config, &mut messages);
|
||
let mut project = Project::from_config(config, &mut messages);
|
||
project.analyse();
|
||
*vhdl_project = VhdlProject { project, std_config: vhdl_project.std_config.clone(), config_file_strs: vhdl_project.config_file_strs.clone() };
|
||
}
|
||
|
||
let arch_and_entity = vhdl_project.project.get_analyzed_units(&pathbuf);
|
||
if let Some(mut fast) = make_fast_from_units(arch_and_entity) {
|
||
fast.file_type = file_type.to_string();
|
||
// for module in &fast.content {
|
||
// if module.name == "None" {
|
||
// info!("debug, module : {:?}, path: {:?}", module, pathbuf);
|
||
// }
|
||
// }
|
||
hdl_param.update_fast(path.to_string(), fast.clone());
|
||
return Ok(fast);
|
||
}
|
||
}
|
||
|
||
Err(tower_lsp::jsonrpc::Error {
|
||
code: tower_lsp::jsonrpc::ErrorCode::ParseError,
|
||
message: Cow::Owned(format!("error happen when parse {path} in [do_vhdl_fast]")),
|
||
data: None
|
||
})
|
||
}
|
||
|
||
|
||
pub fn sync_fast(
|
||
path: String,
|
||
file_type: String,
|
||
tool_chain: String,
|
||
backend: &Arc<Backend>
|
||
) -> Result<FastHdlparam> {
|
||
// 根据 file_type 和 tool_chain 计算正确的 path
|
||
let path = {
|
||
if file_type == "ip" && tool_chain == "xilinx" {
|
||
let pathbuf = PathBuf::from_str(&path).unwrap();
|
||
let basename = pathbuf.file_name().unwrap().to_str().unwrap();
|
||
format!("{}/synth/{}.vhd", path, basename)
|
||
} else {
|
||
path
|
||
}
|
||
};
|
||
|
||
{
|
||
let uri = Url::from_file_path(path.to_string()).unwrap();
|
||
let file_id = backend.server.srcs.get_id(&uri);
|
||
if let Some(file) = backend.server.srcs.get_file(file_id) {
|
||
let _unused = file.read().unwrap();
|
||
}
|
||
}
|
||
|
||
let hdl_param = backend.server.srcs.hdl_param.clone();
|
||
let path_to_hdl_file = hdl_param.path_to_hdl_file.read().unwrap();
|
||
if let Some(hdl_file) = path_to_hdl_file.get(&path) {
|
||
let fast = hdl_file.fast.clone();
|
||
Ok(fast)
|
||
} else {
|
||
do_fast(path, file_type, tool_chain, backend)
|
||
}
|
||
} |