Compare commits

...

116 Commits

Author SHA1 Message Date
10fbbd8843 加入 codedoc 部分 2025-02-14 16:39:39 +08:00
3d91cd1ee3 完成龙芯的支持 2025-01-17 17:23:48 +08:00
light-ly
e5256742ea fix vhdl width format 2025-01-09 14:20:18 +08:00
light-ly
a42b87cd97 fix vhdl params/ports in completion 2025-01-09 12:08:58 +08:00
f2ccd76a65 update 2025-01-08 19:32:33 +08:00
1d04a5f1cf 修复 linter mode 未正确使用的 bug 2025-01-06 18:35:34 +08:00
light-ly
8d45345c54 skip close when linter mode is None 2025-01-06 01:07:11 +08:00
a6717a702e 解决 include 解析的问题 2025-01-05 21:44:00 +08:00
229fe4dc97 修复 verilator 找不到文件的问题 2025-01-05 16:04:31 +08:00
ff1037f0d3 支持新的 netlist 2025-01-02 01:12:50 +08:00
7d1b9e6154 Bump sv-parser 2024-12-29 22:36:56 +08:00
light-ly
76af408f3f remove repeat replace logic in parser 2024-12-28 15:05:30 +08:00
light-ly
621dea1819 update vhdl_parser 2024-12-25 21:38:47 +08:00
light-ly
d90d92f635 update submodule 2024-12-25 21:16:47 +08:00
light-ly
77624e9a87 refactor vhdl parse logic 2024-12-25 21:13:38 +08:00
light-ly
9897594fdb fix go to defination of vhdl 2024-12-24 20:33:10 +08:00
8698584bd1 修复 endmodule 后面的 inlay hints 在结尾处的异常 2024-12-23 20:43:13 +08:00
b368bc35f5 修复 文档化 的问题 2024-12-23 20:36:27 +08:00
b52e34bd90 更新 linter,修复测试文档上的内容 2024-12-23 20:03:32 +08:00
95632ace63 更新字段 2024-12-23 17:38:21 +08:00
light-ly
bdec500594 fix vhdl arch range 2024-12-22 22:27:01 +08:00
light-ly
173388df1b fix vhdl instance type 2024-12-21 16:02:11 +08:00
b09921473f 修复 fast 中 endmodule 位置拿错的问题 2024-12-19 22:15:00 +08:00
c24b90ea96 实现三种诊断模式 2024-12-17 23:32:31 +08:00
b9c8a2f451 实现三种诊断模式 2024-12-17 20:36:01 +08:00
9119cfb0ed 实现 did_close 事件 2024-12-17 19:14:56 +08:00
826d62dfbd 实现 server 的 execute_command 管线,并实现前端主动发起 lint 请求 2024-12-17 18:20:44 +08:00
574c50325e 完成返回诊断器的接口 2024-12-16 01:24:09 +08:00
fa7b42f09b 更新文本缓冲区备份的索引模式 | 将 AST 的存储地点从 Sources 中移动到 HdlFile 内部 2024-12-16 00:44:33 +08:00
0cf07fd017 修复 macro usage 无法跳转的问题 2024-12-15 01:35:26 +08:00
9688a330bf 实现 endmodule 的 inlay hints 2024-12-13 22:00:40 +08:00
setsumi
1142aa324f add directives comment 2024-12-13 11:34:50 +00:00
1090face1e 更新 directives 2024-12-13 18:10:27 +08:00
aad333783e 修复 system task 中几个未转义的命令 | 所有自动补全增加 label_details 2024-12-13 01:56:02 +08:00
20500a55ca 更加完善的宏相关的自动补全 2024-12-12 22:09:04 +08:00
7cad6176b6 update sv parser 2024-12-12 21:25:19 +08:00
7875a631a8 剥离 scope tree 逻辑到 core 模块中 2024-12-12 18:18:06 +08:00
36ba7a350d 更好的 vivado 诊断器 2024-12-11 22:01:12 +08:00
4db6837029 完成 modelsim | vivado 的诊断 2024-12-10 23:17:56 +08:00
ae083bdad4 完成 verilator 的诊断 2024-12-10 19:15:39 +08:00
f7caccc338 Merge branch 'main' of https://github.com/Digital-EDA/digital-lsp-server 2024-12-10 18:35:37 +08:00
7dd4f58da6 完成 verilator 的诊断 2024-12-10 18:35:29 +08:00
light-ly
3bdd727267 fix vhdl project init 2024-12-10 00:08:46 +08:00
b1b302bbfb 完成 iverilog 的诊断 2024-12-09 23:07:18 +08:00
19fed383b0 Merge branch 'main' of https://github.com/Digital-EDA/digital-lsp-server 2024-12-09 19:33:39 +08:00
f7583e465f 完成 linter 的诊断 pipeline 架构实现 2024-12-09 19:33:34 +08:00
723ee40a4e 完成 linter 的诊断 pipeline 架构实现 2024-12-09 19:33:25 +08:00
light-ly
de587096b2 fix bigfile parse error | remove vhdl test 2024-12-07 15:47:46 +08:00
e726fffd99 完成诊断器架构重新设计 2024-12-06 23:24:10 +08:00
7824b74c9a 完成 linter 后端请求接口和基本数据结构 2024-12-05 23:53:38 +08:00
8e2b373702 添加 18.3.3 标准 2024-12-04 17:50:42 +08:00
c9b259570a merge 2024-12-04 01:36:29 +08:00
579684061a Merge branch 'main' of https://github.com/Digital-EDA/digital-lsp-server 2024-12-04 01:29:04 +08:00
light-ly
68a498da32 sync Cargo.lock 2024-12-04 01:25:43 +08:00
light-ly
3774068671 Merge branch 'main' into vhdl_project 2024-12-04 01:24:41 +08:00
light-ly
299e4dc031 refactor vhdl fast 2024-12-04 01:23:42 +08:00
ac37cd3e2b save 2024-12-04 00:57:59 +08:00
setsumi
539bb20112 add sys_tasks.rs comment 2024-12-03 16:12:11 +00:00
d9fe1e9ed5 更加丰富的 vlog 的 sys task 补全 2024-12-03 22:10:10 +08:00
4dca88c5c1 更加丰富的 vlog 的 sys task 补全 2024-12-03 21:26:43 +08:00
light-ly
d8ddebc744 Merge branch 'vhdl_project' of https://github.com/Digital-EDA/digital-lsp-server into vhdl_project 2024-12-03 19:30:18 +08:00
light-ly
b52d69bdbb fix new fast: stage 2024-12-03 17:24:01 +08:00
3690d88551 serde rename arch_name as archName 2024-12-03 17:15:35 +08:00
9c911a28d7 Merge branch 'main' of https://github.com/Digital-EDA/digital-lsp-server 2024-12-03 16:12:59 +08:00
f42739e8ec add linter 2024-12-03 09:41:11 +08:00
light-ly
58eaaa824a add arch name to module 2024-12-02 23:11:07 +08:00
509fd1a506 去除 vhdl_parser 中的补全项目 2024-12-02 00:30:03 +08:00
light-ly
99786868b9 fix vhdl name 2024-12-02 00:05:19 +08:00
light-ly
6cecb97555 add dot compeletion to vhdl 2024-12-01 23:16:39 +08:00
light-ly
01b59428c2 fix vhdl std path 2024-12-01 22:24:49 +08:00
light-ly
2fcfed4674 refactor vhdl lsp service 2024-11-27 03:25:55 +08:00
c40e66f3df 完成了 vhdl 的自动补全(关键词自动补全 + 自动例化) 2024-11-26 16:59:15 +08:00
3f9d5ff1cc save 2024-11-19 15:34:18 +08:00
230ef10f59 实现 sync fast 2024-11-17 16:25:27 +08:00
83df59917f fix copy scripts permission 2024-11-16 11:34:59 +08:00
light-ly
f325142046 fix datatype byte_idx out of range 2024-11-16 11:34:04 +08:00
light-ly
05621800a4 fix emu scope idx out of range 2024-11-16 11:18:05 +08:00
light-ly
989b66b737 skip architecture when there is no entity 2024-11-16 10:23:49 +08:00
light-ly
0aa6ef0462 change vhdl parse: only think of entity as module 2024-11-16 10:08:13 +08:00
9bb54e0327 完成 CodeLens 的支持, Run | Test 2024-11-14 20:54:06 +08:00
348214e42d 完成 CodeLens 的支持 2024-11-14 20:43:02 +08:00
b01ff8e371 完成 xilinx 原语适配 2024-11-14 16:18:47 +08:00
light-ly
ce267363fb merge primitives completion to new completion code 2024-11-13 23:22:18 +08:00
light-ly
a11ce70bb4 Merge branch 'main' into dev 2024-11-13 23:03:06 +08:00
12e1b25f11 完成自动补全的 output 自动申明 | 完成配置文件的前后端更新系统 2024-11-13 22:52:27 +08:00
light-ly
8681f29ca7 fix write error 2024-11-13 22:49:35 +08:00
d45c243d62 完成自动补全的 output 自动申明 | 完成配置文件的前后端更新系统 2024-11-13 22:46:00 +08:00
light-ly
ec5e1f19c4 add primitives port/param auto completion 2024-11-13 22:45:18 +08:00
light-ly
82b4f6f092 add primitives auto instantiation 2024-11-13 22:24:07 +08:00
light-ly
290d1aec05 add primitives param hover 2024-11-13 02:25:59 +08:00
light-ly
41b2db2eba add primitives port hover 2024-11-13 02:17:05 +08:00
light-ly
eb210bb3b7 add module hover for primitives 2024-11-12 23:08:08 +08:00
light-ly
397946438c fix primitives module name | add primitives request 2024-11-12 23:05:43 +08:00
light-ly
6039bdf2c8 add primitive init 2024-11-12 21:45:24 +08:00
light-ly
2851c1dfa6 add deserialize function 2024-11-12 18:30:02 +08:00
light-ly
103baacb8f fix primitive bug | add gen primitive bin to copy sh 2024-11-12 16:55:11 +08:00
935a852ba0 解决 vhdl 解析内容和内存中文本不一致的现象 2024-11-12 15:57:26 +08:00
15cfaccec1 将 vhdl 2024-11-12 15:56:29 +08:00
c8aa5e2dcc 完成 IP 的支持(还差自动补全) 2024-11-11 23:53:14 +08:00
5540b0b1f2 增加全局 conf 属性: extensionPath 2024-11-11 21:41:08 +08:00
light-ly
9dc59280e6 change some fuction to pub 2024-11-11 21:20:40 +08:00
9333ed0272 Merge branch 'main' of https://github.com/Digital-EDA/digital-lsp-server 2024-11-11 21:06:21 +08:00
cbddc07bdc xilinx IP 支持 | 增加全局 conf 属性 2024-11-11 21:06:12 +08:00
light-ly
eccde327d7 add primitive parser mod 2024-11-11 16:49:31 +08:00
f055b2bbc3 增加对于 xilinx IP 的 entity 内部 port 的解析 2024-11-11 02:06:41 +08:00
b14f8bd17f 规范 module 的 hover 和 definition 接口 2024-11-08 23:11:27 +08:00
fc34d22c82 增加对于 IP 的解析 2024-11-07 22:11:48 +08:00
5bca9a2263 Merge branch 'main' of https://github.com/Digital-EDA/digital-lsp-server 2024-11-07 22:07:08 +08:00
cea86eb8f9 增加对于 IP 的解析 2024-11-07 22:06:53 +08:00
light-ly
4affc95bfe add simple xml_parser 2024-11-07 17:47:54 +08:00
light-ly
3a4bd0c7e4 fix hover comment find 2024-11-07 11:12:18 +08:00
light-ly
4b0e5f2d63 fix hover digit bug | fix completion detail bug 2024-11-05 21:43:10 +08:00
light-ly
f6ae03d310 fix hover comment order 2024-11-05 15:46:41 +08:00
light-ly
7da5f83661 add factory test 2024-11-02 21:14:45 +08:00
light-ly
dbac4132d0 fix dot completion range 2024-11-02 13:20:19 +08:00
cdcea82947 完成 module 端口赋值的 inlay hints 2024-11-01 20:39:29 +08:00
79 changed files with 200990 additions and 2857 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ build.bat
.DS_Store .DS_Store
.cache .cache
deploy.*

View File

@ -15,5 +15,44 @@
"#[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."
} }
} }

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"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 = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@ -99,6 +99,12 @@ 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"
@ -286,6 +292,15 @@ 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"
@ -328,6 +343,7 @@ 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",
@ -348,7 +364,9 @@ dependencies = [
"tokio", "tokio",
"tower-lsp", "tower-lsp",
"vhdl_lang", "vhdl_lang",
"vhdl_ls",
"walkdir", "walkdir",
"xml",
] ]
[[package]] [[package]]
@ -425,6 +443,29 @@ 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"
@ -543,6 +584,15 @@ 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"
@ -608,6 +658,12 @@ 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"
@ -719,6 +775,18 @@ 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"
@ -732,6 +800,19 @@ 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"
@ -1342,6 +1423,7 @@ 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",
@ -1384,6 +1466,7 @@ 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",
@ -1461,6 +1544,16 @@ 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"
@ -1577,7 +1670,7 @@ dependencies = [
"dashmap", "dashmap",
"futures", "futures",
"httparse", "httparse",
"lsp-types", "lsp-types 0.94.1",
"memchr", "memchr",
"serde", "serde",
"serde_json", "serde_json",
@ -1735,6 +1828,23 @@ 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"
@ -2002,3 +2112,18 @@ 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,6 +6,7 @@ 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"
@ -25,6 +26,8 @@ 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"

11
build_loongson.sh Normal file
View File

@ -0,0 +1,11 @@
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 Normal file → Executable file

Binary file not shown.

25893
primitive_files/verilog0.xml Normal file

File diff suppressed because it is too large Load Diff

97703
primitive_files/verilog1.xml Normal file

File diff suppressed because it is too large Load Diff

69504
primitive_files/verilog2.xml Normal file

File diff suppressed because it is too large Load Diff

27
src/code_lens/mod.rs Normal file
View File

@ -0,0 +1,27 @@
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
}
}
}

85
src/code_lens/sv.rs Normal file
View File

@ -0,0 +1,85 @@
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
}

14
src/code_lens/vhdl.rs Normal file
View File

@ -0,0 +1,14 @@
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
}

2
src/codedoc/mod.rs Normal file
View File

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

137
src/codedoc/vhdl.rs Normal file
View File

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

180
src/codedoc/vlog.rs Normal file
View File

@ -0,0 +1,180 @@
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

@ -0,0 +1,460 @@
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,11 +2,16 @@ 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, Position, Url}; use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails, CompletionList, Documentation, MarkupContent, Position, Url};
use crate::{server::LSPServer, utils::{resolve_path, to_escape_path}}; use crate::{server::LspServer, utils::{is_character_ordered_match, resolve_path, to_escape_path}};
pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Option<CompletionList> { /// 补全 include 内部的系统路径
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;
@ -58,16 +63,26 @@ pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Op
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()
}); });
@ -87,8 +102,95 @@ pub fn include_path_completion(uri: &Url, line: &RopeSlice, pos: Position) -> Op
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,
@ -142,7 +244,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,
@ -151,33 +253,64 @@ fn get_position_port_param_completion(
language_id: &str language_id: &str
) -> Option<CompletionList> { ) -> Option<CompletionList> {
// 判断在不在一个模块内,并获取这个模块 // 判断在不在一个模块内,并获取这个模块
let hdl_param = &server.srcs.hdl_param; let hdl_param = &server.db.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(param_range) = &instance.instparams { if let Some(_) = &instance.instparams {
let mut param_range = param_range.clone(); if instance.gen_dot_completion_param_range().contains(pos) {
param_range.affine(-1, -1);
if param_range.contains(pos) {
// 补全当前 module 的所有 param // 补全当前 module 的所有 param
let inst_module = hdl_param.find_module_by_name(&instance.inst_type); if let Some((inst_module, file_type, _)) = hdl_param.find_module_context_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();
for param in inst_module.params { match file_type.as_str() {
"primitives" => {
if let Some(primitives_inst) = inst_module.instances.first() {
for param_assignment in &primitives_inst.intstparam_assignments {
let name = param_assignment.parameter.clone().unwrap();
let param_desc = format!("parameter {}", name);
let label_details = CompletionItemLabelDetails { let label_details = CompletionItemLabelDetails {
detail: Some("parameter".to_string()), description: Some("parameter".to_string()),
..CompletionItemLabelDetails::default() ..Default::default()
};
let c_item = CompletionItem {
label: name,
detail: Some(param_desc),
label_details: Some(label_details),
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 param_desc = make_param_desc(&param);
let c_item = CompletionItem { let c_item = CompletionItem {
label: param.name, label: param.name,
detail: Some(param_desc), detail: Some(param_desc),
@ -187,6 +320,8 @@ fn get_position_port_param_completion(
}; };
completion_items.push(c_item); completion_items.push(c_item);
} }
}
}
return Some(CompletionList { return Some(CompletionList {
is_incomplete: false, is_incomplete: false,
items: completion_items items: completion_items
@ -198,26 +333,61 @@ 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) {
let inst_module = hdl_param.find_module_by_name(&instance.inst_type); if let Some((inst_module, file_type, _)) = hdl_param.find_module_context_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();
for port in inst_module.ports { match file_type.as_str() {
"primitives" => {
if let Some(primitives_inst) = inst_module.instances.first() {
for port_assignment in &primitives_inst.intstport_assignments {
let label_details = CompletionItemLabelDetails { let label_details = CompletionItemLabelDetails {
detail: Some("port".to_string()), detail: Some("port".to_string()),
..CompletionItemLabelDetails::default() ..CompletionItemLabelDetails::default()
}; };
let param_desc = make_port_desc(&port); let name = port_assignment.port.clone().unwrap();
let port_desc = format!("port {}", name);
let c_item = CompletionItem { let c_item = CompletionItem {
label: port.name, label: name,
detail: Some(param_desc), detail: Some(port_desc),
label_details: Some(label_details), label_details: Some(label_details),
kind: Some(CompletionItemKind::PROPERTY), kind: Some(CompletionItemKind::PROPERTY),
..CompletionItem::default() ..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,
items: completion_items items: completion_items
@ -230,36 +400,3 @@ 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,12 +1,17 @@
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
//
pub fn keyword_completions(keywords: &[(&str, &str)]) -> Vec<CompletionItem> { pub fn provide_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()
}); });
@ -14,6 +19,7 @@ pub fn keyword_completions(keywords: &[(&str, &str)]) -> Vec<CompletionItem> {
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()
@ -23,18 +29,8 @@ pub fn keyword_completions(keywords: &[(&str, &str)]) -> Vec<CompletionItem> {
items items
} }
pub fn other_completions(tasks: &[&str]) -> Vec<CompletionItem> { /// (&str, &str, &str): (label, snippet, description)
tasks pub const VLOG_KEYWORDS: &[(&str, &str)] = &[
.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"),
@ -134,9 +130,9 @@ pub const KEYWORDS: &[(&str, &str)] = &[
("import", ""), ("import", ""),
("incdir", ""), ("incdir", ""),
("include", "`include \"$1\""), ("include", "`include \"$1\""),
("initial", ""), ("initial", "initial begin\n\t$1\nend"),
("inout", ""), ("inout", "inout $1"),
("input", ""), ("input", "input $1"),
("inside", ""), ("inside", ""),
("instance", ""), ("instance", ""),
("int", ""), ("int", ""),
@ -173,7 +169,7 @@ pub const KEYWORDS: &[(&str, &str)] = &[
("notif1", ""), ("notif1", ""),
("null", ""), ("null", ""),
("or", ""), ("or", ""),
("output", ""), ("output", "output $1"),
("package", "package $1;\nendpackage"), ("package", "package $1;\nendpackage"),
("packed", ""), ("packed", ""),
("parameter", ""), ("parameter", ""),
@ -285,183 +281,102 @@ pub const KEYWORDS: &[(&str, &str)] = &[
("xor", ""), ("xor", ""),
]; ];
pub const SYS_TASKS: &[&str] = &[ pub const VHDL_KEYWORDS: &[(&str, &str)] = &[
"finish", ("abs", ""),
"exit", ("access", ""),
"fatal", ("after", ""),
"warning", ("alias", "alias $1 is $2;"),
"stop", ("all", ""),
"error", ("and", ""),
"info", ("architecture", "architecture $1 of $2 is\nbegin\n\t$3\nend $1;"),
"realtime", ("array", "array $1 is $2;"),
"time", ("assert", "assert $1 report $2 severity $3;"),
"asserton", ("attribute", "attribute $1 : $2;"),
"assertkill", ("begin", "begin\n\t$1\nend;"),
"assertpasson", ("block", "block ($1) is\nbegin\n\t$2\nend block;"),
"assertfailon", ("body", "body $1 is\nbegin\n\t$2\nend $1;"),
"assertnonvacuouson", ("buffer", ""),
"stime", ("bus", ""),
"printtimescale", ("case", "case $1 is\n\twhen $2 => $3;\nend case;"),
"timeformat", ("component", "component $1 is\n\tport (\n\t\t$2\n\t);\nend component;"),
"bitstoreal", ("configuration", "configuration $1 of $2 is\nfor $3\n\t$4\nend for;\nend $1;"),
"bitstoshortreal", ("constant", "constant $1 : $2 := $3;"),
"itor", ("disconnect", "disconnect $1 after $2;"),
"signed", ("downto", ""),
"cast", ("else", ""),
"realtobits", ("elsif", ""),
"shortrealtobits", ("end", ""),
"rtoi", ("entity", "entity $1 is\n\tport (\n\t\t$2\n\t);\nend $1;"),
"unsigned", ("exit", "exit $1 when $2;"),
"sampled", ("file", "file $1 : $2;"),
"fell", ("for", "for $1 in $2 loop\n\t$3\nend loop;"),
"changed", ("function", "function $1 return $2 is\nbegin\n\t$3\nend $1;"),
"past_gclk", ("generate", "generate\n\t$1\nend generate;"),
"fell_gclk", ("generic", "generic (\n\t$1\n);"),
"changed_gclk", ("group", "group $1 : $2 ($3);"),
"rising_gclk", ("guarded", ""),
"steady_gclk", ("if", "if $1 then\n\t$2\nend if;"),
"bits", ("impure", ""),
"typename", ("in", ""),
"isunbounded", ("inertial", ""),
"coverage_control", ("inout", ""),
"coverage_get", ("is", ""),
"coverage_save", ("label", ""),
"set_coverage_db_name", ("library", "library $1;"),
"dimensions", ("linkage", ""),
"right", ("literal", ""),
"high", ("loop", "loop\n\t$1\nend loop;"),
"size", ("map", "map ($1 => $2);"),
"random", ("mod", ""),
"dist_erlang", ("nand", ""),
"dist_normal", ("new", ""),
"dist_t", ("next", "next $1 when $2;"),
"asin", ("nor", ""),
"acos", ("not", ""),
"atan", ("null", "null;"),
"atan2", ("of", ""),
"hypot", ("on", ""),
"sinh", ("open", ""),
"cosh", ("or", ""),
"tanh", ("others", ""),
"asinh", ("out", ""),
"acosh", ("package", "package $1 is\n\t$2\nend $1;"),
"atanh", ("port", "port (\n\t$1\n);"),
"q_initialize", ("postponed", ""),
"q_remove", ("procedure", "procedure $1 is\nbegin\n\t$2\nend $1;"),
"q_exam", ("process", "process ($1) is\nbegin\n\t$2\nend process;"),
"q_add", ("pure", ""),
"q_full", ("range", ""),
"async$and$array", ("record", "record\n\t$1\nend record;"),
"async$nand$array", ("register", ""),
"async$or$array", ("reject", ""),
"async$nor$array", ("rem", ""),
"sync$and$array", ("report", "report $1 severity $2;"),
"sync$nand$array", ("return", "return $1;"),
"sync$or$array", ("rol", ""),
"sync$nor$array", ("ror", ""),
"countones", ("select", "select\n\t$1\nend select;"),
"onehot0", ("severity", ""),
"fatal", ("signal", "signal $1 : $2 := $3;"),
"warning", ("shared", ""),
"dist_chi_square", ("sla", ""),
"dist_exponential", ("sll", ""),
"dist_poisson", ("sra", ""),
"dist_uniform", ("srl", ""),
"countbits", ("subtype", "subtype $1 is $2;"),
"onehot", ("then", ""),
"isunknown", ("to", ""),
"coverage_get_max", ("transport", ""),
"coverage_merge", ("type", "type $1 is $2;"),
"get_coverage", ("unaffected", ""),
"load_coverage_db", ("units", "units $1;\n\t$2\nend units;"),
"clog2", ("until", ""),
"ln", ("use", "use $1;"),
"log10", ("variable", "variable $1 : $2 := $3;"),
"exp", ("wait", "wait on $1;"),
"sqrt", ("when", ""),
"pow", ("while", "while $1 loop\n\t$2\nend loop;"),
"floor", ("with", "with $1 select\n\t$2\nend select;"),
"ceil", ("xnor", ""),
"sin", ("xor", ""),
"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,13 +1,17 @@
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,204 +1,152 @@
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}; 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}};
#[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 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()?;
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); // 等待解析完成
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()?;
// 获取当前这行的结果和当前光标所在的 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() {
"." => { "." => {
info!("trigger dot completion"); // 用户按下 . 如果是在例化 scope 中,则补全对应的 port
get_dot_completion(server, &line_text, uri, &pos, &language_id) get_dot_completion(server, &line_text, uri, &pos, &language_id)
}, },
"$" => Some(CompletionList { "$" => {
// 用户按下 $ 补全系统函数
Some(CompletionList {
is_incomplete: false, is_incomplete: false,
items: server.sys_tasks.clone(), 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)
}, },
"\"" => { "\"" => {
info!("trigger include"); // 用户按下 " ,如果开头有 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 => {
let mut comps = server.srcs.get_completions( // 1. 先根据 AST 获取上下文补全项
// 去除如下几种情况module textmacro
let mut completion_items = server.db.get_completions(
&token, &token,
file.text.pos_to_byte(&doc.position), source.text.pos_to_byte(&doc.position),
&doc.text_document.uri, &doc.text_document.uri,
)?; )?;
info!("current completion token: {}", token); // info!("current completion token: {}", token);
// complete keywords // 2. 根据 token 加入关键词
comps.items.extend::<Vec<CompletionItem>>( // TODO: 考虑使用前缀树进行优化
server.key_comps completion_items.items.extend::<Vec<CompletionItem>>(
server.vlog_keyword_completion_items
.iter() .iter()
.filter(|x| x.label.starts_with(&token)) .filter(|x| is_character_ordered_match(&token, &x.label))
.cloned() .cloned()
.collect(), .collect(),
); );
// 加入例化自动补全的 // 3. 根据 token 加入系统函数
comps.items.extend::<Vec<CompletionItem>>( // TODO: 考虑使用前缀树进行优化
completion_items.items.extend::<Vec<CompletionItem>>(
server.vlog_sys_tasks_completion_items
.iter()
.filter(|x| is_character_ordered_match(&token, &x.label))
.cloned()
.collect(),
);
// 4. 加入例化自动补全的
completion_items.items.extend::<Vec<CompletionItem>>(
make_module_completions(server, &token, &language_id) make_module_completions(server, &token, &language_id)
); );
comps.items.dedup_by_key(|i| i.label.clone()); // 5. 加入宏的自动补全
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); // info!("invoked return completion_items {:?}", completion_items);
Some(comps) Some(completion_items)
} }
_ => None, _ => None,
}, },
None => { None => {
let trigger = prev_char(&file.text, &doc.position); return None;
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();
/// get the previous non-whitespace character instantiations.iter().map(|line| {
fn prev_char(text: &Rope, pos: &Position) -> char { let trimmed = line.trim_start();
let char_idx = text.pos_to_char(pos); if trimmed.contains('.') {
if char_idx > 0 { format!("\t{}", trimmed)
for i in (0..char_idx).rev() {
let res = text.char(i);
if !res.is_whitespace() {
return res;
}
}
' '
} else { } else {
' ' trimmed.to_string()
} }
}).collect::<Vec<String>>().join("\n")
} }
/// attempt to get the token the user was trying to complete, by fn make_primitives_module_profile_code(text: &str) -> String {
/// filtering out characters unneeded for name resolution let mut lines: Vec<&str> = text.split_inclusive('\n').collect();
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 if lines.len() > 1 {
while c.is_some() && (c.unwrap() != ';') { lines.remove(0);
c = char_iter.prev(); lines.pop();
}
// 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
}
} }
lines.join("")
}
fn make_instantiation_code(module: &crate::core::hdlparam::Module) -> String { fn make_instantiation_code(module: &crate::core::hdlparam::Module) -> String {
@ -261,44 +209,78 @@ 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 path_to_files = server.srcs.hdl_param.path_to_hdl_file.read().unwrap(); let module_name_to_path = hdl_param.module_name_to_path.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 (path_string, hdl_file) in path_to_files.iter() { for module_name in module_name_to_path.keys() {
for module in &hdl_file.fast.content { if !module_name.to_string().to_lowercase().starts_with(&prefix) {
if !module.name.to_string().to_lowercase().starts_with(&prefix) {
continue; continue;
} }
let insert_text = make_instantiation_code(module); if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(&module_name) {
let module_profile = make_module_profile_code(module); 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 path_uri = Url::from_file_path(path_string.to_string()).unwrap().to_string(); let (insert_text, module_profile, define_info) = if file_type == "primitives" {
let primitive_map = server.db.primitive_text.name_to_text.read().unwrap();
if let Some(text) = primitive_map.get(&module.name) {
insert_text.push(make_primitives_instantiation_code(text));
(
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_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})");
(
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("module instantiation".to_string()), detail: Some(detail),
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("0001".to_string()), sort_text: Some("0".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()
@ -307,5 +289,35 @@ fn make_module_completions(
} }
} }
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
}
}

525
src/completion/sys_tasks.rs Normal file
View File

@ -0,0 +1,525 @@
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,115 +1,118 @@
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 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); server.db.wait_parse_ready(&path_string, false);
let file = server.srcs.get_file(file_id)?; let source = server.db.get_source(&path_string)?;
let file = file.read().ok()?; let source = source.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(
&file.text, &source.text,
line_text.clone(), line_text.clone(),
doc.position, doc.position,
); );
// info!("trigger completion token: {}", token); let project = server.db.vhdl_project.read().ok()?;
// 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"); "." => {
// get_dot_completion(server, &line_text, uri, &pos, &language_id) info!("trigger vhdl dot completion");
// }, let mut completion_items = server.db.get_completions(
// "$" => Some(CompletionList {
// is_incomplete: false,
// items: server.sys_tasks.clone(),
// }),
// "`" => Some(CompletionList {
// is_incomplete: false,
// items: server.directives.clone(),
// }),
// "/" => {
// info!("trigger include");
// include_path_completion(&doc.text_document.uri, &line_text, pos)
// },
// "\"" => {
// info!("trigger include");
// include_path_completion(&doc.text_document.uri, &line_text, pos)
// }
// _ => None,
// }
// }
// CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None,
CompletionTriggerKind::INVOKED => {
let mut comps = server.srcs.get_completions(
&token, &token,
file.text.pos_to_byte(&doc.position), source.text.pos_to_byte(&doc.position),
&doc.text_document.uri, &doc.text_document.uri,
)?; )?;
// complete keywords // completion_items.items.extend(vhdl_project_completion_items);
comps.items.extend::<Vec<CompletionItem>>(
server.key_comps completion_items.items.dedup_by_key(|i| i.label.to_string());
Some(completion_items)
},
_ => None,
}
}
CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None,
// 一般情况下根据字符触发的补全项目
CompletionTriggerKind::INVOKED => {
// 1. 先根据 AST 获取上下文补全项
let mut completion_items = server.db.get_completions(
&token,
source.text.pos_to_byte(&doc.position),
&doc.text_document.uri,
)?;
// 2. 根据 token 再加入关键词
completion_items.items.extend::<Vec<CompletionItem>>(
server.vhdl_keyword_completiom_items
.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());
Some(comps) // 3. 加入例化自动补全的
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?))
@ -181,3 +184,115 @@ 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,9 +1,10 @@
use std::{collections::HashMap, fs::{self}, hash::{DefaultHasher, Hash, Hasher}, path::PathBuf, sync::{Arc, RwLock}}; use std::{collections::HashMap, fs::{self}, hash::{DefaultHasher, Hash, Hasher}, path::PathBuf, str::FromStr, 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}; use crate::utils::{file_size_in_kb, get_last_modified_time, k_deserialize, k_serialize, to_escape_path};
use super::hdlparam::FastHdlparam; use super::hdlparam::FastHdlparam;
@ -25,11 +26,32 @@ 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 内容
@ -39,27 +61,67 @@ 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 meta_path = root_dir.join(meta_name); let cache_info = Arc::new(RwLock::new(CacheInfo {
let meta = get_or_init_meta(&meta_path); version: None,
parser_cache: None,
// 如果不存在 root dir则创建 linter_cache: None,
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(meta)) meta: Arc::new(RwLock::new(HashMap::<String, CacheItem>::new()))
} }
} }
pub fn is_big_file(&self, path: &PathBuf) -> bool { pub fn start(&self, version: &str) {
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;
} }
@ -83,7 +145,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 !self.is_big_file(path) { if !CacheManager::is_big_file(path) {
return CacheResult::NotNeedCache return CacheResult::NotNeedCache
} }
let path_string = path.to_str().unwrap(); let path_string = path.to_str().unwrap();
@ -112,6 +174,7 @@ 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);
@ -128,10 +191,13 @@ 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:?}");
@ -141,6 +207,7 @@ impl CacheManager {
let _ = k_serialize(&cache_save_path, fast); let _ = k_serialize(&cache_save_path, fast);
}); });
} }
}
} }

View File

@ -6,6 +6,8 @@ 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,
@ -21,6 +23,12 @@ 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)]
@ -103,6 +111,13 @@ 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 的位置关系
@ -142,6 +157,70 @@ 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,
@ -188,8 +267,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.end.line; new_port_range.start.line = self.range.start.line;
new_port_range.start.character = self.range.end.character + 1; new_port_range.start.character = self.range.start.character + 1;
} }
new_port_range.end.line += 1; new_port_range.end.line += 1;
@ -205,8 +284,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.end.line; new_param_range.start.line = self.range.start.line;
new_param_range.start.character = self.range.end.character; new_param_range.start.character = self.range.start.character + 1;
new_param_range.end.line += 1; new_param_range.end.line += 1;
return new_param_range; return new_param_range;
} }
@ -215,9 +294,19 @@ 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>,
@ -232,9 +321,16 @@ 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>
} }
@ -267,7 +363,10 @@ pub struct Macro {
pub struct FastHdlparam { pub struct FastHdlparam {
#[serde(rename = "macro")] #[serde(rename = "macro")]
pub fast_macro: Macro, pub fast_macro: Macro,
pub content: Vec<Module> #[serde(rename = "fileType")]
pub file_type: String,
pub content: Vec<Module>,
pub entitys: Vec<Entity>
} }
impl FastHdlparam { impl FastHdlparam {
@ -284,6 +383,7 @@ 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(),
@ -292,6 +392,56 @@ 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;
@ -345,15 +495,21 @@ impl FastHdlparam {
} }
pub struct HdlFile { pub struct HdlFile {
/// 专注于模块树构建和粗粒度 AST 信息的数据结构
pub fast: FastHdlparam, pub fast: FastHdlparam,
pub name_to_module: HashMap<String, Module> /// 名字到 module 映射的 map
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>>,
} }
@ -366,8 +522,14 @@ impl HdlParam {
} }
} }
/// 根据 path 更新 fast /// 根据 path 更新 fast 和 parse_result
pub fn update_fast(&self, path: String, fast: FastHdlparam) { pub fn update_hdl_file(
&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();
@ -380,7 +542,7 @@ impl HdlParam {
} }
} }
let file = HdlFile { fast, name_to_module }; let file = HdlFile { fast, name_to_module, parse_result, ast_like };
fast_map.insert(path, file); fast_map.insert(path, file);
} }
@ -399,6 +561,52 @@ 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

@ -5,3 +5,7 @@ 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

@ -0,0 +1,343 @@
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,8 +1,10 @@
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 {
@ -63,9 +65,23 @@ pub fn copy_scopes(scopes: &[Box<dyn Scope>]) -> Vec<Box<dyn Scope>> {
scope_decs scope_decs
} }
/// 用于定义一个 Symbol 或者 Scope 的内部变量 /// Definition 是所有非 scope 类型 symbol 的父类
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;
@ -83,9 +99,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;
@ -113,23 +129,41 @@ 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 lower_case_token = token.to_lowercase(); // let completion_idents: Vec<String> = completions.iter().map(|x| x.label.clone()).collect();
// now that we are in the users scope, we can attempt to find a relevant completion
// we proceed back upwards through the scope tree, adding any definitions that match
// the users token
let completion_idents: Vec<String> = completions.iter().map(|x| x.label.clone()).collect();
for def in self.defs() {
if !completion_idents.contains(&def.ident()) && def.ident().to_lowercase().starts_with(&lower_case_token) { // 寻找前缀相同的 symbol
completions.push(def.completion()); // 这里面并没有 macro
for symbol in self.defs() {
// 此处的去重会在外部完成
let symbol_name = symbol.ident();
// 此处仍然会给出 module 的补全,但是此处的只是名字的补全,而不是自动例化
match &symbol.def_type() {
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());
@ -138,41 +172,6 @@ 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
@ -220,19 +219,23 @@ 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(),
deprecated: None, range: Range::new(
range: Range::new(doc.byte_to_pos(scope.start()), doc.byte_to_pos(scope.end())), doc.byte_to_pos(scope.start()),
doc.byte_to_pos(scope.end())
),
selection_range: Range::new( selection_range: Range::new(
doc.byte_to_pos(scope.byte_idx()), doc.byte_to_pos(scope.start()),
doc.byte_to_pos(scope.byte_idx() + scope.ident().len()), doc.byte_to_pos(scope.end())
), ),
children: Some(scope.document_symbols(uri, doc)), children: Some(scope.document_symbols(uri, doc)),
tags: None, tags: None,
deprecated: None
}) })
} }
} }
@ -356,38 +359,18 @@ 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)]
@ -449,9 +432,6 @@ impl Definition for GenericDec {
..CompletionItem::default() ..CompletionItem::default()
} }
} }
fn dot_completion(&self, _: &GenericScope) -> Vec<CompletionItem> {
Vec::new()
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -516,9 +496,6 @@ impl Definition for PackageImport {
..CompletionItem::default() ..CompletionItem::default()
} }
} }
fn dot_completion(&self, _: &GenericScope) -> Vec<CompletionItem> {
Vec::new()
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -587,9 +564,6 @@ 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 {
@ -669,9 +643,6 @@ 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)]
@ -734,19 +705,12 @@ impl Definition for ModInst {
..CompletionItem::default() ..CompletionItem::default()
} }
} }
fn dot_completion(&self, scope_tree: &GenericScope) -> Vec<CompletionItem> {
for scope in &scope_tree.scopes {
if scope.ident() == self.mod_ident {
return scope
.defs()
.iter()
.filter(|x| !x.starts_with(&scope.ident()))
.map(|x| x.completion())
.collect();
}
}
Vec::new()
} }
pub struct VhdlProject {
pub project: Project,
pub std_config: vhdl_lang::Config,
pub config_file_strs: Vec<String>
} }
#[derive(Debug)] #[derive(Debug)]
@ -815,19 +779,6 @@ 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 {
@ -920,19 +871,6 @@ 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 {

231
src/core/scope_tree/mod.rs Normal file
View File

@ -0,0 +1,231 @@
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

@ -1,8 +1,12 @@
use crate::definition::def_types::*; use super::common::*;
use crate::definition::match_definitions; use super::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();
@ -43,6 +47,8 @@ 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;
@ -738,7 +744,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, end: x.end.max(var.byte_idx + 1),
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,
@ -802,6 +808,7 @@ 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!(
@ -897,6 +904,7 @@ 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,
@ -2339,6 +2347,7 @@ pub fn text_macro_def(
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,
@ -2347,8 +2356,13 @@ 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

@ -2,6 +2,7 @@ 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;
@ -12,7 +13,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, InstParameter, InstPort, Macro}; use super::hdlparam::{self, FastHdlparam, Include, 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,38 +73,64 @@ pub fn sv_parser(path: &str) -> Option<FastHdlparam> {
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 Ok(fast) = make_fast_from_syntaxtree(&syntax_tree) {
if let Some(syntax_tree) = result { return Some(fast);
if let Ok(hdlparam) = make_fast_from_syntaxtree(&syntax_tree, &path) {
return Some(hdlparam);
} }
} }
None None
} }
pub fn make_fast_from_syntaxtree(syntax_tree: &SyntaxTree, path: &PathBuf) -> Result<FastHdlparam, std::io::Error> { pub fn make_fast_from_syntaxtree(
syntax_tree: &SyntaxTree
) -> Result<FastHdlparam, std::io::Error> {
// 对不同操作系统文件路径的支持 // 对不同操作系统文件路径的支持
let path = to_escape_path(path); let mut fast: FastHdlparam = FastHdlparam {
let mut hdlparam = FastHdlparam {
fast_macro: Macro { fast_macro: Macro {
defines: Vec::new(), defines: Vec::new(),
errors: Vec::new(), errors: Vec::new(),
includes: get_includes(&path), includes: Vec::<Include>::new(),
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();
@ -143,20 +170,20 @@ pub fn make_fast_from_syntaxtree(syntax_tree: &SyntaxTree, path: &PathBuf) -> Re
}; };
let define_range = Range { start: start_pos, end: end_pos }; let define_range = Range { start: start_pos, end: end_pos };
hdlparam.add_define(name, replacement, define_range, params_vec); fast.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 = get_pp_range(&doc, RefNode::ModuleDeclaration(x)); let module_range = Range { start: start_pos.clone(), end: start_pos };
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();
@ -187,7 +214,7 @@ pub fn make_fast_from_syntaxtree(syntax_tree: &SyntaxTree, path: &PathBuf) -> Re
}; };
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 };
hdlparam.add_parameter(name, net_type, init, param_range); fast.add_parameter(name, net_type, init, param_range);
} }
} }
_ => () _ => ()
@ -230,7 +257,7 @@ pub fn make_fast_from_syntaxtree(syntax_tree: &SyntaxTree, path: &PathBuf) -> Re
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) };
hdlparam.add_port(name, dir_type, net_type, width.as_str(), port_range); fast.add_port(name, dir_type, net_type, width.as_str(), port_range);
} }
} }
} }
@ -279,7 +306,7 @@ pub fn make_fast_from_syntaxtree(syntax_tree: &SyntaxTree, path: &PathBuf) -> Re
_ => "1".to_string() _ => "1".to_string()
}; };
hdlparam.add_port(name, ansi_port_last_dir, net_type, width.as_str(), port_range); fast.add_port(name, ansi_port_last_dir, net_type, width.as_str(), port_range);
} }
} }
RefNode::ModuleInstantiation(x) => { RefNode::ModuleInstantiation(x) => {
@ -311,7 +338,7 @@ pub fn make_fast_from_syntaxtree(syntax_tree: &SyntaxTree, path: &PathBuf) -> Re
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();
hdlparam.add_instance( fast.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
@ -337,7 +364,7 @@ pub fn make_fast_from_syntaxtree(syntax_tree: &SyntaxTree, path: &PathBuf) -> Re
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();
hdlparam.add_instance( fast.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
@ -346,18 +373,36 @@ pub fn make_fast_from_syntaxtree(syntax_tree: &SyntaxTree, path: &PathBuf) -> Re
_ => () _ => ()
} }
} }
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(hdlparam) Ok(fast)
} }
// 获取 port 或者 param 的 range // 获取 port 或者 param 的 range
/// 返回的四元组:(start_line, start_character, end_line, end_character) /// 返回的四元组:(start_line, start_character, end_line, end_character)
fn get_pp_range(doc: &Rope, node: RefNode) -> Range { pub 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),
@ -387,7 +432,7 @@ fn get_first_last_locate(node: RefNode) -> Option<(Locate, Locate)> {
} }
} }
fn get_instance_params(syntax_tree: &SyntaxTree, doc: &Rope, node: RefNode) -> Vec<InstParameter> { pub 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 {
@ -452,7 +497,7 @@ fn get_instance_params(syntax_tree: &SyntaxTree, doc: &Rope, node: RefNode) -> V
parameters parameters
} }
fn get_instance_ports(syntax_tree: &SyntaxTree, doc: &Rope, node: RefNode) -> Vec<InstPort> { pub 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) {
@ -629,7 +674,7 @@ fn parse_expression(syntax_tree: &SyntaxTree, x: &sv_parser::NeedParseExpression
} }
} }
fn get_identifier(node: RefNode) -> Option<Locate> { pub 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)) => {
@ -645,62 +690,14 @@ fn get_identifier(node: RefNode) -> Option<Locate> {
} }
} }
fn get_position(doc: &Rope, locate: Locate, offset: usize) -> Position { pub 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, hdlparam: &mut FastHdlparam) { fn update_module_range(path: &PathBuf, fast: &mut FastHdlparam) {
let file = File::open(path).unwrap(); let file = File::open(path).unwrap();
let reader = BufReader::new(file); let reader = BufReader::new(file);
@ -720,7 +717,7 @@ fn update_module_range(path: &PathBuf, hdlparam: &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() {
hdlparam.update_module_range(&module_name, (line_number + 1) as u32, current_offset as u32); fast.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,50 +1,22 @@
use std::collections::HashSet;
use std::path::PathBuf; use std::path::PathBuf;
use vhdl_lang::ast::DesignFile; use log::info;
use vhdl_lang::{kind_str, Token, VHDLParser, VHDLStandard}; use vhdl_lang::ast::{ArchitectureBody, ConcurrentStatement, DesignFile, Designator, EntityDeclaration, InstantiatedUnit, InterfaceDeclaration, InterfaceList, LabeledConcurrentStatement, Mode, ModeIndication, Name};
use vhdl_lang::{kind_str, HasTokenSpan, Token, TokenAccess, TokenSpan, VHDLParser, VHDLStandard};
use super::hdlparam::*; use super::hdlparam::*;
#[allow(unused)] pub fn vhdl_parse_str(path: &PathBuf, code: &str) -> Option<DesignFile> {
pub fn vhdl_parser(path: &str) -> FastHdlparam {
// The path of SystemVerilog source file
let path = PathBuf::from(path);
let mut hdlparam = FastHdlparam {
fast_macro: Macro {
defines: Vec::new(),
errors: Vec::new(),
includes: Vec::new(),
invalid: Vec::new()
},
content: Vec::new()
};
let parser = VHDLParser::new(VHDLStandard::VHDL2008);
let mut diagnostics = Vec::new();
let (_, design_file) = parser.parse_design_file(&path, &mut diagnostics).unwrap();
let mut all_tockens = Vec::new();
for (tokens, _) in design_file.design_units {
all_tockens.extend(tokens);
}
hdlparam.content.extend(parse_tokens(all_tockens));
hdlparam
}
pub fn vhdl_parse(path: &PathBuf) -> Option<DesignFile> {
let mut diagnostics = Vec::new(); let mut diagnostics = Vec::new();
let parser = VHDLParser::new(VHDLStandard::VHDL2008); let parser = VHDLParser::new(VHDLStandard::VHDL2008);
if let Ok((_, design_file)) = parser.parse_design_file(path, &mut diagnostics) { if let Ok((_, design_file)) = parser.parse_vhdl_str(code, path, &mut diagnostics) {
return Some(design_file); return Some(design_file);
} }
None None
} }
pub fn make_fast_from_design_file(design_file: &DesignFile) -> Option<FastHdlparam> { 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(),
@ -52,531 +24,290 @@ pub fn make_fast_from_design_file(design_file: &DesignFile) -> Option<FastHdlpar
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()
}; };
let mut all_tockens = Vec::new(); // info!("arch and entity {arch_and_entity:#?}");
for (tokens, _) in &design_file.design_units {
all_tockens.extend(tokens.clone()); arch_and_entity.iter().for_each(|units| {
match units {
(Some((arch, arch_tokens)), entity_units) => {
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);
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());
});
} }
hdlparam.content.extend(parse_tokens(all_tockens)); 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| {
match statement.statement.item {
ConcurrentStatement::Instance(_) => true,
_ => false
}
})
.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);
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());
});
}
}
(None, None) => ()
}
});
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())
);
#[allow(unused)] let mut parsed_instance = Instance {
fn parse_tokens(tokens: Vec<Token>) -> Vec<Module> {
let mut modules = Vec::new();
let mut last_module_name = String::new();
let mut instance_type = HashSet::new();
let mut i = 0;
while i < tokens.len() {
let token = &tokens[i];
match kind_str(token.kind) {
"entity" => {
let start_pos = tokens[i].pos.range.start;
i += 1;
let entity_name = get_value(&tokens[i]);
// println!("entity name {:?}", entity_name);
if (i >= 2 as usize) && (kind_str(tokens[i-2].kind) != "use") || (i < 2) {
let mut end = i;
while (
end+1 < tokens.len()) &&
!(kind_str(tokens[end].kind) == "end" &&
(kind_str(tokens[end+1].kind) == "entity" || get_value(&tokens[end+1]) == entity_name)) {
end += 1;
}
let end_pos = if end+1 < tokens.len() && get_value(&tokens[end+1]) == entity_name {
i = end + 1;
tokens[end+2].pos.range.end
} else if end + 3 < tokens.len() {
i = end + 2;
tokens[end+3].pos.range.end
} else {
tokens[end].pos.range.end
};
let module = Module {
name: entity_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 = entity_name.to_string();
modules.push(module);
}
}
"architecture" => {
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, name,
inst_type, inst_type: "".to_string(),
instports: None,
instparams: None, instparams: None,
intstport_assignments: Vec::new(),
intstparam_assignments: Vec::new(), intstparam_assignments: Vec::new(),
range: Range { instports: None,
start: Position { intstport_assignments: Vec::new(),
line: tokens[i-1].pos.range.start.line + 1, range
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;
}
_ => {}
}
i += 1;
}
// println!("{:?}", modules); match &statement.statement.item {
modules ConcurrentStatement::Instance(instance) => {
parsed_instance.inst_type = match &instance.unit {
} InstantiatedUnit::Component(name) | InstantiatedUnit::Configuration(name) => {
parse_instance_name(name)
#[allow(unused)]
fn parse_port(tokens: &[Token], start: usize, is_map: bool) -> (Vec<Port>, usize) {
let mut ports = 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;
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
}
}, },
InstantiatedUnit::Entity(name, arch_name_ref) => {
let name = parse_instance_name(name);
let arch_name = if let Some(arch_name_ref) = arch_name_ref {
format!("({})", arch_name_ref.item.item.name_utf8())
} else {
"".to_string()
}; };
ports.push(port);
} else { name + &arch_name
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; _ => "unknown".to_string()
}
// 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); if let Some(parameter_list) = &instance.generic_map {
width = "[".to_string() + &width_str + "]"; parsed_instance.instparams = Some(get_range_from_token(
end_idx = index-1; tokens.get_token(parameter_list.span().get_start_token()),
} else if kind_str(tokens[i+2].kind) == "range" { tokens.get_token(parameter_list.span().get_end_token())
width = "[".to_string() + &get_value(&tokens[i+3]) + ":" + &get_value(&tokens[i+5]) + "]"; ));
end_idx = i+5;
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 { } else {
width = "1".to_string(); get_range_from_token(
end_idx = i+1; tokens.get_token(association_ele.actual.span().get_start_token()),
} tokens.get_token(association_ele.actual.span().get_end_token())
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); InstParameter { parameter: Some(formal), assign_val: Some(actual), assign_type: AssignType::Named, range: assign_range }
} }).collect::<Vec<InstParameter>>();
i = end_idx;
}
}
_ => {}
}
i += 1;
} }
(ports, i) 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())
#[allow(unused)] ));
fn parse_width(tokens: &[Token], start: usize) -> (String, usize) { parsed_instance.intstport_assignments = port_list.list.items.iter().map(|association_ele| {
let mut width = String::new(); let formal = association_ele.formal.clone().and_then(|formal|
let mut i = start; Some(parse_string_from_tokenspan(formal.span(), tokens))
let mut stack = Vec::new(); ).unwrap_or(
"unknown".to_string()
while i < tokens.len() { );
match kind_str(tokens[i].kind) { let actual = parse_string_from_tokenspan(association_ele.actual.span(), tokens);
"(" => { let assign_range = if association_ele.formal.is_some() {
stack.push(&tokens[i]); 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())
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 { } else {
width = width + kind_str(tokens[i].kind); get_range_from_token(
} tokens.get_token(association_ele.actual.span().get_start_token()),
} else { tokens.get_token(association_ele.actual.span().get_end_token())
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; InstPort { port: Some(formal), assign_val: Some(actual), assign_type: AssignType::Named, range: assign_range }
params.push(param); }).collect::<Vec<InstPort>>();
} 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])
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 { } else {
"unknown".to_string() "unknown".to_string()
}; };
let end_pos = if kind_str(tokens[i+2].kind) == ":=" { let net_type = simple_mode.subtype_indication.type_mark.item.to_string();
tokens[i+2].pos.range.end let width = if let Some(constraint) = &simple_mode.subtype_indication.constraint {
parse_width_from_tokenspan(constraint.span(), tokens)
} else { } else {
tokens[i+1].pos.range.end "1".to_string()
}; };
i = if kind_str(tokens[i+2].kind) == ":=" { let init = if let Some(expression) = &simple_mode.expression {
i + 3 parse_width_from_tokenspan(expression.span(), tokens)
} else { } else {
i + 1 "unknown".to_string()
}; };
for param_token in parameters { (dir_type, net_type, width, init)
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
} }
}, _ => ("unknown".to_string(), "unknown".to_string(), "unknown".to_string(), "unknown".to_string())
}; };
params.push(param); interface_list.push((name, dir_type, net_type, width, init, range));
} }
_ => ()
} }
} });
_ => {} interface_list
}
i += 1;
} }
(params, i) fn parse_string_from_tokenspan(span: TokenSpan, tokens: &Vec<Token>) -> String {
span.iter().map(|id| {
if let Some(token) = tokens.get_token(id) {
if get_value(token) == "None" {
kind_str(token.kind).to_string()
} else {
get_value(token)
}
} else {
"".to_string()
}
}).collect()
} }
fn parse_width_from_tokenspan(span: TokenSpan, tokens: &Vec<Token>) -> String {
// skip '(' and ')'
let width = span.iter().skip(1).take(span.len() - 2).map(|id| {
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 {
"".to_string()
}
}).collect::<String>();
"[".to_string() + width.as_str() + "]"
}
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 {
Position { line: token.pos.end().line, character: token.pos.end().character }
} else {
Position { line: 0, character: 0 }
};
Range { start, end }
}
#[allow(unused)] #[allow(unused)]
fn get_value(token: &Token) -> String { fn get_value(token: &Token) -> String {

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::hdlparam::FastHdlparam, server::LSPServer, utils::{get_word_range_at_position, resolve_path, to_escape_path}}; use crate::{core::{self, hdlparam::{self, 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,8 +84,7 @@ pub fn goto_macro_definition(server: &LSPServer, line: &RopeSlice, pos: Position
Err(_) => return None Err(_) => return None
}; };
let mut target_range = macro_define.range.clone(); let target_range = macro_define.range.to_lsp_range();
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),
@ -103,7 +102,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,
@ -115,16 +114,32 @@ 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.srcs.hdl_param.find_module_by_name(&instance.inst_type) { let module = match server.db.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.srcs.hdl_param.find_module_definition_path(&module.name).unwrap(); let def_path = server.db.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,
@ -144,16 +159,32 @@ 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.srcs.hdl_param.find_module_by_name(&instance.inst_type) { let module = match server.db.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.srcs.hdl_param.find_module_definition_path(&module.name).unwrap(); let def_path = server.db.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,
@ -175,7 +206,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
@ -185,7 +216,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.srcs.hdl_param.path_to_hdl_file.read().unwrap(); let fast_map = server.db.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();
@ -203,11 +234,55 @@ 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> {
if let Some(module) = server.srcs.hdl_param.find_module_by_name(token_name) { let hdl_param = server.db.hdl_param.clone();
let def_path = server.srcs.hdl_param.find_module_definition_path(&module.name).unwrap(); info!("get into goto_module_declaration_definition");
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_uri = Url::from_file_path(PathBuf::from_str(&def_path).unwrap()).unwrap();
let target_range = module.range.clone(); let target_range = module.range.clone();
@ -220,8 +295,49 @@ pub fn goto_module_declaration_definition(
target_selection_range: target_range target_selection_range: target_range
}]; }];
let links = GotoDefinitionResponse::Link(link); let links = GotoDefinitionResponse::Link(link);
return Some(links); 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 mut target_range = module.range.clone();
let target_range = target_range.affine(-1, -1).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)
} 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,25 +1,16 @@
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() {
@ -37,222 +28,3 @@ impl LSPServer {
} }
} }
} }
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,39 +1,42 @@
use crate::utils::get_definition_token; use crate::core::scope_tree::common::{Definition, Scope};
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::*;
use super::{feature::*, Definition, Scope}; pub fn goto_definition(server: &LspServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
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 line_text = file.text.line(pos.line as usize); let path_string = from_uri_to_escape_path_string(uri).unwrap();
// 等待解析完成
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(doc, &line_text, pos) { if let Some(definition) = goto_include_definition(uri, &line_text, pos) {
return Some(definition); return Some(definition);
} }
// match macro // match macro usage
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.srcs.scope_tree.read().ok()?; let scope_tree = server.db.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, uri, pos) {
return Some(definition); return Some(definition);
} }
@ -42,11 +45,11 @@ pub fn goto_definition(server: &LSPServer, params: &GotoDefinitionParams) -> Opt
return Some(definition); return Some(definition);
} }
let byte_idx = file.text.pos_to_byte(&pos); let byte_idx = source.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, uri)?;
let def_pos = file.text.byte_to_pos(def.byte_idx()); let def_pos = source.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,21 +1,50 @@
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, sources::LSPSupport, utils::get_definition_token}; use crate::{server::LspServer, utils::{from_lsp_pos, from_uri_to_escape_path_string, get_definition_token, srcpos_to_location, to_escape_path}};
use super::{Definition, Scope}; pub fn goto_vhdl_definition(server: &LspServer, params: &GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
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 line_text = file.text.line(pos.line as usize); let path_string = from_uri_to_escape_path_string(uri).unwrap();
// 等待解析完成
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);
@ -28,7 +57,7 @@ pub fn goto_vhdl_definition(server: &LSPServer, params: &GotoDefinitionParams) -
// match instance // match instance
let scope_tree = server.srcs.scope_tree.read().ok()?; // let scope_tree = server.db.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) {
@ -40,13 +69,14 @@ 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),
))) // )))
} }

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

@ -0,0 +1,99 @@
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,
}
}

147
src/diagnostics/iverilog.rs Normal file
View File

@ -0,0 +1,147 @@
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,201 +1,327 @@
use crate::server::ProjectConfig; use crate::{server::{LspConfiguration, LspServer}, utils::get_language_id_by_uri};
use regex::Regex; use log::info;
use ropey::Rope; use ropey::Rope;
use std::path::PathBuf; use serde::Deserialize;
use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*; use tower_lsp::lsp_types::*;
use walkdir::DirEntry;
pub mod common;
pub fn get_diagnostics( pub mod iverilog;
pub mod verible;
pub mod verilator;
pub mod vivado;
pub mod modelsim;
pub use common::*;
pub use iverilog::*;
pub use verible::*;
pub use verilator::*;
pub use vivado::*;
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,
#[allow(unused_variables)] files: Vec<Url>, server: &LspServer
conf: &ProjectConfig,
) -> PublishDiagnosticsParams { ) -> PublishDiagnosticsParams {
if !(cfg!(test) && (uri.to_string().starts_with("file:///test"))) { let mut diagnostics = Vec::<Diagnostic>::new();
let diagnostics = { let language_id = get_language_id_by_uri(&uri);
if conf.verilator.syntax.enabled {
if let Ok(path) = uri.to_file_path() { let configuration = server.configuration.read().unwrap();
match verilator_syntax(
rope, // 选择对应语言的 lsp
path, let linter_configuration = match language_id.as_str() {
&conf.verilator.syntax.path, "vhdl" => Some(&configuration.vhdl_linter_configuration),
&conf.verilator.syntax.args, "verilog" => Some(&configuration.vlog_linter_configuration),
"systemverilog" => Some(&configuration.svlog_linter_configuration),
_ => None
};
if linter_configuration.is_none() {
info!("未知语言 {} 试图发起诊断", language_id);
return PublishDiagnosticsParams {
uri, diagnostics,
version: None
};
}
let linter_configuration = linter_configuration.unwrap();
// 根据配置决定使用哪一个诊断器
// 外层代码需要保证只有一个 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 => {
// info!("verilator linter enter");
if let Some(diag) = &mut config.verilator.provide_diagnostics(&uri, rope, server) {
diagnostics.append(diag);
}
}
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
) { ) {
Some(diags) => diags, let mut configuration = server.configuration.write().unwrap();
None => Vec::new(),
}
} else {
Vec::new()
}
} else if conf.verible.syntax.enabled {
match verible_syntax(rope, &conf.verible.syntax.path, &conf.verible.syntax.args) {
Some(diags) => diags,
None => Vec::new(),
}
} else {
Vec::new()
}
};
PublishDiagnosticsParams {
uri,
diagnostics,
version: None,
}
} else {
PublishDiagnosticsParams {
uri,
diagnostics: Vec::new(),
version: None,
}
}
}
// 选择对应语言的 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)
}
/// 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),
}
}
/// 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()?;
static RE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
let re = RE.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()
});
// 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.stderr).ok()?;
let filtered_output = raw_output
.lines()
.filter(|line| line.starts_with('%'))
.collect::<Vec<&str>>();
for error in filtered_output {
let caps = match re.captures(error) {
Some(caps) => caps,
None => continue,
}; };
// check if diagnostic is for this file, since verilator can provide diagnostics for if linter_configuration.is_none() {
// included files info!("未知语言 {} 试图配置诊断器", language_id);
if caps.name("filepath")?.as_str() != file_path.to_str().unwrap_or("") { return;
continue;
} }
let severity = verilator_severity(caps.name("severity")?.as_str());
let line: u32 = caps.name("line")?.as_str().to_string().parse().ok()?; let linter_configuration = linter_configuration.unwrap();
let col: u32 = caps.name("col").map_or("1", |m| m.as_str()).parse().ok()?;
let pos = Position::new(line - 1, col - 1); linter_configuration.iverilog.linter.enabled = false;
let msg = match severity { linter_configuration.verilator.linter.enabled = false;
Some(DiagnosticSeverity::ERROR) => caps.name("message")?.as_str().to_string(), linter_configuration.verible.linter.enabled = false;
Some(DiagnosticSeverity::WARNING) => format!( linter_configuration.modelsim.linter.enabled = false;
"{}: {}", linter_configuration.vivado.linter.enabled = false;
caps.name("warning_type")?.as_str(),
caps.name("message")?.as_str() match linter_name {
"iverilog" => {
linter_configuration.iverilog.linter.enabled = true;
linter_configuration.iverilog.linter.path = linter_path.to_string();
let invoke_name = linter_configuration.iverilog.get_invoke_name();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
}
"verilator" => {
linter_configuration.verilator.linter.enabled = true;
linter_configuration.verilator.linter.path = linter_path.to_string();
let invoke_name = linter_configuration.verilator.get_invoke_name();
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
}
"verible" => {
linter_configuration.verible.linter.enabled = true;
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);
}
"vivado" => {
linter_configuration.vivado.linter.enabled = true;
linter_configuration.vivado.linter.path = linter_path.to_string();
let invoke_name = {
if language_id == "vhdl" {
linter_configuration.vivado.get_invoke_name()
} else {
linter_configuration.vivado.get_invoke_name()
}
};
info!("{} 诊断器 {} 已经激活, 工作负载为 {}", language_id, linter_name, invoke_name);
}
_ => {
info!("未找到匹配的诊断器: {}", linter_name);
}
}
}
#[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()
]
), ),
_ => "".to_string(), verible: VeribleConfiguration::new(
}; language_id,
diags.push(Diagnostic::new( "verible-verilog-syntax",
Range::new(pos, pos), vec![]
severity, ),
None, verilator: VerilatorConfiguration::new(
Some("verilator".to_string()), language_id,
msg, "verilator",
None, vec![]
None, ),
)); modelsim: ModelsimConfiguration::new(
} language_id,
Some(diags) "vcom",
} else { vec![
None "-quiet".to_string(),
} "-nologo".to_string(),
} "-2008".to_string()
]
/// syntax checking using verible-verilog-syntax ),
fn verible_syntax( vivado: VivadoConfiguration::new(
rope: &Rope, language_id,
verible_syntax_path: &str, "xvhdl",
verible_syntax_args: &[String], vec![
) -> Option<Vec<Diagnostic>> { "--nolog".to_string()
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()?; "systemverilog" => {
let output = child.wait_with_output().ok()?; DigitalLinterConfiguration {
if !output.status.success() { iverilog: IverilogConfiguration::new(
let mut diags: Vec<Diagnostic> = Vec::new(); language_id,
let raw_output = String::from_utf8(output.stdout).ok()?; "iverilog",
for error in raw_output.lines() { vec![
let caps = re.captures(error)?; "-g2012".to_string()
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()) { verible: VeribleConfiguration::new(
Some(Ok(e)) => Some(e), language_id,
None => None, "verible-verilog-syntax",
Some(Err(_)) => return None, vec![]
}; ),
let start_pos = Position::new(line - 1, startcol - 1); verilator: VerilatorConfiguration::new(
let end_pos = Position::new(line - 1, endcol.unwrap_or(startcol) - 1); language_id,
diags.push(Diagnostic::new( "verilator",
Range::new(start_pos, end_pos), vec![
Some(DiagnosticSeverity::ERROR), "--lint-only".to_string(),
None, "-sv".to_string(),
Some("verible".to_string()), "-Wall".to_string()
caps.name("message")?.as_str().to_string(), ]
None, ),
None, modelsim: ModelsimConfiguration::new(
)); language_id,
} "vlog",
Some(diags) vec![
} else { "-quiet".to_string(),
None "-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()
]
)
}
}
}
} }
} }

143
src/diagnostics/modelsim.rs Normal file
View File

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

137
src/diagnostics/verible.rs Normal file
View File

@ -0,0 +1,137 @@
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

@ -0,0 +1,272 @@
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
}

257
src/diagnostics/vivado.rs Normal file
View File

@ -0,0 +1,257 @@
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,21 +1,25 @@
use crate::{server::LSPServer, utils::{get_definition_token, get_language_id_by_uri}}; use crate::{server::LspServer, utils::{from_uri_to_escape_path_string, 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();
self.srcs.wait_parse_ready(file_id, false); let path_string = from_uri_to_escape_path_string(uri).unwrap();
let file = self.srcs.get_file(file_id)?;
let file = file.read().ok()?; // 等待解析完成
let line_text = file.text.line(pos.line as usize); self.db.wait_parse_ready(&path_string, false);
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);
@ -32,7 +36,7 @@ impl LSPServer {
"verilog" | "systemverilog" => sv::document_highlight( "verilog" | "systemverilog" => sv::document_highlight(
self, self,
&token, &token,
&file, &source,
pos, pos,
&uri &uri
), ),

View File

@ -3,40 +3,39 @@ 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::{definition::{get_ident, Scope}, server::LSPServer, sources::{LSPSupport, ParseIR, Source}}; use crate::{server::LspServer, sources::{AstLike, LSPSupport, Source}, utils::from_uri_to_escape_path_string};
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.srcs.scope_tree.read().ok()?; let scope_tree = server.db.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 = let byte_idx = match scope_tree.as_ref()?.get_definition(token, file.text.pos_to_byte(&pos), uri) {
match scope_tree
.as_ref()?
.get_definition(token, file.text.pos_to_byte(&pos), uri)
{
Some(def) => def.byte_idx, Some(def) => def.byte_idx,
None => file.text.pos_to_byte(&pos), None => file.text.pos_to_byte(&pos),
}; };
let syntax_tree = file.parse_ir.as_ref()?;
match syntax_tree { // 获取对应的 AST
ParseIR::SyntaxTree(syntax_tree) => { let path_to_hdl_file = server.db.hdl_param.path_to_hdl_file.read().unwrap();
if let Some(hdl_file) = path_to_hdl_file.get(&path_string) {
if let Some(AstLike::Svlog(syntax_tree)) = &hdl_file.ast_like {
let references = all_identifiers(&syntax_tree, &token); let references = all_identifiers(&syntax_tree, &token);
Some( let highlights = scope_tree.as_ref()?.document_highlights(&uri, &file.text, references, byte_idx);
scope_tree Some(highlights)
.as_ref()? } else {
.document_highlights(&uri, &file.text, references, byte_idx),
)
},
_ => {
info!("error happen in [sv_document_highlight]");
None None
} }
} else {
None
} }
} }

View File

@ -2,42 +2,29 @@ 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::{definition::{get_ident, Scope}, server::LSPServer, sources::{LSPSupport, ParseIR, Source}}; use crate::{server::LspServer, sources::{LSPSupport, 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.srcs.scope_tree.read().ok()?; let scope_tree = server.db.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 = let byte_idx = match scope_tree.as_ref()?.get_definition(token, file.text.pos_to_byte(&pos), uri) {
match scope_tree
.as_ref()?
.get_definition(token, file.text.pos_to_byte(&pos), uri)
{
Some(def) => def.byte_idx, Some(def) => def.byte_idx,
None => file.text.pos_to_byte(&pos), None => file.text.pos_to_byte(&pos),
}; };
let syntax_tree = file.parse_ir.as_ref()?;
match syntax_tree { // TODO: 完成剩余部分
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 None
} }
}
}
/// return all identifiers in a syntax tree matching a given token /// return all identifiers in a syntax tree matching a given token
fn all_identifiers(syntax_tree: &SyntaxTree, token: &str) -> Vec<(String, usize)> { fn all_identifiers(syntax_tree: &SyntaxTree, token: &str) -> Vec<(String, usize)> {

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,18 +1,22 @@
use crate::{definition::Scope, server::LSPServer}; use crate::{core::scope_tree::common::Scope, server::LspServer, utils::from_uri_to_escape_path_string};
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();
server.srcs.wait_parse_ready(file_id, false); let path_string = from_uri_to_escape_path_string(uri).unwrap();
let file = server.srcs.get_file(file_id)?;
let file = file.read().ok()?; // 等待解析完成
let scope_tree = server.srcs.scope_tree.read().ok()?; server.db.wait_parse_ready(&path_string, false);
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, &file.text), scope_tree.as_ref()?.document_symbols(uri, &source.text),
)) ))
} }

View File

@ -1,19 +1,82 @@
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::{definition::Scope, server::LSPServer}; use vhdl_lang::{EntHierarchy, Token};
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 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_handle = server.db.get_source(&path_string)?;
// 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(
scope_tree.as_ref()?.document_symbols(uri, &file.text), global_project.project
.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

@ -0,0 +1,16 @@
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

@ -0,0 +1,59 @@
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

@ -0,0 +1,30 @@
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,60 +1,61 @@
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>> {
let uri = params.text_document.uri;
info!("formatting {}", &uri);
let file_id = self.srcs.get_id(&uri).to_owned();
self.srcs.wait_parse_ready(file_id, false);
let file = self.srcs.get_file(file_id)?;
let file = file.read().ok()?;
let conf = self.conf.read().unwrap();
if conf.verible.format.enabled {
Some(vec![TextEdit::new(
Range::new(
file.text.char_to_pos(0),
file.text.char_to_pos(file.text.len_chars()),
),
format_document(
&file.text,
None,
&conf.verible.format.path,
&conf.verible.format.args,
)?,
)])
} else {
None None
} // let uri = params.text_document.uri;
// info!("formatting {}", &uri);
// let file_id = self.db.get_id(&uri).to_owned();
// self.db.wait_parse_ready(file_id, false);
// let file = self.db.get_file(file_id)?;
// let file = file.read().ok()?;
// let conf = self.configuration.read().unwrap();
// if conf.verible.format.enabled {
// Some(vec![TextEdit::new(
// Range::new(
// file.text.char_to_pos(0),
// file.text.char_to_pos(file.text.len_chars()),
// ),
// format_document(
// &file.text,
// None,
// &conf.verible.format.path,
// &conf.verible.format.args,
// )?,
// )])
// } else {
// None
// }
} }
pub fn range_formatting(&self, params: DocumentRangeFormattingParams) -> Option<Vec<TextEdit>> { pub fn range_formatting(&self, params: DocumentRangeFormattingParams) -> Option<Vec<TextEdit>> {
let uri = params.text_document.uri;
info!("range formatting {}", &uri);
let file_id = self.srcs.get_id(&uri).to_owned();
self.srcs.wait_parse_ready(file_id, false);
let file = self.srcs.get_file(file_id)?;
let file = file.read().ok()?;
let conf = self.conf.read().unwrap();
if conf.verible.format.enabled {
Some(vec![TextEdit::new(
file.text.char_range_to_range(0..file.text.len_chars()),
format_document(
&file.text,
Some(params.range),
&conf.verible.format.path,
&conf.verible.format.args,
)?,
)])
} else {
None None
} // let uri = params.text_document.uri;
// info!("range formatting {}", &uri);
// let file_id = self.db.get_id(&uri).to_owned();
// self.db.wait_parse_ready(file_id, false);
// let file = self.db.get_file(file_id)?;
// let file = file.read().ok()?;
// let conf = self.configuration.read().unwrap();
// if conf.verible.format.enabled {
// Some(vec![TextEdit::new(
// file.text.char_range_to_range(0..file.text.len_chars()),
// format_document(
// &file.text,
// Some(params.range),
// &conf.verible.format.path,
// &conf.verible.format.args,
// )?,
// )])
// } else {
// None
// }
} }
} }

View File

@ -1,15 +1,18 @@
use std::{path::PathBuf, str::FromStr}; use std::{path::PathBuf, str::FromStr, sync::Arc};
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::hdlparam::{Define, FastHdlparam}, definition::{DefinitionType, GenericDec}, server::LSPServer}; use crate::{core::{self, hdlparam::{Define, FastHdlparam}, primitive_parser::PrimitiveText}, server::LspServer};
use super::{get_word_range_at_position, resolve_path, to_escape_path}; use super::{get_language_id_by_path_str, 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;
@ -29,7 +32,14 @@ fn parse_digit_string(digit_string: &str) -> Option<(&str, &str)> {
return None; return None;
} }
return Some((tag.unwrap(), digit.unwrap())); // 此时4'b0011 被自动分割成了
// 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
}; };
@ -46,13 +56,20 @@ 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 unsigned_decimal = u128::from_str_radix(digit_string, radix); let raw_digit_string = digit_string.replace("_", "");
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;
@ -74,7 +91,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() {
@ -87,7 +104,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.len(), tag, digit) value: format!("{}'{}{}", digit.replace("_", "").len(), tag, digit)
}; };
let markdown = HoverContents::Array(vec![ let markdown = HoverContents::Array(vec![
MarkedString::LanguageString(digit_title), MarkedString::LanguageString(digit_title),
@ -181,7 +198,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("`") {
@ -205,7 +222,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,
@ -218,7 +235,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.srcs.hdl_param.find_module_by_name(&instance.inst_type) { let module = match server.db.hdl_param.find_module_by_name(&instance.inst_type) {
Some(module) => module, Some(module) => module,
None => return None None => return None
}; };
@ -227,19 +244,34 @@ 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);
if file_type == "primitives" {
let primitives_text = server.db.primitive_text.clone();
let params_assignments = &module.instances.first().unwrap().intstparam_assignments;
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 { for param in &module.params {
if token_name == param.name { if token_name == param.name {
let hover = make_param_desc_hover(param, range, language_id); let hover = make_param_desc_hover(&file_type, param, range, language_id);
return Some(hover); return Some(hover);
} }
} }
}
return None; return None;
} }
} }
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.srcs.hdl_param.find_module_by_name(&instance.inst_type) { let module = match server.db.hdl_param.find_module_by_name(&instance.inst_type) {
Some(module) => module, Some(module) => module,
None => return None None => return None
}; };
@ -248,12 +280,27 @@ 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);
if file_type == "primitives" {
let primitives_text = server.db.primitive_text.clone();
let port_assignments = &module.instances.first().unwrap().intstport_assignments;
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 { for port in &module.ports {
if token_name == port.name { if token_name == port.name {
let hover = make_port_desc_hover(port, range, language_id); let hover = make_port_desc_hover(&file_type, port, range, language_id);
return Some(hover); return Some(hover);
} }
} }
}
return None; return None;
} }
} }
@ -266,7 +313,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,
@ -277,7 +324,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.srcs.hdl_param.path_to_hdl_file.read().unwrap(); let fast_map = server.db.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();
@ -294,26 +341,23 @@ pub fn hover_position_port_param(
None None
} }
fn make_port_desc_hover(port: &crate::core::hdlparam::Port, range: &Range, language_id: &str) -> Hover { fn make_primitives_param_desc_hover(
let mut port_desc_array = Vec::<String>::new(); primitives_text: Arc<PrimitiveText>,
port_desc_array.push(port.dir_type.to_string()); primitives_name: &str,
if port.net_type != "unknown" { inst_param: &crate::core::hdlparam::InstParameter,
port_desc_array.push(port.net_type.to_string()); range: &Range, language_id: &str) -> Hover {
}
if port.signed != "unsigned" { let name = inst_param.parameter.clone().unwrap();
port_desc_array.push("signed".to_string()); 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 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: port_desc value: "parameter ".to_string() + &name + " " + &comment,
}; };
Hover { Hover {
contents: HoverContents::Scalar(MarkedString::LanguageString(language_string)), contents: HoverContents::Scalar(MarkedString::LanguageString(language_string)),
@ -321,19 +365,74 @@ fn make_port_desc_hover(port: &crate::core::hdlparam::Port, range: &Range, langu
} }
} }
fn make_param_desc_hover(param: &crate::core::hdlparam::Parameter, range: &Range, language_id: &str) -> Hover { fn make_primitives_port_desc_hover(
let mut param_desc_array = Vec::<String>::new(); primitives_text: Arc<PrimitiveText>,
param_desc_array.push(format!("parameter {}", param.name)); primitives_name: &str,
inst_port: &crate::core::hdlparam::InstPort,
range: &Range, language_id: &str) -> Hover {
if param.init != "unknown" { let name = inst_port.port.clone().unwrap();
param_desc_array.push("=".to_string()); let text_map = primitives_text.name_to_text.read().unwrap();
param_desc_array.push(param.init.to_string()); let comment = if let Some(text) = text_map.get(primitives_name) {
} primitives_text.get_comment(&name, text)
} else {
"".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: param_desc value: "port ".to_string() + &name + " " + &comment,
};
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)),
@ -342,24 +441,60 @@ fn make_param_desc_hover(param: &crate::core::hdlparam::Parameter, range: &Range
} }
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 module_info = { // let test = server.db.hdl_param.module_name_to_path.read().unwrap();
let search_result = || { // info!("module name to path: {:#?}", test);
if let Some(module) = server.srcs.hdl_param.find_module_by_name(token_name) { let hdl_param = server.db.hdl_param.clone();
let path_string = server.srcs.hdl_param.find_module_definition_path(&module.name).unwrap_or("unknown".to_string()); if let Some((module, file_type, def_path)) = hdl_param.find_module_context_by_name(token_name) {
return Some((module, path_string)); match file_type.as_str() {
"common" => {
hover_common_module_declaration(
server,
token_name,
&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 None
}; }
search_result() }
};
if let Some((module, path_string)) = module_info { fn hover_common_module_declaration(
let path_uri = Url::from_file_path(path_string.to_string()).unwrap().to_string(); #[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_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})");
@ -392,6 +527,7 @@ pub fn hover_module_declaration(
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_module_profile_code(&module);
let profile_markdown = LanguageString { let profile_markdown = LanguageString {
language: language_id.to_string(), language: language_id.to_string(),
@ -407,8 +543,113 @@ pub fn hover_module_declaration(
return Some(hover); return Some(hover);
} }
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_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}, `generic` {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_entity_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
};
Some(hover)
} else {
None 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 {
contents: HoverContents::Array(markdowns),
range: None
};
Some(hover)
} else {
None
}
}
/// 根据 module 获取 module 的简单描述代码 /// 根据 module 获取 module 的简单描述代码
@ -508,11 +749,136 @@ 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");
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"); let profile_string = codes.join("\n");
profile_string 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,28 +3,31 @@ 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::{to_escape_path, BracketMatchResult, BracketMatcher}, server::LSPServer, sources::LSPSupport}; use crate::{core::{hdlparam::{Instance, Module}, scope_tree::common::Scope}, hover::{to_escape_path, BracketMatchResult, BracketMatcher}, server::LspServer, sources::LSPSupport};
use super::feature::*; use super::{feature::*, from_uri_to_escape_path_string};
use std::{path::PathBuf, str::FromStr, sync::RwLockReadGuard}; use std::{path::PathBuf, str::FromStr, sync::RwLockReadGuard};
use crate::definition::*; use crate::core::scope_tree::common::*;
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 doc = &params.text_document_position_params.text_document.uri; let uri = &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();
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()?;
let line_text = file.text.line(pos.line as usize); let path_string = from_uri_to_escape_path_string(uri).unwrap();
// 等待解析完成
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);
let language_id = get_language_id_by_uri(doc); let language_id = get_language_id_by_uri(uri);
// match `include // match `include
if let Some(hover) = hover_include(doc, &line_text, pos, &language_id) { if let Some(hover) = hover_include(uri, &line_text, pos, &language_id) {
return Some(hover); return Some(hover);
} }
@ -35,16 +38,14 @@ 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, doc, pos, &language_id) { if let Some(hover) = hover_position_port_param(server, &line_text, uri, 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);
} }
} }
@ -54,14 +55,14 @@ pub fn hover(server: &LSPServer, params: &HoverParams) -> Option<Hover> {
return Some(hover); return Some(hover);
} }
let scope_tree = server.srcs.scope_tree.read().ok()?; let scope_tree = server.db.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, source.text.pos_to_byte(&pos), uri)?;
// 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, &source, uri, pos, &language_id) {
return Some(hover); return Some(hover);
} }
@ -100,7 +101,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("*/") { } else if currentr.ends_with("*/") && line_idx != line - 1 {
multiline = true; multiline = true;
valid = true; valid = true;
} else if currentl.starts_with("/*") { } else if currentl.starts_with("/*") {
@ -171,11 +172,12 @@ 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 {
@ -225,7 +227,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,
@ -238,7 +240,7 @@ fn hover_common_symbol(
// 根据 symbol 的类别进行额外的判断 // 根据 symbol 的类别进行额外的判断
match symbol_definition.def_type { match symbol_definition.def_type {
DefinitionType::ModuleInstantiation => { DefinitionType::ModuleInstantiation => {
let hdlparam = server.srcs.hdl_param.clone(); let hdlparam = server.db.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("\\", "/");
@ -295,11 +297,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.srcs.hdl_param.clone(); let hdlparam = server.db.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);
@ -310,13 +312,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,21 +4,47 @@ 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}, definition::{Definition, DefinitionType, GenericDec, Scope}, hover::{BracketMatchResult, BracketMatcher}, server::LSPServer, sources::LSPSupport}; use crate::{core::hdlparam::{Instance, Module}, hover::{BracketMatchResult, BracketMatcher}, server::LspServer};
use crate::core::scope_tree::common::*;
use super::{feature::hover_format_digit, get_definition_token, get_language_id_by_uri, to_escape_path}; use super::{from_lsp_pos, from_uri_to_escape_path_string, 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 doc = &params.text_document_position_params.text_document.uri; let uri = &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();
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()?;
let line_text = file.text.line(pos.line as usize); let path_string = from_uri_to_escape_path_string(uri).unwrap();
let token: String = get_definition_token(&line_text, pos);
let language_id = get_language_id_by_uri(doc); // 等待解析完成
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 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 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) {
@ -30,11 +56,11 @@ pub fn hover(server: &LSPServer, params: &HoverParams) -> Option<Hover> {
// return Some(hover); // return Some(hover);
// } // }
let scope_tree = server.srcs.scope_tree.read().ok()?; // let scope_tree = server.db.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) {
@ -47,16 +73,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> {
@ -215,7 +241,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,
@ -228,7 +254,7 @@ fn hover_common_symbol(
// 根据 symbol 的类别进行额外的判断 // 根据 symbol 的类别进行额外的判断
match symbol_definition.def_type { match symbol_definition.def_type {
DefinitionType::ModuleInstantiation => { DefinitionType::ModuleInstantiation => {
let hdlparam = server.srcs.hdl_param.clone(); let hdlparam = server.db.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,40 +1,52 @@
use std::{path::PathBuf, str::FromStr}; use std::{collections::HashMap, 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::to_escape_path; use super::{get_language_id_by_path_str, 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 fast_map = server.srcs.hdl_param.path_to_hdl_file.read().unwrap(); let rope = &source.text;
// 先找到 当前 所在的 hdlfile // 先找到 当前 所在的 hdlfile
if let Some(hdl_file) = &fast_map.get(path_string) { let path_to_hdl_file = server.db.hdl_param.path_to_hdl_file.read().unwrap();
info!("enter some"); if let Some(hdl_file) = &path_to_hdl_file.get(path_string) {
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 is_visible_range(&instance.instparams, &visible_range) { if let Some(range) = &instance.instparams {
hints.extend(make_instparam_hints(params, instance)); if is_visible_range(&range, &visible_range) {
hints.extend(make_instparam_hints(server, params, instance, rope));
}
}
if let Some(range) = &instance.instports {
if is_visible_range(range, &visible_range) {
hints.extend(make_instport_hints(server, params, instance, rope));
}
}
} }
if is_visible_range(&instance.instports, &visible_range) { // 在 endmodule 后面添加 module xxx
hints.extend(make_instport_hints(params, instance)); if is_visible_range(&module.range, &visible_range) {
} hints.extend(make_endmodule_hints(module));
} }
} }
return Some(hints); return Some(hints);
@ -44,21 +56,57 @@ pub fn inlay_hint(server: &LSPServer, params: &InlayHintParams) -> Option<Vec<In
} }
fn is_visible_range( fn is_visible_range(
target_range: &Option<core::hdlparam::Range>, target_range: &core::hdlparam::Range,
visible_range: &core::hdlparam::Range visible_range: &core::hdlparam::Range
) -> bool { ) -> bool {
if let Some(target_range) = target_range {
if target_range.before(visible_range) || target_range.after(visible_range) { if target_range.before(visible_range) || target_range.after(visible_range) {
return false;
}
return true;
}
false false
} else {
true
}
}
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();
@ -66,36 +114,115 @@ 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 = port_map.get(&port_name);
if port.is_none() {
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 dir_type = port_info.dir_type.to_string();
// vhdl 转成 verilog
let dir_type = match dir_type.as_str() {
"out" => "output",
"in" => "input",
_ => &dir_type
};
if dir_type == "output" {
format!("{}", dir_type)
} else {
format!("{}{}", dir_type, " ".repeat(1))
}
};
let language_id = get_language_id_by_path_str(&def_path);
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 { let port_desc = MarkupContent {
kind: MarkupKind::Markdown, kind: MarkupKind::Markdown,
value: format!("```verilog\n{:?}\n```", port_assigment.port) value: port_desc_value
}; };
let hint = InlayHint { let hint = InlayHint {
position: port_assigment.range.to_lsp_range().start, position: hint_position,
label: InlayHintLabel::String("start".to_string()), 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);
let port_desc = MarkupContent {
kind: MarkupKind::Markdown,
value: format!("```verilog\n{:?}\n```", port_assigment.port)
};
let hint = InlayHint {
position: port_assigment.range.to_lsp_range().end,
label: InlayHintLabel::String("end".to_string()),
padding_left: Some(true), padding_left: Some(true),
padding_right: Some(true), padding_right: Some(true),
kind: Some(InlayHintKind::PARAMETER), kind: Some(InlayHintKind::PARAMETER),
@ -105,6 +232,7 @@ fn make_instport_hints(
}; };
hints.push(hint); 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,23 +21,33 @@ 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,6 +1,14 @@
#![recursion_limit = "256"] #![recursion_limit = "256"]
use request::{ CustomParamRequest, CustomRequest, DoFastApi, UpdateFastApi }; use request::{
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;
@ -14,12 +22,15 @@ 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;
@ -43,10 +54,13 @@ 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) .custom_method("custom/request", CustomRequest) // for test
.custom_method("custom/paramRequest", CustomParamRequest) .custom_method("custom/paramRequest", CustomParamRequest) // for test
.custom_method("api/fast", DoFastApi) .custom_method("api/fast", DoFastApi)
.custom_method("api/update-fast", UpdateFastApi) .custom_method("api/do-primitives-judge", DoPrimitivesJudgeApi)
.custom_method("api/update-configuration", UpdateConfigurationApi)
.custom_method("api/sync-fast", SyncFastApi)
.custom_method("api/linter-status", LinterStatusApi)
.finish(); .finish();
Server::new(stdin, stdout, socket) Server::new(stdin, stdout, socket)

9
src/request/README.md Normal file
View File

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

102
src/request/config.rs Normal file
View File

@ -0,0 +1,102 @@
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
);
},
_ => {}
}
}

395
src/request/fast.rs Normal file
View File

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

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

@ -0,0 +1,95 @@
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,237 +1,6 @@
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;
#[derive(Clone)] pub mod primitives;
pub struct CustomRequest; pub mod fast;
pub mod test;
impl <'a>tower_lsp::jsonrpc::Method<&'a Arc<Backend>, (), Result<i32>> for CustomRequest { pub mod linter;
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)
}
}

39
src/request/primitives.rs Normal file
View File

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

44
src/request/test.rs Normal file
View File

@ -0,0 +1,44 @@
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,37 +1,49 @@
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::*; use crate::completion::{keyword::*, provide_vlog_sys_tasks_completions};
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::{Mutex, RwLock}; use std::sync::{Arc, 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 {
pub srcs: Sources, /// 文件和 ast 相关的
pub db: DigitalDataBase,
/// 缓存
pub cache: CacheManager, pub cache: CacheManager,
pub key_comps: Vec<CompletionItem>, /// verilog 关键词的自动补全
pub sys_tasks: Vec<CompletionItem>, pub vlog_keyword_completion_items: Vec<CompletionItem>,
pub directives: Vec<CompletionItem>, /// verilog 的系统调用的自动补全
pub conf: RwLock<ProjectConfig>, pub vlog_sys_tasks_completion_items: Vec<CompletionItem>,
/// 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 {
srcs: Sources::new(), db: DigitalDataBase::new(),
cache: CacheManager::new(dide_home), cache: CacheManager::new(dide_home),
key_comps: keyword_completions(KEYWORDS), vlog_keyword_completion_items: provide_keyword_completions(VLOG_KEYWORDS),
sys_tasks: other_completions(SYS_TASKS), vhdl_keyword_completiom_items: provide_keyword_completions(VHDL_KEYWORDS),
directives: other_completions(DIRECTIVES), vlog_sys_tasks_completion_items: provide_vlog_sys_tasks_completions(),
conf: RwLock::new(ProjectConfig::default()), vlog_directives: provide_vlog_directives_completions(),
configuration: Arc::new(RwLock::new(LspConfiguration::default())),
log_handle: Mutex::new(log_handle), log_handle: Mutex::new(log_handle),
} }
} }
@ -39,7 +51,7 @@ impl LSPServer {
pub struct Backend { pub struct Backend {
pub client: Client, pub client: Client,
pub server: LSPServer pub server: LspServer
} }
@ -47,7 +59,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)),
} }
} }
} }
@ -68,117 +80,92 @@ pub enum LogLevel {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct ProjectConfig { pub struct LspConfiguration {
// 用户工作目录的路径
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,
// config options for verilator tools // 下方是和 linter 相关的配置
pub verilator: Verilator, // vlog 的诊断配置
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 ProjectConfig { impl Default for LspConfiguration {
fn default() -> Self { fn default() -> Self {
ProjectConfig { LspConfiguration {
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(),
verible: Verible::default(), vlog_linter_configuration: DigitalLinterConfiguration::new("verilog"),
verilator: Verilator::default(), vhdl_linter_configuration: DigitalLinterConfiguration::new("vhdl"),
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, _: InitializeParams) -> Result<InitializeResult> { async fn initialize(&self, params: 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("0.4.0".to_string()) version: Some(version.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),
@ -208,26 +195,42 @@ 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::default()) Ok(InitializeResult {
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<()> {
@ -235,18 +238,39 @@ impl LanguageServer for Backend {
} }
async fn did_open(&self, params: DidOpenTextDocumentParams) { async fn did_open(&self, params: DidOpenTextDocumentParams) {
// // 如果文件太大则显示错误
// if CacheManager::uri_is_big_file(&params.text_document.uri) {
// self.client.show_message(MessageType::WARNING, "考虑到性能问题,对于大于 1MB 的文件不会主动提供语言服务")
// .await;
// } else {
let diagnostics = self.server.did_open(params); let diagnostics = self.server.did_open(params);
self.client self.client.publish_diagnostics(
.publish_diagnostics(
diagnostics.uri, diagnostics.uri,
diagnostics.diagnostics, diagnostics.diagnostics,
diagnostics.version, diagnostics.version,
) )
.await; .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) {
// // 如果文件太大则显示错误
// if CacheManager::uri_is_big_file(&params.text_document.uri) {
// // self.client.show_message(MessageType::WARNING, "考虑到性能问题,对于大于 1MB 的文件不会主动提供语言服务")
// // .await;
// } else {
self.server.did_change(params); self.server.did_change(params);
// }
} }
async fn did_delete_files(&self, params: DeleteFilesParams) { async fn did_delete_files(&self, params: DeleteFilesParams) {
@ -254,6 +278,9 @@ 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) {
// } else {
let diagnostics = self.server.did_save(params); let diagnostics = self.server.did_save(params);
self.client self.client
.publish_diagnostics( .publish_diagnostics(
@ -262,6 +289,7 @@ impl LanguageServer for Backend {
diagnostics.version, diagnostics.version,
) )
.await; .await;
// }
} }
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> { async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
@ -276,7 +304,6 @@ 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))
} }
@ -295,7 +322,6 @@ 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))
} }
@ -303,7 +329,6 @@ 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))
} }
@ -311,7 +336,13 @@ 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,6 +15,9 @@ 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";
@ -103,6 +106,20 @@ 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() {
// 判断路径是否存在且为文件夹 // 判断路径是否存在且为文件夹
@ -184,7 +201,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}; use super::{INCOMPLETE_EXAMPLE, TEST_FILE, DIGTIAL_IDE_FACTORY};
#[test] #[test]
/// 测试单独的文件 /// 测试单独的文件
@ -229,6 +246,19 @@ mod test_svparse {
} }
} }
#[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)? {
@ -281,7 +311,9 @@ 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::{definition::{get_scopes_from_syntax_tree, GenericScope}, sources::{recovery_sv_parse, recovery_sv_parse_with_retry}}; use crate::sources::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;
@ -300,7 +332,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 {
@ -367,6 +399,19 @@ 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)? {
@ -404,7 +449,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, RecursiveFileIterator}; use crate::utils::{file_size_in_kb, get_language_id_by_pathbuf, is_character_ordered_match, RecursiveFileIterator};
use super::*; use super::*;
#[test] #[test]
@ -431,4 +476,47 @@ 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,46 +2,47 @@
#[cfg(test)] #[cfg(test)]
mod test_vhdl_fast { mod test_vhdl_fast {
use crate::{core::vhdl_parser::{make_fast_from_design_file, vhdl_parse}, test::{DIGTIAL_IDE_TEST, TESTFILES_TEMP_DIR}, utils::*}; // TODO: Rewrite VHDL Test
// 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);
} // }
} // }
} // }
} // }
} }

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

@ -0,0 +1,28 @@
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,23 +1,21 @@
#[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 fast_map = self.srcs.hdl_param.path_to_hdl_file.read().unwrap(); let path_to_hdl_file = self.db.hdl_param.path_to_hdl_file.read().unwrap();
for path in fast_map.keys() { for (path, hdl_file) in path_to_hdl_file.iter() {
if let Some(hdl_file) = fast_map.get(path) {
for define in &hdl_file.fast.fast_macro.defines { for define in &hdl_file.fast.fast_macro.defines {
if define.name == macro_name { if define.name == macro_name {
return Some((define.clone(), path.to_string())); return Some((define.clone(), path.to_string()));
} }
} }
} }
}
None None
} }
} }

View File

@ -1,11 +1,13 @@
use std::{env::consts::OS, path::{Path, PathBuf}}; use std::{env::consts::OS, path::{Path, PathBuf}, str::FromStr};
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::*;
@ -82,6 +84,7 @@ 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)
@ -95,6 +98,7 @@ 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()
@ -106,6 +110,7 @@ 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()
@ -172,6 +177,29 @@ 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,
@ -337,3 +365,33 @@ 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 b6ae8b8a1ff22609e2a837b12dd798226f299f71 Subproject commit 2588d1dac8b7015632a453b293794744a75c240b

57
test/vlog/codedoc.v Normal file
View File

@ -0,0 +1,57 @@
/* @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 a058a7c6411afa7543953bb93a098d9657c408b4 Subproject commit e2b352a670aea6c8bf6d33e1010ad49e1f8eab04