Skip to content

Commit

Permalink
Parse and analyse VHDL2019 Views (#285)
Browse files Browse the repository at this point in the history
  • Loading branch information
Schottkyc137 committed Apr 14, 2024
1 parent 6a892af commit 5af68f9
Show file tree
Hide file tree
Showing 26 changed files with 1,670 additions and 192 deletions.
3 changes: 2 additions & 1 deletion vhdl_lang/src/analysis/concurrent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,8 @@ impl<'a> AnalyzeContext<'a> {
),
ErrorCode::DisallowedInSensitivityList,
)
} else if object_name.base.mode() == Some(Mode::Out) && !object_name.base.is_port()
} else if object_name.base.mode() == Some(&InterfaceMode::Simple(Mode::Out))
&& !object_name.base.is_port()
{
diagnostics.add(
&name.pos,
Expand Down
253 changes: 214 additions & 39 deletions vhdl_lang/src/analysis/declarative.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ use crate::named_entity::{Signature, *};
use crate::{ast, named_entity, HasTokenSpan};
use analyze::*;
use fnv::FnvHashMap;
use itertools::Itertools;
use std::collections::hash_map::Entry;
use std::collections::HashSet;

impl Declaration {
pub fn is_allowed_in_context(&self, parent: &AnyEntKind) -> bool {
use Declaration::*;
use ObjectClass::*;
match parent {
// LRM: block_declarative_item
AnyEntKind::Design(Design::Architecture(..))
| AnyEntKind::Concurrent(Some(Concurrent::Block | Concurrent::Generate)) => matches!(
self,
Expand All @@ -37,10 +40,13 @@ impl Declaration {
| Use(_)
| Package(_)
| Configuration(_)
| View(_)
),
// LRM: configuration_declarative_item
AnyEntKind::Design(Design::Configuration) => {
matches!(self, Use(_) | Attribute(ast::Attribute::Specification(_)))
}
// LRM: entity_declarative_item
AnyEntKind::Design(Design::Entity(..)) => matches!(
self,
Object(_)
Expand All @@ -53,7 +59,9 @@ impl Declaration {
| SubprogramBody(_)
| Use(_)
| Package(_)
| View(_)
),
// LRM: package_body_declarative_item
AnyEntKind::Design(Design::PackageBody | Design::UninstPackage(..))
| AnyEntKind::Overloaded(
Overloaded::SubprogramDecl(_)
Expand All @@ -77,6 +85,7 @@ impl Declaration {
| Use(_)
| Package(_)
),
// LRM: package_declarative_item
AnyEntKind::Design(Design::Package(..)) => matches!(
self,
Object(_)
Expand All @@ -89,6 +98,7 @@ impl Declaration {
| SubprogramInstantiation(_)
| Use(_)
| Package(_)
| View(_)
),
_ => {
// AnyEntKind::Library is used in tests for a generic declarative region
Expand Down Expand Up @@ -297,9 +307,13 @@ impl<'a> AnalyzeContext<'a> {
return Err(EvalError::Unknown);
}
}
ResolvedName::Final(_) => {
// @TODO some of these can probably be aliased
return Err(EvalError::Unknown);
ResolvedName::Final(ent) => {
if let Some(ent) = ViewEnt::from_any(ent) {
AnyEntKind::View(*ent.subtype())
} else {
// @TODO some of these can probably be aliased
return Err(EvalError::Unknown);
}
}
}
};
Expand Down Expand Up @@ -566,7 +580,6 @@ impl<'a> AnalyzeContext<'a> {
Declaration::Use(ref mut use_clause) => {
self.analyze_use_clause(scope, use_clause, diagnostics)?;
}

Declaration::Package(ref mut instance) => {
let ent = self.arena.define(
&mut instance.ident,
Expand All @@ -586,12 +599,79 @@ impl<'a> AnalyzeContext<'a> {
}
}
Declaration::Configuration(..) => {}
Declaration::View(view) => {
if let Some(view) =
as_fatal(self.analyze_view_declaration(scope, parent, view, diagnostics))?
{
scope.add(view, diagnostics);
}
}
Declaration::Type(..) => unreachable!("Handled elsewhere"),
};

Ok(())
}

/// Analyzes a mode view declaration.
/// * Checks that the type of the view declaration is a record type
/// * Checks that all elements are associated in the view
fn analyze_view_declaration(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
view: &mut ModeViewDeclaration,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<EntRef<'a>> {
let typ = self.resolve_subtype_indication(scope, &mut view.typ, diagnostics)?;
let record_region = match typ.type_mark().kind() {
Type::Record(region) => region,
_ => {
let diag = Diagnostic::new(
&view.typ.type_mark,
format!(
"The type of a view must be a record type, not {}",
typ.type_mark().describe()
),
ErrorCode::TypeMismatch,
)
.opt_related(
typ.type_mark().decl_pos.as_ref(),
format!("{} declared here", typ.type_mark().describe()),
);
bail!(diagnostics, diag);
}
};
let mut unassociated: HashSet<_> = record_region.elems.iter().collect();
for element in view.elements.iter_mut() {
for name in element.names.items.iter_mut() {
let desi = Designator::Identifier(name.item.item.clone());
let Some(record_element) = record_region.lookup(&desi) else {
diagnostics.push(Diagnostic::new(
&name.item.pos,
format!("Not a part of {}", typ.type_mark().describe()),
ErrorCode::Unresolved,
));
continue;
};
name.set_unique_reference(&record_element);
unassociated.remove(&record_element);
}
}
if !unassociated.is_empty() {
diagnostics.add(
&view.ident.tree,
pretty_format_unassociated_message(&unassociated),
ErrorCode::Unassociated,
);
}
Ok(self.arena.define(
&mut view.ident,
parent,
AnyEntKind::View(typ),
Some(view.span),
))
}

fn find_deferred_constant_declaration(
&self,
scope: &Scope<'a>,
Expand Down Expand Up @@ -792,41 +872,7 @@ impl<'a> AnalyzeContext<'a> {
)
}
InterfaceDeclaration::Object(ref mut object_decl) => {
let subtype = self.resolve_subtype_indication(
scope,
&mut object_decl.subtype_indication,
diagnostics,
);

if let Some(ref mut expression) = object_decl.expression {
if let Ok(ref subtype) = subtype {
self.expr_pos_with_ttyp(
scope,
subtype.type_mark(),
&expression.pos,
&mut expression.item,
diagnostics,
)?;
} else {
self.expr_unknown_ttyp(scope, expression, diagnostics)?
}
}

let subtype = subtype?;
self.arena.define(
&mut object_decl.ident,
parent,
AnyEntKind::Object(Object {
class: object_decl.class,
iface: Some(ObjectInterface::new(
object_decl.list_type,
object_decl.mode,
)),
subtype,
has_default: object_decl.expression.is_some(),
}),
None,
)
self.analyze_interface_object_declaration(scope, parent, object_decl, diagnostics)?
}
InterfaceDeclaration::Type(ref mut ident) => {
let typ = TypeEnt::from_any(self.arena.define(
Expand Down Expand Up @@ -880,6 +926,91 @@ impl<'a> AnalyzeContext<'a> {
Ok(ent)
}

pub fn analyze_interface_object_declaration(
&self,
scope: &Scope<'a>,
parent: EntRef<'a>,
object_decl: &mut InterfaceObjectDeclaration,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<EntRef<'a>> {
match &mut object_decl.mode {
ModeIndication::Simple(mode) => {
let (subtype, class) =
self.analyze_simple_mode_indication(scope, mode, diagnostics)?;
Ok(self.arena.define(
&mut object_decl.ident,
parent,
AnyEntKind::Object(Object {
class,
iface: Some(ObjectInterface::simple(
object_decl.list_type,
mode.mode.unwrap_or_default(),
)),
subtype,
has_default: mode.expression.is_some(),
}),
None,
))
}
ModeIndication::View(view) => {
let resolved =
self.name_resolve(scope, &view.name.pos, &mut view.name.item, diagnostics)?;
let view_ent = self.resolve_view_ent(&resolved, diagnostics, &view.name.pos)?;
if let Some(ast_declared_subtype) = &mut view.subtype_indication {
let declared_subtype =
self.resolve_subtype_indication(scope, ast_declared_subtype, diagnostics)?;
if declared_subtype.type_mark() != view_ent.subtype().type_mark() {
bail!(
diagnostics,
Diagnostic::new(
&ast_declared_subtype.type_mark,
"Specified subtype must match the subtype declared for the view",
ErrorCode::TypeMismatch
)
);
}
}
Ok(self.arena.define(
&mut object_decl.ident,
parent,
AnyEntKind::Object(Object {
class: ObjectClass::Signal,
iface: Some(ObjectInterface::Port(InterfaceMode::View(view_ent))),
subtype: *view_ent.subtype(),
has_default: false,
}),
None,
))
}
}
}

pub fn analyze_simple_mode_indication(
&self,
scope: &Scope<'a>,
mode: &mut SimpleModeIndication,
diagnostics: &mut dyn DiagnosticHandler,
) -> EvalResult<(Subtype<'a>, ObjectClass)> {
let subtype =
self.resolve_subtype_indication(scope, &mut mode.subtype_indication, diagnostics);

if let Some(ref mut expression) = mode.expression {
if let Ok(ref subtype) = subtype {
self.expr_pos_with_ttyp(
scope,
subtype.type_mark(),
&expression.pos,
&mut expression.item,
diagnostics,
)?;
} else {
self.expr_unknown_ttyp(scope, expression, diagnostics)?
}
}

Ok((subtype?, mode.class))
}

pub fn analyze_interface_list(
&self,
scope: &Scope<'a>,
Expand Down Expand Up @@ -1020,6 +1151,7 @@ fn get_entity_class(ent: EntRef) -> Option<EntityClass> {
Design::InterfacePackageInstance(_) => None,
Design::Context(_) => None,
},
AnyEntKind::View(_) => None,
}
}

Expand All @@ -1043,3 +1175,46 @@ fn find_full_type_definition<'a>(
}
None
}

const UNASSOCIATED_DISPLAY_THRESHOLD: usize = 3;

/// Pretty formats a hash set with unassociated record elements.
/// This is for an improved user experience.
/// The returned message has the format "Missing association of x".
/// * If there is only one element, the message becomes "Missing association of element the_element"
/// * If there are more elements, the message becomes
/// "Missing association of element the_element1, the_element2 and the_element3"
/// * If there are more elements than [UNASSOCIATED_DISPLAY_THRESHOLD], the message will be truncated
/// to "Missing association of element the_element1, the_element2, the_element3 and 17 more"
fn pretty_format_unassociated_message(unassociated: &HashSet<&RecordElement>) -> String {
assert!(
!unassociated.is_empty(),
"Should never be called with an empty set"
);
let mut as_string_vec = unassociated
.iter()
.sorted_by_key(|el| el.decl_pos())
.map(|el| el.designator().describe())
.collect_vec();
let description = if as_string_vec.len() == 1 {
as_string_vec.pop().unwrap()
} else if as_string_vec.len() > UNASSOCIATED_DISPLAY_THRESHOLD {
let mut desc = as_string_vec[..UNASSOCIATED_DISPLAY_THRESHOLD].join(", ");
desc.push_str(" and ");
desc.push_str(&(as_string_vec.len() - UNASSOCIATED_DISPLAY_THRESHOLD).to_string());
desc.push_str(" more");
desc
} else {
// len > 1, therefore we always have a last element
let last = as_string_vec.pop().unwrap();
let mut desc = as_string_vec.join(", ");
desc.push_str(" and ");
desc.push_str(&last);
desc
};
if unassociated.len() == 1 {
format!("Missing association of element {}", description)
} else {
format!("Missing association of elements {}", description)
}
}
Loading

0 comments on commit 5af68f9

Please sign in to comment.