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, (DoFastApiRequestParams, ), Result> for DoFastApi { type Future = future::Ready>; fn invoke(&self, _server: &'a Arc, _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, (SyncFastApiRequestParams, ), Result> for SyncFastApi { type Future = future::Ready>; fn invoke(&self, _server: &'a Arc, _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 { 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 ) -> Result { 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 ) -> Result { 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 = 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 ) -> Result { 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 ) -> Result { // 根据 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) } }