Skip to content

Commit

Permalink
Refactor vhdl lang main (#306)
Browse files Browse the repository at this point in the history
  • Loading branch information
Schottkyc137 authored May 25, 2024
1 parent 5d2038d commit 3d83e55
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 151 deletions.
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

0 comments on commit 3d83e55

Please sign in to comment.