1016 lines
40 KiB
Rust
1016 lines
40 KiB
Rust
use crate::core::hdlparam::HdlParam;
|
||
use crate::core::primitive_parser::PrimitiveText;
|
||
use crate::core::sv_parser::make_fast_from_syntaxtree;
|
||
use crate::core::vhdl_parser::make_fast_from_units;
|
||
use crate::core::vhdl_parser::vhdl_parse_str;
|
||
use crate::core::scope_tree::common::*;
|
||
use crate::core::scope_tree::get_scopes_from_syntax_tree;
|
||
use crate::core::scope_tree::get_scopes_from_vhdl_fast;
|
||
use crate::diagnostics::provide_diagnostics;
|
||
use crate::server::LspServer;
|
||
use crate::server::LspConfiguration;
|
||
use crate::utils::from_uri_to_escape_path_string;
|
||
use crate::utils::to_escape_path;
|
||
#[allow(unused)]
|
||
use log::{info, debug, error};
|
||
use pathdiff::diff_paths;
|
||
use ropey::{Rope, RopeSlice};
|
||
use serde_json::Value;
|
||
use vhdl_lang::ast::DesignFile;
|
||
use std::cmp::min;
|
||
use std::collections::HashMap;
|
||
use std::env::current_dir;
|
||
#[allow(unused)]
|
||
use std::ops::Deref;
|
||
use std::ops::Range as StdRange;
|
||
use std::path::Path;
|
||
use std::path::PathBuf;
|
||
use std::str::FromStr;
|
||
use std::sync::Arc;
|
||
use std::sync::{Condvar, Mutex, RwLock};
|
||
use std::thread;
|
||
use sv_parser::*;
|
||
use vhdl_lang::Project;
|
||
use thread::JoinHandle;
|
||
use tower_lsp::lsp_types::*;
|
||
|
||
impl LspServer {
|
||
pub fn did_open(&self, params: DidOpenTextDocumentParams) -> PublishDiagnosticsParams {
|
||
let document: TextDocumentItem = params.text_document;
|
||
let uri = document.uri.clone();
|
||
let path_string = from_uri_to_escape_path_string(&uri).unwrap();
|
||
|
||
if self.srcs.contain_source(&path_string) {
|
||
// 如果已经存在了,则进行增量更新
|
||
self.did_change(DidChangeTextDocumentParams {
|
||
text_document: VersionedTextDocumentIdentifier::new(document.uri, document.version),
|
||
content_changes: vec![TextDocumentContentChangeEvent {
|
||
range: None,
|
||
range_length: None,
|
||
text: document.text,
|
||
}],
|
||
});
|
||
} else {
|
||
// 如果不存在,直接加入其中
|
||
self.srcs.add(self, document);
|
||
}
|
||
|
||
|
||
// 生成诊断信息
|
||
if let Some(source) = self.srcs.get_source(&path_string) {
|
||
let source = source.read().unwrap();
|
||
let diagnostics = provide_diagnostics(
|
||
uri,
|
||
&source.text,
|
||
&self.configuration.read().unwrap(),
|
||
&self
|
||
);
|
||
diagnostics
|
||
} else {
|
||
PublishDiagnosticsParams {
|
||
uri,
|
||
diagnostics: Vec::new(),
|
||
version: None,
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn did_change(&self, params: DidChangeTextDocumentParams) {
|
||
let path_string = from_uri_to_escape_path_string(¶ms.text_document.uri).unwrap();
|
||
|
||
if let Some(source) = self.srcs.get_source(&path_string) {
|
||
let mut source = source.write().unwrap();
|
||
|
||
// 根据输入的 change 动态更新对应的代码的文本片段
|
||
for change in params.content_changes {
|
||
if change.range.is_none() {
|
||
source.text = Rope::from_str(&change.text);
|
||
} else {
|
||
source.text.apply_change(&change);
|
||
}
|
||
source.last_change_range = change.range;
|
||
}
|
||
source.version = params.text_document.version;
|
||
drop(source);
|
||
|
||
// 唤醒解析线程
|
||
let source_status = self.srcs.get_source_status(&path_string).unwrap();
|
||
let (lock, cvar) = &*source_status.read().unwrap().valid_parse;
|
||
let mut valid = lock.lock().unwrap();
|
||
*valid = false;
|
||
cvar.notify_all();
|
||
}
|
||
}
|
||
|
||
/// 保存时触发的行为
|
||
/// - 提供诊断信息(主要是第三方诊断器,因为 save 后磁盘上的内容就和缓冲区中的一致了)
|
||
pub fn did_save(&self, params: DidSaveTextDocumentParams) -> PublishDiagnosticsParams {
|
||
let uri = params.text_document.uri;
|
||
let path_string = from_uri_to_escape_path_string(&uri).unwrap();
|
||
|
||
if let Some(source) = self.srcs.get_source(&path_string) {
|
||
let source = source.read().unwrap();
|
||
provide_diagnostics(
|
||
uri,
|
||
&source.text,
|
||
&self.configuration.read().unwrap(),
|
||
&self
|
||
)
|
||
} else {
|
||
PublishDiagnosticsParams {
|
||
uri,
|
||
diagnostics: Vec::new(),
|
||
version: None,
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn did_delete_files(&self, params: DeleteFilesParams) {
|
||
// 删除 sources 内对应的文件
|
||
for file_delete in params.files {
|
||
let uri = Url::parse(&file_delete.uri).unwrap();
|
||
let path_string = from_uri_to_escape_path_string(&uri).unwrap();
|
||
|
||
// 同步
|
||
{
|
||
let mut fast_sync_controller = self.srcs.fast_sync_controller.write().unwrap();
|
||
fast_sync_controller.remove(&path_string);
|
||
}
|
||
|
||
// hdlparam
|
||
{
|
||
self.srcs.hdl_param.delete_file(&path_string);
|
||
}
|
||
|
||
// 文本缓冲器
|
||
{
|
||
let mut sources = self.srcs.sources.write().unwrap();
|
||
sources.remove(&path_string);
|
||
}
|
||
|
||
// scope tree
|
||
{
|
||
let mut global_scope = self.srcs.scope_tree.write().unwrap();
|
||
match &mut *global_scope {
|
||
Some(scope) => {
|
||
scope.defs.retain(|x| x.url() != uri);
|
||
scope.scopes.retain(|x| x.url() != uri);
|
||
},
|
||
None => {},
|
||
}
|
||
}
|
||
|
||
// vhdl
|
||
{
|
||
let mut vhdl_project = self.srcs.vhdl_project.write().unwrap();
|
||
match &mut *vhdl_project {
|
||
Some(vhdl_project) => {
|
||
let config_file_strs = vhdl_project.config_file_strs.clone()
|
||
.into_iter()
|
||
.filter(|x| x != &path_string)
|
||
.collect::<Vec<String>>();
|
||
let mut messages = Vec::new();
|
||
let config_str = format!(
|
||
r#"
|
||
[libraries]
|
||
digital_lsp.files = [{}]
|
||
"#, 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 };
|
||
}
|
||
}
|
||
None => ()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// The Source struct holds all file specific information
|
||
pub struct Source {
|
||
/// 文件的 uri
|
||
pub uri: Url,
|
||
/// 代码文本信息
|
||
pub text: Rope,
|
||
/// 版本号
|
||
pub version: i32,
|
||
/// 是否已经完成至少一次解析,用于在初始化相关的函数中使用
|
||
pub finish_at_least_once: bool,
|
||
/// 用于在解析失败时恢复解析的量
|
||
pub last_change_range: Option<Range>,
|
||
}
|
||
|
||
/// file metadata, including whether or not the syntax tree is up to date
|
||
pub struct SourceStatus {
|
||
/// 当前解析的文件的路径
|
||
pub path: String,
|
||
/// 用于进行控制的锁
|
||
pub valid_parse: Arc<(Mutex<bool>, Condvar)>,
|
||
/// 解析当前文件的线程句柄
|
||
#[allow(unused)]
|
||
pub parse_handle: JoinHandle<()>,
|
||
}
|
||
|
||
pub enum AstLike {
|
||
Svlog(SyntaxTree),
|
||
Vhdl(DesignFile)
|
||
}
|
||
|
||
/// The Sources struct manages all source files
|
||
pub struct Sources {
|
||
// 用于存储后端中的前端的文本缓冲区的备份
|
||
pub sources: Arc<RwLock<HashMap<String, Arc<RwLock<Source>>>>>,
|
||
// 存储类似于线程句柄等数据的结构
|
||
pub sources_status: Arc<RwLock<HashMap<String, Arc<RwLock<SourceStatus>>>>>,
|
||
/// scope tree 类型的树形结构,用于提供 sv 的 lsp
|
||
pub scope_tree: Arc<RwLock<Option<GenericScope>>>,
|
||
// vhdl project, store vhdl design files, do lsp
|
||
pub vhdl_project: Arc<RwLock<Option<VhdlProject>>>,
|
||
// include directories, passed to parser to resolve `include
|
||
pub include_dirs: Arc<RwLock<Vec<PathBuf>>>,
|
||
// primitive instance text
|
||
pub primitive_text: Arc<PrimitiveText>,
|
||
/// hdlparam 后端实现
|
||
pub hdl_param: Arc<HdlParam>,
|
||
// 同步解析线程和发送 fast 请求的
|
||
pub fast_sync_controller: Arc<RwLock<HashMap<String, Arc<RwLock<bool>>>>>,
|
||
// lsp 配置相关的
|
||
pub lsp_configuration: Arc<RwLock<HashMap<String, Value>>>
|
||
}
|
||
|
||
impl std::default::Default for Sources {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
impl Sources {
|
||
pub fn new() -> Self {
|
||
Self {
|
||
sources: Arc::new(RwLock::new(HashMap::new())),
|
||
sources_status: Arc::new(RwLock::new(HashMap::new())),
|
||
scope_tree: Arc::new(RwLock::new(None)),
|
||
vhdl_project: Arc::new(RwLock::new(None)),
|
||
include_dirs: Arc::new(RwLock::new(Vec::new())),
|
||
primitive_text: Arc::new(PrimitiveText::new()),
|
||
hdl_param: Arc::new(HdlParam::new()),
|
||
fast_sync_controller: Arc::new(RwLock::new(HashMap::new())),
|
||
lsp_configuration: Arc::new(RwLock::new(HashMap::new()))
|
||
}
|
||
}
|
||
|
||
pub fn init_primitive(&self, tool_chain: &str, extension_path: &str) {
|
||
let primitive_bin = format!("{}/resources/dide-lsp/static/{}/primitive.bin", extension_path, tool_chain);
|
||
info!("attempt to find primitives in {}", primitive_bin);
|
||
|
||
if let Some(primitive_xml) = crate::core::primitive_parser::load_primitive_bin(&primitive_bin) {
|
||
let primitive_text_handle = self.primitive_text.clone();
|
||
let hdl_param_handle = self.hdl_param.clone();
|
||
for (name, template) in primitive_xml.name_to_template {
|
||
// use "primitive/name" as a fake path
|
||
let fake_path = "primitive/".to_string() + &name;
|
||
hdl_param_handle.update_hdl_file(
|
||
fake_path,
|
||
template.fast,
|
||
sv_parser::common::ParseResult::new(),
|
||
None
|
||
);
|
||
primitive_text_handle.update_text(&name, &template.text);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 判断当前文本缓冲器中是否存在指定路径的文件的备份
|
||
pub fn contain_source(&self, path_string: &str) -> bool {
|
||
let sources = self.sources.read().unwrap();
|
||
sources.contains_key(path_string)
|
||
}
|
||
|
||
pub fn init_vhdl_project(&self, extension_path: &str) {
|
||
let project_handle = self.vhdl_project.clone();
|
||
let mut global_project = project_handle.write().unwrap();
|
||
match &mut *global_project {
|
||
Some(_) => (),
|
||
None => {
|
||
let std_cfg_path = match PathBuf::from_str(&(extension_path.to_string() + "/resources/dide-lsp/static/vhdl_std_lib/vhdl_ls.toml")) {
|
||
Ok(path) => path,
|
||
Err(e) => {
|
||
info!("error happen in <vhdl_parse_pipeline>: {:?}", e);
|
||
return;
|
||
}
|
||
};
|
||
match vhdl_lang::Config::read_file_path(&std_cfg_path) {
|
||
Ok(std_config) => {
|
||
let mut messages = Vec::new();
|
||
let mut project = Project::from_config(std_config.clone(), &mut messages);
|
||
project.analyse();
|
||
let config_file_strs = Vec::new();
|
||
*global_project = Some(VhdlProject { project, std_config, config_file_strs });
|
||
}
|
||
Err(e) => info!("error happen in <init_vhdl_project>: Can't load standard vhdl lib from {std_cfg_path:#?} because {e:#?}")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 增加一个 hdl 文件,并为该文件添加单独的解析线程
|
||
pub fn add(&self, server: &LspServer, doc: TextDocumentItem) {
|
||
// 对于当前的文件增加一个解析线程,不断进行解析和同步
|
||
#[allow(clippy::mutex_atomic)] // https://github.com/rust-lang/rust-clippy/issues/1516
|
||
let valid_parse = Arc::new((Mutex::new(false), Condvar::new()));
|
||
let valid_parse2 = valid_parse.clone();
|
||
|
||
let mut sources = self.sources.write().unwrap();
|
||
|
||
// uri 转换为标准文件路径
|
||
let path_string = from_uri_to_escape_path_string(&doc.uri).unwrap();
|
||
|
||
// 为新加入的文件构建后端文本缓冲器副本
|
||
let source_handle = Arc::new(RwLock::new(Source {
|
||
uri: doc.uri.clone(),
|
||
text: Rope::from_str(&doc.text),
|
||
version: doc.version,
|
||
finish_at_least_once: false,
|
||
last_change_range: None,
|
||
}));
|
||
|
||
let parse_loop_used_source_handle = source_handle.clone();
|
||
let scope_handle = self.scope_tree.clone();
|
||
let project_handle = self.vhdl_project.clone();
|
||
let hdl_param_handle = self.hdl_param.clone();
|
||
let inc_dirs = self.include_dirs.clone();
|
||
|
||
let configuration = server.configuration.clone();
|
||
let uri = doc.uri.clone();
|
||
|
||
info!("launch worker to parse {:?}", doc.uri.to_string());
|
||
let language_id = doc.language_id.to_string();
|
||
|
||
// 新建一个 fast controller 用于控制下方的解析线程和外部的请求函数
|
||
let pathbuf = PathBuf::from_str(&doc.uri.path()).unwrap();
|
||
let pathbuf = to_escape_path(&pathbuf);
|
||
let path_string = pathbuf.to_str().unwrap();
|
||
|
||
// 用于进行 fast 的同步(和 request 那边的同步)
|
||
let mut fast_controller = self.fast_sync_controller.write().unwrap();
|
||
let fast_lock = Arc::new(RwLock::new(true));
|
||
fast_controller.insert(path_string.to_string(), fast_lock.clone());
|
||
drop(fast_controller);
|
||
|
||
// 创建一个解析线程
|
||
let parse_handle = thread::spawn(move || {
|
||
let (lock, cvar) = &*valid_parse2;
|
||
loop {
|
||
info!("do parse in {:?}, language_id: {:?}", uri.to_string(), language_id);
|
||
|
||
// 此时 update fast 的地方为 write 会进行阻塞
|
||
{
|
||
let _unused = fast_lock.read().unwrap();
|
||
// 从内存中直接获取增量更新系统维护的代码文本
|
||
let file = parse_loop_used_source_handle.read().unwrap();
|
||
let text = file.text.clone();
|
||
let uri = &file.uri.clone();
|
||
|
||
let range = &file.last_change_range.clone();
|
||
drop(file);
|
||
match language_id.as_str() {
|
||
"vhdl" => {
|
||
vhdl_parser_pipeline(
|
||
&configuration,
|
||
&parse_loop_used_source_handle,
|
||
&scope_handle,
|
||
&project_handle,
|
||
&hdl_param_handle,
|
||
&text,
|
||
uri,
|
||
);
|
||
},
|
||
"verilog" | "systemverilog" => {
|
||
sv_parser_pipeline(
|
||
&configuration,
|
||
&parse_loop_used_source_handle,
|
||
&scope_handle,
|
||
&hdl_param_handle,
|
||
&text,
|
||
uri,
|
||
range,
|
||
&inc_dirs.read().unwrap()
|
||
);
|
||
},
|
||
_ => {}
|
||
}
|
||
}
|
||
|
||
// 通过条件锁进行控制
|
||
let mut valid = lock.lock().unwrap();
|
||
*valid = true;
|
||
cvar.notify_all();
|
||
while *valid {
|
||
valid = cvar.wait(valid).unwrap();
|
||
}
|
||
}
|
||
});
|
||
|
||
// 将新的文件缓冲区数据结构加入 sources 和 status 中
|
||
sources.insert(path_string.to_string(), source_handle);
|
||
|
||
let mut source_status = self.sources_status.write().unwrap();
|
||
let status_handle = Arc::new(RwLock::new(SourceStatus {
|
||
path: path_string.to_string(),
|
||
valid_parse,
|
||
parse_handle,
|
||
}));
|
||
source_status.insert(path_string.to_string(), status_handle);
|
||
}
|
||
|
||
/// 输入路径,获得路径对应的文件的文本缓冲器对象 source
|
||
pub fn get_source(&self, path_string: &str) -> Option<Arc<RwLock<Source>>> {
|
||
let sources = self.sources.read().unwrap();
|
||
if let Some(source_handle) = sources.get(path_string) {
|
||
return Some(source_handle.clone());
|
||
}
|
||
|
||
None
|
||
}
|
||
|
||
/// 输入路径,获得路径对应的文件的文本缓冲器对象 source 的运行时状态
|
||
pub fn get_source_status(&self, path_string: &str) -> Option<Arc<RwLock<SourceStatus>>> {
|
||
let source_status = self.sources_status.read().unwrap();
|
||
if let Some(status_handle) = source_status.get(path_string) {
|
||
return Some(status_handle.clone());
|
||
}
|
||
|
||
None
|
||
}
|
||
|
||
/// 等待解析线程创建完成 ?
|
||
pub fn wait_parse_ready(&self, path_string: &str, wait_valid: bool) {
|
||
if let Some(source_handle) = self.get_source(path_string) {
|
||
let source = source_handle.read().unwrap();
|
||
if !source.finish_at_least_once || wait_valid {
|
||
drop(source);
|
||
let source_status_handle = self.get_source_status(path_string).unwrap(); // status 和 source 是同步创建的,要有一起有
|
||
let (lock, cvar) = &*source_status_handle.read().unwrap().valid_parse;
|
||
let mut valid = lock.lock().unwrap();
|
||
while !*valid {
|
||
valid = cvar.wait(valid).unwrap();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 根据输入 token,计算当前
|
||
pub fn get_completions(
|
||
&self,
|
||
token: &str,
|
||
byte_idx: usize,
|
||
url: &Url,
|
||
) -> Option<CompletionList> {
|
||
Some(CompletionList {
|
||
is_incomplete: false,
|
||
items: self
|
||
.scope_tree
|
||
.read()
|
||
.ok()?
|
||
.as_ref()?
|
||
.get_completion(token, byte_idx, url),
|
||
})
|
||
}
|
||
|
||
|
||
#[allow(unused)]
|
||
pub fn get_lsp_configuration_string_value(&self, name: &str) -> Option<String> {
|
||
let lsp_configuration = self.lsp_configuration.read().unwrap();
|
||
if let Some(Value::String(value)) = lsp_configuration.get(name) {
|
||
return Some(value.to_string());
|
||
}
|
||
None
|
||
}
|
||
|
||
#[allow(unused)]
|
||
pub fn get_lsp_configuration_i64_value(&self, name: &str) -> Option<i64> {
|
||
let lsp_configuration = self.lsp_configuration.read().unwrap();
|
||
if let Some(Value::Number(number)) = lsp_configuration.get(name) {
|
||
return number.as_i64();
|
||
}
|
||
None
|
||
}
|
||
|
||
#[allow(unused)]
|
||
pub fn get_lsp_configuration_bool_value(&self, name: &str) -> Option<bool> {
|
||
let lsp_configuration = self.lsp_configuration.read().unwrap();
|
||
if let Some(Value::Bool(value)) = lsp_configuration.get(name) {
|
||
return Some(*value);
|
||
}
|
||
None
|
||
}
|
||
|
||
/// 同时从 fast 和 primitives 里面找 module
|
||
/// 在内部判断一个 module 是否存在时会用到
|
||
/// - name: 模块的名字
|
||
pub fn contains_module(&self, name: &str) -> bool {
|
||
if let Some(_) = self.hdl_param.find_module_by_name(name) {
|
||
return true;
|
||
}
|
||
|
||
// 从 primitives 里面寻找
|
||
let primitive_text = self.primitive_text.clone();
|
||
let primitive_map = primitive_text.name_to_text.read().unwrap();
|
||
primitive_map.contains_key(name)
|
||
}
|
||
}
|
||
|
||
|
||
pub fn recovery_sv_parse_with_retry(
|
||
doc: &Rope,
|
||
uri: &Url,
|
||
last_change_range: &Option<Range>,
|
||
inc_paths: &[PathBuf],
|
||
) -> Option<(SyntaxTree, sv_parser::common::ParseResult)>{
|
||
if let Some((syntax_tree, parse_result)) = recovery_sv_parse(doc, uri, last_change_range, inc_paths, false) {
|
||
Some((syntax_tree, parse_result))
|
||
} else {
|
||
recovery_sv_parse(doc, uri, last_change_range, inc_paths, true)
|
||
}
|
||
}
|
||
|
||
fn replace_substring(rope: Rope, substring: &str) -> Rope {
|
||
let text = rope.to_string();
|
||
let replaced_text = text.replace(substring, &" ".repeat(substring.len()));
|
||
Rope::from(replaced_text)
|
||
}
|
||
|
||
/// 更加稳定地解析 sv 和 v
|
||
/// 支持遇到错误进行自动修复,然后再解析
|
||
pub fn recovery_sv_parse(
|
||
doc: &Rope,
|
||
uri: &Url,
|
||
last_change_range: &Option<Range>,
|
||
inc_paths: &[PathBuf],
|
||
allow_incomplete: bool,
|
||
) -> Option<(SyntaxTree, sv_parser::common::ParseResult)> {
|
||
let mut parse_iterations = 1;
|
||
let mut i = 0;
|
||
let mut includes: Vec<PathBuf> = inc_paths.to_vec();
|
||
let mut defines = HashMap::new();
|
||
let mut reverted_change = false;
|
||
let mut text = doc.clone();
|
||
|
||
// TODO: find how to fix it
|
||
// remove `reset all to avoid parse error
|
||
text = replace_substring(text, "`resetall");
|
||
|
||
let recover_text_by_byte_idx = |byte_idx: usize, reverted_change: &mut bool, text: &mut Rope| {
|
||
let mut line_start = text.byte_to_line(byte_idx);
|
||
let mut line_end = text.byte_to_line(byte_idx) + 1;
|
||
|
||
// println!("byte: {byte_idx}, line_start: {line_start}, line_end: {line_end}");
|
||
|
||
if !(*reverted_change) {
|
||
if let Some(range) = last_change_range {
|
||
line_start = range.start.line as usize;
|
||
line_end = range.end.line as usize + 1;
|
||
*reverted_change = true;
|
||
}
|
||
}
|
||
|
||
// 把 last_change 处的地方替换成空格
|
||
for line_idx in line_start .. line_end {
|
||
let line = text.line(line_idx);
|
||
let start_char = text.line_to_char(line_idx);
|
||
let line_length = line.len_chars();
|
||
text.remove(start_char..(start_char + line_length - 1));
|
||
text.insert(start_char, &" ".to_owned().repeat(line_length));
|
||
}
|
||
};
|
||
|
||
// 最多解析 50 次
|
||
while i < parse_iterations && i < 50 {
|
||
i += 1;
|
||
match make_ast_from_svlog_code(
|
||
&text.to_string(),
|
||
uri.to_file_path().unwrap(),
|
||
&defines,
|
||
&includes,
|
||
true,
|
||
allow_incomplete
|
||
) {
|
||
Ok((syntax_tree, parse_result)) => {
|
||
return Some((syntax_tree, parse_result));
|
||
}
|
||
Err(err) => {
|
||
// println!("err: {err:?}");
|
||
match err {
|
||
// 语法错误
|
||
sv_parser::Error::Parse(trace) => match trace {
|
||
Some((_, byte_idx)) => {
|
||
// 把出错的地方替换成空格
|
||
recover_text_by_byte_idx(byte_idx, &mut reverted_change, &mut text);
|
||
parse_iterations += 1;
|
||
}
|
||
None => return None,
|
||
},
|
||
|
||
// 遇到 include 错误,那就把提示中的 include 加入解析中再次解析
|
||
sv_parser::Error::Include { source: x } => {
|
||
if let sv_parser::Error::File { source: _, path: z } = *x {
|
||
// Include paths have to be relative to the working directory
|
||
// so we have to convert a source file relative path to a working directory
|
||
// relative path. This should have been handled by sv-parser
|
||
let mut inc_path_given = z.clone();
|
||
let mut uri_path = uri.to_file_path().unwrap();
|
||
uri_path.pop();
|
||
let rel_path = diff_paths(uri_path, current_dir().unwrap()).unwrap();
|
||
inc_path_given.pop();
|
||
let inc_path = rel_path.join(inc_path_given);
|
||
if !includes.contains(&inc_path) {
|
||
includes.push(inc_path);
|
||
} else {
|
||
error!("parser: include error: {:?}", z);
|
||
break;
|
||
}
|
||
parse_iterations += 1;
|
||
}
|
||
}
|
||
|
||
// 宏定义不存在的错误
|
||
sv_parser::Error::DefineNotFound(not_found_macro_name) => {
|
||
let com_define = Define {
|
||
identifier: not_found_macro_name.to_string(),
|
||
arguments: Vec::new(),
|
||
text: Some(DefineText {text: "UNKNOWN_MACRO".to_string(), origin: None})
|
||
};
|
||
defines.insert(not_found_macro_name, Some(com_define));
|
||
parse_iterations += 1;
|
||
}
|
||
sv_parser::Error::Preprocess(trace) => match trace {
|
||
Some((_, byte_idx)) => {
|
||
info!("meet preprocess error, text: {text:?}");
|
||
|
||
// 把出错的地方替换成空格
|
||
// println!("text {text:?}");
|
||
recover_text_by_byte_idx(byte_idx, &mut reverted_change, &mut text);
|
||
parse_iterations += 1;
|
||
}
|
||
None => return None
|
||
}
|
||
sv_parser::Error::DefineArgNotFound(trace) => match trace {
|
||
Some((_, start_byte_idx, _)) => {
|
||
recover_text_by_byte_idx(start_byte_idx, &mut reverted_change, &mut text);
|
||
// recover_text_by_byte_idx(end_byte_idx, &mut reverted_change, &mut text);
|
||
parse_iterations += 1;
|
||
}
|
||
_ => return None
|
||
}
|
||
sv_parser::Error::DefineNoArgs(trace) => match trace {
|
||
Some((_, start_byte_idx, _)) => {
|
||
recover_text_by_byte_idx(start_byte_idx, &mut reverted_change, &mut text);
|
||
parse_iterations += 1;
|
||
}
|
||
_ => return None
|
||
}
|
||
_ => error!("parse error, {:?}", err),
|
||
};
|
||
}
|
||
}
|
||
}
|
||
None
|
||
|
||
}
|
||
|
||
pub fn sv_parser_pipeline(
|
||
#[allow(unused)]
|
||
conf: &Arc<RwLock<LspConfiguration>>,
|
||
source_handle: &Arc<RwLock<Source>>,
|
||
scope_handle: &Arc<RwLock<Option<GenericScope>>>,
|
||
hdl_param_handle: &Arc<HdlParam>,
|
||
doc: &Rope,
|
||
uri: &Url,
|
||
last_change_range: &Option<Range>,
|
||
include_dirs: &Vec<PathBuf>
|
||
) {
|
||
if doc.len_chars() == 0 {
|
||
return;
|
||
}
|
||
|
||
let escape_path_string = from_uri_to_escape_path_string(uri).unwrap();
|
||
let escape_path = PathBuf::from_str(&escape_path_string).unwrap();
|
||
|
||
let ast = recovery_sv_parse_with_retry(
|
||
doc,
|
||
uri,
|
||
last_change_range,
|
||
include_dirs
|
||
);
|
||
|
||
// 更新 scope tree
|
||
let mut scope_tree = match &ast {
|
||
Some((tree, _)) => get_scopes_from_syntax_tree(tree, uri),
|
||
None => None,
|
||
};
|
||
|
||
let mut file = source_handle.write().unwrap();
|
||
|
||
// 加入语法树 & 更新 fast
|
||
if let Some((syntax_tree, parse_result)) = ast {
|
||
if let Ok(fast) = make_fast_from_syntaxtree(&syntax_tree, &escape_path) {
|
||
info!("update parse_result: {:?}", parse_result);
|
||
hdl_param_handle.update_hdl_file(
|
||
escape_path_string.to_string(),
|
||
fast,
|
||
parse_result,
|
||
Some(AstLike::Svlog(syntax_tree))
|
||
);
|
||
}
|
||
|
||
file.finish_at_least_once = true;
|
||
} else {
|
||
file.finish_at_least_once = false;
|
||
}
|
||
// file.syntax_tree = syntax_tree;
|
||
drop(file);
|
||
|
||
info!("finish parse {:?}", uri.to_string());
|
||
|
||
// 更新 global_scope,用于 sv 的解析
|
||
// global_scope 为全局最大的那个 scope,它的 scopes 和 defs 下的元素和每一个文件一一对应
|
||
let mut global_scope = scope_handle.write().unwrap();
|
||
match &mut *global_scope {
|
||
Some(scope) => match &mut scope_tree {
|
||
Some(tree) => {
|
||
// 更新所有 uri 为当前 uri 的文件结构
|
||
scope.defs.retain(|x| &x.url() != uri);
|
||
scope.scopes.retain(|x| &x.url() != uri);
|
||
|
||
scope.defs.append(&mut tree.defs);
|
||
scope.scopes.append(&mut tree.scopes);
|
||
}
|
||
None => (),
|
||
},
|
||
// 使用 scope_tree 来更新全局的 scope
|
||
None => *global_scope = scope_tree,
|
||
}
|
||
// eprintln!("{:#?}", *global_scope);
|
||
drop(global_scope);
|
||
}
|
||
|
||
pub fn vhdl_parser_pipeline(
|
||
conf: &Arc<RwLock<LspConfiguration>>,
|
||
source_handle: &Arc<RwLock<Source>>,
|
||
scope_handle: &Arc<RwLock<Option<GenericScope>>>,
|
||
project_handle: &Arc<RwLock<Option<VhdlProject>>>,
|
||
hdl_param_handle: &Arc<HdlParam>,
|
||
doc: &Rope,
|
||
uri: &Url
|
||
) {
|
||
if doc.len_chars() == 0 {
|
||
return;
|
||
}
|
||
|
||
let path = match PathBuf::from_str(uri.path()) {
|
||
Ok(path) => path,
|
||
Err(error) => {
|
||
info!("error happen in <goto_include_definition>: {:?}", error);
|
||
return;
|
||
}
|
||
};
|
||
let escape_path = to_escape_path(&path);
|
||
let escape_path_string = escape_path.to_str().unwrap_or("");
|
||
if escape_path_string.len() == 0 {
|
||
info!("error happen in [vhdl_parser_pipeline], escape_path_string is empty");
|
||
return;
|
||
}
|
||
|
||
let extension_path = {
|
||
let configure = conf.read().unwrap();
|
||
configure.extension_path.to_string()
|
||
};
|
||
|
||
let mut global_project = project_handle.write().unwrap();
|
||
match &mut *global_project {
|
||
Some(vhdl_project) => {
|
||
if let Some(source) = vhdl_project.project.get_source(&escape_path) {
|
||
source.change(None, &doc.to_string());
|
||
vhdl_project.project.update_source(&source);
|
||
vhdl_project.project.analyse();
|
||
} else {
|
||
vhdl_project.config_file_strs.push(format!("{:?}", escape_path));
|
||
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() };
|
||
}
|
||
}
|
||
}
|
||
None => {
|
||
let std_cfg_path = match PathBuf::from_str(&(extension_path.clone() + "/resources/dide-lsp/static/vhdl_std_lib/vhdl_ls.toml")) {
|
||
Ok(path) => path,
|
||
Err(e) => {
|
||
info!("error happen in <vhdl_parse_pipeline>: {:?}", e);
|
||
return;
|
||
}
|
||
};
|
||
match vhdl_lang::Config::read_file_path(&std_cfg_path) {
|
||
Ok(std_config) => {
|
||
let config_str = format!(
|
||
r#"
|
||
[libraries]
|
||
digital_lsp.files = [{:?}]
|
||
"#, escape_path
|
||
);
|
||
let mut config_file_strs = Vec::new();
|
||
config_file_strs.push(format!("{:?}", escape_path));
|
||
let config = vhdl_lang::Config::from_str(&config_str, Path::new(""));
|
||
if let Ok(mut config) = config {
|
||
let mut messages = Vec::new();
|
||
config.append(&std_config, &mut messages);
|
||
let mut project = Project::from_config(config, &mut messages);
|
||
project.analyse();
|
||
*global_project = Some(VhdlProject { project, std_config, config_file_strs });
|
||
}
|
||
}
|
||
Err(e) => info!("error happen in <vhdl_parse_pipeline>: Can't load standard vhdl lib from {std_cfg_path:#?} because {e:#?}")
|
||
}
|
||
}
|
||
}
|
||
drop(global_project);
|
||
|
||
let mut global_project = project_handle.write().unwrap();
|
||
match &mut *global_project {
|
||
Some(vhdl_project) => {
|
||
let mut source = source_handle.write().unwrap();
|
||
let text = doc.to_string();
|
||
let mut scope_tree = if let Some(design_file) = vhdl_parse_str(&escape_path, &text) {
|
||
let arch_and_entity = vhdl_project.project.get_analyzed_units(&escape_path);
|
||
if let Some(mut fast) = make_fast_from_units(arch_and_entity) {
|
||
// for module in &fast.content {
|
||
// info!("parse port number: {:?}", module.ports.len());
|
||
// }
|
||
|
||
let file_type = {
|
||
let fast_map = hdl_param_handle.path_to_hdl_file.read().unwrap();
|
||
if let Some(hdl_file) = fast_map.get(escape_path_string) {
|
||
hdl_file.fast.file_type.to_string()
|
||
} else {
|
||
if let Some(relative_path) = escape_path_string.strip_prefix(&extension_path) {
|
||
if relative_path.starts_with("/user/ip") || relative_path.starts_with("user/ip") {
|
||
"ip".to_string()
|
||
} else {
|
||
"common".to_string()
|
||
}
|
||
} else {
|
||
"common".to_string()
|
||
}
|
||
}
|
||
};
|
||
|
||
fast.file_type = file_type;
|
||
hdl_param_handle.update_hdl_file(
|
||
escape_path_string.to_string(),
|
||
fast.clone(),
|
||
sv_parser::common::ParseResult::new(),
|
||
Some(AstLike::Vhdl(design_file))
|
||
);
|
||
let scope_tree = get_scopes_from_vhdl_fast(&fast, doc, uri);
|
||
scope_tree
|
||
} else {
|
||
None
|
||
}
|
||
} else {
|
||
source.finish_at_least_once = false;
|
||
None
|
||
};
|
||
drop(source);
|
||
|
||
|
||
info!("finish parse {:?}", uri.to_string());
|
||
|
||
// 更新 global_scope,用于 sv 的解析
|
||
// global_scope 为全局最大的那个 scope,它的 scopes 和 defs 下的元素和每一个文件一一对应
|
||
let mut global_scope = scope_handle.write().unwrap();
|
||
match &mut *global_scope {
|
||
Some(scope) => match &mut scope_tree {
|
||
Some(tree) => {
|
||
// 更新所有 uri 为当前 uri 的文件结构
|
||
scope.defs.retain(|x| &x.url() != uri);
|
||
scope.scopes.retain(|x| &x.url() != uri);
|
||
|
||
scope.defs.append(&mut tree.defs);
|
||
scope.scopes.append(&mut tree.scopes);
|
||
}
|
||
None => (),
|
||
},
|
||
// 使用 scope_tree 来更新全局的 scope
|
||
None => *global_scope = scope_tree,
|
||
}
|
||
// eprintln!("{:#?}", *global_scope);
|
||
|
||
drop(global_scope);
|
||
}
|
||
_ => ()
|
||
}
|
||
}
|
||
|
||
//TODO: add bounds checking for utf8<->utf16 conversions
|
||
/// This trait defines some helper functions to convert between lsp types
|
||
/// and char/byte positions
|
||
pub trait LSPSupport {
|
||
fn pos_to_byte(&self, pos: &Position) -> usize;
|
||
fn pos_to_char(&self, pos: &Position) -> usize;
|
||
fn byte_to_pos(&self, byte_idx: usize) -> Position;
|
||
fn char_to_pos(&self, char_idx: usize) -> Position;
|
||
fn range_to_char_range(&self, range: &Range) -> StdRange<usize>;
|
||
fn char_range_to_range(&self, range: StdRange<usize>) -> Range;
|
||
fn apply_change(&mut self, change: &TextDocumentContentChangeEvent);
|
||
}
|
||
|
||
/// Extend ropey's Rope type with lsp convenience functions
|
||
impl LSPSupport for Rope {
|
||
fn pos_to_byte(&self, pos: &Position) -> usize {
|
||
return self.char_to_byte(self.pos_to_char(pos));
|
||
}
|
||
fn pos_to_char(&self, pos: &Position) -> usize {
|
||
let line_slice = self.line(pos.line as usize);
|
||
return self.line_to_char(pos.line as usize) + line_slice.utf16_cu_to_char(pos.character as usize);
|
||
}
|
||
fn byte_to_pos(&self, byte_idx: usize) -> Position {
|
||
// info!("byte_idx: {byte_idx}");
|
||
let self_len = if self.len_bytes() != 0 { self.len_bytes() - 1 } else { 0 };
|
||
return self.char_to_pos(self.byte_to_char(min(byte_idx, self_len)));
|
||
}
|
||
fn char_to_pos(&self, char_idx: usize) -> Position {
|
||
let line = self.char_to_line(char_idx);
|
||
let line_slice = self.line(line);
|
||
return Position {
|
||
line: line as u32,
|
||
character: line_slice.char_to_utf16_cu(char_idx - self.line_to_char(line)) as u32,
|
||
};
|
||
}
|
||
fn range_to_char_range(&self, range: &Range) -> StdRange<usize> {
|
||
return self.pos_to_char(&range.start)..self.pos_to_char(&range.end);
|
||
}
|
||
fn char_range_to_range(&self, range: StdRange<usize>) -> Range {
|
||
return Range {
|
||
start: self.char_to_pos(range.start),
|
||
end: self.char_to_pos(range.end),
|
||
};
|
||
}
|
||
fn apply_change(&mut self, change: &TextDocumentContentChangeEvent) {
|
||
if let Some(range) = change.range {
|
||
let char_range = self.range_to_char_range(&range);
|
||
self.remove(char_range.clone());
|
||
if !change.text.is_empty() {
|
||
self.insert(char_range.start, &change.text);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl<'a> LSPSupport for RopeSlice<'a> {
|
||
fn pos_to_byte(&self, pos: &Position) -> usize {
|
||
self.char_to_byte(self.pos_to_char(pos))
|
||
}
|
||
fn pos_to_char(&self, pos: &Position) -> usize {
|
||
let line_slice = self.line(pos.line as usize);
|
||
self.line_to_char(pos.line as usize) + line_slice.utf16_cu_to_char(pos.character as usize)
|
||
}
|
||
fn byte_to_pos(&self, byte_idx: usize) -> Position {
|
||
// info!("byte_idx: {byte_idx}");
|
||
self.char_to_pos(self.byte_to_char(min(byte_idx, self.len_bytes() - 1)))
|
||
}
|
||
fn char_to_pos(&self, char_idx: usize) -> Position {
|
||
let line = self.char_to_line(char_idx);
|
||
let line_slice = self.line(line);
|
||
Position {
|
||
line: line as u32,
|
||
character: line_slice.char_to_utf16_cu(char_idx - self.line_to_char(line)) as u32,
|
||
}
|
||
}
|
||
fn range_to_char_range(&self, range: &Range) -> StdRange<usize> {
|
||
self.pos_to_char(&range.start)..self.pos_to_char(&range.end)
|
||
}
|
||
fn char_range_to_range(&self, range: StdRange<usize>) -> Range {
|
||
Range {
|
||
start: self.char_to_pos(range.start),
|
||
end: self.char_to_pos(range.end),
|
||
}
|
||
}
|
||
fn apply_change(&mut self, _: &TextDocumentContentChangeEvent) {
|
||
panic!("can't edit a rope slice");
|
||
}
|
||
}
|