use super::named_entity::*;
use super::region::*;
use super::root::*;
use super::standard::StandardRegion;
use crate::ast::*;
use crate::data::*;
use fnv::FnvHashSet;
use std::cell::RefCell;
use std::ops::Deref;
#[derive(Debug)]
pub enum AnalysisError {
Fatal(CircularDependencyError),
NotFatal(Diagnostic),
}
impl AnalysisError {
pub fn not_fatal_error(pos: impl AsRef<SrcPos>, msg: impl Into<String>) -> AnalysisError {
AnalysisError::NotFatal(Diagnostic::error(pos, msg))
}
}
#[derive(Clone, Debug)]
#[must_use]
pub struct CircularDependencyError {
reference: Option<SrcPos>,
}
impl CircularDependencyError {
pub fn new(reference: Option<&SrcPos>) -> CircularDependencyError {
CircularDependencyError {
reference: reference.cloned(),
}
}
pub fn push_into(self, diagnostics: &mut dyn DiagnosticHandler) {
if let Some(pos) = self.reference {
diagnostics.push(Diagnostic::error(pos, "Found circular dependency"));
}
}
}
pub type AnalysisResult<T> = Result<T, AnalysisError>;
pub type FatalResult<T = ()> = Result<T, CircularDependencyError>;
impl From<CircularDependencyError> for AnalysisError {
fn from(err: CircularDependencyError) -> AnalysisError {
AnalysisError::Fatal(err)
}
}
impl From<Diagnostic> for AnalysisError {
fn from(diagnostic: Diagnostic) -> AnalysisError {
AnalysisError::NotFatal(diagnostic)
}
}
impl AnalysisError {
pub fn add_to(self, diagnostics: &mut dyn DiagnosticHandler) -> FatalResult {
let diag = self.into_non_fatal()?;
diagnostics.push(diag);
Ok(())
}
pub fn into_non_fatal(self) -> FatalResult<Diagnostic> {
match self {
AnalysisError::Fatal(err) => Err(err),
AnalysisError::NotFatal(diag) => Ok(diag),
}
}
}
pub(super) struct AnalyzeContext<'a> {
pub(super) root: &'a DesignRoot,
pub work_sym: Symbol,
std_sym: Symbol,
standard_sym: Symbol,
current_unit: UnitId,
pub(super) arena: &'a Arena,
uses: RefCell<FnvHashSet<UnitId>>,
missing_primary: RefCell<FnvHashSet<(Symbol, Symbol)>>,
uses_library_all: RefCell<FnvHashSet<Symbol>>,
}
impl<'a> AnalyzeContext<'a> {
pub fn new(
root: &'a DesignRoot,
current_unit: &UnitId,
arena: &'a Arena,
) -> AnalyzeContext<'a> {
AnalyzeContext {
work_sym: root.symbol_utf8("work"),
std_sym: root.symbol_utf8("std"),
standard_sym: root.symbol_utf8("standard"),
root,
current_unit: current_unit.clone(),
arena,
uses: RefCell::new(FnvHashSet::default()),
missing_primary: RefCell::new(FnvHashSet::default()),
uses_library_all: RefCell::new(FnvHashSet::default()),
}
}
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 {
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) {
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_primary(&self, library_name: &Symbol, primary_name: &Symbol) {
let key = (library_name.clone(), primary_name.clone());
if self.missing_primary.borrow_mut().insert(key) {
self.root
.make_use_of_missing_primary(&self.current_unit, library_name, primary_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(())
}
pub fn redefine<T: HasIdent>(
&self,
id: EntityId,
decl: &mut WithDecl<T>,
kind: AnyEntKind<'a>,
) -> EntRef<'a> {
decl.decl = Some(id);
unsafe {
self.arena.update(
id,
decl.tree.name().clone().into(),
Related::None,
kind,
Some(decl.tree.pos().clone()),
)
}
}
pub fn is_standard_package(&self) -> bool {
*self.work_library_name() == self.std_sym
&& *self.current_unit.primary_name() == self.standard_sym
}
pub fn universal_integer(&self) -> BaseType<'a> {
TypeEnt::from_any(
self.root
.get_ent(self.root.universal.as_ref().unwrap().integer),
)
.unwrap()
.base()
}
pub fn universal_real(&self) -> BaseType<'a> {
TypeEnt::from_any(
self.root
.get_ent(self.root.universal.as_ref().unwrap().real),
)
.unwrap()
.base()
}
pub fn add_implicit_context_clause(&self, scope: &Scope<'a>) -> FatalResult {
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 self.is_standard_package() {
return Ok(());
};
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);
self.arena.link(&data.result().arena);
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_primary(library_name, name);
None
}
pub fn lookup_in_library(
&self,
library_name: &Symbol,
pos: &SrcPos,
primary_name: &Designator,
reference: &mut Reference,
) -> AnalysisResult<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(ent) = primary.ent_id() {
let design = DesignEnt::from_any(self.arena.get(ent)).map_err(|ent| {
Diagnostic::error(
pos,
format!(
"Found non-design {} unit within library {}",
ent.describe(),
library_name
),
)
})?;
reference.set_unique_reference(design.0);
return Ok(design);
}
}
}
}
Err(AnalysisError::NotFatal(Diagnostic::error(
pos,
format!("No primary unit '{primary_name}' within library '{library_name}'"),
)))
}
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());
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 standard_package(&self) -> Option<StandardRegion<'a, 'a>> {
let region = self.standard_package_region()?;
Some(StandardRegion::new(self.root, self.arena, region))
}
}