Repository: vivekmalneedi/veridian Branch: master Commit: 0c5776a4a4e0 Files: 50 Total size: 301.9 KB Directory structure: gitextract_lil_lsx1/ ├── .github/ │ └── workflows/ │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── extensions/ │ └── vscode/ │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .eslintrc.json │ ├── .gitignore │ ├── .vscodeignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src/ │ │ └── extension.ts │ ├── syntaxes/ │ │ └── systemverilog.tmLanguage.json │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── rustfmt.toml ├── src/ │ ├── completion/ │ │ └── keyword.rs │ ├── completion.rs │ ├── definition/ │ │ ├── def_types.rs │ │ └── extract_defs.rs │ ├── definition.rs │ ├── diagnostics.rs │ ├── format.rs │ ├── lib.rs │ ├── main.rs │ ├── server.rs │ ├── sources.rs │ └── support.rs ├── test_data/ │ ├── complete.sv │ ├── definition_test.sv │ ├── diag/ │ │ └── diag_test.sv │ ├── ind.sv │ ├── interface_obj.sv │ ├── simple_bus.svh │ ├── test_inter.sv │ ├── top.sv │ ├── top_inc.sv │ └── verilator_errors.txt └── veridian-slang/ ├── Cargo.toml ├── build.rs ├── slang_wrapper/ │ ├── CMakeLists.txt │ └── src/ │ ├── BasicClient.h │ ├── FormatBuffer.h │ ├── basic_client.cpp │ ├── slang_lib.cpp │ └── slang_wrapper.h └── src/ ├── lib.rs └── wrapper.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/ci.yml ================================================ on: push: pull_request: workflow_dispatch: schedule: - cron: '0 0 * * 0' name: CI jobs: test: name: Test Suite runs-on: ubuntu-22.04 steps: - name: Checkout sources uses: actions/checkout@v6 with: submodules: true - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: 1.91.1 override: true - name: cache cargo dirs uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Install Deps run: | wget 'https://github.com/chipsalliance/verible/releases/download/v0.0-3607-g46de0f64/verible-v0.0-3607-g46de0f64-linux-static-x86_64.tar.gz' -O verible.tar.gz tar -xf verible.tar.gz sudo cp verible-v0.0-3607-g46de0f64/bin/* /usr/bin sudo apt update sudo apt-get install -y verilator perl - name: Run cargo test uses: actions-rs/cargo@v1 with: command: test args: --all-features #todo: vscode extensions tests - uses: actions/setup-node@v6 with: node-version: "24" - name: build extension working-directory: extensions/vscode run: | npm install npm install -g @vscode/vsce vsce package -o veridian.vsix lints: name: Lints runs-on: ubuntu-22.04 steps: - name: Checkout sources uses: actions/checkout@v6 with: submodules: true - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: 1.91.1 override: true components: rustfmt, clippy - name: cache cargo dirs uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Run cargo fmt uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} args: --all-features -- -D warnings build_ubuntu: name: Build Ubuntu runs-on: ubuntu-22.04 if: ${{ github.ref == 'refs/heads/master' && ( github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' ) }} steps: - name: Checkout sources uses: actions/checkout@v6 - name: Install stable toolchain uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: 1.91.1 override: true - name: cache cargo dirs uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git target key: ${{ runner.os }}-release-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Run cargo build uses: actions-rs/cargo@v1 with: command: build args: --all-features --release - name: Create archive run: | strip target/release/veridian cp target/release/veridian . tar -czvf veridian-ubuntu-22.04.tar.gz veridian - uses: actions/upload-artifact@v4 with: name: veridian-ubuntu-22.04.tar.gz path: veridian-ubuntu-22.04.tar.gz if-no-files-found: error build_vscode: name: Build vscode extension runs-on: ubuntu-latest if: ${{ ( github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' ) }} steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: "24" - name: build extension working-directory: extensions/vscode run: | npm install npm install -g @vscode/vsce vsce package -o veridian.vsix - uses: actions/upload-artifact@v4 with: name: veridian.vsix path: extensions/vscode/veridian.vsix if-no-files-found: error publish: name: Create Release needs: [build_ubuntu, build_vscode] runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: name: veridian-ubuntu-22.04.tar.gz - uses: actions/download-artifact@v4 with: name: veridian.vsix - uses: marvinpinto/action-automatic-releases@latest with: repo_token: ${{ secrets.GITHUB_TOKEN }} automatic_release_tag: nightly prerelease: true title: nightly files: | veridian-ubuntu-22.04.tar.gz veridian.vsix ================================================ FILE: .gitignore ================================================ # Generated by Cargo # will have compiled files and executables debug/ target/ # These are backup files generated by rustfmt **/*.rs.bk tests_rtl log_files test.txt .vscode/ veridian-slang/slang_wrapper/slang/ ================================================ FILE: Cargo.toml ================================================ [package] name = "veridian" version = "0.1.0" authors = ["Vivek Malneedi "] edition = "2018" [features] slang = ["veridian_slang"] [dependencies] sv-parser = "0.8.2" log = "0.4.19" tower-lsp = "0.20.0" flexi_logger = "0.29.8" ropey = "1.6.0" tokio = { version = "1.29.1", features = ["macros", "io-std", "rt-multi-thread"] } path-clean = "1.0.1" pathdiff = "0.2.1" walkdir = "2.3.3" serde_yaml = "0.9.25" anyhow = "1.0.72" serde = "1.0.179" which = "7.0.1" regex = "1.9.1" structopt = "0.3.26" strum = "0.26.1" strum_macros = "0.26.1" [dev-dependencies] tempdir = "0.3.7" [dependencies.veridian_slang] optional = true path = "veridian-slang" ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Vivek Malneedi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # veridian ![build](https://github.com/vivekmalneedi/veridian/workflows/CI/badge.svg) ![GitHub](https://img.shields.io/github/license/vivekmalneedi/veridian) A SystemVerilog Language Server\ ## Installation ### Pre-Installation - It is recommended to install the [verible](https://github.com/google/verible) tools for - formatting support with `verible-verilog-format` - syntax checking support with `verible-verilog-syntax` - It is recommended to install [verilator](https://www.veripool.org/verilator/) for additional linting ### Install from Release - Download the latest release for your OS from the [releases page](https://github.com/vivekmalneedi/veridian/releases) - The nightly release contains the last successful build, and is not guaranteed to be stable - The ubuntu build also includes [slang](https://github.com/MikePopoloski/slang) for linting ### Install from Source - Build dependencies: Rust toolchain (Install through system package manager or through [rustup](https://rustup.rs/])) - optional: C++17 compatible compiler (for linting with slang) ```bash # install with slang feature, if C++17 compiler is available cargo install --git https://github.com/vivekmalneedi/veridian.git --all-features # install if C++17 compiler is not available cargo install --git https://github.com/vivekmalneedi/veridian.git ``` ## Usage ### [neovim](https://github.com/neovim/nvim-lspconfig) ```lua local lspconfutil = require 'lspconfig/util' local root_pattern = lspconfutil.root_pattern("veridian.yml", ".git") require('lspconfig').veridian.setup { cmd = { 'veridian' }, root_dir = function(fname) local filename = lspconfutil.path.is_absolute(fname) and fname or lspconfutil.path.join(vim.loop.cwd(), fname) return root_pattern(filename) or lspconfutil.path.dirname(filename) end; } ```` ### [vscode](https://github.com/vivekmalneedi/veridian/tree/master/extensions/vscode) - download veridian.vsix from the latest release - install the extension using one of the two following methods - In the extensions tab, click on the 3 dots, then click `Install from VSIX` and choose `veridian.vsix` - Run `code --install-extension veridian.vsix` ### [coc.nvim](https://github.com/neoclide/coc.nvim) In `coc-settings.json`: ```json { "languageserver": { "veridian": { "command": "veridian", "filetypes": ["systemverilog", "verilog"] } } ``` ### Emacs - Install the [`verilog-ext`](https://github.com/gmlarumbe/verilog-ext/) package - Copy the following snippet into your init file: ```elisp (require 'verilog-ext) (verilog-ext-mode-setup) (verilog-ext-eglot-set-server 've-veridian) ;`eglot' config (verilog-ext-lsp-set-server 've-veridian) ; `lsp' config ``` The [full list](https://github.com/vivekmalneedi/veridian/wiki/Usage-Instructions-for-various-LSP-Clients) is on the wiki ## Configuration - Specify source directories and include directories using a yaml project config - All settings have defaults so your config file should only specify custom values In `veridian.yml`: ```yaml # list of directories with header files include_dirs: - inc1 - inc2 # list of directories to recursively search for SystemVerilog/Verilog sources source_dirs: - src - src2 # if true, recursively search the working directory for files to run diagnostics on # default: true auto_search_workdir: true|false, # verible tool configuration verible: # verible-verilog-syntax configuration syntax: # default: true if in path enabled: true|false, path: "verible-verilog-syntax" # default: none args: - arg1 - arg2 # verible-verilog-format configuration format: # default: true if in path enabled: true|false, path: "verible-verilog-format" # default: none args: - arg1 - arg2 verilator: # verilator configuration syntax: # default: true if in path enabled: true|false, path: "verilator" # default: specified below args: - --lint-only - --sv - -Wall # set log level # default: Info log_level: Error|Warn|Info|Debug|Trace ``` ## LSP Support See the [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/specification-current/) for more details - diagnostics (using [slang](https://github.com/MikePopoloski/slang) or [verible](https://github.com/google/verible)) - completion - identifier completion - dot completion - keywords & snippets - system task/function and compiler directives - hover (documentation) - definition - documentSymbol - documentHighlight - formatting (using [verible](https://github.com/google/verible)) - rangeFormatting (using [verible](https://github.com/google/verible)) ## Alternatives The Verible project is working on a language server for SystemVerilog, check it out [here](https://github.com/chipsalliance/verible/tree/master/verilog/tools/ls) ================================================ FILE: extensions/vscode/.eslintignore ================================================ node_modules/** out/** ================================================ FILE: extensions/vscode/.eslintrc.js ================================================ /**@type {import('eslint').Linter.Config} */ // eslint-disable-next-line no-undef module.exports = { root: true, parser: '@typescript-eslint/parser', plugins: [ '@typescript-eslint', ], extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', ], rules: { 'semi': [2, "always"], '@typescript-eslint/no-unused-vars': 0, '@typescript-eslint/no-explicit-any': 0, '@typescript-eslint/explicit-module-boundary-types': 0, '@typescript-eslint/no-non-null-assertion': 0, } }; ================================================ FILE: extensions/vscode/.eslintrc.json ================================================ { "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 6, "sourceType": "module" }, "env": { "node": true }, "rules": { "semi": "error", "no-extra-semi": "warn", "curly": "warn", "quotes": ["error", "single", { "allowTemplateLiterals": true } ], "eqeqeq": "error", "indent": ["warn", "tab", { "SwitchCase": 1 } ] } } ================================================ FILE: extensions/vscode/.gitignore ================================================ out node_modules .vscode-test *.vsix ================================================ FILE: extensions/vscode/.vscodeignore ================================================ .vscode/** **/*.ts **/*.map .gitignore **/tsconfig.json **/tsconfig.base.json contributing.md .travis.yml ================================================ FILE: extensions/vscode/LICENSE ================================================ MIT License Copyright (c) 2021 Vivek Malneedi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: extensions/vscode/README.md ================================================ # veridian a vscode client extension for the veridian language server. - veridian must be installed seperately, see https://github.com/vivekmalneedi/veridian for details - provides syntax highlighting, the grammar for which is borrowed from https://github.com/TheClams/SystemVerilog and is under the Apache 2.0 License ================================================ FILE: extensions/vscode/package.json ================================================ { "name": "veridian", "description": "A client for the Veridian Language Server for SystemVerilog/Verilog", "author": "Vivek Malneedi", "publisher": "vivekmalneedi", "license": "MIT", "version": "0.1.0", "categories": [ "Programming Languages", "Snippets", "Linters" ], "keywords": [ "SystemVerilog", "Verilog" ], "repository": { "type": "git", "url": "https://github.com/vivekmalneedi/veridian" }, "activationEvents": [ "onLanguage:systemverilog", "onLanguage:verilog" ], "main": "./out/extension", "scripts": { "vscode:prepublish": "npm run compile", "compile": "tsc -b", "watch": "tsc -b -w" }, "contributes": { "languages": [ { "id": "systemverilog", "extensions": [ ".sv", ".svh", ".v", ".vh", ".verilog" ], "aliases": [ "SystemVerilog", "verilog", "Verilog" ] } ], "grammars": [ { "language": "systemverilog", "scopeName": "source.systemverilog", "path": "./syntaxes/systemverilog.tmLanguage.json" } ], "configuration": { "type": "object", "title": "veridian", "properties": { "veridian.serverPath": { "scope": "window", "type": "string", "default": "veridian", "description": "path of the veridian binary" }, "veridian.trace.server": { "scope": "window", "type": "string", "enum": [ "off", "messages", "verbose" ], "default": "off", "description": "Traces the communication between VS Code and the language server." } } } }, "engines": { "vscode": "^1.100.0" }, "dependencies": { "glob": "^11.0.0", "vscode-languageclient": "^9.0.1" }, "devDependencies": { "@eslint/js": "^9.13.0", "@stylistic/eslint-plugin": "^2.9.0", "@types/mocha": "^10.0.6", "@types/node": "^22", "eslint": "^9.13.0", "mocha": "^10.3.0", "typescript": "^5.9.2", "typescript-eslint": "^8.39.0", "@types/vscode": "^1.100.0" } } ================================================ FILE: extensions/vscode/src/extension.ts ================================================ /* -------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ import { workspace, ExtensionContext } from "vscode"; import { LanguageClient, LanguageClientOptions, ServerOptions, Executable, } from "vscode-languageclient/node"; let client: LanguageClient; const workSpaceFolder = workspace.workspaceFolders?.[0]; let cwd: string = workSpaceFolder.uri.fsPath; const serverPath: string = workspace.getConfiguration().get("veridian.serverPath"); export function activate(context: ExtensionContext) { const run: Executable = { command: serverPath, // options: { cwd }, }; // If the extension is launched in debug mode then the debug server options are used // Otherwise the run options are used let serverOptions: ServerOptions = { run, debug: run, }; // Options to control the language client let clientOptions: LanguageClientOptions = { // Register the server for plain text documents documentSelector: [{ scheme: "file", language: "systemverilog" }], }; // Create the language client and start the client. client = new LanguageClient( "veridian", "veridian", serverOptions, clientOptions ); // Start the client. This will also launch the server client.start(); } export function deactivate(): Thenable | undefined { if (!client) { return undefined; } return client.stop(); } ================================================ FILE: extensions/vscode/syntaxes/systemverilog.tmLanguage.json ================================================ { "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", "fileTypes": [ "sv", "SV", "v", "V", "svh", "SVH", "vh", "VH" ], "hidden": true, "foldingStartMarker": "(begin)\\s*(//.*)?$", "foldingStopMarker": "^\\s*(begin)$", "name": "systemverilog", "patterns": [ { "begin": "\\s*\\b(function|task)\\b(\\s+automatic)?", "beginCaptures": { "1": { "name": "keyword.control.systemverilog" }, "2": { "name": "keyword.control.systemverilog" } }, "end": ";", "patterns": [ { "match": "\\b([a-zA-Z_][a-zA-Z0-9_]*\\s+)?([a-zA-Z_][a-zA-Z0-9_:]*)\\s*(?=\\(|;)", "captures": { "1": { "name": "storage.type.systemverilog" }, "2": { "name": "entity.name.function.systemverilog" } } }, { "include": "#port-dir" }, { "include": "#base-grammar" } ], "name": "meta.function.systemverilog" }, { "match": "\\s*\\b(task)\\s+(automatic)?\\s*(\\w+)\\s*;", "captures": { "1": { "name": "keyword.control.systemverilog" }, "2": { "name": "keyword.control.systemverilog" }, "3": { "name": "entity.name.function.systemverilog" } }, "name": "meta.task.simple.systemverilog" }, { "begin": "\\s*\\b(typedef\\s+(struct|enum|union)\\b)\\s*(packed)?\\s*([a-zA-Z_][a-zA-Z0-9_]*)?", "beginCaptures": { "1": { "name": "keyword.control.systemverilog" }, "2": { "name": "keyword.control.systemverilog" }, "3": { "name": "keyword.control.systemverilog" }, "4": { "name": "storage.type.systemverilog" } }, "end": "(})\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*;", "endCaptures": { "1": { "name": "keyword.operator.other.systemverilog" }, "2": { "name": "entity.name.function.systemverilog" } }, "patterns": [ { "include": "#struct-anonymous" }, { "include": "#base-grammar" } ], "name": "meta.typedef.struct.systemverilog" }, { "match": "\\s*\\b(typedef\\s+class)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*;", "captures": { "1": { "name": "keyword.control.systemverilog" }, "2": { "name": "entity.name.declaration.systemverilog" } }, "name": "meta.typedef.class.systemverilog" }, { "begin": "\\s*\\b(typedef)\\b", "beginCaptures": { "1": { "name": "keyword.control.systemverilog" } }, "end": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=(\\[[a-zA-Z0-9_:\\$\\-\\+]*\\])?;)", "endCaptures": { "1": { "name": "entity.name.function.systemverilog" } }, "patterns": [ { "match": "\\b([a-zA-Z_]\\w*)\\s*(#)\\(", "captures": { "1": { "name": "storage.type.userdefined.systemverilog" }, "2": { "name": "keyword.operator.param.systemverilog" } }, "name": "meta.typedef.class.systemverilog" }, { "include": "#base-grammar" }, { "include": "#module-binding" } ], "name": "meta.typedef.simple.systemverilog" }, { "begin": "\\s*(module)\\s+\\b([a-zA-Z_][a-zA-Z0-9_]*)\\b", "beginCaptures": { "1": { "name": "keyword.control.systemverilog" }, "2": { "name": "entity.name.type.module.systemverilog" } }, "end": ";", "endCaptures": { "1": { "name": "entity.name.function.systemverilog" } }, "patterns": [ { "include": "#port-dir" }, { "match": "\\s*(parameter)", "name": "keyword.other.systemverilog" }, { "include": "#base-grammar" }, { "include": "#ifmodport" } ], "name": "meta.module.systemverilog" }, { "captures": { "1": { "name": "keyword.control.systemverilog" }, "2": { "name": "entity.name.function.systemverilog" } }, "match": "\\b(sequence)\\s+([a-zA-Z_][a-zA-Z0-9_]*)", "name": "meta.sequence.systemverilog" }, { "match": "\\b(bind)\\s+([a-zA-Z_][a-zA-Z0-9_\\.]*)\\b", "captures": { "1": { "name": "keyword.control.systemverilog" } } }, { "captures": { "0": { "name": "meta.section.begin.systemverilog" }, "1": { "name": "keyword.other.block.systemverilog" }, "3": { "name": "keyword.operator.systemverilog" }, "4": { "name": "entity.name.section.systemverilog" } }, "match": "\\s*(begin|fork)\\s*((:)\\s*([a-zA-Z_][a-zA-Z0-9_]*))\\b", "name": "meta.definition.systemverilog" }, { "match": "\\b(property)\\s+(\\w+)", "captures": { "1": { "name": "keyword.sva.systemverilog" }, "2": { "name": "entity.name.sva.systemverilog" } } }, { "match": "\\b(\\w+)\\s*(:)\\s*(assert)\\b", "captures": { "1": { "name": "entity.name.sva.systemverilog" }, "2": { "name": "keyword.operator.systemverilog" }, "3": { "name": "keyword.sva.systemverilog" } } }, { "begin": "\\s*(//)\\s*(psl)\\s+((\\w+)\\s*(:))?\\s*(default|assert|assume)", "beginCaptures": { "0": { "name": "meta.psl.systemverilog" }, "1": { "name": "comment.line.double-slash.systemverilog" }, "2": { "name": "keyword.psl.systemverilog" }, "4": { "name": "entity.psl.name.systemverilog" }, "5": { "name": "keyword.operator.systemverilog" }, "6": { "name": "keyword.psl.systemverilog" } }, "end": ";", "patterns": [ { "match": "\\b(never|always|default|clock|within|rose|fell|stable|until|before|next|eventually|abort|posedge)\\b", "name": "keyword.psl.systemverilog" }, { "include": "#operators" }, { "include": "#functions" }, { "include": "#constants" } ], "name": "meta.psl.systemverilog" }, { "begin": "\\s*(/\\*)\\s*(psl)", "beginCaptures": { "0": { "name": "meta.psl.systemverilog" }, "1": { "name": "comment.block.systemverilog" }, "2": { "name": "keyword.psl.systemverilog" } }, "end": "(\\*/)", "endCaptures": { "1": { "name": "comment.block.systemverilog" } }, "patterns": [ { "match": "^\\s*((\\w+)\\s*(:))?\\s*(default|assert|assume)", "captures": { "0": { "name": "meta.psl.systemverilog" }, "2": { "name": "entity.psl.name.systemverilog" }, "3": { "name": "keyword.operator.systemverilog" }, "4": { "name": "keyword.psl.systemverilog" } } }, { "match": "\\b(property)\\s+(\\w+)", "captures": { "1": { "name": "keyword.psl.systemverilog" }, "2": { "name": "entity.psl.name.systemverilog" } } }, { "match": "\\b(never|always|default|clock|within|rose|fell|stable|until|before|next|eventually|abort|posedge|negedge)\\b", "name": "keyword.psl.systemverilog" }, { "include": "#operators" }, { "include": "#functions" }, { "include": "#constants" } ], "name": "meta.psl.systemverilog" }, { "match": "\\s*\\b(automatic|cell|config|deassign|defparam|design|disable|edge|endconfig|endgenerate|endspecify|endtable|event|generate|genvar|ifnone|incdir|instance|liblist|library|macromodule|negedge|noshowcancelled|posedge|pulsestyle_onevent|pulsestyle_ondetect|scalared|showcancelled|specify|specparam|table|use|vectored)\\b", "captures": { "1": { "name": "keyword.other.systemverilog" } } }, { "match": "\\s*\\b(initial|always|wait|force|release|assign|always_comb|always_ff|always_latch|forever|repeat|while|for|if|iff|else|case|casex|casez|default|endcase|return|break|continue|do|foreach|with|inside|dist|clocking|cover|coverpoint|property|bins|binsof|illegal_bins|ignore_bins|randcase|modport|matches|solve|static|assert|assume|before|expect|cross|ref|first_match|srandom|struct|packed|final|chandle|alias|tagged|extern|throughout|timeprecision|timeunit|priority|type|union|uwire|wait_order|triggered|randsequence|import|export|context|pure|intersect|wildcard|within|new|typedef|enum|this|super|begin|fork|forkjoin|unique|unique0|priority)\\b", "captures": { "1": { "name": "keyword.control.systemverilog" } } }, { "match": "\\s*\\b(end|endtask|endmodule|endfunction|endprimitive|endclass|endpackage|endsequence|endprogram|endclocking|endproperty|endgroup|endinterface|join|join_any|join_none)\\b(\\s*(:)\\s*(\\w+))?", "captures": { "1": { "name": "keyword.control.systemverilog" }, "3": { "name": "keyword.operator.systemverilog" }, "4": { "name": "entity.label.systemverilog" } }, "name": "meta.object.end.systemverilog" }, { "match": "\\b(std)\\b::", "name": "support.class.systemverilog" }, { "captures": { "1": { "name": "constant.other.define.systemverilog" }, "2": { "name": "entity.name.type.define.systemverilog" } }, "match": "^\\s*(`define)\\s+([a-zA-Z_][a-zA-Z0-9_]*)", "name": "meta.define.systemverilog" }, { "include": "#comments" }, { "captures": { "1": { "name": "keyword.control.systemverilog" }, "2": { "name": "entity.name.type.class.systemverilog" } }, "match": "\\s*(primitive|package|constraint|interface|covergroup|program)\\s+\\b([a-zA-Z_][a-zA-Z0-9_]*)\\b", "name": "meta.definition.systemverilog" }, { "captures": { "2": { "name": "entity.name.type.class.systemverilog" }, "3": { "name": "keyword.operator.other.systemverilog" }, "4": { "name": "keyword.control.systemverilog" } }, "match": "(([a-zA-Z_][a-zA-Z0-9_]*)\\s*(:))?\\s*(coverpoint|cross)\\s+([a-zA-Z_][a-zA-Z0-9_]*)", "name": "meta.definition.systemverilog" }, { "captures": { "1": { "name": "keyword.control.systemverilog" }, "2": { "name": "keyword.control.systemverilog" }, "3": { "name": "entity.name.type.class.systemverilog" } }, "match": "\\b(virtual\\s+)?(class)\\s+\\b([a-zA-Z_][a-zA-Z0-9_]*)\\b", "name": "meta.definition.class.systemverilog" }, { "captures": { "1": { "name": "keyword.control.systemverilog" }, "2": { "name": "entity.other.inherited-class.systemverilog" } }, "match": "\\b(extends)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\b", "name": "meta.definition.systemverilog" }, { "include": "#all-types" }, { "include": "#operators" }, { "include": "#port-dir" }, { "match": "\\b(and|nand|nor|or|xor|xnor|buf|not|bufif[01]|notif[01]|r?[npc]mos|tran|r?tranif[01]|pullup|pulldown)\\b", "name": "support.type.systemverilog" }, { "include": "#strings" }, { "match": "\\$\\b([a-zA-Z_][a-zA-Z0-9_]*)\\b", "name": "support.function.systemverilog" }, { "match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)(')(?=\\()", "name": "meta.cast.systemverilog", "captures": { "1": { "name": "storage.type.systemverilog" }, "2": { "name": "keyword.operator.cast.systemverilog" } } }, { "match": "^\\s*(localparam|parameter)\\s+([A-Z_][A-Z0-9_]*)\\b\\s*(?=(=))", "name": "meta.param.systemverilog", "captures": { "1": { "name": "keyword.other.systemverilog" }, "2": { "name": "constant.other.systemverilog" } } }, { "match": "^\\s*(localparam|parameter)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\b\\s*(?=(=))", "name": "meta.param.systemverilog", "captures": { "1": { "name": "keyword.other.systemverilog" } } }, { "match": "^\\s*(local\\s+|protected\\s+|localparam\\s+|parameter\\s+)?(const\\s+|virtual\\s+)?(rand\\s+|randc\\s+)?(([a-zA-Z_][a-zA-Z0-9_]*)(::))?([a-zA-Z_][a-zA-Z0-9_]*)\\b\\s*(?=(#\\s*\\([\\w,]+\\)\\s*)?([a-zA-Z][a-zA-Z0-9_\\s\\[\\]']*)(;|,|=|'\\{))", "name": "meta.userdefined.systemverilog", "captures": { "1": { "name": "keyword.other.systemverilog" }, "2": { "name": "keyword.other.systemverilog" }, "3": { "name": "storage.type.rand.systemverilog" }, "5": { "name": "support.type.scope.systemverilog" }, "6": { "name": "keyword.operator.scope.systemverilog" }, "7": { "name": "storage.type.userdefined.systemverilog" } } }, { "match": "\\s*\\b(option)\\.", "captures": { "1": { "name": "keyword.cover.systemverilog" } } }, { "match": "\\s*\\b(local|const|protected|virtual|localparam|parameter)\\b", "captures": { "1": { "name": "keyword.other.systemverilog" } } }, { "match": "\\s*\\b(rand|randc)\\b", "name": "storage.type.rand.systemverilog" }, { "begin": "^(\\s*(bind)\\s+([a-zA-Z_][\\w\\.]*))?\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=#[^#])", "beginCaptures": { "2": { "name": "keyword.control.systemverilog" }, "4": { "name": "storage.module.systemverilog" } }, "end": "(?=;|=|:)", "patterns": [ { "include": "#module-binding" }, { "include": "#module-param" }, { "include": "#comments" }, { "include": "#operators" }, { "include": "#constants" }, { "include": "#strings" }, { "match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)\\b(?=\\s*(\\(|$))", "name": "entity.name.type.module.systemverilog" } ], "name": "meta.module.inst.param.systemverilog" }, { "begin": "\\b([a-zA-Z_][a-zA-Z0-9_]*)\\s+(?!intersect|and|or|throughout|within)([a-zA-Z_][a-zA-Z0-9_]*)\\s*(\\[(\\d+)(\\:(\\d+))?\\])?\\s*(\\(|$)", "beginCaptures": { "1": { "name": "storage.module.systemverilog" }, "2": { "name": "entity.name.type.module.systemverilog" }, "4": { "name": "constant.numeric.systemverilog" }, "6": { "name": "constant.numeric.systemverilog" } }, "end": ";", "patterns": [ { "include": "#module-binding" }, { "include": "#comments" }, { "include": "#strings" }, { "include": "#operators" }, { "include": "#constants" } ], "name": "meta.module.inst.systemverilog" }, { "name": "meta.struct.assign.systemverilog", "begin": "\\b\\s+(=|<|>)", "name": "keyword.operator.comparison.systemverilog" }, { "match": "(\\-|\\+|\\*|\\/|%)", "name": "keyword.operator.arithmetic.systemverilog" }, { "match": "(!|&&|\\|\\||\\bor\\b)", "name": "keyword.operator.logical.systemverilog" }, { "match": "(&|\\||\\^|~|{|'{|}|<<|>>|\\?|:)", "name": "keyword.operator.bitwise.systemverilog" }, { "match": "(#|@)", "name": "keyword.operator.other.systemverilog" } ] }, "comments": { "patterns": [ { "begin": "/\\*", "captures": { "0": { "name": "punctuation.definition.comment.systemverilog" } }, "end": "\\*/", "name": "comment.block.systemverilog" }, { "captures": { "1": { "name": "punctuation.definition.comment.systemverilog" } }, "match": "(//).*$\\n?", "name": "comment.line.double-slash.systemverilog" } ] }, "port-dir": { "patterns": [ { "match": "\\s*\\b(output|input|inout|ref)\\s+(([a-zA-Z_][a-zA-Z0-9_]*)(::))?([a-zA-Z_][a-zA-Z0-9_]*)?\\s+(?=\\[[a-zA-Z0-9_\\-\\+]*:[a-zA-Z0-9_\\-\\+]*\\]\\s+[a-zA-Z_][a-zA-Z0-9_\\s]*)", "captures": { "1": { "name": "support.type.systemverilog" }, "3": { "name": "support.type.scope.systemverilog" }, "4": { "name": "keyword.operator.scope.systemverilog" }, "5": { "name": "storage.type.interface.systemverilog" } } }, { "match": "\\s*\\b(output|input|inout|ref)\\s+(([a-zA-Z_][a-zA-Z0-9_]*)(::))?([a-zA-Z_][a-zA-Z0-9_]*)?\\s+(?=[a-zA-Z_][a-zA-Z0-9_\\s]*)", "captures": { "1": { "name": "support.type.systemverilog" }, "3": { "name": "support.type.scope.systemverilog" }, "4": { "name": "keyword.operator.scope.systemverilog" }, "5": { "name": "storage.type.interface.systemverilog" } } }, { "match": "\\s*\\b(output|input|inout|ref)\\b", "name": "support.type.systemverilog" } ] }, "base-grammar": { "patterns": [ { "include": "#all-types" }, { "include": "#comments" }, { "include": "#operators" }, { "include": "#constants" }, { "include": "#strings" }, { "match": "^\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s+[a-zA-Z_][a-zA-Z0-9_,=\\s]*", "captures": { "1": { "name": "storage.type.interface.systemverilog" } } }, { "include": "#storage-scope-systemverilog" } ] }, "storage-type-systemverilog": { "patterns": [ { "match": "\\s*\\b(var|wire|tri|tri[01]|supply[01]|wand|triand|wor|trior|trireg|reg|integer|int|longint|shortint|logic|bit|byte|shortreal|string|time|realtime|real|process|void)\\b", "name": "storage.type.systemverilog" }, { "match": "\\s*\\b(uvm_transaction|uvm_component|uvm_monitor|uvm_driver|uvm_test|uvm_env|uvm_object|uvm_agent|uvm_sequence_base|uvm_sequence|uvm_sequence_item|uvm_sequence_state|uvm_sequencer|uvm_sequencer_base|uvm_component_registry|uvm_analysis_imp|uvm_analysis_port|uvm_analysis_export|uvm_config_db|uvm_active_passive_enum|uvm_phase|uvm_verbosity|uvm_tlm_analysis_fifo|uvm_tlm_fifo|uvm_report_server|uvm_objection|uvm_recorder|uvm_domain|uvm_reg_field|uvm_reg|uvm_reg_block|uvm_bitstream_t|uvm_radix_enum|uvm_printer|uvm_packer|uvm_comparer|uvm_scope_stack)\\b", "name": "storage.type.uvm.systemverilog" } ] }, "storage-scope-systemverilog": { "match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)(::)", "captures": { "1": { "name": "support.type.systemverilog" }, "2": { "name": "keyword.operator.scope.systemverilog" } }, "name": "meta.scope.systemverilog" }, "storage-modifier-systemverilog": { "match": "\\b(signed|unsigned|small|medium|large|supply[01]|strong[01]|pull[01]|weak[01]|highz[01])\\b", "name": "storage.modifier.systemverilog" }, "ifmodport": { "match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)\\.([a-zA-Z_][a-zA-Z0-9_]*)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\b", "captures": { "1": { "name": "storage.type.interface.systemverilog" }, "2": { "name": "support.modport.systemverilog" } } }, "strings": { "patterns": [ { "begin": "\"", "beginCaptures": { "0": { "name": "punctuation.definition.string.begin.systemverilog" } }, "end": "\"", "endCaptures": { "0": { "name": "punctuation.definition.string.end.systemverilog" } }, "name": "string.quoted.double.systemverilog", "patterns": [ { "match": "\\\\.", "name": "constant.character.escape.systemverilog" }, { "match": "(?x)%\n\t\t\t\t\t\t\t\t\t\t(\\d+\\$)? # field (argument #)\n\t\t\t\t\t\t\t\t\t\t[#0\\- +']* # flags\n\t\t\t\t\t\t\t\t\t\t[,;:_]? # separator character (AltiVec)\n\t\t\t\t\t\t\t\t\t\t((-?\\d+)|\\*(-?\\d+\\$)?)? # minimum field width\n\t\t\t\t\t\t\t\t\t\t(\\.((-?\\d+)|\\*(-?\\d+\\$)?)?)? # precision\n\t\t\t\t\t\t\t\t\t\t(hh|h|ll|l|j|t|z|q|L|vh|vl|v|hv|hl)? # length modifier\n\t\t\t\t\t\t\t\t\t\t[bdiouxXhHDOUeEfFgGaACcSspnmt%] # conversion type\n\t\t\t\t\t\t\t\t\t", "name": "constant.other.placeholder.systemverilog" }, { "match": "%", "name": "invalid.illegal.placeholder.systemverilog" } ] } ] }, "module-binding": { "begin": "\\.([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\(", "beginCaptures": { "1": { "name": "support.function.port.systemverilog" } }, "end": "\\)", "patterns": [ { "include": "#constants" }, { "include": "#comments" }, { "include": "#operators" }, { "include": "#strings" }, { "include": "#constants" }, { "match": "\\b([a-zA-Z_]\\w*)(::)", "captures": { "1": { "name": "support.type.scope.systemverilog" }, "2": { "name": "keyword.operator.scope.systemverilog" } } }, { "match": "\\b([a-zA-Z_]\\w*)(')", "captures": { "1": { "name": "storage.type.interface.systemverilog" }, "2": { "name": "keyword.operator.cast.systemverilog" } } }, { "match": "\\$\\b([a-zA-Z_][a-zA-Z0-9_]*)\\b", "name": "support.function.systemverilog" }, { "match": "\\b(virtual)\\b", "name": "keyword.control.systemverilog" } ], "match": "\\.([a-zA-Z_][a-zA-Z0-9_]*)\\s*", "captures": { "1": { "name": "support.function.port.implicit.systemverilog" } } }, "module-param": { "name": "meta.module-param.systemverilog", "begin": "(#)\\s*\\(", "beginCaptures": { "1": { "name": "keyword.operator.param.systemverilog" } }, "end": "\\)", "patterns": [ { "include": "#comments" }, { "include": "#constants" }, { "include": "#operators" }, { "include": "#strings" }, { "include": "#module-binding" }, { "match": "\\b(virtual)\\b", "name": "keyword.control.systemverilog" } ] }, "struct-anonymous": { "begin": "\\s*\\b(struct|union)\\s*(packed)?\\s*", "beginCaptures": { "1": { "name": "keyword.control.systemverilog" }, "2": { "name": "keyword.control.systemverilog" } }, "end": "(})\\s*([a-zA-Z_]\\w*)\\s*;", "endCaptures": { "1": { "name": "keyword.operator.other.systemverilog" } }, "patterns": [ { "include": "#base-grammar" } ], "name": "meta.struct.anonymous.systemverilog" } }, "scopeName": "source.systemverilog", "uuid": "789be04c-8b74-352e-8f37-63d336001277" } ================================================ FILE: extensions/vscode/tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "target": "es2019", "lib": ["ES2019"], "outDir": "out", "rootDir": "src", "sourceMap": true }, "include": ["src"], "exclude": ["node_modules", ".vscode-test"] } ================================================ FILE: extensions/vscode/tsconfig.tsbuildinfo ================================================ {"root":["./src/extension.ts"],"version":"5.9.3"} ================================================ FILE: rustfmt.toml ================================================ edition = "2018" max_width = 100 ================================================ FILE: src/completion/keyword.rs ================================================ use tower_lsp::lsp_types::*; pub fn keyword_completions(keywords: &[(&str, &str)]) -> Vec { let mut items: Vec = Vec::new(); for key in keywords { if key.1.is_empty() { items.push(CompletionItem { label: key.0.to_string(), kind: Some(CompletionItemKind::KEYWORD), ..CompletionItem::default() }); } else { items.push(CompletionItem { label: key.0.to_string(), kind: Some(CompletionItemKind::KEYWORD), insert_text: Some(key.1.to_string()), insert_text_format: Some(InsertTextFormat::SNIPPET), ..CompletionItem::default() }) } } items } pub fn other_completions(tasks: &[&str]) -> Vec { tasks .iter() .map(|x| CompletionItem { label: x.to_string(), kind: Some(CompletionItemKind::FUNCTION), ..CompletionItem::default() }) .collect() } pub const KEYWORDS: &[(&str, &str)] = &[ ("accept_on", ""), ("alias", ""), ("always", "always @($1) begin\nend"), ("always_comb", "always_comb begin\n\t$1\nend"), ("always_ff", "always_ff @($1) begin\nend"), ("always_latch", "always_latch begin\n\t$1\nend"), ("and", ""), ("assert", ""), ("assign", ""), ("assume", ""), ("automatic", ""), ("before", ""), ("begin", "begin\n\t$1\nend"), ("bind", ""), ("bins", ""), ("binsof", ""), ("bit", ""), ("break", ""), ("buf", ""), ("bufif0", ""), ("bufif1", ""), ("byte", ""), ("case", "case $1;\nendcase"), ("casex", "casex $1;\nendcase"), ("casez", "casez $1;\nendcase"), ("cell", ""), ("chandle", ""), ("checker", "checker $1;\nendchecker"), ("class", "class $1;\nendclass"), ("clocking", "clocking $1;\nendclocking"), ("cmos", ""), ("config", "config $1;\nendconfig"), ("const", ""), ("constraint", ""), ("context", ""), ("continue", ""), ("cover", ""), ("covergroup", ""), ("coverpoint", ""), ("cross", ""), ("deassign", ""), ("default", ""), ("defparam", ""), ("design", ""), ("disable", ""), ("dist", ""), ("do", ""), ("edge", ""), ("else", ""), ("end", ""), ("endcase", ""), ("endchecker", ""), ("endclass", ""), ("endclocking", ""), ("endconfig", ""), ("endfunction", ""), ("endgenerate", ""), ("endgroup", ""), ("endinterface", ""), ("endmodule", ""), ("endpackage", ""), ("endprimitive", ""), ("endprogram", ""), ("endproperty", ""), ("endspecify", ""), ("endsequence", ""), ("endtable", ""), ("endtask", ""), ("enum", ""), ("event", ""), ("eventually", ""), ("expect", ""), ("export", ""), ("extends", ""), ("extern", ""), ("final", ""), ("first_match", ""), ("for", ""), ("force", ""), ("foreach", ""), ("forever", ""), ("fork", ""), ("forkjoin", ""), ("function", "function $1;\nendfunction"), ("generate", "generate\n\t$1\nendgenerate"), ("genvar", ""), ("global", ""), ("highz0", ""), ("highz1", ""), ("if", ""), ("iff", ""), ("ifnone", ""), ("ignore_bins", ""), ("illegal_bins", ""), ("implements", ""), ("implies", ""), ("import", ""), ("incdir", ""), ("include", ""), ("initial", ""), ("inout", ""), ("input", ""), ("inside", ""), ("instance", ""), ("int", ""), ("integer", ""), ("interconnect", ""), ("interface", "interface $1;\nendinterface"), ("intersect", ""), ("join", ""), ("join_any", ""), ("join_none", ""), ("large", ""), ("let", ""), ("liblist", ""), ("library", ""), ("local", ""), ("localparam", ""), ("logic", ""), ("longint", ""), ("macromodule", ""), ("matches", ""), ("medium", ""), ("modport", ""), ("module", "module $1 ($2);\nendmodule"), ("nand", ""), ("negedge", ""), ("nettype", ""), ("new", ""), ("nexttime", ""), ("nmos", ""), ("nor", ""), ("noshowcancelled", ""), ("not", ""), ("notif0", ""), ("notif1", ""), ("null", ""), ("or", ""), ("output", ""), ("package", "package $1;\nendpackage"), ("packed", ""), ("parameter", ""), ("pmos", ""), ("posedge", ""), ("primitive", "primitive $1;\nendprimitive"), ("priority", ""), ("program", "program $1;\nendprogram"), ("property", "property $1;\nendproperty"), ("protected", ""), ("pull0", ""), ("pull1", ""), ("pulldown", ""), ("pullup", ""), ("pulsestyle_ondetect", ""), ("pulsestyle_onevent", ""), ("pure", ""), ("rand", ""), ("randc", ""), ("randcase", ""), ("randsequence", ""), ("rcmos", ""), ("real", ""), ("realtime", ""), ("ref", ""), ("reg", ""), ("reject_on", ""), ("release", ""), ("repeat", ""), ("restrict", ""), ("return", ""), ("rnmos", ""), ("rpmos", ""), ("rtran", ""), ("rtranif0", ""), ("rtranif1", ""), ("s_always", ""), ("s_eventually", ""), ("s_nexttime", ""), ("s_until", ""), ("s_until_with", ""), ("scalared", ""), ("sequence", "sequence $1;\nendsequence"), ("shortint", ""), ("shortreal", ""), ("showcancelled", ""), ("signed", ""), ("small", ""), ("soft", ""), ("solve", ""), ("specify", "specify $1;\nendspecify"), ("specparam", ""), ("static", ""), ("string", ""), ("strong", ""), ("strong0", ""), ("strong1", ""), ("struct", ""), ("super", ""), ("supply0", ""), ("supply1", ""), ("sync_accept_on", ""), ("sync_reject_on", ""), ("table", "table $1;\nendtable"), ("tagged", ""), ("task", "task $1;\nendtask"), ("this", ""), ("throughout", ""), ("time", ""), ("timeprecision", ""), ("timeunit", ""), ("tran", ""), ("tranif0", ""), ("tranif1", ""), ("tri", ""), ("tri0", ""), ("tri1", ""), ("triand", ""), ("trior", ""), ("trireg", ""), ("type", ""), ("typedef", ""), ("union", ""), ("unique", ""), ("unique0", ""), ("unsigned", ""), ("until", ""), ("until_with", ""), ("untyped", ""), ("use", ""), ("uwire", ""), ("var", ""), ("vectored", ""), ("virtual", ""), ("void", ""), ("wait", ""), ("wait_order", ""), ("wand", ""), ("weak", ""), ("weak0", ""), ("weak1", ""), ("while", ""), ("wildcard", ""), ("wire", ""), ("with", ""), ("within", ""), ("wor", ""), ("xnor", ""), ("xor", ""), ]; pub const SYS_TASKS: &[&str] = &[ "finish", "exit", "fatal", "warning", "stop", "error", "info", "realtime", "time", "asserton", "assertkill", "assertpasson", "assertfailon", "assertnonvacuouson", "stime", "printtimescale", "timeformat", "bitstoreal", "bitstoshortreal", "itor", "signed", "cast", "realtobits", "shortrealtobits", "rtoi", "unsigned", "sampled", "fell", "changed", "past_gclk", "fell_gclk", "changed_gclk", "rising_gclk", "steady_gclk", "bits", "typename", "isunbounded", "coverage_control", "coverage_get", "coverage_save", "set_coverage_db_name", "dimensions", "right", "high", "size", "random", "dist_erlang", "dist_normal", "dist_t", "asin", "acos", "atan", "atan2", "hypot", "sinh", "cosh", "tanh", "asinh", "acosh", "atanh", "q_initialize", "q_remove", "q_exam", "q_add", "q_full", "async$and$array", "async$nand$array", "async$or$array", "async$nor$array", "sync$and$array", "sync$nand$array", "sync$or$array", "sync$nor$array", "countones", "onehot0", "fatal", "warning", "dist_chi_square", "dist_exponential", "dist_poisson", "dist_uniform", "countbits", "onehot", "isunknown", "coverage_get_max", "coverage_merge", "get_coverage", "load_coverage_db", "clog2", "ln", "log10", "exp", "sqrt", "pow", "floor", "ceil", "sin", "cos", "tan", "rose", "stable", "past", "rose_gclk", "stable_gclk", "future_gclk", "falling_gclk", "changing_gclk", "unpacked_dimensions", "left", "low", "increment", "assertoff", "assertcontrol", "assertpassoff", "assertfailoff", "assertvacuousoff", "error", "info", "async$and$plane", "async$nand$plane", "async$or$plane", "async$nor$plane", "sync$and$plane", "sync$nand$plane", "sync$or$plane", "sync$nor$plane", "system", "countdrivers", "getpattern", "incsave", "input", "key", "list", "log", "nokey", "nolog", "reset", "reset_count", "reset_value", "restart", "save", "scale", "scope", "showscopes", "showvars", "sreadmemb", "sreadmemh", ]; pub const DIRECTIVES: &[&str] = &[ "__FILE__", "__LINE__", "begin_keywords", "celldefine", "default_nettype", "define", "else", "elsif", "end_keywords", "endcelldefine", "endif", "ifdef", "ifndef", "include", "line", "nounconnected_drive", "pragma", "resetall", "timescale", "unconnected_drive", "undef", "undefineall", "default_decay_time", "default_trireg_strength", "delay_mode_distributed", "delay_mode_path", "delay_mode_unit", "delay_mode_zero", ]; ================================================ FILE: src/completion.rs ================================================ use crate::server::LSPServer; use crate::sources::LSPSupport; use log::{debug, trace}; use ropey::{Rope, RopeSlice}; use std::time::Instant; use tower_lsp::lsp_types::*; pub mod keyword; impl LSPServer { pub fn completion(&self, params: CompletionParams) -> Option { debug!("completion requested"); trace!("{:#?}", ¶ms); let now = Instant::now(); let doc = params.text_document_position; let file_id = self.srcs.get_id(&doc.text_document.uri).to_owned(); self.srcs.wait_parse_ready(file_id, false); trace!("comp wait parse: {}", now.elapsed().as_millis()); let file = self.srcs.get_file(file_id)?; let file = file.read().ok()?; trace!("comp read: {}", now.elapsed().as_millis()); let token = get_completion_token( &file.text, file.text.line(doc.position.line as usize), doc.position, ); let response = match params.context { Some(context) => match context.trigger_kind { CompletionTriggerKind::TRIGGER_CHARACTER => { debug!( "trigger char completion: {}", context.trigger_character.clone()?.as_str() ); match context.trigger_character?.as_str() { "." => Some(self.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: self.sys_tasks.clone(), }), "`" => Some(CompletionList { is_incomplete: false, items: self.directives.clone(), }), _ => None, } } CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS => None, CompletionTriggerKind::INVOKED => { debug!("Invoked Completion"); let mut comps = self.srcs.get_completions( &token, file.text.pos_to_byte(&doc.position), &doc.text_document.uri, )?; // complete keywords comps.items.extend::>( self.key_comps .iter() .filter(|x| x.label.starts_with(&token)) .cloned() .collect(), ); Some(comps) } _ => None, }, None => { let trigger = prev_char(&file.text, &doc.position); match trigger { '.' => Some(self.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: self.sys_tasks.clone(), }), '`' => Some(CompletionList { is_incomplete: false, items: self.directives.clone(), }), _ => { let mut comps = self.srcs.get_completions( &token, file.text.pos_to_byte(&doc.position), &doc.text_document.uri, )?; comps.items.extend::>( self.key_comps .iter() .filter(|x| x.label.starts_with(&token)) .cloned() .collect(), ); Some(comps) } } } }; // eprintln!("comp response: {}", now.elapsed().as_millis()); Some(CompletionResponse::List(response?)) } } /// get the previous non-whitespace character fn prev_char(text: &Rope, pos: &Position) -> char { let char_idx = text.pos_to_char(pos); if char_idx > 0 { for i in (0..char_idx).rev() { let res = text.char(i); if !res.is_whitespace() { return res; } } ' ' } else { ' ' } } /// attempt to get the token the user was trying to complete, by /// filtering out characters unneeded for name resolution fn get_completion_token(text: &Rope, line: RopeSlice, pos: Position) -> String { let mut token = String::new(); let mut line_iter = line.chars(); for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) { line_iter.next(); } let mut c = line_iter.prev(); //TODO: make this a regex while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_' || c.unwrap() == '.' || c.unwrap() == '[' || c.unwrap() == ']') { token.push(c.unwrap()); c = line_iter.prev(); } let mut result: String = token.chars().rev().collect(); if result.contains('[') { let l_bracket_offset = result.find('[').unwrap_or(result.len()); result.replace_range(l_bracket_offset.., ""); } if &result == "." { // probably a instantiation, the token should be what we're instatiating let mut char_iter = text.chars(); let mut token = String::new(); for _ in 0..text.pos_to_char(&pos) { char_iter.next(); } let mut c = char_iter.prev(); // go to the last semicolon while c.is_some() && (c.unwrap() != ';') { c = char_iter.prev(); } // go the the start of the next symbol while c.is_some() && !(c.unwrap().is_alphanumeric() || c.unwrap() == '_') { c = char_iter.next(); } // then extract the next symbol while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') { token.push(c.unwrap()); c = char_iter.next(); } token } else { result } } #[cfg(test)] mod tests { use super::*; use crate::definition::def_types::Scope; use crate::definition::get_scopes; use crate::sources::{parse, LSPSupport}; use crate::support::test_init; use ropey::Rope; #[test] fn test_get_completion_token() { test_init(); let text = Rope::from_str("abc abc.cba de_fg cde[4]"); let mut result = get_completion_token( &text, text.line(0), Position { line: 0, character: 3, }, ); assert_eq!(&result, "abc"); result = get_completion_token( &text, text.line(0), Position { line: 0, character: 11, }, ); assert_eq!(&result, "abc.cba"); result = get_completion_token( &text, text.line(0), Position { line: 0, character: 16, }, ); assert_eq!(&result, "de_f"); result = get_completion_token( &text, text.line(0), Position { line: 0, character: 23, }, ); assert_eq!(&result, "cde"); } #[test] fn test_completion() { test_init(); let server = LSPServer::new(None); let uri = Url::parse("file:///test.sv").unwrap(); let text = r#"module test; logic abc; logic abcd; endmodule "#; let open_params = DidOpenTextDocumentParams { text_document: TextDocumentItem { uri: uri.clone(), language_id: "systemverilog".to_owned(), version: 0, text: text.to_owned(), }, }; server.did_open(open_params); let fid = server.srcs.get_id(&uri); server.srcs.wait_parse_ready(fid, true); let change_params = DidChangeTextDocumentParams { text_document: VersionedTextDocumentIdentifier { uri: uri.clone(), version: 3, }, content_changes: vec![ TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 3, character: 0, }, end: Position { line: 3, character: 0, }, }), range_length: None, text: "\n".to_owned(), }, TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 4, character: 0, }, end: Position { line: 4, character: 0, }, }), range_length: None, text: " ".to_owned(), }, TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 4, character: 2, }, end: Position { line: 4, character: 2, }, }), range_length: None, text: "a".to_owned(), }, ], }; server.did_change(change_params); server.srcs.wait_parse_ready(fid, true); let completion_params = CompletionParams { text_document_position: TextDocumentPositionParams { text_document: TextDocumentIdentifier { uri }, position: Position { line: 4, character: 3, }, }, work_done_progress_params: WorkDoneProgressParams::default(), partial_result_params: PartialResultParams::default(), context: Some(CompletionContext { trigger_kind: CompletionTriggerKind::INVOKED, trigger_character: None, }), }; let response: CompletionResponse = server.completion(completion_params).unwrap(); let item1 = CompletionItem { label: "abc".to_owned(), kind: Some(CompletionItemKind::VARIABLE), detail: Some("logic".to_string()), ..CompletionItem::default() }; let item2 = CompletionItem { label: "abcd".to_owned(), kind: Some(CompletionItemKind::VARIABLE), detail: Some("logic".to_string()), ..CompletionItem::default() }; if let CompletionResponse::List(item) = response { assert!(item.items.contains(&item1)); assert!(item.items.contains(&item2)); } else { panic!(); } } #[test] fn test_nested_completion() { test_init(); let server = LSPServer::new(None); let uri = Url::parse("file:///test.sv").unwrap(); let text = r#"module test; logic aouter; function func1(); logic abc; func1 = abc; endfunction function func2(); logic abcd; func2 = abcd; endfunction endmodule "#; let open_params = DidOpenTextDocumentParams { text_document: TextDocumentItem { uri: uri.clone(), language_id: "systemverilog".to_owned(), version: 0, text: text.to_owned(), }, }; server.did_open(open_params); let fid = server.srcs.get_id(&uri); server.srcs.wait_parse_ready(fid, true); let change_params = DidChangeTextDocumentParams { text_document: VersionedTextDocumentIdentifier { uri: uri.clone(), version: 3, }, content_changes: vec![ TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 4, character: 0, }, end: Position { line: 4, character: 0, }, }), range_length: None, text: "\n".to_owned(), }, TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 4, character: 0, }, end: Position { line: 4, character: 0, }, }), range_length: None, text: " ".to_owned(), }, TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 4, character: 2, }, end: Position { line: 4, character: 2, }, }), range_length: None, text: "a".to_owned(), }, ], }; server.did_change(change_params); server.srcs.wait_parse_ready(fid, true); let completion_params = CompletionParams { text_document_position: TextDocumentPositionParams { text_document: TextDocumentIdentifier { uri }, position: Position { line: 4, character: 3, }, }, work_done_progress_params: WorkDoneProgressParams::default(), partial_result_params: PartialResultParams::default(), context: Some(CompletionContext { trigger_kind: CompletionTriggerKind::INVOKED, trigger_character: None, }), }; let response: CompletionResponse = server.completion(completion_params).unwrap(); let item1 = CompletionItem { label: "abc".to_owned(), kind: Some(CompletionItemKind::VARIABLE), detail: Some("logic".to_string()), ..CompletionItem::default() }; let item3 = CompletionItem { label: "aouter".to_owned(), kind: Some(CompletionItemKind::VARIABLE), detail: Some("logic".to_string()), ..CompletionItem::default() }; if let CompletionResponse::List(item) = response { eprintln!("{:#?}", item); assert!(item.items.contains(&item1)); for comp in &item.items { assert!(comp.label != "abcd"); } assert!(item.items.contains(&item3)); } else { panic!(); } } #[test] fn test_dot_completion() { test_init(); let server = LSPServer::new(None); let uri = Url::parse("file:///test.sv").unwrap(); let text = r#"interface test_inter; wire abcd; endinterface module test( test_inter abc ); abc. test_inter. endmodule "#; let open_params = DidOpenTextDocumentParams { text_document: TextDocumentItem { uri: uri.clone(), language_id: "systemverilog".to_owned(), version: 0, text: text.to_owned(), }, }; server.did_open(open_params); let fid = server.srcs.get_id(&uri); server.srcs.wait_parse_ready(fid, true); let file = server.srcs.get_file(fid).unwrap(); let file = file.read().unwrap(); eprintln!("{}", file.syntax_tree.as_ref().unwrap()); eprintln!( "{:#?}", server.srcs.scope_tree.read().unwrap().as_ref().unwrap() ); let completion_params = CompletionParams { text_document_position: TextDocumentPositionParams { text_document: TextDocumentIdentifier { uri: uri.clone() }, position: Position { line: 6, character: 8, }, }, work_done_progress_params: WorkDoneProgressParams::default(), partial_result_params: PartialResultParams::default(), context: Some(CompletionContext { trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER, trigger_character: Some(".".to_string()), }), }; let response: CompletionResponse = server.completion(completion_params).unwrap(); dbg!(&response); let item1 = CompletionItem { label: "abcd".to_owned(), kind: Some(CompletionItemKind::VARIABLE), detail: Some("wire".to_string()), ..CompletionItem::default() }; if let CompletionResponse::List(item) = response { eprintln!("{:#?}", item); assert!(item.items.contains(&item1)); assert!(item.items.len() == 1); } else { panic!(); } let completion_params = CompletionParams { text_document_position: TextDocumentPositionParams { text_document: TextDocumentIdentifier { uri }, position: Position { line: 7, character: 14, }, }, work_done_progress_params: WorkDoneProgressParams::default(), partial_result_params: PartialResultParams::default(), context: Some(CompletionContext { trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER, trigger_character: Some(".".to_string()), }), }; let response: CompletionResponse = server.completion(completion_params).unwrap(); if let CompletionResponse::List(item) = response { eprintln!("{:#?}", item); assert!(item.items.contains(&item1)); assert!(item.items.len() == 1); } else { panic!(); } } #[test] fn test_trigger_dot_nocontext() { test_init(); let server = LSPServer::new(None); let uri = Url::parse("file:///test.sv").unwrap(); let text = r#"interface test_inter; wire abcd; endinterface module test( test_inter abc ); abc. test_inter. endmodule "#; let open_params = DidOpenTextDocumentParams { text_document: TextDocumentItem { uri: uri.clone(), language_id: "systemverilog".to_owned(), version: 0, text: text.to_owned(), }, }; server.did_open(open_params); let fid = server.srcs.get_id(&uri); server.srcs.wait_parse_ready(fid, true); let file = server.srcs.get_file(fid).unwrap(); let file = file.read().unwrap(); eprintln!("{}", file.syntax_tree.as_ref().unwrap()); eprintln!( "{:#?}", server.srcs.scope_tree.read().unwrap().as_ref().unwrap() ); let completion_params = CompletionParams { text_document_position: TextDocumentPositionParams { text_document: TextDocumentIdentifier { uri: uri.clone() }, position: Position { line: 6, character: 8, }, }, work_done_progress_params: WorkDoneProgressParams::default(), partial_result_params: PartialResultParams::default(), context: None, }; let response: CompletionResponse = server.completion(completion_params).unwrap(); dbg!(&response); let item1 = CompletionItem { label: "abcd".to_owned(), kind: Some(CompletionItemKind::VARIABLE), detail: Some("wire".to_string()), ..CompletionItem::default() }; if let CompletionResponse::List(item) = response { eprintln!("{:#?}", item); assert!(item.items.contains(&item1)); assert!(item.items.len() == 1); } else { panic!(); } let completion_params = CompletionParams { text_document_position: TextDocumentPositionParams { text_document: TextDocumentIdentifier { uri }, position: Position { line: 7, character: 14, }, }, work_done_progress_params: WorkDoneProgressParams::default(), partial_result_params: PartialResultParams::default(), context: Some(CompletionContext { trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER, trigger_character: Some(".".to_string()), }), }; let response: CompletionResponse = server.completion(completion_params).unwrap(); if let CompletionResponse::List(item) = response { eprintln!("{:#?}", item); assert!(item.items.contains(&item1)); assert!(item.items.len() == 1); } else { panic!(); } } #[test] fn test_dot_completion_instantiation() { test_init(); let text = r#"interface test_inter; wire wrong; logic clk; endinterface module test; logic clk; test_inter2 t ( .clk(clk), . ) endmodule interface test_inter2; wire abcd; logic clk; endinterface "#; let doc = Rope::from_str(text); let url = Url::parse("file:///test.sv").unwrap(); let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap(); let scope_tree = get_scopes(&syntax_tree, &url).unwrap(); let pos = Position::new(8, 9); let token = get_completion_token(&doc, doc.line(pos.line as usize), pos); let completions = scope_tree.get_dot_completion( token.trim_end_matches('.'), doc.pos_to_byte(&pos), &url, &scope_tree, ); let labels: Vec = completions.iter().map(|x| x.label.clone()).collect(); assert_eq!(labels, vec!["abcd", "clk"]); } /* #[test] fn test_package_completion() { test_init(); let text = r#"package p; struct {int x;} s1; struct {int x;} s2; function void f(); int x; endfunction endpackage module m; import p::*; if (1) begin : s1 initial begin s1.x = 1; f.x = 1; end int x; end endmodule "#; let doc = Rope::from_str(&text); let url = Url::parse("file:///test.sv").unwrap(); let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap(); let scope_tree = get_scopes(&syntax_tree, &url).unwrap(); dbg!(&scope_tree); /* let pos = Position::new(8, 9); let token = get_completion_token(&doc, doc.line(pos.line as usize), pos); let completions = scope_tree.get_dot_completion( token.trim_end_matches('.'), doc.pos_to_byte(&pos), &url, &scope_tree, ); let labels: Vec = completions.iter().map(|x| x.label.clone()).collect(); assert_eq!(labels, vec!["abcd", "clk"]); */ panic!(); } */ #[test] fn test_inter_file_completion() { test_init(); let server = LSPServer::new(None); let uri = Url::parse("file:///test.sv").unwrap(); let uri2 = Url::parse("file:///test2.sv").unwrap(); let text = r#"module test; s endmodule "#; let text2 = r#"interface simple_bus; logic clk; endinterface"#; let open_params = DidOpenTextDocumentParams { text_document: TextDocumentItem { uri: uri.clone(), language_id: "systemverilog".to_owned(), version: 0, text: text.to_owned(), }, }; let open_params2 = DidOpenTextDocumentParams { text_document: TextDocumentItem { uri: uri2.clone(), language_id: "systemverilog".to_owned(), version: 0, text: text2.to_owned(), }, }; server.did_open(open_params); server.did_open(open_params2); let fid = server.srcs.get_id(&uri); let fid2 = server.srcs.get_id(&uri2); server.srcs.wait_parse_ready(fid, true); server.srcs.wait_parse_ready(fid2, true); let completion_params = CompletionParams { text_document_position: TextDocumentPositionParams { text_document: TextDocumentIdentifier { uri }, position: Position { line: 1, character: 5, }, }, work_done_progress_params: WorkDoneProgressParams::default(), partial_result_params: PartialResultParams::default(), context: Some(CompletionContext { trigger_kind: CompletionTriggerKind::INVOKED, trigger_character: None, }), }; let response: CompletionResponse = server.completion(completion_params).unwrap(); let scope_tree = server.srcs.scope_tree.read().unwrap(); dbg!(scope_tree.as_ref().unwrap()); if let CompletionResponse::List(item) = response { // eprintln!("{:#?}", item); let names: Vec<&String> = item.items.iter().map(|x| &x.label).collect(); assert!(names.contains(&&"simple_bus".to_string())); } else { panic!(); } } } ================================================ FILE: src/definition/def_types.rs ================================================ use crate::sources::LSPSupport; use log::trace; use ropey::Rope; use tower_lsp::lsp_types::*; /// cleanup the text of a definition so it can be included in completions pub fn clean_type_str(type_str: &str, ident: &str) -> String { let endings: &[_] = &[';', ',']; // remove anything after an equals sign let eq_offset = type_str.find('=').unwrap_or(type_str.len()); let mut result = type_str.to_string(); result.replace_range(eq_offset.., ""); result .trim_start() .trim_end() .trim_end_matches(endings) .trim_end_matches(ident) .split_whitespace() .collect::>() .join(" ") .replace("[ ", "[") .replace(" ]", "]") .replace(" : ", ":") } pub fn copy_defs(defs: &[Box]) -> Vec> { let mut decs: Vec> = Vec::new(); for def in defs { decs.push(Box::new(GenericDec { ident: def.ident(), byte_idx: def.byte_idx(), url: def.url(), type_str: def.type_str(), completion_kind: def.completion_kind(), symbol_kind: def.symbol_kind(), def_type: def.def_type(), })) } decs } pub fn copy_scopes(scopes: &[Box]) -> Vec> { let mut scope_decs: Vec> = Vec::new(); for scope in scopes { let mut scope_copy = GenericScope { ident: scope.ident(), byte_idx: scope.byte_idx(), start: scope.start(), end: scope.end(), url: scope.url(), type_str: scope.type_str(), completion_kind: scope.completion_kind(), symbol_kind: scope.symbol_kind(), def_type: scope.def_type(), defs: Vec::new(), scopes: Vec::new(), }; scope_copy.defs.extend(copy_defs(scope.defs())); scope_copy.scopes.extend(copy_scopes(scope.scopes())); scope_decs.push(Box::new(scope_copy)) } scope_decs } /// A definition of any SystemVerilog variable or construct pub trait Definition: std::fmt::Debug + Sync + Send { // identifier fn ident(&self) -> String; // byte index in file of definition fn byte_idx(&self) -> usize; // url pointing to the file the definition is in fn url(&self) -> Url; // cleaned up text of the definition fn type_str(&self) -> String; // the kind of this definition, for use in completions fn completion_kind(&self) -> CompletionItemKind; // the kind of this definition, for use in showing document symbols // for some reason this kind is different than CompletionItemKind fn symbol_kind(&self) -> SymbolKind; // the kind of this definition, simplified for internal use fn def_type(&self) -> DefinitionType; // whether the definition identifier starts with the given token fn starts_with(&self, token: &str) -> bool; // constructs the completion for this definition fn completion(&self) -> CompletionItem; fn dot_completion(&self, scope_tree: &GenericScope) -> Vec; } pub trait Scope: std::fmt::Debug + Definition + Sync + Send { // the start byte of this scope fn start(&self) -> usize; // the end byte of this scope fn end(&self) -> usize; // all the within this scope fn defs(&self) -> &Vec>; // all the scopes within this scope, ex. task inside a module fn scopes(&self) -> &Vec>; // the definition of this scope fn definition(&self) -> GenericDec { GenericDec { ident: self.ident(), byte_idx: self.byte_idx(), url: self.url(), type_str: self.type_str(), completion_kind: self.completion_kind(), symbol_kind: self.symbol_kind(), def_type: DefinitionType::GenericScope, } } /// return a completion from the scope tree, this function should be called on the global scope fn get_completion(&self, token: &str, byte_idx: usize, url: &Url) -> Vec { let mut completions: Vec = Vec::new(); // 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() { completions = scope.get_completion(token, byte_idx, url); break; } } // 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 = completions.iter().map(|x| x.label.clone()).collect(); for def in self.defs() { if !completion_idents.contains(&def.ident()) && def.starts_with(token) { completions.push(def.completion()); } } for scope in self.scopes() { if scope.starts_with(token) { completions.push(scope.completion()); } } 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 { trace!("dot entering: {}, token: {}", self.ident(), token); trace!("{:?}", self.scopes()); // first we need to go down the scope tree, to the scope the user is invoking a completion // in for scope in self.scopes() { trace!( "{}, {}, {}, {}", scope.ident(), byte_idx, scope.start(), scope.end() ); 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() { trace!("def: {:?}", def); if def.starts_with(token) { trace!("complete def: {:?}", def); return def.dot_completion(scope_tree); } } for scope in self.scopes() { if scope.starts_with(token) { trace!("found dot-completion scope: {}", scope.ident()); return scope.dot_completion(scope_tree); } } Vec::new() } /// return a definition from the scope tree, this function should be called on the global /// scope fn get_definition(&self, token: &str, byte_idx: usize, url: &Url) -> Option { let mut definition: Option = None; for scope in self.scopes() { if &scope.url() == url && scope.start() <= byte_idx && byte_idx <= scope.end() { definition = scope.get_definition(token, byte_idx, url); break; } } if definition.is_none() { for def in self.defs() { if def.ident() == token { return Some(GenericDec { ident: def.ident(), byte_idx: def.byte_idx(), url: def.url(), type_str: def.type_str(), completion_kind: def.completion_kind(), symbol_kind: def.symbol_kind(), def_type: DefinitionType::Net, }); } } for scope in self.scopes() { if scope.ident() == token { return Some(scope.definition()); } } } definition } /// returns all symbols in a document fn document_symbols(&self, uri: &Url, doc: &Rope) -> Vec { let mut symbols: Vec = Vec::new(); for scope in self.scopes() { if &scope.url() == uri { #[allow(deprecated)] symbols.push(DocumentSymbol { name: scope.ident(), detail: Some(scope.type_str()), kind: scope.symbol_kind(), deprecated: None, range: Range::new(doc.byte_to_pos(scope.start()), doc.byte_to_pos(scope.end())), selection_range: Range::new( doc.byte_to_pos(scope.byte_idx()), doc.byte_to_pos(scope.byte_idx() + scope.ident().len()), ), children: Some(scope.document_symbols(uri, doc)), tags: None, }) } } for def in self.defs() { #[allow(deprecated)] symbols.push(DocumentSymbol { name: def.ident(), detail: Some(def.type_str()), kind: def.symbol_kind(), deprecated: None, range: Range::new( doc.byte_to_pos(def.byte_idx()), doc.byte_to_pos(def.byte_idx() + def.ident().len()), ), selection_range: Range::new( doc.byte_to_pos(def.byte_idx()), doc.byte_to_pos(def.byte_idx() + def.ident().len()), ), children: None, tags: None, }) } symbols } /// highlight all references of a symbol fn document_highlights( &self, uri: &Url, doc: &Rope, // all references in the doc's syntax tree references: Vec<(String, usize)>, // byte_idx of symbol definition byte_idx: usize, ) -> Vec { // to find references we need to grab references from locations downward from the // definition for scope in self.scopes() { if &scope.url() == uri && scope.start() <= byte_idx && byte_idx <= scope.end() { return scope.document_highlights(uri, doc, references, byte_idx); } } // we should now be in the scope of the definition, so we can grab all references // in this scope. This also grabs references below this scope. references .iter() .filter(|x| self.start() <= x.1 && x.1 <= self.end()) .map(|x| DocumentHighlight { range: Range::new(doc.byte_to_pos(x.1), doc.byte_to_pos(x.1 + x.0.len())), kind: None, }) .collect() } } #[derive(Debug, Clone, Copy)] pub enum DefinitionType { Port, Net, Data, Modport, Subroutine, ModuleInstantiation, GenericScope, Class, } #[derive(Debug)] pub struct PortDec { pub ident: String, pub byte_idx: usize, pub url: Url, pub type_str: String, pub completion_kind: CompletionItemKind, pub symbol_kind: SymbolKind, pub def_type: DefinitionType, pub interface: Option, pub modport: Option, } impl PortDec { pub fn new(url: &Url) -> Self { Self { ident: String::new(), byte_idx: 0, type_str: String::new(), completion_kind: CompletionItemKind::PROPERTY, symbol_kind: SymbolKind::PROPERTY, def_type: DefinitionType::Port, interface: None, modport: None, url: url.clone(), } } } impl Definition for PortDec { fn ident(&self) -> String { self.ident.clone() } fn byte_idx(&self) -> usize { self.byte_idx } fn url(&self) -> Url { self.url.clone() } fn type_str(&self) -> String { self.type_str.clone() } fn completion_kind(&self) -> CompletionItemKind { self.completion_kind } fn symbol_kind(&self) -> SymbolKind { self.symbol_kind } fn def_type(&self) -> DefinitionType { self.def_type } fn starts_with(&self, token: &str) -> bool { self.ident.starts_with(token) } fn completion(&self) -> CompletionItem { CompletionItem { label: self.ident.clone(), detail: Some(clean_type_str(&self.type_str, &self.ident)), kind: Some(self.completion_kind), ..CompletionItem::default() } } fn dot_completion(&self, scope_tree: &GenericScope) -> Vec { 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)] pub struct GenericDec { pub ident: String, pub byte_idx: usize, pub url: Url, pub type_str: String, pub completion_kind: CompletionItemKind, pub symbol_kind: SymbolKind, pub def_type: DefinitionType, } impl GenericDec { pub fn new(url: &Url) -> Self { Self { ident: String::new(), byte_idx: 0, url: url.clone(), type_str: String::new(), completion_kind: CompletionItemKind::VARIABLE, // FIXME: check if this replacement is correct symbol_kind: SymbolKind::NULL, def_type: DefinitionType::Net, } } } impl Definition for GenericDec { fn ident(&self) -> String { self.ident.clone() } fn byte_idx(&self) -> usize { self.byte_idx } fn url(&self) -> Url { self.url.clone() } fn type_str(&self) -> String { self.type_str.clone() } fn completion_kind(&self) -> CompletionItemKind { self.completion_kind } fn symbol_kind(&self) -> SymbolKind { self.symbol_kind } fn def_type(&self) -> DefinitionType { self.def_type } fn starts_with(&self, token: &str) -> bool { self.ident.starts_with(token) } fn completion(&self) -> CompletionItem { CompletionItem { label: self.ident.clone(), detail: Some(clean_type_str(&self.type_str, &self.ident)), kind: Some(self.completion_kind), ..CompletionItem::default() } } fn dot_completion(&self, _: &GenericScope) -> Vec { Vec::new() } } #[derive(Debug)] pub struct PackageImport { pub ident: String, pub byte_idx: usize, pub url: Url, pub type_str: String, pub completion_kind: CompletionItemKind, pub symbol_kind: SymbolKind, pub def_type: DefinitionType, pub asterisk: bool, pub import_ident: Option, } impl PackageImport { pub fn new(url: &Url) -> Self { Self { ident: String::new(), byte_idx: 0, url: url.clone(), type_str: String::new(), completion_kind: CompletionItemKind::TEXT, symbol_kind: SymbolKind::NAMESPACE, def_type: DefinitionType::Data, asterisk: false, import_ident: None, } } } impl Definition for PackageImport { fn ident(&self) -> String { self.ident.clone() } fn byte_idx(&self) -> usize { self.byte_idx } fn url(&self) -> Url { self.url.clone() } fn type_str(&self) -> String { self.type_str.clone() } fn completion_kind(&self) -> CompletionItemKind { self.completion_kind } fn symbol_kind(&self) -> SymbolKind { self.symbol_kind } fn def_type(&self) -> DefinitionType { self.def_type } fn starts_with(&self, token: &str) -> bool { self.ident.starts_with(token) } fn completion(&self) -> CompletionItem { CompletionItem { label: self.ident.clone(), detail: Some(clean_type_str(&self.type_str, &self.ident.clone())), kind: Some(self.completion_kind), ..CompletionItem::default() } } fn dot_completion(&self, _: &GenericScope) -> Vec { Vec::new() } } #[derive(Debug)] pub struct SubDec { pub ident: String, pub byte_idx: usize, pub url: Url, pub type_str: String, pub completion_kind: CompletionItemKind, pub symbol_kind: SymbolKind, pub def_type: DefinitionType, pub start: usize, pub end: usize, pub defs: Vec>, pub scopes: Vec>, } impl SubDec { pub fn new(url: &Url) -> Self { Self { ident: String::new(), byte_idx: 0, url: url.clone(), type_str: String::new(), completion_kind: CompletionItemKind::FUNCTION, symbol_kind: SymbolKind::FUNCTION, def_type: DefinitionType::Subroutine, start: 0, end: 0, defs: Vec::new(), scopes: Vec::new(), } } } impl Definition for SubDec { fn ident(&self) -> String { self.ident.clone() } fn byte_idx(&self) -> usize { self.byte_idx } fn url(&self) -> Url { self.url.clone() } fn type_str(&self) -> String { self.type_str.clone() } fn completion_kind(&self) -> CompletionItemKind { self.completion_kind } fn symbol_kind(&self) -> SymbolKind { self.symbol_kind } fn def_type(&self) -> DefinitionType { self.def_type } fn starts_with(&self, token: &str) -> bool { self.ident.starts_with(token) } fn completion(&self) -> CompletionItem { CompletionItem { label: self.ident.clone(), detail: Some(clean_type_str(&self.type_str, &self.ident)), kind: Some(self.completion_kind), ..CompletionItem::default() } } fn dot_completion(&self, _: &GenericScope) -> Vec { Vec::new() } } impl Scope for SubDec { fn start(&self) -> usize { self.start } fn end(&self) -> usize { self.end } fn defs(&self) -> &Vec> { &self.defs } fn scopes(&self) -> &Vec> { &self.scopes } } #[derive(Debug)] pub struct ModportDec { pub ident: String, pub byte_idx: usize, pub url: Url, pub type_str: String, pub completion_kind: CompletionItemKind, pub symbol_kind: SymbolKind, pub def_type: DefinitionType, pub ports: Vec>, } impl ModportDec { pub fn new(url: &Url) -> Self { Self { ident: String::new(), byte_idx: 0, url: url.clone(), type_str: String::new(), completion_kind: CompletionItemKind::INTERFACE, symbol_kind: SymbolKind::INTERFACE, def_type: DefinitionType::Modport, ports: Vec::new(), } } } impl Definition for ModportDec { fn ident(&self) -> String { self.ident.clone() } fn byte_idx(&self) -> usize { self.byte_idx } fn url(&self) -> Url { self.url.clone() } fn type_str(&self) -> String { self.type_str.clone() } fn completion_kind(&self) -> CompletionItemKind { self.completion_kind } fn symbol_kind(&self) -> SymbolKind { self.symbol_kind } fn def_type(&self) -> DefinitionType { self.def_type } fn starts_with(&self, token: &str) -> bool { self.ident.starts_with(token) } fn completion(&self) -> CompletionItem { CompletionItem { label: self.ident.clone(), detail: Some(clean_type_str(&self.type_str, &self.ident)), kind: Some(self.completion_kind), ..CompletionItem::default() } } fn dot_completion(&self, _: &GenericScope) -> Vec { self.ports.iter().map(|x| x.completion()).collect() } } #[derive(Debug)] pub struct ModInst { pub ident: String, pub byte_idx: usize, pub url: Url, pub type_str: String, pub completion_kind: CompletionItemKind, pub symbol_kind: SymbolKind, pub def_type: DefinitionType, pub mod_ident: String, } impl ModInst { pub fn new(url: &Url) -> Self { Self { ident: String::new(), byte_idx: 0, url: url.clone(), type_str: String::new(), completion_kind: CompletionItemKind::MODULE, symbol_kind: SymbolKind::MODULE, def_type: DefinitionType::ModuleInstantiation, mod_ident: String::new(), } } } impl Definition for ModInst { fn ident(&self) -> String { self.ident.clone() } fn byte_idx(&self) -> usize { self.byte_idx } fn url(&self) -> Url { self.url.clone() } fn type_str(&self) -> String { self.type_str.clone() } fn completion_kind(&self) -> CompletionItemKind { self.completion_kind } fn symbol_kind(&self) -> SymbolKind { self.symbol_kind } fn def_type(&self) -> DefinitionType { self.def_type } fn starts_with(&self, token: &str) -> bool { self.ident.starts_with(token) } fn completion(&self) -> CompletionItem { CompletionItem { label: self.ident.clone(), detail: Some(clean_type_str(&self.type_str, &self.ident)), kind: Some(self.completion_kind), ..CompletionItem::default() } } fn dot_completion(&self, scope_tree: &GenericScope) -> Vec { 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() } } #[derive(Debug)] pub struct GenericScope { pub ident: String, pub byte_idx: usize, pub start: usize, pub end: usize, pub url: Url, pub type_str: String, pub completion_kind: CompletionItemKind, pub symbol_kind: SymbolKind, pub def_type: DefinitionType, pub defs: Vec>, pub scopes: Vec>, } impl GenericScope { pub fn new(url: &Url) -> Self { Self { ident: String::new(), byte_idx: 0, start: 0, end: 0, url: url.clone(), type_str: String::new(), completion_kind: CompletionItemKind::MODULE, symbol_kind: SymbolKind::MODULE, def_type: DefinitionType::GenericScope, defs: Vec::new(), scopes: Vec::new(), } } #[cfg(test)] pub fn contains_scope(&self, scope_ident: &str) -> bool { for scope in &self.scopes { if scope.starts_with(scope_ident) { return true; } } false } } impl Definition for GenericScope { fn ident(&self) -> String { self.ident.clone() } fn byte_idx(&self) -> usize { self.byte_idx } fn url(&self) -> Url { self.url.clone() } fn type_str(&self) -> String { self.type_str.clone() } fn completion_kind(&self) -> CompletionItemKind { self.completion_kind } fn symbol_kind(&self) -> SymbolKind { self.symbol_kind } fn def_type(&self) -> DefinitionType { self.def_type } fn starts_with(&self, token: &str) -> bool { self.ident.starts_with(token) } fn completion(&self) -> CompletionItem { CompletionItem { label: self.ident.clone(), detail: Some(clean_type_str(&self.type_str, &self.ident)), kind: Some(self.completion_kind), ..CompletionItem::default() } } fn dot_completion(&self, scope_tree: &GenericScope) -> Vec { 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 { fn start(&self) -> usize { self.start } fn end(&self) -> usize { self.end } fn defs(&self) -> &Vec> { &self.defs } fn scopes(&self) -> &Vec> { &self.scopes } } #[derive(Debug)] pub struct ClassDec { pub ident: String, pub byte_idx: usize, pub start: usize, pub end: usize, pub url: Url, pub type_str: String, pub completion_kind: CompletionItemKind, pub symbol_kind: SymbolKind, pub def_type: DefinitionType, pub defs: Vec>, pub scopes: Vec>, // class, package pub extends: (Vec, Option), // class, package pub implements: Vec<(String, Option)>, } impl ClassDec { pub fn new(url: &Url) -> Self { Self { ident: String::new(), byte_idx: 0, start: 0, end: 0, url: url.clone(), type_str: String::new(), completion_kind: CompletionItemKind::CLASS, symbol_kind: SymbolKind::CLASS, def_type: DefinitionType::Class, defs: Vec::new(), scopes: Vec::new(), extends: (Vec::new(), None), implements: Vec::new(), } } } impl Definition for ClassDec { fn ident(&self) -> String { self.ident.clone() } fn byte_idx(&self) -> usize { self.byte_idx } fn url(&self) -> Url { self.url.clone() } fn type_str(&self) -> String { self.type_str.clone() } fn completion_kind(&self) -> CompletionItemKind { self.completion_kind } fn symbol_kind(&self) -> SymbolKind { self.symbol_kind } fn def_type(&self) -> DefinitionType { self.def_type } fn starts_with(&self, token: &str) -> bool { self.ident.starts_with(token) } fn completion(&self) -> CompletionItem { CompletionItem { label: self.ident.clone(), detail: Some(clean_type_str(&self.type_str, &self.ident)), kind: Some(self.completion_kind), ..CompletionItem::default() } } fn dot_completion(&self, scope_tree: &GenericScope) -> Vec { 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 { fn start(&self) -> usize { self.start } fn end(&self) -> usize { self.end } fn defs(&self) -> &Vec> { &self.defs } fn scopes(&self) -> &Vec> { &self.scopes } } ================================================ FILE: src/definition/extract_defs.rs ================================================ use crate::definition::def_types::*; use crate::definition::match_definitions; use sv_parser::*; use tower_lsp::lsp_types::*; pub fn get_ident(tree: &SyntaxTree, node: RefNode) -> (String, usize) { let loc = unwrap_locate!(node).unwrap(); let ident_str = tree.get_str(loc).unwrap().to_string(); let byte_idx = tree.get_origin(loc).unwrap().1; (ident_str, byte_idx) } fn get_loc(tree: &SyntaxTree, node: RefNode) -> usize { let loc = unwrap_locate!(node).unwrap(); tree.get_origin(loc).unwrap().1 } macro_rules! advance_until_leave { ($tokens:ident, $tree:ident, $event_iter:ident, $node:path) => {{ let mut result: Option = None; while let Some(event) = $event_iter.next() { match event { NodeEvent::Leave(x) => match x { $node(node) => { result = Some($node(node)); break; } RefNode::Locate(node) => { $tokens.push(' '); $tokens.push_str($tree.get_str(node)?); } _ => (), }, NodeEvent::Enter(_) => (), } } result }}; } macro_rules! advance_until_enter { ($tokens:ident, $tree:ident, $event_iter:ident, $node:path, $type:ty) => {{ let mut result: Option<$type> = None; while let Some(event) = $event_iter.next() { match event { NodeEvent::Enter(x) => match x { $node(node) => { result = Some(node); break; } RefNode::Locate(node) => { $tokens.push(' '); $tokens.push_str($tree.get_str(node)?); } _ => (), }, NodeEvent::Leave(_) => (), } } result }}; } macro_rules! skip_until_enter { ($tree:ident, $event_iter:ident, $node:path, $type:ty) => {{ let mut result: Option<$type> = None; while let Some(event) = $event_iter.next() { match event { NodeEvent::Enter(x) => match x { $node(node) => { result = Some(node); break; } _ => (), }, NodeEvent::Leave(_) => (), } } result }}; } macro_rules! skip_until_leave { ($tree:ident, $event_iter:ident, $node:path) => { while let Some(event) = $event_iter.next() { match event { NodeEvent::Enter(_) => (), NodeEvent::Leave(x) => match x { $node(_) => { break; } _ => (), }, } } }; } macro_rules! match_until_leave { ($tree:ident, $event_iter:ident, $url:ident, $node:path) => {{ let mut scopes: Vec> = Vec::new(); let mut definitions: Vec> = Vec::new(); let mut global_scope: GenericScope = GenericScope::new($url); global_scope.ident = "global".to_string(); while let Some(event) = $event_iter.next() { match event { NodeEvent::Enter(node) => { let mut result = match_definitions($tree, $event_iter, node, $url)?; definitions.append(&mut result.1); scopes.append(&mut result.0); } NodeEvent::Leave(node) => match node { $node(_) => { break; } _ => {} }, } } Some((scopes, definitions)) }}; } pub fn port_dec_ansi( tree: &SyntaxTree, node: &AnsiPortDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut port = PortDec::new(url); let mut tokens = String::new(); match node { AnsiPortDeclaration::Net(x) => { let ident = get_ident(tree, RefNode::PortIdentifier(&x.nodes.1)); port.ident = ident.0; port.byte_idx = ident.1; if let Some(NetPortHeaderOrInterfacePortHeader::InterfacePortHeader(z)) = &x.nodes.0 { match &**z { InterfacePortHeader::Identifier(node) => { port.interface = Some(get_ident(tree, RefNode::InterfaceIdentifier(&node.nodes.0)).0); if let Some((_, mod_ident)) = &node.nodes.1 { port.modport = Some(get_ident(tree, RefNode::ModportIdentifier(mod_ident)).0); } } InterfacePortHeader::Interface(node) => { port.interface = Some("interface".to_string()); if let Some((_, mod_ident)) = &node.nodes.1 { port.modport = Some(get_ident(tree, RefNode::ModportIdentifier(mod_ident)).0); } } } } } AnsiPortDeclaration::Variable(x) => { let ident = get_ident(tree, RefNode::PortIdentifier(&x.nodes.1)); port.ident = ident.0; port.byte_idx = ident.1; } AnsiPortDeclaration::Paren(x) => { let ident = get_ident(tree, RefNode::PortIdentifier(&x.nodes.2)); port.ident = ident.0; port.byte_idx = ident.1; } } advance_until_leave!(tokens, tree, event_iter, RefNode::AnsiPortDeclaration); port.type_str = clean_type_str(&tokens, &port.ident); Some(port) } pub fn list_port_idents( tree: &SyntaxTree, node: &ListOfPortIdentifiers, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut ports: Vec = Vec::new(); for port_def in node.nodes.0.contents() { let mut port = PortDec::new(url); let ident = get_ident(tree, RefNode::PortIdentifier(&port_def.0)); port.ident = ident.0; port.byte_idx = ident.1; for _ in &port_def.1 { let tokens = &mut port.type_str; advance_until_leave!(tokens, tree, event_iter, RefNode::UnpackedDimension); } ports.push(port); } Some(ports) } pub fn list_interface_idents( tree: &SyntaxTree, node: &ListOfInterfaceIdentifiers, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut ports: Vec = Vec::new(); for port_def in node.nodes.0.contents() { let mut port = PortDec::new(url); let ident = get_ident(tree, RefNode::InterfaceIdentifier(&port_def.0)); port.ident = ident.0; port.byte_idx = ident.1; for _ in &port_def.1 { let tokens = &mut port.type_str; advance_until_leave!(tokens, tree, event_iter, RefNode::UnpackedDimension); } ports.push(port); } Some(ports) } pub fn list_variable_idents( tree: &SyntaxTree, node: &ListOfVariableIdentifiers, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut ports: Vec = Vec::new(); for port_def in node.nodes.0.contents() { let mut port = PortDec::new(url); let ident = get_ident(tree, RefNode::VariableIdentifier(&port_def.0)); port.ident = ident.0; port.byte_idx = ident.1; for _ in &port_def.1 { let tokens = &mut port.type_str; advance_until_leave!(tokens, tree, event_iter, RefNode::VariableDimension); } ports.push(port); } Some(ports) } pub fn port_dec_non_ansi( tree: &SyntaxTree, node: &PortDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut ports: Vec; let mut common = String::new(); match node { PortDeclaration::Inout(_) => { let port_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfPortIdentifiers, &ListOfPortIdentifiers )?; ports = list_port_idents(tree, port_list, event_iter, url)?; } PortDeclaration::Input(x) => match &x.nodes.1 { InputDeclaration::Net(_) => { let port_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfPortIdentifiers, &ListOfPortIdentifiers )?; ports = list_port_idents(tree, port_list, event_iter, url)?; } InputDeclaration::Variable(_) => { let port_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfVariableIdentifiers, &ListOfVariableIdentifiers )?; ports = list_variable_idents(tree, port_list, event_iter, url)?; } }, PortDeclaration::Output(x) => match &x.nodes.1 { OutputDeclaration::Net(_) => { let port_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfPortIdentifiers, &ListOfPortIdentifiers )?; ports = list_port_idents(tree, port_list, event_iter, url)?; } OutputDeclaration::Variable(_) => { let port_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfVariableIdentifiers, &ListOfVariableIdentifiers )?; ports = list_variable_idents(tree, port_list, event_iter, url)?; } }, PortDeclaration::Ref(_) => { let port_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfVariableIdentifiers, &ListOfVariableIdentifiers )?; ports = list_variable_idents(tree, port_list, event_iter, url)?; } PortDeclaration::Interface(x) => { let interface = Some(get_ident(tree, RefNode::InterfaceIdentifier(&x.nodes.1.nodes.0)).0); let modport = x .nodes .1 .nodes .1 .as_ref() .map(|(_, mod_ident)| get_ident(tree, RefNode::ModportIdentifier(mod_ident)).0); let port_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfInterfaceIdentifiers, &ListOfInterfaceIdentifiers )?; ports = list_interface_idents(tree, port_list, event_iter, url)?; for port in &mut ports { port.interface = interface.clone(); port.modport = modport.clone(); } } } for port in &mut ports { port.type_str = format!("{} {}", common, port.type_str); } Some(ports) } pub fn list_net_decl( tree: &SyntaxTree, node: &ListOfNetDeclAssignments, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut nets: Vec = Vec::new(); for net_def in node.nodes.0.contents() { let mut net = GenericDec::new(url); let ident = get_ident(tree, RefNode::NetIdentifier(&net_def.nodes.0)); net.ident = ident.0; net.byte_idx = ident.1; for _ in &net_def.nodes.1 { let tokens = &mut net.type_str; advance_until_leave!(tokens, tree, event_iter, RefNode::UnpackedDimension); } nets.push(net); } Some(nets) } pub fn net_dec( tree: &SyntaxTree, node: &NetDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut nets: Vec; let mut common = String::new(); match node { NetDeclaration::NetType(_) => { let net_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfNetDeclAssignments, &ListOfNetDeclAssignments )?; nets = list_net_decl(tree, net_list, event_iter, url)?; } NetDeclaration::NetTypeIdentifier(_) => { let net_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfNetDeclAssignments, &ListOfNetDeclAssignments )?; nets = list_net_decl(tree, net_list, event_iter, url)?; } NetDeclaration::Interconnect(x) => { let mut net = GenericDec::new(url); let ident = get_ident(tree, RefNode::NetIdentifier(&x.nodes.3)); net.ident = ident.0; net.byte_idx = ident.1; advance_until_enter!( common, tree, event_iter, RefNode::NetIdentifier, &NetIdentifier ); for _ in &x.nodes.4 { advance_until_leave!(common, tree, event_iter, RefNode::UnpackedDimension); } nets = vec![net]; } } for net in &mut nets { net.completion_kind = CompletionItemKind::VARIABLE; net.symbol_kind = SymbolKind::VARIABLE; net.type_str = format!("{} {}", common, net.type_str); } Some(nets) } pub fn list_var_decl( tree: &SyntaxTree, node: &ListOfVariableDeclAssignments, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut vars: Vec = Vec::new(); for var_def in node.nodes.0.contents() { let mut var = GenericDec::new(url); match &var_def { VariableDeclAssignment::Variable(node) => { let ident = get_ident(tree, RefNode::VariableIdentifier(&node.nodes.0)); var.ident = ident.0; var.byte_idx = ident.1; for _ in &node.nodes.1 { let tokens = &mut var.type_str; advance_until_leave!(tokens, tree, event_iter, RefNode::VariableDimension); } } VariableDeclAssignment::DynamicArray(node) => { let ident = get_ident(tree, RefNode::DynamicArrayVariableIdentifier(&node.nodes.0)); var.ident = ident.0; var.byte_idx = ident.1; for _ in &node.nodes.2 { let tokens = &mut var.type_str; advance_until_leave!(tokens, tree, event_iter, RefNode::VariableDimension); } } VariableDeclAssignment::Class(node) => { let ident = get_ident(tree, RefNode::ClassVariableIdentifier(&node.nodes.0)); var.ident = ident.0; var.byte_idx = ident.1; } } vars.push(var); } Some(vars) } pub fn package_import( tree: &SyntaxTree, node: &PackageImportDeclaration, _: &mut EventIter, url: &Url, ) -> Option> { let mut imports = Vec::new(); for import_def in node.nodes.1.contents() { let mut import = PackageImport::new(url); match import_def { PackageImportItem::Identifier(y) => { let ident = get_ident(tree, RefNode::PackageIdentifier(&y.nodes.0)); import.ident = ident.0; import.byte_idx = ident.1; let import_loc = match &y.nodes.2 { Identifier::SimpleIdentifier(id) => id.nodes.0, Identifier::EscapedIdentifier(id) => id.nodes.0, }; import.import_ident = Some(tree.get_str(&import_loc)?.to_string()); } PackageImportItem::Asterisk(y) => { let ident = get_ident(tree, RefNode::PackageIdentifier(&y.nodes.0)); import.ident = ident.0; import.byte_idx = ident.1; import.asterisk = true; } } imports.push(import); } Some(imports) } fn struct_union( tree: &SyntaxTree, node: &DataTypeStructUnion, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut scope = GenericScope::new(url); scope.start = get_loc(tree, RefNode::StructUnion(&node.nodes.0)); scope.end = get_loc(tree, RefNode::Symbol(&node.nodes.2.nodes.2)); scope.completion_kind = CompletionItemKind::STRUCT; scope.symbol_kind = SymbolKind::STRUCT; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::Symbol)?; let mut members = vec![&(node.nodes.2.nodes.1).0]; for member_def in &(node.nodes.2.nodes.1).1 { members.push(member_def); } for member_def in members { match member_def.nodes.2 { DataTypeOrVoid::DataType(_) => { let mut common = String::new(); let datatype = advance_until_enter!(common, tree, event_iter, RefNode::DataType, &DataType)?; let dec = data_type(tree, datatype, event_iter, url)?; match dec { Declaration::Dec(x) => { let var_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfVariableDeclAssignments, &ListOfVariableDeclAssignments )?; let mut decs = list_var_decl(tree, var_list, event_iter, url)?; advance_until_leave!(common, tree, event_iter, RefNode::StructUnionMember); for var in &mut decs { var.type_str = format!("{} {} {}", common, x.type_str, var.type_str); var.completion_kind = CompletionItemKind::VARIABLE; var.symbol_kind = SymbolKind::VARIABLE; } for var in decs { scope.defs.push(Box::new(var)); } } Declaration::Scope(x) => { let var_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfVariableDeclAssignments, &ListOfVariableDeclAssignments )?; let mut decs = list_var_decl(tree, var_list, event_iter, url)?; advance_until_leave!(common, tree, event_iter, RefNode::StructUnionMember); for var in &mut decs { var.type_str = format!("{} {} {}", common, x.type_str, var.type_str); var.completion_kind = CompletionItemKind::VARIABLE; var.symbol_kind = SymbolKind::VARIABLE; } for var in decs { let mut member_scope = GenericScope::new(url); member_scope.start = x.start; member_scope.end = x.end; member_scope.defs = copy_defs(&x.defs); member_scope.scopes = copy_scopes(&x.scopes); member_scope.ident = var.ident; member_scope.byte_idx = var.byte_idx; scope.scopes.push(Box::new(member_scope)); } } Declaration::Import(_) => { // datatype should not return import unreachable!() } } } DataTypeOrVoid::Void(_) => { let mut common = String::new(); let var_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfVariableDeclAssignments, &ListOfVariableDeclAssignments )?; let mut decs = list_var_decl(tree, var_list, event_iter, url)?; advance_until_leave!(common, tree, event_iter, RefNode::StructUnionMember); for var in &mut decs { var.type_str = format!("{} {}", common, var.type_str); var.completion_kind = CompletionItemKind::VARIABLE; var.symbol_kind = SymbolKind::VARIABLE; } for var in decs { scope.defs.push(Box::new(var)); } } } } skip_until_leave!(tree, event_iter, RefNode::DataTypeStructUnion); Some(scope) } pub enum Declaration { Dec(GenericDec), Scope(GenericScope), Import(PackageImport), } // this isn't enough for a definition fn data_type( tree: &SyntaxTree, node: &DataType, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut common = String::new(); match node { DataType::Vector(_) | DataType::Atom(_) | DataType::NonIntegerType(_) // TODO: set completion_kind and symbol_kind for string and others | DataType::String(_) | DataType::Chandle(_) // TODO: properly handle the following types | DataType::Virtual(_) | DataType::Type(_) | DataType::ClassType(_) | DataType::Event(_) | DataType::PsCovergroupIdentifier(_) => { advance_until_leave!(common, tree, event_iter, RefNode::DataType)?; let mut dec = GenericDec::new(url); dec.type_str = common; Some(Declaration::Dec(dec)) } DataType::StructUnion(_) => { let struct_union_def = advance_until_enter!( common, tree, event_iter, RefNode::DataTypeStructUnion, &DataTypeStructUnion )?; let def = struct_union( tree, struct_union_def, event_iter, url, )?; advance_until_leave!(common, tree, event_iter, RefNode::DataType)?; Some(Declaration::Scope(def)) } DataType::Enum(node) => { let mut scope = GenericScope::new(url); scope.start = get_loc(tree, RefNode::Symbol(&node.nodes.2.nodes.0)); scope.end = get_loc(tree, RefNode::Symbol(&node.nodes.2.nodes.2)); scope.completion_kind = CompletionItemKind::ENUM; scope.symbol_kind = SymbolKind::ENUM; let mut decs: Vec = Vec::new(); for emem in node.nodes.2.nodes.1.contents() { let mut dec = GenericDec::new(url); let ident = get_ident(tree, RefNode::EnumIdentifier(&emem.nodes.0)); dec.ident = ident.0; dec.byte_idx = ident.1; dec.completion_kind = CompletionItemKind::ENUM_MEMBER; dec.symbol_kind = SymbolKind::ENUM_MEMBER; let tokens = &mut dec.type_str; advance_until_leave!(tokens, tree, event_iter, RefNode::EnumNameDeclaration); decs.push(dec); } advance_until_leave!(common, tree, event_iter, RefNode::DataType)?; Some(Declaration::Scope(scope)) } DataType::TypeReference(node) => { match **node{ TypeReference::Expression(_) => { advance_until_leave!(common, tree, event_iter, RefNode::DataType)?; let mut dec = GenericDec::new(url); dec.type_str = common; Some(Declaration::Dec(dec)) } TypeReference::DataType(_) => { let data_type_node = advance_until_enter!(common, tree, event_iter, RefNode::DataType, &DataType)?; let data_type_def = data_type(tree, data_type_node, event_iter, url); advance_until_leave!(common, tree, event_iter, RefNode::DataType)?; data_type_def } } } } } pub fn data_dec( tree: &SyntaxTree, node: &DataDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut common = String::new(); let mut data: Vec = Vec::new(); match node { DataDeclaration::Variable(x) => match &x.nodes.3 { DataTypeOrImplicit::DataType(_) => { let mut common = String::new(); let datatype = advance_until_enter!(common, tree, event_iter, RefNode::DataType, &DataType)?; let dec = data_type(tree, datatype, event_iter, url)?; match dec { Declaration::Dec(x) => { let var_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfVariableDeclAssignments, &ListOfVariableDeclAssignments )?; let mut decs = list_var_decl(tree, var_list, event_iter, url)?; for var in &mut decs { var.type_str = format!("{} {} {}", common, x.type_str, var.type_str); var.completion_kind = CompletionItemKind::VARIABLE; var.symbol_kind = SymbolKind::VARIABLE; } for var in decs { data.push(Declaration::Dec(var)); } } Declaration::Scope(x) => { let var_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfVariableDeclAssignments, &ListOfVariableDeclAssignments )?; let mut decs = list_var_decl(tree, var_list, event_iter, url)?; for var in &mut decs { var.type_str = format!("{} {} {}", common, x.type_str, var.type_str); var.completion_kind = CompletionItemKind::VARIABLE; var.symbol_kind = SymbolKind::VARIABLE; } for var in decs { data.push(Declaration::Scope(GenericScope { ident: var.ident, byte_idx: var.byte_idx, start: x.start, end: x.end, url: url.clone(), type_str: var.type_str, completion_kind: x.completion_kind, symbol_kind: x.symbol_kind, def_type: x.def_type, defs: copy_defs(&x.defs), scopes: copy_scopes(&x.scopes), })); } } Declaration::Import(_) => { // datatype should not return import unreachable!() } } } DataTypeOrImplicit::ImplicitDataType(_) => { let var_list = advance_until_enter!( common, tree, event_iter, RefNode::ListOfVariableDeclAssignments, &ListOfVariableDeclAssignments )?; let mut decs = list_var_decl(tree, var_list, event_iter, url)?; data = Vec::new(); for var in &mut decs { var.type_str = format!("{} {}", common, var.type_str); var.completion_kind = CompletionItemKind::VARIABLE; var.symbol_kind = SymbolKind::VARIABLE; } for var in decs { data.push(Declaration::Dec(var)); } } }, DataDeclaration::TypeDeclaration(x) => match &**x { TypeDeclaration::DataType(y) => { let mut common = String::new(); let datatype = advance_until_enter!(common, tree, event_iter, RefNode::DataType, &DataType)?; let dec = data_type(tree, datatype, event_iter, url)?; match dec { Declaration::Dec(mut def) => { let ident = get_ident(tree, RefNode::TypeIdentifier(&y.nodes.2)); def.ident = ident.0; def.byte_idx = ident.1; for _ in &y.nodes.3 { let tokens = &mut def.type_str; advance_until_leave!( tokens, tree, event_iter, RefNode::VariableDimension ); } def.type_str = format!("{} {}", common, def.type_str); data = vec![Declaration::Dec(def)]; } Declaration::Scope(mut def) => { let ident = get_ident(tree, RefNode::TypeIdentifier(&y.nodes.2)); def.ident = ident.0; def.byte_idx = ident.1; for _ in &y.nodes.3 { let tokens = &mut def.type_str; advance_until_leave!( tokens, tree, event_iter, RefNode::VariableDimension ); } def.type_str = format!("{} {}", common, def.type_str); data = vec![Declaration::Scope(def)]; } Declaration::Import(_) => unreachable!(), } } TypeDeclaration::Interface(y) => { let mut var = GenericDec::new(url); let ident = get_ident(tree, RefNode::TypeIdentifier(&y.nodes.5)); var.ident = ident.0; var.byte_idx = ident.1; let mut tokens = String::new(); advance_until_enter!( tokens, tree, event_iter, RefNode::TypeIdentifier, &TypeIdentifier ); advance_until_enter!( tokens, tree, event_iter, RefNode::TypeIdentifier, &TypeIdentifier ); var.type_str = tokens; var.type_str = format!("{} {}", common, var.type_str); var.completion_kind = CompletionItemKind::INTERFACE; var.symbol_kind = SymbolKind::INTERFACE; data = vec![Declaration::Dec(var)]; } TypeDeclaration::Reserved(y) => { let mut var = GenericDec::new(url); let ident = get_ident(tree, RefNode::TypeIdentifier(&y.nodes.2)); var.ident = ident.0; var.byte_idx = ident.1; let mut tokens = String::new(); advance_until_enter!( tokens, tree, event_iter, RefNode::TypeIdentifier, &TypeIdentifier ); var.type_str = tokens; var.type_str = format!("{} {}", common, var.type_str); var.completion_kind = CompletionItemKind::VARIABLE; var.symbol_kind = SymbolKind::VARIABLE; data = vec![Declaration::Dec(var)]; } }, DataDeclaration::PackageImportDeclaration(x) => { data = Vec::new(); let imports = package_import(tree, x, event_iter, url)?; for import in imports { data.push(Declaration::Import(import)); } } DataDeclaration::NetTypeDeclaration(x) => match &**x { NetTypeDeclaration::DataType(y) => { let mut common = String::new(); let datatype = advance_until_enter!(common, tree, event_iter, RefNode::DataType, &DataType)?; let dec = data_type(tree, datatype, event_iter, url)?; match dec { Declaration::Dec(mut def) => { let ident = get_ident(tree, RefNode::NetTypeIdentifier(&y.nodes.2)); def.ident = ident.0; def.byte_idx = ident.1; let mut tokens = String::new(); advance_until_enter!( tokens, tree, event_iter, RefNode::NetTypeIdentifier, &NetTypeIdentifier ); def.type_str = tokens; def.type_str = format!("{} {}", common, def.type_str); data = vec![Declaration::Dec(def)]; } Declaration::Scope(mut def) => { let ident = get_ident(tree, RefNode::NetTypeIdentifier(&y.nodes.2)); def.ident = ident.0; def.byte_idx = ident.1; let mut tokens = String::new(); advance_until_enter!( tokens, tree, event_iter, RefNode::NetTypeIdentifier, &NetTypeIdentifier ); def.type_str = tokens; def.type_str = format!("{} {}", common, def.type_str); data = vec![Declaration::Scope(def)]; } Declaration::Import(_) => unreachable!(), } } NetTypeDeclaration::NetType(y) => { let mut var = GenericDec::new(url); let ident = get_ident(tree, RefNode::NetTypeIdentifier(&y.nodes.2)); var.ident = ident.0; var.byte_idx = ident.1; let mut tokens = String::new(); advance_until_leave!(tokens, tree, event_iter, RefNode::NetTypeIdentifier); var.type_str = tokens; var.type_str = format!("{} {}", common, var.type_str); var.completion_kind = CompletionItemKind::VARIABLE; var.symbol_kind = SymbolKind::VARIABLE; data = vec![Declaration::Dec(var)]; } }, } Some(data) } pub fn tfport_list( tree: &SyntaxTree, node: &TfPortList, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut tfports: Vec = Vec::new(); for tfports_def in node.nodes.0.contents() { if let Some(def) = &tfports_def.nodes.4 { let mut tfport = PortDec::new(url); let ident = get_ident(tree, RefNode::PortIdentifier(&def.0)); tfport.ident = ident.0; tfport.byte_idx = ident.1; for _ in &def.1 { let tokens = &mut tfport.type_str; advance_until_leave!(tokens, tree, event_iter, RefNode::UnpackedDimension); } tfports.push(tfport); } } Some(tfports) } pub fn function_dec( tree: &SyntaxTree, node: &FunctionDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut func: SubDec = SubDec::new(url); func.start = get_loc(tree, RefNode::Keyword(&node.nodes.0)); match &node.nodes.2 { FunctionBodyDeclaration::WithoutPort(x) => { func.end = get_loc(tree, RefNode::Keyword(&x.nodes.6)); let ident = get_ident(tree, RefNode::FunctionIdentifier(&x.nodes.2)); func.ident = ident.0; func.byte_idx = ident.1; let mut tokens = String::new(); advance_until_enter!( tokens, tree, event_iter, RefNode::FunctionIdentifier, &FunctionIdentifier ); func.type_str = tokens; } FunctionBodyDeclaration::WithPort(x) => { func.end = get_loc(tree, RefNode::Keyword(&x.nodes.7)); let ident = get_ident(tree, RefNode::FunctionIdentifier(&x.nodes.2)); func.ident = ident.0; func.byte_idx = ident.1; let mut tokens = String::new(); advance_until_enter!( tokens, tree, event_iter, RefNode::FunctionIdentifier, &FunctionIdentifier ); func.type_str = tokens; if let Some(tfports) = &x.nodes.3.nodes.1 { skip_until_enter!(tree, event_iter, RefNode::TfPortList, &TfPortList); let ports = tfport_list(tree, tfports, event_iter, url)?; for port in ports { func.defs.push(Box::new(port)); } } } } let (scopes, mut defs) = match_until_leave!(tree, event_iter, url, RefNode::FunctionDeclaration)?; func.scopes = scopes; func.defs.append(&mut defs); Some(func) } pub fn task_dec( tree: &SyntaxTree, node: &TaskDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut task = SubDec::new(url); task.start = get_loc(tree, RefNode::Keyword(&node.nodes.0)); match &node.nodes.2 { TaskBodyDeclaration::WithoutPort(x) => { task.end = get_loc(tree, RefNode::Keyword(&x.nodes.5)); let ident = get_ident(tree, RefNode::TaskIdentifier(&x.nodes.1)); task.ident = ident.0; task.byte_idx = ident.1; let mut tokens = String::new(); advance_until_enter!( tokens, tree, event_iter, RefNode::TaskIdentifier, &TaskIdentifier ); task.type_str = tokens; } TaskBodyDeclaration::WithPort(x) => { task.end = get_loc(tree, RefNode::Keyword(&x.nodes.6)); let mut task = SubDec::new(url); let ident = get_ident(tree, RefNode::TaskIdentifier(&x.nodes.1)); task.ident = ident.0; task.byte_idx = ident.1; let mut tokens = String::new(); advance_until_enter!( tokens, tree, event_iter, RefNode::TaskIdentifier, &TaskIdentifier ); task.type_str = tokens; if let Some(tfports) = &x.nodes.2.nodes.1 { skip_until_enter!(tree, event_iter, RefNode::TfPortList, &TfPortList); let ports = tfport_list(tree, tfports, event_iter, url)?; for port in ports { task.defs.push(Box::new(port)); } } } } let (scopes, mut defs) = match_until_leave!(tree, event_iter, url, RefNode::TaskDeclaration)?; task.scopes = scopes; task.defs.append(&mut defs); Some(task) } pub fn modport_dec( tree: &SyntaxTree, node: &ModportDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut modports: Vec = Vec::new(); let mut common = String::new(); advance_until_enter!(common, tree, event_iter, RefNode::ModportItem, &ModportItem); for modport_def in node.nodes.1.contents() { let mut modport = ModportDec::new(url); let ident = get_ident(tree, RefNode::ModportIdentifier(&modport_def.nodes.0)); modport.ident = ident.0; modport.byte_idx = ident.1; modport.type_str = common.clone(); for mp_port_dec in modport_def.nodes.1.nodes.1.contents() { match mp_port_dec { ModportPortsDeclaration::Simple(x) => { skip_until_enter!( tree, event_iter, RefNode::ModportPortsDeclarationSimple, &ModportPortsDeclarationSimple ); let mut prepend = String::new(); advance_until_enter!( prepend, tree, event_iter, RefNode::ModportSimplePort, &ModportSimplePort ); for mp_simple_def in x.nodes.1.nodes.1.contents() { match mp_simple_def { ModportSimplePort::Ordered(y) => { let mut port = PortDec::new(url); let ident = get_ident(tree, RefNode::PortIdentifier(&y.nodes.0)); port.ident = ident.0; port.byte_idx = ident.1; port.type_str = prepend.clone(); modport.ports.push(Box::new(port)); } ModportSimplePort::Named(_) => { let port_ident = skip_until_enter!( tree, event_iter, RefNode::PortIdentifier, &PortIdentifier )?; let mut port = PortDec::new(url); let ident = get_ident(tree, RefNode::PortIdentifier(port_ident)); port.ident = ident.0; port.byte_idx = ident.1; let mut append = String::new(); advance_until_leave!( append, tree, event_iter, RefNode::ModportSimplePortNamed ); port.type_str = format!("{} {}", prepend, append); modport.ports.push(Box::new(port)); } } } } ModportPortsDeclaration::Tf(_) => { skip_until_enter!( tree, event_iter, RefNode::ModportPortsDeclarationTf, &ModportPortsDeclarationTf ); let mut prepend = String::new(); let mp_tf_ports_dec = advance_until_enter!( prepend, tree, event_iter, RefNode::ModportTfPortsDeclaration, &ModportTfPortsDeclaration )?; for mp_tf_port in mp_tf_ports_dec.nodes.1.contents() { match mp_tf_port { ModportTfPort::MethodPrototype(y) => match &**y { MethodPrototype::TaskPrototype(z) => { let mut port = SubDec::new(url); let ident = get_ident(tree, RefNode::TaskIdentifier(&z.nodes.1)); port.ident = ident.0; port.byte_idx = ident.1; skip_until_enter!( tree, event_iter, RefNode::TaskPrototype, &TaskPrototype ); let tokens = &mut port.type_str; advance_until_leave!( tokens, tree, event_iter, RefNode::TaskPrototype ); modport.ports.push(Box::new(port)); } MethodPrototype::FunctionPrototype(z) => { let mut port = SubDec::new(url); let ident = get_ident(tree, RefNode::FunctionIdentifier(&z.nodes.2)); port.ident = ident.0; port.byte_idx = ident.1; skip_until_enter!( tree, event_iter, RefNode::FunctionPrototype, &FunctionPrototype ); let tokens = &mut port.type_str; advance_until_leave!( tokens, tree, event_iter, RefNode::FunctionIdentifier ); modport.ports.push(Box::new(port)); } }, ModportTfPort::TfIdentifier(y) => { let mut port = SubDec::new(url); let ident = get_ident(tree, RefNode::TfIdentifier(y)); port.ident = ident.0; port.byte_idx = ident.1; port.type_str = prepend.clone(); modport.ports.push(Box::new(port)); } } } } ModportPortsDeclaration::Clocking(_) => { skip_until_enter!( tree, event_iter, RefNode::ModportPortsDeclarationClocking, &ModportPortsDeclarationClocking ); let mut tokens = String::new(); let clock_ident = advance_until_enter!( tokens, tree, event_iter, RefNode::ClockingIdentifier, &ClockingIdentifier )?; let ident = get_ident(tree, RefNode::ClockingIdentifier(clock_ident)); let mut port = PortDec::new(url); port.ident = ident.0; port.byte_idx = ident.1; port.type_str = tokens; modport.ports.push(Box::new(port)); } } } modports.push(modport); } Some(modports) } pub fn module_inst( tree: &SyntaxTree, node: &ModuleInstantiation, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut defs: Vec = Vec::new(); let mod_ident = get_ident(tree, RefNode::ModuleIdentifier(&node.nodes.0)).0; for _ in node.nodes.2.contents() { let hinst = skip_until_enter!( tree, event_iter, RefNode::HierarchicalInstance, &HierarchicalInstance )?; let mut instance = ModInst::new(url); let ident = get_ident(tree, RefNode::InstanceIdentifier(&hinst.nodes.0.nodes.0)); instance.ident = ident.0; instance.byte_idx = ident.1; instance.type_str = mod_ident.clone(); instance.mod_ident = mod_ident.clone(); let type_str = &mut instance.type_str; for _ in &hinst.nodes.0.nodes.1 { advance_until_leave!(type_str, tree, event_iter, RefNode::UnpackedDimension); } defs.push(instance); } Some(defs) } fn param_assignment( tree: &SyntaxTree, _: &ParamAssignment, event_iter: &mut EventIter, url: &Url, ) -> Option { let param_assign = skip_until_enter!(tree, event_iter, RefNode::ParamAssignment, &ParamAssignment)?; let mut def = GenericDec::new(url); let ident = get_ident(tree, RefNode::ParameterIdentifier(¶m_assign.nodes.0)); def.ident = ident.0; def.byte_idx = ident.1; let type_str = &mut def.type_str; def.completion_kind = CompletionItemKind::TYPE_PARAMETER; def.symbol_kind = SymbolKind::TYPE_PARAMETER; advance_until_leave!(type_str, tree, event_iter, RefNode::ParamAssignment); Some(def) } fn list_param_assignment( tree: &SyntaxTree, _: &ListOfParamAssignments, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut defs: Vec = Vec::new(); let p_a_list = skip_until_enter!( tree, event_iter, RefNode::ListOfParamAssignments, &ListOfParamAssignments )?; for param_assign in p_a_list.nodes.0.contents() { defs.push(param_assignment(tree, param_assign, event_iter, url)?); } Some(defs) } fn type_assignment( tree: &SyntaxTree, _: &TypeAssignment, event_iter: &mut EventIter, url: &Url, ) -> Option { let type_assign = skip_until_enter!(tree, event_iter, RefNode::TypeAssignment, &TypeAssignment)?; let mut def = GenericDec::new(url); let ident = get_ident(tree, RefNode::TypeIdentifier(&type_assign.nodes.0)); def.ident = ident.0; def.byte_idx = ident.1; def.completion_kind = CompletionItemKind::TYPE_PARAMETER; def.symbol_kind = SymbolKind::TYPE_PARAMETER; let type_str = &mut def.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::TypeAssignment); Some(def) } fn list_type_assignment( tree: &SyntaxTree, _: &ListOfTypeAssignments, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut defs: Vec = Vec::new(); let p_a_list = skip_until_enter!( tree, event_iter, RefNode::ListOfTypeAssignments, &ListOfTypeAssignments )?; for type_assign in p_a_list.nodes.0.contents() { defs.push(type_assignment(tree, type_assign, event_iter, url)?); } Some(defs) } pub fn param_dec( tree: &SyntaxTree, param_dec: &ParameterDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option> { match param_dec { ParameterDeclaration::Param(x) => { let mut prepend = String::new(); advance_until_leave!(prepend, tree, event_iter, RefNode::DataTypeOrImplicit); let mut defs = list_param_assignment(tree, &x.nodes.2, event_iter, url)?; for def in &mut defs { def.type_str = format!("{} {}", prepend, def.type_str); def.completion_kind = CompletionItemKind::TYPE_PARAMETER; def.symbol_kind = SymbolKind::TYPE_PARAMETER; } Some(defs) } ParameterDeclaration::Type(x) => { let mut prepend = String::new(); advance_until_leave!(prepend, tree, event_iter, RefNode::Keyword); advance_until_leave!(prepend, tree, event_iter, RefNode::Keyword); let mut defs = list_type_assignment(tree, &x.nodes.2, event_iter, url)?; for def in &mut defs { def.type_str = format!("{} {}", prepend, def.type_str); def.completion_kind = CompletionItemKind::TYPE_PARAMETER; def.symbol_kind = SymbolKind::TYPE_PARAMETER; } Some(defs) } } } pub fn localparam_dec( tree: &SyntaxTree, localparam_dec: &LocalParameterDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option> { match localparam_dec { LocalParameterDeclaration::Param(x) => { let mut prepend = String::new(); advance_until_leave!(prepend, tree, event_iter, RefNode::DataTypeOrImplicit); let mut defs = list_param_assignment(tree, &x.nodes.2, event_iter, url)?; for def in &mut defs { def.type_str = format!("{} {}", prepend, def.type_str); def.completion_kind = CompletionItemKind::TYPE_PARAMETER; def.symbol_kind = SymbolKind::TYPE_PARAMETER; } Some(defs) } LocalParameterDeclaration::Type(x) => { let mut prepend = String::new(); advance_until_leave!(prepend, tree, event_iter, RefNode::Keyword); advance_until_leave!(prepend, tree, event_iter, RefNode::Keyword); let mut defs = list_type_assignment(tree, &x.nodes.2, event_iter, url)?; for def in &mut defs { def.type_str = format!("{} {}", prepend, def.type_str); def.completion_kind = CompletionItemKind::TYPE_PARAMETER; def.symbol_kind = SymbolKind::TYPE_PARAMETER; } Some(defs) } } } fn param_port_dec( tree: &SyntaxTree, node: &ParameterPortDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option> { match node { ParameterPortDeclaration::ParameterDeclaration(_) => { let param = skip_until_enter!( tree, event_iter, RefNode::ParameterDeclaration, &ParameterDeclaration )?; param_dec(tree, param, event_iter, url) } ParameterPortDeclaration::LocalParameterDeclaration(_) => { let localparam = skip_until_enter!( tree, event_iter, RefNode::LocalParameterDeclaration, &LocalParameterDeclaration )?; localparam_dec(tree, localparam, event_iter, url) } ParameterPortDeclaration::ParamList(x) => { let mut prepend = String::new(); advance_until_leave!(prepend, tree, event_iter, RefNode::DataType); let mut defs = list_param_assignment(tree, &x.nodes.1, event_iter, url)?; for def in &mut defs { def.type_str = format!("{} {}", prepend, def.type_str); def.completion_kind = CompletionItemKind::TYPE_PARAMETER; def.symbol_kind = SymbolKind::TYPE_PARAMETER; } Some(defs) } ParameterPortDeclaration::TypeList(x) => { let mut prepend = String::new(); advance_until_leave!(prepend, tree, event_iter, RefNode::Keyword); let mut defs = list_type_assignment(tree, &x.nodes.1, event_iter, url)?; for def in &mut defs { def.type_str = format!("{} {}", prepend, def.type_str); def.completion_kind = CompletionItemKind::TYPE_PARAMETER; def.symbol_kind = SymbolKind::TYPE_PARAMETER; } Some(defs) } } } pub fn param_port_list( tree: &SyntaxTree, node: &ParameterPortList, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut defs: Vec = Vec::new(); match node { ParameterPortList::Assignment(x) => { defs.append(&mut list_param_assignment( tree, &(x.nodes.1.nodes.1).0, event_iter, url, )?); for port_dec in &(x.nodes.1.nodes.1).1 { defs.append(&mut param_port_dec(tree, &port_dec.1, event_iter, url)?); } } ParameterPortList::Declaration(x) => { let mut param_port_decs = vec![&x.nodes.1.nodes.1.nodes.0]; for param_port_dec in &x.nodes.1.nodes.1.nodes.1 { param_port_decs.push(¶m_port_dec.1); } for port_dec in param_port_decs { defs.append(&mut param_port_dec(tree, port_dec, event_iter, url)?); } } ParameterPortList::Empty(_) => {} } Some(defs) } pub fn module_dec( tree: &SyntaxTree, node: &ModuleDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut scope: GenericScope = GenericScope::new(url); match node { ModuleDeclaration::Nonansi(x) => { scope.start = get_loc(tree, RefNode::ModuleKeyword(&x.nodes.0.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.3)); let ident = get_ident(tree, RefNode::ModuleIdentifier(&x.nodes.0.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::ModuleIdentifier); for import_dec in &x.nodes.0.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.0.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } } ModuleDeclaration::Ansi(x) => { scope.start = get_loc(tree, RefNode::ModuleKeyword(&x.nodes.0.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.3)); let ident = get_ident(tree, RefNode::ModuleIdentifier(&x.nodes.0.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::ModuleIdentifier); for import_dec in &x.nodes.0.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.0.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } if let Some(list_port_decs) = &x.nodes.0.nodes.6 { if let Some(port_decs) = &list_port_decs.nodes.0.nodes.1 { let mut prev_type_str = String::new(); for _ in port_decs.contents() { let ansi_dec = skip_until_enter!( tree, event_iter, RefNode::AnsiPortDeclaration, &AnsiPortDeclaration )?; // propogate type str for multi-port declaration let mut port_dec = port_dec_ansi(tree, ansi_dec, event_iter, url)?; if port_dec.type_str.is_empty() && !prev_type_str.is_empty() { port_dec.type_str = prev_type_str.clone(); } else { prev_type_str = port_dec.type_str.clone(); } scope.defs.push(Box::new(port_dec)) } } } } ModuleDeclaration::Wildcard(x) => { scope.start = get_loc(tree, RefNode::ModuleKeyword(&x.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.8)); let ident = get_ident(tree, RefNode::ModuleIdentifier(&x.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::ModuleIdentifier); } ModuleDeclaration::ExternNonansi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0)); scope.end = get_loc(tree, RefNode::Symbol(&x.nodes.1.nodes.7)); let ident = get_ident(tree, RefNode::ModuleIdentifier(&x.nodes.1.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::ModuleIdentifier); for import_dec in &x.nodes.1.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.1.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } } ModuleDeclaration::ExternAnsi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0)); scope.end = get_loc(tree, RefNode::Symbol(&x.nodes.1.nodes.7)); let ident = get_ident(tree, RefNode::ModuleIdentifier(&x.nodes.1.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::ModuleIdentifier); for import_dec in &x.nodes.1.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.1.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } if let Some(list_port_decs) = &x.nodes.1.nodes.6 { if let Some(port_decs) = &list_port_decs.nodes.0.nodes.1 { for _ in port_decs.contents() { let ansi_dec = skip_until_enter!( tree, event_iter, RefNode::AnsiPortDeclaration, &AnsiPortDeclaration )?; scope .defs .push(Box::new(port_dec_ansi(tree, ansi_dec, event_iter, url)?)) } } } } } let (scopes, mut defs) = match_until_leave!(tree, event_iter, url, RefNode::ModuleDeclaration)?; scope.scopes = scopes; scope.defs.append(&mut defs); scope.completion_kind = CompletionItemKind::MODULE; scope.symbol_kind = SymbolKind::MODULE; Some(scope) } pub fn interface_dec( tree: &SyntaxTree, node: &InterfaceDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut scope: GenericScope = GenericScope::new(url); match node { InterfaceDeclaration::Nonansi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.3)); let ident = get_ident(tree, RefNode::InterfaceIdentifier(&x.nodes.0.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::InterfaceIdentifier); for import_dec in &x.nodes.0.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.0.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } } InterfaceDeclaration::Ansi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.3)); let ident = get_ident(tree, RefNode::InterfaceIdentifier(&x.nodes.0.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::InterfaceIdentifier); for import_dec in &x.nodes.0.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.0.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } if let Some(list_port_decs) = &x.nodes.0.nodes.6 { if let Some(port_decs) = &list_port_decs.nodes.0.nodes.1 { for _ in port_decs.contents() { let ansi_dec = skip_until_enter!( tree, event_iter, RefNode::AnsiPortDeclaration, &AnsiPortDeclaration )?; scope .defs .push(Box::new(port_dec_ansi(tree, ansi_dec, event_iter, url)?)) } } } } InterfaceDeclaration::Wildcard(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.8)); let ident = get_ident(tree, RefNode::InterfaceIdentifier(&x.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::InterfaceIdentifier); } InterfaceDeclaration::ExternNonansi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0)); scope.end = get_loc(tree, RefNode::Symbol(&x.nodes.1.nodes.7)); let ident = get_ident(tree, RefNode::InterfaceIdentifier(&x.nodes.1.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::InterfaceIdentifier); for import_dec in &x.nodes.1.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.1.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } } InterfaceDeclaration::ExternAnsi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0)); scope.end = get_loc(tree, RefNode::Symbol(&x.nodes.1.nodes.7)); let ident = get_ident(tree, RefNode::InterfaceIdentifier(&x.nodes.1.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::InterfaceIdentifier); for import_dec in &x.nodes.1.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.1.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } if let Some(list_port_decs) = &x.nodes.1.nodes.6 { if let Some(port_decs) = &list_port_decs.nodes.0.nodes.1 { for _ in port_decs.contents() { let ansi_dec = skip_until_enter!( tree, event_iter, RefNode::AnsiPortDeclaration, &AnsiPortDeclaration )?; scope .defs .push(Box::new(port_dec_ansi(tree, ansi_dec, event_iter, url)?)) } } } } } let (scopes, mut defs) = match_until_leave!(tree, event_iter, url, RefNode::InterfaceDeclaration)?; scope.scopes = scopes; scope.defs.append(&mut defs); scope.completion_kind = CompletionItemKind::INTERFACE; scope.symbol_kind = SymbolKind::INTERFACE; Some(scope) } fn list_udp_port_idents( tree: &SyntaxTree, node: &ListOfUdpPortIdentifiers, _: &mut EventIter, url: &Url, ) -> Vec { let mut ports: Vec = Vec::new(); for port_def in node.nodes.0.contents() { let mut port = PortDec::new(url); let ident = get_ident(tree, RefNode::PortIdentifier(port_def)); port.ident = ident.0; port.byte_idx = ident.1; ports.push(port); } ports } //non-ansi udp ports fn udp_port_dec( tree: &SyntaxTree, node: &UdpPortDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option> { match node { UdpPortDeclaration::UdpOutputDeclaration(x) => match &x.0 { UdpOutputDeclaration::Nonreg(x) => { let mut port = PortDec::new(url); let ident = get_ident(tree, RefNode::PortIdentifier(&x.nodes.2)); port.ident = ident.0; port.byte_idx = ident.1; skip_until_enter!( tree, event_iter, RefNode::UdpOutputDeclarationNonreg, &UdpOutputDeclarationNonreg ); let type_str = &mut port.type_str; advance_until_leave!( type_str, tree, event_iter, RefNode::UdpOutputDeclarationNonreg ); Some(vec![port]) } UdpOutputDeclaration::Reg(x) => { let mut port = PortDec::new(url); let ident = get_ident(tree, RefNode::PortIdentifier(&x.nodes.3)); port.ident = ident.0; port.byte_idx = ident.1; skip_until_enter!( tree, event_iter, RefNode::UdpOutputDeclarationReg, &UdpOutputDeclarationReg ); let type_str = &mut port.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::UdpOutputDeclarationReg); Some(vec![port]) } }, UdpPortDeclaration::UdpInputDeclaration(_) => { skip_until_enter!( tree, event_iter, RefNode::UdpInputDeclaration, &UdpInputDeclaration ); let mut type_str = String::new(); let list_udp_ports = advance_until_enter!( type_str, tree, event_iter, RefNode::ListOfUdpPortIdentifiers, &ListOfUdpPortIdentifiers )?; let mut ports = list_udp_port_idents(tree, list_udp_ports, event_iter, url); for port in &mut ports { port.type_str = type_str.clone(); } Some(ports) } UdpPortDeclaration::UdpRegDeclaration(_) => { let udp_reg_dec = skip_until_enter!( tree, event_iter, RefNode::UdpRegDeclaration, &UdpRegDeclaration )?; let mut port = PortDec::new(url); let type_str = &mut port.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::Keyword); let ident = get_ident(tree, RefNode::VariableIdentifier(&udp_reg_dec.nodes.2)); port.ident = ident.0; port.byte_idx = ident.1; Some(vec![port]) } } } //ansi udp ports fn udp_port_list( tree: &SyntaxTree, node: &UdpDeclarationPortList, event_iter: &mut EventIter, url: &Url, ) -> Option> { let mut ports: Vec = Vec::new(); match &node.nodes.0 { UdpOutputDeclaration::Nonreg(x) => { let mut port = PortDec::new(url); let ident = get_ident(tree, RefNode::PortIdentifier(&x.nodes.2)); port.ident = ident.0; port.byte_idx = ident.1; skip_until_enter!( tree, event_iter, RefNode::UdpOutputDeclarationNonreg, &UdpOutputDeclarationNonreg ); let type_str = &mut port.type_str; advance_until_leave!( type_str, tree, event_iter, RefNode::UdpOutputDeclarationNonreg ); ports.push(port); } UdpOutputDeclaration::Reg(x) => { let mut port = PortDec::new(url); let ident = get_ident(tree, RefNode::PortIdentifier(&x.nodes.3)); port.ident = ident.0; port.byte_idx = ident.1; skip_until_enter!( tree, event_iter, RefNode::UdpOutputDeclarationReg, &UdpOutputDeclarationReg ); let type_str = &mut port.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::UdpOutputDeclarationReg); ports.push(port); } } for _port_def in node.nodes.2.contents() { skip_until_enter!( tree, event_iter, RefNode::UdpInputDeclaration, &UdpInputDeclaration ); let mut type_str = String::new(); let list_udp_ports = advance_until_enter!( type_str, tree, event_iter, RefNode::ListOfUdpPortIdentifiers, &ListOfUdpPortIdentifiers )?; let mut port_decs = list_udp_port_idents(tree, list_udp_ports, event_iter, url); for port in &mut port_decs { port.type_str = type_str.clone(); } ports.append(&mut port_decs); } Some(ports) } pub fn udp_dec( tree: &SyntaxTree, node: &UdpDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut scope: GenericScope = GenericScope::new(url); match node { UdpDeclaration::Nonansi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.4)); let ident = get_ident(tree, RefNode::UdpIdentifier(&x.nodes.0.nodes.2)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::UdpIdentifier); let mut port_decs = vec![&x.nodes.1]; for port_dec in &x.nodes.2 { port_decs.push(port_dec); } for port in port_decs { let ports = udp_port_dec(tree, port, event_iter, url)?; for port_dec in ports { scope.defs.push(Box::new(port_dec)); } } } UdpDeclaration::Ansi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.2)); let ident = get_ident(tree, RefNode::UdpIdentifier(&x.nodes.0.nodes.2)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::UdpIdentifier); let ports = udp_port_list(tree, &x.nodes.0.nodes.3.nodes.1, event_iter, url)?; for port_dec in ports { scope.defs.push(Box::new(port_dec)); } } UdpDeclaration::ExternNonansi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0)); scope.end = get_loc(tree, RefNode::Symbol(&x.nodes.1.nodes.4)); let ident = get_ident(tree, RefNode::UdpIdentifier(&x.nodes.1.nodes.2)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::UdpIdentifier); } UdpDeclaration::ExternAnsi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0)); scope.end = get_loc(tree, RefNode::Symbol(&x.nodes.1.nodes.4)); let ident = get_ident(tree, RefNode::UdpIdentifier(&x.nodes.1.nodes.2)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::UdpIdentifier); let ports = udp_port_list(tree, &x.nodes.1.nodes.3.nodes.1, event_iter, url)?; for port_dec in ports { scope.defs.push(Box::new(port_dec)); } } UdpDeclaration::Wildcard(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.7)); let ident = get_ident(tree, RefNode::UdpIdentifier(&x.nodes.2)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::UdpIdentifier); for port_dec in &x.nodes.5 { let ports = udp_port_dec(tree, port_dec, event_iter, url)?; for port in ports { scope.defs.push(Box::new(port)); } } } } let (scopes, mut defs) = match_until_leave!(tree, event_iter, url, RefNode::UdpDeclaration)?; scope.scopes = scopes; scope.defs.append(&mut defs); scope.completion_kind = CompletionItemKind::MODULE; scope.symbol_kind = SymbolKind::MODULE; Some(scope) } pub fn program_dec( tree: &SyntaxTree, node: &ProgramDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut scope: GenericScope = GenericScope::new(url); match node { ProgramDeclaration::Nonansi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.3)); let ident = get_ident(tree, RefNode::ProgramIdentifier(&x.nodes.0.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::InterfaceIdentifier); for import_dec in &x.nodes.0.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.0.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } } ProgramDeclaration::Ansi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.3)); let ident = get_ident(tree, RefNode::ProgramIdentifier(&x.nodes.0.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::ProgramIdentifier); for import_dec in &x.nodes.0.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.0.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } if let Some(list_port_decs) = &x.nodes.0.nodes.6 { if let Some(port_decs) = &list_port_decs.nodes.0.nodes.1 { for _ in port_decs.contents() { let ansi_dec = skip_until_enter!( tree, event_iter, RefNode::AnsiPortDeclaration, &AnsiPortDeclaration )?; scope .defs .push(Box::new(port_dec_ansi(tree, ansi_dec, event_iter, url)?)) } } } } ProgramDeclaration::Wildcard(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&x.nodes.7)); let ident = get_ident(tree, RefNode::ProgramIdentifier(&x.nodes.2)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::ProgramIdentifier); } ProgramDeclaration::ExternNonansi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0)); scope.end = get_loc(tree, RefNode::Symbol(&x.nodes.1.nodes.7)); let ident = get_ident(tree, RefNode::ProgramIdentifier(&x.nodes.1.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::ProgramIdentifier); for import_dec in &x.nodes.1.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.1.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } } ProgramDeclaration::ExternAnsi(x) => { scope.start = get_loc(tree, RefNode::Keyword(&x.nodes.0)); scope.end = get_loc(tree, RefNode::Symbol(&x.nodes.1.nodes.7)); let ident = get_ident(tree, RefNode::ProgramIdentifier(&x.nodes.1.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::ProgramIdentifier); for import_dec in &x.nodes.1.nodes.4 { let imports = package_import(tree, import_dec, event_iter, url)?; for import in imports { scope.defs.push(Box::new(import)); } } if let Some(pport_list) = &x.nodes.1.nodes.5 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } if let Some(list_port_decs) = &x.nodes.1.nodes.6 { if let Some(port_decs) = &list_port_decs.nodes.0.nodes.1 { for _ in port_decs.contents() { let ansi_dec = skip_until_enter!( tree, event_iter, RefNode::AnsiPortDeclaration, &AnsiPortDeclaration )?; scope .defs .push(Box::new(port_dec_ansi(tree, ansi_dec, event_iter, url)?)) } } } } } let (scopes, mut defs) = match_until_leave!(tree, event_iter, url, RefNode::ProgramDeclaration)?; scope.scopes = scopes; scope.defs.append(&mut defs); scope.completion_kind = CompletionItemKind::MODULE; scope.symbol_kind = SymbolKind::MODULE; Some(scope) } pub fn package_dec( tree: &SyntaxTree, node: &PackageDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut scope: GenericScope = GenericScope::new(url); scope.start = get_loc(tree, RefNode::Keyword(&node.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&node.nodes.7)); let ident = get_ident(tree, RefNode::PackageIdentifier(&node.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::PackageIdentifier); let (scopes, mut defs) = match_until_leave!(tree, event_iter, url, RefNode::PackageDeclaration)?; scope.scopes = scopes; scope.defs.append(&mut defs); scope.completion_kind = CompletionItemKind::MODULE; scope.symbol_kind = SymbolKind::PACKAGE; Some(scope) } pub fn config_dec( tree: &SyntaxTree, node: &ConfigDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut scope: GenericScope = GenericScope::new(url); scope.start = get_loc(tree, RefNode::Keyword(&node.nodes.0)); scope.end = get_loc(tree, RefNode::Keyword(&node.nodes.6)); let ident = get_ident(tree, RefNode::ConfigIdentifier(&node.nodes.1)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::ConfigIdentifier); for localparam in &node.nodes.3 { let params = localparam_dec(tree, &localparam.0, event_iter, url)?; for param in params { scope.defs.push(Box::new(param)); } } let (scopes, mut defs) = match_until_leave!(tree, event_iter, url, RefNode::ConfigDeclaration)?; scope.scopes = scopes; scope.defs.append(&mut defs); scope.completion_kind = CompletionItemKind::MODULE; scope.symbol_kind = SymbolKind::MODULE; Some(scope) } pub fn class_dec( tree: &SyntaxTree, node: &ClassDeclaration, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut scope: ClassDec = ClassDec::new(url); scope.start = get_loc(tree, RefNode::Keyword(&node.nodes.1)); scope.end = get_loc(tree, RefNode::Keyword(&node.nodes.9)); let ident = get_ident(tree, RefNode::ClassIdentifier(&node.nodes.3)); scope.ident = ident.0; scope.byte_idx = ident.1; let type_str = &mut scope.type_str; advance_until_leave!(type_str, tree, event_iter, RefNode::ClassIdentifier); if let Some(pport_list) = &node.nodes.4 { let pports = param_port_list(tree, pport_list, event_iter, url)?; for pport in pports { scope.defs.push(Box::new(pport)); } } if let Some(extend) = &node.nodes.5 { if let Some(package_scope) = &extend.1.nodes.0.nodes.0 { match package_scope { PackageScope::Package(x) => { let ident = get_ident(tree, RefNode::PackageIdentifier(&x.nodes.0)); scope.extends.1 = Some(ident.0); } PackageScope::Unit(_) => {} } } let ident = get_ident(tree, RefNode::ClassIdentifier(&extend.1.nodes.0.nodes.1)); scope.extends.0.push(ident.0); for class in &extend.1.nodes.2 { let ident = get_ident(tree, RefNode::ClassIdentifier(&class.1)); scope.extends.0.push(ident.0); } } if let Some(interfaces) = &node.nodes.6 { for idec in interfaces.1.contents() { let ident = get_ident(tree, RefNode::ClassIdentifier(&idec.nodes.0.nodes.1)); let mut interface: (String, Option) = (ident.0, None); if let Some(package_scope) = &idec.nodes.0.nodes.0 { match package_scope { PackageScope::Package(x) => { let ident = get_ident(tree, RefNode::PackageIdentifier(&x.nodes.0)); interface.1 = Some(ident.0); } PackageScope::Unit(_) => {} } } scope.implements.push(interface); } } let (scopes, mut defs) = match_until_leave!(tree, event_iter, url, RefNode::ClassDeclaration)?; scope.scopes = scopes; scope.defs.append(&mut defs); Some(scope) } // `define definition pub fn text_macro_def( tree: &SyntaxTree, node: &TextMacroDefinition, event_iter: &mut EventIter, url: &Url, ) -> Option { let mut text_macro = GenericDec::new(url); let ident = get_ident(tree, RefNode::TextMacroIdentifier(&node.nodes.2.nodes.0)); text_macro.ident = ident.0; text_macro.byte_idx = ident.1; let type_str = &mut text_macro.type_str; advance_until_enter!( type_str, tree, event_iter, RefNode::TextMacroIdentifier, &TextMacroIdentifier ); text_macro.completion_kind = CompletionItemKind::FUNCTION; text_macro.symbol_kind = SymbolKind::FUNCTION; Some(text_macro) } ================================================ FILE: src/definition.rs ================================================ use crate::definition::extract_defs::get_ident; use crate::server::LSPServer; use crate::sources::LSPSupport; use log::{debug, trace}; use ropey::{Rope, RopeSlice}; use sv_parser::*; use tower_lsp::lsp_types::*; pub mod def_types; pub use def_types::*; mod extract_defs; use extract_defs::*; impl LSPServer { pub fn goto_definition(&self, params: GotoDefinitionParams) -> Option { let doc = params.text_document_position_params.text_document.uri; let pos = params.text_document_position_params.position; let file_id = self.srcs.get_id(&doc).to_owned(); self.srcs.wait_parse_ready(file_id, false); let file = self.srcs.get_file(file_id)?; let file = file.read().ok()?; let token = get_definition_token(file.text.line(pos.line as usize), pos); debug!("goto definition, token: {}", &token); let scope_tree = self.srcs.scope_tree.read().ok()?; trace!("{:#?}", scope_tree.as_ref()?); let def = scope_tree .as_ref()? .get_definition(&token, file.text.pos_to_byte(&pos), &doc)?; let def_pos = file.text.byte_to_pos(def.byte_idx()); debug!("def: {:?}", def_pos); Some(GotoDefinitionResponse::Scalar(Location::new( def.url(), Range::new(def_pos, def_pos), ))) } pub fn hover(&self, params: HoverParams) -> Option { let doc = params.text_document_position_params.text_document.uri; let pos = params.text_document_position_params.position; let file_id = self.srcs.get_id(&doc).to_owned(); self.srcs.wait_parse_ready(file_id, false); let file = self.srcs.get_file(file_id)?; let file = file.read().ok()?; let token = get_definition_token(file.text.line(pos.line as usize), pos); debug!("hover, token: {}", &token); let scope_tree = self.srcs.scope_tree.read().ok()?; let def = scope_tree .as_ref()? .get_definition(&token, file.text.pos_to_byte(&pos), &doc)?; let def_line = file.text.byte_to_line(def.byte_idx()); Some(Hover { contents: HoverContents::Scalar(MarkedString::LanguageString(LanguageString { language: "systemverilog".to_owned(), value: get_hover(&file.text, def_line), })), range: None, }) } pub fn document_symbol(&self, params: DocumentSymbolParams) -> Option { let uri = params.text_document.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 scope_tree = self.srcs.scope_tree.read().ok()?; Some(DocumentSymbolResponse::Nested( scope_tree.as_ref()?.document_symbols(&uri, &file.text), )) } pub fn document_highlight( &self, params: DocumentHighlightParams, ) -> Option> { let uri = params.text_document_position_params.text_document.uri; 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 file = self.srcs.get_file(file_id)?; let file = file.read().ok()?; let token = get_definition_token(file.text.line(pos.line as usize), pos); let scope_tree = self.srcs.scope_tree.read().ok()?; // use the byte_idx of the definition if possible, otherwise use the cursor let byte_idx = match scope_tree .as_ref()? .get_definition(&token, file.text.pos_to_byte(&pos), &uri) { Some(def) => def.byte_idx, None => file.text.pos_to_byte(&pos), }; let syntax_tree = file.syntax_tree.as_ref()?; let references = all_identifiers(syntax_tree, &token); Some( scope_tree .as_ref()? .document_highlights(&uri, &file.text, references, byte_idx), ) } } /// return all identifiers in a syntax tree matching a given token fn all_identifiers(syntax_tree: &SyntaxTree, token: &str) -> Vec<(String, usize)> { let mut idents: Vec<(String, usize)> = Vec::new(); for node in syntax_tree { if let RefNode::Identifier(_) = node { let (ident, byte_idx) = get_ident(syntax_tree, node); if ident == token { idents.push((ident, byte_idx)); } } } idents } /// retrieve the token the user invoked goto definition or hover on fn get_definition_token(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(); while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') { token.push(c.unwrap()); c = line_iter.prev(); } token = token.chars().rev().collect(); line_iter = line.chars(); for _ in 0..(line.utf16_cu_to_char(pos.character as usize)) { line_iter.next(); } let mut c = line_iter.next(); while c.is_some() && (c.unwrap().is_alphanumeric() || c.unwrap() == '_') { token.push(c.unwrap()); c = line_iter.next(); } token } type ScopesAndDefs = Option<(Vec>, Vec>)>; /// 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> = Vec::new(); let mut scopes: Vec> = 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(syntax_tree: &SyntaxTree, url: &Url) -> Option { trace!("{}", syntax_tree); let mut scopes: Vec> = Vec::new(); let mut global_scope: GenericScope = GenericScope::new(url); global_scope.ident = "global".to_string(); 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) } /// get the hover information fn get_hover(doc: &Rope, line: usize) -> String { if line == 0 { return doc.line(line).to_string(); } let mut hover: Vec = Vec::new(); let mut multiline: bool = false; let mut valid: bool = true; let mut current: String = doc.line(line).to_string(); let ltrim: String = " ".repeat(current.len() - current.trim_start().len()); let mut line_idx = line; // iterate upwards from the definition, and grab the comments while valid { hover.push(current.clone()); line_idx -= 1; valid = false; if line_idx > 0 { current = doc.line(line_idx).to_string(); let currentl = current.clone().trim_start().to_owned(); let currentr = current.clone().trim_end().to_owned(); if currentl.starts_with("/*") && currentr.ends_with("*/") { valid = true; } else if currentr.ends_with("*/") { multiline = true; valid = true; } else if currentl.starts_with("/*") { multiline = false; valid = true; } else { valid = currentl.starts_with("//") || multiline; } } } hover.reverse(); let mut result: Vec = Vec::new(); for i in hover { if let Some(stripped) = i.strip_prefix(<rim) { result.push(stripped.to_owned()); } else { result.push(i); } } result.join("").trim_end().to_owned() } #[cfg(test)] mod tests { use super::*; use crate::sources::{parse, LSPSupport}; use crate::support::test_init; use ropey::Rope; use std::fs::read_to_string; use std::path::PathBuf; #[test] fn test_definition_token() { test_init(); let line = Rope::from_str("assign ab_c[2:0] = 3'b000;"); let token = get_definition_token(line.line(0), Position::new(0, 10)); assert_eq!(token, "ab_c".to_owned()); } #[test] fn test_get_definition() { test_init(); let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); d.push("test_data/definition_test.sv"); let text = read_to_string(d).unwrap(); let doc = Rope::from_str(&text); let url = Url::parse("file:///test_data/definition_test.sv").unwrap(); let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap(); trace!("{}", &syntax_tree); let scope_tree = get_scopes(&syntax_tree, &url).unwrap(); trace!("{:#?}", &scope_tree); for def in &scope_tree.defs { trace!("{:?} {:?}", def, doc.byte_to_pos(def.byte_idx())); } let token = get_definition_token(doc.line(3), Position::new(3, 13)); for def in scope_tree.defs { if token == def.ident() { assert_eq!(doc.byte_to_pos(def.byte_idx()), Position::new(3, 9)) } } } #[test] fn test_hover() { test_init(); let text = r#" // module test // test module module test; /* a */ logic a; /** * b */ logic b; endmodule"#; let doc = Rope::from_str(text); eprintln!("{}", get_hover(&doc, 2)); assert_eq!( get_hover(&doc, 3), r#"// module test // test module module test;"# .to_owned() ); assert_eq!( get_hover(&doc, 9), r#"/** * b */ logic b;"# .to_owned() ); } #[test] fn test_symbols() { test_init(); let text = r#" module test; logic a; logic b; endmodule"#; let doc = Rope::from_str(text); let url = Url::parse("file:///test.sv").unwrap(); let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap(); let scope_tree = get_scopes(&syntax_tree, &url).unwrap(); let symbol = scope_tree.document_symbols(&url, &doc); let symbol = symbol.first().unwrap(); assert_eq!(&symbol.name, "test"); let names: Vec = symbol .children .as_ref() .unwrap() .iter() .map(|x| x.name.clone()) .collect(); assert!(names.contains(&"a".to_string())); assert!(names.contains(&"b".to_string())); } #[test] fn test_highlight() { test_init(); let text = r#" module test; logic clk; assign clk = 1'b1; endmodule"#; let doc = Rope::from_str(text); let url = Url::parse("file:///test.sv").unwrap(); let syntax_tree = parse(&doc, &url, &None, &Vec::new()).unwrap(); let scope_tree = get_scopes(&syntax_tree, &url).unwrap(); let references = all_identifiers(&syntax_tree, "clk"); let highlights = scope_tree.document_highlights( &url, &doc, references, doc.pos_to_byte(&Position::new(2, 8)), ); let expected = vec![ DocumentHighlight { range: Range { start: Position { line: 2, character: 8, }, end: Position { line: 2, character: 11, }, }, kind: None, }, DocumentHighlight { range: Range { start: Position { line: 3, character: 9, }, end: Position { line: 3, character: 12, }, }, kind: None, }, ]; assert_eq!(highlights, expected) } } ================================================ FILE: src/diagnostics.rs ================================================ use crate::server::ProjectConfig; #[cfg(feature = "slang")] use path_clean::PathClean; use regex::Regex; use ropey::Rope; #[cfg(feature = "slang")] use std::env::current_dir; #[cfg(feature = "slang")] use std::path::Path; use std::path::PathBuf; use std::process::{Command, Stdio}; use tower_lsp::lsp_types::*; #[cfg(feature = "slang")] use veridian_slang::slang_compile; use walkdir::DirEntry; #[cfg(feature = "slang")] use walkdir::WalkDir; #[cfg(feature = "slang")] pub fn get_diagnostics( uri: Url, rope: &Rope, files: Vec, conf: &ProjectConfig, ) -> PublishDiagnosticsParams { if !(cfg!(test) && (uri.to_string().starts_with("file:///test"))) { let paths = get_paths(files, conf.auto_search_workdir); let mut diagnostics = { if conf.verilator.syntax.enabled { if let Ok(path) = uri.to_file_path() { verilator_syntax( rope, path, &conf.verilator.syntax.path, &conf.verilator.syntax.args, ) .unwrap_or_default() } else { Vec::new() } } else if conf.verible.syntax.enabled { verible_syntax(rope, &conf.verible.syntax.path, &conf.verible.syntax.args) .unwrap_or_default() } else { Vec::new() } }; diagnostics.append(&mut parse_report( uri.clone(), slang_compile(paths).unwrap(), )); PublishDiagnosticsParams { uri, diagnostics, version: None, } } else { PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None, } } } #[cfg(not(feature = "slang"))] pub fn get_diagnostics( uri: Url, rope: &Rope, #[allow(unused_variables)] files: Vec, conf: &ProjectConfig, ) -> PublishDiagnosticsParams { if !(cfg!(test) && (uri.to_string().starts_with("file:///test"))) { let diagnostics = { if conf.verilator.syntax.enabled { if let Ok(path) = uri.to_file_path() { verilator_syntax( rope, path, &conf.verilator.syntax.path, &conf.verilator.syntax.args, ) .unwrap_or_default() } else { Vec::new() } } else if conf.verible.syntax.enabled { verible_syntax(rope, &conf.verible.syntax.path, &conf.verible.syntax.args) .unwrap_or_default() } else { Vec::new() } }; PublishDiagnosticsParams { uri, diagnostics, version: None, } } else { PublishDiagnosticsParams { uri, diagnostics: Vec::new(), version: None, } } } /// recursively find source file paths from working directory /// and open files #[cfg(feature = "slang")] fn get_paths(files: Vec, search_workdir: bool) -> Vec { // check recursively from working dir for source files let mut paths: Vec = Vec::new(); if search_workdir { let walker = WalkDir::new(".").into_iter(); for entry in walker.filter_entry(|e| !is_hidden(e)) { let entry = entry.unwrap(); if entry.file_type().is_file() { let extension = entry.path().extension().unwrap(); if extension == "sv" || extension == "svh" || extension == "v" || extension == "vh" { paths.push(entry.path().to_path_buf()); } } } } // check recursively from opened files for source files for file in files { if let Ok(path) = file.to_file_path() { if !paths.contains(&path) { let walker = WalkDir::new(path.parent().unwrap()).into_iter(); for entry in walker.filter_entry(|e| !is_hidden(e)).flatten() { if entry.file_type().is_file() && entry.path().extension().is_some() { let extension = entry.path().extension().unwrap(); if extension == "sv" || extension == "svh" || extension == "v" || extension == "vh" { let entry_path = entry.path().to_path_buf(); if !paths.contains(&entry_path) { paths.push(entry_path); } } } } } } } paths } pub fn is_hidden(entry: &DirEntry) -> bool { entry .file_name() .to_str() .map(|s| s.starts_with('.')) .unwrap_or(false) } #[cfg(feature = "slang")] /// parse a report from slang fn parse_report(uri: Url, report: String) -> Vec { let mut diagnostics: Vec = Vec::new(); for line in report.lines() { let diag: Vec<&str> = line.splitn(5, ':').collect(); if absolute_path(diag.first().unwrap()) == uri.to_file_path().unwrap().as_os_str() { let pos = Position::new( diag.get(1).unwrap().parse::().unwrap() - 1, diag.get(2).unwrap().parse::().unwrap() - 1, ); diagnostics.push(Diagnostic::new( Range::new(pos, pos), slang_severity(diag.get(3).unwrap()), None, Some("slang".to_owned()), (*diag.get(4).unwrap()).to_owned(), None, None, )) } } diagnostics } #[cfg(feature = "slang")] fn slang_severity(severity: &str) -> Option { match severity { " error" => Some(DiagnosticSeverity::ERROR), " warning" => Some(DiagnosticSeverity::WARNING), " note" => Some(DiagnosticSeverity::INFORMATION), _ => None, } } #[cfg(feature = "slang")] // convert relative path to absolute fn absolute_path(path_str: &str) -> PathBuf { let path = Path::new(path_str); current_dir().unwrap().join(path).clean() } /// convert captured severity string to DiagnosticSeverity fn verilator_severity(severity: &str) -> Option { 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> { 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 = std::sync::OnceLock::new(); let re = RE.get_or_init(|| { Regex::new( r"%(?PError|Warning)(-(?P[A-Z0-9_]+))?: (?P[^:]+):(?P\d+):((?P\d+):)? ?(?P.*)", ) .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 = Vec::new(); let raw_output = String::from_utf8(output.stderr).ok()?; let filtered_output = raw_output .lines() .filter(|line| line.starts_with('%')) .collect::>(); 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 // included files if caps.name("filepath")?.as_str() != file_path.to_str().unwrap_or("") { continue; } let severity = verilator_severity(caps.name("severity")?.as_str()); 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()?; let pos = Position::new(line - 1, col - 1); let msg = match severity { Some(DiagnosticSeverity::ERROR) => caps.name("message")?.as_str().to_string(), Some(DiagnosticSeverity::WARNING) => format!( "{}: {}", caps.name("warning_type")?.as_str(), caps.name("message")?.as_str() ), _ => "".to_string(), }; diags.push(Diagnostic::new( Range::new(pos, pos), severity, None, Some("verilator".to_string()), msg, None, None, )); } Some(diags) } else { None } } /// syntax checking using verible-verilog-syntax fn verible_syntax( rope: &Rope, verible_syntax_path: &str, verible_syntax_args: &[String], ) -> Option> { 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 = std::sync::OnceLock::new(); let re = RE.get_or_init(|| { Regex::new( r"^.+:(?P\d*):(?P\d*)(?:-(?P\d*))?:\s(?P.*)\s.*$", ) .unwrap() }); // write file to stdin, read output from stdout rope.write_to(child.stdin.as_mut()?).ok()?; let output = child.wait_with_output().ok()?; if !output.status.success() { let mut diags: Vec = Vec::new(); let raw_output = String::from_utf8(output.stdout).ok()?; for error in raw_output.lines() { let caps = re.captures(error)?; let line: u32 = caps.name("line")?.as_str().parse().ok()?; let startcol: u32 = caps.name("startcol")?.as_str().parse().ok()?; let endcol: Option = match caps.name("endcol").map(|e| e.as_str().parse()) { Some(Ok(e)) => Some(e), None => None, Some(Err(_)) => return None, }; let start_pos = Position::new(line - 1, startcol - 1); let end_pos = Position::new(line - 1, endcol.unwrap_or(startcol) - 1); diags.push(Diagnostic::new( Range::new(start_pos, end_pos), Some(DiagnosticSeverity::ERROR), None, Some("verible".to_string()), caps.name("message")?.as_str().to_string(), None, None, )); } Some(diags) } else { None } } #[cfg(test)] mod tests { use super::*; use crate::support::test_init; use std::fs::File; use std::io::Write; use tempdir::TempDir; #[test] #[cfg(feature = "slang")] fn test_diagnostics() { test_init(); let uri = Url::from_file_path(absolute_path("test_data/diag/diag_test.sv")).unwrap(); let expected = PublishDiagnosticsParams::new( uri.clone(), vec![Diagnostic::new( Range::new(Position::new(3, 13), Position::new(3, 13)), Some(DiagnosticSeverity::WARNING), None, Some("slang".to_owned()), " cannot refer to element 2 of \'logic[1:0]\' [-Windex-oob]".to_owned(), None, None, )], None, ); let diag = get_diagnostics( uri.clone(), &Rope::default(), vec![uri], &ProjectConfig::default(), ); assert_eq!(diag.uri, expected.uri); assert_eq!(diag.version, expected.version); assert_eq!(diag.diagnostics.last(), expected.diagnostics.last()); } #[test] fn test_unsaved_file() { test_init(); let uri = Url::parse("file://test.sv").unwrap(); get_diagnostics( uri.clone(), &Rope::default(), vec![uri], &ProjectConfig::default(), ); } #[test] fn test_verible_syntax() { let text = r#"module test; logic abc; logic abcd; a endmodule "#; let doc = Rope::from_str(text); let errors = verible_syntax(&doc, "verible-verilog-syntax", &[]) .expect("verible-verilog-syntax not found, test can not run"); let expected: Vec = vec![Diagnostic { range: Range { start: Position { line: 5, character: 0, }, end: Position { line: 5, character: 8, }, }, severity: Some(DiagnosticSeverity::ERROR), code: None, source: Some("verible".to_string()), message: "syntax error at token".to_string(), related_information: None, tags: None, code_description: None, data: None, }]; assert_eq!(errors, expected); } #[test] fn test_verilator_syntax() { let text = r#"module test; logic abc; logic abcd; a endmodule "#; let doc = Rope::from_str(text); // verilator can't read from stdin so we must create a temp dir to place our // test file let dir = TempDir::new("verilator_test").unwrap(); let file_path_1 = dir.path().join("test.sv"); let mut f = File::create(&file_path_1).unwrap(); f.write_all(text.as_bytes()).unwrap(); f.sync_all().unwrap(); let errors = verilator_syntax( &doc, file_path_1, "verilator", &[ "--lint-only".to_string(), "--sv".to_string(), "-Wall".to_string(), ], ) .expect("verilator not found, test can not run"); drop(f); dir.close().unwrap(); let expected: Vec = vec![Diagnostic { range: Range { start: Position { line: 5, character: 0, }, end: Position { line: 5, character: 0, }, }, severity: Some(DiagnosticSeverity::ERROR), code: None, source: Some("verilator".to_string()), message: "syntax error, unexpected endmodule, expecting IDENTIFIER or randomize" .to_string(), related_information: None, tags: None, code_description: None, data: None, }]; assert_eq!(errors[0].severity, expected[0].severity); assert_eq!(errors[0].range.start.line, expected[0].range.start.line); assert_eq!(errors[0].range.end.line, expected[0].range.end.line); assert!(errors[0].message.contains("syntax error")); } } ================================================ FILE: src/format.rs ================================================ use crate::server::LSPServer; use crate::sources::LSPSupport; use log::info; use ropey::Rope; use std::process::{Command, Stdio}; use tower_lsp::lsp_types::*; impl LSPServer { pub fn formatting(&self, params: DocumentFormattingParams) -> Option> { 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 } } pub fn range_formatting(&self, params: DocumentRangeFormattingParams) -> Option> { 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 } } } /// format the document using verible-verilog-format pub fn format_document( rope: &Rope, range: Option, verible_format_path: &str, verible_format_args: &[String], ) -> Option { let mut child = Command::new(verible_format_path); child .stdin(Stdio::piped()) .stderr(Stdio::piped()) .stdout(Stdio::piped()) .args(verible_format_args); // rangeFormatting if let Some(r) = range { child .arg("--lines") .arg(format!("{}-{}", r.start.line + 1, r.end.line + 1)); } let mut child = child.arg("-").spawn().ok()?; // 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() { info!("formatting succeeded"); let raw_output = String::from_utf8(output.stdout).ok()?; Some(raw_output) } else { None } } #[cfg(test)] mod tests { use super::*; use crate::server::ProjectConfig; use crate::support::test_init; use which::which; #[test] fn test_formatting() { test_init(); let text = r#" module test; logic a; logic b; endmodule"#; let text_fixed = r#" module test; logic a; logic b; endmodule "#; let doc = Rope::from_str(text); if which("verible-verilog-format").is_ok() { assert_eq!( format_document( &doc, None, &ProjectConfig::default().verible.format.path, &[] ) .unwrap(), text_fixed.to_string() ); } } #[test] fn test_range_formatting() { test_init(); let text = r#"module t1; logic a; logic b; logic c; endmodule module t2; logic a; logic b; logic c; endmodule"#; let text_fixed = r#"module t1; logic a; logic b; logic c; endmodule module t2; logic a; logic b; logic c; endmodule "#; let doc = Rope::from_str(text); if which("verible-verilog-format").is_ok() { assert_eq!( format_document( &doc, Some(Range::new(Position::new(0, 0), Position::new(4, 9))), &ProjectConfig::default().verible.format.path, &[] ) .unwrap(), text_fixed.to_string() ); } } } ================================================ FILE: src/lib.rs ================================================ #![recursion_limit = "256"] pub mod completion; pub mod definition; pub mod diagnostics; pub mod format; pub mod server; pub mod sources; pub mod support; ================================================ FILE: src/main.rs ================================================ #![recursion_limit = "256"] use log::info; use std::sync::Arc; use structopt::StructOpt; use tower_lsp::{LspService, Server}; mod completion; mod definition; mod diagnostics; mod format; mod server; mod sources; #[cfg(test)] mod support; use server::Backend; #[derive(StructOpt, Debug)] #[structopt(name = "veridian", about = "A SystemVerilog/Verilog Language Server")] struct Opt {} #[tokio::main] async fn main() { let _ = Opt::from_args(); let log_handle = flexi_logger::Logger::with(flexi_logger::LogSpecification::info()) .start() .unwrap(); info!("starting veridian..."); let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); let (service, messages) = LspService::new(|client| Arc::new(Backend::new(client, log_handle))); Server::new(stdin, stdout, messages).serve(service).await; } ================================================ FILE: src/server.rs ================================================ use crate::sources::*; use crate::completion::keyword::*; use flexi_logger::LoggerHandle; use log::{debug, info, warn}; use path_clean::PathClean; use serde::{Deserialize, Serialize}; use std::env::current_dir; use std::fs::File; use std::io::Read; use std::path::PathBuf; use std::string::ToString; use std::sync::{Mutex, RwLock}; use tower_lsp::jsonrpc::{Error, ErrorCode, Result}; use tower_lsp::lsp_types::*; use tower_lsp::{Client, LanguageServer}; use which::which; pub struct LSPServer { pub srcs: Sources, pub key_comps: Vec, pub sys_tasks: Vec, pub directives: Vec, pub conf: RwLock, pub log_handle: Mutex>, } impl LSPServer { pub fn new(log_handle: Option) -> LSPServer { LSPServer { srcs: Sources::new(), key_comps: keyword_completions(KEYWORDS), sys_tasks: other_completions(SYS_TASKS), directives: other_completions(DIRECTIVES), conf: RwLock::new(ProjectConfig::default()), log_handle: Mutex::new(log_handle), } } } pub struct Backend { client: Client, server: LSPServer, } impl Backend { pub fn new(client: Client, log_handle: LoggerHandle) -> Backend { Backend { client, server: LSPServer::new(Some(log_handle)), } } } #[derive(strum_macros::Display, Debug, Serialize, Deserialize)] pub enum LogLevel { #[strum(serialize = "error")] Error, #[strum(serialize = "warn")] Warn, #[strum(serialize = "info")] Info, #[strum(serialize = "debug")] Debug, #[strum(serialize = "trace")] Trace, } #[derive(Debug, Serialize, Deserialize)] #[serde(default)] pub struct ProjectConfig { // if true, recursively search the working directory for files to run diagnostics on pub auto_search_workdir: bool, // list of directories with header files pub include_dirs: Vec, // list of directories to recursively search for SystemVerilog/Verilog sources pub source_dirs: Vec, // config options for verible tools pub verible: Verible, // config options for verilator tools pub verilator: Verilator, // log level pub log_level: LogLevel, } impl Default for ProjectConfig { fn default() -> Self { ProjectConfig { auto_search_workdir: true, include_dirs: Vec::new(), source_dirs: Vec::new(), verible: Verible::default(), verilator: Verilator::default(), 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, } 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, } 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, } impl Default for VeribleFormat { fn default() -> Self { Self { enabled: true, path: "verible-verilog-format".to_string(), args: Vec::new(), } } } fn read_config(root_uri: Option) -> anyhow::Result { let path = root_uri .ok_or_else(|| anyhow::anyhow!("couldn't resolve workdir path"))? .to_file_path() .map_err(|_| anyhow::anyhow!("couldn't resolve workdir path"))?; let mut config: Option = None; for dir in path.ancestors() { let config_path = dir.join("veridian.yaml"); if config_path.exists() { info!("found config: veridian.yaml"); config = Some(config_path); break; } let config_path = dir.join("veridian.yml"); if config_path.exists() { info!("found config: veridian.yml"); config = Some(config_path); break; } } let mut contents = String::new(); File::open(config.ok_or_else(|| anyhow::anyhow!("unable to read config file"))?)? .read_to_string(&mut contents)?; info!("reading config file"); Ok(serde_yaml::from_str(&contents)?) } // convert string path to absolute path fn absolute_path(path_str: &str) -> Option { let path = PathBuf::from(path_str); if !path.exists() { return None; } if !path.has_root() { Some(current_dir().unwrap().join(path).clean()) } else { Some(path) } } #[tower_lsp::async_trait] impl LanguageServer for Backend { async fn initialize(&self, params: InitializeParams) -> Result { // grab include dirs and source dirs from config, and convert to abs path let mut inc_dirs = self.server.srcs.include_dirs.write().unwrap(); let mut src_dirs = self.server.srcs.source_dirs.write().unwrap(); match read_config(params.root_uri) { Ok(conf) => { inc_dirs.extend(conf.include_dirs.iter().filter_map(|x| absolute_path(x))); debug!("{:#?}", inc_dirs); src_dirs.extend(conf.source_dirs.iter().filter_map(|x| absolute_path(x))); debug!("{:#?}", src_dirs); let mut log_handle = self.server.log_handle.lock().unwrap(); let log_handle = log_handle.as_mut(); if let Some(handle) = log_handle { handle .parse_and_push_temp_spec(conf.log_level.to_string()) .map_err(|e| Error { code: ErrorCode::InvalidParams, message: e.to_string().into(), data: None, })?; } *self.server.conf.write().unwrap() = conf; } Err(e) => { warn!("found errors in config file: {:#?}", e); } } let mut conf = self.server.conf.write().unwrap(); conf.verible.syntax.enabled = which(&conf.verible.syntax.path).is_ok(); if cfg!(feature = "slang") { info!("enabled linting with slang"); } if conf.verilator.syntax.enabled { info!("enabled linting with verilator") } else if conf.verible.syntax.enabled { info!("enabled linting with verible-verilog-syntax") } conf.verible.format.enabled = which(&conf.verible.format.path).is_ok(); if conf.verible.format.enabled { info!("enabled formatting with verible-verilog-format"); } else { info!("formatting unavailable"); } drop(inc_dirs); drop(src_dirs); // parse all source files found from walking source dirs and include dirs self.server.srcs.init(); Ok(InitializeResult { server_info: None, capabilities: ServerCapabilities { text_document_sync: Some(TextDocumentSyncCapability::Options( TextDocumentSyncOptions { open_close: Some(true), change: Some(TextDocumentSyncKind::INCREMENTAL), will_save: None, will_save_wait_until: None, save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions { include_text: None, })), }, )), completion_provider: Some(CompletionOptions { resolve_provider: Some(false), trigger_characters: Some(vec![ ".".to_string(), "$".to_string(), "`".to_string(), ]), work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None, }, all_commit_characters: None, //TODO: check if corect completion_item: None, }), definition_provider: Some(OneOf::Left(true)), hover_provider: Some(HoverProviderCapability::Simple(true)), document_symbol_provider: Some(OneOf::Left(true)), document_formatting_provider: Some(OneOf::Left(conf.verible.format.enabled)), document_range_formatting_provider: Some(OneOf::Left(conf.verible.format.enabled)), document_highlight_provider: Some(OneOf::Left(true)), ..ServerCapabilities::default() }, }) } async fn initialized(&self, _: InitializedParams) { self.client .log_message(MessageType::INFO, "veridian initialized!") .await; } async fn shutdown(&self) -> Result<()> { Ok(()) } async fn did_open(&self, params: DidOpenTextDocumentParams) { let diagnostics = self.server.did_open(params); self.client .publish_diagnostics( diagnostics.uri, diagnostics.diagnostics, diagnostics.version, ) .await; } async fn did_change(&self, params: DidChangeTextDocumentParams) { self.server.did_change(params); } async fn did_save(&self, params: DidSaveTextDocumentParams) { let diagnostics = self.server.did_save(params); self.client .publish_diagnostics( diagnostics.uri, diagnostics.diagnostics, diagnostics.version, ) .await; } async fn completion(&self, params: CompletionParams) -> Result> { Ok(self.server.completion(params)) } async fn goto_definition( &self, params: GotoDefinitionParams, ) -> Result> { Ok(self.server.goto_definition(params)) } async fn hover(&self, params: HoverParams) -> Result> { Ok(self.server.hover(params)) } async fn document_symbol( &self, params: DocumentSymbolParams, ) -> Result> { Ok(self.server.document_symbol(params)) } async fn formatting(&self, params: DocumentFormattingParams) -> Result>> { Ok(self.server.formatting(params)) } async fn range_formatting( &self, params: DocumentRangeFormattingParams, ) -> Result>> { Ok(self.server.range_formatting(params)) } async fn document_highlight( &self, params: DocumentHighlightParams, ) -> Result>> { Ok(self.server.document_highlight(params)) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_config() { let config = r#" auto_search_workdir: false format: true verible: syntax: enabled: true path: "verible-verilog-syntax" format: args: - --net_variable_alignment=align log_level: Info "#; let config = serde_yaml::from_str::(config); dbg!(&config); assert!(config.is_ok()); } } ================================================ FILE: src/sources.rs ================================================ use crate::definition::def_types::*; use crate::definition::get_scopes; use crate::diagnostics::{get_diagnostics, is_hidden}; use crate::server::LSPServer; use log::{debug, error, trace}; use pathdiff::diff_paths; use ropey::{Rope, RopeSlice}; use std::cmp::min; use std::collections::HashMap; use std::env::current_dir; use std::fs; use std::ops::Range as StdRange; use std::path::PathBuf; use std::sync::{Arc, Condvar, Mutex, RwLock}; use std::thread; use std::time::Instant; use sv_parser::*; use tower_lsp::lsp_types::*; use walkdir::WalkDir; impl LSPServer { pub fn did_open(&self, params: DidOpenTextDocumentParams) -> PublishDiagnosticsParams { let document: TextDocumentItem = params.text_document; let uri = document.uri.clone(); debug!("did_open: {}", &uri); // check if doc is already added if self.srcs.names.read().unwrap().contains_key(&document.uri) { // convert to a did_change that replace the entire text self.did_change(DidChangeTextDocumentParams { text_document: VersionedTextDocumentIdentifier::new(document.uri, document.version), content_changes: vec![TextDocumentContentChangeEvent { range: None, range_length: None, text: document.text, }], }); } else { self.srcs.add(document); } // diagnostics let urls = self.srcs.names.read().unwrap().keys().cloned().collect(); let file_id = self.srcs.get_id(&uri); let file = self.srcs.get_file(file_id).unwrap(); let file = file.read().unwrap(); get_diagnostics(uri, &file.text, urls, &self.conf.read().unwrap()) } pub fn did_change(&self, params: DidChangeTextDocumentParams) { debug!("did_change: {}", ¶ms.text_document.uri); let file_id = self.srcs.get_id(¶ms.text_document.uri); let file = self.srcs.get_file(file_id).unwrap(); let mut file = file.write().unwrap(); // loop through changes and apply for change in params.content_changes { if change.range.is_none() { file.text = Rope::from_str(&change.text); } else { file.text.apply_change(&change); } file.last_change_range = change.range; } file.version = params.text_document.version; drop(file); // invalidate syntaxtree and wake parse thread let meta_data = self.srcs.get_meta_data(file_id).unwrap(); let (lock, cvar) = &*meta_data.read().unwrap().valid_parse; let mut valid = lock.lock().unwrap(); *valid = false; cvar.notify_all(); } pub fn did_save(&self, params: DidSaveTextDocumentParams) -> PublishDiagnosticsParams { let urls = self.srcs.names.read().unwrap().keys().cloned().collect(); let file_id = self.srcs.get_id(¶ms.text_document.uri); let file = self.srcs.get_file(file_id).unwrap(); let file = file.read().unwrap(); get_diagnostics( params.text_document.uri, &file.text, urls, &self.conf.read().unwrap(), ) } } /// The Source struct holds all file specific information pub struct Source { pub id: usize, pub uri: Url, pub text: Rope, pub version: i32, pub syntax_tree: Option, // if there is a parse error, we can remove the last change pub last_change_range: Option, } /// file metadata, including whether or not the syntax tree is up to date pub struct SourceMeta { pub id: usize, pub valid_parse: Arc<(Mutex, Condvar)>, } /// find SystemVerilog/Verilog sources recursively from opened files fn find_src_paths(dirs: &[PathBuf]) -> Vec { let mut paths: Vec = Vec::new(); for dir in dirs { let walker = WalkDir::new(dir).into_iter(); for entry in walker.filter_entry(|e| !is_hidden(e)) { let entry = entry.unwrap(); if entry.file_type().is_file() && entry.path().extension().is_some() { let extension = entry.path().extension().unwrap(); if extension == "sv" || extension == "svh" || extension == "v" || extension == "vh" { let entry_path = entry.path().to_path_buf(); if !paths.contains(&entry_path) { paths.push(entry_path); } } } } } paths } /// The Sources struct manages all source files pub struct Sources { // all files pub files: Arc>>>>, // map file urls to id pub names: Arc>>, // file metadata pub meta: Arc>>>>, // all source files are indexed into this tree, which can then // be used for completion, name resolution pub scope_tree: Arc>>, // include directories, passed to parser to resolve `include pub include_dirs: Arc>>, // source directories pub source_dirs: Arc>>, } impl std::default::Default for Sources { fn default() -> Self { Self::new() } } impl Sources { pub fn new() -> Self { Self { files: Arc::new(RwLock::new(Vec::new())), names: Arc::new(RwLock::new(HashMap::new())), meta: Arc::new(RwLock::new(Vec::new())), scope_tree: Arc::new(RwLock::new(None)), include_dirs: Arc::new(RwLock::new(Vec::new())), source_dirs: Arc::new(RwLock::new(Vec::new())), } } pub fn init(&self) { let mut paths: Vec = Vec::new(); for path in &*self.include_dirs.read().unwrap() { paths.push(path.clone()); } for path in &*self.source_dirs.read().unwrap() { paths.push(path.clone()); } // find and add all source/header files recursively from configured include and source directories let src_paths = find_src_paths(&paths); for path in src_paths { if let Ok(url) = Url::from_file_path(&path) { if let Ok(text) = fs::read_to_string(&path) { self.add(TextDocumentItem::new( url, "systemverilog".to_string(), -1, text, )); } } } } /// add a source file, creating a parse thread for that file pub fn add(&self, doc: TextDocumentItem) { // use a condvar to synchronize the parse thread // the valid bool decides whether or not the file // needs to be re-parsed #[allow(clippy::mutex_atomic)] // https://github.com/rust-lang/rust-clippy/issues/1516 let valid_parse = Arc::new((Mutex::new(false), Condvar::new())); let valid_parse2 = valid_parse.clone(); let mut files = self.files.write().unwrap(); let source = Arc::new(RwLock::new(Source { id: files.len(), uri: doc.uri.clone(), text: Rope::from_str(&doc.text), version: doc.version, syntax_tree: None, last_change_range: None, })); let source_handle = source.clone(); let scope_handle = self.scope_tree.clone(); let inc_dirs = self.include_dirs.clone(); // spawn parse thread let _ = thread::spawn(move || { let (lock, cvar) = &*valid_parse2; loop { let now = Instant::now(); let file = source_handle.read().unwrap(); let text = file.text.clone(); let uri = &file.uri.clone(); let range = &file.last_change_range.clone(); drop(file); trace!("{}, parse read: {}", uri, now.elapsed().as_millis()); let syntax_tree = parse(&text, uri, range, &inc_dirs.read().unwrap()); let mut scope_tree = match &syntax_tree { Some(tree) => get_scopes(tree, uri), None => None, }; trace!( "{}, parse read complete: {}", uri, now.elapsed().as_millis() ); let mut file = source_handle.write().unwrap(); trace!("{}, parse write: {}", uri, now.elapsed().as_millis()); file.syntax_tree = syntax_tree; drop(file); debug!("try write global scope"); let mut global_scope = scope_handle.write().unwrap(); match &mut *global_scope { Some(scope) => { if let Some(tree) = &mut scope_tree { scope.defs.retain(|x| &x.url() != uri); scope.scopes.retain(|x| &x.url() != uri); scope.defs.append(&mut tree.defs); scope.scopes.append(&mut tree.scopes); } } None => *global_scope = scope_tree, } // eprintln!("{:#?}", *global_scope); drop(global_scope); trace!("{}, write global scope", uri); trace!( "{}, parse write complete: {}", uri, now.elapsed().as_millis() ); let mut valid = lock.lock().unwrap(); *valid = true; cvar.notify_all(); while *valid { valid = cvar.wait(valid).unwrap(); } } }); files.push(source); let fid = files.len() - 1; self.meta .write() .unwrap() .push(Arc::new(RwLock::new(SourceMeta { id: fid, valid_parse, }))); debug!("added {}", &doc.uri); self.names.write().unwrap().insert(doc.uri, fid); } /// get file by id pub fn get_file(&self, id: usize) -> Option>> { let files = self.files.read().ok()?; for file in files.iter() { let source = file.read().ok()?; if source.id == id { return Some(file.clone()); } } None } /// get metadata by file id pub fn get_meta_data(&self, id: usize) -> Option>> { let meta = self.meta.read().ok()?; for data in meta.iter() { let i = data.read().ok()?; if i.id == id { return Some(data.clone()); } } None } /// wait for a valid parse pub fn wait_parse_ready(&self, id: usize, wait_valid: bool) { let file = self.get_file(id).unwrap(); let file = file.read().unwrap(); if file.syntax_tree.is_none() || wait_valid { drop(file); let meta_data = self.get_meta_data(id).unwrap(); let (lock, cvar) = &*meta_data.read().unwrap().valid_parse; let mut valid = lock.lock().unwrap(); while !*valid { valid = cvar.wait(valid).unwrap(); } } } /// get file id from url pub fn get_id(&self, uri: &Url) -> usize { *self.names.read().unwrap().get(uri).unwrap() } /// compute identifier completions pub fn get_completions( &self, token: &str, byte_idx: usize, url: &Url, ) -> Option { debug!("retrieving identifier completion for token: {}", &token); Some(CompletionList { is_incomplete: false, items: self .scope_tree .read() .ok()? .as_ref()? .get_completion(token, byte_idx, url), }) } /// compute dot completions pub fn get_dot_completions( &self, token: &str, byte_idx: usize, url: &Url, ) -> Option { debug!("retrieving dot completion for token: {}", &token); let tree = self.scope_tree.read().ok()?; Some(CompletionList { is_incomplete: false, items: tree .as_ref()? .get_dot_completion(token, byte_idx, url, tree.as_ref()?), }) } } //TODO: show all unrecoverable parse errors to user /// parse the file using sv-parser, attempt to recover if the parse fails pub fn parse( doc: &Rope, uri: &Url, last_change_range: &Option, inc_paths: &[PathBuf], ) -> Option { let mut parse_iterations = 1; let mut i = 0; let mut includes: Vec = inc_paths.to_vec(); let mut reverted_change = false; let mut text = doc.clone(); while i < parse_iterations { i += 1; match parse_sv_str( &text.to_string(), uri.to_file_path().unwrap(), &HashMap::new(), &includes, false, ) { Ok((syntax_tree, _)) => { debug!("parse complete of {}", uri); trace!("{}", syntax_tree.to_string()); return Some(syntax_tree); } Err(err) => { match err { // syntax error sv_parser::Error::Parse(trace) => match trace { Some((_, bpos)) => { let mut line_start = text.byte_to_line(bpos); let mut line_end = text.byte_to_line(bpos) + 1; if !reverted_change { if let Some(range) = last_change_range { line_start = range.start.line as usize; line_end = range.end.line as usize + 1; reverted_change = true; } } for line_idx in line_start..line_end { let line = text.line(line_idx); let start_char = text.line_to_char(line_idx); let line_length = line.len_chars(); text.remove(start_char..(start_char + line_length - 1)); text.insert(start_char, &" ".to_owned().repeat(line_length)); } parse_iterations += 1; } None => return None, }, // include error, take the include path from the error message and // add it as an include dir for the next parser invocation sv_parser::Error::Include { source: x } => { if let sv_parser::Error::File { source: _, path: z } = *x { // Include paths have to be relative to the working directory // so we have to convert a source file relative path to a working directory // relative path. This should have been handled by sv-parser let mut inc_path_given = z.clone(); let mut uri_path = uri.to_file_path().unwrap(); uri_path.pop(); let rel_path = diff_paths(uri_path, current_dir().unwrap()).unwrap(); inc_path_given.pop(); let inc_path = rel_path.join(inc_path_given); if !includes.contains(&inc_path) { includes.push(inc_path); } else { error!("parser: include error: {:?}", z); break; } parse_iterations += 1; } } _ => error!("parse error, {:?}", err), }; } } } None } //TODO: add bounds checking for utf8<->utf16 conversions /// This trait defines some helper functions to convert between lsp types /// and char/byte positions pub trait LSPSupport { fn pos_to_byte(&self, pos: &Position) -> usize; fn pos_to_char(&self, pos: &Position) -> usize; fn byte_to_pos(&self, byte_idx: usize) -> Position; fn char_to_pos(&self, char_idx: usize) -> Position; fn range_to_char_range(&self, range: &Range) -> StdRange; fn char_range_to_range(&self, range: StdRange) -> Range; fn apply_change(&mut self, change: &TextDocumentContentChangeEvent); } /// Extend ropey's Rope type with lsp convenience functions impl LSPSupport for Rope { fn pos_to_byte(&self, pos: &Position) -> usize { self.char_to_byte(self.pos_to_char(pos)) } fn pos_to_char(&self, pos: &Position) -> usize { let line_slice = self.line(pos.line as usize); self.line_to_char(pos.line as usize) + line_slice.utf16_cu_to_char(pos.character as usize) } fn byte_to_pos(&self, byte_idx: usize) -> Position { self.char_to_pos(self.byte_to_char(min(byte_idx, self.len_bytes() - 1))) } fn char_to_pos(&self, char_idx: usize) -> Position { let line = self.char_to_line(char_idx); let line_slice = self.line(line); Position { line: line as u32, character: line_slice.char_to_utf16_cu(char_idx - self.line_to_char(line)) as u32, } } fn range_to_char_range(&self, range: &Range) -> StdRange { self.pos_to_char(&range.start)..self.pos_to_char(&range.end) } fn char_range_to_range(&self, range: StdRange) -> Range { Range { start: self.char_to_pos(range.start), end: self.char_to_pos(range.end), } } fn apply_change(&mut self, change: &TextDocumentContentChangeEvent) { if let Some(range) = change.range { let char_range = self.range_to_char_range(&range); self.remove(char_range.clone()); if !change.text.is_empty() { self.insert(char_range.start, &change.text); } } } } impl LSPSupport for RopeSlice<'_> { fn pos_to_byte(&self, pos: &Position) -> usize { self.char_to_byte(self.pos_to_char(pos)) } fn pos_to_char(&self, pos: &Position) -> usize { let line_slice = self.line(pos.line as usize); self.line_to_char(pos.line as usize) + line_slice.utf16_cu_to_char(pos.character as usize) } fn byte_to_pos(&self, byte_idx: usize) -> Position { self.char_to_pos(self.byte_to_char(min(byte_idx, self.len_bytes() - 1))) } fn char_to_pos(&self, char_idx: usize) -> Position { let line = self.char_to_line(char_idx); let line_slice = self.line(line); Position { line: line as u32, character: line_slice.char_to_utf16_cu(char_idx - self.line_to_char(line)) as u32, } } fn range_to_char_range(&self, range: &Range) -> StdRange { self.pos_to_char(&range.start)..self.pos_to_char(&range.end) } fn char_range_to_range(&self, range: StdRange) -> Range { Range { start: self.char_to_pos(range.start), end: self.char_to_pos(range.end), } } fn apply_change(&mut self, _: &TextDocumentContentChangeEvent) { panic!("can't edit a rope slice"); } } #[cfg(test)] mod tests { use super::*; use crate::support::test_init; use std::fs::read_to_string; #[test] fn test_open_and_change() { test_init(); let server = LSPServer::new(None); let uri = Url::parse("file:///test.sv").unwrap(); let text = r#"module test; logic abc; endmodule"#; let open_params = DidOpenTextDocumentParams { text_document: TextDocumentItem { uri: uri.clone(), language_id: "systemverilog".to_owned(), version: 0, text: text.to_owned(), }, }; server.did_open(open_params); let fid = server.srcs.get_id(&uri); let file = server.srcs.get_file(fid).unwrap(); let file = file.read().unwrap(); assert_eq!(file.text.to_string(), text.to_owned()); drop(file); let change_params = DidChangeTextDocumentParams { text_document: VersionedTextDocumentIdentifier { uri, version: 1 }, content_changes: vec![TextDocumentContentChangeEvent { range: Some(Range { start: Position { line: 1, character: 8, }, end: Position { line: 1, character: 11, }, }), range_length: None, text: "var1".to_owned(), }], }; server.did_change(change_params); let file = server.srcs.get_file(fid).unwrap(); let file = file.read().unwrap(); assert_eq!( file.text.to_string(), r#"module test; logic var1; endmodule"# .to_owned() ); assert_eq!(file.version, 1); } #[test] fn test_fault_tolerance() { test_init(); let server = LSPServer::new(None); let uri = Url::parse("file:///test.sv").unwrap(); let text = r#"module test; logic abc endmodule"#; let open_params = DidOpenTextDocumentParams { text_document: TextDocumentItem { uri: uri.clone(), language_id: "systemverilog".to_owned(), version: 0, text: text.to_owned(), }, }; server.did_open(open_params); let fid = server.srcs.get_id(&uri); server.srcs.wait_parse_ready(fid, true); assert!(server .srcs .scope_tree .read() .unwrap() .as_ref() .unwrap() .contains_scope("test")); } #[test] fn test_header() { test_init(); let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR")); d.push("test_data/top_inc.sv"); let text = read_to_string(&d).unwrap(); let doc = Rope::from_str(&text); assert!(parse(&doc, &Url::from_file_path(d).unwrap(), &None, &Vec::new()).is_some(),); // TODO: add missing header test } } ================================================ FILE: src/support.rs ================================================ pub fn test_init() { let _ = flexi_logger::Logger::with(flexi_logger::LogSpecification::info()).start(); } ================================================ FILE: test_data/complete.sv ================================================ interface test(logic clk); logic a; logic b; modport in (input clk, a , b); modport out (output clk, a, b); endinterface module test1( test.in tinter, test interb ); endmodule ================================================ FILE: test_data/definition_test.sv ================================================ interface simple_bus (input logic clk); // Define the interface logic req, gnt; logic [7:0] addr, data; logic [1:0] mode; logic start, rdy; modport slave( input req, addr, mode, start, clk, output gnt, rdy, ref data, export Read, Write); modport master(input gnt, rdy, clk, output req, addr, mode, start, ref data, import task Read(input logic [7:0] raddr), task Write(input logic [7:0] waddr)); endinterface: simple_bus module test; logic clk; simple_bus bus (.*); endmodule ================================================ FILE: test_data/diag/diag_test.sv ================================================ module test; logic [1:0] abc; assign abc[2] = 1'b0; endmodule ================================================ FILE: test_data/ind.sv ================================================ module test; logic abc; logic abcd; a endmodule ================================================ FILE: test_data/interface_obj.sv ================================================ interface ebus_i; integer I; // reference to I not allowed through modport mp typedef enum {Y,N} choice; choice Q; localparam True = 1; modport mp(input Q); endinterface module Top; ebus_i ebus (); sub s1 (ebus.mp); endmodule module sub(ebus_i.mp i); typedef i.choice yes_no; yes_no P; assign P = i.Q; initial Top.ebus.Q = i.True; initial Top.ebus.I = 0; endmodule ================================================ FILE: test_data/simple_bus.svh ================================================ interface simple_bus (input logic clk); // Define the interface logic req, gnt; logic [7:0] addr, data; logic [1:0] mode; logic start, rdy; modport slave( input req, addr, mode, start, clk, output gnt, rdy, ref data, export Read, Write); modport master(input gnt, rdy, clk, output req, addr, mode, start, ref data, import task Read(input logic [7:0] raddr), task Write(input logic [7:0] waddr)); endinterface: simple_bus ================================================ FILE: test_data/test_inter.sv ================================================ interface simple_bus; logic clk; endinterface module test2; simple_bus b (); endmodule ================================================ FILE: test_data/top.sv ================================================ module test; simple_bus bus (); endmodule ================================================ FILE: test_data/top_inc.sv ================================================ `timescale 1ns/1ps `include "simple_bus.svh" module test; simple_bus bus (); endmodule ================================================ FILE: test_data/verilator_errors.txt ================================================ %Error: test.sv:6: syntax error, unexpected endmodule, expecting IDENTIFIER endmodule %Error: test.sv:6:1: syntax error, unexpected endmodule, expecting IDENTIFIER or randomize %Error: test.sv:6:1: syntax error, unexpected endmodule, expecting IDENTIFIER or '(' or randomize %Warning-ALWCOMBORDER: test.sv:5:4: Always_comb variable driven after use: 'b' %Error: test2.sv:6:1: syntax error, unexpected endmodule, expecting IDENTIFIER or randomize ================================================ FILE: veridian-slang/Cargo.toml ================================================ [package] name = "veridian_slang" version = "0.1.0" authors = ["Vivek Malneedi "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] [build-dependencies] bindgen = "0.71.1" reqwest = { version = "0.12.4", features = ["blocking"] } flate2 = "1.0.20" tar = "0.4.35" cmake = "0.1.50" [dev-dependencies] tempdir = "0.3.7" serial_test = "3.2.0" ================================================ FILE: veridian-slang/build.rs ================================================ use flate2::read::GzDecoder; use std::env; use std::fs::{self, File}; use std::path::{Path, PathBuf}; use tar::Archive; type Result = std::result::Result>; fn download_slang(download_to: &Path) -> Result { // Keep the version the same as the one in `CMakeLists.txt` let target = "https://github.com/MikePopoloski/slang/archive/refs/tags/v9.1.tar.gz"; fs::create_dir_all(download_to)?; // Download source let archive_path = download_to.join("slang-linux.tar.gz"); let mut dest = File::create(&archive_path)?; reqwest::blocking::get(target)?.copy_to(&mut dest)?; drop(dest); // Unpack archive let mut archive = Archive::new(GzDecoder::new(File::open(archive_path)?)); archive.unpack(download_to)?; // Return the source directory let entries = fs::read_dir(&download_to)? .filter_map(|entry| entry.ok()) .filter(|entry| entry.metadata().is_ok_and(|e| e.is_dir())) .collect::>(); // Expected exactly one directory in archive assert_eq!(entries.len(), 1); Ok(entries.first().unwrap().path()) } fn build_slang(slang_src: &Path, slang_install: &Path) { cmake::Config::new(slang_src) .profile("Release") .define("SLANG_USE_MIMALLOC", "OFF") .out_dir(slang_install) .build(); } fn build_slang_wrapper(slang: &Path, wrapper_install: &Path) { cmake::Config::new("slang_wrapper") .profile("Release") .define("CMAKE_PREFIX_PATH", slang) .out_dir(wrapper_install) .build(); } fn main() -> Result<()> { let out_dir = PathBuf::from(env::var("OUT_DIR")?); println!("cargo:rerun-if-changed=slang_wrapper"); let (slang_install, wrapper_install, link_type) = match env::var("SLANG_INSTALL_PATH") { Err(_) => { // Build slang from source let download_dir = out_dir.join("slang-src"); let slang_src = download_slang(&download_dir)?; let slang_install = out_dir.join("slang-install"); let wrapper_install = out_dir.join("slang-wrapper-install"); build_slang(&slang_src, &slang_install); build_slang_wrapper(&slang_install, &wrapper_install); ( slang_install.join("lib"), wrapper_install.join("lib"), "static", ) } Ok(slang_install) => { // Directly use external slang let slang_install = Path::new(&slang_install); let wrapper_install = out_dir.join("slang-wrapper-install"); build_slang_wrapper(slang_install, &wrapper_install); ( slang_install.join("lib"), wrapper_install.join("lib"), "dylib", ) } }; let bindings = bindgen::Builder::default() .clang_arg("-x") .clang_arg("c++") .header("slang_wrapper/src/slang_wrapper.h") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate() .expect("Unable to generate bindings"); println!("cargo:rustc-link-search=native={}", slang_install.display()); println!( "cargo:rustc-link-search=native={}", wrapper_install.display() ); // println!("cargo:rustc-link-search=native=/usr/lib"); println!("cargo:rustc-link-lib=static=slangwrapper"); println!("cargo:rustc-link-lib={link_type}=svlang"); println!("cargo:rustc-link-lib=fmt"); println!("cargo:rustc-link-lib=dylib=stdc++"); bindings .write_to_file(out_dir.join("bindings.rs")) .expect("Couldn't write bindings!"); Ok(()) } ================================================ FILE: veridian-slang/slang_wrapper/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.20) project( slang_wrapper LANGUAGES CXX ) # Keep the version the same as the one in `build.rs` find_package(slang 9.1 REQUIRED) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library( slang_wrapper STATIC src/slang_lib.cpp src/basic_client.cpp ) target_link_libraries(slang_wrapper PRIVATE slang::slang) set_target_properties(slang_wrapper PROPERTIES OUTPUT_NAME "slangwrapper") install( TARGETS slang_wrapper LIBRARY DESTINATION lib RUNTIME DESTINATION bin ) ================================================ FILE: veridian-slang/slang_wrapper/src/BasicClient.h ================================================ //------------------------------------------------------------------------------ //! @file BasicClient.h //! @brief Diagnostic client that formats to a text string // // File is under the MIT license; see LICENSE for details //------------------------------------------------------------------------------ #pragma once #include #include #include "slang/diagnostics/DiagnosticClient.h" namespace slang { class FormatBuffer; namespace ast { class Symbol; } class BasicClient : public DiagnosticClient { public: BasicClient(); ~BasicClient(); void setColorsEnabled(bool enabled); template void setSymbolPathCB(TFunc&& func) { symbolPathCB = std::forward(func); } template static void setDefaultSymbolPathCB(TFunc&& func) { defaultSymbolPathCB = std::forward(func); } void report(const ReportedDiagnostic& diagnostic) override; void clear(); std::string getString() const; private: std::unique_ptr buffer; using SymbolPathCB = std::function; SymbolPathCB symbolPathCB; static SymbolPathCB defaultSymbolPathCB; void formatDiag(SourceLocation loc, DiagnosticSeverity severity, std::string_view message, std::string_view optionName); }; } // namespace slang ================================================ FILE: veridian-slang/slang_wrapper/src/FormatBuffer.h ================================================ //------------------------------------------------------------------------------ // FormatBuffer.h // Internal string formatting helper class // // File is under the MIT license; see LICENSE for details //------------------------------------------------------------------------------ #pragma once #include #include #include namespace slang { class FormatBuffer { public: void append(std::string_view str) { buf.append(str.data(), str.data() + str.size()); } void append(const fmt::text_style& style, std::string_view str) { format(style, "{}", str); } template void format(fmt::format_string format, Args &&...args) { fmt::format_to(fmt::appender(buf), format, std::forward(args)...); } template void format(const fmt::text_style &style, fmt::format_string format, Args &&...args) { if (!showColors) { fmt::format_to(fmt::appender(buf), format, std::forward(args)...); } else { // TODO: Text style + non-literal string does not compile in fmt 10.2.1, but has been fixed in some commit in the master branch. // Let's temporarily abandon style to work around it. // fmt::format_to(fmt::appender(buf), style, format, std::forward(args)...); fmt::format_to(fmt::appender(buf), format, std::forward(args)...); } } size_t size() const { return buf.size(); } const char* data() const { return buf.data(); } char back() const { return buf.data()[buf.size() - 1]; } void pop_back() { buf.resize(buf.size() - 1); } void clear() { buf.clear(); } void resize(size_t newSize) { buf.resize(newSize); } void setColorsEnabled(bool enabled) { showColors = enabled; } std::string str() const { return fmt::to_string(buf); } private: fmt::memory_buffer buf; bool showColors = false; }; } // namespace slang ================================================ FILE: veridian-slang/slang_wrapper/src/basic_client.cpp ================================================ //------------------------------------------------------------------------------ // BasicClient.cpp // Diagnostic client that formats to a text string // // File is under the MIT license; see LICENSE for details //------------------------------------------------------------------------------ #include "BasicClient.h" #include "FormatBuffer.h" #include "slang/text/SourceManager.h" namespace slang { static constexpr auto noteColor = fmt::terminal_color::bright_black; static constexpr auto warningColor = fmt::terminal_color::bright_yellow; static constexpr auto errorColor = fmt::terminal_color::bright_red; static constexpr auto fatalColor = fmt::terminal_color::bright_red; static constexpr auto highlightColor = fmt::terminal_color::bright_green; static constexpr auto filenameColor = fmt::terminal_color::cyan; static constexpr auto locationColor = fmt::terminal_color::bright_cyan; static fmt::terminal_color getSeverityColor(DiagnosticSeverity severity) { switch (severity) { case DiagnosticSeverity::Note: return noteColor; case DiagnosticSeverity::Warning: return warningColor; case DiagnosticSeverity::Error: return errorColor; case DiagnosticSeverity::Fatal: return fatalColor; default: return fmt::terminal_color::black; } } BasicClient::SymbolPathCB BasicClient::defaultSymbolPathCB; BasicClient::BasicClient() : buffer(std::make_unique()), symbolPathCB(defaultSymbolPathCB) {} BasicClient::~BasicClient() = default; void BasicClient::setColorsEnabled(bool enabled) { buffer->setColorsEnabled(enabled); } void BasicClient::report(const ReportedDiagnostic& diag) { if (diag.shouldShowIncludeStack) { SmallVector includeStack; getIncludeStack(diag.location.buffer(), includeStack); // Show the stack in reverse. for (int i = int(includeStack.size()) - 1; i >= 0; i--) { SourceLocation loc = includeStack[size_t(i)]; buffer->format("in file included from {}:{}:\n", sourceManager->getFileName(loc), sourceManager->getLineNumber(loc)); } } // Print out the hierarchy where the diagnostic occurred, if we know it. auto& od = diag.originalDiagnostic; if (od.coalesceCount && od.symbol && symbolPathCB) { if (od.coalesceCount == 1) buffer->append(" in instance: "sv); else buffer->format(" in {} instances, e.g. ", *od.coalesceCount); buffer->append(fmt::emphasis::bold, symbolPathCB(*od.symbol)); buffer->append("\n"sv); } // Get all highlight ranges mapped into the reported location of the // diagnostic. SmallVector mappedRanges; engine->mapSourceRanges(diag.location, diag.ranges, mappedRanges); // Write the diagnostic. formatDiag(diag.location, diag.severity, diag.formattedMessage, engine->getOptionName(diag.originalDiagnostic.code)); // Write out macro expansions, if we have any, in reverse order. for (auto it = diag.expansionLocs.rbegin(); it != diag.expansionLocs.rend(); it++) { SourceLocation loc = *it; std::string name(sourceManager->getMacroName(loc)); if (name.empty()) name = "expanded from here"; else name = fmt::format("expanded from macro '{}'", name); SmallVector macroRanges; engine->mapSourceRanges(loc, diag.ranges, macroRanges); formatDiag(sourceManager->getFullyOriginalLoc(loc), DiagnosticSeverity::Note, name, ""); } } void BasicClient::clear() { buffer->clear(); } std::string BasicClient::getString() const { return buffer->str(); } void BasicClient::formatDiag(SourceLocation loc, DiagnosticSeverity severity, std::string_view message, std::string_view optionName) { size_t col = 0; if (loc != SourceLocation::NoLocation) { col = sourceManager->getColumnNumber(loc); buffer->append(fg(filenameColor), sourceManager->getFileName(loc)); buffer->append(":"); buffer->format(fg(locationColor), "{}:{}", sourceManager->getLineNumber(loc), col); buffer->append(": "); } buffer->format(fg(getSeverityColor(severity)), "{}: ", getSeverityString(severity)); if (severity != DiagnosticSeverity::Note) buffer->format(fmt::text_style(fmt::emphasis::bold), "{}", message); else buffer->append(message); if (!optionName.empty()) buffer->format(" [-W{}]", optionName); buffer->append("\n"sv); } } // namespace slang ================================================ FILE: veridian-slang/slang_wrapper/src/slang_lib.cpp ================================================ #include "BasicClient.h" #include "slang_wrapper.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; using namespace slang; using slang::syntax::SyntaxTree; // Private function static char* report(const std::string &s) { return strcpy(new char[s.length() + 1], s.c_str()); } char* compile_source(const char* name, const char* text) { Bag options; SourceManager sm; SourceBuffer buffer = sm.assignText(name, text); ast::Compilation compilation(options); std::array buffers{std::move(buffer)}; compilation.addSyntaxTree(SyntaxTree::fromBuffers(buffers, sm, options)); DiagnosticEngine diagEngine(sm); Diagnostics pragmaDiags = diagEngine.setMappingsFromPragmas(); auto client = std::make_shared(); client->setColorsEnabled(false); diagEngine.addClient(client); for (auto& diag : compilation.getAllDiagnostics()) diagEngine.issue(diag); return report(client->getString()); } char* compile_path(const char* path) { Bag options; SourceManager sm; auto buffer = sm.readSource(fs::path{path}, /* library */ nullptr); if (!buffer) { return report(fmt::format("'{}': {}", path, buffer.error().message())); } ast::Compilation compilation(options); std::array buffers{std::move(*buffer)}; compilation.addSyntaxTree(SyntaxTree::fromBuffers(buffers, sm, options)); DiagnosticEngine diagEngine(sm); Diagnostics pragmaDiags = diagEngine.setMappingsFromPragmas(); auto client = std::make_shared(); client->setColorsEnabled(false); diagEngine.addClient(client); for (auto& diag : compilation.getAllDiagnostics()) diagEngine.issue(diag); return report(client->getString()); } char* compile_sources(const char** names, const char** texts, unsigned int num_files) { Bag options; SourceManager sm; ast::Compilation compilation(options); std::vector buffers; buffers.reserve(num_files); for (unsigned int i = 0; i < num_files; i++) { buffers.emplace_back(sm.assignText(names[i], texts[i])); } compilation.addSyntaxTree(SyntaxTree::fromBuffers(buffers, sm, options)); DiagnosticEngine diagEngine(sm); Diagnostics pragmaDiags = diagEngine.setMappingsFromPragmas(); auto client = std::make_shared(); client->setColorsEnabled(false); diagEngine.addClient(client); for (auto& diag : compilation.getAllDiagnostics()) diagEngine.issue(diag); return report(client->getString()); } char* compile_paths(const char** paths, unsigned int num_paths) { Bag options; SourceManager sm; ast::Compilation compilation(options); std::vector buffers; buffers.reserve(num_paths); for (unsigned int i = 0; i < num_paths; i++) { auto buffer = sm.readSource(fs::path{paths[i]}, /* library */ nullptr); if (!buffer) { return report(fmt::format("'{}': {}", paths[i], buffer.error().message())); } buffers.emplace_back(std::move(*buffer)); } compilation.addSyntaxTree(SyntaxTree::fromBuffers(buffers, sm, options)); DiagnosticEngine diagEngine(sm); Diagnostics pragmaDiags = diagEngine.setMappingsFromPragmas(); auto client = std::make_shared(); client->setColorsEnabled(false); diagEngine.addClient(client); for (auto& diag : compilation.getAllDiagnostics()) diagEngine.issue(diag); return report(client->getString()); } void delete_report(char* report) { delete report; } ================================================ FILE: veridian-slang/slang_wrapper/src/slang_wrapper.h ================================================ #pragma once extern "C" { void delete_report(char* report); char* compile_source(const char* name, const char* text); char* compile_path(const char* path); char* compile_sources(const char** names, const char** texts, unsigned int num_files); char* compile_paths(const char** paths, unsigned int num_paths); } ================================================ FILE: veridian-slang/src/lib.rs ================================================ #![allow(dead_code)] use std::error; use std::ffi::{CStr, CString}; use std::path::PathBuf; mod wrapper; use wrapper::*; pub fn slang_compile(paths: Vec) -> Result> { if paths.is_empty() { return Ok(String::new()); } // convert pathbufs to strings let mut paths_str: Vec = Vec::new(); for path in paths { paths_str.push(path.to_str().unwrap().to_owned()); } // convert strings to cstrings let mut paths_c: Vec = Vec::new(); for path in paths_str { paths_c.push(CString::new(path)?); } // convert cstrings to char* pointers let mut paths_ptr: Vec<*const std::ffi::c_char> = paths_c.iter().map(|x| x.as_ptr()).collect(); // compile with slang, and convert report from char* to string let report_raw = unsafe { compile_paths(paths_ptr.as_mut_ptr(), paths_ptr.len() as u32) }; let report: &CStr = unsafe { CStr::from_ptr(report_raw) }; let result = report.to_str()?.to_owned(); unsafe { delete_report(report_raw); } Ok(result) } #[cfg(test)] mod tests { use super::*; use serial_test::serial; use std::fs::File; use std::io::Write; use tempdir::TempDir; #[test] #[serial] fn test_paths_wrapper() { let dir = TempDir::new("slang_wrapper_tests").unwrap(); let file_path_1 = dir.path().join("test1.sv"); let mut f = File::create(&file_path_1).unwrap(); f.write_all(b"module test1; logic [1:0] abc; assign abc[2] = 1'b1; endmodule") .unwrap(); f.sync_all().unwrap(); let file_path_2 = dir.path().join("test2.sv"); let mut f = File::create(&file_path_2).unwrap(); f.write_all(b"module test2; logic [1:0] abc; assign abc[2] = 1'b1; endmodule") .unwrap(); f.sync_all().unwrap(); let file_path_3 = dir.path().join("test3.sv"); let mut f = File::create(&file_path_3).unwrap(); f.write_all(b"module test3; logic [1:0] abc; assign abc[2] = 1'b1; endmodule") .unwrap(); f.sync_all().unwrap(); let mut paths: Vec = Vec::new(); paths.push(file_path_1); paths.push(file_path_2); paths.push(file_path_3); let report: String = slang_compile(paths).unwrap(); let mut expected = ":1:43: warning: cannot refer to element 2 of \'logic[1:0]\' [-Windex-oob]\n".repeat(3); expected.pop(); let result_iter = report.lines(); let mut result: String = String::new(); result_iter.for_each(|x| { let mut y: String = x.to_owned(); y.replace_range(..x.find(':').unwrap(), "\n"); result.push_str(&y); }); // get rid of unnecessary newlines result = result.trim_start().to_owned(); assert_eq!(result, expected); } } ================================================ FILE: veridian-slang/src/wrapper.rs ================================================ include!(concat!(env!("OUT_DIR"), "/bindings.rs")); #[cfg(test)] mod tests { use super::*; use serial_test::serial; use std::ffi::{CStr, CString}; use std::fs::File; use std::io::Write; use tempdir::TempDir; #[test] #[serial] fn test_path() { let dir = TempDir::new("slang_wrapper_tests").unwrap(); let file_path = dir.path().join("test.sv"); let mut f = File::create(&file_path).unwrap(); f.write_all(b"module test; logic [1:0] abc; assign abc[2] = 1'b1; endmodule") .unwrap(); f.sync_all().unwrap(); let path = CString::new(file_path.to_str().unwrap()).unwrap(); let report_raw = unsafe { compile_path(path.as_ptr()) }; let report: &CStr = unsafe { CStr::from_ptr(report_raw) }; let expected = ":1:42: warning: cannot refer to element 2 of \'logic[1:0]\' [-Windex-oob]\n"; let mut result = report.to_str().unwrap().to_owned(); let offset = result.find(':').unwrap_or_else(|| result.len()); result.replace_range(..offset, ""); assert_eq!(result, expected); unsafe { delete_report(report_raw); } } #[test] #[serial] fn test_paths() { let dir = TempDir::new("slang_wrapper_tests").unwrap(); let file_path_1 = dir.path().join("test1.sv"); let file_path_1_c = CString::new(file_path_1.to_str().unwrap()).unwrap(); let mut f = File::create(&file_path_1).unwrap(); f.write_all(b"module test1; logic [1:0] abc; assign abc[2] = 1'b1; endmodule") .unwrap(); f.sync_all().unwrap(); let file_path_2 = dir.path().join("test2.sv"); let file_path_2_c = CString::new(file_path_2.to_str().unwrap()).unwrap(); let mut f = File::create(&file_path_2).unwrap(); f.write_all(b"module test2; logic [1:0] abc; assign abc[2] = 1'b1; endmodule") .unwrap(); f.sync_all().unwrap(); let file_path_3 = dir.path().join("test3.sv"); let file_path_3_c = CString::new(file_path_3.to_str().unwrap()).unwrap(); let mut f = File::create(&file_path_3).unwrap(); f.write_all(b"module test3; logic [1:0] abc; assign abc[2] = 1'b1; endmodule") .unwrap(); f.sync_all().unwrap(); let mut paths: Vec<*const i8> = Vec::new(); paths.push(file_path_1_c.as_ptr()); paths.push(file_path_2_c.as_ptr()); paths.push(file_path_3_c.as_ptr()); let report_raw = unsafe { compile_paths(paths.as_mut_ptr(), 3) }; let report: &CStr = unsafe { CStr::from_ptr(report_raw) }; let mut expected = ":1:43: warning: cannot refer to element 2 of \'logic[1:0]\' [-Windex-oob]\n".repeat(3); expected.pop(); let result_raw = report.to_str().unwrap().to_owned(); let result_iter = result_raw.lines(); let mut result: String = String::new(); result_iter.for_each(|x| { let mut y: String = x.to_owned(); y.replace_range(..x.find(":").unwrap(), "\n"); result.push_str(&y); }); // get rid of unnecessary newlines result = result.trim_start().to_owned(); assert_eq!(result, expected); unsafe { delete_report(report_raw); } } #[test] #[serial] fn test_compilation_multi() { let mut names: Vec<*const i8> = Vec::new(); let mut texts: Vec<*const i8> = Vec::new(); let name1 = CString::new("test1.sv").unwrap(); let name2 = CString::new("test2.sv").unwrap(); let name3 = CString::new("test3.sv").unwrap(); names.push(name1.as_ptr()); names.push(name2.as_ptr()); names.push(name3.as_ptr()); let text1 = CString::new("module test1; logic [1:0] abc; assign abc[2] = 1'b1; endmodule").unwrap(); let text2 = CString::new("module test2; logic [1:0] abc; assign abc[2] = 1'b1; endmodule").unwrap(); let text3 = CString::new("module test3; logic [1:0] abc; assign abc[2] = 1'b1; endmodule").unwrap(); texts.push(text1.as_ptr()); texts.push(text2.as_ptr()); texts.push(text3.as_ptr()); let report_raw = unsafe { compile_sources(names.as_mut_ptr(), texts.as_mut_ptr(), 3) }; let report: &CStr = unsafe { CStr::from_ptr(report_raw) }; let expected = "test1.sv:1:43: warning: cannot refer to element 2 of \'logic[1:0]\' [-Windex-oob]\ntest2.sv:1:43: warning: cannot refer to element 2 of \'logic[1:0]\' [-Windex-oob]\ntest3.sv:1:43: warning: cannot refer to element 2 of \'logic[1:0]\' [-Windex-oob]\n"; let result = report.to_str().unwrap(); assert_eq!(result, expected); unsafe { delete_report(report_raw); } } }