Compare commits

..

No commits in common. "10fbbd8843145c35f7b904624b14a1abd5187d90" and "2dd7f98d5f15848df67769bc1d86ebb4e61e456e" have entirely different histories.

79 changed files with 2858 additions and 200991 deletions

3
.gitignore vendored
View File

@ -13,5 +13,4 @@ test.txt
build.bat build.bat
.DS_Store .DS_Store
.cache .cache
deploy.*

View File

@ -15,44 +15,5 @@
"#[allow(unused)]" "#[allow(unused)]"
], ],
"description": "#[allow(unused)]" "description": "#[allow(unused)]"
},
"match file_type": {
"scope": "rust",
"prefix": "matchfiletype",
"body": [
"match file_type.as_str() {",
"\t\"common\" => {",
"\t\t",
"\t}",
"\t\"ip\" => {",
"\t\t",
"\t}",
"\t\"primitives\" => {",
"\t\t",
"\t}",
"\t_ => {",
"\t\t",
"\t}",
"}",
]
},
"new": {
"scope": "rust",
"prefix": "new",
"body": [
"pub fn new($1) -> Self {",
"\t$2",
"}"
]
},
"CompletionItemLabelDetails Snippet": {
"prefix": "label_details",
"body": [
"let label_details = CompletionItemLabelDetails {",
" description: Some(\"${1:port description}\".to_string()),",
" ..Default::default()",
"};"
],
"description": "Create a CompletionItemLabelDetails with description."
} }
} }

View File

@ -1,3 +0,0 @@
{
"rust-analyzer.trace.server": "verbose"
}

129
Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 3
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@ -99,12 +99,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "anyhow"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.83" version = "0.1.83"
@ -292,15 +286,6 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crossbeam-channel"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.8.5" version = "0.8.5"
@ -343,7 +328,6 @@ dependencies = [
name = "digital-lsp" name = "digital-lsp"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"anyhow",
"bincode", "bincode",
"dirs-next", "dirs-next",
"flexi_logger", "flexi_logger",
@ -364,9 +348,7 @@ dependencies = [
"tokio", "tokio",
"tower-lsp", "tower-lsp",
"vhdl_lang", "vhdl_lang",
"vhdl_ls",
"walkdir", "walkdir",
"xml",
] ]
[[package]] [[package]]
@ -443,29 +425,6 @@ dependencies = [
"syn 2.0.79", "syn 2.0.79",
] ]
[[package]]
name = "env_filter"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"humantime",
"log",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.1" version = "1.0.1"
@ -584,15 +543,6 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "fuzzy-matcher"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
dependencies = [
"thread_local",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.15" version = "0.2.15"
@ -658,12 +608,6 @@ version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.61" version = "0.1.61"
@ -775,18 +719,6 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "lsp-server"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "550446e84739dcaf6d48a4a093973850669e13e8a34d8f8d64851041be267cd9"
dependencies = [
"crossbeam-channel",
"log",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "lsp-types" name = "lsp-types"
version = "0.94.1" version = "0.94.1"
@ -800,19 +732,6 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "lsp-types"
version = "0.95.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e34d33a8e9b006cd3fc4fe69a921affa097bae4bb65f76271f4644f9a334365"
dependencies = [
"bitflags 1.3.2",
"serde",
"serde_json",
"serde_repr",
"url",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@ -1423,7 +1342,6 @@ dependencies = [
name = "sv-parser" name = "sv-parser"
version = "0.13.3" version = "0.13.3"
dependencies = [ dependencies = [
"log",
"nom", "nom",
"nom-greedyerror", "nom-greedyerror",
"sv-parser-error", "sv-parser-error",
@ -1466,7 +1384,6 @@ dependencies = [
name = "sv-parser-pp" name = "sv-parser-pp"
version = "0.13.3" version = "0.13.3"
dependencies = [ dependencies = [
"log",
"nom", "nom",
"nom-greedyerror", "nom-greedyerror",
"sv-parser-error", "sv-parser-error",
@ -1544,16 +1461,6 @@ dependencies = [
"syn 2.0.79", "syn 2.0.79",
] ]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.8.0" version = "1.8.0"
@ -1670,7 +1577,7 @@ dependencies = [
"dashmap", "dashmap",
"futures", "futures",
"httparse", "httparse",
"lsp-types 0.94.1", "lsp-types",
"memchr", "memchr",
"serde", "serde",
"serde_json", "serde_json",
@ -1828,23 +1735,6 @@ dependencies = [
"syn 2.0.79", "syn 2.0.79",
] ]
[[package]]
name = "vhdl_ls"
version = "0.83.0"
dependencies = [
"clap 4.5.19",
"env_logger",
"fnv",
"fuzzy-matcher",
"log",
"lsp-server",
"lsp-types 0.95.1",
"serde",
"serde_json",
"tower-lsp",
"vhdl_lang",
]
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.5.0" version = "2.5.0"
@ -2112,18 +2002,3 @@ checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "xml"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede1c99c55b4b3ad0349018ef0eccbe954ce9c342334410707ee87177fcf2ab4"
dependencies = [
"xml-rs",
]
[[package]]
name = "xml-rs"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f"

View File

@ -6,7 +6,6 @@ edition = "2018"
[dependencies] [dependencies]
sv-parser = { version = "0.13.3", path = "sv-parser/sv-parser"} sv-parser = { version = "0.13.3", path = "sv-parser/sv-parser"}
vhdl_ls = { version = "^0.83.0", path = "vhdl-parser/vhdl_ls" }
vhdl_lang = { version = "^0.83.0", path = "vhdl-parser/vhdl_lang" } vhdl_lang = { version = "^0.83.0", path = "vhdl-parser/vhdl_lang" }
dirs-next = "2.0" dirs-next = "2.0"
bincode = "1.3" bincode = "1.3"
@ -26,8 +25,6 @@ regex = "1.9.1"
structopt = "0.3.26" structopt = "0.3.26"
strum = "0.26.1" strum = "0.26.1"
strum_macros = "0.26.1" strum_macros = "0.26.1"
xml = "0.8.16"
anyhow = "1.0.95"
[dev-dependencies] [dev-dependencies]
tempdir = "0.3.7" tempdir = "0.3.7"

View File

@ -1,11 +0,0 @@
export LC_ALL=POSIX
export CROSS_TARGET="loongarch64-unknown-linux-gnu"
export MABI="lp64d"
export BUILD64="-mabi=lp64d"
export PATH=$PATH:$HOME/package/cross-tools/bin
export CARGO_TARGET_LOONGARCH64_UNKNOWN_LINUX_GNU_LINKER=loongarch64-unknown-linux-gnu-gcc
export CC_loongarch64_unknown_linux_gnu=loongarch64-unknown-linux-gnu-gcc
export CXX_loongarch64_unknown_linux_gnu=loongarch64-unknown-linux-gnu-g++
cargo build --release --target "${CROSS_TARGET}"

BIN
copy_server_to_ide.sh Executable file → Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
use crate::server::LspServer;
use crate::utils::*;
#[allow(unused)]
use log::info;
use tower_lsp::lsp_types::*;
mod sv;
mod vhdl;
impl LspServer {
pub fn code_lens(&self, params: CodeLensParams) -> Option<Vec<CodeLens>> {
let language_id = get_language_id_by_uri(&params.text_document.uri);
match language_id.as_str() {
"vhdl" => vhdl::code_lens(
self,
&params
),
"verilog" | "systemverilog" => sv::code_lens(
self,
&params
),
_ => None
}
}
}

View File

@ -1,85 +0,0 @@
use std::{path::PathBuf, str::FromStr};
use crate::{core, server::LspServer};
#[allow(unused)]
use log::info;
use tower_lsp::lsp_types::*;
use super::to_escape_path;
pub fn code_lens(
#[allow(unused)]
server: &LspServer,
#[allow(unused)]
params: &CodeLensParams
) -> Option<Vec<CodeLens>> {
let hdl_param = server.db.hdl_param.clone();
let uri = &params.text_document.uri;
let path_to_hdl_file = hdl_param.path_to_hdl_file.read().unwrap();
let pathbuf = PathBuf::from_str(uri.path()).unwrap();
let pathbuf = to_escape_path(&pathbuf);
let path_string = pathbuf.to_str().unwrap();
let mut code_lens_array = Vec::<CodeLens>::new();
if let Some(hdl_file) = path_to_hdl_file.get(path_string) {
for module in &hdl_file.fast.content {
code_lens_array.push(make_run_code_lens(path_string, module));
code_lens_array.push(make_test_code_lens(path_string, module));
}
return Some(code_lens_array);
}
None
}
/// 快速仿真用的 按钮
fn make_test_code_lens(
path_string: &str,
module: &core::hdlparam::Module
) -> CodeLens {
// 此处只有 name 和 path 有用
let module_data_item: serde_json::Value = serde_json::json!({
"icon": "",
"name": module.name.to_string(),
"type": "",
"doFastFileType": "common",
"range": null,
"path": path_string.to_string(),
"parent": null
});
let command = Command {
title: "Simulate".to_string(),
command: "digital-ide.tool.icarus.simulateFile".to_string(),
arguments: Some(vec![module_data_item])
};
let code_lens = CodeLens {
range: module.range.to_lsp_range(),
command: Some(command),
data: None
};
code_lens
}
/// netlist 用的按钮
fn make_run_code_lens(
path_string: &str,
module: &core::hdlparam::Module
) -> CodeLens {
let file = serde_json::json!(path_string);
let module_name = serde_json::json!(module.name.to_string());
let command = Command {
title: "Netlist".to_string(),
command: "digital-ide.netlist.show".to_string(),
arguments: Some(vec![file, module_name])
};
let code_lens = CodeLens {
range: module.range.to_lsp_range(),
command: Some(command),
data: None
};
code_lens
}

View File

@ -1,14 +0,0 @@
use crate::server::LspServer;
#[allow(unused)]
use log::info;
use tower_lsp::lsp_types::*;
pub fn code_lens(
#[allow(unused)]
server: &LspServer,
#[allow(unused)]
params: &CodeLensParams
) -> Option<Vec<CodeLens>> {
None
}

View File

@ -1,2 +0,0 @@
pub mod vlog;
pub mod vhdl;

View File

@ -1,137 +0,0 @@
use std::collections::HashMap;
use ropey::Rope;
use sv_parser::{RefNode, SyntaxTree};
use serde_json::{Value, json};
use crate::core::sv_parser::{get_identifier, get_position};
struct CommentSymbol {
comment_string: String,
start_pos: crate::core::hdlparam::Position,
end_pos: crate::core::hdlparam::Position
}
pub fn get_comments_from_ast(doc: &Rope, syntax_tree: &SyntaxTree) {
// 创造一个收容 comment 的栈,对连续的注释进行合并
let mut comments = Vec::<CommentSymbol>::new();
for node in syntax_tree {
match node {
RefNode::Comment(x) => {
let locate = x.nodes.0;
let comment_string = syntax_tree.get_str(&locate).unwrap();
let start_pos = get_position(doc, locate, 0);
let end_pos = get_position(doc, locate, locate.len);
println!("{:?}", comment_string);
println!("start {:?}", start_pos);
println!("end {:?}", end_pos);
if let Some(mut last_comment) = comments.last_mut() {
// 判断是否需要合并
let last_end_line = last_comment.end_pos.line;
let current_start_line = start_pos.line;
}
// 否则,直接加入
comments.push(CommentSymbol {
comment_string: comment_string.to_string(),
start_pos, end_pos
});
}
_ => {}
}
}
}
/// 解析第一行 tag
/// /* @meta
/// *
/// */
///
/// /* @module
/// *
/// */
///
/// /* @wavedrom xxx
/// *
/// */
///
/// 或者压根儿没有
/// /*
/// *
/// */
fn parse_first_line_tag(comment: &str) {
}
/// 解析不同的注释块
fn codedoc_parse_pipeline(comment: &str) {
}
fn parse_c_style_comment(comment: &str) -> Result<Value, String> {
let mut result = HashMap::new();
let mut revision_vec = Vec::new();
let mut in_revision = false;
for line in comment.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with("/*") || line.starts_with("*/") {
continue;
}
if line.starts_with("*") {
let line = line[1..].trim();
if line.starts_with("@meta") {
continue;
}
if let Some((key, value)) = line.split_once(':') {
let key = key.trim();
let value = value.trim();
match key {
"Tool Versions" => {
let tools: Vec<&str> = value.split('&').map(|s| s.trim()).collect();
result.insert(key.to_string(), json!(tools));
}
"Revision" => {
in_revision = true;
}
_ => {
if in_revision {
if value.matches('&').count() == 2 {
let parts: Vec<&str> = value.split('&').map(|s| s.trim()).collect();
let revision = json!({
"date": parts[0],
"version": parts[1],
"revision": parts[2]
});
revision_vec.push(revision);
} else {
in_revision = false;
result.insert(key.to_string(), json!(revision_vec));
}
} else {
result.insert(key.to_string(), json!(value));
}
}
}
}
}
}
// Handle Copyright field
if let Some(copyright) = result.get_mut("Copyright") {
let copyright_str = copyright.as_str().unwrap_or("");
*copyright = json!(format!("Copyright(c) {}", copyright_str));
}
Ok(json!(result))
}

View File

@ -1,180 +0,0 @@
use std::collections::HashMap;
use ropey::Rope;
use sv_parser::{RefNode, SyntaxTree};
use serde_json::{Value, json};
use crate::core::sv_parser::{get_identifier, get_position};
struct CommentSymbol {
comment_string: String,
start_pos: crate::core::hdlparam::Position,
end_pos: crate::core::hdlparam::Position
}
pub fn get_comments_from_ast(doc: &Rope, syntax_tree: &SyntaxTree) {
let comments = make_comment_list(doc, syntax_tree);
for comment in &comments {
if let Some(comment_meta) = get_meta_info(comment) {
} else {
// 处理为普通注释
}
}
}
/// 创造一个收容 comment 的栈,对连续的注释进行合并
fn make_comment_list(doc: &Rope, syntax_tree: &SyntaxTree) -> Vec<CommentSymbol> {
let mut comments = Vec::<CommentSymbol>::new();
for node in syntax_tree {
match node {
RefNode::Comment(x) => {
let locate = x.nodes.0;
let comment_string = syntax_tree.get_str(&locate).unwrap();
let start_pos = get_position(doc, locate, 0);
let end_pos = get_position(doc, locate, locate.len);
println!("{:?}", comment_string);
println!("start {:?}", start_pos);
println!("end {:?}", end_pos);
if let Some(last_comment) = comments.last_mut() {
// 判断是否需要合并
// vlog 只合并单行注释
let last_end_line = last_comment.end_pos.line;
let current_start_line = start_pos.line;
if current_start_line - last_end_line <= 1
&& last_comment.comment_string.starts_with("//")
&& comment_string.starts_with("//") {
last_comment.comment_string += comment_string;
last_comment.end_pos = end_pos;
continue;
}
}
// 否则,直接加入
comments.push(CommentSymbol {
comment_string: comment_string.to_string(),
start_pos, end_pos
});
}
_ => {}
}
}
comments
}
struct CommentMeta<'a> {
comment_type: &'a str,
argruments: Vec::<&'a str>
}
/// 解析第一行 tag
/// /* @meta
/// *
/// */
///
/// /* @module
/// *
/// */
///
/// /* @wavedrom xxx
/// *
/// */
///
/// 或者压根儿没有
/// /*
/// *
/// */
fn get_meta_info(comment: &str) -> Option<CommentMeta> {
let first_line = comment.lines().next().unwrap_or("").trim();
// 注释开头都是 // 或者 /* ,都是两个字符
let comment_meta = &first_line[2..];
if comment_meta.trim().starts_with("@") {
let arguments: Vec<&str> = comment_meta.split_whitespace().collect();
if arguments.len() > 0 {
let comment_type = arguments[0];
let arguments = &arguments[1..];
Some(CommentMeta {
comment_type,
argruments: arguments.to_vec()
})
} else {
None
}
} else {
None
}
}
/// 解析不同的注释块
fn codedoc_parse_pipeline(comment: &str) {
}
fn parse_c_style_comment(comment: &str) -> Result<Value, String> {
let mut result = HashMap::new();
let mut revision_vec = Vec::new();
let mut in_revision = false;
for line in comment.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with("/*") || line.starts_with("*/") {
continue;
}
if line.starts_with("*") {
let line = line[1..].trim();
if line.starts_with("@meta") {
continue;
}
if let Some((key, value)) = line.split_once(':') {
let key = key.trim();
let value = value.trim();
match key {
"Tool Versions" => {
let tools: Vec<&str> = value.split('&').map(|s| s.trim()).collect();
result.insert(key.to_string(), json!(tools));
}
"Revision" => {
in_revision = true;
}
_ => {
if in_revision {
if value.matches('&').count() == 2 {
let parts: Vec<&str> = value.split('&').map(|s| s.trim()).collect();
let revision = json!({
"date": parts[0],
"version": parts[1],
"revision": parts[2]
});
revision_vec.push(revision);
} else {
in_revision = false;
result.insert(key.to_string(), json!(revision_vec));
}
} else {
result.insert(key.to_string(), json!(value));
}
}
}
}
}
}
// Handle Copyright field
if let Some(copyright) = result.get_mut("Copyright") {
let copyright_str = copyright.as_str().unwrap_or("");
*copyright = json!(format!("Copyright(c) {}", copyright_str));
}
Ok(json!(result))
}

View File

@ -1,460 +0,0 @@
use tower_lsp::lsp_types::*;
/// 文档IEEE 1364-2005
/// author: LSTM-Kirigaya
/// date: 2024.12.13
pub const VLOG_DIRECTIVES: &[(&str, &str, &str)] = &[
// 1800-2009 22.13 `__FILE__ and `__LINE__
(
"__FILE__",
"`__FILE__",
r#"用于获取当前文件名
```verilog
// 文件名: /path/to/your/project/test.v
`timescale 1ns/1ps
module test;
initial begin
// 输出 当前文件名:/path/to/your/project/test.v
$display("当前文件名: %s", `__FILE__);
end
endmodule
```
"#),
(
"__LINE__",
"`__LINE__",
r#"用于获取当前行号
```verilog
// 文件名: /path/to/your/project/test.v
`timescale 1ns/1ps
module test;
initial begin
// 使用 __FILE__ 和 __LINE__ 宏输出当前文件名和行号
$display("当前文件名: %s, 当前行号: %0d", `__FILE__, `__LINE__);
end
endmodule
```
"#),
// 19.1 `celldefine and `endcelldefine
(
"celldefine",
"`celldefine\n$1\n`endcelldefine",
r#"开始定义单元格
`celldefine` `endcelldefine`
```verilog
// 使用 celldefine 和 endcelldefine 定义一个单元库模块
`celldefine
module my_cell (
input a,
input b,
output out
);
assign out = a & b; // 简单的与门逻辑
endmodule
`endcelldefine
// 测试模块
module test;
reg a, b;
wire out;
// 实例化单元库模块
my_cell uut (
.a(a),
.b(b),
.out(out)
);
initial begin
// 初始化输入
a = 0;
b = 0;
// 仿真逻辑
#10;
a = 1;
#10;
b = 1;
#10;
$display("输出结果: %b", out);
end
endmodule
```
使 `celldefine`
- `celldefine` 仿
- 使 celldefine
"#),
(
"endcelldefine",
"`endcelldefine",
"结束单元格定义"
),
// 19.2 `default_nettype
(
"default_nettype",
"`default_nettype $1",
"设置默认的网络类型"
),
// 19.3 `define and `undef
(
"define",
"`define $1 $2",
r#"定义一个宏
###
```verilog
`define MACRO_NAME value
```
###
```verilog
// 使用 `define 定义常量和代码片段
`define PORT_NUM 8 // 定义常量 PORT_NUM 为 8
`define ADD_ONE(x) (x + 1) // 定义带参数的宏 ADD_ONE
module test;
reg [(`PORT_NUM - 1):0] data; // 使用常量 PORT_NUM
integer value;
initial begin
// 使用宏 ADD_ONE
value = 5;
$display("原始值: %0d", value);
value = `ADD_ONE(value);
$display("加一后的值: %0d", value);
// 使用常量 PORT_NUM
data = 8'hFF;
$display("数据宽度: %0d 位", `PORT_NUM);
$display("数据值: %h", data);
end
endmodule
```
"#),
(
"undef",
"`undef $1",
"取消定义一个宏"
),
// 1800-2009 22.5 `define, `undef and `undefineall
(
"undefineall",
"`undefineall",
"取消定义所有宏"
),
// 19.4 `ifdef, `else, `elsif, `endif, `ifndef
(
"ifdef",
"`ifdef $1\n\t//ifdef\n\t$2\n`else\n//else\n\t$3\n`endif",
r#"ifdef、else 和 endif 是条件编译指令,用于根据宏定义的存在与否来选择性地编译代码。它们通常用于实现条件编译逻辑,以便在不同的编译环境下生成不同的代码。
###
```verilog
`ifdef MACRO_NAME
// 如果 MACRO_NAME 已定义,编译此段代码
`else
// 如果 MACRO_NAME 未定义,编译此段代码
`endif
```
###
```verilog
// 使用 `ifdef 和 `else 进行条件编译
`define DEBUG // 定义 DEBUG 宏
module test;
reg [7:0] data;
initial begin
data = 8'hFF;
// 使用 `ifdef 和 `else 进行条件编译
`ifdef DEBUG
$display("调试模式: 数据值 = %h", data);
`else
$display("发布模式: 数据值 = %h", data);
`endif
end
endmodule
```
test.v 仿
```
: = FF
```
"#
),
(
"else",
"`else",
"条件编译的 else 分支"
),
(
"elsif",
"`elsif $1",
"条件编译的 elsif 分支"
),
(
"endif",
"`endif",
"结束条件编译"
),
(
"ifndef",
"`ifndef $1",
"如果未定义宏,则编译代码"
),
// 19.5 `include
(
"include",
"`include $1",
"包含一个外部文件"
),
// 19.6 `resetall
(
"resetall",
"`resetall",
// resetall 也常用于开始
// 1834-2005中指出建议的用法是将 `resetall 放在每个源文本文件的开头,后面紧跟着文件中所需的指令。
// 通常可用于源文本开头,避免该文件受到其他文件代码的影响。或放置在末尾,避免编译器设置的进一步传递
r#"`resetall` 用于重置所有编译器设置为默认值。通常可用于源文本开头,避免该文件受到其他文件代码的影响。或放置在末尾,避免编译器设置的进一步传递。
1834-2005 `resetall
```verilog
`timescale 1ns/1ps // 设置时间单位和精度
module test;
// 一些 verilog 代码
endmodule
`resetall // 重置所有编译器设置,上文的 timescale 设置自此开始无效
```
"#
),
// 19.7 `line
(
"line",
"`line $1 $2 $3",
r#"`line` 用于指定当前代码的行号和文件名。它通常用于调试或日志记录,以便在仿真或编译时能够追踪代码的来源。
###
```verilog
`line line_number "filename" level
```
###
```verilog
// 使用 `line 指令指定行号和文件名
`line 10 "example.v" 0
module test;
initial begin
$display("当前行号: %0d", `__LINE__);
$display("当前文件名: %s", `__FILE__);
end
endmodule
```
"#
),
// 19.8 `timescale
(
"timescale",
"`timescale ${1:1ns}/${2:1ps}",
// 原文是The time_precision argument shall be at least as precise as the time_unit argument; it cannot specify a longer unit of time than time_unit.
// 也即是,仿真时间单位必须大于等于时间精度
// `timescale 1ns/1ns不会报错但是`timescale 1ps/1ns会报错
// [Synth 8-522] parsing error: error in timescale or timeprecision statement. <timeprecision> must be at least as precise as <timeunit>
r#"设置时间单位和精度,设置时间单位和精度,格式为 <code> `timescale </code> 仿真时间单位/时间精度,<code>仿真时间单位</code> 必须大于等于 <code>时间精度</code>,例如
```verilog
`timescale 1ns/1ps
module test;
initial begin
$display("当前时间单位: %0t", $time); // 输出当前时间
#10; // 延迟 10 纳秒
$display("延迟 10 纳秒后的时间: %0t", $time);
end
endmodule
```
#10.5仿 10.5
`timescale` `timescale` `resetall`
"#),
// 19.9 `unconnected_drive and `nounconnected_drive
(
"unconnected_drive",
"`unconnected_drive ${1:pull1}\n$2\n`nounconnected_drive\n",
r#"`nounconnected_drive` 和 `unconnected_drive` 是配合使用的编译指令,用于控制未连接端口的驱动行为。它们通常成对出现,分别用于禁用和启用未连接端口的驱动处理。
###
```verilog
`unconnected_drive (pull1 | pull0) // 启用未连接端口的驱动,若为 pull1则默认驱动为高电平若为 pull0则默认为低电平
// 你的 verilog 代码
`nounconnected_drive // 禁用未连接端口的驱动
```
###
```verilog
// 使用 `line 指令指定行号和文件名
`unconnected_drive pull1
module my_and(y, a, b);
output y;
input a, b;
assign y = a & b;
endmodule
module test(y,b);
output y;
input b;
my_and ul(y, ,b);
endmodule
`nounconnected_drive
```
"#
),
(
"nounconnected_drive",
"`nounconnected_drive",
r#"`nounconnected_drive` 和 `unconnected_drive` 是配合使用的编译指令,用于控制未连接端口的驱动行为。它们通常成对出现,分别用于禁用和启用未连接端口的驱动处理。
###
```verilog
`unconnected_drive (pull1 | pull0) // 启用未连接端口的驱动,若为 pull1则默认驱动为高电平若为 pull0则默认为低电平
// 你的 verilog 代码
`nounconnected_drive // 禁用未连接端口的驱动
```
"#
),
// 19.10 `pragma
(
"pragma",
"`pragma $1",
"编译指示,用于传递编译器指令"
),
// 19.11 `begin_keywords, `end_keywords
(
"begin_keywords",
"`begin_keywords\n$1\n`end_keywords ",
// begin_keywords并不是指定Verilog的编译版本而是指定使用的Verilog关键字版本
r#"`begin_keywords` 和 `end_keywords` 是用于指定 Verilog 关键字版本的编译指令。它们允许设计者在同一个文件中使用不同版本的 Verilog 关键字语法,从而实现兼容性或逐步迁移到新版本。
`begin_keywords` Verilog
- `1364-1995`Verilog-1995
- `1364-2001`Verilog-2001
- `1364-2005`Verilog-2005
- `1800-2009`SystemVerilog-2009
- `1800-2012`SystemVerilog-2012
```verilog
// 使用 begin_keywords 和 end_keywords 指定 Verilog 版本
`begin_keywords "1364-2001" // 指定 Verilog-2001 语法
module my_module;
reg [7:0] data;
initial begin
data = 8'hFF; // 使用 Verilog-2001 的语法
$display("Verilog-2001 语法: data = %h", data);
end
endmodule
`end_keywords // 结束 Verilog-2001 语法
// 恢复默认的 Verilog 语法
module another_module;
reg [7:0] data;
initial begin
data = 8'hAA; // 使用默认的 Verilog 语法
$display("默认语法: data = %h", data);
end
endmodule
```
"#),
(
"end_keywords",
"`end_keywords",
"结束关键字版本指定"
),
// Annex D Compiler directives
(
"default_decay_time",
"`default_decay_time $1",
r#"`default_decay_time` 用于设置电荷衰减时间decay time的默认值。它通常用于模拟电荷存储元件如电容或寄生电容的行为。
###
```verilog
`default_decay_time time_value
```
```
"#
),
(
"default_trireg_strength",
"`default_trireg_strength $1",
r#"`default_trireg_strength ` 用于设置三态寄存器trireg的默认驱动强度strength。三态寄存器是一种特殊的寄存器可以存储电荷并且在未被驱动时会衰减。"#
),
(
"delay_mode_distributed",
"`delay_mode_distributed",
r#"`delay_mode_distributed` 用于设置延迟模式的类型。它通常用于控制逻辑门和网络的延迟分布方式。"#
),
(
"delay_mode_path",
"`delay_mode_path",
r#"`delay_mode_path` 用于设置延迟模式的类型。它通常用于控制路径延迟path delay的计算方式特别是在时序逻辑电路中。"#
),
(
"delay_mode_unit",
"`delay_mode_unit",
r#"`delay_mode_unit` 用于设置延迟模式的类型。它通常用于控制逻辑门和网络的延迟计算方式,特别是在仿真中需要精确控制延迟时。"#
),
(
"delay_mode_zero",
"`delay_mode_zero",
r#"`delay_mode_zero` 用于设置延迟模式的类型。它通常用于控制逻辑门和网络的延迟计算方式,特别是在仿真中需要忽略延迟时。"#
),
];
fn make_function_profile(
description: &str
) -> MarkupContent {
MarkupContent {
kind: MarkupKind::Markdown,
value: format!("{}", description)
}
}
pub fn provide_vlog_directives_completions() -> Vec<CompletionItem> {
let mut completion_items: Vec<CompletionItem> = Vec::new();
for (label, snippet_code, description) in VLOG_DIRECTIVES {
let function_profile = make_function_profile(description);
let label_details = CompletionItemLabelDetails {
description: Some("directive".to_string()),
..Default::default()
};
completion_items.push(CompletionItem {
label: label.to_string(),
detail: Some("directive".to_string()),
label_details: Some(label_details),
documentation: Some(Documentation::MarkupContent(function_profile)),
kind: Some(CompletionItemKind::FUNCTION),
insert_text: Some(snippet_code.to_string()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
insert_text_mode: Some(InsertTextMode::ADJUST_INDENTATION),
..CompletionItem::default()
});
}
completion_items
}

View File

@ -2,16 +2,11 @@ use std::{fs, path::PathBuf, str::FromStr};
use log::info; use log::info;
use ropey::RopeSlice; use ropey::RopeSlice;
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionList, Documentation, MarkupContent, Position, Url}; use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionList, Position, Url};
use crate::{server::LspServer, utils::{is_character_ordered_match, resolve_path, to_escape_path}}; use crate::{server::LSPServer, utils::{resolve_path, to_escape_path}};
/// 补全 include 内部的系统路径 pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Option<CompletionList> {
pub fn include_path_completion(
uri: &Url,
line: &RopeSlice,
pos: Position
) -> Option<CompletionList> {
let line_text = line.as_str().unwrap_or(""); let line_text = line.as_str().unwrap_or("");
if line_text.trim().starts_with("`include") { if line_text.trim().starts_with("`include") {
let character = pos.character as usize; let character = pos.character as usize;
@ -63,26 +58,16 @@ pub fn include_path_completion(
let file_name = file_name.unwrap(); let file_name = file_name.unwrap();
if path.is_dir() { if path.is_dir() {
let label_details = CompletionItemLabelDetails {
description: Some("directory".to_string()),
..Default::default()
};
completion_items.push(CompletionItem { completion_items.push(CompletionItem {
label: file_name.to_string(), label: file_name.to_string(),
label_details: Some(label_details),
kind: Some(CompletionItemKind::FOLDER), kind: Some(CompletionItemKind::FOLDER),
..CompletionItem::default() ..CompletionItem::default()
}); });
} }
if path.is_file() { if path.is_file() {
let label_details = CompletionItemLabelDetails {
description: Some("file".to_string()),
..Default::default()
};
completion_items.push(CompletionItem { completion_items.push(CompletionItem {
label: file_name.to_string(), label: file_name.to_string(),
label_details: Some(label_details),
kind: Some(CompletionItemKind::FILE), kind: Some(CompletionItemKind::FILE),
..CompletionItem::default() ..CompletionItem::default()
}); });
@ -102,95 +87,8 @@ pub fn include_path_completion(
None None
} }
/// 补全宏
pub fn vlog_directives_completion(
token: &str,
server: &LspServer
) -> Option<CompletionList> {
// 先把固定宏定义比如 include, define 这种的放入其中
let mut completion_items = server.vlog_directives.clone();
// 再从每个文件中搜集定义的宏
let hdl_param = server.db.hdl_param.clone();
let path_to_hdl_file = hdl_param.path_to_hdl_file.read().unwrap();
// 遍历所有的 defines
for hdl_file in path_to_hdl_file.values() {
for define in &hdl_file.fast.fast_macro.defines {
if is_character_ordered_match(token, &define.name) {
let label_details = CompletionItemLabelDetails {
description: Some("`define".to_string()),
..Default::default()
};
let documentation = Documentation::MarkupContent(MarkupContent {
kind: tower_lsp::lsp_types::MarkupKind::Markdown,
value: format!("```verilog\n`define {} {}\n```", define.name.trim(), define.replacement.trim())
});
completion_items.push(CompletionItem {
label: define.name.to_string(),
kind: Some(CompletionItemKind::CONSTANT),
detail: Some("macro".to_string()),
// 用户定义的宏默认最高优先级
sort_text: Some("0".to_string()),
label_details: Some(label_details),
documentation: Some(documentation),
..CompletionItem::default()
});
}
}
}
Some(CompletionList {
is_incomplete: false,
items: completion_items
})
}
/// 不是由 ` 触发的宏补全
pub fn vlog_directives_completion_without_prefix(
token: &str,
server: &LspServer
) -> Vec<CompletionItem> {
// 先把固定宏定义比如 include, define 这种的放入其中
let mut completion_items = server.vlog_directives.clone();
// 再从每个文件中搜集定义的宏
let hdl_param = server.db.hdl_param.clone();
let path_to_hdl_file = hdl_param.path_to_hdl_file.read().unwrap();
// 遍历所有的 defines
for hdl_file in path_to_hdl_file.values() {
for define in &hdl_file.fast.fast_macro.defines {
if is_character_ordered_match(token, &define.name) {
let label_details = CompletionItemLabelDetails {
description: Some("`define".to_string()),
..Default::default()
};
let documentation = Documentation::MarkupContent(MarkupContent {
kind: tower_lsp::lsp_types::MarkupKind::Markdown,
value: format!("```verilog\n`define {} {}\n```", define.name.trim(), define.replacement.trim())
});
completion_items.push(CompletionItem {
label: define.name.to_string(),
kind: Some(CompletionItemKind::CONSTANT),
insert_text: Some(format!("`{}", define.name)),
detail: Some("macro".to_string()),
label_details: Some(label_details),
documentation: Some(documentation),
// 用户定义的宏默认最高优先级
sort_text: Some("0".to_string()),
..CompletionItem::default()
});
}
}
}
completion_items
}
pub fn get_dot_completion( pub fn get_dot_completion(
server: &LspServer, server: &LSPServer,
line: &RopeSlice, line: &RopeSlice,
url: &Url, url: &Url,
pos: &Position, pos: &Position,
@ -244,7 +142,7 @@ fn is_port_completion(line: &RopeSlice, pos: &Position) -> bool {
} }
fn get_position_port_param_completion( fn get_position_port_param_completion(
server: &LspServer, server: &LSPServer,
#[allow(unused)] #[allow(unused)]
line: &RopeSlice, line: &RopeSlice,
url: &Url, url: &Url,
@ -253,74 +151,41 @@ fn get_position_port_param_completion(
language_id: &str language_id: &str
) -> Option<CompletionList> { ) -> Option<CompletionList> {
// 判断在不在一个模块内,并获取这个模块 // 判断在不在一个模块内,并获取这个模块
let hdl_param = &server.db.hdl_param; let hdl_param = &server.srcs.hdl_param;
let fast_map = hdl_param.path_to_hdl_file.read().unwrap(); let fast_map = hdl_param.path_to_hdl_file.read().unwrap();
let path = PathBuf::from_str(url.path()).unwrap(); let path = PathBuf::from_str(url.path()).unwrap();
let path = to_escape_path(&path); let path = to_escape_path(&path);
let path_string = path.to_str().unwrap(); let path_string = path.to_str().unwrap();
// info!("enter get_position_port_param_completion, pos: {pos:?}"); info!("enter get_position_port_param_completion, pos: {pos:?}");
if let Some(hdl_file) = fast_map.get(path_string) { if let Some(hdl_file) = fast_map.get(path_string) {
// info!("find hdl_file, content: {:?}", hdl_file.fast.content); info!("find hdl_file, content: {:?}", hdl_file.fast.content);
// 在当前文件的 fast 中寻找 // 在当前文件的 fast 中寻找
for module in &hdl_file.fast.content { for module in &hdl_file.fast.content {
for instance in &module.instances { for instance in &module.instances {
if let Some(_) = &instance.instparams { if let Some(param_range) = &instance.instparams {
if instance.gen_dot_completion_param_range().contains(pos) { let mut param_range = param_range.clone();
param_range.affine(-1, -1);
if param_range.contains(pos) {
// 补全当前 module 的所有 param // 补全当前 module 的所有 param
if let Some((inst_module, file_type, _)) = hdl_param.find_module_context_by_name(&instance.inst_type) { let inst_module = hdl_param.find_module_by_name(&instance.inst_type);
if inst_module.is_some() {
let inst_module = inst_module.unwrap();
let mut completion_items = Vec::<CompletionItem>::new(); let mut completion_items = Vec::<CompletionItem>::new();
match file_type.as_str() { for param in inst_module.params {
"primitives" => { let label_details = CompletionItemLabelDetails {
if let Some(primitives_inst) = inst_module.instances.first() { detail: Some("parameter".to_string()),
for param_assignment in &primitives_inst.intstparam_assignments { ..CompletionItemLabelDetails::default()
let name = param_assignment.parameter.clone().unwrap(); };
let param_desc = format!("parameter {}", name);
let label_details = CompletionItemLabelDetails { let param_desc = make_param_desc(&param);
description: Some("parameter".to_string()), let c_item = CompletionItem {
..Default::default() label: param.name,
}; detail: Some(param_desc),
label_details: Some(label_details),
let c_item = CompletionItem { kind: Some(CompletionItemKind::TYPE_PARAMETER),
label: name, ..CompletionItem::default()
detail: Some(param_desc), };
label_details: Some(label_details), completion_items.push(c_item);
kind: Some(CompletionItemKind::TYPE_PARAMETER),
..CompletionItem::default()
};
completion_items.push(c_item);
}
}
}
_ => {
for param in inst_module.params {
let label_details = CompletionItemLabelDetails {
description: Some("parameter".to_string()),
..Default::default()
};
let param_desc = match file_type.as_str() {
"common" => {
param.to_vlog_description()
}
"ip" => {
param.to_vhdl_description()
}
_ => {
param.to_vlog_description()
}
};
let c_item = CompletionItem {
label: param.name,
detail: Some(param_desc),
label_details: Some(label_details),
kind: Some(CompletionItemKind::TYPE_PARAMETER),
..CompletionItem::default()
};
completion_items.push(c_item);
}
}
} }
return Some(CompletionList { return Some(CompletionList {
is_incomplete: false, is_incomplete: false,
@ -331,62 +196,27 @@ fn get_position_port_param_completion(
} }
if instance.instports.is_some() { if instance.instports.is_some() {
let port_range = instance.gen_dot_completion_port_range(); let port_range = instance.gen_dot_completion_port_range();
if port_range.contains(pos) { if port_range.contains(pos) {
if let Some((inst_module, file_type, _)) = hdl_param.find_module_context_by_name(&instance.inst_type) { let inst_module = hdl_param.find_module_by_name(&instance.inst_type);
if inst_module.is_some() {
let inst_module = inst_module.unwrap();
let mut completion_items = Vec::<CompletionItem>::new(); let mut completion_items = Vec::<CompletionItem>::new();
match file_type.as_str() { for port in inst_module.ports {
"primitives" => { let label_details = CompletionItemLabelDetails {
if let Some(primitives_inst) = inst_module.instances.first() { detail: Some("port".to_string()),
for port_assignment in &primitives_inst.intstport_assignments { ..CompletionItemLabelDetails::default()
let label_details = CompletionItemLabelDetails { };
detail: Some("port".to_string()),
..CompletionItemLabelDetails::default()
};
let name = port_assignment.port.clone().unwrap(); let param_desc = make_port_desc(&port);
let port_desc = format!("port {}", name); let c_item = CompletionItem {
label: port.name,
let c_item = CompletionItem { detail: Some(param_desc),
label: name, label_details: Some(label_details),
detail: Some(port_desc), kind: Some(CompletionItemKind::PROPERTY),
label_details: Some(label_details), ..CompletionItem::default()
kind: Some(CompletionItemKind::PROPERTY), };
..CompletionItem::default() completion_items.push(c_item);
};
completion_items.push(c_item);
}
}
}
_ => {
for port in inst_module.ports {
let label_details = CompletionItemLabelDetails {
description: Some("port".to_string()),
..Default::default()
};
let port_desc = match file_type.as_str() {
"common" => {
port.to_vlog_description()
}
"ip" => {
port.to_vhdl_description()
}
_ => {
port.to_vlog_description()
}
};
let c_item = CompletionItem {
label: port.name,
detail: Some(port_desc),
label_details: Some(label_details),
kind: Some(CompletionItemKind::PROPERTY),
..CompletionItem::default()
};
completion_items.push(c_item);
}
}
} }
return Some(CompletionList { return Some(CompletionList {
is_incomplete: false, is_incomplete: false,
@ -400,3 +230,36 @@ fn get_position_port_param_completion(
} }
None None
} }
fn make_port_desc(port: &crate::core::hdlparam::Port) -> String {
let mut port_desc_array = Vec::<String>::new();
port_desc_array.push(port.dir_type.to_string());
if port.net_type != "unknown" {
port_desc_array.push(port.net_type.to_string());
}
if port.signed != "unsigned" {
port_desc_array.push("signed".to_string());
}
if port.width != "1" {
port_desc_array.push(port.width.to_string());
}
port_desc_array.push(port.name.to_string());
let port_desc = port_desc_array.join(" ");
port_desc
}
fn make_param_desc(param: &crate::core::hdlparam::Parameter) -> String {
let mut param_desc_array = Vec::<String>::new();
param_desc_array.push(format!("parameter {}", param.name));
if param.init != "unknown" {
param_desc_array.push("=".to_string());
param_desc_array.push(param.init.to_string());
}
let param_desc = param_desc_array.join(" ");
param_desc
}

View File

@ -1,17 +1,12 @@
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
//
pub fn provide_keyword_completions(keywords: &[(&str, &str)]) -> Vec<CompletionItem> { pub fn keyword_completions(keywords: &[(&str, &str)]) -> Vec<CompletionItem> {
let mut items: Vec<CompletionItem> = Vec::new(); let mut items: Vec<CompletionItem> = Vec::new();
for key in keywords { for key in keywords {
let label_details = CompletionItemLabelDetails {
description: Some("keyword".to_string()),
..Default::default()
};
if key.1.is_empty() { if key.1.is_empty() {
items.push(CompletionItem { items.push(CompletionItem {
label: key.0.to_string(), label: key.0.to_string(),
label_details: Some(label_details),
kind: Some(CompletionItemKind::KEYWORD), kind: Some(CompletionItemKind::KEYWORD),
..CompletionItem::default() ..CompletionItem::default()
}); });
@ -19,7 +14,6 @@ pub fn provide_keyword_completions(keywords: &[(&str, &str)]) -> Vec<CompletionI
items.push(CompletionItem { items.push(CompletionItem {
label: key.0.to_string(), label: key.0.to_string(),
kind: Some(CompletionItemKind::KEYWORD), kind: Some(CompletionItemKind::KEYWORD),
label_details: Some(label_details),
insert_text: Some(key.1.to_string()), insert_text: Some(key.1.to_string()),
insert_text_format: Some(InsertTextFormat::SNIPPET), insert_text_format: Some(InsertTextFormat::SNIPPET),
..CompletionItem::default() ..CompletionItem::default()
@ -29,8 +23,18 @@ pub fn provide_keyword_completions(keywords: &[(&str, &str)]) -> Vec<CompletionI
items items
} }
/// (&str, &str, &str): (label, snippet, description) pub fn other_completions(tasks: &[&str]) -> Vec<CompletionItem> {
pub const VLOG_KEYWORDS: &[(&str, &str)] = &[ tasks
.iter()
.map(|x| CompletionItem {
label: x.to_string(),
kind: Some(CompletionItemKind::FUNCTION),
..CompletionItem::default()
})
.collect()
}
pub const KEYWORDS: &[(&str, &str)] = &[
("accept_on", ""), ("accept_on", ""),
("alias", ""), ("alias", ""),
("always", "always @($1) begin\nend"), ("always", "always @($1) begin\nend"),
@ -130,9 +134,9 @@ pub const VLOG_KEYWORDS: &[(&str, &str)] = &[
("import", ""), ("import", ""),
("incdir", ""), ("incdir", ""),
("include", "`include \"$1\""), ("include", "`include \"$1\""),
("initial", "initial begin\n\t$1\nend"), ("initial", ""),
("inout", "inout $1"), ("inout", ""),
("input", "input $1"), ("input", ""),
("inside", ""), ("inside", ""),
("instance", ""), ("instance", ""),
("int", ""), ("int", ""),
@ -169,7 +173,7 @@ pub const VLOG_KEYWORDS: &[(&str, &str)] = &[
("notif1", ""), ("notif1", ""),
("null", ""), ("null", ""),
("or", ""), ("or", ""),
("output", "output $1"), ("output", ""),
("package", "package $1;\nendpackage"), ("package", "package $1;\nendpackage"),
("packed", ""), ("packed", ""),
("parameter", ""), ("parameter", ""),
@ -281,102 +285,183 @@ pub const VLOG_KEYWORDS: &[(&str, &str)] = &[
("xor", ""), ("xor", ""),
]; ];
pub const VHDL_KEYWORDS: &[(&str, &str)] = &[ pub const SYS_TASKS: &[&str] = &[
("abs", ""), "finish",
("access", ""), "exit",
("after", ""), "fatal",
("alias", "alias $1 is $2;"), "warning",
("all", ""), "stop",
("and", ""), "error",
("architecture", "architecture $1 of $2 is\nbegin\n\t$3\nend $1;"), "info",
("array", "array $1 is $2;"), "realtime",
("assert", "assert $1 report $2 severity $3;"), "time",
("attribute", "attribute $1 : $2;"), "asserton",
("begin", "begin\n\t$1\nend;"), "assertkill",
("block", "block ($1) is\nbegin\n\t$2\nend block;"), "assertpasson",
("body", "body $1 is\nbegin\n\t$2\nend $1;"), "assertfailon",
("buffer", ""), "assertnonvacuouson",
("bus", ""), "stime",
("case", "case $1 is\n\twhen $2 => $3;\nend case;"), "printtimescale",
("component", "component $1 is\n\tport (\n\t\t$2\n\t);\nend component;"), "timeformat",
("configuration", "configuration $1 of $2 is\nfor $3\n\t$4\nend for;\nend $1;"), "bitstoreal",
("constant", "constant $1 : $2 := $3;"), "bitstoshortreal",
("disconnect", "disconnect $1 after $2;"), "itor",
("downto", ""), "signed",
("else", ""), "cast",
("elsif", ""), "realtobits",
("end", ""), "shortrealtobits",
("entity", "entity $1 is\n\tport (\n\t\t$2\n\t);\nend $1;"), "rtoi",
("exit", "exit $1 when $2;"), "unsigned",
("file", "file $1 : $2;"), "sampled",
("for", "for $1 in $2 loop\n\t$3\nend loop;"), "fell",
("function", "function $1 return $2 is\nbegin\n\t$3\nend $1;"), "changed",
("generate", "generate\n\t$1\nend generate;"), "past_gclk",
("generic", "generic (\n\t$1\n);"), "fell_gclk",
("group", "group $1 : $2 ($3);"), "changed_gclk",
("guarded", ""), "rising_gclk",
("if", "if $1 then\n\t$2\nend if;"), "steady_gclk",
("impure", ""), "bits",
("in", ""), "typename",
("inertial", ""), "isunbounded",
("inout", ""), "coverage_control",
("is", ""), "coverage_get",
("label", ""), "coverage_save",
("library", "library $1;"), "set_coverage_db_name",
("linkage", ""), "dimensions",
("literal", ""), "right",
("loop", "loop\n\t$1\nend loop;"), "high",
("map", "map ($1 => $2);"), "size",
("mod", ""), "random",
("nand", ""), "dist_erlang",
("new", ""), "dist_normal",
("next", "next $1 when $2;"), "dist_t",
("nor", ""), "asin",
("not", ""), "acos",
("null", "null;"), "atan",
("of", ""), "atan2",
("on", ""), "hypot",
("open", ""), "sinh",
("or", ""), "cosh",
("others", ""), "tanh",
("out", ""), "asinh",
("package", "package $1 is\n\t$2\nend $1;"), "acosh",
("port", "port (\n\t$1\n);"), "atanh",
("postponed", ""), "q_initialize",
("procedure", "procedure $1 is\nbegin\n\t$2\nend $1;"), "q_remove",
("process", "process ($1) is\nbegin\n\t$2\nend process;"), "q_exam",
("pure", ""), "q_add",
("range", ""), "q_full",
("record", "record\n\t$1\nend record;"), "async$and$array",
("register", ""), "async$nand$array",
("reject", ""), "async$or$array",
("rem", ""), "async$nor$array",
("report", "report $1 severity $2;"), "sync$and$array",
("return", "return $1;"), "sync$nand$array",
("rol", ""), "sync$or$array",
("ror", ""), "sync$nor$array",
("select", "select\n\t$1\nend select;"), "countones",
("severity", ""), "onehot0",
("signal", "signal $1 : $2 := $3;"), "fatal",
("shared", ""), "warning",
("sla", ""), "dist_chi_square",
("sll", ""), "dist_exponential",
("sra", ""), "dist_poisson",
("srl", ""), "dist_uniform",
("subtype", "subtype $1 is $2;"), "countbits",
("then", ""), "onehot",
("to", ""), "isunknown",
("transport", ""), "coverage_get_max",
("type", "type $1 is $2;"), "coverage_merge",
("unaffected", ""), "get_coverage",
("units", "units $1;\n\t$2\nend units;"), "load_coverage_db",
("until", ""), "clog2",
("use", "use $1;"), "ln",
("variable", "variable $1 : $2 := $3;"), "log10",
("wait", "wait on $1;"), "exp",
("when", ""), "sqrt",
("while", "while $1 loop\n\t$2\nend loop;"), "pow",
("with", "with $1 select\n\t$2\nend select;"), "floor",
("xnor", ""), "ceil",
("xor", ""), "sin",
]; "cos",
"tan",
"rose",
"stable",
"past",
"rose_gclk",
"stable_gclk",
"future_gclk",
"falling_gclk",
"changing_gclk",
"unpacked_dimensions",
"left",
"low",
"increment",
"assertoff",
"assertcontrol",
"assertpassoff",
"assertfailoff",
"assertvacuousoff",
"error",
"info",
"async$and$plane",
"async$nand$plane",
"async$or$plane",
"async$nor$plane",
"sync$and$plane",
"sync$nand$plane",
"sync$or$plane",
"sync$nor$plane",
"system",
"countdrivers",
"getpattern",
"incsave",
"input",
"key",
"list",
"log",
"nokey",
"nolog",
"reset",
"reset_count",
"reset_value",
"restart",
"save",
"scale",
"scope",
"showscopes",
"showvars",
"sreadmemb",
"sreadmemh",
];
pub const DIRECTIVES: &[&str] = &[
"__FILE__",
"__LINE__",
"begin_keywords",
"celldefine",
"default_nettype",
"define",
"else",
"elsif",
"end_keywords",
"endcelldefine",
"endif",
"ifdef",
"ifndef",
"include",
"line",
"nounconnected_drive",
"pragma",
"resetall",
"timescale",
"unconnected_drive",
"undef",
"undefineall",
"default_decay_time",
"default_trireg_strength",
"delay_mode_distributed",
"delay_mode_path",
"delay_mode_unit",
"delay_mode_zero",
];

View File

@ -1,17 +1,13 @@
use crate::{server::LspServer, utils::get_language_id_by_uri}; use crate::{server::LSPServer, utils::get_language_id_by_uri};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
pub mod keyword; pub mod keyword;
pub mod feature; pub mod feature;
pub mod sys_tasks;
pub mod directives;
pub use sys_tasks::provide_vlog_sys_tasks_completions;
mod vhdl; mod vhdl;
mod sv; mod sv;
impl LspServer { impl LSPServer {
pub fn completion(&self, params: CompletionParams) -> Option<CompletionResponse> { pub fn completion(&self, params: CompletionParams) -> Option<CompletionResponse> {
let language_id = get_language_id_by_uri(&params.text_document_position.text_document.uri); let language_id = get_language_id_by_uri(&params.text_document_position.text_document.uri);
match language_id.as_str() { match language_id.as_str() {

View File

@ -1,153 +1,205 @@
use crate::{completion::feature::{get_dot_completion, include_path_completion}, core, hover::feature::make_module_profile_code, server::LspServer, sources::LSPSupport, utils::{from_uri_to_escape_path_string, get_definition_token, get_language_id_by_uri, is_character_ordered_match}}; use crate::{completion::feature::{get_dot_completion, include_path_completion}, hover::feature::make_module_profile_code, server::LSPServer, sources::LSPSupport, utils::get_language_id_by_uri};
#[allow(unused)]
use log::info; use log::info;
use ropey::{Rope, RopeSlice};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use super::feature::{vlog_directives_completion, vlog_directives_completion_without_prefix};
pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option<CompletionResponse> {
pub fn completion(server: &LspServer, params: &CompletionParams) -> Option<CompletionResponse> {
let doc = &params.text_document_position; let doc = &params.text_document_position;
let uri = &params.text_document_position.text_document.uri; let uri = &params.text_document_position.text_document.uri;
let pos = doc.position; let pos = doc.position;
let language_id = get_language_id_by_uri(uri); let language_id = get_language_id_by_uri(uri);
let path_string = from_uri_to_escape_path_string(uri).unwrap(); let file_id = server.srcs.get_id(uri).to_owned();
server.srcs.wait_parse_ready(file_id, false);
let file = server.srcs.get_file(file_id)?;
let file = file.read().ok()?;
let line_text = file.text.line(doc.position.line as usize);
let token = get_completion_token(
&file.text,
line_text.clone(),
doc.position,
);
// 等待解析完成 // info!("trigger completion token: {}", token);
server.db.wait_parse_ready(&path_string, false); let line_text = file.text.line(pos.line as usize);
let source = server.db.get_source(&path_string)?;
let source = source.read().ok()?;
// 获取当前这行的结果和当前光标所在的 token
let line_text = source.text.line(doc.position.line as usize);
let token = get_definition_token(&line_text, doc.position);
let response = match &params.context { let response = match &params.context {
Some(context) => match context.trigger_kind { Some(context) => match context.trigger_kind {
// 特殊字符触发 // 特殊字符触发
CompletionTriggerKind::TRIGGER_CHARACTER => { CompletionTriggerKind::TRIGGER_CHARACTER => {
// info!("trigger character");
let trigger_character = context.trigger_character.clone().unwrap(); let trigger_character = context.trigger_character.clone().unwrap();
match trigger_character.as_str() { match trigger_character.as_str() {
"." => { "." => {
// 用户按下 . 如果是在例化 scope 中,则补全对应的 port info!("trigger dot completion");
get_dot_completion(server, &line_text, uri, &pos, &language_id) get_dot_completion(server, &line_text, uri, &pos, &language_id)
}, },
"$" => { "$" => Some(CompletionList {
// 用户按下 $ 补全系统函数 is_incomplete: false,
Some(CompletionList { items: server.sys_tasks.clone(),
is_incomplete: false, }),
items: server.vlog_sys_tasks_completion_items.clone(), "`" => Some(CompletionList {
}) is_incomplete: false,
}, items: server.directives.clone(),
"`" => { }),
// 用户按下 ` , 补全系统宏定义和用户自己的宏定义
vlog_directives_completion(&token, server)
},
"/" => { "/" => {
// 用户按下 / ,如果在 "" 内触发,路径的自动补全 info!("trigger include");
include_path_completion(&doc.text_document.uri, &line_text, pos) include_path_completion(&doc.text_document.uri, &line_text, pos)
}, },
"\"" => { "\"" => {
// 用户按下 " ,如果开头有 include则自动补全 info!("trigger include");
include_path_completion(&doc.text_document.uri, &line_text, pos) include_path_completion(&doc.text_document.uri, &line_text, pos)
} }
_ => None, _ => None,
} }
} }
// 对于上一次自动补全结果 is_incomplete: true 的操作,还会额外触发一次自动补全,
// 它的逻辑在这个分支里面
CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None, CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None,
// 常规触发 // 常规触发
CompletionTriggerKind::INVOKED => { CompletionTriggerKind::INVOKED => {
// 1. 先根据 AST 获取上下文补全项 let mut comps = server.srcs.get_completions(
// 去除如下几种情况module textmacro
let mut completion_items = server.db.get_completions(
&token, &token,
source.text.pos_to_byte(&doc.position), file.text.pos_to_byte(&doc.position),
&doc.text_document.uri, &doc.text_document.uri,
)?; )?;
// info!("current completion token: {}", token); info!("current completion token: {}", token);
// 2. 根据 token 加入关键词 // complete keywords
// TODO: 考虑使用前缀树进行优化 comps.items.extend::<Vec<CompletionItem>>(
completion_items.items.extend::<Vec<CompletionItem>>( server.key_comps
server.vlog_keyword_completion_items
.iter() .iter()
.filter(|x| is_character_ordered_match(&token, &x.label)) .filter(|x| x.label.starts_with(&token))
.cloned()
.collect(),
);
// 3. 根据 token 加入系统函数
// TODO: 考虑使用前缀树进行优化
completion_items.items.extend::<Vec<CompletionItem>>(
server.vlog_sys_tasks_completion_items
.iter()
.filter(|x| is_character_ordered_match(&token, &x.label))
.cloned() .cloned()
.collect(), .collect(),
); );
// 4. 加入例化自动补全的 // 加入例化自动补全的
completion_items.items.extend::<Vec<CompletionItem>>( comps.items.extend::<Vec<CompletionItem>>(
make_module_completions(server, &token, &language_id) make_module_completions(server, &token, &language_id)
); );
// 5. 加入宏的自动补全 comps.items.dedup_by_key(|i| i.label.clone());
completion_items.items.extend::<Vec<CompletionItem>>(
vlog_directives_completion_without_prefix(&token, server)
);
// 不知道为什么会有重复,去重就完事
completion_items.items.dedup_by_key(|i| i.label.clone()); // info!("invoked return comps {:?}", comps);
Some(comps)
// info!("invoked return completion_items {:?}", completion_items);
Some(completion_items)
} }
_ => None, _ => None,
}, },
None => { None => {
return None; let trigger = prev_char(&file.text, &doc.position);
match trigger {
'.' => Some(server.srcs.get_dot_completions(
token.trim_end_matches('.'),
file.text.pos_to_byte(&doc.position),
&doc.text_document.uri,
)?),
'$' => Some(CompletionList {
is_incomplete: false,
items: server.sys_tasks.clone(),
}),
'`' => Some(CompletionList {
is_incomplete: false,
items: server.directives.clone(),
}),
_ => {
let mut comps = server.srcs.get_completions(
&token,
file.text.pos_to_byte(&doc.position),
&doc.text_document.uri,
)?;
info!("current completion token: {}", token);
comps.items.extend::<Vec<CompletionItem>>(
server.key_comps
.iter()
.filter(|x| x.label.starts_with(&token))
.cloned()
.collect(),
);
comps.items.dedup_by_key(|i| i.label.clone());
Some(comps)
}
}
} }
}; };
// eprintln!("comp response: {}", now.elapsed().as_millis());
Some(CompletionResponse::List(response?)) Some(CompletionResponse::List(response?))
} }
fn make_primitives_instantiation_code(text: &str) -> String {
let mut instantiations = text.lines()
.filter(|line| !line.trim().is_empty() && !line.trim().starts_with("//"))
.collect::<Vec<&str>>();
// remove fake module and endmodule
instantiations.remove(0);
instantiations.pop();
instantiations.iter().map(|line| { /// get the previous non-whitespace character
let trimmed = line.trim_start(); fn prev_char(text: &Rope, pos: &Position) -> char {
if trimmed.contains('.') { let char_idx = text.pos_to_char(pos);
format!("\t{}", trimmed) if char_idx > 0 {
} else { for i in (0..char_idx).rev() {
trimmed.to_string() let res = text.char(i);
if !res.is_whitespace() {
return res;
}
} }
}).collect::<Vec<String>>().join("\n") ' '
} } else {
' '
fn make_primitives_module_profile_code(text: &str) -> String {
let mut lines: Vec<&str> = text.split_inclusive('\n').collect();
if lines.len() > 1 {
lines.remove(0);
lines.pop();
} }
lines.join("")
} }
/// attempt to get the token the user was trying to complete, by
/// filtering out characters unneeded for name resolution
fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String {
let mut token = String::new();
let mut line_iter = line.chars();
for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) {
line_iter.next();
}
let mut c = line_iter.prev();
//TODO: make this a regex
while c.is_some()
&& (c.unwrap().is_alphanumeric()
|| c.unwrap() == '_'
|| c.unwrap() == '.'
|| c.unwrap() == '['
|| c.unwrap() == ']')
{
token.push(c.unwrap());
c = line_iter.prev();
}
let mut result: String = token.chars().rev().collect();
if result.contains('[') {
let l_bracket_offset = result.find('[').unwrap_or(result.len());
result.replace_range(l_bracket_offset.., "");
}
if &result == "." {
// probably a instantiation, the token should be what we're instatiating
let mut char_iter = text.chars();
let mut token = String::new();
for _ in 0..text.pos_to_char(&pos) {
char_iter.next();
}
let mut c = char_iter.prev();
// go to the last semicolon
while c.is_some() && (c.unwrap() != ';') {
c = char_iter.prev();
}
// go the the start of the next symbol
while c.is_some() && !(c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
c = char_iter.next();
}
// then extract the next symbol
while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') {
token.push(c.unwrap());
c = char_iter.next();
}
token
} else {
result
}
}
fn make_instantiation_code(module: &crate::core::hdlparam::Module) -> String { fn make_instantiation_code(module: &crate::core::hdlparam::Module) -> String {
// makeVlogParamAssignments 参数信息列表 // makeVlogParamAssignments 参数信息列表
@ -209,115 +261,51 @@ fn make_instantiation_code(module: &crate::core::hdlparam::Module) -> String {
snippet_codes.join("") snippet_codes.join("")
} }
/// 自动补全例化模块
fn make_module_completions( fn make_module_completions(
server: &LspServer, server: &LSPServer,
token: &str, token: &str,
language_id: &str language_id: &str
) -> Vec<CompletionItem> { ) -> Vec<CompletionItem> {
let mut module_completioms = Vec::<CompletionItem>::new(); let mut module_completioms = Vec::<CompletionItem>::new();
let hdl_param = server.db.hdl_param.clone();
let prefix = token.to_string().to_lowercase(); let prefix = token.to_string().to_lowercase();
let module_name_to_path = hdl_param.module_name_to_path.read().unwrap(); let path_to_files = server.srcs.hdl_param.path_to_hdl_file.read().unwrap();
// 获取和自动补全相关的配置
let auto_add_output_declaration = server.db.get_lsp_configuration_bool_value("digital-ide.function.lsp.completion.vlog.auto-add-output-declaration").unwrap_or(true);
// 遍历 hdlparam 中所有的 modules // 遍历 hdlparam 中所有的 modules
for module_name in module_name_to_path.keys() { for (path_string, hdl_file) in path_to_files.iter() {
if !module_name.to_string().to_lowercase().starts_with(&prefix) { for module in &hdl_file.fast.content {
continue; if !module.name.to_string().to_lowercase().starts_with(&prefix) {
} continue;
if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(&module_name) {
let mut insert_text = Vec::<String>::new();
if auto_add_output_declaration {
if let Some(declaration_string) = make_output_declaration(&module) {
insert_text.push(declaration_string);
}
} }
let insert_text = make_instantiation_code(module);
let module_profile = make_module_profile_code(module);
let (insert_text, module_profile, define_info) = if file_type == "primitives" { let path_uri = Url::from_file_path(path_string.to_string()).unwrap().to_string();
let primitive_map = server.db.primitive_text.name_to_text.read().unwrap(); let def_row = module.range.start.line;
if let Some(text) = primitive_map.get(&module.name) { let def_col = module.range.start.character;
insert_text.push(make_primitives_instantiation_code(text)); let define_info = format!("Go to [Definition]({path_uri}#L{def_row}:{def_col})");
(
insert_text.join("\n"),
make_primitives_module_profile_code(text),
format!("[Definition] Primitive module: {}", module.name)
)
} else {
continue;
}
} else {
insert_text.push(make_instantiation_code(&module));
let path_uri = Url::from_file_path(def_path).unwrap().to_string();
let def_row = module.range.start.line;
let def_col = module.range.start.character;
(
insert_text.join("\n"),
make_module_profile_code(&module),
format!("Go to [Definition]({path_uri}#L{def_row}:{def_col})")
)
};
let module_profile = MarkupContent { let module_profile = MarkupContent {
kind: MarkupKind::Markdown, kind: MarkupKind::Markdown,
value: format!("```{}\n{}\n```\n{}", language_id, module_profile, define_info) value: format!("```{}\n{}\n```\n{}", language_id, module_profile, define_info)
}; };
let detail = format!("module instantiation ({})", file_type);
let label_details = CompletionItemLabelDetails {
description: Some("module instantiation".to_string()),
..Default::default()
};
let item = CompletionItem { let item = CompletionItem {
label: module.name.to_string(), label: module.name.to_string(),
detail: Some(detail), detail: Some("module instantiation".to_string()),
label_details: Some(label_details),
documentation: Some(Documentation::MarkupContent(module_profile)), documentation: Some(Documentation::MarkupContent(module_profile)),
kind: Some(CompletionItemKind::CLASS), kind: Some(CompletionItemKind::CLASS),
insert_text: Some(insert_text), insert_text: Some(insert_text),
// 给模块例化自动补全附上最高权重 // 给模块例化自动补全附上最高权重
sort_text: Some("0".to_string()), sort_text: Some("0001".to_string()),
insert_text_format: Some(InsertTextFormat::SNIPPET), insert_text_format: Some(InsertTextFormat::SNIPPET),
insert_text_mode: Some(InsertTextMode::ADJUST_INDENTATION), insert_text_mode: Some(InsertTextMode::ADJUST_INDENTATION),
..CompletionItem::default() ..CompletionItem::default()
}; };
module_completioms.push(item); module_completioms.push(item);
} }
} }
module_completioms module_completioms
}
fn make_output_declaration(
module: &core::hdlparam::Module
) -> Option<String> {
let mut output_declaration = Vec::<String>::new();
for port in &module.ports {
if port.dir_type == "output" || port.dir_type == "out" {
let mut declaration = Vec::<String>::new();
if port.net_type == "reg" || port.net_type == "wire" {
declaration.push(port.net_type.to_string());
} else {
declaration.push("wire".to_string());
}
if port.width != "1" {
declaration.push(port.width.to_string());
}
declaration.push(port.name.to_string());
output_declaration.push(format!("{};", declaration.join(" ")));
}
}
if output_declaration.len() > 0 {
let output_declaration = format!("// output declaration of module {}\n{}\n", module.name, output_declaration.join("\n"));
Some(output_declaration)
} else {
None
}
} }

View File

@ -1,525 +0,0 @@
use tower_lsp::lsp_types::*;
/// 文档IEEE 1364-2005 page 308
/// author: LSTM-Kirigaya
/// date: 2024.12.03
/// Display and write tasks IEEE 1364-2005 17.1.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DISPLAY_WRITE_TASKS: &[(&str, &str, &str)] = &[
("display", "\\$display($1);", "打印格式化字符串到标准输出。\n```verilog\n$display(\"Hello, World!\");\n```"),
("displayb", "\\$displayb($1);", "以二进制格式打印表达式。\n```verilog\n$displayb(data);\n```"),
("displayo", "\\$displayo($1);", "以八进制格式打印表达式。\n```verilog\n$displayo(data);\n```"),
("displayh", "\\$displayh($1);", "以十六进制格式打印表达式。\n```verilog\n$displayh(data);\n```"),
("write", "\\$write($1);", "类似于 $display但不自动添加换行符。\n```verilog\n$write(\"Hello, World!\");\n```"),
("writeb", "\\$writeb($1);", "以二进制格式打印表达式,不自动添加换行符。\n```verilog\n$writeb(data);\n```"),
("writeo", "\\$writeo($1);", "以八进制格式打印表达式,不自动添加换行符。\n```verilog\n$writeo(data);\n```"),
("writeh", "\\$writeh($1);", "以十六进制格式打印表达式,不自动添加换行符。\n```verilog\n$writeh(data);\n```"),
];
/// Strobed monitoring IEEE 1364-2005 17.1.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_STROBED_MONITOR_TASKS: &[(&str, &str, &str)] = &[
("strobe", "\\$strobe($1);", "在当前时间步结束时打印格式化字符串。\n```verilog\n$strobe(\"Data: %d\", data);\n```"),
("strobeb", "\\$strobeb($1);", "在当前时间步结束时以二进制格式打印表达式。\n```verilog\n$strobeb(data);\n```"),
("strobeo", "\\$strobeo($1);", "在当前时间步结束时以八进制格式打印表达式。\n```verilog\n$strobeo(data);\n```"),
("strobeh", "\\$strobeh($1);", "在当前时间步结束时以十六进制格式打印表达式。\n```verilog\n$strobeh(data);\n```"),
];
/// Continuous monitoring tasks IEEE 1364-2005 17.1.3
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_CONTINUOUS_MONITORING_TASKS: &[(&str, &str, &str)] = &[
("monitor", "\\$monitor($1);", "监控变量并在变量变化时打印。\n```verilog\n$monitor(\"Data: %d\", data);\n```"),
("monitorb", "\\$monitorb($1);", "监控变量并在变量变化时以二进制格式打印。\n```verilog\n$monitorb(data);\n```"),
("monitoro", "\\$monitoro($1);", "监控变量并在变量变化时以八进制格式打印。\n```verilog\n$monitoro(data);\n```"),
("monitorh", "\\$monitorh($1);", "监控变量并在变量变化时以十六进制格式打印。\n```verilog\n$monitorh(data);\n```"),
// monitoron和monitoroff与后面的dumpon和dumpoff等保持一致不带无参括号
("monitoron", "\\$monitoron;", "启用监控任务。\n```verilog\n$monitoron();\n```"),
("monitoroff", "\\$monitoroff;", "关闭监控任务。\n```verilog\n$monitoroff();\n```"),
];
/// File input-output system tasks and functions IEEE 1364-2005 17.2.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_FILE_IO_TASKS: &[(&str, &str, &str)] = &[
("fopen", "\\$fopen($1, $2);", "打开文件并返回文件句柄。\n```verilog\ninteger file_handle;\n// 默认以 \"w\" 作为 flag如果 file_handle == 0文件打开失败file_handle == 1文件打开成功\nfile_handle = $fopen(\"data.bin\", \"r\");\n```\n\n其余文件描述符\n| 参数 | 描述 |\n|------|-------|\n| `r` 或 `rb` | 以只读方式打开文件 |\n| `w` 或 `wb` | 截断文件长度为零或创建新文件以进行写入 |\n| `a` 或 `ab` | 追加;在文件末尾打开以进行写入,或创建新文件以进行写入 |\n| `r+`、`r+b` 或 `rb+` | 以读写方式打开文件 |\n| `w+`、`w+b` 或 `wb+` | 截断文件或创建新文件以进行读写 |\n| `a+`、`a+b` 或 `ab+` | 追加;在文件末尾打开或创建新文件以进行读写 |"),
("fclose", "\\$fclose($1);", "关闭文件。\n```verilog\n$fclose(file_handle);\n```"),
];
/// File output system tasks IEEE 1364-2005 17.2.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_FILE_OUTPUT_TASKS: &[(&str, &str, &str)] = &[
("fdisplay", "\\$fdisplay($1, $2);", "将格式化字符串写入文件。\n```verilog\n$fdisplay(file_handle, \"Data: %d\", data);\n```"),
("fdisplayb", "\\$fdisplayb($1, $2);", "将二进制格式的表达式写入文件。\n```verilog\n$fdisplayb(file_handle, data);\n```"),
("fdisplayo", "\\$fdisplayo($1, $2);", "将八进制格式的表达式写入文件。\n```verilog\n$fdisplayo(file_handle, data);\n```"),
("fdisplayh", "\\$fdisplayh($1, $2);", "将十六进制格式的表达式写入文件。\n```verilog\n$fdisplayh(file_handle, data);\n```"),
("fwrite", "\\$fwrite($1, $2);", "将格式化字符串写入文件,不自动添加换行符。\n```verilog\n$fwrite(file_handle, \"Data: %d\", data);\n```"),
("fwriteb", "\\$fwriteb($1, $2);", "将二进制格式的表达式写入文件,不自动添加换行符。\n```verilog\n$fwriteb(file_handle, data);\n```"),
("fwriteo", "\\$fwriteo($1, $2);", "将八进制格式的表达式写入文件,不自动添加换行符。\n```verilog\n$fwriteo(file_handle, data);\n```"),
("fwriteh", "\\$fwriteh($1, $2);", "将十六进制格式的表达式写入文件,不自动添加换行符。\n```verilog\n$fwriteh(file_handle, data);\n```"),
("fstrobe", "\\$fstrobe($1, $2);", "在当前时间步结束时将格式化字符串写入文件。\n```verilog\n$fstrobe(file_handle, \"Data: %d\", data);\n```"),
("fstrobeb", "\\$fstrobeb($1, $2);", "在当前时间步结束时将二进制格式的表达式写入文件。\n```verilog\n$fstrobeb(file_handle, data);\n```"),
("fstrobeo", "\\$fstrobeo($1, $2);", "在当前时间步结束时将八进制格式的表达式写入文件。\n```verilog\n$fstrobeo(file_handle, data);\n```"),
("fstrobeh", "\\$fstrobeh($1, $2);", "在当前时间步结束时将十六进制格式的表达式写入文件。\n```verilog\n$fstrobeh(file_handle, data);\n```"),
("fmonitor", "\\$fmonitor($1, $2);", "监控变量并在变量变化时写入文件。\n```verilog\n$fmonitor(file_handle, \"Data: %d\", data);\n```"),
("fmonitorb", "\\$fmonitorb($1, $2);", "监控变量并在变量变化时将二进制格式的表达式写入文件。\n```verilog\n$fmonitorb(file_handle, data);\n```"),
("fmonitoro", "\\$fmonitoro($1, $2);", "监控变量并在变量变化时将八进制格式的表达式写入文件。\n```verilog\n$fmonitoro(file_handle, data);\n```"),
("fmonitorh", "\\$fmonitorh($1, $2);", "监控变量并在变量变化时将十六进制格式的表达式写入文件。\n```verilog\n$fmonitorh(file_handle, data);\n```"),
];
/// Formatting data to a string tasks IEEE 1364-2005 17.2.3
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_FORMATTING_TASKS: &[(&str, &str, &str)] = &[
("swrite", "\\$swrite($1, $2);", "将格式化字符串存储到字符串变量中,不自动添加换行符。\n```verilog\nstring formatted_string;\n$swrite(formatted_string, \"Data: %d\", data);\n```"),
("swriteb", "\\$swriteb($1, $2);", "将二进制格式的表达式存储到字符串变量中,不自动添加换行符。\n```verilog\nstring formatted_string;\n$swriteb(formatted_string, data);\n```"),
("swriteo", "\\$swriteo($1, $2);", "将八进制格式的表达式存储到字符串变量中,不自动添加换行符。\n```verilog\nstring formatted_string;\n$swriteo(formatted_string, data);\n```"),
("swriteh", "\\$swriteh($1, $2);", "将十六进制格式的表达式存储到字符串变量中,不自动添加换行符。\n```verilog\nstring formatted_string;\n$swriteh(formatted_string, data);\n```"),
("sformat", "\\$sformat($1, $2);", "将格式化字符串存储到字符串变量中。\n```verilog\nstring formatted_string;\n$sformat(formatted_string, \"Data: %d\", data);\n```"),
];
/// Reading data from a file tasks IEEE 1364-2005 17.2.4
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_FILE_READING_TASKS: &[(&str, &str, &str)] = &[
// 17.2.4.1 Reading a character at a time
("fgetc", "\\$fgetc($1);", "从文件中读取一个字符。\n```verilog\ninteger char;\nchar = $fgetc(file_handle);\n```"),
("ungetc", "\\$ungetc($1, $2);", "将字符推回到文件流中。\n```verilog\n$ungetc(char, file_handle);\n```"),
// 17.2.4.2 Reading a line at a time、
// 只有两个参数 char_num = $fgets(fbuf,fd);
("fgets", "\\$fgets($1, $2);", "从文件中读取一行数据。\n```verilog\nstring line;\n$fgets(line, file_handle);\n```"),
// 17.2.4.3 Reading formatted data
("fscanf", "\\$fscanf($1, $2, $3);", "从文件中读取格式化数据。\n```verilog\ninteger value;\n$fscanf(file_handle, \"%d\", value);\n```"),
("sscanf", "\\$sscanf($1, $2, $3);", "从字符串中读取格式化数据。\n```verilog\ninteger value;\n$sscanf(\"123\", \"%d\", value);\n```"),
// 17.2.4.4 Reading binary data
// integer <integer>;
// <integer> = $fread(<store><file_desc>);
// <integer> = $fread(<store><file_desc>, <start> );
// <integer> = $fread(<store><file_desc>, <start>, <count> );
// <integer> = $fread(<store><file_desc>, , <count> );
// integer整型数值返回本次 $fread 读取的真实字节数量当返回值为0 ,表示错误读取或者文件结束。
// store将二进制文件中的数据读取到寄存器或者二维数组中。
// file_desc为打开的文件句柄
// start: 为二维数组的起始地址
// count: 从起始地址开始, 写入二维数组的数量。
// fread
// integer fd;
// reg [7:0] fbuf [3:0];
// char_num = $fread(fbuf, fd, 0, 4); // 读取二进制文件中的数据存放到fbuf[0],fbuf[1],fbuf[2],fbuf[3]
// char_num = $fread(fbuf, fd, 1, 2); // 读取二进制文件中的数据存放到fbuf[1],fbuf[2]
("fread", "\\$fread($1, $2, $3, $4);", "读取二进制文件中的数据。\n```verilog\ninteger fd;\nreg [7:0] fbuf [3:0];\nchar_num = $fread(fbuf, fd, 0, 4); // 读取二进制文件中的数据存放到fbuf[0],fbuf[1],fbuf[2],fbuf[3]\nchar_num = $fread(fbuf, fd, 1, 2); // 读取二进制文件中的数据存放到fbuf[1],fbuf[2]\n```"),
];
/// File positioning tasks IEEE 1364-2005 17.2.5
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_FILE_POSITIONING_TASKS: &[(&str, &str, &str)] = &[
("fseek", "\\$fseek($1, $2, $3);", "设置文件指针位置。\n```verilog\n$fseek(file_handle, 0, 0);\n```"),
("ftell", "\\$ftell($1);", "返回文件指针的当前位置。\n```verilog\ninteger position;\nposition = $ftell(file_handle);\n```"),
("rewind", "\\$rewind($1);", "将文件指针重置到文件开头。\n```verilog\n$rewind(file_handle);\n```"),
];
/// Flushing output tasks IEEE 1364-2005 17.2.6
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_FLUSHING_OUTPUT_TASKS: &[(&str, &str, &str)] = &[
("fflush", "\\$fflush($1);", "刷新文件缓冲区。\n```verilog\n$fflush(file_handle);\n```"),
];
/// I/O error status tasks IEEE 1364-2005 17.2.7
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_IO_ERROR_STATUS_TASKS: &[(&str, &str, &str)] = &[
// err = $ferror(fd, str) ; str会提供更加详细的错误信息
// err 返回非零值表示错误, str 返回非零值存储错误类型, 官方建议 str 长度为 640bit 位宽
// 假如打开一个不存在的文件则err会得到00000002str则会得到No such file or directory
("ferror", "\\$ferror($1);", "检查文件读写错误。\n```verilog\ninteger error;\nerror = $ferror(file_handle);\n```"),
];
/// Detecting EOF tasks IEEE 1364-2005 17.2.8
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DETECTING_EOF_TASKS: &[(&str, &str, &str)] = &[
("feof", "\\$feof($1);", "检查文件是否到达文件末尾。\n```verilog\ninteger eof;\neof = $feof(file_handle);\n```"),
];
/// Loading memory data from a file tasks IEEE 1364-2005 17.2.9
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_LOADING_MEMORY_TASKS: &[(&str, &str, &str)] = &[
("readmemb", "\\$readmemb($1, $2);", "从文件中读取二进制数据到内存。\n```verilog\n$readmemb(\"data.bin\", memory);\n```"),
("readmemh", "\\$readmemh($1, $2);", "从文件中读取十六进制数据到内存。\n```verilog\n$readmemh(\"data.hex\", memory);\n```"),
];
/// Loading timing data from an SDF file tasks IEEE 1364-2005 17.2.10
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_LOADING_TIMING_TASKS: &[(&str, &str, &str)] = &[
// $sdf_annotate ("sdf_file"[, module_instance][,"sdf_configfile"][,"sdf_logfile"][,"mtm_spec"][,"scale_factors"][,"scale_type"]);
// 最简单的方式可以只需要两个参数,大概就这样就好?
// $sdf_annotate(“ring_oscillator.sdf”,ring_oscillator);
("sdf_annotate", "\\$sdf_annotate($1, $2);", "从 SDF 文件加载时序数据并应用于模块实例。\n```verilog\n$sdf_annotate(\"timing.sdf\", top_module);\n```"),
];
/// $printtimescale tasks IEEE 1364-2005 17.3.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_PRINTTIMESCALE_TASKS: &[(&str, &str, &str)] = &[
("printtimescale", "\\$printtimescale($1);", "打印指定模块的时间刻度信息。\n```verilog\n$printtimescale(module_instance);\n```"),
];
/// $timeformat tasks IEEE 1364-2005 17.3.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_TIMEFORMAT_TASKS: &[(&str, &str, &str)] = &[
("timeformat", "\\$timeformat($1, $2, $3, $4);", "设置时间格式。\n```verilog\n$timeformat(-9, 3, \" ns\", 8);\n```"),
];
/// $finish tasks IEEE 1364-2005 17.4.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_FINISH_TASKS: &[(&str, &str, &str)] = &[
("finish", "\\$finish($1);", "终止仿真。\n```verilog\n$finish(0);\n```"),
];
/// $stop tasks IEEE 1364-2005 17.4.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_STOP_TASKS: &[(&str, &str, &str)] = &[
("stop", "\\$stop($1);", "暂停仿真。\n```verilog\n$stop(0);\n```"),
];
/// Array types tasks IEEE 1364-2005 17.5.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_ARRAY_TYPES_TASKS: &[(&str, &str, &str)] = &[
("async$and$array", "\\$async\\$and\\$array($1, $2);", "对数组进行异步与操作。\n```verilog\n$async$and$array(array1, array2);\n```"),
("async$nand$array", "\\$async\\$nand\\$array($1, $2);", "对数组进行异步与非操作。\n```verilog\n$async$nand$array(array1, array2);\n```"),
("async$or$array", "\\$async\\$or\\$array($1, $2);", "对数组进行异步或操作。\n```verilog\n$async$or$array(array1, array2);\n```"),
("async$nor$array", "\\$async\\$nor\\$array($1, $2);", "对数组进行异步或非操作。\n```verilog\n$async$nor$array(array1, array2);\n```"),
("sync$and$array", "\\$sync\\$and\\$array($1, $2);", "对数组进行同步与操作。\n```verilog\n$sync$and$array(array1, array2);\n```"),
("sync$nand$array", "\\$sync\\$nand\\$array($1, $2);", "对数组进行同步与非操作。\n```verilog\n$sync$nand$array(array1, array2);\n```"),
("sync$or$array", "\\$sync\\$or\\$array($1, $2);", "对数组进行同步或操作。\n```verilog\n$sync$or$array(array1, array2);\n```"),
("sync$nor$array", "\\$sync\\$nor\\$array($1, $2);", "对数组进行同步或非操作。\n```verilog\n$sync$nor$array(array1, array2);\n```"),
];
/// Array logic types tasks IEEE 1364-2005 17.5.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_ARRAY_LOGIC_TYPES_TASKS: &[(&str, &str, &str)] = &[
("async$and$plane", "\\$async\\$and\\$plane($1, $2);", "对数组平面进行异步与操作。\n```verilog\n$async$and$plane(plane1, plane2);\n```"),
("async$nand$plane", "\\$async\\$nand\\$plane($1, $2);", "对数组平面进行异步与非操作。\n```verilog\n$async$nand$plane(plane1, plane2);\n```"),
("async$or$plane", "\\$async\\$or\\$plane($1, $2);", "对数组平面进行异步或操作。\n```verilog\n$async$or$plane(plane1, plane2);\n```"),
("async$nor$plane", "\\$async\\$nor\\$plane($1, $2);", "对数组平面进行异步或非操作。\n```verilog\n$async$nor$plane(plane1, plane2);\n```"),
("sync$and$plane", "\\$sync\\$and\\$plane($1, $2);", "对数组平面进行同步与操作。\n```verilog\n$sync$and$plane(plane1, plane2);\n```"),
("sync$nand$plane", "\\$sync\\$nand\\$plane($1, $2);", "对数组平面进行同步与非操作。\n```verilog\n$sync$nand$plane(plane1, plane2);\n```"),
("sync$or$plane", "\\$sync\\$or\\$plane($1, $2);", "对数组平面进行同步或操作。\n```verilog\n$sync$or$plane(plane1, plane2);\n```"),
("sync$nor$plane", "\\$sync\\$nor\\$plane($1, $2);", "对数组平面进行同步或非操作。\n```verilog\n$sync$nor$plane(plane1, plane2);\n```"),
];
/// $q_initialize tasks IEEE 1364-2005 17.6.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_Q_INITIALIZE_TASKS: &[(&str, &str, &str)] = &[
("q_initialize", "\\$q_initialize($1, $2, $3, $4);", "创建新的队列。\n```verilog\n$q_initialize(q_id, q_type, max_length, status);\n```\n\nTable 17-14—队列类型表:\n\n| q_type 值 | 队列类型 |\n|-----------|----------|\n| 1 | 先进先出 |\n| 2 | 后进先出 |\n\nTable 17-16—状态代码表:\n\n| 状态代码 | 含义 |\n|----------|------|\n| 0 | 成功 |\n| 1 | 队列已满,无法添加 |\n| 2 | 未定义的 q_id |\n| 3 | 队列已空,无法移除 |\n| 4 | 不支持的队列类型,无法创建队列 |\n| 5 | 指定的长度 <= 0无法创建队列 |\n| 6 | 重复的 q_id无法创建队列 |\n| 7 | 内存不足,无法创建队列 |"),
];
/// $q_add tasks IEEE 1364-2005 17.6.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_Q_ADD_TASKS: &[(&str, &str, &str)] = &[
("q_add", "\\$q_add($1, $2, $3, $4);", "向队列中添加一个条目。\n```verilog\n$q_add(q_id, job_id, inform_id, status);\n```\n\nTable 17-16—状态代码表:\n\n| 状态代码 | 含义 |\n|----------|------|\n| 0 | 成功 |\n| 1 | 队列已满,无法添加 |\n| 2 | 未定义的 q_id |\n| 3 | 队列已空,无法移除 |\n| 4 | 不支持的队列类型,无法创建队列 |\n| 5 | 指定的长度 <= 0无法创建队列 |\n| 6 | 重复的 q_id无法创建队列 |\n| 7 | 内存不足,无法创建队列 |"),
];
/// $q_remove tasks IEEE 1364-2005 17.6.3
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_Q_REMOVE_TASKS: &[(&str, &str, &str)] = &[
("q_remove", "\\$q_remove($1, $2, $3, $4);", "从队列中移除一个条目。\n```verilog\n$q_remove(q_id, job_id, inform_id, status);\n```\n\nTable 17-16—状态代码表:\n\n| 状态代码 | 含义 |\n|----------|------|\n| 0 | 成功 |\n| 1 | 队列已满,无法添加 |\n| 2 | 未定义的 q_id |\n| 3 | 队列已空,无法移除 |\n| 4 | 不支持的队列类型,无法创建队列 |\n| 5 | 指定的长度 <= 0无法创建队列 |\n| 6 | 重复的 q_id无法创建队列 |\n| 7 | 内存不足,无法创建队列 |"),
];
/// $q_full tasks IEEE 1364-2005 17.6.4
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_Q_FULL_TASKS: &[(&str, &str, &str)] = &[
("q_full", "\\$q_full($1, $2);", "检查队列是否有空间再添加一个条目。\n```verilog\ninteger is_full;\nis_full = $q_full(q_id, status);\n```\n\nTable 17-16—状态代码表:\n\n| 状态代码 | 含义 |\n|----------|------|\n| 0 | 成功 |\n| 1 | 队列已满,无法添加 |\n| 2 | 未定义的 q_id |\n| 3 | 队列已空,无法移除 |\n| 4 | 不支持的队列类型,无法创建队列 |\n| 5 | 指定的长度 <= 0无法创建队列 |\n| 6 | 重复的 q_id无法创建队列 |\n| 7 | 内存不足,无法创建队列 |"),
];
/// $q_exam tasks IEEE 1364-2005 17.6.5
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_Q_EXAM_TASKS: &[(&str, &str, &str)] = &[
("q_exam", "\\$q_exam($1, $2, $3, $4);", "提供关于队列 q_id 活动的统计信息。\n```verilog\n$q_exam(q_id, q_stat_code, q_stat_value, status);\n```\n\nTable 17-15—$q_exam 系统任务的参数值:\n\n| 请求的 q_stat_code 值 | 从 q_stat_value 接收到的信息 |\n|-----------------------|------------------------------|\n| 1 | 当前队列长度 |\n| 2 | 平均到达间隔时间 |\n| 3 | 最大队列长度 |\n| 4 | 最短等待时间 |\n| 5 | 队列中作业的最长等待时间 |\n| 6 | 队列中的平均等待时间 |\n\nTable 17-16—状态代码表:\n\n| 状态代码 | 含义 |\n|----------|------|\n| 0 | 成功 |\n| 1 | 队列已满,无法添加 |\n| 2 | 未定义的 q_id |\n| 3 | 队列已空,无法移除 |\n| 4 | 不支持的队列类型,无法创建队列 |\n| 5 | 指定的长度 <= 0无法创建队列 |\n| 6 | 重复的 q_id无法创建队列 |\n| 7 | 内存不足,无法创建队列 |"),
];
/// $time tasks IEEE 1364-2005 17.7.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_TIME_TASKS: &[(&str, &str, &str)] = &[
("time", "\\$time;", "返回当前仿真时间。\n```verilog\ninteger current_time;\ncurrent_time = $time;\n```"),
];
/// $stime tasks IEEE 1364-2005 17.7.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_STIME_TASKS: &[(&str, &str, &str)] = &[
("stime", "\\$stime;", "返回当前仿真时间(以整数形式)。\n```verilog\ninteger current_time;\ncurrent_time = $stime;\n```"),
];
/// $realtime tasks IEEE 1364-2005 17.7.3
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_REALTIME_TASKS: &[(&str, &str, &str)] = &[
("realtime", "\\$realtime;", "返回当前仿真时间(以实数形式)。\n```verilog\nreal current_time;\ncurrent_time = $realtime;\n```"),
];
/// Conversion functions tasks IEEE 1364-2005 17.8
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_CONVERSION_FUNCTIONS_TASKS: &[(&str, &str, &str)] = &[
("itor", "\\$itor($1);", "将整数转换为实数。\n```verilog\nreal real_value;\nreal_value = $itor(integer_value);\n```"),
("rtoi", "\\$rtoi($1);", "将实数转换为整数。\n```verilog\ninteger integer_value;\ninteger_value = $rtoi(real_value);\n```"),
("bitstoreal", "\\$bitstoreal($1);", "将 64 位位向量转换为实数。\n```verilog\nreal real_value;\nreal_value = $bitstoreal(bit_vector);\n```"),
("realtobits", "\\$realtobits($1);", "将实数转换为 64 位位向量。\n```verilog\nreg [63:0] bit_vector;\nbit_vector = $realtobits(real_value);\n```"),
// bitstoshortreal 和 shortrealtobits 是 sv 中的 task
("bitstoshortreal", "\\$bitstoshortreal($1);", "(system verilog) 将 32 位位向量转换为短实数。\n```verilog\nshortreal shortreal_value;\nshortreal_value = $bitstoshortreal(bit_vector);\n```"),
("shortrealtobits", "\\$shortrealtobits($1);", "(system verilog) 将短实数转换为 32 位位向量。\n```verilog\nreg [31:0] bit_vector;\nbit_vector = $shortrealtobits(shortreal_value);\n```"),
];
/// $random function tasks IEEE 1364-2005 17.9.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_RANDOM_FUNCTION_TASKS: &[(&str, &str, &str)] = &[
("random", "\\$random($1);", "生成一个随机数。\n```verilog\ninteger rand_num;\nrand_num = $random(seed);\n```"),
];
/// $dist_functions tasks IEEE 1364-2005 17.9.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DIST_FUNCTIONS_TASKS: &[(&str, &str, &str)] = &[
("dist_chi_square", "\\$dist_chi_square($1, $2);", "生成一个卡方分布的随机数。\n```verilog\nreal rand_num;\nrand_num = $dist_chi_square(seed, k);\n```"),
("dist_erlang", "\\$dist_erlang($1, $2, $3);", "生成一个埃尔朗分布的随机数。\n```verilog\nreal rand_num;\nrand_num = $dist_erlang(seed, k, lambda);\n```"),
("dist_exponential", "\\$dist_exponential($1, $2);", "生成一个指数分布的随机数。\n```verilog\nreal rand_num;\nrand_num = $dist_exponential(seed, lambda);\n```"),
("dist_normal", "\\$dist_normal($1, $2, $3);", "生成一个正态分布的随机数。\n```verilog\nreal rand_num;\nrand_num = $dist_normal(seed, mean, stddev);\n```"),
("dist_poisson", "\\$dist_poisson($1, $2);", "生成一个泊松分布的随机数。\n```verilog\nreal rand_num;\nrand_num = $dist_poisson(seed, lambda);\n```"),
("dist_t", "\\$dist_t($1, $2);", "生成一个 t 分布的随机数。\n```verilog\nreal rand_num;\nrand_num = $dist_t(seed, v);\n```"),
("dist_uniform", "\\$dist_uniform($1, $2, $3);", "生成一个均匀分布的随机数。\n```verilog\nreal rand_num;\nrand_num = $dist_uniform(seed, low, high);\n```"),
];
/// $test$plusargs (string) tasks IEEE 1364-2005 17.10.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_TEST_PLUSARGS_TASKS: &[(&str, &str, &str)] = &[
("test$plusargs", "\\$test\\$plusargs($1);", "检查仿真命令行参数中是否包含指定的字符串。\n```verilog\ninteger result;\nresult = $test$plusargs(\"test_string\");\n```\n\n示例代码:\n```verilog\ninitial begin\n\tif ($test$plusargs(\"HELLO\")) $display(\"Hello argument found.\");\n\tif ($test$plusargs(\"HE\")) $display(\"The HE subset string is detected.\");\n\tif ($test$plusargs(\"H\")) $display(\"Argument starting with H found.\");\n\tif ($test$plusargs(\"HELLO_HERE\")) $display(\"Long argument.\");\n\tif ($test$plusargs(\"HI\")) $display(\"Simple greeting.\");\n\tif ($test$plusargs(\"LO\")) $display(\"Does not match.\");\nend\n```"),
];
/// $value$plusargs (user_string, variable) tasks IEEE 1364-2005 17.10.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_VALUE_PLUSARGS_TASKS: &[(&str, &str, &str)] = &[
("value$plusargs", "\\$value\\$plusargs($1, $2);", "从仿真命令行参数中提取值并赋给变量。\n```verilog\ninteger result;\nresult = $value$plusargs(\"test_string=\", value);\n```\n\n示例代码:\n```verilog\n`define STRING reg [1024 * 8:1]\nmodule goodtasks;\n\t`STRING str;\n\tinteger int;\n\treg [31:0] vect;\n\treal realvar;\n\tinitial\n\tbegin\n\t\tif ($value$plusargs(\"TEST=%d\", int))\n\t\t\t$display(\"value was %d\", int);\n\t\telse\n\t\t\t$display(\"+TEST= not found\");\n\t\t#100 $finish;\n\tend\nendmodule\n```"),
];
/// Integer math functions tasks IEEE 1364-2005 17.11.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_INTEGER_MATH_FUNCTIONS_TASKS: &[(&str, &str, &str)] = &[
("clog2", "\\$clog2($1);", "返回大于或等于给定整数的最小 2 的幂次。\n```verilog\ninteger result;\nresult = $clog2(value);\n```"),
("countones", "\\$countones($1);", "返回位向量中 1 的个数。\n```verilog\ninteger count;\ncount = $countones(bit_vector);\n```"),
("isunknown", "\\$isunknown($1);", "检查位向量中是否有未知值x 或 z\n```verilog\ninteger result;\nresult = $isunknown(bit_vector);\n```"),
("onehot", "\\$onehot($1);", "检查位向量中是否只有一个位为 1。\n```verilog\ninteger result;\nresult = $onehot(bit_vector);\n```"),
("onehot0", "\\$onehot0($1);", "检查位向量中是否只有一个位为 1 或没有位为 1。\n```verilog\ninteger result;\nresult = $onehot0(bit_vector);\n```"),
];
/// Real math functions tasks IEEE 1364-2005 17.11.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_REAL_MATH_FUNCTIONS_TASKS: &[(&str, &str, &str)] = &[
("ln", "\\$ln($1);", "计算自然对数。\n```verilog\nreal result;\nresult = $ln(value);\n```"),
("log10", "\\$log10($1);", "计算以 10 为底的对数。\n```verilog\nreal result;\nresult = $log10(value);\n```"),
("exp", "\\$exp($1);", "计算指数函数。\n```verilog\nreal result;\nresult = $exp(value);\n```"),
("sqrt", "\\$sqrt($1);", "计算平方根。\n```verilog\nreal result;\nresult = $sqrt(value);\n```"),
("pow", "\\$pow($1, $2);", "计算幂函数。\n```verilog\nreal result;\nresult = $pow(base, exponent);\n```"),
("floor", "\\$floor($1);", "返回不大于给定实数的最大整数。\n```verilog\nreal result;\nresult = $floor(value);\n```"),
("ceil", "\\$ceil($1);", "返回不小于给定实数的最小整数。\n```verilog\nreal result;\nresult = $ceil(value);\n```"),
("sin", "\\$sin($1);", "计算正弦函数。\n```verilog\nreal result;\nresult = $sin(value);\n```"),
("cos", "\\$cos($1);", "计算余弦函数。\n```verilog\nreal result;\nresult = $cos(value);\n```"),
("tan", "\\$tan($1);", "计算正切函数。\n```verilog\nreal result;\nresult = $tan(value);\n```"),
("asin", "\\$asin($1);", "计算反正弦函数。\n```verilog\nreal result;\nresult = $asin(value);\n```"),
("acos", "\\$acos($1);", "计算反余弦函数。\n```verilog\nreal result;\nresult = $acos(value);\n```"),
("atan", "\\$atan($1);", "计算反正切函数。\n```verilog\nreal result;\nresult = $atan(value);\n```"),
("atan2", "\\$atan2($1, $2);", "计算反正切函数(带两个参数)。\n```verilog\nreal result;\nresult = $atan2(y, x);\n```"),
("hypot", "\\$hypot($1, $2);", "计算直角三角形的斜边长度。\n```verilog\nreal result;\nresult = $hypot(x, y);\n```"),
("sinh", "\\$sinh($1);", "计算双曲正弦函数。\n```verilog\nreal result;\nresult = $sinh(value);\n```"),
("cosh", "\\$cosh($1);", "计算双曲余弦函数。\n```verilog\nreal result;\nresult = $cosh(value);\n```"),
("tanh", "\\$tanh($1);", "计算双曲正切函数。\n```verilog\nreal result;\nresult = $tanh(value);\n```"),
("asinh", "\\$asinh($1);", "计算反双曲正弦函数。\n```verilog\nreal result;\nresult = $asinh(value);\n```"),
("acosh", "\\$acosh($1);", "计算反双曲余弦函数。\n```verilog\nreal result;\nresult = $acosh(value);\n```"),
("atanh", "\\$atanh($1);", "计算反双曲正切函数。\n```verilog\nreal result;\nresult = $atanh(value);\n```"),
];
/// Specifying name of dump file ($dumpfile) tasks IEEE 1364-2005 18.1.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DUMPFILE_TASKS: &[(&str, &str, &str)] = &[
("dumpfile", "\\$dumpfile($1);", "指定波形转储文件的名称。\n```verilog\n$dumpfile(\"waveform.vcd\");\n```"),
];
/// Specifying variables to be dumped ($dumpvars) tasks IEEE 1364-2005 18.1.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DUMPVARS_TASKS: &[(&str, &str, &str)] = &[
("dumpvars", "\\$dumpvars($1, $2);", "指定要转储的变量。\n```verilog\n$dumpvars(1, module_instance);\n```"),
];
/// Stopping and resuming the dump ($dumpoff/$dumpon) tasks IEEE 1364-2005 18.1.3
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DUMPOFF_DUMPON_TASKS: &[(&str, &str, &str)] = &[
("dumpoff", "\\$dumpoff;", "暂停波形转储。\n```verilog\n$dumpoff;\n```"),
("dumpon", "\\$dumpon;", "恢复波形转储。\n```verilog\n$dumpon;\n```"),
];
/// Generating a checkpoint ($dumpall) tasks IEEE 1364-2005 18.1.4
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DUMPALL_TASKS: &[(&str, &str, &str)] = &[
("dumpall", "\\$dumpall;", "生成一个检查点,转储所有变量的当前状态。\n```verilog\n$dumpall;\n```"),
];
/// Limiting size of dump file ($dumplimit) tasks IEEE 1364-2005 18.1.5
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DUMPLIMIT_TASKS: &[(&str, &str, &str)] = &[
("dumplimit", "\\$dumplimit($1);", "限制波形转储文件的大小。\n```verilog\n$dumplimit(1000000);\n```"),
];
/// Reading dump file during simulation ($dumpflush) tasks IEEE 1364-2005 18.1.6
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DUMPFLUSH_TASKS: &[(&str, &str, &str)] = &[
("dumpflush", "\\$dumpflush;", "刷新波形转储文件,确保所有数据都写入文件。\n```verilog\n$dumpflush;\n```"),
];
// 18.3 Creating extended VCD File
// 主要比传统的dump多了一个指定文件名的功能
// dumpports $dumpports ( scope_list , file_pathname ) ;
// dumpportsoff $dumpportsoff ( file_pathname ) ;
// dumpportson $dumpportson ( file_pathname ) ;
// dumpportslimit $dumpportslimit ( filesize , file_pathname ) ;
// dumpportsflush $dumpportsflush ( file_pathname ) ;
// vcdclose $vcdclose
// vcdclose的信息比较少文档中的sample是$vcdclose #13000 $endvcdclose_task ::= $vcdclose final_simulation_time $end感觉应该是要和$end连用的
/// Specifying dump file name and ports to be dumped ($dumpports) tasks IEEE 1364-2005 18.3.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DUMPPORTS_TASKS: &[(&str, &str, &str)] = &[
("dumpports", "\\$dumpports($1, $2);", "`$dumpports` 任务用于指定 VCD 文件的名称以及要转储的端口。\n```verilog\n$dumpports(top_module, \"waveform.vcd\");\n$dumpports(module_A, module_B, module_C, \"waveform.vcd\");\n```"),
];
/// Stopping and resuming the dump ($dumpportsoff/$dumpportson) tasks IEEE 1364-2005 18.3.2
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DUMPPORTSOFF_DUMPPORTSON_TASKS: &[(&str, &str, &str)] = &[
("dumpportsoff", "\\$dumpportsoff($1);", "暂停端口波形转储。\n```verilog\n$dumpportsoff(\"waveform.vcd\");\n```"),
("dumpportson", "\\$dumpportson($1);", "恢复端口波形转储。\n```verilog\n$dumpportson(\"waveform.vcd\");\n```"),
];
/// Generating a checkpoint ($dumpportsall) tasks IEEE 1364-2005 18.3.3
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DUMPPORTSALL_TASKS: &[(&str, &str, &str)] = &[
("dumpportsall", "\\$dumpportsall($1);", "`$dumpportsall` 系统任务在 VCD 文件中创建一个检查点,显示仿真中该时刻所有选定端口的值,无论自上次时间步以来端口值是否发生变化。\n```verilog\n$dumpportsall(\"waveform.vcd\");\n```"),
];
/// Limiting size of dump file ($dumpportslimit) tasks IEEE 1364-2005 18.3.4
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DUMPPORTSLIMIT_TASKS: &[(&str, &str, &str)] = &[
("dumpportslimit", "\\$dumpportslimit($1, $2);", "`$dumpportslimit` 系统任务允许控制 VCD 文件的大小。\n```verilog\n$dumpportslimit(1000000, \"waveform.vcd\");\n```"),
];
/// Reading dump file during simulation ($dumpportsflush) tasks IEEE 1364-2005 18.3.5
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_DUMPPORTSFLUSH_TASKS: &[(&str, &str, &str)] = &[
("dumpportsflush", "\\$dumpportsflush($1);", "为了提高性能,仿真器通常会缓冲 VCD 输出并在间隔时间内写入文件,而不是逐行写入。`$dumpportsflush` 系统任务将所有端口值写入关联文件,清空仿真器的 VCD 缓冲区。\n```verilog\n$dumpportsflush(\"waveform.vcd\");\n```"),
];
/// Closing VCD file ($vcdclose) tasks IEEE 1364-2005 18.3.6.1
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_VCDCLOSE_TASKS: &[(&str, &str, &str)] = &[
("vcdclose", "\\$vcdclose; #$1 \\$end", "`$vcdclose` 关键字指示在扩展 VCD 文件关闭时的最终仿真时间。这使得无论信号变化状态如何,都能准确记录仿真结束时间,以协助需要此信息的解析器。\n```verilog\n$vcdclose #13000 $end\n```"),
];
/// FSDB related tasks
/// (&str, &str, &str): (label, snippet, description)
pub const VLOG_FSDB_TASKS: &[(&str, &str, &str)] = &[
("fsdbDumpfile", "\\$fsdbDumpfile($1);", "指定 FSDB 文件的名称。\n```verilog\n$fsdbDumpfile(\"waveform.fsdb\");\n```"),
("fsdbDumpvars", "\\$fsdbDumpvars($1, $2);", "指定要转储到 FSDB 文件的变量。\n```verilog\n$fsdbDumpvars(1, module_instance);\n```"),
("fsdbDumpoff", "\\$fsdbDumpoff;", "暂停 FSDB 文件的转储。\n```verilog\n$fsdbDumpoff;\n```"),
("fsdbDumpon", "\\$fsdbDumpon;", "恢复 FSDB 文件的转储。\n```verilog\n$fsdbDumpon;\n```"),
("fsdbDumpflush", "\\$fsdbDumpflush;", "刷新 FSDB 文件,确保所有数据都写入文件。\n```verilog\n$fsdbDumpflush;\n```"),
];
fn make_function_profile(
#[allow(unused)]
section: &str,
description: &str
) -> MarkupContent {
MarkupContent {
kind: MarkupKind::Markdown,
value: format!("{}", description)
}
}
fn update_task_completions(
items: &mut Vec<CompletionItem>,
tasks: &[(&str, &str, &str)],
section: &str
) {
for (label, snippet_code, description) in tasks {
let function_profile = make_function_profile(section, description);
let label_details = CompletionItemLabelDetails {
description: Some("system task".to_string()),
..Default::default()
};
items.push(CompletionItem {
label: label.to_string(),
detail: Some(section.to_string()),
label_details: Some(label_details),
documentation: Some(Documentation::MarkupContent(function_profile)),
kind: Some(CompletionItemKind::FUNCTION),
insert_text: Some(snippet_code.to_string()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
insert_text_mode: Some(InsertTextMode::ADJUST_INDENTATION),
..CompletionItem::default()
});
}
}
/// 提供 verilog sys task 相关的函数补全
/// 遵循 IEEE 1364 标准
pub fn provide_vlog_sys_tasks_completions() -> Vec<CompletionItem> {
let mut items: Vec<CompletionItem> = Vec::new();
update_task_completions(&mut items, VLOG_DISPLAY_WRITE_TASKS, "Display and write tasks IEEE 1364-2005 17.1.1");
update_task_completions(&mut items, VLOG_STROBED_MONITOR_TASKS, "Strobed monitoring IEEE 1364-2005 17.1.2");
update_task_completions(&mut items, VLOG_CONTINUOUS_MONITORING_TASKS, "Continuous monitoring tasks IEEE 1364-2005 17.1.3");
update_task_completions(&mut items, VLOG_FILE_IO_TASKS, "File input-output system tasks and functions IEEE 1364-2005 17.2.1");
update_task_completions(&mut items, VLOG_FILE_OUTPUT_TASKS, "File output system tasks IEEE 1364-2005 17.2.2");
update_task_completions(&mut items, VLOG_FORMATTING_TASKS, "Formatting data to a string tasks IEEE 1364-2005 17.2.3");
update_task_completions(&mut items, VLOG_FILE_READING_TASKS, "Reading data from a file tasks IEEE 1364-2005 17.2.4");
update_task_completions(&mut items, VLOG_FILE_POSITIONING_TASKS, "File positioning tasks IEEE 1364-2005 17.2.5");
update_task_completions(&mut items, VLOG_FLUSHING_OUTPUT_TASKS, "Flushing output tasks IEEE 1364-2005 17.2.6");
update_task_completions(&mut items, VLOG_IO_ERROR_STATUS_TASKS, "I/O error status tasks IEEE 1364-2005 17.2.7");
update_task_completions(&mut items, VLOG_DETECTING_EOF_TASKS, "Detecting EOF tasks IEEE 1364-2005 17.2.8");
update_task_completions(&mut items, VLOG_LOADING_MEMORY_TASKS, "Loading memory data from a file tasks IEEE 1364-2005 17.2.9");
update_task_completions(&mut items, VLOG_LOADING_TIMING_TASKS, "Loading timing data from an SDF file tasks IEEE 1364-2005 17.2.10");
update_task_completions(&mut items, VLOG_PRINTTIMESCALE_TASKS, "$printtimescale tasks IEEE 1364-2005 17.3.1");
update_task_completions(&mut items, VLOG_TIMEFORMAT_TASKS, "$timeformat tasks IEEE 1364-2005 17.3.2");
update_task_completions(&mut items, VLOG_FINISH_TASKS, "$finish tasks IEEE 1364-2005 17.4.1");
update_task_completions(&mut items, VLOG_STOP_TASKS, "$stop tasks IEEE 1364-2005 17.4.2");
update_task_completions(&mut items, VLOG_ARRAY_TYPES_TASKS, "Array types tasks IEEE 1364-2005 17.5.1");
update_task_completions(&mut items, VLOG_ARRAY_LOGIC_TYPES_TASKS, "Array logic types tasks IEEE 1364-2005 17.5.2");
update_task_completions(&mut items, VLOG_Q_INITIALIZE_TASKS, "$q_initialize tasks IEEE 1364-2005 17.6.1");
update_task_completions(&mut items, VLOG_Q_ADD_TASKS, "$q_add tasks IEEE 1364-2005 17.6.2");
update_task_completions(&mut items, VLOG_Q_REMOVE_TASKS, "$q_remove tasks IEEE 1364-2005 17.6.3");
update_task_completions(&mut items, VLOG_Q_FULL_TASKS, "$q_full tasks IEEE 1364-2005 17.6.4");
update_task_completions(&mut items, VLOG_Q_EXAM_TASKS, "$q_exam tasks IEEE 1364-2005 17.6.5");
update_task_completions(&mut items, VLOG_TIME_TASKS, "$time tasks IEEE 1364-2005 17.7.1");
update_task_completions(&mut items, VLOG_STIME_TASKS, "$stime tasks IEEE 1364-2005 17.7.2");
update_task_completions(&mut items, VLOG_REALTIME_TASKS, "$realtime tasks IEEE 1364-2005 17.7.3");
update_task_completions(&mut items, VLOG_CONVERSION_FUNCTIONS_TASKS, "Conversion functions tasks IEEE 1364-2005 17.8");
update_task_completions(&mut items, VLOG_RANDOM_FUNCTION_TASKS, "$random function tasks IEEE 1364-2005 17.9.1");
update_task_completions(&mut items, VLOG_DIST_FUNCTIONS_TASKS, "$dist_functions tasks IEEE 1364-2005 17.9.2");
update_task_completions(&mut items, VLOG_TEST_PLUSARGS_TASKS, "$test$plusargs (string) tasks IEEE 1364-2005 17.10.1");
update_task_completions(&mut items, VLOG_VALUE_PLUSARGS_TASKS, "$value$plusargs (user_string, variable) tasks IEEE 1364-2005 17.10.2");
update_task_completions(&mut items, VLOG_INTEGER_MATH_FUNCTIONS_TASKS, "Integer math functions tasks IEEE 1364-2005 17.11.1");
update_task_completions(&mut items, VLOG_REAL_MATH_FUNCTIONS_TASKS, "Real math functions tasks IEEE 1364-2005 17.11.2");
update_task_completions(&mut items, VLOG_DUMPFILE_TASKS, "Specifying name of dump file ($dumpfile) tasks IEEE 1364-2005 18.1.1");
update_task_completions(&mut items, VLOG_DUMPVARS_TASKS, "Specifying variables to be dumped ($dumpvars) tasks IEEE 1364-2005 18.1.2");
update_task_completions(&mut items, VLOG_DUMPOFF_DUMPON_TASKS, "Stopping and resuming the dump ($dumpoff/$dumpon) tasks IEEE 1364-2005 18.1.3");
update_task_completions(&mut items, VLOG_DUMPALL_TASKS, "Generating a checkpoint ($dumpall) tasks IEEE 1364-2005 18.1.4");
update_task_completions(&mut items, VLOG_DUMPLIMIT_TASKS, "Limiting size of dump file ($dumplimit) tasks IEEE 1364-2005 18.1.5");
update_task_completions(&mut items, VLOG_DUMPFLUSH_TASKS, "Reading dump file during simulation ($dumpflush) tasks IEEE 1364-2005 18.1.6");
update_task_completions(&mut items, VLOG_DUMPPORTS_TASKS, "Specifying dump file name and ports to be dumped ($dumpports) tasks IEEE 1364-2005 18.3.1");
update_task_completions(&mut items, VLOG_DUMPPORTSOFF_DUMPPORTSON_TASKS, "Stopping and resuming the dump ($dumpportsoff/$dumpportson) tasks IEEE 1364-2005 18.3.2");
update_task_completions(&mut items, VLOG_DUMPPORTSALL_TASKS, "Generating a checkpoint ($dumpportsall) tasks IEEE 1364-2005 18.3.3");
update_task_completions(&mut items, VLOG_DUMPPORTSLIMIT_TASKS, "Limiting size of dump file ($dumpportslimit) tasks IEEE 1364-2005 18.3.4");
update_task_completions(&mut items, VLOG_DUMPPORTSFLUSH_TASKS, "Reading dump file during simulation ($dumpportsflush) tasks IEEE 1364-2005 18.3.5");
update_task_completions(&mut items, VLOG_VCDCLOSE_TASKS, "Closing VCD file ($vcdclose) tasks IEEE 1364-2005 18.3.6.1");
update_task_completions(&mut items, VLOG_FSDB_TASKS, "FSDB related tasks");
items
}

View File

@ -1,118 +1,115 @@
use std::{path::PathBuf, str::FromStr};
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
use ropey::{Rope, RopeSlice}; use ropey::{Rope, RopeSlice};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use crate::{hover::feature::make_vhdl_module_profile_code, utils::{from_uri_to_escape_path_string, to_escape_path}};
#[allow(unused)] #[allow(unused)]
use crate::{server::LspServer, sources::LSPSupport, utils::get_language_id_by_uri}; use crate::{server::LSPServer, sources::LSPSupport, utils::get_language_id_by_uri};
/// Called when the client requests a completion. /// Called when the client requests a completion.
/// This function looks in the source code to find suitable options and then returns them /// This function looks in the source code to find suitable options and then returns them
pub fn completion(server: &LspServer, params: &CompletionParams) -> Option<CompletionResponse> { pub fn completion(server: &LSPServer, params: &CompletionParams) -> Option<CompletionResponse> {
let doc = &params.text_document_position; let doc = &params.text_document_position;
let uri = &params.text_document_position.text_document.uri; let uri = &params.text_document_position.text_document.uri;
let language_id = get_language_id_by_uri(uri); // let pos = doc.position;
let path_string = from_uri_to_escape_path_string(uri).unwrap(); // let language_id = get_language_id_by_uri(uri);
// 等待解析完成 let file_id = server.srcs.get_id(uri).to_owned();
server.db.wait_parse_ready(&path_string, false); server.srcs.wait_parse_ready(file_id, false);
let source = server.db.get_source(&path_string)?; let file = server.srcs.get_file(file_id)?;
let source = source.read().ok()?; let file = file.read().ok()?;
let line_text = file.text.line(doc.position.line as usize);
let line_text = source.text.line(doc.position.line as usize);
let token = get_completion_token( let token = get_completion_token(
&source.text, &file.text,
line_text.clone(), line_text.clone(),
doc.position, doc.position,
); );
let project = server.db.vhdl_project.read().ok()?; // info!("trigger completion token: {}", token);
// let line_text = file.text.line(pos.line as usize);
#[allow(unused)]
let global_project = project.as_ref().unwrap();
let path = match PathBuf::from_str(uri.path()) {
Ok(path) => path,
Err(error) => {
info!("error happen in vhdl <hover>: {:?}", error);
return None;
}
};
let escape_path = to_escape_path(&path);
#[allow(unused)]
let project_file = escape_path.as_path();
// let Some(source) = global_project.project.get_source(project_file) else {
// return None
// };
// let cursor = from_lsp_pos(params.text_document_position.position);
// let vhdl_project_completion_items = global_project.project
// .list_completion_options(&source, cursor)
// .into_iter()
// .map(|item| vhdl_ls::VHDLServer::completion_item_to_tower_lsp_item(item))
// .collect::<Vec<CompletionItem>>();
let response = match &params.context { let response = match &params.context {
Some(context) => match context.trigger_kind { Some(context) => match context.trigger_kind {
CompletionTriggerKind::TRIGGER_CHARACTER => { // CompletionTriggerKind::TRIGGER_CHARACTER => {
let trigger_character = context.trigger_character.clone().unwrap(); // let trigger_character = context.trigger_character.clone().unwrap();
match trigger_character.as_str() { // match trigger_character.as_str() {
// 按下 . 时需要触发的补全效果 // "." => {
"." => { // info!("trigger dot completion");
info!("trigger vhdl dot completion"); // get_dot_completion(server, &line_text, uri, &pos, &language_id)
let mut completion_items = server.db.get_completions( // },
&token, // "$" => Some(CompletionList {
source.text.pos_to_byte(&doc.position), // is_incomplete: false,
&doc.text_document.uri, // items: server.sys_tasks.clone(),
)?; // }),
// "`" => Some(CompletionList {
// completion_items.items.extend(vhdl_project_completion_items); // is_incomplete: false,
// items: server.directives.clone(),
completion_items.items.dedup_by_key(|i| i.label.to_string()); // }),
Some(completion_items) // "/" => {
}, // info!("trigger include");
_ => None, // include_path_completion(&doc.text_document.uri, &line_text, pos)
} // },
} // "\"" => {
CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None, // info!("trigger include");
// include_path_completion(&doc.text_document.uri, &line_text, pos)
// 一般情况下根据字符触发的补全项目 // }
// _ => None,
// }
// }
// CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None,
CompletionTriggerKind::INVOKED => { CompletionTriggerKind::INVOKED => {
// 1. 先根据 AST 获取上下文补全项 let mut comps = server.srcs.get_completions(
let mut completion_items = server.db.get_completions(
&token, &token,
source.text.pos_to_byte(&doc.position), file.text.pos_to_byte(&doc.position),
&doc.text_document.uri, &doc.text_document.uri,
)?; )?;
// 2. 根据 token 再加入关键词 // complete keywords
completion_items.items.extend::<Vec<CompletionItem>>( comps.items.extend::<Vec<CompletionItem>>(
server.vhdl_keyword_completiom_items server.key_comps
.iter() .iter()
.filter(|x| x.label.starts_with(&token)) .filter(|x| x.label.starts_with(&token))
.cloned() .cloned()
.collect(), .collect(),
); );
comps.items.dedup_by_key(|i| i.label.clone());
// 3. 加入例化自动补全的 Some(comps)
completion_items.items.extend::<Vec<CompletionItem>>(
make_module_completions(server, &token, &language_id)
);
// completion_items.items.extend(vhdl_project_completion_items);
// 去重
completion_items.items.dedup_by_key(|i| i.label.to_string());
Some(completion_items)
} }
_ => None, _ => None,
}, },
None => None None => {
let trigger = prev_char(&file.text, &doc.position);
match trigger {
// '.' => Some(server.srcs.get_dot_completions(
// token.trim_end_matches('.'),
// file.text.pos_to_byte(&doc.position),
// &doc.text_document.uri,
// )?),
// '$' => Some(CompletionList {
// is_incomplete: false,
// items: server.sys_tasks.clone(),
// }),
// '`' => Some(CompletionList {
// is_incomplete: false,
// items: server.directives.clone(),
// }),
_ => {
let mut comps = server.srcs.get_completions(
&token,
file.text.pos_to_byte(&doc.position),
&doc.text_document.uri,
)?;
comps.items.extend::<Vec<CompletionItem>>(
server.key_comps
.iter()
.filter(|x| x.label.starts_with(&token))
.cloned()
.collect(),
);
comps.items.dedup_by_key(|i| i.label.clone());
Some(comps)
}
}
}
}; };
// eprintln!("comp response: {}", now.elapsed().as_millis()); // eprintln!("comp response: {}", now.elapsed().as_millis());
Some(CompletionResponse::List(response?)) Some(CompletionResponse::List(response?))
@ -184,115 +181,3 @@ fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String {
} }
} }
fn make_module_completions(
server: &LspServer,
token: &str,
language_id: &str
) -> Vec<CompletionItem> {
let mut module_completioms = Vec::<CompletionItem>::new();
let hdl_param = server.db.hdl_param.clone();
let prefix = token.to_string().to_lowercase();
let module_name_to_path = hdl_param.module_name_to_path.read().unwrap();
// 遍历 hdlparam 中所有的 modules匹配符合 prefix 前缀的(不区分大小写)
for module_name in module_name_to_path.keys() {
if !module_name.to_string().to_lowercase().starts_with(&prefix) {
continue;
}
if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(&module_name) {
let mut insert_text = Vec::<String>::new();
let (insert_text, module_profile, define_info) = if file_type == "primitives" {
// TODO: 支持原语
continue;
} else {
insert_text.push(make_instantiation_code(&module));
let path_uri = Url::from_file_path(def_path).unwrap().to_string();
let def_row = module.range.start.line;
let def_col = module.range.start.character;
(
insert_text.join("\n"),
make_vhdl_module_profile_code(&module),
format!("Go to [Definition]({path_uri}#L{def_row}:{def_col})")
)
};
let module_profile = MarkupContent {
kind: MarkupKind::Markdown,
value: format!("```{}\n{}\n```\n{}", language_id, module_profile, define_info)
};
let detail = format!("module instantiation ({})", file_type);
let item = CompletionItem {
label: module.name.to_string(),
detail: Some(detail),
documentation: Some(Documentation::MarkupContent(module_profile)),
kind: Some(CompletionItemKind::CLASS),
insert_text: Some(insert_text),
// 给模块例化自动补全附上最高权重
sort_text: Some("0".to_string()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
insert_text_mode: Some(InsertTextMode::ADJUST_INDENTATION),
..CompletionItem::default()
};
module_completioms.push(item);
}
}
module_completioms
}
/// 实现 vhdl 例化补全的代码片段
fn make_instantiation_code(module: &crate::core::hdlparam::Module) -> String {
// TODO: 显性和隐性例化
let mut snippet_codes = Vec::<String>::new();
let mut placeholder_id: u32 = 1;
snippet_codes.push(format!("u_{} : {}\n", module.name, module.name));
// 2001 style先计算出 generic 和 port然后加入总体例化样板中
let params_length = module.params.len();
let ports_length = module.ports.len();
if params_length > 0 {
snippet_codes.push("generic map(\n".to_string());
let max_param_name = module.params.iter().map(|param| param.name.len()).max().unwrap_or(0);
let mut i: usize = 0;
for generic in &module.params {
let n_padding = " ".repeat(max_param_name - generic.name.len() + 1);
let placeholder_init = format!("${{{}:{}}}", placeholder_id, generic.init);
snippet_codes.push(format!("\t{}{} => {}", generic.name, n_padding, placeholder_init));
placeholder_id += 1;
if i < params_length - 1 {
snippet_codes.push(",\n".to_string());
}
i += 1;
}
snippet_codes.push(")\n".to_string());
}
if ports_length > 0 {
snippet_codes.push("port map(\n\t-- ports\n".to_string());
let max_port_name = module.ports.iter().map(|port| port.name.len()).max().unwrap_or(0);
let mut i: usize = 0;
for port in &module.ports {
let n_padding = " ".repeat(max_port_name - port.name.len() + 1);
let placeholder_name = format!("${{{}:{}}}", placeholder_id, port.name);
snippet_codes.push(format!("\t{}{} => {}", port.name, n_padding, placeholder_name));
placeholder_id += 1;
if i < ports_length - 1 {
snippet_codes.push(",\n".to_string());
}
i += 1;
}
snippet_codes.push("\n);\n".to_string());
}
snippet_codes.join("")
}

View File

@ -1,10 +1,9 @@
use std::{collections::HashMap, fs::{self}, hash::{DefaultHasher, Hash, Hasher}, path::PathBuf, str::FromStr, sync::{Arc, RwLock}}; use std::{collections::HashMap, fs::{self}, hash::{DefaultHasher, Hash, Hasher}, path::PathBuf, sync::{Arc, RwLock}};
use log::info; use log::info;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::Url;
use crate::utils::{file_size_in_kb, get_last_modified_time, k_deserialize, k_serialize, to_escape_path}; use crate::utils::{file_size_in_kb, get_last_modified_time, k_deserialize, k_serialize};
use super::hdlparam::FastHdlparam; use super::hdlparam::FastHdlparam;
@ -26,32 +25,11 @@ pub struct CacheItem {
pub cache_name: String pub cache_name: String
} }
impl Default for CacheItem {
fn default() -> Self {
CacheItem {
file_name: "".to_string(),
size: 0,
version: "".to_string(),
cache_name: "".to_string()
}
}
}
pub struct CacheInfo {
// version
pub version: Option<String>,
/// 解析类型文件缓存的根目录
pub parser_cache: Option<PathBuf>,
/// 第三方 linter 文件缓存的根目录
pub linter_cache: Option<PathBuf>,
}
/// 用于进行高效 IR 缓存的模块 /// 用于进行高效 IR 缓存的模块
pub struct CacheManager { pub struct CacheManager {
/// 缓存文件夹根目录 /// 缓存文件夹根目录
pub root_dir: PathBuf, pub root_dir: PathBuf,
pub cache_info: Arc<RwLock<CacheInfo>>,
/// meta 文件内容 /// meta 文件内容
pub meta_name: String, pub meta_name: String,
/// meta 内容 /// meta 内容
@ -61,67 +39,27 @@ pub struct CacheManager {
impl CacheManager { impl CacheManager {
pub fn new(root_dir: PathBuf) -> Self { pub fn new(root_dir: PathBuf) -> Self {
// 读入 meta 文件
let meta_name = "index.cache"; let meta_name = "index.cache";
let cache_info = Arc::new(RwLock::new(CacheInfo { let meta_path = root_dir.join(meta_name);
version: None, let meta = get_or_init_meta(&meta_path);
parser_cache: None,
linter_cache: None, // 如果不存在 root dir则创建
})); if !root_dir.exists() {
match fs::create_dir_all(&root_dir) {
Ok(_) => {},
Err(err) => info!("error happen when create {root_dir:?}: {err:?}")
}
}
CacheManager { CacheManager {
root_dir, root_dir,
cache_info,
meta_name: meta_name.to_string(), meta_name: meta_name.to_string(),
meta: Arc::new(RwLock::new(HashMap::<String, CacheItem>::new())) meta: Arc::new(RwLock::new(meta))
} }
} }
pub fn start(&self, version: &str) { pub fn is_big_file(&self, path: &PathBuf) -> bool {
let root_dir = &self.root_dir;
let mut cache_info = self.cache_info.write().unwrap();
cache_info.version = Some(version.to_string());
let linter_cache = root_dir.join(version).join("lc");
let parser_cache = root_dir.join(version).join("pc");
// 如果不存在指定的缓存文件夹,则创建
// ~/digital-ide/{版本号}/
// 📁 lc linter的cache
// 📁 pc parser的cache
CacheManager::check_dir(&linter_cache);
CacheManager::check_dir(&parser_cache);
// 读入 meta 文件
let meta_path = parser_cache.join(&self.meta_name);
let meta = get_or_init_meta(&meta_path);
let mut cache_meta = self.meta.write().unwrap();
for (key, value) in meta.into_iter() {
cache_meta.insert(key, value);
}
info!("缓存系统初始化完成pc: {:?}, lc: {:?}", parser_cache, linter_cache);
cache_info.parser_cache = Some(parser_cache);
cache_info.linter_cache = Some(linter_cache);
}
fn check_dir(dir: &PathBuf) {
if !dir.exists() {
let _ = fs::create_dir_all(dir);
}
}
pub fn is_big_file(path: &PathBuf) -> bool {
if let Ok(size) = file_size_in_kb(path.to_str().unwrap()) {
return size >= 1024;
}
false
}
#[allow(unused)]
pub fn uri_is_big_file(uri: &Url) -> bool {
let path = PathBuf::from_str(uri.path()).unwrap();
let path = to_escape_path(&path);
if let Ok(size) = file_size_in_kb(path.to_str().unwrap()) { if let Ok(size) = file_size_in_kb(path.to_str().unwrap()) {
return size >= 1024; return size >= 1024;
} }
@ -145,7 +83,7 @@ impl CacheManager {
/// * 如果本来就没有缓存 /// * 如果本来就没有缓存
/// * 缓冲的 version 对不上 /// * 缓冲的 version 对不上
pub fn try_get_fast_cache(&self, path: &PathBuf) -> CacheResult<FastHdlparam> { pub fn try_get_fast_cache(&self, path: &PathBuf) -> CacheResult<FastHdlparam> {
if !CacheManager::is_big_file(path) { if !self.is_big_file(path) {
return CacheResult::NotNeedCache return CacheResult::NotNeedCache
} }
let path_string = path.to_str().unwrap(); let path_string = path.to_str().unwrap();
@ -174,7 +112,6 @@ impl CacheManager {
hash_string hash_string
} }
/// 更新缓存,并写入磁盘
pub fn update_cache(&self, path: &PathBuf, fast: FastHdlparam) { pub fn update_cache(&self, path: &PathBuf, fast: FastHdlparam) {
let path_string = path.to_str().unwrap(); let path_string = path.to_str().unwrap();
let version = self.get_version(path); let version = self.get_version(path);
@ -190,23 +127,19 @@ impl CacheManager {
let mut meta_handle = self.meta.write().unwrap(); let mut meta_handle = self.meta.write().unwrap();
meta_handle.insert(path_string.to_string(), cache_item); meta_handle.insert(path_string.to_string(), cache_item);
let cache_info = self.cache_info.read().unwrap();
// 准备必要的独立数据塞入线程进行调度 // 准备必要的独立数据塞入线程进行调度
if let Some(parser_cache) = &cache_info.parser_cache { let meta = (&*meta_handle).clone();
let meta = (&*meta_handle).clone(); let meta_save_path = self.root_dir.join(self.meta_name.clone());
let meta_save_path = parser_cache.join(&self.meta_name); let cache_save_path = self.root_dir.join(cache_name);
let cache_save_path = parser_cache.join(cache_name);
std::thread::spawn(move || {
std::thread::spawn(move || { info!("save meta to {meta_save_path:?}");
info!("save meta to {meta_save_path:?}"); let _ = k_serialize(&meta_save_path, meta);
let _ = k_serialize(&meta_save_path, meta);
info!("save index to {cache_save_path:?}");
info!("save index to {cache_save_path:?}"); let _ = k_serialize(&cache_save_path, fast);
let _ = k_serialize(&cache_save_path, fast); });
});
}
} }
} }

View File

@ -6,8 +6,6 @@ use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::Position as LspPosition; use tower_lsp::lsp_types::Position as LspPosition;
use tower_lsp::lsp_types::Range as LspRange; use tower_lsp::lsp_types::Range as LspRange;
use crate::sources::AstLike;
#[derive(Debug, Clone, Serialize, PartialEq, PartialOrd, Eq, Ord, Deserialize)] #[derive(Debug, Clone, Serialize, PartialEq, PartialOrd, Eq, Ord, Deserialize)]
pub struct Position { pub struct Position {
pub line: u32, pub line: u32,
@ -23,12 +21,6 @@ impl Position {
pub fn from_lsp_position(pos: &LspPosition) -> Position { pub fn from_lsp_position(pos: &LspPosition) -> Position {
Position { line: pos.line, character: pos.character } Position { line: pos.line, character: pos.character }
} }
pub fn new(line: u32, character: u32) -> Position {
Position {
line, character
}
}
} }
#[derive(Debug, Clone, Serialize, PartialEq, PartialOrd, Eq, Ord, Deserialize)] #[derive(Debug, Clone, Serialize, PartialEq, PartialOrd, Eq, Ord, Deserialize)]
@ -111,13 +103,6 @@ impl Range {
Some(self.clone()) Some(self.clone())
} }
} }
pub fn default() -> Range {
Range {
start: Position::new(0, 0),
end: Position::new(0, 0)
}
}
} }
/// 比较两个 pos 的位置关系 /// 比较两个 pos 的位置关系
@ -157,70 +142,6 @@ pub struct Parameter {
pub range: Range pub range: Range
} }
impl Port {
pub fn to_vlog_description(&self) -> String {
let mut port_desc_array = Vec::<String>::new();
let dir_type = match self.dir_type.as_str() {
"in" => "input",
"out" => "output",
_ => &self.dir_type
};
port_desc_array.push(dir_type.to_string());
if self.net_type != "unknown" {
port_desc_array.push(self.net_type.to_string());
}
if self.signed != "unsigned" {
port_desc_array.push("signed".to_string());
}
if self.width != "1" {
port_desc_array.push(self.width.to_string());
}
port_desc_array.push(self.name.to_string());
let port_desc = port_desc_array.join(" ");
port_desc
}
pub fn to_vhdl_description(&self) -> String {
let mut port_desc_array = Vec::<String>::new();
port_desc_array.push(self.name.to_string());
port_desc_array.push(":".to_string());
port_desc_array.push(self.dir_type.to_string());
let width_string = self.width.replace("[", "(").replace("]", ")").replace(":", " downto ");
port_desc_array.push(format!("{}{};", self.net_type.to_lowercase(), width_string));
let port_desc = port_desc_array.join(" ");
port_desc
}
}
impl Parameter {
pub fn to_vlog_description(&self) -> String {
let mut param_desc_array = Vec::<String>::new();
param_desc_array.push(format!("parameter {}", self.name));
if self.init != "unknown" {
param_desc_array.push("=".to_string());
param_desc_array.push(self.init.to_string());
}
let param_desc = param_desc_array.join(" ");
param_desc
}
pub fn to_vhdl_description(&self) -> String {
let mut param_desc_array = Vec::<String>::new();
param_desc_array.push(format!("{} : {}", self.name, self.net_type.to_lowercase()));
if self.init != "unknown" {
param_desc_array.push(format!(" := {}", self.init));
}
let param_desc = param_desc_array.join(" ");
param_desc
}
}
#[derive(Debug, Serialize, PartialEq, PartialOrd, Eq, Ord, Deserialize, Clone)] #[derive(Debug, Serialize, PartialEq, PartialOrd, Eq, Ord, Deserialize, Clone)]
pub enum AssignType { pub enum AssignType {
Named, Named,
@ -267,8 +188,8 @@ impl Instance {
new_port_range.start.line = param_range.end.line - 1; new_port_range.start.line = param_range.end.line - 1;
new_port_range.start.character = param_range.end.character; new_port_range.start.character = param_range.end.character;
} else { } else {
new_port_range.start.line = self.range.start.line; new_port_range.start.line = self.range.end.line;
new_port_range.start.character = self.range.start.character + 1; new_port_range.start.character = self.range.end.character + 1;
} }
new_port_range.end.line += 1; new_port_range.end.line += 1;
@ -284,8 +205,8 @@ impl Instance {
// TODO: 精心调制这个方法 // TODO: 精心调制这个方法
if let Some(param_range) = &self.instparams { if let Some(param_range) = &self.instparams {
let mut new_param_range = param_range.clone(); let mut new_param_range = param_range.clone();
new_param_range.start.line = self.range.start.line; new_param_range.start.line = self.range.end.line;
new_param_range.start.character = self.range.start.character + 1; new_param_range.start.character = self.range.end.character;
new_param_range.end.line += 1; new_param_range.end.line += 1;
return new_param_range; return new_param_range;
} }
@ -294,19 +215,9 @@ impl Instance {
} }
} }
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Entity {
pub name: String,
pub params: Vec<Parameter>,
pub ports: Vec<Port>,
pub range: Range
}
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Module { pub struct Module {
pub name: String, pub name: String,
#[serde(rename = "archName")]
pub arch_name: String,
pub params: Vec<Parameter>, pub params: Vec<Parameter>,
pub ports: Vec<Port>, pub ports: Vec<Port>,
pub instances: Vec<Instance>, pub instances: Vec<Instance>,
@ -321,16 +232,9 @@ pub struct DefineParam {
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Define { pub struct Define {
/// 宏的名字
/// <code>`define {name} {replacement}</code>
pub name: String, pub name: String,
/// 宏的名字
/// <code>`define {name} {replacement}</code>
pub replacement: String, pub replacement: String,
/// 宏的范围
pub range: Range, pub range: Range,
/// 宏的参数(如果为函数宏才会有这个选项)
/// <code>`define {name} {replacement}({...params})</code>
pub params: Vec<DefineParam> pub params: Vec<DefineParam>
} }
@ -363,10 +267,7 @@ pub struct Macro {
pub struct FastHdlparam { pub struct FastHdlparam {
#[serde(rename = "macro")] #[serde(rename = "macro")]
pub fast_macro: Macro, pub fast_macro: Macro,
#[serde(rename = "fileType")] pub content: Vec<Module>
pub file_type: String,
pub content: Vec<Module>,
pub entitys: Vec<Entity>
} }
impl FastHdlparam { impl FastHdlparam {
@ -383,7 +284,6 @@ impl FastHdlparam {
pub fn new_module(&mut self, name: &str, range: Range) { pub fn new_module(&mut self, name: &str, range: Range) {
let module = Module { let module = Module {
name: name.to_string(), name: name.to_string(),
arch_name: "".to_string(),
params: Vec::new(), params: Vec::new(),
ports: Vec::new(), ports: Vec::new(),
instances: Vec::new(), instances: Vec::new(),
@ -392,56 +292,6 @@ impl FastHdlparam {
self.content.push(module); self.content.push(module);
} }
pub fn new_vhdl_module(&mut self, name: String, arch_name: String, range: Range) {
let module = Module {
name,
arch_name,
params: Vec::new(),
ports: Vec::new(),
instances: Vec::new(),
range
};
self.content.push(module);
}
pub fn new_entity(&mut self, name: String, range: Range) {
let entity = Entity {
name,
params: Vec::new(),
ports: Vec::new(),
range
};
self.entitys.push(entity);
}
pub fn add_entity_parameter(&mut self, name: &str, net_type: &str, init: &str, range: Range) {
if let Some(last_entity) = self.entitys.last_mut() {
let parameter = Parameter {
name: name.to_string(),
net_type: net_type.to_string(),
init: init.to_string(),
range
};
last_entity.params.push(parameter);
last_entity.params.dedup();
}
}
pub fn add_entity_port(&mut self, name: &str, dir_type: &str, net_type: &str, width: &str, range: Range) {
if let Some(last_entity) = self.entitys.last_mut() {
let port = Port {
name: name.to_string(),
dir_type: dir_type.to_string(),
net_type: net_type.to_string(),
width: width.to_string(),
signed: "unsigned".to_string(),
range
};
last_entity.ports.push(port);
last_entity.ports.dedup();
}
}
pub fn update_module_range(&mut self, name: &str, end_line: u32, end_character: u32) { pub fn update_module_range(&mut self, name: &str, end_line: u32, end_character: u32) {
if let Some(matched_module) = self.content.iter_mut().find(|module| module.name == name) { if let Some(matched_module) = self.content.iter_mut().find(|module| module.name == name) {
matched_module.range.end.line = end_line; matched_module.range.end.line = end_line;
@ -495,21 +345,15 @@ impl FastHdlparam {
} }
pub struct HdlFile { pub struct HdlFile {
/// 专注于模块树构建和粗粒度 AST 信息的数据结构
pub fast: FastHdlparam, pub fast: FastHdlparam,
/// 名字到 module 映射的 map pub name_to_module: HashMap<String, Module>
pub name_to_module: HashMap<String, Module>,
/// 解析器生成的额外信息
pub parse_result: sv_parser::common::ParseResult,
/// 解析器生成的 AST 或者类似 AST 的数据结构
pub ast_like: Option<AstLike>
} }
pub struct HdlParam { pub struct HdlParam {
/// 路径到 HdlFile 的映射 /// 路径到 HdlFile 的映射
pub path_to_hdl_file: RwLock<HashMap<String, HdlFile>>, pub path_to_hdl_file: RwLock<HashMap<String, HdlFile>>,
/// 模块名字到其所在的 HdlFile 路径的映射 /// 模块名字到其所在的 HdlFile 路径的映射
pub module_name_to_path: RwLock<HashMap<String, String>>, pub module_name_to_path: RwLock<HashMap<String, String>>
} }
@ -522,14 +366,8 @@ impl HdlParam {
} }
} }
/// 根据 path 更新 fast 和 parse_result /// 根据 path 更新 fast
pub fn update_hdl_file( pub fn update_fast(&self, path: String, fast: FastHdlparam) {
&self,
path: String,
fast: FastHdlparam,
parse_result: sv_parser::common::ParseResult,
ast_like: Option<AstLike>
) {
let mut fast_map = self.path_to_hdl_file.write().unwrap(); let mut fast_map = self.path_to_hdl_file.write().unwrap();
// 构建映射 // 构建映射
let mut name_to_module = HashMap::<String, Module>::new(); let mut name_to_module = HashMap::<String, Module>::new();
@ -542,7 +380,7 @@ impl HdlParam {
} }
} }
let file = HdlFile { fast, name_to_module, parse_result, ast_like }; let file = HdlFile { fast, name_to_module };
fast_map.insert(path, file); fast_map.insert(path, file);
} }
@ -552,7 +390,7 @@ impl HdlParam {
if let Some(path) = module_name_to_path.get(name) { if let Some(path) = module_name_to_path.get(name) {
let fast_map = self.path_to_hdl_file.read().unwrap(); let fast_map = self.path_to_hdl_file.read().unwrap();
if let Some(hdl_file) = fast_map.get(path) { if let Some(hdl_file) = fast_map.get(path) {
if let Some(module) = hdl_file.name_to_module.get(name) { if let Some(module) = hdl_file.name_to_module.get(name) {
return Some(module.clone()); return Some(module.clone());
} }
} }
@ -561,52 +399,6 @@ impl HdlParam {
None None
} }
/// 相比于 find_module_by_name该方法会返回更多有关 该 module 的必要上下文,
/// 避免重复获取锁,提升性能
/// 返回三元组 module, file_type, def_path
pub fn find_module_context_by_name(&self, name: &str) -> Option<(Module, String, String)> {
// 获取 module_name_to_path 的读锁并查找路径
let module_name_to_path = self.module_name_to_path.read().unwrap();
if let Some(path) = module_name_to_path.get(name) {
// 获取 path_to_hdl_file 的读锁并查找 HdlFile
let fast_map = self.path_to_hdl_file.read().unwrap();
if let Some(hdl_file) = fast_map.get(path) {
// 查找模块
if let Some(module) = hdl_file.name_to_module.get(name) {
// check vhdl entity params and ports
let entitys = hdl_file.fast.entitys
.iter()
.filter(|ent| ent.name == name)
.cloned()
.collect::<Vec<Entity>>();
// set entity params and ports to arch
let mut module = module.clone();
if let Some(entity) = entitys.first() {
module.params = entity.params.clone();
module.ports = entity.ports.clone();
}
return Some((
module,
hdl_file.fast.file_type.to_string(),
path.to_string()
));
}
}
}
None
}
/// 根据 module name 计算出对应的 file type默认为 common
pub fn find_file_type_by_module_name(&self, module_name: &str) -> String {
if let Some((_, file_type, _)) = self.find_module_context_by_name(module_name) {
return file_type;
}
"common".to_string()
}
/// 输入 module 名字,找到 module 定义的文件的路径 /// 输入 module 名字,找到 module 定义的文件的路径
pub fn find_module_definition_path(&self, module_name: &str) -> Option<String> { pub fn find_module_definition_path(&self, module_name: &str) -> Option<String> {
let module_name_to_path = self.module_name_to_path.read().unwrap(); let module_name_to_path = self.module_name_to_path.read().unwrap();

View File

@ -4,8 +4,4 @@ pub mod sv_parser;
pub mod vhdl_parser; pub mod vhdl_parser;
pub mod cache_storage; pub mod cache_storage;
pub mod primitive_parser;
pub mod scope_tree;

View File

@ -1,343 +0,0 @@
use std::fs;
use std::path::PathBuf;
use std::sync::RwLock;
use std::{collections::HashMap, fs::File};
use std::io::{BufReader, Read};
use bincode::Error;
use regex::Regex;
use ropey::Rope;
use serde::{Serialize, Deserialize};
use sv_parser::{unwrap_node, RefNode};
use xml::reader::{EventReader, XmlEvent};
use super::hdlparam::{FastHdlparam, InstParameter, Macro, Range};
use super::sv_parser::{get_identifier, get_instance_params, get_instance_ports, get_position, get_pp_range};
pub struct PrimitiveText {
pub name_to_text: RwLock<HashMap<String, String>>
}
impl PrimitiveText {
pub fn new() -> Self {
Self {
name_to_text: RwLock::new(HashMap::<String, String>::new())
}
}
pub fn update_text(&self, name: &str, text: &str) {
let mut text_map = self.name_to_text.write().unwrap();
text_map.insert(name.to_owned(), text.to_owned());
}
pub fn get_comment(&self, name: &str, text: &str) -> String {
if let Some(comment) = PrimitiveText::extract_pattern_comments(text).get(name) {
"// ".to_string() + comment
} else {
"".to_string()
}
}
fn extract_pattern_comments(text: &str) -> HashMap<String, String> {
let re = Regex::new(r"\s*\.(?P<x>[^\(]+)\((?P<y>[^\)]+)\)(?:,)?\s*//\s*(?P<z>.+)").unwrap();
text.lines()
.filter_map(|line| {
re.captures(line).map(|caps| {
let name = caps.name("x").unwrap().as_str().to_string();
let comment = caps.name("z").unwrap().as_str().to_string();
(name, comment)
})
})
.collect()
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Template {
pub text: String,
pub fast: FastHdlparam
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct PrimitiveXml {
pub name_to_template: HashMap<String, Template>
}
pub fn load_primitive_bin(file: &str) -> Option<PrimitiveXml> {
if let Ok(mut file) = File::open(file) {
let mut buffer = Vec::new();
match file.read_to_end(&mut buffer) {
Ok(_) => {
let deserialized_data: PrimitiveXml = bincode::deserialize(&buffer).unwrap();
Some(deserialized_data)
}
Err(_) => {
None
}
}
} else {
None
}
}
#[allow(unused)]
pub fn init_parse_primitive_files(dir: &str) -> Result<PrimitiveXml, Error> {
let mut primitive_xml = PrimitiveXml::default();
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if let Some(ext) = path.extension() {
if ext == "xml" {
println!("parse xml file {}", path.to_str().unwrap());
if let Ok(primitive) = xml_parser(path.to_str().unwrap()) {
primitive_xml.name_to_template.extend(
primitive.name_to_template
);
};
}
}
}
Ok(primitive_xml)
}
fn xml_parser(path: &str) -> Result<PrimitiveXml, Error> {
let file = File::open(path)?;
let file = BufReader::new(file);
let mut parser = EventReader::new(file);
let mut primitive_xml = PrimitiveXml::default();
loop {
match parser.next() {
Ok(XmlEvent::StartElement { name, .. }) => {
if name.local_name == "Template" {
if let Some((inst_name , template)) = xml_parse_template(&mut parser) {
primitive_xml.name_to_template.insert(inst_name, template);
}
}
}
Ok(XmlEvent::EndElement { name }) => {
if name.local_name == "RootFolder" { break; }
}
_ => ()
}
}
Ok(primitive_xml)
}
fn xml_parse_template(parser: &mut EventReader<BufReader<File>>) -> Option<(String, Template)> {
loop {
match parser.next() {
Ok(XmlEvent::Characters(text)) => {
if text.contains("Cut code below this line") {
if let Some((name, text, fast)) = xml_parse_text(&text) {
return Some((name, Template { text, fast }));
} else {
return None
}
}
}
_ => return None
}
}
}
fn xml_parse_text(text: &str) -> Option<(String, String, FastHdlparam)> {
let module_string = "module primitive_module();\n".to_string() + text + "\nendmodule";
if let Ok((syntax_tree, _)) = sv_parser::parse_sv_str(
&module_string,
PathBuf::new(),
&HashMap::new(),
&Vec::<PathBuf>::new(),
true,
true
) {
let doc = Rope::from_str(syntax_tree.text.text());
let mut res_inst_name = String::new();
let mut hdlparam = FastHdlparam {
fast_macro: Macro {
defines: Vec::new(),
errors: Vec::new(),
includes: Vec::new(),
invalid: Vec::new()
},
content: Vec::new(),
entitys: Vec::new(),
file_type: "primitives".to_string()
};
let res_text = syntax_tree.text.text().to_string();
for node in &syntax_tree {
match node {
RefNode::ModuleDeclaration(x) => {
let start_keyword = unwrap_node!(x, Keyword).unwrap();
let start_keyword = get_identifier(start_keyword).unwrap();
let start_pos = get_position(&doc, start_keyword, 0);
let module_range = get_pp_range(&doc, RefNode::ModuleDeclaration(x));
let module_range = Range { start: start_pos, end: module_range.end };
let id = unwrap_node!(x, ModuleIdentifier).unwrap();
let id = get_identifier(id).unwrap();
let name = syntax_tree.get_str(&id).unwrap();
hdlparam.new_module(name, module_range);
}
RefNode::ModuleInstantiation(x) => {
if let Some(id) = unwrap_node!(x, ModuleIdentifier) {
let id = get_identifier(id).unwrap();
let inst_type = syntax_tree.get_str(&id).unwrap();
let start_pos = get_position(&doc, id, 0);
let range = get_pp_range(&doc, RefNode::ModuleInstantiation(x));
let inst_range = Range { start: start_pos, end: range.end };
if let Some(id) = unwrap_node!(x, HierarchicalInstance) {
let hier_node = id.clone();
let id = get_identifier(id).unwrap();
let name = syntax_tree.get_str(&id).unwrap();
let param_range = match unwrap_node!(x, ParameterValueAssignment) {
Some(inst_node) => {
get_pp_range(&doc, inst_node).to_option()
}
_ => None
};
let inst_param_assignments = if let Some(param_node) = unwrap_node!(x, ParameterValueAssignment) {
get_instance_params(&syntax_tree, &doc, param_node)
} else {
Vec::<InstParameter>::new()
};
let inst_port_assignments = get_instance_ports(&syntax_tree, &doc, hier_node.clone());
let port_range = get_pp_range(&doc, hier_node).to_option();
res_inst_name = inst_type.to_string();
hdlparam.add_instance(
name, inst_type, inst_range,
param_range, inst_param_assignments,
port_range, inst_port_assignments
);
}
}
}
RefNode::GateInstantiation(x) => {
let id = unwrap_node!(x, GateInstantiation).unwrap();
let id = get_identifier(id).unwrap();
let inst_type = syntax_tree.get_str(&id).unwrap();
let start_pos = get_position(&doc, id, 0);
let range = get_pp_range(&doc, RefNode::GateInstantiation(x));
let inst_range = Range { start: start_pos, end: range.end };
match unwrap_node!(x, NInputGateInstance, NOutputGateInstance) {
Some(id) => {
let gate_node = id.clone();
let id = get_identifier(id).unwrap();
let name = syntax_tree.get_str(&id).unwrap();
let param_range = None;
let inst_port_assignments = get_instance_ports(&syntax_tree, &doc, gate_node.clone());
let port_range = get_pp_range(&doc, gate_node).to_option();
hdlparam.add_instance(
name, inst_type, inst_range,
param_range, Vec::<InstParameter>::new(),
port_range, inst_port_assignments
);
}
_ => ()
}
}
_ => ()
}
}
for module in hdlparam.content.iter_mut() {
module.name = res_inst_name.clone();
}
Some((res_inst_name, res_text, hdlparam))
} else {
None
}
}
#[cfg(test)]
mod tests {
use std::{fs::{self, File}, io::Write, path::Path};
use super::{init_parse_primitive_files, xml_parser};
const TESTFILE: &str = "primitive_files/verilog2.xml";
const TESTDIR: &str = "primitive_files";
#[test]
fn gen_primitive_bin() {
if let Ok(primitive_info) = init_parse_primitive_files("primitive_files") {
let serialized_data = bincode::serialize(&primitive_info).unwrap();
let path = "target/primitive.bin";
let mut file = File::create(path).unwrap();
let _ = file.write_all(&serialized_data);
} else {
println!("parse primitive error");
}
}
#[test]
fn read_primitive_bin() {
let target_path = "target/primitive.bin";
let load = crate::core::primitive_parser::load_primitive_bin(&target_path);
assert!(load.is_some());
}
#[test]
fn test_xml() {
let res = xml_parser(TESTFILE);
match res {
Ok(r) => {
println!("res len {:#?}", r.name_to_template.len())
}
Err(e) => {
println!("error {:#?}", e)
}
}
}
#[test]
fn test_dir() {
// 判断路径是否存在且为文件夹
let path = Path::new(TESTDIR);
if path.exists() && path.is_dir() {
// 递归遍历文件夹
if let Err(e) = traverse_directory(path) {
eprintln!("Error: {}", e);
}
} else {
eprintln!("Path does not exist or is not a directory");
}
}
fn traverse_directory(dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
// 递归遍历子文件夹
traverse_directory(&path)?;
} else if path.is_file() {
// 检查文件扩展名
if let Some(ext) = path.extension() {
if ext == "xml" {
println!("Test file: {:?}", path);
let file_path = path.to_str().unwrap();
let _ = xml_parser(file_path);
}
}
}
}
}
Ok(())
}
}

View File

@ -1,231 +0,0 @@
use crate::core::hdlparam::{self, FastHdlparam};
use log::info;
use ropey::Rope;
use sv_parser::*;
use tower_lsp::lsp_types::*;
// 定义有关 scope 相关的基础类的
pub mod common;
// 定义如何把 ast 转变为类似于 common 中的数据结构
pub mod parse;
use common::*;
use parse::*;
type ScopesAndDefs = Option<(Vec<Box<dyn Scope>>, Vec<Box<dyn Definition>>)>;
/// Take a given syntax node from a sv-parser syntax tree and extract out the definition/scope at
/// that point.
pub fn match_definitions(
syntax_tree: &SyntaxTree,
event_iter: &mut EventIter,
node: RefNode,
url: &Url,
) -> ScopesAndDefs {
let mut definitions: Vec<Box<dyn Definition>> = Vec::new();
let mut scopes: Vec<Box<dyn Scope>> = Vec::new();
match node {
RefNode::IncludeStatement(n) => {
info!("enter IncludeStatement");
info!("{:?}", n);
}
RefNode::ModuleDeclaration(n) => {
let module = module_dec(syntax_tree, n, event_iter, url);
if module.is_some() {
scopes.push(Box::new(module?));
}
}
RefNode::InterfaceDeclaration(n) => {
let interface = interface_dec(syntax_tree, n, event_iter, url);
if interface.is_some() {
scopes.push(Box::new(interface?));
}
}
RefNode::UdpDeclaration(n) => {
let dec = udp_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::ProgramDeclaration(n) => {
let dec = program_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::PackageDeclaration(n) => {
let dec = package_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::ConfigDeclaration(n) => {
let dec = config_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::ClassDeclaration(n) => {
let dec = class_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::PortDeclaration(n) => {
let ports = port_dec_non_ansi(syntax_tree, n, event_iter, url);
if ports.is_some() {
for port in ports? {
definitions.push(Box::new(port));
}
}
}
RefNode::NetDeclaration(n) => {
let nets = net_dec(syntax_tree, n, event_iter, url);
if nets.is_some() {
for net in nets? {
definitions.push(Box::new(net));
}
}
}
RefNode::DataDeclaration(n) => {
let vars = data_dec(syntax_tree, n, event_iter, url);
if let Some(vars) = vars {
for var in vars {
match var {
Declaration::Dec(dec) => definitions.push(Box::new(dec)),
Declaration::Import(dec) => definitions.push(Box::new(dec)),
Declaration::Scope(scope) => scopes.push(Box::new(scope)),
}
}
}
}
RefNode::ParameterDeclaration(n) => {
let vars = param_dec(syntax_tree, n, event_iter, url);
if vars.is_some() {
for var in vars? {
definitions.push(Box::new(var));
}
}
}
RefNode::LocalParameterDeclaration(n) => {
let vars = localparam_dec(syntax_tree, n, event_iter, url);
if vars.is_some() {
for var in vars? {
definitions.push(Box::new(var));
}
}
}
RefNode::FunctionDeclaration(n) => {
let dec = function_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::TaskDeclaration(n) => {
let dec = task_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::ModportDeclaration(n) => {
let decs = modport_dec(syntax_tree, n, event_iter, url);
if decs.is_some() {
for dec in decs? {
definitions.push(Box::new(dec));
}
}
}
RefNode::ModuleInstantiation(n) => {
let decs = module_inst(syntax_tree, n, event_iter, url);
if decs.is_some() {
for dec in decs? {
definitions.push(Box::new(dec));
}
}
}
RefNode::TextMacroDefinition(n) => {
let dec = text_macro_def(syntax_tree, n, event_iter, url);
if dec.is_some() {
definitions.push(Box::new(dec?));
}
}
_ => (),
}
Some((scopes, definitions))
}
/// convert the syntax tree to a scope tree
/// the root node is the global scope
pub fn get_scopes_from_syntax_tree(syntax_tree: &SyntaxTree, url: &Url) -> Option<GenericScope> {
let mut scopes: Vec<Box<dyn Scope>> = Vec::new();
let mut global_scope: GenericScope = GenericScope::new(url);
global_scope.ident = String::from("global");
let mut event_iter = syntax_tree.into_iter().event();
// iterate over each enter event and extract out any scopes or definitions
// match_definitions is recursively called so we get a tree in the end
while let Some(event) = event_iter.next() {
match event {
NodeEvent::Enter(node) => {
let mut result = match_definitions(syntax_tree, &mut event_iter, node, url)?;
global_scope.defs.append(&mut result.1);
scopes.append(&mut result.0);
}
NodeEvent::Leave(_) => (),
}
}
global_scope.scopes.append(&mut scopes);
Some(global_scope)
}
pub fn get_scopes_from_vhdl_fast(fast: &FastHdlparam, text: &Rope, url: &Url) -> Option<GenericScope> {
let mut scopes: Vec<Box<dyn Scope>> = Vec::new();
let mut global_scope: GenericScope = GenericScope::new(url);
global_scope.ident = String::from("global");
for module in &fast.content {
let mut scope: GenericScope = GenericScope::new(url);
scope.ident = module.name.clone();
let module_range = module.range.clone();
scope.start = position_to_byte_idx(text, &module_range.start);
scope.end = position_to_byte_idx(text, &module_range.end);
scope.byte_idx = scope.start + 7;
for parameter in &module.params {
let mut def = GenericDec::new(url);
def.ident = parameter.name.clone();
let parameter_range = parameter.range.clone();
def.byte_idx = position_to_byte_idx(text, &parameter_range.start);
def.completion_kind = CompletionItemKind::TYPE_PARAMETER;
def.symbol_kind = SymbolKind::TYPE_PARAMETER;
scope.defs.push(Box::new(def));
}
for port in &module.ports {
let mut port_def = PortDec::new(url);
port_def.ident = port.name.clone();
let port_range = port.range.clone();
port_def.byte_idx = position_to_byte_idx(text, &port_range.start);
port_def.type_str = port.dir_type.clone();
scope.defs.push(Box::new(port_def));
}
for inst in &module.instances {
let mut instance = ModInst::new(url);
instance.ident = inst.name.clone();
let inst_range = inst.range.clone();
instance.byte_idx = position_to_byte_idx(text, &inst_range.start);
instance.type_str = inst.inst_type.clone();
instance.mod_ident = inst.inst_type.clone();
scope.defs.push(Box::new(instance));
}
scopes.push(Box::new(scope));
}
global_scope.scopes.append(&mut scopes);
Some(global_scope)
}
fn position_to_byte_idx(text: &Rope, pos: &hdlparam::Position) -> usize {
let char = text.line_to_char(pos.line as usize) + pos.character as usize;
text.char_to_byte(char)
}

View File

@ -2,7 +2,6 @@ use std::fs::{self, File};
use std::io::BufRead; use std::io::BufRead;
use std::io::BufReader; use std::io::BufReader;
use std::path::PathBuf; use std::path::PathBuf;
#[allow(unused)]
use log::info; use log::info;
use regex::Regex; use regex::Regex;
use ropey::Rope; use ropey::Rope;
@ -13,7 +12,7 @@ use crate::core::hdlparam::{AssignType, Position, Range};
use crate::sources::{recovery_sv_parse_with_retry, LSPSupport}; use crate::sources::{recovery_sv_parse_with_retry, LSPSupport};
use crate::utils::to_escape_path; use crate::utils::to_escape_path;
use super::hdlparam::{self, FastHdlparam, Include, InstParameter, InstPort, Macro}; use super::hdlparam::{self, FastHdlparam, InstParameter, InstPort, Macro};
macro_rules! advance_until_leave { macro_rules! advance_until_leave {
($tokens:ident, $tree:ident, $event_iter:ident, $node:path) => {{ ($tokens:ident, $tree:ident, $event_iter:ident, $node:path) => {{
@ -72,65 +71,39 @@ pub fn sv_parser(path: &str) -> Option<FastHdlparam> {
let doc = Rope::from_str(&text); let doc = Rope::from_str(&text);
let uri = Url::from_file_path(&path).unwrap(); let uri = Url::from_file_path(&path).unwrap();
let result = recovery_sv_parse_with_retry(&doc, &uri, &None, &includes); let result = recovery_sv_parse_with_retry(&doc, &uri, &None, &includes);
// println!("result: {result:?}");
if let Some((syntax_tree, _)) = result { if let Some(syntax_tree) = result {
if let Ok(fast) = make_fast_from_syntaxtree(&syntax_tree) { if let Ok(hdlparam) = make_fast_from_syntaxtree(&syntax_tree, &path) {
return Some(fast); return Some(hdlparam);
} }
} }
None None
} }
pub fn make_fast_from_syntaxtree( pub fn make_fast_from_syntaxtree(syntax_tree: &SyntaxTree, path: &PathBuf) -> Result<FastHdlparam, std::io::Error> {
syntax_tree: &SyntaxTree
) -> Result<FastHdlparam, std::io::Error> {
// 对不同操作系统文件路径的支持 // 对不同操作系统文件路径的支持
let mut fast: FastHdlparam = FastHdlparam { let path = to_escape_path(path);
let mut hdlparam = FastHdlparam {
fast_macro: Macro { fast_macro: Macro {
defines: Vec::new(), defines: Vec::new(),
errors: Vec::new(), errors: Vec::new(),
includes: Vec::<Include>::new(), includes: get_includes(&path),
invalid: Vec::new() invalid: Vec::new()
}, },
content: Vec::new(), content: Vec::new()
entitys: Vec::new(),
file_type: "common".to_string()
}; };
let mut ansi_port_last_dir = ""; let mut ansi_port_last_dir = "";
// println!("{:?}", syntax_tree);
// &SyntaxTree is iterable // &SyntaxTree is iterable
let doc = Rope::from_str(syntax_tree.text.text()); let doc = Rope::from_str(syntax_tree.text.text());
// 上一个 module 可以在 fast 的 content 的最后一个中找到
for node in syntax_tree { for node in syntax_tree {
match node { match node {
RefNode::IncludeCompilerDirective(x) => {
match x {
sv_parser::IncludeCompilerDirective::DoubleQuote(x) => {
let (_, ref keyword, ref literal) = x.nodes;
let (keyword_locate, _) = keyword.nodes;
let (literal_locate, _) = literal.nodes;
let include_path_string = syntax_tree.get_str_trim(literal).unwrap().trim_matches('"');
let include_range = Range {
start: get_position(&doc, keyword_locate, 0),
end: get_position(&doc, literal_locate, literal_locate.len)
};
fast.fast_macro.includes.push(Include {
path: include_path_string.to_string(),
range: include_range
});
}
sv_parser::IncludeCompilerDirective::AngleBracket(_) => {
},
sv_parser::IncludeCompilerDirective::TextMacroUsage(_) => {
},
}
}
RefNode::TextMacroDefinition(x) => { RefNode::TextMacroDefinition(x) => {
if let Some(start) = unwrap_node!(x, TextMacroDefinition) { if let Some(start) = unwrap_node!(x, TextMacroDefinition) {
let start = get_identifier(start).unwrap(); let start = get_identifier(start).unwrap();
@ -170,20 +143,20 @@ pub fn make_fast_from_syntaxtree(
}; };
let define_range = Range { start: start_pos, end: end_pos }; let define_range = Range { start: start_pos, end: end_pos };
fast.add_define(name, replacement, define_range, params_vec); hdlparam.add_define(name, replacement, define_range, params_vec);
} }
} }
RefNode::ModuleDeclaration(x) => { RefNode::ModuleDeclaration(x) => {
let start_keyword = unwrap_node!(x, Keyword).unwrap(); let start_keyword = unwrap_node!(x, Keyword).unwrap();
let start_keyword = get_identifier(start_keyword).unwrap(); let start_keyword = get_identifier(start_keyword).unwrap();
let start_pos = get_position(&doc, start_keyword, 0); let start_pos = get_position(&doc, start_keyword, 0);
let module_range = Range { start: start_pos.clone(), end: start_pos }; let module_range = get_pp_range(&doc, RefNode::ModuleDeclaration(x));
let module_range = Range { start: start_pos, end: module_range.end };
let id = unwrap_node!(x, ModuleIdentifier).unwrap(); let id = unwrap_node!(x, ModuleIdentifier).unwrap();
let id = get_identifier(id).unwrap(); let id = get_identifier(id).unwrap();
let name = syntax_tree.get_str(&id).unwrap(); let name = syntax_tree.get_str(&id).unwrap();
hdlparam.new_module(name, module_range);
fast.new_module(name, module_range);
} }
RefNode::ParameterDeclaration(param_dec) => { RefNode::ParameterDeclaration(param_dec) => {
let mut event_iter = param_dec.into_iter().event(); let mut event_iter = param_dec.into_iter().event();
@ -214,7 +187,7 @@ pub fn make_fast_from_syntaxtree(
}; };
let end_pos = get_position(&doc, loc.clone(), loc.len); let end_pos = get_position(&doc, loc.clone(), loc.len);
let param_range = Range { start: start_pos, end: end_pos }; let param_range = Range { start: start_pos, end: end_pos };
fast.add_parameter(name, net_type, init, param_range); hdlparam.add_parameter(name, net_type, init, param_range);
} }
} }
_ => () _ => ()
@ -257,7 +230,7 @@ pub fn make_fast_from_syntaxtree(
let name = syntax_tree.get_str(&id).unwrap(); let name = syntax_tree.get_str(&id).unwrap();
let port_range = Range { start: start_pos.clone(), end: get_position(&doc, id, id.len) }; let port_range = Range { start: start_pos.clone(), end: get_position(&doc, id, id.len) };
fast.add_port(name, dir_type, net_type, width.as_str(), port_range); hdlparam.add_port(name, dir_type, net_type, width.as_str(), port_range);
} }
} }
} }
@ -306,7 +279,7 @@ pub fn make_fast_from_syntaxtree(
_ => "1".to_string() _ => "1".to_string()
}; };
fast.add_port(name, ansi_port_last_dir, net_type, width.as_str(), port_range); hdlparam.add_port(name, ansi_port_last_dir, net_type, width.as_str(), port_range);
} }
} }
RefNode::ModuleInstantiation(x) => { RefNode::ModuleInstantiation(x) => {
@ -338,7 +311,7 @@ pub fn make_fast_from_syntaxtree(
let inst_port_assignments = get_instance_ports(&syntax_tree, &doc, hier_node.clone()); let inst_port_assignments = get_instance_ports(&syntax_tree, &doc, hier_node.clone());
let port_range = get_pp_range(&doc, hier_node).to_option(); let port_range = get_pp_range(&doc, hier_node).to_option();
fast.add_instance( hdlparam.add_instance(
name, inst_type, inst_range, name, inst_type, inst_range,
param_range, inst_param_assignments, param_range, inst_param_assignments,
port_range, inst_port_assignments port_range, inst_port_assignments
@ -364,7 +337,7 @@ pub fn make_fast_from_syntaxtree(
let inst_port_assignments = get_instance_ports(&syntax_tree, &doc, gate_node.clone()); let inst_port_assignments = get_instance_ports(&syntax_tree, &doc, gate_node.clone());
let port_range = get_pp_range(&doc, gate_node).to_option(); let port_range = get_pp_range(&doc, gate_node).to_option();
fast.add_instance( hdlparam.add_instance(
name, inst_type, inst_range, name, inst_type, inst_range,
param_range, Vec::<InstParameter>::new(), param_range, Vec::<InstParameter>::new(),
port_range, inst_port_assignments port_range, inst_port_assignments
@ -373,36 +346,18 @@ pub fn make_fast_from_syntaxtree(
_ => () _ => ()
} }
} }
RefNode::Keyword(x) => {
let id = x.nodes.0;
let name = syntax_tree.get_str(&id).unwrap();
let pos = get_position(&doc, id, name.len());
// 根据关键词进行特殊处理
// 比如找到 endmodule 的位置来确定目前的这个 module 的 end
match name {
"endmodule" => {
// 尝试获取 content 最后一个元素的可变引用,该引用如果存在,说明已经创建了一个 module
if let Some(last_module) = fast.content.last_mut() {
last_module.range.end.line = pos.line;
last_module.range.end.character = pos.character;
}
},
_ => {}
}
}
_ => () _ => ()
} }
} }
// update_module_range(&path, &mut hdlparam); // update_module_range(&path, &mut hdlparam);
Ok(fast) Ok(hdlparam)
} }
// 获取 port 或者 param 的 range // 获取 port 或者 param 的 range
/// 返回的四元组:(start_line, start_character, end_line, end_character) /// 返回的四元组:(start_line, start_character, end_line, end_character)
pub fn get_pp_range(doc: &Rope, node: RefNode) -> Range { fn get_pp_range(doc: &Rope, node: RefNode) -> Range {
if let Some(locate) = get_first_last_locate(node) { if let Some(locate) = get_first_last_locate(node) {
Range { Range {
start: get_position(doc, locate.0, 0), start: get_position(doc, locate.0, 0),
@ -432,7 +387,7 @@ fn get_first_last_locate(node: RefNode) -> Option<(Locate, Locate)> {
} }
} }
pub fn get_instance_params(syntax_tree: &SyntaxTree, doc: &Rope, node: RefNode) -> Vec<InstParameter> { fn get_instance_params(syntax_tree: &SyntaxTree, doc: &Rope, node: RefNode) -> Vec<InstParameter> {
let mut parameters = Vec::new(); let mut parameters = Vec::new();
for list in node { for list in node {
@ -497,7 +452,7 @@ pub fn get_instance_params(syntax_tree: &SyntaxTree, doc: &Rope, node: RefNode)
parameters parameters
} }
pub fn get_instance_ports(syntax_tree: &SyntaxTree, doc: &Rope, node: RefNode) -> Vec<InstPort> { fn get_instance_ports(syntax_tree: &SyntaxTree, doc: &Rope, node: RefNode) -> Vec<InstPort> {
let mut ports = Vec::new(); let mut ports = Vec::new();
for list in node { for list in node {
match unwrap_node!(list, ListOfPortConnectionsNamed, ListOfPortConnectionsOrdered, InputTerminal, OutputTerminal) { match unwrap_node!(list, ListOfPortConnectionsNamed, ListOfPortConnectionsOrdered, InputTerminal, OutputTerminal) {
@ -674,7 +629,7 @@ fn parse_expression(syntax_tree: &SyntaxTree, x: &sv_parser::NeedParseExpression
} }
} }
pub fn get_identifier(node: RefNode) -> Option<Locate> { fn get_identifier(node: RefNode) -> Option<Locate> {
// unwrap_node! can take multiple types // unwrap_node! can take multiple types
match unwrap_node!(node, SimpleIdentifier, EscapedIdentifier, Keyword) { match unwrap_node!(node, SimpleIdentifier, EscapedIdentifier, Keyword) {
Some(RefNode::SimpleIdentifier(x)) => { Some(RefNode::SimpleIdentifier(x)) => {
@ -690,14 +645,62 @@ pub fn get_identifier(node: RefNode) -> Option<Locate> {
} }
} }
pub fn get_position(doc: &Rope, locate: Locate, offset: usize) -> Position { fn get_position(doc: &Rope, locate: Locate, offset: usize) -> Position {
let byte = locate.offset + offset; let byte = locate.offset + offset;
let pos = doc.byte_to_pos(byte); let pos = doc.byte_to_pos(byte);
hdlparam::Position::from_lsp_position(&pos) hdlparam::Position::from_lsp_position(&pos)
} }
fn get_includes(path: &PathBuf) -> Vec<crate::core::hdlparam::Include> {
let mut includes = Vec::new();
let file = File::open(path).unwrap();
let reader = BufReader::new(file);
for (line_number, line_content) in reader.lines().enumerate() {
let line_content = match line_content {
Ok(content) => content,
Err(e) => {
println!("line {} has error {}", line_number, e);
"".to_string()
}
};
if line_content.trim().starts_with("`include") {
let parts: Vec<&str> = line_content.split_whitespace().collect();
if parts.len() >= 2 {
let mut path = parts[1].trim();
if path.starts_with("\"") {
path = path.strip_prefix("\"").unwrap();
}
if path.ends_with("\"") {
path = path.strip_suffix("\"").unwrap();
}
let last_character = line_content.find(path).unwrap() + path.len();
includes.push(crate::core::hdlparam::Include {
path: path.to_string(),
range: crate::core::hdlparam::Range {
start: crate::core::hdlparam::Position {
line: (line_number + 1) as u32, character: 1
},
end: crate::core::hdlparam::Position {
line: (line_number + 1) as u32, character: last_character as u32
}
}
});
}
}
}
includes
}
#[allow(unused)] #[allow(unused)]
fn update_module_range(path: &PathBuf, fast: &mut FastHdlparam) { fn update_module_range(path: &PathBuf, hdlparam: &mut FastHdlparam) {
let file = File::open(path).unwrap(); let file = File::open(path).unwrap();
let reader = BufReader::new(file); let reader = BufReader::new(file);
@ -717,7 +720,7 @@ fn update_module_range(path: &PathBuf, fast: &mut FastHdlparam) {
module_stack.push(module_name.clone()); module_stack.push(module_name.clone());
} else if re_endmodule.is_match(&line) { } else if re_endmodule.is_match(&line) {
if let Some(module_name) = module_stack.pop() { if let Some(module_name) = module_stack.pop() {
fast.update_module_range(&module_name, (line_number + 1) as u32, current_offset as u32); hdlparam.update_module_range(&module_name, (line_number + 1) as u32, current_offset as u32);
// println!("Module {} ends.", module_name); // println!("Module {} ends.", module_name);
} }
} }

View File

@ -1,22 +1,15 @@
use std::collections::HashSet;
use std::path::PathBuf; use std::path::PathBuf;
use log::info; use vhdl_lang::ast::DesignFile;
use vhdl_lang::ast::{ArchitectureBody, ConcurrentStatement, DesignFile, Designator, EntityDeclaration, InstantiatedUnit, InterfaceDeclaration, InterfaceList, LabeledConcurrentStatement, Mode, ModeIndication, Name}; use vhdl_lang::{kind_str, Token, VHDLParser, VHDLStandard};
use vhdl_lang::{kind_str, HasTokenSpan, Token, TokenAccess, TokenSpan, VHDLParser, VHDLStandard};
use super::hdlparam::*; use super::hdlparam::*;
pub fn vhdl_parse_str(path: &PathBuf, code: &str) -> Option<DesignFile> { #[allow(unused)]
let mut diagnostics = Vec::new(); pub fn vhdl_parser(path: &str) -> FastHdlparam {
let parser = VHDLParser::new(VHDLStandard::VHDL2008); // The path of SystemVerilog source file
if let Ok((_, design_file)) = parser.parse_vhdl_str(code, path, &mut diagnostics) { let path = PathBuf::from(path);
return Some(design_file);
}
None
}
pub fn make_fast_from_units(
arch_and_entity: Vec<(Option<(ArchitectureBody, Vec<Token>)>, Option<(EntityDeclaration, Vec<Token>)>)>,
) -> Option<FastHdlparam> {
let mut hdlparam = FastHdlparam { let mut hdlparam = FastHdlparam {
fast_macro: Macro { fast_macro: Macro {
defines: Vec::new(), defines: Vec::new(),
@ -24,291 +17,567 @@ pub fn make_fast_from_units(
includes: Vec::new(), includes: Vec::new(),
invalid: Vec::new() invalid: Vec::new()
}, },
content: Vec::new(), content: Vec::new()
entitys: Vec::new(),
file_type: "common".to_string()
}; };
// info!("arch and entity {arch_and_entity:#?}"); let parser = VHDLParser::new(VHDLStandard::VHDL2008);
let mut diagnostics = Vec::new();
let (_, design_file) = parser.parse_design_file(&path, &mut diagnostics).unwrap();
arch_and_entity.iter().for_each(|units| { let mut all_tockens = Vec::new();
match units { for (tokens, _) in design_file.design_units {
(Some((arch, arch_tokens)), entity_units) => { all_tockens.extend(tokens);
let name = arch.entity_name.item.item.name_utf8(); }
let arch_name = arch.ident.tree.item.name_utf8();
let range = get_range_from_token(
arch_tokens.get_token(arch.span().get_start_token()),
arch_tokens.get_token(arch.span().get_end_token())
);
hdlparam.new_vhdl_module(name, arch_name, range); hdlparam.content.extend(parse_tokens(all_tockens));
hdlparam
}
if let Some((entity, entity_tokens)) = entity_units {
if let Some(param_list) = &entity.generic_clause {
parse_interface_list(param_list, &entity_tokens).iter().for_each(|(name, _, net_type, _, init, range)| {
hdlparam.add_entity_parameter(name, net_type, init, range.clone());
});
}
if let Some(port_list) = &entity.port_clause {
parse_interface_list(port_list, &entity_tokens).iter().for_each(|(name, dir_type, net_type, width, init, range)| {
hdlparam.add_entity_port(name, dir_type, net_type, width, range.clone());
});
}
}
let instances = arch.statements.iter().filter(|statement| { pub fn vhdl_parse(path: &PathBuf) -> Option<DesignFile> {
match statement.statement.item { let mut diagnostics = Vec::new();
ConcurrentStatement::Instance(_) => true, let parser = VHDLParser::new(VHDLStandard::VHDL2008);
_ => false if let Ok((_, design_file)) = parser.parse_design_file(path, &mut diagnostics) {
} return Some(design_file);
}) }
.map(|statement| parse_instance(statement, arch_tokens))
.collect::<Vec<Instance>>();
if let Some(last_module) = hdlparam.content.last_mut() {
last_module.instances = instances
}
}
(None, Some((entity, entity_tokens))) => {
let name = entity.ident.tree.item.name_utf8();
let range = get_range_from_token(
entity_tokens.get_token(entity.span().get_start_token()),
entity_tokens.get_token(entity.span().get_end_token())
);
hdlparam.new_entity(name, range); None
}
if let Some(param_list) = &entity.generic_clause { pub fn make_fast_from_design_file(design_file: &DesignFile) -> Option<FastHdlparam> {
parse_interface_list(param_list, &entity_tokens).iter().for_each(|(name, _, net_type, _, init, range)| { let mut hdlparam = FastHdlparam {
hdlparam.add_entity_parameter(name, net_type, init, range.clone()); fast_macro: Macro {
}); defines: Vec::new(),
} errors: Vec::new(),
includes: Vec::new(),
if let Some(port_list) = &entity.port_clause { invalid: Vec::new()
parse_interface_list(port_list, &entity_tokens).iter().for_each(|(name, dir_type, net_type, width, init, range)| { },
hdlparam.add_entity_port(name, dir_type, net_type, width, range.clone()); content: Vec::new()
}); };
}
}
(None, None) => ()
}
});
let mut all_tockens = Vec::new();
for (tokens, _) in &design_file.design_units {
all_tockens.extend(tokens.clone());
}
hdlparam.content.extend(parse_tokens(all_tockens));
Some(hdlparam) Some(hdlparam)
} }
fn parse_instance(statement: &LabeledConcurrentStatement, tokens: &Vec<Token>) -> Instance {
let name = if let Some(tree) = &statement.label.tree { tree.item.name_utf8() } else { "unknown".to_string() };
let range = get_range_from_token(
tokens.get_token(statement.span().get_start_token()),
tokens.get_token(statement.span().get_end_token())
);
let mut parsed_instance = Instance { #[allow(unused)]
name, fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
inst_type: "".to_string(), let mut modules = Vec::new();
instparams: None, let mut last_module_name = String::new();
intstparam_assignments: Vec::new(), let mut instance_type = HashSet::new();
instports: None,
intstport_assignments: Vec::new(),
range
};
match &statement.statement.item { let mut i = 0;
ConcurrentStatement::Instance(instance) => { while i < tokens.len() {
parsed_instance.inst_type = match &instance.unit { let token = &tokens[i];
InstantiatedUnit::Component(name) | InstantiatedUnit::Configuration(name) => { match kind_str(token.kind) {
parse_instance_name(name) "entity" => {
}, let start_pos = tokens[i].pos.range.start;
InstantiatedUnit::Entity(name, arch_name_ref) => { i += 1;
let name = parse_instance_name(name); let entity_name = get_value(&tokens[i]);
// println!("entity name {:?}", entity_name);
let arch_name = if let Some(arch_name_ref) = arch_name_ref { if (i >= 2 as usize) && (kind_str(tokens[i-2].kind) != "use") || (i < 2) {
format!("({})", arch_name_ref.item.item.name_utf8()) let mut end = i;
} else { while (
"".to_string() end+1 < tokens.len()) &&
}; !(kind_str(tokens[end].kind) == "end" &&
(kind_str(tokens[end+1].kind) == "entity" || get_value(&tokens[end+1]) == entity_name)) {
name + &arch_name end += 1;
}
_ => "unknown".to_string()
};
if let Some(parameter_list) = &instance.generic_map {
parsed_instance.instparams = Some(get_range_from_token(
tokens.get_token(parameter_list.span().get_start_token()),
tokens.get_token(parameter_list.span().get_end_token())
));
parsed_instance.intstparam_assignments = parameter_list.list.items.iter().map(|association_ele| {
let formal = association_ele.formal.clone().and_then(|formal|
Some(parse_string_from_tokenspan(formal.span(), tokens))
).unwrap_or(
"unknown".to_string()
);
let actual = parse_string_from_tokenspan(association_ele.actual.span(), tokens);
let assign_range = if association_ele.formal.is_some() {
get_range_from_token(
tokens.get_token(association_ele.formal.clone().unwrap().span().get_start_token()),
tokens.get_token(association_ele.actual.span().get_end_token())
)
} else {
get_range_from_token(
tokens.get_token(association_ele.actual.span().get_start_token()),
tokens.get_token(association_ele.actual.span().get_end_token())
)
};
InstParameter { parameter: Some(formal), assign_val: Some(actual), assign_type: AssignType::Named, range: assign_range }
}).collect::<Vec<InstParameter>>();
}
if let Some(port_list) = &instance.port_map {
parsed_instance.instports = Some(get_range_from_token(
tokens.get_token(port_list.span().get_start_token()),
tokens.get_token(port_list.span().get_end_token())
));
parsed_instance.intstport_assignments = port_list.list.items.iter().map(|association_ele| {
let formal = association_ele.formal.clone().and_then(|formal|
Some(parse_string_from_tokenspan(formal.span(), tokens))
).unwrap_or(
"unknown".to_string()
);
let actual = parse_string_from_tokenspan(association_ele.actual.span(), tokens);
let assign_range = if association_ele.formal.is_some() {
get_range_from_token(
tokens.get_token(association_ele.formal.clone().unwrap().span().get_start_token()),
tokens.get_token(association_ele.actual.span().get_end_token())
)
} else {
get_range_from_token(
tokens.get_token(association_ele.actual.span().get_start_token()),
tokens.get_token(association_ele.actual.span().get_end_token())
)
};
InstPort { port: Some(formal), assign_val: Some(actual), assign_type: AssignType::Named, range: assign_range }
}).collect::<Vec<InstPort>>();
}
}
_ => ()
};
parsed_instance
}
fn parse_instance_name(name: &vhdl_lang::ast::token_range::WithTokenSpan<Name>) -> String {
match &name.item {
Name::Designator(designator) => {
match &designator.item {
Designator::Identifier(symbol) => symbol.name_utf8(),
_ => "unknown".to_string()
}
}
Name::Selected(_lib_name, designator_token) => {
match &designator_token.item.item {
Designator::Identifier(symbol) => symbol.name_utf8(),
_ => "unknown".to_string()
}
}
_ => "unknown".to_string()
}
}
fn parse_interface_list(list: &InterfaceList, tokens: &Vec<Token>) -> Vec<(String, String, String, String, String, Range)> {
let mut interface_list = Vec::new();
list.items.iter().for_each(|interface| {
let range = get_range_from_token(
tokens.get_token(interface.span().get_start_token()),
tokens.get_token(interface.span().get_end_token())
);
match interface {
InterfaceDeclaration::Object(object) => {
let name = object.idents.first().unwrap().tree.item.name_utf8();
let (dir_type, net_type, width, init) = match &object.mode {
ModeIndication::Simple(simple_mode) => {
let dir_type = if let Some(mode_token) = &simple_mode.mode {
match mode_token.item {
Mode::In => "in",
Mode::Out => "out",
Mode::Buffer | Mode::InOut => "inout",
Mode::Linkage => "unknown"
}.to_string()
} else {
"unknown".to_string()
};
let net_type = simple_mode.subtype_indication.type_mark.item.to_string();
let width = if let Some(constraint) = &simple_mode.subtype_indication.constraint {
parse_width_from_tokenspan(constraint.span(), tokens)
} else {
"1".to_string()
};
let init = if let Some(expression) = &simple_mode.expression {
parse_width_from_tokenspan(expression.span(), tokens)
} else {
"unknown".to_string()
};
(dir_type, net_type, width, init)
} }
_ => ("unknown".to_string(), "unknown".to_string(), "unknown".to_string(), "unknown".to_string()) let end_pos = if end+1 < tokens.len() && get_value(&tokens[end+1]) == entity_name {
}; i = end + 1;
interface_list.push((name, dir_type, net_type, width, init, range)); tokens[end+2].pos.range.end
} } else if end + 3 < tokens.len() {
_ => () i = end + 2;
} tokens[end+3].pos.range.end
}); } else {
interface_list tokens[end].pos.range.end
} };
let module = Module {
fn parse_string_from_tokenspan(span: TokenSpan, tokens: &Vec<Token>) -> String { name: entity_name.to_string(),
span.iter().map(|id| { params: Vec::new(),
if let Some(token) = tokens.get_token(id) { ports: Vec::new(),
if get_value(token) == "None" { instances: Vec::new(),
kind_str(token.kind).to_string() range: Range {
} else { start: Position {
get_value(token) line: start_pos.line + 1,
} character: start_pos.character + 1
} else { },
"".to_string() end: Position {
} line: end_pos.line + 1,
}).collect() character: end_pos.character + 1
} }
}
fn parse_width_from_tokenspan(span: TokenSpan, tokens: &Vec<Token>) -> String { };
// skip '(' and ')' last_module_name = entity_name.to_string();
let width = span.iter().skip(1).take(span.len() - 2).map(|id| { modules.push(module);
if let Some(token) = tokens.get_token(id) {
if get_value(token) == "None" {
if kind_str(token.kind) == "downto" || kind_str(token.kind) == "to" {
":".to_string()
} else {
kind_str(token.kind).to_string()
} }
} else {
get_value(token)
} }
} else { "architecture" => {
"".to_string() if (i >= 1 as usize) && (kind_str(tokens[i-1].kind) != "end") || (i < 1) {
let name = get_value(&tokens[i+3]);
if let None = modules.iter().find(|module| module.name == name) {
let start_pos = tokens[i].pos.range.start;
i += 1;
let arch_name = get_value(&tokens[i]);
// println!("arch name {:?}", arch_name);
let mut end = i;
while (end+1 < tokens.len()) &&
!(kind_str(tokens[end].kind) == "end" &&
(kind_str(tokens[end+1].kind) == "architecture" || get_value(&tokens[end+1]) == arch_name)) {
end += 1;
}
let end_pos = if end+1 < tokens.len() && get_value(&tokens[end+1]) == arch_name {
// i = end + 2;
tokens[end+2].pos.range.end
} else if end + 3 < tokens.len() {
// i = end + 3;
tokens[end+3].pos.range.end
} else {
// i = end;
tokens[end].pos.range.end
};
let module = Module {
name: name.to_string(),
params: Vec::new(),
ports: Vec::new(),
instances: Vec::new(),
range: Range {
start: Position {
line: start_pos.line + 1,
character: start_pos.character + 1
},
end: Position {
line: end_pos.line + 1,
character: end_pos.character + 1
}
}
};
last_module_name = name.to_string();
modules.push(module);
}
}
}
"configuration" => {
i += 1;
while i < tokens.len() && !(kind_str(tokens[i].kind) == "end" && kind_str(tokens[i+1].kind) == "configuration") {
i += 1;
}
i += 1;
}
"package" => {
i += 1;
// println!("get package");
while i < tokens.len() && !(kind_str(tokens[i].kind) == "end" && kind_str(tokens[i+1].kind) == "package") {
i += 1;
}
i += 1;
}
"component" => {
i += 1;
instance_type.insert(get_value(&tokens[i]));
// println!("instance {:?}", kind_str(tokens[i].kind));
while i < tokens.len() && !(kind_str(tokens[i].kind) == "end" && kind_str(tokens[i+1].kind) == "component") {
i += 1;
}
i += 1;
}
"signal" => {
i += 1;
while !(kind_str(tokens[i+1].kind) == ";") {
i += 1;
}
}
"attribute" => {
i += 1;
while !(kind_str(tokens[i+1].kind) == ";") {
i += 1;
}
}
":" => {
if (i+2 < tokens.len()) && (kind_str(tokens[i+2].kind) != "use") {
if instance_type.contains(&get_value(&tokens[i+1])) {
let instance = Instance {
name: get_value(&tokens[i-1]),
inst_type: get_value(&tokens[i+1]),
instports: None,
instparams: None,
intstport_assignments: Vec::new(),
intstparam_assignments: Vec::new(),
range: Range {
start: Position {
line: tokens[i-1].pos.range.start.line + 1,
character: tokens[i-1].pos.range.start.character + 1
},
end: Position {
line: tokens[i+1].pos.range.start.line + 1,
character: tokens[i+1].pos.range.start.character + 1
}
}
};
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
module.instances.push(instance);
}
} else if kind_str(tokens[i+1].kind) == "entity" {
let instance = Instance {
name: get_value(&tokens[i-1]),
inst_type: get_value(&tokens[i+2]),
instports: None,
instparams: None,
intstport_assignments: Vec::new(),
intstparam_assignments: Vec::new(),
range: Range {
start: Position {
line: tokens[i-1].pos.range.start.line + 1,
character: tokens[i-1].pos.range.start.character + 1
},
end: Position {
line: tokens[i+2].pos.range.start.line + 1,
character: tokens[i+2].pos.range.start.character + 1
}
}
};
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
module.instances.push(instance);
}
i += 1;
} else {
let name = get_value(&tokens[i-1]);
let mut inst_type = String::new();
while i < tokens.len() {
if kind_str(tokens[i].kind) == "generic" || kind_str(tokens[i].kind) == "port" || kind_str(tokens[i].kind) == "end" {
i = i - 1;
break;
}
inst_type = inst_type + &get_value(&tokens[i]);
i += 1;
}
let instance = Instance {
name,
inst_type,
instports: None,
instparams: None,
intstport_assignments: Vec::new(),
intstparam_assignments: Vec::new(),
range: Range {
start: Position {
line: tokens[i-1].pos.range.start.line + 1,
character: tokens[i-1].pos.range.start.character + 1
},
end: Position {
line: tokens[i+1].pos.range.start.line + 1,
character: tokens[i+1].pos.range.start.character + 1
}
}
};
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
module.instances.push(instance);
}
}
}
}
"generic" => {
let is_map = kind_str(tokens[i+1].kind) == "map";
let (params, next_index) = parse_parameters(&tokens, i + 1, is_map);
if is_map {
let start = params.first().unwrap().range.start.clone();
let end = params.last().unwrap().range.start.clone();
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
module.instances.last_mut().unwrap().instparams = Some(Range { start, end });;
}
} else {
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
module.params.extend(params);
}
}
i = next_index;
}
"port" => {
let is_map = kind_str(tokens[i+1].kind) == "map";
let (ports, next_index) = parse_port(&tokens, i + 1, is_map);
if is_map {
let start = ports.first().unwrap().range.start.clone();
let end = ports.last().unwrap().range.start.clone();
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
module.instances.last_mut().unwrap().instports = Some(Range { start, end });
}
} else {
if let Some(module) = modules.iter_mut().find(|module| module.name == last_module_name) {
module.ports.extend(ports);
}
}
i = next_index;
}
_ => {}
} }
}).collect::<String>(); i += 1;
"[".to_string() + width.as_str() + "]" }
// println!("{:?}", modules);
modules
} }
fn get_range_from_token(start_token: Option<&Token>, end_token: Option<&Token>) -> Range {
let start = if let Some(token) = start_token {
Position { line: token.pos.start().line, character: token.pos.start().character }
} else {
Position { line: 0, character: 0 }
};
let end = if let Some(token) = end_token { #[allow(unused)]
Position { line: token.pos.end().line, character: token.pos.end().character } fn parse_port(tokens: &[Token], start: usize, is_map: bool) -> (Vec<Port>, usize) {
} else { let mut ports = Vec::new();
Position { line: 0, character: 0 } let mut i = start;
}; let mut stack = Vec::new();
Range { start, end } while i < tokens.len() {
let token = &tokens[i];
match kind_str(token.kind) {
"(" => {
stack.push(token);
}
")" => {
if stack.is_empty() {
break;
}
stack.pop();
}
";" => {
if stack.is_empty() {
break;
}
}
"{identifier}" => {
if is_map {
let start_pos = tokens[i].pos.range.start;
while kind_str(tokens[i+1].kind) != ")" {
i += 1;
}
let end_pos = tokens[i].pos.range.end;
let port = Port {
name: "none".to_string(),
dir_type: "none".to_string(),
net_type: "none".to_string(),
width: "none".to_string(),
signed: "unsigned".to_string(),
range: Range {
start: Position {
line: start_pos.line + 1,
character: start_pos.character + 1
},
end: Position {
line: end_pos.line + 1,
character: end_pos.character + 1
}
},
};
ports.push(port);
} else {
let mut ports_token = Vec::new();
while kind_str(tokens[i].kind) != ":" {
// println!("{:?}", kind_str(tokens[i].kind));
if kind_str(tokens[i].kind) == "{identifier}" {
ports_token.push(&tokens[i]);
}
i += 1;
}
// let start_pos = tokens[i].pos.range.start;
let width ;
let end_idx;
let direction = if kind_str(tokens[i+1].kind) == "buffer" || kind_str(tokens[i+1].kind) == "out" {
i += 1;
"out"
} else if kind_str(tokens[i+1].kind) == "in" {
i += 1;
"in"
} else {
"unknown"
};
if kind_str(tokens[i+2].kind) == "(" {
let (width_str, index) = parse_width(&tokens, i+2);
width = "[".to_string() + &width_str + "]";
end_idx = index-1;
} else if kind_str(tokens[i+2].kind) == "range" {
width = "[".to_string() + &get_value(&tokens[i+3]) + ":" + &get_value(&tokens[i+5]) + "]";
end_idx = i+5;
} else {
width = "1".to_string();
end_idx = i+1;
}
let end_pos = tokens[end_idx].pos.range.end;
for tok in ports_token {
let port = Port {
name: get_value(&tok),
dir_type: direction.to_string(),
net_type: get_value(&tokens[i+1]),
width: width.to_string(),
signed: "unsigned".to_string(),
range: Range {
start: Position {
line: tok.pos.range.start.line + 1,
character: tok.pos.range.start.character + 1
},
end: Position {
line: end_pos.line + 1,
character: end_pos.character + 1
}
},
};
ports.push(port);
}
i = end_idx;
}
}
_ => {}
}
i += 1;
}
(ports, i)
} }
#[allow(unused)]
fn parse_width(tokens: &[Token], start: usize) -> (String, usize) {
let mut width = String::new();
let mut i = start;
let mut stack = Vec::new();
while i < tokens.len() {
match kind_str(tokens[i].kind) {
"(" => {
stack.push(&tokens[i]);
}
")" => {
if stack.is_empty() {
break;
}
stack.pop();
}
";" => {
if stack.is_empty() {
break;
}
}
_ => {
if stack.len() >= 1 {
if get_value(&tokens[i]) == "None" {
if kind_str(tokens[i].kind) == "downto" || kind_str(tokens[i].kind) == "to" {
width = width + ":";
} else {
width = width + kind_str(tokens[i].kind);
}
} else {
width = width + &get_value(&tokens[i]);
}
}
}
}
i += 1;
}
(width, i)
}
#[allow(unused)]
fn parse_parameters(tokens: &[Token], start: usize, is_map: bool) -> (Vec<Parameter>, usize) {
// println!("I am here, start {start}");
let mut params = Vec::new();
let mut i = start;
let mut stack = Vec::new();
while i < tokens.len() {
let token = &tokens[i];
match kind_str(token.kind) {
"(" => {
stack.push(token);
}
")" => {
if stack.is_empty() {
break;
}
stack.pop();
}
";" => {
if stack.is_empty() {
break;
}
}
"{identifier}" => {
if is_map {
let start_pos = tokens[i].pos.range.start;
let end_pos = tokens[i+2].pos.range.end;
let param = Parameter {
name: get_value(&tokens[i]),
net_type: get_value(&tokens[i+2]),
init: get_value(&tokens[i+2]),
range: Range {
start: Position {
line: start_pos.line + 1,
character: start_pos.character + 1
},
end: Position {
line: end_pos.line + 1,
character: end_pos.character + 1
}
},
};
i += 2;
params.push(param);
} else {
let mut parameters = Vec::new();
while kind_str(tokens[i].kind) != ":" {
// println!("{:?}", kind_str(tokens[i].kind));
if kind_str(tokens[i].kind) == "{identifier}" {
parameters.push(&tokens[i]);
}
i += 1;
}
let net_type = get_value(&tokens[i+1]);
let init = if kind_str(tokens[i+2].kind) == ":=" {
get_value(&tokens[i+3])
} else {
"unknown".to_string()
};
let end_pos = if kind_str(tokens[i+2].kind) == ":=" {
tokens[i+2].pos.range.end
} else {
tokens[i+1].pos.range.end
};
i = if kind_str(tokens[i+2].kind) == ":=" {
i + 3
} else {
i + 1
};
for param_token in parameters {
let start_pos = param_token.pos.range.start;
let param = Parameter {
name: get_value(param_token),
net_type: net_type.to_string(),
init: init.to_string(),
range: Range {
start: Position {
line: start_pos.line + 1,
character: start_pos.character + 1
},
end: Position {
line: end_pos.line + 1,
character: end_pos.character + 1
}
},
};
params.push(param);
}
}
}
_ => {}
}
i += 1;
}
(params, i)
}
#[allow(unused)] #[allow(unused)]
fn get_value(token: &Token) -> String { fn get_value(token: &Token) -> String {
match &token.value { match &token.value {

View File

@ -1,10 +1,8 @@
use crate::sources::LSPSupport;
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
use ropey::Rope; use ropey::Rope;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use vhdl_lang::Project;
use crate::{sources::LSPSupport, utils::is_character_ordered_match};
/// cleanup the text of a definition so it can be included in completions /// cleanup the text of a definition so it can be included in completions
pub fn clean_type_str(type_str: &str, ident: &str) -> String { pub fn clean_type_str(type_str: &str, ident: &str) -> String {
@ -65,23 +63,9 @@ pub fn copy_scopes(scopes: &[Box<dyn Scope>]) -> Vec<Box<dyn Scope>> {
scope_decs scope_decs
} }
/// Definition 是所有非 scope 类型 symbol 的父类 /// 用于定义一个 Symbol 或者 Scope 的内部变量
pub trait Definition: std::fmt::Debug + Sync + Send { pub trait Definition: std::fmt::Debug + Sync + Send {
/// symbol 或者 scope 的名字identity 的缩写 /// symbol 或者 scope 的名字identity 的缩写
///
/// 对于 definition而言 ident 就是它的字面量,比如
/// ```verilog
/// parameter wire_size = 12;
/// ```
/// 上面的 `wire_size` 这个 symbol 的 name 就是 `wire_size`
///
/// 对于 scope 而言
/// ```verilog
/// module beginner();
/// adwadwa
/// endmodule
/// 上面这个 module scope 的 ident 就是 `beginner`
/// ```
fn ident(&self) -> String; fn ident(&self) -> String;
/// 相对于文本的偏移量,可以利用 Rope 的 `byte_to_pos` 函数转换成 Position /// 相对于文本的偏移量,可以利用 Rope 的 `byte_to_pos` 函数转换成 Position
fn byte_idx(&self) -> usize; fn byte_idx(&self) -> usize;
@ -99,9 +83,9 @@ pub trait Definition: std::fmt::Debug + Sync + Send {
fn starts_with(&self, token: &str) -> bool; fn starts_with(&self, token: &str) -> bool;
/// 构造 completion 的 item /// 构造 completion 的 item
fn completion(&self) -> CompletionItem; fn completion(&self) -> CompletionItem;
fn dot_completion(&self, scope_tree: &GenericScope) -> Vec<CompletionItem>;
} }
/// Scope 是所有 scope 类型 symbol 的父类
pub trait Scope: std::fmt::Debug + Definition + Sync + Send { pub trait Scope: std::fmt::Debug + Definition + Sync + Send {
// the start byte of this scope // the start byte of this scope
fn start(&self) -> usize; fn start(&self) -> usize;
@ -129,41 +113,23 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send {
// first we need to go down the scope tree, to the scope the user is invoking a completion // first we need to go down the scope tree, to the scope the user is invoking a completion
// in // in
for scope in self.scopes() { for scope in self.scopes() {
// 如果当前的 token 在这个 scope 中,那么需要递归获取这个 scope 中的所有补全项目
if &scope.url() == url && scope.start() <= byte_idx && byte_idx <= scope.end() { if &scope.url() == url && scope.start() <= byte_idx && byte_idx <= scope.end() {
completions = scope.get_completion(token, byte_idx, url); completions = scope.get_completion(token, byte_idx, url);
break; break;
} }
} }
// let completion_idents: Vec<String> = completions.iter().map(|x| x.label.clone()).collect(); let lower_case_token = token.to_lowercase();
// now that we are in the users scope, we can attempt to find a relevant completion
// 寻找前缀相同的 symbol // we proceed back upwards through the scope tree, adding any definitions that match
// 这里面并没有 macro // the users token
for symbol in self.defs() { let completion_idents: Vec<String> = completions.iter().map(|x| x.label.clone()).collect();
// 此处的去重会在外部完成 for def in self.defs() {
let symbol_name = symbol.ident();
// 此处仍然会给出 module 的补全,但是此处的只是名字的补全,而不是自动例化 if !completion_idents.contains(&def.ident()) && def.ident().to_lowercase().starts_with(&lower_case_token) {
match &symbol.def_type() { completions.push(def.completion());
DefinitionType::Port => {},
DefinitionType::Net => {},
DefinitionType::Macro => {
// 对于 TextMacro 类型的,因为是全局属性的,不进行补全
// 对于它的补全请参考 vlog_directives_completion & vlog_directives_completion_without_prefix
continue;
},
DefinitionType::Data => {},
DefinitionType::Modport => {},
DefinitionType::Subroutine => {},
DefinitionType::ModuleInstantiation => {},
DefinitionType::GenericScope => {},
DefinitionType::Class => {},
};
if is_character_ordered_match(token, &symbol_name) {
completions.push(symbol.completion());
} }
} }
// 寻找前缀相同的 scope
for scope in self.scopes() { for scope in self.scopes() {
if scope.starts_with(token) { if scope.starts_with(token) {
completions.push(scope.completion()); completions.push(scope.completion());
@ -172,6 +138,41 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send {
completions completions
} }
/// return a dot completion from the scope tree, this function should be called on the global
/// scope
fn get_dot_completion(
&self,
token: &str,
byte_idx: usize,
url: &Url,
scope_tree: &GenericScope,
) -> Vec<CompletionItem> {
// first we need to go down the scope tree, to the scope the user is invoking a completion
// in
for scope in self.scopes() {
if &scope.url() == url && scope.start() <= byte_idx && byte_idx <= scope.end() {
eprintln!("checking dot completion: {}", scope.ident());
let result = scope.get_dot_completion(token, byte_idx, url, scope_tree);
if !result.is_empty() {
return result;
}
}
}
// now that we are in the users scope, we can attempt to find the relevant definition
// we proceed back upwards through the scope tree, and if a definition matches our token,
// we invoke dot completion on that definition and pass it the syntax tree
for def in self.defs() {
if def.starts_with(token) {
return def.dot_completion(scope_tree);
}
}
for scope in self.scopes() {
if scope.starts_with(token) {
return scope.dot_completion(scope_tree);
}
}
Vec::new()
}
/// 根据输入的 token计算出这个 token 在 scope 中的定义 /// 根据输入的 token计算出这个 token 在 scope 中的定义
/// 比如输入 clock则返回 clock 这个变量在哪里被定义,没有则返回 None /// 比如输入 clock则返回 clock 这个变量在哪里被定义,没有则返回 None
@ -219,23 +220,19 @@ pub trait Scope: std::fmt::Debug + Definition + Sync + Send {
let mut symbols: Vec<DocumentSymbol> = Vec::new(); let mut symbols: Vec<DocumentSymbol> = Vec::new();
for scope in self.scopes() { for scope in self.scopes() {
if &scope.url() == uri { if &scope.url() == uri {
#[allow(deprecated)] #[allow(deprecated)]
symbols.push(DocumentSymbol { symbols.push(DocumentSymbol {
name: scope.ident(), name: scope.ident(),
detail: Some(scope.type_str()), detail: Some(scope.type_str()),
kind: scope.symbol_kind(), kind: scope.symbol_kind(),
range: Range::new( deprecated: None,
doc.byte_to_pos(scope.start()), range: Range::new(doc.byte_to_pos(scope.start()), doc.byte_to_pos(scope.end())),
doc.byte_to_pos(scope.end())
),
selection_range: Range::new( selection_range: Range::new(
doc.byte_to_pos(scope.start()), doc.byte_to_pos(scope.byte_idx()),
doc.byte_to_pos(scope.end()) doc.byte_to_pos(scope.byte_idx() + scope.ident().len()),
), ),
children: Some(scope.document_symbols(uri, doc)), children: Some(scope.document_symbols(uri, doc)),
tags: None, tags: None,
deprecated: None
}) })
} }
} }
@ -359,18 +356,38 @@ impl Definition for PortDec {
self.ident.starts_with(token) self.ident.starts_with(token)
} }
fn completion(&self) -> CompletionItem { fn completion(&self) -> CompletionItem {
let label_details = CompletionItemLabelDetails {
description: Some("module port".to_string()),
..Default::default()
};
CompletionItem { CompletionItem {
label: self.ident.clone(), label: self.ident.clone(),
detail: Some(clean_type_str(&self.type_str, &self.ident)), detail: Some(clean_type_str(&self.type_str, &self.ident)),
label_details: Some(label_details),
kind: Some(self.completion_kind), kind: Some(self.completion_kind),
..CompletionItem::default() ..CompletionItem::default()
} }
} }
fn dot_completion(&self, scope_tree: &GenericScope) -> Vec<CompletionItem> {
for scope in &scope_tree.scopes {
if let Some(interface) = &self.interface {
if &scope.ident() == interface {
return match &self.modport {
Some(modport) => {
for def in scope.defs() {
if def.starts_with(modport) {
return def.dot_completion(scope_tree);
}
}
Vec::new()
}
None => scope
.defs()
.iter()
.filter(|x| !x.starts_with(&scope.ident()))
.map(|x| x.completion())
.collect(),
};
}
}
}
Vec::new()
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -432,6 +449,9 @@ impl Definition for GenericDec {
..CompletionItem::default() ..CompletionItem::default()
} }
} }
fn dot_completion(&self, _: &GenericScope) -> Vec<CompletionItem> {
Vec::new()
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -496,6 +516,9 @@ impl Definition for PackageImport {
..CompletionItem::default() ..CompletionItem::default()
} }
} }
fn dot_completion(&self, _: &GenericScope) -> Vec<CompletionItem> {
Vec::new()
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -564,6 +587,9 @@ impl Definition for SubDec {
..CompletionItem::default() ..CompletionItem::default()
} }
} }
fn dot_completion(&self, _: &GenericScope) -> Vec<CompletionItem> {
Vec::new()
}
} }
impl Scope for SubDec { impl Scope for SubDec {
@ -643,6 +669,9 @@ impl Definition for ModportDec {
..CompletionItem::default() ..CompletionItem::default()
} }
} }
fn dot_completion(&self, _: &GenericScope) -> Vec<CompletionItem> {
self.ports.iter().map(|x| x.completion()).collect()
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -705,12 +734,19 @@ impl Definition for ModInst {
..CompletionItem::default() ..CompletionItem::default()
} }
} }
} fn dot_completion(&self, scope_tree: &GenericScope) -> Vec<CompletionItem> {
for scope in &scope_tree.scopes {
pub struct VhdlProject { if scope.ident() == self.mod_ident {
pub project: Project, return scope
pub std_config: vhdl_lang::Config, .defs()
pub config_file_strs: Vec<String> .iter()
.filter(|x| !x.starts_with(&scope.ident()))
.map(|x| x.completion())
.collect();
}
}
Vec::new()
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -779,6 +815,19 @@ impl Definition for GenericScope {
..CompletionItem::default() ..CompletionItem::default()
} }
} }
fn dot_completion(&self, scope_tree: &GenericScope) -> Vec<CompletionItem> {
for scope in scope_tree.scopes() {
if scope.ident() == self.ident {
return scope
.defs()
.iter()
.filter(|x| !x.starts_with(&scope.ident()))
.map(|x| x.completion())
.collect();
}
}
Vec::new()
}
} }
impl Scope for GenericScope { impl Scope for GenericScope {
@ -871,6 +920,19 @@ impl Definition for ClassDec {
..CompletionItem::default() ..CompletionItem::default()
} }
} }
fn dot_completion(&self, scope_tree: &GenericScope) -> Vec<CompletionItem> {
for scope in scope_tree.scopes() {
if scope.ident() == self.ident {
return scope
.defs()
.iter()
.filter(|x| !x.starts_with(&scope.ident()))
.map(|x| x.completion())
.collect();
}
}
Vec::new()
}
} }
impl Scope for ClassDec { impl Scope for ClassDec {

View File

@ -1,12 +1,8 @@
use super::common::*; use crate::definition::def_types::*;
use super::match_definitions; use crate::definition::match_definitions;
#[allow(unused)]
use log::info;
use sv_parser::*; use sv_parser::*;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
/// 找到 node 的 名字,开始的位置和结束的位置 /// 找到 node 的 名字,开始的位置和结束的位置
pub fn get_ident(tree: &SyntaxTree, node: RefNode) -> (String, usize) { pub fn get_ident(tree: &SyntaxTree, node: RefNode) -> (String, usize) {
let loc = unwrap_locate!(node).unwrap(); let loc = unwrap_locate!(node).unwrap();
@ -47,8 +43,6 @@ macro_rules! advance_until_leave {
}}; }};
} }
/// 遍历 $tree直到找到 $node
/// 中间遍历遇到的所有 node 的字面量会被存储进入 $tokens 中
macro_rules! advance_until_enter { macro_rules! advance_until_enter {
($tokens:ident, $tree:ident, $event_iter:ident, $node:path, $type:ty) => {{ ($tokens:ident, $tree:ident, $event_iter:ident, $node:path, $type:ty) => {{
let mut result: Option<$type> = None; let mut result: Option<$type> = None;
@ -744,7 +738,7 @@ pub fn data_dec(
ident: var.ident, ident: var.ident,
byte_idx: var.byte_idx, byte_idx: var.byte_idx,
start: x.start, start: x.start,
end: x.end.max(var.byte_idx + 1), end: x.end,
url: url.clone(), url: url.clone(),
type_str: var.type_str, type_str: var.type_str,
completion_kind: x.completion_kind, completion_kind: x.completion_kind,
@ -808,7 +802,6 @@ pub fn data_dec(
let ident = get_ident(tree, RefNode::TypeIdentifier(&y.nodes.2)); let ident = get_ident(tree, RefNode::TypeIdentifier(&y.nodes.2));
def.ident = ident.0; def.ident = ident.0;
def.byte_idx = ident.1; def.byte_idx = ident.1;
def.end = def.end.max(def.byte_idx + 1);
for _ in &y.nodes.3 { for _ in &y.nodes.3 {
let tokens = &mut def.type_str; let tokens = &mut def.type_str;
advance_until_leave!( advance_until_leave!(
@ -904,7 +897,6 @@ pub fn data_dec(
let ident = get_ident(tree, RefNode::NetTypeIdentifier(&y.nodes.2)); let ident = get_ident(tree, RefNode::NetTypeIdentifier(&y.nodes.2));
def.ident = ident.0; def.ident = ident.0;
def.byte_idx = ident.1; def.byte_idx = ident.1;
def.end = def.end.max(def.byte_idx + 1);
let mut tokens = String::new(); let mut tokens = String::new();
advance_until_enter!( advance_until_enter!(
tokens, tokens,
@ -2346,8 +2338,7 @@ pub fn text_macro_def(
let ident = get_ident(tree, RefNode::TextMacroIdentifier(&node.nodes.2.nodes.0)); let ident = get_ident(tree, RefNode::TextMacroIdentifier(&node.nodes.2.nodes.0));
text_macro.ident = ident.0; text_macro.ident = ident.0;
text_macro.byte_idx = ident.1; text_macro.byte_idx = ident.1;
let type_str = &mut text_macro.type_str; let type_str = &mut text_macro.type_str;
advance_until_enter!( advance_until_enter!(
type_str, type_str,
tree, tree,
@ -2356,13 +2347,8 @@ pub fn text_macro_def(
&TextMacroIdentifier &TextMacroIdentifier
); );
// 最终渲染的基本字面量
text_macro.type_str = "`define".to_string();
// 自动补全用的
text_macro.completion_kind = CompletionItemKind::CONSTANT; text_macro.completion_kind = CompletionItemKind::CONSTANT;
// document 用的
text_macro.symbol_kind = SymbolKind::FUNCTION; text_macro.symbol_kind = SymbolKind::FUNCTION;
// 内部用于标定当前类型的
text_macro.def_type = DefinitionType::Macro; text_macro.def_type = DefinitionType::Macro;
Some(text_macro) Some(text_macro)
} }

View File

@ -5,7 +5,7 @@ use regex::Regex;
use ropey::RopeSlice; use ropey::RopeSlice;
use tower_lsp::lsp_types::{GotoDefinitionResponse, LocationLink, Position, Range, Url}; use tower_lsp::lsp_types::{GotoDefinitionResponse, LocationLink, Position, Range, Url};
use crate::{core::{self, hdlparam::{self, FastHdlparam}}, server::LspServer, utils::{get_word_range_at_position, resolve_path, to_escape_path}}; use crate::{core::hdlparam::FastHdlparam, server::LSPServer, utils::{get_word_range_at_position, resolve_path, to_escape_path}};
/// 跳转到 include 的文件 /// 跳转到 include 的文件
pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Option<GotoDefinitionResponse> { pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Option<GotoDefinitionResponse> {
@ -73,8 +73,8 @@ pub fn goto_include_definition(uri: &Url, line: &RopeSlice, pos: Position) -> Op
/// 跳转到宏定义 /// 跳转到宏定义
pub fn goto_macro_definition(server: &LspServer, line: &RopeSlice, pos: Position) -> Option<GotoDefinitionResponse> { pub fn goto_macro_definition(server: &LSPServer, line: &RopeSlice, pos: Position) -> Option<GotoDefinitionResponse> {
let macro_text_regex = Regex::new(r"[`_0-9a-zA-Z]").unwrap(); let macro_text_regex = Regex::new(r"[`0-9a-zA-Z]").unwrap();
if let Some((macro_text, range)) = get_word_range_at_position(line, pos, macro_text_regex) { if let Some((macro_text, range)) = get_word_range_at_position(line, pos, macro_text_regex) {
if macro_text.starts_with("`") { if macro_text.starts_with("`") {
if let Some((macro_define, define_path)) = server.find_macros(&macro_text) { if let Some((macro_define, define_path)) = server.find_macros(&macro_text) {
@ -84,7 +84,8 @@ pub fn goto_macro_definition(server: &LspServer, line: &RopeSlice, pos: Position
Err(_) => return None Err(_) => return None
}; };
let target_range = macro_define.range.to_lsp_range(); let mut target_range = macro_define.range.clone();
let target_range = target_range.affine(-1, -1).to_lsp_range();
let link = vec![LocationLink { let link = vec![LocationLink {
target_uri, target_uri,
origin_selection_range: Some(range), origin_selection_range: Some(range),
@ -102,7 +103,7 @@ pub fn goto_macro_definition(server: &LspServer, line: &RopeSlice, pos: Position
fn goto_instantiation<'a>( fn goto_instantiation<'a>(
server: &LspServer, server: &LSPServer,
fast: &'a FastHdlparam, fast: &'a FastHdlparam,
token_name: &str, token_name: &str,
pos: &Position, pos: &Position,
@ -114,32 +115,16 @@ fn goto_instantiation<'a>(
// let in_scope = compare_pos(&range.start, pos) != 1 && compare_pos(pos, &range.end) != 1; // let in_scope = compare_pos(&range.start, pos) != 1 && compare_pos(pos, &range.end) != 1;
// info!("pos: {pos:?}, param_range: {range:?}, in_scope: {in_scope:?}"); // info!("pos: {pos:?}, param_range: {range:?}, in_scope: {in_scope:?}");
if param_range.contains(pos) { if param_range.contains(pos) {
let module = match server.db.hdl_param.find_module_by_name(&instance.inst_type) { let module = match server.srcs.hdl_param.find_module_by_name(&instance.inst_type) {
Some(module) => module, Some(module) => module,
None => return None None => return None
}; };
for param in &module.params { for param in &module.params {
if token_name == param.name { if token_name == param.name {
let def_path = server.db.hdl_param.find_module_definition_path(&module.name).unwrap(); let def_path = server.srcs.hdl_param.find_module_definition_path(&module.name).unwrap();
let target_uri = Url::from_file_path(def_path).unwrap(); let target_uri = Url::from_file_path(def_path).unwrap();
let target_range = param.range.clone(); let target_range = param.range.clone();
let target_range = target_range.to_lsp_range();
let file_type = server.db.hdl_param.find_file_type_by_module_name(&instance.inst_type);
let target_range = match file_type.as_str() {
"common" => {
target_range.to_lsp_range()
}
"ip" => {
let mut target_range = target_range.clone();
target_range.affine(-1, -1).to_lsp_range()
}
"primitives" => {
target_range.to_lsp_range()
}
_ => {
target_range.to_lsp_range()
}
};
let link = vec![LocationLink { let link = vec![LocationLink {
target_uri, target_uri,
@ -159,32 +144,16 @@ fn goto_instantiation<'a>(
// let in_scope = compare_pos(&range.start, pos) != 1 && compare_pos(pos, &range.end) != 1; // let in_scope = compare_pos(&range.start, pos) != 1 && compare_pos(pos, &range.end) != 1;
// info!("pos: {pos:?}, port_range: {range:?}, in_scope: {in_scope:?}"); // info!("pos: {pos:?}, port_range: {range:?}, in_scope: {in_scope:?}");
if port_range.contains(pos) { if port_range.contains(pos) {
let module = match server.db.hdl_param.find_module_by_name(&instance.inst_type) { let module = match server.srcs.hdl_param.find_module_by_name(&instance.inst_type) {
Some(module) => module, Some(module) => module,
None => return None None => return None
}; };
for port in &module.ports { for port in &module.ports {
if token_name == port.name { if token_name == port.name {
let def_path = server.db.hdl_param.find_module_definition_path(&module.name).unwrap(); let def_path = server.srcs.hdl_param.find_module_definition_path(&module.name).unwrap();
let target_uri = Url::from_file_path(def_path).unwrap(); let target_uri = Url::from_file_path(def_path).unwrap();
let target_range = port.range.clone(); let target_range = port.range.clone();
let target_range = target_range.to_lsp_range();
let file_type = server.db.hdl_param.find_file_type_by_module_name(&instance.inst_type);
let target_range = match file_type.as_str() {
"common" => {
target_range.to_lsp_range()
}
"ip" => {
let mut target_range = target_range.clone();
target_range.affine(-1, -1).to_lsp_range()
}
"primitives" => {
target_range.to_lsp_range()
}
_ => {
target_range.to_lsp_range()
}
};
let link = vec![LocationLink { let link = vec![LocationLink {
target_uri, target_uri,
@ -206,7 +175,7 @@ fn goto_instantiation<'a>(
} }
pub fn goto_position_port_param_definition( pub fn goto_position_port_param_definition(
server: &LspServer, server: &LSPServer,
line: &RopeSlice, line: &RopeSlice,
url: &Url, url: &Url,
pos: Position pos: Position
@ -216,7 +185,7 @@ pub fn goto_position_port_param_definition(
if name.starts_with(".") { if name.starts_with(".") {
let name = &name[1..]; let name = &name[1..];
// 进入最近的 scope 寻找 // 进入最近的 scope 寻找
let fast_map = server.db.hdl_param.path_to_hdl_file.read().unwrap(); let fast_map = server.srcs.hdl_param.path_to_hdl_file.read().unwrap();
let path = PathBuf::from_str(url.path()).unwrap(); let path = PathBuf::from_str(url.path()).unwrap();
let path = to_escape_path(&path); let path = to_escape_path(&path);
let path_string = path.to_str().unwrap(); let path_string = path.to_str().unwrap();
@ -234,86 +203,15 @@ pub fn goto_position_port_param_definition(
} }
pub fn goto_module_declaration_definition( pub fn goto_module_declaration_definition(
server: &LspServer, server: &LSPServer,
token_name: &str token_name: &str
) -> Option<GotoDefinitionResponse> { ) -> Option<GotoDefinitionResponse> {
let hdl_param = server.db.hdl_param.clone(); if let Some(module) = server.srcs.hdl_param.find_module_by_name(token_name) {
info!("get into goto_module_declaration_definition"); let def_path = server.srcs.hdl_param.find_module_definition_path(&module.name).unwrap();
if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(token_name) {
match file_type.as_str() {
"common" => {
goto_common_module_declaration_definition(
server,
token_name,
&module,
&def_path
)
},
"ip" => {
goto_ip_module_declaration_definition(
server,
token_name,
&module,
&def_path
)
},
"primitives" => {
goto_primitives_module_declaration_definition(
server,
token_name,
&module,
&def_path
)
},
_ => None
}
} else {
None
}
}
fn goto_common_module_declaration_definition(
#[allow(unused)]
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]
module: &core::hdlparam::Module,
#[allow(unused)]
def_path: &str
) -> Option<GotoDefinitionResponse> {
let target_uri = Url::from_file_path(PathBuf::from_str(&def_path).unwrap()).unwrap();
let target_range = module.range.clone();
let target_range = target_range.to_lsp_range();
let link = vec![LocationLink {
target_uri,
origin_selection_range: None,
target_range: target_range,
target_selection_range: target_range
}];
let links = GotoDefinitionResponse::Link(link);
Some(links)
}
fn goto_ip_module_declaration_definition(
#[allow(unused)]
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]
module: &core::hdlparam::Module,
#[allow(unused)]
def_path: &str
) -> Option<GotoDefinitionResponse> {
let pathbuf = PathBuf::from_str(def_path).unwrap();
if pathbuf.exists() {
let target_uri = Url::from_file_path(PathBuf::from_str(&def_path).unwrap()).unwrap(); let target_uri = Url::from_file_path(PathBuf::from_str(&def_path).unwrap()).unwrap();
let mut target_range = module.range.clone(); let target_range = module.range.clone();
let target_range = target_range.affine(-1, -1).to_lsp_range(); let target_range = target_range.to_lsp_range();
let link = vec![LocationLink { let link = vec![LocationLink {
target_uri, target_uri,
@ -322,22 +220,8 @@ fn goto_ip_module_declaration_definition(
target_selection_range: target_range target_selection_range: target_range
}]; }];
let links = GotoDefinitionResponse::Link(link); let links = GotoDefinitionResponse::Link(link);
Some(links) return Some(links);
} else {
None
} }
}
fn goto_primitives_module_declaration_definition(
#[allow(unused)]
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]
module: &core::hdlparam::Module,
#[allow(unused)]
def_path: &str
) -> Option<GotoDefinitionResponse> {
None None
} }

View File

@ -1,16 +1,25 @@
use crate::core::hdlparam::{self, FastHdlparam};
use crate::utils::get_language_id_by_uri; use crate::utils::get_language_id_by_uri;
use crate::server::LspServer; use crate::server::LSPServer;
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
use ropey::Rope;
use sv_parser::*;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
pub mod def_types;
pub use def_types::*;
pub mod feature; pub mod feature;
pub mod extract_defs;
pub use extract_defs::*;
mod sv; mod sv;
mod vhdl; mod vhdl;
impl LspServer { impl LSPServer {
pub fn goto_definition(&self, params: GotoDefinitionParams) -> Option<GotoDefinitionResponse> { pub fn goto_definition(&self, params: GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
let language_id = get_language_id_by_uri(&params.text_document_position_params.text_document.uri); let language_id = get_language_id_by_uri(&params.text_document_position_params.text_document.uri);
match language_id.as_str() { match language_id.as_str() {
@ -27,4 +36,223 @@ impl LspServer {
_ => None _ => None
} }
} }
} }
type ScopesAndDefs = Option<(Vec<Box<dyn Scope>>, Vec<Box<dyn Definition>>)>;
/// Take a given syntax node from a sv-parser syntax tree and extract out the definition/scope at
/// that point.
pub fn match_definitions(
syntax_tree: &SyntaxTree,
event_iter: &mut EventIter,
node: RefNode,
url: &Url,
) -> ScopesAndDefs {
let mut definitions: Vec<Box<dyn Definition>> = Vec::new();
let mut scopes: Vec<Box<dyn Scope>> = Vec::new();
match node {
RefNode::ModuleDeclaration(n) => {
let module = module_dec(syntax_tree, n, event_iter, url);
if module.is_some() {
scopes.push(Box::new(module?));
}
}
RefNode::InterfaceDeclaration(n) => {
let interface = interface_dec(syntax_tree, n, event_iter, url);
if interface.is_some() {
scopes.push(Box::new(interface?));
}
}
RefNode::UdpDeclaration(n) => {
let dec = udp_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::ProgramDeclaration(n) => {
let dec = program_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::PackageDeclaration(n) => {
let dec = package_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::ConfigDeclaration(n) => {
let dec = config_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::ClassDeclaration(n) => {
let dec = class_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::PortDeclaration(n) => {
let ports = port_dec_non_ansi(syntax_tree, n, event_iter, url);
if ports.is_some() {
for port in ports? {
definitions.push(Box::new(port));
}
}
}
RefNode::NetDeclaration(n) => {
let nets = net_dec(syntax_tree, n, event_iter, url);
if nets.is_some() {
for net in nets? {
definitions.push(Box::new(net));
}
}
}
RefNode::DataDeclaration(n) => {
let vars = data_dec(syntax_tree, n, event_iter, url);
if let Some(vars) = vars {
for var in vars {
match var {
Declaration::Dec(dec) => definitions.push(Box::new(dec)),
Declaration::Import(dec) => definitions.push(Box::new(dec)),
Declaration::Scope(scope) => scopes.push(Box::new(scope)),
}
}
}
}
RefNode::ParameterDeclaration(n) => {
let vars = param_dec(syntax_tree, n, event_iter, url);
if vars.is_some() {
for var in vars? {
definitions.push(Box::new(var));
}
}
}
RefNode::LocalParameterDeclaration(n) => {
let vars = localparam_dec(syntax_tree, n, event_iter, url);
if vars.is_some() {
for var in vars? {
definitions.push(Box::new(var));
}
}
}
RefNode::FunctionDeclaration(n) => {
let dec = function_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::TaskDeclaration(n) => {
let dec = task_dec(syntax_tree, n, event_iter, url);
if dec.is_some() {
scopes.push(Box::new(dec?));
}
}
RefNode::ModportDeclaration(n) => {
let decs = modport_dec(syntax_tree, n, event_iter, url);
if decs.is_some() {
for dec in decs? {
definitions.push(Box::new(dec));
}
}
}
RefNode::ModuleInstantiation(n) => {
let decs = module_inst(syntax_tree, n, event_iter, url);
if decs.is_some() {
for dec in decs? {
definitions.push(Box::new(dec));
}
}
}
RefNode::TextMacroDefinition(n) => {
let dec = text_macro_def(syntax_tree, n, event_iter, url);
if dec.is_some() {
definitions.push(Box::new(dec?));
}
}
_ => (),
}
Some((scopes, definitions))
}
/// convert the syntax tree to a scope tree
/// the root node is the global scope
pub fn get_scopes_from_syntax_tree(syntax_tree: &SyntaxTree, url: &Url) -> Option<GenericScope> {
let mut scopes: Vec<Box<dyn Scope>> = Vec::new();
let mut global_scope: GenericScope = GenericScope::new(url);
global_scope.ident = String::from("global");
let mut event_iter = syntax_tree.into_iter().event();
// iterate over each enter event and extract out any scopes or definitions
// match_definitions is recursively called so we get a tree in the end
while let Some(event) = event_iter.next() {
match event {
NodeEvent::Enter(node) => {
let mut result = match_definitions(syntax_tree, &mut event_iter, node, url)?;
global_scope.defs.append(&mut result.1);
scopes.append(&mut result.0);
}
NodeEvent::Leave(_) => (),
}
}
global_scope.scopes.append(&mut scopes);
Some(global_scope)
}
pub fn get_scopes_from_vhdl_fast(fast: &FastHdlparam, text: &Rope, url: &Url) -> Option<GenericScope> {
let mut scopes: Vec<Box<dyn Scope>> = Vec::new();
let mut global_scope: GenericScope = GenericScope::new(url);
global_scope.ident = String::from("global");
for module in &fast.content {
let mut scope: GenericScope = GenericScope::new(url);
scope.ident = module.name.clone();
let mut module_range = module.range.clone();
module_range.affine(-1, -1);
scope.start = position_to_byte_idx(text, &module_range.start);
scope.end = position_to_byte_idx(text, &module_range.end);
scope.byte_idx = scope.start + 7;
for parameter in &module.params {
let mut def = GenericDec::new(url);
def.ident = parameter.name.clone();
let mut parameter_range = parameter.range.clone();
parameter_range.affine(-1, -1);
def.byte_idx = position_to_byte_idx(text, &parameter_range.start);
def.completion_kind = CompletionItemKind::TYPE_PARAMETER;
def.symbol_kind = SymbolKind::TYPE_PARAMETER;
scope.defs.push(Box::new(def));
}
for port in &module.ports {
let mut port_def = PortDec::new(url);
port_def.ident = port.name.clone();
let mut port_range = port.range.clone();
port_range.affine(-1, -1);
port_def.byte_idx = position_to_byte_idx(text, &port_range.start);
port_def.type_str = port.dir_type.clone();
scope.defs.push(Box::new(port_def));
}
for inst in &module.instances {
let mut instance = ModInst::new(url);
instance.ident = inst.name.clone();
let mut inst_range = inst.range.clone();
inst_range.affine(-1, -1);
instance.byte_idx = position_to_byte_idx(text, &inst_range.start);
instance.type_str = inst.inst_type.clone();
instance.mod_ident = inst.inst_type.clone();
scope.defs.push(Box::new(instance));
}
scopes.push(Box::new(scope));
}
global_scope.scopes.append(&mut scopes);
Some(global_scope)
}
fn position_to_byte_idx(text: &Rope, pos: &hdlparam::Position) -> usize {
let char = text.line_to_char(pos.line as usize) + pos.character as usize;
text.char_to_byte(char)
}

View File

@ -1,42 +1,39 @@
use crate::core::scope_tree::common::{Definition, Scope}; use crate::utils::get_definition_token;
use crate::server::LSPServer;
use crate::sources::LSPSupport; use crate::sources::LSPSupport;
use crate::utils::{from_uri_to_escape_path_string, get_definition_token};
use crate::server::LspServer;
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use super::feature::*;
pub fn goto_definition(server: &LspServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> { use super::{feature::*, Definition, Scope};
let uri = &params.text_document_position_params.text_document.uri;
pub fn goto_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
let doc = &params.text_document_position_params.text_document.uri;
let pos = params.text_document_position_params.position; let pos = params.text_document_position_params.position;
let file_id = server.srcs.get_id(doc).to_owned();
server.srcs.wait_parse_ready(file_id, false);
let file = server.srcs.get_file(file_id)?;
let file = file.read().ok()?;
let path_string = from_uri_to_escape_path_string(uri).unwrap(); let line_text = file.text.line(pos.line as usize);
// 等待解析完成
server.db.wait_parse_ready(&path_string, false);
let source = server.db.get_source(&path_string)?;
let source = source.read().ok()?;
let line_text = source.text.line(pos.line as usize);
let token: String = get_definition_token(&line_text, pos); let token: String = get_definition_token(&line_text, pos);
// match include // match include
if let Some(definition) = goto_include_definition(uri, &line_text, pos) { if let Some(definition) = goto_include_definition(doc, &line_text, pos) {
return Some(definition); return Some(definition);
} }
// match macro usage // match macro
if let Some(definition) = goto_macro_definition(server, &line_text, pos) { if let Some(definition) = goto_macro_definition(server, &line_text, pos) {
return Some(definition); return Some(definition);
} }
// match instance // match instance
let scope_tree = server.db.scope_tree.read().ok()?; let scope_tree = server.srcs.scope_tree.read().ok()?;
// match position port & param // match position port & param
if let Some(definition) = goto_position_port_param_definition(server, &line_text, uri, pos) { if let Some(definition) = goto_position_port_param_definition(server, &line_text, doc, pos) {
return Some(definition); return Some(definition);
} }
@ -45,11 +42,11 @@ pub fn goto_definition(server: &LspServer, params: &GotoDefinitionParams) -> Opt
return Some(definition); return Some(definition);
} }
let byte_idx = source.text.pos_to_byte(&pos); let byte_idx = file.text.pos_to_byte(&pos);
let global_scope = scope_tree.as_ref()?; let global_scope = scope_tree.as_ref()?;
let def = global_scope.get_definition(&token, byte_idx, uri)?; let def = global_scope.get_definition(&token, byte_idx, doc)?;
let def_pos = source.text.byte_to_pos(def.byte_idx()); let def_pos = file.text.byte_to_pos(def.byte_idx());
Some(GotoDefinitionResponse::Scalar(Location::new( Some(GotoDefinitionResponse::Scalar(Location::new(
def.url(), def.url(),
Range::new(def_pos, def_pos), Range::new(def_pos, def_pos),

View File

@ -1,50 +1,21 @@
use std::{path::PathBuf, str::FromStr};
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use crate::{server::LspServer, utils::{from_lsp_pos, from_uri_to_escape_path_string, get_definition_token, srcpos_to_location, to_escape_path}}; use crate::{server::LSPServer, sources::LSPSupport, utils::get_definition_token};
pub fn goto_vhdl_definition(server: &LspServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> { use super::{Definition, Scope};
let uri = &params.text_document_position_params.text_document.uri;
pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
let doc = &params.text_document_position_params.text_document.uri;
let pos = params.text_document_position_params.position; let pos = params.text_document_position_params.position;
let file_id = server.srcs.get_id(doc).to_owned();
server.srcs.wait_parse_ready(file_id, false);
let file = server.srcs.get_file(file_id)?;
let file = file.read().ok()?;
let path_string = from_uri_to_escape_path_string(uri).unwrap(); let line_text = file.text.line(pos.line as usize);
// 等待解析完成
server.db.wait_parse_ready(&path_string, false);
let source = server.db.get_source(&path_string)?;
let source = source.read().ok()?;
let line_text = source.text.line(pos.line as usize);
#[allow(unused)]
let token: String = get_definition_token(&line_text, pos); let token: String = get_definition_token(&line_text, pos);
let project = server.db.vhdl_project.read().ok()?;
let global_project = project.as_ref().unwrap();
let path = match PathBuf::from_str(uri.path()) {
Ok(path) => path,
Err(error) => {
info!("error happen in vhdl <hover>: {:?}", error);
return None;
}
};
let escape_path = to_escape_path(&path);
let project_file = escape_path.as_path();
let Some(source) = global_project.project.get_source(project_file) else {
return None
};
let ents = global_project.project.find_declaration(&source, from_lsp_pos(pos));
Some(GotoDefinitionResponse::Array(
ents.into_iter()
.filter_map(|ent| ent.decl_pos().map(srcpos_to_location))
.collect(),
))
// // match include // // match include
// if let Some(definition) = goto_include_definition(doc, &line_text, pos) { // if let Some(definition) = goto_include_definition(doc, &line_text, pos) {
// return Some(definition); // return Some(definition);
@ -57,7 +28,7 @@ pub fn goto_vhdl_definition(server: &LspServer, params: &GotoDefinitionParams) -
// match instance // match instance
// let scope_tree = server.db.scope_tree.read().ok()?; let scope_tree = server.srcs.scope_tree.read().ok()?;
// // match position port & param // // match position port & param
// if let Some(definition) = goto_position_port_param_definition(server, &line_text, doc, pos) { // if let Some(definition) = goto_position_port_param_definition(server, &line_text, doc, pos) {
@ -69,14 +40,13 @@ pub fn goto_vhdl_definition(server: &LspServer, params: &GotoDefinitionParams) -
// return Some(definition); // return Some(definition);
// } // }
// let byte_idx = file.text.pos_to_byte(&pos); let byte_idx = file.text.pos_to_byte(&pos);
// let global_scope = scope_tree.as_ref()?; let global_scope = scope_tree.as_ref()?;
// let def = global_scope.get_definition(&token, byte_idx, doc)?; let def = global_scope.get_definition(&token, byte_idx, doc)?;
// let def_pos = file.text.byte_to_pos(def.byte_idx()); let def_pos = file.text.byte_to_pos(def.byte_idx());
// Some(GotoDefinitionResponse::Scalar(Location::new( Some(GotoDefinitionResponse::Scalar(Location::new(
// def.url(), def.url(),
// Range::new(def_pos, def_pos), Range::new(def_pos, def_pos),
// ))) )))
} }

View File

@ -1,99 +0,0 @@
use std::{path::PathBuf, str::FromStr};
#[allow(unused)]
use log::info;
use serde::{Deserialize, Serialize};
use ropey::Rope;
use tower_lsp::lsp_types::{Diagnostic, Url};
use crate::{server::LspServer, utils::command::is_command_valid};
#[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 {
/// - language_id: 语言 id
/// - invoker: 调用可执行文件的名字,比如 `iverilog``xvlog``verible-verilog-syntax`
/// - args: 调用执行器进行诊断的命令行参数(不包含文件路径)
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self;
/// 提供一次诊断目前基于 uri 中的地址读取真实路径来获取诊断结果
fn provide_diagnostics(&self, uri: &Url, rope: &Rope, server: &LspServer) -> Option<Vec<Diagnostic>>;
/// 获取 linter 安装路径,内部实现,内部调用
fn get_linter_path(&self) -> &str;
/// 获取 可执行文件 的名字,比如
/// - iverilog.exe
/// - iverilog
/// - xvlog.bat
fn get_exe_name(&self) -> String;
/// 获取真实调用路径,比如
/// iverilog.exe 或者 /path/to/verilog.exe
fn get_invoke_name(&self) -> String {
let exe_name = self.get_exe_name();
let linter_path = self.get_linter_path();
let pathbuf = PathBuf::from_str(linter_path);
if linter_path.len() == 0 || !pathbuf.as_ref().unwrap().exists() {
exe_name.to_string()
} else {
// 路径存在
let pathbuf = pathbuf.unwrap();
let pathbuf = pathbuf.join(exe_name);
let path_string = pathbuf.to_str().unwrap();
path_string.to_string()
}
}
/// 获取与该工具匹配的诊断器名字与调用函数名。并检查它们是否有效
///
/// 参考链接https://kirigaya.cn/blog/article?seq=284
fn linter_status(
&self,
server: &LspServer
) -> LinterStatus {
let invoke_name = self.get_invoke_name();
let available = is_command_valid(&invoke_name, server);
LinterStatus {
tool_name: self.get_exe_name(),
available,
invoke_name
}
}
}
/// rope 的第 line_index 行文字中,第一个和最后一个非空字符在本行的索引
pub fn find_non_whitespace_indices(rope: &Rope, line_index: usize) -> Option<(usize, usize)> {
let line_text = rope.line(line_index);
let mut first_non_whitespace_index = None;
let mut last_non_whitespace_index = None;
for (i, c) in line_text.chars().enumerate() {
if !c.is_whitespace() {
if first_non_whitespace_index.is_none() {
first_non_whitespace_index = Some(i);
}
last_non_whitespace_index = Some(i);
}
}
match (first_non_whitespace_index, last_non_whitespace_index) {
(Some(first), Some(last)) => Some((first, last)),
_ => None,
}
}

View File

@ -1,147 +0,0 @@
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()
}
}
}

View File

@ -1,327 +1,201 @@
use crate::{server::{LspConfiguration, LspServer}, utils::get_language_id_by_uri}; use crate::server::ProjectConfig;
use log::info; use regex::Regex;
use ropey::Rope; use ropey::Rope;
use serde::Deserialize; use std::path::PathBuf;
use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use walkdir::DirEntry;
pub mod common;
pub mod iverilog; pub fn get_diagnostics(
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::*;
pub use modelsim::*;
/// description
/// 诊断功能需要提供两套函数,一套函数用于从给定路径读取文件并给出诊断结果;一套用于从 lsp 的文件缓冲区直接读取文本然后给出诊断结果。
/// 前者用于扫描整个项目使用,后者在用户实时修改代码时,给出实时的诊断信息。
///
/// 诊断模块的每一个子诊断器都需要实现如下的函数
/// - provide_diagnostics: 提供一次诊断
/// -
/// 获取诊断核心函数
/// - uri: 当前正在诊断的文件的 uri
/// - rope: 当前正在诊断的文件在后端增量更新的文本内容
/// - files: 所有 hdl 文件,方便后续进行联合诊断使用
/// - server: 服务实例
pub fn provide_diagnostics(
uri: Url, uri: Url,
rope: &Rope, rope: &Rope,
server: &LspServer #[allow(unused_variables)] files: Vec<Url>,
conf: &ProjectConfig,
) -> PublishDiagnosticsParams { ) -> PublishDiagnosticsParams {
let mut diagnostics = Vec::<Diagnostic>::new(); if !(cfg!(test) && (uri.to_string().starts_with("file:///test"))) {
let language_id = get_language_id_by_uri(&uri); let diagnostics = {
if conf.verilator.syntax.enabled {
let configuration = server.configuration.read().unwrap(); if let Ok(path) = uri.to_file_path() {
match verilator_syntax(
// 选择对应语言的 lsp rope,
let linter_configuration = match language_id.as_str() { path,
"vhdl" => Some(&configuration.vhdl_linter_configuration), &conf.verilator.syntax.path,
"verilog" => Some(&configuration.vlog_linter_configuration), &conf.verilator.syntax.args,
"systemverilog" => Some(&configuration.svlog_linter_configuration), ) {
_ => None Some(diags) => diags,
}; None => Vec::new(),
}
if linter_configuration.is_none() { } else {
info!("未知语言 {} 试图发起诊断", language_id); Vec::new()
return PublishDiagnosticsParams { }
uri, diagnostics, } else if conf.verible.syntax.enabled {
version: None match verible_syntax(rope, &conf.verible.syntax.path, &conf.verible.syntax.args) {
Some(diags) => diags,
None => Vec::new(),
}
} else {
Vec::new()
}
}; };
} PublishDiagnosticsParams {
uri,
let linter_configuration = linter_configuration.unwrap(); diagnostics,
version: None,
// 根据配置决定使用哪一个诊断器
// 外层代码需要保证只有一个 linter.enable 为 true
match linter_configuration {
config if config.iverilog.linter.enabled => {
// info!("iverilog linter enter");
if let Some(diag) = &mut config.iverilog.provide_diagnostics(&uri, rope, server) {
diagnostics.append(diag);
}
} }
config if config.verilator.linter.enabled => { } else {
// info!("verilator linter enter"); PublishDiagnosticsParams {
if let Some(diag) = &mut config.verilator.provide_diagnostics(&uri, rope, server) { uri,
diagnostics.append(diag); diagnostics: Vec::new(),
} version: None,
} }
config if config.verible.linter.enabled => {
// info!("verible linter enter");
if let Some(diag) = &mut config.verible.provide_diagnostics(&uri, rope, server) {
diagnostics.append(diag);
}
}
config if config.modelsim.linter.enabled => {
// info!("modelsim linter enter");
if let Some(diag) = &mut config.modelsim.provide_diagnostics(&uri, rope, server) {
diagnostics.append(diag);
}
}
config if config.vivado.linter.enabled => {
// info!("vivado linter enter");
if let Some(diag) = &mut config.vivado.provide_diagnostics(&uri, rope, server) {
diagnostics.append(diag);
}
}
_ => {}
}
PublishDiagnosticsParams {
uri, diagnostics,
version: None
} }
} }
/// 根据输入的名字选择诊断器,并更新所有诊断器的基本路径
/// - `linter_name` 为 `"vivado" | "modelsim" | "verilator" | "verible" | "iverilog"`
/// - `language_id` 为 `"vhdl" | "verilog" | "systemverilog"`
/// - `linter_path` 为第三方的可执行文件的路径
pub fn update_diagnostics_configuration(
server: &LspServer,
linter_name: &str,
language_id: &str,
linter_path: &str
) {
let mut configuration = server.configuration.write().unwrap();
// 选择对应语言的 lsp pub fn is_hidden(entry: &DirEntry) -> bool {
let linter_configuration = match language_id { entry
"vhdl" => Some(&mut configuration.vhdl_linter_configuration), .file_name()
"verilog" => Some(&mut configuration.vlog_linter_configuration), .to_str()
"systemverilog" => Some(&mut configuration.svlog_linter_configuration), .map(|s| s.starts_with('.'))
_ => None .unwrap_or(false)
}; }
if linter_configuration.is_none() {
info!("未知语言 {} 试图配置诊断器", language_id); /// convert captured severity string to DiagnosticSeverity
return; fn verilator_severity(severity: &str) -> Option<DiagnosticSeverity> {
match severity {
"Error" => Some(DiagnosticSeverity::ERROR),
s if s.starts_with("Warning") => Some(DiagnosticSeverity::WARNING),
// NOTE: afaik, verilator doesn't have an info or hint severity
_ => Some(DiagnosticSeverity::INFORMATION),
} }
}
let linter_configuration = linter_configuration.unwrap(); /// syntax checking using verilator --lint-only
fn verilator_syntax(
rope: &Rope,
file_path: PathBuf,
verilator_syntax_path: &str,
verilator_syntax_args: &[String],
) -> Option<Vec<Diagnostic>> {
let mut child = Command::new(verilator_syntax_path)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(verilator_syntax_args)
.arg(file_path.to_str()?)
.spawn()
.ok()?;
linter_configuration.iverilog.linter.enabled = false; static RE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
linter_configuration.verilator.linter.enabled = false; let re = RE.get_or_init(|| {
linter_configuration.verible.linter.enabled = false; Regex::new(
linter_configuration.modelsim.linter.enabled = false; r"%(?P<severity>Error|Warning)(-(?P<warning_type>[A-Z0-9_]+))?: (?P<filepath>[^:]+):(?P<line>\d+):((?P<col>\d+):)? ?(?P<message>.*)",
linter_configuration.vivado.linter.enabled = false; )
.unwrap()
match linter_name { });
"iverilog" => { // write file to stdin, read output from stdout
linter_configuration.iverilog.linter.enabled = true; rope.write_to(child.stdin.as_mut()?).ok()?;
linter_configuration.iverilog.linter.path = linter_path.to_string(); let output = child.wait_with_output().ok()?;
let invoke_name = linter_configuration.iverilog.get_invoke_name(); if !output.status.success() {
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); let mut diags: Vec<Diagnostic> = Vec::new();
} let raw_output = String::from_utf8(output.stderr).ok()?;
"verilator" => { let filtered_output = raw_output
linter_configuration.verilator.linter.enabled = true; .lines()
linter_configuration.verilator.linter.path = linter_path.to_string(); .filter(|line| line.starts_with('%'))
let invoke_name = linter_configuration.verilator.get_invoke_name(); .collect::<Vec<&str>>();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); for error in filtered_output {
} let caps = match re.captures(error) {
"verible" => { Some(caps) => caps,
linter_configuration.verible.linter.enabled = true; None => continue,
linter_configuration.verible.linter.path = linter_path.to_string();
let invoke_name = linter_configuration.verible.get_invoke_name();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
}
"modelsim" => {
linter_configuration.modelsim.linter.enabled = true;
linter_configuration.modelsim.linter.path = linter_path.to_string();
let invoke_name = {
if language_id == "vhdl" {
linter_configuration.modelsim.get_invoke_name()
} else {
linter_configuration.modelsim.get_invoke_name()
}
}; };
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
} // check if diagnostic is for this file, since verilator can provide diagnostics for
"vivado" => { // included files
linter_configuration.vivado.linter.enabled = true; if caps.name("filepath")?.as_str() != file_path.to_str().unwrap_or("") {
linter_configuration.vivado.linter.path = linter_path.to_string(); continue;
let invoke_name = { }
if language_id == "vhdl" { let severity = verilator_severity(caps.name("severity")?.as_str());
linter_configuration.vivado.get_invoke_name() let line: u32 = caps.name("line")?.as_str().to_string().parse().ok()?;
} else { let col: u32 = caps.name("col").map_or("1", |m| m.as_str()).parse().ok()?;
linter_configuration.vivado.get_invoke_name() let pos = Position::new(line - 1, col - 1);
} let msg = match severity {
Some(DiagnosticSeverity::ERROR) => caps.name("message")?.as_str().to_string(),
Some(DiagnosticSeverity::WARNING) => format!(
"{}: {}",
caps.name("warning_type")?.as_str(),
caps.name("message")?.as_str()
),
_ => "".to_string(),
}; };
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name); diags.push(Diagnostic::new(
} Range::new(pos, pos),
_ => { severity,
info!("未找到匹配的诊断器: {}", linter_name); None,
} Some("verilator".to_string()),
} msg,
None,
} None,
));
#[derive(Debug, Deserialize)]
#[derive(serde::Serialize)]
pub struct DigitalLinterConfiguration {
// iverilog 相关的工具配置
pub iverilog: IverilogConfiguration,
// verible 相关的工具配置
pub verible: VeribleConfiguration,
// verilator 相关的工具配置
pub verilator: VerilatorConfiguration,
// modelsim 相关的工具配置
pub modelsim: ModelsimConfiguration,
// vivado 相关的工具配置
pub vivado: VivadoConfiguration
}
impl DigitalLinterConfiguration {
pub fn new(language_id: &str) -> Self {
match language_id {
"vhdl" => {
DigitalLinterConfiguration {
iverilog: IverilogConfiguration::new(
language_id,
"iverilog",
vec![
"-t null".to_string()
]
),
verible: VeribleConfiguration::new(
language_id,
"verible-verilog-syntax",
vec![]
),
verilator: VerilatorConfiguration::new(
language_id,
"verilator",
vec![]
),
modelsim: ModelsimConfiguration::new(
language_id,
"vcom",
vec![
"-quiet".to_string(),
"-nologo".to_string(),
"-2008".to_string()
]
),
vivado: VivadoConfiguration::new(
language_id,
"xvhdl",
vec![
"--nolog".to_string()
]
)
}
}
"systemverilog" => {
DigitalLinterConfiguration {
iverilog: IverilogConfiguration::new(
language_id,
"iverilog",
vec![
"-g2012".to_string()
]
),
verible: VeribleConfiguration::new(
language_id,
"verible-verilog-syntax",
vec![]
),
verilator: VerilatorConfiguration::new(
language_id,
"verilator",
vec![
"--lint-only".to_string(),
"-sv".to_string(),
"-Wall".to_string()
]
),
modelsim: ModelsimConfiguration::new(
language_id,
"vlog",
vec![
"-quiet".to_string(),
"-nologo".to_string(),
"-sv".to_string()
]
),
vivado: VivadoConfiguration::new(
language_id,
"xvlog",
vec![
"--sv".to_string(),
"--nolog".to_string()
]
)
}
}
// 默认为 verilog
_ => {
DigitalLinterConfiguration {
iverilog: IverilogConfiguration::new(
language_id,
"iverilog",
vec![
"-t null".to_string()
]
),
verible: VeribleConfiguration::new(
language_id,
"verible-verilog-syntax",
vec![]
),
verilator: VerilatorConfiguration::new(
language_id,
"verilator",
vec![
"--lint-only".to_string(),
"-Wall".to_string()
]
),
modelsim: ModelsimConfiguration::new(
language_id,
"vlog",
vec![
"-quiet".to_string(),
"-nologo".to_string()
]
),
vivado: VivadoConfiguration::new(
language_id,
"xvlog",
vec![
"--nolog".to_string()
]
)
}
}
} }
Some(diags)
} else {
None
}
}
/// syntax checking using verible-verilog-syntax
fn verible_syntax(
rope: &Rope,
verible_syntax_path: &str,
verible_syntax_args: &[String],
) -> Option<Vec<Diagnostic>> {
let mut child = Command::new(verible_syntax_path)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(verible_syntax_args)
.arg("-")
.spawn()
.ok()?;
static RE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let re = RE.get_or_init(|| {
Regex::new(
r"^.+:(?P<line>\d*):(?P<startcol>\d*)(?:-(?P<endcol>\d*))?:\s(?P<message>.*)\s.*$",
)
.unwrap()
});
// write file to stdin, read output from stdout
rope.write_to(child.stdin.as_mut()?).ok()?;
let output = child.wait_with_output().ok()?;
if !output.status.success() {
let mut diags: Vec<Diagnostic> = Vec::new();
let raw_output = String::from_utf8(output.stdout).ok()?;
for error in raw_output.lines() {
let caps = re.captures(error)?;
let line: u32 = caps.name("line")?.as_str().parse().ok()?;
let startcol: u32 = caps.name("startcol")?.as_str().parse().ok()?;
let endcol: Option<u32> = match caps.name("endcol").map(|e| e.as_str().parse()) {
Some(Ok(e)) => Some(e),
None => None,
Some(Err(_)) => return None,
};
let start_pos = Position::new(line - 1, startcol - 1);
let end_pos = Position::new(line - 1, endcol.unwrap_or(startcol) - 1);
diags.push(Diagnostic::new(
Range::new(start_pos, end_pos),
Some(DiagnosticSeverity::ERROR),
None,
Some("verible".to_string()),
caps.name("message")?.as_str().to_string(),
None,
None,
));
}
Some(diags)
} else {
None
} }
} }

View File

@ -1,143 +0,0 @@
use std::process::{Command, Stdio};
#[allow(unused)]
use log::info;
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::{diagnostics::find_non_whitespace_indices, server::LspServer};
use super::AbstractLinterConfiguration;
use ropey::Rope;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url};
#[derive(Debug, Serialize, Deserialize)]
pub struct ModelsimConfiguration {
pub language_id: String,
pub linter: ModelsimLinter
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ModelsimLinter {
pub name: String,
pub exe_name: String,
/// 目前是否启动
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl ModelsimLinter {
fn new(invoker: &str, args: Vec<String>) -> Self {
Self {
name: "modelsim".to_string(),
exe_name: invoker.to_string(),
enabled: false,
path: invoker.to_string(),
args,
}
}
}
impl AbstractLinterConfiguration for ModelsimConfiguration {
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
ModelsimConfiguration {
language_id: language_id.to_string(),
linter: ModelsimLinter::new(invoker, args)
}
}
fn provide_diagnostics(
&self,
uri: &Url,
#[allow(unused)]
rope: &Rope,
#[allow(unused)]
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 cache_info = server.cache.cache_info.read().unwrap();
let cwd = match &cache_info.linter_cache {
Some(pc) => pc,
None => {
info!("缓存系统尚未完成初始化,本次诊断取消");
return None;
}
};
let child = Command::new(&invoke_name)
.current_dir(cwd)
.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.stdout).ok()?;
info!("modelsim linter: {:?}, output:\n{}", path_string, output_string);
static REGEX_STORE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"\*\* (Error|Warning): \(\S+\) (?P<file>.*?)(\((?P<line>\d+)\))?: (?P<description>.+)").unwrap() });
for error_line in output_string.lines() {
let caps = match regex.captures(error_line) {
Some(caps) => caps,
None => continue
};
info!("get caps: {:?}", caps);
let error_description = caps.name("description").unwrap().as_str();
let error_no = caps.name("line").unwrap().as_str();
let error_no = match error_no.parse::<usize>() {
// Mentor Modelsim vlog 在报告错误和警告时,行数是从 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 }
};
let diagnostic = Diagnostic {
range,
code: None,
severity: Some(DiagnosticSeverity::ERROR),
source: Some("Digital IDE: modelsim".to_string()),
message: error_description.to_string(),
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()
}
}
}

View File

@ -1,137 +0,0 @@
use log::info;
use serde::{Deserialize, Serialize};
use regex::Regex;
use ropey::Rope;
use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*;
use crate::server::LspServer;
use super::AbstractLinterConfiguration;
#[derive(Debug, Serialize, Deserialize)]
pub struct VeribleConfiguration {
pub language_id: String,
pub linter: VeribleLinter,
pub format: VeribleFormat,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VeribleLinter {
pub name: String,
pub exe_name: String,
/// 目前是否启动
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VeribleFormat {
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl VeribleLinter {
fn new(invoker: &str, args: Vec<String>) -> Self {
Self {
name: "verible".to_string(),
exe_name: invoker.to_string(),
enabled: false,
path: invoker.to_string(),
args,
}
}
}
impl Default for VeribleFormat {
fn default() -> Self {
Self {
enabled: false,
path: "verible-verilog-format".to_string(),
args: Vec::new(),
}
}
}
impl AbstractLinterConfiguration for VeribleConfiguration {
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
VeribleConfiguration {
language_id: language_id.to_string(),
linter: VeribleLinter::new(invoker, args),
format: VeribleFormat::default()
}
}
fn provide_diagnostics(
&self,
uri: &Url,
#[allow(unused)]
rope: &Rope,
#[allow(unused)]
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.stdout).ok()?;
info!("verible linter: {:?}, output:\n{}", path_string, output_string);
static REGEX_STORE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"^.+:(?P<line>\d*):(?P<startcol>\d*)(?:-(?P<endcol>\d*))?:\s(?P<message>.*)\s.*$").unwrap() });
for error_line in output_string.lines() {
let caps = regex.captures(error_line)?;
let line: u32 = caps.name("line")?.as_str().parse().ok()?;
let startcol: u32 = caps.name("startcol")?.as_str().parse().ok()?;
let endcol: Option<u32> = match caps.name("endcol").map(|e| e.as_str().parse()) {
Some(Ok(e)) => Some(e),
None => None,
Some(Err(_)) => return None,
};
let start_pos = Position::new(line - 1, startcol - 1);
let end_pos = Position::new(line - 1, endcol.unwrap_or(startcol) - 1);
let diagnostic = Diagnostic {
range: Range::new(start_pos, end_pos),
severity: Some(DiagnosticSeverity::ERROR),
code: None,
source: Some("Digital IDE: verible".to_string()),
message: caps.name("message")?.as_str().to_string(),
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()
}
}
}

View File

@ -1,272 +0,0 @@
use log::info;
use serde::{Deserialize, Serialize};
use regex::Regex;
use ropey::Rope;
use std::{collections::HashSet, path::{Path, PathBuf}, process::{Command, Stdio}, str::FromStr};
use tower_lsp::lsp_types::*;
use crate::{server::LspServer, utils::from_uri_to_escape_path_string};
use super::AbstractLinterConfiguration;
#[derive(Debug, Serialize, Deserialize)]
pub struct VerilatorConfiguration {
pub language_id: String,
pub linter: VerilatorLinter,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VerilatorLinter {
pub name: String,
pub exe_name: String,
/// 目前是否启动
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl VerilatorLinter {
fn new(invoker: &str, args: Vec<String>) -> Self {
Self {
name: "verilator".to_string(),
exe_name: invoker.to_string(),
enabled: false,
path: invoker.to_string(),
args,
}
}
}
/// convert captured severity string to DiagnosticSeverity
fn verilator_severity(severity: &str) -> Option<DiagnosticSeverity> {
match severity {
"Error" => Some(DiagnosticSeverity::ERROR),
s if s.starts_with("Warning") => Some(DiagnosticSeverity::WARNING),
// NOTE: afaik, verilator doesn't have an info or hint severity
_ => Some(DiagnosticSeverity::INFORMATION),
}
}
impl AbstractLinterConfiguration for VerilatorConfiguration {
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
VerilatorConfiguration {
language_id: language_id.to_string(),
linter: VerilatorLinter::new(invoker, args)
}
}
fn provide_diagnostics(
&self,
uri: &Url,
#[allow(unused)]
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 include_args = make_include_args(server, path_string);
let child = Command::new(&invoke_name)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&self.linter.args)
.args(include_args)
.arg(path_string)
.spawn()
.ok()?;
let output = child.wait_with_output().ok()?;
let output_string = String::from_utf8(output.stderr).ok()?;
let lint_level = server.db.get_lsp_configuration_string_value("digital-ide.function.lsp.linter.linter-level").unwrap_or("error".to_string());
info!("verilator linter: {:?}, output:\n{}", path_string, output_string);
static REGEX_STORE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"%(?P<severity>Error|Warning)(-(?P<warning_type>[A-Z0-9_]+))?: (?P<filepath>[^:]+):(?P<line>\d+):((?P<col>\d+):)? ?(?P<message>.*)").unwrap() });
// verilator 错误输出样例
// %Warning-IMPLICIT: library/Apply/Comm/FDE/AGC/AGC.v:23:12: Signal definition not found, creating implicitly: 'r_rms'
// : ... Suggested alternative: 'ref_rms'
// 23 | assign r_rms = (reference * reference);
// | ^~~~~
// ... Use "/* verilator lint_off IMPLICIT */" and lint_on around source to disable this message.
// %Error: Exiting due to 5 warning(s)
// 先将上面的输出处理为一整块一整块的数组,每块分解为两个部分:第一行(用于解析行号和列号)和后面几行(具体错误地址)
let mut error_tuples = Vec::<(String, String)>::new();
let mut current_error_tuple: Option<(&str, Vec<&str>)> = None;
for error_line in output_string.lines() {
if error_line.starts_with("%") {
if let Some((first, ref description)) = current_error_tuple {
error_tuples.push((first.to_string(), description.join("\n")));
}
current_error_tuple = Some((error_line, Vec::<&str>::new()));
} else {
if let Some((_, ref mut description)) = current_error_tuple {
description.push(error_line);
} else {
continue;
}
}
}
for error_tuple in error_tuples {
// 对于 NOTFOUNDMODULE 进行过滤
// 如果存在于 fast 中,则直接跳过
if let Some(module_name) = match_not_module_found(error_tuple.0.as_str()) {
if server.db.contains_module(&module_name) {
continue;
}
}
// 使用模板进行解析
let caps = match regex.captures(error_tuple.0.as_str()) {
Some(caps) => caps,
None => continue
};
// 因为 verilator 会递归检查,因此报错信息中可能会出现非本文件内的信息
if caps.name("filepath")?.as_str().replace("\\", "/") != path_string {
continue;
}
let line: u32 = caps.name("line")?.as_str().to_string().parse().ok()?;
let col: u32 = caps.name("col").map_or("1", |m| m.as_str()).parse().ok()?;
// verilator 的诊断索引都是 one index 的
let pos = Position::new(line - 1, col - 1);
let severity = verilator_severity(caps.name("severity")?.as_str());
let message = match severity {
Some(DiagnosticSeverity::ERROR) => {
caps.name("message")?.as_str().to_string()
},
Some(DiagnosticSeverity::WARNING) => {
// 如果诊断等级为 error ,也就是只显示错误,那么直接跳过
if lint_level == "error" {
continue;
}
format!(
"{}: {}",
caps.name("warning_type")?.as_str(),
caps.name("message")?.as_str()
)
},
_ => "".to_string(),
};
// TODO: 支持更多的报错
let end_pos = refine_end_pos(rope, &message, pos);
let message = format!("{}\n\n{}", message, error_tuple.1);
let diagnostic = Diagnostic {
range: Range::new(pos, end_pos),
code: None,
severity,
source: Some("Digital IDE: verilator".to_string()),
message,
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()
}
}
}
/// %Warning-DECLFILENAME: /home/dide/project/Digital-Test/DIDEtemp/user/sim/FFT/FFT_IFFT_tb.v:82:5: Filename 'FFT_IFFT_tb' does not match NOTFOUNDMODULE name: 'FFT_IFFT'
/// 匹配最后一个 module 的 name
fn match_not_module_found(error_line: &str) -> Option<String> {
if let Some(start_index) = error_line.find("NOTFOUNDMODULE") {
let mut strings = "".to_string();
for char in error_line.chars().skip(start_index).skip_while(|x| x.to_string() != "'") {
strings.push(char);
}
if strings.starts_with("'") && strings.ends_with("'") {
strings = strings[1 .. strings.len() - 1].to_string();
}
return Some(strings);
}
None
}
fn make_include_args(
server: &LspServer,
path_string: &str
) -> Vec::<String> {
let mut include_paths = HashSet::<String>::new();
let configuration = server.configuration.read().unwrap();
let workspace = configuration.workspace_folder.clone().unwrap();
let path = Path::new(path_string);
if let Some(parent) = path.parent() {
let folder_string = parent.to_str().unwrap();
// 加入目标文件的 __dirname
include_paths.insert(folder_string.to_string());
}
let workspace_path = from_uri_to_escape_path_string(&workspace);
if let Some(workspace_path) = workspace_path {
let workspace = PathBuf::from_str(&workspace_path).unwrap();
let src_path = workspace.join("user").join("src");
let sim_path = workspace.join("user").join("sim");
// 加入 user/src
include_paths.insert(src_path.to_str().unwrap().to_string());
// 加入 user/sim
include_paths.insert(sim_path.to_str().unwrap().to_string());
// 加入 workspace
include_paths.insert(workspace_path);
}
let mut include_args = Vec::<String>::new();
for path in include_paths {
include_args.push(format!("-I{}", path));
}
include_args
}
fn refine_end_pos(
rope: &Rope,
message: &str,
pos: Position
) -> Position {
if message.starts_with("Cannot find include file") {
let mut pls = message.split(":");
if let Some(include_name) = pls.nth(1) {
let include_name = include_name.trim();
let line_text = rope.line(pos.line as usize).to_string();
if let Some(index) = line_text.find(include_name) {
let end_character = index + include_name.len() + 1;
let pos = Position {
line: pos.line,
character: end_character as u32
};
return pos;
}
}
}
pos
}

View File

@ -1,257 +0,0 @@
use std::{collections::HashSet, process::{Command, Stdio}};
#[allow(unused)]
use log::info;
use regex::{escape, Regex};
use serde::{Deserialize, Serialize};
use crate::{diagnostics::find_non_whitespace_indices, server::LspServer, utils::from_uri_to_escape_path_string};
use ropey::Rope;
use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Position, Range, Url};
use super::AbstractLinterConfiguration;
#[derive(Debug, Serialize, Deserialize)]
pub struct VivadoConfiguration {
pub language_id: String,
pub linter: VivadoLinter,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct VivadoLinter {
pub name: String,
pub exe_name: String,
/// 目前是否启动
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl VivadoLinter {
fn new(invoker: &str, args: Vec<String>) -> Self {
Self {
name: "vivado".to_string(),
exe_name: invoker.to_string(),
enabled: false,
path: invoker.to_string(),
args,
}
}
}
impl AbstractLinterConfiguration for VivadoConfiguration {
fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
VivadoConfiguration {
language_id: language_id.to_string(),
linter: VivadoLinter::new(invoker, args)
}
}
fn provide_diagnostics(
&self,
uri: &Url,
#[allow(unused)]
rope: &Rope,
#[allow(unused)]
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 cache_info = server.cache.cache_info.read().unwrap();
let cwd = match &cache_info.linter_cache {
Some(pc) => pc,
None => {
info!("缓存系统尚未完成初始化,本次诊断取消");
return None;
}
};
// vivado 比较特殊,需要先分析出当前文件用了哪些其他文件的宏,然后把那部分宏所在的文件加入编译参数中
let dependence_files = get_all_dependence_files(uri, server);
let child = Command::new(&invoke_name)
.current_dir(cwd)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&self.linter.args)
.args(dependence_files)
.arg(path_string)
.spawn()
.ok()?;
let output = child.wait_with_output().ok()?;
let output_string = String::from_utf8(output.stdout).ok()?;
info!("vivado linter: {:?}, output:\n{}", path_string, output_string);
static REGEX_STORE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"ERROR: \[VRFC (?P<error_code>\S+)] (?P<description>.+) \[(?P<file>.+):(?P<line>\d+)\]").unwrap() });
for error_line in output_string.lines() {
let caps = match regex.captures(error_line) {
Some(caps) => caps,
None => continue
};
let error_code = caps.name("error_code").unwrap().as_str();
let error_description = caps.name("description").unwrap().as_str();
let error_no = caps.name("line").unwrap().as_str();
let error_no = match error_no.parse::<usize>() {
// Xilinx Vivado xvlog 在报告错误和警告时,行数是从 1 开始计数的。
Ok(no) => no - 1,
Err(_) => 0
};
if let Some((start_char, end_char)) = find_vivado_suitable_range(rope, error_no, error_description) {
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 }
};
if error_description.contains("due to previous errors") {
continue;
}
let diagnostic = Diagnostic {
range,
code: Some(NumberOrString::String(error_code.to_string())),
severity: Some(DiagnosticSeverity::ERROR),
source: Some("Digital IDE: vivado".to_string()),
message: error_description.to_string(),
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!("{}.bat", self.linter.exe_name)
} else {
self.linter.exe_name.to_string()
}
}
}
/// 计算出当前文件所有用到的别的文件(比如使用了其他文件的宏)
/// 必须把这些文件也编入诊断中,才能基于 vivado 得到合理的结果
fn get_all_dependence_files(
uri: &Url,
server: &LspServer
) -> Vec<String> {
let mut files = HashSet::<String>::new();
let path_string = from_uri_to_escape_path_string(uri).unwrap();
let mut used_macro_names = HashSet::<String>::new();
let hdl_param = server.db.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_string) {
for macro_symbol in &hdl_file.parse_result.symbol_table.macro_usages {
used_macro_names.insert(macro_symbol.name.to_string());
}
}
for (file_path, hdl_file) in path_to_hdl_file.iter() {
if file_path == path_string.as_str() {
// 只看其他文件
continue;
}
for define in hdl_file.fast.fast_macro.defines.iter() {
let macro_name = define.name.to_string();
if used_macro_names.contains(&macro_name) {
used_macro_names.remove(&macro_name);
files.insert(file_path.to_string());
}
}
// 如果 unused_macro_names 都找到了对应的 path直接 break 即可
if used_macro_names.is_empty() {
break;
}
}
// 释放锁
drop(path_to_hdl_file);
files.into_iter().collect()
}
/// 根据 vivado 返回的诊断信息,返回适合的错误在那一行的起始位置
/// 默认是返回该行的第一个非空字符到最后一个非空字符中间的位置,即 find_non_whitespace_indices
fn find_vivado_suitable_range(
rope: &Rope,
error_no: usize,
error_description: &str
) -> Option<(usize, usize)> {
// 一般 vivado 会把出错的关键词用单引号包起来
// 只需要提取这个单词,并在行中匹配它,如果任何一步失败,都采用 find_non_whitespace_indices 即可
static REGEX_STORE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let regex = REGEX_STORE.get_or_init(|| { Regex::new(r"'([^']+)'").unwrap() });
let error_keyword = match regex.captures(error_description) {
Some(caps) => {
caps.get(1).unwrap().as_str()
}
None => {
return find_non_whitespace_indices(rope, error_no)
}
};
let error_keyword = escape(error_keyword);
let pattern = format!(r"\b(?i){}\b", error_keyword);
let regex = Regex::new(&pattern).unwrap();
if let Some(line_text) = rope.line(error_no).as_str() {
// 处理特殊情况: error_keyword 为 ;
if error_keyword == ";" {
if let Some(index) = line_text.find(";") {
return Some((index, index));
}
}
if let Some(mat) = regex.find(line_text) {
// info!("mat {} {}", mat.start(), mat.end());
return Some((mat.start(), mat.end()));
}
}
find_non_whitespace_indices(rope, error_no)
}
/// 判断是否为类似于 xxx ignored 的错误
/// ERROR: [VRFC 10-8530] module 'main' is ignored due to previous errors [/home/dide/project/Digital-Test/Digital-macro/user/src/main.v:1]
#[allow(unused)]
fn is_ignore_type(diag: &Diagnostic) -> bool {
// 获取 vrfc 编码
let vrfc_code = if let Some(NumberOrString::String(code)) = &diag.code {
code
} else {
return false;
};
match vrfc_code.as_str() {
"10-8530" => {
true
}
_ => {
false
}
}
}

View File

@ -1,25 +1,21 @@
use crate::{server::LspServer, utils::{from_uri_to_escape_path_string, get_definition_token, get_language_id_by_uri}}; use crate::{server::LSPServer, utils::{get_definition_token, get_language_id_by_uri}};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
mod sv; mod sv;
mod vhdl; mod vhdl;
impl LspServer { impl LSPServer {
pub fn document_highlight( pub fn document_highlight(
&self, &self,
params: DocumentHighlightParams, params: DocumentHighlightParams,
) -> Option<Vec<DocumentHighlight>> { ) -> Option<Vec<DocumentHighlight>> {
let uri = &params.text_document_position_params.text_document.uri; let uri = &params.text_document_position_params.text_document.uri;
let pos = params.text_document_position_params.position; let pos = params.text_document_position_params.position;
let file_id = self.srcs.get_id(&uri).to_owned();
let path_string = from_uri_to_escape_path_string(uri).unwrap(); self.srcs.wait_parse_ready(file_id, false);
let file = self.srcs.get_file(file_id)?;
// 等待解析完成 let file = file.read().ok()?;
self.db.wait_parse_ready(&path_string, false); let line_text = file.text.line(pos.line as usize);
let source = self.db.get_source(&path_string)?;
let source = source.read().ok()?;
let line_text = source.text.line(pos.line as usize);
let token = get_definition_token(&line_text, pos); let token = get_definition_token(&line_text, pos);
let language_id = get_language_id_by_uri(&uri); let language_id = get_language_id_by_uri(&uri);
@ -36,7 +32,7 @@ impl LspServer {
"verilog" | "systemverilog" => sv::document_highlight( "verilog" | "systemverilog" => sv::document_highlight(
self, self,
&token, &token,
&source, &file,
pos, pos,
&uri &uri
), ),

View File

@ -3,39 +3,40 @@ use log::info;
use sv_parser::{RefNode, SyntaxTree}; use sv_parser::{RefNode, SyntaxTree};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use crate::{server::LspServer, sources::{AstLike, LSPSupport, Source}, utils::from_uri_to_escape_path_string}; use crate::{definition::{get_ident, Scope}, server::LSPServer, sources::{LSPSupport, ParseIR, Source}};
use crate::core::scope_tree::parse::get_ident;
use crate::core::scope_tree::common::Scope;
pub fn document_highlight( pub fn document_highlight(
server: &LspServer, server: &LSPServer,
token: &str, token: &str,
file: &Source, file: &Source,
pos: Position, pos: Position,
uri: &Url uri: &Url
) -> Option<Vec<DocumentHighlight>> { ) -> Option<Vec<DocumentHighlight>> {
let scope_tree = server.db.scope_tree.read().ok()?; let scope_tree = server.srcs.scope_tree.read().ok()?;
let path_string = from_uri_to_escape_path_string(uri).unwrap();
// use the byte_idx of the definition if possible, otherwise use the cursor // use the byte_idx of the definition if possible, otherwise use the cursor
let byte_idx = match scope_tree.as_ref()?.get_definition(token, file.text.pos_to_byte(&pos), uri) { let byte_idx =
Some(def) => def.byte_idx, match scope_tree
None => file.text.pos_to_byte(&pos), .as_ref()?
}; .get_definition(token, file.text.pos_to_byte(&pos), uri)
{
// 获取对应的 AST Some(def) => def.byte_idx,
let path_to_hdl_file = server.db.hdl_param.path_to_hdl_file.read().unwrap(); None => file.text.pos_to_byte(&pos),
if let Some(hdl_file) = path_to_hdl_file.get(&path_string) { };
if let Some(AstLike::Svlog(syntax_tree)) = &hdl_file.ast_like { let syntax_tree = file.parse_ir.as_ref()?;
match syntax_tree {
ParseIR::SyntaxTree(syntax_tree) => {
let references = all_identifiers(&syntax_tree, &token); let references = all_identifiers(&syntax_tree, &token);
let highlights = scope_tree.as_ref()?.document_highlights(&uri, &file.text, references, byte_idx); Some(
Some(highlights) scope_tree
} else { .as_ref()?
.document_highlights(&uri, &file.text, references, byte_idx),
)
},
_ => {
info!("error happen in [sv_document_highlight]");
None None
} }
} else {
None
} }
} }

View File

@ -2,28 +2,41 @@ use log::info;
use sv_parser::{RefNode, SyntaxTree}; use sv_parser::{RefNode, SyntaxTree};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use crate::{server::LspServer, sources::{LSPSupport, Source}}; use crate::{definition::{get_ident, Scope}, server::LSPServer, sources::{LSPSupport, ParseIR, Source}};
use crate::core::scope_tree::parse::get_ident;
use crate::core::scope_tree::common::Scope;
pub fn document_highlight( pub fn document_highlight(
server: &LspServer, server: &LSPServer,
token: &str, token: &str,
file: &Source, file: &Source,
pos: Position, pos: Position,
uri: &Url uri: &Url
) -> Option<Vec<DocumentHighlight>> { ) -> Option<Vec<DocumentHighlight>> {
let scope_tree = server.db.scope_tree.read().ok()?; let scope_tree = server.srcs.scope_tree.read().ok()?;
// use the byte_idx of the definition if possible, otherwise use the cursor // use the byte_idx of the definition if possible, otherwise use the cursor
let byte_idx = match scope_tree.as_ref()?.get_definition(token, file.text.pos_to_byte(&pos), uri) { let byte_idx =
Some(def) => def.byte_idx, match scope_tree
None => file.text.pos_to_byte(&pos), .as_ref()?
}; .get_definition(token, file.text.pos_to_byte(&pos), uri)
{
// TODO: 完成剩余部分 Some(def) => def.byte_idx,
None => file.text.pos_to_byte(&pos),
None };
let syntax_tree = file.parse_ir.as_ref()?;
match syntax_tree {
ParseIR::SyntaxTree(syntax_tree) => {
let references = all_identifiers(&syntax_tree, &token);
Some(
scope_tree
.as_ref()?
.document_highlights(&uri, &file.text, references, byte_idx),
)
},
_ => {
info!("error happen in [vhdl_document_highlight]");
None
}
}
} }
/// return all identifiers in a syntax tree matching a given token /// return all identifiers in a syntax tree matching a given token

View File

@ -1,11 +1,11 @@
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use crate::{server::LspServer, utils::get_language_id_by_uri}; use crate::{server::LSPServer, utils::get_language_id_by_uri};
mod sv; mod sv;
mod vhdl; mod vhdl;
impl LspServer { impl LSPServer {
pub fn document_symbol(&self, params: DocumentSymbolParams) -> Option<DocumentSymbolResponse> { pub fn document_symbol(&self, params: DocumentSymbolParams) -> Option<DocumentSymbolResponse> {
let uri = &params.text_document.uri; let uri = &params.text_document.uri;
let language_id = get_language_id_by_uri(uri); let language_id = get_language_id_by_uri(uri);

View File

@ -1,22 +1,18 @@
use crate::{core::scope_tree::common::Scope, server::LspServer, utils::from_uri_to_escape_path_string}; use crate::{definition::Scope, server::LSPServer};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
pub fn document_symbol( pub fn document_symbol(
server: &LspServer, server: &LSPServer,
params: &DocumentSymbolParams params: &DocumentSymbolParams
) -> Option<DocumentSymbolResponse> { ) -> Option<DocumentSymbolResponse> {
let uri = &params.text_document.uri; let uri = &params.text_document.uri;
let file_id = server.srcs.get_id(uri).to_owned();
let path_string = from_uri_to_escape_path_string(uri).unwrap(); server.srcs.wait_parse_ready(file_id, false);
let file = server.srcs.get_file(file_id)?;
// 等待解析完成 let file = file.read().ok()?;
server.db.wait_parse_ready(&path_string, false); let scope_tree = server.srcs.scope_tree.read().ok()?;
let source = server.db.get_source(&path_string)?;
let source = source.read().ok()?;
let scope_tree = server.db.scope_tree.read().ok()?;
Some(DocumentSymbolResponse::Nested( Some(DocumentSymbolResponse::Nested(
scope_tree.as_ref()?.document_symbols(uri, &source.text), scope_tree.as_ref()?.document_symbols(uri, &file.text),
)) ))
} }

View File

@ -1,82 +1,19 @@
use std::{path::PathBuf, str::FromStr};
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use vhdl_lang::{EntHierarchy, Token}; use crate::{definition::Scope, server::LSPServer};
use crate::{server::LspServer, utils::{from_uri_to_escape_path_string, to_escape_path, to_lsp_range, to_symbol_kind}};
pub fn document_symbol(server: &LSPServer, params: &DocumentSymbolParams) -> Option<DocumentSymbolResponse> {
// info!("enter document symbol");
pub fn document_symbol(server: &LspServer, params: &DocumentSymbolParams) -> Option<DocumentSymbolResponse> {
let uri = &params.text_document.uri; let uri = &params.text_document.uri;
let path_string = from_uri_to_escape_path_string(uri).unwrap(); let file_id = server.srcs.get_id(&uri).to_owned();
server.srcs.wait_parse_ready(file_id, false);
// 等待解析完成 let file = server.srcs.get_file(file_id)?;
server.db.wait_parse_ready(&path_string, false); let file = file.read().ok()?;
// let source_handle = server.db.get_source(&path_string)?; let scope_tree = server.srcs.scope_tree.read().ok()?;
// let source_handle = source_handle.read().ok()?;
let scope_tree = server.db.scope_tree.read().ok()?;
let project = server.db.vhdl_project.read().ok()?;
let global_project = project.as_ref().unwrap();
let path = match PathBuf::from_str(uri.path()) {
Ok(path) => path,
Err(error) => {
info!("error happen in vhdl <hover>: {:?}", error);
return None;
}
};
let escape_path = to_escape_path(&path);
let project_file = escape_path.as_path();
let Some(source) = global_project.project.get_source(project_file) else {
return None
};
// Some files are mapped to multiple libraries, only use the first library for document symbols
let library_name = global_project.project
.library_mapping_of(&source)
.into_iter()
.next()?;
fn to_document_symbol(
EntHierarchy { ent, children }: EntHierarchy,
ctx: &Vec<Token>,
) -> DocumentSymbol {
// Use the declaration position, if it exists,
// else the position of the first source range token.
// The latter is applicable for unnamed elements, e.g., processes or loops.
let selection_pos = ent.decl_pos().unwrap_or(ent.src_span.start_token.pos(ctx));
let src_range = ent.src_span.pos(ctx).range();
#[allow(deprecated)]
DocumentSymbol {
name: ent.describe(),
kind: to_symbol_kind(ent.kind()),
tags: None,
detail: None,
selection_range: to_lsp_range(selection_pos.range),
range: to_lsp_range(src_range),
children: if !children.is_empty() {
Some(
children
.into_iter()
.map(|hierarchy| to_document_symbol(hierarchy, ctx))
.collect(),
)
} else {
None
},
deprecated: None,
}
}
Some(DocumentSymbolResponse::Nested( Some(DocumentSymbolResponse::Nested(
global_project.project scope_tree.as_ref()?.document_symbols(uri, &file.text),
.document_symbols(&library_name, &source)
.into_iter()
.map(|(hierarchy, tokens)| to_document_symbol(hierarchy, tokens))
.collect(),
)) ))
// Some(DocumentSymbolResponse::Nested(
// scope_tree.as_ref()?.document_symbols(uri, &file.text),
// ))
} }

View File

@ -1,16 +0,0 @@
use std::{path::PathBuf, str::FromStr};
use log::info;
use serde_json::Value;
use tower_lsp::lsp_types::{Diagnostic, Url};
use crate::{diagnostics::provide_diagnostics, server::Backend, utils::{from_uri_to_escape_path_string, open_doc_as_rope}};
// /// 前端请求,发布诊断结果,仅在初始化和修改配置时触发
// /// 参数为 [file_path: string]
// pub async fn publish_diagnostics(
// backend: &Backend,
// arguments: Vec<Value>
// ) -> tower_lsp::jsonrpc::Result<Option<Value>> {
// }

View File

@ -1,59 +0,0 @@
use std::{path::PathBuf, str::FromStr};
use log::info;
use serde_json::Value;
use tower_lsp::lsp_types::{Diagnostic, Url};
use crate::{diagnostics::provide_diagnostics, server::Backend, utils::{from_uri_to_escape_path_string, open_doc_as_rope}};
/// 前端请求,发布诊断结果,仅在初始化和修改配置时触发
/// 参数为 [file_path: string]
pub async fn publish_diagnostics(
backend: &Backend,
arguments: Vec<Value>
) -> tower_lsp::jsonrpc::Result<Option<Value>> {
let path_string = arguments.get(0).unwrap().as_str().unwrap();
info!("path_string: {:?}", path_string);
let uri = Url::from_file_path(path_string).unwrap();
let path_string = from_uri_to_escape_path_string(&uri).unwrap();
let pathbuf = PathBuf::from_str(&path_string).unwrap();
// 考虑到性能,如果后端文本缓冲器内存在当前路径的 文本备份,则使用它作为 rope
// 否则,进行 IO 后再转换
info!("open {:?} as rope", pathbuf);
let diagnostics_params = if let Some(source) = backend.server.db.get_source(&path_string) {
let source_handle = source.read().unwrap();
provide_diagnostics(uri, &source_handle.text, &backend.server)
} else {
// 读取文件vscode 前端是有可能存在不存在的文件的,如果文件不存在,直接返回
let rope = open_doc_as_rope(&pathbuf);
if rope.is_none() {
return Ok(None);
}
provide_diagnostics(uri, &rope.unwrap(), &backend.server)
};
backend.client.publish_diagnostics(
diagnostics_params.uri,
diagnostics_params.diagnostics,
None
).await;
Ok(None)
}
/// 前端请求,清除诊断结果,仅在初始化和修改配置时触发
/// 参数为 [file_path: string]
pub async fn clear_diagnostics(
backend: &Backend,
arguments: Vec<Value>
) -> tower_lsp::jsonrpc::Result<Option<Value>> {
let path_string = arguments.get(0).unwrap().as_str().unwrap();
let uri = Url::from_file_path(path_string).unwrap();
let diagnostics = Vec::<Diagnostic>::new();
backend.client.publish_diagnostics(uri, diagnostics, None).await;
Ok(None)
}

View File

@ -1,30 +0,0 @@
use serde_json::Value;
use tower_lsp::lsp_types::*;
use crate::server::Backend;
mod diagnostics;
pub use codedoc::*;
mod codedoc;
pub async fn execute_command(
backend: &Backend,
params: ExecuteCommandParams
) -> tower_lsp::jsonrpc::Result<Option<Value>> {
match params.command.as_str() {
"publish-diagnostics" => {
diagnostics::publish_diagnostics(backend, params.arguments).await
}
"clear-diagnostics" => {
diagnostics::clear_diagnostics(backend, params.arguments).await
}
_ => {
Ok(None)
}
}
}

View File

@ -1,61 +1,60 @@
use crate::server::LspServer; use crate::server::LSPServer;
use crate::sources::LSPSupport;
use log::info; use log::info;
use ropey::Rope; use ropey::Rope;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
impl LspServer { impl LSPServer {
pub fn formatting(&self, params: DocumentFormattingParams) -> Option<Vec<TextEdit>> { pub fn formatting(&self, params: DocumentFormattingParams) -> Option<Vec<TextEdit>> {
None let uri = params.text_document.uri;
// let uri = params.text_document.uri; info!("formatting {}", &uri);
// info!("formatting {}", &uri); let file_id = self.srcs.get_id(&uri).to_owned();
// let file_id = self.db.get_id(&uri).to_owned(); self.srcs.wait_parse_ready(file_id, false);
// self.db.wait_parse_ready(file_id, false); let file = self.srcs.get_file(file_id)?;
// let file = self.db.get_file(file_id)?; let file = file.read().ok()?;
// let file = file.read().ok()?;
// let conf = self.configuration.read().unwrap(); let conf = self.conf.read().unwrap();
// if conf.verible.format.enabled { if conf.verible.format.enabled {
// Some(vec![TextEdit::new( Some(vec![TextEdit::new(
// Range::new( Range::new(
// file.text.char_to_pos(0), file.text.char_to_pos(0),
// file.text.char_to_pos(file.text.len_chars()), file.text.char_to_pos(file.text.len_chars()),
// ), ),
// format_document( format_document(
// &file.text, &file.text,
// None, None,
// &conf.verible.format.path, &conf.verible.format.path,
// &conf.verible.format.args, &conf.verible.format.args,
// )?, )?,
// )]) )])
// } else { } else {
// None None
// } }
} }
pub fn range_formatting(&self, params: DocumentRangeFormattingParams) -> Option<Vec<TextEdit>> { pub fn range_formatting(&self, params: DocumentRangeFormattingParams) -> Option<Vec<TextEdit>> {
None let uri = params.text_document.uri;
// let uri = params.text_document.uri; info!("range formatting {}", &uri);
// info!("range formatting {}", &uri); let file_id = self.srcs.get_id(&uri).to_owned();
// let file_id = self.db.get_id(&uri).to_owned(); self.srcs.wait_parse_ready(file_id, false);
// self.db.wait_parse_ready(file_id, false); let file = self.srcs.get_file(file_id)?;
// let file = self.db.get_file(file_id)?; let file = file.read().ok()?;
// let file = file.read().ok()?;
// let conf = self.configuration.read().unwrap(); let conf = self.conf.read().unwrap();
// if conf.verible.format.enabled { if conf.verible.format.enabled {
// Some(vec![TextEdit::new( Some(vec![TextEdit::new(
// file.text.char_range_to_range(0..file.text.len_chars()), file.text.char_range_to_range(0..file.text.len_chars()),
// format_document( format_document(
// &file.text, &file.text,
// Some(params.range), Some(params.range),
// &conf.verible.format.path, &conf.verible.format.path,
// &conf.verible.format.args, &conf.verible.format.args,
// )?, )?,
// )]) )])
// } else { } else {
// None None
// } }
} }
} }

View File

@ -1,18 +1,15 @@
use std::{path::PathBuf, str::FromStr, sync::Arc}; use std::{path::PathBuf, str::FromStr};
use log::info; use log::info;
use regex::Regex; use regex::Regex;
use ropey::RopeSlice; use ropey::RopeSlice;
use tower_lsp::lsp_types::{Hover, HoverContents, LanguageString, MarkedString, Position, Range, Url}; use tower_lsp::lsp_types::{Hover, HoverContents, LanguageString, MarkedString, Position, Range, Url};
use crate::{core::{self, hdlparam::{Define, FastHdlparam}, primitive_parser::PrimitiveText}, server::LspServer}; use crate::{core::hdlparam::{Define, FastHdlparam}, definition::{DefinitionType, GenericDec}, server::LSPServer};
use super::{get_language_id_by_path_str, get_word_range_at_position, resolve_path, to_escape_path}; use super::{get_word_range_at_position, resolve_path, to_escape_path};
/// 将 4'b0011 分解为 ("b", "0011") /// 将 4'b0011 分解为 ("b", "0011")
/// 需要支持的格式:
/// - `4'b0011`
/// - `8'b0000_1111`
fn parse_digit_string(digit_string: &str) -> Option<(&str, &str)> { fn parse_digit_string(digit_string: &str) -> Option<(&str, &str)> {
if digit_string.len() == 0 { if digit_string.len() == 0 {
return None; return None;
@ -32,14 +29,7 @@ fn parse_digit_string(digit_string: &str) -> Option<(&str, &str)> {
return None; return None;
} }
// 此时4'b0011 被自动分割成了 return Some((tag.unwrap(), digit.unwrap()));
// tag: b
// digit: 0011
// 前面的 4 被丢弃了,因为 4 可以通过 0011 的长度判断出来
let tag = tag.unwrap();
let digit = digit.unwrap();
return Some((tag, digit));
}, },
None => return None None => return None
}; };
@ -56,20 +46,13 @@ fn convert_tag_to_radix(tag: &str) -> Option<u32> {
} }
/// 计算出有符号和无符号下的表示 /// 计算出有符号和无符号下的表示
/// - `tag`: b, o, h 这些代表进制的字符,
/// - `digit_string`: 进制数
///
/// 需要支持的格式:
/// - `4'b0011`
/// - `8'b0000_1111`
fn convert_to_sign_unsign<'a>(tag: &'a str, digit_string: &str) -> Option<(String, String)> { fn convert_to_sign_unsign<'a>(tag: &'a str, digit_string: &str) -> Option<(String, String)> {
let radix = convert_tag_to_radix(tag); let radix = convert_tag_to_radix(tag);
if radix.is_none() { if radix.is_none() {
return None; return None;
} }
let radix = radix.unwrap(); let radix = radix.unwrap();
let raw_digit_string = digit_string.replace("_", ""); let unsigned_decimal = u128::from_str_radix(digit_string, radix);
let unsigned_decimal = u128::from_str_radix(&raw_digit_string, radix);
if unsigned_decimal.is_err() { if unsigned_decimal.is_err() {
return None; return None;
@ -91,7 +74,7 @@ fn convert_to_sign_unsign<'a>(tag: &'a str, digit_string: &str) -> Option<(Strin
/// 将 1'b1 翻译成 10进制 /// 将 1'b1 翻译成 10进制
pub fn hover_format_digit(line: &RopeSlice, pos: Position, language_id: &str) -> Option<Hover> { pub fn hover_format_digit(line: &RopeSlice, pos: Position, language_id: &str) -> Option<Hover> {
let regex = Regex::new(r"[0-9'bho_]").unwrap(); let regex = Regex::new(r"[0-9'bho]").unwrap();
let token_result = get_word_range_at_position(line, pos, regex); let token_result = get_word_range_at_position(line, pos, regex);
if token_result.is_none() { if token_result.is_none() {
@ -104,7 +87,7 @@ pub fn hover_format_digit(line: &RopeSlice, pos: Position, language_id: &str) ->
if let Some((signed_string, unsigned_string)) = convert_to_sign_unsign(tag, digit) { if let Some((signed_string, unsigned_string)) = convert_to_sign_unsign(tag, digit) {
let digit_title = LanguageString { let digit_title = LanguageString {
language: language_id.to_string(), language: language_id.to_string(),
value: format!("{}'{}{}", digit.replace("_", "").len(), tag, digit) value: format!("{}'{}{}", digit.len(), tag, digit)
}; };
let markdown = HoverContents::Array(vec![ let markdown = HoverContents::Array(vec![
MarkedString::LanguageString(digit_title), MarkedString::LanguageString(digit_title),
@ -198,7 +181,7 @@ fn make_macro_define_content(macro_define: &Define) -> String {
} }
} }
pub fn hover_macro(server: &LspServer, line: &RopeSlice, pos: Position, language_id: &str) -> Option<Hover> { pub fn hover_macro(server: &LSPServer, line: &RopeSlice, pos: Position, language_id: &str) -> Option<Hover> {
let macro_text_regex = Regex::new(r"[`_0-9a-zA-Z]").unwrap(); let macro_text_regex = Regex::new(r"[`_0-9a-zA-Z]").unwrap();
if let Some((macro_text, range)) = get_word_range_at_position(line, pos, macro_text_regex) { if let Some((macro_text, range)) = get_word_range_at_position(line, pos, macro_text_regex) {
if macro_text.starts_with("`") { if macro_text.starts_with("`") {
@ -222,7 +205,7 @@ pub fn hover_macro(server: &LspServer, line: &RopeSlice, pos: Position, language
fn goto_instantiation<'a>( fn goto_instantiation<'a>(
server: &LspServer, server: &LSPServer,
fast: &'a FastHdlparam, fast: &'a FastHdlparam,
token_name: &str, token_name: &str,
pos: &Position, pos: &Position,
@ -235,7 +218,7 @@ fn goto_instantiation<'a>(
// let in_scope = compare_pos(&range.start, pos) != 1 && compare_pos(pos, &range.end) != 1; // let in_scope = compare_pos(&range.start, pos) != 1 && compare_pos(pos, &range.end) != 1;
// info!("pos: {pos:?}, param_range: {range:?}, in_scope: {in_scope:?}"); // info!("pos: {pos:?}, param_range: {range:?}, in_scope: {in_scope:?}");
if param_range.contains(pos) { if param_range.contains(pos) {
let module = match server.db.hdl_param.find_module_by_name(&instance.inst_type) { let module = match server.srcs.hdl_param.find_module_by_name(&instance.inst_type) {
Some(module) => module, Some(module) => module,
None => return None None => return None
}; };
@ -244,25 +227,10 @@ fn goto_instantiation<'a>(
// info!("param_range: {param_range:#?}"); // info!("param_range: {param_range:#?}");
// info!("position param find belong module: {:?}", module); // info!("position param find belong module: {:?}", module);
let file_type = server.db.hdl_param.find_file_type_by_module_name(&instance.inst_type); for param in &module.params {
if file_type == "primitives" { if token_name == param.name {
let primitives_text = server.db.primitive_text.clone(); let hover = make_param_desc_hover(param, range, language_id);
let params_assignments = &module.instances.first().unwrap().intstparam_assignments; return Some(hover);
for assignment in params_assignments {
if assignment.parameter.clone().unwrap() == token_name {
let hover = make_primitives_param_desc_hover(
primitives_text, &instance.inst_type,
assignment, range, language_id
);
return Some(hover);
}
}
} else {
for param in &module.params {
if token_name == param.name {
let hover = make_param_desc_hover(&file_type, param, range, language_id);
return Some(hover);
}
} }
} }
return None; return None;
@ -271,7 +239,7 @@ fn goto_instantiation<'a>(
if let Some(port_range) = &instance.instports { if let Some(port_range) = &instance.instports {
if port_range.contains(pos) { if port_range.contains(pos) {
let module = match server.db.hdl_param.find_module_by_name(&instance.inst_type) { let module = match server.srcs.hdl_param.find_module_by_name(&instance.inst_type) {
Some(module) => module, Some(module) => module,
None => return None None => return None
}; };
@ -280,25 +248,10 @@ fn goto_instantiation<'a>(
// info!("port_range: {port_range:#?}"); // info!("port_range: {port_range:#?}");
// info!("position port find belong module: {:?}", module); // info!("position port find belong module: {:?}", module);
let file_type = server.db.hdl_param.find_file_type_by_module_name(&instance.inst_type); for port in &module.ports {
if file_type == "primitives" { if token_name == port.name {
let primitives_text = server.db.primitive_text.clone(); let hover = make_port_desc_hover(port, range, language_id);
let port_assignments = &module.instances.first().unwrap().intstport_assignments; return Some(hover);
for assignment in port_assignments {
if assignment.port.clone().unwrap() == token_name {
let hover = make_primitives_port_desc_hover(
primitives_text, &instance.inst_type,
assignment, range, language_id
);
return Some(hover);
}
}
} else {
for port in &module.ports {
if token_name == port.name {
let hover = make_port_desc_hover(&file_type, port, range, language_id);
return Some(hover);
}
} }
} }
return None; return None;
@ -313,7 +266,7 @@ fn goto_instantiation<'a>(
/// 计算 position 赋值的 port 或者 param /// 计算 position 赋值的 port 或者 param
/// 比如 .clk ( clk ) 中的 .clk /// 比如 .clk ( clk ) 中的 .clk
pub fn hover_position_port_param( pub fn hover_position_port_param(
server: &LspServer, server: &LSPServer,
line: &RopeSlice, line: &RopeSlice,
url: &Url, url: &Url,
pos: Position, pos: Position,
@ -324,7 +277,7 @@ pub fn hover_position_port_param(
if name.starts_with(".") { if name.starts_with(".") {
let name = &name[1..]; let name = &name[1..];
// 进入最近的 scope 寻找 // 进入最近的 scope 寻找
let fast_map = server.db.hdl_param.path_to_hdl_file.read().unwrap(); let fast_map = server.srcs.hdl_param.path_to_hdl_file.read().unwrap();
let path = PathBuf::from_str(url.path()).unwrap(); let path = PathBuf::from_str(url.path()).unwrap();
let path = to_escape_path(&path); let path = to_escape_path(&path);
let path_string = path.to_str().unwrap(); let path_string = path.to_str().unwrap();
@ -341,23 +294,26 @@ pub fn hover_position_port_param(
None None
} }
fn make_primitives_param_desc_hover( fn make_port_desc_hover(port: &crate::core::hdlparam::Port, range: &Range, language_id: &str) -> Hover {
primitives_text: Arc<PrimitiveText>, let mut port_desc_array = Vec::<String>::new();
primitives_name: &str, port_desc_array.push(port.dir_type.to_string());
inst_param: &crate::core::hdlparam::InstParameter, if port.net_type != "unknown" {
range: &Range, language_id: &str) -> Hover { port_desc_array.push(port.net_type.to_string());
}
let name = inst_param.parameter.clone().unwrap(); if port.signed != "unsigned" {
let text_map = primitives_text.name_to_text.read().unwrap(); port_desc_array.push("signed".to_string());
let comment = if let Some(text) = text_map.get(primitives_name) { }
primitives_text.get_comment(&name, text)
} else {
"".to_string()
};
if port.width != "1" {
port_desc_array.push(port.width.to_string());
}
port_desc_array.push(port.name.to_string());
let port_desc = port_desc_array.join(" ");
let language_string = LanguageString { let language_string = LanguageString {
language: language_id.to_string(), language: language_id.to_string(),
value: "parameter ".to_string() + &name + " " + &comment, value: port_desc
}; };
Hover { Hover {
contents: HoverContents::Scalar(MarkedString::LanguageString(language_string)), contents: HoverContents::Scalar(MarkedString::LanguageString(language_string)),
@ -365,74 +321,19 @@ fn make_primitives_param_desc_hover(
} }
} }
fn make_primitives_port_desc_hover( fn make_param_desc_hover(param: &crate::core::hdlparam::Parameter, range: &Range, language_id: &str) -> Hover {
primitives_text: Arc<PrimitiveText>, let mut param_desc_array = Vec::<String>::new();
primitives_name: &str, param_desc_array.push(format!("parameter {}", param.name));
inst_port: &crate::core::hdlparam::InstPort,
range: &Range, language_id: &str) -> Hover {
let name = inst_port.port.clone().unwrap();
let text_map = primitives_text.name_to_text.read().unwrap();
let comment = if let Some(text) = text_map.get(primitives_name) {
primitives_text.get_comment(&name, text)
} else {
"".to_string()
};
if param.init != "unknown" {
param_desc_array.push("=".to_string());
param_desc_array.push(param.init.to_string());
}
let param_desc = param_desc_array.join(" ");
let language_string = LanguageString { let language_string = LanguageString {
language: language_id.to_string(), language: language_id.to_string(),
value: "port ".to_string() + &name + " " + &comment, value: param_desc
};
Hover {
contents: HoverContents::Scalar(MarkedString::LanguageString(language_string)),
range: Some(range.clone())
}
}
fn make_port_desc_hover(file_type: &str, port: &crate::core::hdlparam::Port, range: &Range, language_id: &str) -> Hover {
info!("enter make_port_desc_hover, file_type: {}", file_type);
let (language, value) = match file_type {
"common" => {
(language_id.to_string(), port.to_vlog_description())
}
"ip" => {
("vhdl".to_string(), port.to_vhdl_description())
}
"primitives" => {
(language_id.to_string(), port.to_vlog_description())
}
_ => {
(language_id.to_string(), port.to_vlog_description())
}
};
let language_string = LanguageString { language, value };
Hover {
contents: HoverContents::Scalar(MarkedString::LanguageString(language_string)),
range: Some(range.clone())
}
}
fn make_param_desc_hover(file_type: &str, param: &crate::core::hdlparam::Parameter, range: &Range, language_id: &str) -> Hover {
let value = match file_type {
"common" => {
param.to_vlog_description()
}
"ip" => {
param.to_vhdl_description()
}
"primitives" => {
param.to_vlog_description()
}
_ => {
param.to_vlog_description()
}
};
let language_string = LanguageString {
language: language_id.to_string(),
value
}; };
Hover { Hover {
contents: HoverContents::Scalar(MarkedString::LanguageString(language_string)), contents: HoverContents::Scalar(MarkedString::LanguageString(language_string)),
@ -441,140 +342,39 @@ fn make_param_desc_hover(file_type: &str, param: &crate::core::hdlparam::Paramet
} }
pub fn hover_module_declaration( pub fn hover_module_declaration(
server: &LspServer, server: &LSPServer,
token_name: &str, token_name: &str,
#[allow(unused)]
language_id: &str language_id: &str
) -> Option<Hover> { ) -> Option<Hover> {
// info!("hover_module_declaration token: {:?}", token_name);
// let test = server.db.hdl_param.module_name_to_path.read().unwrap(); let module_info = {
// info!("module name to path: {:#?}", test); let search_result = || {
let hdl_param = server.db.hdl_param.clone(); if let Some(module) = server.srcs.hdl_param.find_module_by_name(token_name) {
if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(token_name) { let path_string = server.srcs.hdl_param.find_module_definition_path(&module.name).unwrap_or("unknown".to_string());
match file_type.as_str() { return Some((module, path_string));
"common" => { }
hover_common_module_declaration( None
server, };
token_name, search_result()
&module,
&def_path
)
},
"ip" => {
hover_ip_module_declaration(
server,
token_name,
&module,
&def_path
)
},
"primitives" => {
hover_primitives_module_declaration(
server,
token_name,
&module,
&def_path
)
},
_ => None
}
} else {
None
}
}
fn hover_common_module_declaration(
#[allow(unused)]
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]
module: &core::hdlparam::Module,
#[allow(unused)]
def_path: &str
) -> Option<Hover> {
let path_uri = Url::from_file_path(def_path.to_string()).unwrap().to_string();
let def_row = module.range.start.line;
let def_col = module.range.start.character;
let define_info = format!("Go to [Definition]({path_uri}#L{def_row}:{def_col})");
let port_num = module.ports.len();
let param_num = module.params.len();
let instance_num = module.instances.len();
let port_desc = format!("`port` {port_num}, `param` {param_num}, `instantiation` {instance_num}");
// 统计 dir
let mut input_count = 0 as u32;
let mut output_count = 0 as u32;
let mut inout_count = 0 as u32;
for port in &module.ports {
match port.dir_type.as_str() {
"input" => input_count += 1,
"output" => output_count += 1,
"inout" => inout_count += 1,
_ => {}
}
}
let io_desc = format!("`input` {input_count}, `output` {output_count}, `inout` {inout_count}");
let mut markdowns = Vec::<MarkedString>::new();
markdowns.push(MarkedString::String(port_desc));
markdowns.push(MarkedString::String(io_desc));
markdowns.push(MarkedString::String(define_info));
markdowns.push(MarkedString::String("---".to_string()));
let language_id = get_language_id_by_path_str(def_path);
let module_profile = make_module_profile_code(&module);
let profile_markdown = LanguageString {
language: language_id.to_string(),
value: module_profile
};
markdowns.push(MarkedString::LanguageString(profile_markdown));
let hover = Hover {
contents: HoverContents::Array(markdowns),
range: None
}; };
return Some(hover); if let Some((module, path_string)) = module_info {
} let path_uri = Url::from_file_path(path_string.to_string()).unwrap().to_string();
fn hover_ip_module_declaration(
#[allow(unused)]
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]
module: &core::hdlparam::Module,
#[allow(unused)]
def_path: &str
) -> Option<Hover> {
let def_path_buf = PathBuf::from_str(def_path).unwrap();
if def_path_buf.exists() {
// TODO: 当前工具链只支持 Xilinx 下的工具链,所以此处的代码是 vhdl 的代码
// 如果未来需要支持其他的工具链,则需要从 server 下读取对应的变量
let path_uri = Url::from_file_path(def_path.to_string()).unwrap().to_string();
let def_row = module.range.start.line; let def_row = module.range.start.line;
let def_col = module.range.start.character; let def_col = module.range.start.character;
let define_info = format!("Go to [Definition]({path_uri}#L{def_row}:{def_col})"); let define_info = format!("Go to [Definition]({path_uri}#L{def_row}:{def_col})");
let port_num = module.ports.len(); let port_num = module.ports.len();
let param_num = module.params.len(); let param_num = module.params.len();
let instance_num = module.instances.len(); let instance_num = module.instances.len();
let port_desc = format!("`port` {port_num}, `generic` {param_num}, `instantiation` {instance_num}"); let port_desc = format!("`port` {port_num}, `param` {param_num}, `instantiation` {instance_num}");
// 统计 dir // 统计 dir
let mut input_count = 0 as u32; let mut input_count = 0 as u32;
let mut output_count = 0 as u32; let mut output_count = 0 as u32;
let mut inout_count = 0 as u32; let mut inout_count = 0 as u32;
for port in &module.ports { for port in &module.ports {
match port.dir_type.as_str() { match port.dir_type.as_str() {
"input" => input_count += 1, "input" => input_count += 1,
@ -583,72 +383,31 @@ fn hover_ip_module_declaration(
_ => {} _ => {}
} }
} }
let io_desc = format!("`input` {input_count}, `output` {output_count}, `inout` {inout_count}"); let io_desc = format!("`input` {input_count}, `output` {output_count}, `inout` {inout_count}");
let mut markdowns = Vec::<MarkedString>::new(); let mut markdowns = Vec::<MarkedString>::new();
markdowns.push(MarkedString::String(port_desc)); markdowns.push(MarkedString::String(port_desc));
markdowns.push(MarkedString::String(io_desc)); markdowns.push(MarkedString::String(io_desc));
markdowns.push(MarkedString::String(define_info)); markdowns.push(MarkedString::String(define_info));
markdowns.push(MarkedString::String("---".to_string())); markdowns.push(MarkedString::String("---".to_string()));
let language_id = get_language_id_by_path_str(def_path); let module_profile = make_module_profile_code(&module);
let module_profile = make_entity_profile_code(&module);
let profile_markdown = LanguageString { let profile_markdown = LanguageString {
language: language_id.to_string(), language: language_id.to_string(),
value: module_profile value: module_profile
}; };
markdowns.push(MarkedString::LanguageString(profile_markdown)); markdowns.push(MarkedString::LanguageString(profile_markdown));
let hover = Hover {
contents: HoverContents::Array(markdowns),
range: None
};
Some(hover)
} else {
None
}
}
fn hover_primitives_module_declaration(
#[allow(unused)]
server: &LspServer,
#[allow(unused)]
token_name: &str,
#[allow(unused)]
module: &core::hdlparam::Module,
#[allow(unused)]
def_path: &str
) -> Option<Hover> {
let primitive_map = server.db.primitive_text.name_to_text.read().unwrap();
if let Some(text) = primitive_map.get(token_name) {
let mut markdowns = Vec::<MarkedString>::new();
let mut lines: Vec<&str> = text.split_inclusive('\n').collect();
if lines.len() > 1 {
lines.remove(0);
lines.pop();
}
let profile_markdown = LanguageString {
language: "systemverilog".to_string(),
value: lines.join("")
};
markdowns.push(MarkedString::LanguageString(profile_markdown));
let hover = Hover { let hover = Hover {
contents: HoverContents::Array(markdowns), contents: HoverContents::Array(markdowns),
range: None range: None
}; };
Some(hover) return Some(hover);
} else {
None
} }
None
} }
@ -749,136 +508,11 @@ pub fn make_module_profile_code(module: &crate::core::hdlparam::Module) -> Strin
codes.push(format!("\t{port_desc},")); codes.push(format!("\t{port_desc},"));
} }
} }
codes.push(format!(")"));
} else {
codes.push(format!(")"));
} }
codes.push(format!(");"));
let profile_string = codes.join("\n"); let profile_string = codes.join("\n");
profile_string profile_string
} }
pub fn make_vhdl_module_profile_code(module: &crate::core::hdlparam::Module) -> String {
let mut snippet_codes = Vec::<String>::new();
snippet_codes.push(format!("u_{} : {}\n", module.name, module.name));
// 2001 style先计算出 generic 和 port然后加入总体例化样板中
let params_length = module.params.len();
let ports_length = module.ports.len();
if params_length > 0 {
snippet_codes.push("generic map(\n".to_string());
let max_param_name = module.params.iter().map(|param| param.name.len()).max().unwrap_or(0);
let mut i: usize = 0;
for generic in &module.params {
let n_padding = " ".repeat(max_param_name - generic.name.len() + 1);
snippet_codes.push(format!("\t{}{} => {}", generic.name, n_padding, generic.init));
if i < params_length - 1 {
snippet_codes.push(",\n".to_string());
}
i += 1;
}
snippet_codes.push(")\n".to_string());
}
if ports_length > 0 {
snippet_codes.push("port map(\n\t-- ports\n".to_string());
let max_port_name = module.ports.iter().map(|port| port.name.len()).max().unwrap_or(0);
let mut i: usize = 0;
for port in &module.ports {
let n_padding = " ".repeat(max_port_name - port.name.len() + 1);
snippet_codes.push(format!("\t{}{} => {}", port.name, n_padding, port.name));
if i < ports_length - 1 {
snippet_codes.push(",\n".to_string());
}
i += 1;
}
snippet_codes.push(");\n".to_string());
}
snippet_codes.join("")
}
/// vhdl 的 entity 的 profile
pub fn make_entity_profile_code(module: &crate::core::hdlparam::Module) -> String {
let mut codes = Vec::<String>::new();
// param 其实就是 generic
let param_num = module.params.len();
let port_num = module.ports.len();
codes.push(format!("entity {} is", module.name));
// 缩进字符
if module.params.len() > 0 {
codes.push("\tgeneric (".to_string());
let max_param_name_length = module.params.iter().map(|param| param.name.len()).max().unwrap_or(0);
let max_param_type_length = module.params.iter().map(|param| param.net_type.len()).max().unwrap_or(0);
for (i, param) in module.params.iter().enumerate() {
let mut param_desc_array = Vec::<String>::new();
let param_name_align_spaces = " ".repeat(max_param_name_length - param.name.len() + 1);
param_desc_array.push(format!("{}{}:", param.name, param_name_align_spaces));
let param_type_align_spaces = " ".repeat(max_param_type_length - param.net_type.len() + 1);
param_desc_array.push(format!("{}{}", param.net_type, param_type_align_spaces));
if param.init != "unknown" {
param_desc_array.push(format!(":= {}", param.init.to_string()));
}
let param_desc = param_desc_array.join(" ");
if i == param_num - 1 {
codes.push(format!("\t\t{param_desc}"));
} else {
codes.push(format!("\t\t{param_desc};"));
}
}
codes.push("\t);".to_string());
}
if module.ports.len() > 0 {
codes.push("\tport (".to_string());
let max_port_length = module.ports.iter().map(|port| port.name.len()).max().unwrap_or(0);
// let max_width_length = module.ports.iter().map(|port| width_mapper(&port.width)).max().unwrap_or(0);
let max_dir_length = module.ports.iter().map(|port| port.dir_type.len()).max().unwrap_or(0);
for (i, port) in module.ports.iter().enumerate() {
let mut port_desc_array = Vec::<String>::new();
// port 的名字
let port_name_align_spaces = " ".repeat(max_port_length - port.name.len() + 1);
port_desc_array.push(format!("{}{}: ", port.name, port_name_align_spaces));
// in, out, inout
let port_dir_align_spaces = " ".repeat(max_dir_length - port.dir_type.len() + 1);
port_desc_array.push(format!("{}{}", port.dir_type, port_dir_align_spaces));
// std_logic, signed, unsigned 等等
if port.net_type != "unknown" {
port_desc_array.push(port.net_type.to_lowercase());
}
// (57 downto 0)
if port.width != "1" {
// 内部的 width 统一表示成 [57:0] 这样的格式,需要转换一下
let width_string = port.width.replace("[", "(").replace("]", ")").replace(":", " downto ");
port_desc_array.push(width_string);
}
let port_desc = port_desc_array.join("");
if i == port_num - 1 {
codes.push(format!("\t\t{port_desc}"));
} else {
codes.push(format!("\t\t{port_desc};"));
}
}
codes.push("\t);".to_string());
}
codes.push(format!("end entity {};", module.name));
let profile_string = codes.join("\n");
profile_string
}

View File

@ -1,4 +1,4 @@
use crate::server::LspServer; use crate::server::LSPServer;
use crate::utils::*; use crate::utils::*;
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
@ -9,7 +9,7 @@ pub mod feature;
mod sv; mod sv;
mod vhdl; mod vhdl;
impl LspServer { impl LSPServer {
pub fn hover(&self, params: HoverParams) -> Option<Hover> { pub fn hover(&self, params: HoverParams) -> Option<Hover> {
let language_id = get_language_id_by_uri(&params.text_document_position_params.text_document.uri); let language_id = get_language_id_by_uri(&params.text_document_position_params.text_document.uri);
match language_id.as_str() { match language_id.as_str() {

View File

@ -3,31 +3,28 @@ use log::info;
use regex::Regex; use regex::Regex;
use ropey::Rope; use ropey::Rope;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use crate::{core::{hdlparam::{Instance, Module}, scope_tree::common::Scope}, hover::{to_escape_path, BracketMatchResult, BracketMatcher}, server::LspServer, sources::LSPSupport}; use crate::{core::hdlparam::{Instance, Module}, hover::{to_escape_path, BracketMatchResult, BracketMatcher}, server::LSPServer, sources::LSPSupport};
use super::{feature::*, from_uri_to_escape_path_string}; use super::feature::*;
use std::{path::PathBuf, str::FromStr, sync::RwLockReadGuard}; use std::{path::PathBuf, str::FromStr, sync::RwLockReadGuard};
use crate::core::scope_tree::common::*; use crate::definition::*;
use super::{get_definition_token, get_language_id_by_uri}; use super::{get_definition_token, get_language_id_by_uri};
pub fn hover(server: &LspServer, params: &HoverParams) -> Option<Hover> { pub fn hover(server: &LSPServer, params: &HoverParams) -> Option<Hover> {
let uri = &params.text_document_position_params.text_document.uri; let doc = &params.text_document_position_params.text_document.uri;
let pos: Position = params.text_document_position_params.position; let pos: Position = params.text_document_position_params.position;
let file_id: usize = server.srcs.get_id(doc).to_owned();
let path_string = from_uri_to_escape_path_string(uri).unwrap(); server.srcs.wait_parse_ready(file_id, false);
let file: std::sync::Arc<std::sync::RwLock<crate::sources::Source>> = server.srcs.get_file(file_id)?;
// 等待解析完成 let file: std::sync::RwLockReadGuard<'_, crate::sources::Source> = file.read().ok()?;
server.db.wait_parse_ready(&path_string, false);
let source = server.db.get_source(&path_string)?;
let source = source.read().ok()?;
let line_text = source.text.line(pos.line as usize); let line_text = file.text.line(pos.line as usize);
let token: String = get_definition_token(&line_text, pos); let token: String = get_definition_token(&line_text, pos);
let language_id = get_language_id_by_uri(uri); let language_id = get_language_id_by_uri(doc);
// match `include // match `include
if let Some(hover) = hover_include(uri, &line_text, pos, &language_id) { if let Some(hover) = hover_include(doc, &line_text, pos, &language_id) {
return Some(hover); return Some(hover);
} }
@ -38,14 +35,16 @@ pub fn hover(server: &LspServer, params: &HoverParams) -> Option<Hover> {
// info!("enter hover_position_port_param"); // info!("enter hover_position_port_param");
// match positional port param // match positional port param
if let Some(hover) = hover_position_port_param(server, &line_text, uri, pos, &language_id) { if let Some(hover) = hover_position_port_param(server, &line_text, doc, pos, &language_id) {
return Some(hover); return Some(hover);
} }
// info!("enter hover_module_declaration"); // info!("enter hover_module_declaration");
// match module name // match module name
if hover_for_module(server, pos, uri) { if let Some(hover) = hover_module_declaration(server, &token, &language_id) {
if let Some(hover) = hover_module_declaration(server, &token, &language_id) { // info!("[LSPServer] in hover: get module hover");
if hover_for_module(server, pos, doc) {
// info!("[LSPServer] in hover: it is instance");
return Some(hover); return Some(hover);
} }
} }
@ -55,14 +54,14 @@ pub fn hover(server: &LspServer, params: &HoverParams) -> Option<Hover> {
return Some(hover); return Some(hover);
} }
let scope_tree = server.db.scope_tree.read().ok()?; let scope_tree = server.srcs.scope_tree.read().ok()?;
let global_scope = scope_tree.as_ref().unwrap(); let global_scope = scope_tree.as_ref().unwrap();
let symbol_definition: GenericDec = global_scope let symbol_definition: GenericDec = global_scope
.get_definition(&token, source.text.pos_to_byte(&pos), uri)?; .get_definition(&token, file.text.pos_to_byte(&pos), doc)?;
// match 正常 symbol // match 正常 symbol
if let Some(hover) = hover_common_symbol(server, &token, &symbol_definition, &source, uri, pos, &language_id) { if let Some(hover) = hover_common_symbol(server, &token, &symbol_definition, &file, doc, pos, &language_id) {
return Some(hover); return Some(hover);
} }
@ -101,7 +100,7 @@ fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_c
let currentr = current.clone().trim_end().to_owned(); let currentr = current.clone().trim_end().to_owned();
if currentl.starts_with("/*") && currentr.ends_with("*/") { if currentl.starts_with("/*") && currentr.ends_with("*/") {
valid = true; valid = true;
} else if currentr.ends_with("*/") && line_idx != line - 1 { } else if currentr.ends_with("*/") {
multiline = true; multiline = true;
valid = true; valid = true;
} else if currentl.starts_with("/*") { } else if currentl.starts_with("/*") {
@ -172,12 +171,11 @@ fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_c
if line_no == hover.len() - 1 { if line_no == hover.len() - 1 {
// 最后一个为定义所在的一行 // 最后一个为定义所在的一行
if let Some((code, comment)) = line_comment_extractor(&line_text) { if let Some((code, comment)) = line_comment_extractor(&line_text) {
comment_markdowns.push(MarkedString::String(comment.trim().to_string()));
comment_markdowns.push(MarkedString::LanguageString(LanguageString { comment_markdowns.push(MarkedString::LanguageString(LanguageString {
language: language_id.to_string(), language: language_id.to_string(),
value: code value: code
})); }));
// comment_markdowns.insert(0, MarkedString::String(comment.trim().to_string())); comment_markdowns.insert(0, MarkedString::String(comment.trim().to_string()));
} else { } else {
// 这行只有代码,没有注释 // 这行只有代码,没有注释
comment_markdowns.push(MarkedString::LanguageString(LanguageString { comment_markdowns.push(MarkedString::LanguageString(LanguageString {
@ -227,7 +225,7 @@ fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_c
/// 计算正常 symbol 的 hover /// 计算正常 symbol 的 hover
fn hover_common_symbol( fn hover_common_symbol(
#[allow(unused)] #[allow(unused)]
server: &LspServer, server: &LSPServer,
#[allow(unused)] #[allow(unused)]
token: &String, token: &String,
symbol_definition: &GenericDec, symbol_definition: &GenericDec,
@ -240,7 +238,7 @@ fn hover_common_symbol(
// 根据 symbol 的类别进行额外的判断 // 根据 symbol 的类别进行额外的判断
match symbol_definition.def_type { match symbol_definition.def_type {
DefinitionType::ModuleInstantiation => { DefinitionType::ModuleInstantiation => {
let hdlparam = server.db.hdl_param.clone(); let hdlparam = server.srcs.hdl_param.clone();
let pathbuf = PathBuf::from_str(doc.path()).unwrap(); let pathbuf = PathBuf::from_str(doc.path()).unwrap();
let pathbuf = to_escape_path(&pathbuf); let pathbuf = to_escape_path(&pathbuf);
let path_string = pathbuf.to_str().unwrap().replace("\\", "/"); let path_string = pathbuf.to_str().unwrap().replace("\\", "/");
@ -297,11 +295,11 @@ fn hover_common_symbol(
make_hover_with_comment(&file.text, def_line, &language_id, false) make_hover_with_comment(&file.text, def_line, &language_id, false)
} }
fn hover_for_module(server: &LspServer, pos: Position, doc: &Url) -> bool { fn hover_for_module(server: &LSPServer, pos: Position, doc: &Url) -> bool {
let pathbuf = PathBuf::from_str(doc.path()).unwrap(); let pathbuf = PathBuf::from_str(doc.path()).unwrap();
let pathbuf = to_escape_path(&pathbuf); let pathbuf = to_escape_path(&pathbuf);
let path_string = pathbuf.to_str().unwrap().replace("\\", "/"); let path_string = pathbuf.to_str().unwrap().replace("\\", "/");
let hdlparam = server.db.hdl_param.clone(); let hdlparam = server.srcs.hdl_param.clone();
let find_instance_range = |_: &Module, instance: &Instance| { let find_instance_range = |_: &Module, instance: &Instance| {
// info!("instance start pos: {:#?}", instance.range.start); // info!("instance start pos: {:#?}", instance.range.start);
@ -312,13 +310,13 @@ fn hover_for_module(server: &LspServer, pos: Position, doc: &Url) -> bool {
}; };
if let Some(_) = hdlparam.walk_module(&path_string, find_module_range) { if let Some(_) = hdlparam.walk_module(&path_string, find_module_range) {
// info!("[LspServer] in hover: it is module"); // info!("[LSPServer] in hover: it is module");
true true
} else if let Some(_) = hdlparam.walk_instantiation(&path_string, find_instance_range) { } else if let Some(_) = hdlparam.walk_instantiation(&path_string, find_instance_range) {
// info!("[LspServer] in hover: it is instance"); // info!("[LSPServer] in hover: it is instance");
true true
} else { } else {
// info!("[LspServer] in hover: it is not instance"); // info!("[LSPServer] in hover: it is not instance");
false false
} }
} }

View File

@ -4,47 +4,21 @@ use log::info;
use regex::Regex; use regex::Regex;
use ropey::Rope; use ropey::Rope;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use crate::{core::hdlparam::{Instance, Module}, hover::{BracketMatchResult, BracketMatcher}, server::LspServer}; use crate::{core::hdlparam::{Instance, Module}, definition::{Definition, DefinitionType, GenericDec, Scope}, hover::{BracketMatchResult, BracketMatcher}, server::LSPServer, sources::LSPSupport};
use crate::core::scope_tree::common::*;
use super::{from_lsp_pos, from_uri_to_escape_path_string, get_definition_token, get_language_id_by_uri, to_escape_path}; use super::{feature::hover_format_digit, get_definition_token, get_language_id_by_uri, to_escape_path};
pub fn hover(server: &LspServer, params: &HoverParams) -> Option<Hover> { pub fn hover(server: &LSPServer, params: &HoverParams) -> Option<Hover> {
let uri = &params.text_document_position_params.text_document.uri; let doc = &params.text_document_position_params.text_document.uri;
let pos: Position = params.text_document_position_params.position; let pos: Position = params.text_document_position_params.position;
let file_id: usize = server.srcs.get_id(doc).to_owned();
let path_string = from_uri_to_escape_path_string(uri).unwrap(); server.srcs.wait_parse_ready(file_id, false);
let file: std::sync::Arc<std::sync::RwLock<crate::sources::Source>> = server.srcs.get_file(file_id)?;
// 等待解析完成 let file: std::sync::RwLockReadGuard<'_, crate::sources::Source> = file.read().ok()?;
server.db.wait_parse_ready(&path_string, false);
let source = server.db.get_source(&path_string)?;
let source = source.read().ok()?;
let line_text = source.text.line(pos.line as usize); let line_text = file.text.line(pos.line as usize);
let token: String = get_definition_token(&line_text, pos);
let project = server.db.vhdl_project.read().ok()?; let language_id = get_language_id_by_uri(doc);
let global_project = project.as_ref().unwrap();
let path = match PathBuf::from_str(uri.path()) {
Ok(path) => path,
Err(error) => {
info!("error happen in vhdl <hover>: {:?}", error);
return None;
}
};
let escape_path = to_escape_path(&path);
let source = global_project.project.get_source(&escape_path)?;
let ent = global_project.project.find_declaration(&source, from_lsp_pos(pos))?;
let value = global_project.project.format_declaration(ent)?;
Some(Hover {
contents: HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value: format!("```vhdl\n{value}\n```"),
}),
range: None,
})
// // match `include // // match `include
// if let Some(hover) = hover_include(doc, &line_text, pos, &language_id) { // if let Some(hover) = hover_include(doc, &line_text, pos, &language_id) {
@ -56,11 +30,11 @@ pub fn hover(server: &LspServer, params: &HoverParams) -> Option<Hover> {
// return Some(hover); // return Some(hover);
// } // }
// let scope_tree = server.db.scope_tree.read().ok()?; let scope_tree = server.srcs.scope_tree.read().ok()?;
// let global_scope = scope_tree.as_ref().unwrap(); let global_scope = scope_tree.as_ref().unwrap();
// let symbol_definition: GenericDec = global_scope let symbol_definition: GenericDec = global_scope
// .get_definition(&token, file.text.pos_to_byte(&pos), doc)?; .get_definition(&token, file.text.pos_to_byte(&pos), doc)?;
// // match positional port param // // match positional port param
// if let Some(hover) = hover_position_port_param(server, &line_text, doc, pos, &language_id) { // if let Some(hover) = hover_position_port_param(server, &line_text, doc, pos, &language_id) {
@ -73,16 +47,16 @@ pub fn hover(server: &LspServer, params: &HoverParams) -> Option<Hover> {
// } // }
// match 正常 symbol // match 正常 symbol
// if let Some(hover) = hover_common_symbol(server, &token, &symbol_definition, &file, doc, pos, &language_id) { if let Some(hover) = hover_common_symbol(server, &token, &symbol_definition, &file, doc, pos, &language_id) {
// return Some(hover); return Some(hover);
// } }
// // match digit 5'b00110 // match digit 5'b00110
// if let Some(hover) = hover_format_digit(&line_text, pos, &language_id) { if let Some(hover) = hover_format_digit(&line_text, pos, &language_id) {
// return Some(hover); return Some(hover);
// } }
// None None
} }
fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_code: bool) -> Option<Hover> { fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_code: bool) -> Option<Hover> {
@ -241,7 +215,7 @@ fn make_hover_with_comment(doc: &Rope, line: usize, language_id: &str, exclude_c
fn hover_common_symbol( fn hover_common_symbol(
#[allow(unused)] #[allow(unused)]
server: &LspServer, server: &LSPServer,
#[allow(unused)] #[allow(unused)]
token: &String, token: &String,
symbol_definition: &GenericDec, symbol_definition: &GenericDec,
@ -254,7 +228,7 @@ fn hover_common_symbol(
// 根据 symbol 的类别进行额外的判断 // 根据 symbol 的类别进行额外的判断
match symbol_definition.def_type { match symbol_definition.def_type {
DefinitionType::ModuleInstantiation => { DefinitionType::ModuleInstantiation => {
let hdlparam = server.db.hdl_param.clone(); let hdlparam = server.srcs.hdl_param.clone();
let pathbuf = PathBuf::from_str(doc.path()).unwrap(); let pathbuf = PathBuf::from_str(doc.path()).unwrap();
let pathbuf = to_escape_path(&pathbuf); let pathbuf = to_escape_path(&pathbuf);
let path_string = pathbuf.to_str().unwrap().replace("\\", "/"); let path_string = pathbuf.to_str().unwrap().replace("\\", "/");

View File

@ -1,4 +1,4 @@
use crate::server::LspServer; use crate::server::LSPServer;
use crate::utils::*; use crate::utils::*;
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
@ -7,7 +7,7 @@ use tower_lsp::lsp_types::*;
mod sv; mod sv;
mod vhdl; mod vhdl;
impl LspServer { impl LSPServer {
pub fn inlay_hint(&self, params: InlayHintParams) -> Option<Vec<InlayHint>> { pub fn inlay_hint(&self, params: InlayHintParams) -> Option<Vec<InlayHint>> {
let language_id = get_language_id_by_uri(&params.text_document.uri); let language_id = get_language_id_by_uri(&params.text_document.uri);
match language_id.as_str() { match language_id.as_str() {

View File

@ -1,52 +1,40 @@
use std::{collections::HashMap, path::PathBuf, str::FromStr}; use std::{path::PathBuf, str::FromStr};
use crate::{core, server::LspServer}; use crate::{core, server::LSPServer};
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
use ropey::Rope;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use super::{get_language_id_by_path_str, to_escape_path}; use super::to_escape_path;
pub fn inlay_hint(server: &LspServer, params: &InlayHintParams) -> Option<Vec<InlayHint>> { pub fn inlay_hint(server: &LSPServer, params: &InlayHintParams) -> Option<Vec<InlayHint>> {
let uri = &params.text_document.uri; let uri = &params.text_document.uri;
let path = PathBuf::from_str(uri.path()).unwrap(); let path = PathBuf::from_str(uri.path()).unwrap();
let path = to_escape_path(&path); let path = to_escape_path(&path);
let path_string = path.to_str().unwrap(); let path_string = path.to_str().unwrap();
let visible_range = core::hdlparam::Range::from_lsp_range(&params.range); let visible_range = core::hdlparam::Range::from_lsp_range(&params.range);
// 等待解析完成 info!("enter hints");
server.db.wait_parse_ready(&path_string, false);
let source = server.db.get_source(&path_string)?;
let source = source.read().ok()?;
let rope = &source.text; let fast_map = server.srcs.hdl_param.path_to_hdl_file.read().unwrap();
// 先找到 当前 所在的 hdlfile // 先找到 当前 所在的 hdlfile
let path_to_hdl_file = server.db.hdl_param.path_to_hdl_file.read().unwrap(); if let Some(hdl_file) = &fast_map.get(path_string) {
if let Some(hdl_file) = &path_to_hdl_file.get(path_string) { info!("enter some");
let fast = &hdl_file.fast; let fast = &hdl_file.fast;
let mut hints = Vec::<InlayHint>::new(); let mut hints = Vec::<InlayHint>::new();
// 制作例化模块的 hint // 制作例化模块的 hint
for module in &fast.content { for module in &fast.content {
for instance in &module.instances { for instance in &module.instances {
// 根据在可见视图外面的 range 就不管了 // 根据在可见视图外面的 range 就不管了
if let Some(range) = &instance.instparams { if is_visible_range(&instance.instparams, &visible_range) {
if is_visible_range(&range, &visible_range) { hints.extend(make_instparam_hints(params, instance));
hints.extend(make_instparam_hints(server, params, instance, rope));
}
} }
if let Some(range) = &instance.instports {
if is_visible_range(range, &visible_range) { if is_visible_range(&instance.instports, &visible_range) {
hints.extend(make_instport_hints(server, params, instance, rope)); hints.extend(make_instport_hints(params, instance));
}
} }
} }
// 在 endmodule 后面添加 module xxx
if is_visible_range(&module.range, &visible_range) {
hints.extend(make_endmodule_hints(module));
}
} }
return Some(hints); return Some(hints);
@ -56,57 +44,21 @@ pub fn inlay_hint(server: &LspServer, params: &InlayHintParams) -> Option<Vec<In
} }
fn is_visible_range( fn is_visible_range(
target_range: &core::hdlparam::Range, target_range: &Option<core::hdlparam::Range>,
visible_range: &core::hdlparam::Range visible_range: &core::hdlparam::Range
) -> bool { ) -> bool {
if target_range.before(visible_range) || target_range.after(visible_range) { if let Some(target_range) = target_range {
false if target_range.before(visible_range) || target_range.after(visible_range) {
} else { return false;
true }
return true;
} }
} false
fn make_endmodule_hints(
module: &core::hdlparam::Module
) -> Vec<InlayHint> {
let mut hints = Vec::<InlayHint>::new();
let end_pos = &module.range.end;
let start_pos = &module.range.start;
if end_pos.character == start_pos.character && end_pos.line == start_pos.line {
// 说明解析器没有找到 endmodule
return hints;
}
let module_desc = MarkupContent {
kind: MarkupKind::Markdown,
value: format!("module {}", module.name)
};
let mut pos = end_pos.to_lsp_position();
pos.character += 1;
let hint = InlayHint {
position: pos,
label: InlayHintLabel::String(format!("module {}", module.name)),
padding_left: Some(true),
padding_right: Some(true),
kind: Some(InlayHintKind::PARAMETER),
text_edits: None,
tooltip: Some(InlayHintTooltip::MarkupContent(module_desc)),
data: None
};
hints.push(hint);
hints
} }
fn make_instparam_hints( fn make_instparam_hints(
server: &LspServer,
params: &InlayHintParams, params: &InlayHintParams,
instance: &core::hdlparam::Instance, instance: &core::hdlparam::Instance
rope: &Rope
) -> Vec<InlayHint> { ) -> Vec<InlayHint> {
let mut hints = Vec::<InlayHint>::new(); let mut hints = Vec::<InlayHint>::new();
@ -114,124 +66,44 @@ fn make_instparam_hints(
hints hints
} }
pub fn position_to_index(rope: &Rope, position: Position) -> usize {
let line_start = rope.line_to_char(position.line as usize);
let char_index = line_start + position.character as usize;
char_index
}
pub fn index_to_position(slice: &Rope, char_index: usize) -> Position {
let line = slice.char_to_line(char_index);
let line_start = slice.line_to_char(line);
let character = char_index - line_start;
Position {
line: line as u32,
character: character as u32,
}
}
fn find_instport_inlay_hints_position(
rope: &Rope,
range: &Range
) -> Option<Position> {
// let start = rope.pos_to_byte(&range.start);
// let end = rope.pos_to_byte(&range.end);
let start_offset = position_to_index(rope, range.start);
let end_offset = position_to_index(rope, range.end);
let instport_text = rope.slice(start_offset .. end_offset);
let instport_text = instport_text.to_string();
if let Some(offset) = instport_text.find("(") {
let target_offset = start_offset + offset + 1;
let target_position = index_to_position(rope, target_offset);
return Some(target_position);
}
None
}
fn make_instport_hints( fn make_instport_hints(
server: &LspServer,
#[allow(unused)]
params: &InlayHintParams, params: &InlayHintParams,
instance: &core::hdlparam::Instance, instance: &core::hdlparam::Instance
rope: &Rope
) -> Vec<InlayHint> { ) -> Vec<InlayHint> {
let hdl_param = server.db.hdl_param.clone();
let mut hints = Vec::<InlayHint>::new(); let mut hints = Vec::<InlayHint>::new();
let module_context = hdl_param.find_module_context_by_name(&instance.inst_type);
if module_context.is_none() {
return hints;
}
let (define_module, file_type, def_path) = module_context.unwrap();
// 制作 port name 到 port 的映射
let mut port_map = HashMap::<String, &core::hdlparam::Port>::new();
for port in &define_module.ports {
port_map.insert(port.name.to_string(), port);
}
for port_assigment in &instance.intstport_assignments { for port_assigment in &instance.intstport_assignments {
let port_name = port_assigment.port.clone().unwrap_or("".to_string()); let port_desc = MarkupContent {
let port = port_map.get(&port_name); kind: MarkupKind::Markdown,
if port.is_none() { value: format!("```verilog\n{:?}\n```", port_assigment.port)
continue; };
}
let port_info = port.unwrap();
let instport_range = port_assigment.range.to_lsp_range();
if let Some(hint_position) = find_instport_inlay_hints_position(rope, &instport_range) {
let label = { let hint = InlayHint {
let dir_type = port_info.dir_type.to_string(); position: port_assigment.range.to_lsp_range().start,
label: InlayHintLabel::String("start".to_string()),
padding_left: Some(true),
padding_right: Some(true),
kind: Some(InlayHintKind::PARAMETER),
text_edits: None,
tooltip: Some(InlayHintTooltip::MarkupContent(port_desc)),
data: None
};
hints.push(hint);
// vhdl 转成 verilog let port_desc = MarkupContent {
let dir_type = match dir_type.as_str() { kind: MarkupKind::Markdown,
"out" => "output", value: format!("```verilog\n{:?}\n```", port_assigment.port)
"in" => "input", };
_ => &dir_type let hint = InlayHint {
}; position: port_assigment.range.to_lsp_range().end,
label: InlayHintLabel::String("end".to_string()),
if dir_type == "output" { padding_left: Some(true),
format!("{}", dir_type) padding_right: Some(true),
} else { kind: Some(InlayHintKind::PARAMETER),
format!("{}{}", dir_type, " ".repeat(1)) text_edits: None,
} tooltip: Some(InlayHintTooltip::MarkupContent(port_desc)),
}; data: None
};
let language_id = get_language_id_by_path_str(&def_path); hints.push(hint);
let port_desc_value = match file_type.as_str() {
"common" => {
format!("```{}\n{}\n```", language_id, port_info.to_vlog_description())
}
"ip" => {
// TODO: 支持更多的 IP
format!("```verilog\n{}\n```", port_info.to_vhdl_description())
}
"primitives" => {
format!("```{}\n{}\n```", language_id, port_info.to_vlog_description())
}
_ => {
format!("```{}\n{}\n```", language_id, port_info.to_vlog_description())
}
};
let port_desc = MarkupContent {
kind: MarkupKind::Markdown,
value: port_desc_value
};
let hint = InlayHint {
position: hint_position,
label: InlayHintLabel::String(label),
padding_left: Some(true),
padding_right: Some(true),
kind: Some(InlayHintKind::PARAMETER),
text_edits: None,
tooltip: Some(InlayHintTooltip::MarkupContent(port_desc)),
data: None
};
hints.push(hint);
}
} }
hints hints

View File

@ -1,8 +1,8 @@
use crate::server::LspServer; use crate::server::LSPServer;
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
pub fn inlay_hint(server: &LspServer, params: &InlayHintParams) -> Option<Vec<InlayHint>> { pub fn inlay_hint(server: &LSPServer, params: &InlayHintParams) -> Option<Vec<InlayHint>> {
None None
} }

View File

@ -21,33 +21,23 @@ pub mod document_highlight;
// 内部提示 // 内部提示
pub mod inlay_hint; pub mod inlay_hint;
// code lens 按钮
pub mod code_lens;
// 诊断 // 诊断
pub mod diagnostics; pub mod diagnostics;
// 格式化 // 格式化
pub mod format; pub mod format;
// 格式化
pub mod codedoc;
// 基础工具 // 基础工具
pub mod utils; pub mod utils;
// LSP 服务器 // LSP 服务器
pub mod server; pub mod server;
// 管理所有代码 // 管理所有代码
pub mod sources; pub mod sources;
// 自定义发送请求 // 自定义发送请求
pub mod request; pub mod request;
// 自定义异步命令
pub mod execute_command;
// 测试模块 // 测试模块
pub mod test; pub mod test;

View File

@ -1,14 +1,6 @@
#![recursion_limit = "256"] #![recursion_limit = "256"]
use request::{ use request::{ CustomParamRequest, CustomRequest, DoFastApi, UpdateFastApi };
test::CustomParamRequest,
test::CustomRequest,
fast::DoFastApi,
fast::SyncFastApi,
config::UpdateConfigurationApi,
primitives::DoPrimitivesJudgeApi,
linter::LinterStatusApi
};
use log::info; use log::info;
use std::sync::Arc; use std::sync::Arc;
@ -22,15 +14,12 @@ mod hover;
mod document_symbol; mod document_symbol;
mod document_highlight; mod document_highlight;
mod inlay_hint; mod inlay_hint;
mod code_lens;
mod utils; mod utils;
mod codedoc;
mod diagnostics; mod diagnostics;
mod format; mod format;
mod server; mod server;
mod sources; mod sources;
mod request; mod request;
mod execute_command;
use server::Backend; use server::Backend;
@ -54,13 +43,10 @@ async fn main() {
let backend = Arc::new(Backend::new(client, log_handle)); let backend = Arc::new(Backend::new(client, log_handle));
backend backend
}) })
.custom_method("custom/request", CustomRequest) // for test .custom_method("custom/request", CustomRequest)
.custom_method("custom/paramRequest", CustomParamRequest) // for test .custom_method("custom/paramRequest", CustomParamRequest)
.custom_method("api/fast", DoFastApi) .custom_method("api/fast", DoFastApi)
.custom_method("api/do-primitives-judge", DoPrimitivesJudgeApi) .custom_method("api/update-fast", UpdateFastApi)
.custom_method("api/update-configuration", UpdateConfigurationApi)
.custom_method("api/sync-fast", SyncFastApi)
.custom_method("api/linter-status", LinterStatusApi)
.finish(); .finish();
Server::new(stdin, stdout, socket) Server::new(stdin, stdout, socket)

View File

@ -1,9 +0,0 @@
request 负责实现前后端非 LSP 协议的通信,比如前端把配置文件同步到后端,前端获取 AST 的一部分数据用于前端的界面功能。
request 是单向的,只能由前端主动发起。
```mermaid
graph TB
前端 --编码发送--> request.rs --解码处理--> 后端
```

View File

@ -1,102 +0,0 @@
use std::sync::Arc;
use std::future;
use log::info;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tower_lsp::jsonrpc::Result;
use crate::{diagnostics::update_diagnostics_configuration, server::Backend};
#[derive(Clone)]
pub struct UpdateConfigurationApi;
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct UpdateConfigurationParams {
configs: Vec<UpdateConfigurationItem>,
config_type: String
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct UpdateConfigurationItem {
name: String,
value: Value
}
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (UpdateConfigurationParams, ), Result<()>> for UpdateConfigurationApi {
type Future = future::Ready<Result<()>>;
fn invoke(&self, _server: &'a Arc<Backend>, _params: (UpdateConfigurationParams, )) -> Self::Future {
let request_param = _params.0;
let configs = request_param.configs;
// 用于未来进行配置分区
let config_type = request_param.config_type;
update_configuration(configs, config_type, &_server);
future::ready(Ok(()))
}
}
/// 前端配置文件的更新
fn update_configuration(
configs: Vec<UpdateConfigurationItem>,
config_type: String,
backend: &Arc<Backend>
) {
let mut lsp_configuration = backend.server.db.lsp_configuration.write().unwrap();
match config_type.as_str() {
// 所有配置同步到 lsp_configuration 中
"lsp" => {
for config in configs {
info!("update config, name: {}, value: {}", config.name, config.value);
lsp_configuration.insert(config.name, config.value);
}
},
// 针对当前项目选择的诊断器的更新
// 此时 configs 的长度必然为 2
// configs.0 含有当前选择的诊断器的名字的信息,比如 "digital-ide.function.lsp.linter.vlog.diagnostor": "vivado"
// configs.1 含有第三方诊断器的合法路径相关的信息,比如 "path": "/opt/xilinx/Vivado/2022.2/bin/xvlog"
"linter" => {
if configs.len() < 2 {
info!("update_configuration, type : {}, 发生错误原因configs 数量不为 2", config_type);
return;
}
let linter_name_configuration = &configs[0];
let linter_path_configuration = &configs[1];
// linter_name_config_name 形如 digital-ide.function.lsp.linter.vlog.diagnostor
let linter_name_config_name = &linter_name_configuration.name;
// 同步到全局配置中
lsp_configuration.insert(
linter_name_config_name.to_string(),
linter_name_configuration.value.clone()
);
// 从 linter_name_configuration.name 解析出 language_id
let language_id = {
// linter_name_config_name 形如 digital-ide.function.lsp.linter.vlog.diagnostor
let mut cookies = linter_name_config_name.split(".");
let name = cookies.nth(4).unwrap();
name
};
let linter_name = linter_name_configuration.value.as_str().unwrap();
let linter_path = linter_path_configuration.value.as_str().unwrap();
update_diagnostics_configuration(
&backend.server,
linter_name,
language_id,
linter_path
);
},
_ => {}
}
}

View File

@ -1,395 +0,0 @@
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::core::scope_tree::common::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.db;
if let Some((syntax_tree, parse_result)) = parse_result {
if let Ok(mut fast) = make_fast_from_syntaxtree(&syntax_tree) {
fast.file_type = file_type.to_string();
let hdl_param = sources.hdl_param.clone();
hdl_param.update_hdl_file(
path.to_string(),
fast.clone(),
parse_result,
Some(crate::sources::AstLike::Svlog(syntax_tree))
);
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.db;
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,
entitys: vec![]
};
hdl_param.update_hdl_file(
path.to_string(),
ip_fast.clone(),
sv_parser::common::ParseResult::new(),
None
);
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();
}
// 为了兼容 verilog 而制作的空的
hdl_param.update_hdl_file(
path.to_string(),
fast.clone(),
sv_parser::common::ParseResult::new(),
None
);
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_hdl_file(
path.to_string(),
fast.clone(),
sv_parser::common::ParseResult::new(),
None
);
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
})
}
/// 将后端新的 fast 同步到前端去
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 path_string = from_uri_to_escape_path_string(&uri).unwrap();
if let Some(source_handle) = backend.server.db.get_source(&path_string) {
let _unused = source_handle.read().unwrap();
}
}
let hdl_param = backend.server.db.hdl_param.clone();
// TODO: 检查加锁的有效性,因为前端在请求该方法时,后端可能仍然在计算
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)
}
}

View File

@ -1,95 +0,0 @@
use std::{borrow::Cow, sync::Arc};
use std::future;
#[allow(unused)]
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(&backend.server))
}
"vivado" => {
Ok(configuration.vivado.linter_status(&backend.server))
}
"modelsim" => {
Ok(configuration.modelsim.linter_status(&backend.server))
}
"verible" => {
Ok(configuration.verible.linter_status(&backend.server))
}
"verilator" => {
Ok(configuration.verilator.linter_status(&backend.server))
}
_ => {
return Err(tower_lsp::jsonrpc::Error {
code: tower_lsp::jsonrpc::ErrorCode::InvalidRequest,
message: Cow::Owned(format!("无效的诊断器 {}", linter_name)),
data: None
});
}
}
}

View File

@ -1,6 +1,237 @@
use std::borrow::Cow;
use std::str::FromStr;
use std::{fs, future};
use std::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 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_design_file, vhdl_parse};
use crate::utils::*;
use crate::server::Backend;
use crate::sources::recovery_sv_parse_with_retry;
pub mod notification; pub mod notification;
pub mod config;
pub mod primitives; #[derive(Clone)]
pub mod fast; pub struct CustomRequest;
pub mod test;
pub mod linter; impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (), Result<i32>> for CustomRequest {
type Future = future::Ready<Result<i32>>;
fn invoke(&self, _server: &'a Arc<Backend>, _params: ()) -> Self::Future {
future::ready(custom_request())
}
}
pub fn custom_request() -> Result<i32> {
Ok(123)
}
#[derive(Deserialize, Serialize, Debug)]
pub struct CustomParamRequestParams {
param: String,
}
#[derive(Clone)]
pub struct CustomParamRequest;
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (CustomParamRequestParams, ), Result<i32>> for CustomParamRequest {
type Future = future::Ready<Result<i32>>;
fn invoke(&self, _server: &'a Arc<Backend>, _params: (CustomParamRequestParams, )) -> Self::Future {
future::ready(custom_param_request(_params.0.param))
}
}
pub fn custom_param_request(param: String) -> Result<i32> {
info!("receive param: {:?}", param);
Ok(123)
}
#[derive(Deserialize, Serialize, Debug)]
pub struct DoFastApiRequestParams {
path: 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 hdlparam = do_fast(path, _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, backend: &Arc<Backend>) -> Result<FastHdlparam> {
info!("parse fast \"{}\"", 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, backend)
}
"verilog" | "systemverilog" => {
do_sv_fast(&path, 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, backend: &Arc<Backend>) -> Result<FastHdlparam> {
let path_buf = PathBuf::from(&path);
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(fast) = make_fast_from_syntaxtree(&syntax_tree, &path_buf) {
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, backend: &Arc<Backend>) -> Result<FastHdlparam> {
let sources = &backend.server.srcs;
let pathbuf = PathBuf::from_str(path).unwrap();
if let Some(design_file) = vhdl_parse(&pathbuf) {
let hdl_param = sources.hdl_param.clone();
if let Some(fast) = make_fast_from_design_file(&design_file) {
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
})
}
#[derive(Clone)]
pub struct UpdateFastApi;
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (DoFastApiRequestParams, ), Result<FastHdlparam>> for UpdateFastApi {
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 hdlparam = update_fast(path, _server);
future::ready(hdlparam)
}
}
pub fn update_fast(path: String, backend: &Arc<Backend>) -> Result<FastHdlparam> {
{
let fast_sync_controller = backend.server.srcs.fast_sync_controller.read().unwrap();
if let Some(fast_lock) = fast_sync_controller.get(&path) {
let fast_lock = fast_lock.clone();
drop(fast_sync_controller);
let _unused = fast_lock.write().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, backend)
}
}

View File

@ -1,39 +0,0 @@
use std::future;
use std::sync::Arc;
#[allow(unused)]
use log::info;
use serde::{Deserialize, Serialize};
use tower_lsp::jsonrpc::Result;
use crate::server::Backend;
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct DoPrimitivesJudgeParams {
name: String
}
#[derive(Clone)]
pub struct DoPrimitivesJudgeApi;
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (DoPrimitivesJudgeParams, ), Result<bool>> for DoPrimitivesJudgeApi {
type Future = future::Ready<Result<bool>>;
fn invoke(&self, _server: &'a Arc<Backend>, _params: (DoPrimitivesJudgeParams, )) -> Self::Future {
let request_param = _params.0;
let primitive_name = request_param.name;
let is_primitive = do_primitives_judge(&primitive_name, &_server);
future::ready(Ok(is_primitive))
}
}
fn do_primitives_judge(
name: &str,
backend: &Arc<Backend>
) -> bool {
let sources = &backend.server.db;
let primitive_text = sources.primitive_text.clone();
let primitive_map = primitive_text.name_to_text.read().unwrap();
primitive_map.contains_key(name)
}

View File

@ -1,44 +0,0 @@
use std::future;
use std::sync::Arc;
use log::info;
use serde::{Deserialize, Serialize};
use tower_lsp::jsonrpc::Result;
use crate::server::Backend;
#[derive(Clone)]
pub struct CustomRequest;
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (), Result<i32>> for CustomRequest {
type Future = future::Ready<Result<i32>>;
fn invoke(&self, _server: &'a Arc<Backend>, _params: ()) -> Self::Future {
future::ready(custom_request())
}
}
pub fn custom_request() -> Result<i32> {
Ok(123)
}
#[derive(Deserialize, Serialize, Debug)]
pub struct CustomParamRequestParams {
param: String,
}
#[derive(Clone)]
pub struct CustomParamRequest;
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (CustomParamRequestParams, ), Result<i32>> for CustomParamRequest {
type Future = future::Ready<Result<i32>>;
fn invoke(&self, _server: &'a Arc<Backend>, _params: (CustomParamRequestParams, )) -> Self::Future {
future::ready(custom_param_request(_params.0.param))
}
}
pub fn custom_param_request(param: String) -> Result<i32> {
info!("receive param: {:?}", param);
Ok(123)
}

View File

@ -1,49 +1,37 @@
use crate::completion::directives::provide_vlog_directives_completions;
use crate::core::cache_storage::CacheManager; use crate::core::cache_storage::CacheManager;
use crate::diagnostics::DigitalLinterConfiguration;
use crate::sources::*; use crate::sources::*;
use crate::completion::{keyword::*, provide_vlog_sys_tasks_completions}; use crate::completion::keyword::*;
use flexi_logger::LoggerHandle; use flexi_logger::LoggerHandle;
#[allow(unused)] #[allow(unused)]
use log::{debug, info, warn}; use log::{debug, info, warn};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::string::ToString; use std::string::ToString;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Mutex, RwLock};
use tower_lsp::jsonrpc::Result; use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer}; use tower_lsp::{Client, LanguageServer};
pub struct LspServer { pub struct LSPServer {
/// 文件和 ast 相关的 pub srcs: Sources,
pub db: DigitalDataBase,
/// 缓存
pub cache: CacheManager, pub cache: CacheManager,
/// verilog 关键词的自动补全 pub key_comps: Vec<CompletionItem>,
pub vlog_keyword_completion_items: Vec<CompletionItem>, pub sys_tasks: Vec<CompletionItem>,
/// verilog 的系统调用的自动补全 pub directives: Vec<CompletionItem>,
pub vlog_sys_tasks_completion_items: Vec<CompletionItem>, pub conf: RwLock<ProjectConfig>,
/// verilog 的所有宏的自动补全
pub vlog_directives: Vec<CompletionItem>,
/// vhdl 关键词的自动补全
pub vhdl_keyword_completiom_items: Vec<CompletionItem>,
/// 相关的配置项目
pub configuration: Arc<RwLock<LspConfiguration>>,
#[allow(unused)] #[allow(unused)]
pub log_handle: Mutex<Option<LoggerHandle>>, pub log_handle: Mutex<Option<LoggerHandle>>,
} }
impl LspServer { impl LSPServer {
pub fn new(log_handle: Option<LoggerHandle>) -> LspServer { pub fn new(log_handle: Option<LoggerHandle>) -> LSPServer {
let user_home = dirs_next::home_dir().unwrap(); let user_home = dirs_next::home_dir().unwrap();
let dide_home = user_home.join(".digital-ide"); let dide_home = user_home.join(".digital-ide");
LspServer { LSPServer {
db: DigitalDataBase::new(), srcs: Sources::new(),
cache: CacheManager::new(dide_home), cache: CacheManager::new(dide_home),
vlog_keyword_completion_items: provide_keyword_completions(VLOG_KEYWORDS), key_comps: keyword_completions(KEYWORDS),
vhdl_keyword_completiom_items: provide_keyword_completions(VHDL_KEYWORDS), sys_tasks: other_completions(SYS_TASKS),
vlog_sys_tasks_completion_items: provide_vlog_sys_tasks_completions(), directives: other_completions(DIRECTIVES),
vlog_directives: provide_vlog_directives_completions(), conf: RwLock::new(ProjectConfig::default()),
configuration: Arc::new(RwLock::new(LspConfiguration::default())),
log_handle: Mutex::new(log_handle), log_handle: Mutex::new(log_handle),
} }
} }
@ -51,7 +39,7 @@ impl LspServer {
pub struct Backend { pub struct Backend {
pub client: Client, pub client: Client,
pub server: LspServer pub server: LSPServer
} }
@ -59,7 +47,7 @@ impl Backend {
pub fn new(client: Client, log_handle: LoggerHandle) -> Backend { pub fn new(client: Client, log_handle: LoggerHandle) -> Backend {
Backend { Backend {
client, client,
server: LspServer::new(Some(log_handle)), server: LSPServer::new(Some(log_handle)),
} }
} }
} }
@ -80,92 +68,117 @@ pub enum LogLevel {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct LspConfiguration { pub struct ProjectConfig {
// 用户工作目录的路径
pub workspace_folder: Option<Url>,
// 插件安装的根路径
pub extension_path: String,
// 当前工具链
pub tool_chain: String,
// if true, recursively search the working directory for files to run diagnostics on // if true, recursively search the working directory for files to run diagnostics on
pub auto_search_workdir: bool, pub auto_search_workdir: bool,
// list of directories with header files // list of directories with header files
pub include_dirs: Vec<String>, pub include_dirs: Vec<String>,
// list of directories to recursively search for SystemVerilog/Verilog sources // list of directories to recursively search for SystemVerilog/Verilog sources
pub source_dirs: Vec<String>, pub source_dirs: Vec<String>,
// config options for verible tools
pub verible: Verible,
// 下方是和 linter 相关的配置 // config options for verilator tools
// vlog 的诊断配置 pub verilator: Verilator,
pub vlog_linter_configuration: DigitalLinterConfiguration,
// vhdl 的诊断配置
pub vhdl_linter_configuration: DigitalLinterConfiguration,
// svlog 的诊断配置
pub svlog_linter_configuration: DigitalLinterConfiguration,
// log level // log level
pub log_level: LogLevel pub log_level: LogLevel,
} }
impl Default for LspConfiguration { impl Default for ProjectConfig {
fn default() -> Self { fn default() -> Self {
LspConfiguration { ProjectConfig {
workspace_folder: None,
extension_path: "".to_string(),
tool_chain: "xilinx".to_string(),
auto_search_workdir: true, auto_search_workdir: true,
include_dirs: Vec::new(), include_dirs: Vec::new(),
source_dirs: Vec::new(), source_dirs: Vec::new(),
vlog_linter_configuration: DigitalLinterConfiguration::new("verilog"), verible: Verible::default(),
vhdl_linter_configuration: DigitalLinterConfiguration::new("vhdl"), verilator: Verilator::default(),
svlog_linter_configuration: DigitalLinterConfiguration::new("systemverilog"),
log_level: LogLevel::Info, log_level: LogLevel::Info,
} }
} }
} }
#[derive(Default, Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct Verible {
pub syntax: VeribleSyntax,
pub format: VeribleFormat,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VeribleSyntax {
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl Default for VeribleSyntax {
fn default() -> Self {
Self {
enabled: true,
path: "verible-verilog-syntax".to_string(),
args: Vec::new(),
}
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct Verilator {
pub syntax: VerilatorSyntax,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VerilatorSyntax {
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl Default for VerilatorSyntax {
fn default() -> Self {
Self {
enabled: true,
path: "verilator".to_string(),
args: vec![
"--lint-only".to_string(),
"--sv".to_string(),
"-Wall".to_string(),
],
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct VeribleFormat {
pub enabled: bool,
pub path: String,
pub args: Vec<String>,
}
impl Default for VeribleFormat {
fn default() -> Self {
Self {
enabled: true,
path: "verible-verilog-format".to_string(),
args: Vec::new(),
}
}
}
#[tower_lsp::async_trait] #[tower_lsp::async_trait]
impl LanguageServer for Backend { impl LanguageServer for Backend {
async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> { async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
self.server.srcs.init();
// 申明 LSP 的基本信息和提供的能力 // 申明 LSP 的基本信息和提供的能力
let mut version = "0.4.0".to_string();
let root_uri = &params.root_uri;
let mut configure = self.server.configuration.write().unwrap();
configure.workspace_folder = root_uri.clone();
if let Some(serde_json::Value::Object(options)) = params.initialization_options {
let extension_path = options.get("extensionPath").unwrap().as_str().unwrap();
let tool_chain = options.get("toolChain").unwrap().as_str().unwrap();
version = options.get("version").unwrap().as_str().unwrap().to_string();
configure.tool_chain = tool_chain.to_string();
configure.extension_path = extension_path.to_string();
}
let server_info = Some(ServerInfo { let server_info = Some(ServerInfo {
name: "Digital IDE 专用 LSP 后端服务器".to_string(), name: "Digital IDE 专用 LSP 后端服务器".to_string(),
version: Some(version.to_string()) version: Some("0.4.0".to_string())
}); });
info!("当前客户端初始化结果");
if let Some(workspace_path) = &configure.workspace_folder {
info!("workspaceFolder: {:?}", workspace_path.to_file_path());
}
info!("extensionPath: {:?}", configure.extension_path);
info!("toolChain: {:?}", configure.tool_chain);
// 初始化原语系统
self.server.db.init_primitive(
&configure.tool_chain,
&configure.extension_path
);
self.server.db.init_vhdl_project(&configure.extension_path);
// 初始化系统缓存路径
self.server.cache.start(&version);
let text_document_sync = TextDocumentSyncCapability::Options( let text_document_sync = TextDocumentSyncCapability::Options(
TextDocumentSyncOptions { TextDocumentSyncOptions {
open_close: Some(true), open_close: Some(true),
@ -195,82 +208,45 @@ impl LanguageServer for Backend {
completion_item: None, completion_item: None,
}; };
let workspace = WorkspaceServerCapabilities {
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
supported: Some(true),
change_notifications: Some(OneOf::Left(true)),
}),
..Default::default()
};
let capabilities = ServerCapabilities { let capabilities = ServerCapabilities {
text_document_sync: Some(text_document_sync), text_document_sync: Some(text_document_sync),
completion_provider: Some(completion_provider), completion_provider: Some(completion_provider),
definition_provider: Some(OneOf::Left(true)), definition_provider: Some(OneOf::Left(true)),
hover_provider: Some(HoverProviderCapability::Simple(true)), hover_provider: Some(HoverProviderCapability::Simple(true)),
inlay_hint_provider: Some(OneOf::Left(true)), // inlay_hint_provider: Some(OneOf::Left(true)),
document_symbol_provider: Some(OneOf::Left(true)), document_symbol_provider: Some(OneOf::Left(true)),
document_highlight_provider: Some(OneOf::Left(true)), document_highlight_provider: Some(OneOf::Left(true)),
workspace: Some(workspace),
code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
..ServerCapabilities::default() ..ServerCapabilities::default()
}; };
Ok(InitializeResult { Ok(InitializeResult::default())
server_info,
capabilities,
offset_encoding: None
})
} }
async fn initialized(&self, _: InitializedParams) { async fn initialized(&self, _: InitializedParams) {
self.client self.client
.log_message(MessageType::INFO, "Digital LSP initialized!") .log_message(MessageType::INFO, "digital lsp initialized!")
.await; .await;
// self.client.send_notification::<StringNotification>(StringNotification { content: "hello from lsp server".to_string() }).await;
} }
async fn execute_command(&self, params: ExecuteCommandParams) -> Result<Option<serde_json::Value>> {
crate::execute_command::execute_command(self, params).await
}
async fn shutdown(&self) -> Result<()> { async fn shutdown(&self) -> Result<()> {
Ok(()) Ok(())
} }
async fn did_open(&self, params: DidOpenTextDocumentParams) { async fn did_open(&self, params: DidOpenTextDocumentParams) {
// // 如果文件太大则显示错误 let diagnostics = self.server.did_open(params);
// if CacheManager::uri_is_big_file(&params.text_document.uri) { self.client
// self.client.show_message(MessageType::WARNING, "考虑到性能问题,对于大于 1MB 的文件不会主动提供语言服务") .publish_diagnostics(
// .await; diagnostics.uri,
// } else { diagnostics.diagnostics,
let diagnostics = self.server.did_open(params); diagnostics.version,
self.client.publish_diagnostics( )
diagnostics.uri, .await;
diagnostics.diagnostics,
diagnostics.version,
)
.await;
// }
}
async fn did_close(&self, params: DidCloseTextDocumentParams) {
// 获取诊断相关的配置信息,如果 mode 为 common则需要清空关闭文件的诊断信息
// 如果同一个文件短时间内多次调用该方法,则读写锁会失效
if let Some(linter_mode) = self.server.db.get_lsp_configuration_string_value("digital-ide.function.lsp.linter.mode") {
if linter_mode == "common" {
self.client.publish_diagnostics(params.text_document.uri, vec![], None).await;
}
}
} }
async fn did_change(&self, params: DidChangeTextDocumentParams) { async fn did_change(&self, params: DidChangeTextDocumentParams) {
// // 如果文件太大则显示错误 self.server.did_change(params);
// if CacheManager::uri_is_big_file(&params.text_document.uri) {
// // self.client.show_message(MessageType::WARNING, "考虑到性能问题,对于大于 1MB 的文件不会主动提供语言服务")
// // .await;
// } else {
self.server.did_change(params);
// }
} }
async fn did_delete_files(&self, params: DeleteFilesParams) { async fn did_delete_files(&self, params: DeleteFilesParams) {
@ -278,18 +254,14 @@ impl LanguageServer for Backend {
} }
async fn did_save(&self, params: DidSaveTextDocumentParams) { async fn did_save(&self, params: DidSaveTextDocumentParams) {
// if CacheManager::uri_is_big_file(&params.text_document.uri) { let diagnostics = self.server.did_save(params);
self.client
// } else { .publish_diagnostics(
let diagnostics = self.server.did_save(params); diagnostics.uri,
self.client diagnostics.diagnostics,
.publish_diagnostics( diagnostics.version,
diagnostics.uri, )
diagnostics.diagnostics, .await;
diagnostics.version,
)
.await;
// }
} }
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> { async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
@ -304,6 +276,7 @@ impl LanguageServer for Backend {
} }
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> { async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
Ok(self.server.hover(params)) Ok(self.server.hover(params))
} }
@ -322,6 +295,7 @@ impl LanguageServer for Backend {
&self, &self,
params: DocumentSymbolParams, params: DocumentSymbolParams,
) -> Result<Option<DocumentSymbolResponse>> { ) -> Result<Option<DocumentSymbolResponse>> {
info!("enter document");
Ok(self.server.document_symbol(params)) Ok(self.server.document_symbol(params))
} }
@ -329,6 +303,7 @@ impl LanguageServer for Backend {
&self, &self,
params: DocumentHighlightParams, params: DocumentHighlightParams,
) -> Result<Option<Vec<DocumentHighlight>>> { ) -> Result<Option<Vec<DocumentHighlight>>> {
info!("enter highlight");
Ok(self.server.document_highlight(params)) Ok(self.server.document_highlight(params))
} }
@ -336,13 +311,7 @@ impl LanguageServer for Backend {
&self, &self,
params: InlayHintParams params: InlayHintParams
) -> Result<Option<Vec<InlayHint>>> { ) -> Result<Option<Vec<InlayHint>>> {
info!("enter inlay_hint");
Ok(self.server.inlay_hint(params)) Ok(self.server.inlay_hint(params))
} }
async fn code_lens(
&self,
params: CodeLensParams
) -> Result<Option<Vec<CodeLens>>> {
Ok(self.server.code_lens(params))
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -15,9 +15,6 @@ const TEST_FILE: &str = "/home/dide/project/Digital-Test/MipsDesign/src/MyCpu.v"
#[allow(unused)] #[allow(unused)]
const INCOMPLETE_EXAMPLE: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user/src/incomplete-example"; const INCOMPLETE_EXAMPLE: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user/src/incomplete-example";
#[allow(unused)]
const DIGTIAL_IDE_FACTORY: &str = "/home/dide/project/Digital-Test/Digital-IDE-test/user/factory";
#[allow(unused)] #[allow(unused)]
const TESTFILES_TEMP_DIR: &str = "/home/dide/project/Digital-Test/Digital-IDE-temp"; const TESTFILES_TEMP_DIR: &str = "/home/dide/project/Digital-Test/Digital-IDE-temp";
@ -106,20 +103,6 @@ mod test_fast {
} }
} }
#[test]
fn test_factory() {
// 判断路径是否存在且为文件夹
let path = Path::new(DIGTIAL_IDE_FACTORY);
if path.exists() && path.is_dir() {
// 递归遍历文件夹
if let Err(e) = traverse_directory(path) {
eprintln!("Error: {}", e);
}
} else {
eprintln!("Path does not exist or is not a directory");
}
}
#[test] #[test]
fn test_mips_design() { fn test_mips_design() {
// 判断路径是否存在且为文件夹 // 判断路径是否存在且为文件夹
@ -201,7 +184,7 @@ mod test_svparse {
use tower_lsp::lsp_types::{Position, Range, Url}; use tower_lsp::lsp_types::{Position, Range, Url};
use crate::sources::recovery_sv_parse_with_retry; use crate::sources::recovery_sv_parse_with_retry;
use super::{INCOMPLETE_EXAMPLE, TEST_FILE, DIGTIAL_IDE_FACTORY}; use super::{INCOMPLETE_EXAMPLE, TEST_FILE};
#[test] #[test]
/// 测试单独的文件 /// 测试单独的文件
@ -245,19 +228,6 @@ mod test_svparse {
eprintln!("Path does not exist or is not a directory"); eprintln!("Path does not exist or is not a directory");
} }
} }
#[test]
fn test_factory() {
let path = Path::new(DIGTIAL_IDE_FACTORY);
if path.exists() && path.is_dir() {
// 递归遍历文件夹
if let Err(e) = traverse_directory(path) {
eprintln!("Error: {}", e);
}
} else {
eprintln!("Path does not exist or is not a directory");
}
}
fn traverse_directory(dir: &Path) -> Result<(), Box<dyn std::error::Error>> { fn traverse_directory(dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
if dir.is_dir() { if dir.is_dir() {
@ -311,9 +281,7 @@ mod test_svparse {
#[cfg(test)] #[cfg(test)]
mod test_scope_tree { mod test_scope_tree {
use std::{fs, path::{Path, PathBuf}}; use std::{fs, path::{Path, PathBuf}};
use crate::sources::recovery_sv_parse_with_retry; use crate::{definition::{get_scopes_from_syntax_tree, GenericScope}, sources::{recovery_sv_parse, recovery_sv_parse_with_retry}};
use crate::core::scope_tree::get_scopes_from_syntax_tree;
use crate::core::scope_tree::common::GenericScope;
use ropey::Rope; use ropey::Rope;
use tower_lsp::lsp_types::Url; use tower_lsp::lsp_types::Url;
@ -332,7 +300,7 @@ mod test_scope_tree {
let result = recovery_sv_parse_with_retry(&doc, &uri, &None, &includes); let result = recovery_sv_parse_with_retry(&doc, &uri, &None, &includes);
// let result = recovery_sv_parse(&doc, &uri, &None, &includes, true); // let result = recovery_sv_parse(&doc, &uri, &None, &includes, true);
if let Some((syntax_tree, _)) = result { if let Some(syntax_tree) = result {
let file_url = format!("file://{}", file_path); let file_url = format!("file://{}", file_path);
let uri = Url::parse(&file_url); let uri = Url::parse(&file_url);
if let Ok(uri) = uri { if let Ok(uri) = uri {
@ -399,19 +367,6 @@ mod test_scope_tree {
} }
} }
#[test]
fn test_factory() {
let path = Path::new(DIGTIAL_IDE_FACTORY);
if path.exists() && path.is_dir() {
// 递归遍历文件夹
if let Err(e) = traverse_directory(path) {
eprintln!("Error: {}", e);
}
} else {
eprintln!("Path does not exist or is not a directory");
}
}
fn traverse_directory(dir: &Path) -> Result<(), Box<dyn std::error::Error>> { fn traverse_directory(dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
if dir.is_dir() { if dir.is_dir() {
for entry in fs::read_dir(dir)? { for entry in fs::read_dir(dir)? {
@ -449,7 +404,7 @@ mod test_scope_tree {
mod test_file { mod test_file {
use std::fs; use std::fs;
use crate::utils::{file_size_in_kb, get_language_id_by_pathbuf, is_character_ordered_match, RecursiveFileIterator}; use crate::utils::{file_size_in_kb, get_language_id_by_pathbuf, RecursiveFileIterator};
use super::*; use super::*;
#[test] #[test]
@ -476,47 +431,4 @@ mod test_file {
fn test_cache() { fn test_cache() {
let _ = fs::create_dir_all("/home/dide/project/digital-lsp-server/.cache"); let _ = fs::create_dir_all("/home/dide/project/digital-lsp-server/.cache");
} }
#[test]
fn test_utils() {
println!("enter function");
assert!(is_character_ordered_match("parm", "param"));
assert!(is_character_ordered_match("param", "param"));
assert!(is_character_ordered_match("prm", "PARAM"));
assert!(is_character_ordered_match("car", "careful"));
assert!(!is_character_ordered_match("suprt", "super"));
}
}
#[cfg(test)]
mod test_code_doc {
use std::{env, fs, path::PathBuf};
use ropey::Rope;
use tower_lsp::lsp_types::Url;
use crate::sources::recovery_sv_parse_with_retry;
use crate::codedoc;
#[test]
fn dev_code_doc() {
match env::current_dir() {
Ok(path) => println!("当前工作目录: {}", path.display()),
Err(e) => println!("获取当前工作目录失败: {}", e),
}
let path = PathBuf::from("E:/Project/Digital-IDE/digital-lsp-server/test/vlog/codedoc.v");
let includes: Vec<PathBuf> = Vec::new();
let text = match fs::read_to_string(&path) {
Ok(text) => text,
Err(_) => return
};
let doc = Rope::from_str(&text);
let uri = Url::from_file_path(&path).unwrap();
let result = recovery_sv_parse_with_retry(&doc, &uri, &None, &includes);
if let Some((syntax_tree, _)) = result {
codedoc::vlog::get_comments_from_ast(&doc, &syntax_tree);
}
}
} }

View File

@ -2,47 +2,46 @@
#[cfg(test)] #[cfg(test)]
mod test_vhdl_fast { mod test_vhdl_fast {
// TODO: Rewrite VHDL Test use crate::{core::vhdl_parser::{make_fast_from_design_file, vhdl_parse}, test::{DIGTIAL_IDE_TEST, TESTFILES_TEMP_DIR}, utils::*};
// use crate::{core::vhdl_parser::{make_fast_from_design_file, vhdl_parse}, test::{DIGTIAL_IDE_TEST, TESTFILES_TEMP_DIR}, utils::*};
// #[test] #[test]
// fn test_temp() { fn test_temp() {
// let file_iter = RecursiveFileIterator::new(TESTFILES_TEMP_DIR); let file_iter = RecursiveFileIterator::new(TESTFILES_TEMP_DIR);
// for file in file_iter { for file in file_iter {
// let language_id = get_language_id_by_pathbuf(&file); let language_id = get_language_id_by_pathbuf(&file);
// if language_id == "vhdl" { if language_id == "vhdl" {
// println!("test file: {:?}", file); println!("test file: {:?}", file);
// if let Some(design_file) = vhdl_parse(&file) { if let Some(design_file) = vhdl_parse(&file) {
// if let Some(_) = make_fast_from_design_file(&design_file) { if let Some(_) = make_fast_from_design_file(&design_file) {
// println!(""); println!("");
// } else { } else {
// eprintln!("error happen when make fast {:?}", file); eprintln!("error happen when make fast {:?}", file);
// } }
// } else { } else {
// eprintln!("error happen when parse {:?}", file); eprintln!("error happen when parse {:?}", file);
// } }
// } }
// } }
// } }
// #[test] #[test]
// fn test_digital_ide_test() { fn test_digital_ide_test() {
// let file_iter = RecursiveFileIterator::new(DIGTIAL_IDE_TEST); let file_iter = RecursiveFileIterator::new(DIGTIAL_IDE_TEST);
// for file in file_iter { for file in file_iter {
// let language_id = get_language_id_by_pathbuf(&file); let language_id = get_language_id_by_pathbuf(&file);
// if language_id == "vhdl" { if language_id == "vhdl" {
// println!("test file: {:?}", file); println!("test file: {:?}", file);
// if let Some(design_file) = vhdl_parse(&file) { if let Some(design_file) = vhdl_parse(&file) {
// if let Some(_) = make_fast_from_design_file(&design_file) { if let Some(_) = make_fast_from_design_file(&design_file) {
// println!(""); println!("");
// } else { } else {
// eprintln!("error happen when make fast {:?}", file); eprintln!("error happen when make fast {:?}", file);
// } }
// } else { } else {
// eprintln!("error happen when parse {:?}", file); eprintln!("error happen when parse {:?}", file);
// } }
// } }
// } }
// } }
} }

View File

@ -1,28 +0,0 @@
use std::process::Command;
use log::info;
use crate::server::LspServer;
pub fn is_command_valid(
command: &str,
server: &LspServer
) -> bool {
let cache_info = server.cache.cache_info.read().unwrap();
let cwd = match &cache_info.linter_cache {
Some(pc) => pc,
None => {
info!("缓存系统尚未完成初始化,获取命令有效性取消");
return false;
}
};
// 尝试执行命令
match Command::new(command)
.current_dir(cwd)
.output() {
Ok(_) => true,
Err(_) => false,
}
}

View File

@ -1,18 +1,20 @@
#[allow(unused)] #[allow(unused)]
use log::info; use log::info;
use crate::{core::hdlparam::Define, server::LspServer}; use crate::{core::hdlparam::Define, server::LSPServer};
impl LspServer { impl LSPServer {
/// 根据输入的 macro 名字,寻找 fast 中存在的第一个 macro /// 根据输入的 macro 名字,寻找 fast 中存在的第一个 macro
/// macro 可以以 ` 开头 /// macro 可以以 ` 开头
pub fn find_macros(&self, macro_name: &str) -> Option<(Define, String)> { pub fn find_macros(&self, macro_name: &str) -> Option<(Define, String)> {
let macro_name = macro_name.replace("`", ""); let macro_name = macro_name.replace("`", "");
let path_to_hdl_file = self.db.hdl_param.path_to_hdl_file.read().unwrap(); let fast_map = self.srcs.hdl_param.path_to_hdl_file.read().unwrap();
for (path, hdl_file) in path_to_hdl_file.iter() { for path in fast_map.keys() {
for define in &hdl_file.fast.fast_macro.defines { if let Some(hdl_file) = fast_map.get(path) {
if define.name == macro_name { for define in &hdl_file.fast.fast_macro.defines {
return Some((define.clone(), path.to_string())); if define.name == macro_name {
return Some((define.clone(), path.to_string()));
}
} }
} }
} }

View File

@ -1,13 +1,11 @@
use std::{env::consts::OS, path::{Path, PathBuf}, str::FromStr}; use std::{env::consts::OS, path::{Path, PathBuf}};
use log::info;
use percent_encoding::percent_decode_str; use percent_encoding::percent_decode_str;
use regex::Regex; use regex::Regex;
use ropey::RopeSlice; use ropey::RopeSlice;
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use vhdl_lang::{ast::ObjectClass, AnyEntKind, Concurrent, Object, Overloaded, SrcPos, Type}; use vhdl_lang::{ast::ObjectClass, AnyEntKind, Concurrent, Object, Overloaded, SrcPos, Type};
pub mod command;
pub mod fast; pub mod fast;
pub mod file; pub mod file;
pub use file::*; pub use file::*;
@ -84,7 +82,6 @@ pub fn get_word_range_at_position(line: &RopeSlice, pos: Position, regex: Regex)
/// 根据 uri 获取 hdl 的 language id /// 根据 uri 获取 hdl 的 language id
/// 返回值为 "vhdl" | "verilog" | "systemverilog" | "plaintext" /// 返回值为 "vhdl" | "verilog" | "systemverilog" | "plaintext"
/// 不采用枚举是因为需要在 lsp 中使用到它们的字符串值 /// 不采用枚举是因为需要在 lsp 中使用到它们的字符串值
#[allow(unused)]
pub fn get_language_id_by_uri(uri: &Url) -> String { pub fn get_language_id_by_uri(uri: &Url) -> String {
let path = uri.path(); let path = uri.path();
let ext_name = std::path::Path::new(path) let ext_name = std::path::Path::new(path)
@ -98,7 +95,6 @@ pub fn get_language_id_by_uri(uri: &Url) -> String {
/// 根据路径字符串获取 hdl 的 language id /// 根据路径字符串获取 hdl 的 language id
/// 返回值为 "vhdl" | "verilog" | "systemverilog" | "plaintext" /// 返回值为 "vhdl" | "verilog" | "systemverilog" | "plaintext"
/// 不采用枚举是因为需要在 lsp 中使用到它们的字符串值 /// 不采用枚举是因为需要在 lsp 中使用到它们的字符串值
#[allow(unused)]
pub fn get_language_id_by_pathbuf(pathbuf: &PathBuf) -> String { pub fn get_language_id_by_pathbuf(pathbuf: &PathBuf) -> String {
let ext_name = pathbuf.as_path() let ext_name = pathbuf.as_path()
.extension() .extension()
@ -110,7 +106,6 @@ pub fn get_language_id_by_pathbuf(pathbuf: &PathBuf) -> String {
/// 根据路径字符串获取 hdl 的 language id /// 根据路径字符串获取 hdl 的 language id
/// 返回值为 "vhdl" | "verilog" | "systemverilog" | "plaintext" /// 返回值为 "vhdl" | "verilog" | "systemverilog" | "plaintext"
/// 不采用枚举是因为需要在 lsp 中使用到它们的字符串值 /// 不采用枚举是因为需要在 lsp 中使用到它们的字符串值
#[allow(unused)]
pub fn get_language_id_by_path_str(path_str: &str) -> String { pub fn get_language_id_by_path_str(path_str: &str) -> String {
let ext_name = std::path::Path::new(path_str) let ext_name = std::path::Path::new(path_str)
.extension() .extension()
@ -177,29 +172,6 @@ pub fn to_escape_path(path: &PathBuf) -> PathBuf {
} }
} }
/// 将 uri 转换为 digital lsp 内部使用的路径字符串
/// 比如 hdlparam 里面有一些以 String 作为主键的 hashmap
/// 它们的 String 如果代表路径,那么都是通过该函数从 uri 转换而来的
pub fn from_uri_to_escape_path_string(
uri: &Url
) -> Option<String> {
let path = match PathBuf::from_str(uri.path()) {
Ok(path) => path,
Err(error) => {
info!("PathBuf::from_str(uri.path()) 发生错误 {:?}", error);
return None;
}
};
let escape_path = to_escape_path(&path);
let escape_path_string = escape_path.to_str().unwrap_or("");
if escape_path_string.len() == 0 {
info!("escape_path_string 为空");
return None;
}
Some(escape_path_string.to_string())
}
pub fn to_lsp_pos(position: vhdl_lang::Position) -> Position { pub fn to_lsp_pos(position: vhdl_lang::Position) -> Position {
Position { Position {
line: position.line, line: position.line,
@ -364,34 +336,4 @@ impl BracketMatcher {
BracketMatchResult::Valid BracketMatchResult::Valid
} }
} }
/// 基于字符顺序判断是否匹配
/// 检查是否可以从 `candidate` 中提取出与 `input` 顺序一致的字符
/// input 忽略大小写
pub fn is_character_ordered_match(
input: &str,
candidate: &str
) -> bool {
let mut input_chars = input.chars().peekable();
let mut candidate_chars = candidate.chars().peekable();
while let Some(input_char) = input_chars.next() {
// 在 candidate 中找到与 input_char 匹配的字符
let mut matched = false;
while let Some(candidate_char) = candidate_chars.next() {
if candidate_char.to_lowercase().eq(input_char.to_lowercase()) {
matched = true;
break;
}
}
// 如果未找到匹配的字符,返回 false
if !matched {
return false;
}
}
// 如果 input 的所有字符都匹配完成,返回 true
true
}

@ -1 +1 @@
Subproject commit 2588d1dac8b7015632a453b293794744a75c240b Subproject commit b6ae8b8a1ff22609e2a837b12dd798226f299f71

View File

@ -1,57 +0,0 @@
/* @meta
* Create Date : 2/6/2025 14:58
* Author : nitcloud
* Target Device : [Target FPGA and ASIC Device]
* Tool Versions : vivado 18.3 & DC 2016
* Revision Historyc :
* Revision :
* 04/12 0.01 - File Created
* Description :
* Company : ncai Technology .Inc
* Copyright : 1999, ncai Technology Inc, All right reserved
*/
/* @module
* Netlist : level-1
* FSMView : on
* Overview: 4-stage pipelined accumulator.
*/
/*
* 这是一些简单的文字可以随意渲染
* :::info
* 请注意版权问题
* :::
*
* 使用 C 语言如此进行简单的编译
* ```c
* int main() {
* return 0;
* }
* ```
*/
/* @wavedrom accuml this is accuml wavedrom
{signal: [
{name: 'clock', wave: '10101010101010101'},
{name: 'reset', wave: '10...............'},
{name: 'clr', wave: '01.0.............'},
{name: 'idata', wave: 'x3...............', data: ['5']},
{name: 'odata', wave: 'x........5.5.5.5.', data: ['5','10','25','30']},
]}
*/
module adder(
// 这是一个简单的注释
// 这是它们的第二行注释
input a,
input b,
// 这是输出信号
output c,
);
// 具体的代码实现
meta_add u_meta_add(a, b, c);
endmodule

@ -1 +1 @@
Subproject commit e2b352a670aea6c8bf6d33e1010ad49e1f8eab04 Subproject commit a058a7c6411afa7543953bb93a098d9657c408b4