Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor vhdl lang main #306

Merged
merged 7 commits into from
May 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 182 additions & 66 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions vhdl_lang/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ tempfile = "3"
pretty_assertions = "1"
assert_matches = "1"
brunch = "0"
assert_cmd = "2.0.14"
predicates = "3.1.0"

[[bench]]
name = "benchmark"
Expand Down
23 changes: 20 additions & 3 deletions vhdl_lang/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,15 @@ impl Config {
}

/// Load configuration file from installation folder
fn load_installed_config(&mut self, messages: &mut dyn MessageHandler) {
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",
Expand Down Expand Up @@ -325,8 +333,17 @@ impl Config {
}

/// Load all external configuration
pub fn load_external_config(&mut self, messages: &mut dyn MessageHandler) {
self.load_installed_config(messages);
/// 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);
}
Expand Down
104 changes: 23 additions & 81 deletions vhdl_lang/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,26 @@
// Copyright (c) 2018, Olof Kraigher [email protected]

use clap::Parser;
use itertools::Itertools;
use std::path::Path;
use std::time::SystemTime;
use vhdl_lang::{Config, Diagnostic, MessagePrinter, NullMessages, Project, Severity, SeverityMap};
use vhdl_lang::{Config, Diagnostic, MessagePrinter, Project, Severity, SeverityMap};

/// Run vhdl analysis
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// The number of threads to use. By default the maximum is selected based on process cores
/// The number of threads to use. By default, the maximum is selected based on process cores
#[arg(short = 'p', long)]
num_threads: Option<usize>,

/// Prints the number of files processed and the execution time
#[arg(long, default_value_t = false)]
perf: bool,

/// Run repeatedly to get a reliable benchmark result
#[arg(long, default_value_t = false)]
bench: bool,

/// Hide hint diagnostics
#[arg(long, default_value_t = false)]
no_hint: bool,
/// Path to the config file for the VHDL standard libraries (i.e., IEEE std_logic_1164).
/// If omitted, will search for these libraries in a set of standard paths
#[arg(short = 'l', long)]
libraries: Option<String>,

/// Config file in TOML format containing libraries and settings
#[arg(short, long)]
config: String,

/// Dump items that are not resolved into an unique reference
/// This is used for development to test where the language server is blind
#[arg(long)]
dump_unresolved: bool,

/// Count items that are not resolved into an unique reference
/// This is used for development to test where the language server is blind
#[arg(long)]
count_unresolved: bool,
}

fn main() {
Expand All @@ -53,77 +36,36 @@ fn main() {

let mut config = Config::default();
let mut msg_printer = MessagePrinter::default();
config.load_external_config(&mut msg_printer);
config.load_external_config(&mut msg_printer, args.libraries.clone());
config.append(
&Config::read_file_path(Path::new(&args.config)).expect("Failed to read config file"),
&mut msg_printer,
);

let start = SystemTime::now();

let iterations = if args.bench {
let iterations = 10;
println!("Running {iterations} iterations for benchmarking");
for _ in 0..(iterations - 1) {
let mut project = Project::from_config(config.clone(), &mut NullMessages);
project.analyse();
}
iterations
} else {
1
};

let severity_map = *config.severities();
let mut project = Project::from_config(config, &mut msg_printer);
let mut diagnostics = project.analyse();
let duration = start.elapsed().unwrap() / iterations;

if args.no_hint {
diagnostics.retain(|diag| severity_map[diag.code] != Some(Severity::Hint));
}
project.enable_unused_declaration_detection();
let diagnostics = project.analyse();

show_diagnostics(&diagnostics, &severity_map);

if args.perf || args.bench {
let mut num_files = 0;
let mut num_lines = 0;
for source_file in project.files() {
num_files += 1;
num_lines += source_file.num_lines();
}
let duration_per_line = duration.checked_div(num_lines as u32).unwrap();

println!("Analyzed {num_files} files with {num_lines} lines of code");
println!(
"Total time to run was {} ms with an average of {} ns per line",
duration.as_millis(),
duration_per_line.as_nanos()
);
}

if args.dump_unresolved || args.count_unresolved {
let (total, unresolved) = project.find_all_unresolved();

if args.dump_unresolved {
for pos in unresolved.iter() {
println!("{}", pos.show("Unresolved"));
}
}

if args.count_unresolved {
println!("{} out of {} positions unresolved", unresolved.len(), total);
}
if diagnostics
.iter()
.any(|diag| severity_map[diag.code].is_some_and(|severity| severity == Severity::Error))
{
std::process::exit(1);
} else {
std::process::exit(0);
}

// Exit without running Drop on entire allocated AST
std::process::exit(0);
}

fn show_diagnostics(diagnostics: &[Diagnostic], severity_map: &SeverityMap) {
for diagnostic in diagnostics {
if let Some(str) = diagnostic.show(severity_map) {
println!("{str}");
}
let diagnostics = diagnostics
.iter()
.filter_map(|diag| diag.show(severity_map))
.collect_vec();
for str in &diagnostics {
println!("{str}");
}

if !diagnostics.is_empty() {
Expand Down
60 changes: 60 additions & 0 deletions vhdl_lang/tests/integration_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use assert_cmd::prelude::*;
use itertools::Itertools;
use predicates::prelude::*;
use std::error::Error;
use std::path::PathBuf;
use std::process::Command;
use vhdl_lang::{Config, MessagePrinter, Project, Severity};

#[test]
pub fn parses_example_project_without_errors() {
let mut config = Config::default();
let mut msg_printer = MessagePrinter::default();

let mut vhdl_libraries_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
// Load the VHDL standard libraries
vhdl_libraries_path.push("../vhdl_libraries/vhdl_ls.toml");
config.append(
&Config::read_file_path(&vhdl_libraries_path).expect("Failed to read config file"),
&mut msg_printer,
);

// Load the configuration from the example project
let mut config_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
config_path.push("../example_project/vhdl_ls.toml");
config.append(
&Config::read_file_path(&config_path).expect("Failed to read config file"),
&mut msg_printer,
);

let severity_map = *config.severities();
let mut project = Project::from_config(config, &mut msg_printer);
project.enable_unused_declaration_detection();

let diagnostics = project.analyse();
let diagnostics_with_errors = diagnostics
.iter()
.filter(|diag| severity_map[diag.code] == Some(Severity::Error))
.collect_vec();
if !diagnostics_with_errors.is_empty() {
for diagnostic in diagnostics_with_errors {
println!("{}", diagnostic.show(&severity_map).unwrap())
}
panic!("Found diagnostics with severity error in the example project");
}
}

#[test]
fn unused_function_gets_detected() -> Result<(), Box<dyn Error>> {
let mut cmd = Command::cargo_bin("vhdl_lang")?;

cmd.arg("--config")
.arg("tests/unused_declarations/vhdl_ls.toml")
.arg("--libraries")
.arg("../vhdl_libraries/vhdl_ls.toml");
cmd.assert().failure().stdout(predicate::str::contains(
"error: Unused declaration of port 'baz' : inout",
));

Ok(())
}
15 changes: 15 additions & 0 deletions vhdl_lang/tests/unused_declarations/my_entity.vhd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
library ieee;
use ieee.std_logic_1164.all;

entity my_ent is
port (
foo : in std_logic;
bar : out std_logic;
baz : inout std_logic
);
end my_ent;

architecture arch of my_ent is
begin
bar <= foo;
end architecture arch;
6 changes: 6 additions & 0 deletions vhdl_lang/tests/unused_declarations/vhdl_ls.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[libraries]

my_library.files = ["my_entity.vhd"]

[lint]
unused = "error"
2 changes: 1 addition & 1 deletion vhdl_ls/src/vhdl_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl VHDLServer {
let mut config = Config::default();

if self.use_external_config {
config.load_external_config(&mut self.message_filter());
config.load_external_config(&mut self.message_filter(), None);
}

match self.load_root_uri_config() {
Expand Down
Loading