first commit

This commit is contained in:
锦恢 2024-10-04 23:29:34 +08:00
commit 8593beff8c
207 changed files with 139180 additions and 0 deletions

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
.git
.github
Dockerfile
example_project
target

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
target/
.vscode/
**/*.rs.bk
.DS_Store

15
.gitmodules vendored Normal file
View File

@ -0,0 +1,15 @@
[submodule "example_project/UVVM"]
path = example_project/UVVM
url = https://github.com/UVVM/UVVM.git
[submodule "example_project/vunit"]
path = example_project/vunit
url = https://github.com/VUnit/vunit.git
[submodule "example_project/PoC"]
path = example_project/PoC
url = https://github.com/VLSI-EDA/PoC.git
[submodule "example_project/OSVVM"]
path = example_project/OSVVM
url = https://github.com/OSVVM/OSVVM.git
[submodule "example_project/neorv32"]
path = example_project/neorv32
url = https://github.com/stnolting/neorv32.git

1219
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
[workspace]
resolver = "2"
members = ["vhdl_lang_macros", "vhdl_lang", "vhdl_ls"]

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
ARG RUST_VERSION=stable
FROM clux/muslrust:$RUST_VERSION as builder
WORKDIR /volume
COPY . /volume/
ARG CRATE
RUN cargo build --manifest-path $CRATE/Cargo.toml --release
FROM scratch
ARG CRATE
COPY --from=builder /volume/target/x86_64-unknown-linux-musl/release/$CRATE /app/bin/exe
COPY --from=builder /volume/vhdl_libraries /app/vhdl_libraries
ENTRYPOINT ["/app/bin/exe"]

5
LICENSE.txt Normal file
View File

@ -0,0 +1,5 @@
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this file,
You can obtain one at http://mozilla.org/MPL/2.0/.
Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com

225
README.md Normal file
View File

@ -0,0 +1,225 @@
# Overview
This repository contains a fast VHDL language server and analysis library written in Rust.
The speed makes the tool very pleasant to use since it loads projects really fast and does not consume a lot of ram.
A 200.000 line VHDL project is analyzed in 160 ms on my Desktop using 8 cores and only consumes 180 MByte of RAM when
loaded.
I very much appreciate help from other people especially regarding semantic analysis of VHDL. You do not need to be a
programmer to help, it is even more helpful to interpret and clarify the VHDL standard and provide minimal examples and
describe how they should work according to the standard. Further information about contributing can be found by reading
the [Contributors Guide](https://github.com/kraigher/rust_hdl/wiki/Contributor-Guide)
[![Chat](https://img.shields.io/matrix/VHDL-LS:matrix.org)](https://matrix.to/#/#VHDL-LS:matrix.org)
[![Build Status](https://github.com/kraigher/rust_hdl/workflows/Build%20%26%20test%20all%20configs/badge.svg)](https://github.com/kraigher/rust_hdl/actions?query=workflow%3A%22Build+%26+test+all+configs%22)
## Contributors
- Maintainer: [Lukas Scheller](https://github.com/Schottkyc137)
- Founder: [Olof Kraigher](https://github.com/kraigher)
# Projects
## VHDL Language Server
[![vhdl ls crate](https://img.shields.io/crates/v/vhdl_ls.svg)](https://crates.io/crates/vhdl_ls)
### Goals
- A complete VHDL language server protocol implementation with diagnostics, navigate to symbol, find all references etc.
### Features
- Live syntax and type checking
- Checks for missing and duplicate declarations
- Supports goto-definition/declaration (also in presence of overloading)
- Supports find-references (also in presence of overloading)
- Supports goto-implementation
- From component declaration to matching entity by default binding
- From entity to matching component declaration by default binding
- Supports hovering symbols
- Rename symbol
- Find workspace symbols
- View/find document symbols
## When Installing it from Crate
When installing the VHDL_LS from [crates.io](https://crates.io/crates/vhdl_ls) the required
[vhdl_libraries](https://github.com/VHDL-LS/rust_hdl/tree/master/vhdl_libraries) directory will not be installed
automatically and
will need to be copied into the parent directory of the VHDL_LS binary manually.
## Trying it out
A language server is never used directly by the end user and it is integrated into different editor plugins. The ones I
know about are listed here.
## Use in VSCode
https://github.com/Bochlin/rust_hdl_vscode
## Use in emacs
VHDL LS has built-in support by emacs `lsp-mode` since 2020-01-04.
It can be set up automatically by installing the package
[`vhdl-ext`](https://github.com/gmlarumbe/vhdl-ext/) and adding the
following snippet to your config:
```elisp
(require 'vhdl-ext)
(vhdl-ext-mode-setup)
(vhdl-ext-eglot-set-server 've-rust-hdl) ;`eglot' config
(vhdl-ext-lsp-set-server 've-rust-hdl) ; `lsp' config
```
## Installation for Neovim
### Automatic Installation
You can install `rust_hdl` automatically in Neovim using [`:Mason`](https://github.com/williamboman/mason.nvim). Within
Mason, the package is called `rust_hdl`. If you don't have `:Mason`, you can simply install the binary as previously
described.
### Automatic Configuration using `nvim-lspconfig`
[`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig) has a built in configuration
for [`vhdl_ls`](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#vhdl_ls)
In order to configure it, simply add
```lua
lspconfig = require('lspconfig')
lspconfig['vhdl_ls'].setup({
on_attach = on_attach,
capabilities = capabilities
})
```
### Manual Configuration using Neovim's built in client
Neovim provides an LSP client to the VHDL_LS language server. Download the
VHDL_LS release. The binary must be on the path and executable (if you can run
"vhdl_ls -h" in the terminal then you're good).
In your Neovim config.lua add the following:
```lua
function STARTVHDLLS()
vim.lsp.start({
name = 'vhdl_ls',
cmd = {'vhdl_ls'},
})
end
vim.api.nvim_set_keymap('n', '<F5>', ':lua STARTVHDLLS()<CR>', { noremap = true, silent = true })
```
Using the example above, pressing F5 while inside Neovim starts the language
server. There are also other options, like automatically starting it when
opening a certain file type, see the [Neovim LSP documentation](https://neovim.io/doc/user/lsp.html) for more.
## Configuration
The language server needs to know your library mapping to perform full analysis of the code. For this it uses a
configuration file in the [TOML](https://github.com/toml-lang/toml) format named `vhdl_ls.toml`.
`vhdl_ls` will load configuration files in the following order of priority (first to last):
1. A file named `.vhdl_ls.toml` in the user home folder.
2. A file name from the `VHDL_LS_CONFIG` environment variable.
3. A file named `vhdl_ls.toml` in the workspace root.
Settings in a later files overwrites those from previously loaded files.
Define the VHDL revision to use for parsing and analysis with the `standard` key.
The expected value is the year associated the VHDL standard.
Supported standards are 1993, 2008 and 2019 where both the long version ("2008") and the short version ("08") can be
used.
If nothing is specified, 2008 is used.
> [!NOTE]
> Defining the standard feature is a relatively new feature (since april 2024).
> Anything but the 2008 standard will not change much at the moment.
**Example vhdl_ls.toml**
```toml
# What standard to use. This is optional and defaults to VHDL2008.
standard = "2008"
# File names are either absolute or relative to the parent folder of the vhdl_ls.toml file
[libraries]
lib2.files = [
'pkg2.vhd',
]
lib1.files = [
'pkg1.vhd',
'tb_ent.vhd'
]
# Wildcards are supported
lib3.files = [
'test/*.vhd',
'src/*.vhd',
'src/*/*.vhd',
]
# Libraries can be marked as third-party to disable some analysis warnings, such as unused declarations
UNISIM.files = [
'C:\Xilinx\Vivado\2023.1\data\vhdl\src\unisims\unisim_VCOMP.vhd',
]
UNISIM.is_third_party = true
[lint]
unused = 'error' # Upgrade the 'unused' diagnostic to the 'error' severity
unnecessary_work_library = false # Disable linting for the 'library work;' statement
```
Using the `lint` table, you can configure the severity of diagnostics or turn of diagnostics altogether.
> [!WARNING]
> You can overwrite every diagnostic error code including syntax or analysis errors using the lint table.
> However, the intended use-case is for lints only.
> Overwriting syntax or analysis errors (e.g., error codes `unused` or `syntax`) can cause unwanted side effects
Paths in the `vhdl_ls.toml` can contain glob patterns (i.e., `.../*/`).
On Unix machines, they can contain environment variables using the `$NAME` or `${NAME}` syntax.
On Windows machines, use the `%NAME%` syntax to substitute environment variables.
## As an LSP-client developer how should I integrate VHDL-LS?
I recommend that the `lsp-client` polls GitHub and downloads
the [latest](https://github.com/VHDL-LS/rust_hdl/releases/latest) VHDL-LS release from GitHub.
VHDL-LS has frequent releases and the automatic update ensures minimal maintenance for the `lsp-client` developer as
well as ensuring the users are not running and outdated version.
## VHDL Language Frontend
[![vhdl language frontend crate](https://img.shields.io/crates/v/vhdl_lang.svg)](https://crates.io/crates/vhdl_lang)
### Goals
- This project aims to provide a fully featured open source VHDL frontend that is easy to integrate into other tools.
- A design goal of the frontend is to be able to recover from syntax errors such that it is useful for building a
language server.
- Analysis order must be automatically computed such that the user does not have to maintain a compile order.
- Comments will be part of the AST to support document generation.
- Separate parsing from semantic analysis to allow code formatting on non-semantically correct code.
## Building the project locally
1) Make sure that you have the [Rust toolchain](https://www.rust-lang.org/tools/install) installed.
This repository always follows the latest toolchain in the `stable` channel.
2) Run `cargo install --path vhdl_lang` to install the language frontend. Run instead `cargo install --path vhdl_ls`
to install the language server.
3) Make sure that the default libraries are available at a visible path. Search paths are, for example,
`/usr/lib/rust_hdl/vhdl_libraries` or `/usr/local/lib/rust_hdl/vhdl_libraries`.
4) Run the command `vhdl_lang` or `vhdl_ls` to run the language front-end binary or the language server
**Testing the Language Server**
For checking the language server, [rust_hdl_vscode](https://github.com/VHDL-LS/rust_hdl_vscode) is recommended.
To instruct the extension to use the new binary, instead of a downloaded one, go to the extension settings and set
the Language server location to `systemPath`. To specify the exact path, set it to `user` and set Language Server User
Path to the path that points to the `vhdl_ls` binary.

1
clippy.toml Normal file
View File

@ -0,0 +1 @@
ignore-interior-mutability = ["vhdl_lang::data::source::UniqueSource"]

139
logo.svg Normal file
View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="39.5mm"
height="39.5mm"
viewBox="0 0 39.5 39.5"
version="1.1"
id="svg8"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"
sodipodi:docname="logo.svg">
<defs
id="defs2">
<rect
x="208.45387"
y="212.42262"
width="142.686"
height="50.081844"
id="rect3731" />
<linearGradient
id="linearGradient1591"
osb:paint="solid">
<stop
style="stop-color:#fcfffc;stop-opacity:1;"
offset="0"
id="stop1589" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient1245"
osb:paint="gradient">
<stop
style="stop-color:#2ba84a;stop-opacity:1;"
offset="0"
id="stop1241" />
<stop
style="stop-color:#2ba84a;stop-opacity:0;"
offset="1"
id="stop1243" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="-91.103547"
inkscape:cy="110.91776"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="2560"
inkscape:window-height="1440"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Lager 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-43.749996,-115.56547)">
<rect
style="fill:#272d2d;fill-opacity:1;stroke:#50514f;stroke-width:1.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect1212-7-9-8"
width="38"
height="38"
x="44.499996"
y="116.31547"
ry="8.3826399" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15.7143px;line-height:1.25;font-family:'Lucida Console';-inkscape-font-specification:'Lucida Console';fill:#2ba84a;fill-opacity:1;stroke:none;stroke-width:0.245993;stroke-miterlimit:4;stroke-dasharray:none"
x="49.270264"
y="125.38107"
id="text12-8-0-4"
transform="scale(0.94807947,1.0547639)"><tspan
sodipodi:role="line"
id="tspan10-4-3-5"
x="49.270264"
y="125.38107"
style="font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:15.7143px;font-family:Calibri;-inkscape-font-specification:'Calibri Bold Italic';fill:#2ba84a;fill-opacity:1;stroke-width:0.245993;stroke-miterlimit:4;stroke-dasharray:none">VHDL</tspan></text>
<text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24.182px;line-height:1.25;font-family:Calibri;-inkscape-font-specification:'Calibri Italic';fill:#fcfffc;fill-opacity:1;stroke:none;stroke-width:0.453411"
x="57.981281"
y="149.74188"
id="text21-7-0-7"><tspan
sodipodi:role="line"
id="tspan19-3-6-0"
x="57.981281"
y="149.74188"
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24.182px;font-family:Calibri;-inkscape-font-specification:'Calibri Italic';fill:#fcfffc;fill-opacity:1;stroke-width:0.453411">LS</tspan></text>
<path
d="m 57.818791,145.85477 c 0,0.0472 -0.0135,0.11017 -0.0406,0.18892 0,0.0787 -0.0406,0.1653 -0.12173,0.25976 -0.0812,0.0866 -0.17598,0.17712 -0.28446,0.27158 -0.10826,0.0866 -0.24365,0.16924 -0.40618,0.24796 -0.13533,0.0787 -0.32487,0.14169 -0.56862,0.18892 -0.2167,0.0473 -0.46038,0.0709 -0.73104,0.0709 h -7.613608 c -0.352,0 -0.60923,-0.0354 -0.77169,-0.10627 -0.16247,-0.0708 -0.2437,-0.17712 -0.2437,-0.31881 0,-0.0393 0.0135,-0.0944 0.0406,-0.1653 0.0271,-0.0787 0.0677,-0.16137 0.12184,-0.24796 0.0812,-0.0945 0.17599,-0.18892 0.2843,-0.28339 0.1083,-0.0945 0.24368,-0.17711 0.40614,-0.24796 0.16247,-0.0787 0.33847,-0.13775 0.528,-0.17711 0.21661,-0.0473 0.47385,-0.0709 0.7717,-0.0709 h 7.613528 c 0.37907,0 0.6363,0.0354 0.77169,0.10627 0.16235,0.0709 0.24353,0.16531 0.24353,0.28339 z"
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24.182px;line-height:1.25;font-family:Calibri;-inkscape-font-specification:'Calibri Italic';fill:#266831;fill-opacity:1;stroke:none;stroke-width:0.840919"
id="path1327-2-43-84"
sodipodi:nodetypes="ccccccssssccsccssccc" />
<path
d="m 58.553304,141.5302 c 0,0.0472 -0.0135,0.11017 -0.0406,0.18892 0,0.0787 -0.0406,0.1653 -0.12173,0.25976 -0.0812,0.0866 -0.17598,0.17712 -0.28446,0.27158 -0.10826,0.0866 -0.24365,0.16924 -0.40618,0.24796 -0.13533,0.0787 -0.32487,0.14169 -0.56862,0.18892 -0.2167,0.0473 -0.46038,0.0709 -0.73104,0.0709 h -7.479976 c -0.352,0 -0.60923,-0.0354 -0.77169,-0.10627 -0.16247,-0.0708 -0.2437,-0.17712 -0.2437,-0.31881 0,-0.0393 0.0135,-0.0944 0.0406,-0.1653 0.0271,-0.0787 0.0677,-0.16137 0.12184,-0.24796 0.0812,-0.0945 0.17599,-0.18892 0.2843,-0.28339 0.1083,-0.0945 0.24368,-0.17711 0.40614,-0.24796 0.16247,-0.0787 0.33847,-0.13775 0.528,-0.17711 0.21661,-0.0473 0.47385,-0.0709 0.7717,-0.0709 h 7.479896 c 0.37907,0 0.6363,0.0354 0.77169,0.10627 0.16235,0.0709 0.24353,0.16531 0.24353,0.28339 z"
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24.182px;line-height:1.25;font-family:Calibri;-inkscape-font-specification:'Calibri Italic';fill:#2ba84a;fill-opacity:1;stroke:none;stroke-width:0.840919"
id="path1327-2-43-1-5"
sodipodi:nodetypes="ccccccssssccsccssccc" />
<path
d="m 59.53935,137.21478 c 0,0.0472 -0.0135,0.11017 -0.0406,0.18892 0,0.0787 -0.0406,0.1653 -0.12173,0.25976 -0.0812,0.0866 -0.17598,0.17712 -0.28446,0.27158 -0.10826,0.0866 -0.24365,0.16924 -0.40618,0.24796 -0.13533,0.0787 -0.32487,0.14169 -0.56862,0.18892 -0.2167,0.0473 -0.46038,0.0709 -0.73104,0.0709 h -7.613619 c -0.352,0 -0.60923,-0.0354 -0.77169,-0.10627 -0.16247,-0.0708 -0.2437,-0.17712 -0.2437,-0.31881 0,-0.0393 0.0135,-0.0944 0.0406,-0.1653 0.0271,-0.0787 0.0677,-0.16137 0.12184,-0.24796 0.0812,-0.0945 0.17599,-0.18892 0.2843,-0.28339 0.1083,-0.0945 0.24368,-0.17711 0.40614,-0.24796 0.16247,-0.0787 0.33847,-0.13775 0.528,-0.17711 0.21661,-0.0473 0.47385,-0.0709 0.7717,-0.0709 h 7.613539 c 0.37907,0 0.6363,0.0354 0.77169,0.10627 0.16235,0.0709 0.24353,0.16531 0.24353,0.28339 z"
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:24.182px;line-height:1.25;font-family:Calibri;-inkscape-font-specification:'Calibri Italic';fill:#c2fcd1;fill-opacity:0.723809;stroke:none;stroke-width:0.840919"
id="path1327-2-43-9-1"
sodipodi:nodetypes="ccccccssssccsccssccc" />
<text
xml:space="preserve"
id="text3729"
style="font-size:12.7px;line-height:1.25;font-family:'Ink Free';-inkscape-font-specification:'Ink Free';white-space:pre;shape-inside:url(#rect3731);" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

46
vhdl_lang/Cargo.toml Normal file
View File

@ -0,0 +1,46 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
[package]
name = "vhdl_lang"
version = "0.83.0"
authors = ["Olof Kraigher <olof.kraigher@gmail.com>"]
license = "MPL-2.0"
description = "VHDL Language Frontend"
repository = "https://github.com/kraigher/rust_hdl"
edition = "2021"
[dependencies]
vhdl_lang_macros = { version = "^0.83.0", path = "../vhdl_lang_macros" }
pad = "0"
fnv = "1"
clap = { version = "4", features = ["derive"] }
toml = "0"
glob = "0"
dirs = "5"
rayon = "1"
parking_lot = "0"
dunce = "1"
pinned_vec = "0"
itertools = "0"
subst = "0.3.0"
strum = { version = "0.26.2", features = ["derive"] }
enum-map = "2.7.3"
[dev-dependencies]
tempfile = "3"
pretty_assertions = "1"
assert_matches = "1"
brunch = "0"
assert_cmd = "2.0.14"
predicates = "3.1.0"
[[bench]]
name = "benchmark"
harness = false
[features]
default = []

View File

@ -0,0 +1,95 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use brunch::{Bench, Benches};
use std::{path::Path, time::Duration};
use vhdl_lang::{
ast::search::{SearchState, Searcher},
Config, MessagePrinter, NullMessages, Project,
};
fn load_config(include_example_project: bool) -> Config {
let repo_root = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
let mut config = Config::default();
config.append(
&Config::read_file_path(&repo_root.join("vhdl_libraries").join("vhdl_ls.toml"))
.expect("Failed to read installed config file"),
&mut MessagePrinter::default(),
);
if include_example_project {
config.append(
&Config::read_file_path(&repo_root.join("example_project").join("vhdl_ls.toml"))
.expect("Failed to read project config file"),
&mut MessagePrinter::default(),
);
}
config
}
fn main() {
let mut benches = Benches::default();
{
// Only use standard libraries to benchmark parse and analyze as the time taken to get 100 samples
// is very big with the example project
let config = load_config(false);
benches.push(Bench::new("parse and analyze").with_samples(10).run(|| {
let mut project = Project::from_config(config.clone(), &mut NullMessages);
project.analyse();
}));
}
{
let mut project = Project::from_config(load_config(true), &mut NullMessages);
project.analyse();
let integer = project
.public_symbols()
.find(|ent| matches!(ent.designator().as_identifier(), Some(sym) if sym.name_utf8() == "INTEGER"))
.unwrap();
benches.push(Bench::new("find all references").run(|| {
assert!(!project.find_all_references(integer).is_empty());
}));
let integer_pos = integer.decl_pos().unwrap();
benches.push(Bench::new("item at cursor").run(|| {
assert_eq!(
project
.item_at_cursor(&integer_pos.source, integer_pos.start())
.unwrap()
.1,
integer
);
}));
benches.push(
Bench::new("search entire ast")
.with_timeout(Duration::from_secs(30))
.run(|| {
project.search(&mut MySearcher {});
}),
);
}
benches.finish();
}
struct MySearcher {}
impl Searcher for MySearcher {
fn search_pos_with_ref(
&mut self,
_ctx: &dyn vhdl_lang::TokenAccess,
_pos: &vhdl_lang::SrcPos,
_ref: &vhdl_lang::Reference,
) -> SearchState {
std::hint::black_box(SearchState::NotFinished)
}
}

36
vhdl_lang/src/analysis.rs Normal file
View File

@ -0,0 +1,36 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
#[macro_use]
mod analyze;
mod assignment;
mod association;
mod concurrent;
mod declarative;
mod design_unit;
mod expression;
mod literals;
mod lock;
mod names;
mod overloaded;
mod package_instance;
mod range;
mod root;
mod scope;
mod semantic;
mod sequential;
mod standard;
mod static_expression;
mod subprogram;
mod target;
mod types;
#[cfg(test)]
pub(crate) mod tests;
pub(crate) use root::{Library, LockedUnit};
pub use self::root::{DesignRoot, EntHierarchy};

View File

@ -0,0 +1,492 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::root::*;
pub(crate) use super::scope::Scope;
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::*;
use crate::syntax::TokenAccess;
use crate::TokenSpan;
use fnv::FnvHashSet;
use std::cell::RefCell;
use std::ops::Deref;
/// Indicates that a circular dependency is found at the position denoted by `reference`.
///
/// A circular dependency occurs when module A uses module B, which in turn
/// (either directly or indirectly via other modules) uses module A again.
///
/// ## Example
///
/// ```vhdl
/// use work.bar;
///
/// package foo is
/// end package;
///
/// use work.foo;
///
/// package bar is
/// end package;
/// ```
/// In this example, the package `bar` uses the package `foo` which in turn uses package `bar`
/// making the dependency chain cyclic.
///
/// Commonly, two or more `CircularDependencyError`s are pushed to indicate what modules
/// the error affects.
#[derive(Clone, Debug, PartialEq, Eq)]
#[must_use]
pub struct CircularDependencyError {
/// The position where the circular dependency was found.
/// Is `None` when the circular dependency is found in the standard library.
/// This should, in practice, never happen.
reference: Option<SrcPos>,
}
impl CircularDependencyError {
pub fn new(reference: Option<&SrcPos>) -> CircularDependencyError {
CircularDependencyError {
reference: reference.cloned(),
}
}
/// Pushes this error into a diagnostic handler.
pub fn push_into(self, diagnostics: &mut dyn DiagnosticHandler) {
if let Some(pos) = self.reference {
diagnostics.push(Diagnostic::circular_dependency(pos));
}
}
}
/// A `FatalResult` is a result that is either OK or contains a `CircularDependencyError`.
/// If this type contains the error case, most other errors encountered during analysis are ignored
/// as analysis cannot continue (resp. no further analysis is pursued)
pub type FatalResult<T = ()> = Result<T, CircularDependencyError>;
#[derive(Debug, PartialEq, Eq)]
pub enum EvalError {
/// A circular dependency was found, see [CircularDependencyError](CircularDependencyError)
Circular(CircularDependencyError),
/// Indicates that evaluation is no longer possible, for example if encountering illegal code.
/// Typically, functions returning Unknown will have published diagnostics on the side-channel
/// and the unknown is returned to stop further upstream analysis
Unknown,
}
impl From<CircularDependencyError> for EvalError {
fn from(err: CircularDependencyError) -> EvalError {
EvalError::Circular(err)
}
}
/// The result of the evaluation of an AST element.
/// The result has either a value of `Ok(T)`, indicating a successful evaluation of
/// the AST and returning the result of that evaluation, or `Err(EvalError)`, indicating
/// an error during evaluation.
///
/// Most of the time, the error will be `EvalError::Unknown`. This means that the evaluation
/// step has pushed found problems in the code to some side-channel and simply returns an error,
/// signifying that some problem was found without further specifying that problem.
pub type EvalResult<T = ()> = Result<T, EvalError>;
/// Pushes the diagnostic to the provided handler and returns
/// with an `EvalError::Unknown` result.
///
/// This macro can be used for the common case of encountering an analysis error and
/// immediately returning as the error is not recoverable.
macro_rules! bail {
($diagnostics:expr, $error:expr) => {
$diagnostics.push($error);
return Err(EvalError::Unknown);
};
}
pub trait IntoEvalResult<T> {
/// Transforms `Self` into an `EvalResult`.
///
/// If `Self` has any severe errors, these should be pushed to the provided handler
/// and an `Err(EvalError::Unknown)` should be returned.
fn into_eval_result(self, diagnostics: &mut dyn DiagnosticHandler) -> EvalResult<T>;
}
impl<T> IntoEvalResult<T> for Result<T, Diagnostic> {
fn into_eval_result(self, diagnostics: &mut dyn DiagnosticHandler) -> EvalResult<T> {
match self {
Ok(value) => Ok(value),
Err(diagnostic) => {
bail!(diagnostics, diagnostic);
}
}
}
}
pub fn as_fatal<T>(res: EvalResult<T>) -> FatalResult<Option<T>> {
match res {
Ok(val) => Ok(Some(val)),
Err(EvalError::Unknown) => Ok(None),
Err(EvalError::Circular(circ)) => Err(circ),
}
}
pub(super) struct AnalyzeContext<'a, 't> {
pub(super) root: &'a DesignRoot,
pub work_sym: Symbol,
std_sym: Symbol,
standard_sym: Symbol,
pub(super) is_std_logic_1164: bool,
// Record dependencies and sensitives when
// analyzing design units
//
// Dependencies define the order in which design units must be analyzed
// - for example when doing 'use library.pkg' the pkg is a dependency
//
// Sensitivity defines conditions that require re-analysis of a design unit
// - for example when doing 'use library.missing' the file is sensitive to adding
// primary unit missing to library
// - for example when doing 'use missing' the file is sensitive to adding
// missing library
// - for example when doing 'use library.all' the file is sensitive to adding/removing
// anything from library
current_unit: UnitId,
source: Source,
pub(super) arena: &'a Arena,
uses: RefCell<FnvHashSet<UnitId>>,
missing_unit: RefCell<FnvHashSet<(Symbol, Symbol, Option<Symbol>)>>,
uses_library_all: RefCell<FnvHashSet<Symbol>>,
pub ctx: &'t dyn TokenAccess,
}
impl<'a, 't> AnalyzeContext<'a, 't> {
pub fn new(
root: &'a DesignRoot,
current_unit: &UnitId,
source: Source,
arena: &'a Arena,
ctx: &'t dyn TokenAccess,
) -> AnalyzeContext<'a, 't> {
AnalyzeContext {
work_sym: root.symbol_utf8("work"),
std_sym: root.symbol_utf8("std"),
standard_sym: root.symbol_utf8("standard"),
is_std_logic_1164: current_unit
== &UnitId::package(
&root.symbol_utf8("ieee"),
&root.symbol_utf8("std_logic_1164"),
),
root,
current_unit: current_unit.clone(),
source,
arena,
uses: RefCell::new(FnvHashSet::default()),
missing_unit: RefCell::new(FnvHashSet::default()),
uses_library_all: RefCell::new(FnvHashSet::default()),
ctx,
}
}
pub fn work_library_name(&self) -> &Symbol {
self.current_unit.library_name()
}
pub fn work_library(&self) -> EntRef<'a> {
self.get_library(self.current_unit.library_name()).unwrap()
}
pub fn current_unit_id(&self) -> &UnitId {
&self.current_unit
}
fn make_use_of(&self, use_pos: Option<&SrcPos>, unit_id: &UnitId) -> FatalResult {
// Check local cache before taking lock
if self.uses.borrow_mut().insert(unit_id.clone()) {
self.root.make_use_of(use_pos, &self.current_unit, unit_id)
} else {
Ok(())
}
}
fn make_use_of_library_all(&self, library_name: &Symbol) {
// Check local cache before taking lock
if self
.uses_library_all
.borrow_mut()
.insert(library_name.clone())
{
self.root
.make_use_of_library_all(&self.current_unit, library_name);
}
}
fn make_use_of_missing_unit(
&self,
library_name: &Symbol,
primary_name: &Symbol,
secondary_name: Option<&Symbol>,
) {
let key = (
library_name.clone(),
primary_name.clone(),
secondary_name.cloned(),
);
// Check local cache before taking lock
if self.missing_unit.borrow_mut().insert(key) {
self.root.make_use_of_missing_unit(
&self.current_unit,
library_name,
primary_name,
secondary_name,
);
}
}
pub fn use_all_in_library(
&self,
use_pos: &SrcPos,
library_name: &Symbol,
scope: &Scope<'a>,
) -> FatalResult {
let units = self.root.get_library_units(library_name).unwrap();
for unit in units.values() {
match unit.kind() {
AnyKind::Primary(..) => {
let data = self.get_analysis(Some(use_pos), unit)?;
if let AnyDesignUnit::Primary(primary) = data.deref() {
if let Some(id) = primary.ent_id() {
scope.make_potentially_visible(Some(use_pos), self.arena.get(id));
}
}
}
AnyKind::Secondary(..) => {}
}
}
self.make_use_of_library_all(library_name);
Ok(())
}
/// Add implicit context clause for all packages except STD.STANDARD
/// library STD, WORK;
/// use STD.STANDARD.all;
pub fn add_implicit_context_clause(&self, scope: &Scope<'a>) -> FatalResult {
// work is not visible in context declarations
if self.current_unit.kind() != AnyKind::Primary(PrimaryKind::Context) {
scope.make_potentially_visible_with_name(
None,
self.work_sym.clone().into(),
self.work_library(),
);
}
if let Some(std_library) = self.get_library(&self.std_sym) {
scope.make_potentially_visible(None, std_library);
let standard_region = self
.standard_package_region()
.expect("Expected standard package");
scope.make_all_potentially_visible(None, standard_region);
}
Ok(())
}
pub fn get_library(&self, library_name: &Symbol) -> Option<EntRef<'a>> {
let (arena, id) = self.root.get_library_arena(library_name)?;
self.arena.link(arena);
Some(self.arena.get(id))
}
fn get_package_body(&self) -> Option<&'a LockedUnit> {
let units = self.root.get_library_units(self.work_library_name())?;
let name = self.current_unit.primary_name();
units
.get(&UnitKey::Secondary(name.clone(), name.clone()))
.filter(|&unit| unit.kind() == AnyKind::Secondary(SecondaryKind::PackageBody))
}
pub fn has_package_body(&self) -> bool {
self.get_package_body().is_some()
}
fn get_analysis(
&self,
use_pos: Option<&SrcPos>,
unit: &'a LockedUnit,
) -> FatalResult<UnitReadGuard<'a>> {
self.make_use_of(use_pos, unit.unit_id())?;
let data = self.root.get_analysis(unit);
// Add all referenced declaration arenas from other unit
self.arena.link(&data.result().arena);
// Change circular dependency reference when used by another unit during analysis
// The error is changed from within the used unit into the position of the use of the unit
if data.result().has_circular_dependency {
Err(CircularDependencyError::new(use_pos))
} else {
Ok(data)
}
}
fn get_primary_unit(&self, library_name: &Symbol, name: &Symbol) -> Option<&'a LockedUnit> {
let units = self.root.get_library_units(library_name)?;
if let Some(unit) = units.get(&UnitKey::Primary(name.clone())) {
return Some(unit);
}
self.make_use_of_missing_unit(library_name, name, None);
None
}
fn get_secondary_unit(
&self,
library_name: &Symbol,
primary: &Symbol,
name: &Symbol,
) -> Option<&'a LockedUnit> {
let units = self.root.get_library_units(library_name)?;
if let Some(unit) = units.get(&UnitKey::Secondary(primary.clone(), name.clone())) {
return Some(unit);
}
self.make_use_of_missing_unit(library_name, primary, Some(name));
None
}
/// Given an Entity, returns a reference to the architecture denoted by `architecture_name`.
/// If this architecture cannot be found, pushes an appropriate error to the diagnostics-handler
/// and returns `EvalError::Unknown`.
///
/// # Arguments
///
/// * `diagnostics` Reference to the diagnostic handler
/// * `library_name` The name of the library where the entity resides
/// * `pos` The position where the architecture name was declared
/// * `entity_name` Name of the entity
/// * `architecture_name` Name of the architecture to be resolved
pub(super) fn get_architecture(
&self,
diagnostics: &mut dyn DiagnosticHandler,
library_name: &Symbol,
pos: &SrcPos,
entity_name: &Symbol,
architecture_name: &Symbol,
) -> EvalResult<DesignEnt<'a>> {
if let Some(unit) = self.get_secondary_unit(library_name, entity_name, architecture_name) {
let data = self.get_analysis(Some(pos), unit)?;
if let AnyDesignUnit::Secondary(AnySecondaryUnit::Architecture(arch)) = data.deref() {
if let Some(id) = arch.ident.decl.get() {
let ent = self.arena.get(id);
if let Some(design) = DesignEnt::from_any(ent) {
return Ok(design);
} else {
bail!(
diagnostics,
Diagnostic::internal(
pos,
format!(
"Found non-design {} unit within library {}",
ent.describe(),
library_name
)
)
);
}
}
}
}
bail!(
diagnostics,
Diagnostic::new(
pos,
format!("No architecture '{architecture_name}' for entity '{library_name}.{entity_name}'"),
ErrorCode::Unresolved,
)
);
}
pub fn lookup_in_library(
&self,
diagnostics: &mut dyn DiagnosticHandler,
library_name: &Symbol,
pos: &SrcPos,
primary_name: &Designator,
) -> EvalResult<DesignEnt<'a>> {
if let Designator::Identifier(ref primary_name) = primary_name {
if let Some(unit) = self.get_primary_unit(library_name, primary_name) {
let data = self.get_analysis(Some(pos), unit)?;
if let AnyDesignUnit::Primary(primary) = data.deref() {
if let Some(id) = primary.ent_id() {
let ent = self.arena.get(id);
if let Some(design) = DesignEnt::from_any(ent) {
return Ok(design);
} else {
bail!(
diagnostics,
Diagnostic::internal(
pos,
format!(
"Found non-design {} unit within library {}",
ent.describe(),
library_name
),
)
);
}
}
}
}
}
bail!(
diagnostics,
Diagnostic::new(
pos,
format!("No primary unit '{primary_name}' within library '{library_name}'"),
ErrorCode::Unresolved,
)
);
}
// Returns None when analyzing the standard package itself
fn standard_package_region(&self) -> Option<&'a Region<'a>> {
if let Some(pkg) = self.root.standard_pkg_id.as_ref() {
self.arena.link(self.root.standard_arena.as_ref().unwrap());
// Ensure things that depend on the standard package are re-analyzed
self.make_use_of(None, &UnitId::package(&self.std_sym, &self.standard_sym))
.unwrap();
if let AnyEntKind::Design(Design::Package(_, region)) = self.arena.get(*pkg).kind() {
Some(region)
} else {
unreachable!("Standard package is not a package");
}
} else {
None
}
}
pub fn source(&self) -> Source {
self.source.clone()
}
pub fn define<T: HasIdent>(
&self,
decl: &mut WithDecl<T>,
parent: EntRef<'a>,
kind: AnyEntKind<'a>,
src_span: TokenSpan,
) -> EntRef<'a> {
self.arena
.define(self.ctx, decl, parent, kind, src_span, Some(self.source()))
}
}

View File

@ -0,0 +1,151 @@
//! This Source Code Form is subject to the terms of the Mozilla Public
//! License, v. 2.0. If a copy of the MPL was not distributed with this file,
//! You can obtain one at http://mozilla.org/MPL/2.0/.
//!
//! Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
use super::analyze::*;
use super::scope::*;
use super::target::AssignmentType;
use crate::ast::token_range::WithTokenSpan;
use crate::ast::*;
use crate::data::*;
use crate::named_entity::*;
impl<'a, 't> AnalyzeContext<'a, 't> {
// @TODO maybe make generic function for expression/waveform.
// wait until type checking to see if it makes sense
pub fn analyze_expr_assignment(
&self,
scope: &Scope<'a>,
target: &mut WithTokenSpan<Target>,
assignment_type: AssignmentType,
rhs: &mut AssignmentRightHand<WithTokenSpan<Expression>>,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let ttyp = as_fatal(self.resolve_target(scope, target, assignment_type, diagnostics))?;
match rhs {
AssignmentRightHand::Simple(expr) => {
self.analyze_expression_for_target(scope, ttyp, expr, diagnostics)?;
}
AssignmentRightHand::Conditional(conditionals) => {
let Conditionals {
conditionals,
else_item,
} = conditionals;
for conditional in conditionals {
let Conditional { condition, item } = conditional;
self.analyze_expression_for_target(scope, ttyp, item, diagnostics)?;
self.boolean_expr(scope, condition, diagnostics)?;
}
if let Some((expr, _)) = else_item {
self.analyze_expression_for_target(scope, ttyp, expr, diagnostics)?;
}
}
AssignmentRightHand::Selected(selection) => {
let Selection {
expression,
alternatives,
} = selection;
let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?;
for Alternative {
choices,
item,
span: _,
} in alternatives.iter_mut()
{
self.analyze_expression_for_target(scope, ttyp, item, diagnostics)?;
self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?;
}
}
}
Ok(())
}
pub fn analyze_waveform_assignment(
&self,
scope: &Scope<'a>,
assignment: &mut SignalAssignment,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let ttyp = as_fatal(self.resolve_target(
scope,
&mut assignment.target,
AssignmentType::Signal,
diagnostics,
))?;
match &mut assignment.rhs {
AssignmentRightHand::Simple(wavf) => {
self.analyze_waveform(scope, ttyp, wavf, diagnostics)?;
}
AssignmentRightHand::Conditional(conditionals) => {
let Conditionals {
conditionals,
else_item,
} = conditionals;
for conditional in conditionals {
let Conditional { condition, item } = conditional;
self.analyze_waveform(scope, ttyp, item, diagnostics)?;
self.boolean_expr(scope, condition, diagnostics)?;
}
if let Some((wavf, _)) = else_item {
self.analyze_waveform(scope, ttyp, wavf, diagnostics)?;
}
}
AssignmentRightHand::Selected(selection) => {
let Selection {
expression,
alternatives,
} = selection;
let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?;
for Alternative {
choices,
item,
span: _,
} in alternatives.iter_mut()
{
self.analyze_waveform(scope, ttyp, item, diagnostics)?;
self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?;
}
}
}
Ok(())
}
fn analyze_waveform(
&self,
scope: &Scope<'a>,
ttyp: Option<TypeEnt<'a>>,
wavf: &mut Waveform,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
match wavf {
Waveform::Elements(ref mut elems) => {
for elem in elems.iter_mut() {
let WaveformElement { value, after } = elem;
self.analyze_expression_for_target(scope, ttyp, value, diagnostics)?;
if let Some(expr) = after {
self.expr_with_ttyp(scope, self.time(), expr, diagnostics)?;
}
}
}
Waveform::Unaffected(_) => {}
}
Ok(())
}
pub fn analyze_expression_for_target(
&self,
scope: &Scope<'a>,
ttyp: Option<TypeEnt<'a>>,
expr: &mut WithTokenSpan<Expression>,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
if let Some(ttyp) = ttyp {
self.expr_with_ttyp(scope, ttyp, expr, diagnostics)?;
} else {
self.expr_unknown_ttyp(scope, expr, diagnostics)?;
}
Ok(())
}
}

View File

@ -0,0 +1,665 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::names::ObjectName;
use fnv::FnvHashMap;
use itertools::Itertools;
use vhdl_lang::TokenSpan;
use super::analyze::*;
use super::names::ResolvedName;
use super::scope::*;
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::*;
#[derive(Copy, Clone)]
struct ResolvedFormal<'a> {
/// The index in the formal parameter list
idx: usize,
/// The underlying interface object
iface: InterfaceEnt<'a>,
// Has the formal been selected, indexed or sliced?
/// Example:
/// port map(foo.field => 0)
/// port map(foo(0) => 0)
is_partial: bool,
/// Has the formal been converted by a function?
/// Example:
/// port map(to_slv(foo) => sig)
is_converted: bool,
/// The type of the potentially partial or converted formal
type_mark: TypeEnt<'a>,
}
impl<'a> ResolvedFormal<'a> {
fn new_basic(idx: usize, iface: InterfaceEnt<'a>) -> Self {
Self {
idx,
iface,
is_partial: false,
is_converted: false,
type_mark: iface.type_mark(),
}
}
fn convert(&self, into_type: TypeEnt<'a>) -> Self {
Self {
idx: self.idx,
iface: self.iface,
is_partial: self.is_partial,
is_converted: true,
type_mark: into_type,
}
}
fn partial_with_typ(&self, suffix_type: TypeEnt<'a>) -> Option<Self> {
if !self.is_converted {
Some(Self {
idx: self.idx,
iface: self.iface,
is_partial: true,
is_converted: self.is_converted,
type_mark: suffix_type,
})
} else {
// Converted formals may not be further selected
None
}
}
fn partial(&self) -> Self {
Self {
is_partial: true,
..*self
}
}
}
impl<'a, 't> AnalyzeContext<'a, 't> {
fn resolve_formal(
&self,
formal_region: &FormalRegion<'a>,
scope: &Scope<'a>,
name_pos: TokenSpan,
name: &mut Name,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<ResolvedFormal<'a>> {
match name {
Name::Selected(prefix, suffix) => {
let resolved_prefix = self.resolve_formal(
formal_region,
scope,
prefix.span,
&mut prefix.item,
diagnostics,
)?;
let suffix_ent = resolved_prefix
.type_mark
.selected(self.ctx, prefix.span, suffix)
.into_eval_result(diagnostics)?;
if let TypedSelection::RecordElement(elem) = suffix_ent {
suffix.set_unique_reference(elem.into());
if let Some(resolved_formal) =
resolved_prefix.partial_with_typ(elem.type_mark())
{
Ok(resolved_formal)
} else {
bail!(
diagnostics,
Diagnostic::invalid_formal(name_pos.pos(self.ctx))
);
}
} else {
bail!(
diagnostics,
Diagnostic::invalid_formal(name_pos.pos(self.ctx))
);
}
}
Name::SelectedAll(_) => {
bail!(
diagnostics,
Diagnostic::invalid_formal(name_pos.pos(self.ctx))
);
}
Name::Designator(designator) => {
let (idx, ent) = formal_region
.lookup(&name_pos.pos(self.ctx), designator.item.designator())
.into_eval_result(diagnostics)?;
designator.set_unique_reference(ent.inner());
Ok(ResolvedFormal::new_basic(idx, ent))
}
Name::Slice(ref mut prefix, ref mut drange) => {
let resolved_prefix = self.resolve_formal(
formal_region,
scope,
prefix.span,
&mut prefix.item,
diagnostics,
)?;
if resolved_prefix.is_converted {
// Converted formals may not be further selected
bail!(
diagnostics,
Diagnostic::invalid_formal(name_pos.pos(self.ctx))
);
}
self.drange_unknown_type(scope, drange.as_mut(), diagnostics)?;
Ok(resolved_prefix.partial())
}
Name::Attribute(..) => {
bail!(
diagnostics,
Diagnostic::invalid_formal(name_pos.pos(self.ctx))
);
}
Name::CallOrIndexed(ref mut fcall) => {
let prefix = if let Some(prefix) = fcall.name.item.prefix() {
prefix
} else {
bail!(
diagnostics,
Diagnostic::invalid_formal(name_pos.pos(self.ctx))
);
};
if formal_region
.lookup(&name_pos.pos(self.ctx), prefix.designator())
.is_err()
{
// The prefix of the name was not found in the formal region
// it must be a type conversion or a single parameter function call
let (pos, resolved_formal) = if let Some((inner_pos, inner_name)) =
to_formal_conversion_argument(&mut fcall.parameters.items)
{
(
inner_pos,
self.resolve_formal(
formal_region,
scope,
inner_pos,
inner_name,
diagnostics,
)?,
)
} else {
bail!(
diagnostics,
Diagnostic::invalid_formal_conversion(name_pos.pos(self.ctx))
);
};
let converted_typ = match as_fatal(self.name_resolve(
scope,
fcall.name.span,
&mut fcall.name.item,
diagnostics,
))? {
Some(ResolvedName::Type(typ)) => {
let ctyp = resolved_formal.type_mark.base();
if !typ.base().is_closely_related(ctyp) {
bail!(
diagnostics,
Diagnostic::invalid_type_conversion(
pos.pos(self.ctx),
ctyp,
typ
)
);
}
typ
}
Some(ResolvedName::Overloaded(des, overloaded)) => {
let mut candidates = Vec::with_capacity(overloaded.len());
for ent in overloaded.entities() {
if ent.is_function()
&& ent.signature().can_be_called_with_single_parameter(
resolved_formal.type_mark,
)
{
candidates.push(ent);
}
}
if candidates.len() > 1 {
// Ambiguous call
bail!(
diagnostics,
Diagnostic::ambiguous_call(self.ctx, &des, candidates)
);
} else if let Some(ent) = candidates.pop() {
fcall.name.set_unique_reference(&ent);
ent.return_type().unwrap()
} else {
// No match
bail!(
diagnostics,
Diagnostic::new(
fcall.name.pos(self.ctx),
format!(
"No function '{}' accepting {}",
fcall.name,
resolved_formal.type_mark.describe()
),
ErrorCode::Unresolved,
)
);
}
}
_ => {
bail!(
diagnostics,
Diagnostic::invalid_formal_conversion(name_pos.pos(self.ctx))
);
}
};
Ok(resolved_formal.convert(converted_typ))
} else if let Some(mut indexed_name) = fcall.as_indexed() {
let resolved_prefix = self.resolve_formal(
formal_region,
scope,
indexed_name.name.span,
&mut indexed_name.name.item,
diagnostics,
)?;
let new_typ = self.analyze_indexed_name(
scope,
name_pos,
indexed_name.name.suffix_pos(),
resolved_prefix.type_mark,
&mut indexed_name.indexes,
diagnostics,
)?;
if let Some(resolved_formal) = resolved_prefix.partial_with_typ(new_typ) {
Ok(resolved_formal)
} else {
bail!(
diagnostics,
Diagnostic::invalid_formal(name_pos.pos(self.ctx))
);
}
} else {
bail!(
diagnostics,
Diagnostic::invalid_formal(name_pos.pos(self.ctx))
);
}
}
Name::External(..) => {
bail!(
diagnostics,
Diagnostic::invalid_formal(name_pos.pos(self.ctx))
);
}
}
}
pub fn check_positional_before_named(
&self,
elems: &[AssociationElement],
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult {
let mut is_positional = false;
let mut fail = false;
for AssociationElement { formal, .. } in elems.iter().rev() {
if let Some(formal) = formal {
if is_positional {
fail = true;
diagnostics.add(
formal.pos(self.ctx),
"Named arguments are not allowed before positional arguments",
ErrorCode::NamedBeforePositional,
);
}
} else {
is_positional = true;
}
}
if fail {
Err(EvalError::Unknown)
} else {
Ok(())
}
}
fn combine_formal_with_actuals<'e>(
&self,
formal_region: &FormalRegion<'a>,
scope: &Scope<'a>,
elems: &'e mut [AssociationElement],
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<Vec<(TokenSpan, Option<ResolvedFormal<'a>>)>> {
self.check_positional_before_named(elems, diagnostics)?;
// Formal region index => actual position, resolved formal
let mut result = Vec::default();
for (actual_idx, AssociationElement { formal, actual }) in elems.iter_mut().enumerate() {
if let Some(ref mut formal) = formal {
// Named argument
let resolved_formal = as_fatal(self.resolve_formal(
formal_region,
scope,
formal.span,
&mut formal.item,
diagnostics,
))?;
result.push((formal.span, resolved_formal));
} else if let Some(formal) = formal_region.nth(actual_idx) {
// Actual index is same as formal index for positional argument
let formal = ResolvedFormal::new_basic(actual_idx, formal);
result.push((actual.span, Some(formal)));
} else {
diagnostics.add(
actual.pos(self.ctx),
"Unexpected extra argument",
ErrorCode::TooManyArguments,
);
result.push((actual.span, None));
};
}
Ok(result)
}
fn check_missing_and_duplicates(
&self,
error_pos: &SrcPos, // The position of the instance/call-site
resolved_pairs: &[(TokenSpan, Option<ResolvedFormal<'a>>)],
formal_region: &FormalRegion<'a>,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<Vec<TypeEnt<'a>>> {
let mut is_error = false;
let mut result = Vec::default();
let mut associated: FnvHashMap<usize, (TokenSpan, ResolvedFormal<'_>)> = Default::default();
for (actual_pos, resolved_formal) in resolved_pairs.iter() {
match resolved_formal {
Some(resolved_formal) => {
if let Some((prev_pos, prev_formal)) = associated.get(&resolved_formal.idx) {
if !(resolved_formal.is_partial && prev_formal.is_partial) {
let mut diag = Diagnostic::new(
actual_pos.pos(self.ctx),
format!(
"{} has already been associated",
resolved_formal.iface.describe(),
),
ErrorCode::AlreadyAssociated,
);
diag.add_related(prev_pos.pos(self.ctx), "Previously associated here");
is_error = true;
diagnostics.push(diag);
}
}
result.push(resolved_formal.type_mark);
associated.insert(resolved_formal.idx, (*actual_pos, *resolved_formal));
}
None => {
is_error = true;
}
}
}
for (idx, formal) in formal_region.iter().enumerate() {
if !(associated.contains_key(&idx)
// Default may be unconnected
|| formal.has_default()
// Output ports are allowed to be unconnected
|| (formal_region.typ == InterfaceType::Port && formal.is_out_or_inout_signal()))
{
let diagnostic = Diagnostic::new(
error_pos,
format!("No association of {}", formal.describe()),
ErrorCode::Unassociated,
)
.opt_related(formal.decl_pos(), "Defined here");
is_error = true;
diagnostics.push(diagnostic);
}
}
if !is_error {
Ok(result)
} else {
Err(EvalError::Unknown)
}
}
pub fn resolve_association_formals<'e>(
&self,
error_pos: &SrcPos, // The position of the instance/call-site
formal_region: &FormalRegion<'a>,
scope: &Scope<'a>,
elems: &'e mut [AssociationElement],
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<Vec<TypeEnt<'a>>> {
let resolved_pairs =
self.combine_formal_with_actuals(formal_region, scope, elems, diagnostics)?;
self.check_missing_and_duplicates(error_pos, &resolved_pairs, formal_region, diagnostics)
}
pub fn check_association<'e>(
&self,
error_pos: &SrcPos, // The position of the instance/call-site
formal_region: &FormalRegion<'a>,
scope: &Scope<'a>,
elems: &'e mut [AssociationElement],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let resolved_pairs =
as_fatal(self.combine_formal_with_actuals(formal_region, scope, elems, diagnostics))?;
if let Some(resolved_pairs) = resolved_pairs {
as_fatal(self.check_missing_and_duplicates(
error_pos,
&resolved_pairs,
formal_region,
diagnostics,
))?;
let resolved_formals = resolved_pairs
.into_iter()
.map(|(_, resolved_formal)| resolved_formal)
.collect_vec();
for (resolved_formal, actual) in resolved_formals
.iter()
.zip(elems.iter_mut().map(|assoc| &mut assoc.actual))
{
match &mut actual.item {
ActualPart::Expression(expr) => {
if let Some(resolved_formal) = resolved_formal {
if formal_region.typ == InterfaceType::Parameter {
self.check_parameter_interface(
resolved_formal,
expr,
scope,
actual.span,
diagnostics,
)?;
}
self.expr_pos_with_ttyp(
scope,
resolved_formal.type_mark,
actual.span,
expr,
diagnostics,
)?;
} else {
self.expr_pos_unknown_ttyp(scope, actual.span, expr, diagnostics)?;
}
}
ActualPart::Open => {}
}
}
}
Ok(())
}
// LRM 4.2.2.1: In a subprogram, the interface mode must match the mode of the actual designator
// when the interface mode is signal, variable or file. Furthermore, they must be a single name.
fn check_parameter_interface(
&self,
resolved_formal: &ResolvedFormal<'a>,
expr: &mut Expression,
scope: &Scope<'a>,
actual_pos: TokenSpan,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
match resolved_formal.iface.interface_class() {
InterfaceClass::Signal => {
let Some(name) =
as_fatal(self.expression_as_name(expr, scope, actual_pos, diagnostics))?
else {
diagnostics.add(
actual_pos.pos(self.ctx),
"Expression must be a name denoting a signal",
ErrorCode::InterfaceModeMismatch,
);
return Ok(());
};
if !matches!(name, ResolvedName::ObjectName(
ObjectName { base, .. },
) if base.class() == ObjectClass::Signal)
{
diagnostics.add(
actual_pos.pos(self.ctx),
"Name must denote a signal name",
ErrorCode::InterfaceModeMismatch,
);
}
}
InterfaceClass::Variable => {
let Some(name) =
as_fatal(self.expression_as_name(expr, scope, actual_pos, diagnostics))?
else {
diagnostics.add(
actual_pos.pos(self.ctx),
"Expression must be a name denoting a variable or shared variable",
ErrorCode::InterfaceModeMismatch,
);
return Ok(());
};
if !matches!(name, ResolvedName::ObjectName(
ObjectName { base, .. },
) if base.class() == ObjectClass::Variable || base.class() == ObjectClass::SharedVariable)
{
diagnostics.add(
actual_pos.pos(self.ctx),
"Name must denote a variable name",
ErrorCode::InterfaceModeMismatch,
);
}
}
InterfaceClass::File => {
let Some(name) =
as_fatal(self.expression_as_name(expr, scope, actual_pos, diagnostics))?
else {
diagnostics.add(
actual_pos.pos(self.ctx),
"Expression must be a name denoting a file",
ErrorCode::InterfaceModeMismatch,
);
return Ok(());
};
if !matches!(name, ResolvedName::Final(ent) if matches!(
ent.kind(),
AnyEntKind::File(_) | AnyEntKind::InterfaceFile(_)
)) {
diagnostics.add(
actual_pos.pos(self.ctx),
"Name must denote a file name",
ErrorCode::InterfaceModeMismatch,
);
}
}
_ => {}
}
Ok(())
}
fn expression_as_name(
&self,
expr: &mut Expression,
scope: &Scope<'a>,
span: TokenSpan,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<ResolvedName<'_>> {
match expr {
Expression::Name(name) => {
let resolved = self.name_resolve(scope, span, name, diagnostics)?;
Ok(resolved)
}
_ => Err(EvalError::Unknown),
}
}
}
fn to_formal_conversion_argument(
parameters: &mut [AssociationElement],
) -> Option<(TokenSpan, &mut Box<Name>)> {
if let &mut [AssociationElement {
ref formal,
ref mut actual,
}] = parameters
{
if formal.is_some() {
return None;
} else if let ActualPart::Expression(Expression::Name(ref mut actual_name)) = actual.item {
return Some((actual.span, actual_name));
}
}
None
}
impl Diagnostic {
pub fn invalid_formal(pos: impl AsRef<SrcPos>) -> Diagnostic {
Diagnostic::new(pos, "Invalid formal", ErrorCode::InvalidFormal)
}
pub fn invalid_formal_conversion(pos: impl AsRef<SrcPos>) -> Diagnostic {
Diagnostic::new(
pos,
"Invalid formal conversion",
ErrorCode::InvalidFormalConversion,
)
}
pub fn invalid_type_conversion(
pos: impl AsRef<SrcPos>,
from: BaseType<'_>,
to: TypeEnt<'_>,
) -> Diagnostic {
Diagnostic::new(
pos,
format!(
"{} cannot be converted to {}",
from.describe(),
to.describe()
),
ErrorCode::TypeMismatch,
)
}
}

View File

@ -0,0 +1,483 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use crate::analysis::names::ResolvedName;
use crate::ast::token_range::WithTokenSpan;
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::*;
use crate::{HasTokenSpan, TokenSpan};
use analyze::*;
impl<'a, 't> AnalyzeContext<'a, 't> {
pub fn analyze_concurrent_part(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
statements: &mut [LabeledConcurrentStatement],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
for statement in statements.iter_mut() {
let parent = if let Some(id) = statement.label.decl.get() {
self.arena.get(id)
} else {
parent
};
self.analyze_concurrent_statement(scope, parent, statement, diagnostics)?;
}
Ok(())
}
pub fn define_labels_for_concurrent_part(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
statements: &mut [LabeledConcurrentStatement],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
for statement in statements.iter_mut() {
let span = statement.span();
if let Some(ref mut label) = statement.label.tree {
let ent = self.arena.explicit(
label.name(),
parent,
AnyEntKind::Concurrent(statement.statement.item.label_typ()),
Some(label.pos(self.ctx)),
span,
Some(self.source()),
);
statement.label.decl.set(ent.id());
scope.add(ent, diagnostics);
} else if statement.statement.item.can_have_label() {
// Generate an anonymous label if it is not explicitly defined
let ent = self.arena.alloc(
scope.anonymous_designator(),
Some(parent),
Related::None,
AnyEntKind::Concurrent(statement.statement.item.label_typ()),
None,
span,
Some(self.source()),
);
statement.label.decl.set(ent.id());
}
}
Ok(())
}
fn analyze_concurrent_statement(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
statement: &mut LabeledConcurrentStatement,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let src_span = statement.span();
match statement.statement.item {
ConcurrentStatement::Block(ref mut block) => {
if let Some(ref mut guard_condition) = block.guard_condition {
self.boolean_expr(scope, guard_condition, diagnostics)?;
}
let nested = scope.nested();
if let Some(ref mut list) = block.header.generic_clause {
self.analyze_interface_list(&nested, parent, list, diagnostics)?;
}
if let Some(ref mut list) = block.header.generic_map {
self.analyze_assoc_elems(scope, &mut list.list.items[..], diagnostics)?;
}
if let Some(ref mut list) = block.header.port_clause {
self.analyze_interface_list(&nested, parent, list, diagnostics)?;
}
if let Some(ref mut list) = block.header.port_map {
self.analyze_assoc_elems(scope, &mut list.list.items[..], diagnostics)?;
}
self.define_labels_for_concurrent_part(
&nested,
parent,
&mut block.statements,
diagnostics,
)?;
self.analyze_declarative_part(&nested, parent, &mut block.decl, diagnostics)?;
self.analyze_concurrent_part(&nested, parent, &mut block.statements, diagnostics)?;
}
ConcurrentStatement::Process(ref mut process) => {
let ProcessStatement {
postponed: _,
sensitivity_list,
decl,
statements,
end_label_pos: _,
..
} = process;
if let Some(sensitivity_list) = sensitivity_list {
match &mut sensitivity_list.item {
SensitivityList::Names(names) => {
self.sensitivity_list_check(scope, names, diagnostics)?;
}
SensitivityList::All => {}
}
}
let nested = scope.nested();
self.define_labels_for_sequential_part(&nested, parent, statements, diagnostics)?;
self.analyze_declarative_part(&nested, parent, decl, diagnostics)?;
self.analyze_sequential_part(&nested, parent, statements, diagnostics)?;
}
ConcurrentStatement::ForGenerate(ref mut gen) => {
let ForGenerateStatement {
index_name,
discrete_range,
body,
end_label_pos: _,
..
} = gen;
let typ = as_fatal(self.drange_type(scope, discrete_range, diagnostics))?;
let nested = scope.nested();
nested.add(
index_name.define(
self.ctx,
self.arena,
parent,
AnyEntKind::LoopParameter(typ),
src_span,
Some(self.source()),
),
diagnostics,
);
self.analyze_generate_body(&nested, parent, body, src_span, diagnostics)?;
}
ConcurrentStatement::IfGenerate(ref mut gen) => {
let Conditionals {
conditionals,
else_item,
} = &mut gen.conds;
for conditional in conditionals.iter_mut() {
let Conditional { condition, item } = conditional;
self.boolean_expr(scope, condition, diagnostics)?;
let nested = scope.nested();
self.analyze_generate_body(&nested, parent, item, src_span, diagnostics)?;
}
if let Some((ref mut else_item, _)) = else_item {
let nested = scope.nested();
self.analyze_generate_body(&nested, parent, else_item, src_span, diagnostics)?;
}
}
ConcurrentStatement::CaseGenerate(ref mut gen) => {
let CaseGenerateStatement {
sels:
Selection {
ref mut expression,
ref mut alternatives,
},
end_label_pos: _,
..
} = gen;
let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?;
for alternative in alternatives.iter_mut() {
let Alternative {
ref mut choices,
ref mut item,
span: _,
} = alternative;
self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?;
let nested = scope.nested();
self.analyze_generate_body(&nested, parent, item, src_span, diagnostics)?;
}
}
ConcurrentStatement::Instance(ref mut instance) => {
self.analyze_instance(scope, instance, diagnostics)?;
}
ConcurrentStatement::Assignment(ref mut assign) => {
// @TODO more delaymechanism
let ConcurrentSignalAssignment { assignment, .. } = assign;
self.analyze_waveform_assignment(scope, assignment, diagnostics)?;
}
ConcurrentStatement::ProcedureCall(ref mut pcall) => {
let ConcurrentProcedureCall { call, .. } = pcall;
self.analyze_procedure_call(scope, call, diagnostics)?;
}
ConcurrentStatement::Assert(ref mut assert) => {
let ConcurrentAssertStatement {
postponed: _postponed,
statement:
AssertStatement {
condition,
report,
severity,
},
} = assert;
self.boolean_expr(scope, condition, diagnostics)?;
if let Some(expr) = report {
self.expr_with_ttyp(scope, self.string(), expr, diagnostics)?;
}
if let Some(expr) = severity {
self.expr_with_ttyp(scope, self.severity_level(), expr, diagnostics)?;
}
}
};
Ok(())
}
fn analyze_generate_body(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
body: &mut GenerateBody,
span: TokenSpan,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let GenerateBody {
alternative_label,
decl,
statements,
..
} = body;
let mut inner_parent = parent;
if let Some(label) = alternative_label {
let ent = label.define(
self.ctx,
self.arena,
parent,
AnyEntKind::Concurrent(Some(Concurrent::Generate)),
span,
Some(self.source()),
);
scope.add(ent, diagnostics);
inner_parent = ent;
}
// Pre-declare labels
self.define_labels_for_concurrent_part(scope, parent, statements, diagnostics)?;
if let Some((ref mut decl, _)) = decl {
self.analyze_declarative_part(scope, parent, decl, diagnostics)?;
}
self.analyze_concurrent_part(scope, inner_parent, statements, diagnostics)?;
Ok(())
}
fn analyze_instance(
&self,
scope: &Scope<'a>,
instance: &mut InstantiationStatement,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
match instance.unit {
InstantiatedUnit::Entity(ref mut entity_name, ref mut architecture_name) => {
let Some(resolved) = as_fatal(self.name_resolve(
scope,
entity_name.span,
&mut entity_name.item,
diagnostics,
))?
else {
return Ok(());
};
match resolved {
ResolvedName::Design(ent) => match ent.kind() {
Design::Entity(_, ent_region) => {
if let Designator::Identifier(entity_ident) = ent.designator() {
if let Some(library_name) = ent.library_name() {
if let Some(ref mut architecture_name) = architecture_name {
if let Some(arch) = as_fatal(self.get_architecture(
diagnostics,
library_name,
self.ctx.get_pos(architecture_name.item.token),
entity_ident,
&architecture_name.item.item,
))? {
architecture_name.set_unique_reference(&arch);
}
}
}
}
let (generic_region, port_region) = ent_region.to_entity_formal();
self.check_association(
&entity_name.pos(self.ctx),
&generic_region,
scope,
instance
.generic_map
.as_mut()
.map(|it| it.list.items.as_mut_slice())
.unwrap_or(&mut []),
diagnostics,
)?;
self.check_association(
&entity_name.pos(self.ctx),
&port_region,
scope,
instance
.port_map
.as_mut()
.map(|it| it.list.items.as_mut_slice())
.unwrap_or(&mut []),
diagnostics,
)?;
Ok(())
}
_ => {
diagnostics.push(
resolved
.kind_error(entity_name.suffix_pos().pos(self.ctx), "entity"),
);
Ok(())
}
},
other => {
diagnostics.push(
other.kind_error(entity_name.suffix_pos().pos(self.ctx), "entity"),
);
Ok(())
}
}
}
InstantiatedUnit::Component(ref mut component_name) => {
let Some(resolved) = as_fatal(self.name_resolve(
scope,
component_name.span,
&mut component_name.item,
diagnostics,
))?
else {
return Ok(());
};
let ent = match resolved {
ResolvedName::Final(ent) => ent,
other => {
diagnostics.push(
other
.kind_error(component_name.suffix_pos().pos(self.ctx), "component"),
);
return Ok(());
}
};
if let AnyEntKind::Component(ent_region) = ent.kind() {
let (generic_region, port_region) = ent_region.to_entity_formal();
self.check_association(
&component_name.pos(self.ctx),
&generic_region,
scope,
instance
.generic_map
.as_mut()
.map(|it| it.list.items.as_mut_slice())
.unwrap_or(&mut []),
diagnostics,
)?;
self.check_association(
&component_name.pos(self.ctx),
&port_region,
scope,
instance
.port_map
.as_mut()
.map(|it| it.list.items.as_mut_slice())
.unwrap_or(&mut []),
diagnostics,
)?;
Ok(())
} else {
diagnostics.push(
resolved.kind_error(component_name.suffix_pos().pos(self.ctx), "component"),
);
Ok(())
}
}
InstantiatedUnit::Configuration(ref mut config_name) => {
let Some(resolved) = as_fatal(self.name_resolve(
scope,
config_name.span,
&mut config_name.item,
diagnostics,
))?
else {
return Ok(());
};
match resolved {
ResolvedName::Design(ent) if matches!(ent.kind(), Design::Configuration) => {}
other => {
diagnostics.push(
other.kind_error(
config_name.suffix_pos().pos(self.ctx),
"configuration",
),
);
return Ok(());
}
}
self.analyze_map_aspect(scope, &mut instance.generic_map, diagnostics)?;
self.analyze_map_aspect(scope, &mut instance.port_map, diagnostics)
}
}
}
pub fn analyze_map_aspect(
&self,
scope: &Scope<'a>,
map: &mut Option<MapAspect>,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let Some(aspect) = map else {
return Ok(());
};
self.analyze_assoc_elems(scope, aspect.list.items.as_mut_slice(), diagnostics)
}
pub fn sensitivity_list_check(
&self,
scope: &Scope<'a>,
names: &mut [WithTokenSpan<Name>],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
for name in names.iter_mut() {
if let Some(object_name) = as_fatal(self.resolve_object_name(
scope,
name.span,
&mut name.item,
"is not a signal and cannot be in a sensitivity list",
ErrorCode::DisallowedInSensitivityList,
diagnostics,
))? {
if object_name.base.class() != ObjectClass::Signal {
diagnostics.add(
name.pos(self.ctx),
format!(
"{} is not a signal and cannot be in a sensitivity list",
object_name.base.describe_class()
),
ErrorCode::DisallowedInSensitivityList,
)
} else if object_name.base.mode() == Some(&InterfaceMode::Simple(Mode::Out))
&& !object_name.base.is_port()
{
diagnostics.add(
name.pos(self.ctx),
format!(
"{} cannot be in a sensitivity list",
object_name.base.describe_class()
),
ErrorCode::DisallowedInSensitivityList,
)
}
}
}
Ok(())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,747 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use crate::analysis::names::ResolvedName;
use crate::ast::token_range::WithTokenSpan;
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::*;
use crate::HasTokenSpan;
use analyze::*;
impl<'a, 't> AnalyzeContext<'a, 't> {
pub fn analyze_primary_unit(
&self,
unit: &mut AnyPrimaryUnit,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
match unit {
AnyPrimaryUnit::Entity(unit) => self.analyze_entity(unit, diagnostics),
AnyPrimaryUnit::Configuration(unit) => self.analyze_configuration(unit, diagnostics),
AnyPrimaryUnit::Package(unit) => self.analyze_package(unit, diagnostics),
AnyPrimaryUnit::PackageInstance(unit) => {
self.analyze_package_instance(unit, diagnostics)
}
AnyPrimaryUnit::Context(unit) => self.analyze_context(unit, diagnostics),
}
}
pub fn analyze_secondary_unit(
&self,
unit: &mut AnySecondaryUnit,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
match unit {
AnySecondaryUnit::Architecture(unit) => self.analyze_architecture(unit, diagnostics),
AnySecondaryUnit::PackageBody(unit) => self.analyze_package_body(unit, diagnostics),
}
}
fn analyze_entity(
&self,
unit: &mut EntityDeclaration,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
// Pre-define entity and overwrite it later
let ent = self.arena.explicit(
unit.name().clone(),
self.work_library(),
AnyEntKind::Design(Design::Entity(Visibility::default(), Region::default())),
Some(unit.ident_pos(self.ctx)),
unit.span(),
Some(self.source()),
);
unit.ident.decl.set(ent.id());
let root_scope = Scope::default();
self.add_implicit_context_clause(&root_scope)?;
self.analyze_context_clause(&root_scope, &mut unit.context_clause, diagnostics)?;
root_scope.add(ent, diagnostics);
let primary_scope = root_scope.nested();
if let Some(ref mut list) = unit.generic_clause {
self.analyze_interface_list(&primary_scope, ent, list, diagnostics)?;
}
if let Some(ref mut list) = unit.port_clause {
self.analyze_interface_list(&primary_scope, ent, list, diagnostics)?;
}
self.define_labels_for_concurrent_part(
&primary_scope,
ent,
&mut unit.statements,
diagnostics,
)?;
self.analyze_declarative_part(&primary_scope, ent, &mut unit.decl, diagnostics)?;
self.analyze_concurrent_part(&primary_scope, ent, &mut unit.statements, diagnostics)?;
let region = primary_scope.into_region();
let visibility = root_scope.into_visibility();
let kind = AnyEntKind::Design(Design::Entity(visibility, region));
unsafe { ent.set_kind(kind) }
Ok(())
}
fn analyze_configuration(
&self,
unit: &mut ConfigurationDeclaration,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let src_span = unit.span();
let root_region = Scope::default();
self.add_implicit_context_clause(&root_region)?;
self.analyze_context_clause(&root_region, &mut unit.context_clause, diagnostics)?;
if let Some(named_entity) =
as_fatal(self.lookup_entity_for_configuration(&root_region, unit, diagnostics))?
{
if let Some(primary_pos) = named_entity.decl_pos() {
let secondary_pos = unit.ident_pos(self.ctx);
if primary_pos.source == secondary_pos.source
&& primary_pos.start() > secondary_pos.start()
{
diagnostics.add(
secondary_pos,
capitalize(&format!(
"{} declared before {}",
self.current_unit_id().describe(),
named_entity.describe()
)),
ErrorCode::DeclaredBefore,
);
}
}
};
self.define(
&mut unit.ident,
self.work_library(),
AnyEntKind::Design(Design::Configuration),
src_span,
);
Ok(())
}
fn analyze_package(
&self,
unit: &mut PackageDeclaration,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let ent = self.arena.explicit(
unit.name().clone(),
self.work_library(),
AnyEntKind::Design(Design::Package(Visibility::default(), Region::default())),
Some(unit.ident_pos(self.ctx)),
unit.span(),
Some(self.source()),
);
unit.ident.decl.set(ent.id());
let root_scope = Scope::default();
self.add_implicit_context_clause(&root_scope)?;
self.analyze_context_clause(&root_scope, &mut unit.context_clause, diagnostics)?;
root_scope.add(ent, diagnostics);
let scope = root_scope.nested().in_package_declaration();
if let Some(ref mut list) = unit.generic_clause {
self.analyze_interface_list(&scope, ent, list, diagnostics)?;
}
self.analyze_declarative_part(&scope, ent, &mut unit.decl, diagnostics)?;
if !self.has_package_body() {
scope.close(diagnostics);
}
let region = scope.into_region();
let visibility = root_scope.into_visibility();
let kind = if unit.generic_clause.is_some() {
AnyEntKind::Design(Design::UninstPackage(visibility, region))
} else {
AnyEntKind::Design(Design::Package(visibility, region))
};
unsafe {
ent.set_kind(kind);
}
Ok(())
}
fn analyze_package_instance(
&self,
unit: &mut PackageInstantiation,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let ent = self.arena.explicit(
unit.name().clone(),
self.work_library(),
AnyEntKind::Design(Design::PackageInstance(Region::default())),
Some(unit.ident_pos(self.ctx)),
unit.span(),
Some(self.source()),
);
unit.ident.decl.set(ent.id());
let root_scope = Scope::default();
self.add_implicit_context_clause(&root_scope)?;
self.analyze_context_clause(&root_scope, &mut unit.context_clause, diagnostics)?;
if let Some(pkg_region) =
as_fatal(self.generic_package_instance(&root_scope, ent, unit, diagnostics))?
{
let kind = AnyEntKind::Design(Design::PackageInstance(pkg_region));
unsafe {
ent.set_kind(kind);
}
}
Ok(())
}
fn analyze_context(
&self,
unit: &mut ContextDeclaration,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let root_scope = Scope::default();
self.add_implicit_context_clause(&root_scope)?;
let scope = root_scope.nested();
let src_span = unit.span();
self.analyze_context_clause(&scope, &mut unit.items, diagnostics)?;
self.define(
&mut unit.ident,
self.work_library(),
AnyEntKind::Design(Design::Context(scope.into_region())),
src_span,
);
Ok(())
}
fn analyze_architecture(
&self,
unit: &mut ArchitectureBody,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let src_span = unit.span();
let Some(primary) = as_fatal(self.lookup_in_library(
diagnostics,
self.work_library_name(),
unit.entity_name.item.pos(self.ctx),
&Designator::Identifier(unit.entity_name.item.item.clone()),
))?
else {
return Ok(());
};
unit.entity_name.set_unique_reference(primary.into());
self.check_secondary_before_primary(&primary, unit.ident_pos(self.ctx), diagnostics);
let (visibility, region) =
if let Design::Entity(ref visibility, ref region) = primary.kind() {
(visibility, region)
} else {
let mut diagnostic =
Diagnostic::mismatched_kinds(unit.ident_pos(self.ctx), "Expected an entity");
if let Some(pos) = primary.decl_pos() {
diagnostic.add_related(pos, format!("Found {}", primary.describe()))
}
return Ok(());
};
let root_scope = Scope::new(Region::with_visibility(visibility.clone()));
self.analyze_context_clause(&root_scope, &mut unit.context_clause, diagnostics)?;
let arch = self.define(
&mut unit.ident,
primary.into(),
AnyEntKind::Design(Design::Architecture(
Visibility::default(),
Region::default(),
primary,
)),
src_span,
);
root_scope.add(arch, diagnostics);
let scope = Scope::extend(region, Some(&root_scope));
// Entity name is visible
scope.make_potentially_visible(primary.decl_pos(), primary.into());
self.define_labels_for_concurrent_part(&scope, arch, &mut unit.statements, diagnostics)?;
self.analyze_declarative_part(&scope, arch, &mut unit.decl, diagnostics)?;
self.analyze_concurrent_part(&scope, arch, &mut unit.statements, diagnostics)?;
scope.close(diagnostics);
let region = scope.into_region();
let visibility = root_scope.into_visibility();
unsafe {
arch.set_kind(AnyEntKind::Design(Design::Architecture(
visibility, region, primary,
)))
}
Ok(())
}
fn analyze_package_body(
&self,
unit: &mut PackageBody,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let Some(primary) = as_fatal(self.lookup_in_library(
diagnostics,
self.work_library_name(),
unit.ident_pos(self.ctx),
&Designator::Identifier(unit.ident.tree.item.clone()),
))?
else {
return Ok(());
};
let (visibility, region) = match primary.kind() {
Design::Package(ref visibility, ref region)
| Design::UninstPackage(ref visibility, ref region) => (visibility, region),
_ => {
let mut diagnostic =
Diagnostic::mismatched_kinds(unit.ident_pos(self.ctx), "Expected a package");
if let Some(pos) = primary.decl_pos() {
diagnostic.add_related(pos, format!("Found {}", primary.describe()))
}
return Ok(());
}
};
let body = self.arena.alloc(
unit.ident.name().clone().into(),
Some(self.work_library()),
Related::DeclaredBy(primary.into()),
AnyEntKind::Design(Design::PackageBody(
Visibility::default(),
Region::default(),
)),
Some(unit.ident_pos(self.ctx).clone()),
unit.span(),
Some(self.source()),
);
unit.ident.decl.set(body.id());
self.check_secondary_before_primary(&primary, unit.ident_pos(self.ctx), diagnostics);
// @TODO make pattern of primary/secondary extension
let root_scope = Scope::new(Region::with_visibility(visibility.clone()));
self.analyze_context_clause(&root_scope, &mut unit.context_clause, diagnostics)?;
let scope = Scope::extend(region, Some(&root_scope));
// Package name is visible
scope.make_potentially_visible(primary.decl_pos(), primary.into());
self.analyze_declarative_part(&scope, body, &mut unit.decl, diagnostics)?;
scope.close(diagnostics);
let region = scope.into_region();
let visibility = root_scope.into_visibility();
unsafe { body.set_kind(AnyEntKind::Design(Design::PackageBody(visibility, region))) }
Ok(())
}
fn check_secondary_before_primary(
&self,
primary: &DesignEnt<'_>,
secondary_pos: &SrcPos,
diagnostics: &mut dyn DiagnosticHandler,
) {
if let Some(primary_pos) = primary.decl_pos() {
if primary_pos.source == secondary_pos.source
&& primary_pos.start() > secondary_pos.start()
{
diagnostics.add(
secondary_pos,
format!(
"{} declared before {}",
capitalize(&self.current_unit_id().describe()),
primary.describe(),
),
ErrorCode::DeclaredBefore,
);
}
}
}
fn lookup_entity_for_configuration(
&self,
scope: &Scope<'a>,
config: &mut ConfigurationDeclaration,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<DesignEnt<'_>> {
let ent_name = &mut config.entity_name;
let ent_name_span = ent_name.span;
match ent_name.item {
// Entities are implicitly defined for configurations
// configuration cfg of ent
Name::Designator(ref mut designator) => Ok(self
.lookup_in_library(
diagnostics,
self.work_library_name(),
&ent_name_span.pos(self.ctx),
&designator.item,
)
.map(|design| {
designator.set_unique_reference(design.into());
design
})?),
// configuration cfg of lib.ent
Name::Selected(ref mut prefix, ref mut designator) => {
let name = self.name_resolve(scope, prefix.span, &mut prefix.item, diagnostics)?;
match name {
ResolvedName::Library(ref library_name) => {
if library_name != self.work_library_name() {
diagnostics.add(
prefix.pos(self.ctx),
format!("Configuration must be within the same library '{}' as the corresponding entity", self.work_library_name()), ErrorCode::ConfigNotInSameLibrary);
Err(EvalError::Unknown)
} else {
let primary_ent = self.lookup_in_library(
diagnostics,
library_name,
designator.pos(self.ctx),
&designator.item.item,
)?;
designator
.item
.reference
.set_unique_reference(primary_ent.into());
match primary_ent.kind() {
Design::Entity(..) => Ok(primary_ent),
_ => {
diagnostics.add(
designator.pos(self.ctx),
format!(
"{} does not denote an entity",
primary_ent.describe()
),
ErrorCode::MismatchedKinds,
);
Err(EvalError::Unknown)
}
}
}
}
other => {
diagnostics.push(other.kind_error(prefix.pos(self.ctx), "library"));
Err(EvalError::Unknown)
}
}
}
_ => {
diagnostics.add(
ent_name.pos(self.ctx),
"Expected selected name",
ErrorCode::MismatchedKinds,
);
Err(EvalError::Unknown)
}
}
}
fn resolve_context_item_prefix(
&self,
diagnostics: &mut dyn DiagnosticHandler,
scope: &Scope<'a>,
prefix: &mut WithTokenSpan<Name>,
) -> EvalResult<EntRef<'a>> {
match self.resolve_context_item_name(diagnostics, scope, prefix)? {
UsedNames::Single(visible) => match visible.into_non_overloaded() {
Ok(ent_ref) => Ok(ent_ref),
Err(_) => {
bail!(
diagnostics,
Diagnostic::mismatched_kinds(
prefix.pos(self.ctx),
"Invalid prefix of a selected name",
)
);
}
},
UsedNames::AllWithin(..) => {
bail!(
diagnostics,
Diagnostic::mismatched_kinds(
prefix.pos(self.ctx),
"'.all' may not be the prefix of a selected name",
)
);
}
}
}
fn resolve_context_item_name(
&self,
diagnostics: &mut dyn DiagnosticHandler,
scope: &Scope<'a>,
name: &mut WithTokenSpan<Name>,
) -> EvalResult<UsedNames<'a>> {
match &mut name.item {
Name::Selected(ref mut prefix, ref mut suffix) => {
let prefix_ent = self.resolve_context_item_prefix(diagnostics, scope, prefix)?;
let visible = self.lookup_selected(diagnostics, prefix.span, prefix_ent, suffix)?;
suffix.set_reference(&visible);
Ok(UsedNames::Single(visible))
}
Name::SelectedAll(prefix) => {
let prefix_ent = self.resolve_context_item_prefix(diagnostics, scope, prefix)?;
Ok(UsedNames::AllWithin(prefix.pos(self.ctx), prefix_ent))
}
Name::Designator(designator) => {
let visible = scope
.lookup(designator.designator())
.map_err(|err| err.into_diagnostic(self.ctx, name.span))
.into_eval_result(diagnostics)?;
designator.set_reference(&visible);
Ok(UsedNames::Single(visible))
}
Name::Slice(..)
| Name::Attribute(..)
| Name::CallOrIndexed(..)
| Name::External(..) => {
bail!(
diagnostics,
Diagnostic::mismatched_kinds(name.pos(self.ctx), "Invalid selected name",)
);
}
}
}
pub(crate) fn analyze_context_clause(
&self,
scope: &Scope<'a>,
context_clause: &mut [ContextItem],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
for context_item in context_clause.iter_mut() {
match context_item {
ContextItem::Library(LibraryClause {
ref mut name_list, ..
}) => {
for library_name in name_list.iter_mut() {
if self.work_sym == library_name.item.item {
library_name.set_unique_reference(self.work_library());
diagnostics.add(
library_name.item.pos(self.ctx),
"Library clause not necessary for current working library",
ErrorCode::UnnecessaryWorkLibrary,
)
} else if let Some(library) = self.get_library(&library_name.item.item) {
library_name.set_unique_reference(library);
scope.make_potentially_visible(
Some(library_name.item.pos(self.ctx)),
library,
);
} else {
diagnostics.add(
library_name.item.pos(self.ctx),
format!("No such library '{}'", library_name.item),
ErrorCode::Unresolved,
);
}
}
}
ContextItem::Use(ref mut use_clause) => {
self.analyze_use_clause(scope, use_clause, diagnostics)?;
}
ContextItem::Context(ContextReference {
ref mut name_list, ..
}) => {
for name in name_list.iter_mut() {
match name.item {
Name::Selected(..) => {}
_ => {
diagnostics.add(
name.pos(self.ctx),
"Context reference must be a selected name",
ErrorCode::MismatchedKinds,
);
continue;
}
}
let Some(context_item) =
as_fatal(self.resolve_context_item_name(diagnostics, scope, name))?
else {
continue;
};
match context_item {
UsedNames::Single(visible) => {
let ent = visible.first();
match ent.kind() {
// OK
AnyEntKind::Design(Design::Context(ref context_region)) => {
scope.add_context_visibility(
Some(&name.pos(self.ctx)),
context_region,
);
}
_ => {
if let Name::Selected(_, ref suffix) = name.item {
diagnostics.add(
suffix.pos(self.ctx),
format!(
"{} does not denote a context declaration",
ent.describe()
),
ErrorCode::MismatchedKinds,
);
}
}
}
}
UsedNames::AllWithin(..) => {
// Handled above
}
}
}
}
}
}
Ok(())
}
pub fn analyze_use_clause(
&self,
scope: &Scope<'a>,
use_clause: &mut UseClause,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
for name in use_clause.name_list.iter_mut() {
match name.item {
Name::Selected(..) => {}
Name::SelectedAll(..) => {}
_ => {
diagnostics.add(
name.pos(self.ctx),
"Use clause must be a selected name",
ErrorCode::MismatchedKinds,
);
// We still want to resolve the name,
// so that it is available for completion purposes.
// We ignore the errors here, since there is already a diagnostic at that position.
let mut empty_diag = Vec::new();
let _ = self.name_resolve(scope, name.span(), &mut name.item, &mut empty_diag);
continue;
}
}
let Some(context_item) =
as_fatal(self.resolve_context_item_name(diagnostics, scope, name))?
else {
continue;
};
match context_item {
UsedNames::Single(visible) => {
visible.make_potentially_visible_in(Some(&name.pos(self.ctx)), scope);
}
UsedNames::AllWithin(visibility_pos, named_entity) => match named_entity.kind() {
AnyEntKind::Library => {
let library_name = named_entity.designator().expect_identifier();
self.use_all_in_library(&name.pos(self.ctx), library_name, scope)?;
}
AnyEntKind::Design(design) => match design {
Design::UninstPackage(..) => {
diagnostics.push(Diagnostic::invalid_selected_name_prefix(
named_entity,
&visibility_pos,
));
}
Design::Package(_, ref primary_region)
| Design::PackageInstance(ref primary_region)
| Design::InterfacePackageInstance(ref primary_region) => {
scope.make_all_potentially_visible(
Some(&name.pos(self.ctx)),
primary_region,
);
}
_ => {
diagnostics.add(
visibility_pos,
"Invalid prefix for selected name",
ErrorCode::MismatchedKinds,
);
}
},
_ => {
diagnostics.add(
visibility_pos,
"Invalid prefix for selected name",
ErrorCode::MismatchedKinds,
);
}
},
}
}
Ok(())
}
/// Returns a reference to the uninstantiated package
pub fn analyze_package_instance_name(
&self,
scope: &Scope<'a>,
package_name: &mut WithTokenSpan<Name>,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<Region<'a>> {
let name = self.name_resolve(
scope,
package_name.span,
&mut package_name.item,
diagnostics,
)?;
if let ResolvedName::Design(ref unit) = name {
if let AnyEntKind::Design(Design::UninstPackage(_, package_region)) = &unit.kind {
return Ok(package_region.clone());
}
}
diagnostics.add(
package_name.pos(self.ctx),
format!("'{package_name}' is not an uninstantiated generic package"),
ErrorCode::MismatchedKinds,
);
Err(EvalError::Unknown)
}
}
pub enum UsedNames<'a> {
/// A single name was used selected
Single(NamedEntities<'a>),
/// All names within was selected
/// @TODO add pos for where declaration was made visible into VisibleDeclaration
AllWithin(SrcPos, EntRef<'a>),
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,228 @@
use fnv::FnvHashSet;
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use super::analyze::*;
use super::scope::*;
use crate::analysis::static_expression::{bit_string_to_string, BitStringConversionError};
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::*;
use crate::TokenSpan;
impl<'a, 't> AnalyzeContext<'a, 't> {
/// Analyze a string literal or expanded bit-string literal for type-matching
fn analyze_string_literal(
&self,
span: TokenSpan,
string_lit: Latin1String,
target_base: TypeEnt<'_>,
target_type: TypeEnt<'_>,
diagnostics: &mut dyn DiagnosticHandler,
) {
if let Some((elem_type, literals)) = as_single_index_enum_array(target_base) {
for chr in string_lit.chars() {
let chr = Designator::Character(*chr);
if !literals.contains(&chr) {
diagnostics.add(
span.pos(self.ctx),
format!("{} does not define character {}", elem_type.describe(), chr),
ErrorCode::InvalidLiteral,
);
break;
}
}
} else {
diagnostics.add(
span.pos(self.ctx),
format!("string literal does not match {}", target_type.describe()),
ErrorCode::TypeMismatch,
);
}
}
/// Returns true if the name actually matches the target type
/// None if it was uncertain
pub fn analyze_literal_with_target_type(
&self,
scope: &Scope<'a>,
target_type: TypeEnt<'a>,
span: TokenSpan,
literal: &mut Literal,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let target_base = target_type.base_type();
match literal {
Literal::AbstractLiteral(abst) => match abst {
AbstractLiteral::Integer(_) => {
if !self.can_be_target_type(self.universal_integer().into(), target_type.base())
{
diagnostics.add(
span.pos(self.ctx),
format!("integer literal does not match {}", target_type.describe()),
ErrorCode::TypeMismatch,
);
}
}
AbstractLiteral::Real(_) => {
if !self.can_be_target_type(self.universal_real().into(), target_type.base()) {
diagnostics.add(
span.pos(self.ctx),
format!("real literal does not match {}", target_type.describe()),
ErrorCode::TypeMismatch,
);
}
}
},
Literal::Character(char) => match target_base.kind() {
Type::Enum(literals) => {
if !literals.contains(&Designator::Character(*char)) {
diagnostics.add(
span.pos(self.ctx),
format!(
"character literal does not match {}",
target_type.describe()
),
ErrorCode::TypeMismatch,
);
}
}
_ => {
diagnostics.add(
span.pos(self.ctx),
format!(
"character literal does not match {}",
target_type.describe()
),
ErrorCode::TypeMismatch,
);
}
},
Literal::String(string_lit) => {
self.analyze_string_literal(
span,
string_lit.to_owned(),
target_base,
target_type,
diagnostics,
);
}
Literal::BitString(bit_string) => {
match bit_string_to_string(bit_string) {
Ok(string_lit) => self.analyze_string_literal(
span,
string_lit,
target_base,
target_type,
diagnostics,
),
Err(err) => {
match err {
BitStringConversionError::IllegalDecimalCharacter(rel_pos) => {
diagnostics.add(
span.pos(self.ctx),
format!(
"Illegal digit '{}' for base 10",
bit_string.value.bytes[rel_pos] as char,
),
ErrorCode::InvalidLiteral,
)
}
BitStringConversionError::IllegalTruncate(_, _) => {
diagnostics.add(
span.pos(self.ctx),
format!(
"Truncating vector to length {} would lose information",
bit_string.length.unwrap() // Safe as this error can only happen when there is a length
),
ErrorCode::InvalidLiteral,
);
}
BitStringConversionError::EmptySignedExpansion => {
diagnostics.add(
span.pos(self.ctx),
"Cannot expand an empty signed bit string",
ErrorCode::InvalidLiteral,
);
}
}
}
}
}
Literal::Physical(PhysicalLiteral { ref mut unit, .. }) => {
match self.resolve_physical_unit(scope, unit) {
Ok(physical_type) => {
if physical_type.base_type() != target_base {
diagnostics.push(Diagnostic::type_mismatch(
&span.pos(self.ctx),
&physical_type.describe(),
target_type,
))
}
}
Err(diagnostic) => {
diagnostics.push(diagnostic);
}
}
}
Literal::Null => {
if !matches!(target_base.kind(), Type::Access(_)) {
diagnostics.add(
span.pos(self.ctx),
format!("null literal does not match {}", target_base.describe()),
ErrorCode::TypeMismatch,
);
}
}
};
Ok(())
}
pub fn resolve_physical_unit(
&self,
scope: &Scope<'a>,
unit: &mut WithRef<Ident>,
) -> Result<TypeEnt<'a>, Diagnostic> {
match scope
.lookup(&Designator::Identifier(unit.item.item.clone()))
.map_err(|err| err.into_diagnostic(self.ctx, unit.item.token))?
{
NamedEntities::Single(unit_ent) => {
unit.set_unique_reference(unit_ent);
if let AnyEntKind::PhysicalLiteral(physical_ent) = unit_ent.actual_kind() {
Ok(*physical_ent)
} else {
Err(Diagnostic::new(
unit.item.pos(self.ctx),
format!("{} is not a physical unit", unit_ent.describe()),
ErrorCode::InvalidLiteral,
))
}
}
NamedEntities::Overloaded(_) => Err(Diagnostic::mismatched_kinds(
unit.item.pos(self.ctx),
"Overloaded name may not be physical unit",
)),
}
}
}
/// Must be an array type with a single index of enum type
fn as_single_index_enum_array(typ: TypeEnt<'_>) -> Option<(TypeEnt<'_>, &FnvHashSet<Designator>)> {
if let Type::Array {
indexes, elem_type, ..
} = typ.kind()
{
if indexes.len() == 1 {
if let Type::Enum(literals) = elem_type.base_type().kind() {
return Some((*elem_type, literals));
}
}
}
None
}

View File

@ -0,0 +1,204 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
//! This module contains types to handle the analysis data in a thread-safe way,
//! in particular when the dependencies between design units are not known.
use parking_lot::{MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
/// Combines an item to be analyzed (typically, a design unit) with the optional results
/// of that analysis.
struct AnalysisState<T, R> {
/// Data gathered during analysis; `None` while not yet analyzed.
result: Option<R>,
/// The subject of analysis; typically, this is a design unit.
data: T,
}
/// A thread-safe r/w-lock on an item to be analyzed (`T`) and the analysis results (`R`).
///
/// Ensures mutable access during analysis and immutable access after analysis.
pub struct AnalysisLock<T, R> {
state: RwLock<AnalysisState<T, R>>,
}
impl<T, R> AnalysisLock<T, R> {
pub fn new(data: T) -> AnalysisLock<T, R> {
AnalysisLock {
state: RwLock::new(AnalysisState { result: None, data }),
}
}
/// Returns an immutable reference to the data and result if it has already been analyzed.
pub fn get(&self) -> Option<ReadGuard<'_, T, R>> {
let guard = self.state.read();
if guard.result.is_some() {
Some(ReadGuard { guard })
} else {
None
}
}
// Has been analyzed
pub fn is_analyzed(&self) -> bool {
self.get().is_some()
}
/// Returns an mutable reference to the data.
pub fn write(&self) -> MappedRwLockWriteGuard<'_, T> {
RwLockWriteGuard::map(self.state.write(), |data| &mut data.data)
}
/// Reset analysis state, analysis needs to be redone.
pub fn reset(&self) {
let mut guard = self.state.write();
guard.result = None;
}
/// Returns an immmutable reference to the data and result.
///
/// Panics if the analysis result is not available.
pub fn expect_analyzed(&self) -> ReadGuard<'_, T, R> {
let guard = self.state.read();
if guard.result.is_none() {
panic!("Expected analysis to have already been done");
}
ReadGuard { guard }
}
/// Creates a view into this lock.
///
/// This view provides:
/// - a mutable reference to the data if not analyzed
/// - an immmutable reference to the data if already analyzed
pub fn entry(&self) -> AnalysisEntry<'_, T, R> {
if let Some(guard) = self.get() {
AnalysisEntry::Occupied(guard)
} else {
let guard = self.state.write();
if guard.result.is_some() {
let guard = ReadGuard {
guard: RwLockWriteGuard::downgrade(guard),
};
AnalysisEntry::Occupied(guard)
} else {
let guard = WriteGuard { guard };
AnalysisEntry::Vacant(guard)
}
}
}
}
/// A view into a thread-safe r/w-lock on an [`AnalysisState`](struct.AnalysisState.html).
///
/// Instances of this type are created by
/// [`AnalysisLock::entry()`](struct.AnalysisLock.html#method.entry)
/// and allow read-access to a completed analysis data and
/// read/write-access to incomplete analysis data.
pub enum AnalysisEntry<'a, T, R> {
Occupied(ReadGuard<'a, T, R>),
Vacant(WriteGuard<'a, T, R>),
}
pub struct ReadGuard<'a, T, R> {
guard: RwLockReadGuard<'a, AnalysisState<T, R>>,
}
impl<'a, T, R> ReadGuard<'a, T, R> {
pub fn result(&self) -> &R {
self.guard.result.as_ref().unwrap()
}
pub fn data(&self) -> &T {
&self.guard.data
}
}
impl<'a, T, R> std::ops::Deref for ReadGuard<'a, T, R> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.guard.data
}
}
pub struct WriteGuard<'a, T, R> {
guard: RwLockWriteGuard<'a, AnalysisState<T, R>>,
}
impl<'a, T, R> WriteGuard<'a, T, R> {
pub fn finish(&mut self, result: R) {
self.guard.result = Some(result);
}
pub fn downgrade(self) -> ReadGuard<'a, T, R> {
if self.guard.result.is_none() {
panic!("Cannot downgrade unit without result");
}
ReadGuard {
guard: RwLockWriteGuard::downgrade(self.guard),
}
}
}
impl<'a, T, R> std::ops::Deref for WriteGuard<'a, T, R> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.guard.data
}
}
impl<'a, T, R> std::ops::DerefMut for WriteGuard<'a, T, R> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.guard.data
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn analysis_result_is_memoized() {
let lock = AnalysisLock::new(1);
match lock.entry() {
AnalysisEntry::Vacant(mut entry) => {
*entry = 2;
entry.finish(1.0);
}
_ => panic!("Expected Vacant entry"),
};
match lock.entry() {
AnalysisEntry::Occupied(entry) => {
assert_eq!(*entry, 2);
assert!((*entry.result() - 1.0_f64).abs() < f64::EPSILON);
}
_ => panic!("Expected Occupied entry"),
};
assert_eq!(*lock.get().unwrap(), 2);
assert!((*lock.get().unwrap().result() - 1.0_f64).abs() < f64::EPSILON);
// Check that lock is reset
lock.reset();
assert!(lock.get().is_none());
match lock.entry() {
AnalysisEntry::Vacant(mut entry) => {
*entry = 2;
entry.finish(1.0);
}
_ => panic!("Expected Vacant entry"),
};
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,769 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use fnv::FnvHashSet;
use vhdl_lang::TokenAccess;
use super::analyze::*;
use super::expression::ExpressionType;
use super::scope::*;
use crate::ast::search::clear_references;
use crate::ast::token_range::WithToken;
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::*;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Disambiguated<'a> {
Unambiguous(OverloadedEnt<'a>),
Ambiguous(Vec<OverloadedEnt<'a>>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DisambiguatedType<'a> {
Unambiguous(TypeEnt<'a>),
Ambiguous(FnvHashSet<BaseType<'a>>),
}
#[derive(Copy, Clone)]
pub enum SubprogramKind<'a> {
Function(Option<TypeEnt<'a>>),
Procedure,
}
// The reason a subprogram was rejected as a candidate of a call
#[derive(Debug)]
enum Rejection<'a> {
// The return type of the subprogram is not correct
ReturnType,
// The subprogram is a procedure but we expected a function
Procedure,
// The amount of actuals or named actuals do not match formals
MissingFormals(Vec<InterfaceEnt<'a>>),
}
struct Candidate<'a> {
ent: OverloadedEnt<'a>,
rejection: Option<Rejection<'a>>,
}
struct Candidates<'a>(Vec<Candidate<'a>>);
impl<'a> Candidates<'a> {
fn new(candidates: &OverloadedName<'a>) -> Self {
Self(
candidates
.entities()
.map(|ent| Candidate {
ent,
rejection: None,
})
.collect(),
)
}
fn remaining(&mut self) -> impl Iterator<Item = &mut Candidate<'a>> {
self.0.iter_mut().filter(|cand| cand.rejection.is_none())
}
fn split(self) -> (Vec<OverloadedEnt<'a>>, Vec<Candidate<'a>>) {
let (remaining, rejected): (Vec<_>, Vec<_>) = self
.0
.into_iter()
.partition(|cand| cand.rejection.is_none());
let remaining: Vec<_> = remaining.into_iter().map(|cand| cand.ent).collect();
(remaining, rejected)
}
fn finish(
self,
ctx: &dyn TokenAccess,
name: &WithToken<Designator>,
ttyp: Option<TypeEnt<'a>>,
) -> Result<Disambiguated<'a>, Diagnostic> {
let (remaining, mut rejected) = self.split();
if remaining.len() == 1 {
Ok(Disambiguated::Unambiguous(remaining[0]))
} else if !remaining.is_empty() {
Ok(Disambiguated::Ambiguous(remaining))
} else {
if let [ref single] = rejected.as_slice() {
if let Some(ttyp) = ttyp {
if matches!(single.rejection, Some(Rejection::ReturnType))
&& matches!(single.ent.kind(), Overloaded::EnumLiteral(_))
{
// Special case to get better error for single rejected enumeration literal
// For example when assigning true to an integer.
return Err(Diagnostic::new(
name.pos(ctx),
format!("'{}' does not match {}", name, ttyp.describe()),
ErrorCode::TypeMismatch,
));
}
}
}
let (err_prefix, code) = if rejected.len() == 1 {
// Provide better error for unique function name
("Invalid call to", ErrorCode::InvalidCall)
} else {
("Could not resolve", ErrorCode::Unresolved)
};
let mut diag = Diagnostic::new(name.pos(ctx), format!("{err_prefix} '{name}'"), code);
rejected.sort_by(|x, y| x.ent.decl_pos().cmp(&y.ent.decl_pos()));
for cand in rejected {
if let Some(decl_pos) = cand.ent.decl_pos() {
let rejection = cand.rejection.unwrap();
match rejection {
Rejection::ReturnType => diag.add_related(
decl_pos,
format!("Does not match return type of {}", cand.ent.describe()),
),
Rejection::Procedure => diag.add_related(
decl_pos,
format!(
"Procedure {} cannot be used as a function",
cand.ent.describe()
),
),
Rejection::MissingFormals(missing) => {
for formal in missing.iter() {
diag.add_related(
decl_pos,
format!("Missing association of {}", formal.describe()),
)
}
}
};
}
}
Err(diag)
}
}
}
impl<'a> Disambiguated<'a> {
pub fn into_type(self) -> DisambiguatedType<'a> {
match self {
Disambiguated::Unambiguous(ent) => {
DisambiguatedType::Unambiguous(ent.return_type().unwrap())
}
Disambiguated::Ambiguous(overloaded) => DisambiguatedType::Ambiguous(
overloaded
.into_iter()
.map(|e| e.return_type().unwrap().base())
.collect(),
),
}
}
}
#[derive(Clone)]
pub(super) struct ResolvedCall<'a> {
pub subpgm: OverloadedEnt<'a>,
pub formals: Vec<TypeEnt<'a>>,
}
impl<'a> AsRef<OverloadedEnt<'a>> for OverloadedEnt<'a> {
fn as_ref(&self) -> &OverloadedEnt<'a> {
self
}
}
impl<'a> AsRef<OverloadedEnt<'a>> for ResolvedCall<'a> {
fn as_ref(&self) -> &OverloadedEnt<'a> {
&self.subpgm
}
}
impl<'a, 't> AnalyzeContext<'a, 't> {
/// Typecheck one overloaded call where the exact subprogram is known
pub fn check_call(
&self,
scope: &Scope<'a>,
error_pos: &SrcPos,
ent: OverloadedEnt<'a>,
assocs: &mut [AssociationElement],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
self.check_association(error_pos, ent.formals(), scope, assocs, diagnostics)?;
Ok(())
}
fn disambiguate_by_kind(candidates: &mut Vec<OverloadedEnt<'a>>, kind: SubprogramKind<'a>) {
match kind {
SubprogramKind::Function(_) => candidates.retain(|cand| cand.is_function()),
SubprogramKind::Procedure => candidates.retain(|cand| cand.is_procedure()),
};
}
fn disambiguate_by_assoc_formals(
&self,
scope: &Scope<'a>,
call_pos: &SrcPos,
candidates: &[OverloadedEnt<'a>],
assocs: &mut [AssociationElement],
) -> EvalResult<Vec<ResolvedCall<'a>>> {
let mut result = Vec::with_capacity(candidates.len());
for ent in candidates.iter() {
if let Some(resolved) = as_fatal(self.resolve_association_formals(
call_pos,
ent.formals(),
scope,
assocs,
&mut NullDiagnostics,
))? {
result.push(ResolvedCall {
subpgm: *ent,
formals: resolved,
});
}
for elem in assocs.iter_mut() {
clear_references(elem, self.ctx);
}
}
Ok(result)
}
fn actual_types(
&self,
scope: &Scope<'a>,
assocs: &mut [AssociationElement],
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<Vec<Option<ExpressionType<'a>>>> {
let mut actual_types = Vec::with_capacity(assocs.len());
for assoc in assocs.iter_mut() {
match &mut assoc.actual.item {
ActualPart::Expression(expr) => {
let actual_type =
self.expr_pos_type(scope, assoc.actual.span, expr, diagnostics)?;
actual_types.push(Some(actual_type));
}
ActualPart::Open => {
actual_types.push(None);
}
}
}
Ok(actual_types)
}
#[allow(clippy::too_many_arguments)]
pub fn disambiguate(
&self,
scope: &Scope<'a>,
call_pos: &SrcPos, // The position of the entire call
call_name: &WithToken<Designator>, // The position and name of the subprogram name in the call
assocs: &mut [AssociationElement],
kind: SubprogramKind<'a>,
all_overloaded: Vec<OverloadedEnt<'a>>,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<Disambiguated<'a>> {
if all_overloaded.len() == 1 {
let ent = all_overloaded[0];
self.check_call(scope, call_pos, ent, assocs, diagnostics)?;
return Ok(Disambiguated::Unambiguous(ent));
}
let mut ok_kind = all_overloaded.clone();
Self::disambiguate_by_kind(&mut ok_kind, kind);
// Does not need disambiguation
if ok_kind.len() == 1 {
let ent = ok_kind[0];
self.check_call(scope, call_pos, ent, assocs, diagnostics)?;
return Ok(Disambiguated::Unambiguous(ent));
} else if ok_kind.is_empty() {
diagnostics.push(Diagnostic::ambiguous(self.ctx, call_name, all_overloaded));
return Err(EvalError::Unknown);
}
// Disambiguate based on uninstantiated subprogram
ok_kind.retain(|ent| !ent.is_uninst_subprogram());
if ok_kind.len() == 1 {
let ent = ok_kind[0];
self.check_call(scope, call_pos, ent, assocs, diagnostics)?;
return Ok(Disambiguated::Unambiguous(ent));
} else if ok_kind.is_empty() {
diagnostics.add(
call_name.pos(self.ctx),
format!("uninstantiated subprogram {} cannot be called", call_name),
ErrorCode::InvalidCall,
);
return Err(EvalError::Unknown);
}
let ok_formals = self.disambiguate_by_assoc_formals(scope, call_pos, &ok_kind, assocs)?;
// Only one candidate matched actual/formal profile
if ok_formals.len() == 1 {
let ent = ok_formals[0].subpgm;
self.check_call(scope, call_pos, ent, assocs, diagnostics)?;
return Ok(Disambiguated::Unambiguous(ent));
} else if ok_formals.is_empty() {
// No candidate matched actual/formal profile
diagnostics.push(Diagnostic::ambiguous(self.ctx, call_name, ok_kind));
return Err(EvalError::Unknown);
}
let actual_types = self.actual_types(scope, assocs, diagnostics)?;
let mut ok_assoc_types = ok_formals.clone();
self.implicit_matcher()
.disambiguate_by_assoc_types(&actual_types, &mut ok_assoc_types);
if ok_assoc_types.len() == 1 {
let ent = ok_assoc_types[0].subpgm;
self.check_call(scope, call_pos, ent, assocs, diagnostics)?;
return Ok(Disambiguated::Unambiguous(ent));
} else if ok_assoc_types.is_empty() {
diagnostics.push(Diagnostic::ambiguous(
self.ctx,
call_name,
ok_formals.into_iter().map(|resolved| resolved.subpgm),
));
return Err(EvalError::Unknown);
}
let ok_return_type = if let SubprogramKind::Function(rtyp) = kind {
let mut ok_return_type = ok_assoc_types.clone();
self.any_matcher()
.disambiguate_op_by_return_type(&mut ok_return_type, rtyp);
// Only one candidate matches type profile, check it
if ok_return_type.len() == 1 {
let ent = ok_return_type[0].subpgm;
self.check_call(scope, call_pos, ent, assocs, diagnostics)?;
return Ok(Disambiguated::Unambiguous(ent));
} else if ok_return_type.is_empty() {
diagnostics.push(Diagnostic::ambiguous(
self.ctx,
call_name,
ok_assoc_types.into_iter().map(|resolved| resolved.subpgm),
));
return Err(EvalError::Unknown);
}
ok_return_type
} else {
ok_assoc_types
};
let mut strict_ok_assoc_types = ok_return_type.clone();
self.strict_matcher()
.disambiguate_by_assoc_types(&actual_types, &mut strict_ok_assoc_types);
if strict_ok_assoc_types.len() == 1 {
let ent = strict_ok_assoc_types[0].subpgm;
self.check_call(scope, call_pos, ent, assocs, diagnostics)?;
return Ok(Disambiguated::Unambiguous(ent));
} else if strict_ok_assoc_types.is_empty() {
// Do not disambiguate away to emtpy result
strict_ok_assoc_types.clone_from(&ok_return_type);
}
Ok(Disambiguated::Ambiguous(
strict_ok_assoc_types
.into_iter()
.map(|resolved| resolved.subpgm)
.collect(),
))
}
pub fn disambiguate_no_actuals(
&self,
name: &WithToken<Designator>,
ttyp: Option<TypeEnt<'a>>,
overloaded: &OverloadedName<'a>,
) -> Result<Option<Disambiguated<'a>>, Diagnostic> {
let mut candidates = Candidates::new(overloaded);
let tbase = ttyp.map(|ttyp| ttyp.base());
for cand in candidates.remaining() {
if !cand.ent.signature().can_be_called_without_actuals() {
cand.rejection = Some(Rejection::MissingFormals(
cand.ent.signature().formals_without_defaults().collect(),
));
} else if let Some(return_type) = cand.ent.return_type() {
if let Some(tbase) = tbase {
if !self.can_be_target_type(return_type, tbase) {
cand.rejection = Some(Rejection::ReturnType);
}
}
} else {
cand.rejection = Some(Rejection::Procedure);
}
}
Ok(Some(candidates.finish(self.ctx, name, ttyp)?))
}
}
impl Diagnostic {
fn ambiguous<'a>(
ctx: &dyn TokenAccess,
name: &WithToken<Designator>,
rejected: impl IntoIterator<Item = OverloadedEnt<'a>>,
) -> Self {
let mut diag = Diagnostic::new(
name.pos(ctx),
format!("Could not resolve call to '{}'", name.item.designator()),
ErrorCode::AmbiguousCall,
);
diag.add_subprogram_candidates("Does not match", rejected);
diag
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analysis::overloaded;
use crate::analysis::tests::TestSetup;
use crate::data::DiagnosticHandler;
use crate::syntax::test::check_diagnostics;
use crate::syntax::test::Code;
impl<'a> TestSetup<'a> {
fn disambiguate(
&'a self,
code: &Code,
ttyp: Option<TypeEnt<'a>>,
diagnostics: &mut dyn DiagnosticHandler,
) -> Option<Disambiguated<'a>> {
let mut fcall = code.function_call();
let des = if let Name::Designator(des) = &fcall.item.name.item {
WithToken::new(des.item.clone(), fcall.span.start_token)
} else {
panic!("Expected designator")
};
let overloaded = if let NamedEntities::Overloaded(overloaded) =
self.scope.lookup(&des.item).unwrap()
{
overloaded
} else {
panic!("Expected overloaded")
};
as_fatal(self.ctx(&code.tokenize()).disambiguate(
&self.scope,
&fcall.pos(&code.tokenize()),
&des,
&mut fcall.item.parameters.items,
overloaded::SubprogramKind::Function(ttyp),
overloaded.entities().collect(),
diagnostics,
))
.unwrap()
}
}
#[test]
fn single_fcall() {
let test = TestSetup::new();
let decl = test.declarative_part(
"
function myfun return integer;
",
);
let func = test.lookup_overloaded(decl.s1("myfun"));
assert_eq!(
test.disambiguate(&test.snippet("myfun"), None, &mut NoDiagnostics),
Some(Disambiguated::Unambiguous(func))
);
}
#[test]
fn single_fcall_bad_type() {
let test = TestSetup::new();
let decl = test.declarative_part(
"
function myfun(arg : integer) return integer;
",
);
let mut diagnostics = Vec::new();
let call = test.snippet("myfun('c')");
assert_eq!(
test.disambiguate(&call, None, &mut diagnostics),
Some(Disambiguated::Unambiguous(
test.lookup_overloaded(decl.s1("myfun"))
)),
);
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
call.s1("'c'"),
"character literal does not match integer type 'INTEGER'",
ErrorCode::TypeMismatch,
)],
);
}
#[test]
fn disambiguate_fcall_based_on_named_association() {
let test = TestSetup::new();
let decl = test.declarative_part(
"
function myfun(arg1 : integer) return integer;
function myfun(arg2 : integer) return character;
",
);
let func = test.lookup_overloaded(decl.s1("myfun(arg1").s1("myfun"));
assert_eq!(
test.disambiguate(&test.snippet("myfun(arg1 => 0)"), None, &mut NoDiagnostics),
Some(Disambiguated::Unambiguous(func))
);
}
#[test]
fn disambiguate_fcall_formal_mismatch() {
let test = TestSetup::new();
let decl = test.declarative_part(
"
function myfun(arg1 : integer) return integer;
",
);
let fcall = test.snippet("myfun(missing => 0)");
let mut diagnostics = Vec::new();
assert_eq!(
test.disambiguate(&fcall, None, &mut diagnostics),
Some(Disambiguated::Unambiguous(
test.lookup_overloaded(decl.s1("myfun"))
))
);
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
fcall.s1("missing"),
"No declaration of 'missing'",
ErrorCode::Unresolved,
),
Diagnostic::new(
fcall,
"No association of parameter 'arg1'",
ErrorCode::Unassociated,
)
.related(decl.s1("arg1"), "Defined here"),
],
);
}
#[test]
fn disambiguate_fcall_no_candidates_matching_actuals() {
let test = TestSetup::new();
let decl = test.declarative_part(
"
function myfun(arg1 : integer) return integer;
function myfun(arg2 : integer) return character;
",
);
let fcall = test.snippet("myfun");
let mut diagnostics = Vec::new();
assert_eq!(test.disambiguate(&fcall, None, &mut diagnostics), None);
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
fcall.s1("myfun"),
"Could not resolve call to 'myfun'",
ErrorCode::AmbiguousCall,
)
.related(
decl.s1("myfun"),
"Does not match function myfun[INTEGER return INTEGER]",
)
.related(
decl.s("myfun", 2),
"Does not match function myfun[INTEGER return CHARACTER]",
)],
);
}
#[test]
fn disambiguate_fcall_based_on_type() {
let test = TestSetup::new();
let decl = test.declarative_part(
"
function myfun(arg1 : character) return integer;
function myfun(arg1 : integer) return integer;
",
);
let fcall = test.snippet("myfun('c')");
assert_eq!(
test.disambiguate(&fcall, None, &mut NoDiagnostics),
Some(Disambiguated::Unambiguous(
test.lookup_overloaded(decl.s1("myfun"))
))
);
let fcall = test.snippet("myfun(0)");
assert_eq!(
test.disambiguate(&fcall, None, &mut NoDiagnostics),
Some(Disambiguated::Unambiguous(
test.lookup_overloaded(decl.s("myfun", 2))
))
);
}
#[test]
fn ambiguous_call() {
let test = TestSetup::new();
let decl = test.declarative_part(
"
function myfun(arg1 : integer) return character;
function myfun(arg1 : integer) return integer;
",
);
let fcall = test.snippet("myfun(0)");
assert_eq!(
test.disambiguate(&fcall, None, &mut NoDiagnostics),
Some(Disambiguated::Ambiguous(vec![
test.lookup_overloaded(decl.s("myfun", 2)),
test.lookup_overloaded(decl.s1("myfun")),
]))
);
}
#[test]
fn ambiguous_call_without_match() {
let test = TestSetup::new();
let decl = test.declarative_part(
"
function myfun(arg1 : character) return integer;
function myfun(arg1 : character) return character;
",
);
let fcall = test.snippet("myfun(0)");
let mut diagnostics = Vec::new();
assert_eq!(test.disambiguate(&fcall, None, &mut diagnostics), None);
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
fcall.s1("myfun"),
"Could not resolve call to 'myfun'",
ErrorCode::AmbiguousCall,
)
.related(
decl.s1("myfun"),
"Does not match function myfun[CHARACTER return INTEGER]",
)
.related(
decl.s("myfun", 2),
"Does not match function myfun[CHARACTER return CHARACTER]",
)],
);
}
#[test]
fn disambiguates_target_type() {
let test = TestSetup::new();
let decl = test.declarative_part(
"
function myfun(arg1 : integer) return integer;
function myfun(arg1 : integer) return character;
",
);
let fcall = test.snippet("myfun(0)");
assert_eq!(
test.disambiguate(
&fcall,
Some(test.lookup_type("integer")),
&mut NoDiagnostics
),
Some(Disambiguated::Unambiguous(
test.lookup_overloaded(decl.s1("myfun"))
))
);
}
#[test]
fn target_type_no_match() {
let test = TestSetup::new();
let decl = test.declarative_part(
"
function myfun(arg1 : integer) return integer;
function myfun(arg1 : integer) return character;
",
);
let fcall = test.snippet("myfun(0)");
let mut diagnostics = Vec::new();
assert_eq!(
test.disambiguate(&fcall, Some(test.lookup_type("boolean")), &mut diagnostics),
None,
);
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
fcall.s1("myfun"),
"Could not resolve call to 'myfun'",
ErrorCode::AmbiguousCall,
)
.related(
decl.s1("myfun"),
"Does not match function myfun[INTEGER return INTEGER]",
)
.related(
decl.s("myfun", 2),
"Does not match function myfun[INTEGER return CHARACTER]",
)],
)
}
#[test]
fn ambiguous_builtin_favors_non_implicit_conversion_unary() {
let test = TestSetup::new();
let code = test.snippet("to_string(0)");
let uint_to_string = test
.ctx(&code.tokenize())
.universal_integer()
.lookup_implicit_of("TO_STRING");
assert_eq!(
test.disambiguate(&code, None, &mut NoDiagnostics),
Some(Disambiguated::Unambiguous(uint_to_string))
);
}
#[test]
fn ambiguous_builtin_favors_non_implicit_conversion_binary() {
let test = TestSetup::new();
let code = test.snippet("minimum(0, integer'(0))");
let minimum = test
.ctx(&code.tokenize())
.integer()
.lookup_implicit_of("MINIMUM");
assert_eq!(
test.disambiguate(&code, None, &mut NoDiagnostics),
Some(Disambiguated::Unambiguous(minimum))
);
}
}

View File

@ -0,0 +1,627 @@
//! This Source Code Form is subject to the terms of the Mozilla Public
//! License, v. 2.0. If a copy of the MPL was not distributed with this file,
//! You can obtain one at http://mozilla.org/MPL/2.0/.
//!
//! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use fnv::FnvHashMap;
use vhdl_lang::SrcPos;
use super::analyze::*;
use super::names::ResolvedName;
use super::scope::*;
use crate::ast::AssociationElement;
use crate::ast::Expression;
use crate::ast::Literal;
use crate::ast::Name;
use crate::ast::Operator;
use crate::ast::PackageInstantiation;
use crate::ast::{ActualPart, MapAspect};
use crate::data::error_codes::ErrorCode;
use crate::data::DiagnosticHandler;
use crate::named_entity::*;
use crate::Diagnostic;
use crate::NullDiagnostics;
impl<'a, 't> AnalyzeContext<'a, 't> {
pub fn generic_package_instance(
&self,
scope: &Scope<'a>,
package_ent: EntRef<'a>,
unit: &mut PackageInstantiation,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<Region<'a>> {
let PackageInstantiation {
package_name,
generic_map,
..
} = unit;
let package_region =
self.analyze_package_instance_name(scope, package_name, diagnostics)?;
self.generic_instance(
package_ent,
scope,
unit.ident.tree.pos(self.ctx),
&package_region,
generic_map,
diagnostics,
)
.map(|(region, _)| region)
}
pub fn generic_map(
&self,
scope: &Scope<'a>,
generics: GpkgRegion<'a>,
generic_map: &mut [AssociationElement],
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<FnvHashMap<EntityId, TypeEnt<'a>>> {
let mut mapping = FnvHashMap::default();
// @TODO check missing associations
for (idx, assoc) in generic_map.iter_mut().enumerate() {
let formal = if let Some(formal) = &mut assoc.formal {
let formal_pos = formal.pos(self.ctx);
if let Name::Designator(des) = &mut formal.item {
match generics.lookup(&formal_pos, &des.item) {
Ok((_, ent)) => {
des.set_unique_reference(&ent);
ent
}
Err(err) => {
diagnostics.push(err);
continue;
}
}
} else {
diagnostics.add(
formal.pos(self.ctx),
"Expected simple name for package generic formal",
ErrorCode::MismatchedKinds,
);
continue;
}
} else if let Some(ent) = generics.nth(idx) {
ent
} else {
diagnostics.add(
assoc.actual.pos(self.ctx),
"Extra actual for generic map",
ErrorCode::TooManyArguments,
);
continue;
};
match &mut assoc.actual.item {
ActualPart::Expression(expr) => match formal {
GpkgInterfaceEnt::Type(uninst_typ) => {
let typ = if let Expression::Name(name) = expr {
match name.as_mut() {
// Could be an array constraint such as integer_vector(0 to 3)
// @TODO we ignore the suffix for now
Name::Slice(prefix, drange) => {
let typ = self.type_name(
scope,
prefix.span,
&mut prefix.item,
diagnostics,
)?;
if let Type::Array { indexes, .. } = typ.base().kind() {
if let Some(Some(idx_typ)) = indexes.first() {
self.drange_with_ttyp(
scope,
(*idx_typ).into(),
drange,
diagnostics,
)?;
}
} else {
diagnostics.add(
assoc.actual.pos(self.ctx),
format!(
"Array constraint cannot be used for {}",
typ.describe()
),
ErrorCode::TypeMismatch,
);
}
typ
}
// Could be a record constraint such as rec_t(field(0 to 3))
// @TODO we ignore the suffix for now
Name::CallOrIndexed(call) if call.could_be_indexed_name() => self
.type_name(
scope,
call.name.span,
&mut call.name.item,
diagnostics,
)?,
_ => self.type_name(scope, assoc.actual.span, name, diagnostics)?,
}
} else {
diagnostics.add(
assoc.actual.pos(self.ctx),
"Cannot map expression to type generic",
ErrorCode::MismatchedKinds,
);
continue;
};
mapping.insert(uninst_typ.id(), typ);
}
GpkgInterfaceEnt::Constant(obj) => self.expr_pos_with_ttyp(
scope,
self.map_type_ent(&mapping, obj.type_mark(), scope),
assoc.actual.span,
expr,
diagnostics,
)?,
GpkgInterfaceEnt::Subprogram(target) => match expr {
Expression::Name(name) => {
let resolved =
self.name_resolve(scope, assoc.actual.span, name, diagnostics)?;
if let ResolvedName::Overloaded(des, overloaded) = resolved {
let signature = target.subprogram_key().map(|base_type| {
mapping
.get(&base_type.id())
.map(|ent| ent.base())
.unwrap_or(base_type)
});
if let Some(ent) = overloaded.get(&signature) {
name.set_unique_reference(&ent);
} else {
let mut diag = Diagnostic::mismatched_kinds(
assoc.actual.pos(self.ctx),
format!(
"Cannot map '{des}' to subprogram generic {}{}",
target.designator(),
signature.key().describe()
),
);
diag.add_subprogram_candidates(
"Does not match",
overloaded.entities(),
);
diagnostics.push(diag)
}
} else {
diagnostics.add(
assoc.actual.pos(self.ctx),
format!(
"Cannot map {} to subprogram generic",
resolved.describe()
),
ErrorCode::MismatchedKinds,
)
}
}
Expression::Literal(Literal::String(string)) => {
if Operator::from_latin1(string.clone()).is_none() {
diagnostics.add(
assoc.actual.pos(self.ctx),
"Invalid operator symbol",
ErrorCode::InvalidOperatorSymbol,
);
}
}
_ => diagnostics.add(
assoc.actual.pos(self.ctx),
"Cannot map expression to subprogram generic",
ErrorCode::MismatchedKinds,
),
},
GpkgInterfaceEnt::Package(_) => match expr {
Expression::Name(name) => {
self.name_resolve(scope, assoc.actual.span, name, diagnostics)?;
}
_ => diagnostics.add(
assoc.actual.pos(self.ctx),
"Cannot map expression to package generic",
ErrorCode::MismatchedKinds,
),
},
},
ActualPart::Open => {
// @TODO
}
}
}
Ok(mapping)
}
pub fn generic_instance(
&self,
ent: EntRef<'a>,
scope: &Scope<'a>,
decl_pos: &SrcPos,
uninst_region: &Region<'a>,
generic_map: &mut Option<MapAspect>,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<(Region<'a>, FnvHashMap<EntityId, TypeEnt<'a>>)> {
let nested = scope.nested().in_package_declaration();
let (generics, other) = uninst_region.to_package_generic();
let mapping = if let Some(generic_map) = generic_map {
self.generic_map(
&nested,
generics,
generic_map.list.items.as_mut_slice(),
diagnostics,
)?
} else {
FnvHashMap::default()
};
for uninst in other {
match self.instantiate(Some(ent), &mapping, uninst, &nested) {
Ok(inst) => {
// We ignore diagnostics here, for example when adding implicit operators EQ and NE for interface types
// They can collide if there are more than one interface type that map to the same actual type
nested.add(inst, &mut NullDiagnostics);
}
Err((err, code)) => {
let mut diag = Diagnostic::new(decl_pos, err, code);
if let Some(pos) = uninst.decl_pos() {
diag.add_related(pos, "When instantiating this declaration");
}
diagnostics.push(diag);
}
}
}
Ok((nested.into_region(), mapping))
}
pub(crate) fn instantiate(
&self,
parent: Option<EntRef<'a>>,
mapping: &FnvHashMap<EntityId, TypeEnt<'a>>,
uninst: EntRef<'a>,
scope: &Scope<'a>,
) -> Result<EntRef<'a>, (String, ErrorCode)> {
let designator = uninst.designator().clone();
let decl_pos = uninst.decl_pos().cloned();
let inst = self.arena.alloc(
designator,
parent.or(uninst.parent),
Related::InstanceOf(uninst),
AnyEntKind::Library, // Will be immediately overwritten below
decl_pos,
uninst.src_span,
Some(self.source()),
);
let kind = self.map_kind(Some(inst), mapping, uninst.kind(), scope)?;
unsafe {
inst.set_kind(kind);
}
for implicit_uninst in uninst.implicits.iter() {
unsafe {
self.arena.add_implicit(
inst.id(),
self.instantiate(Some(inst), mapping, implicit_uninst, scope)?,
);
}
}
Ok(inst)
}
fn map_kind(
&self,
parent: Option<EntRef<'a>>,
mapping: &FnvHashMap<EntityId, TypeEnt<'a>>,
kind: &'a AnyEntKind<'a>,
scope: &Scope<'a>,
) -> Result<AnyEntKind<'a>, (String, ErrorCode)> {
Ok(match kind {
AnyEntKind::ExternalAlias { class, type_mark } => AnyEntKind::ExternalAlias {
class: *class,
type_mark: self.map_type_ent(mapping, *type_mark, scope),
},
AnyEntKind::ObjectAlias {
base_object,
type_mark,
} => AnyEntKind::ObjectAlias {
base_object: if let Some(obj) =
ObjectEnt::from_any(self.instantiate(None, mapping, base_object, scope)?)
{
obj
} else {
return Err((
"Internal error, expected instantiated object to be object".to_owned(),
ErrorCode::Internal,
));
},
type_mark: self.map_type_ent(mapping, *type_mark, scope),
},
AnyEntKind::File(subtype) => {
AnyEntKind::File(self.map_subtype(mapping, *subtype, scope))
}
AnyEntKind::InterfaceFile(typ) => {
AnyEntKind::InterfaceFile(self.map_type_ent(mapping, *typ, scope))
}
AnyEntKind::Component(region) => {
AnyEntKind::Component(self.map_region(parent, mapping, region, scope)?)
}
AnyEntKind::Attribute(typ) => {
AnyEntKind::Attribute(self.map_type_ent(mapping, *typ, scope))
}
AnyEntKind::Overloaded(overloaded) => {
AnyEntKind::Overloaded(self.map_overloaded(parent, mapping, overloaded, scope)?)
}
AnyEntKind::Type(typ) => AnyEntKind::Type(self.map_type(parent, mapping, typ, scope)?),
AnyEntKind::ElementDeclaration(subtype) => {
AnyEntKind::ElementDeclaration(self.map_subtype(mapping, *subtype, scope))
}
AnyEntKind::Sequential(s) => AnyEntKind::Sequential(*s),
AnyEntKind::Concurrent(c) => AnyEntKind::Concurrent(*c),
AnyEntKind::Object(obj) => AnyEntKind::Object(self.map_object(mapping, obj, scope)),
AnyEntKind::LoopParameter(typ) => AnyEntKind::LoopParameter(
typ.map(|typ| self.map_type_ent(mapping, typ.into(), scope).base()),
),
AnyEntKind::PhysicalLiteral(typ) => {
AnyEntKind::PhysicalLiteral(self.map_type_ent(mapping, *typ, scope))
}
AnyEntKind::DeferredConstant(subtype) => {
AnyEntKind::DeferredConstant(self.map_subtype(mapping, *subtype, scope))
}
AnyEntKind::Library => AnyEntKind::Library,
AnyEntKind::Design(design) => match design {
Design::PackageInstance(region) => AnyEntKind::Design(Design::PackageInstance(
self.map_region(parent, mapping, region, scope)?,
)),
Design::InterfacePackageInstance(region) => {
AnyEntKind::Design(Design::InterfacePackageInstance(
self.map_region(parent, mapping, region, scope)?,
))
}
_ => {
return Err((
format!(
"Internal error, did not expect to instantiate {}",
design.describe()
),
ErrorCode::Internal,
));
}
},
AnyEntKind::View(typ) => AnyEntKind::View(self.map_subtype(mapping, *typ, scope)),
})
}
fn map_overloaded(
&self,
parent: Option<EntRef<'a>>,
mapping: &FnvHashMap<EntityId, TypeEnt<'a>>,
overloaded: &'a Overloaded<'a>,
scope: &Scope<'a>,
) -> Result<Overloaded<'a>, (String, ErrorCode)> {
Ok(match overloaded {
Overloaded::SubprogramDecl(signature) => {
Overloaded::SubprogramDecl(self.map_signature(parent, mapping, signature, scope)?)
}
Overloaded::Subprogram(signature) => {
Overloaded::Subprogram(self.map_signature(parent, mapping, signature, scope)?)
}
Overloaded::UninstSubprogramDecl(signature, generic_map) => {
Overloaded::UninstSubprogramDecl(
self.map_signature(parent, mapping, signature, scope)?,
generic_map.clone(),
)
}
Overloaded::UninstSubprogram(signature, generic_map) => Overloaded::UninstSubprogram(
self.map_signature(parent, mapping, signature, scope)?,
generic_map.clone(),
),
Overloaded::InterfaceSubprogram(signature) => Overloaded::InterfaceSubprogram(
self.map_signature(parent, mapping, signature, scope)?,
),
Overloaded::EnumLiteral(signature) => {
Overloaded::EnumLiteral(self.map_signature(parent, mapping, signature, scope)?)
}
Overloaded::Alias(alias) => {
let alias_inst = self.instantiate(parent, mapping, alias, scope)?;
if let Some(overloaded) = OverloadedEnt::from_any(alias_inst) {
Overloaded::Alias(overloaded)
} else {
return Err((
"Internal error, expected overloaded when instantiating overloaded entity"
.to_owned(),
ErrorCode::Internal,
));
}
}
})
}
pub(crate) fn map_signature(
&self,
parent: Option<EntRef<'a>>,
mapping: &FnvHashMap<EntityId, TypeEnt<'a>>,
signature: &'a Signature<'a>,
scope: &Scope<'a>,
) -> Result<Signature<'a>, (String, ErrorCode)> {
let Signature {
formals,
return_type,
} = signature;
let FormalRegion {
typ,
entities: uninst_entities,
} = formals;
let mut inst_entities = Vec::with_capacity(uninst_entities.len());
for uninst in uninst_entities {
let inst = self.instantiate(parent, mapping, uninst, scope)?;
if let Some(inst) = InterfaceEnt::from_any(inst) {
inst_entities.push(inst);
} else {
return Err((
"Internal error, expected interface to be instantiated as interface".to_owned(),
ErrorCode::Internal,
));
}
}
Ok(Signature {
formals: FormalRegion {
typ: *typ,
entities: inst_entities,
},
return_type: return_type.map(|typ| self.map_type_ent(mapping, typ, scope)),
})
}
fn map_region(
&self,
parent: Option<EntRef<'a>>,
mapping: &FnvHashMap<EntityId, TypeEnt<'a>>,
region: &'a Region<'a>,
scope: &Scope<'a>,
) -> Result<Region<'a>, (String, ErrorCode)> {
let Region {
entities: uninst_entities,
kind,
..
} = region;
let mut inst_region = Region {
kind: *kind,
..Region::default()
};
for uninst in uninst_entities.values() {
match uninst {
NamedEntities::Single(uninst) => {
let inst = self.instantiate(parent, mapping, uninst, scope)?;
inst_region.add(inst, &mut NullDiagnostics);
}
NamedEntities::Overloaded(overloaded) => {
for uninst in overloaded.entities() {
let inst = self.instantiate(parent, mapping, uninst.into(), scope)?;
inst_region.add(inst, &mut NullDiagnostics);
}
}
}
}
Ok(inst_region)
}
fn map_type(
&self,
parent: Option<EntRef<'a>>,
mapping: &FnvHashMap<EntityId, TypeEnt<'a>>,
typ: &'a Type<'a>,
scope: &Scope<'a>,
) -> Result<Type<'a>, (String, ErrorCode)> {
Ok(match typ {
Type::Array { indexes, elem_type } => {
let mut mapped_indexes = Vec::with_capacity(indexes.len());
for index_typ in indexes.iter() {
mapped_indexes.push(index_typ.map(|index_typ| {
self.map_type_ent(mapping, index_typ.into(), scope).base()
}))
}
Type::Array {
indexes: mapped_indexes,
elem_type: self.map_type_ent(mapping, *elem_type, scope),
}
}
Type::Enum(symbols) => Type::Enum(symbols.clone()),
Type::Integer => Type::Integer,
Type::Real => Type::Real,
Type::Physical => Type::Physical,
Type::Access(subtype) => Type::Access(self.map_subtype(mapping, *subtype, scope)),
Type::Record(record_region) => {
let mut elems = Vec::with_capacity(record_region.elems.len());
for uninst in record_region.elems.iter() {
let inst = self.instantiate(parent, mapping, uninst, scope)?;
if let Some(inst) = RecordElement::from_any(inst) {
elems.push(inst);
} else {
return Err(("Internal error, expected instantiated record element to be record element".to_owned(), ErrorCode::Internal));
}
}
Type::Record(RecordRegion { elems })
}
Type::Subtype(subtype) => Type::Subtype(self.map_subtype(mapping, *subtype, scope)),
Type::Protected(region, is_body) => {
Type::Protected(self.map_region(parent, mapping, region, scope)?, *is_body)
}
Type::File => Type::File,
Type::Alias(typ) => Type::Alias(self.map_type_ent(mapping, *typ, scope)),
Type::Universal(utyp) => Type::Universal(*utyp),
Type::Incomplete => Type::Incomplete,
Type::Interface => Type::Interface,
})
}
fn map_object(
&self,
mapping: &FnvHashMap<EntityId, TypeEnt<'a>>,
obj: &Object<'a>,
scope: &Scope<'a>,
) -> Object<'a> {
let Object {
class,
iface,
subtype,
has_default,
} = obj;
Object {
class: *class,
iface: iface.clone(),
subtype: self.map_subtype(mapping, *subtype, scope),
has_default: *has_default,
}
}
fn map_type_ent(
&self,
mapping: &FnvHashMap<EntityId, TypeEnt<'a>>,
typ: TypeEnt<'a>,
scope: &Scope<'a>,
) -> TypeEnt<'a> {
match mapping.get(&typ.id()) {
None => {
if let Some(entity) = scope
.lookup(&typ.designator)
.ok()
.and_then(|result| result.into_non_overloaded().ok())
.and_then(TypeEnt::from_any)
{
entity
} else {
typ
}
}
Some(typ) => *typ,
}
}
fn map_subtype(
&self,
mapping: &FnvHashMap<EntityId, TypeEnt<'a>>,
subtype: Subtype<'a>,
scope: &Scope<'a>,
) -> Subtype<'a> {
let Subtype { type_mark } = subtype;
Subtype {
type_mark: self.map_type_ent(mapping, type_mark, scope),
}
}
}

View File

@ -0,0 +1,663 @@
//! This Source Code Form is subject to the terms of the Mozilla Public
//! License, v. 2.0. If a copy of the MPL was not distributed with this file,
//! You can obtain one at http://mozilla.org/MPL/2.0/.
//!
//! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use super::analyze::*;
use super::expression::ExpressionType;
use super::names::AttributeSuffix;
use super::names::ResolvedName;
use super::overloaded::Disambiguated;
use super::overloaded::DisambiguatedType;
use super::scope::*;
use crate::ast::token_range::WithTokenSpan;
use crate::ast::Range;
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::*;
impl<'a, 't> AnalyzeContext<'a, 't> {
pub fn range_unknown_typ(
&self,
scope: &Scope<'a>,
range: &mut Range,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
as_fatal(self.range_type(scope, range, diagnostics))?;
Ok(())
}
pub fn drange_unknown_type(
&self,
scope: &Scope<'a>,
drange: &mut DiscreteRange,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
as_fatal(self.drange_type(scope, drange, diagnostics))?;
Ok(())
}
fn range_expr_type(
&self,
scope: &Scope<'a>,
expr: &mut WithTokenSpan<Expression>,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<DisambiguatedType<'a>> {
match self.expr_type(scope, expr, diagnostics)? {
ExpressionType::Unambiguous(typ) => {
if typ.base().is_scalar() {
Ok(DisambiguatedType::Unambiguous(typ))
} else {
diagnostics.add(
expr.pos(self.ctx),
format!("Non-scalar {} cannot be used in a range", typ.describe()),
ErrorCode::NonScalarInRange,
);
Err(EvalError::Unknown)
}
}
ExpressionType::Ambiguous(types) => Ok(DisambiguatedType::Ambiguous(
types.into_iter().filter(|typ| typ.is_scalar()).collect(),
)),
ExpressionType::String | ExpressionType::Null | ExpressionType::Aggregate => {
diagnostics.add(
expr.pos(self.ctx),
"Non-scalar expression cannot be used in a range",
ErrorCode::NonScalarInRange,
);
Err(EvalError::Unknown)
}
}
}
pub fn range_attribute_type(
&self,
scope: &Scope<'a>,
attr: &mut AttributeName,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<BaseType<'a>> {
let resolved =
self.name_resolve(scope, attr.name.span, &mut attr.name.item, diagnostics)?;
let typ = match resolved {
ResolvedName::Type(typ) => typ,
ResolvedName::ObjectName(oname) => oname.type_mark(),
ResolvedName::Overloaded(ref des, ref overloaded) => {
let disamb = self
.disambiguate_no_actuals(des, None, overloaded)
.into_eval_result(diagnostics)?;
if let Some(disamb) = disamb {
if let Disambiguated::Unambiguous(ref ent) = disamb {
attr.name.set_unique_reference(ent);
ent.return_type().unwrap()
} else {
diagnostics.add(
attr.name.pos(self.ctx),
format!(
"{} cannot be prefix of range attribute, array type or object is required",
resolved.describe()
),
ErrorCode::MismatchedKinds,
);
return Err(EvalError::Unknown);
}
} else {
return Err(EvalError::Unknown);
}
}
ResolvedName::Expression(DisambiguatedType::Unambiguous(typ)) => typ,
ResolvedName::Expression(_)
| ResolvedName::Final(_)
| ResolvedName::Library(_)
| ResolvedName::Design(_) => {
diagnostics.add(
attr.name.pos(self.ctx),
format!(
"{} cannot be prefix of range attribute, array type or object is required",
resolved.describe()
),
ErrorCode::MismatchedKinds,
);
return Err(EvalError::Unknown);
}
};
if let Some((_, indexes)) = typ.array_type() {
if let Some(first) = indexes.first() {
if let Some(base_type) = first {
Ok(*base_type)
} else {
// There was probably an error in the type definition of this signal/variable
Err(EvalError::Unknown)
}
} else {
// This should never happen
if let Some(decl_pos) = typ.decl_pos() {
// To debug if it ever happens
// eprintln!("{}", decl_pos.show("Array with no indexes"));
// eprintln!("{}", attr.name.pos(self.ctx).show("Used here"));
panic!("Internal error")
}
Err(EvalError::Unknown)
}
} else {
diagnostics.add(
attr.name.pos(self.ctx),
format!(
"{} cannot be prefix of range attribute, array type or object is required",
resolved.describe()
),
ErrorCode::MismatchedKinds,
);
Err(EvalError::Unknown)
}
}
pub fn range_type(
&self,
scope: &Scope<'a>,
range: &mut Range,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<BaseType<'a>> {
match range {
Range::Range(ref mut constraint) => {
let left_types =
self.range_expr_type(scope, &mut constraint.left_expr, diagnostics)?;
let right_types =
self.range_expr_type(scope, &mut constraint.right_expr, diagnostics)?;
let left_ambig = matches!(left_types, DisambiguatedType::Ambiguous(_));
let right_ambig = matches!(right_types, DisambiguatedType::Ambiguous(_));
let types = match (left_types, right_types) {
(DisambiguatedType::Unambiguous(l), DisambiguatedType::Unambiguous(r)) => {
return if let Some(typ) = self.common_type(l.base(), r.base()) {
Ok(typ)
} else {
diagnostics.add(
constraint.span().pos(self.ctx),
format!(
"Range type mismatch, left is {}, right is {}",
l.base().describe(),
r.base().describe()
),
ErrorCode::TypeMismatch,
);
Err(EvalError::Unknown)
}
}
(DisambiguatedType::Unambiguous(l), DisambiguatedType::Ambiguous(r)) => {
self.common_types(r, l.base())
}
(DisambiguatedType::Ambiguous(l), DisambiguatedType::Unambiguous(r)) => {
self.common_types(l, r.base())
}
(DisambiguatedType::Ambiguous(_), DisambiguatedType::Ambiguous(_)) => {
diagnostics.add(
constraint.span().pos(self.ctx),
"Range is ambiguous",
ErrorCode::TypeMismatch,
);
return Err(EvalError::Unknown);
}
};
if types.len() == 1 {
let typ = types.into_iter().next().unwrap();
if left_ambig {
self.expr_with_ttyp(
scope,
typ.into(),
&mut constraint.left_expr,
diagnostics,
)?;
}
if right_ambig {
self.expr_with_ttyp(
scope,
typ.into(),
&mut constraint.right_expr,
diagnostics,
)?;
}
Ok(typ)
} else if types.is_empty() {
diagnostics.add(
constraint.span().pos(self.ctx),
"Range type of left and right side does not match",
ErrorCode::TypeMismatch,
);
Err(EvalError::Unknown)
} else {
diagnostics.add(
constraint.span().pos(self.ctx),
"Range is ambiguous",
ErrorCode::TypeMismatch,
);
Err(EvalError::Unknown)
}
}
Range::Attribute(ref mut attr) => self.range_attribute_type(scope, attr, diagnostics),
}
}
pub fn drange_type(
&self,
scope: &Scope<'a>,
drange: &mut DiscreteRange,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<BaseType<'a>> {
let typ = match drange {
DiscreteRange::Discrete(ref mut type_mark, ref mut range) => {
let typ = self
.type_name(scope, type_mark.span, &mut type_mark.item, diagnostics)?
.base();
if let Some(ref mut range) = range {
self.range_with_ttyp(scope, typ.into(), range, diagnostics)?;
}
typ
}
DiscreteRange::Range(ref mut range) => self.range_type(scope, range, diagnostics)?,
};
if typ.is_discrete() {
Ok(typ)
} else {
diagnostics.add(
drange.span().pos(self.ctx),
format!(
"Non-discrete {} cannot be used in discrete range",
typ.describe()
),
ErrorCode::MismatchedKinds,
);
Err(EvalError::Unknown)
}
}
pub fn range_with_ttyp(
&self,
scope: &Scope<'a>,
target_type: TypeEnt<'a>,
range: &mut Range,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
match range {
Range::Range(ref mut constraint) => {
self.expr_pos_with_ttyp(
scope,
target_type,
constraint.left_expr.span,
&mut constraint.left_expr.item,
diagnostics,
)?;
self.expr_pos_with_ttyp(
scope,
target_type,
constraint.right_expr.span,
&mut constraint.right_expr.item,
diagnostics,
)?;
}
Range::Attribute(ref mut name) => {
let AttributeName {
name,
signature,
expr,
attr, // Parser ensures this must be 'range or we would not end up here
} = name.as_mut();
let prefix_typ = as_fatal(
self.name_resolve(scope, name.span, &mut name.item, diagnostics)
.and_then(|prefix| {
prefix.as_type_of_attr_prefix(
self.ctx,
name.span,
&AttributeSuffix { attr, expr },
diagnostics,
)
}),
)?;
if let Some(ref mut signature) = signature {
diagnostics.add(
signature.pos(self.ctx),
format!("Did not expect signature for '{attr} attribute"),
ErrorCode::UnexpectedSignature,
);
}
if let Some(prefix_typ) = prefix_typ {
if let Some((_, indexes)) = prefix_typ.array_type() {
if let Some(index_typ) =
as_fatal(self.array_index_expression_in_attribute(
indexes,
expr.as_mut().map(|expr| expr.as_mut()),
diagnostics,
))?
{
if !self.can_be_target_type(index_typ.into(), target_type.base()) {
diagnostics.push(Diagnostic::type_mismatch(
&range.span().pos(self.ctx),
&index_typ.describe(),
target_type,
))
}
}
}
}
}
}
Ok(())
}
pub fn drange_with_ttyp(
&self,
scope: &Scope<'a>,
target_type: TypeEnt<'a>,
drange: &mut DiscreteRange,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
match drange {
DiscreteRange::Discrete(ref mut type_mark, ref mut range) => {
let _ = as_fatal(self.name_resolve(
scope,
type_mark.span,
&mut type_mark.item,
diagnostics,
))?;
if let Some(ref mut range) = range {
self.range_with_ttyp(scope, target_type, range, diagnostics)?;
}
}
DiscreteRange::Range(ref mut range) => {
self.range_with_ttyp(scope, target_type, range, diagnostics)?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::analysis::analyze::EvalError;
use crate::analysis::analyze::EvalResult;
use crate::analysis::tests::TestSetup;
use crate::ast::search::check_no_unresolved;
use crate::ast::Range;
use crate::data::error_codes::ErrorCode;
use crate::data::DiagnosticHandler;
use crate::data::NoDiagnostics;
use crate::named_entity::BaseType;
use crate::syntax::test::check_diagnostics;
use crate::syntax::test::Code;
use crate::Diagnostic;
use vhdl_lang::Token;
impl<'a> TestSetup<'a> {
fn range_type(
&'a self,
code: &Code,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<BaseType<'a>> {
self.ctx(&code.tokenize())
.range_type(&self.scope, &mut code.range(), diagnostics)
}
fn range_type_ast(
&'a self,
rng: &mut Range,
tokens: &Vec<Token>,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<BaseType<'a>> {
self.ctx(tokens).range_type(&self.scope, rng, diagnostics)
}
}
#[test]
fn universal_integer_range() {
let test = TestSetup::new();
let code = test.snippet("0 to 1");
assert_eq!(
test.range_type(&code, &mut NoDiagnostics),
Ok(test.ctx(&code.tokenize()).universal_integer())
);
}
#[test]
fn universal_integer_range_expression() {
let test = TestSetup::new();
let code = test.snippet("-1 to 1");
assert_eq!(
test.range_type(&code, &mut NoDiagnostics),
Ok(test.ctx(&code.tokenize()).universal_integer())
);
}
#[test]
fn character_range() {
let test = TestSetup::new();
let code = test.snippet("'a' to 'b'");
assert_eq!(
test.range_type(&code, &mut NoDiagnostics),
Ok(test.lookup_type("CHARACTER").base())
);
}
#[test]
fn discrete_range_not_discrete_type() {
let test = TestSetup::new();
let code = test.snippet("0.0 to 1.0");
let mut diagnostics = Vec::new();
assert_eq!(
test.ctx(&code.tokenize()).drange_type(
&test.scope,
&mut code.discrete_range(),
&mut diagnostics
),
Err(EvalError::Unknown)
);
check_diagnostics(
diagnostics,
vec![Diagnostic::mismatched_kinds(
code.s1("0.0 to 1.0"),
"Non-discrete type universal_real cannot be used in discrete range",
)],
)
}
#[test]
fn range_not_discrete_expr() {
let test = TestSetup::new();
let code = test.snippet("0 to (0, 0)");
let mut diagnostics = Vec::new();
assert_eq!(
test.range_type(&code, &mut diagnostics),
Err(EvalError::Unknown)
);
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("(0, 0)"),
"Non-scalar expression cannot be used in a range",
ErrorCode::NonScalarInRange,
)],
)
}
#[test]
fn range_one_side_ambiguous() {
let test = TestSetup::new();
test.declarative_part(
"
function f1 return character;
function f1 return integer;
",
);
let code = test.snippet("f1 to 'a'");
let mut rng = code.range();
assert_eq!(
test.range_type_ast(&mut rng, &code.tokenize(), &mut NoDiagnostics),
Ok(test.lookup_type("CHARACTER").base())
);
check_no_unresolved(&mut rng, &code.tokenize());
let code = test.snippet("'a' to f1");
let mut rng = code.range();
assert_eq!(
test.range_type_ast(&mut rng, &code.tokenize(), &mut NoDiagnostics),
Ok(test.lookup_type("CHARACTER").base())
);
check_no_unresolved(&mut rng, &code.tokenize());
}
#[test]
fn range_type_mismatch_error() {
let test = TestSetup::new();
let code = test.snippet("0 to false");
let mut diagnostics = Vec::new();
assert_eq!(
test.range_type(&code, &mut diagnostics),
Err(EvalError::Unknown)
);
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("0 to false"),
"Range type mismatch, left is type universal_integer, right is type 'BOOLEAN'",
ErrorCode::TypeMismatch,
)],
);
}
#[test]
fn range_one_side_ambiguous_error() {
let test = TestSetup::new();
test.declarative_part(
"
function f1 return character;
function f1 return integer;
",
);
let code = test.snippet("f1 to false");
let mut diagnostics = Vec::new();
assert_eq!(
test.range_type(&code, &mut diagnostics),
Err(EvalError::Unknown)
);
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("f1 to false"),
"Range type of left and right side does not match",
ErrorCode::TypeMismatch,
)],
);
}
#[test]
fn range_ambiguous() {
let test = TestSetup::new();
test.declarative_part(
"
function f1 return character;
function f1 return integer;
",
);
let code = test.snippet("f1 to f1");
let mut diagnostics = Vec::new();
assert_eq!(
test.range_type(&code, &mut diagnostics),
Err(EvalError::Unknown)
);
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("f1 to f1"),
"Range is ambiguous",
ErrorCode::TypeMismatch,
)],
);
}
#[test]
fn range_attribute_name() {
let test = TestSetup::new();
test.declarative_part(
"
type arr_t is array (integer range <>) of boolean;
function myfun return arr_t;
",
);
let code = test.snippet("arr_t'range");
assert_eq!(
test.range_type(&code, &mut NoDiagnostics),
Ok(test.lookup_type("integer").base())
);
let code = test.snippet("myfun'range");
assert_eq!(
test.range_type(&code, &mut NoDiagnostics),
Ok(test.lookup_type("integer").base())
);
let mut diagnostics = Vec::new();
let code = test.snippet("character'range");
assert_eq!(
test.range_type(&code, &mut diagnostics),
Err(EvalError::Unknown)
);
check_diagnostics(
diagnostics,
vec![Diagnostic::mismatched_kinds(
code.s1("character"),
"type 'CHARACTER' cannot be prefix of range attribute, array type or object is required",
)],
);
}
#[test]
fn range_attribute_name_of_access_type() {
let test = TestSetup::new();
test.declarative_part(
"
type arr_t is array (integer range <>) of boolean;
type ptr_t is access arr_t;
variable v : ptr_t;
",
);
let code = test.snippet("v'range");
assert_eq!(
test.range_type(&code, &mut NoDiagnostics),
Ok(test.lookup_type("integer").base())
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,405 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::*;
use crate::data::*;
use crate::named_entity::*;
use crate::data::error_codes::ErrorCode;
use crate::TokenSpan;
use fnv::FnvHashMap;
use std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::rc::Rc;
use vhdl_lang::TokenAccess;
#[derive(Default, Clone)]
pub(crate) struct Scope<'a>(Rc<RefCell<ScopeInner<'a>>>);
#[derive(Default)]
struct ScopeInner<'a> {
parent: Option<Scope<'a>>,
region: Region<'a>,
cache: FnvHashMap<Designator, NamedEntities<'a>>,
anon_idx: usize,
}
#[derive(Debug)]
pub(crate) enum UndeclaredKind {
Identifier(Symbol),
Operator(Operator),
Character(u8),
Anonymous,
}
#[derive(Debug)]
pub(crate) enum LookupError {
IntoUnambiguousError(IntoUnambiguousError),
Undeclared(UndeclaredKind),
}
impl From<IntoUnambiguousError> for LookupError {
fn from(value: IntoUnambiguousError) -> Self {
Self::IntoUnambiguousError(value)
}
}
impl LookupError {
pub fn into_diagnostic(self, ctx: &dyn TokenAccess, span: impl Into<TokenSpan>) -> Diagnostic {
let span = span.into();
match self {
LookupError::IntoUnambiguousError(err) => err.into_diagnostic(ctx, span),
LookupError::Undeclared(kind) => {
let msg = match kind {
UndeclaredKind::Identifier(ident) => format!("No declaration of '{ident}'"),
UndeclaredKind::Operator(operator) => {
format!("No declaration of operator '{operator}'")
}
UndeclaredKind::Character(chr) => format!("No declaration of '{chr}'"),
UndeclaredKind::Anonymous => "No declaration of <anonymous>".to_owned(),
};
Diagnostic::new(span.pos(ctx), msg, ErrorCode::Unresolved)
}
}
}
}
impl<'a> ScopeInner<'a> {
pub fn into_region(self) -> Region<'a> {
self.region
}
pub fn into_visibility(self) -> Visibility<'a> {
self.region.visibility
}
pub fn close(&self, diagnostics: &mut dyn DiagnosticHandler) {
self.region.close(diagnostics)
}
pub fn add(&mut self, ent: EntRef<'a>, diagnostics: &mut dyn DiagnosticHandler) {
self.cache.remove(&ent.designator);
self.region.add(ent, diagnostics)
}
fn make_potentially_visible(
&mut self,
visible_pos: Option<&SrcPos>,
designator: Designator,
ent: EntRef<'a>,
) {
self.cache.remove(&ent.designator);
self.region
.visibility
.make_potentially_visible_with_name(visible_pos, designator, ent);
}
pub fn make_all_potentially_visible(
&mut self,
visible_pos: Option<&SrcPos>,
region: &'a Region<'a>,
) {
self.cache.clear();
self.region
.visibility
.make_all_potentially_visible(visible_pos, region);
}
/// Used when using context clauses
pub fn add_context_visibility(&mut self, visible_pos: Option<&SrcPos>, region: &Region<'a>) {
self.cache.clear();
// ignores parent but used only for contexts where this is true
self.region
.visibility
.add_context_visibility(visible_pos, &region.visibility);
}
pub fn lookup_immediate(&self, designator: &Designator) -> Option<&NamedEntities<'a>> {
self.region.lookup_immediate(designator)
}
/// Lookup a named entity declared in this region or an enclosing region
fn lookup_enclosing(&self, designator: &Designator) -> Option<NamedEntities<'a>> {
// We do not need to look in the enclosing region of the extended region
// since extended region always has the same parent except for protected types
// split into package / package body.
// In that case the package / package body parent of the protected type / body
// is the same extended region anyway
match self.lookup_immediate(designator).cloned() {
// A non-overloaded name is found in the immediate region
// no need to look further up
Some(NamedEntities::Single(single)) => Some(NamedEntities::Single(single)),
// The name is overloaded we must also check enclosing regions
Some(NamedEntities::Overloaded(immediate)) => {
if let Some(NamedEntities::Overloaded(enclosing)) = self
.parent
.as_ref()
.and_then(|region| region.0.borrow().lookup_enclosing(designator))
{
Some(NamedEntities::Overloaded(immediate.with_visible(enclosing)))
} else {
Some(NamedEntities::Overloaded(immediate))
}
}
None => self
.parent
.as_ref()
.and_then(|region| region.0.borrow().lookup_enclosing(designator)),
}
}
fn lookup_visiblity_into(&self, designator: &Designator, visible: &mut Visible<'a>) {
self.region.visibility.lookup_into(designator, visible);
if let Some(ref parent) = self.parent {
parent.0.borrow().lookup_visiblity_into(designator, visible);
}
}
/// Lookup a named entity that was made potentially visible via a use clause
fn lookup_visible(
&self,
designator: &Designator,
) -> Result<Option<NamedEntities<'a>>, LookupError> {
let mut visible = Visible::default();
self.lookup_visiblity_into(designator, &mut visible);
visible
.into_unambiguous(designator)
.map_err(|err| err.into())
}
/// Lookup a designator from within the region itself
/// Thus all parent regions and visibility is relevant
fn lookup_uncached(&self, designator: &Designator) -> Result<NamedEntities<'a>, LookupError> {
let result = if let Some(enclosing) = self.lookup_enclosing(designator) {
match enclosing {
// non overloaded in enclosing region ignores any visible overloaded names
NamedEntities::Single(..) => Some(enclosing),
// In case of overloaded local, non-conflicting visible names are still relevant
NamedEntities::Overloaded(enclosing_overloaded) => {
if let Ok(Some(NamedEntities::Overloaded(overloaded))) =
self.lookup_visible(designator)
{
Some(NamedEntities::Overloaded(
enclosing_overloaded.with_visible(overloaded),
))
} else {
Some(NamedEntities::Overloaded(enclosing_overloaded))
}
}
}
} else {
self.lookup_visible(designator)?
};
match result {
Some(visible) => Ok(visible),
None => Err(LookupError::Undeclared(match designator {
Designator::Identifier(ident) => UndeclaredKind::Identifier(ident.clone()),
Designator::OperatorSymbol(operator) => UndeclaredKind::Operator(*operator),
Designator::Character(chr) => UndeclaredKind::Character(*chr),
Designator::Anonymous(_) => UndeclaredKind::Anonymous,
})),
}
}
fn lookup(&mut self, designator: &Designator) -> Result<NamedEntities<'a>, LookupError> {
if let Some(res) = self.cache.get(designator) {
return Ok(res.clone());
}
let ents = self.lookup_uncached(designator)?;
if let Entry::Vacant(vacant) = self.cache.entry(designator.clone()) {
Ok(vacant.insert(ents).clone())
} else {
unreachable!("Cache miss cannot be followed by occupied entry")
}
}
}
impl<'a> Scope<'a> {
pub fn new(region: Region<'a>) -> Scope<'a> {
Self(Rc::new(RefCell::new(ScopeInner {
parent: None,
region,
cache: Default::default(),
anon_idx: 0,
})))
}
pub fn nested(&self) -> Scope<'a> {
Self(Rc::new(RefCell::new(ScopeInner {
region: Region::default(),
parent: Some(self.clone()),
cache: self.0.borrow().cache.clone(),
anon_idx: 0,
})))
}
pub fn with_parent(self, scope: &Scope<'a>) -> Scope<'a> {
Self(Rc::new(RefCell::new(ScopeInner {
parent: Some(scope.clone()),
region: self.into_inner().region,
cache: Default::default(),
anon_idx: 0,
})))
}
pub fn extend(region: &Region<'a>, parent: Option<&Scope<'a>>) -> Scope<'a> {
let kind = match region.kind {
RegionKind::PackageDeclaration => RegionKind::PackageBody,
_ => RegionKind::Other,
};
let extended_region = Region {
visibility: region.visibility.clone(),
entities: region.entities.clone(),
kind,
};
if let Some(parent) = parent {
Scope::new(extended_region).with_parent(parent)
} else {
Scope::new(extended_region)
}
}
pub fn in_package_declaration(self) -> Scope<'a> {
let inner = self.into_inner();
Self(Rc::new(RefCell::new(ScopeInner {
parent: inner.parent,
region: inner.region.in_package_declaration(),
cache: inner.cache,
anon_idx: inner.anon_idx,
})))
}
pub fn add(&self, ent: EntRef<'a>, diagnostics: &mut dyn DiagnosticHandler) {
self.0.as_ref().borrow_mut().add(ent, diagnostics);
}
pub fn make_potentially_visible(&self, visible_pos: Option<&SrcPos>, ent: EntRef<'a>) {
self.0.as_ref().borrow_mut().make_potentially_visible(
visible_pos,
ent.designator().clone(),
ent,
);
}
pub fn make_potentially_visible_with_name(
&self,
visible_pos: Option<&SrcPos>,
designator: Designator,
ent: EntRef<'a>,
) {
self.0
.as_ref()
.borrow_mut()
.make_potentially_visible(visible_pos, designator, ent);
}
pub fn make_all_potentially_visible(
&self,
visible_pos: Option<&SrcPos>,
region: &'a Region<'a>,
) {
self.0
.as_ref()
.borrow_mut()
.make_all_potentially_visible(visible_pos, region);
}
pub fn close(&self, diagnostics: &mut dyn DiagnosticHandler) {
self.0.as_ref().borrow().close(diagnostics)
}
fn into_inner(self) -> ScopeInner<'a> {
if let Ok(cell) = Rc::try_unwrap(self.0) {
cell.into_inner()
} else {
panic!("Expect no child regions");
}
}
pub fn into_region(self) -> Region<'a> {
self.into_inner().into_region()
}
pub fn into_visibility(self) -> Visibility<'a> {
self.into_inner().into_visibility()
}
pub fn lookup_immediate(&self, designator: &Designator) -> Option<NamedEntities<'a>> {
let inner = self.0.as_ref().borrow();
let names = inner.lookup_immediate(designator)?;
Some(names.clone())
}
pub fn lookup(&self, designator: &Designator) -> Result<NamedEntities<'a>, LookupError> {
self.0.as_ref().borrow_mut().lookup(designator)
}
/// Used when using context clauses
pub fn add_context_visibility(&self, visible_pos: Option<&SrcPos>, region: &Region<'a>) {
self.0
.as_ref()
.borrow_mut()
.add_context_visibility(visible_pos, region)
}
pub fn next_anonymous(&self) -> usize {
let mut inner = self.0.borrow_mut();
let idx = inner.anon_idx;
inner.anon_idx += 1;
idx
}
pub fn anonymous_designator(&self) -> Designator {
Designator::Anonymous(self.next_anonymous())
}
}
impl<'a> NamedEntities<'a> {
pub(crate) fn make_potentially_visible_in(
&self,
visible_pos: Option<&SrcPos>,
scope: &Scope<'a>,
) {
match self {
Self::Single(ent) => {
scope.make_potentially_visible(visible_pos, ent);
}
Self::Overloaded(overloaded) => {
for ent in overloaded.entities() {
scope.make_potentially_visible(visible_pos, ent.into());
}
}
}
}
}
impl Diagnostic {
pub(crate) fn duplicate_error(
name: &impl std::fmt::Display,
pos: &SrcPos,
prev_pos: Option<&SrcPos>,
) -> Diagnostic {
let mut diagnostic = Diagnostic::new(
pos,
format!("Duplicate declaration of '{name}'"),
ErrorCode::Duplicate,
);
if let Some(prev_pos) = prev_pos {
diagnostic.add_related(prev_pos, "Previously defined here");
}
diagnostic
}
}

View File

@ -0,0 +1,253 @@
//! This Source Code Form is subject to the terms of the Mozilla Public
//! License, v. 2.0. If a copy of the MPL was not distributed with this file,
//! You can obtain one at http://mozilla.org/MPL/2.0/.
//!
//! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use super::analyze::*;
use super::names::ResolvedName;
use super::overloaded::Disambiguated;
use super::overloaded::SubprogramKind;
use super::scope::*;
use crate::ast::token_range::WithTokenSpan;
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::*;
impl<'a, 't> AnalyzeContext<'a, 't> {
pub fn choice_with_ttyp(
&self,
scope: &Scope<'a>,
ttyp: Option<TypeEnt<'a>>,
choices: &mut [WithTokenSpan<Choice>],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
for choice in choices.iter_mut() {
match choice.item {
Choice::Expression(ref mut expr) => {
if let Some(ttyp) = ttyp {
self.expr_pos_with_ttyp(scope, ttyp, choice.span, expr, diagnostics)?;
} else {
self.expr_pos_unknown_ttyp(scope, choice.span, expr, diagnostics)?;
}
}
Choice::DiscreteRange(ref mut drange) => {
if let Some(ttyp) = ttyp {
self.drange_with_ttyp(scope, ttyp, drange, diagnostics)?;
} else {
self.drange_unknown_type(scope, drange, diagnostics)?;
}
}
Choice::Others => {}
}
}
Ok(())
}
pub fn analyze_assoc_elems(
&self,
scope: &Scope<'a>,
elems: &mut [AssociationElement],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
for AssociationElement { actual, .. } in elems.iter_mut() {
match actual.item {
ActualPart::Expression(ref mut expr) => {
self.expr_pos_unknown_ttyp(scope, actual.span, expr, diagnostics)?;
}
ActualPart::Open => {}
}
}
Ok(())
}
pub fn analyze_procedure_call(
&self,
scope: &Scope<'a>,
fcall: &mut WithTokenSpan<CallOrIndexed>,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let fcall_span = fcall.span;
let CallOrIndexed { name, parameters } = &mut fcall.item;
let resolved =
match as_fatal(self.name_resolve(scope, name.span, &mut name.item, diagnostics))? {
Some(resolved) => resolved,
None => {
// Continue checking missing names even if procedure is not found
self.analyze_assoc_elems(scope, &mut parameters.items, diagnostics)?;
return Ok(());
}
};
match resolved {
ResolvedName::Overloaded(ref des, names) => {
match as_fatal(self.disambiguate(
scope,
&fcall_span.pos(self.ctx),
des,
&mut parameters.items,
SubprogramKind::Procedure,
names.entities().collect(),
diagnostics,
))? {
Some(Disambiguated::Ambiguous(candidates)) => {
diagnostics.push(Diagnostic::ambiguous_call(self.ctx, des, candidates))
}
Some(Disambiguated::Unambiguous(ent)) => {
name.set_unique_reference(&ent);
if !ent.is_procedure() {
let mut diagnostic = Diagnostic::new(
name.pos(self.ctx),
"Invalid procedure call",
ErrorCode::InvalidCall,
);
for ent in names.sorted_entities() {
if let Some(decl_pos) = ent.decl_pos() {
diagnostic.add_related(
decl_pos,
format!("{} is not a procedure", ent.describe()),
);
}
}
diagnostics.push(diagnostic);
} else if ent.is_uninst_subprogram_body() {
diagnostics.add(
name.pos(self.ctx),
format!("uninstantiated {} cannot be called", ent.describe()),
ErrorCode::InvalidCall,
)
}
}
None => {}
}
}
ResolvedName::Final(ent) => {
if let AnyEntKind::Component(region) = ent.kind() {
name.set_unique_reference(ent);
let (generic_region, port_region) = region.to_entity_formal();
self.check_association(
&fcall.item.name.pos(self.ctx),
&generic_region,
scope,
&mut [],
diagnostics,
)?;
self.check_association(
&fcall.item.name.pos(self.ctx),
&port_region,
scope,
&mut [],
diagnostics,
)?;
} else {
diagnostics.add(
name.pos(self.ctx),
format!("{} is not a procedure", resolved.describe_type()),
ErrorCode::MismatchedKinds,
);
self.analyze_assoc_elems(scope, &mut parameters.items, diagnostics)?;
}
}
resolved => {
diagnostics.add(
name.pos(self.ctx),
format!("{} is not a procedure", resolved.describe_type()),
ErrorCode::MismatchedKinds,
);
self.analyze_assoc_elems(scope, &mut parameters.items, diagnostics)?;
}
};
Ok(())
}
}
impl Diagnostic {
pub fn add_subprogram_candidates<'a>(
&mut self,
prefix: &str,
candidates: impl IntoIterator<Item = OverloadedEnt<'a>>,
) {
let mut candidates: Vec<_> = candidates.into_iter().collect();
candidates.sort_by(|x, y| x.decl_pos().cmp(&y.decl_pos()));
for ent in candidates {
if let Some(decl_pos) = ent.decl_pos() {
self.add_related(decl_pos, format!("{} {}", prefix, ent.describe()))
}
}
}
pub fn add_type_candididates<'a>(
&mut self,
prefix: &str,
candidates: impl IntoIterator<Item = BaseType<'a>>,
) {
let mut candidates: Vec<_> = candidates.into_iter().collect();
candidates.sort_by(|x, y| x.decl_pos().cmp(&y.decl_pos()));
for ent in candidates {
if let Some(decl_pos) = ent.decl_pos() {
self.add_related(decl_pos, format!("{} {}", prefix, ent.describe()))
}
}
}
}
impl<'a> ResolvedName<'a> {
pub(super) fn kind_error(&self, pos: impl AsRef<SrcPos>, expected: &str) -> Diagnostic {
let mut error = Diagnostic::mismatched_kinds(
pos,
format!("Expected {expected}, got {}", self.describe()),
);
if let Some(decl_pos) = self.decl_pos() {
error.add_related(decl_pos, "Defined here");
}
error
}
}
impl Diagnostic {
pub(crate) fn type_mismatch(
pos: &SrcPos,
desc: &str,
expected_type: TypeEnt<'_>,
) -> Diagnostic {
Diagnostic::new(
pos,
format!("{} does not match {}", desc, expected_type.describe(),),
ErrorCode::TypeMismatch,
)
}
pub(crate) fn invalid_selected_name_prefix(
named_entity: EntRef<'_>,
prefix: &SrcPos,
) -> Diagnostic {
Diagnostic::mismatched_kinds(
prefix,
capitalize(&format!(
"{} may not be the prefix of a selected name",
named_entity.describe(),
)),
)
}
pub(crate) fn no_declaration_within(
named_entity: EntRef<'_>,
pos: &SrcPos,
suffix: &Designator,
) -> Diagnostic {
Diagnostic::new(
pos,
format!(
"No declaration of '{}' within {}",
suffix,
named_entity.describe(),
),
ErrorCode::Unresolved,
)
}
}

View File

@ -0,0 +1,445 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::*;
use crate::HasTokenSpan;
use analyze::*;
use target::AssignmentType;
impl<'a, 't> AnalyzeContext<'a, 't> {
pub fn define_labels_for_sequential_part(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
statements: &mut [LabeledSequentialStatement],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
for statement in statements.iter_mut() {
let span = statement.span();
let parent = if let Some(ref mut label) = statement.label.tree {
let ent = self.arena.explicit(
label.name(),
parent,
AnyEntKind::Sequential(statement.statement.item.label_typ()),
Some(label.pos(self.ctx)),
span,
Some(self.source()),
);
statement.label.decl.set(ent.id());
scope.add(ent, diagnostics);
ent
} else if statement.statement.item.can_have_label() {
// Generate an anonymous label if it is not explicitly defined
let ent = self.arena.alloc(
scope.anonymous_designator(),
Some(parent),
Related::None,
AnyEntKind::Sequential(statement.statement.item.label_typ()),
None,
span,
Some(self.source()),
);
statement.label.decl.set(ent.id());
ent
} else {
parent
};
match statement.statement.item {
SequentialStatement::If(ref mut ifstmt) => {
let Conditionals {
conditionals,
else_item,
} = &mut ifstmt.conds;
for conditional in conditionals {
self.define_labels_for_sequential_part(
scope,
parent,
&mut conditional.item,
diagnostics,
)?;
}
if let Some((else_item, _)) = else_item {
self.define_labels_for_sequential_part(
scope,
parent,
else_item,
diagnostics,
)?;
}
}
SequentialStatement::Case(ref mut case_stmt) => {
for alternative in case_stmt.alternatives.iter_mut() {
self.define_labels_for_sequential_part(
scope,
parent,
&mut alternative.item,
diagnostics,
)?;
}
}
SequentialStatement::Loop(ref mut loop_stmt) => {
self.define_labels_for_sequential_part(
scope,
parent,
&mut loop_stmt.statements,
diagnostics,
)?;
}
_ => {
// Does not have sequential part
}
}
}
Ok(())
}
fn analyze_sequential_statement(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
statement: &mut LabeledSequentialStatement,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let statement_span = statement.statement.span;
match statement.statement.item {
SequentialStatement::Return(ref mut ret) => {
let ReturnStatement { ref mut expression } = ret;
match SequentialRoot::from(parent) {
SequentialRoot::Function(ttyp) => {
if let Some(ref mut expression) = expression {
self.expr_with_ttyp(scope, ttyp, expression, diagnostics)?;
} else {
diagnostics.add(
statement.statement.pos(self.ctx),
"Functions cannot return without a value",
ErrorCode::VoidReturn,
);
}
}
SequentialRoot::Procedure => {
if expression.is_some() {
diagnostics.add(
statement.statement.pos(self.ctx),
"Procedures cannot return a value",
ErrorCode::NonVoidReturn,
);
}
}
SequentialRoot::Process => {
diagnostics.add(
statement.statement.pos(self.ctx),
"Cannot return from a process",
ErrorCode::IllegalReturn,
);
}
}
}
SequentialStatement::Wait(ref mut wait_stmt) => {
let WaitStatement {
sensitivity_clause,
condition_clause,
timeout_clause,
} = wait_stmt;
if let Some(list) = sensitivity_clause {
self.sensitivity_list_check(scope, list, diagnostics)?;
}
if let Some(expr) = condition_clause {
self.boolean_expr(scope, expr, diagnostics)?;
}
if let Some(expr) = timeout_clause {
self.expr_with_ttyp(scope, self.time(), expr, diagnostics)?;
}
}
SequentialStatement::Assert(ref mut assert_stmt) => {
let AssertStatement {
condition,
report,
severity,
} = assert_stmt;
self.boolean_expr(scope, condition, diagnostics)?;
if let Some(expr) = report {
self.expr_with_ttyp(scope, self.string(), expr, diagnostics)?;
}
if let Some(expr) = severity {
self.expr_with_ttyp(scope, self.severity_level(), expr, diagnostics)?;
}
}
SequentialStatement::Report(ref mut report_stmt) => {
let ReportStatement { report, severity } = report_stmt;
self.expr_with_ttyp(scope, self.string(), report, diagnostics)?;
if let Some(expr) = severity {
self.expr_with_ttyp(scope, self.severity_level(), expr, diagnostics)?;
}
}
SequentialStatement::Exit(ref mut exit_stmt) => {
let ExitStatement {
condition,
loop_label,
} = exit_stmt;
if let Some(loop_label) = loop_label {
self.check_loop_label(scope, parent, loop_label, diagnostics);
} else if !find_outer_loop(parent, None) {
diagnostics.add(
statement_span.pos(self.ctx),
"Exit can only be used inside a loop",
ErrorCode::ExitOutsideLoop,
)
}
if let Some(expr) = condition {
self.boolean_expr(scope, expr, diagnostics)?;
}
}
SequentialStatement::Next(ref mut next_stmt) => {
let NextStatement {
condition,
loop_label,
} = next_stmt;
if let Some(loop_label) = loop_label {
self.check_loop_label(scope, parent, loop_label, diagnostics);
} else if !find_outer_loop(parent, None) {
diagnostics.add(
statement_span.pos(self.ctx),
"Next can only be used inside a loop",
ErrorCode::NextOutsideLoop,
)
}
if let Some(expr) = condition {
self.boolean_expr(scope, expr, diagnostics)?;
}
}
SequentialStatement::If(ref mut ifstmt) => {
let Conditionals {
conditionals,
else_item,
} = &mut ifstmt.conds;
// @TODO write generic function for this
for conditional in conditionals {
let Conditional { condition, item } = conditional;
self.boolean_expr(scope, condition, diagnostics)?;
self.analyze_sequential_part(scope, parent, item, diagnostics)?;
}
if let Some((else_item, _)) = else_item {
self.analyze_sequential_part(scope, parent, else_item, diagnostics)?;
}
}
SequentialStatement::Case(ref mut case_stmt) => {
let CaseStatement {
expression,
alternatives,
..
} = case_stmt;
let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?;
for alternative in alternatives.iter_mut() {
let Alternative {
choices,
item,
span: _,
} = alternative;
self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?;
self.analyze_sequential_part(scope, parent, item, diagnostics)?;
}
}
SequentialStatement::Loop(ref mut loop_stmt) => {
let LoopStatement {
iteration_scheme,
statements,
..
} = loop_stmt;
match iteration_scheme {
Some(IterationScheme::For(ref mut index, ref mut drange)) => {
let typ = as_fatal(self.drange_type(scope, drange, diagnostics))?;
let region = scope.nested();
region.add(
self.define(
index,
parent,
AnyEntKind::LoopParameter(typ),
index.tree.token.into(),
),
diagnostics,
);
self.analyze_sequential_part(&region, parent, statements, diagnostics)?;
}
Some(IterationScheme::While(ref mut expr)) => {
self.boolean_expr(scope, expr, diagnostics)?;
self.analyze_sequential_part(scope, parent, statements, diagnostics)?;
}
None => {
self.analyze_sequential_part(scope, parent, statements, diagnostics)?;
}
}
}
SequentialStatement::ProcedureCall(ref mut pcall) => {
self.analyze_procedure_call(scope, pcall, diagnostics)?;
}
SequentialStatement::SignalAssignment(ref mut assign) => {
self.analyze_waveform_assignment(scope, assign, diagnostics)?;
}
SequentialStatement::VariableAssignment(ref mut assign) => {
let VariableAssignment { target, rhs } = assign;
self.analyze_expr_assignment(
scope,
target,
AssignmentType::Variable,
rhs,
diagnostics,
)?;
}
SequentialStatement::SignalForceAssignment(ref mut assign) => {
let SignalForceAssignment {
target,
force_mode: _,
rhs,
} = assign;
self.analyze_expr_assignment(
scope,
target,
AssignmentType::Signal,
rhs,
diagnostics,
)?;
}
SequentialStatement::SignalReleaseAssignment(ref mut assign) => {
let SignalReleaseAssignment {
target,
force_mode: _,
span: _,
} = assign;
as_fatal(self.resolve_target(scope, target, AssignmentType::Signal, diagnostics))?;
}
SequentialStatement::Null => {}
}
Ok(())
}
fn check_loop_label(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
label: &mut WithRef<Ident>,
diagnostics: &mut dyn DiagnosticHandler,
) {
match scope.lookup(&Designator::Identifier(label.item.item.clone())) {
Ok(NamedEntities::Single(ent)) => {
label.set_unique_reference(ent);
if matches!(ent.kind(), AnyEntKind::Sequential(Some(Sequential::Loop))) {
if !find_outer_loop(parent, Some(label.item.name())) {
diagnostics.add(
label.item.pos(self.ctx),
format!("Cannot be used outside of loop '{}'", ent.designator()),
ErrorCode::InvalidLoopLabel,
);
}
} else {
diagnostics.add(
label.item.pos(self.ctx),
format!("Expected loop label, got {}", ent.describe()),
ErrorCode::MismatchedKinds,
);
}
}
Ok(NamedEntities::Overloaded(_)) => diagnostics.add(
label.item.pos(self.ctx),
format!(
"Expected loop label, got overloaded name {}",
&label.item.item
),
ErrorCode::MismatchedKinds,
),
Err(diag) => {
diagnostics.push(diag.into_diagnostic(self.ctx, label.item.token));
}
}
}
pub fn analyze_sequential_part(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
statements: &mut [LabeledSequentialStatement],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
for statement in statements.iter_mut() {
let parent = if let Some(id) = statement.label.decl.get() {
self.arena.get(id)
} else {
parent
};
self.analyze_sequential_statement(scope, parent, statement, diagnostics)?;
}
Ok(())
}
}
enum SequentialRoot<'a> {
Process,
Procedure,
Function(TypeEnt<'a>),
}
fn find_outer_loop(ent: EntRef<'_>, label: Option<&Symbol>) -> bool {
match ent.kind() {
AnyEntKind::Sequential(Some(Sequential::Loop)) => {
if let Some(label) = label {
if matches!(ent.designator(), Designator::Identifier(ident) if ident == label) {
return true;
}
} else {
return true;
}
}
AnyEntKind::Sequential(_) => {}
_ => {
return false;
}
}
if let Some(parent) = ent.parent {
find_outer_loop(parent, label)
} else {
false
}
}
impl<'a> From<EntRef<'a>> for SequentialRoot<'a> {
fn from(value: EntRef<'a>) -> Self {
match value.kind() {
AnyEntKind::Overloaded(overloaded) => {
if let Some(return_type) = overloaded.signature().return_type() {
SequentialRoot::Function(return_type)
} else {
SequentialRoot::Procedure
}
}
AnyEntKind::Sequential(_) => {
if let Some(parent) = value.parent {
SequentialRoot::from(parent)
} else {
// A sequential statement must always have a parent this should never happen
SequentialRoot::Process
}
}
AnyEntKind::Concurrent(Some(Concurrent::Process)) => SequentialRoot::Process,
_ => SequentialRoot::Process,
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,481 @@
use crate::analysis::static_expression::BitStringConversionError::EmptySignedExpansion;
use crate::ast::{BaseSpecifier, BitString};
use crate::Latin1String;
use itertools::Itertools;
use std::cmp::Ordering;
use std::iter;
/// returns whether `byte` is an odd number when interpreted as decimal.
/// byte must be between '0' and '9', but it is up to the caller to enforce this.
fn byte_is_odd_decimal(byte: u8) -> bool {
(byte - b'0') % 2 == 1
}
/// Converts a decimal string (i.e. "123") to a binary string (i.e. "1111011").
///
/// When there are illegal characters in the string (i.e. non-decimal characters),
/// returns an `Err` with the position of the first character.
///
/// # Special cases
/// - For an empty string, return a single string containing '0'
/// - For a string with zeros, return a single string containing '0'
/// - For a string that is padded with zeros, return a string without the padding. If the
/// String without the padding is empty, rule 1 applies.
pub(crate) fn decimal_str_to_binary_str(
value: &[u8],
) -> Result<Latin1String, BitStringConversionError> {
/// Divides `value` by two where `value` is a vector of u8's representing decimals.
/// Returns an empty string when `value` is zero
fn str_divide_by_2(value: Vec<u8>) -> Vec<u8> {
let mut new_s: Vec<u8> = Vec::new();
let mut add_next = 0;
for ch in value {
let new_digit = (ch + b'0') / 2 + add_next;
new_s.push(new_digit);
add_next = if byte_is_odd_decimal(ch) { 5 } else { 0 };
}
// remove the first element if it's '0'
if new_s.first() == Some(&b'0') {
new_s.drain(..1);
}
new_s
}
if let Some(idx) = value.iter().position(|b| *b < b'0' || *b > b'9') {
return Err(BitStringConversionError::IllegalDecimalCharacter(idx));
}
let mut num: Vec<u8> = value.iter().copied().skip_while(|el| *el == b'0').collect();
if num.is_empty() {
return Ok(Latin1String::new(b"0"));
}
let mut stack: Vec<u8> = Vec::new();
while !num.is_empty() {
if byte_is_odd_decimal(*num.last().unwrap()) {
stack.push(b'1');
} else {
stack.push(b'0');
}
num = str_divide_by_2(num);
}
stack.reverse();
Ok(Latin1String::from_vec(stack))
}
#[test]
fn test_decimal_to_binary() {
let test_cases = [
("", "0"),
("0", "0"),
("000", "0"),
("001", "1"),
("1", "1"),
("12345", "11000000111001"),
(
"123456781234567812345678",
"11010001001001001101100000011011011101100011101100101101101011110111101001110",
),
];
for (dec, bin) in test_cases {
assert_eq!(
decimal_str_to_binary_str(dec.as_bytes()),
Ok(Latin1String::from_utf8_unchecked(bin))
);
}
}
impl BaseSpecifier {
/// Returns whether this base specifier represents a signed value
/// (i.e. `SX` for signed hexadecimal) or an unsigned value
/// (i.e. `UX` or `X` for unsigned hexadecimal)
pub fn is_signed(&self) -> bool {
match self {
BaseSpecifier::SX | BaseSpecifier::SO | BaseSpecifier::SB => true,
BaseSpecifier::B
| BaseSpecifier::UB
| BaseSpecifier::O
| BaseSpecifier::UO
| BaseSpecifier::X
| BaseSpecifier::UX
| BaseSpecifier::D => false,
}
}
/// Get the digits that are obtained by replacing `byte` with the
/// appropriate sequence of characters as defined in the standard (section 15.8).
///
/// # Special Cases
/// If the base specifier is `D`, i.e. decimal, return the byte itself (wrapped as array)
///
/// # Example
/// ```
/// use vhdl_lang::ast::BaseSpecifier;
///
/// let digits: Vec<u8> = BaseSpecifier::UX.get_extended_digits(b'C');
/// assert_eq!(digits, Vec::from("1100"));
///
/// let digits: Vec<u8> = BaseSpecifier::O.get_extended_digits(b'F');
/// assert_eq!(digits, Vec::from("FFF"))
/// ```
pub fn get_extended_digits(&self, byte: u8) -> Vec<u8> {
match self {
// For O, UO and SO, the values 1-7 are replaced.
// All other values are left as-is.
BaseSpecifier::O | BaseSpecifier::UO | BaseSpecifier::SO => match byte {
b'0' => Vec::from("000"),
b'1' => Vec::from("001"),
b'2' => Vec::from("010"),
b'3' => Vec::from("011"),
b'4' => Vec::from("100"),
b'5' => Vec::from("101"),
b'6' => Vec::from("110"),
b'7' => Vec::from("111"),
_ => vec![byte; 3],
},
// For U, UX and SX, the values 1-9 and A-F are replaced.
// All other values are left as-is.
BaseSpecifier::X | BaseSpecifier::UX | BaseSpecifier::SX => match byte {
b'0' => Vec::from("0000"),
b'1' => Vec::from("0001"),
b'2' => Vec::from("0010"),
b'3' => Vec::from("0011"),
b'4' => Vec::from("0100"),
b'5' => Vec::from("0101"),
b'6' => Vec::from("0110"),
b'7' => Vec::from("0111"),
b'8' => Vec::from("1000"),
b'9' => Vec::from("1001"),
b'A' | b'a' => Vec::from("1010"),
b'B' | b'b' => Vec::from("1011"),
b'C' | b'c' => Vec::from("1100"),
b'D' | b'd' => Vec::from("1101"),
b'E' | b'e' => Vec::from("1110"),
b'F' | b'f' => Vec::from("1111"),
_ => vec![byte; 4],
},
// Binary values are simply the values left as they are.
BaseSpecifier::B | BaseSpecifier::UB | BaseSpecifier::SB | BaseSpecifier::D => {
vec![byte]
}
}
}
}
/// Represents errors that occur when converting a bit string to a regular string
#[derive(PartialEq, Eq, Clone, Debug)]
pub(crate) enum BitStringConversionError {
/// Illegal decimal character encountered while converting a decimal bit string (i.e. D"12AFFE")
/// The `usize` argument represent the position for the first illegal character in the
/// bit_string's `value` string, (i.e. 2 for the example above)
IllegalDecimalCharacter(usize),
/// Signals that when converting a value and truncating, information would be lost.
/// # Example
/// 5B"111111" => The first '0' would be lost
/// 8SX"0FF" => The bit-string is positive but would be converted to a negative value
/// The `usize` argument is the index of the first character that cannot be truncated.
/// The `Latin1String` argument is the expanded (erroneous) String
IllegalTruncate(usize, Latin1String),
/// Trying to expand an empty signed expression, i.e.
/// SX""
EmptySignedExpansion,
}
/// Converts a `BitString` to a `Latin1String` respecting the replacement values defined in LRM
/// Returns the string as Latin1String when successful and a `BitStringConversionError` else
/// 15.8 Bit string literals
pub(crate) fn bit_string_to_string(
bit_string: &BitString,
) -> Result<Latin1String, BitStringConversionError> {
// Simplifies the bit string by removing all occurrences of the underscore
// character
let simplified_value: Vec<u8> = bit_string
.value
.bytes
.clone()
.into_iter()
.filter(|&b| b != b'_')
.collect();
// For empty signed bit-strings it is unclear what the expanded value should be,
// according to the reference:
// For example, 2SB"" could be
// 1) A string containing '0's, i.e. "00"
// 2) An error
// According to the standard, the padding value should be the leftmost character in the string
// but an empty string does not have a leftmost character.
if simplified_value.is_empty() {
return match bit_string.length {
None => Ok(Latin1String::empty()),
Some(value) => {
if bit_string.base.is_signed() {
Err(EmptySignedExpansion)
} else {
Ok(Latin1String::from_vec(
iter::repeat(b'0').take(value as usize).collect_vec(),
))
}
}
};
}
let mut extended_value = Vec::new();
if bit_string.base == BaseSpecifier::D {
match decimal_str_to_binary_str(&simplified_value) {
Err(e) => return Err(e),
Ok(binary_string) => extended_value = binary_string.bytes,
}
} else {
for ch in simplified_value {
extended_value.append(&mut bit_string.base.get_extended_digits(ch));
}
}
// append, truncate or leave the bit-string dependent on the user-specified length
match bit_string.length {
None => Ok(Latin1String::from_vec(extended_value)),
Some(length) => {
let length = length as usize;
match length.cmp(&extended_value.len()) {
Ordering::Equal => Ok(Latin1String::from_vec(extended_value)),
Ordering::Less => {
let pivot = extended_value.len() - length;
let first_elements = &extended_value[..pivot];
let last_elements = &extended_value[pivot..];
// This char is allowed and may be truncated from the vector
let allowed_char = if bit_string.base.is_signed() {
last_elements[0]
} else {
b'0'
};
let idx = first_elements
.iter()
.rev()
.position(|el| *el != allowed_char);
match idx {
Some(value) => {
let real_idx = last_elements.len() + value - 1;
let erroneous_string = Latin1String::from_vec(extended_value);
Err(BitStringConversionError::IllegalTruncate(
real_idx,
erroneous_string,
))
}
None => Ok(Latin1String::new(last_elements)),
}
}
Ordering::Greater => {
let pad_char = if bit_string.base.is_signed() {
extended_value[0]
} else {
b'0'
};
let pad_vector = iter::repeat(pad_char)
.take(length - extended_value.len())
.chain(extended_value)
.collect_vec();
Ok(Latin1String::from_vec(pad_vector))
}
}
}
}
}
#[cfg(test)]
mod test_mod {
use crate::analysis::static_expression::{bit_string_to_string, BitStringConversionError};
use crate::ast::{BaseSpecifier, BitString};
use crate::Latin1String;
impl BitString {
fn new(length: Option<u32>, base: BaseSpecifier, value: &str) -> BitString {
BitString {
length,
base,
value: Latin1String::from_utf8_unchecked(value),
}
}
}
#[test]
fn an_empty_bit_string_converts_to_an_empty_string() {
let all_base_specifiers = [
BaseSpecifier::O,
BaseSpecifier::UO,
BaseSpecifier::SO,
BaseSpecifier::X,
BaseSpecifier::UX,
BaseSpecifier::SX,
BaseSpecifier::B,
BaseSpecifier::UB,
BaseSpecifier::SB,
BaseSpecifier::D,
];
for base_specifier in all_base_specifiers {
assert_eq!(
bit_string_to_string(&BitString::new(None, base_specifier, "")).unwrap(),
Latin1String::empty()
)
}
}
#[test]
fn test_illegal_decimal_character() {
assert_eq!(
bit_string_to_string(&BitString::new(None, BaseSpecifier::D, "12AFFE")),
Err(BitStringConversionError::IllegalDecimalCharacter(2))
);
assert_eq!(
bit_string_to_string(&BitString::new(None, BaseSpecifier::D, "?")),
Err(BitStringConversionError::IllegalDecimalCharacter(0))
);
assert_eq!(
bit_string_to_string(&BitString::new(None, BaseSpecifier::D, "78234+")),
Err(BitStringConversionError::IllegalDecimalCharacter(5))
);
}
#[test]
fn test_decimal_conversion() {
let test_cases = [
(BitString::new(None, BaseSpecifier::D, ""), ""),
(BitString::new(None, BaseSpecifier::D, "0"), "0"),
(BitString::new(None, BaseSpecifier::D, "00"), "0"),
(BitString::new(None, BaseSpecifier::D, "000"), "0"),
(BitString::new(None, BaseSpecifier::D, "1"), "1"),
(BitString::new(None, BaseSpecifier::D, "01"), "1"),
(BitString::new(None, BaseSpecifier::D, "10"), "1010"),
(
BitString::new(None, BaseSpecifier::D, "164824"),
"101000001111011000",
),
(
BitString::new(None, BaseSpecifier::D, "123456781234567812345678"),
"11010001001001001101100000011011011101100011101100101101101011110111101001110",
),
];
for (bit_string, result_string) in test_cases {
assert_eq!(
bit_string_to_string(&bit_string).unwrap(),
Latin1String::from_utf8_unchecked(result_string)
)
}
}
#[test]
fn test_illegal_truncate_position() {
assert_eq!(
bit_string_to_string(&BitString::new(Some(8), BaseSpecifier::SX, "0FF")),
Err(BitStringConversionError::IllegalTruncate(
7,
Latin1String::new(b"000011111111")
))
);
assert_eq!(
bit_string_to_string(&BitString::new(Some(8), BaseSpecifier::SX, "1FF")),
Err(BitStringConversionError::IllegalTruncate(
8,
Latin1String::new(b"000111111111")
))
);
assert_eq!(
bit_string_to_string(&BitString::new(Some(8), BaseSpecifier::SX, "3FF")),
Err(BitStringConversionError::IllegalTruncate(
9,
Latin1String::new(b"001111111111")
))
);
}
// Examples defined in 15.8
#[test]
fn spec_examples() {
let test_cases = [
(
BitString::new(None, BaseSpecifier::B, "1111_1111_1111"),
"111111111111",
),
(
BitString::new(None, BaseSpecifier::X, "FFF"),
"111111111111",
),
(BitString::new(None, BaseSpecifier::O, "777"), "111111111"),
(
BitString::new(None, BaseSpecifier::X, "777"),
"011101110111",
),
(
BitString::new(None, BaseSpecifier::B, "XXXX_01LH"),
"XXXX01LH",
),
(BitString::new(None, BaseSpecifier::UO, "27"), "010111"),
(BitString::new(None, BaseSpecifier::SX, "3W"), "0011WWWW"),
(BitString::new(None, BaseSpecifier::D, "35"), "100011"),
(
BitString::new(Some(12), BaseSpecifier::UB, "X1"),
"0000000000X1",
),
(
BitString::new(Some(12), BaseSpecifier::SB, "X1"),
"XXXXXXXXXXX1",
),
(
BitString::new(Some(12), BaseSpecifier::UX, "F-"),
"00001111----",
),
(
BitString::new(Some(12), BaseSpecifier::SX, "F-"),
"11111111----",
),
(
BitString::new(Some(12), BaseSpecifier::UX, "000WWW"),
"WWWWWWWWWWWW",
),
(
BitString::new(Some(12), BaseSpecifier::SX, "FFFC00"),
"110000000000",
),
];
let error_cases = [
BitString::new(Some(8), BaseSpecifier::D, "511"),
BitString::new(Some(8), BaseSpecifier::UO, "477"),
BitString::new(Some(8), BaseSpecifier::SX, "0FF"),
BitString::new(Some(8), BaseSpecifier::SX, "FXX"),
];
for bit_string in error_cases {
assert!(bit_string_to_string(&bit_string).err().is_some());
}
for (bit_string, result_string) in test_cases {
assert_eq!(
bit_string_to_string(&bit_string).unwrap(),
Latin1String::from_utf8_unchecked(result_string)
)
}
}
// Issue 332
#[test]
fn underscore_in_decimal_bit_string() {
assert!(
bit_string_to_string(&BitString::new(Some(32), BaseSpecifier::D, "1_000_000_000"))
.is_ok()
);
}
}

View File

@ -0,0 +1,497 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use super::names::*;
use super::*;
use crate::ast::token_range::WithTokenSpan;
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::{Signature, *};
use crate::{ast, HasTokenSpan};
use analyze::*;
use itertools::Itertools;
use vhdl_lang::TokenSpan;
impl<'a, 't> AnalyzeContext<'a, 't> {
fn subprogram_header(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
header: &mut SubprogramHeader,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult<Region<'a>> {
let mut region = Region::default();
for decl in header.generic_list.items.iter_mut() {
if let Some(ents) =
as_fatal(self.analyze_interface_declaration(scope, parent, decl, diagnostics))?
{
for ent in ents {
region.add(ent, diagnostics);
scope.add(ent, diagnostics);
}
}
}
self.analyze_map_aspect(scope, &mut header.map_aspect, diagnostics)?;
Ok(region)
}
pub(crate) fn subprogram_body(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
body: &mut SubprogramBody,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let (subpgm_region, subpgm_ent) = match as_fatal(self.subprogram_specification(
scope,
parent,
&mut body.specification,
body.span,
Overloaded::Subprogram,
diagnostics,
))? {
Some(r) => r,
None => {
return Ok(());
}
};
scope.add(subpgm_ent, diagnostics);
self.define_labels_for_sequential_part(
&subpgm_region,
subpgm_ent,
&mut body.statements,
diagnostics,
)?;
self.analyze_declarative_part(
&subpgm_region,
subpgm_ent,
&mut body.declarations,
diagnostics,
)?;
self.analyze_sequential_part(
&subpgm_region,
subpgm_ent,
&mut body.statements,
diagnostics,
)?;
Ok(())
}
pub(crate) fn subprogram_specification(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
subprogram: &mut SubprogramSpecification,
span: TokenSpan,
to_kind: impl Fn(Signature<'a>) -> Overloaded<'a>,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<(Scope<'a>, EntRef<'a>)> {
let subpgm_region = scope.nested();
let ent = self.arena.explicit(
subprogram
.subpgm_designator()
.item
.clone()
.into_designator(),
parent,
AnyEntKind::Overloaded(to_kind(Signature::new(FormalRegion::new_params(), None))),
Some(subprogram.subpgm_designator().pos(self.ctx)),
span,
Some(self.source()),
);
let (signature, generic_map) = match subprogram {
SubprogramSpecification::Function(fun) => {
let generic_map = if let Some(header) = &mut fun.header {
Some(self.subprogram_header(&subpgm_region, ent, header, diagnostics)?)
} else {
None
};
let params = if let Some(parameter_list) = &mut fun.parameter_list {
self.analyze_interface_list(&subpgm_region, ent, parameter_list, diagnostics)
} else {
Ok(FormalRegion::new_params())
};
let return_type = self.type_name(
&subpgm_region,
fun.return_type.span,
&mut fun.return_type.item,
diagnostics,
);
(Signature::new(params?, Some(return_type?)), generic_map)
}
SubprogramSpecification::Procedure(procedure) => {
let generic_map = if let Some(header) = &mut procedure.header {
Some(self.subprogram_header(&subpgm_region, ent, header, diagnostics)?)
} else {
None
};
let params = if let Some(parameter_list) = &mut procedure.parameter_list {
self.analyze_interface_list(&subpgm_region, ent, parameter_list, diagnostics)
} else {
Ok(FormalRegion::new_params())
};
(Signature::new(params?, None), generic_map)
}
};
let mut kind = to_kind(signature);
if let Some(map) = generic_map {
match kind {
Overloaded::SubprogramDecl(signature) => {
kind = Overloaded::UninstSubprogramDecl(signature, map)
}
Overloaded::Subprogram(signature) => {
kind = Overloaded::UninstSubprogram(signature, map)
}
_ => unreachable!(),
}
}
match kind {
Overloaded::Subprogram(_) => {
let declared_by =
self.find_subpgm_specification(scope, subprogram, kind.signature());
if let Some(declared_by) = declared_by {
unsafe {
ent.set_declared_by(declared_by.into());
}
}
}
Overloaded::UninstSubprogram(_, _) => {
let declared_by =
self.find_uninst_subpgm_specification(scope, subprogram, kind.signature());
if let Some(declared_by) = declared_by {
unsafe {
ent.set_declared_by(declared_by.into());
}
}
}
_ => {}
}
unsafe {
ent.set_kind(AnyEntKind::Overloaded(kind));
}
subprogram.set_decl_id(ent.id());
Ok((subpgm_region, ent))
}
pub fn resolve_signature(
&self,
scope: &Scope<'a>,
signature: &mut WithTokenSpan<ast::Signature>,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<SignatureKey<'_>> {
let (args, return_type) = match &mut signature.item {
ast::Signature::Function(ref mut args, ref mut ret) => {
let args: Vec<_> = args
.iter_mut()
.map(|arg| self.type_name(scope, arg.span, &mut arg.item, diagnostics))
.collect();
let return_type = self.type_name(scope, ret.span, &mut ret.item, diagnostics);
(args, Some(return_type))
}
ast::Signature::Procedure(args) => {
let args: Vec<_> = args
.iter_mut()
.map(|arg| self.type_name(scope, arg.span, &mut arg.item, diagnostics))
.collect();
(args, None)
}
};
let mut params = Vec::with_capacity(args.len());
for arg in args {
params.push(arg?.base());
}
if let Some(return_type) = return_type {
Ok(SignatureKey::new(
params,
Some(return_type?.base_type().base()),
))
} else {
Ok(SignatureKey::new(params, None))
}
}
/// Analyze a generic subprogram instance, i.e.,
/// ```vhdl
/// procedure my_proc is new my_proc generic map (T => std_logic);
/// ```
///
/// # Arguments
///
/// * `scope` - The scope that this instance was declared in
/// * `inst_subprogram_ent` - A reference to the instantiated subprogram entity.
/// Used to set the parent reference of the signature
/// * `uninst_name` - The [ResolvedName] of the uninstantiated subprogram
/// * `instance` - A reference to the AST element of the subprogram instantiation
/// * `diagnostics` - The diagnostics handler
///
/// # Returns
/// The signature after applying the optional map aspect of the uninstantiated subprogram
pub(crate) fn generic_subprogram_instance(
&self,
scope: &Scope<'a>,
inst_subprogram_ent: &EntRef<'a>,
uninst_name: &ResolvedName<'a>,
instance: &mut SubprogramInstantiation,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<Signature<'_>> {
let uninstantiated_subprogram =
self.resolve_uninstantiated_subprogram(scope, uninst_name, instance, diagnostics)?;
self.check_instantiated_subprogram_kind_matches_declared(
&uninstantiated_subprogram,
instance,
diagnostics,
);
instance
.subprogram_name
.item
.set_unique_reference(&uninstantiated_subprogram);
let region = match uninstantiated_subprogram.kind() {
Overloaded::UninstSubprogramDecl(_, region) => region,
Overloaded::UninstSubprogram(_, region) => region,
_ => unreachable!(),
};
let nested = scope.nested();
match as_fatal(self.generic_instance(
inst_subprogram_ent,
scope,
instance.ident.tree.pos(self.ctx),
region,
&mut instance.generic_map,
diagnostics,
))? {
None => Ok(uninstantiated_subprogram.signature().clone()),
Some((_, mapping)) => {
match self.map_signature(
Some(inst_subprogram_ent),
&mapping,
uninstantiated_subprogram.signature(),
&nested,
) {
Ok(signature) => Ok(signature),
Err((err, code)) => {
let mut diag =
Diagnostic::new(instance.ident.tree.pos(self.ctx), err, code);
if let Some(pos) = uninstantiated_subprogram.decl_pos() {
diag.add_related(pos, "When instantiating this declaration");
}
diagnostics.push(diag);
Err(EvalError::Unknown)
}
}
}
}
}
/// Given a `ResolvedName` and the subprogram instantiation,
/// find the uninstantiated subprogram that the resolved name references.
/// Return that resolved subprogram, if it exists, else return an `Err`
fn resolve_uninstantiated_subprogram(
&self,
scope: &Scope<'a>,
name: &ResolvedName<'a>,
instantiation: &mut SubprogramInstantiation,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<OverloadedEnt<'a>> {
let signature_key = match &mut instantiation.signature {
None => None,
Some(ref mut signature) => Some((
self.resolve_signature(scope, signature, diagnostics)?,
signature.pos(self.ctx),
)),
};
let overloaded_ent = match name {
ResolvedName::Overloaded(_, overloaded) => {
let choices = overloaded
.entities()
.filter(|ent| ent.is_uninst_subprogram())
.collect_vec();
if choices.is_empty() {
diagnostics.add(
instantiation.ident.tree.pos(self.ctx),
format!(
"{} does not denote an uninstantiated subprogram",
name.describe()
),
ErrorCode::MismatchedKinds,
);
return Err(EvalError::Unknown);
} else if choices.len() == 1 {
// There is only one possible candidate
let ent = choices[0];
// If the instantiated program has a signature, check that it matches
// that of the uninstantiated subprogram
if let Some((key, pos)) = signature_key {
match overloaded.get(&SubprogramKey::Uninstantiated(key)) {
None => {
diagnostics.add(
pos.clone(),
format!(
"Signature does not match the the signature of {}",
ent.describe()
),
ErrorCode::SignatureMismatch,
);
return Err(EvalError::Unknown);
}
Some(_) => ent,
}
} else {
ent
}
} else if let Some((key, _)) = signature_key {
// There are multiple candidates
// but there is a signature that we can try to resolve
if let Some(resolved_ent) =
overloaded.get(&SubprogramKey::Uninstantiated(key.clone()))
{
resolved_ent
} else {
diagnostics.add(
instantiation.subprogram_name.pos(self.ctx),
format!(
"No uninstantiated subprogram exists with signature {}",
key.describe()
),
ErrorCode::Unresolved,
);
return Err(EvalError::Unknown);
}
} else {
// There are multiple candidates
// and there is no signature to resolve
let mut err = Diagnostic::new(
instantiation.subprogram_name.pos(self.ctx),
format!("Ambiguous instantiation of '{}'", overloaded.designator()),
ErrorCode::AmbiguousInstantiation,
);
for ent in choices {
if let Some(pos) = &ent.decl_pos {
err.add_related(pos.clone(), format!("Might be {}", ent.describe()))
}
}
diagnostics.push(err);
return Err(EvalError::Unknown);
}
}
_ => {
diagnostics.add(
instantiation.subprogram_name.pos(self.ctx),
format!(
"{} does not denote an uninstantiated subprogram",
name.describe()
),
ErrorCode::MismatchedKinds,
);
return Err(EvalError::Unknown);
}
};
if overloaded_ent.is_uninst_subprogram() {
Ok(overloaded_ent)
} else {
diagnostics.add(
instantiation.subprogram_name.pos(self.ctx),
format!("{} cannot be instantiated", overloaded_ent.describe()),
ErrorCode::MismatchedKinds,
);
Err(EvalError::Unknown)
}
}
/// Checks that an instantiated subprogram kind matches the declared subprogram.
/// For instance, when a subprogram was instantiated using
/// ```vhdl
/// function my_func is new proc;
/// ```
/// where proc is
/// ```vhdl
/// procedure proc is
/// ...
/// ```
///
/// This function will push an appropriate diagnostic.
fn check_instantiated_subprogram_kind_matches_declared(
&self,
ent: &OverloadedEnt<'_>,
instance: &SubprogramInstantiation,
diagnostics: &mut dyn DiagnosticHandler,
) {
let err_msg = if ent.is_function() && instance.kind != SubprogramKind::Function {
Some("Instantiating function as procedure")
} else if ent.is_procedure() && instance.kind != SubprogramKind::Procedure {
Some("Instantiating procedure as function")
} else {
None
};
if let Some(msg) = err_msg {
let mut err = Diagnostic::new(
self.ctx.get_pos(instance.get_start_token()),
msg,
ErrorCode::MismatchedSubprogramInstantiation,
);
if let Some(pos) = ent.decl_pos() {
err.add_related(pos, format!("{} declared here", ent.describe()));
}
diagnostics.push(err)
}
}
fn find_subpgm_specification(
&self,
scope: &Scope<'a>,
decl: &SubprogramSpecification,
signature: &Signature<'_>,
) -> Option<OverloadedEnt<'a>> {
let des = decl.subpgm_designator().item.clone().into_designator();
if let Some(NamedEntities::Overloaded(overloaded)) = scope.lookup_immediate(&des) {
let ent = overloaded.get(&SubprogramKey::Normal(signature.key()))?;
if ent.is_subprogram_decl() {
return Some(ent);
}
}
None
}
fn find_uninst_subpgm_specification(
&self,
scope: &Scope<'a>,
decl: &SubprogramSpecification,
signature: &Signature<'_>,
) -> Option<OverloadedEnt<'a>> {
let des = decl.subpgm_designator().item.clone().into_designator();
if let Some(NamedEntities::Overloaded(overloaded)) = scope.lookup_immediate(&des) {
// Note: This does not work in common circumstances with a generic type parameter
// since the parameters of the declared subprogram and the subprogram with body
// point to two different type-ID's. For example:
// function foo generic (type F) return F;
// ^-- F has EntityId X
// function foo generic (type F) return F is ... end function foo;
// ^-- F has EntityId Y
// A future improvement must take this fact into account.
let ent = overloaded.get(&SubprogramKey::Uninstantiated(signature.key()))?;
if ent.is_uninst_subprogram_decl() {
return Some(ent);
}
}
None
}
}

View File

@ -0,0 +1,89 @@
use super::analyze::*;
use super::scope::*;
use crate::ast::token_range::WithTokenSpan;
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::*;
use vhdl_lang::TokenSpan;
/// Analysis of assignment targets
///
/// examples:
/// target <= 1;
/// target(0).elem := 1
impl<'a, 't> AnalyzeContext<'a, 't> {
pub fn resolve_target(
&self,
scope: &Scope<'a>,
target: &mut WithTokenSpan<Target>,
assignment_type: AssignmentType,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<TypeEnt<'a>> {
match target.item {
Target::Name(ref mut name) => {
self.resolve_target_name(scope, name, target.span, assignment_type, diagnostics)
}
Target::Aggregate(ref mut assocs) => {
self.analyze_aggregate(scope, assocs, diagnostics)?;
Err(EvalError::Unknown)
}
}
}
pub fn resolve_target_name(
&self,
scope: &Scope<'a>,
target: &mut Name,
target_pos: TokenSpan,
assignment_type: AssignmentType,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<TypeEnt<'a>> {
let object_name = self.resolve_object_name(
scope,
target_pos,
target,
"may not be the target of an assignment",
ErrorCode::MismatchedKinds,
diagnostics,
)?;
if !object_name.base.can_be_assigned_to() {
diagnostics.add(
target_pos.pos(self.ctx),
format!(
"{} may not be the target of an assignment",
object_name.base.describe_class()
),
ErrorCode::MismatchedKinds,
);
} else if !object_name.base.is_valid_assignment_type(assignment_type) {
diagnostics.add(
target_pos.pos(self.ctx),
format!(
"{} may not be the target of a {} assignment",
object_name.base.describe_class(),
assignment_type.to_str()
),
ErrorCode::MismatchedKinds,
);
}
Ok(object_name.type_mark())
}
}
#[derive(Copy, Clone)]
pub enum AssignmentType {
// Assignment with <=
Signal,
// Assignment with :=
Variable,
}
impl AssignmentType {
fn to_str(self) -> &'static str {
match self {
AssignmentType::Signal => "signal",
AssignmentType::Variable => "variable",
}
}
}

View File

@ -0,0 +1,682 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use vhdl_lang::data::error_codes::ErrorCode;
#[test]
fn overloaded_name_may_not_be_assignment_target() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
function foo1 return natural is
begin
return 0;
end;
type enum_t is (foo2, enum_value);
begin
main : process
begin
foo1 := 1;
foo2 := 1;
end process;
end architecture;
",
);
let expected = vec![
Diagnostic::mismatched_kinds(
code.s("foo1", 2),
"function foo1[return NATURAL] may not be the target of an assignment",
),
Diagnostic::mismatched_kinds(
code.s("foo2", 2),
"foo2[return enum_t] may not be the target of an assignment",
),
];
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, expected);
}
#[test]
fn attribute_name_may_not_be_assignment_target() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
signal foo : boolean;
begin
main : process
begin
foo'stable := true;
end process;
end architecture;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::mismatched_kinds(
code.s("foo'stable", 1),
"signal 'stable' may not be the target of a variable assignment",
)],
);
}
#[test]
fn subprogram_call_may_not_be_assignment_target() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
function foo1(arg : natural) return natural;
end package;
package body pkg is
function foo1(arg : natural) return natural is
begin
return 0;
end function;
end package body;
entity ent is
end entity;
architecture a of ent is
function foo2(arg : natural) return natural is
begin
return 0;
end function;
begin
main : process
begin
work.pkg.foo1(2) := 1;
foo2(2) := 1;
work.pkg.foo1(arg => 2) := 1;
foo2(arg => 2) := 1;
end process;
end architecture;
",
);
let expected = vec![
Diagnostic::mismatched_kinds(
code.s1("work.pkg.foo1(2)"),
"Expression may not be the target of an assignment",
),
Diagnostic::mismatched_kinds(
code.s1("foo2(2)"),
"Expression may not be the target of an assignment",
),
Diagnostic::mismatched_kinds(
code.s1("work.pkg.foo1(arg => 2)"),
"Expression may not be the target of an assignment",
),
Diagnostic::mismatched_kinds(
code.s1("foo2(arg => 2)"),
"Expression may not be the target of an assignment",
),
];
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, expected);
}
#[test]
fn constant_may_not_be_assignment_target() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
constant foo1 : natural := 0;
alias foo2 is foo1;
begin
main : process
begin
foo1 := 1;
foo2 := 1;
end process;
end architecture;
",
);
let expected = vec![
Diagnostic::mismatched_kinds(
code.s("foo1", 3),
"constant 'foo1' may not be the target of an assignment",
),
Diagnostic::mismatched_kinds(
code.s("foo2", 2),
"alias 'foo2' of constant may not be the target of an assignment",
),
];
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, expected);
}
#[test]
fn objects_may_be_assignment_target() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
signal foo1 : natural := 0;
alias foo2 is foo1;
shared variable foo3 : natural := 0;
begin
main : process
variable foo4 : natural := 0;
begin
foo1 <= 1;
foo2 <= 1;
foo3 := 1;
foo4 := 1;
end process;
end architecture;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn indexed_names_may_be_assignment_target() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
type arr1_t is array (natural range 0 to 1) of natural;
type arr2_t is array (natural range 0 to 1, natural range 0 to 1) of natural;
type arr3_t is array (natural range 0 to 1) of arr1_t;
signal foo1 : arr1_t;
signal foo2 : arr2_t;
signal foo3 : arr3_t;
begin
foo1(0) <= 0;
foo2(0, 0) <= 0;
foo3(0)(0) <= 0;
end architecture;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn interface_objects_may_be_assignment_target() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
procedure proc1(signal foo : out natural) is
begin
foo <= 1;
end;
begin
main : process
procedure proc2(variable foo : inout natural) is
begin
foo := 1;
end;
begin
end process;
end architecture;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn interface_constant_may_not_be_assignment_target() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
begin
main : process
procedure proc1(constant foo1 : natural) is
begin
foo1 := 1;
end;
procedure proc2(variable foo2 : in natural) is
begin
foo2 := 1;
end;
begin
end process;
end architecture;
",
);
let expected = vec![
Diagnostic::mismatched_kinds(
code.s("foo1", 2),
"interface constant 'foo1' may not be the target of an assignment",
),
Diagnostic::mismatched_kinds(
code.s("foo2", 2),
"interface variable 'foo2' of mode in may not be the target of an assignment",
),
];
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, expected);
}
#[test]
fn checks_signal_vs_variable_assignment_target() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
signal foo3 : natural;
begin
main : process
variable foo4 : natural;
procedure proc1(signal foo1 : out natural) is
begin
foo1 := 1;
end;
procedure proc2(variable foo2 : out natural) is
begin
foo2 <= 1;
end;
begin
foo3 := 1;
foo4 <= 1;
end process;
end architecture;
",
);
let expected = vec![
Diagnostic::mismatched_kinds(
code.s("foo1", 2),
"interface signal 'foo1' of mode out may not be the target of a variable assignment",
),
Diagnostic::mismatched_kinds(
code.s("foo2", 2),
"interface variable 'foo2' of mode out may not be the target of a signal assignment",
),
Diagnostic::mismatched_kinds(
code.s("foo3", 2),
"signal 'foo3' may not be the target of a variable assignment",
),
Diagnostic::mismatched_kinds(
code.s("foo4", 2),
"variable 'foo4' may not be the target of a signal assignment",
),
];
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, expected);
}
#[test]
fn indexed_assignment_target() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
signal foo : natural;
begin
foo(0) <= 0;
end architecture;
",
);
let expected = vec![Diagnostic::mismatched_kinds(
code.s("foo", 2),
"signal 'foo' of subtype 'NATURAL' cannot be indexed",
)];
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, expected);
}
#[test]
fn sliced_assignment_target() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
signal foo : natural;
begin
foo(0 to 1) <= (0, 2);
end architecture;
",
);
let expected = vec![Diagnostic::mismatched_kinds(
code.s("foo", 2),
"signal 'foo' of subtype 'NATURAL' cannot be sliced",
)];
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, expected);
}
#[test]
fn sliced_names_may_be_assignment_target() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
type arr1_t is array (natural range 0 to 1) of natural;
signal foo1 : arr1_t;
begin
foo1(0 to 1) <= (others => 0);
main : process
begin
foo1(0 to 1) := (others => 0);
end process;
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("foo1(0 to 1)", 2),
"signal 'foo1' may not be the target of a variable assignment",
ErrorCode::MismatchedKinds,
)],
);
}
#[test]
fn test_array_element_target_can_be_selected() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
type rec_t is record
elem : natural;
end record;
type arr_t is array (0 to 1) of rec_t;
signal c1 : arr_t;
begin
c1(0).elem <= 1;
end architecture;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn test_alias_target() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
type rec_t is record
elem : natural;
end record;
type arr_t is array (0 to 1) of rec_t;
signal c1 : arr_t;
alias a1 is c1;
alias a2 is c1(0);
alias a3 is a2.elem;
begin
a1(0) <= (elem => 0);
a2.elem <= 1;
a3 <= 1;
end architecture;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn assignment_target_all() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
type rec_t is record
field: natural;
end record;
type ptr_t is access rec_t;
procedure proc is
variable vptr : ptr_t;
begin
-- Good
vptr.all := (field => 0);
vptr.all.field := 0;
vptr.field := 0;
-- Bad
vptr.all := vptr;
vptr.all.all := vptr;
end procedure;
begin
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s1("vptr.all := vptr").s("vptr", 2),
"variable 'vptr' of access type 'ptr_t' does not match record type 'rec_t'",
ErrorCode::TypeMismatch,
),
Diagnostic::new(
code.s1("vptr.all.all").s1("vptr.all"),
"record type 'rec_t' cannot be accessed with .all",
ErrorCode::MismatchedKinds,
),
],
);
}
#[test]
fn issue_177() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package Test is
type MyArray_t is array (0 to kConst) of integer;
end Test;
package body Test is
variable v : MyArray_t := (others => 0);
procedure Foo is
begin
-- v'range previously paniced due to MyArray_t having None as index
for i in v'range loop
v(i) := 1;
end loop;
end procedure Foo;
end package body Test;",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("kConst"),
"No declaration of 'kConst'",
ErrorCode::Unresolved,
)],
)
}
#[test]
pub fn assignment_mode_checking() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
entity bar is
end bar;
architecture foo of bar is
signal a : integer;
shared variable b: integer;
file c: integer;
procedure proc(
signal f_a: integer;
variable f_b: integer;
constant f_c: integer;
file f_d: integer
) is
begin
end proc;
begin
baz: process is
variable d: integer;
variable e: character;
begin
proc(d, c, a, b);
proc(a, b, 1 + 1, c);
proc(a, e, 1 + 1, c);
end process baz;
end architecture foo;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s1("proc(d, c, a, b)").s1("d"),
"Name must denote a signal name",
ErrorCode::InterfaceModeMismatch,
),
Diagnostic::new(
code.s1("proc(d, c, a, b)").s("c", 2),
"Name must denote a variable name",
ErrorCode::InterfaceModeMismatch,
),
Diagnostic::new(
code.s1("proc(d, c, a, b)").s1("b"),
"Name must denote a file name",
ErrorCode::InterfaceModeMismatch,
),
Diagnostic::new(
code.s1("proc(a, e, 1 + 1, c)").s1("e"),
"variable 'e' of type 'CHARACTER' does not match integer type 'INTEGER'",
ErrorCode::TypeMismatch,
),
],
)
}
#[test]
fn legal_file_names() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"\
use std.textio.all;
package foo is
procedure tee(
file file_handle : text;
variable my_line : inout line
);
end package foo;
package body foo is
procedure tee(
file file_handle : text;
variable my_line : inout line
) is
variable v_line : line;
begin
write(v_line, my_line.all);
writeline(file_handle, v_line);
end procedure tee;
end package body;
",
);
check_no_diagnostics(&builder.analyze())
}

View File

@ -0,0 +1,746 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2022, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use crate::VHDLStandard::VHDL2019;
use pretty_assertions::assert_eq;
use vhdl_lang::data::error_codes::ErrorCode;
#[test]
fn missing_port_name() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent_inst is
end entity;
architecture a of ent_inst is
begin
end architecture;
entity ent is
end entity;
architecture a of ent is
signal sig : boolean;
begin
ent: entity work.ent_inst
port map (missing => sig);
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("missing"),
"No declaration of 'missing'",
ErrorCode::Unresolved,
)],
);
}
#[test]
fn missing_generic_name() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent_inst is
end entity;
architecture a of ent_inst is
begin
end architecture;
entity ent is
end entity;
architecture a of ent is
signal sig : boolean;
begin
ent: entity work.ent_inst
generic map (missing => sig);
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("missing"),
"No declaration of 'missing'",
ErrorCode::Unresolved,
)],
);
}
#[test]
fn resolve_port_name() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent_inst is
port (
theport : in boolean
);
end entity;
architecture a of ent_inst is
begin
end architecture;
entity ent is
end entity;
architecture a of ent is
signal sig : boolean;
begin
ent: entity work.ent_inst
port map (theport => sig);
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("theport", 2).end()),
Some(code.s1("theport").pos())
);
}
#[test]
fn resolve_generic_name() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent_inst is
generic (
thegeneric : boolean
);
end entity;
architecture a of ent_inst is
begin
end architecture;
entity ent is
end entity;
architecture a of ent is
begin
ent: entity work.ent_inst
generic map (thegeneric => false);
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("thegeneric", 2).end()),
Some(code.s1("thegeneric").pos())
);
}
#[test]
fn does_not_mixup_ports_and_generics() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent_inst is
generic (
thegeneric : boolean
);
port (
theport : in boolean
);
end entity;
architecture a of ent_inst is
begin
end architecture;
entity ent is
end entity;
architecture a of ent is
signal sig : boolean;
begin
ent: entity work.ent_inst
generic map (theport => sig)
port map (thegeneric => 0);
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s("theport", 2),
"No declaration of 'theport'",
ErrorCode::Unresolved,
),
Diagnostic::new(
code.s1("work.ent_inst"),
"No association of port 'theport' : in",
ErrorCode::Unassociated,
)
.related(code.s1("theport"), "Defined here"),
Diagnostic::new(
code.s("thegeneric", 2),
"No declaration of 'thegeneric'",
ErrorCode::Unresolved,
),
Diagnostic::new(
code.s1("work.ent_inst"),
"No association of generic 'thegeneric'",
ErrorCode::Unassociated,
)
.related(code.s1("thegeneric"), "Defined here"),
],
);
}
#[test]
fn resolve_port_and_surrounding_name() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent_inst is
port (
theport : in integer_vector(0 to 0)
);
end entity;
architecture a of ent_inst is
begin
end architecture;
entity ent is
end entity;
architecture a of ent is
constant const0 : natural := 0;
begin
ent: entity work.ent_inst
port map (theport(const0) => 0);
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("theport", 2).end()),
Some(code.s1("theport").pos())
);
assert_eq!(
root.search_reference_pos(code.source(), code.s("const0", 2).end()),
Some(code.s1("const0").pos())
);
}
#[test]
fn function_conversion_of_port_name() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
port (
theport: out natural
);
end entity;
architecture a of ent is
begin
end architecture;
entity ent2 is
end entity;
architecture a of ent2 is
function fun1(arg : natural) return natural is
begin
return arg;
end function;
signal sig : natural;
begin
inst: entity work.ent
port map (
fun1(theport) => sig);
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("fun1", 2).end()),
Some(code.s1("fun1").pos())
);
assert_eq!(
root.search_reference_pos(code.source(), code.s("theport", 2).end()),
Some(code.s1("theport").pos())
);
}
#[test]
fn type_conversion_of_port_name() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
port (
theport: out natural
);
end entity;
architecture a of ent is
begin
end architecture;
entity ent2 is
end entity;
architecture a of ent2 is
signal sig : real;
begin
inst: entity work.ent
port map (
real(theport) => sig);
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("theport", 2).end()),
Some(code.s1("theport").pos())
);
}
#[test]
fn function_conversion_of_port_name_must_be_single_argument() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
port (
theport: out natural
);
end entity;
architecture a of ent is
begin
end architecture;
entity ent2 is
end entity;
architecture a of ent2 is
function fun1(arg : natural; arg2: natural) return natural is
begin
return arg;
end function;
signal sig : natural;
begin
inst: entity work.ent
port map (
fun1(theport, 2) => sig);
end architecture;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("fun1(theport, 2)"),
"Invalid formal conversion",
ErrorCode::InvalidFormalConversion,
)],
);
}
#[test]
fn function_conversion_of_port_name_must_not_have_its_own_formal() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
port (
theport: out natural
);
end entity;
architecture a of ent is
begin
end architecture;
entity ent2 is
end entity;
architecture a of ent2 is
function fun1(arg : natural) return natural is
begin
return arg;
end function;
signal sig : natural;
begin
inst: entity work.ent
port map (
fun1(arg => theport) => sig);
end architecture;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("fun1(arg => theport)"),
"Invalid formal conversion",
ErrorCode::InvalidFormalConversion,
)],
);
}
#[test]
fn conversion_of_sliced_formal() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity module is
port (
theport : out bit_vector(0 to 5)
);
end;
architecture a of module is
begin
end architecture;
entity ent is
end;
architecture behav of ent is
signal data0 : bit_vector(0 to 5);
signal data1 : bit_vector(0 to 5);
begin
inst0: entity work.module
port map (
bit_vector(theport(data0'range)) => data0
);
inst1: entity work.module
port map (
bit_vector(theport(0 to 5)) => data1
);
end;",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("theport", 2).end()),
Some(code.s1("theport").pos())
);
assert_eq!(
root.search_reference_pos(code.source(), code.s("theport", 3).end()),
Some(code.s1("theport").pos())
);
}
#[test]
fn output_ports_may_be_left_open() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent2 is
port (
signal inport: in natural;
signal outport: out natural );
end entity;
architecture a of ent2 is
begin
end architecture;
entity ent is
end entity;
architecture a of ent is
signal sig : natural;
begin
inst: entity work.ent2
port map (
inport => sig
);
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
// Still resolves references when missing output port
assert!(root
.search_reference(code.source(), code.s1("inport => sig").s1("sig").start())
.is_some())
}
#[test]
fn does_not_stop_on_first_error() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent_inst is
port (
prt0 : in boolean;
prt1 : in boolean
);
end entity;
architecture a of ent_inst is
begin
end architecture;
entity ent is
end entity;
architecture a of ent is
signal sig0, sig1 : boolean;
begin
ent: entity work.ent_inst
port map (
missing => sig0,
prt1 => sig1);
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s1("missing"),
"No declaration of 'missing'",
ErrorCode::Unresolved,
),
Diagnostic::new(
code.s1("work.ent_inst"),
"No association of port 'prt0' : in",
ErrorCode::Unassociated,
)
.related(code.s1("prt0"), "Defined here"),
],
);
// Still sets the reference even if it fails
assert_eq!(
root.search_reference_pos(code.source(), code.s1("=> sig0").s1("sig0").pos().start())
.unwrap(),
code.s1("sig0").pos()
);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("=> sig1").s1("sig1").pos().start())
.unwrap(),
code.s1("sig1").pos()
);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("prt1 => ").s1("prt1").pos().start())
.unwrap(),
code.s1("prt1").pos()
);
}
#[test]
fn view_array_with_explicit_type() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"
package test_pkg is
type test_t is record
foo : bit;
end record;
view vone of test_t is
foo : in;
end view;
type test_array is array (natural range <>) of test_t;
end package;
use work.test_pkg.all;
entity test_sub_entity is
port (
my_array_if: view (vone) of test_array(1 downto 0)
);
end entity;
architecture arch of test_sub_entity is
begin
my_array_if(0).foo <= '1';
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("test_array(1 downto 0)").start())
.unwrap(),
code.s1("test_array").pos()
);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("my_array_if(0).foo").end())
.unwrap(),
code.s1("foo").pos()
);
}
#[test]
fn view_array_without_explicit_type() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"
package test_pkg is
type test_t is record
foo : bit;
end record;
view vone of test_t is
foo : in;
end view;
end package;
use work.test_pkg.all;
entity test_sub_entity is
port (
my_array_if: view (vone)
);
end entity;
architecture arch of test_sub_entity is
begin
my_array_if(0).foo <= '1';
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("my_array_if(0).foo").end())
.unwrap(),
code.s1("foo").pos()
);
}
#[test]
fn view_array_with_non_array_type() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"
package test_pkg is
type test_t is record
foo : natural;
end record;
view vone of test_t is
foo : in;
end view;
end package;
use work.test_pkg.all;
entity test_sub_entity is
port (
my_array_if: view (vone) of bit
);
end entity;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("bit"),
"Subtype must be an array",
ErrorCode::TypeMismatch,
)],
);
}
#[test]
fn view_array_with_mismatched_element_type() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"
package test_pkg is
type test_t is record
foo : natural;
end record;
view vone of test_t is
foo : in;
end view;
end package;
use work.test_pkg.all;
entity test_sub_entity is
port (
my_array_if: view (vone) of bit_vector
);
end entity;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("bit_vector"),
"Array element type 'BIT' must match record type 'test_t' declared for the view",
ErrorCode::TypeMismatch,
)],
);
}

View File

@ -0,0 +1,309 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use crate::data::error_codes::ErrorCode;
#[test]
fn context() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
context ctx1 is
library libname;
context libname.ctx2;
end context;
context ctx2 is
library libname;
context libname.ctx1;
end context;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s("ctx1", 2),
"Found circular dependency",
ErrorCode::CircularDependency,
),
Diagnostic::new(
code.s("ctx2", 1),
"Found circular dependency",
ErrorCode::CircularDependency,
),
],
);
}
#[test]
fn use_package() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
use work.pkg2.const;
package pkg1 is
constant const : natural := 0;
end package;
use work.pkg1.const;
package pkg2 is
constant const : natural := 0;
end package;",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s("pkg1", 2),
"Found circular dependency",
ErrorCode::CircularDependency,
),
Diagnostic::new(
code.s("pkg2", 1),
"Found circular dependency",
ErrorCode::CircularDependency,
),
],
);
}
#[test]
fn use_package_instance() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (c : boolean);
constant const : boolean := c;
end package;
use work.pkg2.const;
package pkg1 is new work.gpkg generic map(c => false);
use work.pkg1.const;
package pkg2 is new work.gpkg generic map(c => true);
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s("pkg1", 2),
"Found circular dependency",
ErrorCode::CircularDependency,
),
Diagnostic::new(
code.s1("pkg2"),
"Found circular dependency",
ErrorCode::CircularDependency,
),
],
);
}
#[test]
fn package_instance_in_declarative_region() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
use work.pkg2.const;
package gpkg is
generic (c : boolean);
constant const : boolean := c;
end package;
package pkg2 is
package ipkg is new work.gpkg generic map(c => false);
constant const : boolean := false;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s1("pkg2"),
"Found circular dependency",
ErrorCode::CircularDependency,
),
Diagnostic::new(
code.s("gpkg", 2),
"Found circular dependency",
ErrorCode::CircularDependency,
),
],
);
}
#[test]
fn package_instance_in_interface() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
use work.pkg3.const;
package gpkg is
generic (c : boolean);
constant const : boolean := c;
end package;
package pkg2 is
generic (
package ipkg is new work.gpkg generic map(c => true)
);
constant const : boolean := false;
end package;
package pkg3 is new work.pkg2;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s1("pkg3"),
"Found circular dependency",
ErrorCode::CircularDependency,
),
Diagnostic::new(
code.s("gpkg", 2),
"Found circular dependency",
ErrorCode::CircularDependency,
),
Diagnostic::new(
code.s("pkg2", 2),
"Found circular dependency",
ErrorCode::CircularDependency,
),
],
);
}
#[test]
fn use_package_all() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
use work.pkg2.all;
package pkg1 is
constant const : natural := 0;
end package;
use work.pkg1.all;
package pkg2 is
constant const : natural := 0;
end package;",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s("pkg1", 2),
"Found circular dependency",
ErrorCode::CircularDependency,
),
Diagnostic::new(
code.s1("pkg2"),
"Found circular dependency",
ErrorCode::CircularDependency,
),
],
);
}
#[test]
fn use_package_instance_all() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (g : boolean);
end package;
use work.pkg2.all;
package pkg1 is
constant const : natural := 0;
end package;
use work.pkg1.all;
package pkg2 is new work.gpkg generic map (g => true);
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s("pkg1", 2),
"Found circular dependency",
ErrorCode::CircularDependency,
),
Diagnostic::new(
code.s1("pkg2"),
"Found circular dependency",
ErrorCode::CircularDependency,
),
],
);
}
#[test]
fn use_library_all() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
use work.all;
package pkg1 is
constant const : natural := 0;
end package;
use work.pkg1.const;
package pkg2 is
constant const : natural := 0;
end package;",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s("pkg1", 2),
"Found circular dependency",
ErrorCode::CircularDependency,
),
Diagnostic::new(
code.s("work.all", 1),
"Found circular dependency",
ErrorCode::CircularDependency,
),
],
);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,329 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use crate::data::error_codes::ErrorCode;
use itertools::Itertools;
use super::*;
#[test]
fn signal_attribute_must_in_the_same_declarative_part() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
attribute myattr : boolean;
signal good, bad : natural;
attribute myattr of good : signal is true;
begin
process
attribute myattr of bad : signal is true;
begin
end process;
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("bad", 2),
"Attribute specification must be in the immediate declarative part",
ErrorCode::MisplacedAttributeSpec,
)],
);
}
#[test]
fn entity_attribute_must_in_the_same_declarative_part() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity myent is
attribute myattr : boolean;
attribute myattr of myent : entity is true;
end entity;
architecture a of myent is
attribute myattr of myent : entity is true;
begin
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("myent", 4),
"Attribute specification must be in the immediate declarative part",
ErrorCode::MisplacedAttributeSpec,
)],
);
}
#[test]
fn interface_attribute_must_in_the_same_declarative_part() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
port (good, bad : out boolean);
attribute myattr : natural;
attribute myattr of good : signal is 1337;
end entity;
architecture a of ent is
attribute myattr of bad : signal is 1337;
begin
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("bad", 2),
"Attribute specification must be in the immediate declarative part",
ErrorCode::MisplacedAttributeSpec,
)],
);
}
#[test]
fn custom_attribute_can_be_used() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
entity ent is
attribute myattr : boolean;
attribute myattr of ent : entity is false;
end entity;
architecture a of ent is
constant c0 : boolean := ent'myattr;
signal mysig : natural;
attribute myattr of mysig : signal is false;
constant c1 : boolean := mysig'myattr;
type mytype is (alpha, beta);
attribute myattr of mytype : type is false;
constant c2 : boolean := mytype'myattr;
begin
end architecture;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn finds_references_of_custom_attributes() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
attribute myattr : boolean;
attribute myattr of ent : entity is false;
end entity;
architecture a of ent is
constant c0 : boolean := ent'myattr;
begin
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.find_all_references_pos(&code.s1("myattr").pos()),
vec![
code.s1("attribute myattr : boolean"),
code.s1("myattr of ent"),
code.s1("ent'myattr"),
]
.into_iter()
.map(|c| c.s1("myattr").pos())
.collect_vec()
);
}
#[test]
fn invalid_prefix_for_custom_attribute() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
attribute myattr : boolean;
constant c1 : boolean := std'myattr;
begin
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::mismatched_kinds(
code.s1("std'myattr"),
"library std may not be the prefix of a user defined attribute",
)],
);
}
#[test]
fn incorrect_entity_class() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
attribute myattr : boolean;
signal good, bad : natural;
attribute myattr of good : signal is true;
attribute myattr of bad : variable is true;
begin
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("bad", 2),
"signal 'bad' is not of class variable",
ErrorCode::MismatchedEntityClass,
)],
);
}
#[test]
fn subtype_entity_class() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
attribute myattr : boolean;
subtype good is integer range 0 to 3;
type bad is (alpha, beta);
attribute myattr of good : subtype is true;
attribute myattr of bad : subtype is true;
begin
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("bad", 2),
"type 'bad' is not of class subtype",
ErrorCode::MismatchedEntityClass,
)],
);
}
#[test]
fn duplicate_attribute() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
attribute myattr : boolean;
signal mysig : natural;
attribute myattr of mysig : signal is false;
attribute myattr of mysig : signal is true;
begin
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("mysig", 3),
"Duplicate specification of attribute 'myattr' for signal 'mysig'",
ErrorCode::Duplicate,
)
.related(code.s("mysig", 2), "Previously specified here")],
);
}
#[test]
fn attributes_affect_aliased_object_and_not_alias_itself() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
attribute myattr : boolean;
signal mysig : natural;
alias myalias is mysig;
attribute myattr of myalias : signal is false;
attribute myattr of mysig : signal is false;
constant c0 : boolean := mysig'myattr;
constant c1 : boolean := myalias'myattr;
begin
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("mysig : signal").s1("mysig"),
"Duplicate specification of attribute 'myattr' for signal 'mysig'",
ErrorCode::Duplicate,
)
.related(
code.s1("myalias : signal").s1("myalias"),
"Previously specified here",
)],
);
}

View File

@ -0,0 +1,102 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::tests::{check_diagnostics, LibraryBuilder};
use crate::data::error_codes::ErrorCode;
use crate::Diagnostic;
#[test]
pub fn declaration_not_allowed_everywhere() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
entity ent is
end entity;
architecture arch of ent is
function my_func return natural is
signal x : bit;
begin
end my_func;
begin
my_block : block
variable y: natural;
begin
end block my_block;
end architecture;
",
);
check_diagnostics(
builder.analyze(),
vec![
Diagnostic::new(
code.s1("signal x : bit;"),
"signal declaration not allowed here",
ErrorCode::DeclarationNotAllowed,
),
Diagnostic::new(
code.s1("variable y: natural;"),
"variable declaration not allowed here",
ErrorCode::DeclarationNotAllowed,
),
],
)
}
// Issue #242
#[test]
pub fn attribute_with_wrong_type() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
entity test is
attribute some_attr : string;
attribute some_attr of test : signal is \"some value\";
end entity test;
",
);
let (_, diag) = builder.get_analyzed_root();
check_diagnostics(
diag,
vec![Diagnostic::new(
code.s1("test : signal").s1("test"),
"entity 'test' is not of class signal",
ErrorCode::MismatchedEntityClass,
)],
)
}
#[test]
pub fn attribute_sees_through_aliases() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
entity test is
port (
clk: in bit
);
alias aliased_clk is clk;
attribute some_attr : string;
attribute some_attr of aliased_clk : entity is \"some value\";
end entity test;
",
);
let (_, diag) = builder.get_analyzed_root();
check_diagnostics(
diag,
vec![Diagnostic::new(
code.s1("aliased_clk : entity").s1("aliased_clk"),
"port 'clk' : in is not of class entity",
ErrorCode::MismatchedEntityClass,
)],
)
}

View File

@ -0,0 +1,158 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use vhdl_lang::data::error_codes::ErrorCode;
#[test]
fn allows_deferred_constant() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package pkg is
constant a : natural;
end package;
package body pkg is
constant a : natural := 0;
end package body;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn forbid_deferred_constant_after_constant() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
constant a1 : natural := 0;
constant a1 : natural;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, duplicates(&code, &["a1"]));
}
#[test]
fn forbid_deferred_constant_outside_of_package_declaration() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
end package;
package body pkg is
constant a1 : natural;
constant a1 : natural := 0;
end package body;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("a1"),
"Deferred constants are only allowed in package declarations (not body)",
ErrorCode::IllegalDeferredConstant,
)],
);
}
#[test]
fn forbid_full_declaration_of_deferred_constant_outside_of_package_body() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
constant a1 : natural;
constant a1 : natural := 0;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("a1", 1),
"Deferred constant 'a1' lacks corresponding full constant declaration in package body",
ErrorCode::MissingDeferredDeclaration
),Diagnostic::new(
code.s("a1", 2),
"Full declaration of deferred constant is only allowed in a package body",
ErrorCode::IllegalDeferredConstant
)],
);
}
#[test]
fn error_on_missing_full_constant_declaration() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg_no_body is
constant a1 : natural;
end package;
package pkg is
constant b1 : natural;
end package;
package body pkg is
end package body;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s1("a1"),
"Deferred constant 'a1' lacks corresponding full constant declaration in package body",
ErrorCode::MissingDeferredDeclaration
),
Diagnostic::new(
code.s1("b1"),
"Deferred constant 'b1' lacks corresponding full constant declaration in package body",
ErrorCode::MissingDeferredDeclaration
),
],
);
}
#[test]
fn forbid_multiple_constant_after_deferred_constant() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
constant a1 : natural;
end package;
package body pkg is
constant a1 : natural := 0;
constant a1 : natural := 0;
end package body;
",
);
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, vec![duplicate(&code, "a1", 2, 3)]);
}

View File

@ -0,0 +1,426 @@
//! This Source Code Form is subject to the terms of the Mozilla Public
//! License, v. 2.0. If a copy of the MPL was not distributed with this file,
//! You can obtain one at http://mozilla.org/MPL/2.0/.
//!
//! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use crate::data::error_codes::ErrorCode;
use crate::EntHierarchy;
use crate::Source;
use pretty_assertions::assert_eq;
#[test]
fn entity_architecture() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
generic (
g0 : natural
);
port (
p0 : bit
);
end entity;
architecture a of ent is
signal s0 : natural;
begin
block
begin
process
variable v0 : natural;
begin
loop0: loop
end loop;
if false then
end if;
end process;
end block;
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
get_hierarchy(&root, "libname", code.source()),
vec![
nested("ent", vec![single("g0"), single("p0"),]),
nested(
"a",
vec![
single("s0"),
nested(
"block",
vec![nested(
"process",
vec![single("v0"), single("loop0"), single("if statement")]
)]
),
]
)
]
);
}
#[test]
fn package() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
function fun0(arg : natural) return natural;
end package;
package body pkg is
function fun0(arg : natural) return natural is
variable v0 : natural;
begin
end function;
end package body;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
get_hierarchy(&root, "libname", code.source()),
vec![
nested("pkg", vec![nested("fun0", vec![single("arg"),]),]),
nested(
"pkg",
vec![nested("fun0", vec![single("arg"), single("v0")]),]
)
]
);
}
#[test]
fn generic_package() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
generic (
type type_t;
value: type_t
);
constant c0 : type_t := value;
function fun0(arg: type_t) return boolean;
end package;
package body pkg is
function fun0(arg: type_t) return boolean is
begin
return arg = value;
end function;
end package body;
package ipkg is new work.pkg generic map(type_t => integer, value => 0);
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
get_hierarchy(&root, "libname", code.source()),
vec![
nested(
"pkg",
vec![
single("type_t"),
single("value"),
single("c0"),
nested("fun0", vec![single("arg"),]),
]
),
nested("pkg", vec![nested("fun0", vec![single("arg")]),]),
single("ipkg"),
]
);
let ipkg = root
.search_reference(code.source(), code.s1("ipkg").start())
.unwrap();
let instances: Vec<_> = if let AnyEntKind::Design(Design::PackageInstance(region)) = ipkg.kind()
{
let mut symbols: Vec<_> = region.immediates().collect();
symbols.sort_by_key(|ent| ent.decl_pos());
symbols.into_iter().map(|ent| ent.path_name()).collect()
} else {
panic!("Expected instantiated package");
};
assert_eq!(instances, vec!["libname.ipkg.c0", "libname.ipkg.fun0"]);
}
#[test]
fn public_symbols() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package pkg is
type type_t is (alpha, beta);
constant const0 : type_t := alpha;
function fun0(arg: type_t) return boolean;
function \"+\"(arg: type_t) return boolean;
type prot_t is protected
procedure proc0(arg: type_t);
end protected;
end package;
package body pkg is
type prot_t is protected body
procedure proc0(arg: type_t) is
begin
end;
end protected body;
end package body;
entity ent is
generic (
g0 : natural
);
port (
p0 : natural
);
end entity;
architecture a of ent is
signal not_public : bit;
begin
main: process
begin
end process;
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let mut symbols: Vec<_> = root
.public_symbols()
.filter(|ent| ent.library_name() == Some(&root.symbol_utf8("libname")))
.collect();
symbols.sort_by_key(|ent| ent.decl_pos());
assert_eq!(
symbols
.into_iter()
.map(|ent| ent.path_name())
.collect::<Vec<_>>(),
vec![
"libname",
"libname.pkg",
"libname.pkg.type_t",
"libname.pkg.type_t.alpha",
"libname.pkg.type_t.beta",
"libname.pkg.const0",
"libname.pkg.fun0",
"libname.pkg.\"+\"",
"libname.pkg.prot_t",
"libname.pkg.prot_t.proc0",
"libname.pkg",
"libname.ent",
"libname.ent.g0",
"libname.ent.p0",
"libname.ent.a",
]
);
}
#[derive(PartialEq, Debug)]
struct NameHierarchy {
name: String,
children: Vec<NameHierarchy>,
}
/// For compact test data creation
fn nested(name: &str, children: Vec<NameHierarchy>) -> NameHierarchy {
NameHierarchy {
name: name.to_owned(),
children,
}
}
/// For compact test data creation
fn single(name: &str) -> NameHierarchy {
NameHierarchy {
name: name.to_owned(),
children: Vec::new(),
}
}
impl<'a> From<EntHierarchy<'a>> for NameHierarchy {
fn from(ent: EntHierarchy<'_>) -> Self {
NameHierarchy {
name: if matches!(ent.ent.designator(), Designator::Anonymous(_)) {
ent.ent.kind().describe().to_string()
} else {
ent.ent.designator().to_string()
},
children: ent.children.into_iter().map(NameHierarchy::from).collect(),
}
}
}
fn get_hierarchy(root: &DesignRoot, libname: &str, source: &Source) -> Vec<NameHierarchy> {
root.document_symbols(&root.symbol_utf8(libname), source)
.into_iter()
.map(|item| item.0)
.map(NameHierarchy::from)
.collect()
}
#[test]
fn find_implementation_of_entity_vs_component() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent0 is
end entity;
architecture a of ent0 is
begin
end architecture;
entity ent1 is
end entity;
architecture a of ent1 is
component ent0 is
end component;
begin
inst: ent0;
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let ent = root
.search_reference(code.source(), code.s1("ent0").start())
.unwrap();
let arch = root
.search_reference(code.source(), code.s1("a ").start())
.unwrap();
let comp = root
.search_reference(code.source(), code.sa("component ", "ent0").start())
.unwrap();
assert_eq!(root.find_implementation(ent), vec![arch, comp]);
assert_eq!(root.find_implementation(comp), vec![ent]);
}
#[test]
fn exit_and_next_outside_of_loop() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
begin
process
begin
exit;
next;
loop
exit;
end loop;
loop
next;
end loop;
end process;
end architecture;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s1("exit;"),
"Exit can only be used inside a loop",
ErrorCode::ExitOutsideLoop,
),
Diagnostic::new(
code.s1("next;"),
"Next can only be used inside a loop",
ErrorCode::NextOutsideLoop,
),
],
);
}
#[test]
fn exit_and_next_label_outside_of_loop() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
begin
main: process
begin
good0: loop
good1: loop
exit good0;
end loop;
end loop;
bad0: loop
exit;
end loop;
l1: loop
exit bad0;
end loop;
l0: loop
next bad0;
end loop;
end process;
end architecture;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.sa("exit ", "bad0"),
"Cannot be used outside of loop 'bad0'",
ErrorCode::InvalidLoopLabel,
),
Diagnostic::new(
code.sa("next ", "bad0"),
"Cannot be used outside of loop 'bad0'",
ErrorCode::InvalidLoopLabel,
),
],
);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,285 @@
use super::*;
use vhdl_lang::data::error_codes::ErrorCode;
#[test]
fn adds_to_string_for_standard_types() {
check_code_with_no_diagnostics(
"
package pkg is
alias alias1 is to_string[integer return string];
alias alias2 is minimum[integer, integer return integer];
alias alias3 is maximum[integer, integer return integer];
end package;
",
);
}
// procedure FILE_OPEN (file F: FT; External_Name: in STRING; Open_Kind: in FILE_OPEN_KIND := READ_MODE);
// procedure FILE_OPEN (Status: out FILE_OPEN_STATUS; file F: FT; External_Name: in STRING; Open_Kind: in FILE_OPEN_KIND := READ_MODE);
// procedure FILE_CLOSE (file F: FT);
// procedure READ (file F: FT; VALUE: out TM);
// procedure WRITE (file F: FT; VALUE: in TM);
// procedure FLUSH (file F: FT);
// function ENDFILE (file F: FT) return BOOLEAN
#[test]
fn adds_file_subprograms_implicitly() {
check_code_with_no_diagnostics(
"
package pkg is
end package;
package body pkg is
type binary_file_t is file of character;
procedure proc is
file f : binary_file_t;
variable char : character;
begin
file_open(f, \"foo.txt\");
assert not endfile(f);
write(f, 'c');
flush(f);
read(f, char);
file_close(f);
end procedure;
end package body;
",
);
}
#[test]
fn adds_to_string_for_integer_types() {
check_code_with_no_diagnostics(
"
package pkg is
type type_t is range 0 to 1;
alias my_to_string is to_string[type_t return string];
end package;
",
);
}
#[test]
fn adds_to_string_for_array_types() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"lib",
"
package pkg is
type type_t is array (natural range 0 to 1) of bit;
alias my_to_string is to_string[type_t return string];
type bad_t is array (natural range 0 to 1) of integer;
alias bad_to_string is to_string[bad_t return string];
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
without_related(&diagnostics),
vec![Diagnostic::new(
code.sa("bad_to_string is ", "to_string"),
"Could not find declaration of 'to_string' with given signature",
ErrorCode::NoOverloadedWithSignature,
)],
)
}
#[test]
fn adds_to_string_for_enum_types() {
check_code_with_no_diagnostics(
"
package pkg is
type enum_t is (alpha, beta);
alias my_to_string is to_string[enum_t return string];
end package;
",
);
}
#[test]
fn no_error_for_duplicate_alias_of_implicit() {
check_code_with_no_diagnostics(
"
package pkg is
type type_t is array (natural range 0 to 1) of integer;
alias alias_t is type_t;
-- Should result in no error for duplication definiton of for example TO_STRING
end package;
",
);
}
#[test]
fn deallocate_is_defined_for_access_type() {
check_code_with_no_diagnostics(
"
package pkg is
type arr_t is array (natural range <>) of character;
type ptr_t is access arr_t;
end package;
package body pkg is
procedure theproc is
variable theptr: ptr_t;
begin
deallocate(theptr);
end procedure;
end package body;
",
);
}
#[test]
fn enum_implicit_function_is_added_on_use() {
check_code_with_no_diagnostics(
"
package pkg1 is
type enum_t is (alpha, beta);
end package;
use work.pkg1.enum_t;
package pkg is
alias my_to_string is to_string[enum_t return string];
end package;
",
);
}
#[test]
fn find_all_references_does_not_include_implicits() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"lib",
"
package pkg is
type enum_t is (alpha, beta);
alias my_to_string is to_string[enum_t return string];
end package;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq_unordered(
&root.find_all_references_pos(&code.s1("enum_t").pos()),
&[code.s("enum_t", 1).pos(), code.s("enum_t", 2).pos()],
);
}
#[test]
fn goto_references_for_implicit() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"lib",
"
package pkg is
type enum_t is (alpha, beta);
alias thealias is to_string[enum_t return string];
end package;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let to_string = code.s1("to_string");
assert_eq!(
root.search_reference_pos(to_string.source(), to_string.start()),
Some(code.s1("enum_t").pos())
);
}
#[test]
fn hover_for_implicit() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"lib",
"
package pkg is
type enum_t is (alpha, beta);
alias thealias is to_string[enum_t return string];
end package;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let to_string = code.s1("to_string");
assert_eq!(
root.format_declaration(
root.search_reference(to_string.source(), to_string.start())
.unwrap()
),
Some(
"\
-- function TO_STRING[enum_t return STRING]
-- Implicitly defined by:
type enum_t is (alpha, beta);
"
.to_owned()
)
);
}
#[test]
fn implicit_functions_on_physical_type() {
check_code_with_no_diagnostics(
"
package pkg is
type time_t is range 0 to 1
units
small;
big = 1000 small;
end units;
constant c0 : time_t := 10 small;
constant good1 : time_t := - c0;
constant good2 : time_t := + c0;
constant good3 : time_t := abs c0;
constant good4 : time_t := c0 + c0;
constant good5 : time_t := c0 - c0;
constant good6 : time_t := minimum(c0, c0);
constant good7 : time_t := maximum(c0, c0);
end package;
",
);
}
#[test]
fn implicit_functions_on_integer_type() {
check_code_with_no_diagnostics(
"
package pkg is
type type_t is range 0 to 1;
constant c0 : type_t := 10;
constant good1 : type_t := - c0;
constant good2 : type_t := + c0;
constant good3 : type_t := abs c0;
constant good4 : type_t := c0 + c0;
constant good5 : type_t := c0 - c0;
constant good6 : type_t := minimum(c0, c0);
constant good7 : type_t := maximum(c0, c0);
constant good8 : string := to_string(c0);
end package;
",
);
}
#[test]
fn implicit_real_vs_integer_functions() {
check_code_with_no_diagnostics(
"
package pkg is
constant x : real := real(2.0 / 2);
constant y : real := real(2.0 * 2);
constant z : real := real(2 * 2.0);
end package;
",
);
}

View File

@ -0,0 +1,183 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
// @TODO
// 5.4.2 Incomplete type declarations
// Prior to the end of the corresponding full type declaration, the only allowed use of a name that denotes a type
// declared by an incomplete type declaration is as the type mark in the subtype indication of an access type
// definition; no constraints are allowed in this subtype indication.
use super::*;
use crate::data::error_codes::ErrorCode;
use crate::data::SrcPos;
#[test]
fn allows_incomplete_type_definition() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package pkg is
type rec_t;
type rec_t is record
end record;
type enum_t;
type enum_t is (alpha, beta);
end package;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn error_on_duplicate_incomplete_type_definition() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
type rec_t;
type rec_t;
type rec_t is record
end record;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, duplicates(&code, &["rec_t"]));
}
#[test]
fn error_on_missing_full_type_definition_for_incomplete() {
let mut builder = LibraryBuilder::new();
let code_pkg = builder.code(
"libname",
"
package pkg is
type rec_t;
end package;
package body pkg is
-- Must appear in the same immediate declarative region
type rec_t is record
end record;
end package body;
",
);
let code_ent = builder.code(
"libname",
"
entity ent is
end entity;
architecture rtl of ent is
type rec_t;
begin
blk : block
-- Must appear in the same immediate declarative region
type rec_t is record
end record;
begin
end block;
end architecture;
",
);
let code_pkg2 = builder.code(
"libname",
"
-- To check that no duplicate errors are made when closing the immediate and extended regions
package pkg2 is
type rec_t;
end package;
package body pkg2 is
end package body;
",
);
let mut expected_diagnostics = Vec::new();
for code in [&code_pkg, &code_ent, &code_pkg2].iter() {
expected_diagnostics.push(missing_full_error(&code.s1("rec_t")));
}
expected_diagnostics.push(duplicate(&code_pkg, "rec_t", 1, 2));
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, expected_diagnostics);
}
#[test]
fn incomplete_type_references_point_to_full_definition() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
type rec_t;
type access_t is access rec_t;
type rec_t is record
node: access_t;
end record;
procedure proc(val : rec_t);
end package;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
// Reference from incomplete goes to full
for i in 1..=4 {
assert_eq!(
root.search_reference_pos(code.source(), code.s("rec_t", i).start()),
Some(code.s("rec_t", 3).pos()),
"{i}"
);
}
let references: Vec<_> = (1..=4).map(|idx| code.s("rec_t", idx).pos()).collect();
assert_eq!(
root.find_all_references_pos(&code.s("rec_t", 3).pos()),
references
);
}
#[test]
fn error_on_missing_full_type_definition_for_incomplete_still_defines_the_type() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
type rec_t;
type acces_t is access rec_t;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, vec![missing_full_error(&code.s1("rec_t"))]);
}
fn missing_full_error(pos: &impl AsRef<SrcPos>) -> Diagnostic {
let mut error = Diagnostic::new(
pos,
"Missing full type declaration of incomplete type 'rec_t'",
ErrorCode::MissingFullTypeDeclaration,
);
error.add_related(
pos,
"The full type declaration shall occur immediately within the same declarative part",
);
error
}

View File

@ -0,0 +1,357 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use crate::analysis::DesignRoot;
use crate::ast::search::*;
use crate::data::SrcPos;
use crate::named_entity::{EntityId, Reference};
use crate::syntax::TokenAccess;
use fnv::FnvHashSet;
use pretty_assertions::assert_eq;
#[test]
fn incremental_analysis_of_use_within_package() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package pkg is
constant const : natural := 0;
end package;
",
);
builder.code(
"libname",
"
use work.pkg.const;
package pkg2 is
end package;
",
);
check_incremental_analysis(builder, vec![]);
}
#[test]
fn incremental_analysis_of_package_use() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package pkg is
constant const : natural := 0;
end package;
",
);
builder.code(
"libname",
"
use work.pkg;
package pkg2 is
end package;
",
);
check_incremental_analysis(builder, vec![]);
}
#[test]
fn incremental_analysis_of_entity_architecture() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
entity ent is
end entity;
",
);
builder.code(
"libname",
"
architecture a of ent is
begin
end architecture;
",
);
check_incremental_analysis(builder, vec![]);
}
#[test]
fn incremental_analysis_of_package_and_body() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package pkg is
end package;
",
);
builder.code(
"libname",
"
package body pkg is
end package body;
",
);
check_incremental_analysis(builder, vec![]);
}
#[test]
fn incremental_analysis_of_entity_instance() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
begin
end architecture;
",
);
builder.code(
"libname",
"
entity ent2 is
end entity;
architecture a of ent2 is
begin
inst: entity work.ent;
end architecture;
",
);
check_incremental_analysis(builder, vec![]);
}
#[test]
fn incremental_analysis_of_configuration_instance() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
begin
end architecture;
",
);
builder.code(
"libname",
"
configuration cfg of ent is
for rtl
end for;
end configuration;
",
);
builder.code(
"libname",
"
entity ent2 is
end entity;
architecture a of ent2 is
begin
inst : configuration work.cfg;
end architecture;
",
);
check_incremental_analysis(builder, vec![]);
}
#[test]
fn incremental_analysis_library_all_collision() {
let mut builder = LibraryBuilder::new();
let lib1 = builder.code(
"libname1",
"
package pkg is
end package;
",
);
let lib2 = builder.code(
"libname2",
"
package pkg is
constant const : natural := 0;
end package;
",
);
let code = builder.code(
"libname3",
"
library libname1;
use libname1.all;
library libname2;
use libname2.all;
use pkg.const;
package pkg is
end package;
",
);
use super::visibility::hidden_error;
check_incremental_analysis(
builder,
vec![hidden_error(
&code,
"pkg",
1,
&[
(&code, "libname1.all", 1, false),
(&lib1, "pkg", 1, true),
(&code, "libname2.all", 1, false),
(&lib2, "pkg", 1, true),
],
)],
);
}
#[test]
fn incremental_analysis_of_package_and_body_with_deferred_constant() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package pkg is
constant deferred : natural;
end package;
",
);
builder.code(
"libname",
"
package body pkg is
constant deferred : natural := 0;
end package body;
",
);
check_incremental_analysis(builder, vec![]);
}
fn check_incremental_analysis(builder: LibraryBuilder, expected_diagnostics: Vec<Diagnostic>) {
let symbols = builder.symbols();
let codes = builder.take_code();
// Generate all combinations of removing and adding source
for i in 0..codes.len() {
let mut fresh_root = DesignRoot::new(symbols.clone());
add_standard_library(symbols.clone(), &mut fresh_root);
let mut root = DesignRoot::new(symbols.clone());
add_standard_library(symbols.clone(), &mut root);
for (j, (library_name, code)) in codes.iter().enumerate() {
root.add_design_file(library_name.clone(), code.design_file());
if i != j {
fresh_root.add_design_file(library_name.clone(), code.design_file());
} else {
fresh_root.ensure_library(library_name.clone());
}
}
let mut diagnostics = Vec::new();
root.analyze(&mut diagnostics);
check_diagnostics(diagnostics, expected_diagnostics.clone());
let (library_name, code) = &codes[i];
// Remove a files
root.remove_source(library_name.clone(), code.source());
check_analysis_equal(&mut root, &mut fresh_root);
// Add back files again
root.add_design_file(library_name.clone(), code.design_file());
fresh_root.add_design_file(library_name.clone(), code.design_file());
let diagnostics = check_analysis_equal(&mut root, &mut fresh_root);
// Ensure expected diagnostics when all files are added
check_diagnostics(diagnostics, expected_diagnostics.clone());
}
}
fn check_analysis_equal(got: &mut DesignRoot, expected: &mut DesignRoot) -> Vec<Diagnostic> {
let mut got_diagnostics = Vec::new();
got.analyze(&mut got_diagnostics);
let mut expected_diagnostics = Vec::new();
expected.analyze(&mut expected_diagnostics);
// Check that diagnostics are equal to doing analysis from scratch
check_diagnostics(got_diagnostics.clone(), expected_diagnostics);
// Check that all references are equal, ensures the incremental
// analysis has cleared references
let mut got_searcher = FindAnyReferences::default();
let _ = got.search(&mut got_searcher);
let mut expected_searcher = FindAnyReferences::default();
let _ = expected.search(&mut expected_searcher);
let got_refs: FnvHashSet<_> = got_searcher
.references
.into_iter()
.map(|id| got.get_ent(id).decl_pos())
.collect();
let expected_refs: FnvHashSet<_> = expected_searcher
.references
.into_iter()
.map(|id| expected.get_ent(id).decl_pos())
.collect();
let diff: FnvHashSet<_> = got_refs.symmetric_difference(&expected_refs).collect();
assert_eq!(diff, FnvHashSet::default());
got_diagnostics
}
/// Find any reference
/// Added to help ensure that there are no references to removed sources
#[derive(Default)]
struct FindAnyReferences {
references: Vec<EntityId>,
}
impl Searcher for FindAnyReferences {
fn search_pos_with_ref(
&mut self,
_ctx: &dyn TokenAccess,
_: &SrcPos,
reference: &Reference,
) -> SearchState {
if let Some(id) = reference.get() {
self.references.push(id);
};
NotFinished
}
}

View File

@ -0,0 +1,142 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
mod assignment_typecheck;
mod association_formal;
mod circular_dependencies;
mod context_clause;
mod custom_attributes;
mod declarations;
mod deferred_constant;
mod hierarchy;
mod homographs;
mod implicit;
mod incomplete_type;
mod incremental_analysis;
mod package_instance;
mod protected_type;
mod resolves_design_units;
mod resolves_names;
mod resolves_type_mark;
mod sensitivity_list;
mod subprogram_arguments;
mod subprogram_instance;
mod tool_directive;
mod typecheck_expression;
mod util;
mod view_declarations;
mod visibility;
use std::cell::RefCell;
use std::path::PathBuf;
use vhdl_lang::TokenSpan;
pub use self::util::*;
use crate::ast::Designator;
use crate::ast::UnitId;
pub use crate::data::Diagnostic;
use crate::data::NoDiagnostics;
pub use crate::syntax::test::*;
use crate::syntax::Token;
use super::analyze::AnalyzeContext;
use super::scope::*;
use super::DesignRoot;
use crate::named_entity::*;
use crate::Source;
pub(super) struct TestSetup<'a> {
builder: RefCell<LibraryBuilder>,
root: DesignRoot,
arena: Arena,
pub scope: Scope<'a>,
}
impl<'a> TestSetup<'a> {
pub fn new() -> Self {
let builder = LibraryBuilder::new();
let (mut root, _) = builder.get_analyzed_root();
root.ensure_library(root.symbol_utf8("libname"));
let arena = Arena::new(ArenaId::default());
Self {
arena,
root,
builder: RefCell::new(builder),
scope: Scope::new(Region::default()),
}
}
#[allow(clippy::ptr_arg)]
pub fn ctx<'t>(&'a self, tokens: &'t Vec<Token>) -> AnalyzeContext<'a, 't> {
let ctx = AnalyzeContext::new(
&self.root,
&UnitId::package(
&self.root.symbol_utf8("libname"),
&self.root.symbol_utf8("dummy"),
),
Source::inline(&PathBuf::new(), ""),
&self.arena,
tokens,
);
ctx.add_implicit_context_clause(&self.scope).unwrap();
ctx
}
pub fn snippet(&self, code: &str) -> Code {
self.builder.borrow_mut().snippet(code)
}
pub fn declarative_part(&'a self, code: &str) -> Code {
let code = self.snippet(code);
let dummy_parent = self.arena.alloc(
Designator::Anonymous(0),
None,
Related::None,
AnyEntKind::Library,
None,
TokenSpan::for_library(),
Some(code.source().clone()),
);
self.ctx(&code.tokenize())
.analyze_declarative_part(
&self.scope,
dummy_parent,
code.declarative_part().as_mut(),
&mut NoDiagnostics,
)
.unwrap();
code
}
pub fn lookup(&'a self, sym: &str) -> EntRef<'a> {
// We cheat and create a source pos as the lookup method requires it
let designator = self.snippet(sym).designator();
self.scope
.lookup(&designator.item)
.unwrap()
.into_non_overloaded()
.unwrap()
}
pub fn lookup_overloaded(&'a self, code: Code) -> OverloadedEnt<'a> {
let des = code.designator();
if let NamedEntities::Overloaded(overloaded) = self.scope.lookup(&des.item).unwrap() {
overloaded
.entities()
.find(|ent| ent.decl_pos() == Some(&code.pos()))
.unwrap()
} else {
panic!("Expected overloaded name");
}
}
pub fn lookup_type(&'a self, sym: &str) -> TypeEnt<'a> {
TypeEnt::from_any(self.lookup(sym)).unwrap()
}
}

View File

@ -0,0 +1,687 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use pretty_assertions::assert_eq;
use vhdl_lang::data::error_codes::ErrorCode;
#[test]
fn package_name_must_be_visible_in_package_instance() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (const : natural);
end package;
package ipkg_err is new gpkg generic map (const => 0);
package ipkg_ok is new work.gpkg generic map (const => 0);
package nested is
package ipkg_err is new gpkg generic map (const => 0);
package ipkg_ok is new work.gpkg generic map (const => 0);
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s("gpkg", 2),
"No declaration of 'gpkg'",
ErrorCode::Unresolved,
),
Diagnostic::new(
code.s("gpkg", 4),
"No declaration of 'gpkg'",
ErrorCode::Unresolved,
),
],
);
}
#[test]
fn package_name_must_be_an_uninstantiated_package_in_package_instance() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
constant const : natural := 0;
end package;
package ipkg is new work.pkg generic map (const => 0);
package nested is
package ipkg2 is new work.pkg.const generic map (const => 0);
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::mismatched_kinds(
code.s1("work.pkg"),
"'work.pkg' is not an uninstantiated generic package",
),
Diagnostic::mismatched_kinds(
code.s1("work.pkg.const"),
"'work.pkg.const' is not an uninstantiated generic package",
),
],
);
}
#[test]
fn resolves_generic_package_interface_list() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (type type_t);
end package;
package ipkg1 is new work.gpkg
generic map (
missing => integer
);
package ipkg2 is new work.gpkg
generic map (
type_t => missing
);
package ipkg3 is new work.gpkg
generic map (
type_t => integer
);
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s("missing", 1),
"No declaration of 'missing'",
ErrorCode::Unresolved,
),
Diagnostic::new(
code.s("missing", 2),
"No declaration of 'missing'",
ErrorCode::Unresolved,
),
],
);
let typ = root
.search_reference(
code.source(),
code.s1("type_t => integer").s1("type_t").pos().start(),
)
.unwrap();
assert_eq!(typ.decl_pos(), Some(&code.s1("type_t").pos()));
assert_eq!(root.format_declaration(typ), Some("type type_t".to_owned()));
}
#[test]
fn generic_package_interface_kind_mismatch() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (
type type_t;
constant c0 : integer
);
end package;
package good_pkg is new work.gpkg
generic map (
type_t => integer,
c0 => 0
);
package bad_pkg1 is new work.gpkg
generic map (
type_t => 16#bad#,
c0 => natural
);
package bad_pkg2 is new work.gpkg
generic map (
type_t => work,
c0 => 0
);
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::mismatched_kinds(
code.s("16#bad#", 1),
"Cannot map expression to type generic",
),
Diagnostic::mismatched_kinds(
code.s1("natural"),
"subtype 'NATURAL' cannot be used in an expression",
),
Diagnostic::mismatched_kinds(
code.s1("=> work").s1("work"),
"Expected type, got library libname",
),
],
);
}
#[test]
fn subtype_constraints_are_supported() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package pkg is
type rec_t is record
field: integer_vector;
end record;
end package;
package gpkg is
generic (
type type_t
);
end package;
package arr_pkg is new work.gpkg
generic map (
type_t => integer_vector(0 to 3)
);
use work.pkg.rec_t;
package rec_pkg is new work.gpkg
generic map (
type_t => rec_t(field(0 to 3))
);
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn interface_subprogram() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (
type type_t;
function to_string(value : type_t) return string
);
end package;
package pkg is
package ipkg is new work.gpkg
generic map (
type_t => integer,
to_string => to_string);
end package;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let typ = root
.search_reference(
code.source(),
code.s1("to_string => to_string")
.s1("to_string")
.pos()
.start(),
)
.unwrap();
assert_eq!(typ.decl_pos(), Some(&code.s1("to_string").pos()));
assert!(root.format_declaration(typ).is_some());
assert!(root
.search_reference(code.source(), code.sa("to_string => ", "to_string").start())
.is_some());
}
#[test]
fn interface_subprogram_rhs_not_a_subprogram() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (
type type_t;
function to_string(value : type_t) return string
);
end package;
package pkg is
package ipkg is new work.gpkg
generic map (
type_t => integer,
to_string => character);
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::mismatched_kinds(
code.s1("character"),
"Cannot map type 'CHARACTER' to subprogram generic",
)],
);
}
#[test]
fn interface_subprogram_rhs_does_not_match_signature() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (
type type_t;
function to_string(value : type_t) return string
);
end package;
package pkg is
function my_to_string(arg: real) return string;
package ipkg is new work.gpkg
generic map (
type_t => integer,
to_string => my_to_string);
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::mismatched_kinds(
code.sa("to_string => ", "my_to_string"),
"Cannot map 'my_to_string' to subprogram generic to_string[INTEGER return STRING]",
)
.related(
code.s1("my_to_string"),
"Does not match function my_to_string[REAL return STRING]",
)],
);
}
#[test]
fn interface_subprogram_operator() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (
type type_t;
function match(l, r : type_t) return boolean
);
end package;
package pkg is
package ipkg is new work.gpkg
generic map (
type_t => integer,
match => \"=\");
package bad_pkg is new work.gpkg
generic map (
type_t => integer,
match => \"invalid\");
end package;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("\"invalid\""),
"Invalid operator symbol",
ErrorCode::InvalidOperatorSymbol,
)],
);
}
#[test]
fn interface_package() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg_base is
generic (
type type_t
);
end package;
package gpkg is
generic (
package iface_pkg is new work.gpkg_base generic map (<>)
);
end package;
package ipkg_base is new work.gpkg_base
generic map (
type_t => integer);
package ipkg is new work.gpkg
generic map (
iface_pkg => work.ipkg_base
);
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let typ = root
.search_reference(
code.source(),
code.s1("iface_pkg => ").s1("iface_pkg").pos().start(),
)
.unwrap();
assert_eq!(typ.decl_pos(), Some(&code.s1("iface_pkg").pos()));
assert!(root.format_declaration(typ).is_some());
}
#[test]
fn generics_are_not_visible_by_selection() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (
type type_t
);
constant also_visible : natural := 0;
end package;
package pkg is
package ipkg is new work.gpkg
generic map (
type_t => integer);
subtype sub_t is ipkg.type_t;
constant c1 : natural := ipkg.also_visible;
end package;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("ipkg.type_t").s1("type_t"),
"No declaration of 'type_t' within package instance 'ipkg'",
ErrorCode::Unresolved,
)],
);
}
#[test]
fn intantiate_items_with_correct_type() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package gpkg is
generic (
type type_t;
value: type_t
);
subtype sub_t is type_t;
end package;
package pkg is
package ipkg is new work.gpkg
generic map (
type_t => integer,
value => 0);
constant c1 : ipkg.sub_t := 0;
end package;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
}
#[test]
fn interface_type_has_comparison_operations() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package gpkg is
generic (
type type_t
);
procedure check(a, b : type_t);
end package;
package body gpkg is
procedure check(a, b : type_t) is
constant c0 : boolean := a = b;
constant c1 : boolean := a /= b;
begin
end;
end package body;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
}
#[test]
fn hover_and_references_for_instantiated_entities() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (
type type_t
);
subtype sub_t is type_t;
end package;
package pkg is
package ipkg is new work.gpkg
generic map (
type_t => integer);
constant c1 : ipkg.sub_t := 0;
end package;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let sub_t = root
.search_reference(
code.source(),
code.s1("ipkg.sub_t").s1("sub_t").pos().start(),
)
.unwrap();
assert_eq!(sub_t.decl_pos(), Some(&code.s1("sub_t").pos()));
assert_eq!(
root.format_declaration(sub_t),
Some("subtype sub_t is type_t;".to_owned())
);
assert_eq!(
root.find_all_references(sub_t),
vec![code.s("sub_t", 1).pos(), code.s("sub_t", 2).pos()]
);
// Ensure find all reference from within generic package also matches
assert_eq!(
root.find_all_references(
root.search_reference(code.source(), code.s1("sub_t").pos().start())
.unwrap()
),
vec![code.s("sub_t", 1).pos(), code.s("sub_t", 2).pos()]
);
}
#[test]
fn references_of_instantiated_do_not_include_siblings() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package gpkg is
generic (
type type_t
);
subtype sub_t is type_t;
end package;
package pkg is
package ipkg0 is new work.gpkg
generic map (
type_t => integer);
package ipkg1 is new work.gpkg
generic map (
type_t => natural);
constant c0 : ipkg0.sub_t := 0;
constant c1 : ipkg1.sub_t := 0;
end package;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
// References from parent matches all sibling
assert_eq!(
root.find_all_references(
root.search_reference(code.source(), code.s1("sub_t").pos().start())
.unwrap()
),
vec![
code.s("sub_t", 1).pos(),
code.s("sub_t", 2).pos(),
code.s("sub_t", 3).pos()
]
);
// Siblings only include parent
assert_eq!(
root.find_all_references(
root.search_reference(code.source(), code.s("sub_t", 2).pos().start())
.unwrap()
),
vec![code.s("sub_t", 1).pos(), code.s("sub_t", 2).pos(),]
);
// Siblings only include parent
assert_eq!(
root.find_all_references(
root.search_reference(code.source(), code.s("sub_t", 3).pos().start())
.unwrap()
),
vec![code.s("sub_t", 1).pos(), code.s("sub_t", 3).pos(),]
);
}
#[test]
pub fn using_all_from_generic_package() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package foo is
generic (
x: natural := 1
);
end package;
package bar is
generic (
package foo0 is new work.foo
generic map (<>)
);
use foo0.all;
end package;
",
);
let diag = builder.analyze();
check_no_diagnostics(&diag);
}
#[test]
pub fn aliases_in_generic_packages() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package test_pkg is
generic (N : integer);
type my_type is array (N - 1 downto 0) of bit;
alias also_my_type is my_type;
end package;
package pkg_inst is new work.test_pkg generic map (N => 8);
use work.pkg_inst.all;
entity test_top_entity is
end entity;
architecture rtl of test_top_entity is
signal my_sig : my_type;
signal my_sig2: also_my_type;
begin
my_sig <= my_sig2;
end architecture;
",
);
let diag = builder.analyze();
check_no_diagnostics(&diag);
}

View File

@ -0,0 +1,363 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use vhdl_lang::data::error_codes::ErrorCode;
#[test]
fn error_on_missing_protected_body() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg_no_body is
type a1 is protected
end protected;
end package;
package pkg is
type b1 is protected
end protected;
end package;
package body pkg is
end package body;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s1("a1"),
"Missing body for protected type 'a1'",
ErrorCode::MissingProtectedBodyType,
),
Diagnostic::new(
code.s1("b1"),
"Missing body for protected type 'b1'",
ErrorCode::MissingProtectedBodyType,
),
],
);
}
#[test]
fn error_on_missing_protected_type_for_body() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg_no_body is
type a1 is protected body
end protected body;
end package;
package pkg is
end package;
package body pkg is
type b1 is protected body
end protected body;
type b1 is protected
end protected;
end package body;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s1("a1"),
"No declaration of protected type 'a1'",
ErrorCode::Unresolved,
),
Diagnostic::new(
code.s1("b1"),
"No declaration of protected type 'b1'",
ErrorCode::Unresolved,
),
Diagnostic::new(
code.s("b1", 2),
"Missing body for protected type 'b1'",
ErrorCode::MissingProtectedBodyType,
),
],
);
}
#[test]
fn allows_protected_type_and_body_with_same_name() {
check_code_with_no_diagnostics(
"
package pkg is
type prot_t is protected
end protected;
type prot_t is protected body
end protected body;
end package;
",
);
}
#[test]
fn allows_protected_type_and_body_in_package_header_and_body() {
check_code_with_no_diagnostics(
"
package pkg is
type prot_t is protected
end protected;
end package;
package body pkg is
type prot_t is protected body
end protected body;
end package body;
",
);
}
#[test]
fn forbid_duplicate_protected_type() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
type prot_t is protected
end protected;
type prot_t is protected
end protected;
type prot_t is protected body
end protected body;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, duplicates(&code, &["prot_t"]));
}
#[test]
fn forbid_duplicate_protected_type_body() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
type prot_t is protected
end protected;
type prot_t is protected body
end protected body;
type prot_t is protected body
end protected body;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, vec![duplicate(&code, "prot_t", 2, 3)]);
}
#[test]
fn protected_type_is_visible_in_declaration() {
check_code_with_no_diagnostics(
"
package pkg1 is
type prot_t is protected
procedure proc(val : inout prot_t);
end protected;
type prot_t is protected body
procedure proc(val : inout prot_t) is
begin
end;
end protected body;
end package;",
);
}
#[test]
fn forbid_incompatible_deferred_items() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
-- Protected type vs constant
type a1 is protected
end protected;
constant a1 : natural := 0;
-- Just to avoid missing body error
type a1 is protected body
end protected body;
-- Deferred constant vs protected body
constant b1 : natural;
type b1 is protected body
end protected body;
end package;
package body pkg is
constant b1 : natural := 0;
end package body;
",
);
let diagnostics = builder.analyze();
let expected = vec![
duplicate(&code, "a1", 1, 2),
Diagnostic::new(
code.s("b1", 2),
"'b1' is not a protected type",
ErrorCode::TypeMismatch,
),
];
check_diagnostics(diagnostics, expected);
}
#[test]
fn protected_type_body_extends_declaration() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg1 is
type prot_t is protected
function fun1 return natural;
function fun2 return natural;
end protected;
type prot_t is protected body
-- Function 2 should be visible before
function fun1 return natural is
begin
return fun2;
end;
function private return natural is
begin
return missing;
end;
function fun2 return natural is
begin
return 0;
end;
end protected body;
end package;",
);
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, vec![missing(&code, "missing", 1)]);
}
#[test]
fn protected_type_body_reference() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent1 is
end ent1;
architecture arch of ent1 is
type prot_t is protected
end protected;
type prot_t is protected body
end protected body;
shared variable var : prot_t;
begin
end architecture;",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("prot_t", 2).start()),
Some(code.s("prot_t", 2).pos())
);
assert_eq!(
root.search_reference_pos(code.source(), code.s("prot_t", 3).start()),
Some(code.s("prot_t", 2).pos())
);
let ptype = root
.search_reference(code.source(), code.s1("prot_t").start())
.unwrap();
assert_eq!(
root.find_all_references(ptype),
vec![
code.s("prot_t", 1).pos(),
code.s("prot_t", 2).pos(),
code.s("prot_t", 3).pos()
]
);
assert_eq!(
root.find_definition_of(ptype).unwrap().decl_pos(),
Some(&code.s("prot_t", 2).pos())
);
}
/// This was a bug where a procedure declared between the protected type and the body
/// did not have the same signature as the definition which was after the body
#[test]
fn protected_type_and_body_result_in_the_same_signature() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
end package;
package body pkg is
type prot_t is protected
end protected;
procedure myproc(arg : prot_t);
type prot_t is protected body
end protected body;
procedure myproc(arg : prot_t) is
begin
end procedure;
end package body;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.find_all_references_pos(&code.s1("prot_t").pos()).len(),
4
);
assert_eq!(
root.find_all_references_pos(&code.s1("myproc").pos()).len(),
2
);
}

View File

@ -0,0 +1,550 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::resolves_type_mark::kind_error;
use super::*;
use vhdl_lang::data::error_codes::ErrorCode;
#[test]
fn error_on_configuration_before_entity_in_same_file() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
configuration cfg of ent is
for rtl
end for;
end configuration;
entity ent is
end entity;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s("cfg", 1),
"Configuration 'cfg' declared before entity 'ent'",
ErrorCode::DeclaredBefore,
)],
);
}
#[test]
fn error_on_configuration_of_missing_entity() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
configuration cfg of ent is
for rtl
end for;
end configuration;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s("ent", 1),
"No primary unit 'ent' within library 'libname'",
ErrorCode::Unresolved,
)],
);
}
#[test]
fn good_configurations() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
entity ent is
end entity;
configuration cfg_good1 of ent is
for rtl
end for;
end configuration;
configuration cfg_good2 of work.ent is
for rtl
end for;
end configuration;
library libname;
configuration cfg_good3 of libname.ent is
for rtl
end for;
end configuration;
",
);
check_no_diagnostics(&builder.analyze());
}
#[test]
fn error_on_configuration_of_entity_outside_of_library() {
let mut builder = LibraryBuilder::new();
builder.code(
"lib2",
"
entity ent is
end entity;",
);
let code = builder.code(
"libname",
"
library lib2;
configuration cfg of lib2.ent is
for rtl
end for;
end configuration;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s("lib2", 2),
"Configuration must be within the same library 'libname' as the corresponding entity",
ErrorCode::ConfigNotInSameLibrary,
)],
);
}
#[test]
fn search_reference_from_configuration_to_entity() {
check_search_reference(
"
entity decl is
end entity;
configuration cfg_good1 of decl is
for rtl
end for;
end configuration;
configuration cfg_good2 of work.decl is
for rtl
end for;
end configuration;
",
);
}
#[test]
fn error_on_architecture_of_missing_entity() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
architecture a of missing is
begin
end architecture;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s("missing", 1),
"No primary unit 'missing' within library 'libname'",
ErrorCode::Unresolved,
)],
);
}
#[test]
fn error_on_architecture_before_entity_in_same_file() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
architecture aname of ent is
begin
end architecture;
entity ent is
end entity;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s("aname", 1),
"Architecture 'aname' of 'ent' declared before entity 'ent'",
ErrorCode::DeclaredBefore,
)],
);
}
#[test]
fn error_on_body_of_missing_package() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package body missing is
end package body;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s("missing", 1),
"No primary unit 'missing' within library 'libname'",
ErrorCode::Unresolved,
)],
);
}
#[test]
fn error_on_package_body_before_package_in_same_file() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package body pkg is
end package body;
package pkg is
end package;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s("pkg", 1),
"Package body 'pkg' declared before package 'pkg'",
ErrorCode::DeclaredBefore,
)],
);
}
#[test]
fn resolves_reference_from_architecture_to_entity() {
check_search_reference(
"
entity decl is
end entity;
architecture a of decl is
begin
end architecture;
",
);
}
#[test]
fn resolves_reference_from_package_body_to_package() {
check_search_reference(
"
package decl is
end package;
package body decl is
end package body;
",
);
}
#[test]
fn resolves_reference_to_entity_instance() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ename1 is
end entity;
architecture a of ename1 is
begin
end architecture;
entity ename2 is
end entity;
architecture a of ename2 is
begin
bad_inst : entity work.missing;
inst : entity work.ename1;
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("missing"),
"No primary unit 'missing' within library 'libname'",
ErrorCode::Unresolved,
)],
);
// From reference position
assert_eq!(
root.search_reference_pos(code.source(), code.s("ename1", 3).start()),
Some(code.s("ename1", 1).pos())
);
// Find all references
assert_eq_unordered(
&root.find_all_references_pos(&code.s1("ename1").pos()),
&[
code.s("ename1", 1).pos(),
code.s("ename1", 2).pos(),
code.s("ename1", 3).pos(),
],
);
}
#[test]
fn resolves_component_instance() {
check_missing(
"
entity ent is
end entity;
architecture a of ent is
begin
inst : component missing;
end architecture;
",
);
}
#[test]
fn search_component_instance() {
check_search_reference(
"
entity ent is
end entity;
architecture a of ent is
component decl is
end component;
begin
inst : component decl;
end architecture;
",
);
}
#[test]
fn resolves_configuration_instance() {
check_missing(
"
entity ent is
end entity;
architecture a of ent is
begin
inst : configuration missing;
end architecture;
",
);
}
#[test]
fn search_configuration_instance() {
check_search_reference(
"
entity ent is
end entity;
configuration decl of ent is
for a
end for;
end configuration;
architecture a of ent is
begin
inst : configuration work.decl;
end architecture;
",
);
}
#[test]
fn resolves_reference_to_package_body() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
end package;
package body pkg is
end package body;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
// From declaration position
assert_eq!(
root.search_reference_pos(code.source(), code.s("pkg", 1).start()),
Some(code.s("pkg", 1).pos())
);
// From reference position
assert_eq!(
root.search_reference(code.source(), code.s("pkg", 2).start())
.unwrap()
.declaration()
.decl_pos(),
Some(&code.s("pkg", 1).pos())
);
// Find all references
assert_eq_unordered(
&root.find_all_references_pos(&code.s1("pkg").pos()),
&[code.s("pkg", 1).pos(), code.s("pkg", 2).pos()],
);
assert_eq_unordered(
&root.find_all_references_pos(&code.s("pkg", 2).pos()),
&[code.s("pkg", 1).pos(), code.s("pkg", 2).pos()],
);
}
#[test]
fn component_instantiation_is_correct() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
type enum_t is (alpha, beta);
begin
inst : component enum_t;
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![kind_error(
&code,
"enum_t",
2,
1,
"component",
"type 'enum_t'",
)],
);
}
#[test]
fn entity_instantiation_is_correct() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package bad is
end package;
entity ent is
end entity;
architecture a of ent is
begin
inst : entity work.bad;
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![kind_error(&code, "bad", 2, 1, "entity", "package 'bad'")],
);
}
#[test]
fn configuration_instantiation_is_correct() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity bad is
end entity;
entity ent is
end entity;
architecture a of ent is
begin
inst : configuration work.bad;
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![kind_error(
&code,
"bad",
2,
1,
"configuration",
"entity 'bad'",
)],
);
}
#[test]
fn empty_component_instantiation() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
component empty
end component;
begin
inst: empty;
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference(code.source(), code.sa("inst: ", "empty").start())
.unwrap()
.decl_pos(),
Some(&code.s1("empty").pos())
);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,513 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use pretty_assertions::assert_eq;
use vhdl_lang::data::error_codes::ErrorCode;
#[test]
fn resolves_type_mark_in_subtype_indications() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg1 is
-- Object declaration
constant const : natural := 0;
constant const2 : missing := 0;
-- File declaration
file fil : std.textio.text;
file fil2 : missing;
-- Alias declaration
alias foo : natural is const;
alias foo2 : missing is const;
-- Array type definiton
type arr_t is array (natural range <>) of natural;
type arr_t2 is array (natural range <>) of missing;
-- Access type definiton
type acc_t is access natural;
type acc_t2 is access missing;
-- Subtype definiton
subtype sub_t is natural range 0 to 1;
subtype sub_t2 is missing range 0 to 1;
-- Record definition
type rec_t is record
f1 : natural;
f2 : missing;
end record;
-- Interface file
procedure p1 (fil : std.textio.text);
procedure p2 (fil : missing);
-- Interface object
function f1 (const : natural) return natural;
function f2 (const : missing) return natural;
end package;",
);
let expected = (0..9)
.map(|idx| {
Diagnostic::new(
code.s("missing", 1 + idx),
"No declaration of 'missing'",
ErrorCode::Unresolved,
)
})
.collect();
let diagnostics = builder.analyze();
check_diagnostics(diagnostics, expected);
}
#[test]
fn resolves_return_type() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
function f1 (const : natural) return natural;
function f2 (const : natural) return missing;
end package;",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("missing"),
"No declaration of 'missing'",
ErrorCode::Unresolved,
)],
);
}
#[test]
fn resolves_attribute_declaration_type_mark() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
attribute attr : string;
attribute attr2 : missing;
end package;",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("missing"),
"No declaration of 'missing'",
ErrorCode::Unresolved,
)],
);
}
#[test]
fn search_resolved_type_mark() {
let mut builder = LibraryBuilder::new();
let code1 = builder.code(
"libname",
"
package pkg is
type typ_t is (foo, bar);
end package;",
);
let code2 = builder.code(
"libname",
"
use work.pkg.all;
package pkg2 is
constant c : typ_t := bar;
end package;",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let decl_pos = code1.s1("typ_t").pos();
// Cursor before symbol
assert_eq!(
root.search_reference_pos(code2.source(), code2.s1(" typ_t").start()),
None
);
// Cursor at beginning of symbol
assert_eq!(
root.search_reference_pos(code2.source(), code2.s1("typ_t").start()),
Some(decl_pos.clone())
);
// Cursor at end of symbol
assert_eq!(
root.search_reference_pos(code2.source(), code2.s1("typ_t").end()),
Some(decl_pos)
);
// Cursor after end of symbol
assert_eq!(
root.search_reference_pos(code2.source(), code2.s1("typ_t ").end()),
None
);
}
#[test]
fn search_reference_on_declaration_returns_declaration() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
type typ_t is (foo, bar);
end package;",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let decl_pos = code.s1("typ_t").pos();
assert_eq!(
root.search_reference_pos(code.source(), decl_pos.start()),
Some(decl_pos)
);
}
#[test]
fn find_all_references_of_type_mark() {
let mut builder = LibraryBuilder::new();
let code1 = builder.code(
"libname",
"
package pkg is
type typ_t is (foo, bar);
constant c1 : typ_t := bar;
end package;",
);
let code2 = builder.code(
"libname",
"
use work.pkg.all;
package pkg2 is
constant c2 : typ_t := bar;
constant c3 : typ_t := bar;
end package;",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let references = vec![
code1.s("typ_t", 1).pos(),
code1.s("typ_t", 2).pos(),
code2.s("typ_t", 1).pos(),
code2.s("typ_t", 2).pos(),
];
assert_eq_unordered(
&root.find_all_references_pos(&code1.s1("typ_t").pos()),
&references,
);
}
#[test]
fn find_references_in_record_defintions() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
type typ_t is (foo, bar);
type rec_t is record
field : typ_t;
end record;
end package;",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
// Goto declaration from declaration
assert_eq!(
root.search_reference_pos(code.source(), code.s("typ_t", 1).end()),
Some(code.s("typ_t", 1).pos())
);
// Goto declaration from reference
assert_eq!(
root.search_reference_pos(code.source(), code.s("typ_t", 2).end()),
Some(code.s("typ_t", 1).pos())
);
assert_eq_unordered(
&root.find_all_references_pos(&code.s("typ_t", 1).pos()),
&[code.s("typ_t", 1).pos(), code.s("typ_t", 2).pos()],
);
}
#[test]
fn find_references_in_array_defintions() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
-- Dummy type to use where we do not care
type placeholder_t is (a, b);
-- The type we want to resolve references to
type typ_t is (foo, bar);
-- With index subtype constraint
type arr1_t is array (typ_t range <>) of typ_t;
type arr2_t is array (missing_t range <>) of placeholder_t;
type arr3_t is array (typ_t) of typ_t;
type arr4_t is array (missing_t) of placeholder_t;
end package;",
);
let (root, diagnostics) = builder.get_analyzed_root();
let num_missing = 2;
let expected = (1..=num_missing)
.map(|idx| {
Diagnostic::new(
code.s("missing_t", idx),
"No declaration of 'missing_t'",
ErrorCode::Unresolved,
)
})
.collect();
check_diagnostics(diagnostics, expected);
let num_references = 5;
let mut references = Vec::new();
for i in 1..=num_references {
let refpos = code.s("typ_t", i).pos();
assert_eq!(
root.search_reference_pos(code.source(), refpos.end()),
Some(code.s("typ_t", 1).pos()),
"i={}",
i
);
references.push(refpos.clone());
}
assert_eq_unordered(
&root.find_all_references_pos(&code.s("typ_t", 1).pos()),
&references,
);
}
#[test]
fn search_type_mark_in_file_object_declaration() {
check_search_reference(
"
package pkg is
type decl is file of character;
file foo : decl;
end package;
",
);
}
#[test]
fn error_on_type_mark_with_overloaded() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
end package;
package body pkg is
function bad return natural is
begin
end function;
constant err : bad := 0;
end package body;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![kind_error(
&code,
"bad",
2,
1,
"type",
"function bad[return NATURAL]",
)],
);
}
#[test]
fn error_on_type_mark_with_non_type() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
end package;
package body pkg is
constant bad : natural := 0;
constant err : bad := 0;
end package body;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![kind_error(&code, "bad", 2, 1, "type", "constant 'bad'")],
);
}
#[test]
fn error_on_type_mark_with_alias_of_non_type() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
constant const : natural := 0;
alias bad is const;
constant name : bad := 0;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![kind_error(
&code,
"bad",
2,
1,
"type",
"alias 'bad' of constant",
)],
);
}
#[test]
fn test_qualified_expression_must_be_a_type() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
constant bar : natural := 0;
constant foo : natural := bar'(0);
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![kind_error(&code, "bar", 2, 1, "type", "constant 'bar'")],
);
}
#[test]
fn test_type_mark_with_subtype_attribute_is_ok() {
let mut builder = LibraryBuilder::new();
builder.in_declarative_region(
"
signal sig0 : integer_vector(0 to 7);
type rec_t is record
field: integer_vector(0 to 7);
end record;
signal rec : rec_t;
attribute attr : sig0'subtype;
signal sig1 : sig0'subtype;
signal sig2 : rec.field'subtype;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn check_good_type_marks() {
check_code_with_no_diagnostics(
"
package gpkg is
-- Interface type
generic (type type_t);
type record_t is record
field : type_t;
end record;
end package;
entity ent is
end entity ent;
architecture arch of ent is
type incomplete;
-- Incomplete type
type access_t is access incomplete;
type incomplete is record
field : access_t;
end record;
type ptype_t is protected
end protected;
type ptype_t is protected body
end protected body;
-- Protected type
shared variable ptype : ptype_t;
-- Other type
type enum_t is (alpha, beta);
constant const : enum_t := alpha;
-- Alias of type
alias alias_t is enum_t;
constant const2 : alias_t := alpha;
begin
end architecture;
",
);
}
pub fn kind_error(
code: &Code,
name: &str,
occ: usize,
occ_decl: usize,
expected: &str,
got: &str,
) -> Diagnostic {
Diagnostic::mismatched_kinds(code.s(name, occ), format!("Expected {expected}, got {got}"))
.related(code.s(name, occ_decl), "Defined here")
}

View File

@ -0,0 +1,121 @@
//! This Source Code Form is subject to the terms of the Mozilla Public
//! License, v. 2.0. If a copy of the MPL was not distributed with this file,
//! You can obtain one at http://mozilla.org/MPL/2.0/.
//!
//! Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use vhdl_lang::data::error_codes::ErrorCode;
#[test]
fn must_be_object_name() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
end package;
package body pkg is
procedure proc(signal good : in bit) is
begin
wait on good;
wait on proc;
end;
end package body;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("wait on proc").s1("proc"),
"procedure proc[BIT] is not a signal and cannot be in a sensitivity list",
ErrorCode::DisallowedInSensitivityList,
)],
)
}
#[test]
fn must_be_signal_name() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
end package;
package body pkg is
constant c0 : bit := '0';
procedure proc(signal good : in bit) is
begin
wait on good;
wait on c0;
end;
end package body;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("wait on c0").s1("c0"),
"constant 'c0' is not a signal and cannot be in a sensitivity list",
ErrorCode::DisallowedInSensitivityList,
)],
)
}
#[test]
fn must_not_be_output() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
end package;
package body pkg is
procedure proc(signal bad : out bit) is
begin
wait on bad;
end;
end package body;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("wait on bad").s1("bad"),
"interface signal 'bad' of mode out cannot be in a sensitivity list",
ErrorCode::DisallowedInSensitivityList,
)],
)
}
#[test]
fn may_be_output_port() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
entity ent is
port (oport : out bit);
end entity;
architecture a of ent is
begin
main: process (oport)
begin
end process main;
end architecture;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
}

View File

@ -0,0 +1,432 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2022, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use vhdl_lang::data::error_codes::ErrorCode;
#[test]
fn wrong_number_of_arguments() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
function subpgm(arg: natural) return natural
is begin
end;
signal good : natural := subpgm(0);
signal bad : natural := subpgm;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("subpgm;").s1("subpgm"),
"Invalid call to 'subpgm'",
ErrorCode::InvalidCall,
)
.related(code.s1("subpgm"), "Missing association of parameter 'arg'")],
);
}
#[test]
fn procedure_calls() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"lib",
"
entity ent is
end entity;
architecture a of ent is
function subpgm(arg: natural) return natural
is begin
end;
procedure theproc(arg: natural)
is begin
end;
signal thesig : integer_vector(0 to 1);
begin
subpgm(0);
theproc(0);
thesig(0);
end architecture;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s("subpgm", 2),
"Invalid procedure call",
ErrorCode::InvalidCall,
)
.related(
code.s("subpgm", 1),
"function subpgm[NATURAL return NATURAL] is not a procedure",
),
Diagnostic::mismatched_kinds(
code.s("thesig", 2),
"signal 'thesig' of array type 'INTEGER_VECTOR' is not a procedure",
),
],
);
}
#[test]
fn resolve_overloaded_subprogram_by_return_type() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
function subpgm(arg: natural) return character
is begin
end;
function subpgm(arg: natural) return natural
is begin
end;
signal good1 : natural := subpgm(0);
signal good2 : character := subpgm(0);
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("subpgm", 3).end()),
Some(code.s("subpgm", 2).pos())
);
assert_eq!(
root.search_reference_pos(code.source(), code.s("subpgm", 4).end()),
Some(code.s("subpgm", 1).pos())
);
}
#[test]
fn resolves_formals() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
function subpgm(arg1 : integer) return integer;
constant good : integer := subpgm(arg1 => 1);
constant bad : integer := subpgm(arg2 => 1);
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s1("arg2"),
"No declaration of 'arg2'",
ErrorCode::Unresolved,
),
Diagnostic::new(
code.s1("subpgm(arg2 => 1)"),
"No association of parameter 'arg1'",
ErrorCode::Unassociated,
)
.related(code.s1("arg1"), "Defined here"),
],
);
assert_eq!(
root.search_reference_pos(code.source(), code.s("arg1", 2).end()),
Some(code.s("arg1", 1).pos())
);
}
#[test]
fn resolve_overloaded_subprogram_by_argument() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
function subpgm(arg: character) return natural
is begin
end;
function subpgm(arg: natural) return natural
is begin
end;
signal good1 : natural := subpgm(0);
signal good2 : natural := subpgm('c');
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("subpgm", 3).end()),
Some(code.s("subpgm", 2).pos())
);
assert_eq!(
root.search_reference_pos(code.source(), code.s("subpgm", 4).end()),
Some(code.s("subpgm", 1).pos())
);
}
#[test]
fn subprogram_argument_not_associated() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
function subpgm(arg1: natural; arg2: character) return natural
is begin
end;
signal bad : natural := subpgm(0);
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("subpgm(0)"),
"No association of parameter 'arg2'",
ErrorCode::Unassociated,
)
.related(code.s1("arg2"), "Defined here")],
);
assert_eq!(
root.search_reference_pos(code.source(), code.s("subpgm", 2).end()),
Some(code.s("subpgm", 1).pos())
);
}
#[test]
fn subprogram_extra_argument_not_associated() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
function subpgm(arg1: natural) return natural
is begin
end;
signal bad : natural := subpgm(1111, 2222);
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s1("2222"),
"Unexpected extra argument",
ErrorCode::TooManyArguments,
)],
);
assert_eq!(
root.search_reference_pos(code.source(), code.s("subpgm", 2).end()),
Some(code.s("subpgm", 1).pos())
);
}
#[test]
fn for_loop_indexes_no_false_positives() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
procedure theproc(arg: integer) is
begin
end procedure;
procedure theproc(arg: boolean) is
begin
end procedure;
procedure calling is
variable foo : natural;
begin
for i in 0 to 3 loop
theproc(i);
end loop;
end procedure;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("theproc(i)").start()),
Some(code.s1("theproc").pos())
);
}
#[test]
fn default_before_positional_disambiguation() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
procedure theproc(arg: integer) is
begin
end procedure;
procedure theproc(arg: integer := 0; arg2: boolean) is
begin
end procedure;
procedure calling is
begin
theproc(0);
end procedure;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("theproc(0)").start()),
Some(code.s1("theproc").pos())
);
}
#[test]
fn named_argument_before_positional() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
procedure theproc(arg1: integer; arg2 : natural) is
begin
end procedure;
procedure calling is
begin
theproc(arg1 => 0, 0);
end procedure;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("arg1", 2),
"Named arguments are not allowed before positional arguments",
ErrorCode::NamedBeforePositional,
)],
);
}
#[test]
fn argument_associated_as_both_named_and_positional() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
procedure theproc(arg: integer) is
begin
end procedure;
procedure calling is
begin
theproc(0, arg => 0);
end procedure;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("arg", 2),
"parameter 'arg' has already been associated",
ErrorCode::AlreadyAssociated,
)
.related(code.s1("theproc(0, ").s1("0"), "Previously associated here")],
);
}
#[test]
fn duplicate_named_argument() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
procedure theproc(arg: integer) is
begin
end procedure;
procedure calling is
begin
theproc(arg => 0, arg => 0);
end procedure;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("arg", 3),
"parameter 'arg' has already been associated",
ErrorCode::AlreadyAssociated,
)
.related(code.s("arg", 2), "Previously associated here")],
);
}
#[test]
fn partial_named_argument_is_allowed_multiple_times() {
let mut builder = LibraryBuilder::new();
builder.in_declarative_region(
"
procedure theproc(arg: bit_vector) is
begin
end procedure;
procedure calling is
begin
theproc(arg(0) => '0', arg(1 to 2) => \"01\");
end procedure;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
}
#[test]
fn cannot_combine_partial_and_full_argument() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
procedure theproc(arg: bit_vector) is
begin
end procedure;
procedure calling is
begin
theproc(arg(0) => '0', arg => \"01\");
end procedure;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("arg", 3),
"parameter 'arg' has already been associated",
ErrorCode::AlreadyAssociated,
)
.related(code.s1("arg(0)"), "Previously associated here")],
);
}

View File

@ -0,0 +1,558 @@
use vhdl_lang::data::error_codes::ErrorCode;
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder};
use crate::syntax::test::check_diagnostics;
use crate::Diagnostic;
#[test]
pub fn cannot_instantiate_procedure_that_does_not_exist() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"\
procedure proc
generic ( x: natural := 1 ) is
begin
end proc;
procedure proc is new foo;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("foo").pos(),
"No declaration of 'foo'",
ErrorCode::Unresolved,
)],
);
}
#[test]
pub fn instantiate_wrong_type() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"\
signal x : bit;
function proc is new x;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::mismatched_kinds(
code.s1("new x").s1("x"),
"signal 'x' does not denote an uninstantiated subprogram",
)],
);
}
#[test]
pub fn ambiguous_multiple_uninstantiated_subprograms() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"\
procedure foo
generic (type T)
parameter (x : bit)
is begin
end foo;
procedure foo
generic (type T)
parameter (x : bit; y: bit)
is begin
end foo;
procedure proc is new foo;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("new foo").s1("foo"),
"Ambiguous instantiation of 'foo'",
ErrorCode::AmbiguousInstantiation,
)
.related(code.s("foo", 1), "Might be procedure foo[BIT]")
.related(code.s("foo", 3), "Might be procedure foo[BIT, BIT]")],
)
}
#[test]
pub fn by_signature_resolved_multiple_uninstantiated_subprograms() {
let mut builder = LibraryBuilder::new();
builder.in_declarative_region(
"\
procedure foo
generic (a: natural)
parameter (x : bit)
is begin
end foo;
procedure foo
generic (a: natural)
parameter (x : bit; y: bit)
is begin
end foo;
procedure proc is new foo [bit] generic map (a => 5);
procedure proc2 is new foo [bit, bit] generic map (a => 5);
",
);
check_no_diagnostics(&builder.analyze());
}
#[test]
pub fn complain_on_mismatching_signature() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"\
procedure foo
generic (type T)
parameter (x : bit)
is begin
end foo;
procedure proc is new foo [bit, bit];
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("[bit, bit]").pos(),
"Signature does not match the the signature of procedure foo[BIT]",
ErrorCode::SignatureMismatch,
)],
);
}
#[test]
pub fn can_instantiate_procedure_that_exists() {
let mut builder = LibraryBuilder::new();
builder.in_declarative_region(
"\
procedure proc
generic ( x: natural := 1 ) is
begin
end proc;
procedure proc is new proc;
",
);
check_no_diagnostics(&builder.analyze());
}
#[test]
pub fn instantiated_kind_vs_declared_kind() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"\
procedure prok
generic ( x: natural := 1 ) is
begin
end prok;
function func is new prok;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("function"),
"Instantiating procedure as function",
ErrorCode::MismatchedSubprogramInstantiation,
)
.related(code.s1("prok"), "procedure prok[] declared here")],
);
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"\
function funk
generic ( x: natural := 1 ) return bit is
begin
end funk;
procedure proc is new funk;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("procedure"),
"Instantiating function as procedure",
ErrorCode::MismatchedSubprogramInstantiation,
)
.related(code.s1("funk"), "function funk[return BIT] declared here")],
);
let mut builder = LibraryBuilder::new();
builder.in_declarative_region(
"\
function proc generic (type T) return bit is
begin
end proc;
function proc is new proc;
",
);
check_no_diagnostics(&builder.analyze());
let mut builder = LibraryBuilder::new();
builder.in_declarative_region(
"\
procedure proc generic (type T) is
begin
end proc;
procedure proc is new proc;
",
);
check_no_diagnostics(&builder.analyze())
}
#[test]
pub fn cannot_instantiate_procedure_without_header() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"\
procedure proc is
begin
end proc;
procedure proc is new proc;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::mismatched_kinds(
code.s1("procedure proc is new").s("proc", 2).pos(),
"procedure proc[] does not denote an uninstantiated subprogram",
)],
);
}
#[test]
pub fn cannot_call_procedure_with_header() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
entity ent is
end ent;
architecture arch of ent is
procedure proc
generic ( x: natural := 1 )
is
begin
end proc;
begin
proc;
end architecture arch;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("begin\n proc;").s1("proc").pos(),
"uninstantiated procedure proc[] cannot be called",
ErrorCode::InvalidCall,
)],
)
}
#[test]
pub fn resolves_the_correct_instantiated_subprogram() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"\
entity ent is
end ent;
architecture arch of ent is
procedure proc
generic ( type T )
is
begin
end proc;
procedure proc is new proc generic map (T => natural);
begin
proc;
end architecture arch;
",
);
check_no_diagnostics(&builder.analyze())
}
#[test]
pub fn resolves_its_generic_map() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
entity ent is
end ent;
architecture arch of ent is
procedure foo
generic ( type T )
parameter (param : T)
is
begin
end foo;
procedure foo is new foo generic map (T => natural);
procedure foo is new foo generic map (T => bit);
begin
foo('1');
foo(42);
end architecture arch;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("foo('1')").s1("foo").end(),),
Some(
code.s1("procedure foo is new foo generic map (T => bit)")
.s1("foo")
.pos()
)
);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("foo(42)").s1("foo").end(),),
Some(
code.s1("procedure foo is new foo generic map (T => natural)")
.s1("foo")
.pos()
)
);
assert_eq!(
root.search_reference_pos(
code.source(),
code.s1("procedure foo is new foo generic map (T => natural)")
.s("foo", 2)
.end(),
),
Some(code.s1("procedure foo").s1("foo").pos())
);
assert_eq!(
root.search_reference_pos(
code.source(),
code.s1("procedure foo is new foo generic map (T => bit)")
.s("foo", 2)
.end(),
),
Some(code.s1("procedure foo").s1("foo").pos())
);
}
#[test]
pub fn resolve_instantiated_function() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"\
function foo
generic (type F)
parameter (x: F)
return F
is begin
return x;
end foo;
function foo is new foo generic map (F => bit);
constant x : bit := foo('1');
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("foo('1')").s1("foo").end(),),
Some(
code.s1("function foo is new foo generic map (F => bit);")
.s1("foo")
.pos()
)
);
}
#[test]
pub fn generic_function_declaration_with_separate_body() {
let mut builder = LibraryBuilder::new();
builder.in_declarative_region(
"\
function foo generic (type F) parameter (x: bit) return bit;
function foo generic (type F) parameter (x: bit) return bit
is
begin
return x;
end foo;
function foo is new foo generic map (F => natural);
",
);
check_no_diagnostics(&builder.analyze());
}
// LRM 4.2.1: "An uninstantiated subprogram shall not be called,
// except as a recursive call within the body of the uninstantiated subprogram"
#[test]
#[ignore]
pub fn generic_subprogram_can_be_called_recursively() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"\
function foo
generic (type F)
parameter (x: F)
return F
is begin
return foo(x);
end foo;
procedure bar
generic (type F)
parameter (x: F)
is begin
bar(x);
end bar;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("foo(x)").s1("foo").end(),),
Some(code.s1("function foo").s1("foo").pos())
);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("bar(x)").s1("bar").end(),),
Some(code.s1("procedure bar").s1("bar").pos())
);
}
// LRM 4.2.1: "an uninstantiated subprogram shall not be used as a resolution
// function or used as a conversion function in an association list."
#[test]
#[ignore]
pub fn generic_subprogram_cannot_be_used_as_resolution_function() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"\
function resolved generic (type F) parameter (x: F) return F;
subtype x is resolved bit;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("subtype x is resolved bit").s1("resolved"),
"uninstantiated function resolved[F] return F cannot be used as resolution function",
ErrorCode::MismatchedKinds,
)],
);
}
#[test]
#[ignore]
pub fn generic_subprogram_cannot_be_used_as_conversion_function() {
let mut builder = LibraryBuilder::new();
builder.in_declarative_region(
"\
procedure bar (x : in bit) is
begin
end bar;
function foo generic (type F) parameter (x: bit) return bit;
",
);
let code = builder.snippet("bar(foo('1'))");
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("foo"),
"uninstantiated function foo[F] return F cannot be used as conversion",
ErrorCode::MismatchedKinds,
)],
);
}
#[test]
#[ignore]
pub fn generic_function_declaration_with_separate_body_and_type_parameters() {
let mut builder = LibraryBuilder::new();
builder.in_declarative_region(
"\
function foo generic (type F) parameter (x: F) return F;
function foo generic (type F) parameter (x: F) return F
is
begin
return x;
end foo;
function foo is new foo generic map (F => std_logic);
",
);
check_no_diagnostics(&builder.analyze());
}
#[test]
#[ignore]
pub fn by_signature_resolved_multiple_uninstantiated_subprograms_with_generics() {
let mut builder = LibraryBuilder::new();
builder.in_declarative_region(
"\
procedure foo
generic (type T)
parameter (x : T)
is begin
end foo;
procedure foo
generic (type T)
parameter (x : T; y: T)
is begin
end foo;
procedure proc is new foo [bit] generic map (T => bit);
procedure proc2 is new foo [bit, bit] generic map (T => bit);
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}

View File

@ -0,0 +1,33 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder};
#[test]
fn simple_tool_directive() {
let mut builder = LibraryBuilder::new();
builder.code("libname", "`protect begin");
check_no_diagnostics(&builder.analyze());
}
#[test]
fn tool_directive() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"\
entity my_ent is
end my_ent;
`protect begin_protected
`protect version = 1
`protect encrypt_agent = \"XILINX\"
`protect encrypt_agent_info = \"Xilinx Encryption Tool 2020.2\"
`protect key_keyowner = \"Cadence Design Systems.\", key_keyname = \"cds_rsa_key\", key_method = \"rsa\"
`protect encoding = (enctype = \"BASE64\", line_length = 76, bytes = 64)
",
);
check_no_diagnostics(&builder.analyze());
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,255 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::DesignRoot;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::syntax::test::*;
use crate::syntax::Symbols;
use pretty_assertions::assert_eq;
use std::collections::{hash_map::Entry, HashMap};
use std::sync::Arc;
use vhdl_lang::VHDLStandard;
pub struct LibraryBuilder {
code_builder: CodeBuilder,
libraries: HashMap<Symbol, Vec<Code>>,
}
impl LibraryBuilder {
pub fn new() -> LibraryBuilder {
LibraryBuilder::with_standard(VHDLStandard::default())
}
pub fn with_standard(standard: VHDLStandard) -> LibraryBuilder {
LibraryBuilder {
code_builder: CodeBuilder::with_standard(standard),
libraries: HashMap::default(),
}
}
fn add_code(&mut self, library_name: &str, code: Code) {
let library_name = self.code_builder.symbol(library_name);
match self.libraries.entry(library_name) {
Entry::Occupied(mut entry) => {
entry.get_mut().push(code);
}
Entry::Vacant(entry) => {
entry.insert(vec![code]);
}
}
}
pub fn code(&mut self, library_name: &str, code: &str) -> Code {
let code = self.code_builder.code(code);
self.add_code(library_name, code.clone());
code
}
/// Just get a Code object using the same symbol table but without adding it to any library
pub fn snippet(&mut self, code: &str) -> Code {
self.code_builder.code(code)
}
pub fn in_declarative_region(&mut self, code: &str) -> Code {
self.code(
"libname",
&format!(
"
entity ent is
end entity;
architecture a of ent is
{code}
begin
end architecture;"
),
)
}
pub fn add_std_logic_1164(&mut self) {
let std_logic_1164 = self.code_builder.code_from_source(std_logic_1164_package());
self.add_code("ieee", std_logic_1164);
}
pub fn get_analyzed_root(&self) -> (DesignRoot, Vec<Diagnostic>) {
let mut root = DesignRoot::new(self.code_builder.symbols.clone());
let mut diagnostics = Vec::new();
add_standard_library(self.symbols(), &mut root);
for (library_name, codes) in self.libraries.iter() {
for code in codes {
root.add_design_file(
library_name.clone(),
code.design_file_diagnostics(&mut diagnostics),
);
}
}
root.analyze(&mut diagnostics);
(root, diagnostics)
}
pub fn take_code(self) -> Vec<(Symbol, Code)> {
let mut res = Vec::new();
for (library_name, codes) in self.libraries.into_iter() {
for code in codes.into_iter() {
res.push((library_name.clone(), code));
}
}
res
}
pub fn symbols(&self) -> Arc<Symbols> {
self.code_builder.symbols.clone()
}
pub fn analyze(&self) -> Vec<Diagnostic> {
self.get_analyzed_root().1
}
}
fn standard_package() -> Source {
Source::inline(
Path::new("standard.vhd"),
&Latin1String::new(include_bytes!(
"../../../../vhdl_libraries/std/standard.vhd"
))
.to_string(),
)
}
fn textio_package() -> Source {
Source::inline(
Path::new("textio.vhd"),
&Latin1String::new(include_bytes!("../../../../vhdl_libraries/std/textio.vhd")).to_string(),
)
}
fn env_package() -> Source {
Source::inline(
Path::new("env.vhd"),
&Latin1String::new(include_bytes!("../../../../vhdl_libraries/std/env.vhd")).to_string(),
)
}
fn std_logic_1164_package() -> Source {
Source::inline(
Path::new("std_logic_1164.vhd"),
&Latin1String::new(include_bytes!(
"../../../../vhdl_libraries/ieee2008/std_logic_1164.vhdl"
))
.to_string(),
)
}
pub fn add_standard_library(symbols: Arc<Symbols>, root: &mut DesignRoot) {
let builder = CodeBuilder {
symbols: symbols.clone(),
standard: VHDLStandard::default(),
};
let std_standard = builder.code_from_source(standard_package());
let std_textio = builder.code_from_source(textio_package());
let std_env = builder.code_from_source(env_package());
let std_sym = symbols.symtab().insert_utf8("std");
root.add_design_file(std_sym.clone(), std_standard.design_file());
root.add_design_file(std_sym.clone(), std_textio.design_file());
root.add_design_file(std_sym, std_env.design_file());
}
pub fn missing(code: &Code, name: &str, occ: usize) -> Diagnostic {
Diagnostic::new(
code.s(name, occ),
format!("No declaration of '{name}'"),
ErrorCode::Unresolved,
)
}
pub fn duplicate(code: &Code, name: &str, occ1: usize, occ2: usize) -> Diagnostic {
Diagnostic::new(
code.s(name, occ2),
format!("Duplicate declaration of '{}'", &name),
ErrorCode::Duplicate,
)
.related(code.s(name, occ1), "Previously defined here")
}
pub fn duplicates(code: &Code, names: &[&str]) -> Vec<Diagnostic> {
let mut diagnostics = Vec::new();
for name in names {
diagnostics.push(duplicate(code, name, 1, 2));
}
diagnostics
}
pub fn duplicate_in_two_files(code1: &Code, code2: &Code, names: &[&str]) -> Vec<Diagnostic> {
let mut diagnostics = Vec::new();
for name in names {
diagnostics.push(
Diagnostic::new(
code2.s1(name),
format!("Duplicate declaration of '{}'", &name),
ErrorCode::Duplicate,
)
.related(code1.s1(name), "Previously defined here"),
)
}
diagnostics
}
pub fn check_missing(contents: &str) {
let mut builder = LibraryBuilder::new();
let code = builder.code("libname", contents);
let diagnostics = builder.analyze();
let occurences = contents.matches("missing").count();
assert!(occurences > 0);
check_diagnostics(
diagnostics,
(1..=occurences)
.map(|idx| missing(&code, "missing", idx))
.collect(),
);
}
pub fn check_code_with_no_diagnostics(contents: &str) {
let mut builder = LibraryBuilder::new();
builder.code("libname", contents);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
pub fn check_search_reference(contents: &str) {
check_search_reference_with_name("decl", contents);
}
/// Check that all occurrences of decl_name references the first occurrence of if
/// Also check that find all references returns all occurrences of decl_name
pub fn check_search_reference_with_name(decl_name: &str, contents: &str) {
let mut builder = LibraryBuilder::new();
let code = builder.code("libname", contents);
let occurences = contents.matches(decl_name).count();
assert!(occurences > 0);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let mut references = Vec::new();
for idx in 1..=occurences {
assert_eq!(
root.search_reference(code.source(), code.s(decl_name, idx).end())
.and_then(|ent| ent.declaration().decl_pos().cloned()),
Some(code.s(decl_name, 1).pos()),
"{decl_name}, occurence {}",
idx
);
references.push(code.s(decl_name, idx).pos());
}
assert_eq!(
root.find_all_references_pos(&code.s(decl_name, 1).pos()),
references,
);
}

View File

@ -0,0 +1,619 @@
use crate::analysis::tests::{check_diagnostics, check_no_diagnostics, LibraryBuilder};
use crate::data::ErrorCode;
use crate::Diagnostic;
use crate::VHDLStandard::VHDL2019;
use pretty_assertions::assert_eq;
#[test]
pub fn view_mode_declaration_must_have_declared_subtype() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.in_declarative_region(
"\
view my_view of undeclared is
end view;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("undeclared"),
"No declaration of 'undeclared'",
ErrorCode::Unresolved,
)],
)
}
#[test]
pub fn view_mode_declaration_must_have_record_as_type() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.in_declarative_region(
"\
type foo is (A, B);
view my_view of foo is
end view;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s("foo", 2),
"The type of a view must be a record type, not type 'foo'",
ErrorCode::TypeMismatch,
)
.related(code.s1("foo"), "type 'foo' declared here")],
);
let mut builder = LibraryBuilder::with_standard(VHDL2019);
builder.in_declarative_region(
"\
type foo is record
x: bit;
end record;
view my_view of foo is
x : in;
end view;
",
);
check_no_diagnostics(&builder.analyze());
}
#[test]
pub fn element_in_view_that_is_not_in_record() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.in_declarative_region(
"\
type foo is record
bar: bit;
end record;
view my_view of foo is
baz: in;
bar: out;
end view;
",
);
let (_, diag) = builder.get_analyzed_root();
check_diagnostics(
diag,
vec![Diagnostic::new(
code.s1("baz"),
"Not a part of record type 'foo'",
ErrorCode::Unresolved,
)],
);
}
#[test]
pub fn view_reference_set_to_the_original_element() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.in_declarative_region(
"\
type foo is record
bar: bit;
end record;
view my_view of foo is
bar: in;
end view;
",
);
let (root, diag) = builder.get_analyzed_root();
check_no_diagnostics(&diag);
let record_element = root
.search_reference(code.source(), code.s1("bar: bit").s1("bar").start())
.unwrap();
let view_element = root
.search_reference(code.source(), code.s1("bar: in").s1("bar").start())
.unwrap();
assert_eq!(record_element, view_element)
}
#[test]
pub fn diagnostic_when_elements_are_missing() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.in_declarative_region(
"\
type foo is record
bar: bit;
baz: bit;
end record;
view my_view of foo is
bar: in;
end view;
",
);
let diag = builder.analyze();
check_diagnostics(
diag,
vec![Diagnostic::new(
code.s1("my_view"),
"Missing association of element 'baz'",
ErrorCode::Unassociated,
)],
);
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.in_declarative_region(
"\
type foo is record
bar: bit;
baz: bit;
foobar: bit;
end record;
view my_view of foo is
bar: in;
end view;
",
);
let diag = builder.analyze();
check_diagnostics(
diag,
vec![Diagnostic::new(
code.s1("my_view"),
"Missing association of elements 'baz' and 'foobar'",
ErrorCode::Unassociated,
)],
);
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.in_declarative_region(
"\
type foo is record
bar: bit;
baz: bit;
foobar: bit;
foobaz: bit;
end record;
view my_view of foo is
bar: in;
end view;
",
);
let diag = builder.analyze();
check_diagnostics(
diag,
vec![Diagnostic::new(
code.s1("my_view"),
"Missing association of elements 'baz', 'foobar' and 'foobaz'",
ErrorCode::Unassociated,
)],
);
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.in_declarative_region(
"\
type foo is record
a, b, c, d, e, f, g, h: bit;
end record;
view my_view of foo is
a: in;
end view;
",
);
let diag = builder.analyze();
check_diagnostics(
diag,
vec![Diagnostic::new(
code.s1("my_view"),
"Missing association of elements 'b', 'c', 'd' and 4 more",
ErrorCode::Unassociated,
)],
);
}
#[test]
pub fn view_interface_declarations_must_be_views() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"\
entity my_ent is
port (
foo: view not_declared
);
end entity;
",
);
let diag = builder.analyze();
check_diagnostics(
diag,
vec![Diagnostic::new(
code.s1("not_declared"),
"No declaration of 'not_declared'",
ErrorCode::Unresolved,
)],
);
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"\
entity my_ent is
port (
foo: view bit
);
end entity;
",
);
let diag = builder.analyze();
check_diagnostics(
diag,
vec![Diagnostic::mismatched_kinds(
code.s1("bit"),
"type 'BIT' is not a view",
)],
);
let mut builder = LibraryBuilder::with_standard(VHDL2019);
builder.code(
"libname",
"\
package x is
type bar is record
x: bit;
end bar;
view foo of bar is
x: in;
end view;
end x;
use work.x.all;
entity my_ent is
port (
foo: view foo
);
end entity;
",
);
let diag = builder.analyze();
check_no_diagnostics(&diag);
}
#[test]
pub fn view_interface_declaration_subtype_must_match_declared_subtype() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"\
package x is
type bar is record
x: bit;
end bar;
view foo of bar is
x: in;
end view;
end x;
use work.x.all;
entity my_ent is
port (
foo: view foo of bit
);
end entity;
",
);
let diag = builder.analyze();
check_diagnostics(
diag,
vec![Diagnostic::new(
code.s1("of bit").s1("bit"),
"Specified subtype must match the subtype declared for the view",
ErrorCode::TypeMismatch,
)],
);
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"\
package x is
type bar is record
x: bit;
end bar;
view foo of bar is
x: in;
end view;
end x;
use work.x.all;
entity my_ent is
port (
x: view foo of bar
);
end entity;
",
);
let (root, diag) = builder.get_analyzed_root();
check_no_diagnostics(&diag);
let in_view = root
.search_reference(
code.source(),
code.s1("x: view foo of bar").s1("bar").start(),
)
.unwrap();
let declared = root
.search_reference(
code.source(),
code.s1("type bar is record").s1("bar").start(),
)
.unwrap();
assert_eq!(in_view, declared)
}
#[test]
fn view_reference() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"\
package x is
type bar is record
x: bit;
end bar;
view foo of bar is
x: in;
end view;
end x;
use work.x;
entity my_ent is
port (
x: view x.foo
);
end entity;
",
);
let (root, diag) = builder.get_analyzed_root();
check_no_diagnostics(&diag);
let in_view = root
.search_reference(code.source(), code.s1("x: view x.foo").s1("foo").start())
.unwrap();
let declared = root
.search_reference(
code.source(),
code.s1("view foo of bar is").s1("foo").start(),
)
.unwrap();
assert_eq!(in_view, declared)
}
#[test]
fn converse_attribute() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
builder.code(
"libname",
"\
package my_pkg is
type bar is record
x: bit;
end bar;
view foo of bar is
x: in;
end view;
alias foo_conv is foo'converse;
end my_pkg;
use work.my_pkg;
entity my_ent is
port (
x: view my_pkg.foo;
y: view my_pkg.foo_conv
);
end entity;
",
);
let diag = builder.analyze();
// The mode of views is not analyzed at the moment, so the 'converse attribute
// should simply not show any diagnostics.
check_no_diagnostics(&diag);
}
#[test]
#[ignore]
fn view_declaration_in_cannot_be_assigned_to() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"\
package my_pkg is
type bar is record
x: bit;
end bar;
view foo of bar is
x: in;
end view;
end my_pkg;
use work.my_pkg;
entity my_ent is
port ( y: view my_pkg.foo );
end entity;
architecture arch of my_ent is
begin
y.x <= '1';
end arch;
",
);
let diag = builder.analyze();
check_diagnostics(
diag,
vec![Diagnostic::new(
code.s1("y.x"),
"interface signal 'y' of mode in may not be the target of an assignment",
ErrorCode::MismatchedKinds,
)],
);
}
// GitHub issue #324
#[test]
fn view_in_generic_package() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
builder.code(
"libname",
"\
package test_pkg is
generic ( width : integer );
type test_t is record
a : bit;
end record;
view vone of test_t is
a : in;
end view;
alias vtwo is vone'converse;
end package;
package w8_pkg is new work.test_pkg generic map (width => 8);
use work.w8_pkg.all;
entity test_sub_entity is
port (
my_if : view vone
);
end entity;
use work.w8_pkg.all;
entity test_top_entity is
end entity;
architecture rtl of test_top_entity is
signal my_sig : test_t;
begin
my_if : entity work.test_sub_entity
port map (
my_if => my_sig
);
end architecture;
",
);
check_no_diagnostics(&builder.analyze());
}
#[test]
fn arrays_of_views_with_matching_type() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
builder.code(
"libname",
"\
package test_pkg is
type test_t is record
a : bit;
end record;
view vone of test_t is
a : in;
end view;
type test_array is array (natural range <>) of test_t;
end package;
use work.test_pkg.all;
entity test_sub_entity is
port (
my_if : view vone;
my_array_if: view (vone) of test_array(0 to 1)
);
end entity;
",
);
check_no_diagnostics(&builder.analyze());
}
#[test]
fn arrays_of_views_with_non_matching_type() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"\
package test_pkg is
type test_t is record
a : bit;
end record;
view vone of test_t is
a : in;
end view;
type test_array is array (natural range <>) of bit;
end package;
use work.test_pkg.all;
entity test_sub_entity is
port (
my_if : view vone;
my_array_if: view (vone) of test_array(0 to 1)
);
end entity;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("test_array(0 to 1)").s1("test_array"),
"Array element type 'BIT' must match record type 'test_t' declared for the view",
ErrorCode::TypeMismatch,
)],
)
}
#[test]
fn arrays_of_views_that_are_not_arrays() {
let mut builder = LibraryBuilder::with_standard(VHDL2019);
let code = builder.code(
"libname",
"\
package test_pkg is
type test_t is record
a : bit;
end record;
view vone of test_t is
a : in;
end view;
end package;
use work.test_pkg.all;
entity test_sub_entity is
port (
my_if : view vone;
my_array_if: view (vone) of test_t
);
end entity;
",
);
check_diagnostics(
builder.analyze(),
vec![Diagnostic::new(
code.s1("view (vone) of test_t").s1("test_t"),
"Subtype must be an array",
ErrorCode::TypeMismatch,
)],
)
}

View File

@ -0,0 +1,792 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2020, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use vhdl_lang::data::error_codes::ErrorCode;
#[test]
fn secondary_units_share_root_region_and_visibility_in_extended_region() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg2 is
constant const : natural := 0;
end package;
package pkg is
use work.pkg2;
end package;
-- Does not work
use pkg2.const;
package body pkg is
-- Does work, share visibility of extended region
use pkg2.const;
end package body;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
code.s("pkg2", 3),
"No declaration of 'pkg2'",
ErrorCode::Unresolved,
)],
)
}
#[test]
fn immediate_region_takes_precedence_over_local_visibility() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package false_pkg is
constant decl : natural := 0;
end package;
package pkg is
constant decl : natural := 1;
use work.false_pkg.decl;
-- Should refer to constant in pkg
constant ref : natural := decl;
end package pkg;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("decl", 4).start()),
Some(code.s("decl", 2).pos())
);
}
#[test]
fn extended_region_takes_precedence_over_local_visibility() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package false_pkg is
constant decl : natural := 0;
end package;
package pkg is
constant decl : natural := 1;
end package pkg;
use work.false_pkg.decl;
package body pkg is
use work.false_pkg.decl;
-- Should refer to constant in pkg
constant ref : natural := decl;
end package body pkg;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("decl", 5).start()),
Some(code.s("decl", 2).pos())
);
}
#[test]
fn enclosing_region_takes_precedence_over_local_visibility() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
constant decl : natural := 0;
end package;
entity tb_ent is
end entity;
architecture a of tb_ent is
constant decl : natural := 1;
begin
main : process
use work.pkg.decl;
begin
assert decl = 1;
end process;
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s("decl", 4).start()),
Some(code.s("decl", 2).pos())
);
}
#[test]
fn context_clause_makes_names_visible() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
-- Package will be used for testing
package usepkg is
constant const : natural := 0;
type enum_t is (alpha, beta);
end package;
context ctx is
library libname;
-- Test both used by name
use libname.usepkg;
-- .. as well as used by all
use libname.usepkg.all;
end context;
context work.ctx;
use usepkg.const;
package pkg is
constant c : enum_t := alpha;
end package;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn cannot_reference_potentially_visible_name_by_selection() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg2 is
constant const1 : natural := 0;
constant const2 : natural := 0;
end package;
use work.pkg2.const1;
package pkg is
use work.pkg2.const2;
constant const3 : natural := 0;
end package;
use work.pkg.const1;
use work.pkg.const2;
use work.pkg.const3;
entity ent is
end entity;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
Diagnostic::new(
code.s("const1", 3),
"No declaration of 'const1' within package 'pkg'",
ErrorCode::Unresolved,
),
Diagnostic::new(
code.s("const2", 3),
"No declaration of 'const2' within package 'pkg'",
ErrorCode::Unresolved,
),
],
);
}
#[test]
fn duplicate_identifer_is_not_directly_visible() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg1 is
constant name : natural := 0;
end package;
package pkg2 is
constant name : natural := 1;
end package;
use work.pkg1.name;
use work.pkg2.name;
package user is
constant b : natural := name;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![hidden_error(
&code,
"name",
5,
&[
(&code, "work.pkg1.name", 1, false),
(&code, "name", 1, true),
(&code, "work.pkg2.name", 1, false),
(&code, "name", 2, true),
],
)],
);
}
#[test]
fn duplicate_identifer_visiblity_is_traced_through_context() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"lib",
"
package pkg1 is
constant name : natural := 0;
end package;
package pkg2 is
constant name : natural := 1;
end package;
context ctx is
library lib;
use lib.pkg1.name;
end context;
context work.ctx;
use work.pkg2.name;
package user is
constant b : natural := name;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![hidden_error(
&code,
"name",
5,
&[
(&code, "work.ctx", 1, false),
(&code, "lib.pkg1.name", 1, false),
(&code, "name", 1, true),
(&code, "work.pkg2.name", 1, false),
(&code, "name", 2, true),
],
)],
);
}
#[test]
fn duplicate_identifer_is_directly_visible_when_it_is_the_same_named_entitty() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package pkg1 is
constant name : natural := 0;
end package;
use work.pkg1.name;
use work.pkg1.name;
package user is
constant b : natural := name;
end package;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn duplicate_identifer_of_parent_visibility_is_not_directly_visible() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg1 is
constant name : natural := 0;
end package;
package pkg2 is
constant name : natural := 1;
end package;
use work.pkg1.name;
package user is
use work.pkg2.name;
constant b : natural := name;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![hidden_error(
&code,
"name",
5,
&[
(&code, "work.pkg1.name", 1, false),
(&code, "name", 1, true),
(&code, "work.pkg2.name", 1, false),
(&code, "name", 2, true),
],
)],
);
}
#[test]
fn local_is_still_visible_under_duplicate_identifer() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package pkg1 is
constant name : natural := 0;
end package;
package pkg2 is
constant name : natural := 1;
end package;
package user is
use work.pkg1.name;
use work.pkg2.name;
constant name : natural := 0;
constant b : natural := name;
procedure foo(constant b : natural := name);
end package;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
#[test]
fn mixed_overloaded_names_require_disambiguation() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
function fun1 return natural;
function fun2 return natural;
end package;
package pkg2 is
constant fun1 : boolean := false;
constant fun2 : natural := 0;
end package;
use work.pkg.all;
use work.pkg2.all;
package user is
-- Requires disambiguation
constant ident : natural := fun1;
-- Requires disambiguation
constant ident2 : natural := fun2;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![
hidden_error(
&code,
"fun1",
3,
&[
(&code, "work.pkg.all", 1, false),
(&code, "fun1", 1, true),
(&code, "work.pkg2.all", 1, false),
(&code, "fun1", 2, true),
],
),
hidden_error(
&code,
"fun2",
3,
&[
(&code, "work.pkg.all", 1, false),
(&code, "fun2", 1, true),
(&code, "work.pkg2.all", 1, false),
(&code, "fun2", 2, true),
],
),
],
);
}
/// This was a bug during development
#[test]
fn explicit_library_std_is_not_duplicate_of_implicit() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
library std;
use std.standard.all;
package pkg1 is
end package;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
/// This was a bug during development
#[test]
fn duplicate_explicit_library_is_not_duplicate() {
let mut builder = LibraryBuilder::new();
builder.code(
"libname",
"
package pkg is
end package;
context ctx is
library libname;
use libname.pkg;
end context;
library libname;
context libname.ctx;
use libname.pkg;
package user is
end package;
",
);
let diagnostics = builder.analyze();
check_no_diagnostics(&diagnostics);
}
pub fn hidden_error(
code: &Code,
name: &str,
occ: usize,
related: &[(&Code, &str, usize, bool)],
) -> Diagnostic {
let mut error = Diagnostic::new(
code.s(name, occ),
format!("Name '{name}' is hidden by conflicting use clause"),
ErrorCode::ConflictingUseClause,
);
for (code, substr, occ, declared) in related.iter() {
if *declared {
error.add_related(
code.s(substr, *occ),
format!("Conflicting name '{name}' declared here"),
)
} else {
error.add_related(
code.s(substr, *occ),
format!("Conflicting name '{name}' made visible here"),
)
}
}
error
}
#[test]
fn duplicate_alias_is_not_directly_visible() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg1 is
type enum_t is (alpha, beta);
alias alias_t is enum_t;
end package;
package pkg2 is
type enum_t is (alpha, beta);
alias alias_t is enum_t;
end package;
use work.pkg1.alias_t;
use work.pkg2.alias_t;
package user is
constant b : alias_t := alpha;
end package;
",
);
let diagnostics = builder.analyze();
check_diagnostics(
diagnostics,
vec![hidden_error(
&code,
"alias_t",
5,
&[
(&code, "work.pkg1.alias_t", 1, false),
(&code, "alias_t", 1, true),
(&code, "work.pkg2.alias_t", 1, false),
(&code, "alias_t", 2, true),
],
)],
);
}
/// It is more convenient for a language server user to goto-declaration for
/// the alias rather than going directly to the declaration without knowing about the visible alias
/// The user can always navigate from the alias to the original declaration if she desires
#[test]
fn deepest_alias_is_visible() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg is
type enum_t is (alpha, beta);
end package;
package pkg2 is
alias enum_t is work.pkg.enum_t;
end package;
package pkg3 is
alias enum_t is work.pkg2.enum_t;
end package;
use work.pkg2.enum_t;
use work.pkg3.enum_t;
use work.pkg.enum_t;
package pkg4 is
constant c : enum_t := alpha;
end package;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let ref_pos = code.s1("constant c : enum_t := alpha").s1("enum_t");
let deepest_pos = code.s1("alias enum_t is work.pkg2.enum_t").s1("enum_t");
assert_eq!(
root.search_reference_pos(code.source(), ref_pos.start()),
Some(deepest_pos.pos())
);
}
/// Using an overloaded name should not conflict with an immediate declaration if they
/// have different signatures
#[test]
fn non_conflicting_used_names_are_still_visible_in_prescence_of_immediate() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
package pkg1 is
type enum1_t is (alpha, beta);
end package;
use work.pkg1.enum1_t;
package user is
type enum2_t is (alpha, beta);
constant a : enum1_t := alpha;
constant b : enum2_t := alpha;
end package;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let alpha1_pos = code.s("alpha", 1).pos();
let alpha1_ref = code.s("alpha", 3).pos();
assert_eq!(
root.search_reference_pos(code.source(), alpha1_ref.start()),
Some(alpha1_pos.clone())
);
let alpha2_pos = code.s("alpha", 2).pos();
let alpha2_ref = code.s("alpha", 4).pos();
assert_eq!(
root.search_reference_pos(code.source(), alpha2_ref.start()),
Some(alpha2_pos.clone())
);
assert_eq_unordered(
&root.find_all_references_pos(&alpha1_pos),
&[alpha1_pos, alpha1_ref],
);
assert_eq_unordered(
&root.find_all_references_pos(&alpha2_pos),
&[alpha2_pos, alpha2_ref],
);
}
#[test]
fn nested_subprogram_shadows_outer() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"
procedure theproc(arg: character) is
begin
end procedure;
function thefun(arg: integer) return natural is
procedure theproc(arg: integer) is
begin
theproc('c');
end procedure;
begin
theproc(arg);
return 0;
end function;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("theproc('c')").start()),
Some(code.s1("theproc").pos())
);
assert_eq!(
root.search_reference_pos(code.source(), code.s1("theproc(arg)").start()),
Some(code.s("theproc", 2).pos())
);
}
#[test]
fn labels_are_visible_in_declarative_region() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
attribute debug : boolean;
attribute debug of main : label is true;
begin
main: process
begin
wait;
end process;
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let label = root
.search_reference(code.source(), code.sa("debug of ", "main").start())
.unwrap();
assert_eq!(label.decl_pos(), Some(&code.sb("main", ": process").pos()));
}
#[test]
fn sequential_labels_are_visible_in_declarative_region() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"
entity ent is
end entity;
architecture a of ent is
begin
process
attribute debug : boolean;
attribute debug of main : label is true;
begin
main: for i in 0 to 1 loop
end loop;
wait;
end process;
end architecture;
",
);
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
let label = root
.search_reference(code.source(), code.sa("debug of ", "main").start())
.unwrap();
assert_eq!(label.decl_pos(), Some(&code.sb("main", ": for i ").pos()));
}
#[test]
fn generics_are_visible_in_procedures_but_not_outside() {
let mut builder = LibraryBuilder::new();
let code = builder.in_declarative_region(
"\
procedure swap
generic ( type T )
parameter (a, b : inout T) is
variable temp : T;
begin
temp := a;
a := b;
b := temp;
end procedure swap;
shared variable temp2: T;
",
);
let (_, diagnostics) = builder.get_analyzed_root();
assert_eq!(
diagnostics,
vec![Diagnostic::new(
code.s("T", 4),
"No declaration of 'T'",
ErrorCode::Unresolved
)]
)
}

View File

@ -0,0 +1,670 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use super::*;
use crate::ast::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::{Signature, *};
use crate::HasTokenSpan;
use analyze::*;
impl<'a, 't> AnalyzeContext<'a, 't> {
pub fn resolve_subtype_indication(
&self,
scope: &Scope<'a>,
subtype_indication: &mut SubtypeIndication,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<Subtype<'a>> {
// @TODO more
let SubtypeIndication {
type_mark,
constraint,
..
} = subtype_indication;
let base_type = self.type_name(scope, type_mark.span, &mut type_mark.item, diagnostics)?;
if let Some(constraint) = constraint {
self.analyze_subtype_constraint(
scope,
&type_mark.pos(self.ctx),
base_type.base(),
&mut constraint.item,
diagnostics,
)?;
}
Ok(Subtype::new(base_type))
}
pub(crate) fn analyze_type_declaration(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
type_decl: &mut TypeDeclaration,
// Is the full type declaration of an incomplete type
// Overwrite id when defining full type
overwrite_id: Option<EntityId>,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let src_span = type_decl.span();
match type_decl.def {
TypeDefinition::Enumeration(ref mut enumeration) => {
let enum_type = TypeEnt::define_with_opt_id(
self.ctx,
self.arena,
overwrite_id,
&mut type_decl.ident,
parent,
None,
Type::Enum(
enumeration
.iter()
.map(|literal| literal.tree.item.clone().into_designator())
.collect(),
),
src_span,
self.source(),
);
let signature =
Signature::new(FormalRegion::new(InterfaceType::Parameter), Some(enum_type));
for literal in enumeration.iter_mut() {
let literal_ent = self.arena.explicit(
literal.tree.item.clone().into_designator(),
enum_type.into(),
AnyEntKind::Overloaded(Overloaded::EnumLiteral(signature.clone())),
Some(literal.pos(self.ctx)),
src_span,
Some(self.source()),
);
literal.decl.set(literal_ent.id());
unsafe {
self.arena.add_implicit(enum_type.id(), literal_ent);
}
scope.add(literal_ent, diagnostics);
}
scope.add(enum_type.into(), diagnostics);
for ent in self.enum_implicits(enum_type, self.has_matching_op(enum_type)) {
unsafe {
self.arena.add_implicit(enum_type.id(), ent);
}
scope.add(ent, diagnostics);
}
}
TypeDefinition::ProtectedBody(ref mut body) => {
match scope.lookup_immediate(&type_decl.ident.tree.item.clone().into()) {
Some(visible) => {
let is_ok = match visible.clone().into_non_overloaded() {
Ok(ent) => {
if let AnyEntKind::Type(Type::Protected(ptype_region, is_body)) =
ent.kind()
{
if *is_body {
if let Some(prev_pos) = ent.decl_pos() {
diagnostics.push(Diagnostic::duplicate_error(
&type_decl.ident.tree,
type_decl.ident.tree.pos(self.ctx),
Some(prev_pos),
))
}
} else {
let ptype_body: EntRef<'_> = TypeEnt::define_with_opt_id(
self.ctx,
self.arena,
overwrite_id,
&mut type_decl.ident,
parent,
Some(ent),
Type::Protected(Region::default(), true),
src_span,
self.source(),
)
.into();
let region = Scope::extend(ptype_region, Some(scope));
self.analyze_declarative_part(
&region,
ptype_body,
&mut body.decl,
diagnostics,
)?;
let kind = Type::Protected(region.into_region(), true);
unsafe {
ptype_body.set_kind(AnyEntKind::Type(kind));
}
scope.add(ptype_body, diagnostics);
}
true
} else {
false
}
}
_ => false,
};
if !is_ok {
diagnostics.add(
type_decl.ident.pos(self.ctx),
format!("'{}' is not a protected type", &type_decl.ident),
ErrorCode::TypeMismatch,
);
}
}
None => {
diagnostics.add(
type_decl.ident.pos(self.ctx),
format!("No declaration of protected type '{}'", &type_decl.ident),
ErrorCode::Unresolved,
);
}
};
}
TypeDefinition::Protected(ref mut prot_decl) => {
// Protected type name is visible inside its declarative region
// This will be overwritten later when the protected type region is finished
let ptype: EntRef<'_> = TypeEnt::define_with_opt_id(
self.ctx,
self.arena,
overwrite_id,
&mut type_decl.ident,
parent,
None,
Type::Protected(Region::default(), false),
src_span,
self.source(),
)
.into();
scope.add(ptype, diagnostics);
let region = scope.nested();
for item in prot_decl.items.iter_mut() {
match item {
ProtectedTypeDeclarativeItem::Subprogram(ref mut subprogram) => {
match as_fatal(self.subprogram_specification(
scope,
ptype,
&mut subprogram.specification,
subprogram.span,
Overloaded::SubprogramDecl,
diagnostics,
))? {
Some((_, ent)) => {
region.add(ent, diagnostics);
}
None => {
return Ok(());
}
}
}
}
}
// This is safe since we are in a single thread and no other reference can exist yes
// Also the region is stored inside an Arc which cannot move
{
let AnyEntKind::Type(Type::Protected(region_ptr, _)) = ptype.kind() else {
unreachable!();
};
#[allow(invalid_reference_casting)]
let region_ptr = unsafe {
let region_ptr = region_ptr as *const Region<'_>;
let region_ptr = region_ptr as *mut Region<'_>;
&mut *region_ptr as &mut Region<'_>
};
*region_ptr = region.into_region();
}
}
TypeDefinition::Record(ref mut element_decls) => {
let type_ent = TypeEnt::define_with_opt_id(
self.ctx,
self.arena,
overwrite_id,
&mut type_decl.ident,
parent,
None,
Type::Record(RecordRegion::default()),
src_span,
self.source(),
);
let mut elems = RecordRegion::default();
let mut region = Region::default();
for elem_decl in element_decls.iter_mut() {
let subtype =
self.resolve_subtype_indication(scope, &mut elem_decl.subtype, diagnostics);
if let Some(subtype) = as_fatal(subtype)? {
for ident in &mut elem_decl.idents {
let elem = self.define(
ident,
type_ent.into(),
AnyEntKind::ElementDeclaration(subtype),
elem_decl.span,
);
region.add(elem, diagnostics);
elems.add(elem);
}
}
}
region.close(diagnostics);
unsafe {
let kind = AnyEntKind::Type(Type::Record(elems));
type_ent.set_kind(kind)
}
scope.add(type_ent.into(), diagnostics);
for ent in self.record_implicits(type_ent) {
unsafe {
self.arena.add_implicit(type_ent.id(), ent);
}
scope.add(ent, diagnostics);
}
}
TypeDefinition::Access(ref mut subtype_indication) => {
let subtype =
self.resolve_subtype_indication(scope, subtype_indication, diagnostics);
if let Some(subtype) = as_fatal(subtype)? {
let type_ent = TypeEnt::define_with_opt_id(
self.ctx,
self.arena,
overwrite_id,
&mut type_decl.ident,
parent,
None,
Type::Access(subtype),
src_span,
self.source(),
);
scope.add(type_ent.into(), diagnostics);
for ent in self.access_implicits(type_ent) {
unsafe {
self.arena.add_implicit(type_ent.id(), ent);
}
scope.add(ent, diagnostics);
}
}
}
TypeDefinition::Array(ref mut array_indexes, _, ref mut subtype_indication) => {
let mut indexes: Vec<Option<BaseType<'_>>> =
Vec::with_capacity(array_indexes.len());
for index in array_indexes.iter_mut() {
indexes.push(as_fatal(self.analyze_array_index(
scope,
index,
diagnostics,
))?);
}
let elem_type = match as_fatal(self.resolve_subtype_indication(
scope,
subtype_indication,
diagnostics,
))? {
Some(subtype) => subtype.type_mark().to_owned(),
None => return Ok(()),
};
let is_1d = indexes.len() == 1;
let array_ent = TypeEnt::define_with_opt_id(
self.ctx,
self.arena,
overwrite_id,
&mut type_decl.ident,
parent,
None,
Type::Array { indexes, elem_type },
src_span,
self.source(),
);
scope.add(array_ent.into(), diagnostics);
for ent in self.array_implicits(array_ent, is_1d && self.has_matching_op(elem_type))
{
unsafe {
self.arena.add_implicit(array_ent.id(), ent);
}
scope.add(ent, diagnostics);
}
}
TypeDefinition::Subtype(ref mut subtype_indication) => {
if let Some(subtype) = as_fatal(self.resolve_subtype_indication(
scope,
subtype_indication,
diagnostics,
))? {
let type_ent = TypeEnt::define_with_opt_id(
self.ctx,
self.arena,
overwrite_id,
&mut type_decl.ident,
parent,
None,
Type::Subtype(subtype),
src_span,
self.source(),
);
scope.add(type_ent.into(), diagnostics);
}
}
TypeDefinition::Physical(ref mut physical) => {
self.range_with_ttyp(
scope,
self.universal_integer().into(),
&mut physical.range,
diagnostics,
)?;
let phys_type = TypeEnt::define_with_opt_id(
self.ctx,
self.arena,
overwrite_id,
&mut type_decl.ident,
parent,
None,
Type::Physical,
src_span,
self.source(),
);
scope.add(phys_type.into(), diagnostics);
let primary = self.define(
&mut physical.primary_unit,
parent,
AnyEntKind::PhysicalLiteral(phys_type),
src_span,
);
unsafe {
self.arena.add_implicit(phys_type.id(), primary);
}
scope.add(primary, diagnostics);
for (secondary_unit_name, value) in physical.secondary_units.iter_mut() {
match self.resolve_physical_unit(scope, &mut value.item.unit) {
Ok(secondary_unit_type) => {
if secondary_unit_type.base_type() != phys_type {
diagnostics.add(
value.item.unit.item.pos(self.ctx),
format!(
"Physical unit of type '{}' does not match {}",
secondary_unit_type.designator(),
phys_type.describe()
),
ErrorCode::TypeMismatch,
)
}
}
Err(err) => diagnostics.push(err),
}
let secondary_unit = self.define(
secondary_unit_name,
parent,
AnyEntKind::PhysicalLiteral(phys_type),
src_span,
);
unsafe {
self.arena.add_implicit(phys_type.id(), secondary_unit);
}
scope.add(secondary_unit, diagnostics)
}
for ent in self.physical_implicits(phys_type) {
unsafe {
self.arena.add_implicit(phys_type.id(), ent);
}
scope.add(ent, diagnostics);
}
}
TypeDefinition::Incomplete(..) => {
unreachable!("Handled elsewhere");
}
TypeDefinition::Numeric(ref mut range) => {
self.range_unknown_typ(scope, range, diagnostics)?;
let universal_type = if let Some(range_typ) =
as_fatal(self.range_type(scope, range, diagnostics))?
{
if range_typ.is_any_integer() {
UniversalType::Integer
} else if range_typ.is_any_real() {
UniversalType::Real
} else {
diagnostics.add(
range.span().pos(self.ctx),
"Expected real or integer range",
ErrorCode::TypeMismatch,
);
return Ok(());
}
} else {
return Ok(());
};
let type_ent = TypeEnt::define_with_opt_id(
self.ctx,
self.arena,
overwrite_id,
&mut type_decl.ident,
parent,
None,
match universal_type {
UniversalType::Integer => Type::Integer,
UniversalType::Real => Type::Real,
},
src_span,
self.source(),
);
scope.add(type_ent.into(), diagnostics);
for ent in self.numeric_implicits(universal_type, type_ent) {
unsafe {
self.arena.add_implicit(type_ent.id(), ent);
}
scope.add(ent, diagnostics);
}
}
TypeDefinition::File(ref mut type_mark) => {
let file_type = TypeEnt::define_with_opt_id(
self.ctx,
self.arena,
overwrite_id,
&mut type_decl.ident,
parent,
None,
Type::File,
src_span,
self.source(),
);
if let Some(type_mark) = as_fatal(self.type_name(
scope,
type_mark.span,
&mut type_mark.item,
diagnostics,
))? {
for ent in self.create_implicit_file_type_subprograms(file_type, type_mark) {
unsafe {
self.arena.add_implicit(file_type.id(), ent);
}
scope.add(ent, diagnostics);
}
}
scope.add(file_type.into(), diagnostics);
}
}
Ok(())
}
/// The matching operators such as ?= are defined for 1d arrays of bit and std_ulogic element type
fn has_matching_op(&self, typ: TypeEnt<'a>) -> bool {
if self.is_std_logic_1164 {
// Within the std_logic_1164 we do not have efficient access to the types
typ.designator() == &Designator::Identifier(self.root.symbol_utf8("std_ulogic"))
} else {
if let Some(ref standard_types) = self.root.standard_types {
if typ.id() == standard_types.bit {
return true;
}
}
if let Some(id) = self.root.std_ulogic {
if typ.id() == id {
return true;
}
}
false
}
}
fn analyze_subtype_constraint(
&self,
scope: &Scope<'a>,
pos: &SrcPos, // The position of the root type mark
base_type: BaseType<'a>,
constraint: &mut SubtypeConstraint,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
match constraint {
SubtypeConstraint::Array(ref mut dranges, ref mut constraint) => {
if let Type::Array { indexes, elem_type } = base_type.kind() {
for (idx, drange) in dranges.iter_mut().enumerate() {
if let Some(index_typ) = indexes.get(idx) {
if let Some(index_typ) = index_typ {
self.drange_with_ttyp(
scope,
(*index_typ).into(),
&mut drange.item,
diagnostics,
)?;
} else {
self.drange_unknown_type(scope, &mut drange.item, diagnostics)?;
}
} else {
diagnostics.add(
drange.span().pos(self.ctx),
format!("Got extra index constraint for {}", base_type.describe()),
ErrorCode::TooManyConstraints,
);
}
}
// empty dranges means (open)
if dranges.len() < indexes.len() && !dranges.is_empty() {
diagnostics.add(
pos,
format!(
"Too few index constraints for {}. Got {} but expected {}",
base_type.describe(),
dranges.len(),
indexes.len()
),
ErrorCode::TooFewConstraints,
);
}
if let Some(constraint) = constraint {
self.analyze_subtype_constraint(
scope,
&constraint.span.pos(self.ctx),
elem_type.base(),
&mut constraint.item,
diagnostics,
)?;
}
} else {
diagnostics.add(
pos,
format!(
"Array constraint cannot be used for {}",
base_type.describe()
),
ErrorCode::IllegalConstraint,
);
}
}
SubtypeConstraint::Range(ref mut range) => {
if base_type.is_scalar() {
self.range_with_ttyp(scope, base_type.into(), range, diagnostics)?;
} else {
diagnostics.add(
pos,
format!(
"Scalar constraint cannot be used for {}",
base_type.describe()
),
ErrorCode::IllegalConstraint,
);
}
}
SubtypeConstraint::Record(ref mut constraints) => {
if let Type::Record(region) = base_type.kind() {
for constraint in constraints.iter_mut() {
let ElementConstraint { ident, constraint } = constraint;
let des = Designator::Identifier(ident.item.clone());
if let Some(elem) = region.lookup(&des) {
self.analyze_subtype_constraint(
scope,
&constraint.pos(self.ctx),
elem.type_mark().base(),
&mut constraint.item,
diagnostics,
)?;
} else {
diagnostics.push(Diagnostic::no_declaration_within(
&base_type,
ident.pos(self.ctx),
&des,
))
}
}
} else {
diagnostics.add(
pos,
format!(
"Record constraint cannot be used for {}",
base_type.describe()
),
ErrorCode::IllegalConstraint,
);
}
}
}
Ok(())
}
pub fn analyze_subtype_indication(
&self,
scope: &Scope<'a>,
subtype_indication: &mut SubtypeIndication,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
as_fatal(self.resolve_subtype_indication(scope, subtype_indication, diagnostics))
.map(|_| ())
}
}

1649
vhdl_lang/src/ast.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,232 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2020, Olof Kraigher olof.kraigher@gmail.com
use super::*;
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub enum PrimaryKind {
Entity,
Configuration,
Package,
PackageInstance,
Context,
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub enum SecondaryKind {
Architecture,
PackageBody,
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
pub enum AnyKind {
Primary(PrimaryKind),
Secondary(SecondaryKind),
}
/// Stores a design unit's name and, for secondary units,
/// the name of its associated primary unit.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum UnitKey {
Primary(Symbol),
Secondary(Symbol, Symbol),
}
/// Identifies a design unit.
///
/// Additionally, a `UnitId` specifies a unit's name, kind, library,
/// and, for secondary units, its associated primary unit.
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct UnitId {
library_name: Symbol,
kind: AnyKind,
key: UnitKey,
}
impl UnitId {
pub fn primary(library_name: &Symbol, kind: PrimaryKind, name: &Symbol) -> UnitId {
UnitId {
library_name: library_name.clone(),
kind: AnyKind::Primary(kind),
key: UnitKey::Primary(name.clone()),
}
}
pub fn secondary(
library_name: &Symbol,
kind: SecondaryKind,
primary_name: &Symbol,
name: &Symbol,
) -> UnitId {
UnitId {
library_name: library_name.clone(),
kind: AnyKind::Secondary(kind),
key: UnitKey::Secondary(primary_name.clone(), name.clone()),
}
}
pub fn package(library_name: &Symbol, name: &Symbol) -> UnitId {
Self::primary(library_name, PrimaryKind::Package, name)
}
pub fn library_name(&self) -> &Symbol {
&self.library_name
}
/// For a secondary unit, returns the name of the associated primary unit;
/// for a primary unit, returns its own name.
pub fn primary_name(&self) -> &Symbol {
match self.key {
UnitKey::Primary(ref name) => name,
UnitKey::Secondary(ref name, _) => name,
}
}
pub fn secondary_name(&self) -> Option<&Symbol> {
match self.key {
UnitKey::Primary(_) => None,
UnitKey::Secondary(_, ref name) => Some(name),
}
}
}
pub trait HasUnitId {
fn unit_id(&self) -> &UnitId;
fn kind(&self) -> AnyKind {
self.unit_id().kind
}
fn key(&self) -> &UnitKey {
&self.unit_id().key
}
fn secondary_kind(&self) -> Option<SecondaryKind> {
match self.unit_id().kind {
AnyKind::Secondary(kind) => Some(kind),
AnyKind::Primary(..) => None,
}
}
fn describe(&self) -> String {
match self.key() {
UnitKey::Primary(name) => format!("{} '{}'", self.kind().describe(), name),
UnitKey::Secondary(primary_name, name) => match self.secondary_kind().unwrap() {
SecondaryKind::Architecture => format!(
"{} '{}' of '{}'",
self.kind().describe(),
name,
primary_name
),
SecondaryKind::PackageBody => format!("{} '{}'", self.kind().describe(), name),
},
}
}
}
impl HasUnitId for UnitId {
fn unit_id(&self) -> &UnitId {
self
}
}
macro_rules! delegate_primary {
($primary:expr, $unit:ident, $block:expr) => {
match $primary {
AnyPrimaryUnit::Entity($unit) => $block,
AnyPrimaryUnit::Package($unit) => $block,
AnyPrimaryUnit::PackageInstance($unit) => $block,
AnyPrimaryUnit::Context($unit) => $block,
AnyPrimaryUnit::Configuration($unit) => $block,
}
};
}
macro_rules! delegate_secondary {
($primary:expr, $unit:ident, $block:expr) => {
match $primary {
AnySecondaryUnit::Architecture($unit) => $block,
AnySecondaryUnit::PackageBody($unit) => $block,
}
};
}
macro_rules! delegate_any {
($primary:expr, $unit:ident, $block:expr) => {
match $primary {
AnyDesignUnit::Primary($unit) => $block,
AnyDesignUnit::Secondary($unit) => $block,
}
};
}
impl AnyKind {
pub fn describe(&self) -> &str {
match self {
AnyKind::Primary(kind) => kind.describe(),
AnyKind::Secondary(kind) => kind.describe(),
}
}
}
impl PrimaryKind {
pub fn kind_of(unit: &AnyPrimaryUnit) -> PrimaryKind {
match unit {
AnyPrimaryUnit::Entity(..) => PrimaryKind::Entity,
AnyPrimaryUnit::Configuration(..) => PrimaryKind::Configuration,
AnyPrimaryUnit::Package(..) => PrimaryKind::Package,
AnyPrimaryUnit::PackageInstance(..) => PrimaryKind::PackageInstance,
AnyPrimaryUnit::Context(..) => PrimaryKind::Context,
}
}
pub fn describe(&self) -> &str {
match self {
PrimaryKind::Entity => "entity",
PrimaryKind::Configuration => "configuration",
PrimaryKind::Package => "package",
PrimaryKind::PackageInstance => "package instance",
PrimaryKind::Context => "context",
}
}
}
impl SecondaryKind {
pub fn kind_of(unit: &AnySecondaryUnit) -> SecondaryKind {
match unit {
AnySecondaryUnit::Architecture(..) => SecondaryKind::Architecture,
AnySecondaryUnit::PackageBody(..) => SecondaryKind::PackageBody,
}
}
pub fn describe(&self) -> &str {
match self {
SecondaryKind::Architecture => "architecture",
SecondaryKind::PackageBody => "package body",
}
}
}
impl AnyDesignUnit {
pub fn as_primary_mut(&mut self) -> Option<&mut AnyPrimaryUnit> {
if let AnyDesignUnit::Primary(unit) = self {
Some(unit)
} else {
None
}
}
}
/// Upper case first letter
pub fn capitalize(string: &str) -> String {
let mut result = String::with_capacity(string.len());
let mut chars = string.chars();
if let Some(chr) = chars.next() {
result.push(chr.to_ascii_uppercase());
}
for chr in chars {
result.push(chr);
}
result
}

View File

@ -0,0 +1,83 @@
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{LabeledConcurrentStatement, LabeledSequentialStatement, WithDecl};
use crate::{HasTokenSpan, TokenId};
use vhdl_lang::ast::WithToken;
use vhdl_lang::TokenSpan;
impl HasTokenSpan for TokenId {
fn get_start_token(&self) -> TokenId {
*self
}
fn get_end_token(&self) -> TokenId {
*self
}
}
impl HasTokenSpan for TokenSpan {
fn get_start_token(&self) -> TokenId {
self.start_token
}
fn get_end_token(&self) -> TokenId {
self.end_token
}
}
impl<T> HasTokenSpan for WithToken<T> {
fn get_start_token(&self) -> TokenId {
self.token
}
fn get_end_token(&self) -> TokenId {
self.token
}
}
impl<T> HasTokenSpan for WithDecl<WithToken<T>> {
fn get_start_token(&self) -> TokenId {
self.tree.get_start_token()
}
fn get_end_token(&self) -> TokenId {
self.tree.get_end_token()
}
}
impl<T> HasTokenSpan for WithTokenSpan<T> {
fn get_start_token(&self) -> TokenId {
self.span.start_token
}
fn get_end_token(&self) -> TokenId {
self.span.end_token
}
}
impl HasTokenSpan for LabeledConcurrentStatement {
fn get_start_token(&self) -> TokenId {
if let Some(label) = &self.label.tree {
label.token
} else {
self.statement.span.start_token
}
}
fn get_end_token(&self) -> TokenId {
self.statement.span.end_token
}
}
impl HasTokenSpan for LabeledSequentialStatement {
fn get_start_token(&self) -> TokenId {
if let Some(label) = &self.label.tree {
label.token
} else {
self.statement.span.start_token
}
}
fn get_end_token(&self) -> TokenId {
self.statement.span.end_token
}
}

2343
vhdl_lang/src/ast/display.rs Normal file

File diff suppressed because it is too large Load Diff

2049
vhdl_lang/src/ast/search.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,121 @@
/// For most applications in the context of a language server,
/// the lexical position (i.e., a position in the source code)
/// of all AST nodes must be known.
/// In the context of `vhdl_lang`, this information is provided
/// using Token information. Each AST element knows the token span that it was declared in.
/// Information, such as the position can be queried using the `pos(TokenAccess)` method.
/// A [TokenAccess] is a context object that is passed in all relevant operations
/// (i.e., when traversing the AST using the [Search] trait
/// or when getting the source code information when generating code outlines in a language server).
/// This is also the mechanic used to extract supplementary information, such as comments for
/// documentation generation.
use crate::{SrcPos, TokenAccess, TokenId, TokenSpan};
/// A struct that associates some generic item to a single token.
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct WithToken<T> {
pub item: T,
pub token: TokenId,
}
impl<T> WithToken<T> {
pub fn new(item: T, token: TokenId) -> WithToken<T> {
WithToken { item, token }
}
/// Retrieves the position of this object using the provided `TokenAccess`.
pub fn pos<'a>(&'a self, ctx: &'a dyn TokenAccess) -> &'a SrcPos {
ctx.get_pos(self.token)
}
/// Maps this element into another element applying the given function
/// but retaining the source location (i.e., the token).
pub(crate) fn map_into<F, U>(self, f: F) -> WithToken<U>
where
F: FnOnce(T) -> U,
{
WithToken {
item: f(self.item),
token: self.token,
}
}
/// Maps this element into another element applying the given function
/// but retaining the source location.
/// The returned object's `TokenSpan` will have this token id as start and end.
pub(crate) fn map_into_span<F, U>(self, f: F) -> WithTokenSpan<U>
where
F: FnOnce(T) -> U,
{
WithTokenSpan {
item: f(self.item),
span: self.token.into(),
}
}
}
/// A struct that associates some generic item to a contiguous span of tokens.
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct WithTokenSpan<T> {
pub item: T,
pub span: TokenSpan,
}
impl<T> WithTokenSpan<T> {
pub fn new(item: T, span: TokenSpan) -> WithTokenSpan<T> {
WithTokenSpan { item, span }
}
pub fn from(item: T, span: impl Into<TokenSpan>) -> WithTokenSpan<T> {
WithTokenSpan {
item,
span: span.into(),
}
}
/// Retrieves the position of this object using the provided `TokenAccess`.
pub fn pos(&self, ctx: &dyn TokenAccess) -> SrcPos {
self.span.pos(ctx)
}
/// Maps this element into another element applying the given function
/// but retaining the source location (i.e., the token span).
pub(crate) fn map_into<F, U>(self, f: F) -> WithTokenSpan<U>
where
F: FnOnce(T) -> U,
{
WithTokenSpan {
item: f(self.item),
span: self.span,
}
}
/// Attempts to map this element into another element applying the given function.
/// If the function returns `None`, this will also return `None`.
/// Otherwise, the semantics are the same as [map_into](WithTokenSpan::map_into)
pub(crate) fn try_map_into<F, U>(self, f: F) -> Option<WithTokenSpan<U>>
where
F: FnOnce(T) -> Option<U>,
{
Some(WithTokenSpan {
item: f(self.item)?,
span: self.span,
})
}
/// Returns a new `WithTokenSpan` object that encompasses this item
/// but extends the token span starting with the given token.
pub(crate) fn start_with(self, id: TokenId) -> Self {
WithTokenSpan {
item: self.item,
span: self.span.start_with(id),
}
}
pub fn as_ref(&self) -> WithTokenSpan<&T> {
WithTokenSpan {
item: &self.item,
span: self.span,
}
}
}

564
vhdl_lang/src/ast/util.rs Normal file
View File

@ -0,0 +1,564 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
//! Name conversions
use super::*;
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::named_entity::{Concurrent, Sequential};
use crate::TokenSpan;
impl WithTokenSpan<Name> {
pub fn suffix_pos(&self) -> TokenSpan {
match self.item {
Name::Designator(..) => self.span,
Name::Selected(_, ref suffix) => suffix.token.into(),
// @TODO add pos of .all?
Name::SelectedAll(ref prefix) => prefix.span,
Name::CallOrIndexed(ref fcall) => fcall.name.span,
Name::Slice(ref prefix, ..) => prefix.span,
Name::Attribute(ref attr, ..) => attr.name.span,
Name::External(..) => self.span,
}
}
}
pub fn to_simple_name(ctx: &dyn TokenAccess, name: WithTokenSpan<Name>) -> DiagnosticResult<Ident> {
match name.item {
Name::Designator(WithRef {
item: Designator::Identifier(ident),
..
}) => Ok(WithToken {
item: ident,
token: name.span.start_token,
}),
_ => Err(Diagnostic::new(
name.span.pos(ctx),
"Expected simple name",
ErrorCode::SyntaxError,
)),
}
}
pub fn as_simple_name_mut(name: &mut Name) -> Option<&mut WithRef<Designator>> {
match name {
Name::Designator(
des @ WithRef {
item: Designator::Identifier(_),
..
},
) => Some(des),
_ => None,
}
}
pub fn as_name_mut(expr: &mut Expression) -> Option<&mut Name> {
match expr {
Expression::Name(name) => Some(name.as_mut()),
_ => None,
}
}
pub trait HasDesignator {
fn designator(&self) -> &Designator;
}
impl<T: HasDesignator> HasDesignator for WithTokenSpan<T> {
fn designator(&self) -> &Designator {
self.item.designator()
}
}
impl HasDesignator for Designator {
fn designator(&self) -> &Designator {
self
}
}
impl<T: HasDesignator> HasDesignator for WithRef<T> {
fn designator(&self) -> &Designator {
self.item.designator()
}
}
impl HasIdent for WithRef<Ident> {
fn ident(&self) -> &Ident {
&self.item
}
}
impl Designator {
pub fn into_ref(self) -> WithRef<Designator> {
WithRef::new(self)
}
}
impl Ident {
pub fn into_ref(self) -> WithRef<Ident> {
WithRef::new(self)
}
}
impl WithTokenSpan<Designator> {
pub fn into_ref(self) -> WithTokenSpan<WithRef<Designator>> {
self.map_into(|name| name.into_ref())
}
}
impl WithToken<Designator> {
pub fn into_ref(self) -> WithToken<WithRef<Designator>> {
self.map_into(|name| name.into_ref())
}
}
pub trait HasIdent {
fn ident(&self) -> &Ident;
fn name(&self) -> &Symbol {
&self.ident().item
}
fn ident_pos<'a>(&'a self, ctx: &'a dyn TokenAccess) -> &SrcPos {
self.ident().pos(ctx)
}
}
impl HasIdent for Ident {
fn ident(&self) -> &Ident {
self
}
}
impl<T: HasIdent> HasIdent for WithDecl<T> {
fn ident(&self) -> &Ident {
self.tree.ident()
}
}
impl HasIdent for EntityDeclaration {
fn ident(&self) -> &Ident {
self.ident.ident()
}
}
impl HasIdent for PackageDeclaration {
fn ident(&self) -> &Ident {
self.ident.ident()
}
}
impl HasIdent for PackageBody {
fn ident(&self) -> &Ident {
&self.ident.tree
}
}
impl HasIdent for ArchitectureBody {
fn ident(&self) -> &Ident {
self.ident.ident()
}
}
impl HasIdent for PackageInstantiation {
fn ident(&self) -> &Ident {
self.ident.ident()
}
}
impl HasIdent for ContextDeclaration {
fn ident(&self) -> &Ident {
self.ident.ident()
}
}
impl HasIdent for ConfigurationDeclaration {
fn ident(&self) -> &Ident {
self.ident.ident()
}
}
impl HasIdent for AnyPrimaryUnit {
fn ident(&self) -> &Ident {
match self {
AnyPrimaryUnit::Entity(ref unit) => unit.ident(),
AnyPrimaryUnit::Configuration(ref unit) => unit.ident(),
AnyPrimaryUnit::Package(ref unit) => unit.ident(),
AnyPrimaryUnit::PackageInstance(ref unit) => unit.ident(),
AnyPrimaryUnit::Context(ref unit) => unit.ident(),
}
}
}
impl HasIdent for AnySecondaryUnit {
fn ident(&self) -> &Ident {
match self {
AnySecondaryUnit::PackageBody(ref unit) => unit.ident(),
AnySecondaryUnit::Architecture(ref unit) => unit.ident(),
}
}
}
impl HasIdent for AnyDesignUnit {
fn ident(&self) -> &Ident {
match self {
AnyDesignUnit::Primary(ref unit) => unit.ident(),
AnyDesignUnit::Secondary(ref unit) => unit.ident(),
}
}
}
impl<'a, T: HasIdent> From<&'a T> for WithToken<Designator> {
fn from(other: &'a T) -> WithToken<Designator> {
other.ident().to_owned().map_into(Designator::Identifier)
}
}
/// Primary identifier in secondary units
pub trait HasPrimaryIdent {
fn primary_ident(&self) -> &Ident;
fn primary_name(&self) -> &Symbol {
&self.primary_ident().item
}
}
impl HasPrimaryIdent for ArchitectureBody {
fn primary_ident(&self) -> &Ident {
&self.entity_name.item
}
}
impl HasPrimaryIdent for PackageBody {
fn primary_ident(&self) -> &Ident {
&self.ident.tree
}
}
impl HasPrimaryIdent for AnySecondaryUnit {
fn primary_ident(&self) -> &Ident {
match self {
AnySecondaryUnit::Architecture(unit) => unit.primary_ident(),
AnySecondaryUnit::PackageBody(unit) => unit.primary_ident(),
}
}
}
impl From<EnumerationLiteral> for Designator {
fn from(other: EnumerationLiteral) -> Designator {
match other {
EnumerationLiteral::Identifier(ident) => Designator::Identifier(ident),
EnumerationLiteral::Character(byte) => Designator::Character(byte),
}
}
}
impl From<Symbol> for Designator {
fn from(other: Symbol) -> Designator {
Designator::Identifier(other)
}
}
impl From<WithTokenSpan<Symbol>> for WithTokenSpan<Designator> {
fn from(other: WithTokenSpan<Symbol>) -> WithTokenSpan<Designator> {
other.map_into(|sym| sym.into())
}
}
impl<'a> From<&'a Symbol> for Designator {
fn from(other: &'a Symbol) -> Designator {
other.clone().into()
}
}
impl SubprogramDesignator {
pub fn into_designator(self) -> Designator {
match self {
SubprogramDesignator::Identifier(ident) => Designator::Identifier(ident),
SubprogramDesignator::OperatorSymbol(ident) => Designator::OperatorSymbol(ident),
}
}
}
impl SubprogramSpecification {
pub fn token(&self) -> TokenId {
match self {
SubprogramSpecification::Function(ref function) => function.designator.tree.token,
SubprogramSpecification::Procedure(ref procedure) => procedure.designator.tree.token,
}
}
}
impl EnumerationLiteral {
pub fn into_designator(self) -> Designator {
match self {
EnumerationLiteral::Identifier(ident) => Designator::Identifier(ident),
EnumerationLiteral::Character(byte) => Designator::Character(byte),
}
}
}
impl Designator {
pub fn as_identifier(&self) -> Option<&Symbol> {
if let Designator::Identifier(sym) = self {
Some(sym)
} else {
None
}
}
pub fn expect_identifier(&self) -> &Symbol {
self.as_identifier().unwrap()
}
pub fn describe(&self) -> String {
match self {
Designator::Character(chr) => format!("'{chr}'"),
Designator::Identifier(ident) => format!("'{ident}'"),
Designator::OperatorSymbol(op) => format!("operator \"{op}\""),
Designator::Anonymous(_) => "<anonymous>".to_owned(),
}
}
}
impl Name {
pub fn suffix_reference_mut(&mut self) -> Option<&mut Reference> {
match self {
Name::Designator(suffix) => Some(&mut suffix.reference),
Name::Selected(_, suffix) => Some(&mut suffix.item.reference),
_ => None,
}
}
// Get an already set suffix reference such as when an ambiguous overloaded call has already been resolved
pub fn get_suffix_reference(&self) -> Option<EntityId> {
match self {
Name::Designator(suffix) => suffix.reference.get(),
Name::Selected(_, suffix) => suffix.item.reference.get(),
_ => None,
}
}
pub fn prefix(&self) -> Option<&Designator> {
match self {
Self::Attribute(attr) => attr.name.item.prefix(),
Self::Designator(d) => Some(d.designator()),
Self::External(..) => None,
Self::CallOrIndexed(fcall) => fcall.name.item.prefix(),
Self::SelectedAll(name) => name.item.prefix(),
Self::Selected(name, ..) => name.item.prefix(),
Self::Slice(name, ..) => name.item.prefix(),
}
}
/// Returns true if the name is purely a selected name
/// Example: a.b.c
pub fn is_selected_name(&self) -> bool {
match self {
Name::Designator(_) => true,
Name::Selected(prefix, _) => prefix.item.is_selected_name(),
_ => false,
}
}
}
impl CallOrIndexed {
// During parsing function calls and indexed names are ambiguous
// Thus we convert function calls to indexed names during the analysis stage
pub fn as_indexed(&mut self) -> Option<IndexedName<'_>> {
if !self.could_be_indexed_name() {
return None;
}
let CallOrIndexed {
ref mut name,
ref mut parameters,
} = self;
let mut indexes: Vec<Index<'_>> = Vec::with_capacity(parameters.items.len());
for elem in parameters.items.iter_mut() {
if let ActualPart::Expression(ref mut expr) = &mut elem.actual.item {
indexes.push(Index {
pos: elem.actual.span,
expr,
});
}
}
Some(IndexedName { name, indexes })
}
pub fn could_be_indexed_name(&self) -> bool {
self.parameters
.items
.iter()
.all(|assoc| assoc.formal.is_none() && !matches!(assoc.actual.item, ActualPart::Open))
}
}
pub struct IndexedName<'a> {
pub name: &'a mut WithTokenSpan<Name>,
pub indexes: Vec<Index<'a>>,
}
pub struct Index<'a> {
pub pos: TokenSpan,
pub expr: &'a mut Expression,
}
impl AttributeName {
pub fn as_range(&self) -> Option<RangeAttribute> {
if let AttributeDesignator::Range(r) = self.attr.item {
Some(r)
} else {
None
}
}
pub fn as_type(&self) -> Option<TypeAttribute> {
if self.signature.is_none() && self.expr.is_none() {
if let AttributeDesignator::Type(t) = self.attr.item {
Some(t)
} else {
None
}
} else {
None
}
}
}
impl RangeConstraint {
pub fn span(&self) -> TokenSpan {
self.left_expr.span.combine(self.right_expr.span)
}
}
impl crate::ast::Range {
pub fn span(&self) -> TokenSpan {
use crate::ast::Range::*;
match self {
Range(constraint) => constraint.span(),
Attribute(attr) => attr.name.span.end_with(attr.attr.token),
}
}
}
impl DiscreteRange {
pub fn span(&self) -> TokenSpan {
match self {
DiscreteRange::Discrete(type_mark, _) => type_mark.span,
DiscreteRange::Range(range) => range.span(),
}
}
}
impl SubprogramSpecification {
pub fn subpgm_designator(&self) -> &WithToken<SubprogramDesignator> {
match self {
SubprogramSpecification::Procedure(s) => &s.designator.tree,
SubprogramSpecification::Function(s) => &s.designator.tree,
}
}
pub fn reference_mut(&mut self) -> &mut Reference {
match self {
SubprogramSpecification::Function(ref mut function) => &mut function.designator.decl,
SubprogramSpecification::Procedure(ref mut procedure) => &mut procedure.designator.decl,
}
}
}
impl SubprogramDeclaration {
pub fn subpgm_designator(&self) -> &WithToken<SubprogramDesignator> {
self.specification.subpgm_designator()
}
pub fn reference_mut(&mut self) -> &mut Reference {
self.specification.reference_mut()
}
}
impl ConcurrentStatement {
pub fn label_typ(&self) -> Option<Concurrent> {
use ConcurrentStatement::*;
match self {
ProcedureCall(_) => None,
Block(_) => Some(Concurrent::Block),
Process(_) => Some(Concurrent::Process),
Assert(_) => None,
Assignment(_) => None,
Instance(_) => Some(Concurrent::Instance),
ForGenerate(_) | IfGenerate(_) | CaseGenerate(_) => Some(Concurrent::Generate),
}
}
pub fn end_label_pos(&self) -> Option<&SrcPos> {
use ConcurrentStatement::*;
match self {
ProcedureCall(_) => None,
Block(value) => value.end_label_pos.as_ref(),
Process(value) => value.end_label_pos.as_ref(),
Assert(_) => None,
Assignment(_) => None,
Instance(_) => None,
ForGenerate(value) => value.end_label_pos.as_ref(),
IfGenerate(value) => value.end_label_pos.as_ref(),
CaseGenerate(value) => value.end_label_pos.as_ref(),
}
}
pub fn can_have_label(&self) -> bool {
self.label_typ().is_some()
}
}
impl SequentialStatement {
pub fn label_typ(&self) -> Option<Sequential> {
use SequentialStatement::*;
match self {
Wait(_) => None,
Assert(_) => None,
Report(_) => None,
VariableAssignment(_) => None,
SignalAssignment(_) => None,
SignalForceAssignment(_) => None,
SignalReleaseAssignment(_) => None,
ProcedureCall(_) => None,
If(_) => Some(Sequential::If),
Case(_) => Some(Sequential::Case),
Loop(_) => Some(Sequential::Loop),
Next(_) => None,
Exit(_) => None,
Return(_) => None,
Null => None,
}
}
pub fn end_label_pos(&self) -> Option<&SrcPos> {
use SequentialStatement::*;
match self {
Wait(_) => None,
Assert(_) => None,
Report(_) => None,
VariableAssignment(_) => None,
SignalAssignment(_) => None,
SignalForceAssignment(_) => None,
SignalReleaseAssignment(_) => None,
ProcedureCall(_) => None,
If(value) => value.end_label_pos.as_ref(),
Case(value) => value.end_label_pos.as_ref(),
Loop(value) => value.end_label_pos.as_ref(),
Next(_) => None,
Exit(_) => None,
Return(_) => None,
Null => None,
}
}
pub fn can_have_label(&self) -> bool {
self.label_typ().is_some()
}
}

113
vhdl_lang/src/completion.rs Normal file
View File

@ -0,0 +1,113 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::DesignRoot;
use crate::ast::{AttributeDesignator, Designator};
use crate::completion::attributes::completions_for_attribute_name;
use crate::completion::generic::generic_completions;
use crate::completion::libraries::list_all_libraries;
use crate::completion::map_aspect::completions_for_map_aspect;
use crate::completion::selected::completions_for_selected_name;
use crate::completion::tokenizer::tokenize_input;
use crate::syntax::Kind;
use crate::{EntRef, Position, Source};
mod attributes;
mod entity_instantiation;
mod generic;
mod libraries;
mod map_aspect;
mod region;
mod selected;
mod tokenizer;
#[derive(Debug, PartialEq, Clone)]
pub enum CompletionItem<'a> {
/// Simply complete the entities
/// e.g., `use std.` should simply list all elements in the std library
Simple(EntRef<'a>),
/// Formal parameter, e.g., in a port map
/// `port map (` might choose to complete `<item> => $1`
Formal(EntRef<'a>),
/// Multiple overloaded items are applicable.
/// The argument is the count of overloaded items in total.
Overloaded(Designator, usize),
/// Complete a keyword
Keyword(Kind),
/// Complete the 'work' library.
/// This is handled in a special manner because the
/// actual work library (using [CompletionItem::Simple] would complete the actual name
/// of the library, not the string 'work'.
Work,
/// Entity or component instantiation, i.e.,
/// ```vhdl
/// my_ent: entity work.foo
/// generic map (
/// -- ...
/// )
/// port map (
/// -- ...
/// );
/// ```
///
/// The second argument is a vector of architectures that are associated
/// to the entity, if the first argument is an entity.
///
/// For a component instantiation, the first argument is a reference to the
/// component. The second argument will always be empty.
Instantiation(EntRef<'a>, Vec<EntRef<'a>>),
/// Complete an attribute designator (i.e. `'range`, `'stable`, ...)
Attribute(AttributeDesignator),
}
macro_rules! kind {
($kind: pat) => {
crate::syntax::Token { kind: $kind, .. }
};
}
/// Main entry point for completion. Given a source-file and a cursor position,
/// lists available completion options at the cursor position.
pub fn list_completion_options<'a>(
root: &'a DesignRoot,
source: &Source,
cursor: Position,
) -> Vec<CompletionItem<'a>> {
use crate::syntax::Kind::*;
let tokens = tokenize_input(root.symbols(), source, cursor);
match &tokens[..] {
// With the current implementation of completions, this is annoying, rather than helpful.
// SemiColons will try to complete the ';' character, which when pressing enter will cause
// ';' to appear instead of a simple ; character.
// Therefore, we do not return any completions here.
[.., kind!(SemiColon)] => vec![],
[.., kind!(Library)]
| [.., kind!(Library), kind!(Identifier)]
| [.., kind!(Use)]
| [.., kind!(Use), kind!(Identifier)] => list_all_libraries(root),
[.., token, kind!(Dot)] | [.., token, kind!(Dot), kind!(Identifier)] => {
// get the entity before the token.
// We rely on the syntax parsing to be resilient enough for this to yield a reasonable value.
// Otherwise, we just return an empty value.
if let Some((_, ent)) = root.item_at_cursor(source, token.pos.start()) {
completions_for_selected_name(root, ent)
} else {
vec![]
}
}
[.., token, kind!(Tick)] | [.., token, kind!(Tick), kind!(Identifier)] => {
if let Some((_, ent)) = root.item_at_cursor(source, token.pos.start()) {
completions_for_attribute_name(ent)
} else {
vec![]
}
}
[.., kind!(LeftPar | Comma)] | [.., kind!(LeftPar | Comma), kind!(Identifier)] => {
completions_for_map_aspect(root, cursor, source)
}
_ => generic_completions(root, cursor, source),
}
}

View File

@ -0,0 +1,189 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{AttributeDesignator, ObjectClass, RangeAttribute, TypeAttribute};
use crate::{named_entity, AnyEntKind, CompletionItem, EntRef, Object};
/// Produces completions for an attribute name, i.e.,
/// `foo'`
/// The provided ent is the entity directly before the tick, i.e.,
/// `foo` in the example above.
pub(crate) fn completions_for_attribute_name(ent: EntRef<'_>) -> Vec<CompletionItem<'_>> {
let mut attributes: Vec<AttributeDesignator> = Vec::new();
attributes.extend([
AttributeDesignator::SimpleName,
AttributeDesignator::InstanceName,
AttributeDesignator::PathName,
]);
match ent.kind() {
AnyEntKind::Type(typ) => extend_attributes_of_type(typ, &mut attributes),
AnyEntKind::Object(obj) => extend_attributes_of_objects(obj, &mut attributes),
AnyEntKind::View(_) => attributes.push(AttributeDesignator::Converse),
_ => {}
}
attributes
.into_iter()
.map(CompletionItem::Attribute)
.chain(
ent.attrs
.values()
.map(|(_, b)| b)
.map(|ent| CompletionItem::Simple(ent.ent)),
)
.collect()
}
/// Extends applicable attributes when the attribute name is a type.
fn extend_attributes_of_type(
typ: &named_entity::Type<'_>,
attributes: &mut Vec<AttributeDesignator>,
) {
use AttributeDesignator::*;
if typ.is_scalar() {
attributes.extend([Left, Right, Low, High, Ascending, Image, Value]);
} else if typ.is_array() {
attributes.extend([
Left,
Right,
Low,
High,
Range(RangeAttribute::Range),
Range(RangeAttribute::ReverseRange),
Length,
Ascending,
Type(TypeAttribute::Element),
]);
}
if typ.is_discrete() {
attributes.extend([Pos, Val, Succ, Pred, LeftOf, RightOf]);
}
}
/// Extends applicable attributes when the attribute name is an object.
fn extend_attributes_of_objects(obj: &Object<'_>, attributes: &mut Vec<AttributeDesignator>) {
extend_attributes_of_type(obj.subtype.type_mark().base_type().kind(), attributes);
attributes.push(AttributeDesignator::Type(TypeAttribute::Subtype));
if obj.class == ObjectClass::Signal {
use crate::ast::SignalAttribute::*;
attributes.extend(
[
Delayed,
Stable,
Quiet,
Transaction,
Event,
Active,
LastEvent,
LastActive,
LastValue,
Driving,
DrivingValue,
]
.map(AttributeDesignator::Signal),
);
}
}
#[cfg(test)]
mod tests {
use crate::analysis::tests::LibraryBuilder;
use crate::ast::RangeAttribute;
use crate::list_completion_options;
use crate::syntax::test::assert_eq_unordered;
use crate::CompletionItem;
#[test]
pub fn completes_attributes() {
use crate::ast::AttributeDesignator::*;
use crate::ast::TypeAttribute::*;
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libA",
"\
package my_pkg is
constant foo : BIT_VECTOR := \"001\";
constant bar: NATURAL := foo'
end package;
",
);
let (root, _) = builder.get_analyzed_root();
let cursor = code.s1("foo'").end();
let options = list_completion_options(&root, code.source(), cursor);
let expected_options = [
Type(Element),
Type(Subtype),
Range(RangeAttribute::Range),
Range(RangeAttribute::ReverseRange),
Ascending,
Left,
Right,
High,
Low,
Length,
InstanceName,
SimpleName,
PathName,
]
.map(CompletionItem::Attribute);
assert_eq_unordered(&options, &expected_options);
}
#[test]
pub fn completes_signal_attributes() {
use crate::ast::AttributeDesignator::*;
use crate::ast::SignalAttribute::*;
use crate::ast::TypeAttribute::*;
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libA",
"\
package my_pkg is
signal foo : BIT_VECTOR := \"001\";
signal bar: NATURAL := foo'
end package;
",
);
let (root, _) = builder.get_analyzed_root();
let cursor = code.s1("foo'").end();
let options = list_completion_options(&root, code.source(), cursor);
let expected_options = [
Type(Element),
Type(Subtype),
Range(RangeAttribute::Range),
Range(RangeAttribute::ReverseRange),
Signal(Delayed),
Signal(Stable),
Signal(Quiet),
Signal(Transaction),
Signal(Event),
Signal(Active),
Signal(LastEvent),
Signal(LastActive),
Signal(LastValue),
Signal(Driving),
Signal(DrivingValue),
Ascending,
Left,
Right,
High,
Low,
Length,
InstanceName,
SimpleName,
PathName,
]
.map(CompletionItem::Attribute);
assert_eq_unordered(&options, &expected_options);
}
}

View File

@ -0,0 +1,369 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::DesignRoot;
use crate::completion::region::any_ent_to_completion_item;
use crate::named_entity::DesignEnt;
use crate::{AnyEntKind, CompletionItem, Design, EntRef, EntityId, HasEntityId};
use itertools::Itertools;
use std::collections::HashSet;
/// List all entities that are visible from a VHDL architecture and return
/// these entities as completion item.
pub(crate) fn get_visible_entities_from_architecture<'a>(
root: &'a DesignRoot,
ent: &DesignEnt<'a>,
) -> Vec<CompletionItem<'a>> {
let mut entities: HashSet<EntityId> = HashSet::new();
if let Design::Architecture(vis, _, ent_of_arch) = ent.kind() {
for ent_ref in vis.visible() {
match ent_ref.kind() {
AnyEntKind::Design(Design::Entity(..)) => {
entities.insert(ent_ref.id());
}
AnyEntKind::Library => {
let Some(name) = ent_ref.library_name() else {
continue;
};
let Some(lib) = root.get_lib(name) else {
continue;
};
let itr = lib
.units()
.flat_map(|locked_unit| locked_unit.unit.get())
.filter(|design_unit| design_unit.is_entity())
.flat_map(|design_unit| design_unit.ent_id())
.filter(|id| id != &ent_of_arch.id()); // Remove the entity that belongs to this architecture
entities.extend(itr);
}
_ => {}
}
}
}
entities
.into_iter()
.map(|eid| any_ent_to_completion_item(root.get_ent(eid), root))
.collect_vec()
}
/// Returns a vec populated with all architectures that belong to a given entity
pub(crate) fn get_architectures_for_entity<'a>(
ent: EntRef<'a>,
root: &'a DesignRoot,
) -> Vec<EntRef<'a>> {
let Some(lib_symbol) = ent.library_name() else {
return vec![];
};
let Some(lib) = root.get_lib(lib_symbol) else {
return vec![];
};
let Some(sym) = ent.designator().as_identifier() else {
return vec![];
};
lib.secondary_units(sym)
.filter_map(|locked_unit| locked_unit.unit.get())
.filter_map(|read_guard| read_guard.ent_id())
.map(|eid| root.get_ent(eid))
.collect()
}
#[cfg(test)]
mod tests {
use crate::analysis::tests::{assert_eq_unordered, check_no_diagnostics, LibraryBuilder};
use crate::{list_completion_options, CompletionItem};
use itertools::Itertools;
#[test]
fn complete_entities() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
entity my_ent is
end my_ent;
entity my_other_ent is
end my_other_ent;
entity my_third_ent is
end my_third_ent;
architecture arch of my_third_ent is
begin
end arch;
",
);
let (root, _) = builder.get_analyzed_root();
let cursor = code.s1("begin").end();
let options = list_completion_options(&root, code.source(), cursor);
let my_ent = root
.search_reference(code.source(), code.s1("my_ent").start())
.unwrap();
let my_other_ent = root
.search_reference(code.source(), code.s1("my_other_ent").start())
.unwrap();
assert!(options.contains(&CompletionItem::Instantiation(my_ent, vec![])));
assert!(options.contains(&CompletionItem::Instantiation(my_other_ent, vec![])));
}
#[test]
fn does_not_complete_in_architecture_declarative_part() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
entity my_ent is
end my_ent;
entity my_other_ent is
end my_other_ent;
entity my_third_ent is
end my_third_ent;
architecture arch of my_third_ent is
begin
end arch;
",
);
let (root, _) = builder.get_analyzed_root();
let cursor = code.s("is", 4).end();
let options = list_completion_options(&root, code.source(), cursor);
let my_ent = root
.search_reference(code.source(), code.s1("my_ent").start())
.unwrap();
let my_other_ent = root
.search_reference(code.source(), code.s1("my_other_ent").start())
.unwrap();
assert!(!options.contains(&CompletionItem::Instantiation(my_ent, vec![])));
assert!(!options.contains(&CompletionItem::Instantiation(my_other_ent, vec![])));
}
#[test]
fn complete_entities_from_different_libraries() {
let mut builder = LibraryBuilder::new();
let code1 = builder.code(
"libA",
"\
entity my_ent is
end my_ent;
",
);
let code2 = builder.code(
"libB",
"\
entity my_ent2 is
end my_ent2;
entity my_ent3 is
end my_ent3;
architecture arch of my_ent3 is
begin
end arch;
",
);
let code3 = builder.code(
"libC",
"\
entity my_ent2 is
end my_ent2;
library libA;
entity my_ent3 is
end my_ent3;
architecture arch of my_ent3 is
begin
end arch;
",
);
let (root, diag) = builder.get_analyzed_root();
check_no_diagnostics(&diag[..]);
let cursor = code2.s1("begin").end();
let options = list_completion_options(&root, code2.source(), cursor);
let my_ent2 = root
.search_reference(code2.source(), code2.s1("my_ent2").start())
.unwrap();
assert!(options.contains(&CompletionItem::Instantiation(my_ent2, vec![])));
let ent1 = root
.search_reference(code1.source(), code1.s1("my_ent").start())
.unwrap();
let cursor = code3.s1("begin").end();
let options = list_completion_options(&root, code3.source(), cursor);
let my_ent2 = root
.search_reference(code3.source(), code3.s1("my_ent2").start())
.unwrap();
assert!(options.contains(&CompletionItem::Instantiation(my_ent2, vec![])));
assert!(options.contains(&CompletionItem::Instantiation(ent1, vec![])));
}
#[test]
pub fn entity_with_two_architecture() {
let mut builder = LibraryBuilder::new();
let code1 = builder.code(
"libA",
"\
entity my_ent is
end my_ent;
architecture arch1 of my_ent is
begin
end arch1;
architecture arch2 of my_ent is
begin
end arch2;
",
);
let code2 = builder.code(
"libA",
"\
entity my_ent2 is
end my_ent2;
architecture arch of my_ent2 is
begin
end arch;
",
);
let (root, diag) = builder.get_analyzed_root();
check_no_diagnostics(&diag[..]);
let cursor = code2.s("begin", 1).end();
let options = list_completion_options(&root, code2.source(), cursor);
let ent = root
.search_reference(code1.source(), code1.s1("my_ent").start())
.unwrap();
let arch1 = root
.search_reference(code1.source(), code1.s1("arch1").start())
.unwrap();
let arch2 = root
.search_reference(code1.source(), code1.s1("arch2").start())
.unwrap();
let applicable_options = options
.into_iter()
.filter_map(|option| match option {
CompletionItem::Instantiation(ent, architectures) => Some((ent, architectures)),
_ => None,
})
.collect_vec();
// println!("{:?}", applicable_options);
match &applicable_options[..] {
[(got_ent, architectures)] => {
pretty_assertions::assert_eq!(*got_ent, ent);
assert_eq_unordered(architectures, &[arch1, arch2]);
}
_ => panic!("Expected entity instantiation"),
}
}
#[test]
fn component_instantiations() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
package foo is
component comp_A is
end component;
end foo;
use work.foo.all;
entity my_ent is
end my_ent;
architecture arch1 of my_ent is
component comp_B is
end component;
component comp_C is
end component;
begin
end arch1;
",
);
let (root, diag) = builder.get_analyzed_root();
check_no_diagnostics(&diag[..]);
let cursor = code.s1("begin").end();
let options = list_completion_options(&root, code.source(), cursor);
for component in ["comp_A", "comp_B", "comp_C"] {
let entity = root
.search_reference(code.source(), code.s1(component).start())
.unwrap();
assert!(options.contains(&CompletionItem::Instantiation(entity, vec![])))
}
}
#[test]
fn complete_entities_in_block() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
entity my_ent is
end my_ent;
entity my_other_ent is
end my_other_ent;
entity my_third_ent is
end my_third_ent;
architecture arch of my_third_ent is
begin
foo: block is
begin
end block foo;
end arch;
",
);
let (root, _) = builder.get_analyzed_root();
let cursor = code.s("begin", 2).end();
let options = list_completion_options(&root, code.source(), cursor);
let my_ent = root
.search_reference(code.source(), code.s1("my_ent").start())
.unwrap();
let my_other_ent = root
.search_reference(code.source(), code.s1("my_other_ent").start())
.unwrap();
assert!(options.contains(&CompletionItem::Instantiation(my_ent, vec![])));
assert!(options.contains(&CompletionItem::Instantiation(my_other_ent, vec![])));
}
}

View File

@ -0,0 +1,154 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::search::{
DeclarationItem, Finished, Found, FoundDeclaration, NotFinished, SearchState, Searcher,
};
use crate::ast::ArchitectureBody;
use crate::completion::entity_instantiation::get_visible_entities_from_architecture;
use crate::completion::region::completion_items_from_region;
use crate::named_entity::{DesignEnt, Visibility};
use crate::{CompletionItem, Design, HasTokenSpan, Position, Source, TokenAccess};
use itertools::{chain, Itertools};
use vhdl_lang::analysis::DesignRoot;
pub(crate) fn generic_completions<'a>(
root: &'a DesignRoot,
cursor: Position,
source: &Source,
) -> Vec<CompletionItem<'a>> {
let mut searcher = CompletionSearcher::new(cursor, root);
let _ = root.search_source(source, &mut searcher);
searcher.completions
}
/// This is the most general-purpose completion provider.
/// This provider publishes all visible symbols reachable from some context.
/// This will, among other things produce many "non-regular" symbols, such as
/// operator symbols or specific characters. If possible,
/// this searcher should therefore be avoided in favor of a more specific completion provider.
struct CompletionSearcher<'a> {
root: &'a DesignRoot,
cursor: Position,
completions: Vec<CompletionItem<'a>>,
}
impl<'a> CompletionSearcher<'a> {
pub fn new(cursor: Position, design_root: &'a DesignRoot) -> CompletionSearcher<'a> {
CompletionSearcher {
root: design_root,
cursor,
completions: Vec::new(),
}
}
}
impl<'a> CompletionSearcher<'a> {
/// Add entity instantiation completions that are visible from within an architecture body
fn add_entity_instantiations(&mut self, body: &ArchitectureBody) {
let Some(ent_id) = body.ident.decl.get() else {
return;
};
let Some(ent) = DesignEnt::from_any(self.root.get_ent(ent_id)) else {
return;
};
self.completions
.extend(get_visible_entities_from_architecture(self.root, &ent));
}
}
impl<'a> Searcher for CompletionSearcher<'a> {
fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration<'_>) -> SearchState {
let ent_id = match &decl.ast {
DeclarationItem::Entity(ent_decl) => {
if !ent_decl.get_pos(ctx).contains(self.cursor) {
return NotFinished;
}
ent_decl.ident.decl.get()
}
DeclarationItem::Architecture(body) => {
if !body.get_pos(ctx).contains(self.cursor) {
return NotFinished;
}
if body.statement_span().get_pos(ctx).contains(self.cursor) {
self.add_entity_instantiations(body);
}
body.ident.decl.get()
}
DeclarationItem::Package(package) => {
if !package.get_pos(ctx).contains(self.cursor) {
return NotFinished;
}
package.ident.decl.get()
}
DeclarationItem::PackageBody(package) => {
if !package.get_pos(ctx).contains(self.cursor) {
return NotFinished;
}
package.ident.decl.get()
}
DeclarationItem::Subprogram(subprogram) => {
if !subprogram.get_pos(ctx).contains(self.cursor) {
return NotFinished;
}
self.completions.extend(
subprogram
.declarations
.iter()
.flat_map(|decl| decl.item.declarations())
.map(|id| CompletionItem::Simple(self.root.get_ent(id))),
);
return NotFinished;
}
_ => return NotFinished,
};
let Some(ent_id) = ent_id else {
return Finished(Found);
};
let Some(ent) = DesignEnt::from_any(self.root.get_ent(ent_id)) else {
return Finished(Found);
};
self.completions
.extend(visible_entities_from(self.root, ent.kind()));
NotFinished
}
}
fn visible_entities_from<'a>(
root: &'a DesignRoot,
design: &'a Design<'a>,
) -> Vec<CompletionItem<'a>> {
use Design::*;
match design {
Entity(visibility, region)
| UninstPackage(visibility, region)
| Architecture(visibility, region, _)
| Package(visibility, region)
| PackageBody(visibility, region) => chain(
completion_items_from_region(root, region),
completion_items_from_visibility(root, visibility),
)
.collect_vec(),
PackageInstance(region) | InterfacePackageInstance(region) | Context(region) => {
completion_items_from_region(root, region).collect_vec()
}
Configuration => vec![],
}
}
fn completion_items_from_visibility<'a>(
root: &'a DesignRoot,
visibility: &'a Visibility<'a>,
) -> impl Iterator<Item = CompletionItem<'a>> {
visibility
.visible()
.unique()
.map(CompletionItem::Simple)
.chain(
visibility.all_in_region().flat_map(|visible_region| {
completion_items_from_region(root, visible_region.region())
}),
)
}

View File

@ -0,0 +1,41 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::DesignRoot;
use crate::CompletionItem;
use std::iter::once;
/// Produces all available libraries.
pub(crate) fn list_all_libraries(root: &DesignRoot) -> Vec<CompletionItem<'_>> {
root.libraries()
.map(|lib| CompletionItem::Simple(root.get_ent(lib.id())))
.chain(once(CompletionItem::Work))
.collect()
}
#[cfg(test)]
mod tests {
use crate::analysis::tests::{Code, LibraryBuilder};
use crate::syntax::test::assert_eq_unordered;
use crate::{list_completion_options, CompletionItem};
#[test]
pub fn completing_libraries() {
let input = LibraryBuilder::new();
let code = Code::new("library ");
let (root, _) = input.get_analyzed_root();
let cursor = code.end();
let options = list_completion_options(&root, code.source(), cursor);
assert_eq_unordered(
&options,
&[
CompletionItem::Simple(
root.get_ent(root.get_lib(&root.symbol_utf8("std")).unwrap().id()),
),
CompletionItem::Work,
],
)
}
}

View File

@ -0,0 +1,353 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::DesignRoot;
use crate::ast::search::{
DeclarationItem, Finished, Found, FoundDeclaration, NotFinished, SearchState, Searcher,
};
use crate::ast::{ConcurrentStatement, MapAspect, ObjectClass};
use crate::named_entity::{AsUnique, Region};
use crate::{
named_entity, AnyEntKind, CompletionItem, Design, EntityId, HasTokenSpan, Overloaded, Position,
Source, TokenAccess,
};
use std::collections::HashSet;
/// Produces completions for the left hand side of a map aspect, i.e.,
/// `port map (`
pub(crate) fn completions_for_map_aspect<'a>(
root: &'a DesignRoot,
cursor: Position,
source: &Source,
) -> Vec<CompletionItem<'a>> {
let mut searcher = MapAspectSearcher::new(root, cursor);
let _ = root.search_source(source, &mut searcher);
searcher.completions
}
/// Searches completions for map aspects (VHDL port maps and generic maps).
/// Currently, this only means the formal part (i.e., the left hand side of a port or generic assignment)
/// but not the actual part.
struct MapAspectSearcher<'a> {
root: &'a DesignRoot,
cursor: Position,
completions: Vec<CompletionItem<'a>>,
}
impl<'a> MapAspectSearcher<'a> {
pub fn new(root: &'a DesignRoot, cursor: Position) -> MapAspectSearcher<'a> {
MapAspectSearcher {
root,
cursor,
completions: Vec::new(),
}
}
/// Loads completion options for the given map aspect.
/// Returns `true`, when the cursor is inside the map aspect and the search should not continue.
/// Returns `false` otherwise
fn load_completions_for_map_aspect(
&mut self,
ent_ref: Option<EntityId>,
map: &MapAspect,
ctx: &dyn TokenAccess,
kind: MapAspectKind,
) -> bool {
if !map.get_span(ctx).contains(self.cursor) {
return false;
}
let formals_in_map: HashSet<EntityId> = HashSet::from_iter(map.formals().flatten());
if let Some(ent) = ent_ref {
let ids = match kind {
MapAspectKind::Port => extract_port_names(self.root, ent),
MapAspectKind::Generic => extract_generic_names(self.root, ent),
};
self.completions.extend(
ids.into_iter()
.filter(|id| !formals_in_map.contains(id))
.map(|id| CompletionItem::Formal(self.root.get_ent(id))),
);
}
true
}
}
impl<'a> Searcher for MapAspectSearcher<'a> {
/// Visit an instantiation statement extracting completions for ports or generics.
fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration<'_>) -> SearchState {
match &decl.ast {
DeclarationItem::ConcurrentStatement(stmt) => {
if let ConcurrentStatement::Instance(inst) = &stmt.statement.item {
if let Some(map) = &inst.generic_map {
if self.load_completions_for_map_aspect(
inst.entity_reference(),
map,
ctx,
MapAspectKind::Generic,
) {
return Finished(Found);
}
}
if let Some(map) = &inst.port_map {
if self.load_completions_for_map_aspect(
inst.entity_reference(),
map,
ctx,
MapAspectKind::Port,
) {
return Finished(Found);
}
}
}
}
DeclarationItem::PackageInstance(inst) => {
if let Some(map) = &inst.generic_map {
if self.load_completions_for_map_aspect(
inst.package_name.item.get_suffix_reference(),
map,
ctx,
MapAspectKind::Generic,
) {
return Finished(Found);
}
}
}
_ => {}
}
NotFinished
}
}
#[derive(Eq, PartialEq, Debug)]
enum MapAspectKind {
Port,
Generic,
}
/// From this region, extracts those `AnyEntKind::Object`s where the class of the
/// object matches the specified class.
fn extract_objects_with_class(region: &Region<'_>, object_class: ObjectClass) -> Vec<EntityId> {
region
.entities
.values()
.filter_map(|ent| ent.as_unique())
.filter_map(|ent| match &ent.kind {
AnyEntKind::Object(obj) if obj.class == object_class => Some(ent.id),
AnyEntKind::Overloaded(Overloaded::InterfaceSubprogram(_))
if object_class == ObjectClass::Constant =>
{
Some(ent.id)
}
AnyEntKind::Type(named_entity::Type::Interface)
if object_class == ObjectClass::Constant =>
{
Some(ent.id)
}
_ => None,
})
.collect()
}
/// Extracts the name of ports or generics from an AST for an entity with a certain ID.
/// The entity can be an `Entity`, `Component` or `Package`.
///
/// # Arguments
///
/// * `object_class` - What to extract. `ObjectClass::Signal` extracts ports
/// while `ObjectClass::Constant` extracts constants.
fn extract_port_or_generic_names(
root: &DesignRoot,
id: EntityId,
object_class: ObjectClass,
) -> Vec<EntityId> {
let cmp_ent = root.get_ent(id);
match cmp_ent.kind() {
AnyEntKind::Component(region) => extract_objects_with_class(region, object_class),
AnyEntKind::Design(Design::Entity(_, region)) => {
extract_objects_with_class(region, object_class)
}
AnyEntKind::Design(Design::UninstPackage(_, region)) => {
extract_objects_with_class(region, object_class)
}
_ => vec![],
}
}
pub fn extract_port_names(root: &DesignRoot, id: EntityId) -> Vec<EntityId> {
extract_port_or_generic_names(root, id, ObjectClass::Signal)
}
pub fn extract_generic_names(root: &DesignRoot, id: EntityId) -> Vec<EntityId> {
extract_port_or_generic_names(root, id, ObjectClass::Constant)
}
#[cfg(test)]
mod tests {
use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder};
use crate::{list_completion_options, CompletionItem};
#[test]
pub fn complete_component_instantiation_map() {
let mut input = LibraryBuilder::new();
let code = input.code(
"libname",
"\
entity my_ent is
end entity my_ent;
architecture arch of my_ent is
component comp is
generic (
A: natural := 5;
B: integer
);
port (
clk : in bit;
rst : in bit;
dout : out bit
);
end component comp;
signal clk, rst: bit;
begin
comp_inst: comp
generic map (
A => 2
)
port map (
clk => clk
);
end arch;
",
);
let (root, _) = input.get_analyzed_root();
let cursor = code.s1("generic map (").pos().end();
let options = list_completion_options(&root, code.source(), cursor);
let ent = root
.search_reference(code.source(), code.s1("B").start())
.unwrap();
assert_eq!(options, vec![CompletionItem::Formal(ent)]);
let rst = root
.search_reference(code.source(), code.s1("rst").start())
.unwrap();
let dout = root
.search_reference(code.source(), code.s1("dout").start())
.unwrap();
let clk_signal = root
.search_reference(
code.source(),
code.s1("signal clk, rst: bit;").s1("clk").start(),
)
.unwrap();
let rst_signal = root
.search_reference(
code.source(),
code.s1("signal clk, rst: bit;").s1("rst").start(),
)
.unwrap();
let cursor = code.s1("port map (").pos().end();
let options = list_completion_options(&root, code.source(), cursor);
assert!(options.contains(&CompletionItem::Formal(rst)));
assert!(options.contains(&CompletionItem::Formal(dout)));
assert_eq!(options.len(), 2);
let cursor = code
.s1("port map (
clk =>")
.pos()
.end();
let options = list_completion_options(&root, code.source(), cursor);
assert!(options.contains(&CompletionItem::Simple(clk_signal)));
assert!(options.contains(&CompletionItem::Simple(rst_signal)));
let cursor = code
.s1("port map (
clk => c")
.pos()
.end();
let options = list_completion_options(&root, code.source(), cursor);
assert!(options.contains(&CompletionItem::Simple(clk_signal)));
assert!(options.contains(&CompletionItem::Simple(rst_signal)));
}
#[test]
pub fn complete_in_generic_map() {
let mut input = LibraryBuilder::new();
let code = input.code(
"libname",
"\
package my_pkg is
generic (
function foo(x: Integer) return bit;
function bar(y: Integer) return boolean;
type T;
x: natural
);
end my_pkg;
use work.my_pkg.all ;
package my_pkg_inst is new work.my_pkg
generic map (
foo => foo
);",
);
let (root, _) = input.get_analyzed_root();
let bar_func = root
.search_reference(code.source(), code.s1("bar").start())
.unwrap();
let x = root
.search_reference(code.source(), code.s1("x: natural").s1("x").start())
.unwrap();
let t = root
.search_reference(code.source(), code.s1("type T").s1("T").start())
.unwrap();
let cursor = code.s1("generic map (").pos().end();
let options = list_completion_options(&root, code.source(), cursor);
assert!(options.contains(&CompletionItem::Formal(bar_func)));
assert!(options.contains(&CompletionItem::Formal(x)));
assert!(options.contains(&CompletionItem::Formal(t)));
pretty_assertions::assert_eq!(options.len(), 3);
}
#[test]
pub fn completes_signals_and_ports() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libA",
"\
entity my_ent is
port (
foo : in bit
);
end my_ent;
architecture arch of my_ent is
signal bar : natural;
type foobaz is array(natural range <>) of bit;
begin
end arch;
",
);
let (root, diag) = builder.get_analyzed_root();
check_no_diagnostics(&diag);
let cursor = code.s1("begin").end();
let options = list_completion_options(&root, code.source(), cursor);
let ent1 = root
.search_reference(code.source(), code.s1("foo").start())
.unwrap();
let ent2 = root
.search_reference(code.source(), code.s1("bar").start())
.unwrap();
assert!(options.contains(&CompletionItem::Simple(ent1)));
assert!(options.contains(&CompletionItem::Simple(ent2)));
}
}

View File

@ -0,0 +1,47 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::DesignRoot;
use crate::completion::entity_instantiation::get_architectures_for_entity;
use crate::named_entity::{AsUnique, NamedEntities, Region};
use crate::{AnyEntKind, CompletionItem, Design};
use vhdl_lang::EntRef;
pub(crate) fn completion_items_from_region<'a>(
root: &'a DesignRoot,
region: &'a Region<'a>,
) -> impl Iterator<Item = CompletionItem<'a>> {
region
.entities
.values()
.map(|entities| named_entities_to_completion_item(root, entities))
}
fn named_entities_to_completion_item<'a>(
root: &'a DesignRoot,
named_entities: &'a NamedEntities<'a>,
) -> CompletionItem<'a> {
match named_entities {
NamedEntities::Single(ent) => any_ent_to_completion_item(ent, root),
NamedEntities::Overloaded(overloaded) => match overloaded.as_unique() {
None => CompletionItem::Overloaded(overloaded.designator().clone(), overloaded.len()),
Some(ent) => CompletionItem::Simple(ent),
},
}
}
pub(crate) fn any_ent_to_completion_item<'a>(
ent: EntRef<'a>,
root: &'a DesignRoot,
) -> CompletionItem<'a> {
match ent.kind() {
AnyEntKind::Design(Design::Entity(..)) | AnyEntKind::Component(_) => {
let architectures = get_architectures_for_entity(ent, root);
CompletionItem::Instantiation(ent, architectures)
}
_ => CompletionItem::Simple(ent),
}
}

View File

@ -0,0 +1,204 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::analysis::DesignRoot;
use crate::completion::region::completion_items_from_region;
use crate::data::Symbol;
use crate::syntax::Kind::All;
use crate::{named_entity, CompletionItem, EntRef, HasEntityId};
use std::iter::once;
/// Produces completions for a selected name, i.e.,
/// `foo.`
/// The provided `ent` is the entity directly before the dot, i.e.,
/// `foo` in the example above.
pub(crate) fn completions_for_selected_name<'b>(
root: &'b DesignRoot,
ent: EntRef<'b>,
) -> Vec<CompletionItem<'b>> {
use crate::named_entity::AnyEntKind::*;
match ent.kind() {
Object(object) => completions_for_type(root, object.subtype.type_mark().kind()),
Design(design) => completions_for_design(root, design),
Library => ent
.library_name()
.map(|sym| list_primaries_for_lib(root, sym))
.unwrap_or_default(),
_ => vec![],
}
}
/// Returns completions applicable when calling `foo.` where `foo` is amn object of some type.
fn completions_for_type<'a>(
root: &'a DesignRoot,
typ: &'a named_entity::Type<'a>,
) -> Vec<CompletionItem<'a>> {
use crate::named_entity::Type::*;
match typ {
Record(record_region) => record_region
.iter()
.map(|item| CompletionItem::Simple(item.ent))
.collect(),
Alias(type_ent) => completions_for_type(root, type_ent.kind()),
Access(subtype) => {
let mut completions = completions_for_type(root, subtype.type_mark().kind());
completions.push(CompletionItem::Keyword(All));
completions
}
Protected(region, _) => completion_items_from_region(root, region).collect(),
_ => vec![],
}
}
/// Returns completions applicable when calling `foo.` where `foo` is some design
/// (i.e. entity or package).
fn completions_for_design<'a>(
root: &'a DesignRoot,
design: &'a crate::Design<'a>,
) -> Vec<CompletionItem<'a>> {
use crate::named_entity::Design::*;
match design {
Package(_, region) | PackageInstance(region) | InterfacePackageInstance(region) => {
completion_items_from_region(root, region)
.chain(once(CompletionItem::Keyword(All)))
.collect()
}
_ => vec![],
}
}
/// List the name of all primary units for a given library.
/// If the library is non-resolvable, list an empty vector
fn list_primaries_for_lib<'a>(root: &'a DesignRoot, lib: &Symbol) -> Vec<CompletionItem<'a>> {
let Some(lib) = root.get_lib(lib) else {
return vec![];
};
lib.primary_units()
.filter_map(|it| it.unit.get().and_then(|unit| unit.ent_id()))
.map(|id| CompletionItem::Simple(root.get_ent(id)))
.collect()
}
#[cfg(test)]
mod tests {
use crate::analysis::tests::{assert_eq_unordered, LibraryBuilder};
use crate::ast::Designator;
use crate::syntax::Kind::All;
use crate::{list_completion_options, CompletionItem};
#[test]
pub fn completes_selected_names() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libA",
"\
package foo is
type my_record is record
abc: bit;
def: bit;
end record;
constant y: my_record := ('1', '1');
constant z: bit := y.
end foo;
",
);
let (root, _) = builder.get_analyzed_root();
let cursor = code.s1("y.").end();
let options = list_completion_options(&root, code.source(), cursor);
let ent1 = root
.search_reference(code.source(), code.s1("abc").start())
.unwrap();
let ent2 = root
.search_reference(code.source(), code.s1("def").start())
.unwrap();
assert_eq_unordered(
&options,
&[CompletionItem::Simple(ent1), CompletionItem::Simple(ent2)],
)
}
#[test]
pub fn completing_primaries() {
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
use std.
-- Need this so that the 'use std.' is associated to a context and can be analyzed correctly
package x is
end package x;
",
);
let (root, _) = builder.get_analyzed_root();
let cursor = code.s1("use std.").end();
let options = list_completion_options(&root, code.source(), cursor);
assert_eq_unordered(
&options,
&[
CompletionItem::Simple(root.find_textio_pkg()),
CompletionItem::Simple(root.find_standard_pkg()),
CompletionItem::Simple(root.find_env_pkg()),
],
);
let mut builder = LibraryBuilder::new();
let code = builder.code(
"libname",
"\
use std.t
-- Need this so that the 'use std.' is associated to a context and can be analyzed correctly
package x is
end package x;
",
);
let (root, _) = builder.get_analyzed_root();
let cursor = code.s1("use std.t").end();
let options = list_completion_options(&root, code.source(), cursor);
// Note that the filtering only happens at client side
assert_eq_unordered(
&options,
&[
CompletionItem::Simple(root.find_textio_pkg()),
CompletionItem::Simple(root.find_standard_pkg()),
CompletionItem::Simple(root.find_env_pkg()),
],
);
}
#[test]
pub fn completing_declarations() {
let mut input = LibraryBuilder::new();
let code = input.code(
"libname",
"\
use std.env.
-- Need this so that the 'use std.env.' is associated to a context and can be analyzed correctly
package x is
end package x;
",
);
let (root, _) = input.get_analyzed_root();
let cursor = code.s1("use std.env.").end();
let options = list_completion_options(&root, code.source(), cursor);
assert_eq_unordered(
&options,
&[
CompletionItem::Overloaded(Designator::Identifier(root.symbol_utf8("stop")), 2),
CompletionItem::Overloaded(Designator::Identifier(root.symbol_utf8("finish")), 2),
CompletionItem::Simple(root.find_env_symbol("resolution_limit")),
CompletionItem::Keyword(All),
],
);
}
}

View File

@ -0,0 +1,111 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::data::ContentReader;
use crate::syntax::{Symbols, Tokenizer};
use crate::{Position, Source, Token};
/// Tokenizes `source` up to `cursor` but no further. The last token returned is the token
/// where the cursor currently resides or the token right before the cursor.
///
/// Examples:
///
/// input = "use ieee.std_logic_1|164.a"
/// ^ cursor position
/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164}
///
/// input = "use ieee.std_logic_1164|.a"
/// ^ cursor position
/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164}
///
/// input = "use ieee.std_logic_1164.|a"
/// ^ cursor position
/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164, DOT}
/// input = "use ieee.std_logic_1164.a|"
/// ^ cursor position
/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164, DOT, a}
///
/// On error, or if the source is empty, returns an empty vector.
pub(crate) fn tokenize_input(symbols: &Symbols, source: &Source, cursor: Position) -> Vec<Token> {
let contents = source.contents();
let mut tokenizer = Tokenizer::new(symbols, source, ContentReader::new(&contents));
let mut tokens = Vec::new();
loop {
match tokenizer.pop() {
Ok(Some(token)) => {
if token.pos.start() >= cursor {
break;
}
tokens.push(token);
}
Ok(None) => break,
Err(_) => return vec![],
}
}
tokens
}
#[cfg(test)]
mod tests {
use super::*;
use crate::syntax::test::Code;
use crate::syntax::Kind::*;
use assert_matches::assert_matches;
macro_rules! kind {
($kind: pat) => {
crate::syntax::Token { kind: $kind, .. }
};
}
#[test]
fn tokenizing_an_empty_input() {
let input = Code::new("");
let tokens = tokenize_input(&input.symbols, input.source(), Position::new(0, 0));
assert_eq!(tokens.len(), 0);
}
#[test]
fn tokenizing_stops_at_the_cursors_position() {
let input = Code::new("use ieee.std_logic_1164.all");
let mut cursor = input.s1("std_logic_11").pos().end();
let tokens = tokenize_input(&input.symbols, input.source(), cursor);
assert_matches!(
tokens[..],
[kind!(Use), kind!(Identifier), kind!(Dot), kind!(Identifier)]
);
cursor = input.s1("std_logic_1164").pos().end();
let tokens = tokenize_input(&input.symbols, input.source(), cursor);
assert_matches!(
tokens[..],
[kind!(Use), kind!(Identifier), kind!(Dot), kind!(Identifier)]
);
cursor = input.s1("std_logic_1164.").pos().end();
let tokens = tokenize_input(&input.symbols, input.source(), cursor);
assert_matches!(
tokens[..],
[
kind!(Use),
kind!(Identifier),
kind!(Dot),
kind!(Identifier),
kind!(Dot)
]
);
cursor = input.s1("std_logic_1164.all").pos().end();
let tokens = tokenize_input(&input.symbols, input.source(), cursor);
assert_matches!(
tokens[..],
[
kind!(Use),
kind!(Identifier),
kind!(Dot),
kind!(Identifier),
kind!(Dot),
kind!(All)
]
);
}
}

800
vhdl_lang/src/config.rs Normal file
View File

@ -0,0 +1,800 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
//! Configuration of the design hierarchy and other settings
use std::env;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::Path;
use fnv::FnvHashMap;
use itertools::Itertools;
use subst::VariableMap;
use toml::{Table, Value};
use crate::data::error_codes::ErrorCode;
use crate::data::*;
use crate::standard::VHDLStandard;
#[derive(Clone, PartialEq, Eq, Default, Debug)]
pub struct Config {
// A map from library name to file name
libraries: FnvHashMap<String, LibraryConfig>,
standard: VHDLStandard,
// Defines the severity that diagnostics are displayed with
severities: SeverityMap,
}
#[derive(Clone, PartialEq, Eq, Default, Debug)]
pub struct LibraryConfig {
name: String,
patterns: Vec<String>,
pub(crate) is_third_party: bool,
}
impl LibraryConfig {
/// Return a vector of file names
/// Only include files that exists
/// Files that do not exist produce a warning message
pub fn file_names(&self, messages: &mut dyn MessageHandler) -> Vec<PathBuf> {
let mut result = Vec::new();
for pattern in self.patterns.iter() {
let stripped_pattern = if cfg!(windows) {
pattern.strip_prefix("\\\\?\\").unwrap_or(pattern.as_str())
} else {
pattern.as_str()
};
if is_literal(stripped_pattern) {
let file_path = PathBuf::from(pattern);
if file_path.exists() {
result.push(file_path);
} else {
messages.push(Message::warning(format! {"File {pattern} does not exist"}));
}
} else {
match glob::glob(stripped_pattern) {
Ok(paths) => {
let mut empty_pattern = true;
for file_path_or_error in paths {
empty_pattern = false;
match file_path_or_error {
Ok(file_path) => {
result.push(file_path);
}
Err(err) => {
messages.push(Message::error(err.to_string()));
}
}
}
if empty_pattern {
messages.push(Message::warning(format!(
"Pattern '{stripped_pattern}' did not match any file"
)));
}
}
Err(err) => {
messages.push(Message::error(format!("Invalid pattern '{pattern}' {err}")));
}
}
}
}
// Remove duplicate file names from the result
result.into_iter().unique().collect()
}
/// Returns the name of the library
pub fn name(&self) -> &str {
self.name.as_str()
}
}
impl Config {
pub fn from_str(string: &str, parent: &Path) -> Result<Config, String> {
let config = string.parse::<Value>().map_err(|err| err.to_string())?;
let mut libraries = FnvHashMap::default();
let standard = if let Some(std) = config.get("standard") {
let std_str = std.as_str().ok_or("standard must be a string")?;
VHDLStandard::try_from(std_str)
.map_err(|_| format!("Unsupported standard '{std_str}'"))?
} else {
VHDLStandard::default()
};
let libs = config
.get("libraries")
.ok_or("missing field libraries")?
.as_table()
.ok_or("libraries must be a table")?;
for (name, lib) in libs.iter() {
if name.to_lowercase() == "work" {
return Err(format!(
"The '{}' library is not a valid library.\nHint: To use a library that contains all files, use a common name for all libraries, i.e., 'defaultlib'",
name
));
}
let file_arr = lib
.get("files")
.ok_or_else(|| format!("missing field files for library {name}"))?
.as_array()
.ok_or_else(|| format!("files for library {name} is not array"))?;
let mut patterns = Vec::new();
for file in file_arr.iter() {
let file = file
.as_str()
.ok_or_else(|| format!("not a string {file}"))?;
let file = substitute_environment_variables(file, &subst::Env)?;
let path = parent.join(file);
let path = path
.to_str()
.ok_or_else(|| format!("Could not convert {path:?} to string"))?
.to_owned();
patterns.push(path);
}
let mut is_third_party = false;
if let Some(opt) = lib.get("is_third_party") {
if let Some(opt) = opt.as_bool() {
is_third_party = opt;
} else {
return Err(format!(
"Expected is_third_party to be boolean for library {name}"
));
}
}
libraries.insert(
name.to_owned(),
LibraryConfig {
name: name.to_owned(),
patterns,
is_third_party,
},
);
}
let severities = if let Some(lint) = config.get("lint") {
Self::read_severity_overwrites(lint.as_table().ok_or("lint must be a table")?)?
} else {
SeverityMap::default()
};
Ok(Config {
libraries,
severities,
standard,
})
}
fn read_severity_overwrites(severity_overwrites: &Table) -> Result<SeverityMap, String> {
let mut severities = SeverityMap::default();
for (name, severity) in severity_overwrites {
let error_code = ErrorCode::try_from(name.as_str())
.map_err(|_| format!("'{name}' is not a valid error code"))?;
match severity {
Value::String(severity) => {
let severity = Severity::try_from(severity.as_str())
.map_err(|_| format!("'{severity}' is not a valid severity level"))?;
severities[error_code] = Some(severity);
}
Value::Boolean(should_show) => {
if !should_show {
severities[error_code] = None
}
}
_ => return Err("severity must be a string or boolean".to_string()),
}
}
Ok(severities)
}
pub fn read_file_path(file_name: &Path) -> io::Result<Config> {
let mut file = File::open(file_name)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let parent = file_name.parent().unwrap();
Config::from_str(&contents, parent).map_err(|msg| io::Error::new(io::ErrorKind::Other, msg))
}
pub fn get_library(&self, name: &str) -> Option<&LibraryConfig> {
self.libraries.get(name)
}
pub fn iter_libraries(&self) -> impl Iterator<Item = &LibraryConfig> {
self.libraries.values()
}
/// Append another config to self
///
/// In case of conflict the appended config takes precedence
pub fn append(&mut self, config: &Config, messages: &mut dyn MessageHandler) {
self.standard = config.standard;
for library in config.iter_libraries() {
if let Some(parent_library) = self.libraries.get_mut(&library.name) {
*parent_library = library.clone();
messages.push(Message::warning(format!(
"Re-defined library {}",
&library.name
)));
} else {
self.libraries.insert(library.name.clone(), library.clone());
}
}
self.severities = config.severities;
}
/// Load configuration file from installation folder
fn load_installed_config(
&mut self,
messages: &mut dyn MessageHandler,
location: Option<String>,
) {
if let Some(location) = location {
self.load_config(&PathBuf::from(location), "Installation", messages);
return;
}
let search_paths = [
"../vhdl_libraries",
"../../vhdl_libraries",
"/usr/lib/rust_hdl/vhdl_libraries",
"/usr/local/lib/rust_hdl/vhdl_libraries",
"../share/vhdl_libraries",
];
for dir in search_paths.into_iter() {
let mut file_name = PathBuf::from(dir);
// Expand a relative path
if !file_name.is_absolute() {
let exe_path = env::current_exe().expect("Executable path needed");
let exe_folder = exe_path.parent().expect("Executable folder must exist");
file_name = exe_folder.join(file_name)
}
file_name.push("vhdl_ls.toml");
if file_name.exists() {
self.load_config(&file_name, "Installation", messages);
return;
}
}
// Panic if we did not yet find the installed libraries
panic!(
"Couldn't find installed libraries at {}.",
search_paths.join(", ")
);
}
/// Load configuration file from home folder
fn load_home_config(&mut self, messages: &mut dyn MessageHandler) {
if let Some(home_dir) = dirs::home_dir() {
let file_name = home_dir.join(".vhdl_ls.toml");
if !file_name.exists() {
return;
}
self.load_config(&file_name, "HOME folder", messages);
}
}
/// Load configuration file from environment
fn load_env_config(&mut self, env_name: &str, messages: &mut dyn MessageHandler) {
if let Some(file_name) = std::env::var_os(env_name) {
self.load_config(Path::new(&file_name), env_name, messages);
};
}
/// Load and append configuration file
fn load_config(&mut self, file_name: &Path, desc: &str, messages: &mut dyn MessageHandler) {
match Config::read_file_path(Path::new(&file_name)) {
Ok(env_config) => {
messages.push(Message::log(format!(
"Loaded {} configuration file: {}",
desc,
file_name.to_string_lossy()
)));
self.append(&env_config, messages);
}
Err(ref err) => {
messages.push(Message::error(format!(
"Error while loading {desc} configuration file: {err} "
)));
}
}
}
/// Load all external configuration
/// If the `standard_libraries_path` is given, it must point to a valid
/// `vhdl_ls.toml` file, which will be used as source for the standard libraries
/// i.e., `std` or `ieee`.
/// If this path is `None`, a set of standard search paths will be queried for the location
/// of this file.
pub fn load_external_config(
&mut self,
messages: &mut dyn MessageHandler,
standard_libraries_path: Option<String>,
) {
self.load_installed_config(messages, standard_libraries_path);
self.load_home_config(messages);
self.load_env_config("VHDL_LS_CONFIG", messages);
}
pub fn severities(&self) -> &SeverityMap {
&self.severities
}
/// The VHDL standard to use if no more specific config is present.
/// By default, VHDL 2008 is assumed
pub fn standard(&self) -> VHDLStandard {
self.standard
}
}
fn substitute_environment_variables<'a, M>(s: &str, map: &'a M) -> Result<String, String>
where
M: VariableMap<'a> + ?Sized,
M::Value: AsRef<str>,
{
if cfg!(windows) {
substitute_variables_windows(s, map)
} else {
subst::substitute(s, map).map_err(|err| err.to_string())
}
}
fn substitute_variables_windows<'a, M>(s: &str, map: &'a M) -> Result<String, String>
where
M: VariableMap<'a> + ?Sized,
M::Value: AsRef<str>,
{
let mut output: Vec<char> = Vec::with_capacity(s.len());
let mut var_buf: Vec<char> = Vec::new();
let mut var_found = false;
for ch in s.chars() {
if ch == '%' {
if var_found {
let var_name = String::from_iter(var_buf);
var_buf = Vec::new();
match map.get(&var_name) {
None => {
return Err(format!("Variable '{var_name}' not found"));
}
Some(value) => {
output.extend(value.as_ref().chars());
}
}
}
var_found = !var_found;
} else if !var_found {
output.push(ch);
} else {
var_buf.push(ch)
}
}
if var_found {
Err("Unterminated variable".into())
} else {
Ok(String::from_iter(output))
}
}
/// Returns true if the pattern is a plain file name and not a glob pattern
fn is_literal(pattern: &str) -> bool {
!pattern.chars().any(|chr| matches!(&chr, '?' | '*' | '['))
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use pretty_assertions::assert_eq;
use super::*;
/// Utility function to create an empty file in parent folder
fn touch(parent: &Path, file_name: &str) -> PathBuf {
let path = parent.join(file_name);
File::create(&path).expect("Assume file can be created");
path
}
fn abspath(path: &Path) -> PathBuf {
dunce::canonicalize(path).unwrap()
}
fn abspaths(paths: &[PathBuf]) -> Vec<PathBuf> {
paths.iter().map(|path| abspath(path)).collect()
}
fn assert_files_eq(got: &[PathBuf], expected: &[PathBuf]) {
assert_eq!(abspaths(got), abspaths(expected));
}
#[test]
fn test_is_literal() {
assert!(is_literal("file.vhd"));
assert!(!is_literal("file*.vhd"));
assert!(!is_literal("file?.vhd"));
assert!(!is_literal("file[ab].vhd"));
}
#[test]
fn config_from_str() {
let tempdir = tempfile::tempdir().unwrap();
let parent = tempdir.path();
let tempdir2 = tempfile::tempdir().unwrap();
let absolute_path = abspath(tempdir2.path());
let absolute_vhd = touch(&absolute_path, "absolute.vhd");
let config = Config::from_str(
&format!(
"
[libraries]
lib2.files = [
'pkg2.vhd',
'{}'
]
lib1.files = [
'pkg1.vhd',
'tb_ent.vhd'
]
[lint]
unused = 'error'
duplicate = false
",
absolute_vhd.to_str().unwrap()
),
parent,
)
.unwrap();
let mut libraries: Vec<&str> = config.iter_libraries().map(|lib| lib.name()).collect();
libraries.sort_unstable();
assert_eq!(libraries, &["lib1", "lib2"]);
let lib1 = config.get_library("lib1").unwrap();
let lib2 = config.get_library("lib2").unwrap();
let pkg1_path = touch(parent, "pkg1.vhd");
let pkg2_path = touch(parent, "pkg2.vhd");
let tb_ent_path = touch(parent, "tb_ent.vhd");
let mut messages = vec![];
assert_files_eq(&lib1.file_names(&mut messages), &[pkg1_path, tb_ent_path]);
assert_files_eq(&lib2.file_names(&mut messages), &[pkg2_path, absolute_vhd]);
assert_eq!(messages, vec![]);
let mut expected_map = SeverityMap::default();
expected_map[ErrorCode::Unused] = Some(Severity::Error);
expected_map[ErrorCode::Duplicate] = None;
assert_eq!(config.severities, expected_map)
}
#[test]
fn test_append_config() {
let parent0 = Path::new("parent_folder0");
let config0 = Config::from_str(
"
[libraries]
lib1.files = [
'pkg1.vhd',
]
lib2.files = [
'pkg2.vhd'
]
",
parent0,
)
.unwrap();
let parent1 = Path::new("parent_folder1");
let config1 = Config::from_str(
"
[libraries]
lib2.files = [
'ent.vhd'
]
lib3.files = [
'pkg3.vhd',
]
",
parent1,
)
.unwrap();
let expected_parent = Path::new("");
let expected_config = Config::from_str(
&format!(
"
[libraries]
lib1.files = [
'{pkg1}',
]
lib2.files = [
'{ent}'
]
lib3.files = [
'{pkg3}',
]
",
pkg1 = parent0.join("pkg1.vhd").to_str().unwrap(),
ent = parent1.join("ent.vhd").to_str().unwrap(),
pkg3 = parent1.join("pkg3.vhd").to_str().unwrap()
),
expected_parent,
)
.unwrap();
let mut merged_config = config0;
merged_config.append(&config1, &mut Vec::new());
assert_eq!(merged_config, expected_config);
}
#[test]
fn test_warning_on_missing_file() {
let parent = Path::new("parent_folder");
let config = Config::from_str(
"
[libraries]
lib.files = [
'missing.vhd'
]
",
parent,
)
.unwrap();
let mut messages = vec![];
let file_names = config.get_library("lib").unwrap().file_names(&mut messages);
assert_files_eq(&file_names, &[]);
assert_eq!(
messages,
vec![Message::warning(format!(
"File {} does not exist",
parent.join("missing.vhd").to_str().unwrap()
))]
);
}
#[test]
fn test_file_wildcard_pattern() {
let tempdir = tempfile::tempdir().unwrap();
let parent = tempdir.path();
let config = Config::from_str(
"
[libraries]
lib.files = [
'*.vhd'
]
",
parent,
)
.unwrap();
let file1 = touch(parent, "file1.vhd");
let file2 = touch(parent, "file2.vhd");
let mut messages = vec![];
let file_names = config.get_library("lib").unwrap().file_names(&mut messages);
assert_files_eq(&file_names, &[file1, file2]);
assert_eq!(messages, vec![]);
}
#[test]
fn test_file_wildcard_pattern_removes_duplicates() {
let tempdir = tempfile::tempdir().unwrap();
let parent = tempdir.path();
let config = Config::from_str(
"
[libraries]
lib.files = [
'*.vhd',
'file*.vhd'
]
",
parent,
)
.unwrap();
let file1 = touch(parent, "file1.vhd");
let file2 = touch(parent, "file2.vhd");
let mut messages = vec![];
let file_names = config.get_library("lib").unwrap().file_names(&mut messages);
assert_files_eq(&file_names, &[file1, file2]);
assert_eq!(messages, vec![]);
}
#[test]
fn test_warning_on_emtpy_glob_pattern() {
let parent = Path::new("parent_folder");
let config = Config::from_str(
"
[libraries]
lib.files = [
'missing*.vhd'
]
",
parent,
)
.unwrap();
let mut messages = vec![];
let file_names = config.get_library("lib").unwrap().file_names(&mut messages);
assert_files_eq(&file_names, &[]);
assert_eq!(
messages,
vec![Message::warning(format!(
"Pattern '{}' did not match any file",
parent.join("missing*.vhd").to_str().unwrap()
))]
);
}
#[test]
fn the_work_library_is_an_illegal_library() {
let parent = Path::new("parent_folder");
let config = Config::from_str(
"
[libraries]
work.files = [
'a.vhd', 'b.vhd'
]
",
parent,
);
assert_eq!(config.expect_err("Expected erroneous config"), "The 'work' library is not a valid library.\nHint: To use a library that contains all files, use a common name for all libraries, i.e., 'defaultlib'")
}
#[test]
#[cfg(unix)]
fn substitute() {
let mut map = HashMap::new();
map.insert("A".to_owned(), "a".to_owned());
map.insert("ABCD".to_owned(), "abcd".to_owned());
map.insert("A_0".to_owned(), "a0".to_owned());
map.insert("_".to_owned(), "u".to_owned());
map.insert("PATH".to_owned(), "some/path".to_owned());
// simple pattern tests
assert_eq!(
Ok("test".to_owned()),
substitute_environment_variables("test", &map)
);
assert_eq!(
Ok("a".to_owned()),
substitute_environment_variables("$A", &map)
);
assert_eq!(
Ok("abcd".to_owned()),
substitute_environment_variables("$ABCD", &map)
);
assert_eq!(
Ok("a0".to_owned()),
substitute_environment_variables("$A_0", &map)
);
assert_eq!(
Ok("u".to_owned()),
substitute_environment_variables("$_", &map)
);
assert_eq!(
Ok("some/path".to_owned()),
substitute_environment_variables("$PATH", &map)
);
// embedded in longer string
assert_eq!(
Ok("test/a/test".to_owned()),
substitute_environment_variables("test/$A/test", &map)
);
assert_eq!(
Ok("test/a".to_owned()),
substitute_environment_variables("test/$A", &map)
);
assert_eq!(
Ok("a/test".to_owned()),
substitute_environment_variables("$A/test", &map)
);
assert_eq!(
Ok("test/some/path/test".to_owned()),
substitute_environment_variables("test/$PATH/test", &map)
);
// error cases
assert!(substitute_environment_variables("$not_present", &map).is_err());
assert!(substitute_environment_variables("$not_unicode", &map).is_err());
}
#[test]
fn windows_variable_names() {
let mut map = HashMap::new();
map.insert("A".to_owned(), "a".to_owned());
map.insert("ABCD".to_owned(), "abcd".to_owned());
map.insert("A_0".to_owned(), "a0".to_owned());
map.insert("_".to_owned(), "u".to_owned());
map.insert("PATH".to_owned(), r#"some\path"#.to_owned());
assert_eq!(Ok("".to_owned()), substitute_variables_windows("", &map));
assert_eq!(
Ok("test".to_owned()),
substitute_variables_windows("test", &map)
);
assert_eq!(
Ok("a".to_owned()),
substitute_variables_windows("%A%", &map)
);
assert_eq!(
Ok("abcd".to_owned()),
substitute_variables_windows("%ABCD%", &map)
);
assert_eq!(
Ok("a0".to_owned()),
substitute_variables_windows("%A_0%", &map)
);
assert_eq!(
Ok("u".to_owned()),
substitute_variables_windows("%_%", &map)
);
assert_eq!(
Ok(r#"some\path"#.to_owned()),
substitute_variables_windows("%PATH%", &map)
);
// embedded in longer string
assert_eq!(
Ok(r#"test\a\test"#.to_owned()),
substitute_variables_windows(r#"test\%A%\test"#, &map)
);
assert_eq!(
Ok(r#"test\a"#.to_owned()),
substitute_variables_windows(r#"test\%A%"#, &map)
);
assert_eq!(
Ok(r#"a\test"#.to_owned()),
substitute_variables_windows(r#"%A%\test"#, &map)
);
assert_eq!(
Ok(r#"C:\test\some\path\test"#.to_owned()),
substitute_variables_windows(r#"C:\test\%PATH%\test"#, &map)
);
// error cases
assert_eq!(
substitute_variables_windows("%not_present%", &map),
Err("Variable 'not_present' not found".into())
);
assert!(substitute_variables_windows("%not_unicode%", &map).is_err());
}
// Issue #278
#[test]
#[cfg(windows)]
fn substitute_ok_windows_paths() {
let map: HashMap<String, String> = HashMap::default();
let str = r#"\\networklocation\cad$\apps\xilinx_vitis\Vivado_2020.2\Vivado\2020.2\data\vhdl\src\unisims\unisim_VCOMP.vhd"#;
let res = substitute_environment_variables(str, &map);
assert_eq!(res, Ok(str.to_owned()));
}
}

22
vhdl_lang/src/data.rs Normal file
View File

@ -0,0 +1,22 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
mod contents;
mod diagnostic;
pub mod error_codes;
mod latin_1;
mod message;
mod source;
mod symbol_table;
pub use contents::*;
pub use diagnostic::*;
pub use error_codes::*;
pub use latin_1::*;
pub use message::*;
pub use source::*;
pub use symbol_table::*;

View File

@ -0,0 +1,632 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use super::latin_1::{char_to_latin1, Latin1String, Utf8ToLatin1Error};
use super::source::{Position, Range};
use std::fs::File;
use std::io;
use std::io::prelude::Read;
use std::path::Path;
pub struct Contents {
lines: Vec<String>,
}
impl Contents {
pub fn from_latin1_file(file_name: &Path) -> io::Result<Contents> {
let mut file = File::open(file_name)?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
Ok(Contents::from_str(
&Latin1String::from_vec(bytes).to_string(),
))
}
pub fn from_str(code: &str) -> Contents {
Contents {
lines: split_lines(code),
}
}
pub fn start(&self) -> Position {
Position {
line: 0,
character: 0,
}
}
pub fn end(&self) -> Position {
let line = self.num_lines().saturating_sub(1) as u32;
let character = self
.lines
.last()
.map(|line| line.chars().map(|chr| chr.len_utf16()).sum())
.unwrap_or(0) as u32;
Position { line, character }
}
#[cfg(test)]
pub fn range(&self) -> Range {
Range::new(self.start(), self.end())
}
#[cfg(test)]
pub fn crop(&self, range: Range) -> Contents {
let mut reader = ContentReader::new(self);
reader.seek_pos(range.start);
let mut result = String::new();
while reader.pos() < range.end {
if let Some(chr) = reader.pop_char() {
result.push(chr);
}
}
Contents {
lines: split_lines(&result),
}
}
pub fn num_lines(&self) -> usize {
self.lines.len()
}
pub fn get_line(&self, lineno: usize) -> Option<&str> {
self.lines.get(lineno).map(|string| string.as_str())
}
pub fn change(&mut self, range: &Range, content: &str) {
if self.lines.is_empty() {
self.lines = split_lines(content);
return;
}
let Range { start, end } = range;
let start_char = start.character as usize;
let end_char = end.character as usize;
let start_line = start.line as usize;
let end_line = end.line as usize;
let mut merged_content = String::new();
if let Some(line) = self.lines.get(start_line) {
let mut i = 0;
for chr in line.chars() {
if i < start_char {
merged_content.push(chr);
} else {
break;
};
i += chr.len_utf16();
}
}
merged_content.push_str(content);
if let Some(line) = self.lines.get(end_line) {
let mut i = 0;
for chr in line.chars() {
if i >= end_char {
merged_content.push(chr);
};
i += chr.len_utf16();
}
}
let last_line_index = self.lines.len() - 1;
if (end.line as usize) < last_line_index
&& merged_content.chars().last().unwrap_or('\0') != '\n'
{
merged_content.push('\n');
}
let end_line = std::cmp::min(self.lines.len().saturating_sub(1), end_line);
self.lines
.splice(start_line..=end_line, split_lines(&merged_content))
.count();
}
}
/// Split code into several lines
fn split_lines(code: &str) -> Vec<String> {
let mut lines = Vec::new();
let bytes = code.as_bytes();
let mut i = 0;
let mut start = 0;
while i < bytes.len() {
let byte = bytes[i];
if byte == b'\n' {
i += 1;
let line = bytes[start..i].to_owned();
let line = unsafe { String::from_utf8_unchecked(line) };
lines.push(line);
start = i;
} else if byte == b'\r' {
i += 1;
let mut line = bytes[start..i].to_owned();
let last = line.len().saturating_sub(1);
line[last] = b'\n';
let line = unsafe { String::from_utf8_unchecked(line) };
lines.push(line);
if bytes.get(i) == Some(&b'\n') {
i += 1;
}
start = i;
} else {
i += 1;
}
}
if start < bytes.len() {
let bytes = bytes[start..].to_owned();
let line = unsafe { String::from_utf8_unchecked(bytes) };
lines.push(line);
}
lines
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct ReaderState {
pos: Position,
idx: usize, // Byte offset in line
}
impl ReaderState {
pub fn pos(&self) -> Position {
self.pos
}
}
#[derive(Clone)]
pub struct ContentReader<'a> {
contents: &'a Contents,
state: ReaderState,
}
impl<'a> ContentReader<'a> {
pub fn new(contents: &'a Contents) -> ContentReader<'a> {
ContentReader {
contents,
state: ReaderState {
pos: Position::default(),
idx: 0,
},
}
}
#[must_use]
pub fn get_char(&self) -> Option<char> {
if let Some(line) = self.contents.get_line(self.state.pos.line as usize) {
let bytes = line.as_bytes();
let idx = self.state.idx;
if idx < bytes.len() {
let slice = unsafe { std::str::from_utf8_unchecked(&bytes[idx..]) };
slice.chars().next()
} else {
None
}
} else {
None
}
}
pub fn pop(&mut self) -> Result<Option<u8>, Utf8ToLatin1Error> {
if let Some(chr) = self.get_char() {
if let Some(latin1) = char_to_latin1(chr) {
self.skip_char(chr);
Ok(Some(latin1))
} else {
let pos = self.pos();
self.skip_char(chr);
Err(Utf8ToLatin1Error { pos, value: chr })
}
} else {
Ok(None)
}
}
pub fn get(&self) -> Result<Option<u8>, Utf8ToLatin1Error> {
if let Some(chr) = self.get_char() {
if let Some(latin1) = char_to_latin1(chr) {
Ok(Some(latin1))
} else {
Err(Utf8ToLatin1Error {
pos: self.pos(),
value: chr,
})
}
} else {
Ok(None)
}
}
#[must_use]
pub fn pop_char(&mut self) -> Option<char> {
let chr = self.get_char()?;
self.skip_char(chr);
Some(chr)
}
fn skip_char(&mut self, chr: char) {
self.state.pos.move_after_char(chr);
if self.state.pos.character == 0 {
self.state.idx = 0;
} else {
self.state.idx += chr.len_utf8();
}
}
pub fn skip(&mut self) {
let _ = self.pop_char();
}
pub fn pop_lowercase(&mut self) -> Result<Option<u8>, Utf8ToLatin1Error> {
Ok(self.pop()?.map(Latin1String::lowercase))
}
pub fn peek_lowercase(&mut self) -> Result<Option<u8>, Utf8ToLatin1Error> {
Ok(self.peek()?.map(Latin1String::lowercase))
}
/// Returns whether the chars that follow are the given substring.
///
/// # Examples
/// ```
/// use super::{ContentReader, Contents};
/// let reader = ContentReader::new(Contents::from_str("foo bar"));
/// assert!(reader.matches("foo"));
/// assert!(!reader.matches("bar"));
/// for _ in 0..3 {
/// reader.skip();
/// }
/// assert!(reader.matches("bar"));
/// assert!(!reader.matches("foo"));
/// ```
#[cfg(test)]
pub fn matches(&mut self, substr: &str) -> bool {
let mut lookahead = self.clone();
for exp in substr.chars() {
if let Some(chr) = lookahead.pop_char() {
if chr != exp {
return false;
}
} else {
return false;
}
}
true
}
pub fn skip_if(&mut self, value: u8) -> Result<bool, Utf8ToLatin1Error> {
if self.peek()? == Some(value) {
self.skip();
Ok(true)
} else {
Ok(false)
}
}
pub fn set_state(&mut self, state: ReaderState) {
self.state = state;
}
pub fn set_to(&mut self, reader: &ContentReader<'_>) {
self.state = reader.state;
}
pub fn pos(&self) -> Position {
self.state.pos()
}
#[cfg(test)]
pub fn seek_pos(&mut self, pos: Position) {
self.state = ReaderState {
pos: Position {
line: pos.line,
character: 0,
},
idx: 0,
};
while self.pos() < pos {
self.skip();
}
assert_eq!(self.pos(), pos);
}
pub fn state(&self) -> ReaderState {
self.state
}
pub fn peek(&self) -> Result<Option<u8>, Utf8ToLatin1Error> {
self.get()
}
pub fn peek_char(&self) -> Option<char> {
self.get_char()
}
pub fn value_at(&self, line: usize, start: usize, stop: usize) -> Option<Latin1String> {
let line = self.contents.get_line(line)?;
if stop > line.len() {
return None;
}
Latin1String::from_utf8(&line[start..stop]).ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn new(code: &str) -> Contents {
Contents::from_str(code)
}
fn reader(contents: &Contents) -> ContentReader<'_> {
ContentReader::new(contents)
}
#[test]
fn pop_latin1_ok() {
let contents = new("hi");
let mut reader = reader(&contents);
assert_eq!(reader.pop(), Ok(Some(b'h')));
assert_eq!(reader.pop(), Ok(Some(b'i')));
assert_eq!(reader.pop(), Ok(None));
}
#[test]
fn pop_latin1_err() {
let contents = new("h€i");
let mut reader = reader(&contents);
assert_eq!(reader.pop(), Ok(Some(b'h')));
assert_eq!(
reader.pop(),
Err(Utf8ToLatin1Error {
pos: Position::new(0, 1),
value: ''
})
);
assert_eq!(reader.pop(), Ok(Some(b'i')));
assert_eq!(reader.pop(), Ok(None));
}
#[test]
fn pop_single_line() {
let contents = new("hi");
let mut reader = reader(&contents);
assert_eq!(reader.pop_char(), Some('h'));
assert_eq!(reader.pop_char(), Some('i'));
assert_eq!(reader.pop_char(), None);
}
#[test]
fn pop_char() {
let contents = new("");
let mut reader = reader(&contents);
assert_eq!(reader.pop_char(), Some('h'));
assert_eq!(reader.pop_char(), Some('å'));
assert_eq!(reader.pop_char(), None);
}
#[test]
fn pop_multi_line_no_newline_at_end() {
let contents = new("h\ni");
let mut reader = reader(&contents);
assert_eq!(reader.pop_char(), Some('h'));
assert_eq!(reader.pop_char(), Some('\n'));
assert_eq!(reader.pop_char(), Some('i'));
assert_eq!(reader.pop_char(), None);
}
#[test]
fn pop_multi_line() {
let contents = new("h\ni\n");
let mut reader = reader(&contents);
assert_eq!(reader.pop_char(), Some('h'));
assert_eq!(reader.pop_char(), Some('\n'));
assert_eq!(reader.pop_char(), Some('i'));
assert_eq!(reader.pop_char(), Some('\n'));
assert_eq!(reader.pop_char(), None);
}
#[test]
fn empty_lines() {
let contents = new("\n\n\n");
let mut reader = reader(&contents);
assert_eq!(reader.pop_char(), Some('\n'));
assert_eq!(reader.pop_char(), Some('\n'));
assert_eq!(reader.pop_char(), Some('\n'));
}
#[test]
fn peek() {
let contents = new("hi");
let mut reader = reader(&contents);
assert_eq!(reader.peek_char(), Some('h'));
assert_eq!(reader.pop_char(), Some('h'));
assert_eq!(reader.peek_char(), Some('i'));
assert_eq!(reader.pop_char(), Some('i'));
assert_eq!(reader.peek_char(), None);
assert_eq!(reader.pop_char(), None);
}
#[test]
fn cr_is_removed() {
let contents = new("1\r2\r\n");
let mut reader = reader(&contents);
assert_eq!(reader.pop_char(), Some('1'));
assert_eq!(reader.pop_char(), Some('\n'));
assert_eq!(reader.pop_char(), Some('2'));
assert_eq!(reader.pop_char(), Some('\n'));
assert_eq!(reader.pop_char(), None);
}
#[test]
fn matches() {
let contents = new("abc");
let mut reader = reader(&contents);
assert!(reader.matches("abc"));
assert!(!reader.matches("bc"));
reader.skip();
assert!(reader.matches("bc"));
}
#[test]
fn character_is_utf16_len() {
// Bomb emojii requires 2 utf-16 codes
let bomb = '\u{1F4A3}';
let contents = new(&format!("{bomb}"));
assert_eq!(contents.end(), Position::new(0, 4));
let mut reader = reader(&contents);
assert_eq!(reader.pop_char(), Some('a'));
assert_eq!(reader.pos(), Position::new(0, 1));
assert_eq!(reader.pop_char(), Some('ä'));
assert_eq!(reader.pos(), Position::new(0, 2));
assert_eq!(reader.pop_char(), Some(bomb));
assert_eq!(reader.pos(), Position::new(0, 4));
}
fn flatten(contents: &Contents) -> String {
let mut result = String::new();
for line in contents.lines.iter() {
result.push_str(line);
}
result
}
#[test]
fn change_first() {
let mut contents = new("hello");
assert_eq!(flatten(&contents), "hello");
contents.change(&Range::new(Position::new(0, 0), Position::new(0, 1)), "_");
assert_eq!(flatten(&contents), "_ello");
}
#[test]
fn change_last() {
let mut contents = new("hello");
assert_eq!(flatten(&contents), "hello");
contents.change(&Range::new(Position::new(0, 4), Position::new(0, 5)), "_");
assert_eq!(flatten(&contents), "hell_");
}
#[test]
fn change_middle() {
let mut contents = new("hello");
assert_eq!(flatten(&contents), "hello");
contents.change(&Range::new(Position::new(0, 2), Position::new(0, 4)), "__");
assert_eq!(flatten(&contents), "he__o");
}
#[test]
fn change_shrink() {
let mut contents = new("hello");
assert_eq!(flatten(&contents), "hello");
contents.change(&Range::new(Position::new(0, 2), Position::new(0, 4)), "_");
assert_eq!(flatten(&contents), "he_o");
}
#[test]
fn change_grow() {
let mut contents = new("hello");
assert_eq!(flatten(&contents), "hello");
contents.change(&Range::new(Position::new(0, 2), Position::new(0, 4)), "___");
assert_eq!(flatten(&contents), "he___o");
}
#[test]
fn change_multi_line() {
let mut contents = new("hello\nworld");
assert_eq!(flatten(&contents), "hello\nworld");
contents.change(
&Range::new(Position::new(0, 3), Position::new(1, 2)),
"__\n__",
);
assert_eq!(flatten(&contents), "hel__\n__rld");
assert_eq!(contents.num_lines(), 2);
assert_eq!(contents.get_line(0).unwrap().to_string(), "hel__\n");
assert_eq!(contents.get_line(1).unwrap().to_string(), "__rld");
}
#[test]
fn change_to_less_lines() {
let mut contents = new("hello\nworld");
assert_eq!(flatten(&contents), "hello\nworld");
contents.change(&Range::new(Position::new(0, 3), Position::new(1, 2)), "");
assert_eq!(flatten(&contents), "helrld");
assert_eq!(contents.num_lines(), 1);
assert_eq!(contents.get_line(0).unwrap().to_string(), "helrld");
}
#[test]
fn change_past_end_of_line() {
let mut contents = new("hello\nworld");
assert_eq!(flatten(&contents), "hello\nworld");
contents.change(&Range::new(Position::new(0, 3), Position::new(0, 7)), "");
assert_eq!(flatten(&contents), "hel\nworld");
assert_eq!(contents.num_lines(), 2);
assert_eq!(contents.get_line(0).unwrap().to_string(), "hel\n");
assert_eq!(contents.get_line(1).unwrap().to_string(), "world");
}
#[test]
fn change_to_more_lines() {
let mut contents = new("hello\nworld");
assert_eq!(flatten(&contents), "hello\nworld");
contents.change(
&Range::new(Position::new(0, 3), Position::new(1, 2)),
"\nmiddle\n",
);
assert_eq!(flatten(&contents), "hel\nmiddle\nrld");
assert_eq!(contents.num_lines(), 3);
assert_eq!(contents.get_line(0).unwrap().to_string(), "hel\n");
assert_eq!(contents.get_line(1).unwrap().to_string(), "middle\n");
assert_eq!(contents.get_line(2).unwrap().to_string(), "rld");
}
#[test]
fn change_keeps_surrounding_lines() {
let mut contents = new("___\nhello\nworld\n...");
assert_eq!(flatten(&contents), "___\nhello\nworld\n...");
contents.change(&Range::new(Position::new(1, 3), Position::new(2, 2)), "");
assert_eq!(flatten(&contents), "___\nhelrld\n...");
assert_eq!(contents.num_lines(), 3);
assert_eq!(contents.get_line(0).unwrap().to_string(), "___\n");
assert_eq!(contents.get_line(1).unwrap().to_string(), "helrld\n");
assert_eq!(contents.get_line(2).unwrap().to_string(), "...");
}
#[test]
fn change_empty() {
let mut contents = new("");
assert_eq!(flatten(&contents), "");
contents.change(&Range::new(Position::new(0, 0), Position::new(0, 0)), "H");
assert_eq!(flatten(&contents), "H");
}
#[test]
fn change_to_empty() {
let mut contents = new("H");
assert_eq!(flatten(&contents), "H");
contents.change(&Range::new(Position::new(0, 0), Position::new(0, 1)), "");
assert_eq!(flatten(&contents), "");
}
#[test]
fn change_add_missing_newline() {
// LSP client will use end line outside of text
let mut contents = new("a");
assert_eq!(flatten(&contents), "a");
contents.change(&Range::new(Position::new(0, 1), Position::new(1, 0)), "\n");
assert_eq!(flatten(&contents), "a\n");
assert_eq!(contents.num_lines(), 1);
assert_eq!(contents.get_line(0).unwrap().to_string(), "a\n");
}
}

View File

@ -0,0 +1,238 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
use super::SrcPos;
use crate::data::error_codes::{ErrorCode, SeverityMap};
use std::convert::{AsRef, Into};
use strum::{EnumString, IntoStaticStr};
#[derive(PartialEq, Debug, Clone, Copy, Eq, Hash, EnumString, IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
pub enum Severity {
Hint,
Info,
Warning,
Error,
}
#[must_use]
#[derive(PartialEq, Debug, Clone, Eq, Hash)]
pub struct Diagnostic {
pub pos: SrcPos,
pub message: String,
pub related: Vec<(SrcPos, String)>,
pub code: ErrorCode,
}
impl Diagnostic {
pub fn new(item: impl AsRef<SrcPos>, msg: impl Into<String>, code: ErrorCode) -> Diagnostic {
Diagnostic {
pos: item.as_ref().clone(),
message: msg.into(),
related: vec![],
code,
}
}
pub fn when(self, message: impl AsRef<str>) -> Diagnostic {
Diagnostic {
message: format!("{}, when {}", &self.message, message.as_ref()),
pos: self.pos,
related: vec![],
code: self.code,
}
}
pub fn related(self, item: impl AsRef<SrcPos>, message: impl Into<String>) -> Diagnostic {
let mut diagnostic = self;
diagnostic.add_related(item, message);
diagnostic
}
pub fn opt_related(
self,
item: Option<impl AsRef<SrcPos>>,
message: impl Into<String>,
) -> Diagnostic {
let mut diagnostic = self;
if let Some(item) = item {
diagnostic.add_related(item, message);
}
diagnostic
}
pub fn add_related(&mut self, item: impl AsRef<SrcPos>, message: impl Into<String>) {
self.related
.push((item.as_ref().to_owned(), message.into()));
}
pub fn drain_related(&mut self) -> Vec<Diagnostic> {
let mut diagnostics = Vec::with_capacity(self.related.len());
let related = std::mem::take(&mut self.related);
for (pos, msg) in related {
diagnostics.push(Diagnostic::new(
pos,
format!("related: {msg}"),
ErrorCode::Related,
));
}
diagnostics
}
pub fn show(&self, severities: &SeverityMap) -> Option<String> {
let severity = severities[self.code]?;
let mut result = String::new();
for (pos, message) in self.related.iter() {
result.push_str(&pos.show(&format!("related: {message}")));
result.push('\n');
}
let severity: &str = severity.into();
result.push_str(&self.pos.show(&format!("{}: {}", severity, self.message)));
Some(result)
}
#[cfg(test)]
pub fn show_default(&self) -> String {
self.show(&SeverityMap::default())
.expect("All severities should be defined in the default severity map")
}
}
pub type DiagnosticResult<T> = Result<T, Diagnostic>;
pub trait DiagnosticHandler {
fn push(&mut self, diagnostic: Diagnostic);
}
impl<'a> dyn DiagnosticHandler + 'a {
pub fn add(&mut self, item: impl AsRef<SrcPos>, msg: impl Into<String>, code: ErrorCode) {
self.push(Diagnostic::new(item, msg, code))
}
pub fn push_result<T>(&mut self, diagnostic: Result<T, Diagnostic>) {
if let Err(diagnostic) = diagnostic {
self.push(diagnostic);
}
}
pub fn push_some(&mut self, diagnostic: Option<Diagnostic>) {
if let Some(diagnostic) = diagnostic {
self.push(diagnostic);
}
}
pub fn append(&mut self, diagnostics: impl IntoIterator<Item = Diagnostic>) {
for diagnostic in diagnostics.into_iter() {
self.push(diagnostic);
}
}
}
impl DiagnosticHandler for Vec<Diagnostic> {
fn push(&mut self, diagnostic: Diagnostic) {
self.push(diagnostic)
}
}
pub struct NullDiagnostics;
impl DiagnosticHandler for NullDiagnostics {
fn push(&mut self, _diagnostic: Diagnostic) {
// Ignore
}
}
#[cfg(test)]
pub struct NoDiagnostics;
#[cfg(test)]
impl DiagnosticHandler for NoDiagnostics {
fn push(&mut self, diagnostic: Diagnostic) {
panic!("{}", diagnostic.show_default())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::syntax::test::Code;
use std::path::Path;
#[test]
fn show_warning() {
let code = Code::new_with_file_name(Path::new("{unknown file}"), "hello\nworld\nline\n");
assert_eq!(
Diagnostic::new(code.s1("world"), "Greetings", ErrorCode::Unused).show_default(),
"\
warning: Greetings
--> {unknown file}:2
|
1 | hello
2 --> world
| ~~~~~
3 | line
"
);
}
#[test]
fn show_error() {
let code = Code::new_with_file_name(Path::new("{unknown file}"), "hello\nworld\nline\n");
assert_eq!(
Diagnostic::new(code.s1("world"), "Greetings", ErrorCode::SyntaxError).show_default(),
"\
error: Greetings
--> {unknown file}:2
|
1 | hello
2 --> world
| ~~~~~
3 | line
"
);
}
#[test]
fn show_related() {
let code = Code::new_with_file_name(Path::new("{unknown file}"), "hello\nworld\nline\n");
let err = Diagnostic::new(code.s1("line"), "Greetings", ErrorCode::SyntaxError)
.related(code.s1("hello"), "From here");
assert_eq!(
err.show_default(),
"\
related: From here
--> {unknown file}:1
|
1 --> hello
| ~~~~~
2 | world
3 | line
error: Greetings
--> {unknown file}:3
|
1 | hello
2 | world
3 --> line
| ~~~~
"
);
}
#[test]
fn do_not_show_ignored_errors() {
let code = Code::new_with_file_name(Path::new("{unknown file}"), "hello\nworld\nline\n");
let mut severity_map = SeverityMap::default();
severity_map[ErrorCode::Unused] = None;
assert_eq!(
Diagnostic::new(code.s1("world"), "Greetings", ErrorCode::Unused).show(&severity_map),
None
);
}
}

View File

@ -0,0 +1,586 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::{Diagnostic, Severity, SrcPos};
use enum_map::{enum_map, Enum, EnumMap};
use std::fmt::{Display, Formatter};
use std::ops::{Index, IndexMut};
use strum::{EnumString, IntoStaticStr};
#[derive(PartialEq, Debug, Clone, Copy, Eq, Hash, EnumString, IntoStaticStr, Enum)]
#[strum(serialize_all = "snake_case")]
pub enum ErrorCode {
/// A syntax error happens during tokenization or parsing.
///
/// # Example
/// ```vhdl
/// entity foo is
/// port (
/// clk: in bit;
/// ^ Last interface element may not end with ';'
/// );
/// end entity;
/// ```
SyntaxError,
// Analysis
/// A circular dependency was found where one module depends on another module which
/// (directly or indirectly) again depends on the first module.
///
/// # Example
/// ```vhdl
/// use work.bar;
///
/// package foo is
/// end package;
///
/// use work.foo;
///
/// package bar is
/// end package;
/// ```
CircularDependency,
/// A formal parameter is invalid / malformed in a certain context
///
/// # Example
/// ```vhdl
/// constant x : bit := foo(b.all => '0');
/// ```
InvalidFormal,
/// Invalid conversion from a formal parameter
///
/// # Example
/// ```vhdl
/// entity foo is
/// port (
/// bar: out natural
/// );
/// end entity;
///
/// -- When instantiating foo
/// function fun1(arg : natural) return natural;
///
/// foo_inst: entity work.foo
/// port map (
/// fun1(arg => bar) => sig
/// );
/// ```
InvalidFormalConversion,
/// Issued when two types don't match in a context where they should.
///
/// # Example
/// ```vhdl
/// constant x : integer := 'a';
/// ```
TypeMismatch,
/// There are multiple functions that a call could address.
/// All methods to disambiguate are exhausted.
///
/// # Example
/// ```vhdl
/// function foo return integer;
/// function foo return character;
/// function bar(arg: integer) return integer;
/// function bar(arg: character) return integer;
/// constant baz: integer := bar(foo);
/// ```
AmbiguousCall,
/// Named arguments appear before positional arguments when calling a function
///
/// # Example
/// ```vhdl
/// function foo(a, b: integer) return bit;
/// constant bar: bit := foo(a => 1, 2);
/// ```
NamedBeforePositional,
/// When calling a function, more arguments are passed to that function than it can accept.
///
/// # Example
/// ```vhdl
/// function foo(a, b: integer) return bit;
/// constant bar: bit := foo(1, 2, 3);
/// ```
TooManyArguments,
/// A formal parameter wasn't associated in a function call.
/// Also emitted when a record element was not associated in a view mode declaration.
///
/// # Example
/// ```vhdl
/// function foo(a, b: integer) return bit;
/// constant bar: bit := foo(a => 1);
/// ```
Unassociated,
/// A formal element has already been associated
///
/// # Example
/// ```vhdl
/// function foo(a: integer) return bit;
/// constant bar: bit := foo(a => 1, a => 2);
/// ```
AlreadyAssociated,
/// The interface mode of a formal parameter (i.e., `signal`, `variable`, ...)
/// of a function does not match the declared more
///
/// # Example
/// ```vhdl
/// function foo(signal a: integer) return bit;
/// constant bar: bit := foo(a => 1); -- a must be associated to a signal, not a constant
/// ```
InterfaceModeMismatch,
/// An element is not allowed inside a sensitivity list
///
/// # Example
/// ```vhdl
/// architecture foo of bar is
/// shared variable x : bit;
/// begin
///
/// process (x)
/// begin
/// end process;
///
/// end architecture;
/// ```
DisallowedInSensitivityList,
/// A declaration is not allowed in a certain context.
/// For example, in an architecture declarative part,
/// `signal`s, `constant`s or `shared variable`s can be declared.
/// However, variables may not be declared in that context.
///
/// # Example
/// ```vhdl
/// architecture foo of bar is
/// variable baz : bit;
/// begin
/// end architecture;
/// ```
DeclarationNotAllowed,
/// The entity class of an attribute does not match the declared
///
/// # Example
/// ```vhdl
/// signal bad : boolean;
/// attribute foo : boolean;
/// attribute foo of bad : variable is true; -- should be signal, not variable
/// ```
MismatchedEntityClass,
/// The attribute specification is not in the immediate declarative part
///
/// # Example
/// ```vhdl
/// entity bar is
/// port (
/// a : in bit
/// );
/// end entity;
///
/// architecture foo of bar is
/// attribute baz : string;
/// attribute baz of a : signal is "foobar";
/// begin
/// end architecture;
/// ```
MisplacedAttributeSpec,
/// There is no overloaded function with an explicitly provided signature available
///
/// # Example
///
/// ```vhdl
/// function foo return natural;
/// attribute bar : natural;
///
/// attribute bar of foo[return boolean] : function is 0;
/// ```
NoOverloadedWithSignature,
/// An explicit signature is used in a context where no signature is expected.
///
/// # Example
/// ```vhdl
/// type enum_t is (alpha, beta);
/// alias alias_t is enum_t[return integer];
/// ```
IllegalSignature,
/// A signature is required to disambiguate
///
/// # Example
/// ```vhdl
/// procedure foo(arg: natural);
/// alias bar is subpgm;
/// ```
SignatureRequired,
/// The value of an expression is ambiguous
AmbiguousExpression,
/// A declaration was already declared previously
///
/// # Example
/// ```vhdl
/// constant foo: bit := '0';
/// constant foo: bit := '1';
/// ```
Duplicate,
/// A designator is hidden by a conflicting use clause
ConflictingUseClause,
/// A protected type that does not have a body
///
/// # Example
///
/// ```vhdl
/// type a1 is protected
/// end protected;
///
/// -- No `type a1 is protected body ... follows`
/// ```
MissingProtectedBodyType,
/// A deferred constant is not allowed in the given context
IllegalDeferredConstant,
/// The signature between an uninstantiated subprogram and it's instantiated
/// counterpart does not match
///
/// # Example
/// ```vhdl
/// procedure foo
/// generic (type T)
/// parameter (x : bit)
/// is begin
/// end foo;
///
/// procedure proc is new foo [bit, bit];
/// ```
SignatureMismatch,
/// When instantiating an uninstantiated subprogram, no distinct subprogram is available
AmbiguousInstantiation,
/// Instantiating a function as procedure or vice-versa
MismatchedSubprogramInstantiation,
/// Function returns without a value
VoidReturn,
/// Procedure returns with value
NonVoidReturn,
/// Illegal return statement, for example in a process
///
/// # Example
///
/// ```vhdl
/// process (clk)
/// begin
/// if rising_edge(clk) then
/// return;
/// end if;
/// end process;
/// ```
IllegalReturn,
/// Exit statement called outside a loop
ExitOutsideLoop,
/// Next statement called outside a loop
NextOutsideLoop,
/// A loop label was found at a position where it shouldn't be
///
/// # Example
/// ```vhdl
/// bad0: loop
/// end loop;
///
/// loop
/// exit bad0;
/// end loop;
/// ```
InvalidLoopLabel,
/// Got something (a named entity such as a type, procedure, e.t.c.)
/// while expecting another thing. For example, got something that names a procedure while
/// expecting a type name.
///
/// This is different from a type error. When a type error occurs,
/// the kinds already match.
MismatchedKinds,
/// An extra index constraint is present
TooManyConstraints,
/// There are not enough constraints
TooFewConstraints,
/// A constraint cannot be used for a given type
IllegalConstraint,
/// A string or symbol was used in a context where an operator was expected but there
/// is no operator for that string.
InvalidOperatorSymbol,
/// An unresolved name was used
///
/// # Example
/// ```vhdl
/// -- There is nothing named 'bar' in this scope
/// variable foo: integer = bar;
/// ```
Unresolved,
/// An index that is out of range for an N-Dimensional array
DimensionMismatch,
/// A literal that cannot be assigned to its target type
InvalidLiteral,
/// A Design Unit (such as an architecture) was declared before another
///Design Unit (such as an entity) which is illegal.
DeclaredBefore,
/// A configuration was found that is not in the same library as the entity
ConfigNotInSameLibrary,
/// No implicit conversion using the `??` operator is possible
NoImplicitConversion,
/// Expected sub-aggregate
ExpectedSubAggregate,
/// An attribute was used on an element that it cannot be used on
IllegalAttribute,
/// Something cannot be prefixed
CannotBePrefixed,
/// A non-scalar is used in a range
NonScalarInRange,
/// A signature appeared that was not expected
UnexpectedSignature,
/// A deferred constant is missing its full constant declaration in the package body
MissingDeferredDeclaration,
/// A deferred type declaration is missing its full declaration
MissingFullTypeDeclaration,
/// Calling a name like a function or procedure where that is not applicable
InvalidCall,
// Linting
/// A declaration that is unused
Unused,
/// The declaration
/// ```vhdl
/// library work;
/// ```
/// was made.
UnnecessaryWorkLibrary,
/// A context clause that is not associated to a design unit
///
/// # Example
/// ```vhdl
/// library ieee;
/// use ieee.std_logic_1164.all;
///
/// -- End of file
/// ```
UnassociatedContext,
// Misc
/// An internal error that signifies that some precondition within vhdl_lang wasn't met.
/// If an error with this error code occurs,
/// please file an issue at https://github.com/VHDL-LS/rust_hdl/issues
Internal,
/// A related error message. This error code is never generated directly and only used
/// as 'drop-in' when related messages are drained from a bigger error message
Related,
}
/// The `SeverityMap` maps error codes to severities.
///
/// Implementations for `Index` and `IndexMut` are provided, so elements within the map can
/// be accessed using the `[]` operator.
/// The value returned by indexing into the severity map has the following meaning:
/// * If the value is `Some(Severity)`,
/// a diagnostic with the given error code should be displayed with that severity
/// * If the value is `None`, a diagnostic with that severity should not be displayed
#[derive(Clone, PartialEq, Eq, Debug, Copy)]
pub struct SeverityMap {
// Using an `EnumMap` ensures that each error code is mapped to exactly one severity.
// Additionally, this allows efficient implementation using an array internally.
inner: EnumMap<ErrorCode, Option<Severity>>,
}
impl Default for SeverityMap {
fn default() -> Self {
use ErrorCode::*;
use Severity::*;
let map = enum_map! {
SyntaxError
| CircularDependency
| InvalidFormal
| InvalidFormalConversion
| TypeMismatch
| AmbiguousCall
| NamedBeforePositional
| TooManyArguments
| Unassociated
| AlreadyAssociated
| InterfaceModeMismatch
| DisallowedInSensitivityList
| DeclarationNotAllowed
| MismatchedEntityClass
| MisplacedAttributeSpec
| NoOverloadedWithSignature
| IllegalSignature
| SignatureRequired
| AmbiguousExpression
| Duplicate
| ConflictingUseClause
| MissingProtectedBodyType
| IllegalDeferredConstant
| SignatureMismatch
| AmbiguousInstantiation
| MismatchedSubprogramInstantiation
| VoidReturn
| NonVoidReturn
| IllegalReturn
| ExitOutsideLoop
| NextOutsideLoop
| InvalidLoopLabel
| MismatchedKinds
| TooManyConstraints
| TooFewConstraints
| IllegalConstraint
| InvalidOperatorSymbol
| Unresolved
| DimensionMismatch
| InvalidLiteral
| DeclaredBefore
| ConfigNotInSameLibrary
| NoImplicitConversion
| ExpectedSubAggregate
| IllegalAttribute
| CannotBePrefixed
| NonScalarInRange
| UnexpectedSignature
| MissingDeferredDeclaration
| MissingFullTypeDeclaration
| InvalidCall => Some(Error),
Unused
| UnnecessaryWorkLibrary
| UnassociatedContext => Some(Warning),
Internal => Some(Error),
Related => Some(Hint)
};
SeverityMap { inner: map }
}
}
impl Index<ErrorCode> for SeverityMap {
type Output = Option<Severity>;
fn index(&self, key: ErrorCode) -> &Self::Output {
self.inner.index(key)
}
}
impl IndexMut<ErrorCode> for SeverityMap {
fn index_mut(&mut self, key: ErrorCode) -> &mut Self::Output {
self.inner.index_mut(key)
}
}
impl ErrorCode {
pub fn as_str(&self) -> &str {
self.into()
}
}
#[test]
fn serialize_from_string() {
assert_eq!(
ErrorCode::try_from("void_return"),
Ok(ErrorCode::VoidReturn)
);
assert_eq!(
ErrorCode::try_from("misplaced_attribute_spec"),
Ok(ErrorCode::MisplacedAttributeSpec)
);
assert_eq!(
ErrorCode::try_from("syntax_error"),
Ok(ErrorCode::SyntaxError)
);
assert_eq!(
ErrorCode::try_from("not_an_error_code"),
Err(strum::ParseError::VariantNotFound)
);
}
#[test]
fn serialize_to_string() {
assert_eq!(ErrorCode::VoidReturn.as_str(), "void_return");
assert_eq!(
ErrorCode::MisplacedAttributeSpec.as_str(),
"misplaced_attribute_spec"
);
assert_eq!(ErrorCode::SyntaxError.as_str(), "syntax_error");
}
/// Specialized diagnostics with pre-defined messages and error codes
impl Diagnostic {
pub fn syntax_error(item: impl AsRef<SrcPos>, msg: impl Into<String>) -> Diagnostic {
Self::new(item, msg, ErrorCode::SyntaxError)
}
pub fn circular_dependency(item: impl AsRef<SrcPos>) -> Diagnostic {
Self::new(
item,
"Found circular dependency",
ErrorCode::CircularDependency,
)
}
pub fn internal(item: impl AsRef<SrcPos>, msg: impl Into<String>) -> Diagnostic {
Self::new(item, msg, ErrorCode::Internal)
}
pub fn illegal_attribute(item: impl AsRef<SrcPos>, msg: impl Into<String>) -> Diagnostic {
Self::new(item, msg, ErrorCode::IllegalAttribute)
}
pub fn mismatched_kinds(item: impl AsRef<SrcPos>, msg: impl Into<String>) -> Diagnostic {
Self::new(item, msg, ErrorCode::MismatchedKinds)
}
}
impl Display for ErrorCode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}

View File

@ -0,0 +1,267 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
use super::Position;
use std::fmt;
use std::str;
pub fn iso_8859_1_to_utf8(bytes: &[u8]) -> String {
let mut utf8_bytes = Vec::new();
for byte in bytes.iter() {
let byte = *byte;
if byte < 128 {
utf8_bytes.push(byte);
} else if byte < 192 {
utf8_bytes.push(0xc2);
utf8_bytes.push(byte);
} else {
utf8_bytes.push(0xc3);
utf8_bytes.push(byte - 64);
}
}
unsafe { str::from_utf8_unchecked(utf8_bytes.as_slice()).to_string() }
}
pub fn char_to_latin1(chr: char) -> Option<u8> {
let mut bytes = [0; 4];
chr.encode_utf8(&mut bytes);
let byte = bytes[0];
if byte < 128 {
Some(byte)
} else if byte == 0xc2 {
let next_byte = bytes[1];
if (128..192).contains(&next_byte) {
Some(next_byte)
} else {
None
}
} else if byte == 0xc3 {
let next_byte = bytes[1];
if (128..192).contains(&next_byte) {
Some(next_byte + 64)
} else {
None
}
} else {
None
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
pub struct Latin1String {
pub bytes: Vec<u8>,
}
impl Latin1String {
pub fn empty() -> Latin1String {
Latin1String { bytes: Vec::new() }
}
pub fn new(bytes: &[u8]) -> Latin1String {
Latin1String {
bytes: Vec::from(bytes),
}
}
pub fn chars(&self) -> impl Iterator<Item = &u8> {
self.bytes.iter()
}
pub fn from_vec(bytes: Vec<u8>) -> Latin1String {
Latin1String { bytes }
}
pub fn len(&self) -> usize {
self.bytes.len()
}
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
pub fn lowercase(chr: u8) -> u8 {
match chr {
215 => chr,
b'A'..=b'Z' | 192..=214 | 216..=222 => chr + 32,
_ => chr,
}
}
pub fn make_lowercase(&mut self) {
for i in 0..self.bytes.len() {
self.bytes[i] = Self::lowercase(self.bytes[i]);
}
}
pub fn to_lowercase(&self) -> Latin1String {
let mut latin1 = Latin1String {
bytes: self.bytes.clone(),
};
latin1.make_lowercase();
latin1
}
pub fn starts_with(&self, other: &Latin1String) -> bool {
if other.len() <= self.len() {
self.bytes[0..other.len()] == other.bytes
} else {
false
}
}
#[cfg(test)]
pub fn from_utf8_unchecked(string: &str) -> Latin1String {
Self::from_utf8(string).unwrap()
}
pub fn from_utf8(string: &str) -> Result<Latin1String, Utf8ToLatin1Error> {
let bytes = string.as_bytes();
let mut latin1_bytes = Vec::with_capacity(string.len());
let mut i = 0;
let mut line = 0;
let mut column = 0;
while i < bytes.len() {
let byte = bytes[i];
let mut error = false;
if byte < 128 {
latin1_bytes.push(byte);
i += 1;
} else if byte == 0xc2 {
let next_byte = bytes[i + 1];
if (128..192).contains(&next_byte) {
latin1_bytes.push(next_byte);
i += 2;
} else {
error = true;
}
} else if byte == 0xc3 {
let next_byte = bytes[i + 1];
if (128..192).contains(&next_byte) {
latin1_bytes.push(next_byte + 64);
i += 2;
} else {
error = true;
}
} else {
error = true;
}
if error {
let value = string[i..].chars().next().unwrap();
return Err(Utf8ToLatin1Error {
pos: Position::new(line, column),
value,
});
}
if byte == b'\n' {
line += 1;
column = 0;
} else {
column += 1;
}
}
Ok(Latin1String::from_vec(latin1_bytes))
}
pub fn push(&mut self, byte: u8) {
self.bytes.push(byte)
}
pub fn clear(&mut self) {
self.bytes.clear()
}
pub fn append(&mut self, other: &mut Latin1String) {
self.bytes.append(&mut other.bytes);
}
}
impl Default for Latin1String {
fn default() -> Self {
Self::empty()
}
}
impl fmt::Debug for Latin1String {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", iso_8859_1_to_utf8(&self.bytes))
}
}
impl fmt::Display for Latin1String {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", iso_8859_1_to_utf8(&self.bytes))
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct Utf8ToLatin1Error {
pub pos: Position,
pub value: char,
}
impl Utf8ToLatin1Error {
pub fn message(&self) -> String {
format!("Found invalid latin-1 character '{}'", self.value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn latin1_to_utf8() {
for byte in 0..=255 {
let latin1 = Latin1String::new(&[byte]);
assert_eq!(
Latin1String::from_utf8(&latin1.to_string()).unwrap(),
latin1
)
}
}
#[test]
fn latin1_lowercase() {
for byte in 0..=255 {
let latin1 = Latin1String::new(&[byte]);
let utf8 = latin1.to_string();
assert_eq!(latin1.to_lowercase().to_string(), utf8.to_lowercase());
}
}
#[test]
fn utf8_to_latin1() {
let utf8 = "åäö";
assert_matches!(Latin1String::from_utf8(utf8), Ok(latin1) => {
assert_eq!(latin1.bytes, [229, 228, 246]);
assert_eq!(latin1.to_string(), utf8);
})
}
#[test]
fn utf8_to_latin1_error() {
let utf8 = "abö€";
assert_matches!(Latin1String::from_utf8(utf8), Err(err) => {
assert_eq!(err.pos.line, 0);
assert_eq!(err.pos.character, 3);
assert_eq!(err.value, '€');
});
let utf8 = "a\n\n";
assert_matches!(Latin1String::from_utf8(utf8), Err(err) => {
assert_eq!(err.pos.line, 2);
assert_eq!(err.pos.character, 0);
assert_eq!(err.value, '€');
assert_eq!(err.message(), "Found invalid latin-1 character '€'");
});
}
}

View File

@ -0,0 +1,99 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com
use std::path::Path;
use strum::AsRefStr;
#[derive(Debug, PartialEq, Eq, AsRefStr)]
#[strum(serialize_all = "snake_case")]
pub enum MessageType {
Error,
Warning,
Info,
Log,
}
#[must_use]
#[derive(Debug, PartialEq, Eq)]
pub struct Message {
pub message_type: MessageType,
pub message: String,
}
impl Message {
pub fn log(message: impl Into<String>) -> Message {
Message {
message_type: MessageType::Log,
message: message.into(),
}
}
pub fn info(message: impl Into<String>) -> Message {
Message {
message_type: MessageType::Info,
message: message.into(),
}
}
pub fn warning(message: impl Into<String>) -> Message {
Message {
message_type: MessageType::Warning,
message: message.into(),
}
}
pub fn error(message: impl Into<String>) -> Message {
Message {
message_type: MessageType::Error,
message: message.into(),
}
}
pub fn file_error(message: impl Into<String>, file_name: &Path) -> Message {
Message {
message_type: MessageType::Error,
message: format!(
"{} (In file {})",
message.into(),
file_name.to_string_lossy()
),
}
}
}
impl std::fmt::Display for Message {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.message_type.as_ref(), self.message)
}
}
pub trait MessageHandler {
fn push(&mut self, message: Message);
}
impl MessageHandler for Vec<Message> {
fn push(&mut self, message: Message) {
self.push(message)
}
}
#[derive(Default)]
pub struct MessagePrinter {}
impl MessageHandler for MessagePrinter {
fn push(&mut self, message: Message) {
// println!("{message}");
}
}
#[derive(Default)]
pub struct NullMessages;
impl MessageHandler for NullMessages {
fn push(&mut self, _message: Message) {
// Ignore
}
}

View File

@ -0,0 +1,803 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
use super::contents::Contents;
use parking_lot::{RwLock, RwLockReadGuard};
use std::cmp::{max, min};
use std::collections::hash_map::DefaultHasher;
use std::convert::AsRef;
use std::fmt;
use std::fmt::Write;
use std::hash::{Hash, Hasher};
use std::io;
pub use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(Debug)]
struct FileId {
name: FilePath,
/// Hash value of `self.name`.
hash: u64,
}
impl FileId {
fn new(name: &Path) -> FileId {
let name = FilePath::new(name);
let hash = hash(&name);
Self { name, hash }
}
}
impl PartialEq for FileId {
fn eq(&self, other: &Self) -> bool {
// Use file name hash to speedup comparison
if self.hash == other.hash {
self.name == other.name
} else {
false
}
}
}
fn hash(value: &Path) -> u64 {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
}
/// Represents a single source file and its contents.
struct UniqueSource {
file_id: FileId,
contents: RwLock<Contents>,
}
impl fmt::Debug for UniqueSource {
/// Custom implementation to avoid large contents strings.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(stringify!(UniqueSource))
.field(stringify!(file_id), &self.file_id)
.field(stringify!(contents), &"...")
.finish()
}
}
impl UniqueSource {
fn inline(file_name: &Path, contents: &str) -> Self {
Self {
file_id: FileId::new(file_name),
contents: RwLock::new(Contents::from_str(contents)),
}
}
fn from_latin1_file(file_name: &Path) -> io::Result<Self> {
let contents = Contents::from_latin1_file(file_name)?;
Ok(Self {
file_id: FileId::new(file_name),
contents: RwLock::new(contents),
})
}
#[cfg(test)]
pub fn from_contents(file_name: &Path, contents: Contents) -> UniqueSource {
Self {
file_id: FileId::new(file_name),
contents: RwLock::new(contents),
}
}
fn contents(&self) -> RwLockReadGuard<'_, Contents> {
self.contents.read()
}
fn file_name(&self) -> &Path {
self.file_id.name.as_ref()
}
fn file_path(&self) -> &FilePath {
&self.file_id.name
}
}
/// A thread-safe reference to a source file.
/// Multiple objects of this type can refer to the same source.
#[derive(Debug, Clone)]
pub struct Source(Arc<UniqueSource>);
impl PartialEq for Source {
fn eq(&self, other: &Self) -> bool {
self.0.file_id == other.0.file_id
}
}
impl PartialOrd for Source {
fn partial_cmp(&self, other: &Source) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Source {
fn cmp(&self, other: &Source) -> std::cmp::Ordering {
self.file_name().cmp(other.file_name())
}
}
impl Eq for Source {}
impl Hash for Source {
fn hash<H: Hasher>(&self, hasher: &mut H) {
hasher.write_u64(self.0.file_id.hash)
}
}
impl Source {
/// Creates a source from a (virtual) name and in-memory contents.
///
/// Note: For differing values of `contents`, the value of `file_name`
/// *must* differ as well.
pub fn inline(file_name: &Path, contents: &str) -> Source {
Source(Arc::new(UniqueSource::inline(file_name, contents)))
}
pub fn from_latin1_file(file_name: &Path) -> io::Result<Source> {
Ok(Source(Arc::new(UniqueSource::from_latin1_file(file_name)?)))
}
#[cfg(test)]
pub fn from_contents(file_name: &Path, contents: Contents) -> Source {
Source(Arc::new(UniqueSource::from_contents(file_name, contents)))
}
pub fn contents(&self) -> RwLockReadGuard<'_, Contents> {
self.0.contents()
}
pub fn file_name(&self) -> &Path {
self.0.file_name()
}
pub(crate) fn file_path(&self) -> &FilePath {
self.0.file_path()
}
pub fn pos(&self, start: Position, end: Position) -> SrcPos {
SrcPos {
source: self.clone(),
range: Range { start, end },
}
}
pub fn change(&self, range: Option<&Range>, content: &str) {
let mut contents = self.0.contents.write();
if let Some(range) = range {
contents.change(range, content);
} else {
*contents = Contents::from_str(content);
}
}
}
/// A lexical position (line, column) in a source.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug, Default)]
pub struct Position {
/// Line (zero-based).
pub line: u32,
/// Column (zero-based).
pub character: u32,
}
impl Position {
pub fn new(line: u32, character: u32) -> Position {
Position { line, character }
}
pub fn next_char(self) -> Position {
Position {
line: self.line,
character: self.character + 1,
}
}
pub fn move_after_char(&mut self, chr: char) {
if chr == '\n' {
self.line += 1;
self.character = 0;
} else {
self.character += chr.len_utf16() as u32;
}
}
pub fn after_char(mut self, chr: char) -> Position {
self.move_after_char(chr);
self
}
pub fn prev_char(self) -> Position {
Position {
line: self.line,
character: self.character.saturating_sub(1),
}
}
pub fn range_to(self, end: Position) -> Range {
Range { start: self, end }
}
}
/// A lexical range in a source.
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
pub struct Range {
/// Start of the range (inclusive).
pub start: Position,
/// End of the range (exclusive).
pub end: Position,
}
impl Range {
pub fn new(start: Position, end: Position) -> Range {
Range { start, end }
}
pub fn contains(&self, position: Position) -> bool {
self.start <= position && self.end >= position
}
}
/// A lexical range within a specific source file.
#[derive(PartialEq, Clone, Debug, Eq, Hash)]
pub struct SrcPos {
/// The referenced source file.
pub source: Source,
pub range: Range,
}
impl Ord for SrcPos {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let ordering = self.source.cmp(&other.source);
if std::cmp::Ordering::Equal == ordering {
self.range.start.cmp(&other.range.start)
} else {
ordering
}
}
}
impl PartialOrd for SrcPos {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl AsRef<SrcPos> for SrcPos {
fn as_ref(&self) -> &SrcPos {
self
}
}
impl SrcPos {
const LINE_CONTEXT: u32 = 2;
pub fn new(source: Source, range: Range) -> SrcPos {
SrcPos { source, range }
}
fn get_line_context(&self, context_lines: u32, contents: &Contents) -> Vec<(u32, String)> {
let mut lines = Vec::new();
let start = self.range.start.line.saturating_sub(context_lines);
let end = self.range.end.line + context_lines;
for lineno in start..=end {
if let Some(line) = contents.get_line(lineno as usize) {
lines.push((lineno, line.to_owned()));
}
}
if lines.is_empty() {
lines.push((self.range.start.line, String::new()));
}
lines
}
fn push_replicate(line: &mut String, chr: char, times: usize) {
for _ in 0..times {
line.push(chr);
}
}
fn visual_width(chr: char) -> usize {
if chr == '\t' {
4
} else {
1
}
}
/// Write ~~~ to underline symbol
fn underline(&self, lineno_len: usize, lineno: u32, line: &str, into: &mut String) {
const NEWLINE_SIZE: usize = 1;
into.reserve(" | ".len() + lineno_len + line.len() + NEWLINE_SIZE);
// Prefix
for _ in 0..lineno_len {
into.push(' ');
}
into.push_str(" | ");
let mut pos = Position {
line: lineno,
character: 0,
};
// Padding before underline
for chr in line.chars() {
if pos < self.range.start {
Self::push_replicate(into, ' ', Self::visual_width(chr));
} else if pos < self.range.end {
Self::push_replicate(into, '~', Self::visual_width(chr));
} else {
break;
}
pos.character += chr.len_utf16() as u32;
}
if lineno == self.range.end.line {
while pos < self.range.end {
into.push('~');
pos.character += 1;
}
}
// Newline
into.push('\n');
}
fn code_context_from_contents(
&self,
contents: &Contents,
context_lines: u32,
) -> (usize, String) {
let lines = self.get_line_context(context_lines, contents);
use pad::{Alignment, PadStr};
// +1 since lines are shown with 1-index
let lineno_len = (self.range.start.line + context_lines + 1)
.to_string()
.len();
let mut result = String::new();
for (lineno, line) in lines.iter() {
let line = line.to_string();
let line = line.trim_matches('\n');
let lineno_str = (lineno + 1)
.to_string()
.pad_to_width_with_alignment(lineno_len, Alignment::Right);
let overlaps = self.range.start.line <= *lineno && *lineno <= self.range.end.line;
if overlaps {
write!(result, "{lineno_str} --> ").unwrap();
} else {
write!(result, "{lineno_str} | ").unwrap();
}
for chr in line.trim_end().chars() {
if chr == '\t' {
Self::push_replicate(&mut result, ' ', Self::visual_width(chr));
} else {
result.push(chr);
}
}
result.push('\n');
if overlaps {
self.underline(lineno_len, *lineno, line, &mut result);
}
}
(lineno_len, result)
}
/// Create a string for pretty printing.
pub fn code_context(&self) -> String {
self.lineno_len_and_code_context().1
}
fn lineno_len_and_code_context(&self) -> (usize, String) {
let contents = self.source.contents();
self.code_context_from_contents(&contents, Self::LINE_CONTEXT)
}
pub fn show(&self, message: &str) -> String {
let (lineno_len, pretty_str) = self.lineno_len_and_code_context();
let file_name = self.source.file_name();
let mut result = String::new();
let lineno = self.range.start.line;
writeln!(result, "{}", &message).unwrap();
for _ in 0..lineno_len {
result.push(' ');
}
writeln!(
result,
" --> {}:{}",
file_name.to_string_lossy(),
lineno + 1
)
.unwrap();
for _ in 0..lineno_len {
result.push(' ');
}
writeln!(result, " |").unwrap();
result.push_str(&pretty_str);
result
}
/// Combines two lexical positions into a larger lexical position overlapping both.
/// The file name is assumed to be the same.
pub fn combine_into(self, other: &dyn AsRef<Self>) -> Self {
let other = other.as_ref();
debug_assert!(self.source == other.source, "Assumes sources are equal");
let start = min(self.range.start, other.range.start);
let end = max(self.range.end, other.range.end);
SrcPos {
source: self.source,
range: Range { start, end },
}
}
pub fn start(&self) -> Position {
self.range.start
}
pub fn end(&self) -> Position {
self.range.end
}
pub fn pos_at_end(&self) -> SrcPos {
SrcPos {
source: self.source.clone(),
range: Range::new(self.range.end, self.range.end),
}
}
pub fn pos_at_beginning(&self) -> SrcPos {
SrcPos {
source: self.source.clone(),
range: Range::new(self.range.start, self.range.start),
}
}
pub fn range(&self) -> Range {
self.range
}
pub fn file_name(&self) -> &Path {
self.source.file_name()
}
pub fn combine(&self, other: &dyn AsRef<Self>) -> Self {
self.clone().combine_into(other)
}
pub fn contains(&self, pos: Position) -> bool {
self.range.contains(pos)
}
pub fn end_pos(&self) -> SrcPos {
SrcPos::new(self.source.clone(), Range::new(self.end(), self.end()))
}
}
/// Denotes an item with an associated source file.
///
/// Most types that implement this trait do so through the blanket implementation
/// on [`HasSrcPos`](trait.HasSrcPos.html).
pub trait HasSource {
fn source(&self) -> &Source;
}
impl HasSource for Source {
fn source(&self) -> &Source {
self
}
}
/// Denotes an item with an associated lexical range in a source file.
pub trait HasSrcPos {
fn pos(&self) -> &SrcPos;
}
impl HasSrcPos for SrcPos {
fn pos(&self) -> &SrcPos {
self
}
}
impl<T: HasSrcPos> HasSource for T {
fn source(&self) -> &Source {
&self.pos().source
}
}
/// A wrapper around a PathBuf that ensures the path is absolute and simplified.
///
/// This struct can be used similar to a [PathBuf], i.e., dereferencing it will return a [Path]
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub(crate) struct FilePath(PathBuf);
impl std::ops::Deref for FilePath {
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FilePath {
pub fn new(path: &Path) -> Self {
// In tests, when using inline files, paths are used that do not point to an existing file.
// In this case, we simply want to preserve the name without changing it.
if cfg!(test) && !path.exists() {
return Self(path.to_owned());
}
// It would also be possible to use dunce::canonicalize here instead of path::absolute
// and dunce::simplify, but dunce::canonicalize resolves symlinks
// which we don't want (see issue #327)
let path = match std::path::absolute(path) {
// dunce::simplified converts UNC paths to regular paths.
// UNC paths have caused issues when a file was mounted on a network drive.
// Related issue: #115
Ok(path) => dunce::simplified(&path).to_owned(),
Err(err) => {
// eprintln!(
// "Could not create absolute path {}: {:?}",
// path.to_string_lossy(),
// err
// );
path.to_owned()
}
};
Self(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::Latin1String;
use crate::syntax::test::{Code, CodeBuilder};
use pretty_assertions::assert_eq;
#[test]
fn srcpos_combine() {
let code = Code::new("hello world");
assert_eq!(
code.s1("hello").pos().combine(&code.s1("world").pos()),
code.pos()
);
assert_eq!(code.s1("h").pos().combine(&code.s1("d").pos()), code.pos());
assert_eq!(code.s1("d").pos().combine(&code.s1("h").pos()), code.pos());
}
fn with_code_from_file<F, R>(contents: &str, fun: F) -> R
where
F: Fn(Code) -> R,
{
use std::io::Write;
let mut file = tempfile::NamedTempFile::new().unwrap();
let file_name = file.path().to_owned();
file.write_all(&Latin1String::from_utf8_unchecked(contents).bytes)
.unwrap();
fun(CodeBuilder::new().code_from_source(Source::from_latin1_file(&file_name).unwrap()))
}
#[test]
fn code_context_pos_from_filename() {
with_code_from_file("hello\nworld\n", |code: Code| {
assert_eq!(
code.s1("hello").pos().code_context(),
"\
1 --> hello
| ~~~~~
2 | world
"
)
});
}
#[test]
fn code_context_pos_last_line_without_newline() {
let code = Code::new("hello world");
let pos = code.s1("hello").pos();
assert_eq!(
pos.code_context(),
"\
1 --> hello world
| ~~~~~
"
);
}
#[test]
fn code_context_pos_with_indent() {
let code = Code::new(" hello world");
let pos = code.s1("hello").pos();
assert_eq!(
pos.code_context(),
"\
1 --> hello world
| ~~~~~
"
);
}
#[test]
fn code_context_eof() {
let code = Code::new("h");
assert_eq!(
code.eof_pos().code_context(),
"\
1 --> h
| ~
",
);
}
#[test]
fn code_context_eof_empty() {
let code = Code::new("");
assert_eq!(code.eof_pos().code_context(), "1 --> \n | ~\n",);
}
#[test]
fn code_context_with_context() {
let code = Code::new("hello\nworld");
let pos = code.s1("hello").pos();
assert_eq!(
pos.code_context(),
"\
1 --> hello
| ~~~~~
2 | world
",
);
}
#[test]
fn code_context_with_tabs() {
let code = Code::new("\thello\t");
let pos = code.s1("hello\t").pos();
assert_eq!(
pos.code_context(),
"\
1 --> hello
| ~~~~~~~~~
",
);
}
#[test]
fn code_context_non_ascii() {
let code = Code::new("åäö\nåäö\n__å_ä_ö__");
let substr = code.s1("å_ä_ö");
assert_eq!(substr.end().character - substr.start().character, 5);
assert_eq!(
substr.pos().code_context(),
"\
1 | åäö
2 | åäö
3 --> __å_ä_ö__
| ~~~~~
",
);
}
#[test]
fn code_context_double_utf16() {
// Bomb emojii requires 2 utf-16 codes
let code = Code::new("\u{1F4A3}");
assert_eq!(code.end().character - code.start().character, 2);
assert_eq!(
code.pos().code_context(),
"\
1 --> \u{1F4A3}
| ~
",
);
}
#[test]
fn code_context_non_ascii_from_file() {
with_code_from_file("åäö\nåäö\n__å_ä_ö__", |code: Code| {
let substr = code.s1("å_ä_ö");
assert_eq!(substr.end().character - substr.start().character, 5);
assert_eq!(
substr.pos().code_context(),
"\
1 | åäö
2 | åäö
3 --> __å_ä_ö__
| ~~~~~
",
);
});
}
#[test]
fn code_context_with_full_context() {
let code = Code::new(
"\
line1
line2
line3
line4
line5
line6
line7
line8
line9
line10
line11
line12
line13",
);
let pos = code.s1("line10").pos();
assert_eq!(
pos.code_context(),
" \
8 | line8
9 | line9
10 --> line10
| ~~~~~~
11 | line11
12 | line12
",
);
}
#[test]
fn show_from_filename() {
with_code_from_file("hello\nworld\nline\n", |code: Code| {
assert_eq!(
code.s1("world").pos().show("Greetings"),
format!(
"\
Greetings
--> {}:2
|
1 | hello
2 --> world
| ~~~~~
3 | line
",
code.source().file_name().to_string_lossy()
)
)
});
}
#[test]
fn show_contents() {
let code = Code::new("hello\nworld\nline\n");
assert_eq!(
code.s1("world").pos().show("Greetings"),
format!(
"\
Greetings
--> {}:2
|
1 | hello
2 --> world
| ~~~~~
3 | line
",
code.source().file_name().to_string_lossy()
)
);
}
}

View File

@ -0,0 +1,238 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
use super::latin_1::Latin1String;
use parking_lot::RwLock;
use std::sync::Arc;
use fnv::FnvHashMap;
/// Represents a unique string symbol.
///
/// The `id` field can be used as a fast comparison key for symbols.
/// Two symbols are compared for equality based on VHDL's rules for identifiers:
/// * basic identifiers are compared case-insensitive (LRM 15.4.2)
/// * extended identifiers are compared case-sensitive (LRM 15.4.3)
#[derive(Clone, Debug, Eq)]
pub struct Symbol {
/// The unique ID of the symbol.
///
/// Note: IDs are not necessarily contiguous.
pub(crate) id: usize,
/// The name of the symbol
name: Arc<Latin1String>,
}
impl Symbol {
/// Creates a new symbol. The `id` parameter is assumed to be a valid
/// [`SymbolTable`](struct.SymbolTable.html) ID.
fn new(id: usize, name: &Arc<Latin1String>) -> Symbol {
Symbol {
id,
name: Arc::clone(name),
}
}
/// Returns the name of the symbol.
pub fn name(&self) -> &Latin1String {
self.name.as_ref()
}
/// Returns the name of the symbol as a UTF-8 string.
pub fn name_utf8(&self) -> String {
self.name.to_string()
}
}
impl PartialEq for Symbol {
/// Symbols are compared just based on the `id` field.
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl std::fmt::Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name_utf8())
}
}
impl std::hash::Hash for Symbol {
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
self.id.hash(hasher);
}
}
/// A thread-safe symbol table to keep track of identifiers.
///
/// This table maintains a mapping from symbol strings to
/// [`Symbol`](struct.Symbol.html) objects.
/// Equivalent identifiers get identical IDs.
#[derive(Default)]
pub struct SymbolTable {
/// Symbol mapping.
///
/// Basic identifiers containing upper-case letters are stored in verbatim
/// and normalized forms, with distinct [`Symbol`](struct.Symbol.html) objects,
/// but these objects contain the same ID.
name_to_symbol: RwLock<FnvHashMap<Arc<Latin1String>, Symbol>>,
}
impl SymbolTable {
pub fn insert_utf8(&self, name: &str) -> Symbol {
let name = Latin1String::from_utf8(name).unwrap();
self.insert(&name)
}
#[cfg(test)]
pub fn insert_extended_utf8(&self, name: &str) -> Symbol {
let name = Latin1String::from_utf8_unchecked(name);
self.insert_extended(&name)
}
/// Looks up an identifier (basic or extended).
///
/// Returns the corresponding `Symbol` instance if the identifier already exists,
/// and `None` otherwise.
pub fn lookup(&self, name: &Latin1String) -> Option<Symbol> {
let name_to_symbol = self.name_to_symbol.read();
// Symbol already exists with identical case
name_to_symbol.get(name).cloned()
}
/// Inserts a basic identifier and returns a corresponding `Symbol` instance.
pub fn insert(&self, name: &Latin1String) -> Symbol {
if let Some(symbol) = self.lookup(name) {
symbol
} else {
self.insert_new(name, false)
}
}
/// Inserts an extended identifier and returns a corresponding `Symbol` instance.
pub fn insert_extended(&self, name: &Latin1String) -> Symbol {
if let Some(symbol) = self.lookup(name) {
symbol
} else {
self.insert_new(name, true)
}
}
fn insert_new(&self, name: &Latin1String, is_extended: bool) -> Symbol {
let mut name_to_symbol = self.name_to_symbol.write();
// Lookup again after taking lock to avoid race-condition where new symbols are created in parallel
if let Some(sym) = name_to_symbol.get(name) {
// Symbol already exists with identical case
return sym.clone();
}
debug_assert_eq!(name.bytes.first() == Some(&b'\\'), is_extended);
let name = Arc::from(name.clone());
if is_extended {
let id = name_to_symbol.len();
let sym = Symbol::new(id, &name);
name_to_symbol.insert(name, sym.clone());
return sym;
}
// Symbol does not exists with the given case, try normalizing case
let normal_name = Arc::from(name.to_lowercase());
match name_to_symbol.get(&normal_name).cloned() {
// Symbol exists in normalized case
Some(normal_sym) => {
// Copy id from previous symbol
// Insert new symbol with given case and return it
let id = normal_sym.id;
let sym = Symbol::new(id, &name);
name_to_symbol.insert(name, sym.clone());
sym
}
// Symbol does not exist
None => {
// Create new id
let id = name_to_symbol.len();
if normal_name != name {
// If symbol is not already normalized case insert it
let sym = Symbol::new(id, &normal_name);
name_to_symbol.insert(normal_name, sym);
}
let sym = Symbol::new(id, &name);
name_to_symbol.insert(name, sym.clone());
sym
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn symbol_table_insert() {
let symtab = SymbolTable::default();
let sym = symtab.insert_utf8("hello");
assert_eq!(sym.name_utf8(), "hello");
}
#[test]
fn symbols_are_equal() {
let symtab = SymbolTable::default();
let sym0 = symtab.insert_utf8("hello");
let sym1 = symtab.insert_utf8("hello");
assert_eq!(sym0, sym1);
assert_eq!(sym0.name_utf8(), "hello");
assert_eq!(sym1.name_utf8(), "hello");
let sym0 = symtab.insert_utf8("Hello");
let sym1 = symtab.insert_utf8("hello");
assert_eq!(sym0, sym1);
assert_eq!(sym0.name_utf8(), "Hello");
assert_eq!(sym1.name_utf8(), "hello");
}
#[test]
fn symbols_are_case_insensitive() {
let symtab = SymbolTable::default();
let sym0 = symtab.insert_utf8("Hello");
let sym1 = symtab.insert_utf8("hello");
let sym2 = symtab.insert_utf8("heLLo");
assert_eq!(sym0, sym1);
assert_eq!(sym0, sym2);
assert_eq!(sym1, sym2);
assert_eq!(sym0.name_utf8(), "Hello");
assert_eq!(sym1.name_utf8(), "hello");
assert_eq!(sym2.name_utf8(), "heLLo");
}
#[test]
fn extended_identifiers_symbols_are_case_sensitive() {
let symtab = SymbolTable::default();
let sym0 = symtab.insert_extended_utf8("\\hello\\");
let sym1 = symtab.insert_extended_utf8("\\HELLO\\");
let sym2 = symtab.insert_extended_utf8("\\hello\\");
assert_ne!(sym0, sym1);
assert_eq!(sym0, sym2);
assert_ne!(sym1, sym2);
assert_eq!(sym0.name_utf8(), "\\hello\\");
assert_eq!(sym1.name_utf8(), "\\HELLO\\");
assert_eq!(sym2.name_utf8(), "\\hello\\");
}
#[test]
fn symbols_are_not_equal() {
let symtab = SymbolTable::default();
let sym0 = symtab.insert_utf8("hello");
let sym1 = symtab.insert_utf8("abc");
assert_ne!(sym0, sym1);
}
}

View File

@ -0,0 +1,133 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::ArchitectureBody;
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::{indented, HasTokenSpan, TokenSpan};
impl VHDLFormatter<'_> {
pub fn format_architecture(&self, arch: &ArchitectureBody, buffer: &mut Buffer) {
self.format_context_clause(&arch.context_clause, buffer);
if let Some(item) = arch.context_clause.last() {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
let span = arch.span();
// architecture <ident> of <ident> is
self.format_token_span(TokenSpan::new(span.start_token, arch.is_token()), buffer);
indented!(buffer, { self.format_declarations(&arch.decl, buffer) });
buffer.line_break();
self.format_token_id(arch.begin_token, buffer);
indented!(buffer, {
self.format_concurrent_statements(&arch.statements, buffer);
});
buffer.line_break();
// end [architecture] [name];
self.format_token_span(TokenSpan::new(arch.end_token, span.end_token - 1), buffer);
self.format_token_id(span.end_token, buffer);
}
}
#[cfg(test)]
mod test {
use crate::syntax::test::Code;
use vhdl_lang::formatting::test_utils::check_formatted;
fn check_architecture_formatted(input: &str) {
check_formatted(
input,
input,
Code::architecture_body,
|formatter, arch, buffer| formatter.format_architecture(arch, buffer),
)
}
#[test]
fn format_empty_architecture() {
check_architecture_formatted(
"\
architecture foo of bar is
begin
end foo;",
);
check_architecture_formatted(
"\
architecture foo of bar is
begin
end architecture foo;",
);
check_architecture_formatted(
"\
architecture foo of bar is
begin
end;",
);
}
#[test]
fn format_architecture_with_declarations() {
check_architecture_formatted(
"\
architecture foo of bar is
constant x: foo := bar;
begin
end foo;",
);
check_architecture_formatted(
"\
architecture foo of bar is
constant x: foo := bar;
signal y: bar := foobar;
begin
end foo;",
);
}
#[test]
fn format_full_architecture() {
check_architecture_formatted(
"\
architecture foo of bar is
constant x: foo := bar;
signal y: bar := foobar;
begin
bar: process(clk) is
variable z: baz;
begin
if rising_edge(clk) then
if rst = '1' then
foo <= '0';
else
foo <= bar and baz;
end if;
end if;
end process bar;
y <= x; -- An assignment
end foo;",
);
}
#[test]
fn format_architecture_preserve_whitespace() {
check_architecture_formatted(
"\
architecture foo of bar is
constant x: foo := bar;
constant baz: char := '1';
signal y: bar := foobar;
signal foobar: std_logic := 'Z';
shared variable sv: natural;
begin
end foo;",
);
}
}

View File

@ -0,0 +1,330 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::syntax::{Comment, Value};
use crate::{kind_str, Token};
use std::cmp::max;
use std::iter;
/// The Buffer is the (mostly) mutable object used to write tokens to a string.
/// It operates mostly on tokens and is capable of indenting,
/// de-indenting and keeping the indentation level.
pub struct Buffer {
inner: String,
/// insert an extra newline before pushing a token.
/// This is relevant when there is a trailing comment
insert_extra_newline: bool,
/// The current indentation level
indentation: usize,
/// The char used for indentation
indent_char: char,
/// The width used at each indentation level
indent_width: usize,
}
impl Buffer {
pub fn new() -> Buffer {
Buffer {
inner: String::new(),
insert_extra_newline: false,
indentation: 0,
indent_char: ' ',
indent_width: 4,
}
}
}
impl Default for Buffer {
fn default() -> Self {
Self::new()
}
}
/// Returns whether a leading comment is on the same line as the token, i.e.,
/// check the case
/// ```vhdl
/// /* some comment */ token
/// ```
fn leading_comment_is_on_token_line(comment: &Comment, token: &Token) -> bool {
if !comment.multi_line {
return false;
}
if comment.range.start.line != comment.range.end.line {
return false;
}
token.pos.start().line == comment.range.start.line
}
impl From<Buffer> for String {
fn from(value: Buffer) -> Self {
value.inner
}
}
impl Buffer {
pub fn as_str(&self) -> &str {
self.inner.as_str()
}
/// pushes a whitespace character to the buffer
pub fn push_whitespace(&mut self) {
if !self.insert_extra_newline {
self.push_ch(' ');
}
}
fn format_comment(&mut self, comment: &Comment) {
if !comment.multi_line {
self.push_str("--");
self.push_str(comment.value.trim_end())
} else {
self.push_str("/*");
self.push_str(&comment.value);
self.push_str("*/");
}
}
fn format_leading_comments(&mut self, comments: &[Comment]) {
for (i, comment) in comments.iter().enumerate() {
self.format_comment(comment);
if let Some(next_comment) = comments.get(i + 1) {
let number_of_line_breaks =
max(next_comment.range.start.line - comment.range.end.line, 1);
self.line_breaks(number_of_line_breaks);
} else {
self.line_break();
}
}
}
fn indent(&mut self) {
self.inner
.extend(iter::repeat(self.indent_char).take(self.indent_width * self.indentation));
}
/// Push a token to this buffer.
/// This takes care of all the leading and trailing comments attached to that token.
pub fn push_token(&mut self, token: &Token) {
if self.insert_extra_newline {
self.line_break();
}
self.insert_extra_newline = false;
if let Some(comments) = &token.comments {
// This is for example the case for situations like
// some_token /* comment in between */ some_other token
if comments.leading.len() == 1
&& leading_comment_is_on_token_line(&comments.leading[0], token)
{
self.format_comment(&comments.leading[0]);
self.push_ch(' ');
} else if !comments.leading.is_empty() {
self.format_leading_comments(comments.leading.as_slice());
}
}
match &token.value {
Value::Identifier(ident) => self.push_str(&ident.to_string()),
Value::String(string) => {
self.push_ch('"');
for byte in &string.bytes {
if *byte == b'"' {
self.push_ch('"');
self.push_ch('"');
} else {
self.push_ch(*byte as char);
}
}
self.push_ch('"');
}
Value::BitString(value, _) => self.push_str(&value.to_string()),
Value::AbstractLiteral(value, _) => self.push_str(&value.to_string()),
Value::Character(char) => {
self.push_ch('\'');
self.push_ch(*char as char);
self.push_ch('\'');
}
Value::Text(text) => self.push_str(&text.to_string()),
Value::None => self.push_str(kind_str(token.kind)),
}
if let Some(comments) = &token.comments {
if let Some(trailing_comment) = &comments.trailing {
self.push_ch(' ');
self.format_comment(trailing_comment);
self.insert_extra_newline = true
}
}
}
fn push_str(&mut self, value: &str) {
self.inner.push_str(value);
}
fn push_ch(&mut self, char: char) {
self.inner.push(char);
}
/// Increase the indentation level.
/// After this call, all new-line pushes will be preceded by an indentation,
/// specified via the `indent_char` and `indent_width` properties.
///
/// This call should always be matched with a `decrease_indent` call.
/// There is also the `indented` macro that combines the two calls.
pub fn increase_indent(&mut self) {
self.indentation += 1;
}
pub fn decrease_indent(&mut self) {
self.indentation -= 1;
}
/// Inserts a line break (i.e., newline) at the current position
pub fn line_break(&mut self) {
self.insert_extra_newline = false;
self.push_ch('\n');
self.indent();
}
/// Inserts multiple line breaks.
/// Note that this method must always be used (i.e., is different from
/// multiple `line_break` calls) as this method only indents the last line break
pub fn line_breaks(&mut self, count: u32) {
self.insert_extra_newline = false;
for _ in 0..count {
self.push_ch('\n');
}
self.indent();
}
}
#[cfg(test)]
mod tests {
use crate::analysis::tests::Code;
use crate::formatting::buffer::Buffer;
use std::iter::zip;
fn check_token_formatted(input: &str, expected: &[&str]) {
let code = Code::new(input);
let tokens = code.tokenize();
for (token, expected) in zip(tokens, expected) {
let mut buffer = Buffer::new();
buffer.push_token(&token);
assert_eq!(buffer.as_str(), *expected);
}
}
#[test]
fn format_simple_token() {
check_token_formatted("entity", &["entity"]);
check_token_formatted("foobar", &["foobar"]);
check_token_formatted("1 23 4E5 4e5", &["1", "23", "4E5", "4e5"]);
}
#[test]
fn preserves_identifier_casing() {
check_token_formatted("FooBar foobar", &["FooBar", "foobar"]);
}
#[test]
fn character_formatting() {
check_token_formatted("'a' 'Z' '''", &["'a'", "'Z'", "'''"]);
}
#[test]
fn string_formatting() {
check_token_formatted(
r#""ABC" "" "DEF" """" "Hello "" ""#,
&["\"ABC\"", "\"\"", "\"DEF\"", "\"\"\"\"", "\"Hello \"\" \""],
);
}
#[test]
fn bit_string_formatting() {
check_token_formatted(r#"B"10" 20B"8" X"2F""#, &["B\"10\"", "20B\"8\"", "X\"2F\""]);
}
#[test]
fn leading_comment() {
check_token_formatted(
"\
-- I am a comment
foobar
",
&["\
-- I am a comment
foobar"],
);
}
#[test]
fn multiple_leading_comments() {
check_token_formatted(
"\
-- I am a comment
-- So am I
foobar
",
&["\
-- I am a comment
-- So am I
foobar"],
);
}
#[test]
fn trailing_comments() {
check_token_formatted(
"\
foobar --After foobar comes foobaz
",
&["foobar --After foobar comes foobaz"],
);
}
#[test]
fn single_multiline_comment() {
check_token_formatted(
"\
/** Some documentation.
* This is a token named 'entity'
*/
entity
",
&["\
/** Some documentation.
* This is a token named 'entity'
*/
entity"],
);
}
#[test]
fn multiline_comment_and_simple_comment() {
check_token_formatted(
"\
/* I am a multiline comment */
-- And I am a single line comment
entity
",
&["\
/* I am a multiline comment */
-- And I am a single line comment
entity"],
);
}
#[test]
fn leading_comment_and_trailing_comment() {
check_token_formatted(
"\
-- Leading comment
entity -- Trailing comment
",
&["\
-- Leading comment
entity -- Trailing comment"],
);
}
}

View File

@ -0,0 +1,928 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{
AssignmentRightHand, BlockStatement, CaseGenerateStatement, ConcurrentAssertStatement,
ConcurrentSignalAssignment, ConcurrentStatement, Conditionals, ElementAssociation,
ForGenerateStatement, GenerateBody, Ident, IfGenerateStatement, InstantiatedUnit,
InstantiationStatement, LabeledConcurrentStatement, ProcessStatement, SensitivityList, Target,
Waveform, WaveformElement,
};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::Kind;
use crate::{HasTokenSpan, TokenAccess};
use vhdl_lang::ast::{Alternative, AssertStatement, ConcurrentProcedureCall};
use vhdl_lang::{indented, TokenSpan};
impl VHDLFormatter<'_> {
pub fn join_on_newline<T>(
&self,
items: &[T],
joiner: impl Fn(&Self, &T, &mut Buffer),
buffer: &mut Buffer,
) {
for item in items {
buffer.line_break();
joiner(self, item, buffer);
}
}
pub fn format_concurrent_statements(
&self,
statements: &[LabeledConcurrentStatement],
buffer: &mut Buffer,
) {
if statements.is_empty() {
return;
}
buffer.line_break();
for (i, item) in statements.iter().enumerate() {
self.format_labeled_concurrent_statement(item, buffer);
if i < statements.len() - 1 {
self.line_break_preserve_whitespace(item.statement.get_end_token(), buffer);
}
}
}
pub fn format_optional_label(&self, label: Option<&Ident>, buffer: &mut Buffer) {
if let Some(label) = label {
self.format_token_id(label.token, buffer);
// :
self.format_token_id(label.token + 1, buffer);
buffer.push_whitespace();
}
}
pub fn format_labeled_concurrent_statement(
&self,
statement: &LabeledConcurrentStatement,
buffer: &mut Buffer,
) {
self.format_optional_label(statement.label.tree.as_ref(), buffer);
self.format_concurrent_statement(&statement.statement, buffer);
}
pub fn format_concurrent_statement(
&self,
statement: &WithTokenSpan<ConcurrentStatement>,
buffer: &mut Buffer,
) {
use ConcurrentStatement::*;
let span = statement.span;
match &statement.item {
ProcedureCall(call) => self.format_procedure_call(call, span, buffer),
Block(block) => self.format_block_statement(block, span, buffer),
Process(process) => self.format_process_statement(process, span, buffer),
Assert(assert) => self.format_concurrent_assert_statement(assert, span, buffer),
Assignment(assignment) => self.format_assignment_statement(assignment, span, buffer),
Instance(instantiation_statement) => {
self.format_instantiation_statement(instantiation_statement, span, buffer)
}
ForGenerate(for_generate) => {
self.format_for_generate_statement(for_generate, span, buffer)
}
IfGenerate(if_generate) => self.format_if_generate_statement(if_generate, span, buffer),
CaseGenerate(case_generate) => {
self.format_case_generate_statement(case_generate, span, buffer)
}
}
}
pub fn format_procedure_call(
&self,
call: &ConcurrentProcedureCall,
span: TokenSpan,
buffer: &mut Buffer,
) {
if call.postponed {
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
}
self.format_call_or_indexed(&call.call.item, call.call.span, buffer);
// ;
self.format_token_id(span.end_token, buffer);
}
pub fn format_block_statement(
&self,
block: &BlockStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// block
self.format_token_id(span.start_token, buffer);
if let Some(guard_condition) = &block.guard_condition {
self.format_token_id(guard_condition.span.start_token - 1, buffer);
self.format_expression(guard_condition.as_ref(), buffer);
self.format_token_id(guard_condition.span.end_token + 1, buffer);
}
if let Some(is_token) = block.is_token {
buffer.push_whitespace();
self.format_token_id(is_token, buffer);
}
indented!(buffer, {
if let Some(generic_clause) = &block.header.generic_clause {
buffer.line_break();
self.format_interface_list(generic_clause, buffer);
}
if let Some(generic_map) = &block.header.generic_map {
buffer.line_break();
self.format_map_aspect(generic_map, buffer);
// ;
self.format_token_id(generic_map.span.end_token + 1, buffer);
}
if let Some(port_clause) = &block.header.port_clause {
buffer.line_break();
self.format_interface_list(port_clause, buffer);
}
if let Some(port_map) = &block.header.port_map {
buffer.line_break();
self.format_map_aspect(port_map, buffer);
// ;
self.format_token_id(port_map.span.end_token + 1, buffer);
}
self.format_declarations(&block.decl, buffer);
});
buffer.line_break();
self.format_token_id(block.begin_token, buffer);
buffer.line_break();
indented!(buffer, {
self.format_concurrent_statements(&block.statements, buffer)
});
self.format_token_span(
TokenSpan::new(block.end_token, block.span.end_token - 1),
buffer,
);
// ;
self.format_token_id(block.span.end_token, buffer);
}
pub fn format_process_statement(
&self,
process: &ProcessStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.token_with_opt_postponed(span, buffer);
if let Some(sensitivity_list) = &process.sensitivity_list {
match &sensitivity_list.item {
SensitivityList::Names(names) => {
self.format_token_id(sensitivity_list.span.start_token, buffer);
self.format_name_list(buffer, names);
self.format_token_id(sensitivity_list.span.end_token, buffer);
}
SensitivityList::All => self.join_token_span(sensitivity_list.span, buffer),
}
}
if let Some(is_token) = process.is_token {
buffer.push_whitespace();
self.format_token_id(is_token, buffer);
}
indented!(buffer, { self.format_declarations(&process.decl, buffer) });
buffer.line_break();
self.format_token_id(process.begin_token, buffer);
self.format_sequential_statements(&process.statements, buffer);
buffer.line_break();
self.format_token_span(
TokenSpan::new(process.end_token, process.span.end_token - 1),
buffer,
);
// ;
self.format_token_id(process.span.end_token, buffer);
}
fn token_with_opt_postponed(&self, span: TokenSpan, buffer: &mut Buffer) {
if self.tokens.index(span.start_token).kind == Kind::Postponed {
// postponed <x>
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 1),
buffer,
);
} else {
// <x>
self.format_token_id(span.start_token, buffer);
}
}
pub fn format_concurrent_assert_statement(
&self,
statement: &ConcurrentAssertStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.token_with_opt_postponed(span, buffer);
buffer.push_whitespace();
self.format_assert_statement(&statement.statement, buffer);
// ;
self.format_token_id(span.end_token, buffer);
}
pub fn format_assert_statement(&self, assert_statement: &AssertStatement, buffer: &mut Buffer) {
self.format_expression(assert_statement.condition.as_ref(), buffer);
if let Some(report) = &assert_statement.report {
buffer.push_whitespace();
self.format_token_id(report.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(report.as_ref(), buffer);
}
self.format_opt_severity(assert_statement.severity.as_ref(), buffer);
}
pub fn format_assignment_statement(
&self,
assignment_statement: &ConcurrentSignalAssignment,
span: TokenSpan,
buffer: &mut Buffer,
) {
if let AssignmentRightHand::Selected(selected) = &assignment_statement.assignment.rhs {
// with
self.format_token_id(selected.expression.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(selected.expression.as_ref(), buffer);
buffer.push_whitespace();
// select
self.format_token_id(selected.expression.span.end_token + 1, buffer);
buffer.push_whitespace();
}
self.format_target(&assignment_statement.assignment.target, buffer);
buffer.push_whitespace();
// <=
self.format_token_id(
assignment_statement.assignment.target.span.end_token + 1,
buffer,
);
buffer.push_whitespace();
if let Some(mechanism) = &assignment_statement.assignment.delay_mechanism {
self.format_delay_mechanism(mechanism, buffer);
buffer.push_whitespace();
}
self.format_assignment_right_hand(
&assignment_statement.assignment.rhs,
Self::format_waveform,
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_assignment_right_hand<T>(
&self,
right_hand: &AssignmentRightHand<T>,
formatter: impl Fn(&Self, &T, &mut Buffer),
buffer: &mut Buffer,
) {
use AssignmentRightHand::*;
match right_hand {
Simple(simple) => formatter(self, simple, buffer),
Conditional(conditionals) => {
self.format_assignment_right_hand_conditionals(conditionals, formatter, buffer)
}
Selected(selection) => {
for alternative in &selection.alternatives {
self.format_alternative(alternative, &formatter, buffer);
if self
.tokens
.get_token(alternative.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(alternative.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
}
}
}
pub fn format_alternative<T>(
&self,
alternative: &Alternative<T>,
formatter: &impl Fn(&Self, &T, &mut Buffer),
buffer: &mut Buffer,
) {
formatter(self, &alternative.item, buffer);
buffer.push_whitespace();
for (i, choice) in alternative.choices.iter().enumerate() {
if i == 0 {
// when
self.format_token_id(choice.span.start_token - 1, buffer);
buffer.push_whitespace();
}
self.format_choice(choice, buffer);
if i < alternative.choices.len() - 1 {
buffer.push_whitespace();
// |
self.format_token_id(choice.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
}
pub fn format_assignment_right_hand_conditionals<T>(
&self,
conditionals: &Conditionals<T>,
formatter: impl Fn(&Self, &T, &mut Buffer),
buffer: &mut Buffer,
) {
for cond in &conditionals.conditionals {
// item
formatter(self, &cond.item, buffer);
let condition = &cond.condition;
buffer.push_whitespace();
// when
self.format_token_id(condition.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(condition.as_ref(), buffer);
// [else]
if self
.tokens
.get_token(cond.condition.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Else)
{
buffer.push_whitespace();
self.format_token_id(cond.condition.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
if let Some((statements, _)) = &conditionals.else_item {
// else handled above
formatter(self, statements, buffer);
}
}
pub fn format_waveform(&self, waveform: &Waveform, buffer: &mut Buffer) {
match waveform {
Waveform::Elements(elements) => {
for (i, element) in elements.iter().enumerate() {
self.format_waveform_element(element, buffer);
if i < elements.len() - 1 {
self.format_token_id(element.get_end_token() + 1, buffer);
buffer.push_whitespace();
}
}
}
Waveform::Unaffected(token) => self.format_token_id(*token, buffer),
}
}
pub fn format_waveform_element(&self, element: &WaveformElement, buffer: &mut Buffer) {
self.format_expression(element.value.as_ref(), buffer);
if let Some(after) = &element.after {
buffer.push_whitespace();
self.format_token_id(after.get_start_token() - 1, buffer);
buffer.push_whitespace();
self.format_expression(after.as_ref(), buffer);
}
}
pub fn format_target(&self, target: &WithTokenSpan<Target>, buffer: &mut Buffer) {
match &target.item {
Target::Name(name) => self.format_name(WithTokenSpan::new(name, target.span), buffer),
Target::Aggregate(associations) => {
self.format_target_aggregate(associations, target.span, buffer)
}
}
}
pub fn format_target_aggregate(
&self,
associations: &[WithTokenSpan<ElementAssociation>],
span: TokenSpan,
buffer: &mut Buffer,
) {
// (
self.format_token_id(span.start_token, buffer);
self.format_element_associations(associations, buffer);
// )
self.format_token_id(span.end_token, buffer);
}
pub fn format_instantiation_statement(
&self,
statement: &InstantiationStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
if matches!(
self.tokens.index(span.start_token).kind,
Kind::Component | Kind::Entity | Kind::Configuration
) {
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
}
match &statement.unit {
InstantiatedUnit::Component(name) | InstantiatedUnit::Configuration(name) => {
self.format_name(name.as_ref(), buffer);
}
InstantiatedUnit::Entity(name, architecture) => {
self.format_name(name.as_ref(), buffer);
if let Some(arch) = architecture {
self.join_token_span(
TokenSpan::new(arch.item.token - 1, arch.item.token + 1),
buffer,
)
}
}
}
if let Some(generic_map) = &statement.generic_map {
indented!(buffer, {
buffer.line_break();
self.format_map_aspect(generic_map, buffer);
});
}
if let Some(port_map) = &statement.port_map {
indented!(buffer, {
buffer.line_break();
self.format_map_aspect(port_map, buffer);
});
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_for_generate_statement(
&self,
statement: &ForGenerateStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// for
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
// index
self.format_ident(&statement.index_name, buffer);
buffer.push_whitespace();
// in
self.format_token_id(statement.index_name.tree.token + 1, buffer);
buffer.push_whitespace();
self.format_discrete_range(&statement.discrete_range, buffer);
buffer.push_whitespace();
self.format_token_id(statement.generate_token, buffer);
self.format_generate_body(&statement.body, buffer);
buffer.line_break();
self.format_token_span(
TokenSpan::new(statement.end_token, span.end_token - 1),
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_if_generate_statement(
&self,
statement: &IfGenerateStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
for cond in &statement.conds.conditionals {
let condition = &cond.condition;
if let Some(label) = &cond.item.alternative_label {
// if | elsif
self.format_token_id(label.tree.token - 1, buffer);
buffer.push_whitespace();
// label
self.format_token_id(label.tree.token, buffer);
// :
self.format_token_id(label.tree.token + 1, buffer);
buffer.push_whitespace();
} else {
self.format_token_id(condition.span.start_token - 1, buffer);
buffer.push_whitespace();
}
self.format_expression(condition.as_ref(), buffer);
buffer.push_whitespace();
// generate
self.format_token_id(condition.span.end_token + 1, buffer);
self.format_generate_body(&cond.item, buffer);
buffer.line_break();
}
if let Some((statements, token)) = &statement.conds.else_item {
if let Some(label) = &statements.alternative_label {
// else
self.format_token_id(label.tree.token - 1, buffer);
buffer.push_whitespace();
// label
self.format_token_id(label.tree.token, buffer);
// :
self.format_token_id(label.tree.token + 1, buffer);
buffer.push_whitespace();
// generate
self.format_token_id(label.tree.token + 2, buffer);
} else {
// else
self.format_token_id(*token, buffer);
buffer.push_whitespace();
// generate
self.format_token_id(*token + 1, buffer);
}
self.format_generate_body(statements, buffer);
buffer.line_break();
}
if statement.end_label_pos.is_some() {
// end if <label>
self.format_token_span(
TokenSpan::new(span.end_token - 3, span.end_token - 1),
buffer,
)
} else {
// end if
self.format_token_span(
TokenSpan::new(span.end_token - 2, span.end_token - 1),
buffer,
)
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_case_generate_statement(
&self,
statement: &CaseGenerateStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// case
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_expression(statement.sels.expression.as_ref(), buffer);
buffer.push_whitespace();
// generate
self.format_token_id(statement.sels.expression.span.end_token + 1, buffer);
indented!(buffer, {
for alternative in &statement.sels.alternatives {
buffer.line_break();
for (i, choice) in alternative.choices.iter().enumerate() {
if i == 0 {
if let Some(label) = &alternative.item.alternative_label {
// when
self.format_token_id(label.tree.token - 1, buffer);
buffer.push_whitespace();
// <ident>
self.format_token_id(label.tree.token, buffer);
// :
self.format_token_id(label.tree.token + 1, buffer);
} else {
// when
self.format_token_id(choice.span.start_token - 1, buffer);
}
buffer.push_whitespace();
}
self.format_choice(choice, buffer);
if i < alternative.choices.len() - 1 {
buffer.push_whitespace();
// |
self.format_token_id(choice.span.end_token + 1, buffer);
buffer.push_whitespace();
}
if i == alternative.choices.len() - 1 {
buffer.push_whitespace();
// =>
self.format_token_id(choice.span.end_token + 1, buffer);
}
}
self.format_generate_body(&alternative.item, buffer);
}
});
buffer.line_break();
self.format_token_span(
TokenSpan::new(statement.end_token, span.end_token - 1),
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_generate_body(&self, generate_body: &GenerateBody, buffer: &mut Buffer) {
if let Some((decl, begin_token)) = &generate_body.decl {
indented!(buffer, { self.format_declarations(decl, buffer) });
buffer.line_break();
self.format_token_id(*begin_token, buffer);
}
indented!(buffer, {
self.format_concurrent_statements(&generate_body.statements, buffer)
});
if let Some(end_token) = generate_body.end_token {
buffer.line_break();
self.format_token_id(end_token, buffer);
if let Some(token) = generate_body.end_label {
buffer.push_whitespace();
self.format_token_id(token, buffer);
// ;
self.format_token_id(token + 1, buffer);
} else {
// ;
self.format_token_id(end_token + 1, buffer);
}
}
}
}
#[cfg(test)]
mod tests {
use crate::formatting::test_utils::check_formatted;
use crate::syntax::test::Code;
fn check_statement(input: &str) {
check_formatted(
input,
input,
Code::concurrent_statement,
|formatter, ast, buffer| formatter.format_labeled_concurrent_statement(ast, buffer),
)
}
#[test]
fn procedure_calls() {
check_statement("foo;");
check_statement("foo(clk);");
check_statement("foo(clk, bar);");
check_statement("foo(0);");
check_statement("foo(arg => 0);");
}
#[test]
fn blocks() {
check_statement(
"\
name: block
begin
end block name;",
);
check_statement(
"\
name: block is
begin
end block name;",
);
check_statement(
"\
name: block(cond = true)
begin
end block;",
);
check_statement(
"\
name: block(cond = true) is
begin
end block;",
);
check_statement(
"\
block(cond = true) is
begin
end block;",
);
check_statement(
"\
name: block is
generic (
gen: integer := 1
);
generic map (
gen => 1
);
port (
prt: integer := 1
);
port map (
prt => 2
);
begin
end block;",
);
}
#[test]
fn check_processes() {
check_statement(
"\
process
begin
end process;",
);
check_statement(
"\
name: process is
begin
end process name;",
);
check_statement(
"\
postponed process
begin
end process;",
);
check_statement(
"\
postponed process
begin
end postponed process;",
);
check_statement(
"\
process(clk, vec(1)) is
begin
end process;",
);
check_statement(
"\
process(all) is
variable foo: boolean;
begin
end process;",
);
}
#[test]
fn check_assert() {
check_statement("assert false;");
check_statement("assert cond = true;");
check_statement("postponed assert cond = true;");
check_statement("assert false report \"message\" severity error;");
}
#[test]
fn check_signal_assignment() {
check_statement("foo <= bar(2 to 3);");
check_statement("x <= bar(1 to 3) after 2 ns;");
check_statement("foo <= bar(1 to 3) after 2 ns, expr after 1 ns;");
}
#[test]
fn check_simple_instantiation_statement() {
check_statement("inst: component lib.foo.bar;");
check_statement("inst: configuration lib.foo.bar;");
check_statement("inst: entity lib.foo.bar;");
check_statement("inst: entity lib.foo.bar(arch);");
}
#[test]
fn check_instantiation_statement_generic_map() {
check_statement(
"\
inst: component lib.foo.bar
generic map (
const => 1
);",
);
check_statement(
"\
inst: component lib.foo.bar
port map (
clk => clk_foo
);",
);
check_statement(
"\
inst: component lib.foo.bar
generic map (
const => 1
)
port map (
clk => clk_foo
);",
);
}
#[test]
fn format_for_generate_statement() {
check_statement(
"\
gen: for idx in 0 to 1 generate
end generate;",
);
check_statement(
"\
gen: for idx in 0 to 1 generate
foo <= bar;
end generate;",
);
check_statement(
"\
gen: for idx in 0 to 1 generate
begin
foo <= bar;
end generate;",
);
check_statement(
"\
gen: for idx in 0 to 1 generate
begin
foo <= bar;
end;
end generate;",
);
check_statement(
"\
gen: for idx in 0 to 1 generate
signal foo: natural;
begin
foo <= bar;
end generate;",
);
check_statement(
"\
gen: for idx in 0 to 1 generate
signal foo: natural;
begin
foo <= bar;
end;
end generate;",
);
}
#[test]
fn format_if_generate_statement() {
check_statement(
"\
gen: if cond = true generate
end generate;",
);
check_statement(
"\
gen: if cond = true generate
begin
end generate;",
);
check_statement(
"\
gen: if cond = true generate
elsif cond2 = true generate
else generate
end generate;",
);
check_statement(
"\
gen: if cond = true generate
variable v1: boolean;
begin
foo1(clk);
elsif cond2 = true generate
variable v2: boolean;
begin
foo2(clk);
else generate
variable v3: boolean;
begin
foo3(clk);
end generate;",
);
check_statement(
"\
gen: if alt1: cond = true generate
end alt1;
elsif alt2: cond2 = true generate
end alt2;
else alt3: generate
end alt3;
end generate;",
);
}
#[test]
fn format_conditional_assignment() {
check_statement("foo(0) <= bar(1, 2) when cond = true;");
check_statement("foo(0) <= bar(1, 2) when cond = true else expr2 when cond2;");
check_statement("foo(0) <= bar(1, 2) when cond = true else expr2;");
check_statement("foo(0) <= bar(1, 2) after 2 ns when cond;");
}
#[test]
fn format_aggregate_assignments() {
check_statement("(foo, 1 => bar) <= integer_vector'(1, 2);");
}
#[test]
fn format_selected_assignments() {
check_statement(
"\
with x(0) + 1 select foo(0) <= bar(1, 2) when 0 | 1, def when others;",
);
check_statement(
"\
with x(0) + 1 select foo(0) <= transport bar(1, 2) after 2 ns when 0 | 1, def when others;",
);
}
#[test]
fn format_case_generate_statements() {
check_statement(
"\
gen: case expr(0) + 2 generate
when 1 | 2 =>
sig <= value;
when others =>
foo(clk);
end generate;",
);
check_statement(
"\
gen1: case expr(0) + 2 generate
when alt1: 1 | 2 =>
sig <= value;
when alt2: others =>
foo(clk);
end generate gen1;",
);
}
}

View File

@ -0,0 +1,378 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{
BindingIndication, BlockConfiguration, ComponentSpecification, ConfigurationDeclaration,
ConfigurationItem, ConfigurationSpecification, EntityAspect, VUnitBindingIndication,
};
use crate::formatting::buffer::Buffer;
use crate::syntax::Kind;
use crate::{indented, HasTokenSpan, TokenAccess, TokenSpan, VHDLFormatter};
use vhdl_lang::ast::{ComponentConfiguration, InstantiationList};
impl VHDLFormatter<'_> {
pub fn format_configuration(
&self,
configuration: &ConfigurationDeclaration,
buffer: &mut Buffer,
) {
self.format_context_clause(&configuration.context_clause, buffer);
if let Some(item) = configuration.context_clause.last() {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
// configuration cfg of entity_name is
self.format_token_span(
TokenSpan::new(
configuration.span.start_token,
configuration.span.start_token + 4,
),
buffer,
);
indented!(buffer, {
self.format_declarations(&configuration.decl, buffer);
self.format_v_unit_binding_indications(&configuration.vunit_bind_inds, buffer);
buffer.line_break();
self.format_block_configuration(&configuration.block_config, buffer);
});
buffer.line_break();
self.format_token_span(
TokenSpan::new(configuration.end_token, configuration.span.end_token - 1),
buffer,
);
self.format_token_id(configuration.span.end_token, buffer);
}
pub fn format_v_unit_binding_indications(
&self,
v_units: &[VUnitBindingIndication],
buffer: &mut Buffer,
) {
for v_unit_bind_ind in v_units {
buffer.line_break();
self.format_v_unit_indication(v_unit_bind_ind, buffer);
}
}
pub fn format_block_configuration(&self, config: &BlockConfiguration, buffer: &mut Buffer) {
if !config.use_clauses.is_empty() {
unreachable!("Not implemented on AST side")
}
// for
self.format_token_id(config.span.start_token, buffer);
buffer.push_whitespace();
self.format_name(config.block_spec.as_ref(), buffer);
indented!(buffer, {
for item in &config.items {
buffer.line_break();
match item {
ConfigurationItem::Block(block_configuration) => {
self.format_block_configuration(block_configuration, buffer)
}
ConfigurationItem::Component(component_configuration) => {
self.format_component_configuration(component_configuration, buffer)
}
}
}
});
buffer.line_break();
// end
self.format_token_id(config.span.end_token - 2, buffer);
buffer.push_whitespace();
// for
self.format_token_id(config.span.end_token - 1, buffer);
// ;
self.format_token_id(config.span.end_token, buffer);
}
pub fn format_component_configuration(
&self,
config: &ComponentConfiguration,
buffer: &mut Buffer,
) {
self.format_component_specification(&config.spec, buffer);
indented!(buffer, {
if let Some(binding_indication) = &config.bind_ind {
buffer.line_break();
self.format_binding_indication(binding_indication, buffer)
}
self.format_v_unit_binding_indications(&config.vunit_bind_inds, buffer);
if let Some(block_configuration) = &config.block_config {
buffer.line_break();
self.format_block_configuration(block_configuration, buffer);
}
});
buffer.line_break();
// end
self.format_token_id(config.span.end_token - 2, buffer);
buffer.push_whitespace();
// for
self.format_token_id(config.span.end_token - 1, buffer);
// ;
self.format_token_id(config.span.end_token, buffer);
}
pub fn format_binding_indication(&self, indication: &BindingIndication, buffer: &mut Buffer) {
// use
self.format_token_id(indication.span.start_token, buffer);
if let Some(aspect) = &indication.entity_aspect {
buffer.push_whitespace();
self.format_token_id(indication.span.start_token + 1, buffer);
buffer.push_whitespace();
match aspect {
EntityAspect::Entity(entity, architecture) => {
self.format_name(entity.as_ref(), buffer);
if let Some(arch) = architecture {
self.format_token_id(arch.token - 1, buffer);
self.format_token_id(arch.token, buffer);
self.format_token_id(arch.token + 1, buffer);
}
}
EntityAspect::Configuration(config) => {
self.format_name(config.as_ref(), buffer);
}
EntityAspect::Open => {}
}
}
if let Some(map_aspect) = &indication.generic_map {
indented!(buffer, {
buffer.line_break();
self.format_map_aspect(map_aspect, buffer);
});
}
if let Some(map_aspect) = &indication.port_map {
indented!(buffer, {
buffer.line_break();
self.format_map_aspect(map_aspect, buffer);
});
}
self.format_token_id(indication.span.end_token, buffer);
}
pub fn format_configuration_specification(
&self,
configuration: &ConfigurationSpecification,
buffer: &mut Buffer,
) {
self.format_component_specification(&configuration.spec, buffer);
indented!(buffer, {
buffer.line_break();
self.format_binding_indication(&configuration.bind_ind, buffer);
self.format_v_unit_binding_indications(&configuration.vunit_bind_inds, buffer);
});
if let Some(end_token) = configuration.end_token {
buffer.line_break();
self.format_token_id(end_token, buffer);
buffer.push_whitespace();
self.format_token_id(end_token + 1, buffer);
self.format_token_id(configuration.span.end_token, buffer);
}
}
pub fn format_component_specification(
&self,
spec: &ComponentSpecification,
buffer: &mut Buffer,
) {
// for
self.format_token_id(spec.span.start_token, buffer);
buffer.push_whitespace();
match &spec.instantiation_list {
InstantiationList::Labels(labels) => self.format_ident_list(labels, buffer),
InstantiationList::Others => self.format_token_id(spec.span.start_token + 1, buffer),
InstantiationList::All => self.format_token_id(spec.span.start_token + 1, buffer),
}
// :
self.format_token_id(spec.colon_token, buffer);
buffer.push_whitespace();
self.format_name(spec.component_name.as_ref(), buffer);
}
pub fn format_v_unit_indication(
&self,
v_unit_binding_indication: &VUnitBindingIndication,
buffer: &mut Buffer,
) {
// use
self.format_token_id(v_unit_binding_indication.span.start_token, buffer);
buffer.push_whitespace();
// v_unit
self.format_token_id(v_unit_binding_indication.span.start_token + 1, buffer);
buffer.push_whitespace();
for v_unit in &v_unit_binding_indication.vunit_list {
self.format_name(v_unit.as_ref(), buffer);
if self
.tokens
.get_token(v_unit.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(v_unit.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
self.format_token_id(v_unit_binding_indication.span.end_token, buffer);
}
}
#[cfg(test)]
mod test {
use crate::analysis::tests::Code;
use crate::formatting::test_utils::check_formatted;
fn check_design_unit_formatted(input: &str) {
check_formatted(
input,
input,
Code::design_file,
|formatter, file, buffer| {
formatter.format_any_design_unit(&file.design_units[0].1, buffer, true)
},
);
}
#[test]
fn check_configuration() {
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
end for;
end;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
use lib.foo.bar;
use lib2.foo.bar;
for rtl(0)
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for name(0 to 3)
end for;
for other_name
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for name(0 to 3)
for name(7 to 8)
end for;
end for;
for other_name
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
use lib.foo.bar;
use vunit baz.foobar;
for rtl(0)
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for inst: lib.pkg.comp
for arch
end for;
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for inst: lib.pkg.comp
use entity work.bar;
use vunit baz;
for arch
end for;
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for inst: lib.pkg.comp
use entity lib.use_name;
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for inst: lib.pkg.comp
end for;
for inst1, inst2, inst3: lib2.pkg.comp
end for;
for all: lib3.pkg.comp
end for;
for others: lib4.pkg.comp
end for;
end for;
end configuration cfg;",
);
}
#[test]
fn check_entity_aspect() {
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for foo
for inst: lib.pkg.comp
use entity lib.use_name;
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for foo
for inst: lib.pkg.comp
use entity lib.foo.name(arch);
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for foo
for inst: lib.pkg.comp
use configuration lib.foo.name;
end for;
end for;
end configuration cfg;",
);
}
}

View File

@ -0,0 +1,167 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{DiscreteRange, SubtypeConstraint};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::Kind;
use crate::TokenAccess;
use vhdl_lang::ast::{ElementConstraint, Range, RangeConstraint};
impl VHDLFormatter<'_> {
pub fn format_subtype_constraint(
&self,
constraint: &WithTokenSpan<SubtypeConstraint>,
buffer: &mut Buffer,
) {
match &constraint.item {
SubtypeConstraint::Range(range) => {
self.format_token_id(constraint.span.start_token, buffer);
buffer.push_whitespace();
self.format_range(range, buffer)
}
SubtypeConstraint::Array(ranges, opt_constraint) => {
self.format_token_id(constraint.span.start_token, buffer);
if ranges.is_empty() {
// open
self.format_token_id(constraint.span.start_token + 1, buffer);
}
for range in ranges {
self.format_discrete_range(&range.item, buffer);
if self
.tokens
.get_token(range.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(range.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
if let Some(constraint) = opt_constraint {
self.format_token_id(constraint.span.start_token - 1, buffer);
self.format_subtype_constraint(constraint, buffer);
} else {
self.format_token_id(constraint.span.end_token, buffer);
}
}
SubtypeConstraint::Record(records) => {
self.format_token_id(constraint.span.start_token, buffer);
for record in records {
self.format_element_constraint(record, buffer);
if self
.tokens
.get_token(record.constraint.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(record.constraint.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
self.format_token_id(constraint.span.end_token, buffer);
}
}
}
pub fn format_element_constraint(&self, constraint: &ElementConstraint, buffer: &mut Buffer) {
self.format_token_id(constraint.ident.token, buffer);
self.format_subtype_constraint(&constraint.constraint, buffer);
}
pub fn format_range_constraint(&self, constraint: &RangeConstraint, buffer: &mut Buffer) {
self.format_expression(constraint.left_expr.as_ref().as_ref(), buffer);
buffer.push_whitespace();
self.format_token_id(constraint.direction_token(), buffer);
buffer.push_whitespace();
self.format_expression(constraint.right_expr.as_ref().as_ref(), buffer);
}
pub fn format_range(&self, range: &Range, buffer: &mut Buffer) {
match range {
Range::Range(constraint) => self.format_range_constraint(constraint, buffer),
Range::Attribute(attribute) => self.format_attribute_name(attribute, buffer),
}
}
pub fn format_discrete_range(&self, range: &DiscreteRange, buffer: &mut Buffer) {
match range {
DiscreteRange::Discrete(name, range) => {
self.format_name(name.as_ref(), buffer);
if let Some(range) = range {
buffer.push_whitespace();
// range
self.format_token_id(name.span.end_token + 1, buffer);
buffer.push_whitespace();
self.format_range(range, buffer);
}
}
DiscreteRange::Range(range) => self.format_range(range, buffer),
}
}
}
#[cfg(test)]
mod test {
use crate::formatting::buffer::Buffer;
use crate::formatting::test_utils::check_formatted;
use crate::formatting::VHDLFormatter;
use crate::syntax::test::Code;
fn check_range(input: &str) {
let code = Code::new(input);
let range = code.range();
let tokens = code.tokenize();
let formatter = VHDLFormatter::new(&tokens);
let mut buffer = Buffer::new();
formatter.format_range(&range, &mut buffer);
assert_eq!(buffer.as_str(), input);
}
#[test]
fn check_simple_range() {
check_range("0 to 5");
check_range("0 downto 5 - C_OFFSET");
}
fn check_subtype_indications(inputs: &[&str]) {
for input in inputs {
check_formatted(
input,
input,
Code::subtype_indication,
|formatter, subtype_indication, buffer| {
formatter.format_subtype_indication(subtype_indication, buffer)
},
)
}
}
#[test]
fn format_range_subtype_constraint() {
check_subtype_indications(&[
"integer range 0 to 2 - 1",
"integer range lib.foo.bar'range",
]);
}
#[test]
fn format_array_subtype_constraint() {
check_subtype_indications(&[
"integer_vector(2 - 1 downto 0)",
"integer_vector(lib.foo.bar)",
"integer_vector(lib.pkg.bar'range)",
"integer_vector(open)",
"integer_vector(2 - 1 downto 0, 11 to 14)",
"integer_vector(2 - 1 downto 0, 11 to 14)(foo to bar)",
]);
}
#[test]
fn format_record_subtype_constraint() {
check_subtype_indications(&["axi_m2s_t(tdata(2 - 1 downto 0), tuser(3 to 5))"]);
}
}

View File

@ -0,0 +1,51 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{ContextClause, ContextDeclaration, ContextItem};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::{HasTokenSpan, TokenSpan};
use vhdl_lang::indented;
impl VHDLFormatter<'_> {
pub fn format_context(&self, context: &ContextDeclaration, buffer: &mut Buffer) {
// context <name> is
self.format_token_span(
TokenSpan::new(context.span.start_token, context.span.start_token + 2),
buffer,
);
indented!(buffer, {
if !context.items.is_empty() {
buffer.line_break();
}
self.format_context_clause(&context.items, buffer);
});
buffer.line_break();
self.format_token_span(
TokenSpan::new(context.end_token, context.span.end_token - 1),
buffer,
);
self.format_token_id(context.span.end_token, buffer);
}
pub fn format_context_clause(&self, clause: &ContextClause, buffer: &mut Buffer) {
for (i, item) in clause.iter().enumerate() {
match item {
ContextItem::Use(use_clause) => self.format_use_clause(use_clause, buffer),
ContextItem::Library(library_clause) => {
self.format_library_clause(library_clause, buffer)
}
ContextItem::Context(context_reference) => {
self.format_context_reference(context_reference, buffer)
}
}
if i < clause.len() - 1 {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
}
}
}

View File

@ -0,0 +1,865 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{
ArrayIndex, ComponentDeclaration, ContextReference, ElementDeclaration, EntityName,
FileDeclaration, ModeViewDeclaration, ObjectDeclaration, PackageInstantiation,
ProtectedTypeDeclarativeItem, SubtypeIndication, TypeDeclaration, TypeDefinition,
};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::Kind;
use crate::{indented, HasTokenSpan, TokenAccess, TokenId, TokenSpan};
use vhdl_lang::ast::token_range::WithTokenSpan;
use vhdl_lang::ast::{
AliasDeclaration, Attribute, AttributeDeclaration, AttributeSpecification, Declaration,
LibraryClause, ObjectClass, PhysicalTypeDeclaration, ProtectedTypeBody,
ProtectedTypeDeclaration, UseClause,
};
impl VHDLFormatter<'_> {
pub(crate) fn format_declarations(
&self,
declarations: &[WithTokenSpan<Declaration>],
buffer: &mut Buffer,
) {
if declarations.is_empty() {
return;
}
buffer.line_break();
for (i, item) in declarations.iter().enumerate() {
self.format_declaration(item, buffer);
if i < declarations.len() - 1 {
self.line_break_preserve_whitespace(item.get_end_token(), buffer);
}
}
}
pub fn format_declaration(
&self,
declaration: &WithTokenSpan<Declaration>,
buffer: &mut Buffer,
) {
use Declaration::*;
match &declaration.item {
Object(object_decl) => {
self.format_object_declaration(object_decl, declaration.span, buffer)
}
File(file_decl) => self.format_file_declaration(file_decl, declaration.span, buffer),
Type(type_decl) => self.format_type_declaration(type_decl, declaration.span, buffer),
Component(component) => self.format_component_declaration(component, buffer),
Attribute(attribute) => self.format_attribute(attribute, declaration.span, buffer),
Alias(alias) => self.format_alias_declaration(alias, declaration.span, buffer),
SubprogramDeclaration(subprogram_declaration) => {
self.format_subprogram_declaration(subprogram_declaration, buffer)
}
SubprogramInstantiation(subprogram_instantiation) => {
self.format_subprogram_instantiation(subprogram_instantiation, buffer)
}
SubprogramBody(subprogram_body) => self.format_subprogram_body(subprogram_body, buffer),
Use(use_clause) => self.format_use_clause(use_clause, buffer),
Package(package_instantiation) => {
self.format_package_instance(package_instantiation, buffer)
}
Configuration(configuration) => {
self.format_configuration_specification(configuration, buffer)
}
View(view_declaration) => self.format_view(view_declaration, declaration.span, buffer),
}
}
pub fn format_component_declaration(
&self,
component: &ComponentDeclaration,
buffer: &mut Buffer,
) {
self.format_token_span(
TokenSpan::new(
component.span.start_token,
component.is_token.unwrap_or(component.span.start_token + 1),
),
buffer,
);
if let Some(generic_clause) = &component.generic_list {
indented!(buffer, {
buffer.line_break();
self.format_interface_list(generic_clause, buffer);
});
}
if let Some(port_clause) = &component.port_list {
indented!(buffer, {
buffer.line_break();
self.format_interface_list(port_clause, buffer);
});
}
buffer.line_break();
self.format_token_span(
TokenSpan::new(component.end_token, component.span.end_token - 1),
buffer,
);
self.format_token_id(component.span.end_token, buffer);
}
pub fn format_object_declaration(
&self,
object_decl: &ObjectDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_token_id(span.start_token, buffer);
if object_decl.class == ObjectClass::SharedVariable {
buffer.push_whitespace();
self.format_token_id(span.start_token + 1, buffer);
}
buffer.push_whitespace();
self.format_ident_list(&object_decl.idents, buffer);
self.format_token_id(object_decl.colon_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(&object_decl.subtype_indication, buffer);
self.format_default_expression(object_decl.expression.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_file_declaration(
&self,
file_decl: &FileDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_ident_list(&file_decl.idents, buffer);
self.format_token_id(file_decl.colon_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(&file_decl.subtype_indication, buffer);
if let Some((token, open_information)) = &file_decl.open_info {
buffer.push_whitespace();
self.format_token_id(*token, buffer);
buffer.push_whitespace();
self.format_expression(open_information.as_ref(), buffer);
}
if let Some((token, file_name)) = &file_decl.file_name {
buffer.push_whitespace();
self.format_token_id(*token, buffer);
buffer.push_whitespace();
self.format_expression(file_name.as_ref(), buffer);
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_type_declaration(
&self,
type_decl: &TypeDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_token_span(
TokenSpan::new(span.start_token, type_decl.ident.tree.token),
buffer,
);
if let Some(is_token) = type_decl.is_token() {
buffer.push_whitespace();
self.format_token_id(is_token, buffer);
}
if let Some(is_token) = type_decl.is_token() {
buffer.push_whitespace();
self.format_type_definition(
&type_decl.def,
TokenSpan::new(
is_token + 1,
type_decl.end_ident_pos.unwrap_or(span.end_token) - 1,
),
buffer,
);
}
if let Some(end_ident) = type_decl.end_ident_pos {
buffer.push_whitespace();
self.format_token_id(end_ident, buffer);
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_type_definition(
&self,
definition: &TypeDefinition,
span: TokenSpan,
buffer: &mut Buffer,
) {
use TypeDefinition::*;
match definition {
Enumeration(literals) => {
self.format_token_id(span.start_token, buffer);
for literal in literals {
self.format_token_id(literal.tree.token, buffer);
if self
.tokens
.get_token(literal.tree.token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(literal.tree.token + 1, buffer);
buffer.push_whitespace();
}
}
self.format_token_id(span.end_token, buffer);
}
Numeric(range) => {
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_range(range, buffer)
}
Physical(physical_type) => {
self.format_physical_type_declaration(physical_type, span, buffer)
}
Array(indices, of_token, subtype) => {
self.format_array_type_declaration(indices, *of_token, subtype, span, buffer)
}
Record(elements) => self.format_record_declaration(elements, span, buffer),
Access(subtype_indication) => {
// access
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(subtype_indication, buffer);
}
Incomplete(_) => {
// nothing to do
}
File(name) => {
// file of
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 1),
buffer,
);
buffer.push_whitespace();
self.format_name(name.as_ref(), buffer);
}
Protected(protected) => self.format_protected_type_declaration(protected, span, buffer),
ProtectedBody(protected_body) => {
self.format_protected_body_type_declaration(protected_body, span, buffer)
}
Subtype(subtype) => self.format_subtype_indication(subtype, buffer),
}
}
pub fn format_physical_type_declaration(
&self,
declaration: &PhysicalTypeDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
// range
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_range(&declaration.range, buffer);
indented!(buffer, {
buffer.line_break();
self.format_token_id(declaration.units_token, buffer);
indented!(buffer, {
buffer.line_break();
// primary_unit;
self.format_ident(&declaration.primary_unit, buffer);
self.format_token_id(declaration.primary_unit.tree.token + 1, buffer);
for (ident, literal) in &declaration.secondary_units {
buffer.line_break();
self.format_ident(ident, buffer);
buffer.push_whitespace();
// =
self.format_token_id(ident.tree.token + 1, buffer);
buffer.push_whitespace();
self.format_token_span(literal.span, buffer);
// ;
self.format_token_id(literal.span.end_token + 1, buffer);
}
});
buffer.line_break();
// end units
self.format_token_span(TokenSpan::new(span.end_token - 1, span.end_token), buffer);
});
}
pub fn format_array_type_declaration(
&self,
indices: &[ArrayIndex],
of_token: TokenId,
subtype: &SubtypeIndication,
span: TokenSpan,
buffer: &mut Buffer,
) {
// array
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
// (
self.format_token_id(span.start_token + 1, buffer);
for (i, index) in indices.iter().enumerate() {
let end_token = match index {
ArrayIndex::IndexSubtypeDefintion(name) => {
self.format_name(name.as_ref(), buffer);
buffer.push_whitespace();
self.format_token_span(
TokenSpan::new(name.span.end_token + 1, name.span.end_token + 2),
buffer,
);
name.span.end_token + 3
}
ArrayIndex::Discrete(discrete_range) => {
self.format_discrete_range(&discrete_range.item, buffer);
discrete_range.span.end_token + 1
}
};
if i < indices.len() - 1 {
self.format_token_id(end_token, buffer);
buffer.push_whitespace();
}
}
// )
self.format_token_id(of_token - 1, buffer);
buffer.push_whitespace();
// of
self.format_token_id(of_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(subtype, buffer);
}
pub fn format_record_declaration(
&self,
elements: &[ElementDeclaration],
span: TokenSpan,
buffer: &mut Buffer,
) {
// record
self.format_token_id(span.start_token, buffer);
let mut last_token = span.start_token;
indented!(buffer, {
for element in elements {
buffer.line_break();
self.format_element_declaration(element, buffer);
last_token = element.span.end_token;
}
});
buffer.line_break();
// end record
self.format_token_span(TokenSpan::new(last_token + 1, span.end_token), buffer)
}
pub fn format_element_declaration(
&self,
declaration: &ElementDeclaration,
buffer: &mut Buffer,
) {
self.format_ident_list(&declaration.idents, buffer);
// :
self.format_token_id(declaration.colon_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(&declaration.subtype, buffer);
// ;
self.format_token_id(declaration.span.end_token, buffer);
}
pub fn format_protected_type_declaration(
&self,
declaration: &ProtectedTypeDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
// protected
self.format_token_id(span.start_token, buffer);
let mut last_token = span.start_token;
indented!(buffer, {
for element in &declaration.items {
buffer.line_break();
match element {
ProtectedTypeDeclarativeItem::Subprogram(subprogram) => {
self.format_subprogram_declaration(subprogram, buffer);
last_token = subprogram.span.end_token;
}
}
}
});
buffer.line_break();
// end protected
self.format_token_span(TokenSpan::new(last_token + 1, span.end_token), buffer)
}
pub fn format_protected_body_type_declaration(
&self,
declaration: &ProtectedTypeBody,
span: TokenSpan,
buffer: &mut Buffer,
) {
// protected body
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 1),
buffer,
);
indented!(buffer, {
self.format_declarations(&declaration.decl, buffer)
});
buffer.line_break();
let last_token = declaration
.decl
.last()
.map(|last| last.span.end_token)
.unwrap_or(span.start_token + 1);
// end protected body
self.format_token_span(TokenSpan::new(last_token + 1, span.end_token), buffer)
}
pub fn format_attribute(&self, attribute: &Attribute, span: TokenSpan, buffer: &mut Buffer) {
use Attribute::*;
match attribute {
Specification(spec) => self.format_attribute_specification(spec, span, buffer),
Declaration(dec) => self.format_attribute_declaration(dec, span, buffer),
}
}
pub fn format_attribute_declaration(
&self,
attribute: &AttributeDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_token_span(
TokenSpan::new(span.start_token, attribute.ident.tree.token),
buffer,
);
// :
self.format_token_id(attribute.ident.tree.token + 1, buffer);
buffer.push_whitespace();
self.format_name(attribute.type_mark.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_attribute_specification(
&self,
attribute: &AttributeSpecification,
span: TokenSpan,
buffer: &mut Buffer,
) {
// attribute <name> of
self.format_token_span(
TokenSpan::new(span.start_token, attribute.ident.item.token + 1),
buffer,
);
buffer.push_whitespace();
match &attribute.entity_name {
EntityName::Name(name) => {
self.format_token_id(name.designator.token, buffer);
if let Some(signature) = &name.signature {
self.format_signature(signature, buffer);
}
}
EntityName::All | EntityName::Others => {
self.format_token_id(attribute.ident.item.token + 2, buffer)
}
}
// : <entity_class> is
self.format_token_span(
TokenSpan::new(attribute.colon_token, attribute.colon_token + 2),
buffer,
);
buffer.push_whitespace();
self.format_expression(attribute.expr.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_alias_declaration(
&self,
alias: &AliasDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
// alias <name>
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 1),
buffer,
);
if let Some(subtype) = &alias.subtype_indication {
// :
self.format_token_id(span.start_token + 2, buffer);
buffer.push_whitespace();
self.format_subtype_indication(subtype, buffer);
}
buffer.push_whitespace();
self.format_token_id(alias.is_token, buffer);
buffer.push_whitespace();
self.format_name(alias.name.as_ref(), buffer);
if let Some(signature) = &alias.signature {
self.format_signature(signature, buffer);
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_use_clause(&self, use_clause: &UseClause, buffer: &mut Buffer) {
// use
self.format_token_id(use_clause.get_start_token(), buffer);
buffer.push_whitespace();
self.format_name_list(buffer, &use_clause.name_list);
self.format_token_id(use_clause.get_end_token(), buffer);
}
pub fn format_library_clause(&self, library_clause: &LibraryClause, buffer: &mut Buffer) {
// use
self.format_token_id(library_clause.get_start_token(), buffer);
buffer.push_whitespace();
self.format_ident_list(&library_clause.name_list, buffer);
self.format_token_id(library_clause.get_end_token(), buffer);
}
pub fn format_context_reference(
&self,
context_reference: &ContextReference,
buffer: &mut Buffer,
) {
// use
self.format_token_id(context_reference.get_start_token(), buffer);
buffer.push_whitespace();
self.format_name_list(buffer, &context_reference.name_list);
self.format_token_id(context_reference.get_end_token(), buffer);
}
pub fn format_package_instance(&self, instance: &PackageInstantiation, buffer: &mut Buffer) {
self.format_context_clause(&instance.context_clause, buffer);
// package <name> is new
self.format_token_span(
TokenSpan::new(instance.get_start_token(), instance.get_start_token() + 3),
buffer,
);
buffer.push_whitespace();
self.format_name(instance.package_name.as_ref(), buffer);
if let Some(generic_map) = &instance.generic_map {
buffer.push_whitespace();
self.format_map_aspect(generic_map, buffer);
}
self.format_token_id(instance.get_end_token(), buffer);
}
pub fn format_view(&self, view: &ModeViewDeclaration, span: TokenSpan, buffer: &mut Buffer) {
// view <name> of
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 2),
buffer,
);
buffer.push_whitespace();
self.format_subtype_indication(&view.typ, buffer);
buffer.push_whitespace();
self.format_token_id(view.is_token, buffer);
indented!(buffer, {
self.join_on_newline(&view.elements, Self::format_mode_view_element, buffer);
});
buffer.line_break();
self.format_token_span(TokenSpan::new(view.end_token, span.end_token - 1), buffer);
self.format_token_id(span.end_token, buffer);
}
}
#[cfg(test)]
mod tests {
use crate::formatting::test_utils::{check_formatted, check_formatted_std};
use crate::VHDLStandard;
use crate::VHDLStandard::VHDL2019;
fn check_declaration(input: &str) {
check_formatted(
input,
input,
|code| code.declarative_part().into_iter().next().unwrap(),
|formatter, ast, buffer| formatter.format_declaration(ast, buffer),
);
}
fn check_declaration_std(input: &str, std: VHDLStandard) {
check_formatted_std(
input,
input,
std,
|code| code.declarative_part().into_iter().next().unwrap(),
|formatter, ast, buffer| formatter.format_declaration(ast, buffer),
);
}
#[test]
fn object_declarations() {
check_declaration("constant my_const: std_logic;");
check_declaration("variable my_var: std_logic;");
check_declaration("signal foo: std_logic;");
check_declaration("shared variable bar: std_logic;");
check_declaration("shared variable bar: std_logic := '0';");
}
#[test]
fn file_declarations() {
check_declaration("file my_file: text;");
check_declaration("file my_file: text is \"my_file.txt\";");
check_declaration("file my_file: text open mode is \"my_file.txt\";");
check_declaration("file FileID1, FileID2: text;");
}
#[test]
fn enum_declaration() {
check_declaration("type my_enum is (A);");
check_declaration("type my_enum is (A, B);");
check_declaration("type my_enum is ('0', '1', 'U', 'X');");
}
#[test]
fn numeric_type_declaration() {
check_declaration("type my_enum is range 0 to 5;");
}
#[test]
fn physical_types() {
check_declaration(
"\
type TIME is range -9223372036854775807 to 9223372036854775807
units
fs; -- femtosecond
end units;",
);
check_declaration(
"\
type TIME is range -9223372036854775807 to 9223372036854775807
units
fs; -- femtosecond
ps = 1000 fs; -- picosecond
ns = 1000 ps; -- nanosecond
us = 1000 ns; -- microsecond
ms = 1000 us; -- millisecond
sec = 1000 ms; -- second
min = 60 sec; -- minute
hr = 60 min; -- hour
end units;",
);
check_declaration(
"\
type TIME is range -9223372036854775807 to 9223372036854775807
units
fs; -- femtosecond
ps = fs; -- picosecond
end units;",
);
}
#[test]
fn array_type_definition() {
check_declaration("type my_array is array (natural range <>) of std_logic_vector;");
check_declaration("type foo is array (2 - 1 downto 0, integer range <>) of boolean;");
check_declaration("type foo is array (2 - 1 downto 0) of boolean;");
}
#[test]
fn record_type_definition() {
check_declaration(
"\
type x is record
end record;",
);
check_declaration(
"\
type foo is record
element: boolean;
end record;",
);
check_declaration(
"\
type foo is record
element: boolean;
other_element: std_logic_vector;
end foo;",
);
check_declaration(
"\
type dummy_rec is record
dummy: bit;
end record;",
);
}
#[test]
fn access_definition() {
check_declaration("type dummy_rec is access bit;");
}
#[test]
fn incomplete_type_definition() {
check_declaration("type incomplete;");
}
#[test]
fn file_definitions() {
check_declaration("type foo is file of character;");
}
#[test]
fn protected_declaration() {
check_declaration(
"type foo is protected
end protected;",
);
check_declaration(
"type foo is protected
end protected foo;",
);
check_declaration(
"type foo is protected
procedure proc;
function fun return ret;
end protected;",
);
}
#[test]
fn protected_body_declaration() {
check_declaration(
"type foo is protected body
end protected body;",
);
check_declaration(
"\
type foo is protected body
variable foo: natural;
procedure proc is
begin
end;
end protected body;",
);
}
#[test]
fn protected_subtype_declaration() {
check_declaration("subtype vec_t is integer_vector(2 - 1 downto 0);");
}
#[test]
fn component_declaration() {
check_declaration(
"\
component foo
end component;",
);
check_declaration(
"\
component foo is
end component;",
);
check_declaration(
"\
component foo is
end component foo;",
);
check_declaration(
"\
component foo is
generic (
foo: natural
);
end component;",
);
check_declaration(
"\
component foo is
port (
foo: natural
);
end component;",
);
}
#[test]
fn check_attribute_declaration() {
check_declaration("attribute foo: name;");
check_declaration("attribute attr_name of foo: signal is 0 + 1;");
check_declaration("attribute attr_name of \"**\": function is 0 + 1;");
check_declaration("attribute attr_name of all: signal is 0 + 1;");
check_declaration("attribute attr_name of foo[return natural]: function is 0 + 1;");
}
#[test]
fn check_alias_declaration() {
check_declaration("alias foo is name;");
check_declaration("alias foo: vector(0 to 1) is name;");
check_declaration("alias foo is name[return natural];");
check_declaration("alias \"and\" is name;");
check_declaration("alias 'c' is 'b';");
}
#[test]
fn check_use_clause() {
check_declaration("use foo;");
check_declaration("use foo, bar;");
check_declaration("use foo, bar, baz;");
}
#[test]
fn format_package_instance() {
check_declaration("package ident is new foo;");
check_declaration(
"package ident is new foo generic map (
foo => bar
);",
);
}
#[test]
fn format_view() {
check_declaration_std(
"\
view foo of bar is
end view;",
VHDL2019,
);
check_declaration_std(
"\
view foo of bar is
end view foo;",
VHDL2019,
);
check_declaration_std(
"\
view foo of bar is
baz: in;
end view;",
VHDL2019,
);
check_declaration_std(
"\
view foo of bar is
baz: in;
bar, baz: view foo;
end view;",
VHDL2019,
);
}
#[test]
fn format_configuration_specification() {
check_declaration(
"\
for all: lib.pkg.comp
use entity work.foo(rtl);",
);
check_declaration(
"\
for all: lib.pkg.comp
use entity work.foo(rtl);
end for;",
);
check_declaration(
"\
for all: lib.pkg.comp
use entity work.foo(rtl);
use vunit bar, baz;
end for;",
);
}
}

View File

@ -0,0 +1,304 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{AnyDesignUnit, AnyPrimaryUnit, AnySecondaryUnit, PackageBody};
use crate::formatting::buffer::Buffer;
use crate::HasTokenSpan;
use vhdl_lang::ast::PackageDeclaration;
use vhdl_lang::formatting::VHDLFormatter;
use vhdl_lang::{indented, TokenSpan};
impl VHDLFormatter<'_> {
pub fn format_any_design_unit(&self, unit: &AnyDesignUnit, buffer: &mut Buffer, is_last: bool) {
use AnyDesignUnit::*;
match unit {
Primary(primary) => self.format_any_primary_unit(primary, buffer),
Secondary(secondary) => self.format_any_secondary_unit(secondary, buffer),
}
if !is_last {
buffer.line_breaks(2);
}
}
pub fn format_any_primary_unit(&self, unit: &AnyPrimaryUnit, buffer: &mut Buffer) {
use AnyPrimaryUnit::*;
match unit {
Entity(entity) => self.format_entity(entity, buffer),
Configuration(configuration) => self.format_configuration(configuration, buffer),
Package(package) => self.format_package(package, buffer),
PackageInstance(package_instance) => {
self.format_package_instance(package_instance, buffer)
}
Context(context) => self.format_context(context, buffer),
}
}
pub fn format_any_secondary_unit(&self, unit: &AnySecondaryUnit, buffer: &mut Buffer) {
use AnySecondaryUnit::*;
match unit {
Architecture(architecture) => self.format_architecture(architecture, buffer),
PackageBody(body) => self.format_package_body(body, buffer),
}
}
pub fn format_package(&self, package: &PackageDeclaration, buffer: &mut Buffer) {
self.format_context_clause(&package.context_clause, buffer);
if let Some(item) = package.context_clause.last() {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
// package <ident> is
self.format_token_span(
TokenSpan::new(package.span.start_token, package.span.start_token + 2),
buffer,
);
indented!(buffer, {
if let Some(generic_clause) = &package.generic_clause {
buffer.line_break();
self.format_interface_list(generic_clause, buffer);
}
self.format_declarations(&package.decl, buffer);
});
buffer.line_break();
self.format_token_span(
TokenSpan::new(package.end_token, package.span.end_token - 1),
buffer,
);
self.format_token_id(package.span.end_token, buffer);
}
pub fn format_package_body(&self, body: &PackageBody, buffer: &mut Buffer) {
self.format_context_clause(&body.context_clause, buffer);
if let Some(item) = body.context_clause.last() {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
// package body <ident> is
self.format_token_span(
TokenSpan::new(body.span.start_token, body.span.start_token + 3),
buffer,
);
indented!(buffer, { self.format_declarations(&body.decl, buffer) });
buffer.line_break();
self.format_token_span(
TokenSpan::new(body.end_token, body.span.end_token - 1),
buffer,
);
self.format_token_id(body.span.end_token, buffer);
}
}
#[cfg(test)]
mod test {
use crate::analysis::tests::Code;
use vhdl_lang::formatting::test_utils::check_formatted;
fn check_package_formatted(input: &str) {
check_formatted(
input,
input,
Code::package_declaration,
|formatter, package, buffer| formatter.format_package(package, buffer),
);
}
#[test]
fn format_simple_package() {
check_package_formatted(
"\
package foo is
end package;",
);
check_package_formatted(
"\
package foo is
end package foo;",
);
}
#[test]
fn format_package_with_declarations() {
check_package_formatted(
"\
package pkg_name is
type foo;
constant bar: natural := 0;
end package;",
);
}
#[test]
fn format_package_with_generics() {
check_package_formatted(
"\
package pkg_name is
generic (
type foo;
type bar
);
end package;",
);
}
#[test]
fn format_package_with_context_clause() {
check_package_formatted(
"\
package pkg_name is
generic (
type foo;
type bar
);
end package;",
);
}
fn check_context_formatted(input: &str) {
check_formatted(
input,
input,
Code::context_declaration,
|formatter, package, buffer| formatter.format_context(package, buffer),
);
}
#[test]
fn check_simple_context() {
check_context_formatted(
"\
context ident is
end;",
);
check_context_formatted(
"\
context ident is
end context;",
);
check_context_formatted(
"\
context ident is
end ident;",
);
check_context_formatted(
"\
context ident is
end context ident;",
);
}
#[test]
fn check_context_items() {
check_context_formatted(
"\
context ident is
library foo;
use foo.bar;
context foo.ctx;
end context;",
);
}
fn check_design_unit_formatted(input: &str) {
check_formatted(
input,
input,
Code::design_file,
|formatter, file, buffer| {
formatter.format_any_design_unit(&file.design_units[0].1, buffer, true)
},
);
}
#[test]
fn design_unit_context_clause_preserve_whitespaces() {
check_design_unit_formatted(
"\
library lib;
use lib.foo.all;
package pkg_name is
end package;",
);
check_design_unit_formatted(
"\
library lib;
use lib.foo.all;
package pkg_name is
end package;",
);
check_design_unit_formatted(
"\
library lib;
use lib.foo.all;
package pkg_name is
end package;",
);
}
#[test]
fn check_package_body() {
check_design_unit_formatted(
"\
package body foo is
end package body;",
)
}
#[test]
fn check_whitespace_preservation_context() {
check_design_unit_formatted(
"\
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;
library third_party;
use third_party.baz;
use work.foo.bar;
package body foo is
end package body;",
)
}
#[test]
fn check_whitespace_preservation_tokens_with_comments() {
check_design_unit_formatted(
"\
library ieee;
-- This is a comment
-- This is another comment
-- Third comment
library third_party;
use third_party.baz;
package body foo is
end package body;",
)
}
#[test]
fn check_whitespace_preservation_within_comments() {
check_design_unit_formatted(
"\
-- This is a comment
-- This ine appears later
-- Third comment
library third_party;
use third_party.baz;
package body foo is
end package body;",
)
}
}

View File

@ -0,0 +1,187 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::EntityDeclaration;
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::{HasTokenSpan, TokenSpan};
use vhdl_lang::indented;
impl VHDLFormatter<'_> {
pub fn format_entity(&self, entity: &EntityDeclaration, buffer: &mut Buffer) {
self.format_context_clause(&entity.context_clause, buffer);
if let Some(item) = entity.context_clause.last() {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
let span = entity.span();
// entity <ident> is
self.format_token_span(TokenSpan::new(span.start_token, entity.is_token()), buffer);
if let Some(generic_clause) = &entity.generic_clause {
indented!(buffer, {
buffer.line_break();
self.format_interface_list(generic_clause, buffer);
});
}
if let Some(port_clause) = &entity.port_clause {
indented!(buffer, {
buffer.line_break();
self.format_interface_list(port_clause, buffer);
});
}
indented!(buffer, { self.format_declarations(&entity.decl, buffer) });
if let Some(token) = entity.begin_token {
buffer.line_break();
self.format_token_id(token, buffer);
}
self.format_concurrent_statements(&entity.statements, buffer);
buffer.line_break();
// end [entity] [name];
self.format_token_span(TokenSpan::new(entity.end_token, span.end_token - 1), buffer);
self.format_token_id(span.end_token, buffer);
}
}
#[cfg(test)]
mod test {
use crate::analysis::tests::Code;
use vhdl_lang::formatting::test_utils::check_formatted;
fn check_entity_formatted(input: &str) {
check_formatted(
input,
input,
Code::entity_decl,
|formatter, entity, buffer| formatter.format_entity(entity, buffer),
);
}
#[test]
fn test_format_simple_entity() {
check_entity_formatted(
"\
entity my_ent is
end entity my_ent;",
);
check_entity_formatted(
"\
entity my_ent is
end my_ent;",
);
check_entity_formatted(
"\
entity my_ent is
end;",
);
check_entity_formatted(
"\
entity my_ent is
end entity;",
);
check_entity_formatted(
"\
entity my_ent is
begin
end entity;",
);
}
#[test]
fn test_entity_with_comments() {
check_entity_formatted(
"\
-- Some comment about the entity
entity my_ent is
end entity;",
);
check_entity_formatted(
"\
entity my_ent is -- trailing comment
end entity;",
);
check_entity_formatted(
"\
entity /* Why would you put a comment here? */ my_ent is
end entity;",
);
check_entity_formatted(
"\
entity /* Why would you put a comment here? */ my_ent is -- this is an entity
end entity;",
);
}
#[test]
fn test_entity_with_simple_generic() {
check_entity_formatted(
"\
entity foo is
-- Generics come here
generic (
foo: in std_logic --<This is it
);
end foo;",
);
}
#[test]
fn test_entity_generic_default_value() {
check_entity_formatted(
"\
entity foo is
generic (
foo: in std_logic := '1'
);
end foo;",
);
}
#[test]
fn test_entity_with_ports() {
check_entity_formatted(
"\
entity foo is
port (
foo: in std_logic := '1'
);
end foo;",
);
}
#[test]
fn test_entity_with_generics_and_ports() {
check_entity_formatted(
"\
entity foo is
generic (
a: in std_logic := '1'
);
port (
B: in std_logic := '1'
);
end foo;",
);
}
#[test]
fn test_entity_with_declarations() {
check_entity_formatted(
"\
entity foo is
port (
foo: in std_logic := '1'
);
constant x: foo := bar;
signal y: bar := foobar;
end foo;",
);
}
}

View File

@ -0,0 +1,286 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{
ElementAssociation, Expression, Operator, ResolutionIndication, SubtypeConstraint,
SubtypeIndication,
};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::HasTokenSpan;
use vhdl_lang::ast::{Allocator, QualifiedExpression};
impl VHDLFormatter<'_> {
pub fn format_expression(&self, expression: WithTokenSpan<&Expression>, buffer: &mut Buffer) {
let span = expression.span;
use Expression::*;
match &expression.item {
Binary(op, lhs, rhs) => {
self.format_expression(lhs.as_ref().as_ref(), buffer);
buffer.push_whitespace();
self.format_token_id(op.token, buffer);
buffer.push_whitespace();
self.format_expression(rhs.as_ref().as_ref(), buffer);
}
Unary(op, rhs) => {
self.format_token_id(op.token, buffer);
match op.item.item {
Operator::Minus | Operator::Plus | Operator::QueQue => {
// Leave as unary operator without whitespace
}
_ => buffer.push_whitespace(),
}
self.format_expression(rhs.as_ref().as_ref(), buffer);
}
Aggregate(aggregate) => {
self.format_token_id(span.start_token, buffer);
self.format_element_associations(aggregate, buffer);
self.format_token_id(span.end_token, buffer);
}
Qualified(qualified_expr) => self.format_qualified_expression(qualified_expr, buffer),
Name(name) => self.format_name(WithTokenSpan::new(name, span), buffer),
Literal(_) => self.format_token_span(span, buffer),
New(allocator) => self.format_allocator(allocator, buffer),
Parenthesized(expression) => {
self.format_token_id(span.start_token, buffer);
self.format_expression(expression.as_ref().as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
}
}
pub fn format_element_associations(
&self,
associations: &[WithTokenSpan<ElementAssociation>],
buffer: &mut Buffer,
) {
for (i, association) in associations.iter().enumerate() {
match &association.item {
ElementAssociation::Positional(expression) => {
self.format_expression(expression.as_ref(), buffer)
}
ElementAssociation::Named(choices, expression) => {
for (j, choice) in choices.iter().enumerate() {
self.format_choice(choice, buffer);
if j < choices.len() - 1 {
buffer.push_whitespace();
self.format_token_id(choice.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
buffer.push_whitespace();
self.format_token_id(expression.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(expression.as_ref(), buffer);
}
}
if i < associations.len() - 1 {
self.format_token_id(association.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
}
pub fn format_subtype_indication(&self, indication: &SubtypeIndication, buffer: &mut Buffer) {
if let Some(resolution) = &indication.resolution {
self.format_resolution_indication(resolution, buffer);
buffer.push_whitespace();
}
self.format_name(indication.type_mark.as_ref(), buffer);
if let Some(constraint) = &indication.constraint {
if matches!(constraint.item, SubtypeConstraint::Range(_)) {
buffer.push_whitespace();
}
self.format_subtype_constraint(constraint, buffer)
}
}
pub fn format_resolution_indication(
&self,
indication: &ResolutionIndication,
buffer: &mut Buffer,
) {
match &indication {
ResolutionIndication::FunctionName(name) => self.format_name(name.as_ref(), buffer),
ResolutionIndication::ArrayElement(element) => {
self.format_token_id(element.span.start_token - 1, buffer);
self.format_name(element.as_ref(), buffer);
self.format_token_id(element.span.end_token + 1, buffer);
}
ResolutionIndication::Record(record) => {
let span = record.span;
self.format_token_id(span.start_token, buffer);
for (i, element_resolution) in record.item.iter().enumerate() {
self.format_token_id(element_resolution.ident.token, buffer);
buffer.push_whitespace();
self.format_resolution_indication(&element_resolution.resolution, buffer);
if i < record.item.len() - 1 {
// ,
self.format_token_id(
element_resolution.resolution.get_end_token() + 1,
buffer,
);
buffer.push_whitespace();
}
}
self.format_token_id(span.end_token, buffer);
}
}
}
// Helper to format ` := <expression>`
pub(crate) fn format_default_expression(
&self,
expression: Option<&WithTokenSpan<Expression>>,
buffer: &mut Buffer,
) {
if let Some(expr) = expression {
buffer.push_whitespace();
self.format_token_id(expr.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(expr.as_ref(), buffer);
}
}
pub fn format_qualified_expression(
&self,
expression: &QualifiedExpression,
buffer: &mut Buffer,
) {
self.format_name(expression.type_mark.as_ref(), buffer);
// '
self.format_token_id(expression.type_mark.span.end_token + 1, buffer);
self.format_expression(expression.expr.as_ref(), buffer);
}
pub fn format_allocator(&self, allocator: &WithTokenSpan<Allocator>, buffer: &mut Buffer) {
// new
self.format_token_id(allocator.span.start_token - 1, buffer);
buffer.push_whitespace();
match &allocator.item {
Allocator::Qualified(expr) => self.format_qualified_expression(expr, buffer),
Allocator::Subtype(subtype) => self.format_subtype_indication(subtype, buffer),
}
}
}
#[cfg(test)]
mod test {
use crate::analysis::tests::Code;
use crate::ast::token_range::WithTokenSpan;
use crate::formatting::VHDLFormatter;
use vhdl_lang::formatting::buffer::Buffer;
use vhdl_lang::formatting::test_utils::check_formatted;
fn check_expression(input: &str) {
let code = Code::new(input);
let expression = code.expr();
let tokens = code.tokenize();
let formatter = VHDLFormatter::new(&tokens);
let mut buffer = Buffer::new();
formatter.format_expression(
WithTokenSpan::new(&expression.item, code.token_span()),
&mut buffer,
);
assert_eq!(buffer.as_str(), input);
}
#[test]
fn test_simple_expression() {
check_expression("name")
}
#[test]
fn test_parenthesized_expression() {
check_expression("(name)");
check_expression("(A) or (B)");
}
#[test]
fn formal_literal() {
check_expression("12387.44e7");
check_expression("7");
}
#[test]
fn binary_expressions() {
check_expression("1 + B");
check_expression("2 sll 2");
}
#[test]
fn unary_expressions() {
check_expression("+B");
check_expression("-2");
check_expression("not A")
}
#[test]
fn complex_expression() {
check_expression("A + B - C");
check_expression("(A * B) + C");
check_expression("((A * B) + C)");
}
#[test]
fn aggregate() {
check_expression("(1, 2)");
check_expression("(1 => 2, 3)");
check_expression("(others => 1, others => 2)");
check_expression("(1 downto 0 => 2)");
check_expression("(0 to 1 => 2)");
check_expression("(1 | 2 => 3)");
}
#[test]
fn qualified_expressions() {
check_expression("integer_vector'(0, 1)");
check_expression("foo'(1 + 2)");
check_expression("foo'(others => '1')");
}
#[test]
fn allocator_expressions() {
check_expression("new integer_vector'(0, 1)");
check_expression("new integer_vector");
check_expression("new integer_vector(0 to 1)");
check_expression("new integer_vector(foo'range)");
}
fn check_subtype_indication(input: &str) {
check_formatted(
input,
input,
Code::subtype_indication,
|formatter, subtype_indication, buffer| {
formatter.format_subtype_indication(subtype_indication, buffer)
},
);
}
#[test]
fn resolution_indication() {
check_subtype_indication("resolve std_logic");
check_subtype_indication("(resolve) integer_vector");
check_subtype_indication("(elem resolve) rec_t");
check_subtype_indication(
"(elem1 (resolve1), elem2 resolve2, elem3 (sub_elem sub_resolve)) rec_t",
);
}
#[test]
fn expression_with_comments() {
check_expression(
"\
-- Some comment
A & -- And
B & -- as well as
C",
)
}
}

View File

@ -0,0 +1,389 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{
ActualPart, AssociationElement, ElementMode, InterfaceDeclaration, InterfaceList,
InterfaceObjectDeclaration, InterfacePackageDeclaration, InterfacePackageGenericMapAspect,
InterfaceSubprogramDeclaration, MapAspect, ModeIndication, ModeViewElement,
ModeViewIndicationKind, SeparatedList, SimpleModeIndication, SubprogramDefault,
};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::Kind;
use crate::{indented, HasTokenSpan, TokenAccess};
use vhdl_lang::ast::token_range::WithTokenSpan;
use vhdl_lang::ast::{InterfaceFileDeclaration, ModeViewIndication};
use vhdl_lang::TokenSpan;
impl VHDLFormatter<'_> {
pub(crate) fn format_interface_list(&self, clause: &InterfaceList, buffer: &mut Buffer) {
let span = clause.span;
let end_token = if self.tokens.index(span.start_token).kind == Kind::LeftPar {
// We start with a `(` immediately
// applicable for parameters (though VHDL 2008 allows an optional `parameter` keyword)
span.start_token
} else {
// We start with a `generic`, `port` or `parameter` keyword
span.start_token + 1
};
// port (
// generic (
// parameter (
// (
self.format_token_span(TokenSpan::new(span.start_token, end_token), buffer);
indented!(buffer, {
for (i, item) in clause.items.iter().enumerate() {
buffer.line_break();
self.format_interface_declaration(item, buffer);
if i < clause.items.len() - 1 {
self.format_token_id(item.get_end_token() + 1, buffer);
}
}
});
if !clause.items.is_empty() {
buffer.line_break();
}
if self.tokens.index(span.end_token).kind == Kind::SemiColon {
// );
self.format_token_id(span.end_token - 1, buffer);
self.format_token_id(span.end_token, buffer);
} else {
self.format_token_id(span.end_token, buffer);
}
}
pub fn format_map_aspect_span(
&self,
list: &SeparatedList<AssociationElement>,
span: TokenSpan,
buffer: &mut Buffer,
) {
// port map (
// generic map (
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 2),
buffer,
);
indented!(buffer, {
for (i, item) in list.items.iter().enumerate() {
buffer.line_break();
self.format_association_element(item, buffer);
if let Some(token) = list.tokens.get(i) {
self.format_token_id(*token, buffer);
}
}
});
if !list.items.is_empty() {
buffer.line_break();
}
// )
self.format_token_id(span.end_token, buffer);
}
pub fn format_map_aspect(&self, aspect: &MapAspect, buffer: &mut Buffer) {
self.format_map_aspect_span(&aspect.list, aspect.span, buffer);
}
pub fn format_association_element(&self, element: &AssociationElement, buffer: &mut Buffer) {
if let Some(formal) = &element.formal {
self.format_name(formal.as_ref(), buffer);
buffer.push_whitespace();
self.format_token_id(formal.span.end_token + 1, buffer);
buffer.push_whitespace();
}
self.format_actual_part(&element.actual, buffer)
}
pub fn format_actual_part(&self, actual_part: &WithTokenSpan<ActualPart>, buffer: &mut Buffer) {
match &actual_part.item {
ActualPart::Expression(expression) => {
self.format_expression(WithTokenSpan::new(expression, actual_part.span), buffer)
}
ActualPart::Open => self.format_token_span(actual_part.span, buffer),
}
}
pub fn format_interface_declaration(
&self,
declaration: &InterfaceDeclaration,
buffer: &mut Buffer,
) {
use InterfaceDeclaration::*;
match declaration {
Object(object) => self.format_interface_object(object, buffer),
File(file) => self.format_interface_file_declaration(file, buffer),
Type(type_decl) => {
self.format_token_id(type_decl.tree.token - 1, buffer);
buffer.push_whitespace();
self.format_ident(type_decl, buffer);
}
Subprogram(subprogram) => {
self.format_interface_subprogram_declaration(subprogram, buffer)
}
Package(package) => self.format_interface_package_declaration(package, buffer),
}
}
pub fn format_interface_file_declaration(
&self,
declaration: &InterfaceFileDeclaration,
buffer: &mut Buffer,
) {
self.format_token_id(declaration.span.start_token, buffer);
buffer.push_whitespace();
self.format_ident_list(&declaration.idents, buffer);
self.format_token_id(declaration.colon_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(&declaration.subtype_indication, buffer);
}
pub fn format_interface_subprogram_declaration(
&self,
subprogram: &InterfaceSubprogramDeclaration,
buffer: &mut Buffer,
) {
self.format_subprogram_specification(&subprogram.specification, buffer);
if let Some(default) = &subprogram.default {
buffer.push_whitespace();
// is
self.format_token_id(subprogram.specification.span().end_token + 1, buffer);
buffer.push_whitespace();
match default {
SubprogramDefault::Name(name) => self.format_name(name.as_ref(), buffer),
SubprogramDefault::Box => {
self.format_token_id(subprogram.specification.span().end_token + 2, buffer)
}
}
}
}
pub fn format_interface_package_declaration(
&self,
package: &InterfacePackageDeclaration,
buffer: &mut Buffer,
) {
// package <ident> is new
self.format_token_span(
TokenSpan::new(package.span.start_token, package.span.start_token + 3),
buffer,
);
buffer.push_whitespace();
self.format_name(package.package_name.as_ref(), buffer);
buffer.push_whitespace();
let span = package.generic_map.span();
match &package.generic_map.item {
InterfacePackageGenericMapAspect::Map(map) => {
self.format_map_aspect_span(map, span, buffer)
}
InterfacePackageGenericMapAspect::Box | InterfacePackageGenericMapAspect::Default => {
// generic
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
// map
self.format_token_id(span.start_token + 1, buffer);
buffer.push_whitespace();
//(
self.format_token_id(span.start_token + 2, buffer);
// <> | default
self.format_token_id(span.start_token + 3, buffer);
// )
self.format_token_id(span.start_token + 4, buffer);
}
}
}
pub fn format_interface_object(
&self,
object: &InterfaceObjectDeclaration,
buffer: &mut Buffer,
) {
// [signal] my_signal :
self.format_token_span(
TokenSpan::new(object.span.start_token, object.colon_token - 1),
buffer,
);
self.format_token_id(object.colon_token, buffer);
buffer.push_whitespace();
self.format_mode(&object.mode, buffer);
}
pub fn format_mode(&self, mode: &ModeIndication, buffer: &mut Buffer) {
use ModeIndication::*;
match mode {
Simple(simple) => self.format_simple_mode(simple, buffer),
View(mode) => self.format_mode_view_indication(mode, buffer),
}
}
pub fn format_mode_view_indication(&self, mode: &ModeViewIndication, buffer: &mut Buffer) {
// view
self.format_token_id(mode.span.start_token, buffer);
buffer.push_whitespace();
match &mode.kind {
ModeViewIndicationKind::Array => {
self.format_token_id(mode.name.span.start_token - 1, buffer);
self.format_name(mode.name.as_ref(), buffer);
self.format_token_id(mode.name.span.end_token + 1, buffer);
}
ModeViewIndicationKind::Record => {
self.format_name(mode.name.as_ref(), buffer);
}
}
if let Some((token, subtype)) = &mode.subtype_indication {
buffer.push_whitespace();
self.format_token_id(*token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(subtype, buffer);
}
}
pub fn format_element_mode(&self, mode: &ElementMode, buffer: &mut Buffer) {
match mode {
ElementMode::Simple(simple) => self.format_token_id(simple.token, buffer),
ElementMode::Record(name) => {
// view
self.format_token_id(name.get_start_token() - 1, buffer);
buffer.push_whitespace();
self.format_name(name.as_ref(), buffer)
}
ElementMode::Array(name) => {
//view (
self.format_token_span(
TokenSpan::new(name.get_start_token() - 2, name.get_start_token() - 1),
buffer,
);
self.format_name(name.as_ref(), buffer);
// )
self.format_token_id(name.get_end_token() + 1, buffer);
}
}
}
pub fn format_mode_view_element(&self, mode: &ModeViewElement, buffer: &mut Buffer) {
self.format_ident_list(&mode.names, buffer);
self.format_token_id(mode.colon_token, buffer);
buffer.push_whitespace();
self.format_element_mode(&mode.mode, buffer);
// ;
self.format_token_id(mode.span.end_token, buffer);
}
pub fn format_simple_mode(&self, mode: &SimpleModeIndication, buffer: &mut Buffer) {
if let Some(mode) = &mode.mode {
self.format_token_id(mode.token, buffer);
buffer.push_whitespace();
}
self.format_subtype_indication(&mode.subtype_indication, buffer);
self.format_default_expression(mode.expression.as_ref(), buffer);
}
}
#[cfg(test)]
mod tests {
use crate::analysis::tests::Code;
use crate::formatting::test_utils::{check_formatted, check_formatted_std};
use crate::VHDLStandard::VHDL2019;
fn check_generic(input: &str) {
check_formatted(
input,
input,
Code::generic,
|formatter, interface, buffer| {
formatter.format_interface_declaration(interface, buffer)
},
);
}
#[test]
fn format_simple_object() {
check_generic("my_generic: natural");
}
#[test]
fn format_simple_object_with_default() {
check_generic("my_generic: natural := 7");
}
#[test]
fn format_simple_object_with_explicit_mode() {
check_generic("my_generic: in natural");
}
#[test]
fn format_object_with_class() {
check_generic("constant my_generic: in natural");
check_generic("constant my_generic: natural");
}
fn check_element_mode(input: &str) {
check_formatted_std(
input,
input,
VHDL2019,
Code::element_mode,
|formatter, mode, buffer| formatter.format_element_mode(mode, buffer),
);
}
#[test]
fn format_element_mode() {
check_element_mode("in");
check_element_mode("out");
check_element_mode("inout");
check_element_mode("buffer");
check_element_mode("linkage");
check_element_mode("view foo");
check_element_mode("view (foo)");
}
fn check_port(input: &str) {
check_formatted_std(
input,
input,
VHDL2019,
Code::port,
|formatter, interface, buffer| {
formatter.format_interface_declaration(interface, buffer)
},
);
}
#[test]
fn format_mode_view_indication() {
check_port("signal foo: view bar");
check_port("signal foo: view (bar)");
check_port("signal foo: view bar of baz");
check_port("signal foo: view (bar) of baz");
}
#[test]
fn format_interface_file_declaration() {
check_port("file valid: text");
}
#[test]
fn format_interface_subprogram_declaration() {
check_generic("function foo return bar");
check_generic("procedure foo");
check_generic("impure function foo return bar");
check_generic("function foo return bar is lib.name");
check_generic("function foo return bar is <>");
}
#[test]
fn format_interface_package_declaration() {
check_generic(
"\
package foo is new lib.pkg generic map (
foo => bar
)",
);
check_generic("package foo is new lib.pkg generic map (<>)");
check_generic("package foo is new lib.pkg generic map (default)");
}
}

View File

@ -0,0 +1,122 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::DesignFile;
use crate::formatting::buffer::Buffer;
use crate::syntax::Kind;
use crate::{Token, TokenAccess};
use vhdl_lang::ast::HasIdent;
mod architecture;
mod buffer;
mod concurrent_statement;
mod configuration;
mod constraint;
mod context;
mod declaration;
mod design;
mod entity;
mod expression;
mod interface;
mod name;
mod sequential_statement;
mod statement;
mod subprogram;
mod token;
/// The formatter is the main entry point used for formatting a single
/// Design Unit from AST representation to string representation. In that sense,
/// the Formatter is the inverse to the Parser.
///
/// Most methods herein are called `format_<node>` where `node` is the AST node to format.
/// Rather than returning a string, the methods accept a mutable [Buffer] object that they
/// use to format.
///
/// The formatter is capable of retaining comment information as well as preserving newlines.
pub struct VHDLFormatter<'b> {
tokens: &'b Vec<Token>,
}
impl<'b> VHDLFormatter<'b> {
pub fn new(tokens: &'b Vec<Token>) -> VHDLFormatter<'b> {
VHDLFormatter { tokens }
}
/// Format a whole design file.
pub fn format_design_file(file: &DesignFile) -> String {
let mut result = Buffer::new();
for (i, (tokens, design_unit)) in file.design_units.iter().enumerate() {
let formatter = VHDLFormatter::new(tokens);
formatter.format_any_design_unit(
design_unit,
&mut result,
i == file.design_units.len() - 1,
);
}
result.into()
}
}
impl VHDLFormatter<'_> {
pub fn format_ident_list<T: HasIdent>(&self, idents: &[T], buffer: &mut Buffer) {
for ident in idents {
let token = ident.ident().token;
self.format_token_id(token, buffer);
if self
.tokens
.get_token(token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(token + 1, buffer);
buffer.push_whitespace();
}
}
}
}
/// indents the provided block and de-indents at the end.
#[macro_export]
macro_rules! indented {
($buffer:ident, $block:block) => {
$buffer.increase_indent();
$block
$buffer.decrease_indent();
};
}
#[cfg(test)]
pub mod test_utils {
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::test::Code;
use vhdl_lang::VHDLStandard;
pub(crate) fn check_formatted<T>(
input: &str,
expected: &str,
to_ast: impl FnOnce(&Code) -> T,
format: impl FnOnce(&VHDLFormatter<'_>, &T, &mut Buffer),
) {
check_formatted_std(input, expected, VHDLStandard::default(), to_ast, format)
}
pub(crate) fn check_formatted_std<T>(
input: &str,
expected: &str,
std: VHDLStandard,
to_ast: impl FnOnce(&Code) -> T,
format: impl FnOnce(&VHDLFormatter<'_>, &T, &mut Buffer),
) {
let code = Code::with_standard(input, std);
let ast_element = to_ast(&code);
let tokens = code.tokenize();
let formatter = VHDLFormatter::new(&tokens);
let mut buffer = Buffer::new();
format(&formatter, &ast_element, &mut buffer);
assert_eq!(buffer.as_str(), expected);
}
}

View File

@ -0,0 +1,192 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{CallOrIndexed, ExternalName, ExternalPath};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::Kind;
use crate::{TokenAccess, TokenSpan};
use vhdl_lang::ast::{AttributeName, Name};
impl VHDLFormatter<'_> {
pub fn format_name(&self, name: WithTokenSpan<&Name>, buffer: &mut Buffer) {
use Name::*;
let span = name.span;
match &name.item {
Designator(_) => self.join_token_span(span, buffer),
Selected(name, designator) => {
self.format_name(name.as_ref().as_ref(), buffer);
self.join_token_span(
TokenSpan::new(designator.token - 1, designator.token),
buffer,
);
}
SelectedAll(name) => {
self.format_name(name.as_ref().as_ref(), buffer);
self.join_token_span(TokenSpan::new(span.end_token - 1, span.end_token), buffer);
}
Slice(name, range) => {
self.format_name(name.as_ref().as_ref(), buffer);
self.format_token_id(name.span.end_token + 1, buffer);
self.format_discrete_range(range, buffer);
self.format_token_id(span.end_token, buffer);
}
Attribute(attr_name) => self.format_attribute_name(attr_name, buffer),
CallOrIndexed(call_or_indexed) => {
self.format_call_or_indexed(call_or_indexed, span, buffer)
}
External(external) => {
self.format_external_name(WithTokenSpan::new(external, span), buffer)
}
}
}
pub fn format_call_or_indexed(
&self,
call: &CallOrIndexed,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_name(call.name.as_ref(), buffer);
let open_paren = call.name.span.end_token + 1;
if self.tokens.index(open_paren).kind == Kind::LeftPar {
self.format_token_id(open_paren, buffer);
}
for (i, parameter) in call.parameters.items.iter().enumerate() {
self.format_association_element(parameter, buffer);
if let Some(token) = call.parameters.tokens.get(i) {
self.format_token_id(*token, buffer);
buffer.push_whitespace();
}
}
let close_paren = span.end_token;
if self.tokens.index(close_paren).kind == Kind::RightPar {
self.format_token_id(close_paren, buffer);
}
}
pub fn format_attribute_name(&self, name: &AttributeName, buffer: &mut Buffer) {
self.format_name(name.name.as_ref(), buffer);
if let Some(signature) = &name.signature {
self.format_signature(signature, buffer);
}
// '
self.format_token_id(name.attr.token - 1, buffer);
self.format_token_id(name.attr.token, buffer);
if let Some(expr) = &name.expr {
self.format_token_id(expr.span.start_token - 1, buffer);
self.format_expression(expr.as_ref().as_ref(), buffer);
self.format_token_id(expr.span.end_token + 1, buffer);
}
}
pub fn format_external_name(&self, name: WithTokenSpan<&ExternalName>, buffer: &mut Buffer) {
// <<
self.format_token_id(name.span.start_token, buffer);
buffer.push_whitespace();
// entity class
self.format_token_id(name.span.start_token + 1, buffer);
buffer.push_whitespace();
let path = &name.item.path;
match &path.item {
ExternalPath::Package(name) => {
// @
self.format_token_id(name.span.start_token - 1, buffer);
self.format_name(name.as_ref(), buffer)
}
ExternalPath::Absolute(name) => {
// .
self.format_token_id(name.span.start_token - 1, buffer);
self.format_name(name.as_ref(), buffer);
}
ExternalPath::Relative(name, up_levels) => {
for i in (1..=*up_levels).rev() {
// ^
self.format_token_id(name.span.start_token - (2 * i), buffer);
// .
self.format_token_id(name.span.start_token - (2 * i - 1), buffer);
}
self.format_name(name.as_ref(), buffer)
}
}
buffer.push_whitespace();
self.format_token_id(name.item.colon_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(&name.item.subtype, buffer);
buffer.push_whitespace();
// >>
self.format_token_id(name.span.end_token, buffer);
}
pub fn format_name_list(&self, buffer: &mut Buffer, names: &[WithTokenSpan<Name>]) {
for name in names {
self.format_name(name.as_ref(), buffer);
if self
.tokens
.get_token(name.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(name.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
}
}
#[cfg(test)]
pub mod tests {
use crate::syntax::test::Code;
use vhdl_lang::formatting::test_utils::check_formatted;
pub fn check_name(input: &str) {
check_formatted(input, input, Code::name, |formatter, ast, buffer| {
formatter.format_name(ast.as_ref(), buffer)
})
}
#[test]
fn simple_names() {
check_name("\"+\"");
check_name("\"AND\"");
check_name("\"and\"");
}
#[test]
fn selected_names() {
check_name("foo.bar.baz");
check_name("foo.all");
}
#[test]
fn slice_names() {
check_name("prefix(0 to 3)");
check_name("prefix(3 downto 0)");
}
#[test]
fn attribute_name() {
check_name("prefix'subtype");
check_name("prefix'element");
check_name("prefix'foo(expr + 1)");
check_name("prefix[return natural]'foo(expr + 1)");
}
#[test]
fn complex_names() {
check_name("prefix(foo(0)'range)");
}
#[test]
fn external_names() {
check_name("<< signal dut.gen(0) : std_logic >>");
check_name("<< signal .dut.gen(0) : std_logic >>");
check_name("<< signal @dut.gen(0) : std_logic >>");
check_name("<< signal ^.dut.gen(0) : std_logic >>");
check_name("<< signal ^.^.^.dut.gen(0) : std_logic >>");
}
}

View File

@ -0,0 +1,671 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{
AssignmentRightHand, CaseStatement, Choice, DelayMechanism, Expression, Ident, IterationScheme,
LabeledSequentialStatement, LoopStatement, ReportStatement, SequentialStatement,
SignalAssignment, WaitStatement, WithRef,
};
use crate::formatting::buffer::Buffer;
use crate::{HasTokenSpan, TokenSpan};
use vhdl_lang::ast::{
ExitStatement, IfStatement, NextStatement, SignalForceAssignment, SignalReleaseAssignment,
VariableAssignment,
};
use vhdl_lang::formatting::VHDLFormatter;
use vhdl_lang::indented;
impl VHDLFormatter<'_> {
pub fn format_sequential_statements(
&self,
statements: &[LabeledSequentialStatement],
buffer: &mut Buffer,
) {
if statements.is_empty() {
return;
}
indented!(buffer, {
buffer.line_break();
for (i, item) in statements.iter().enumerate() {
self.format_labeled_sequential_statement(item, buffer);
if i < statements.len() - 1 {
self.line_break_preserve_whitespace(item.statement.get_end_token(), buffer);
}
}
});
}
pub fn format_labeled_sequential_statement(
&self,
statement: &LabeledSequentialStatement,
buffer: &mut Buffer,
) {
self.format_optional_label(statement.label.tree.as_ref(), buffer);
self.format_sequential_statement(&statement.statement, buffer);
}
pub fn format_sequential_statement(
&self,
statement: &WithTokenSpan<SequentialStatement>,
buffer: &mut Buffer,
) {
use SequentialStatement::*;
let span = statement.span;
match &statement.item {
Wait(wait_statement) => self.format_wait_statement(wait_statement, span, buffer),
Assert(assert) => {
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_assert_statement(assert, buffer);
self.format_token_id(span.end_token, buffer);
}
Report(report) => self.format_report_statement(report, span, buffer),
VariableAssignment(variable_assignment) => {
self.format_variable_assignment(variable_assignment, span, buffer)
}
SignalAssignment(signal_assignment) => {
self.format_signal_assignment(signal_assignment, span, buffer)
}
SignalForceAssignment(signal_assignment) => {
self.format_signal_force_assignment(signal_assignment, span, buffer)
}
SignalReleaseAssignment(signal_assignment) => {
self.format_signal_release_assignment(signal_assignment, span, buffer)
}
ProcedureCall(call_or_indexed) => {
self.format_call_or_indexed(&call_or_indexed.item, call_or_indexed.span, buffer);
self.format_token_id(span.end_token, buffer);
}
If(if_statement) => {
self.format_if_statement(if_statement, span, buffer);
}
Case(case_statement) => {
self.format_case_statement(case_statement, span, buffer);
}
Loop(loop_statement) => {
self.format_loop_statement(loop_statement, span, buffer);
}
Next(next_statement) => self.format_next_statement(next_statement, span, buffer),
Exit(exit_statement) => self.format_exit_statement(exit_statement, span, buffer),
Return(stmt) => {
self.format_token_id(span.start_token, buffer);
if let Some(expr) = &stmt.expression {
buffer.push_whitespace();
self.format_expression(expr.as_ref(), buffer);
}
self.format_token_id(span.end_token, buffer);
}
Null => self.join_token_span(span, buffer),
}
}
pub fn format_wait_statement(
&self,
statement: &WaitStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// wait
self.format_token_id(span.start_token, buffer);
if let Some(name_list) = &statement.sensitivity_clause {
buffer.push_whitespace();
// on
self.format_token_id(span.start_token + 1, buffer);
buffer.push_whitespace();
self.format_name_list(buffer, name_list);
}
if let Some(condition_clause) = &statement.condition_clause {
buffer.push_whitespace();
self.format_token_id(condition_clause.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(condition_clause.as_ref(), buffer);
}
if let Some(timeout_clause) = &statement.timeout_clause {
buffer.push_whitespace();
self.format_token_id(timeout_clause.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(timeout_clause.as_ref(), buffer);
}
// ;
self.format_token_id(span.end_token, buffer);
}
pub fn format_report_statement(
&self,
report: &ReportStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// report
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_expression(report.report.as_ref(), buffer);
self.format_opt_severity(report.severity.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_variable_assignment(
&self,
assignment: &VariableAssignment,
span: TokenSpan,
buffer: &mut Buffer,
) {
if let AssignmentRightHand::Selected(selected) = &assignment.rhs {
// with
self.format_token_id(selected.expression.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(selected.expression.as_ref(), buffer);
buffer.push_whitespace();
// select
self.format_token_id(selected.expression.span.end_token + 1, buffer);
buffer.push_whitespace();
}
self.format_target(&assignment.target, buffer);
buffer.push_whitespace();
self.format_token_id(assignment.target.span.end_token + 1, buffer);
buffer.push_whitespace();
self.format_assignment_right_hand(
&assignment.rhs,
|formatter, expr, buffer| formatter.format_expression(expr.as_ref(), buffer),
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_signal_assignment(
&self,
assignment: &SignalAssignment,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_target(&assignment.target, buffer);
buffer.push_whitespace();
self.format_token_id(assignment.target.span.end_token + 1, buffer);
buffer.push_whitespace();
if let Some(delay_mechanism) = &assignment.delay_mechanism {
self.format_delay_mechanism(delay_mechanism, buffer);
buffer.push_whitespace();
}
self.format_assignment_right_hand(&assignment.rhs, Self::format_waveform, buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_delay_mechanism(
&self,
delay_mechanism: &WithTokenSpan<DelayMechanism>,
buffer: &mut Buffer,
) {
match &delay_mechanism.item {
DelayMechanism::Transport => {
self.format_token_span(delay_mechanism.span, buffer);
}
DelayMechanism::Inertial { reject } => {
if let Some(reject) = reject {
self.format_token_id(delay_mechanism.span.start_token, buffer);
buffer.push_whitespace();
self.format_expression(reject.as_ref(), buffer);
buffer.push_whitespace();
}
// inertial
self.format_token_id(delay_mechanism.span.end_token, buffer);
}
}
}
pub fn format_signal_force_assignment(
&self,
assignment: &SignalForceAssignment,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_target(&assignment.target, buffer);
buffer.push_whitespace();
// <=
self.format_token_id(assignment.target.span.end_token + 1, buffer);
buffer.push_whitespace();
// force
self.format_token_id(assignment.target.span.end_token + 2, buffer);
buffer.push_whitespace();
if assignment.force_mode.is_some() {
self.format_token_id(assignment.target.span.end_token + 3, buffer);
buffer.push_whitespace();
}
self.format_assignment_right_hand(
&assignment.rhs,
|formatter, expr, buffer| formatter.format_expression(expr.as_ref(), buffer),
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_signal_release_assignment(
&self,
assignment: &SignalReleaseAssignment,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_target(&assignment.target, buffer);
buffer.push_whitespace();
// <=
self.format_token_id(assignment.target.span.end_token + 1, buffer);
buffer.push_whitespace();
// release
self.format_token_id(assignment.target.span.end_token + 2, buffer);
if assignment.force_mode.is_some() {
buffer.push_whitespace();
self.format_token_id(assignment.target.span.end_token + 3, buffer);
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_if_statement(
&self,
statement: &IfStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
for cond in &statement.conds.conditionals {
let condition = &cond.condition;
// if | elsif
self.format_token_id(condition.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(condition.as_ref(), buffer);
buffer.push_whitespace();
// then
self.format_token_id(condition.span.end_token + 1, buffer);
self.format_sequential_statements(&cond.item, buffer);
buffer.line_break();
}
if let Some((statements, token)) = &statement.conds.else_item {
self.format_token_id(*token, buffer);
self.format_sequential_statements(statements, buffer);
buffer.line_break();
}
if statement.end_label_pos.is_some() {
// end if <label>
self.format_token_span(
TokenSpan::new(span.end_token - 3, span.end_token - 1),
buffer,
)
} else {
// end if
self.format_token_span(
TokenSpan::new(span.end_token - 2, span.end_token - 1),
buffer,
)
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_choice(&self, choice: &WithTokenSpan<Choice>, buffer: &mut Buffer) {
match &choice.item {
Choice::Expression(expr) => {
self.format_expression(WithTokenSpan::new(expr, choice.span), buffer)
}
Choice::DiscreteRange(range) => self.format_discrete_range(range, buffer),
Choice::Others => self.format_token_span(choice.span, buffer),
}
}
pub fn format_case_statement(
&self,
statement: &CaseStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// case
self.format_token_id(span.start_token, buffer);
if statement.is_matching {
// ?
self.format_token_id(span.start_token + 1, buffer);
}
buffer.push_whitespace();
self.format_expression(statement.expression.as_ref(), buffer);
buffer.push_whitespace();
// is
self.format_token_id(statement.expression.span.end_token + 1, buffer);
indented!(buffer, {
for alternative in &statement.alternatives {
buffer.line_break();
for (i, choice) in alternative.choices.iter().enumerate() {
if i == 0 {
// when
self.format_token_id(choice.span.start_token - 1, buffer);
buffer.push_whitespace();
}
self.format_choice(choice, buffer);
if i < alternative.choices.len() - 1 {
buffer.push_whitespace();
// |
self.format_token_id(choice.span.end_token + 1, buffer);
buffer.push_whitespace();
}
if i == alternative.choices.len() - 1 {
buffer.push_whitespace();
// =>
self.format_token_id(choice.span.end_token + 1, buffer);
}
}
self.format_sequential_statements(&alternative.item, buffer);
}
});
buffer.line_break();
if statement.is_matching {
self.format_token_span(
TokenSpan::new(statement.end_token, span.end_token - 2),
buffer,
);
self.format_token_id(span.end_token - 1, buffer);
} else {
self.format_token_span(
TokenSpan::new(statement.end_token, span.end_token - 1),
buffer,
);
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_loop_statement(
&self,
statement: &LoopStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
if let Some(scheme) = &statement.iteration_scheme {
match scheme {
IterationScheme::While(expression) => {
// while
self.format_token_id(expression.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(expression.as_ref(), buffer);
buffer.push_whitespace();
}
IterationScheme::For(ident, range) => {
// for <ident> in
self.format_token_span(
TokenSpan::new(ident.tree.token - 1, ident.tree.token + 1),
buffer,
);
buffer.push_whitespace();
self.format_discrete_range(range, buffer);
buffer.push_whitespace();
}
}
}
self.format_token_id(statement.loop_token, buffer);
self.format_sequential_statements(&statement.statements, buffer);
buffer.line_break();
self.format_token_span(
TokenSpan::new(statement.end_token, span.end_token - 1),
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_next_statement(
&self,
statement: &NextStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// next
self.format_token_id(span.start_token, buffer);
self.format_opt_loop_label(statement.loop_label.as_ref(), buffer);
self.format_opt_condition(statement.condition.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_exit_statement(
&self,
statement: &ExitStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// next
self.format_token_id(span.start_token, buffer);
self.format_opt_loop_label(statement.loop_label.as_ref(), buffer);
self.format_opt_condition(statement.condition.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
fn format_opt_loop_label(&self, loop_label: Option<&WithRef<Ident>>, buffer: &mut Buffer) {
if let Some(label) = loop_label {
buffer.push_whitespace();
self.format_token_id(label.item.token, buffer);
}
}
fn format_opt_condition(
&self,
condition: Option<&WithTokenSpan<Expression>>,
buffer: &mut Buffer,
) {
if let Some(condition) = &condition {
buffer.push_whitespace();
// when
self.format_token_id(condition.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(condition.as_ref(), buffer);
}
}
}
#[cfg(test)]
mod tests {
use crate::formatting::test_utils::check_formatted;
use crate::syntax::test::Code;
fn check_statement(input: &str) {
check_formatted(
input,
input,
Code::sequential_statement,
|formatter, ast, buffer| formatter.format_labeled_sequential_statement(ast, buffer),
)
}
fn check_statements(inputs: &[&str]) {
for input in inputs {
check_statement(input)
}
}
#[test]
fn null_statement() {
check_statement("null;");
}
#[test]
fn return_statement() {
check_statement("return;");
check_statement("return 2 + 2;");
}
#[test]
fn calls() {
check_statement("something;");
check_statement("something(arg);");
}
#[test]
fn assertions() {
check_statement("assert x;");
check_statement("assert x report y;");
check_statement("assert x report y severity NOTE;");
}
#[test]
fn wait_statement() {
check_statement("wait;");
check_statement("foo: wait;");
check_statement("wait on foo, bar;");
check_statement("wait until a = b;");
check_statement("wait for 2 ns;");
check_statement("wait on foo until bar for 2 ns;");
}
#[test]
fn report_statement() {
check_statements(&["report \"message\" severity error;", "report \"message\";"]);
}
#[test]
fn variable_assignment() {
check_statements(&["foo(0) := bar(1, 2);", "name: foo(0) := bar(1, 2);"]);
}
#[test]
fn signal_assignment() {
check_statements(&["foo(0) <= bar(1, 2);", "name: foo(0) <= bar(1, 2);"]);
}
#[test]
fn signal_force_assignment() {
check_statements(&[
"foo(0) <= force bar(1, 2);",
"foo(0) <= force out bar(1, 2);",
"foo(0) <= force in bar(1, 2);",
]);
}
#[test]
fn signal_release_assignment() {
check_statements(&[
"foo(0) <= release;",
"foo(0) <= release out;",
"foo(0) <= release in;",
]);
}
#[test]
fn if_statements() {
check_statements(&[
"\
if cond = true then
foo(1, 2);
x := 1;
end if;",
"\
mylabel: if cond = true then
foo(1, 2);
x := 1;
end if mylabel;",
"\
if cond = true then
foo(1, 2);
else
x := 1;
end if;",
"\
mylabel: if cond = true then
foo(1, 2);
else
x := 1;
end if mylabel;",
"\
if cond = true then
foo(1, 2);
elsif cond2 = false then
y := 2;
else
x := 1;
end if;",
"\
mylabel: if cond = true then
foo(1, 2);
elsif cond2 = false then
y := 2;
else
x := 1;
end if mylabel;",
]);
}
#[test]
fn case_statements() {
check_statements(&[
"\
case foo(1) is
when 1 | 2 =>
stmt1;
stmt2;
when others =>
stmt3;
stmt4;
end case;",
"\
case? foo(1) is
when others =>
null;
end case?;",
])
}
#[test]
fn check_loop() {
check_statements(&[
"\
lbl: loop
end loop lbl;",
"\
lbl: loop
stmt1;
stmt2;
end loop lbl;",
"\
while foo = true loop
stmt1;
stmt2;
end loop;",
"\
for idx in 0 to 3 loop
stmt1;
stmt2;
end loop;",
]);
}
#[test]
fn check_next_statement() {
check_statements(&[
"next;",
"next foo;",
"next when condition;",
"next foo when condition;",
]);
}
#[test]
fn check_exit_statement() {
check_statements(&[
"exit;",
"exit foo;",
"exit when condition;",
"exit foo when condition;",
]);
}
#[test]
fn check_delay_mechanisms() {
check_statements(&[
"foo(0) <= transport bar(1, 2);",
"bar <= reject 2 ns inertial bar(1, 2);",
"bar <= inertial bar(1, 2);",
]);
}
#[test]
fn format_selected_assignments() {
check_statement(
"\
with x(0) + 1 select foo(0) := bar(1, 2) when 0 | 1, def when others;",
);
}
}

Some files were not shown because too many files have changed in this diff Show More