├── .dockerignore ├── .github └── workflows │ ├── build-test-all.yml │ ├── crates-io.yml │ └── docker.yml ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE.txt ├── README.md ├── clippy.toml ├── example_project ├── README.md ├── from_vunit_export.py └── vhdl_ls.toml ├── logo.svg ├── vhdl_lang ├── Cargo.toml ├── benches │ └── benchmark.rs ├── src │ ├── analysis.rs │ ├── analysis │ │ ├── analyze.rs │ │ ├── assignment.rs │ │ ├── association.rs │ │ ├── concurrent.rs │ │ ├── declarative.rs │ │ ├── design_unit.rs │ │ ├── expression.rs │ │ ├── literals.rs │ │ ├── lock.rs │ │ ├── names.rs │ │ ├── overloaded.rs │ │ ├── package_instance.rs │ │ ├── range.rs │ │ ├── root.rs │ │ ├── scope.rs │ │ ├── semantic.rs │ │ ├── sequential.rs │ │ ├── standard.rs │ │ ├── static_expression.rs │ │ ├── subprogram.rs │ │ ├── target.rs │ │ ├── tests │ │ │ ├── assignment_typecheck.rs │ │ │ ├── association_formal.rs │ │ │ ├── circular_dependencies.rs │ │ │ ├── context_clause.rs │ │ │ ├── custom_attributes.rs │ │ │ ├── declarations.rs │ │ │ ├── deferred_constant.rs │ │ │ ├── discrete_ranges.rs │ │ │ ├── external_names.rs │ │ │ ├── hierarchy.rs │ │ │ ├── homographs.rs │ │ │ ├── implicit.rs │ │ │ ├── incomplete_type.rs │ │ │ ├── incremental_analysis.rs │ │ │ ├── mod.rs │ │ │ ├── package_instance.rs │ │ │ ├── protected_type.rs │ │ │ ├── resolves_design_units.rs │ │ │ ├── resolves_names.rs │ │ │ ├── resolves_type_mark.rs │ │ │ ├── sensitivity_list.rs │ │ │ ├── subprogram_arguments.rs │ │ │ ├── subprogram_instance.rs │ │ │ ├── tool_directive.rs │ │ │ ├── typecheck_expression.rs │ │ │ ├── util.rs │ │ │ ├── view_declarations.rs │ │ │ └── visibility.rs │ │ └── types.rs │ ├── ast.rs │ ├── ast │ │ ├── any_design_unit.rs │ │ ├── ast_span.rs │ │ ├── display.rs │ │ ├── search.rs │ │ ├── token_range.rs │ │ └── util.rs │ ├── completion.rs │ ├── completion │ │ ├── attributes.rs │ │ ├── entity_instantiation.rs │ │ ├── generic.rs │ │ ├── libraries.rs │ │ ├── map_aspect.rs │ │ ├── region.rs │ │ ├── selected.rs │ │ └── tokenizer.rs │ ├── config.rs │ ├── data.rs │ ├── data │ │ ├── contents.rs │ │ ├── diagnostic.rs │ │ ├── error_codes.rs │ │ ├── latin_1.rs │ │ ├── message.rs │ │ ├── source.rs │ │ └── symbol_table.rs │ ├── formatting │ │ ├── architecture.rs │ │ ├── buffer.rs │ │ ├── concurrent_statement.rs │ │ ├── configuration.rs │ │ ├── constraint.rs │ │ ├── context.rs │ │ ├── declaration.rs │ │ ├── design.rs │ │ ├── entity.rs │ │ ├── expression.rs │ │ ├── interface.rs │ │ ├── mod.rs │ │ ├── name.rs │ │ ├── sequential_statement.rs │ │ ├── statement.rs │ │ ├── subprogram.rs │ │ └── token.rs │ ├── lib.rs │ ├── lint.rs │ ├── lint │ │ ├── dead_code.rs │ │ └── sensitivity_list.rs │ ├── main.rs │ ├── named_entity.rs │ ├── named_entity │ │ ├── arena.rs │ │ ├── attribute.rs │ │ ├── design.rs │ │ ├── file.rs │ │ ├── formal_region.rs │ │ ├── object.rs │ │ ├── overloaded.rs │ │ ├── region.rs │ │ ├── types.rs │ │ └── visibility.rs │ ├── project.rs │ ├── standard.rs │ ├── syntax.rs │ └── syntax │ │ ├── alias_declaration.rs │ │ ├── attributes.rs │ │ ├── common.rs │ │ ├── component_declaration.rs │ │ ├── concurrent_statement.rs │ │ ├── configuration.rs │ │ ├── context.rs │ │ ├── declarative_part.rs │ │ ├── design_unit.rs │ │ ├── expression.rs │ │ ├── interface_declaration.rs │ │ ├── names.rs │ │ ├── object_declaration.rs │ │ ├── parser.rs │ │ ├── range.rs │ │ ├── recover.rs │ │ ├── separated_list.rs │ │ ├── sequential_statement.rs │ │ ├── subprogram.rs │ │ ├── subtype_indication.rs │ │ ├── test.rs │ │ ├── tokens.rs │ │ ├── tokens │ │ ├── keywords.rs │ │ ├── tokenizer.rs │ │ └── tokenstream.rs │ │ ├── type_declaration.rs │ │ ├── view.rs │ │ └── waveform.rs └── tests │ ├── format_example_project.rs │ ├── integration_tests.rs │ └── unused_declarations │ ├── my_entity.vhd │ └── vhdl_ls.toml ├── vhdl_lang_macros ├── Cargo.toml └── src │ ├── lib.rs │ ├── token_span_attribute.rs │ └── token_span_derive.rs ├── vhdl_libraries ├── ieee2008 │ ├── README.ieee │ ├── fixed_float_types.vhdl │ ├── fixed_generic_pkg-body.vhdl │ ├── fixed_generic_pkg.vhdl │ ├── fixed_pkg.vhdl │ ├── float_generic_pkg-body.vhdl │ ├── float_generic_pkg.vhdl │ ├── float_pkg.vhdl │ ├── ieee_bit_context.vhdl │ ├── ieee_std_context.vhdl │ ├── math_complex-body.vhdl │ ├── math_complex.vhdl │ ├── math_real-body.vhdl │ ├── math_real.vhdl │ ├── numeric_bit-body.vhdl │ ├── numeric_bit.vhdl │ ├── numeric_bit_unsigned-body.vhdl │ ├── numeric_bit_unsigned.vhdl │ ├── numeric_std-body.vhdl │ ├── numeric_std.vhdl │ ├── numeric_std_unsigned-body.vhdl │ ├── numeric_std_unsigned.vhdl │ ├── std_logic_1164-body.vhdl │ ├── std_logic_1164.vhdl │ └── std_logic_textio.vhdl ├── std │ ├── env.vhd │ ├── standard.vhd │ └── textio.vhd ├── synopsys │ ├── std_logic_arith.vhdl │ ├── std_logic_misc.vhdl │ ├── std_logic_signed.vhdl │ └── std_logic_unsigned.vhdl ├── vhdl_ls.toml └── vital2000 │ ├── memory_b.vhdl │ ├── memory_p.vhdl │ ├── prmtvs_b.vhdl │ ├── prmtvs_p.vhdl │ ├── timing_b.vhdl │ └── timing_p.vhdl └── vhdl_ls ├── Cargo.toml └── src ├── lib.rs ├── main.rs ├── rpc_channel.rs ├── stdio_server.rs ├── vhdl_server.rs └── vhdl_server ├── completion.rs ├── diagnostics.rs ├── lifecycle.rs ├── rename.rs ├── text_document.rs └── workspace.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | Dockerfile 4 | example_project 5 | target 6 | -------------------------------------------------------------------------------- /.github/workflows/build-test-all.yml: -------------------------------------------------------------------------------- 1 | name: Build & test all configs 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | release-notice: 7 | name: This is a release build 8 | if: startsWith(github.ref, 'refs/tags/v') 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Show version 13 | run: echo "This is a release build for v${GITHUB_REF/refs\/tags\/v/}" 14 | 15 | build: 16 | strategy: 17 | matrix: 18 | crate: 19 | - vhdl_lang 20 | - vhdl_ls 21 | target: 22 | - x86_64-unknown-linux-gnu 23 | - x86_64-unknown-linux-musl 24 | - x86_64-pc-windows-msvc 25 | - aarch64-apple-darwin 26 | rust: 27 | - stable 28 | 29 | include: 30 | - target: x86_64-unknown-linux-gnu 31 | os: ubuntu-latest 32 | ext: "" 33 | - target: x86_64-unknown-linux-musl 34 | os: ubuntu-latest 35 | ext: "" 36 | - target: x86_64-pc-windows-msvc 37 | os: windows-latest 38 | ext: .exe 39 | - target: aarch64-apple-darwin 40 | os: macos-latest 41 | ext: "" 42 | 43 | runs-on: ${{ matrix.os }} 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | 48 | - name: Setup Rust toolchain 49 | uses: dtolnay/rust-toolchain@stable 50 | with: 51 | toolchain: ${{ matrix.rust }} 52 | target: x86_64-unknown-linux-musl 53 | components: rustfmt, clippy 54 | 55 | - name: Add Apple Silicon Dependencies 56 | run: rustup target add aarch64-apple-darwin 57 | 58 | - uses: awalsh128/cache-apt-pkgs-action@v1 59 | if: matrix.os == 'ubuntu-latest' 60 | with: 61 | packages: musl-tools # provides musl-gcc 62 | version: 1.0 63 | 64 | - name: Build 65 | run: cargo build --manifest-path ${{ matrix.crate }}/Cargo.toml --release --target ${{ matrix.target }} 66 | 67 | - name: Test 68 | if: matrix.os != 'macos-latest' # There are no free runners for Apple Silicon available at the moment 69 | run: cargo test --manifest-path ${{ matrix.crate }}/Cargo.toml --release --target ${{ matrix.target }} 70 | 71 | - name: rustfmt 72 | if: matrix.os == 'ubuntu-latest' && matrix.rust == 'stable' 73 | run: cargo fmt --package ${{ matrix.crate }} -- --check 74 | 75 | - name: clippy 76 | if: matrix.os == 'ubuntu-latest' 77 | run: cargo clippy --package ${{ matrix.crate }} --all-targets --all-features -- -D warnings 78 | 79 | - name: Assemble 80 | if: matrix.rust == 'stable' 81 | run: | 82 | mkdir ${{ matrix.crate }}-${{ matrix.target }} 83 | mkdir ${{ matrix.crate }}-${{ matrix.target }}/bin 84 | cp -R vhdl_libraries ${{ matrix.crate }}-${{ matrix.target }} 85 | cp target/${{ matrix.target }}/release/${{ matrix.crate }}${{ matrix.ext }} ${{ matrix.crate }}-${{ matrix.target }}/bin 86 | 87 | - name: Upload 88 | if: matrix.rust == 'stable' 89 | uses: actions/upload-artifact@v4 90 | with: 91 | name: ${{ matrix.crate }}-${{ matrix.target }} 92 | path: ${{ matrix.crate }}-${{ matrix.target }} 93 | 94 | release: 95 | name: Release 96 | if: startsWith(github.ref, 'refs/tags/v') 97 | runs-on: ubuntu-latest 98 | needs: build 99 | 100 | steps: 101 | - name: Get version 102 | id: v 103 | run: | 104 | echo "v=${GITHUB_REF/refs\/tags\/v/}" >> $GITHUB_ENV 105 | echo "Version is v${GITHUB_REF/refs\/tags\/v/}" 106 | - uses: actions/checkout@v4 107 | - name: Download Artifacts 108 | uses: actions/download-artifact@v4 109 | with: 110 | pattern: vhdl_* 111 | path: ~/temp 112 | - name: Check vhdl_lang version 113 | run: | 114 | chmod u+x ~/temp/vhdl_lang-x86_64-unknown-linux-musl/bin/vhdl_lang 115 | version_string=$(~/temp/vhdl_lang-x86_64-unknown-linux-musl/bin/vhdl_lang --version) 116 | if [ "$version_string" != "vhdl_lang $v" ] 117 | then 118 | echo "Version string mismatch (\"$version_string\" != \"vhdl_lang $v\"" 119 | exit 1 120 | else 121 | echo "Version string matched" 122 | fi 123 | - name: Check vhdl_ls version 124 | run: | 125 | chmod u+x ~/temp/vhdl_ls-x86_64-unknown-linux-musl/bin/vhdl_ls 126 | version_string=$(~/temp/vhdl_ls-x86_64-unknown-linux-musl/bin/vhdl_ls --version) 127 | if [ "$version_string" != "vhdl_ls $v" ] 128 | then 129 | echo "Version string mismatch (\"$version_string\" != \"vhdl_lang $v\"" 130 | exit 1 131 | else 132 | echo "Version string matched" 133 | fi 134 | - name: Zip artifacts 135 | run: | 136 | cd ~/temp 137 | chmod +x */bin/vhdl* 138 | zip -r vhdl_lang-x86_64-unknown-linux-gnu.zip vhdl_lang-x86_64-unknown-linux-gnu 139 | zip -r vhdl_ls-x86_64-unknown-linux-gnu.zip vhdl_ls-x86_64-unknown-linux-gnu 140 | zip -r vhdl_lang-x86_64-unknown-linux-musl.zip vhdl_lang-x86_64-unknown-linux-musl 141 | zip -r vhdl_ls-x86_64-unknown-linux-musl.zip vhdl_ls-x86_64-unknown-linux-musl 142 | zip -r vhdl_lang-x86_64-pc-windows-msvc.zip vhdl_lang-x86_64-pc-windows-msvc 143 | zip -r vhdl_ls-x86_64-pc-windows-msvc.zip vhdl_ls-x86_64-pc-windows-msvc 144 | zip -r vhdl_ls-aarch64-apple-darwin.zip vhdl_ls-aarch64-apple-darwin 145 | zip -r vhdl_lang-aarch64-apple-darwin.zip vhdl_lang-aarch64-apple-darwin 146 | - name: Do release 147 | uses: ncipollo/release-action@v1 148 | with: 149 | draft: false 150 | artifacts: "~/temp/*.zip" 151 | token: ${{ secrets.GITHUB_TOKEN }} 152 | -------------------------------------------------------------------------------- /.github/workflows/crates-io.yml: -------------------------------------------------------------------------------- 1 | name: Crates.io 2 | 3 | on: 4 | push: 5 | tags: 6 | - '**' 7 | 8 | jobs: 9 | publish: 10 | name: Publish 11 | if: github.repository == 'VHDL-LS/rust_hdl' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | override: true 19 | - uses: katyo/publish-crates@v2 20 | with: 21 | registry-token: ${{ secrets.CRATES_IO_TOKEN }} 22 | ignore-unpublished-changes: true -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - '**' 9 | 10 | jobs: 11 | publish: 12 | name: Publish 13 | if: github.repository == 'VHDL-LS/rust_hdl' 14 | strategy: 15 | matrix: 16 | crate: 17 | - vhdl_lang 18 | - vhdl_ls 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: jerray/publish-docker-action@v1.0.3 23 | env: 24 | CRATE: ${{ matrix.crate }} 25 | with: 26 | username: ${{ secrets.DOCKER_USERNAME }} 27 | password: ${{ secrets.DOCKER_PASSWORD }} 28 | repository: kraigher/${{ matrix.crate }} 29 | auto_tag: true 30 | build_args: CRATE 31 | 32 | # A job is required when the publish job is skipped 33 | skip-publish: 34 | name: Skip publish 35 | if: github.repository != 'VHDL-LS/rust_hdl' 36 | runs-on: ubuntu-latest 37 | steps: 38 | - name: Report 39 | run: echo Skipped action from repository ${{ github.repository }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .vscode/ 3 | **/*.rs.bk 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "example_project/UVVM"] 2 | path = example_project/UVVM 3 | url = https://github.com/UVVM/UVVM.git 4 | [submodule "example_project/vunit"] 5 | path = example_project/vunit 6 | url = https://github.com/VUnit/vunit.git 7 | [submodule "example_project/PoC"] 8 | path = example_project/PoC 9 | url = https://github.com/VLSI-EDA/PoC.git 10 | [submodule "example_project/OSVVM"] 11 | path = example_project/OSVVM 12 | url = https://github.com/OSVVM/OSVVM.git 13 | [submodule "example_project/neorv32"] 14 | path = example_project/neorv32 15 | url = https://github.com/stnolting/neorv32.git 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | # You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | [workspace] 8 | resolver = "2" 9 | members = ["vhdl_lang_macros", "vhdl_lang", "vhdl_ls"] 10 | 11 | [workspace.package] 12 | version = "0.85.0" 13 | authors = ["Olof Kraigher "] 14 | license = "MPL-2.0" 15 | edition = "2021" 16 | rust-version = "1.82" 17 | repository = "https://github.com/VHDL-LS/rust_hdl" 18 | readme = "README.md" 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG RUST_VERSION=stable 2 | FROM clux/muslrust:$RUST_VERSION as builder 3 | WORKDIR /volume 4 | COPY . /volume/ 5 | ARG CRATE 6 | RUN cargo build --manifest-path $CRATE/Cargo.toml --release 7 | 8 | FROM scratch 9 | ARG CRATE 10 | COPY --from=builder /volume/target/x86_64-unknown-linux-musl/release/$CRATE /app/bin/exe 11 | COPY --from=builder /volume/vhdl_libraries /app/vhdl_libraries 12 | ENTRYPOINT ["/app/bin/exe"] 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This Source Code Form is subject to the terms of the Mozilla Public 2 | License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | ignore-interior-mutability = ["vhdl_lang::data::source::UniqueSource"] 2 | -------------------------------------------------------------------------------- /example_project/README.md: -------------------------------------------------------------------------------- 1 | # Example Project 2 | This example project (`vhdl_ls.toml`) demonstrates the Parser/Language Server on a medium-sized project consiting of VUnit, OSVVM, UVVM and PoC. 3 | 4 | To setup the project run 5 | ``` 6 | git submodule init 7 | ``` 8 | To update the reference projects to their latest commits run 9 | ``` 10 | git submodule update --remote --merge 11 | ``` 12 | Note that the `vhdl_ls.toml` file will need to be updated to reflect any changes in the project files. 13 | 14 | This folder also contains a utility script `from_vunit_export.py` which converts a VUnit `--export-json` file into a `vhdl_ls.toml` file including the STD and IEEE libraries. -------------------------------------------------------------------------------- /example_project/from_vunit_export.py: -------------------------------------------------------------------------------- 1 | """ 2 | Create a vhdl_ls.toml file from a VUnit --export-json file 3 | """ 4 | 5 | import argparse 6 | import json 7 | import toml 8 | from os.path import relpath, dirname, join 9 | from glob import glob 10 | 11 | 12 | def main(): 13 | parser = argparse.ArgumentParser("Create a vhdl_ls.toml file from a VUnit --export-json file") 14 | parser.add_argument("json_file", nargs=1, 15 | help="The input .json file") 16 | parser.add_argument("-o", "--output", default="vhdl_ls.toml", 17 | help="The output vhdl_ls.toml file") 18 | 19 | args = parser.parse_args() 20 | 21 | with open(args.json_file[0], "r") as fptr: 22 | data = json.load(fptr) 23 | 24 | libraries = {} 25 | for source_file in data["files"]: 26 | file_name = source_file["file_name"] 27 | library_name = source_file["library_name"] 28 | 29 | if not library_name in libraries: 30 | libraries[library_name] = set() 31 | 32 | libraries[library_name].add(file_name) 33 | 34 | with open(args.output, "w") as fptr: 35 | for key in libraries: 36 | libraries[key] = dict(files=[relpath(file_name, dirname(args.output)) 37 | for file_name in libraries[key]]) 38 | toml.dump(dict(libraries=libraries), fptr) 39 | 40 | 41 | 42 | if __name__ == "__main__": 43 | main() 44 | -------------------------------------------------------------------------------- /vhdl_lang/Cargo.toml: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | # You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | [package] 8 | name = "vhdl_lang" 9 | version.workspace = true 10 | authors.workspace = true 11 | license.workspace = true 12 | description = "VHDL Language Frontend" 13 | repository.workspace = true 14 | edition.workspace = true 15 | rust-version.workspace = true 16 | 17 | [dependencies] 18 | vhdl_lang_macros = { version = "^0.85.0", path = "../vhdl_lang_macros" } 19 | pad = "0.1.6" 20 | fnv = "1" 21 | clap = { version = "4", features = ["derive"] } 22 | toml = "0.8.22" 23 | glob = "0.3.2" 24 | dirs = "6.0.0" 25 | rayon = "1" 26 | parking_lot = "0.12.3" 27 | dunce = "1" 28 | pinned_vec = "0.1.1" 29 | itertools = "0.14.0" 30 | subst = "0.3.8" 31 | strum = { version = "0.27.1", features = ["derive"] } 32 | enum-map = "2.7.3" 33 | 34 | [dev-dependencies] 35 | tempfile = "3" 36 | pretty_assertions = "1" 37 | assert_matches = "1" 38 | brunch = "0.8" 39 | assert_cmd = "2.0.14" 40 | predicates = "3.1.0" 41 | 42 | [[bench]] 43 | name = "benchmark" 44 | harness = false 45 | 46 | [features] 47 | default = [] 48 | -------------------------------------------------------------------------------- /vhdl_lang/benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use brunch::{Bench, Benches}; 8 | use std::{path::Path, time::Duration}; 9 | use vhdl_lang::{ 10 | ast::search::{SearchState, Searcher}, 11 | Config, MessagePrinter, NullMessages, Project, 12 | }; 13 | 14 | fn load_config(include_example_project: bool) -> Config { 15 | let repo_root = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); 16 | 17 | let mut config = Config::default(); 18 | config.append( 19 | &Config::read_file_path(&repo_root.join("vhdl_libraries").join("vhdl_ls.toml")) 20 | .expect("Failed to read installed config file"), 21 | &mut MessagePrinter::default(), 22 | ); 23 | 24 | if include_example_project { 25 | config.append( 26 | &Config::read_file_path(&repo_root.join("example_project").join("vhdl_ls.toml")) 27 | .expect("Failed to read project config file"), 28 | &mut MessagePrinter::default(), 29 | ); 30 | } 31 | 32 | config 33 | } 34 | 35 | fn main() { 36 | let mut benches = Benches::default(); 37 | 38 | { 39 | // Only use standard libraries to benchmark parse and analyze as the time taken to get 100 samples 40 | // is very big with the example project 41 | let config = load_config(false); 42 | benches.push(Bench::new("parse and analyze").with_samples(10).run(|| { 43 | let mut project = Project::from_config(config.clone(), &mut NullMessages); 44 | project.analyse(); 45 | })); 46 | } 47 | 48 | { 49 | let mut project = Project::from_config(load_config(true), &mut NullMessages); 50 | project.analyse(); 51 | 52 | let integer = project 53 | .public_symbols() 54 | .find(|ent| matches!(ent.designator().as_identifier(), Some(sym) if sym.name_utf8() == "INTEGER")) 55 | .unwrap(); 56 | 57 | benches.push(Bench::new("find all references").run(|| { 58 | assert!(!project.find_all_references(integer).is_empty()); 59 | })); 60 | 61 | let integer_pos = integer.decl_pos().unwrap(); 62 | benches.push(Bench::new("item at cursor").run(|| { 63 | assert_eq!( 64 | project 65 | .item_at_cursor(&integer_pos.source, integer_pos.start()) 66 | .unwrap() 67 | .1, 68 | integer 69 | ); 70 | })); 71 | 72 | benches.push( 73 | Bench::new("search entire ast") 74 | .with_timeout(Duration::from_secs(30)) 75 | .run(|| { 76 | project.search(&mut MySearcher {}); 77 | }), 78 | ); 79 | } 80 | 81 | benches.finish(); 82 | } 83 | 84 | struct MySearcher {} 85 | 86 | impl Searcher for MySearcher { 87 | fn search_pos_with_ref( 88 | &mut self, 89 | _ctx: &dyn vhdl_lang::TokenAccess, 90 | _pos: &vhdl_lang::SrcPos, 91 | _ref: &vhdl_lang::Reference, 92 | ) -> SearchState { 93 | std::hint::black_box(SearchState::NotFinished) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | #[macro_use] 8 | mod analyze; 9 | mod assignment; 10 | mod association; 11 | mod concurrent; 12 | mod declarative; 13 | mod design_unit; 14 | mod expression; 15 | mod literals; 16 | mod lock; 17 | mod names; 18 | mod overloaded; 19 | mod package_instance; 20 | mod range; 21 | mod root; 22 | mod scope; 23 | mod semantic; 24 | mod sequential; 25 | mod standard; 26 | mod static_expression; 27 | mod subprogram; 28 | mod target; 29 | mod types; 30 | 31 | #[cfg(test)] 32 | pub(crate) mod tests; 33 | 34 | pub(crate) use root::{Library, LockedUnit}; 35 | 36 | pub use self::root::{DesignRoot, EntHierarchy}; 37 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis/assignment.rs: -------------------------------------------------------------------------------- 1 | //! This Source Code Form is subject to the terms of the Mozilla Public 2 | //! License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | //! You can obtain one at http://mozilla.org/MPL/2.0/. 4 | //! 5 | //! Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use super::analyze::*; 8 | use super::scope::*; 9 | use super::target::AssignmentType; 10 | use crate::ast::token_range::WithTokenSpan; 11 | use crate::ast::*; 12 | use crate::data::*; 13 | use crate::named_entity::*; 14 | 15 | impl<'a> AnalyzeContext<'a, '_> { 16 | // @TODO maybe make generic function for expression/waveform. 17 | // wait until type checking to see if it makes sense 18 | pub fn analyze_expr_assignment( 19 | &self, 20 | scope: &Scope<'a>, 21 | target: &mut WithTokenSpan, 22 | assignment_type: AssignmentType, 23 | rhs: &mut AssignmentRightHand>, 24 | diagnostics: &mut dyn DiagnosticHandler, 25 | ) -> FatalResult { 26 | let ttyp = as_fatal(self.resolve_target(scope, target, assignment_type, diagnostics))?; 27 | match rhs { 28 | AssignmentRightHand::Simple(expr) => { 29 | self.analyze_expression_for_target(scope, ttyp, expr, diagnostics)?; 30 | } 31 | AssignmentRightHand::Conditional(conditionals) => { 32 | let Conditionals { 33 | conditionals, 34 | else_item, 35 | } = conditionals; 36 | for conditional in conditionals { 37 | let Conditional { condition, item } = conditional; 38 | self.analyze_expression_for_target(scope, ttyp, item, diagnostics)?; 39 | self.boolean_expr(scope, condition, diagnostics)?; 40 | } 41 | if let Some((expr, _)) = else_item { 42 | self.analyze_expression_for_target(scope, ttyp, expr, diagnostics)?; 43 | } 44 | } 45 | AssignmentRightHand::Selected(selection) => { 46 | let Selection { 47 | expression, 48 | alternatives, 49 | } = selection; 50 | let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?; 51 | for Alternative { 52 | choices, 53 | item, 54 | span: _, 55 | } in alternatives.iter_mut() 56 | { 57 | self.analyze_expression_for_target(scope, ttyp, item, diagnostics)?; 58 | self.choices_with_ttyp(scope, ctyp, choices, diagnostics)?; 59 | } 60 | } 61 | } 62 | Ok(()) 63 | } 64 | 65 | pub fn analyze_waveform_assignment( 66 | &self, 67 | scope: &Scope<'a>, 68 | assignment: &mut SignalAssignment, 69 | diagnostics: &mut dyn DiagnosticHandler, 70 | ) -> FatalResult { 71 | let ttyp = as_fatal(self.resolve_target( 72 | scope, 73 | &mut assignment.target, 74 | AssignmentType::Signal, 75 | diagnostics, 76 | ))?; 77 | match &mut assignment.rhs { 78 | AssignmentRightHand::Simple(wavf) => { 79 | self.analyze_waveform(scope, ttyp, wavf, diagnostics)?; 80 | } 81 | AssignmentRightHand::Conditional(conditionals) => { 82 | let Conditionals { 83 | conditionals, 84 | else_item, 85 | } = conditionals; 86 | for conditional in conditionals { 87 | let Conditional { condition, item } = conditional; 88 | self.analyze_waveform(scope, ttyp, item, diagnostics)?; 89 | self.boolean_expr(scope, condition, diagnostics)?; 90 | } 91 | if let Some((wavf, _)) = else_item { 92 | self.analyze_waveform(scope, ttyp, wavf, diagnostics)?; 93 | } 94 | } 95 | AssignmentRightHand::Selected(selection) => { 96 | let Selection { 97 | expression, 98 | alternatives, 99 | } = selection; 100 | let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?; 101 | for Alternative { 102 | choices, 103 | item, 104 | span: _, 105 | } in alternatives.iter_mut() 106 | { 107 | self.analyze_waveform(scope, ttyp, item, diagnostics)?; 108 | self.choices_with_ttyp(scope, ctyp, choices, diagnostics)?; 109 | } 110 | } 111 | } 112 | Ok(()) 113 | } 114 | 115 | fn analyze_waveform( 116 | &self, 117 | scope: &Scope<'a>, 118 | ttyp: Option>, 119 | wavf: &mut Waveform, 120 | diagnostics: &mut dyn DiagnosticHandler, 121 | ) -> FatalResult { 122 | match wavf { 123 | Waveform::Elements(ref mut elems) => { 124 | for elem in elems.iter_mut() { 125 | let WaveformElement { value, after } = elem; 126 | self.analyze_expression_for_target(scope, ttyp, value, diagnostics)?; 127 | if let Some(expr) = after { 128 | self.expr_with_ttyp(scope, self.time(), expr, diagnostics)?; 129 | } 130 | } 131 | } 132 | Waveform::Unaffected(_) => {} 133 | } 134 | Ok(()) 135 | } 136 | 137 | pub fn analyze_expression_for_target( 138 | &self, 139 | scope: &Scope<'a>, 140 | ttyp: Option>, 141 | expr: &mut WithTokenSpan, 142 | diagnostics: &mut dyn DiagnosticHandler, 143 | ) -> FatalResult { 144 | if let Some(ttyp) = ttyp { 145 | self.expr_with_ttyp(scope, ttyp, expr, diagnostics)?; 146 | } else { 147 | self.expr_unknown_ttyp(scope, expr, diagnostics)?; 148 | } 149 | Ok(()) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis/lock.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | //! This module contains types to handle the analysis data in a thread-safe way, 8 | //! in particular when the dependencies between design units are not known. 9 | 10 | use parking_lot::{MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; 11 | 12 | /// Combines an item to be analyzed (typically, a design unit) with the optional results 13 | /// of that analysis. 14 | struct AnalysisState { 15 | /// Data gathered during analysis; `None` while not yet analyzed. 16 | result: Option, 17 | 18 | /// The subject of analysis; typically, this is a design unit. 19 | data: T, 20 | } 21 | 22 | /// A thread-safe r/w-lock on an item to be analyzed (`T`) and the analysis results (`R`). 23 | /// 24 | /// Ensures mutable access during analysis and immutable access after analysis. 25 | pub struct AnalysisLock { 26 | state: RwLock>, 27 | } 28 | 29 | impl AnalysisLock { 30 | pub fn new(data: T) -> AnalysisLock { 31 | AnalysisLock { 32 | state: RwLock::new(AnalysisState { result: None, data }), 33 | } 34 | } 35 | 36 | /// Returns an immutable reference to the data and result if it has already been analyzed. 37 | pub fn get(&self) -> Option> { 38 | let guard = self.state.read(); 39 | if guard.result.is_some() { 40 | Some(ReadGuard { guard }) 41 | } else { 42 | None 43 | } 44 | } 45 | 46 | // Has been analyzed 47 | pub fn is_analyzed(&self) -> bool { 48 | self.get().is_some() 49 | } 50 | 51 | /// Returns an mutable reference to the data. 52 | pub fn write(&self) -> MappedRwLockWriteGuard<'_, T> { 53 | RwLockWriteGuard::map(self.state.write(), |data| &mut data.data) 54 | } 55 | 56 | /// Reset analysis state, analysis needs to be redone. 57 | pub fn reset(&self) { 58 | let mut guard = self.state.write(); 59 | guard.result = None; 60 | } 61 | 62 | /// Returns an immmutable reference to the data and result. 63 | /// 64 | /// Panics if the analysis result is not available. 65 | pub fn expect_analyzed(&self) -> ReadGuard<'_, T, R> { 66 | let guard = self.state.read(); 67 | 68 | if guard.result.is_none() { 69 | panic!("Expected analysis to have already been done"); 70 | } 71 | 72 | ReadGuard { guard } 73 | } 74 | 75 | /// Creates a view into this lock. 76 | /// 77 | /// This view provides: 78 | /// - a mutable reference to the data if not analyzed 79 | /// - an immmutable reference to the data if already analyzed 80 | pub fn entry(&self) -> AnalysisEntry<'_, T, R> { 81 | if let Some(guard) = self.get() { 82 | AnalysisEntry::Occupied(guard) 83 | } else { 84 | let guard = self.state.write(); 85 | 86 | if guard.result.is_some() { 87 | let guard = ReadGuard { 88 | guard: RwLockWriteGuard::downgrade(guard), 89 | }; 90 | 91 | AnalysisEntry::Occupied(guard) 92 | } else { 93 | let guard = WriteGuard { guard }; 94 | AnalysisEntry::Vacant(guard) 95 | } 96 | } 97 | } 98 | } 99 | 100 | /// A view into a thread-safe r/w-lock on an [`AnalysisState`](struct.AnalysisState.html). 101 | /// 102 | /// Instances of this type are created by 103 | /// [`AnalysisLock::entry()`](struct.AnalysisLock.html#method.entry) 104 | /// and allow read-access to a completed analysis data and 105 | /// read/write-access to incomplete analysis data. 106 | pub enum AnalysisEntry<'a, T, R> { 107 | Occupied(ReadGuard<'a, T, R>), 108 | Vacant(WriteGuard<'a, T, R>), 109 | } 110 | 111 | pub struct ReadGuard<'a, T, R> { 112 | guard: RwLockReadGuard<'a, AnalysisState>, 113 | } 114 | 115 | impl ReadGuard<'_, T, R> { 116 | pub fn result(&self) -> &R { 117 | self.guard.result.as_ref().unwrap() 118 | } 119 | 120 | pub fn data(&self) -> &T { 121 | &self.guard.data 122 | } 123 | } 124 | 125 | impl std::ops::Deref for ReadGuard<'_, T, R> { 126 | type Target = T; 127 | 128 | fn deref(&self) -> &Self::Target { 129 | &self.guard.data 130 | } 131 | } 132 | 133 | pub struct WriteGuard<'a, T, R> { 134 | guard: RwLockWriteGuard<'a, AnalysisState>, 135 | } 136 | 137 | impl<'a, T, R> WriteGuard<'a, T, R> { 138 | pub fn finish(&mut self, result: R) { 139 | self.guard.result = Some(result); 140 | } 141 | pub fn downgrade(self) -> ReadGuard<'a, T, R> { 142 | if self.guard.result.is_none() { 143 | panic!("Cannot downgrade unit without result"); 144 | } 145 | ReadGuard { 146 | guard: RwLockWriteGuard::downgrade(self.guard), 147 | } 148 | } 149 | } 150 | 151 | impl std::ops::Deref for WriteGuard<'_, T, R> { 152 | type Target = T; 153 | 154 | fn deref(&self) -> &Self::Target { 155 | &self.guard.data 156 | } 157 | } 158 | 159 | impl std::ops::DerefMut for WriteGuard<'_, T, R> { 160 | fn deref_mut(&mut self) -> &mut Self::Target { 161 | &mut self.guard.data 162 | } 163 | } 164 | 165 | #[cfg(test)] 166 | mod tests { 167 | use super::*; 168 | 169 | #[test] 170 | fn analysis_result_is_memoized() { 171 | let lock = AnalysisLock::new(1); 172 | 173 | match lock.entry() { 174 | AnalysisEntry::Vacant(mut entry) => { 175 | *entry = 2; 176 | entry.finish(1.0); 177 | } 178 | _ => panic!("Expected Vacant entry"), 179 | }; 180 | 181 | match lock.entry() { 182 | AnalysisEntry::Occupied(entry) => { 183 | assert_eq!(*entry, 2); 184 | assert!((*entry.result() - 1.0_f64).abs() < f64::EPSILON); 185 | } 186 | _ => panic!("Expected Occupied entry"), 187 | }; 188 | 189 | assert_eq!(*lock.get().unwrap(), 2); 190 | assert!((*lock.get().unwrap().result() - 1.0_f64).abs() < f64::EPSILON); 191 | 192 | // Check that lock is reset 193 | lock.reset(); 194 | assert!(lock.get().is_none()); 195 | 196 | match lock.entry() { 197 | AnalysisEntry::Vacant(mut entry) => { 198 | *entry = 2; 199 | entry.finish(1.0); 200 | } 201 | _ => panic!("Expected Vacant entry"), 202 | }; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis/target.rs: -------------------------------------------------------------------------------- 1 | use super::analyze::*; 2 | use super::scope::*; 3 | use crate::ast::token_range::WithTokenSpan; 4 | use crate::ast::*; 5 | use crate::data::error_codes::ErrorCode; 6 | use crate::data::*; 7 | use crate::named_entity::*; 8 | use vhdl_lang::TokenSpan; 9 | 10 | /// Analysis of assignment targets 11 | /// 12 | /// examples: 13 | /// target <= 1; 14 | /// target(0).elem := 1 15 | impl<'a> AnalyzeContext<'a, '_> { 16 | pub fn resolve_target( 17 | &self, 18 | scope: &Scope<'a>, 19 | target: &mut WithTokenSpan, 20 | assignment_type: AssignmentType, 21 | diagnostics: &mut dyn DiagnosticHandler, 22 | ) -> EvalResult> { 23 | match target.item { 24 | Target::Name(ref mut name) => { 25 | self.resolve_target_name(scope, name, target.span, assignment_type, diagnostics) 26 | } 27 | Target::Aggregate(ref mut assocs) => { 28 | self.analyze_aggregate(scope, assocs, diagnostics)?; 29 | Err(EvalError::Unknown) 30 | } 31 | } 32 | } 33 | 34 | pub fn resolve_target_name( 35 | &self, 36 | scope: &Scope<'a>, 37 | target: &mut Name, 38 | target_pos: TokenSpan, 39 | assignment_type: AssignmentType, 40 | diagnostics: &mut dyn DiagnosticHandler, 41 | ) -> EvalResult> { 42 | let object_name = self.resolve_object_name( 43 | scope, 44 | target_pos, 45 | target, 46 | "may not be the target of an assignment", 47 | ErrorCode::MismatchedKinds, 48 | diagnostics, 49 | )?; 50 | if !object_name.base.can_be_assigned_to() { 51 | diagnostics.add( 52 | target_pos.pos(self.ctx), 53 | format!( 54 | "{} may not be the target of an assignment", 55 | object_name.base.describe_class() 56 | ), 57 | ErrorCode::MismatchedKinds, 58 | ); 59 | } else if !object_name.base.is_valid_assignment_type(assignment_type) { 60 | diagnostics.add( 61 | target_pos.pos(self.ctx), 62 | format!( 63 | "{} may not be the target of a {} assignment", 64 | object_name.base.describe_class(), 65 | assignment_type.to_str() 66 | ), 67 | ErrorCode::MismatchedKinds, 68 | ); 69 | } 70 | Ok(object_name.type_mark()) 71 | } 72 | } 73 | 74 | #[derive(Copy, Clone)] 75 | pub enum AssignmentType { 76 | // Assignment with <= 77 | Signal, 78 | // Assignment with := 79 | Variable, 80 | } 81 | 82 | impl AssignmentType { 83 | fn to_str(self) -> &'static str { 84 | match self { 85 | AssignmentType::Signal => "signal", 86 | AssignmentType::Variable => "variable", 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis/tests/declarations.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com 6 | use crate::analysis::tests::{check_diagnostics, LibraryBuilder}; 7 | use crate::data::error_codes::ErrorCode; 8 | use crate::Diagnostic; 9 | 10 | #[test] 11 | pub fn declaration_not_allowed_everywhere() { 12 | let mut builder = LibraryBuilder::new(); 13 | let code = builder.code( 14 | "libname", 15 | "\ 16 | entity ent is 17 | end entity; 18 | 19 | architecture arch of ent is 20 | 21 | function my_func return natural is 22 | signal x : bit; 23 | begin 24 | 25 | end my_func; 26 | begin 27 | 28 | my_block : block 29 | variable y: natural; 30 | begin 31 | end block my_block; 32 | 33 | end architecture; 34 | ", 35 | ); 36 | check_diagnostics( 37 | builder.analyze(), 38 | vec![ 39 | Diagnostic::new( 40 | code.s1("signal x : bit;"), 41 | "signal declaration not allowed here", 42 | ErrorCode::DeclarationNotAllowed, 43 | ), 44 | Diagnostic::new( 45 | code.s1("variable y: natural;"), 46 | "variable declaration not allowed here", 47 | ErrorCode::DeclarationNotAllowed, 48 | ), 49 | ], 50 | ) 51 | } 52 | 53 | // Issue #242 54 | #[test] 55 | pub fn attribute_with_wrong_type() { 56 | let mut builder = LibraryBuilder::new(); 57 | let code = builder.code( 58 | "libname", 59 | "\ 60 | entity test is 61 | attribute some_attr : string; 62 | attribute some_attr of test : signal is \"some value\"; 63 | end entity test; 64 | ", 65 | ); 66 | let (_, diag) = builder.get_analyzed_root(); 67 | check_diagnostics( 68 | diag, 69 | vec![Diagnostic::new( 70 | code.s1("test : signal").s1("test"), 71 | "entity 'test' is not of class signal", 72 | ErrorCode::MismatchedEntityClass, 73 | )], 74 | ) 75 | } 76 | 77 | #[test] 78 | pub fn attribute_sees_through_aliases() { 79 | let mut builder = LibraryBuilder::new(); 80 | let code = builder.code( 81 | "libname", 82 | "\ 83 | entity test is 84 | port ( 85 | clk: in bit 86 | ); 87 | alias aliased_clk is clk; 88 | attribute some_attr : string; 89 | attribute some_attr of aliased_clk : entity is \"some value\"; 90 | end entity test; 91 | ", 92 | ); 93 | let (_, diag) = builder.get_analyzed_root(); 94 | check_diagnostics( 95 | diag, 96 | vec![Diagnostic::new( 97 | code.s1("aliased_clk : entity").s1("aliased_clk"), 98 | "port 'clk' : in is not of class entity", 99 | ErrorCode::MismatchedEntityClass, 100 | )], 101 | ) 102 | } 103 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis/tests/deferred_constant.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use super::*; 8 | use vhdl_lang::data::error_codes::ErrorCode; 9 | 10 | #[test] 11 | fn allows_deferred_constant() { 12 | let mut builder = LibraryBuilder::new(); 13 | builder.code( 14 | "libname", 15 | " 16 | package pkg is 17 | constant a : natural; 18 | end package; 19 | 20 | package body pkg is 21 | constant a : natural := 0; 22 | end package body; 23 | ", 24 | ); 25 | 26 | let diagnostics = builder.analyze(); 27 | check_no_diagnostics(&diagnostics); 28 | } 29 | 30 | #[test] 31 | fn forbid_deferred_constant_after_constant() { 32 | let mut builder = LibraryBuilder::new(); 33 | let code = builder.code( 34 | "libname", 35 | " 36 | package pkg is 37 | constant a1 : natural := 0; 38 | constant a1 : natural; 39 | end package; 40 | ", 41 | ); 42 | 43 | let diagnostics = builder.analyze(); 44 | check_diagnostics(diagnostics, duplicates(&code, &["a1"])); 45 | } 46 | 47 | #[test] 48 | fn forbid_deferred_constant_outside_of_package_declaration() { 49 | let mut builder = LibraryBuilder::new(); 50 | let code = builder.code( 51 | "libname", 52 | " 53 | package pkg is 54 | end package; 55 | 56 | package body pkg is 57 | constant a1 : natural; 58 | constant a1 : natural := 0; 59 | end package body; 60 | ", 61 | ); 62 | 63 | let diagnostics = builder.analyze(); 64 | check_diagnostics( 65 | diagnostics, 66 | vec![Diagnostic::new( 67 | code.s1("a1"), 68 | "Deferred constants are only allowed in package declarations (not body)", 69 | ErrorCode::IllegalDeferredConstant, 70 | )], 71 | ); 72 | } 73 | 74 | #[test] 75 | fn forbid_full_declaration_of_deferred_constant_outside_of_package_body() { 76 | let mut builder = LibraryBuilder::new(); 77 | let code = builder.code( 78 | "libname", 79 | " 80 | package pkg is 81 | constant a1 : natural; 82 | constant a1 : natural := 0; 83 | end package; 84 | ", 85 | ); 86 | 87 | let diagnostics = builder.analyze(); 88 | check_diagnostics( 89 | diagnostics, 90 | vec![Diagnostic::new( 91 | code.s("a1", 1), 92 | "Deferred constant 'a1' lacks corresponding full constant declaration in package body", 93 | ErrorCode::MissingDeferredDeclaration 94 | ),Diagnostic::new( 95 | code.s("a1", 2), 96 | "Full declaration of deferred constant is only allowed in a package body", 97 | ErrorCode::IllegalDeferredConstant 98 | )], 99 | ); 100 | } 101 | 102 | #[test] 103 | fn error_on_missing_full_constant_declaration() { 104 | let mut builder = LibraryBuilder::new(); 105 | let code = builder.code( 106 | "libname", 107 | " 108 | package pkg_no_body is 109 | constant a1 : natural; 110 | end package; 111 | 112 | package pkg is 113 | constant b1 : natural; 114 | end package; 115 | 116 | package body pkg is 117 | end package body; 118 | ", 119 | ); 120 | 121 | let diagnostics = builder.analyze(); 122 | check_diagnostics( 123 | diagnostics, 124 | vec![ 125 | Diagnostic::new( 126 | code.s1("a1"), 127 | "Deferred constant 'a1' lacks corresponding full constant declaration in package body", 128 | ErrorCode::MissingDeferredDeclaration 129 | ), 130 | Diagnostic::new( 131 | code.s1("b1"), 132 | "Deferred constant 'b1' lacks corresponding full constant declaration in package body", 133 | ErrorCode::MissingDeferredDeclaration 134 | ), 135 | ], 136 | ); 137 | } 138 | 139 | #[test] 140 | fn forbid_multiple_constant_after_deferred_constant() { 141 | let mut builder = LibraryBuilder::new(); 142 | let code = builder.code( 143 | "libname", 144 | " 145 | package pkg is 146 | constant a1 : natural; 147 | end package; 148 | 149 | package body pkg is 150 | constant a1 : natural := 0; 151 | constant a1 : natural := 0; 152 | end package body; 153 | ", 154 | ); 155 | 156 | let diagnostics = builder.analyze(); 157 | check_diagnostics(diagnostics, vec![duplicate(&code, "a1", 2, 3)]); 158 | } 159 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis/tests/discrete_ranges.rs: -------------------------------------------------------------------------------- 1 | use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder}; 2 | use crate::data::ErrorCode; 3 | use crate::syntax::test::check_diagnostics; 4 | use crate::Diagnostic; 5 | 6 | #[test] 7 | fn allows_ranges_with_case_statements() { 8 | let mut builder = LibraryBuilder::new(); 9 | builder.code( 10 | "libname", 11 | " 12 | entity test is 13 | end entity; 14 | 15 | architecture rtl of test is 16 | type t_my_enum is (A1, A2, A3); 17 | subtype t_my_enum_range is t_my_enum range A1 to A2; 18 | signal my_sig_sub : t_my_enum; 19 | begin 20 | 21 | process 22 | begin 23 | case my_sig_sub is 24 | when t_my_enum'range => null; 25 | when t_my_enum_range => null; 26 | when t_my_enum_range'range => null; 27 | when A1 to A2 => null; 28 | when t_my_enum => null; 29 | when others => null; 30 | end case; 31 | end process; 32 | end architecture; 33 | ", 34 | ); 35 | 36 | let diagnostics = builder.analyze(); 37 | check_no_diagnostics(&diagnostics); 38 | } 39 | 40 | #[test] 41 | fn disallows_ranges_with_wrong_type() { 42 | let mut builder = LibraryBuilder::new(); 43 | let code = builder.code( 44 | "libname", 45 | " 46 | entity test is 47 | end entity; 48 | 49 | architecture rtl of test is 50 | type t_my_enum is (A1, A2, A3); 51 | type t_my_other_enum is (B1, B2, B3); 52 | subtype t_my_other_enum_range is t_my_other_enum range B1 to B2; 53 | signal my_sig_sub : t_my_enum; 54 | begin 55 | 56 | process 57 | begin 58 | case my_sig_sub is 59 | when t_my_other_enum'range => null; 60 | when t_my_other_enum_range => null; 61 | when others => null; 62 | end case; 63 | end process; 64 | end architecture; 65 | ", 66 | ); 67 | 68 | check_diagnostics( 69 | builder.analyze(), 70 | vec![ 71 | Diagnostic::new( 72 | code.s1("t_my_other_enum'range"), 73 | "type 't_my_other_enum' does not match type 't_my_enum'", 74 | ErrorCode::TypeMismatch, 75 | ), 76 | Diagnostic::new( 77 | code.s("t_my_other_enum_range", 2), 78 | "subtype 't_my_other_enum_range' does not match type 't_my_enum'", 79 | ErrorCode::TypeMismatch, 80 | ), 81 | ], 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis/tests/external_names.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2025, Lukas Scheller lukasscheller@icloud.com 6 | 7 | use crate::analysis::tests::LibraryBuilder; 8 | use crate::data::ErrorCode; 9 | use crate::syntax::test::check_diagnostics; 10 | use crate::Diagnostic; 11 | 12 | #[test] 13 | fn external_package_name_must_point_to_signal_constant_or_variable() { 14 | let mut builder = LibraryBuilder::new(); 15 | let code = builder.code( 16 | "libname", 17 | "\ 18 | package test_pkg is 19 | procedure foo(x: bit); 20 | end package; 21 | 22 | use work.test_pkg.all; 23 | 24 | entity test_ent is 25 | end entity; 26 | 27 | architecture arch of test_ent is 28 | begin 29 | << signal @work.test_pkg.foo : bit >> <= '1'; 30 | end arch; 31 | ", 32 | ); 33 | 34 | check_diagnostics( 35 | builder.analyze(), 36 | vec![Diagnostic::mismatched_kinds( 37 | code.s1("@work.test_pkg.foo"), 38 | "External path must point to a constant, variable or signal", 39 | )], 40 | ); 41 | } 42 | #[test] 43 | fn external_package_name_must_match_target_type() { 44 | let mut builder = LibraryBuilder::new(); 45 | let code = builder.code( 46 | "libname", 47 | "\ 48 | package test_pkg is 49 | signal foo : natural; 50 | end package; 51 | 52 | use work.test_pkg.all; 53 | 54 | entity test_ent is 55 | end entity; 56 | 57 | architecture arch of test_ent is 58 | begin 59 | << signal @work.test_pkg.foo : bit >> <= '1'; 60 | end arch; 61 | ", 62 | ); 63 | 64 | check_diagnostics( 65 | builder.analyze(), 66 | vec![Diagnostic::new( 67 | code.s1("@work.test_pkg.foo"), 68 | "signal 'foo' of subtype 'NATURAL' does not match type 'BIT'", 69 | ErrorCode::TypeMismatch, 70 | )], 71 | ); 72 | } 73 | 74 | #[test] 75 | fn external_package_class_must_match_target_class() { 76 | let mut builder = LibraryBuilder::new(); 77 | let code = builder.code( 78 | "libname", 79 | "\ 80 | package test_pkg is 81 | shared variable foo : bit; 82 | end package; 83 | 84 | use work.test_pkg.all; 85 | 86 | entity test_ent is 87 | end entity; 88 | 89 | architecture arch of test_ent is 90 | begin 91 | << signal @work.test_pkg.foo : bit >> <= '1'; 92 | end arch; 93 | ", 94 | ); 95 | 96 | check_diagnostics( 97 | builder.analyze(), 98 | vec![Diagnostic::new( 99 | code.s1("@work.test_pkg.foo"), 100 | "class signal does not match shared variable", 101 | ErrorCode::MismatchedObjectClass, 102 | )], 103 | ); 104 | } 105 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis/tests/incomplete_type.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | // @TODO 8 | // 5.4.2 Incomplete type declarations 9 | // Prior to the end of the corresponding full type declaration, the only allowed use of a name that denotes a type 10 | // declared by an incomplete type declaration is as the type mark in the subtype indication of an access type 11 | // definition; no constraints are allowed in this subtype indication. 12 | 13 | use super::*; 14 | use crate::data::error_codes::ErrorCode; 15 | use crate::data::SrcPos; 16 | 17 | #[test] 18 | fn allows_incomplete_type_definition() { 19 | let mut builder = LibraryBuilder::new(); 20 | builder.code( 21 | "libname", 22 | " 23 | package pkg is 24 | type rec_t; 25 | type rec_t is record 26 | end record; 27 | 28 | type enum_t; 29 | type enum_t is (alpha, beta); 30 | end package; 31 | ", 32 | ); 33 | 34 | let diagnostics = builder.analyze(); 35 | check_no_diagnostics(&diagnostics); 36 | } 37 | 38 | #[test] 39 | fn error_on_duplicate_incomplete_type_definition() { 40 | let mut builder = LibraryBuilder::new(); 41 | let code = builder.code( 42 | "libname", 43 | " 44 | package pkg is 45 | type rec_t; 46 | type rec_t; 47 | type rec_t is record 48 | end record; 49 | end package; 50 | ", 51 | ); 52 | 53 | let diagnostics = builder.analyze(); 54 | check_diagnostics(diagnostics, duplicates(&code, &["rec_t"])); 55 | } 56 | 57 | #[test] 58 | fn error_on_missing_full_type_definition_for_incomplete() { 59 | let mut builder = LibraryBuilder::new(); 60 | let code_pkg = builder.code( 61 | "libname", 62 | " 63 | package pkg is 64 | type rec_t; 65 | end package; 66 | 67 | package body pkg is 68 | -- Must appear in the same immediate declarative region 69 | type rec_t is record 70 | end record; 71 | end package body; 72 | ", 73 | ); 74 | 75 | let code_ent = builder.code( 76 | "libname", 77 | " 78 | entity ent is 79 | end entity; 80 | 81 | architecture rtl of ent is 82 | type rec_t; 83 | begin 84 | blk : block 85 | -- Must appear in the same immediate declarative region 86 | type rec_t is record 87 | end record; 88 | begin 89 | end block; 90 | end architecture; 91 | ", 92 | ); 93 | 94 | let code_pkg2 = builder.code( 95 | "libname", 96 | " 97 | -- To check that no duplicate errors are made when closing the immediate and extended regions 98 | package pkg2 is 99 | type rec_t; 100 | end package; 101 | 102 | package body pkg2 is 103 | end package body; 104 | ", 105 | ); 106 | 107 | let mut expected_diagnostics = Vec::new(); 108 | for code in [&code_pkg, &code_ent, &code_pkg2].iter() { 109 | expected_diagnostics.push(missing_full_error(&code.s1("rec_t"))); 110 | } 111 | 112 | expected_diagnostics.push(duplicate(&code_pkg, "rec_t", 1, 2)); 113 | 114 | let diagnostics = builder.analyze(); 115 | check_diagnostics(diagnostics, expected_diagnostics); 116 | } 117 | 118 | #[test] 119 | fn incomplete_type_references_point_to_full_definition() { 120 | let mut builder = LibraryBuilder::new(); 121 | let code = builder.code( 122 | "libname", 123 | " 124 | package pkg is 125 | type rec_t; 126 | type access_t is access rec_t; 127 | type rec_t is record 128 | node: access_t; 129 | end record; 130 | 131 | procedure proc(val : rec_t); 132 | end package; 133 | ", 134 | ); 135 | 136 | let (root, diagnostics) = builder.get_analyzed_root(); 137 | check_no_diagnostics(&diagnostics); 138 | 139 | // Reference from incomplete goes to full 140 | for i in 1..=4 { 141 | assert_eq!( 142 | root.search_reference_pos(code.source(), code.s("rec_t", i).start()), 143 | Some(code.s("rec_t", 3).pos()), 144 | "{i}" 145 | ); 146 | } 147 | 148 | let references: Vec<_> = (1..=4).map(|idx| code.s("rec_t", idx).pos()).collect(); 149 | assert_eq!( 150 | root.find_all_references_pos(&code.s("rec_t", 3).pos()), 151 | references 152 | ); 153 | } 154 | 155 | #[test] 156 | fn error_on_missing_full_type_definition_for_incomplete_still_defines_the_type() { 157 | let mut builder = LibraryBuilder::new(); 158 | let code = builder.code( 159 | "libname", 160 | " 161 | package pkg is 162 | type rec_t; 163 | type acces_t is access rec_t; 164 | end package; 165 | ", 166 | ); 167 | 168 | let diagnostics = builder.analyze(); 169 | check_diagnostics(diagnostics, vec![missing_full_error(&code.s1("rec_t"))]); 170 | } 171 | 172 | fn missing_full_error(pos: &impl AsRef) -> Diagnostic { 173 | let mut error = Diagnostic::new( 174 | pos, 175 | "Missing full type declaration of incomplete type 'rec_t'", 176 | ErrorCode::MissingFullTypeDeclaration, 177 | ); 178 | error.add_related( 179 | pos, 180 | "The full type declaration shall occur immediately within the same declarative part", 181 | ); 182 | error 183 | } 184 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis/tests/mod.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | mod assignment_typecheck; 8 | mod association_formal; 9 | mod circular_dependencies; 10 | mod context_clause; 11 | mod custom_attributes; 12 | mod declarations; 13 | mod deferred_constant; 14 | mod discrete_ranges; 15 | mod external_names; 16 | mod hierarchy; 17 | mod homographs; 18 | mod implicit; 19 | mod incomplete_type; 20 | mod incremental_analysis; 21 | mod package_instance; 22 | mod protected_type; 23 | mod resolves_design_units; 24 | mod resolves_names; 25 | mod resolves_type_mark; 26 | mod sensitivity_list; 27 | mod subprogram_arguments; 28 | mod subprogram_instance; 29 | mod tool_directive; 30 | mod typecheck_expression; 31 | mod util; 32 | mod view_declarations; 33 | mod visibility; 34 | 35 | use std::cell::RefCell; 36 | use std::path::PathBuf; 37 | use vhdl_lang::TokenSpan; 38 | 39 | pub use self::util::*; 40 | use crate::ast::Designator; 41 | use crate::ast::UnitId; 42 | pub use crate::data::Diagnostic; 43 | use crate::data::NoDiagnostics; 44 | pub use crate::syntax::test::*; 45 | use crate::syntax::Token; 46 | 47 | use super::analyze::AnalyzeContext; 48 | use super::scope::*; 49 | use super::DesignRoot; 50 | use crate::named_entity::*; 51 | use crate::Source; 52 | 53 | pub(super) struct TestSetup<'a> { 54 | builder: RefCell, 55 | root: DesignRoot, 56 | arena: Arena, 57 | pub scope: Scope<'a>, 58 | } 59 | 60 | impl<'a> TestSetup<'a> { 61 | pub fn new() -> Self { 62 | let builder = LibraryBuilder::new(); 63 | let (mut root, _) = builder.get_analyzed_root(); 64 | root.ensure_library(root.symbol_utf8("libname")); 65 | let arena = Arena::new(ArenaId::default()); 66 | 67 | Self { 68 | arena, 69 | root, 70 | builder: RefCell::new(builder), 71 | scope: Scope::new(Region::default()), 72 | } 73 | } 74 | 75 | #[allow(clippy::ptr_arg)] 76 | pub fn ctx<'t>(&'a self, tokens: &'t Vec) -> AnalyzeContext<'a, 't> { 77 | let ctx = AnalyzeContext::new( 78 | &self.root, 79 | &UnitId::package( 80 | &self.root.symbol_utf8("libname"), 81 | &self.root.symbol_utf8("dummy"), 82 | ), 83 | Source::inline(&PathBuf::new(), ""), 84 | &self.arena, 85 | tokens, 86 | ); 87 | ctx.add_implicit_context_clause(&self.scope).unwrap(); 88 | ctx 89 | } 90 | 91 | pub fn snippet(&self, code: &str) -> Code { 92 | self.builder.borrow_mut().snippet(code) 93 | } 94 | 95 | pub fn declarative_part(&'a self, code: &str) -> Code { 96 | let code = self.snippet(code); 97 | let dummy_parent = self.arena.alloc( 98 | Designator::Anonymous(0), 99 | None, 100 | Related::None, 101 | AnyEntKind::Library, 102 | None, 103 | TokenSpan::for_library(), 104 | Some(code.source().clone()), 105 | ); 106 | self.ctx(&code.tokenize()) 107 | .analyze_declarative_part( 108 | &self.scope, 109 | dummy_parent, 110 | code.declarative_part().as_mut(), 111 | &mut NoDiagnostics, 112 | ) 113 | .unwrap(); 114 | code 115 | } 116 | 117 | pub fn lookup(&'a self, sym: &str) -> EntRef<'a> { 118 | // We cheat and create a source pos as the lookup method requires it 119 | let designator = self.snippet(sym).designator(); 120 | 121 | self.scope 122 | .lookup(&designator.item) 123 | .unwrap() 124 | .into_non_overloaded() 125 | .unwrap() 126 | } 127 | 128 | pub fn lookup_overloaded(&'a self, code: Code) -> OverloadedEnt<'a> { 129 | let des = code.designator(); 130 | 131 | if let NamedEntities::Overloaded(overloaded) = self.scope.lookup(&des.item).unwrap() { 132 | overloaded 133 | .entities() 134 | .find(|ent| ent.decl_pos() == Some(&code.pos())) 135 | .unwrap() 136 | } else { 137 | panic!("Expected overloaded name"); 138 | } 139 | } 140 | 141 | pub fn lookup_type(&'a self, sym: &str) -> TypeEnt<'a> { 142 | TypeEnt::from_any(self.lookup(sym)).unwrap() 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis/tests/sensitivity_list.rs: -------------------------------------------------------------------------------- 1 | //! This Source Code Form is subject to the terms of the Mozilla Public 2 | //! License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | //! You can obtain one at http://mozilla.org/MPL/2.0/. 4 | //! 5 | //! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use super::*; 8 | use vhdl_lang::data::error_codes::ErrorCode; 9 | 10 | #[test] 11 | fn must_be_object_name() { 12 | let mut builder = LibraryBuilder::new(); 13 | let code = builder.code( 14 | "libname", 15 | " 16 | package pkg is 17 | end package; 18 | 19 | package body pkg is 20 | procedure proc(signal good : in bit) is 21 | begin 22 | wait on good; 23 | wait on proc; 24 | end; 25 | end package body; 26 | 27 | ", 28 | ); 29 | let (_, diagnostics) = builder.get_analyzed_root(); 30 | check_diagnostics( 31 | diagnostics, 32 | vec![Diagnostic::new( 33 | code.s1("wait on proc").s1("proc"), 34 | "procedure proc[BIT] is not a signal and cannot be in a sensitivity list", 35 | ErrorCode::DisallowedInSensitivityList, 36 | )], 37 | ) 38 | } 39 | 40 | #[test] 41 | fn must_be_signal_name() { 42 | let mut builder = LibraryBuilder::new(); 43 | let code = builder.code( 44 | "libname", 45 | " 46 | package pkg is 47 | end package; 48 | 49 | package body pkg is 50 | constant c0 : bit := '0'; 51 | procedure proc(signal good : in bit) is 52 | begin 53 | wait on good; 54 | wait on c0; 55 | end; 56 | end package body; 57 | 58 | ", 59 | ); 60 | let (_, diagnostics) = builder.get_analyzed_root(); 61 | check_diagnostics( 62 | diagnostics, 63 | vec![Diagnostic::new( 64 | code.s1("wait on c0").s1("c0"), 65 | "constant 'c0' is not a signal and cannot be in a sensitivity list", 66 | ErrorCode::DisallowedInSensitivityList, 67 | )], 68 | ) 69 | } 70 | 71 | #[test] 72 | fn must_not_be_output() { 73 | let mut builder = LibraryBuilder::new(); 74 | let code = builder.code( 75 | "libname", 76 | " 77 | package pkg is 78 | end package; 79 | 80 | package body pkg is 81 | procedure proc(signal bad : out bit) is 82 | begin 83 | wait on bad; 84 | end; 85 | end package body; 86 | 87 | ", 88 | ); 89 | let (_, diagnostics) = builder.get_analyzed_root(); 90 | check_diagnostics( 91 | diagnostics, 92 | vec![Diagnostic::new( 93 | code.s1("wait on bad").s1("bad"), 94 | "interface signal 'bad' of mode out cannot be in a sensitivity list", 95 | ErrorCode::DisallowedInSensitivityList, 96 | )], 97 | ) 98 | } 99 | 100 | #[test] 101 | fn may_be_output_port() { 102 | let mut builder = LibraryBuilder::new(); 103 | builder.code( 104 | "libname", 105 | " 106 | entity ent is 107 | port (oport : out bit); 108 | end entity; 109 | 110 | architecture a of ent is 111 | begin 112 | main: process (oport) 113 | begin 114 | end process main; 115 | end architecture; 116 | 117 | ", 118 | ); 119 | let (_, diagnostics) = builder.get_analyzed_root(); 120 | check_no_diagnostics(&diagnostics); 121 | } 122 | -------------------------------------------------------------------------------- /vhdl_lang/src/analysis/tests/tool_directive.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com 6 | use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder}; 7 | 8 | #[test] 9 | fn simple_tool_directive() { 10 | let mut builder = LibraryBuilder::new(); 11 | builder.code("libname", "`protect begin"); 12 | check_no_diagnostics(&builder.analyze()); 13 | } 14 | 15 | #[test] 16 | fn tool_directive() { 17 | let mut builder = LibraryBuilder::new(); 18 | builder.code( 19 | "libname", 20 | "\ 21 | entity my_ent is 22 | end my_ent; 23 | 24 | `protect begin_protected 25 | `protect version = 1 26 | `protect encrypt_agent = \"XILINX\" 27 | `protect encrypt_agent_info = \"Xilinx Encryption Tool 2020.2\" 28 | `protect key_keyowner = \"Cadence Design Systems.\", key_keyname = \"cds_rsa_key\", key_method = \"rsa\" 29 | `protect encoding = (enctype = \"BASE64\", line_length = 76, bytes = 64) 30 | ", 31 | ); 32 | check_no_diagnostics(&builder.analyze()); 33 | } 34 | -------------------------------------------------------------------------------- /vhdl_lang/src/ast/ast_span.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::token_range::WithTokenSpan; 2 | use crate::ast::{LabeledConcurrentStatement, LabeledSequentialStatement, WithDecl}; 3 | use crate::{HasTokenSpan, TokenId}; 4 | use std::ops::Deref; 5 | use vhdl_lang::ast::WithToken; 6 | use vhdl_lang::TokenSpan; 7 | 8 | impl HasTokenSpan for TokenId { 9 | fn get_start_token(&self) -> TokenId { 10 | *self 11 | } 12 | 13 | fn get_end_token(&self) -> TokenId { 14 | *self 15 | } 16 | } 17 | 18 | impl HasTokenSpan for TokenSpan { 19 | fn get_start_token(&self) -> TokenId { 20 | self.start_token 21 | } 22 | 23 | fn get_end_token(&self) -> TokenId { 24 | self.end_token 25 | } 26 | } 27 | 28 | impl HasTokenSpan for WithToken { 29 | fn get_start_token(&self) -> TokenId { 30 | self.token 31 | } 32 | 33 | fn get_end_token(&self) -> TokenId { 34 | self.token 35 | } 36 | } 37 | 38 | impl HasTokenSpan for WithDecl> { 39 | fn get_start_token(&self) -> TokenId { 40 | self.tree.get_start_token() 41 | } 42 | 43 | fn get_end_token(&self) -> TokenId { 44 | self.tree.get_end_token() 45 | } 46 | } 47 | 48 | impl HasTokenSpan for WithTokenSpan { 49 | fn get_start_token(&self) -> TokenId { 50 | self.span.start_token 51 | } 52 | 53 | fn get_end_token(&self) -> TokenId { 54 | self.span.end_token 55 | } 56 | } 57 | 58 | impl HasTokenSpan for Box 59 | where 60 | T: HasTokenSpan, 61 | { 62 | fn get_start_token(&self) -> TokenId { 63 | self.deref().get_start_token() 64 | } 65 | 66 | fn get_end_token(&self) -> TokenId { 67 | self.deref().get_end_token() 68 | } 69 | } 70 | 71 | impl HasTokenSpan for LabeledConcurrentStatement { 72 | fn get_start_token(&self) -> TokenId { 73 | if let Some(label) = &self.label.tree { 74 | label.token 75 | } else { 76 | self.statement.span.start_token 77 | } 78 | } 79 | 80 | fn get_end_token(&self) -> TokenId { 81 | self.statement.span.end_token 82 | } 83 | } 84 | 85 | impl HasTokenSpan for LabeledSequentialStatement { 86 | fn get_start_token(&self) -> TokenId { 87 | if let Some(label) = &self.label.tree { 88 | label.token 89 | } else { 90 | self.statement.span.start_token 91 | } 92 | } 93 | 94 | fn get_end_token(&self) -> TokenId { 95 | self.statement.span.end_token 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /vhdl_lang/src/ast/token_range.rs: -------------------------------------------------------------------------------- 1 | /// For most applications in the context of a language server, 2 | /// the lexical position (i.e., a position in the source code) 3 | /// of all AST nodes must be known. 4 | /// In the context of `vhdl_lang`, this information is provided 5 | /// using Token information. Each AST element knows the token span that it was declared in. 6 | /// Information, such as the position can be queried using the `pos(TokenAccess)` method. 7 | /// A [TokenAccess] is a context object that is passed in all relevant operations 8 | /// (i.e., when traversing the AST using the [Search] trait 9 | /// or when getting the source code information when generating code outlines in a language server). 10 | /// This is also the mechanic used to extract supplementary information, such as comments for 11 | /// documentation generation. 12 | use crate::{SrcPos, TokenAccess, TokenId, TokenSpan}; 13 | 14 | /// A struct that associates some generic item to a single token. 15 | #[derive(Eq, PartialEq, Debug, Clone)] 16 | pub struct WithToken { 17 | pub item: T, 18 | pub token: TokenId, 19 | } 20 | 21 | impl WithToken { 22 | pub fn new(item: T, token: TokenId) -> WithToken { 23 | WithToken { item, token } 24 | } 25 | 26 | /// Retrieves the position of this object using the provided `TokenAccess`. 27 | pub fn pos<'a>(&'a self, ctx: &'a dyn TokenAccess) -> &'a SrcPos { 28 | ctx.get_pos(self.token) 29 | } 30 | 31 | /// Maps this element into another element applying the given function 32 | /// but retaining the source location (i.e., the token). 33 | pub(crate) fn map_into(self, f: F) -> WithToken 34 | where 35 | F: FnOnce(T) -> U, 36 | { 37 | WithToken { 38 | item: f(self.item), 39 | token: self.token, 40 | } 41 | } 42 | 43 | /// Maps this element into another element applying the given function 44 | /// but retaining the source location. 45 | /// The returned object's `TokenSpan` will have this token id as start and end. 46 | pub(crate) fn map_into_span(self, f: F) -> WithTokenSpan 47 | where 48 | F: FnOnce(T) -> U, 49 | { 50 | WithTokenSpan { 51 | item: f(self.item), 52 | span: self.token.into(), 53 | } 54 | } 55 | } 56 | 57 | /// A struct that associates some generic item to a contiguous span of tokens. 58 | #[derive(PartialEq, Eq, Clone, Debug)] 59 | pub struct WithTokenSpan { 60 | pub item: T, 61 | pub span: TokenSpan, 62 | } 63 | 64 | impl WithTokenSpan { 65 | pub fn new(item: T, span: TokenSpan) -> WithTokenSpan { 66 | WithTokenSpan { item, span } 67 | } 68 | 69 | pub fn from(item: T, span: impl Into) -> WithTokenSpan { 70 | WithTokenSpan { 71 | item, 72 | span: span.into(), 73 | } 74 | } 75 | 76 | /// Retrieves the position of this object using the provided `TokenAccess`. 77 | pub fn pos(&self, ctx: &dyn TokenAccess) -> SrcPos { 78 | self.span.pos(ctx) 79 | } 80 | 81 | /// Maps this element into another element applying the given function 82 | /// but retaining the source location (i.e., the token span). 83 | pub(crate) fn map_into(self, f: F) -> WithTokenSpan 84 | where 85 | F: FnOnce(T) -> U, 86 | { 87 | WithTokenSpan { 88 | item: f(self.item), 89 | span: self.span, 90 | } 91 | } 92 | 93 | /// Attempts to map this element into another element applying the given function. 94 | /// If the function returns `None`, this will also return `None`. 95 | /// Otherwise, the semantics are the same as [map_into](WithTokenSpan::map_into) 96 | pub(crate) fn try_map_into(self, f: F) -> Option> 97 | where 98 | F: FnOnce(T) -> Option, 99 | { 100 | Some(WithTokenSpan { 101 | item: f(self.item)?, 102 | span: self.span, 103 | }) 104 | } 105 | 106 | /// Returns a new `WithTokenSpan` object that encompasses this item 107 | /// but extends the token span starting with the given token. 108 | pub(crate) fn start_with(self, id: TokenId) -> Self { 109 | WithTokenSpan { 110 | item: self.item, 111 | span: self.span.start_with(id), 112 | } 113 | } 114 | 115 | pub fn as_ref(&self) -> WithTokenSpan<&T> { 116 | WithTokenSpan { 117 | item: &self.item, 118 | span: self.span, 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /vhdl_lang/src/completion.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use crate::analysis::DesignRoot; 8 | use crate::ast::{AttributeDesignator, Designator}; 9 | use crate::completion::attributes::completions_for_attribute_name; 10 | use crate::completion::generic::generic_completions; 11 | use crate::completion::libraries::list_all_libraries; 12 | use crate::completion::map_aspect::completions_for_map_aspect; 13 | use crate::completion::selected::completions_for_selected_name; 14 | use crate::completion::tokenizer::tokenize_input; 15 | use crate::syntax::Kind; 16 | use crate::{EntRef, Position, Source}; 17 | 18 | mod attributes; 19 | mod entity_instantiation; 20 | mod generic; 21 | mod libraries; 22 | mod map_aspect; 23 | mod region; 24 | mod selected; 25 | mod tokenizer; 26 | 27 | #[derive(Debug, PartialEq, Clone)] 28 | pub enum CompletionItem<'a> { 29 | /// Simply complete the entities 30 | /// e.g., `use std.` should simply list all elements in the std library 31 | Simple(EntRef<'a>), 32 | /// Formal parameter, e.g., in a port map 33 | /// `port map (` might choose to complete ` => $1` 34 | Formal(EntRef<'a>), 35 | /// Multiple overloaded items are applicable. 36 | /// The argument is the count of overloaded items in total. 37 | Overloaded(Designator, usize), 38 | /// Complete a keyword 39 | Keyword(Kind), 40 | /// Complete the 'work' library. 41 | /// This is handled in a special manner because the 42 | /// actual work library (using [CompletionItem::Simple] would complete the actual name 43 | /// of the library, not the string 'work'. 44 | Work, 45 | /// Entity or component instantiation, i.e., 46 | /// ```vhdl 47 | /// my_ent: entity work.foo 48 | /// generic map ( 49 | /// -- ... 50 | /// ) 51 | /// port map ( 52 | /// -- ... 53 | /// ); 54 | /// ``` 55 | /// 56 | /// The second argument is a vector of architectures that are associated 57 | /// to the entity, if the first argument is an entity. 58 | /// 59 | /// For a component instantiation, the first argument is a reference to the 60 | /// component. The second argument will always be empty. 61 | Instantiation(EntRef<'a>, Vec>), 62 | /// Complete an attribute designator (i.e. `'range`, `'stable`, ...) 63 | Attribute(AttributeDesignator), 64 | } 65 | 66 | macro_rules! kind { 67 | ($kind: pat) => { 68 | crate::syntax::Token { kind: $kind, .. } 69 | }; 70 | } 71 | 72 | /// Main entry point for completion. Given a source-file and a cursor position, 73 | /// lists available completion options at the cursor position. 74 | pub fn list_completion_options<'a>( 75 | root: &'a DesignRoot, 76 | source: &Source, 77 | cursor: Position, 78 | ) -> Vec> { 79 | use crate::syntax::Kind::*; 80 | let tokens = tokenize_input(root.symbols(), source, cursor); 81 | match &tokens[..] { 82 | // With the current implementation of completions, this is annoying, rather than helpful. 83 | // SemiColons will try to complete the ';' character, which when pressing enter will cause 84 | // ';' to appear instead of a simple ; character. 85 | // Therefore, we do not return any completions here. 86 | [.., kind!(SemiColon)] => vec![], 87 | [.., kind!(Library)] 88 | | [.., kind!(Library), kind!(Identifier)] 89 | | [.., kind!(Use)] 90 | | [.., kind!(Use), kind!(Identifier)] => list_all_libraries(root), 91 | [.., token, kind!(Dot)] | [.., token, kind!(Dot), kind!(Identifier)] => { 92 | // get the entity before the token. 93 | // We rely on the syntax parsing to be resilient enough for this to yield a reasonable value. 94 | // Otherwise, we just return an empty value. 95 | if let Some((_, ent)) = root.item_at_cursor(source, token.pos.start()) { 96 | completions_for_selected_name(root, ent) 97 | } else { 98 | vec![] 99 | } 100 | } 101 | [.., token, kind!(Tick)] | [.., token, kind!(Tick), kind!(Identifier)] => { 102 | if let Some((_, ent)) = root.item_at_cursor(source, token.pos.start()) { 103 | completions_for_attribute_name(ent) 104 | } else { 105 | vec![] 106 | } 107 | } 108 | [.., kind!(LeftPar | Comma)] | [.., kind!(LeftPar | Comma), kind!(Identifier)] => { 109 | completions_for_map_aspect(root, cursor, source) 110 | } 111 | [.., kind!(CommAt)] | [.., kind!(CommAt), kind!(Identifier)] => list_all_libraries(root), 112 | _ => generic_completions(root, cursor, source), 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /vhdl_lang/src/completion/attributes.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 6 | use crate::ast::{AttributeDesignator, ObjectClass, RangeAttribute, TypeAttribute}; 7 | use crate::{named_entity, AnyEntKind, CompletionItem, EntRef, Object}; 8 | 9 | /// Produces completions for an attribute name, i.e., 10 | /// `foo'` 11 | /// The provided ent is the entity directly before the tick, i.e., 12 | /// `foo` in the example above. 13 | pub(crate) fn completions_for_attribute_name(ent: EntRef<'_>) -> Vec> { 14 | let mut attributes: Vec = Vec::new(); 15 | attributes.extend([ 16 | AttributeDesignator::SimpleName, 17 | AttributeDesignator::InstanceName, 18 | AttributeDesignator::PathName, 19 | ]); 20 | 21 | match ent.kind() { 22 | AnyEntKind::Type(typ) => extend_attributes_of_type(typ, &mut attributes), 23 | AnyEntKind::Object(obj) => extend_attributes_of_objects(obj, &mut attributes), 24 | AnyEntKind::View(_) => attributes.push(AttributeDesignator::Converse), 25 | _ => {} 26 | } 27 | attributes 28 | .into_iter() 29 | .map(CompletionItem::Attribute) 30 | .chain( 31 | ent.attrs 32 | .values() 33 | .map(|(_, b)| b) 34 | .map(|ent| CompletionItem::Simple(ent.ent)), 35 | ) 36 | .collect() 37 | } 38 | 39 | /// Extends applicable attributes when the attribute name is a type. 40 | fn extend_attributes_of_type( 41 | typ: &named_entity::Type<'_>, 42 | attributes: &mut Vec, 43 | ) { 44 | use AttributeDesignator::*; 45 | if typ.is_scalar() { 46 | attributes.extend([Left, Right, Low, High, Ascending, Image, Value]); 47 | } else if typ.is_array() { 48 | attributes.extend([ 49 | Left, 50 | Right, 51 | Low, 52 | High, 53 | Range(RangeAttribute::Range), 54 | Range(RangeAttribute::ReverseRange), 55 | Length, 56 | Ascending, 57 | Type(TypeAttribute::Element), 58 | ]); 59 | } 60 | if typ.is_discrete() { 61 | attributes.extend([Pos, Val, Succ, Pred, LeftOf, RightOf]); 62 | } 63 | } 64 | 65 | /// Extends applicable attributes when the attribute name is an object. 66 | fn extend_attributes_of_objects(obj: &Object<'_>, attributes: &mut Vec) { 67 | extend_attributes_of_type(obj.subtype.type_mark().base_type().kind(), attributes); 68 | attributes.push(AttributeDesignator::Type(TypeAttribute::Subtype)); 69 | if obj.class == ObjectClass::Signal { 70 | use crate::ast::SignalAttribute::*; 71 | attributes.extend( 72 | [ 73 | Delayed, 74 | Stable, 75 | Quiet, 76 | Transaction, 77 | Event, 78 | Active, 79 | LastEvent, 80 | LastActive, 81 | LastValue, 82 | Driving, 83 | DrivingValue, 84 | ] 85 | .map(AttributeDesignator::Signal), 86 | ); 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use crate::analysis::tests::LibraryBuilder; 93 | use crate::ast::RangeAttribute; 94 | use crate::list_completion_options; 95 | use crate::syntax::test::assert_eq_unordered; 96 | use crate::CompletionItem; 97 | 98 | #[test] 99 | pub fn completes_attributes() { 100 | use crate::ast::AttributeDesignator::*; 101 | use crate::ast::TypeAttribute::*; 102 | 103 | let mut builder = LibraryBuilder::new(); 104 | let code = builder.code( 105 | "libA", 106 | "\ 107 | package my_pkg is 108 | constant foo : BIT_VECTOR := \"001\"; 109 | constant bar: NATURAL := foo' 110 | end package; 111 | ", 112 | ); 113 | 114 | let (root, _) = builder.get_analyzed_root(); 115 | let cursor = code.s1("foo'").end(); 116 | let options = list_completion_options(&root, code.source(), cursor); 117 | 118 | let expected_options = [ 119 | Type(Element), 120 | Type(Subtype), 121 | Range(RangeAttribute::Range), 122 | Range(RangeAttribute::ReverseRange), 123 | Ascending, 124 | Left, 125 | Right, 126 | High, 127 | Low, 128 | Length, 129 | InstanceName, 130 | SimpleName, 131 | PathName, 132 | ] 133 | .map(CompletionItem::Attribute); 134 | 135 | assert_eq_unordered(&options, &expected_options); 136 | } 137 | 138 | #[test] 139 | pub fn completes_signal_attributes() { 140 | use crate::ast::AttributeDesignator::*; 141 | use crate::ast::SignalAttribute::*; 142 | use crate::ast::TypeAttribute::*; 143 | 144 | let mut builder = LibraryBuilder::new(); 145 | let code = builder.code( 146 | "libA", 147 | "\ 148 | package my_pkg is 149 | signal foo : BIT_VECTOR := \"001\"; 150 | signal bar: NATURAL := foo' 151 | end package; 152 | ", 153 | ); 154 | 155 | let (root, _) = builder.get_analyzed_root(); 156 | let cursor = code.s1("foo'").end(); 157 | let options = list_completion_options(&root, code.source(), cursor); 158 | 159 | let expected_options = [ 160 | Type(Element), 161 | Type(Subtype), 162 | Range(RangeAttribute::Range), 163 | Range(RangeAttribute::ReverseRange), 164 | Signal(Delayed), 165 | Signal(Stable), 166 | Signal(Quiet), 167 | Signal(Transaction), 168 | Signal(Event), 169 | Signal(Active), 170 | Signal(LastEvent), 171 | Signal(LastActive), 172 | Signal(LastValue), 173 | Signal(Driving), 174 | Signal(DrivingValue), 175 | Ascending, 176 | Left, 177 | Right, 178 | High, 179 | Low, 180 | Length, 181 | InstanceName, 182 | SimpleName, 183 | PathName, 184 | ] 185 | .map(CompletionItem::Attribute); 186 | 187 | assert_eq_unordered(&options, &expected_options); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /vhdl_lang/src/completion/generic.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 6 | use crate::ast::search::{ 7 | DeclarationItem, Finished, Found, FoundDeclaration, NotFinished, SearchState, Searcher, 8 | }; 9 | use crate::ast::ArchitectureBody; 10 | use crate::completion::entity_instantiation::get_visible_entities_from_architecture; 11 | use crate::completion::region::completion_items_from_region; 12 | use crate::named_entity::{DesignEnt, Visibility}; 13 | use crate::{CompletionItem, Design, HasTokenSpan, Position, Source, TokenAccess}; 14 | use itertools::{chain, Itertools}; 15 | use vhdl_lang::analysis::DesignRoot; 16 | 17 | pub(crate) fn generic_completions<'a>( 18 | root: &'a DesignRoot, 19 | cursor: Position, 20 | source: &Source, 21 | ) -> Vec> { 22 | let mut searcher = CompletionSearcher::new(cursor, root); 23 | let _ = root.search_source(source, &mut searcher); 24 | searcher.completions 25 | } 26 | 27 | /// This is the most general-purpose completion provider. 28 | /// This provider publishes all visible symbols reachable from some context. 29 | /// This will, among other things produce many "non-regular" symbols, such as 30 | /// operator symbols or specific characters. If possible, 31 | /// this searcher should therefore be avoided in favor of a more specific completion provider. 32 | struct CompletionSearcher<'a> { 33 | root: &'a DesignRoot, 34 | cursor: Position, 35 | completions: Vec>, 36 | } 37 | 38 | impl<'a> CompletionSearcher<'a> { 39 | pub fn new(cursor: Position, design_root: &'a DesignRoot) -> CompletionSearcher<'a> { 40 | CompletionSearcher { 41 | root: design_root, 42 | cursor, 43 | completions: Vec::new(), 44 | } 45 | } 46 | } 47 | 48 | impl CompletionSearcher<'_> { 49 | /// Add entity instantiation completions that are visible from within an architecture body 50 | fn add_entity_instantiations(&mut self, body: &ArchitectureBody) { 51 | let Some(ent_id) = body.ident.decl.get() else { 52 | return; 53 | }; 54 | let Some(ent) = DesignEnt::from_any(self.root.get_ent(ent_id)) else { 55 | return; 56 | }; 57 | self.completions 58 | .extend(get_visible_entities_from_architecture(self.root, &ent)); 59 | } 60 | } 61 | 62 | impl Searcher for CompletionSearcher<'_> { 63 | fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration<'_>) -> SearchState { 64 | let ent_id = match &decl.ast { 65 | DeclarationItem::Entity(ent_decl) => { 66 | if !ent_decl.get_pos(ctx).contains(self.cursor) { 67 | return NotFinished; 68 | } 69 | ent_decl.ident.decl.get() 70 | } 71 | DeclarationItem::Architecture(body) => { 72 | if !body.get_pos(ctx).contains(self.cursor) { 73 | return NotFinished; 74 | } 75 | if body.statement_span().get_pos(ctx).contains(self.cursor) { 76 | self.add_entity_instantiations(body); 77 | } 78 | body.ident.decl.get() 79 | } 80 | DeclarationItem::Package(package) => { 81 | if !package.get_pos(ctx).contains(self.cursor) { 82 | return NotFinished; 83 | } 84 | package.ident.decl.get() 85 | } 86 | DeclarationItem::PackageBody(package) => { 87 | if !package.get_pos(ctx).contains(self.cursor) { 88 | return NotFinished; 89 | } 90 | package.ident.decl.get() 91 | } 92 | DeclarationItem::Subprogram(subprogram) => { 93 | if !subprogram.get_pos(ctx).contains(self.cursor) { 94 | return NotFinished; 95 | } 96 | self.completions.extend( 97 | subprogram 98 | .declarations 99 | .iter() 100 | .flat_map(|decl| decl.item.declarations()) 101 | .map(|id| CompletionItem::Simple(self.root.get_ent(id))), 102 | ); 103 | return NotFinished; 104 | } 105 | _ => return NotFinished, 106 | }; 107 | let Some(ent_id) = ent_id else { 108 | return Finished(Found); 109 | }; 110 | let Some(ent) = DesignEnt::from_any(self.root.get_ent(ent_id)) else { 111 | return Finished(Found); 112 | }; 113 | self.completions 114 | .extend(visible_entities_from(self.root, ent.kind())); 115 | NotFinished 116 | } 117 | } 118 | 119 | fn visible_entities_from<'a>( 120 | root: &'a DesignRoot, 121 | design: &'a Design<'a>, 122 | ) -> Vec> { 123 | use Design::*; 124 | match design { 125 | Entity(visibility, region) 126 | | UninstPackage(visibility, region) 127 | | Architecture(visibility, region, _) 128 | | Package(visibility, region) 129 | | PackageBody(visibility, region) => chain( 130 | completion_items_from_region(root, region), 131 | completion_items_from_visibility(root, visibility), 132 | ) 133 | .collect_vec(), 134 | PackageInstance(region) | InterfacePackageInstance(region) | Context(region) => { 135 | completion_items_from_region(root, region).collect_vec() 136 | } 137 | Configuration => vec![], 138 | } 139 | } 140 | 141 | fn completion_items_from_visibility<'a>( 142 | root: &'a DesignRoot, 143 | visibility: &'a Visibility<'a>, 144 | ) -> impl Iterator> { 145 | visibility 146 | .visible() 147 | .unique() 148 | .map(CompletionItem::Simple) 149 | .chain( 150 | visibility.all_in_region().flat_map(|visible_region| { 151 | completion_items_from_region(root, visible_region.region()) 152 | }), 153 | ) 154 | } 155 | -------------------------------------------------------------------------------- /vhdl_lang/src/completion/libraries.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 6 | use crate::analysis::DesignRoot; 7 | use crate::CompletionItem; 8 | use std::iter::once; 9 | 10 | /// Produces all available libraries. 11 | pub(crate) fn list_all_libraries(root: &DesignRoot) -> Vec> { 12 | root.libraries() 13 | .map(|lib| CompletionItem::Simple(root.get_ent(lib.id()))) 14 | .chain(once(CompletionItem::Work)) 15 | .collect() 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use crate::analysis::tests::{Code, LibraryBuilder}; 21 | use crate::syntax::test::assert_eq_unordered; 22 | use crate::{list_completion_options, CompletionItem}; 23 | 24 | #[test] 25 | pub fn completing_libraries() { 26 | let input = LibraryBuilder::new(); 27 | let code = Code::new("library "); 28 | let (root, _) = input.get_analyzed_root(); 29 | let cursor = code.end(); 30 | let options = list_completion_options(&root, code.source(), cursor); 31 | assert_eq_unordered( 32 | &options, 33 | &[ 34 | CompletionItem::Simple( 35 | root.get_ent(root.get_lib(&root.symbol_utf8("std")).unwrap().id()), 36 | ), 37 | CompletionItem::Work, 38 | ], 39 | ) 40 | } 41 | 42 | #[test] 43 | pub fn completes_external_library_names() { 44 | let input = LibraryBuilder::new(); 45 | let code = Code::new("<< signal @"); 46 | let (root, _) = input.get_analyzed_root(); 47 | let cursor = code.end(); 48 | let options = list_completion_options(&root, code.source(), cursor); 49 | assert_eq_unordered( 50 | &options, 51 | &[ 52 | CompletionItem::Simple( 53 | root.get_ent(root.get_lib(&root.symbol_utf8("std")).unwrap().id()), 54 | ), 55 | CompletionItem::Work, 56 | ], 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /vhdl_lang/src/completion/region.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use crate::analysis::DesignRoot; 8 | use crate::completion::entity_instantiation::get_architectures_for_entity; 9 | use crate::named_entity::{AsUnique, NamedEntities, Region}; 10 | use crate::{AnyEntKind, CompletionItem, Design}; 11 | use vhdl_lang::EntRef; 12 | 13 | pub(crate) fn completion_items_from_region<'a>( 14 | root: &'a DesignRoot, 15 | region: &'a Region<'a>, 16 | ) -> impl Iterator> { 17 | region 18 | .entities 19 | .values() 20 | .map(|entities| named_entities_to_completion_item(root, entities)) 21 | } 22 | 23 | fn named_entities_to_completion_item<'a>( 24 | root: &'a DesignRoot, 25 | named_entities: &'a NamedEntities<'a>, 26 | ) -> CompletionItem<'a> { 27 | match named_entities { 28 | NamedEntities::Single(ent) => any_ent_to_completion_item(ent, root), 29 | NamedEntities::Overloaded(overloaded) => match overloaded.as_unique() { 30 | None => CompletionItem::Overloaded(overloaded.designator().clone(), overloaded.len()), 31 | Some(ent) => CompletionItem::Simple(ent), 32 | }, 33 | } 34 | } 35 | 36 | pub(crate) fn any_ent_to_completion_item<'a>( 37 | ent: EntRef<'a>, 38 | root: &'a DesignRoot, 39 | ) -> CompletionItem<'a> { 40 | match ent.kind() { 41 | AnyEntKind::Design(Design::Entity(..)) | AnyEntKind::Component(_) => { 42 | let architectures = get_architectures_for_entity(ent, root); 43 | CompletionItem::Instantiation(ent, architectures) 44 | } 45 | _ => CompletionItem::Simple(ent), 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /vhdl_lang/src/completion/tokenizer.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 6 | use crate::data::ContentReader; 7 | use crate::syntax::{Symbols, Tokenizer}; 8 | use crate::{Position, Source, Token}; 9 | 10 | /// Tokenizes `source` up to `cursor` but no further. The last token returned is the token 11 | /// where the cursor currently resides or the token right before the cursor. 12 | /// 13 | /// Examples: 14 | /// 15 | /// input = "use ieee.std_logic_1|164.a" 16 | /// ^ cursor position 17 | /// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164} 18 | /// 19 | /// input = "use ieee.std_logic_1164|.a" 20 | /// ^ cursor position 21 | /// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164} 22 | /// 23 | /// input = "use ieee.std_logic_1164.|a" 24 | /// ^ cursor position 25 | /// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164, DOT} 26 | /// input = "use ieee.std_logic_1164.a|" 27 | /// ^ cursor position 28 | /// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164, DOT, a} 29 | /// 30 | /// On error, or if the source is empty, returns an empty vector. 31 | pub(crate) fn tokenize_input(symbols: &Symbols, source: &Source, cursor: Position) -> Vec { 32 | let contents = source.contents(); 33 | let mut tokenizer = Tokenizer::new(symbols, source, ContentReader::new(&contents)); 34 | let mut tokens = Vec::new(); 35 | loop { 36 | match tokenizer.pop() { 37 | Ok(Some(token)) => { 38 | if token.pos.start() >= cursor { 39 | break; 40 | } 41 | tokens.push(token); 42 | } 43 | Ok(None) => break, 44 | Err(_) => return vec![], 45 | } 46 | } 47 | tokens 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | use crate::syntax::test::Code; 54 | use crate::syntax::Kind::*; 55 | use assert_matches::assert_matches; 56 | 57 | macro_rules! kind { 58 | ($kind: pat) => { 59 | crate::syntax::Token { kind: $kind, .. } 60 | }; 61 | } 62 | 63 | #[test] 64 | fn tokenizing_an_empty_input() { 65 | let input = Code::new(""); 66 | let tokens = tokenize_input(&input.symbols, input.source(), Position::new(0, 0)); 67 | assert_eq!(tokens.len(), 0); 68 | } 69 | 70 | #[test] 71 | fn tokenizing_stops_at_the_cursors_position() { 72 | let input = Code::new("use ieee.std_logic_1164.all"); 73 | let mut cursor = input.s1("std_logic_11").pos().end(); 74 | let tokens = tokenize_input(&input.symbols, input.source(), cursor); 75 | assert_matches!( 76 | tokens[..], 77 | [kind!(Use), kind!(Identifier), kind!(Dot), kind!(Identifier)] 78 | ); 79 | cursor = input.s1("std_logic_1164").pos().end(); 80 | let tokens = tokenize_input(&input.symbols, input.source(), cursor); 81 | assert_matches!( 82 | tokens[..], 83 | [kind!(Use), kind!(Identifier), kind!(Dot), kind!(Identifier)] 84 | ); 85 | cursor = input.s1("std_logic_1164.").pos().end(); 86 | let tokens = tokenize_input(&input.symbols, input.source(), cursor); 87 | assert_matches!( 88 | tokens[..], 89 | [ 90 | kind!(Use), 91 | kind!(Identifier), 92 | kind!(Dot), 93 | kind!(Identifier), 94 | kind!(Dot) 95 | ] 96 | ); 97 | cursor = input.s1("std_logic_1164.all").pos().end(); 98 | let tokens = tokenize_input(&input.symbols, input.source(), cursor); 99 | assert_matches!( 100 | tokens[..], 101 | [ 102 | kind!(Use), 103 | kind!(Identifier), 104 | kind!(Dot), 105 | kind!(Identifier), 106 | kind!(Dot), 107 | kind!(All) 108 | ] 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /vhdl_lang/src/data.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // You can obtain one at http://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com 7 | 8 | mod contents; 9 | mod diagnostic; 10 | pub mod error_codes; 11 | mod latin_1; 12 | mod message; 13 | mod source; 14 | mod symbol_table; 15 | 16 | pub use contents::*; 17 | pub use diagnostic::*; 18 | pub use error_codes::*; 19 | pub use latin_1::*; 20 | pub use message::*; 21 | pub use source::*; 22 | pub use symbol_table::*; 23 | -------------------------------------------------------------------------------- /vhdl_lang/src/data/message.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use std::path::Path; 8 | use strum::AsRefStr; 9 | 10 | #[derive(Debug, PartialEq, Eq, AsRefStr)] 11 | #[strum(serialize_all = "snake_case")] 12 | pub enum MessageType { 13 | Error, 14 | Warning, 15 | Info, 16 | Log, 17 | } 18 | 19 | #[must_use] 20 | #[derive(Debug, PartialEq, Eq)] 21 | pub struct Message { 22 | pub message_type: MessageType, 23 | pub message: String, 24 | } 25 | 26 | impl Message { 27 | pub fn log(message: impl Into) -> Message { 28 | Message { 29 | message_type: MessageType::Log, 30 | message: message.into(), 31 | } 32 | } 33 | 34 | pub fn info(message: impl Into) -> Message { 35 | Message { 36 | message_type: MessageType::Info, 37 | message: message.into(), 38 | } 39 | } 40 | 41 | pub fn warning(message: impl Into) -> Message { 42 | Message { 43 | message_type: MessageType::Warning, 44 | message: message.into(), 45 | } 46 | } 47 | 48 | pub fn error(message: impl Into) -> Message { 49 | Message { 50 | message_type: MessageType::Error, 51 | message: message.into(), 52 | } 53 | } 54 | 55 | pub fn file_error(message: impl Into, file_name: &Path) -> Message { 56 | Message { 57 | message_type: MessageType::Error, 58 | message: format!( 59 | "{} (In file {})", 60 | message.into(), 61 | file_name.to_string_lossy() 62 | ), 63 | } 64 | } 65 | } 66 | 67 | impl std::fmt::Display for Message { 68 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 69 | write!(f, "{}: {}", self.message_type.as_ref(), self.message) 70 | } 71 | } 72 | 73 | pub trait MessageHandler { 74 | fn push(&mut self, message: Message); 75 | } 76 | 77 | impl MessageHandler for Vec { 78 | fn push(&mut self, message: Message) { 79 | self.push(message) 80 | } 81 | } 82 | 83 | #[derive(Default)] 84 | pub struct MessagePrinter {} 85 | 86 | impl MessageHandler for MessagePrinter { 87 | fn push(&mut self, message: Message) { 88 | println!("{message}"); 89 | } 90 | } 91 | 92 | #[derive(Default)] 93 | pub struct NullMessages; 94 | 95 | impl MessageHandler for NullMessages { 96 | fn push(&mut self, _message: Message) { 97 | // Ignore 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /vhdl_lang/src/formatting/architecture.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // You can obtain one at http://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 7 | 8 | use crate::ast::ArchitectureBody; 9 | use crate::formatting::buffer::Buffer; 10 | use crate::formatting::VHDLFormatter; 11 | use crate::{indented, HasTokenSpan, TokenSpan}; 12 | 13 | impl VHDLFormatter<'_> { 14 | pub fn format_architecture(&self, arch: &ArchitectureBody, buffer: &mut Buffer) { 15 | self.format_context_clause(&arch.context_clause, buffer); 16 | if let Some(item) = arch.context_clause.last() { 17 | self.line_break_preserve_whitespace(item.span().end_token, buffer); 18 | } 19 | let span = arch.span(); 20 | // architecture of is 21 | self.format_token_span(TokenSpan::new(span.start_token, arch.is_token()), buffer); 22 | indented!(buffer, { self.format_declarations(&arch.decl, buffer) }); 23 | buffer.line_break(); 24 | self.format_token_id(arch.begin_token, buffer); 25 | indented!(buffer, { 26 | self.format_concurrent_statements(&arch.statements, buffer); 27 | }); 28 | buffer.line_break(); 29 | // end [architecture] [name]; 30 | self.format_token_span(TokenSpan::new(arch.end_token, span.end_token - 1), buffer); 31 | self.format_token_id(span.end_token, buffer); 32 | } 33 | } 34 | 35 | #[cfg(test)] 36 | mod test { 37 | use crate::syntax::test::Code; 38 | use vhdl_lang::formatting::test_utils::check_formatted; 39 | 40 | fn check_architecture_formatted(input: &str) { 41 | check_formatted( 42 | input, 43 | input, 44 | Code::architecture_body, 45 | |formatter, arch, buffer| formatter.format_architecture(arch, buffer), 46 | ) 47 | } 48 | 49 | #[test] 50 | fn format_empty_architecture() { 51 | check_architecture_formatted( 52 | "\ 53 | architecture foo of bar is 54 | begin 55 | end foo;", 56 | ); 57 | 58 | check_architecture_formatted( 59 | "\ 60 | architecture foo of bar is 61 | begin 62 | end architecture foo;", 63 | ); 64 | 65 | check_architecture_formatted( 66 | "\ 67 | architecture foo of bar is 68 | begin 69 | end;", 70 | ); 71 | } 72 | 73 | #[test] 74 | fn format_architecture_with_declarations() { 75 | check_architecture_formatted( 76 | "\ 77 | architecture foo of bar is 78 | constant x: foo := bar; 79 | begin 80 | end foo;", 81 | ); 82 | 83 | check_architecture_formatted( 84 | "\ 85 | architecture foo of bar is 86 | constant x: foo := bar; 87 | signal y: bar := foobar; 88 | begin 89 | end foo;", 90 | ); 91 | } 92 | 93 | #[test] 94 | fn format_full_architecture() { 95 | check_architecture_formatted( 96 | "\ 97 | architecture foo of bar is 98 | constant x: foo := bar; 99 | signal y: bar := foobar; 100 | begin 101 | bar: process(clk) is 102 | variable z: baz; 103 | begin 104 | if rising_edge(clk) then 105 | if rst = '1' then 106 | foo <= '0'; 107 | else 108 | foo <= bar and baz; 109 | end if; 110 | end if; 111 | end process bar; 112 | y <= x; -- An assignment 113 | end foo;", 114 | ); 115 | } 116 | 117 | #[test] 118 | fn format_architecture_preserve_whitespace() { 119 | check_architecture_formatted( 120 | "\ 121 | architecture foo of bar is 122 | constant x: foo := bar; 123 | constant baz: char := '1'; 124 | 125 | signal y: bar := foobar; 126 | signal foobar: std_logic := 'Z'; 127 | 128 | shared variable sv: natural; 129 | begin 130 | end foo;", 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /vhdl_lang/src/formatting/constraint.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // You can obtain one at http://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 7 | 8 | use crate::ast::token_range::WithTokenSpan; 9 | use crate::ast::{DiscreteRange, SubtypeConstraint}; 10 | use crate::formatting::buffer::Buffer; 11 | use crate::formatting::VHDLFormatter; 12 | use crate::syntax::Kind; 13 | use crate::TokenAccess; 14 | use vhdl_lang::ast::{ElementConstraint, Range, RangeConstraint}; 15 | 16 | impl VHDLFormatter<'_> { 17 | pub fn format_subtype_constraint( 18 | &self, 19 | constraint: &WithTokenSpan, 20 | buffer: &mut Buffer, 21 | ) { 22 | match &constraint.item { 23 | SubtypeConstraint::Range(range) => { 24 | self.format_token_id(constraint.span.start_token, buffer); 25 | buffer.push_whitespace(); 26 | self.format_range(range, buffer) 27 | } 28 | SubtypeConstraint::Array(ranges, opt_constraint) => { 29 | self.format_token_id(constraint.span.start_token, buffer); 30 | if ranges.is_empty() { 31 | // open 32 | self.format_token_id(constraint.span.start_token + 1, buffer); 33 | } 34 | for range in ranges { 35 | self.format_discrete_range(&range.item, buffer); 36 | if self 37 | .tokens 38 | .get_token(range.span.end_token + 1) 39 | .is_some_and(|token| token.kind == Kind::Comma) 40 | { 41 | self.format_token_id(range.span.end_token + 1, buffer); 42 | buffer.push_whitespace(); 43 | } 44 | } 45 | if let Some(constraint) = opt_constraint { 46 | self.format_token_id(constraint.span.start_token - 1, buffer); 47 | self.format_subtype_constraint(constraint, buffer); 48 | } else { 49 | self.format_token_id(constraint.span.end_token, buffer); 50 | } 51 | } 52 | SubtypeConstraint::Record(records) => { 53 | self.format_token_id(constraint.span.start_token, buffer); 54 | for record in records { 55 | self.format_element_constraint(record, buffer); 56 | if self 57 | .tokens 58 | .get_token(record.constraint.span.end_token + 1) 59 | .is_some_and(|token| token.kind == Kind::Comma) 60 | { 61 | self.format_token_id(record.constraint.span.end_token + 1, buffer); 62 | buffer.push_whitespace(); 63 | } 64 | } 65 | self.format_token_id(constraint.span.end_token, buffer); 66 | } 67 | } 68 | } 69 | 70 | pub fn format_element_constraint(&self, constraint: &ElementConstraint, buffer: &mut Buffer) { 71 | self.format_token_id(constraint.ident.token, buffer); 72 | self.format_subtype_constraint(&constraint.constraint, buffer); 73 | } 74 | 75 | pub fn format_range_constraint(&self, constraint: &RangeConstraint, buffer: &mut Buffer) { 76 | self.format_expression(constraint.left_expr.as_ref().as_ref(), buffer); 77 | buffer.push_whitespace(); 78 | self.format_token_id(constraint.direction_token(), buffer); 79 | buffer.push_whitespace(); 80 | self.format_expression(constraint.right_expr.as_ref().as_ref(), buffer); 81 | } 82 | 83 | pub fn format_range(&self, range: &Range, buffer: &mut Buffer) { 84 | match range { 85 | Range::Range(constraint) => self.format_range_constraint(constraint, buffer), 86 | Range::Attribute(attribute) => self.format_attribute_name(attribute, buffer), 87 | } 88 | } 89 | 90 | pub fn format_discrete_range(&self, range: &DiscreteRange, buffer: &mut Buffer) { 91 | match range { 92 | DiscreteRange::Discrete(name, range) => { 93 | self.format_name(name.as_ref(), buffer); 94 | if let Some(range) = range { 95 | buffer.push_whitespace(); 96 | // range 97 | self.format_token_id(name.span.end_token + 1, buffer); 98 | buffer.push_whitespace(); 99 | self.format_range(range, buffer); 100 | } 101 | } 102 | DiscreteRange::Range(range) => self.format_range(range, buffer), 103 | } 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod test { 109 | use crate::formatting::buffer::Buffer; 110 | use crate::formatting::test_utils::check_formatted; 111 | use crate::formatting::VHDLFormatter; 112 | use crate::syntax::test::Code; 113 | 114 | fn check_range(input: &str) { 115 | let code = Code::new(input); 116 | let range = code.range(); 117 | let tokens = code.tokenize(); 118 | let formatter = VHDLFormatter::new(&tokens); 119 | let mut buffer = Buffer::new(); 120 | formatter.format_range(&range, &mut buffer); 121 | assert_eq!(buffer.as_str(), input); 122 | } 123 | 124 | #[test] 125 | fn check_simple_range() { 126 | check_range("0 to 5"); 127 | check_range("0 downto 5 - C_OFFSET"); 128 | } 129 | 130 | fn check_subtype_indications(inputs: &[&str]) { 131 | for input in inputs { 132 | check_formatted( 133 | input, 134 | input, 135 | Code::subtype_indication, 136 | |formatter, subtype_indication, buffer| { 137 | formatter.format_subtype_indication(subtype_indication, buffer) 138 | }, 139 | ) 140 | } 141 | } 142 | 143 | #[test] 144 | fn format_range_subtype_constraint() { 145 | check_subtype_indications(&[ 146 | "integer range 0 to 2 - 1", 147 | "integer range lib.foo.bar'range", 148 | ]); 149 | } 150 | 151 | #[test] 152 | fn format_array_subtype_constraint() { 153 | check_subtype_indications(&[ 154 | "integer_vector(2 - 1 downto 0)", 155 | "integer_vector(lib.foo.bar)", 156 | "integer_vector(lib.pkg.bar'range)", 157 | "integer_vector(open)", 158 | "integer_vector(2 - 1 downto 0, 11 to 14)", 159 | "integer_vector(2 - 1 downto 0, 11 to 14)(foo to bar)", 160 | ]); 161 | } 162 | 163 | #[test] 164 | fn format_record_subtype_constraint() { 165 | check_subtype_indications(&["axi_m2s_t(tdata(2 - 1 downto 0), tuser(3 to 5))"]); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /vhdl_lang/src/formatting/context.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // You can obtain one at http://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 7 | 8 | use crate::ast::{ContextClause, ContextDeclaration, ContextItem}; 9 | use crate::formatting::buffer::Buffer; 10 | use crate::formatting::VHDLFormatter; 11 | use crate::{HasTokenSpan, TokenSpan}; 12 | use vhdl_lang::indented; 13 | 14 | impl VHDLFormatter<'_> { 15 | pub fn format_context(&self, context: &ContextDeclaration, buffer: &mut Buffer) { 16 | // context is 17 | self.format_token_span( 18 | TokenSpan::new(context.span.start_token, context.span.start_token + 2), 19 | buffer, 20 | ); 21 | indented!(buffer, { 22 | if !context.items.is_empty() { 23 | buffer.line_break(); 24 | } 25 | self.format_context_clause(&context.items, buffer); 26 | }); 27 | buffer.line_break(); 28 | self.format_token_span( 29 | TokenSpan::new(context.end_token, context.span.end_token - 1), 30 | buffer, 31 | ); 32 | self.format_token_id(context.span.end_token, buffer); 33 | } 34 | 35 | pub fn format_context_clause(&self, clause: &ContextClause, buffer: &mut Buffer) { 36 | for (i, item) in clause.iter().enumerate() { 37 | match item { 38 | ContextItem::Use(use_clause) => self.format_use_clause(use_clause, buffer), 39 | ContextItem::Library(library_clause) => { 40 | self.format_library_clause(library_clause, buffer) 41 | } 42 | ContextItem::Context(context_reference) => { 43 | self.format_context_reference(context_reference, buffer) 44 | } 45 | } 46 | if i < clause.len() - 1 { 47 | self.line_break_preserve_whitespace(item.span().end_token, buffer); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /vhdl_lang/src/formatting/entity.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // You can obtain one at http://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 7 | 8 | use crate::ast::EntityDeclaration; 9 | use crate::formatting::buffer::Buffer; 10 | use crate::formatting::VHDLFormatter; 11 | use crate::{HasTokenSpan, TokenSpan}; 12 | use vhdl_lang::indented; 13 | 14 | impl VHDLFormatter<'_> { 15 | pub fn format_entity(&self, entity: &EntityDeclaration, buffer: &mut Buffer) { 16 | self.format_context_clause(&entity.context_clause, buffer); 17 | if let Some(item) = entity.context_clause.last() { 18 | self.line_break_preserve_whitespace(item.span().end_token, buffer); 19 | } 20 | let span = entity.span(); 21 | // entity is 22 | self.format_token_span(TokenSpan::new(span.start_token, entity.is_token()), buffer); 23 | if let Some(generic_clause) = &entity.generic_clause { 24 | indented!(buffer, { 25 | buffer.line_break(); 26 | self.format_interface_list(generic_clause, buffer); 27 | }); 28 | } 29 | if let Some(port_clause) = &entity.port_clause { 30 | indented!(buffer, { 31 | buffer.line_break(); 32 | self.format_interface_list(port_clause, buffer); 33 | }); 34 | } 35 | 36 | indented!(buffer, { self.format_declarations(&entity.decl, buffer) }); 37 | if let Some(token) = entity.begin_token { 38 | buffer.line_break(); 39 | self.format_token_id(token, buffer); 40 | } 41 | self.format_concurrent_statements(&entity.statements, buffer); 42 | buffer.line_break(); 43 | // end [entity] [name]; 44 | self.format_token_span(TokenSpan::new(entity.end_token, span.end_token - 1), buffer); 45 | self.format_token_id(span.end_token, buffer); 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod test { 51 | use crate::analysis::tests::Code; 52 | use vhdl_lang::formatting::test_utils::check_formatted; 53 | 54 | fn check_entity_formatted(input: &str) { 55 | check_formatted( 56 | input, 57 | input, 58 | Code::entity_decl, 59 | |formatter, entity, buffer| formatter.format_entity(entity, buffer), 60 | ); 61 | } 62 | 63 | #[test] 64 | fn test_format_simple_entity() { 65 | check_entity_formatted( 66 | "\ 67 | entity my_ent is 68 | end entity my_ent;", 69 | ); 70 | check_entity_formatted( 71 | "\ 72 | entity my_ent is 73 | end my_ent;", 74 | ); 75 | check_entity_formatted( 76 | "\ 77 | entity my_ent is 78 | end;", 79 | ); 80 | 81 | check_entity_formatted( 82 | "\ 83 | entity my_ent is 84 | end entity;", 85 | ); 86 | check_entity_formatted( 87 | "\ 88 | entity my_ent is 89 | begin 90 | end entity;", 91 | ); 92 | } 93 | 94 | #[test] 95 | fn test_entity_with_comments() { 96 | check_entity_formatted( 97 | "\ 98 | -- Some comment about the entity 99 | entity my_ent is 100 | end entity;", 101 | ); 102 | 103 | check_entity_formatted( 104 | "\ 105 | entity my_ent is -- trailing comment 106 | end entity;", 107 | ); 108 | 109 | check_entity_formatted( 110 | "\ 111 | entity /* Why would you put a comment here? */ my_ent is 112 | end entity;", 113 | ); 114 | 115 | check_entity_formatted( 116 | "\ 117 | entity /* Why would you put a comment here? */ my_ent is -- this is an entity 118 | end entity;", 119 | ); 120 | } 121 | 122 | #[test] 123 | fn test_entity_with_simple_generic() { 124 | check_entity_formatted( 125 | "\ 126 | entity foo is 127 | -- Generics come here 128 | generic ( 129 | foo: in std_logic --` where `node` is the AST node to format. 36 | /// Rather than returning a string, the methods accept a mutable [Buffer] object that they 37 | /// use to format. 38 | /// 39 | /// The formatter is capable of retaining comment information as well as preserving newlines. 40 | pub struct VHDLFormatter<'b> { 41 | tokens: &'b Vec, 42 | } 43 | 44 | impl<'b> VHDLFormatter<'b> { 45 | pub fn new(tokens: &'b Vec) -> VHDLFormatter<'b> { 46 | VHDLFormatter { tokens } 47 | } 48 | 49 | /// Format a whole design file. 50 | pub fn format_design_file(file: &DesignFile) -> String { 51 | let mut result = Buffer::new(); 52 | for (i, (tokens, design_unit)) in file.design_units.iter().enumerate() { 53 | let formatter = VHDLFormatter::new(tokens); 54 | formatter.format_any_design_unit( 55 | design_unit, 56 | &mut result, 57 | i == file.design_units.len() - 1, 58 | ); 59 | } 60 | result.into() 61 | } 62 | } 63 | 64 | impl VHDLFormatter<'_> { 65 | pub fn format_ident_list(&self, idents: &[T], buffer: &mut Buffer) { 66 | for ident in idents { 67 | let token = ident.ident().token; 68 | self.format_token_id(token, buffer); 69 | if self 70 | .tokens 71 | .get_token(token + 1) 72 | .is_some_and(|token| token.kind == Kind::Comma) 73 | { 74 | self.format_token_id(token + 1, buffer); 75 | buffer.push_whitespace(); 76 | } 77 | } 78 | } 79 | } 80 | 81 | /// indents the provided block and de-indents at the end. 82 | #[macro_export] 83 | macro_rules! indented { 84 | ($buffer:ident, $block:block) => { 85 | $buffer.increase_indent(); 86 | $block 87 | $buffer.decrease_indent(); 88 | }; 89 | } 90 | 91 | #[cfg(test)] 92 | pub mod test_utils { 93 | use crate::formatting::buffer::Buffer; 94 | use crate::formatting::VHDLFormatter; 95 | use crate::syntax::test::Code; 96 | use vhdl_lang::VHDLStandard; 97 | 98 | pub(crate) fn check_formatted( 99 | input: &str, 100 | expected: &str, 101 | to_ast: impl FnOnce(&Code) -> T, 102 | format: impl FnOnce(&VHDLFormatter<'_>, &T, &mut Buffer), 103 | ) { 104 | check_formatted_std(input, expected, VHDLStandard::default(), to_ast, format) 105 | } 106 | 107 | pub(crate) fn check_formatted_std( 108 | input: &str, 109 | expected: &str, 110 | std: VHDLStandard, 111 | to_ast: impl FnOnce(&Code) -> T, 112 | format: impl FnOnce(&VHDLFormatter<'_>, &T, &mut Buffer), 113 | ) { 114 | let code = Code::with_standard(input, std); 115 | let ast_element = to_ast(&code); 116 | let tokens = code.tokenize(); 117 | let formatter = VHDLFormatter::new(&tokens); 118 | let mut buffer = Buffer::new(); 119 | format(&formatter, &ast_element, &mut buffer); 120 | assert_eq!(buffer.as_str(), expected); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /vhdl_lang/src/formatting/statement.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // You can obtain one at http://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 7 | 8 | use crate::ast::token_range::WithTokenSpan; 9 | use crate::ast::Expression; 10 | use crate::formatting::buffer::Buffer; 11 | use crate::VHDLFormatter; 12 | 13 | impl VHDLFormatter<'_> { 14 | pub(crate) fn format_opt_severity( 15 | &self, 16 | severity: Option<&WithTokenSpan>, 17 | buffer: &mut Buffer, 18 | ) { 19 | if let Some(severity) = &severity { 20 | buffer.push_whitespace(); 21 | self.format_token_id(severity.span.start_token - 1, buffer); 22 | buffer.push_whitespace(); 23 | self.format_expression(severity.as_ref(), buffer); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vhdl_lang/src/formatting/token.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // You can obtain one at http://mozilla.org/MPL/2.0/. 5 | // 6 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 7 | 8 | use crate::ast::WithDecl; 9 | use crate::formatting::buffer::Buffer; 10 | use crate::formatting::VHDLFormatter; 11 | use crate::{TokenAccess, TokenId}; 12 | use std::cmp::max; 13 | use vhdl_lang::ast::Ident; 14 | use vhdl_lang::TokenSpan; 15 | 16 | impl VHDLFormatter<'_> { 17 | pub(crate) fn format_token_id(&self, id: TokenId, buffer: &mut Buffer) { 18 | buffer.push_token(self.tokens.index(id)); 19 | } 20 | 21 | pub(crate) fn format_token_span(&self, span: TokenSpan, buffer: &mut Buffer) { 22 | for (index, id) in span.iter().enumerate() { 23 | self.format_token_id(id, buffer); 24 | if index < span.len() - 1 { 25 | buffer.push_whitespace(); 26 | } 27 | } 28 | } 29 | 30 | pub(crate) fn join_token_span(&self, span: TokenSpan, buffer: &mut Buffer) { 31 | for id in span.iter() { 32 | self.format_token_id(id, buffer); 33 | } 34 | } 35 | 36 | pub(crate) fn format_ident(&self, ident: &WithDecl, buffer: &mut Buffer) { 37 | self.format_token_id(ident.tree.token, buffer) 38 | } 39 | 40 | pub(crate) fn line_break_preserve_whitespace(&self, token_id: TokenId, buffer: &mut Buffer) { 41 | let current_line = self.tokens.get_pos(token_id).end().line; 42 | if let Some(token) = self.tokens.get_token(token_id + 1) { 43 | let next_line = token.full_range().start.line; 44 | let numbers_of_whitespaces = max(next_line - current_line, 1); 45 | buffer.line_breaks(numbers_of_whitespaces) 46 | } else { 47 | buffer.line_break(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /vhdl_lang/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | #![allow(clippy::upper_case_acronyms)] 7 | #![allow(clippy::large_enum_variant)] 8 | #![allow(clippy::result_large_err)] 9 | #![warn(rust_2018_idioms, future_incompatible)] 10 | 11 | #[macro_use] 12 | extern crate vhdl_lang_macros; 13 | extern crate self as vhdl_lang; 14 | 15 | #[macro_use] 16 | pub mod ast; 17 | #[macro_use] 18 | mod analysis; 19 | mod config; 20 | mod data; 21 | mod lint; 22 | mod named_entity; 23 | mod project; 24 | mod syntax; 25 | 26 | mod completion; 27 | mod formatting; 28 | mod standard; 29 | 30 | pub use crate::config::Config; 31 | pub use crate::data::{ 32 | Diagnostic, Latin1String, Message, MessageHandler, MessagePrinter, MessageType, 33 | NullDiagnostics, NullMessages, Position, Range, Severity, SeverityMap, Source, SrcPos, 34 | }; 35 | pub use formatting::VHDLFormatter; 36 | 37 | pub use crate::analysis::EntHierarchy; 38 | pub use crate::named_entity::{ 39 | AnyEnt, AnyEntKind, Concurrent, Design, EntRef, EntityId, HasEntityId, InterfaceEnt, Object, 40 | Overloaded, Reference, Related, Sequential, Type, 41 | }; 42 | 43 | pub use crate::project::{Project, SourceFile}; 44 | pub use crate::syntax::{ 45 | kind_str, HasTokenSpan, ParserResult, Token, TokenAccess, TokenId, TokenSpan, VHDLParser, 46 | }; 47 | 48 | pub use completion::{list_completion_options, CompletionItem}; 49 | pub use standard::VHDLStandard; 50 | -------------------------------------------------------------------------------- /vhdl_lang/src/lint.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2022, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | pub mod dead_code; 8 | pub mod sensitivity_list; 9 | -------------------------------------------------------------------------------- /vhdl_lang/src/main.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use clap::Parser; 8 | use itertools::Itertools; 9 | use std::iter::zip; 10 | use std::path::{Path, PathBuf}; 11 | use vhdl_lang::ast::DesignFile; 12 | use vhdl_lang::{ 13 | Config, Diagnostic, MessagePrinter, Project, Severity, SeverityMap, Source, VHDLFormatter, 14 | VHDLParser, VHDLStandard, 15 | }; 16 | 17 | #[derive(Debug, clap::Args)] 18 | #[group(required = true, multiple = false)] 19 | pub struct Group { 20 | /// Config file in TOML format containing libraries and settings 21 | #[arg(short, long)] 22 | config: Option, 23 | 24 | /// Format the passed file and write the contents to stdout. 25 | /// 26 | /// This is experimental and the formatting behavior will change in the future. 27 | #[arg(short, long)] 28 | format: Option, 29 | } 30 | 31 | /// Run vhdl analysis 32 | #[derive(Parser, Debug)] 33 | #[command(author, version, about, long_about = None)] 34 | struct Args { 35 | /// The number of threads to use. By default, the maximum is selected based on process cores 36 | #[arg(short = 'p', long)] 37 | num_threads: Option, 38 | 39 | /// Path to the config file for the VHDL standard libraries (i.e., IEEE std_logic_1164). 40 | /// If omitted, will search for these libraries in a set of standard paths 41 | #[arg(short = 'l', long)] 42 | libraries: Option, 43 | 44 | #[clap(flatten)] 45 | group: Group, 46 | } 47 | 48 | fn main() { 49 | let args = Args::parse(); 50 | if let Some(config_path) = args.group.config { 51 | parse_and_analyze_project(config_path, args.num_threads, args.libraries); 52 | } else if let Some(format) = args.group.format { 53 | format_file(format); 54 | } 55 | } 56 | 57 | fn format_file(format: String) { 58 | let path = PathBuf::from(format); 59 | let parser = VHDLParser::new(VHDLStandard::default()); 60 | let mut diagnostics = Vec::new(); 61 | let result = parser.parse_design_file(&path, &mut diagnostics); 62 | match result { 63 | Ok((_, design_file)) => { 64 | if !diagnostics.is_empty() { 65 | show_diagnostics(&diagnostics, &SeverityMap::default()); 66 | std::process::exit(1); 67 | } 68 | let result = VHDLFormatter::format_design_file(&design_file); 69 | println!("{result}"); 70 | check_formatted_file(&path, parser, design_file, &result); 71 | std::process::exit(0); 72 | } 73 | Err(err) => { 74 | println!("{err}"); 75 | std::process::exit(1); 76 | } 77 | } 78 | } 79 | 80 | fn check_formatted_file(path: &Path, parser: VHDLParser, design_file: DesignFile, result: &str) { 81 | let mut diagnostics: Vec = Vec::new(); 82 | let new_file = parser.parse_design_source(&Source::inline(path, result), &mut diagnostics); 83 | if !diagnostics.is_empty() { 84 | println!("Formatting failed as it resulted in a syntactically incorrect file."); 85 | show_diagnostics(&diagnostics, &SeverityMap::default()); 86 | std::process::exit(1); 87 | } 88 | for ((tokens_a, _), (tokens_b, _)) in zip(new_file.design_units, design_file.design_units) { 89 | for (a, b) in zip(tokens_a, tokens_b) { 90 | if !a.equal_format(&b) { 91 | println!("Token mismatch"); 92 | println!("New Token={a:#?}"); 93 | let contents = a.pos.source.contents(); 94 | let a_line = contents.get_line(a.pos.range.start.line as usize).unwrap(); 95 | println!(" {a_line}"); 96 | println!("Old Token={b:#?}"); 97 | let b_line = result.lines().nth(b.pos.range.start.line as usize).unwrap(); 98 | println!(" {b_line}"); 99 | break; 100 | } 101 | } 102 | } 103 | } 104 | 105 | fn parse_and_analyze_project( 106 | config_path: String, 107 | num_threads: Option, 108 | libraries: Option, 109 | ) { 110 | rayon::ThreadPoolBuilder::new() 111 | .num_threads(num_threads.unwrap_or(0)) 112 | .build_global() 113 | .unwrap(); 114 | 115 | let mut config = Config::default(); 116 | let mut msg_printer = MessagePrinter::default(); 117 | config.load_external_config(&mut msg_printer, libraries.clone()); 118 | config.append( 119 | &Config::read_file_path(Path::new(&config_path)).expect("Failed to read config file"), 120 | &mut msg_printer, 121 | ); 122 | 123 | let severity_map = *config.severities(); 124 | let mut project = Project::from_config(config, &mut msg_printer); 125 | project.enable_all_linters(); 126 | let diagnostics = project.analyse(); 127 | 128 | show_diagnostics(&diagnostics, &severity_map); 129 | 130 | if diagnostics 131 | .iter() 132 | .any(|diag| severity_map[diag.code].is_some_and(|severity| severity == Severity::Error)) 133 | { 134 | std::process::exit(1); 135 | } else { 136 | std::process::exit(0); 137 | } 138 | } 139 | 140 | fn show_diagnostics(diagnostics: &[Diagnostic], severity_map: &SeverityMap) { 141 | let diagnostics = diagnostics 142 | .iter() 143 | .filter_map(|diag| diag.show(severity_map)) 144 | .collect_vec(); 145 | for str in &diagnostics { 146 | println!("{str}"); 147 | } 148 | 149 | if !diagnostics.is_empty() { 150 | println!("Found {} diagnostics", diagnostics.len()); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /vhdl_lang/src/named_entity/attribute.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use crate::ast::Designator; 8 | use crate::data::Symbol; 9 | use crate::AnyEntKind; 10 | use crate::EntRef; 11 | 12 | use super::TypeEnt; 13 | 14 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 15 | pub struct AttributeEnt<'a> { 16 | pub ent: EntRef<'a>, 17 | } 18 | 19 | impl<'a> AttributeEnt<'a> { 20 | pub fn from_any(ent: EntRef<'a>) -> Option { 21 | if let AnyEntKind::Attribute(..) = ent.actual_kind() { 22 | Some(AttributeEnt { ent }) 23 | } else { 24 | None 25 | } 26 | } 27 | 28 | pub fn name(&self) -> &Symbol { 29 | if let Designator::Identifier(sym) = self.ent.designator() { 30 | sym 31 | } else { 32 | panic!("Internal error: Bad attribute designator: {:?}", self.ent); 33 | } 34 | } 35 | 36 | pub fn typ(&self) -> TypeEnt<'a> { 37 | if let AnyEntKind::Attribute(typ) = self.ent.actual_kind() { 38 | *typ 39 | } else { 40 | unreachable!(); 41 | } 42 | } 43 | } 44 | 45 | impl<'a> From> for EntRef<'a> { 46 | fn from(value: AttributeEnt<'a>) -> Self { 47 | value.ent 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /vhdl_lang/src/named_entity/design.rs: -------------------------------------------------------------------------------- 1 | //! This Source Code Form is subject to the terms of the Mozilla Public 2 | //! License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | //! You can obtain one at http://mozilla.org/MPL/2.0/. 4 | //! 5 | //! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use std::ops::Deref; 8 | 9 | use super::*; 10 | use crate::ast::Designator; 11 | use crate::ast::HasDesignator; 12 | use crate::ast::WithRef; 13 | use crate::named_entity::visibility::Visibility; 14 | use crate::Diagnostic; 15 | 16 | #[derive(Clone)] 17 | pub enum Design<'a> { 18 | Entity(Visibility<'a>, Region<'a>), 19 | /// A VHDL architecture. 20 | /// The linked `DesignEnt` is the entity that belongs to the architecture. 21 | Architecture(Visibility<'a>, Region<'a>, DesignEnt<'a>), 22 | Configuration, 23 | Package(Visibility<'a>, Region<'a>), 24 | PackageBody(Visibility<'a>, Region<'a>), 25 | UninstPackage(Visibility<'a>, Region<'a>), 26 | /// An instantiated Package, i.e., 27 | /// ```vhdl 28 | /// package foo is new bar generic map (...); 29 | /// ``` 30 | PackageInstance(Region<'a>), 31 | /// An instantiated package that is part of some generic interface, i.e., 32 | /// ```vhdl 33 | /// generic ( 34 | /// package foo is new bar generic map (<>) 35 | /// ) 36 | /// ``` 37 | InterfacePackageInstance(Region<'a>), 38 | Context(Region<'a>), 39 | } 40 | 41 | impl Design<'_> { 42 | pub fn describe(&self) -> &'static str { 43 | use Design::*; 44 | match self { 45 | Entity(..) => "entity", 46 | Architecture(..) => "architecture", 47 | Configuration => "configuration", 48 | Package(..) => "package", 49 | PackageBody(..) => "package body", 50 | UninstPackage(..) => "uninstantiated package", 51 | PackageInstance(_) | InterfacePackageInstance(_) => "package instance", 52 | Context(..) => "context", 53 | } 54 | } 55 | } 56 | 57 | // A named entity that is known to be a type 58 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 59 | pub struct DesignEnt<'a>(pub EntRef<'a>); 60 | 61 | impl<'a> DesignEnt<'a> { 62 | pub fn from_any(ent: &'a AnyEnt<'a>) -> Option> { 63 | if matches!(ent.kind(), AnyEntKind::Design(..)) { 64 | Some(DesignEnt(ent)) 65 | } else { 66 | None 67 | } 68 | } 69 | 70 | pub fn inner(&self) -> EntRef<'a> { 71 | self.0 72 | } 73 | 74 | pub fn kind(&self) -> &'a Design<'a> { 75 | if let AnyEntKind::Design(typ) = self.0.kind() { 76 | typ 77 | } else { 78 | unreachable!("Must be a design"); 79 | } 80 | } 81 | 82 | pub fn selected( 83 | &self, 84 | ctx: &dyn TokenAccess, 85 | prefix_pos: TokenSpan, 86 | suffix: &WithToken>, 87 | ) -> Result, Diagnostic> { 88 | match self.kind() { 89 | Design::Package(_, ref region) 90 | | Design::PackageInstance(ref region) 91 | | Design::InterfacePackageInstance(ref region) => { 92 | if let Some(decl) = region.lookup_immediate(suffix.designator()) { 93 | Ok(decl.clone()) 94 | } else { 95 | Err(Diagnostic::no_declaration_within( 96 | self, 97 | suffix.pos(ctx), 98 | &suffix.item.item, 99 | )) 100 | } 101 | } 102 | _ => Err(Diagnostic::invalid_selected_name_prefix( 103 | self, 104 | &prefix_pos.pos(ctx), 105 | )), 106 | } 107 | } 108 | } 109 | 110 | impl<'a> From> for EntRef<'a> { 111 | fn from(ent: DesignEnt<'a>) -> Self { 112 | ent.0 113 | } 114 | } 115 | 116 | impl<'a> Deref for DesignEnt<'a> { 117 | type Target = AnyEnt<'a>; 118 | fn deref(&self) -> EntRef<'a> { 119 | self.0 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /vhdl_lang/src/named_entity/file.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2025, Lukas Scheller lukasscheller@icloud.com 6 | 7 | use crate::named_entity::TypeEnt; 8 | use crate::{AnyEnt, AnyEntKind, EntRef}; 9 | 10 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 11 | pub struct FileEnt<'a>(EntRef<'a>); 12 | 13 | impl<'a> FileEnt<'a> { 14 | pub fn from_any(ent: EntRef<'a>) -> Option> { 15 | match ent.kind() { 16 | AnyEntKind::File(_) | AnyEntKind::InterfaceFile(_) => Some(FileEnt(ent)), 17 | _ => None, 18 | } 19 | } 20 | 21 | pub fn inner(&self) -> EntRef<'a> { 22 | self.0 23 | } 24 | 25 | pub fn type_mark(&self) -> TypeEnt<'a> { 26 | match self.0.kind() { 27 | AnyEntKind::File(subtype) => subtype.type_mark(), 28 | AnyEntKind::InterfaceFile(typ) => *typ, 29 | _ => unreachable!(), 30 | } 31 | } 32 | } 33 | 34 | impl<'a> std::ops::Deref for FileEnt<'a> { 35 | type Target = AnyEnt<'a>; 36 | fn deref(&self) -> EntRef<'a> { 37 | self.inner() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /vhdl_lang/src/named_entity/object.rs: -------------------------------------------------------------------------------- 1 | //! This Source Code Form is subject to the terms of the Mozilla Public 2 | //! License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | //! You can obtain one at http://mozilla.org/MPL/2.0/. 4 | //! 5 | //! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use std::fmt::{Debug, Display, Formatter}; 8 | use std::ops::Deref; 9 | 10 | use super::*; 11 | use crate::ast::InterfaceType; 12 | use crate::ast::Mode; 13 | use crate::ast::ObjectClass; 14 | 15 | // A named entity that is known to be an object 16 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 17 | pub struct ObjectEnt<'a> { 18 | pub ent: EntRef<'a>, 19 | } 20 | 21 | impl<'a> ObjectEnt<'a> { 22 | pub fn from_any(ent: EntRef<'a>) -> Option { 23 | if matches!(ent.actual_kind(), AnyEntKind::Object(..)) { 24 | Some(Self { ent }) 25 | } else { 26 | None 27 | } 28 | } 29 | 30 | pub fn kind(&self) -> &'a Object<'a> { 31 | if let AnyEntKind::Object(object) = self.ent.actual_kind() { 32 | object 33 | } else { 34 | unreachable!("ObjectEnt type invariant broken") 35 | } 36 | } 37 | 38 | pub fn inner(&self) -> EntRef<'a> { 39 | self.ent 40 | } 41 | 42 | pub fn type_mark(&self) -> TypeEnt<'a> { 43 | self.kind().subtype.type_mark() 44 | } 45 | 46 | pub fn class(&self) -> ObjectClass { 47 | self.object().class 48 | } 49 | 50 | pub fn mode(&self) -> Option<&InterfaceMode<'a>> { 51 | self.object().mode() 52 | } 53 | 54 | pub fn describe(&self) -> String { 55 | if let Some(ref iface) = self.object().iface { 56 | match iface { 57 | ObjectInterface::Generic => format!("generic '{}'", self.designator()), 58 | ObjectInterface::Parameter(mode) => { 59 | if self.class() == ObjectClass::Constant { 60 | format!("parameter '{}'", self.designator()) 61 | } else { 62 | format!("{} '{}' : {}", self.class(), self.designator(), mode) 63 | } 64 | } 65 | ObjectInterface::Port(mode) => format!("port '{}' : {}", self.designator(), mode), 66 | } 67 | } else { 68 | self.describe_name() 69 | } 70 | } 71 | 72 | pub fn describe_name(&self) -> String { 73 | format!("{} '{}'", self.class(), self.designator()) 74 | } 75 | 76 | pub fn object(&self) -> &'a Object<'a> { 77 | if let AnyEntKind::Object(object) = self.ent.actual_kind() { 78 | object 79 | } else { 80 | unreachable!("Must be object"); 81 | } 82 | } 83 | } 84 | 85 | #[derive(Clone, Eq, PartialEq)] 86 | pub enum InterfaceMode<'a> { 87 | Simple(Mode), 88 | View(ViewEnt<'a>), 89 | } 90 | 91 | impl Display for InterfaceMode<'_> { 92 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 93 | match self { 94 | InterfaceMode::Simple(mode) => write!(f, "{mode}"), 95 | InterfaceMode::View(view) => write!(f, "view {}", view.ent.designator), 96 | } 97 | } 98 | } 99 | 100 | #[derive(Clone)] 101 | pub enum ObjectInterface<'a> { 102 | Generic, 103 | Port(InterfaceMode<'a>), 104 | Parameter(InterfaceMode<'a>), 105 | } 106 | 107 | impl ObjectInterface<'_> { 108 | pub fn simple(typ: InterfaceType, mode: Mode) -> Self { 109 | match typ { 110 | // @TODO error on non-input mode 111 | InterfaceType::Generic => ObjectInterface::Generic, 112 | InterfaceType::Parameter => ObjectInterface::Parameter(InterfaceMode::Simple(mode)), 113 | InterfaceType::Port => ObjectInterface::Port(InterfaceMode::Simple(mode)), 114 | } 115 | } 116 | 117 | pub fn mode(&self) -> &InterfaceMode<'_> { 118 | match self { 119 | ObjectInterface::Generic => &InterfaceMode::Simple(Mode::In), 120 | ObjectInterface::Parameter(m) | ObjectInterface::Port(m) => m, 121 | } 122 | } 123 | 124 | pub fn typ(&self) -> InterfaceType { 125 | match self { 126 | ObjectInterface::Generic => InterfaceType::Generic, 127 | ObjectInterface::Parameter(..) => InterfaceType::Parameter, 128 | ObjectInterface::Port(..) => InterfaceType::Port, 129 | } 130 | } 131 | } 132 | 133 | /// An object or an interface object, 134 | /// example signal, variable, constant 135 | /// Is either an object (mode = None) or an interface object (mode = Some) 136 | #[derive(Clone)] 137 | pub struct Object<'a> { 138 | pub class: ObjectClass, 139 | pub iface: Option>, 140 | pub subtype: Subtype<'a>, 141 | pub has_default: bool, 142 | } 143 | 144 | impl<'a> Object<'a> { 145 | pub(crate) fn const_param(subtype: Subtype<'a>) -> Object<'a> { 146 | Object { 147 | class: ObjectClass::Constant, 148 | iface: Some(ObjectInterface::Parameter(InterfaceMode::Simple(Mode::In))), 149 | subtype, 150 | has_default: false, 151 | } 152 | } 153 | 154 | pub(crate) fn with_default(mut self) -> Self { 155 | self.has_default = true; 156 | self 157 | } 158 | 159 | pub fn is_signal(&self) -> bool { 160 | self.class == ObjectClass::Signal 161 | } 162 | 163 | pub fn is_constant(&self) -> bool { 164 | self.class == ObjectClass::Constant 165 | } 166 | 167 | pub fn is_port(&self) -> bool { 168 | matches!(self.iface, Some(ObjectInterface::Port(..))) 169 | } 170 | 171 | pub fn is_generic(&self) -> bool { 172 | matches!(self.iface, Some(ObjectInterface::Generic)) 173 | } 174 | 175 | pub fn is_param(&self) -> bool { 176 | matches!(self.iface, Some(ObjectInterface::Parameter(_))) 177 | } 178 | 179 | pub fn mode(&self) -> Option<&InterfaceMode<'_>> { 180 | self.iface.as_ref().map(|i| i.mode()) 181 | } 182 | } 183 | 184 | impl ObjectClass { 185 | pub fn describe(&self) -> &str { 186 | use ObjectClass::*; 187 | match self { 188 | Constant => "constant", 189 | Variable => "variable", 190 | Signal => "signal", 191 | SharedVariable => "shared variable", 192 | } 193 | } 194 | } 195 | 196 | impl<'a> Deref for ObjectEnt<'a> { 197 | type Target = EntRef<'a>; 198 | fn deref(&self) -> &Self::Target { 199 | &self.ent 200 | } 201 | } 202 | 203 | // A named entity that is known to be a view 204 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 205 | pub struct ViewEnt<'a> { 206 | pub ent: EntRef<'a>, 207 | } 208 | 209 | impl<'a> ViewEnt<'a> { 210 | pub fn from_any(ent: EntRef<'a>) -> Option { 211 | if matches!(ent.actual_kind(), AnyEntKind::View(..)) { 212 | Some(Self { ent }) 213 | } else { 214 | None 215 | } 216 | } 217 | 218 | pub fn subtype(&self) -> &'a Subtype<'a> { 219 | if let AnyEntKind::View(subtype) = self.ent.actual_kind() { 220 | subtype 221 | } else { 222 | unreachable!("ViewEnt type invariant broken") 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /vhdl_lang/src/standard.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Ord, PartialOrd)] 2 | pub enum VHDLStandard { 3 | VHDL1993, 4 | #[default] 5 | VHDL2008, 6 | VHDL2019, 7 | } 8 | 9 | #[test] 10 | fn order_of_standards() { 11 | assert!(VHDLStandard::VHDL2008 > VHDLStandard::VHDL1993); 12 | } 13 | 14 | impl TryFrom<&str> for VHDLStandard { 15 | type Error = (); 16 | 17 | fn try_from(value: &str) -> Result { 18 | use VHDLStandard::*; 19 | Ok(match value { 20 | "1993" | "93" => VHDL1993, 21 | "2008" | "08" => VHDL2008, 22 | "2019" | "19" => VHDL2019, 23 | _ => return Err(()), 24 | }) 25 | } 26 | } 27 | 28 | impl AsRef for VHDLStandard { 29 | fn as_ref(&self) -> &str { 30 | use VHDLStandard::*; 31 | match self { 32 | VHDL1993 => "1993", 33 | VHDL2008 => "2008", 34 | VHDL2019 => "2019", 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /vhdl_lang/src/syntax.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | #[macro_use] 8 | mod tokens; 9 | 10 | mod alias_declaration; 11 | mod attributes; 12 | mod common; 13 | mod component_declaration; 14 | mod concurrent_statement; 15 | mod configuration; 16 | mod context; 17 | mod declarative_part; 18 | mod design_unit; 19 | mod expression; 20 | mod interface_declaration; 21 | mod names; 22 | mod object_declaration; 23 | mod parser; 24 | mod range; 25 | mod separated_list; 26 | mod sequential_statement; 27 | mod subprogram; 28 | mod subtype_indication; 29 | mod type_declaration; 30 | mod waveform; 31 | 32 | mod recover; 33 | #[cfg(test)] 34 | pub mod test; 35 | mod view; 36 | 37 | pub use parser::{ParserResult, VHDLParser}; 38 | pub use tokens::*; 39 | -------------------------------------------------------------------------------- /vhdl_lang/src/syntax/alias_declaration.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use super::common::ParseResult; 8 | use super::names::{parse_designator, parse_name}; 9 | use super::subprogram::parse_signature; 10 | use super::subtype_indication::parse_subtype_indication; 11 | use super::tokens::{Kind::*, TokenSpan}; 12 | use crate::ast::token_range::WithTokenSpan; 13 | use crate::ast::{AliasDeclaration, WithDecl}; 14 | use crate::syntax::recover::expect_semicolon_or_last; 15 | use vhdl_lang::syntax::parser::ParsingContext; 16 | 17 | pub fn parse_alias_declaration( 18 | ctx: &mut ParsingContext<'_>, 19 | ) -> ParseResult> { 20 | let start_token = ctx.stream.expect_kind(Alias)?; 21 | let designator = WithDecl::new(parse_designator(ctx)?); 22 | let subtype_indication = { 23 | if ctx.stream.skip_if_kind(Colon) { 24 | Some(parse_subtype_indication(ctx)?) 25 | } else { 26 | None 27 | } 28 | }; 29 | 30 | let is_token = ctx.stream.expect_kind(Is)?; 31 | let name = parse_name(ctx)?; 32 | 33 | let signature = { 34 | if ctx.stream.peek_kind() == Some(LeftSquare) { 35 | Some(parse_signature(ctx)?) 36 | } else { 37 | None 38 | } 39 | }; 40 | 41 | let end_token = expect_semicolon_or_last(ctx); 42 | 43 | Ok(WithTokenSpan::new( 44 | AliasDeclaration { 45 | designator, 46 | subtype_indication, 47 | is_token, 48 | name, 49 | signature, 50 | }, 51 | TokenSpan::new(start_token, end_token), 52 | )) 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | use crate::syntax::test::Code; 59 | 60 | #[test] 61 | fn parse_simple_alias() { 62 | let code = Code::new("alias foo is name;"); 63 | assert_eq!( 64 | code.with_stream(parse_alias_declaration), 65 | WithTokenSpan::new( 66 | AliasDeclaration { 67 | designator: code.s1("foo").decl_designator(), 68 | subtype_indication: None, 69 | is_token: code.s1("is").token(), 70 | name: code.s1("name").name(), 71 | signature: None 72 | }, 73 | code.token_span() 74 | ) 75 | ); 76 | } 77 | 78 | #[test] 79 | fn parse_alias_with_subtype_indication() { 80 | let code = Code::new("alias foo : vector(0 to 1) is name;"); 81 | assert_eq!( 82 | code.with_stream(parse_alias_declaration), 83 | WithTokenSpan::new( 84 | AliasDeclaration { 85 | designator: code.s1("foo").decl_designator(), 86 | subtype_indication: Some(code.s1("vector(0 to 1)").subtype_indication()), 87 | is_token: code.s1("is").token(), 88 | name: code.s1("name").name(), 89 | signature: None 90 | }, 91 | code.token_span() 92 | ) 93 | ); 94 | } 95 | 96 | #[test] 97 | fn parse_alias_with_signature() { 98 | let code = Code::new("alias foo is name [return natural];"); 99 | assert_eq!( 100 | code.with_stream(parse_alias_declaration), 101 | WithTokenSpan::new( 102 | AliasDeclaration { 103 | designator: code.s1("foo").decl_designator(), 104 | subtype_indication: None, 105 | is_token: code.s1("is").token(), 106 | name: code.s1("name").name(), 107 | signature: Some(code.s1("[return natural]").signature()) 108 | }, 109 | code.token_span() 110 | ) 111 | ); 112 | } 113 | 114 | #[test] 115 | fn parse_alias_with_operator_symbol() { 116 | let code = Code::new("alias \"and\" is name;"); 117 | 118 | let designator = code.s1("\"and\"").decl_designator(); 119 | 120 | assert_eq!( 121 | code.with_stream(parse_alias_declaration), 122 | WithTokenSpan::new( 123 | AliasDeclaration { 124 | designator, 125 | subtype_indication: None, 126 | is_token: code.s1("is").token(), 127 | name: code.s1("name").name(), 128 | signature: None 129 | }, 130 | code.token_span() 131 | ) 132 | ); 133 | } 134 | 135 | #[test] 136 | fn parse_alias_with_character() { 137 | let code = Code::new("alias 'c' is 'b';"); 138 | 139 | let designator = code.s1("'c'").decl_designator(); 140 | 141 | assert_eq!( 142 | code.with_stream(parse_alias_declaration), 143 | WithTokenSpan::new( 144 | AliasDeclaration { 145 | designator, 146 | subtype_indication: None, 147 | is_token: code.s1("is").token(), 148 | name: code.s1("'b'").name(), 149 | signature: None 150 | }, 151 | code.token_span() 152 | ) 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /vhdl_lang/src/syntax/common.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use super::tokens::Kind; 8 | use crate::ast::token_range::WithToken; 9 | use crate::ast::Ident; 10 | use crate::data::Diagnostic; 11 | use crate::syntax::parser::ParsingContext; 12 | use crate::{SrcPos, TokenId}; 13 | 14 | /// Parse optional part followed by optional keyword 15 | pub fn parse_optional( 16 | ctx: &mut ParsingContext<'_>, 17 | keyword: Kind, 18 | parse_fun: F, 19 | ) -> ParseResult> 20 | where 21 | F: FnOnce(&mut ParsingContext<'_>) -> ParseResult, 22 | { 23 | let optional = { 24 | if ctx.stream.skip_if_kind(keyword) { 25 | Some(parse_fun(ctx)?) 26 | } else { 27 | None 28 | } 29 | }; 30 | 31 | Ok(optional) 32 | } 33 | 34 | pub fn check_end_identifier_mismatch( 35 | ctx: &mut ParsingContext<'_>, 36 | ident: &WithToken, 37 | end_ident: Option>, 38 | ) -> Option { 39 | if let Some(end_ident) = end_ident { 40 | if ident.item == end_ident.item { 41 | return Some(end_ident.token); 42 | } else { 43 | ctx.diagnostics.push(Diagnostic::syntax_error( 44 | end_ident.pos(ctx), 45 | format!("End identifier mismatch, expected {}", ident.item), 46 | )); 47 | } 48 | } 49 | None 50 | } 51 | 52 | pub fn check_label_identifier_mismatch( 53 | ctx: &mut ParsingContext<'_>, 54 | label: Option<&Ident>, 55 | end_ident: Option, 56 | ) -> Option { 57 | if let Some(ident) = label { 58 | if let Some(end_ident) = end_ident { 59 | if ident.item == end_ident.item { 60 | return Some(end_ident.pos(ctx).clone()); 61 | } else { 62 | ctx.diagnostics.push(Diagnostic::syntax_error( 63 | end_ident.pos(ctx), 64 | format!("End label mismatch, expected {}", ident.item), 65 | )); 66 | } 67 | } 68 | } else if let Some(end_ident) = end_ident { 69 | ctx.diagnostics.push(Diagnostic::syntax_error( 70 | end_ident.pos(ctx), 71 | format!( 72 | "End label '{}' found for unlabeled statement", 73 | end_ident.item 74 | ), 75 | )); 76 | } 77 | None 78 | } 79 | 80 | pub type ParseResult = Result; 81 | -------------------------------------------------------------------------------- /vhdl_lang/src/syntax/parser.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use super::tokens::{Symbols, TokenStream, Tokenizer}; 8 | use crate::ast::DesignFile; 9 | use crate::data::*; 10 | use crate::standard::VHDLStandard; 11 | use crate::syntax::design_unit::parse_design_file; 12 | use crate::{Token, TokenId}; 13 | use std::io; 14 | use std::sync::Arc; 15 | use vhdl_lang::TokenAccess; 16 | 17 | pub struct VHDLParser { 18 | pub symbols: Arc, 19 | pub standard: VHDLStandard, 20 | } 21 | 22 | pub(crate) struct ParsingContext<'a> { 23 | pub stream: &'a TokenStream<'a>, 24 | pub diagnostics: &'a mut dyn DiagnosticHandler, 25 | pub standard: VHDLStandard, 26 | } 27 | 28 | impl TokenAccess for ParsingContext<'_> { 29 | fn get_token(&self, id: TokenId) -> Option<&Token> { 30 | self.stream.get_token(id) 31 | } 32 | 33 | fn index(&self, id: TokenId) -> &Token { 34 | self.stream.index(id) 35 | } 36 | 37 | fn get_token_slice(&self, start_id: TokenId, end_id: TokenId) -> &[Token] { 38 | self.stream.get_token_slice(start_id, end_id) 39 | } 40 | } 41 | 42 | pub type ParserResult = Result<(Source, DesignFile), io::Error>; 43 | 44 | impl VHDLParser { 45 | pub fn new(vhdl_standard: VHDLStandard) -> VHDLParser { 46 | VHDLParser { 47 | symbols: Arc::new(Symbols::from_standard(vhdl_standard)), 48 | standard: vhdl_standard, 49 | } 50 | } 51 | 52 | pub fn symbol(&self, name: &Latin1String) -> Symbol { 53 | self.symbols.symtab().insert(name) 54 | } 55 | 56 | pub fn parse_design_source( 57 | &self, 58 | source: &Source, 59 | diagnostics: &mut dyn DiagnosticHandler, 60 | ) -> DesignFile { 61 | let contents = source.contents(); 62 | let tokenizer = Tokenizer::new(&self.symbols, source, ContentReader::new(&contents)); 63 | let stream = TokenStream::new(tokenizer, diagnostics); 64 | 65 | let mut ctx = ParsingContext { 66 | stream: &stream, 67 | diagnostics, 68 | standard: self.standard, 69 | }; 70 | 71 | match parse_design_file(&mut ctx) { 72 | Ok(design_file) => design_file, 73 | Err(diagnostic) => { 74 | diagnostics.push(diagnostic); 75 | DesignFile::default() 76 | } 77 | } 78 | } 79 | 80 | pub fn parse_design_file( 81 | &self, 82 | file_name: &Path, 83 | diagnostics: &mut dyn DiagnosticHandler, 84 | ) -> ParserResult { 85 | let source = Source::from_latin1_file(file_name)?; 86 | let design_file = self.parse_design_source(&source, diagnostics); 87 | Ok((source, design_file)) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /vhdl_lang/src/syntax/recover.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com 6 | /// Module for robust parsing 7 | use crate::syntax::parser::ParsingContext; 8 | use crate::syntax::Kind::{Colon, SemiColon}; 9 | use crate::syntax::{kinds_error, kinds_str}; 10 | use crate::TokenId; 11 | 12 | /// Special handling when expecting a semicolon. 13 | /// When the next token is 14 | /// * a semicolon, then consume that token and produce no error 15 | /// * a token that could be confused with a semicolon (i.e., a comma), 16 | /// then consume that token and report an error 17 | /// * none of these choices: do not consume the token and report an error 18 | pub fn expect_semicolon(ctx: &mut ParsingContext<'_>) -> Option { 19 | let token = match ctx.stream.peek_expect() { 20 | Ok(token) => token, 21 | Err(err) => { 22 | ctx.diagnostics 23 | .push(err.when(format!("expecting {}", kinds_str(&[SemiColon])))); 24 | return None; 25 | } 26 | }; 27 | match token.kind { 28 | SemiColon => { 29 | ctx.stream.skip(); 30 | Some(ctx.stream.get_last_token_id()) 31 | } 32 | Colon => { 33 | ctx.stream.skip(); 34 | ctx.diagnostics 35 | .push(kinds_error(token.pos.clone(), &[SemiColon])); 36 | Some(ctx.stream.get_last_token_id()) 37 | } 38 | _ => { 39 | ctx.diagnostics 40 | .push(kinds_error(ctx.stream.pos_before(token), &[SemiColon])); 41 | None 42 | } 43 | } 44 | } 45 | 46 | /// Expect the next token to be a SemiColon, or return the last token. 47 | /// The behavior is the same as [expect_semicolon]. 48 | #[must_use] 49 | pub fn expect_semicolon_or_last(ctx: &mut ParsingContext<'_>) -> TokenId { 50 | expect_semicolon(ctx).unwrap_or(ctx.stream.get_last_token_id()) 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use crate::analysis::tests::Code; 56 | use crate::ast::token_range::WithTokenSpan; 57 | use crate::ast::Declaration; 58 | use crate::syntax::declarative_part::parse_declarative_part; 59 | use crate::syntax::test::check_diagnostics; 60 | use vhdl_lang::ast::{ObjectClass, ObjectDeclaration}; 61 | use vhdl_lang::Diagnostic; 62 | 63 | #[test] 64 | fn recover_from_semicolon_in_declarative_path() { 65 | let code = Code::new( 66 | "\ 67 | signal x : std_logic := a. 68 | signal y: bit; 69 | ", 70 | ); 71 | let (declarations, diagnostics) = code.parse_ok(parse_declarative_part); 72 | assert_eq!( 73 | declarations, 74 | vec![ 75 | WithTokenSpan::new( 76 | Declaration::Object(ObjectDeclaration { 77 | class: ObjectClass::Signal, 78 | idents: vec![code.s1("x").decl_ident()], 79 | colon_token: code.s1(":").token(), 80 | subtype_indication: code.s1("std_logic").subtype_indication(), 81 | expression: Some(code.s1("a.").s1("a").expr()) 82 | }), 83 | code.s1("signal x : std_logic := a.").token_span() 84 | ), 85 | WithTokenSpan::new( 86 | Declaration::Object(ObjectDeclaration { 87 | class: ObjectClass::Signal, 88 | idents: vec![code.s1("y").decl_ident()], 89 | colon_token: code.s(":", 3).token(), 90 | subtype_indication: code.s1("bit").subtype_indication(), 91 | expression: None 92 | }), 93 | code.s1("signal y: bit;").token_span() 94 | ), 95 | ] 96 | ); 97 | check_diagnostics( 98 | diagnostics, 99 | vec![ 100 | Diagnostic::syntax_error( 101 | code.s1(".").pos().pos_at_end(), 102 | "Expected '{identifier}', '{character}', '{string}' or 'all'", 103 | ), 104 | Diagnostic::syntax_error(code.s1(".").pos().pos_at_end(), "Expected ';'"), 105 | ], 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /vhdl_lang/src/syntax/tokens.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | #[macro_use] 8 | mod tokenizer; 9 | /// Contains constant keywords for different versions of VHDL. 10 | mod keywords; 11 | mod tokenstream; 12 | 13 | pub use tokenizer::*; 14 | pub use tokenstream::*; 15 | -------------------------------------------------------------------------------- /vhdl_lang/src/syntax/waveform.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use crate::ast::token_range::WithTokenSpan; 8 | use crate::ast::{DelayMechanism, Waveform, WaveformElement}; 9 | use crate::syntax::parser::ParsingContext; 10 | use vhdl_lang::TokenSpan; 11 | 12 | use super::common::{parse_optional, ParseResult}; 13 | use super::expression::parse_expression; 14 | use super::tokens::Kind::*; 15 | 16 | /// LRM 10.5 Signal assignment statement 17 | pub fn parse_delay_mechanism( 18 | ctx: &mut ParsingContext<'_>, 19 | ) -> ParseResult>> { 20 | let token = ctx.stream.peek_expect()?; 21 | let start_token = ctx.stream.get_current_token_id(); 22 | match token.kind { 23 | Transport => { 24 | ctx.stream.skip(); 25 | let span = TokenSpan::new(start_token, start_token); 26 | Ok(Some(WithTokenSpan::new(DelayMechanism::Transport, span))) 27 | } 28 | Inertial => { 29 | ctx.stream.skip(); 30 | let span = TokenSpan::new(start_token, start_token); 31 | Ok(Some(WithTokenSpan::new( 32 | DelayMechanism::Inertial { reject: None }, 33 | span, 34 | ))) 35 | } 36 | Reject => { 37 | ctx.stream.skip(); 38 | let reject = Some(parse_expression(ctx)?); 39 | let end_token = ctx.stream.expect_kind(Inertial)?; 40 | let span = TokenSpan::new(start_token, end_token); 41 | Ok(Some(WithTokenSpan::new( 42 | DelayMechanism::Inertial { reject }, 43 | span, 44 | ))) 45 | } 46 | _ => Ok(None), 47 | } 48 | } 49 | 50 | /// LRM 10.5 Signal assignment statement 51 | pub fn parse_waveform(ctx: &mut ParsingContext<'_>) -> ParseResult { 52 | if let Some(token) = ctx.stream.pop_if_kind(Unaffected) { 53 | return Ok(Waveform::Unaffected(token)); 54 | } 55 | 56 | let mut elems = Vec::new(); 57 | 58 | loop { 59 | let value = parse_expression(ctx)?; 60 | let after = parse_optional(ctx, After, parse_expression)?; 61 | 62 | elems.push(WaveformElement { value, after }); 63 | 64 | if !ctx.stream.skip_if_kind(Comma) { 65 | break; 66 | } 67 | } 68 | Ok(Waveform::Elements(elems)) 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use super::*; 74 | use crate::syntax::test::Code; 75 | 76 | #[test] 77 | fn test_transport_delay_mechanism() { 78 | let code = Code::new("transport"); 79 | assert_eq!( 80 | code.with_stream(parse_delay_mechanism), 81 | Some(WithTokenSpan::new( 82 | DelayMechanism::Transport, 83 | code.token_span() 84 | )) 85 | ); 86 | } 87 | 88 | #[test] 89 | fn test_intertial_delay_mechanism() { 90 | let code = Code::new("inertial"); 91 | assert_eq!( 92 | code.with_stream(parse_delay_mechanism), 93 | Some(WithTokenSpan::new( 94 | DelayMechanism::Inertial { reject: None }, 95 | code.token_span() 96 | )) 97 | ); 98 | } 99 | 100 | #[test] 101 | fn test_reject_intertial_delay_mechanism() { 102 | let code = Code::new("reject 2 ns inertial"); 103 | assert_eq!( 104 | code.with_stream(parse_delay_mechanism), 105 | Some(WithTokenSpan::new( 106 | DelayMechanism::Inertial { 107 | reject: Some(code.s1("2 ns").expr()) 108 | }, 109 | code.token_span() 110 | )) 111 | ); 112 | } 113 | 114 | #[test] 115 | fn test_waveform() { 116 | let code = Code::new("bar(1 to 3)"); 117 | assert_eq!( 118 | code.with_stream(parse_waveform), 119 | Waveform::Elements(vec![WaveformElement { 120 | value: code.s1("bar(1 to 3)").expr(), 121 | after: None 122 | }]) 123 | ); 124 | } 125 | 126 | #[test] 127 | fn test_waveform_after() { 128 | let code = Code::new("bar(1 to 3) after 2 ns"); 129 | assert_eq!( 130 | code.with_stream(parse_waveform), 131 | Waveform::Elements(vec![WaveformElement { 132 | value: code.s1("bar(1 to 3)").expr(), 133 | after: Some(code.s1("2 ns").expr()) 134 | }]) 135 | ); 136 | } 137 | 138 | #[test] 139 | fn test_waveform_after_many() { 140 | let code = Code::new("bar(1 to 3) after 2 ns, expr after 1 ns"); 141 | assert_eq!( 142 | code.with_stream(parse_waveform), 143 | Waveform::Elements(vec![ 144 | WaveformElement { 145 | value: code.s1("bar(1 to 3)").expr(), 146 | after: Some(code.s1("2 ns").expr()), 147 | }, 148 | WaveformElement { 149 | value: code.s1("expr").expr(), 150 | after: Some(code.s1("1 ns").expr()), 151 | }, 152 | ]) 153 | ); 154 | } 155 | 156 | #[test] 157 | fn test_unaffected_waveform() { 158 | let code = Code::new("unaffected"); 159 | assert_eq!( 160 | code.with_stream(parse_waveform), 161 | Waveform::Unaffected(code.token()) 162 | ); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /vhdl_lang/tests/format_example_project.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fs; 3 | use std::iter::zip; 4 | use std::path::{Path, PathBuf}; 5 | use vhdl_lang::{Diagnostic, SeverityMap, Source, VHDLFormatter, VHDLParser, VHDLStandard}; 6 | 7 | // excluded file contains PSL statements 8 | const EXCLUDED_FILES: [&str; 1] = ["vunit/examples/vhdl/array_axis_vcs/src/fifo.vhd"]; 9 | 10 | fn format_file(path: &Path) -> Result<(), Box> { 11 | let severity_map = SeverityMap::default(); 12 | let parser = VHDLParser::new(VHDLStandard::default()); 13 | let mut diagnostics = Vec::new(); 14 | let (_, design_file) = parser.parse_design_file(path, &mut diagnostics)?; 15 | if !diagnostics.is_empty() { 16 | for diagnostic in diagnostics { 17 | println!("{}", diagnostic.show(&severity_map).unwrap()) 18 | } 19 | panic!("Found diagnostics with severity error in the example project"); 20 | } 21 | 22 | let result = VHDLFormatter::format_design_file(&design_file); 23 | let mut diagnostics: Vec = Vec::new(); 24 | let new_file = parser.parse_design_source(&Source::inline(path, &result), &mut diagnostics); 25 | if !diagnostics.is_empty() { 26 | for diagnostic in diagnostics { 27 | println!("{}", diagnostic.show(&severity_map).unwrap()) 28 | } 29 | panic!("Formatting failed! File was OK before, but is not after"); 30 | } 31 | for ((tokens_a, _), (tokens_b, _)) in zip(new_file.design_units, design_file.design_units) { 32 | for (a, b) in zip(tokens_a, tokens_b) { 33 | if !a.equal_format(&b) { 34 | println!("Token mismatch"); 35 | println!("New Token={a:#?}"); 36 | let contents = a.pos.source.contents(); 37 | let a_line = contents.get_line(a.pos.range.start.line as usize).unwrap(); 38 | println!(" {a_line}"); 39 | println!("Old Token={b:#?}"); 40 | let b_line = result.lines().nth(b.pos.range.start.line as usize).unwrap(); 41 | println!(" {b_line}"); 42 | panic!("Token Mismatch") 43 | } 44 | } 45 | } 46 | 47 | Ok(()) 48 | } 49 | 50 | fn format_dir(path: &Path) -> Result<(), Box> { 51 | for entry in fs::read_dir(path)? { 52 | let entry = entry?; 53 | let file_type = entry.file_type()?; 54 | if file_type.is_dir() { 55 | format_dir(&entry.path())? 56 | } else if let Some(extension) = entry.path().extension() { 57 | if (extension == "vhd" || extension == "vhdl") && !is_file_excluded(&entry.path()) { 58 | format_file(&entry.path())? 59 | } 60 | } 61 | } 62 | Ok(()) 63 | } 64 | 65 | fn is_file_excluded(path: &Path) -> bool { 66 | for file in EXCLUDED_FILES { 67 | if path.ends_with(file) { 68 | return true; 69 | } 70 | } 71 | false 72 | } 73 | 74 | // Checks that all files in the example project are correctly formatted 75 | // while retaining their token stream. 76 | #[test] 77 | fn formats_all_vhdl_files_without_producing_different_code() -> Result<(), Box> { 78 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 79 | path.push("../example_project"); 80 | format_dir(&path) 81 | } 82 | -------------------------------------------------------------------------------- /vhdl_lang/tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::prelude::*; 2 | use itertools::Itertools; 3 | use predicates::prelude::*; 4 | use std::error::Error; 5 | use std::path::PathBuf; 6 | use std::process::Command; 7 | use vhdl_lang::{Config, MessagePrinter, Project, Severity}; 8 | 9 | #[test] 10 | pub fn parses_example_project_without_errors() { 11 | let mut config = Config::default(); 12 | let mut msg_printer = MessagePrinter::default(); 13 | 14 | let mut vhdl_libraries_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 15 | // Load the VHDL standard libraries 16 | vhdl_libraries_path.push("../vhdl_libraries/vhdl_ls.toml"); 17 | config.append( 18 | &Config::read_file_path(&vhdl_libraries_path).expect("Failed to read config file"), 19 | &mut msg_printer, 20 | ); 21 | 22 | // Load the configuration from the example project 23 | let mut config_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 24 | config_path.push("../example_project/vhdl_ls.toml"); 25 | config.append( 26 | &Config::read_file_path(&config_path).expect("Failed to read config file"), 27 | &mut msg_printer, 28 | ); 29 | 30 | let severity_map = *config.severities(); 31 | let mut project = Project::from_config(config, &mut msg_printer); 32 | project.enable_all_linters(); 33 | 34 | let diagnostics = project.analyse(); 35 | let diagnostics_with_errors = diagnostics 36 | .iter() 37 | .filter(|diag| severity_map[diag.code] == Some(Severity::Error)) 38 | .collect_vec(); 39 | if !diagnostics_with_errors.is_empty() { 40 | for diagnostic in diagnostics_with_errors { 41 | println!("{}", diagnostic.show(&severity_map).unwrap()) 42 | } 43 | panic!("Found diagnostics with severity error in the example project"); 44 | } 45 | } 46 | 47 | #[test] 48 | fn unused_function_gets_detected() -> Result<(), Box> { 49 | let mut cmd = Command::cargo_bin("vhdl_lang")?; 50 | 51 | cmd.arg("--config") 52 | .arg("tests/unused_declarations/vhdl_ls.toml") 53 | .arg("--libraries") 54 | .arg("../vhdl_libraries"); 55 | cmd.assert().failure().stdout(predicate::str::contains( 56 | "error: Unused declaration of port 'baz' : inout", 57 | )); 58 | 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /vhdl_lang/tests/unused_declarations/my_entity.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | 4 | entity my_ent is 5 | port ( 6 | foo : in std_logic; 7 | bar : out std_logic; 8 | baz : inout std_logic 9 | ); 10 | end my_ent; 11 | 12 | architecture arch of my_ent is 13 | begin 14 | bar <= foo; 15 | end architecture arch; 16 | -------------------------------------------------------------------------------- /vhdl_lang/tests/unused_declarations/vhdl_ls.toml: -------------------------------------------------------------------------------- 1 | [libraries] 2 | 3 | my_library.files = ["my_entity.vhd"] 4 | 5 | [lint] 6 | unused = "error" 7 | -------------------------------------------------------------------------------- /vhdl_lang_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | # You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | [package] 8 | name = "vhdl_lang_macros" 9 | version.workspace = true 10 | authors.workspace = true 11 | license.workspace = true 12 | description = "VHDL Language Frontend - Macros" 13 | repository.workspace = true 14 | edition.workspace = true 15 | rust-version.workspace = true 16 | 17 | [lib] 18 | proc-macro = true 19 | 20 | [dependencies] 21 | syn = { version = "2", features = ["full"] } 22 | quote = "1" 23 | -------------------------------------------------------------------------------- /vhdl_lang_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | extern crate proc_macro; 7 | use proc_macro::TokenStream; 8 | 9 | mod token_span_attribute; 10 | mod token_span_derive; 11 | 12 | #[proc_macro_derive(TokenSpan)] 13 | pub fn impl_token_span_trait(input: TokenStream) -> TokenStream { 14 | token_span_derive::add_token_span_impl(input) 15 | } 16 | 17 | #[proc_macro_attribute] 18 | pub fn with_token_span(args: TokenStream, input: TokenStream) -> TokenStream { 19 | token_span_attribute::add_token_span_fields(args, input) 20 | } 21 | -------------------------------------------------------------------------------- /vhdl_lang_macros/src/token_span_attribute.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use proc_macro::TokenStream; 8 | 9 | use quote::quote; 10 | use syn::{ 11 | parse::{Nothing, Parser}, 12 | parse_macro_input, parse_quote, 13 | punctuated::Punctuated, 14 | spanned::Spanned, 15 | token::Brace, 16 | FieldsNamed, ItemStruct, 17 | }; 18 | 19 | pub fn add_token_span_fields(args: TokenStream, input: TokenStream) -> TokenStream { 20 | let _ = parse_macro_input!(args as Nothing); 21 | let mut item_struct = parse_macro_input!(input as ItemStruct); 22 | 23 | // Add an attribute to the struct for deriving the `HasTokenSpan` implementation 24 | let token_span_attr_raw: syn::Attribute = 25 | parse_quote! { #[derive(::vhdl_lang_macros::TokenSpan)] }; 26 | item_struct.attrs.push(token_span_attr_raw); 27 | 28 | // Create (or move from the given struct) an instance of `FieldsNamed` which holds a list of named fields 29 | let mut fields: FieldsNamed; 30 | if let syn::Fields::Unit = item_struct.fields { 31 | fields = FieldsNamed { 32 | brace_token: Brace::default(), // The token for the '{' braces, with a default span 33 | named: Punctuated::default(), // An empty list of named fields 34 | }; 35 | } else if let syn::Fields::Named(inner_fields) = item_struct.fields { 36 | fields = inner_fields; 37 | } else { 38 | return syn::Error::new( 39 | item_struct.span(), 40 | "A token span cannot be added for tuples!", 41 | ) 42 | .into_compile_error() 43 | .into(); 44 | } 45 | 46 | // Add the required field 47 | fields.named.push( 48 | syn::Field::parse_named 49 | .parse2(quote! {pub(crate) span: ::vhdl_lang::TokenSpan}) 50 | .unwrap(), 51 | ); 52 | 53 | // Put the modified list back into the struct 54 | item_struct.fields = syn::Fields::Named(fields); 55 | 56 | quote! { 57 | #item_struct 58 | } 59 | .into() 60 | } 61 | -------------------------------------------------------------------------------- /vhdl_lang_macros/src/token_span_derive.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use proc_macro::TokenStream; 8 | 9 | use quote::quote; 10 | use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Item, ItemEnum, ItemStruct}; 11 | 12 | pub fn add_token_span_impl(input: TokenStream) -> TokenStream { 13 | let input = parse_macro_input!(input as DeriveInput); 14 | let item = Item::from(input); 15 | 16 | match item { 17 | syn::Item::Struct(item) => add_token_span_impl_struct(item), 18 | syn::Item::Enum(item) => add_token_span_impl_enum(item), 19 | _ => syn::Error::new( 20 | item.span(), 21 | "The TokenSpan macro can only be applied to struct and enums!", 22 | ) 23 | .into_compile_error() 24 | .into(), 25 | } 26 | } 27 | 28 | fn add_token_span_impl_struct(item: ItemStruct) -> TokenStream { 29 | let name = item.ident; 30 | let generics = &item.generics; 31 | let where_clause = &item.generics.where_clause; 32 | 33 | quote! { 34 | #[automatically_derived] 35 | impl #generics ::vhdl_lang::HasTokenSpan for #name #generics #where_clause { 36 | fn get_start_token(&self) -> ::vhdl_lang::TokenId { 37 | self.span.start_token 38 | } 39 | 40 | fn get_end_token(&self) -> ::vhdl_lang::TokenId { 41 | self.span.end_token 42 | } 43 | 44 | fn get_token_slice<'a>(&self, tokens: &'a dyn ::vhdl_lang::TokenAccess) -> &'a[::vhdl_lang::Token] { 45 | tokens.get_token_slice(self.get_start_token(), self.get_end_token()) 46 | } 47 | 48 | fn get_pos(&self, tokens: &dyn ::vhdl_lang::TokenAccess) -> ::vhdl_lang::SrcPos { 49 | tokens.get_span(self.get_start_token(), self.get_end_token()) 50 | } 51 | } 52 | 53 | } 54 | .into() 55 | } 56 | 57 | fn add_token_span_impl_enum(item: ItemEnum) -> TokenStream { 58 | let enum_name = item.ident; 59 | 60 | // Some validity checks for enums 61 | for variant in &item.variants { 62 | // Only allow enums with unnamed fields 63 | if !matches!(variant.fields, syn::Fields::Unnamed(_)) { 64 | return syn::Error::new( 65 | variant.span(), 66 | "All variants of enumerations using the TokenSpan macro must use unnamed fields!", 67 | ) 68 | .into_compile_error() 69 | .into(); 70 | } 71 | 72 | // Only allow enums with exactly 1 field 73 | if variant.fields.len() != 1 { 74 | return syn::Error::new( 75 | variant.span(), 76 | "All variants of enumerations using the TokenSpan macro must use contain exactly one field!", 77 | ) 78 | .into_compile_error() 79 | .into(); 80 | } 81 | } 82 | 83 | let variant_names: Vec = item 84 | .variants 85 | .into_iter() 86 | .map(|variant| variant.ident) 87 | .collect(); 88 | 89 | // Parse the delegating implementations for all `HasTokenSpan` methods 90 | // Note that by calling the methods using the trait itself `HasTokenSpan`, we get nice error messages if 91 | // a fields' type does not implement the trait! 92 | quote! { 93 | impl ::vhdl_lang::HasTokenSpan for #enum_name { 94 | fn get_start_token(&self) -> ::vhdl_lang::TokenId { 95 | match self { 96 | #( #enum_name::#variant_names(inner) => ::vhdl_lang::HasTokenSpan::get_start_token(inner), )* 97 | } 98 | } 99 | fn get_end_token(&self) -> ::vhdl_lang::TokenId { 100 | match self { 101 | #( #enum_name::#variant_names(inner) => ::vhdl_lang::HasTokenSpan::get_end_token(inner), )* 102 | } 103 | } 104 | 105 | fn get_token_slice<'internal_a>(&self, tokens: &'internal_a dyn ::vhdl_lang::TokenAccess) -> &'internal_a [::vhdl_lang::Token] { 106 | match self { 107 | #( #enum_name::#variant_names(inner) => ::vhdl_lang::HasTokenSpan::get_token_slice(inner, tokens), )* 108 | } 109 | } 110 | 111 | fn get_pos(&self, ctx: &dyn ::vhdl_lang::TokenAccess) -> ::vhdl_lang::SrcPos { 112 | match self { 113 | #( #enum_name::#variant_names(inner) => ::vhdl_lang::HasTokenSpan::get_pos(inner, ctx), )* 114 | } 115 | } 116 | } 117 | } 118 | .into() 119 | } 120 | -------------------------------------------------------------------------------- /vhdl_libraries/ieee2008/README.ieee: -------------------------------------------------------------------------------- 1 | https://standards.ieee.org/news/2013/ieee_1076_vhdl.html 2 | 3 | MODIFICATION TO IEEE 1076™ LICENSING TERMS ALLOWS FOR OPEN USE OF VHDL STANDARD’S SUPPLEMENTAL MATERIAL 4 | 5 | IEEE 1076 working group assists with creation of additional packages for developers to cultivate new hardware-designer tools to expand VHDL systems 6 | 7 | Shuang Yu, Senior Manager, Solutions Marketing 8 | +1 732 981 3424; shuang.yu@ieee.org 9 | 10 | PISCATAWAY, N.J., USA, 16 July 2013 - IEEE, the world's largest professional organization advancing technology for humanity, today announced a licensing term modification to the supplemental materials for IEEE 1076™ “Standard VHDL Language Reference Manual.” The licensing change allows the standard’s supplemental materials or “packages” to be made available to the public without prior authorization from the IEEE Standards Association (IEEE-SA). The supplemental packages provide developers with the necessary machine-readable code to build hardware-designer tools that help create systems using Very High Speed Integrated Circuits (VHSIC) Hardware Description Language (VHDL), which is then used to design electronic systems at the component and board levels. 11 | 12 | By allowing free use of the standard’s VHDL packages each time a new VHDL design is implemented, the new license terms are intended to save developers the time required to request authorization for their legal use in real-world applications. Under the previous terms, users of the IEEE 1076 standard’s packages were required to request permission from the IEEE-SA. The various packages include the definition of standard types, subtypes, natures and constants for hardware modeling. 13 | 14 | “This licensing change is very important to vendors and users of VHDL,” said Stan Krolikoski, chair of the IEEE Computer Society's Design Automation Standards Committee (DASC), which supported the modification to the license for the supplemental materials. “We worked closely with the IEEE 1076 VHDL Analysis and Standardization Working Group to allow anyone to use these files, within the guidelines of the IEEE 1076-2008 standard, of course. The previous header to the standard’s supplemental packages instructed users to request permission from the IEEE-SA for all uses, which is no longer required.” 15 | 16 | The collaboration between the DASC and the IEEE 1076 Working Group to modify the licensing of the supplemental materials to IEEE 1076 was conducted in the spirit of the OpenStand paradigm for global, open standards. Globally adopted design automation standards, such as IEEE 1076, have paved the way for a giant leap forward in industry's ability to define complex electronic solutions and are examples of standards developed under the market-driven OpenStand principles. 17 | 18 | For more information about the IEEE 1076 VHDL Analysis and Standardization Working Group, please visit the Working Group web page. 19 | 20 | Download IEEE 1076’s supplemental materials at the working group web page. IEEE 1076 is available for purchase at the IEEE Standards Store. 21 | 22 | To learn more about IEEE-SA, visit us on Facebook, follow us on Twitter, connect with us on LinkedIn, or on the Standards Insight Blog. 23 | 24 | About the IEEE Standards Association 25 | The IEEE Standards Association, a globally recognized standards-setting body within IEEE, develops consensus standards through an open process that engages industry and brings together a broad stakeholder community. IEEE standards set specifications and best practices based on current scientific and technological knowledge. The IEEE-SA has a portfolio of over 900 active standards and more than 500 standards under development. For more information visit the IEEE-SA Web site. 26 | 27 | About IEEE 28 | IEEE, a large, global technical professional organization, is dedicated to advancing technology for the benefit of humanity. Through its highly cited publications, conferences, technology standards, and professional and educational activities, IEEE is the trusted voice on a wide variety of areas ranging from aerospace systems, computers and telecommunications to biomedical engineering, electric power and consumer electronics. Learn more at the IEEE Web site. external link 29 | 30 | - See more at: https://standards.ieee.org/news/2013/ieee_1076_vhdl.html#sthash.UFLcTc7E.dpuf 31 | -------------------------------------------------------------------------------- /vhdl_libraries/ieee2008/fixed_float_types.vhdl: -------------------------------------------------------------------------------- 1 | -- ----------------------------------------------------------------- 2 | -- 3 | -- Copyright 2019 IEEE P1076 WG Authors 4 | -- 5 | -- See the LICENSE file distributed with this work for copyright and 6 | -- licensing information and the AUTHORS file. 7 | -- 8 | -- This file to you under the Apache License, Version 2.0 (the "License"). 9 | -- You may obtain a copy of the License at 10 | -- 11 | -- http://www.apache.org/licenses/LICENSE-2.0 12 | -- 13 | -- Unless required by applicable law or agreed to in writing, software 14 | -- distributed under the License is distributed on an "AS IS" BASIS, 15 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | -- implied. See the License for the specific language governing 17 | -- permissions and limitations under the License. 18 | -- 19 | -- Title : Fixed Point and Floating Point types package 20 | -- 21 | -- Library : This package shall be compiled into a library 22 | -- symbolically named IEEE. 23 | -- 24 | -- Developers: Accellera VHDL-TC and IEEE P1076 Working Group 25 | -- 26 | -- Purpose : Definitions for use in fixed point and floating point 27 | -- arithmetic packages 28 | -- 29 | -- Note : This package may be modified to include additional data 30 | -- : required by tools, but it must in no way change the 31 | -- : external interfaces or simulation behavior of the 32 | -- : description. It is permissible to add comments and/or 33 | -- : attributes to the package declarations, but not to change 34 | -- : or delete any original lines of the package declaration. 35 | -- : The package body may be changed only in accordance with 36 | -- : the terms of Clause 16 of this standard. 37 | -- : 38 | -- -------------------------------------------------------------------- 39 | -- $Revision: 1220 $ 40 | -- $Date: 2008-04-10 17:16:09 +0930 (Thu, 10 Apr 2008) $ 41 | -- -------------------------------------------------------------------- 42 | 43 | package fixed_float_types is 44 | 45 | -- Types used for generics of fixed_generic_pkg 46 | 47 | type fixed_round_style_type is (fixed_round, fixed_truncate); 48 | 49 | type fixed_overflow_style_type is (fixed_saturate, fixed_wrap); 50 | 51 | -- Type used for generics of float_generic_pkg 52 | 53 | -- These are the same as the C FE_TONEAREST, FE_UPWARD, FE_DOWNWARD, 54 | -- and FE_TOWARDZERO floating point rounding macros. 55 | 56 | type round_type is (round_nearest, -- Default, nearest LSB '0' 57 | round_inf, -- Round toward positive infinity 58 | round_neginf, -- Round toward negative infinity 59 | round_zero); -- Round toward zero (truncate) 60 | 61 | end package fixed_float_types; 62 | -------------------------------------------------------------------------------- /vhdl_libraries/ieee2008/fixed_pkg.vhdl: -------------------------------------------------------------------------------- 1 | -- ----------------------------------------------------------------- 2 | -- 3 | -- Copyright 2019 IEEE P1076 WG Authors 4 | -- 5 | -- See the LICENSE file distributed with this work for copyright and 6 | -- licensing information and the AUTHORS file. 7 | -- 8 | -- This file to you under the Apache License, Version 2.0 (the "License"). 9 | -- You may obtain a copy of the License at 10 | -- 11 | -- http://www.apache.org/licenses/LICENSE-2.0 12 | -- 13 | -- Unless required by applicable law or agreed to in writing, software 14 | -- distributed under the License is distributed on an "AS IS" BASIS, 15 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | -- implied. See the License for the specific language governing 17 | -- permissions and limitations under the License. 18 | -- 19 | -- 20 | -- Title : Fixed-point package (Instantiated package declaration) 21 | -- : 22 | -- Library : This package shall be compiled into a library 23 | -- : symbolically named IEEE. 24 | -- : 25 | -- Developers: Accellera VHDL-TC and IEEE P1076 Working Group 26 | -- : 27 | -- Purpose : This packages defines basic binary fixed point 28 | -- : arithmetic functions 29 | -- : 30 | -- Note : This package may be modified to include additional data 31 | -- : required by tools, but it must in no way change the 32 | -- : external interfaces or simulation behavior of the 33 | -- : description. It is permissible to add comments and/or 34 | -- : attributes to the package declarations, but not to change 35 | -- : or delete any original lines of the package declaration. 36 | -- : The package body may be changed only in accordance with 37 | -- : the terms of Clause 16 of this standard. 38 | -- : 39 | -- -------------------------------------------------------------------- 40 | -- $Revision: 1220 $ 41 | -- $Date: 2008-04-10 17:16:09 +0930 (Thu, 10 Apr 2008) $ 42 | -- -------------------------------------------------------------------- 43 | 44 | library IEEE; 45 | 46 | package fixed_pkg is new IEEE.fixed_generic_pkg 47 | generic map ( 48 | fixed_round_style => IEEE.fixed_float_types.fixed_round, 49 | fixed_overflow_style => IEEE.fixed_float_types.fixed_saturate, 50 | fixed_guard_bits => 3, 51 | no_warning => false 52 | ); 53 | -------------------------------------------------------------------------------- /vhdl_libraries/ieee2008/float_pkg.vhdl: -------------------------------------------------------------------------------- 1 | -- ----------------------------------------------------------------- 2 | -- 3 | -- Copyright 2019 IEEE P1076 WG Authors 4 | -- 5 | -- See the LICENSE file distributed with this work for copyright and 6 | -- licensing information and the AUTHORS file. 7 | -- 8 | -- This file to you under the Apache License, Version 2.0 (the "License"). 9 | -- You may obtain a copy of the License at 10 | -- 11 | -- http://www.apache.org/licenses/LICENSE-2.0 12 | -- 13 | -- Unless required by applicable law or agreed to in writing, software 14 | -- distributed under the License is distributed on an "AS IS" BASIS, 15 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 16 | -- implied. See the License for the specific language governing 17 | -- permissions and limitations under the License. 18 | -- 19 | -- Title : Floating-point package (Instantiated package declaration) 20 | -- : 21 | -- Library : This package shall be compiled into a library 22 | -- : symbolically named IEEE. 23 | -- : 24 | -- Developers: Accellera VHDL-TC and IEEE P1076 Working Group 25 | -- : 26 | -- Purpose : This packages defines basic binary floating point 27 | -- : arithmetic functions 28 | -- : 29 | -- Note : This package may be modified to include additional data 30 | -- : required by tools, but it must in no way change the 31 | -- : external interfaces or simulation behavior of the 32 | -- : description. It is permissible to add comments and/or 33 | -- : attributes to the package declarations, but not to change 34 | -- : or delete any original lines of the package declaration. 35 | -- : The package body may be changed only in accordance with 36 | -- : the terms of Clause 16 of this standard. 37 | -- : 38 | -- -------------------------------------------------------------------- 39 | -- $Revision: 1220 $ 40 | -- $Date: 2008-04-10 17:16:09 +0930 (Thu, 10 Apr 2008) $ 41 | -- -------------------------------------------------------------------- 42 | 43 | library ieee; 44 | 45 | package float_pkg is new IEEE.float_generic_pkg 46 | generic map ( 47 | float_exponent_width => 8, -- float32'high 48 | float_fraction_width => 23, -- -float32'low 49 | float_round_style => IEEE.fixed_float_types.round_nearest, -- round nearest algorithm 50 | float_denormalize => true, -- Use IEEE extended floating 51 | float_check_error => true, -- Turn on NAN and overflow processing 52 | float_guard_bits => 3, -- number of guard bits 53 | no_warning => false, -- show warnings 54 | fixed_pkg => IEEE.fixed_pkg 55 | ); 56 | -------------------------------------------------------------------------------- /vhdl_libraries/ieee2008/ieee_bit_context.vhdl: -------------------------------------------------------------------------------- 1 | context IEEE_BIT_CONTEXT is 2 | library IEEE; 3 | use IEEE.NUMERIC_BIT.all; 4 | end context IEEE_BIT_CONTEXT; 5 | -------------------------------------------------------------------------------- /vhdl_libraries/ieee2008/ieee_std_context.vhdl: -------------------------------------------------------------------------------- 1 | context IEEE_STD_CONTEXT is 2 | library IEEE; 3 | use IEEE.STD_LOGIC_1164.all; 4 | use IEEE.NUMERIC_STD.all; 5 | end context IEEE_STD_CONTEXT; 6 | -------------------------------------------------------------------------------- /vhdl_libraries/ieee2008/std_logic_textio.vhdl: -------------------------------------------------------------------------------- 1 | package std_logic_textio is 2 | -- empty. 3 | end std_logic_textio; 4 | -------------------------------------------------------------------------------- /vhdl_libraries/std/env.vhd: -------------------------------------------------------------------------------- 1 | -- Package env as defined by IEEE 1076-2008 2 | 3 | package env is 4 | procedure stop(status : integer); 5 | procedure stop; 6 | 7 | procedure finish(status : integer); 8 | procedure finish; 9 | 10 | function resolution_limit return delay_length; 11 | end package; 12 | -------------------------------------------------------------------------------- /vhdl_libraries/std/standard.vhd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VHDL-LS/rust_hdl/93b7052e98b6a159bf8f929727080a4e86f1ae61/vhdl_libraries/std/standard.vhd -------------------------------------------------------------------------------- /vhdl_libraries/std/textio.vhd: -------------------------------------------------------------------------------- 1 | -- Package texio as defined by IEEE 1076-2008 2 | 3 | package textio is 4 | type LINE is access STRING; 5 | type TEXT is file of STRING; 6 | 7 | procedure FILE_REWIND (file F: TEXT); 8 | function FILE_MODE (file F: TEXT) return FILE_OPEN_KIND; 9 | function FILE_SIZE (file F: TEXT) return INTEGER; 10 | 11 | type SIDE is (RIGHT, LEFT); 12 | subtype WIDTH is NATURAL; -- For specifying widths of output fields. 13 | 14 | function JUSTIFY (VALUE: STRING; JUSTIFIED: SIDE := RIGHT; FIELD: WIDTH := 0 ) return STRING; 15 | -- Standard text files: 16 | file INPUT: TEXT open READ_MODE is "STD_INPUT"; 17 | file OUTPUT: TEXT open WRITE_MODE is "STD_OUTPUT"; 18 | 19 | -- Input routines for standard types: 20 | procedure READLINE (file F: TEXT; L: inout LINE); 21 | procedure READ (L: inout LINE; VALUE: out BIT; GOOD: out BOOLEAN); 22 | procedure READ (L: inout LINE; VALUE: out BIT); 23 | procedure READ (L: inout LINE; VALUE: out BIT_VECTOR; GOOD: out BOOLEAN); 24 | procedure READ (L: inout LINE; VALUE: out BIT_VECTOR); 25 | procedure READ (L: inout LINE; VALUE: out BOOLEAN; GOOD: out BOOLEAN); 26 | procedure READ (L: inout LINE; VALUE: out BOOLEAN); 27 | procedure READ (L: inout LINE; VALUE: out CHARACTER; GOOD: out BOOLEAN); 28 | procedure READ (L: inout LINE; VALUE: out CHARACTER); 29 | procedure READ (L: inout LINE; VALUE: out INTEGER; GOOD: out BOOLEAN); 30 | procedure READ (L: inout LINE; VALUE: out INTEGER); 31 | procedure READ (L: inout LINE; VALUE: out REAL; GOOD: out BOOLEAN); 32 | procedure READ (L: inout LINE; VALUE: out REAL); 33 | procedure READ (L: inout LINE; VALUE: out STRING; GOOD: out BOOLEAN); 34 | procedure READ (L: inout LINE; VALUE: out STRING); 35 | procedure READ (L: inout LINE; VALUE: out TIME; GOOD: out BOOLEAN); 36 | procedure READ (L: inout LINE; VALUE: out TIME); 37 | procedure SREAD (L: inout LINE; VALUE: out STRING; STRLEN: out NATURAL); 38 | alias STRING_READ is SREAD [LINE, STRING, NATURAL]; 39 | alias BREAD is READ [LINE, BIT_VECTOR, BOOLEAN]; 40 | alias BREAD is READ [LINE, BIT_VECTOR]; 41 | alias BINARY_READ is READ [LINE, BIT_VECTOR, BOOLEAN]; 42 | alias BINARY_READ is READ [LINE, BIT_VECTOR]; 43 | procedure OREAD (L: inout LINE; VALUE: out BIT_VECTOR; GOOD: out BOOLEAN); 44 | procedure OREAD (L: inout LINE; VALUE: out BIT_VECTOR); 45 | alias OCTAL_READ is OREAD [LINE, BIT_VECTOR, BOOLEAN]; 46 | alias OCTAL_READ is OREAD [LINE, BIT_VECTOR]; 47 | procedure HREAD (L: inout LINE; VALUE: out BIT_VECTOR; GOOD: out BOOLEAN); 48 | procedure HREAD (L: inout LINE; VALUE: out BIT_VECTOR); 49 | alias HEX_READ is HREAD [LINE, BIT_VECTOR, BOOLEAN]; 50 | alias HEX_READ is HREAD [LINE, BIT_VECTOR]; 51 | 52 | -- Output routines for standard types: 53 | procedure WRITELINE (file F: TEXT; L: inout LINE); 54 | procedure TEE (file F: TEXT; L: inout LINE); 55 | procedure WRITE (L: inout LINE; VALUE: in BIT; JUSTIFIED: in SIDE:= RIGHT; FIELD: in WIDTH := 0); 56 | procedure WRITE (L: inout LINE; VALUE: in BIT_VECTOR; JUSTIFIED: in SIDE:= RIGHT; FIELD: in WIDTH := 0); 57 | procedure WRITE (L: inout LINE; VALUE: in BOOLEAN; JUSTIFIED: in SIDE:= RIGHT; FIELD: in WIDTH := 0); 58 | procedure WRITE (L: inout LINE; VALUE: in CHARACTER; JUSTIFIED: in SIDE:= RIGHT; FIELD: in WIDTH := 0); 59 | procedure WRITE (L: inout LINE; VALUE: in INTEGER; JUSTIFIED: in SIDE:= RIGHT; FIELD: in WIDTH := 0); 60 | procedure WRITE (L: inout LINE; VALUE: in REAL; JUSTIFIED: in SIDE:= RIGHT; FIELD: in WIDTH := 0; DIGITS: in NATURAL:= 0); 61 | procedure WRITE (L: inout LINE; VALUE: in REAL; FORMAT: in STRING); 62 | procedure WRITE (L: inout LINE; VALUE: in STRING; JUSTIFIED: in SIDE:= RIGHT; FIELD: in WIDTH := 0); 63 | procedure WRITE (L: inout LINE; VALUE: in TIME; JUSTIFIED: in SIDE:= RIGHT; FIELD: in WIDTH := 0; UNIT: in TIME:= ns); 64 | alias SWRITE is WRITE [LINE, STRING, SIDE, WIDTH]; 65 | alias STRING_WRITE is WRITE [LINE, STRING, SIDE, WIDTH]; 66 | alias BWRITE is WRITE [LINE, BIT_VECTOR, SIDE, WIDTH]; 67 | alias BINARY_WRITE is WRITE [LINE, BIT_VECTOR, SIDE, WIDTH]; 68 | procedure OWRITE (L: inout LINE; VALUE: in BIT_VECTOR; JUSTIFIED: in SIDE := RIGHT; FIELD: in WIDTH := 0); 69 | alias OCTAL_WRITE is OWRITE [LINE, BIT_VECTOR, SIDE, WIDTH]; 70 | procedure HWRITE (L: inout LINE; VALUE: in BIT_VECTOR; JUSTIFIED: in SIDE := RIGHT; FIELD: in WIDTH := 0); 71 | alias HEX_WRITE is HWRITE [LINE, BIT_VECTOR, SIDE, WIDTH]; 72 | 73 | end package; 74 | -------------------------------------------------------------------------------- /vhdl_libraries/vhdl_ls.toml: -------------------------------------------------------------------------------- 1 | [libraries] 2 | 3 | std.files = ['std/*.vhd'] 4 | std.is_third_party = true 5 | 6 | ieee.files = ['ieee2008/*.vhdl', 'synopsys/*.vhdl', 'vital2000/*.vhdl'] 7 | ieee.is_third_party = true 8 | -------------------------------------------------------------------------------- /vhdl_ls/Cargo.toml: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | # You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | [package] 8 | name = "vhdl_ls" 9 | version.workspace = true 10 | authors.workspace = true 11 | license.workspace = true 12 | description = "VHDL Language Server" 13 | repository.workspace = true 14 | edition.workspace = true 15 | readme.workspace = true 16 | rust-version.workspace = true 17 | 18 | [dependencies] 19 | vhdl_lang = { version = "^0.85.0", path = "../vhdl_lang" } 20 | serde_json = "1" 21 | serde = "1" 22 | lsp-types = "^0.95.1" 23 | fnv = "1" 24 | log = "0.4.27" 25 | env_logger = "0.11.8" 26 | clap = { version = "4", features = ["derive"] } 27 | lsp-server = "0.7.8" 28 | fuzzy-matcher = "0.3.7" 29 | 30 | [dev-dependencies] 31 | tempfile = "3" 32 | pretty_assertions = "1" 33 | regex = "1.10.5" 34 | 35 | [features] 36 | default = [] 37 | -------------------------------------------------------------------------------- /vhdl_ls/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | #![allow(clippy::upper_case_acronyms)] 7 | 8 | #[macro_use] 9 | extern crate log; 10 | 11 | mod rpc_channel; 12 | mod stdio_server; 13 | mod vhdl_server; 14 | pub use crate::stdio_server::start; 15 | pub use crate::vhdl_server::VHDLServerSettings; 16 | -------------------------------------------------------------------------------- /vhdl_ls/src/main.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | // You can obtain one at http://mozilla.org/MPL/2.0/. 4 | // 5 | // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com 6 | 7 | use clap::Parser; 8 | use vhdl_ls::VHDLServerSettings; 9 | 10 | #[derive(Parser)] 11 | #[command(author, version, about, long_about = None)] 12 | struct Args { 13 | /// Disable diagnostic messages, only use navigation and hover features 14 | #[arg(long, default_value_t = false)] 15 | no_lint: bool, 16 | 17 | /// Path to the config file for the VHDL standard libraries (i.e., IEEE std_logic_1164). 18 | /// If omitted, will search for these libraries in a set of standard paths 19 | #[arg(short = 'l', long)] 20 | libraries: Option, 21 | 22 | /// Normally warning and error messages are sent to window/showMessage 23 | /// This will silence all window/showMessage and only use window/logMessage 24 | #[arg(long, default_value_t = false)] 25 | silent: bool, 26 | } 27 | 28 | fn main() { 29 | let args = Args::parse(); 30 | 31 | env_logger::init(); 32 | log::info!("Starting language server"); 33 | vhdl_ls::start(VHDLServerSettings { 34 | no_lint: args.no_lint, 35 | silent: args.silent, 36 | libraries_path: args.libraries, 37 | ..Default::default() 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /vhdl_ls/src/vhdl_server/lifecycle.rs: -------------------------------------------------------------------------------- 1 | use crate::vhdl_server::{NonProjectFileHandling, VHDLServer}; 2 | use lsp_types::*; 3 | use serde_json::Value; 4 | use vhdl_lang::{Message, Project}; 5 | 6 | impl VHDLServer { 7 | fn apply_initial_options(&mut self, options: &Value) { 8 | let Some(non_project_file_handling) = options.get("nonProjectFiles") else { 9 | return; 10 | }; 11 | match non_project_file_handling { 12 | Value::String(handling) => match NonProjectFileHandling::from_string(handling) { 13 | None => self.message(Message::error(format!( 14 | "Illegal setting {handling} for nonProjectFiles setting" 15 | ))), 16 | Some(handling) => self.settings.non_project_file_handling = handling, 17 | }, 18 | _ => self.message(Message::error("nonProjectFiles must be a string")), 19 | } 20 | } 21 | 22 | /// Register capabilities on the client side: 23 | /// - watch workspace config file for changes 24 | fn register_capabilities(&mut self) { 25 | if self.client_supports_did_change_watched_files() { 26 | let register_options = DidChangeWatchedFilesRegistrationOptions { 27 | watchers: vec![FileSystemWatcher { 28 | glob_pattern: GlobPattern::String("**/vhdl_ls.toml".to_owned()), 29 | kind: None, 30 | }], 31 | }; 32 | let params = RegistrationParams { 33 | registrations: vec![Registration { 34 | id: "workspace/didChangeWatchedFiles".to_owned(), 35 | method: "workspace/didChangeWatchedFiles".to_owned(), 36 | register_options: serde_json::to_value(register_options).ok(), 37 | }], 38 | }; 39 | self.rpc.send_request("client/registerCapability", params); 40 | } 41 | } 42 | 43 | pub fn initialized_notification(&mut self) { 44 | self.register_capabilities(); 45 | self.publish_diagnostics(); 46 | } 47 | 48 | pub fn initialize_request(&mut self, init_params: InitializeParams) -> InitializeResult { 49 | self.config_file = self.root_uri_config_file(&init_params); 50 | let config = self.load_config(); 51 | self.severity_map = *config.severities(); 52 | self.project = Project::from_config(config, &mut self.message_filter()); 53 | self.project.enable_all_linters(); 54 | if let Some(options) = &init_params.initialization_options { 55 | self.apply_initial_options(options) 56 | } 57 | self.init_params = Some(init_params); 58 | let trigger_chars: Vec = r"'.".chars().map(|ch| ch.to_string()).collect(); 59 | 60 | let capabilities = ServerCapabilities { 61 | text_document_sync: Some(TextDocumentSyncCapability::Kind( 62 | TextDocumentSyncKind::INCREMENTAL, 63 | )), 64 | declaration_provider: Some(DeclarationCapability::Simple(true)), 65 | definition_provider: Some(OneOf::Left(true)), 66 | hover_provider: Some(HoverProviderCapability::Simple(true)), 67 | references_provider: Some(OneOf::Left(true)), 68 | implementation_provider: Some(ImplementationProviderCapability::Simple(true)), 69 | rename_provider: Some(OneOf::Right(RenameOptions { 70 | prepare_provider: Some(true), 71 | work_done_progress_options: Default::default(), 72 | })), 73 | workspace_symbol_provider: Some(OneOf::Left(true)), 74 | document_symbol_provider: Some(OneOf::Left(true)), 75 | document_highlight_provider: Some(OneOf::Left(true)), 76 | completion_provider: Some(CompletionOptions { 77 | resolve_provider: Some(true), 78 | trigger_characters: Some(trigger_chars), 79 | completion_item: Some(CompletionOptionsCompletionItem { 80 | label_details_support: Some(true), 81 | }), 82 | ..Default::default() 83 | }), 84 | ..Default::default() 85 | }; 86 | 87 | InitializeResult { 88 | capabilities, 89 | server_info: None, 90 | } 91 | } 92 | 93 | pub fn shutdown_server(&mut self) { 94 | self.init_params = None; 95 | } 96 | 97 | pub fn exit_notification(&mut self) { 98 | match self.init_params { 99 | Some(_) => ::std::process::exit(1), 100 | None => ::std::process::exit(0), 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /vhdl_ls/src/vhdl_server/rename.rs: -------------------------------------------------------------------------------- 1 | use crate::vhdl_server::{ 2 | from_lsp_pos, srcpos_to_location, to_lsp_range, uri_to_file_name, VHDLServer, 3 | }; 4 | use lsp_types::{ 5 | PrepareRenameResponse, RenameParams, TextDocumentPositionParams, TextEdit, Url, WorkspaceEdit, 6 | }; 7 | use std::collections::HashMap; 8 | use vhdl_lang::ast::Designator; 9 | 10 | impl VHDLServer { 11 | pub fn prepare_rename( 12 | &mut self, 13 | params: &TextDocumentPositionParams, 14 | ) -> Option { 15 | let source = self 16 | .project 17 | .get_source(&uri_to_file_name(¶ms.text_document.uri))?; 18 | 19 | let (pos, ent) = self 20 | .project 21 | .item_at_cursor(&source, from_lsp_pos(params.position))?; 22 | 23 | if let Designator::Identifier(_) = ent.designator() { 24 | Some(PrepareRenameResponse::Range(to_lsp_range(pos.range))) 25 | } else { 26 | // It does not make sense to rename operator symbols and character literals 27 | // Also they have different representations that would not be handled consistently 28 | // Such as function "+"(arg1, arg2 : integer) but used as foo + bar 29 | None 30 | } 31 | } 32 | 33 | pub fn rename(&mut self, params: &RenameParams) -> Option { 34 | let source = self.project.get_source(&uri_to_file_name( 35 | ¶ms.text_document_position.text_document.uri, 36 | ))?; 37 | 38 | let ent = self.project.find_declaration( 39 | &source, 40 | from_lsp_pos(params.text_document_position.position), 41 | )?; 42 | 43 | let mut changes: HashMap> = Default::default(); 44 | 45 | for srcpos in self.project.find_all_references(ent) { 46 | let loc = srcpos_to_location(&srcpos); 47 | changes.entry(loc.uri).or_default().push(TextEdit { 48 | range: loc.range, 49 | new_text: params.new_name.clone(), 50 | }); 51 | } 52 | 53 | Some(WorkspaceEdit { 54 | changes: Some(changes), 55 | ..Default::default() 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /vhdl_ls/src/vhdl_server/text_document.rs: -------------------------------------------------------------------------------- 1 | use crate::vhdl_server::{ 2 | from_lsp_pos, from_lsp_range, srcpos_to_location, to_lsp_range, uri_to_file_name, 3 | NonProjectFileHandling, VHDLServer, 4 | }; 5 | use lsp_types::{ 6 | DidChangeTextDocumentParams, DidOpenTextDocumentParams, DocumentHighlight, 7 | DocumentHighlightKind, GotoDefinitionResponse, Hover, HoverContents, Location, MarkupContent, 8 | MarkupKind, ReferenceParams, TextDocumentItem, TextDocumentPositionParams, 9 | }; 10 | use vhdl_lang::{Message, Source}; 11 | 12 | impl VHDLServer { 13 | pub fn text_document_did_open_notification(&mut self, params: &DidOpenTextDocumentParams) { 14 | let TextDocumentItem { uri, text, .. } = ¶ms.text_document; 15 | let file_name = uri_to_file_name(uri); 16 | if let Some(source) = self.project.get_source(&file_name) { 17 | source.change(None, text); 18 | self.project.update_source(&source); 19 | self.publish_diagnostics(); 20 | } else { 21 | match self.settings.non_project_file_handling { 22 | NonProjectFileHandling::Ignore => {} 23 | NonProjectFileHandling::Analyze => { 24 | self.message(Message::warning(format!( 25 | "Opening file {} that is not part of the project", 26 | file_name.to_string_lossy() 27 | ))); 28 | self.project 29 | .update_source(&Source::inline(&file_name, text)); 30 | self.publish_diagnostics(); 31 | } 32 | } 33 | } 34 | } 35 | 36 | pub fn text_document_did_change_notification(&mut self, params: &DidChangeTextDocumentParams) { 37 | let file_name = uri_to_file_name(¶ms.text_document.uri); 38 | if let Some(source) = self.project.get_source(&file_name) { 39 | for content_change in params.content_changes.iter() { 40 | let range = content_change.range.map(from_lsp_range); 41 | source.change(range.as_ref(), &content_change.text); 42 | } 43 | self.project.update_source(&source); 44 | self.publish_diagnostics(); 45 | } else if self.settings.non_project_file_handling != NonProjectFileHandling::Ignore { 46 | self.message(Message::error(format!( 47 | "Changing file {} that is not part of the project", 48 | file_name.to_string_lossy() 49 | ))); 50 | } 51 | } 52 | 53 | pub fn text_document_declaration( 54 | &mut self, 55 | params: &TextDocumentPositionParams, 56 | ) -> Option { 57 | let source = self 58 | .project 59 | .get_source(&uri_to_file_name(¶ms.text_document.uri))?; 60 | 61 | let ent = self 62 | .project 63 | .find_declaration(&source, from_lsp_pos(params.position))?; 64 | Some(srcpos_to_location(ent.decl_pos()?)) 65 | } 66 | 67 | pub fn text_document_definition( 68 | &mut self, 69 | params: &TextDocumentPositionParams, 70 | ) -> Option { 71 | let source = self 72 | .project 73 | .get_source(&uri_to_file_name(¶ms.text_document.uri))?; 74 | 75 | let ent = self 76 | .project 77 | .find_definition(&source, from_lsp_pos(params.position))?; 78 | Some(srcpos_to_location(ent.decl_pos()?)) 79 | } 80 | 81 | pub fn text_document_implementation( 82 | &mut self, 83 | params: &TextDocumentPositionParams, 84 | ) -> Option { 85 | let source = self 86 | .project 87 | .get_source(&uri_to_file_name(¶ms.text_document.uri))?; 88 | 89 | let ents = self 90 | .project 91 | .find_implementation(&source, from_lsp_pos(params.position)); 92 | 93 | Some(GotoDefinitionResponse::Array( 94 | ents.into_iter() 95 | .filter_map(|ent| ent.decl_pos().map(srcpos_to_location)) 96 | .collect(), 97 | )) 98 | } 99 | 100 | pub fn text_document_hover(&mut self, params: &TextDocumentPositionParams) -> Option { 101 | let source = self 102 | .project 103 | .get_source(&uri_to_file_name(¶ms.text_document.uri))?; 104 | let ent = self 105 | .project 106 | .find_declaration(&source, from_lsp_pos(params.position))?; 107 | 108 | let value = self.project.format_declaration(ent)?; 109 | 110 | Some(Hover { 111 | contents: HoverContents::Markup(MarkupContent { 112 | kind: MarkupKind::Markdown, 113 | value: format!("```vhdl\n{value}\n```"), 114 | }), 115 | range: None, 116 | }) 117 | } 118 | 119 | pub fn text_document_references(&mut self, params: &ReferenceParams) -> Vec { 120 | let ent = self 121 | .project 122 | .get_source(&uri_to_file_name( 123 | ¶ms.text_document_position.text_document.uri, 124 | )) 125 | .and_then(|source| { 126 | self.project.find_declaration( 127 | &source, 128 | from_lsp_pos(params.text_document_position.position), 129 | ) 130 | }); 131 | 132 | if let Some(ent) = ent { 133 | self.project 134 | .find_all_references(ent) 135 | .iter() 136 | .map(srcpos_to_location) 137 | .collect() 138 | } else { 139 | Vec::new() 140 | } 141 | } 142 | 143 | pub fn document_highlight( 144 | &mut self, 145 | params: &TextDocumentPositionParams, 146 | ) -> Option> { 147 | let source = self 148 | .project 149 | .get_source(&uri_to_file_name(¶ms.text_document.uri))?; 150 | 151 | let ent = self 152 | .project 153 | .find_declaration(&source, from_lsp_pos(params.position))?; 154 | 155 | Some( 156 | self.project 157 | .find_all_references_in_source(&source, ent) 158 | .iter() 159 | .map(|pos| DocumentHighlight { 160 | range: to_lsp_range(pos.range()), 161 | kind: Some(DocumentHighlightKind::TEXT), 162 | }) 163 | .collect(), 164 | ) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /vhdl_ls/src/vhdl_server/workspace.rs: -------------------------------------------------------------------------------- 1 | use crate::vhdl_server::{srcpos_to_location, to_symbol_kind, uri_to_file_name, VHDLServer}; 2 | use fuzzy_matcher::FuzzyMatcher; 3 | use lsp_types::{ 4 | DidChangeWatchedFilesParams, OneOf, WorkspaceSymbol, WorkspaceSymbolParams, 5 | WorkspaceSymbolResponse, 6 | }; 7 | use std::cmp::Ordering; 8 | use std::collections::BinaryHeap; 9 | use vhdl_lang::ast::Designator; 10 | use vhdl_lang::{EntRef, Message}; 11 | 12 | impl VHDLServer { 13 | pub fn workspace_did_change_watched_files(&mut self, params: &DidChangeWatchedFilesParams) { 14 | if let Some(config_file) = &self.config_file { 15 | let config_file_has_changed = params 16 | .changes 17 | .iter() 18 | .any(|change| uri_to_file_name(&change.uri).as_path() == config_file); 19 | if config_file_has_changed { 20 | self.message(Message::log( 21 | "Configuration file has changed, reloading project...", 22 | )); 23 | let config = self.load_config(); 24 | self.severity_map = *config.severities(); 25 | 26 | self.project 27 | .update_config(config, &mut self.message_filter()); 28 | self.publish_diagnostics(); 29 | } 30 | } 31 | } 32 | 33 | pub fn workspace_symbol( 34 | &self, 35 | params: &WorkspaceSymbolParams, 36 | ) -> Option { 37 | let trunc_limit = 200; 38 | let query = params.query.clone(); 39 | let symbols = self 40 | .project 41 | .public_symbols() 42 | .filter_map(|ent| match ent.designator() { 43 | Designator::Identifier(_) | Designator::Character(_) => { 44 | Some((ent, ent.designator().to_string())) 45 | } 46 | Designator::OperatorSymbol(op) => Some((ent, op.to_string())), 47 | Designator::Anonymous(_) => None, 48 | }); 49 | 50 | Some(WorkspaceSymbolResponse::Nested( 51 | self.filter_workspace_symbols(symbols.into_iter(), &query, trunc_limit), 52 | )) 53 | } 54 | 55 | /// Filters found workspace symbols according to a given query. 56 | /// This uses a fuzzy matcher internally to improve the results. 57 | /// Queries 'close' to the target string will score high and be included in the 58 | /// returned vec, while queries 'not close' to the target string will be omitted. 59 | /// The returned vec is sorted according to the score of the fuzzy matcher. 60 | fn filter_workspace_symbols<'a>( 61 | &self, 62 | symbols: impl Iterator, String)>, 63 | query: &str, 64 | trunc_limit: usize, 65 | ) -> Vec { 66 | #[derive(Eq, PartialEq)] 67 | struct WorkspaceSymbolWithScore { 68 | symbol: WorkspaceSymbol, 69 | score: i64, 70 | } 71 | 72 | impl PartialOrd for WorkspaceSymbolWithScore { 73 | fn partial_cmp(&self, other: &Self) -> Option { 74 | Some(self.cmp(other)) 75 | } 76 | } 77 | 78 | impl Ord for WorkspaceSymbolWithScore { 79 | fn cmp(&self, other: &Self) -> Ordering { 80 | self.score.cmp(&other.score) 81 | } 82 | } 83 | 84 | let symbols_with_scores: BinaryHeap<_> = symbols 85 | .into_iter() 86 | .filter_map(|(ent, name)| { 87 | let decl_pos = ent.decl_pos()?; 88 | self.string_matcher.fuzzy_match(&name, query).map(|score| { 89 | WorkspaceSymbolWithScore { 90 | symbol: WorkspaceSymbol { 91 | name: ent.describe(), 92 | kind: to_symbol_kind(ent.kind()), 93 | tags: None, 94 | container_name: ent.parent.map(|ent| ent.path_name()), 95 | location: OneOf::Left(srcpos_to_location(decl_pos)), 96 | data: None, 97 | }, 98 | score, 99 | } 100 | }) 101 | }) 102 | .take(trunc_limit) 103 | .collect(); 104 | symbols_with_scores 105 | .into_sorted_vec() 106 | .into_iter() 107 | .rev() 108 | .map(|wsws| wsws.symbol) 109 | .collect() 110 | } 111 | } 112 | --------------------------------------------------------------------------------