use crate::analysis::DesignRoot;
use crate::ast::DesignFile;
use crate::config::Config;
use crate::data::*;
use crate::syntax::VHDLParser;
use fnv::{FnvHashMap, FnvHashSet};
use std::collections::hash_map::Entry;
use std::path::{Path, PathBuf};
pub struct Project {
parser: VHDLParser,
root: DesignRoot,
files: FnvHashMap<PathBuf, SourceFile>,
empty_libraries: FnvHashSet<Symbol>,
}
impl Project {
pub fn new() -> Project {
let parser = VHDLParser::new();
Project {
root: DesignRoot::new(parser.symbols.clone()),
files: FnvHashMap::default(),
empty_libraries: FnvHashSet::default(),
parser,
}
}
pub fn from_config(config: &Config, messages: &mut dyn MessageHandler) -> Project {
let mut project = Project::new();
let mut files_to_parse: FnvHashMap<PathBuf, FnvHashSet<Symbol>> = FnvHashMap::default();
for library in config.iter_libraries() {
let library_name =
Latin1String::from_utf8(library.name()).expect("Library name not latin-1 encoded");
let library_name = project.parser.symbol(&library_name);
let mut empty_library = true;
for file_name in library.file_names(messages) {
empty_library = false;
match files_to_parse.entry(file_name.clone()) {
Entry::Occupied(mut entry) => {
entry.get_mut().insert(library_name.clone());
}
Entry::Vacant(entry) => {
let mut set = FnvHashSet::default();
set.insert(library_name.clone());
entry.insert(set);
}
}
}
if empty_library {
project.empty_libraries.insert(library_name);
}
}
use rayon::prelude::*;
let parsed: Vec<_> = files_to_parse
.into_par_iter()
.map_init(
|| &project.parser,
|parser, (file_name, library_names)| {
let mut diagnostics = Vec::new();
let result = parser.parse_design_file(&file_name, &mut diagnostics);
(file_name, library_names, diagnostics, result)
},
)
.collect();
for (file_name, library_names, parser_diagnostics, result) in parsed.into_iter() {
let (source, design_file) = match result {
Ok(result) => result,
Err(err) => {
messages.push(Message::file_error(err.to_string(), &file_name));
continue;
}
};
project.files.insert(
source.file_name().to_owned(),
SourceFile {
source,
library_names,
parser_diagnostics,
design_file,
},
);
}
project
}
pub fn get_source(&self, file_name: &Path) -> Option<Source> {
self.files.get(file_name).map(|file| file.source.clone())
}
pub fn update_source(&mut self, source: &Source) {
let mut source_file = {
if let Some(mut source_file) = self.files.remove(source.file_name()) {
for library_name in source_file.library_names.iter() {
self.root.remove_source(library_name.clone(), source);
}
source_file.source = source.clone();
source_file
} else {
SourceFile {
source: source.clone(),
library_names: FnvHashSet::default(),
parser_diagnostics: vec![],
design_file: DesignFile::default(),
}
}
};
source_file.parser_diagnostics.clear();
source_file.design_file = self
.parser
.parse_design_source(source, &mut source_file.parser_diagnostics);
self.files
.insert(source.file_name().to_owned(), source_file);
}
pub fn analyse(&mut self) -> Vec<Diagnostic> {
let mut diagnostics = Vec::new();
for source_file in self.files.values_mut() {
let design_file = source_file.take_design_file();
let mut design_files = multiply(design_file, source_file.library_names.len());
for library_name in source_file.library_names.iter() {
let design_file = design_files.pop().unwrap();
self.root.add_design_file(library_name.clone(), design_file);
}
for diagnostic in source_file.parser_diagnostics.iter().cloned() {
diagnostics.push(diagnostic);
}
}
for library_name in self.empty_libraries.iter() {
self.root.ensure_library(library_name.clone());
}
self.root.analyze(&mut diagnostics);
diagnostics
}
pub fn search_reference(&self, source: &Source, cursor: Position) -> Option<SrcPos> {
self.root.search_reference(source, cursor)
}
pub fn find_all_references(&self, decl_pos: &SrcPos) -> Vec<SrcPos> {
self.root.find_all_references(decl_pos)
}
}
fn multiply<T: Clone>(value: T, n: usize) -> Vec<T> {
if n == 0 {
vec![]
} else if n == 1 {
vec![value]
} else {
let mut res = Vec::with_capacity(n);
for _ in 0..n - 1 {
res.push(value.clone());
}
res.push(value);
res
}
}
impl Default for Project {
fn default() -> Self {
Self::new()
}
}
struct SourceFile {
library_names: FnvHashSet<Symbol>,
source: Source,
design_file: DesignFile,
parser_diagnostics: Vec<Diagnostic>,
}
impl SourceFile {
fn take_design_file(&mut self) -> DesignFile {
std::mem::replace(&mut self.design_file, DesignFile::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::syntax::test::check_no_diagnostics;
#[test]
fn test_empty_library_is_defined() {
let root = tempfile::tempdir().unwrap();
let vhdl_file_path = root.path().join("file.vhd");
std::fs::write(
&vhdl_file_path,
"
library missing;
entity ent is
end entity;
",
)
.unwrap();
let config_str = "
[libraries]
missing.files = []
lib.files = ['file.vhd']
";
let config = Config::from_str(config_str, root.path()).unwrap();
let mut messages = Vec::new();
let mut project = Project::from_config(&config, &mut messages);
assert_eq!(messages, vec![]);
check_no_diagnostics(&project.analyse());
}
#[test]
fn test_same_file_in_multiple_libraries() {
let root = tempfile::tempdir().unwrap();
let vhdl_file_path1 = root.path().join("file.vhd");
std::fs::write(
&vhdl_file_path1,
"
package pkg is
end package;
",
)
.unwrap();
let vhdl_file_path2 = root.path().join("use_file.vhd");
std::fs::write(
&vhdl_file_path2,
"
library lib1;
use lib1.pkg.all;
package use_pkg1 is
end package;
library lib2;
use lib2.pkg.all;
package use_pkg2 is
end package;
",
)
.unwrap();
let config_str = "
[libraries]
lib1.files = ['file.vhd']
lib2.files = ['file.vhd']
use_lib.files = ['use_file.vhd']
";
let config = Config::from_str(config_str, root.path()).unwrap();
let mut messages = Vec::new();
let mut project = Project::from_config(&config, &mut messages);
assert_eq!(messages, vec![]);
check_no_diagnostics(&project.analyse());
}
fn update(project: &mut Project, source: &mut Source, contents: &str) {
std::fs::write(&std::path::Path::new(source.file_name()), contents).unwrap();
*source = Source::from_latin1_file(source.file_name()).unwrap();
project.update_source(source);
}
#[test]
fn test_re_analyze_after_update() {
let tempdir = tempfile::tempdir().unwrap();
let root = dunce::canonicalize(tempdir.path()).unwrap();
let path1 = root.join("file1.vhd");
let path2 = root.join("file2.vhd");
std::fs::write(
&path1,
"
package pkg is
end package;
",
)
.unwrap();
let mut source1 = Source::from_latin1_file(&path1).unwrap();
std::fs::write(
&path2,
"
library lib1;
use lib1.pkg.all;
package pkg is
end package;
",
)
.unwrap();
let mut source2 = Source::from_latin1_file(&path2).unwrap();
let config_str = "
[libraries]
lib1.files = ['file1.vhd']
lib2.files = ['file2.vhd']
";
let config = Config::from_str(config_str, &root).unwrap();
let mut messages = Vec::new();
let mut project = Project::from_config(&config, &mut messages);
assert_eq!(messages, vec![]);
check_no_diagnostics(&project.analyse());
update(
&mut project,
&mut source1,
"
package is
",
);
let diagnostics = project.analyse();
assert_eq!(diagnostics.len(), 2);
assert_eq!(diagnostics[0].pos.source, source1);
assert_eq!(diagnostics[1].pos.source, source2);
update(
&mut project,
&mut source1,
"
package pkg is
end package;
",
);
check_no_diagnostics(&project.analyse());
update(
&mut project,
&mut source2,
"
package pkg is
end package;
package pkg is
end package;
",
);
let diagnostics = project.analyse();
assert_eq!(diagnostics.len(), 1);
assert_eq!(diagnostics[0].pos.source, source2);
update(
&mut project,
&mut source2,
"
package pkg is
end package;
",
);
check_no_diagnostics(&project.analyse());
}
}