~ruther/gtkwave-tcl-generator

0f5f43a5584cc3f4e7571d635a06ce126d1001dd — František Boháček 1 year, 7 months ago
feat: Add parser and tcl generator
7 files changed, 1279 insertions(+), 0 deletions(-)

A .gitignore
A Cargo.lock
A Cargo.toml
A src/display_elements.rs
A src/file_parser.rs
A src/main.rs
A src/tcl_generator.rs
A  => .gitignore +1 -0
@@ 1,1 @@
/target

A  => Cargo.lock +679 -0
@@ 1,679 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "anstream"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c"
dependencies = [
 "anstyle",
 "anstyle-parse",
 "anstyle-query",
 "anstyle-wincon",
 "colorchoice",
 "utf8parse",
]

[[package]]
name = "anstyle"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"

[[package]]
name = "anstyle-parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
dependencies = [
 "utf8parse",
]

[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
 "windows-sys",
]

[[package]]
name = "anstyle-wincon"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd"
dependencies = [
 "anstyle",
 "windows-sys",
]

[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"

[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"

[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"

[[package]]
name = "clap"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27"
dependencies = [
 "clap_builder",
 "clap_derive",
 "once_cell",
]

[[package]]
name = "clap_builder"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d"
dependencies = [
 "anstream",
 "anstyle",
 "clap_lex",
 "strsim",
]

[[package]]
name = "clap_derive"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
dependencies = [
 "heck",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "clap_lex"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"

[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"

[[package]]
name = "crossbeam-channel"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
dependencies = [
 "cfg-if",
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-deque"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
 "cfg-if",
 "crossbeam-epoch",
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-epoch"
version = "0.9.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
dependencies = [
 "autocfg",
 "cfg-if",
 "crossbeam-utils",
 "memoffset",
 "scopeguard",
]

[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
 "cfg-if",
]

[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
 "dirs-sys",
]

[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
 "libc",
 "redox_users",
 "winapi",
]

[[package]]
name = "dunce"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"

[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"

[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"

[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"

[[package]]
name = "getrandom"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
 "cfg-if",
 "libc",
 "wasi",
]

[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"

[[package]]
name = "gtkwave_tcl_generator"
version = "0.1.0"
dependencies = [
 "clap",
 "glob",
 "parking_lot",
 "string-builder",
 "vhdl_lang",
]

[[package]]
name = "hashbrown"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"

[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"

[[package]]
name = "hermit-abi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"

[[package]]
name = "indexmap"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
 "equivalent",
 "hashbrown",
]

[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
 "either",
]

[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"

[[package]]
name = "lock_api"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
dependencies = [
 "autocfg",
 "scopeguard",
]

[[package]]
name = "memchr"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76fc44e2588d5b436dbc3c6cf62aef290f90dab6235744a93dfe1cc18f451e2c"

[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
 "autocfg",
]

[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
 "hermit-abi",
 "libc",
]

[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"

[[package]]
name = "pad"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3"
dependencies = [
 "unicode-width",
]

[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
 "lock_api",
 "parking_lot_core",
]

[[package]]
name = "parking_lot_core"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
dependencies = [
 "cfg-if",
 "libc",
 "redox_syscall 0.3.5",
 "smallvec",
 "windows-targets",
]

[[package]]
name = "pinned_vec"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "268ad82d92622fb0a049ff14b01089b0f1bcd5c507fab44724394d328417348a"

[[package]]
name = "proc-macro2"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "rayon"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
 "either",
 "rayon-core",
]

[[package]]
name = "rayon-core"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
 "crossbeam-channel",
 "crossbeam-deque",
 "crossbeam-utils",
 "num_cpus",
]

[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
 "bitflags",
]

[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
 "bitflags",
]

[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
 "getrandom",
 "redox_syscall 0.2.16",
 "thiserror",
]

[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"

[[package]]
name = "serde"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
version = "1.0.188"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "serde_spanned"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
dependencies = [
 "serde",
]

[[package]]
name = "smallvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"

[[package]]
name = "string-builder"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bd10a070fb1f2796a288abec42695db4682a82b6f12ffacd60fb8d5ad3a4a12"

[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"

[[package]]
name = "syn"
version = "2.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "thiserror"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
dependencies = [
 "thiserror-impl",
]

[[package]]
name = "thiserror-impl"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "toml"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
dependencies = [
 "serde",
 "serde_spanned",
 "toml_datetime",
 "toml_edit",
]

[[package]]
name = "toml_datetime"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
dependencies = [
 "serde",
]

[[package]]
name = "toml_edit"
version = "0.19.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
dependencies = [
 "indexmap",
 "serde",
 "serde_spanned",
 "toml_datetime",
 "winnow",
]

[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"

[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"

[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"

[[package]]
name = "vhdl_lang"
version = "0.65.0"
source = "git+https://github.com/Rutherther/rust_hdl.git#aacf15291c3e112609d3f59bce0c4fb45ac9d637"
dependencies = [
 "clap",
 "dirs",
 "dunce",
 "fnv",
 "glob",
 "itertools",
 "pad",
 "parking_lot",
 "pinned_vec",
 "rayon",
 "toml",
]

[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
 "winapi-i686-pc-windows-gnu",
 "winapi-x86_64-pc-windows-gnu",
]

[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"

[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
 "windows-targets",
]

[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
 "windows_aarch64_gnullvm",
 "windows_aarch64_msvc",
 "windows_i686_gnu",
 "windows_i686_msvc",
 "windows_x86_64_gnu",
 "windows_x86_64_gnullvm",
 "windows_x86_64_msvc",
]

[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"

[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"

[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"

[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"

[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"

[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

[[package]]
name = "winnow"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
dependencies = [
 "memchr",
]

A  => Cargo.toml +13 -0
@@ 1,13 @@
[package]
name = "gtkwave_tcl_generator"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.4.1", features = ["derive"] }
glob = "0.3.1"
parking_lot = "0.12.1"
string-builder = "0.2.0"
vhdl_lang = {git = "https://github.com/Rutherther/rust_hdl.git", version = "0.65.0"}

A  => src/display_elements.rs +150 -0
@@ 1,150 @@
use std::{slice::Iter, fmt::Display};

#[derive(Eq, PartialEq, Clone)]
pub enum DisplayElement {
    Signal(Signal),
    Empty(Vec<DisplayOption>)
}

#[derive(Eq, PartialEq, Clone, Copy)]
pub enum DisplayOption {
    Color(DisplayColor),
    Format(DisplayFormat),
    Omit
}

#[derive(Eq, PartialEq, Clone)]
pub struct Signal {
    name: String,
    options: Vec<DisplayOption>
}

impl From<&str> for Signal {
    fn from(value: &str) -> Self {
        Self {
            name: value.to_owned(),
            options: vec![]
        }
    }
}

#[derive(Eq, PartialEq, Clone)]
pub struct Entity {
    name: String,
    architecture: Option<Architecture>,
}

#[derive(Eq, PartialEq, Clone)]
pub struct Architecture {
    name: String,
    entity_name: String,
    signals: Vec<Signal>
}

#[derive(Eq, PartialEq, Copy, Clone)]
pub enum DisplayColor {
    Normal,
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet,
    Cycle,
}

impl Display for DisplayColor {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", match self {
            DisplayColor::Normal => "Normal",
            DisplayColor::Red => "Red",
            DisplayColor::Orange => "Orange",
            DisplayColor::Yellow => "Yellow",
            DisplayColor::Green => "Green",
            DisplayColor::Blue => "Blue",
            DisplayColor::Indigo => "Indigo",
            DisplayColor::Violet => "Violet",
            DisplayColor::Cycle => "Cycle",
        })
    }
}

#[derive(Eq, PartialEq, Copy, Clone)]
pub enum DisplayFormat {
    Hex,
    Decimal,
    SignedDecimal,
    Binary,
}

impl Display for DisplayFormat {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", match self {
            DisplayFormat::Hex => "Hex",
            DisplayFormat::Decimal => "Decimal",
            DisplayFormat::SignedDecimal => "SignedDecimal",
            DisplayFormat::Binary => "Binary",
        })
    }
}

impl Signal {
    pub fn new(name: String, options: Vec<DisplayOption>) -> Self {
        Self {
            name,
            options
        }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn options(&self) -> Iter<'_, DisplayOption> {
        self.options.iter()
    }
}

impl Entity {
    pub fn new(name: String) -> Self {
        Self {
            name,
            architecture: None
        }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn add_architecture(&mut self, architecture: Architecture) {
        self.architecture = Some(architecture);
    }

    pub fn architecture(&self) -> Option<&Architecture> {
        self.architecture.as_ref()
    }
}

impl Architecture {
    pub fn new(name: String, entity_name: String, signals: Vec<Signal>) -> Self {
        Self {
            name,
            entity_name,
            signals,
        }
    }

    pub fn signals(&self) -> Iter<'_, Signal> {
        self.signals.iter()
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn entity_name(&self) -> &str {
        &self.entity_name
    }
}

A  => src/file_parser.rs +254 -0
@@ 1,254 @@
use vhdl_lang::{Source, syntax::{tokens::{TokenStream, Tokenizer, Kind, Comment}, Symbols}, Diagnostic, data::{Contents, ContentReader}};

use crate::display_elements::{DisplayColor, Entity, Architecture, Signal, DisplayOption, DisplayFormat};

#[derive(PartialEq, Eq, Clone)]
struct Context {
    color: Option<DisplayColor>,
    omit: bool
}

pub struct FileParser<'a> {
    diagnostics: Vec<Diagnostic>,
    stream: TokenStream<'a>,

    entities: Vec<Entity>,
    current_entity: usize
}

#[derive(Debug)]
pub enum ParseError {
    EntityNotPresent,
    ArchitectureNotFound,
    ParsingError(Diagnostic),
    EndOfFile,
}

impl From<Diagnostic> for ParseError {
    fn from(value: Diagnostic) -> Self {
        Self::ParsingError(value)
    }
}

impl From<&Context> for Vec<DisplayOption> {
    fn from(value: &Context) -> Self {
        let mut options = vec![];

        if value.omit {
            options.push(DisplayOption::Omit);
        }

        if let Some(color) = value.color {
            options.push(DisplayOption::Color(color));
        }

        options
    }
}

impl<'a> FileParser<'a> {
    pub fn new(source: &'a Source, contents: &'a Contents, symbols: &'a Symbols) -> Self {
        let mut diagnostics = vec![];
        let tokenizer = Tokenizer::new(symbols, source, ContentReader::new(contents));
        let stream = TokenStream::new(tokenizer, &mut diagnostics);

        Self {
            diagnostics,
            stream,
            entities: vec![],
            current_entity: 0,
        }
    }

    pub fn find_next_entity(&mut self) -> Result<Entity, ParseError> {
        if self.current_entity < self.entities.len() {
            self.current_entity += 1;
            return Ok(self.entities[self.current_entity - 1].clone());
        }

        if self.stream.skip_until(|k| k == Kind::Entity || k == Kind::Architecture).is_err() {
            return Err(ParseError::EndOfFile);
        }

        if self.stream.peek_kind().unwrap() == Kind::Entity {
            let entity = Self::parse_entity(&mut self.stream)?;
            self.entities.push(entity.clone());
            self.current_entity += 1;

            Ok(entity)
        } else {
            let architecture = Self::parse_architecture(&mut self.stream)?;

            if let Some(entity) = self.entities.iter_mut().find(|e| e.name() == architecture.entity_name()) {
                entity.add_architecture(architecture);
            }

            self.find_next_entity()
        }
    }

    pub fn parse_entity_architecture(&mut self, mut entity: Entity) -> Result<Entity, ParseError> {
        let Some(found_entity) = self.entities.iter_mut().find(|e| e.name() == entity.name()) else {
            return Err(ParseError::EntityNotPresent);
        };

        if entity.architecture().is_some() {
            return Ok(entity);
        }

        if found_entity.architecture().is_some() {
            return Ok(found_entity.clone());
        }

        if self.stream.skip_until(|k| k == Kind::Entity || k == Kind::Architecture).is_err() {
            return Err(ParseError::EndOfFile);
        }

        if self.stream.peek_kind().unwrap() == Kind::Entity {
            let entity = Self::parse_entity(&mut self.stream)?;
            self.entities.push(entity.clone());
            return self.parse_entity_architecture(entity)
        }

        let architecture = Self::parse_architecture(&mut self.stream)?;
        if architecture.entity_name() == entity.name() {
            entity.add_architecture(architecture.clone());
            found_entity.add_architecture(architecture);

            return Ok(entity);
        }

        if let Some(matched_entity) = self.entities.iter_mut().find(|e| e.name() == architecture.entity_name()) {
            matched_entity.add_architecture(architecture);
        }

        self.parse_entity_architecture(entity)
    }

    fn parse_architecture(stream: &mut TokenStream) -> Result<Architecture, ParseError> {
        stream.expect_kind(Kind::Architecture)?;

        let architecture_name = Self::parse_identifier(stream)?;

        stream.expect_kind(Kind::Of)?;

        let entity_name = Self::parse_identifier(stream)?;

        stream.expect_kind(Kind::Is)?;

        let mut context = Context { color: None, omit: false };
        let mut signals = vec![];

        while !stream.next_kind_is(Kind::Begin) {
            let token = stream.peek().ok_or(ParseError::EndOfFile)?;

            if let Some(comments) = &token.comments {
                for comment in &comments.leading {
                    Self::update_context(&mut context, comment);
                }
            }

            match token.kind {
                Kind::Signal => signals.append(Self::parse_signals(stream, &context)?.as_mut()),
                Kind::Begin => break,
                _ => stream.skip(),
            }
        }

        let architecture = Architecture::new(architecture_name, entity_name, signals);

        Ok(architecture)
    }

    fn parse_signals(stream: &mut TokenStream, context: &Context) -> Result<Vec<Signal>, ParseError> {
        stream.expect_kind(Kind::Signal)?;

        let mut signal_names = vec![];
        signal_names.push(Self::parse_identifier(stream)?);

        while stream.peek_kind().ok_or(ParseError::EndOfFile)? != Kind::Colon {
            stream.skip();
            signal_names.push(Self::parse_identifier(stream)?);
        }

        stream.skip();

        let signal_type = Self::parse_identifier(stream)?;

        stream.skip_until(|k| k == Kind::SemiColon)?;
        let semicolon_token = stream.peek().ok_or(ParseError::EndOfFile)?;

        let options: Vec<DisplayOption> = if let Some(comments) = &semicolon_token.comments {
            if let Some(trailing) = &comments.trailing {
                let mut context = context.clone();
                Self::update_context(&mut context, trailing);
                (&context).into()
            } else {
                context.into()
            }
        } else {
            context.into()
        };

        let mut signals = vec![];
        for signal_name in signal_names {
            let mut options = options.clone();
            if signal_type.starts_with("std_logic_vector") {
                options.push(DisplayOption::Format(DisplayFormat::Binary));
            }

            signals.push(Signal::new(signal_name, options));
        }

        Ok(signals)
    }

    fn parse_entity(stream: &mut TokenStream) -> Result<Entity, ParseError> {
        stream.expect_kind(Kind::Entity)?;

        let name = Self::parse_identifier(stream)?;
        Ok(Entity::new(name))
    }

    fn parse_identifier(stream: &mut TokenStream) -> Result<String, ParseError> {
        let token = stream.peek_expect()?;
        let identifier = token.to_identifier_value()?;

        stream.skip();

        Ok(identifier.item.name_utf8())
    }

    fn update_context(context: &mut Context, comment: &Comment) {
        let commands = comment.value.split(['\n', ','].as_ref());

        for command in commands {
            match command.trim() {
                "omit" => context.omit = true,
                "reset" => {
                    context.color = None;
                    context.omit = false;
                },
                _ if command.trim().starts_with("color ") => {
                    let color = command["color ".len()..].trim();

                    let color = match color {
                        "normal" => DisplayColor::Normal,
                        "red" => DisplayColor::Red,
                        "orange" => DisplayColor::Orange,
                        "yellow" => DisplayColor::Yellow,
                        "green" => DisplayColor::Green,
                        "blue" => DisplayColor::Blue,
                        "Indigo" => DisplayColor::Indigo,
                        "Violet" => DisplayColor::Violet,
                        "Cycle" => DisplayColor::Cycle,
                        _ => DisplayColor::Normal,
                    };

                    context.color = Some(color);
                },
                _ => ()
            }
        }
    }
}

A  => src/main.rs +89 -0
@@ 1,89 @@
pub mod file_parser;
pub mod display_elements;
pub mod tcl_generator;

use std::{path::PathBuf, fs::File, io::Write};

use clap::{arg, Parser};
use file_parser::FileParser;
use glob::glob;
use tcl_generator::TclGenerator;
use vhdl_lang::{syntax::Symbols, Source};

use crate::display_elements::{Signal, DisplayOption, DisplayColor};

#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
    #[arg(short = 'f', long = "folder")]
    folder: PathBuf,
    #[arg(short = 't', long = "testbench")]
    testbench: String,
    #[arg(short = 'o', long = "output")]
    output: PathBuf,
}

fn main() {
    let cli = Cli::parse();
    let find_wildcard = cli.folder.to_str().unwrap().to_owned() + "/**/*.vhd";
    println!("Going to look into {find_wildcard}");

    let matching_files = glob(&find_wildcard).unwrap();

    let symbols = Symbols::default();

    let mut found = false;
    for file_result in matching_files {
        let file = file_result.unwrap();

        let source = Source::from_latin1_file(file.as_path()).unwrap();
        let contents = source.contents();
        let mut parser = FileParser::new(&source, &contents, &symbols);

        match parser.find_next_entity() {
            Ok(entity) =>  {
                if entity.name() != &cli.testbench[..] {
                    continue;
                }
                found = true;

                println!("Found the testbench.");

                let entity = parser.parse_entity_architecture(entity).unwrap();
                let architecture = entity.architecture().unwrap();

                let mut generator = TclGenerator::new("top.".to_owned() + &cli.testbench + ".");
                generator.add_signal(&Signal::new("clk".to_owned(), vec![DisplayOption::Color(DisplayColor::Indigo)]))
                        .add_signal(&Signal::new("rst".to_owned(), vec![DisplayOption::Color(DisplayColor::Indigo)]))
                        .add_empty()
                        .zoom_out();

                for signal in architecture.signals() {
                    if signal.name() == "clk" || signal.name() == "rst" {
                        continue;
                    }

                    generator.add_signal(signal);
                }

                let generated = generator.generate();

                let mut file = File::create(&cli.output).unwrap();
                file.write_all(generated.as_bytes()).unwrap();

                break;
            },
            Err(err) => {
                println!("{:?}", err);
            }
        }

        if found {
            break;
        }
    }

    if !found {
        println!("Could not find the entity.")
    }
}

A  => src/tcl_generator.rs +93 -0
@@ 1,93 @@
use string_builder::Builder;

use crate::display_elements::{DisplayColor, DisplayFormat, Signal, DisplayOption};


pub struct TclGenerator {
    signals: Vec<(String, Option<DisplayColor>, Option<DisplayFormat>)>,
    zoom_out: bool,
    signal_prefix: String,
}

impl TclGenerator {
    pub fn new(signal_prefix: String) -> Self {
        Self {
            signals: vec![],
            zoom_out: false,
            signal_prefix,
        }
    }

    pub fn add_signal(&mut self, signal: &Signal) -> &mut Self {
        let mut color = None;
        let mut format = None;

        for option in signal.options() {
            match option {
                DisplayOption::Omit => return self,
                DisplayOption::Color(c) => {
                    color = Some(c.clone());
                },
                DisplayOption::Format(f) => {
                    format = Some(f.clone());
                }
            }
        }

        self.signals.push((signal.name().to_owned(), color, format));
        self
    }

    pub fn add_empty(&mut self) -> &mut Self {
        self.signals.push(("".to_owned(), None, None));
        self
    }

    pub fn zoom_out(&mut self) -> &mut Self {
        self.zoom_out = true;
        self
    }

    pub fn generate(self) -> String {
        let mut builder = Builder::new(300);

        builder.append("gtkwave::nop\n");
        builder.append("gtkwave::/Edit/Set_Trace_Max_Hier 2\n");
        builder.append("gtkwave::/View/Show_Filled_High_Values 1\n");
        builder.append("gtkwave::/View/Show_Wave_Highlight 1\n");
        builder.append("gtkwave::/View/Show_Mouseover 1\n");
        builder.append("gtkwave::/View/Left_Justified_Signals 1\n");

        for signal in self.signals {
            if signal.0 == "" {
                builder.append("gtkwave::/Edit/Insert_Blank\n");
            } else {
                builder.append("gtkwave::addSignalsFromList \"");
                builder.append(&self.signal_prefix[..]);
                builder.append(&signal.0[..]);
                builder.append("\"\n");

                builder.append("gtkwave::highlightSignalsFromList \"");
                builder.append(&self.signal_prefix[..]);
                builder.append(&signal.0[..]);
                builder.append("\"\n");

                if let Some(color) = signal.1 {
                    builder.append(format!("gtkwave::/Edit/Color_Format/{color}\n"));
                }

                if let Some(format) = signal.2 {
                    builder.append(format!("gtkwave::/Edit/Data_Format/{format}\n"));
                }

                builder.append("gtkwave::/Edit/UnHighlight_All\n");
            }
        }

        if self.zoom_out {
            builder.append("gtkwave::/Time/Zoom/Zoom_Best_Fit\n");
        }

        builder.string().unwrap()
    }
}

Do not follow this link