Add Formatting Capabilities (#321)

This PR enables the initial support for formatting VHDL language elements and files. The formatter is transforms the AST representation into a string. It is capable of formatting any AST element and preserving comments as well as newlines.
This commit is contained in:
Lukas Scheller 2024-07-28 16:23:56 +02:00 committed by GitHub
parent f5546fae78
commit 86eb38e8b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
78 changed files with 7746 additions and 993 deletions

1
clippy.toml Normal file
View file

@ -0,0 +1 @@
ignore-interior-mutability = ["vhdl_lang::data::source::UniqueSource"]

View file

@ -38,7 +38,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
self.analyze_expression_for_target(scope, ttyp, item, diagnostics)?;
self.boolean_expr(scope, condition, diagnostics)?;
}
if let Some(expr) = else_item {
if let Some((expr, _)) = else_item {
self.analyze_expression_for_target(scope, ttyp, expr, diagnostics)?;
}
}
@ -48,7 +48,12 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
alternatives,
} = selection;
let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?;
for Alternative { choices, item } in alternatives.iter_mut() {
for Alternative {
choices,
item,
span: _,
} in alternatives.iter_mut()
{
self.analyze_expression_for_target(scope, ttyp, item, diagnostics)?;
self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?;
}
@ -83,7 +88,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
self.analyze_waveform(scope, ttyp, item, diagnostics)?;
self.boolean_expr(scope, condition, diagnostics)?;
}
if let Some(wavf) = else_item {
if let Some((wavf, _)) = else_item {
self.analyze_waveform(scope, ttyp, wavf, diagnostics)?;
}
}
@ -93,7 +98,12 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
alternatives,
} = selection;
let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?;
for Alternative { choices, item } in alternatives.iter_mut() {
for Alternative {
choices,
item,
span: _,
} in alternatives.iter_mut()
{
self.analyze_waveform(scope, ttyp, item, diagnostics)?;
self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?;
}
@ -119,7 +129,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
}
}
Waveform::Unaffected => {}
Waveform::Unaffected(_) => {}
}
Ok(())
}

View file

@ -184,7 +184,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
// it must be a type conversion or a single parameter function call
let (pos, resolved_formal) = if let Some((inner_pos, inner_name)) =
to_formal_conversion_argument(&mut fcall.parameters)
to_formal_conversion_argument(&mut fcall.parameters.items)
{
(
inner_pos,
@ -250,7 +250,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
bail!(
diagnostics,
Diagnostic::new(
&fcall.name.pos(self.ctx),
fcall.name.pos(self.ctx),
format!(
"No function '{}' accepting {}",
fcall.name,
@ -372,7 +372,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
result.push((actual.span, Some(formal)));
} else {
diagnostics.add(
&actual.pos(self.ctx),
actual.pos(self.ctx),
"Unexpected extra argument",
ErrorCode::TooManyArguments,
);

View file

@ -119,7 +119,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
..
} = process;
if let Some(sensitivity_list) = sensitivity_list {
match sensitivity_list {
match &mut sensitivity_list.item {
SensitivityList::Names(names) => {
self.sensitivity_list_check(scope, names, diagnostics)?;
}
@ -165,7 +165,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
let nested = scope.nested();
self.analyze_generate_body(&nested, parent, item, src_span, diagnostics)?;
}
if let Some(ref mut else_item) = else_item {
if let Some((ref mut else_item, _)) = else_item {
let nested = scope.nested();
self.analyze_generate_body(&nested, parent, else_item, src_span, diagnostics)?;
}
@ -186,6 +186,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
let Alternative {
ref mut choices,
ref mut item,
span: _,
} = alternative;
self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?;
let nested = scope.nested();
@ -258,7 +259,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
// Pre-declare labels
self.define_labels_for_concurrent_part(scope, parent, statements, diagnostics)?;
if let Some(ref mut decl) = decl {
if let Some((ref mut decl, _)) = decl {
self.analyze_declarative_part(scope, parent, decl, diagnostics)?;
}
self.analyze_concurrent_part(scope, inner_parent, statements, diagnostics)?;
@ -456,7 +457,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
))? {
if object_name.base.class() != ObjectClass::Signal {
diagnostics.add(
&name.pos(self.ctx),
name.pos(self.ctx),
format!(
"{} is not a signal and cannot be in a sensitivity list",
object_name.base.describe_class()
@ -467,7 +468,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
&& !object_name.base.is_port()
{
diagnostics.add(
&name.pos(self.ctx),
name.pos(self.ctx),
format!(
"{} cannot be in a sensitivity list",
object_name.base.describe_class()

View file

@ -234,6 +234,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
name,
subtype_indication,
signature,
is_token: _,
} = alias;
let resolved_name = self.name_resolve(scope, name.span, &mut name.item, diagnostics);
@ -283,7 +284,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
));
}
diagnostics.add(
&name.pos(self.ctx),
name.pos(self.ctx),
format!("{} cannot be aliased", resolved_name.describe_type()),
ErrorCode::MismatchedKinds,
);
@ -447,7 +448,8 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
Declaration::File(ref mut file) => {
let FileDeclaration {
ident,
idents,
colon_token: _,
subtype_indication,
open_info,
file_name,
@ -459,18 +461,20 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
diagnostics,
))?;
if let Some(ref mut expr) = open_info {
if let Some((_, ref mut expr)) = open_info {
self.expr_unknown_ttyp(scope, expr, diagnostics)?;
}
if let Some(ref mut expr) = file_name {
if let Some((_, ref mut expr)) = file_name {
self.expr_unknown_ttyp(scope, expr, diagnostics)?;
}
if let Some(subtype) = subtype {
scope.add(
self.define(ident, parent, AnyEntKind::File(subtype), src_span),
diagnostics,
);
for ident in idents {
scope.add(
self.define(ident, parent, AnyEntKind::File(subtype), src_span),
diagnostics,
);
}
}
}
Declaration::Component(ref mut component) => {
@ -625,7 +629,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
Type::Record(region) => region,
_ => {
let diag = Diagnostic::new(
&view.typ.type_mark.pos(self.ctx),
view.typ.type_mark.pos(self.ctx),
format!(
"The type of a view must be a record type, not {}",
typ.type_mark().describe()
@ -641,17 +645,17 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
};
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());
for name in element.names.iter_mut() {
let desi = Designator::Identifier(name.tree.item.clone());
let Some(record_element) = record_region.lookup(&desi) else {
diagnostics.push(Diagnostic::new(
name.item.pos(self.ctx),
name.pos(self.ctx),
format!("Not a part of {}", typ.type_mark().describe()),
ErrorCode::Unresolved,
));
continue;
};
name.set_unique_reference(&record_element);
name.decl.set_unique_reference(&record_element);
unassociated.remove(&record_element);
}
}
@ -690,6 +694,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
entity_name,
entity_class,
expr,
colon_token: _,
} = attr_spec;
let attr_ent = match scope.lookup(
@ -744,7 +749,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
if let Some(signature) = signature {
diagnostics.push(Diagnostic::should_not_have_signature(
"Attribute specification",
&signature.pos(self.ctx),
signature.pos(self.ctx),
));
}
ent
@ -954,7 +959,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
class,
iface: Some(ObjectInterface::simple(
object_decl.list_type,
mode.mode.unwrap_or_default(),
mode.mode.as_ref().map(|mode| mode.item).unwrap_or_default(),
)),
subtype,
has_default: mode.expression.is_some(),
@ -968,14 +973,14 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
let resolved =
self.name_resolve(scope, view.name.span, &mut view.name.item, diagnostics)?;
let view_ent = self.resolve_view_ent(&resolved, diagnostics, view.name.span)?;
if let Some(ast_declared_subtype) = &mut view.subtype_indication {
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.pos(self.ctx),
ast_declared_subtype.type_mark.pos(self.ctx),
"Specified subtype must match the subtype declared for the view",
ErrorCode::TypeMismatch
)
@ -1061,7 +1066,9 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
ArrayIndex::IndexSubtypeDefintion(ref mut type_mark) => self
.type_name(scope, type_mark.span, &mut type_mark.item, diagnostics)
.map(|typ| typ.base()),
ArrayIndex::Discrete(ref mut drange) => self.drange_type(scope, drange, diagnostics),
ArrayIndex::Discrete(ref mut drange) => {
self.drange_type(scope, &mut drange.item, diagnostics)
}
}
}
}

View file

@ -420,7 +420,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
ResolvedName::Library(ref library_name) => {
if library_name != self.work_library_name() {
diagnostics.add(
&prefix.pos(self.ctx),
prefix.pos(self.ctx),
format!("Configuration must be within the same library '{}' as the corresponding entity", self.work_library_name()), ErrorCode::ConfigNotInSameLibrary);
Err(EvalError::Unknown)
} else {
@ -451,14 +451,14 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
}
other => {
diagnostics.push(other.kind_error(&prefix.pos(self.ctx), "library"));
diagnostics.push(other.kind_error(prefix.pos(self.ctx), "library"));
Err(EvalError::Unknown)
}
}
}
_ => {
diagnostics.add(
&ent_name.pos(self.ctx),
ent_name.pos(self.ctx),
"Expected selected name",
ErrorCode::MismatchedKinds,
);
@ -480,7 +480,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
bail!(
diagnostics,
Diagnostic::mismatched_kinds(
&prefix.pos(self.ctx),
prefix.pos(self.ctx),
"Invalid prefix of a selected name",
)
);
@ -490,7 +490,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
bail!(
diagnostics,
Diagnostic::mismatched_kinds(
&prefix.pos(self.ctx),
prefix.pos(self.ctx),
"'.all' may not be the prefix of a selected name",
)
);
@ -531,7 +531,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
| Name::External(..) => {
bail!(
diagnostics,
Diagnostic::mismatched_kinds(&name.pos(self.ctx), "Invalid selected name",)
Diagnostic::mismatched_kinds(name.pos(self.ctx), "Invalid selected name",)
);
}
}
@ -548,7 +548,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
ContextItem::Library(LibraryClause {
ref mut name_list, ..
}) => {
for library_name in name_list.items.iter_mut() {
for library_name in name_list.iter_mut() {
if self.work_sym == library_name.item.item {
library_name.set_unique_reference(self.work_library());
diagnostics.add(
@ -577,12 +577,12 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
ContextItem::Context(ContextReference {
ref mut name_list, ..
}) => {
for name in name_list.items.iter_mut() {
for name in name_list.iter_mut() {
match name.item {
Name::Selected(..) => {}
_ => {
diagnostics.add(
&name.pos(self.ctx),
name.pos(self.ctx),
"Context reference must be a selected name",
ErrorCode::MismatchedKinds,
);
@ -639,13 +639,13 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
use_clause: &mut UseClause,
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
for name in use_clause.name_list.items.iter_mut() {
for name in use_clause.name_list.iter_mut() {
match name.item {
Name::Selected(..) => {}
Name::SelectedAll(..) => {}
_ => {
diagnostics.add(
&name.pos(self.ctx),
name.pos(self.ctx),
"Use clause must be a selected name",
ErrorCode::MismatchedKinds,
);
@ -729,7 +729,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
}
diagnostics.add(
&package_name.pos(self.ctx),
package_name.pos(self.ctx),
format!("'{package_name}' is not an uninstantiated generic package"),
ErrorCode::MismatchedKinds,
);

View file

@ -487,6 +487,9 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
.resolve_subtype_indication(scope, subtype, diagnostics)
.map(|typ| ExpressionType::Unambiguous(typ.type_mark())),
},
Expression::Parenthesized(expr) => {
self.expr_pos_type(scope, expr.span, &mut expr.item, diagnostics)
}
Expression::Literal(ref mut literal) => match literal {
Literal::Physical(PhysicalLiteral { ref mut unit, .. }) => {
match self.resolve_physical_unit(scope, unit) {
@ -678,7 +681,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
std::cmp::Ordering::Greater => {
let mut diag = Diagnostic::new(
&expr.pos(self.ctx),
expr.pos(self.ctx),
"Ambiguous use of implicit boolean conversion ??",
ErrorCode::AmbiguousCall,
);
@ -842,7 +845,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
target_base,
indexes,
*elem_type,
assoc,
&mut assoc.item,
diagnostics,
))?;
}
@ -870,6 +873,15 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
Expression::New(ref mut alloc) => {
self.analyze_allocation(scope, alloc, diagnostics)?;
}
Expression::Parenthesized(ref mut expr) => {
self.expr_pos_with_ttyp(
scope,
target_type,
expr.span,
&mut expr.item,
diagnostics,
)?;
}
}
Ok(())
@ -878,11 +890,11 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
pub fn analyze_aggregate(
&self,
scope: &Scope<'a>,
assocs: &mut [ElementAssociation],
assocs: &mut [WithTokenSpan<ElementAssociation>],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
for assoc in assocs.iter_mut() {
match assoc {
match &mut assoc.item {
ElementAssociation::Named(ref mut choices, ref mut expr) => {
for choice in choices.iter_mut() {
match choice.item {
@ -911,14 +923,14 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
record_type: TypeEnt<'a>,
elems: &RecordRegion<'a>,
span: TokenSpan,
assocs: &mut [ElementAssociation],
assocs: &mut [WithTokenSpan<ElementAssociation>],
diagnostics: &mut dyn DiagnosticHandler,
) -> FatalResult {
let mut associated = RecordAssociations::default();
let mut is_ok_so_far = true;
for (idx, assoc) in assocs.iter_mut().enumerate() {
match assoc {
match &mut assoc.item {
ElementAssociation::Named(ref mut choices, ref mut actual_expr) => {
let typ = if choices.len() == 1 {
let choice = choices.first_mut().unwrap();
@ -949,7 +961,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
} else {
is_ok_so_far = false;
diagnostics.add(
&choice.pos(self.ctx),
choice.pos(self.ctx),
"Record aggregate choice must be a simple name",
ErrorCode::MismatchedKinds,
);
@ -959,7 +971,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
Choice::DiscreteRange(_) => {
is_ok_so_far = false;
diagnostics.add(
&choice.pos(self.ctx),
choice.pos(self.ctx),
"Record aggregate choice must be a simple name",
ErrorCode::MismatchedKinds,
);
@ -979,7 +991,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
.collect();
if remaining_types.len() > 1 {
let mut diag = Diagnostic::new(&choice.pos(self.ctx), format!("Other elements of record '{}' are not of the same type", record_type.designator()), ErrorCode::TypeMismatch);
let mut diag = Diagnostic::new(choice.pos(self.ctx), format!("Other elements of record '{}' are not of the same type", record_type.designator()), ErrorCode::TypeMismatch);
for elem in elems.iter() {
if !associated.is_associated(&elem) {
if let Some(decl_pos) = elem.decl_pos() {
@ -998,7 +1010,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
} else if remaining_types.is_empty() {
diagnostics.push(
Diagnostic::new(
&choice.pos(self.ctx),
choice.pos(self.ctx),
format!(
"All elements of record '{}' are already associated",
record_type.designator()
@ -1190,13 +1202,13 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
array_type,
&index_types[1..],
elem_type,
assoc,
&mut assoc.item,
diagnostics,
))?;
}
} else {
diagnostics.add(
&expr.pos(self.ctx),
expr.pos(self.ctx),
format!(
"Expected sub-aggregate for target {}",
array_type.describe()
@ -1280,7 +1292,7 @@ mod test {
use crate::analysis::tests::TestSetup;
use crate::data::DiagnosticHandler;
use crate::syntax::test::check_diagnostics;
use crate::syntax::test::without_releated;
use crate::syntax::test::without_related;
use crate::syntax::test::Code;
impl<'a> TestSetup<'a> {
@ -1438,7 +1450,7 @@ mod test {
assert_eq!(test.expr_type(&code, &mut diagnostics), None);
check_diagnostics(
without_releated(&diagnostics),
without_related(&diagnostics),
vec![Diagnostic::new(
code.s1("and"),
"Found no match for operator \"and\"",
@ -1476,7 +1488,7 @@ mod test {
assert_eq!(test.expr_type(&code, &mut diagnostics), None);
check_diagnostics(
without_releated(&diagnostics),
without_related(&diagnostics),
vec![Diagnostic::new(
code.s1("missing"),
"No declaration of 'missing'",

View file

@ -459,7 +459,7 @@ impl<'a> SplitName<'a> {
),
Name::CallOrIndexed(ref mut fcall) => SplitName::Suffix(
&mut fcall.name,
Suffix::CallOrIndexed(&mut fcall.parameters),
Suffix::CallOrIndexed(&mut fcall.parameters.items),
),
}
}
@ -789,7 +789,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
idx as usize
} else {
diagnostics.add(
&expr.span.pos(self.ctx),
expr.span.pos(self.ctx),
"Expected an integer literal",
ErrorCode::MismatchedKinds,
);
@ -810,7 +810,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
if let Some(expr) = expr {
let ndims = indexes.len();
let dimensions = plural("dimension", "dimensions", ndims);
diagnostics.add(&expr.pos(self.ctx), format!("Index {idx} out of range for array with {ndims} {dimensions}, expected 1 to {ndims}"), ErrorCode::DimensionMismatch);
diagnostics.add(expr.pos(self.ctx), format!("Index {idx} out of range for array with {ndims} {dimensions}, expected 1 to {ndims}"), ErrorCode::DimensionMismatch);
}
Err(EvalError::Unknown)
}
@ -1278,7 +1278,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
ResolvedName::Expression(DisambiguatedType::Ambiguous(types));
} else {
diagnostics.add(
&prefix.pos(self.ctx),
prefix.pos(self.ctx),
"Procedure calls are not valid in names and expressions",
ErrorCode::MismatchedKinds,
);
@ -1293,7 +1293,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
ResolvedName::Expression(DisambiguatedType::Unambiguous(typ));
} else {
diagnostics.add(
&prefix.pos(self.ctx),
prefix.pos(self.ctx),
"Procedure calls are not valid in names and expressions",
ErrorCode::MismatchedKinds,
);
@ -1356,7 +1356,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
ResolvedName::Expression(DisambiguatedType::Ambiguous(types));
} else {
diagnostics.add(
&prefix.pos(self.ctx),
prefix.pos(self.ctx),
"Procedure calls are not valid in names and expressions",
ErrorCode::MismatchedKinds,
);
@ -1371,7 +1371,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
);
} else {
diagnostics.add(
&prefix.pos(self.ctx),
prefix.pos(self.ctx),
"Procedure calls are not valid in names and expressions",
ErrorCode::MismatchedKinds,
);
@ -1959,7 +1959,7 @@ fn check_no_attr_argument(
) {
if let Some(ref expr) = suffix.expr {
diagnostics.add(
&expr.pos(ctx),
expr.pos(ctx),
format!("'{} attribute does not take an argument", suffix.attr),
ErrorCode::TooManyArguments,
)
@ -2259,7 +2259,7 @@ variable c0 : integer_vector(0 to 1);
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
&code.s1("c0"),
code.s1("c0"),
"variable 'c0' cannot be called as a function",
ErrorCode::InvalidCall,
)],

View file

@ -467,7 +467,7 @@ mod tests {
&self.scope,
&fcall.pos(&code.tokenize()),
&des,
&mut fcall.item.parameters,
&mut fcall.item.parameters.items,
overloaded::SubprogramKind::Function(ttyp),
overloaded.entities().collect(),
diagnostics,

View file

@ -76,7 +76,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
} else {
diagnostics.add(
&formal.pos(self.ctx),
formal.pos(self.ctx),
"Expected simple name for package generic formal",
ErrorCode::MismatchedKinds,
);
@ -86,7 +86,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
ent
} else {
diagnostics.add(
&assoc.actual.pos(self.ctx),
assoc.actual.pos(self.ctx),
"Extra actual for generic map",
ErrorCode::TooManyArguments,
);
@ -118,7 +118,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
} else {
diagnostics.add(
&assoc.actual.pos(self.ctx),
assoc.actual.pos(self.ctx),
format!(
"Array constraint cannot be used for {}",
typ.describe()
@ -141,7 +141,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
} else {
diagnostics.add(
&assoc.actual.pos(self.ctx),
assoc.actual.pos(self.ctx),
"Cannot map expression to type generic",
ErrorCode::MismatchedKinds,
);
@ -172,7 +172,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
name.set_unique_reference(&ent);
} else {
let mut diag = Diagnostic::mismatched_kinds(
&assoc.actual.pos(self.ctx),
assoc.actual.pos(self.ctx),
format!(
"Cannot map '{des}' to subprogram generic {}{}",
target.designator(),
@ -189,7 +189,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
} else {
diagnostics.add(
&assoc.actual.pos(self.ctx),
assoc.actual.pos(self.ctx),
format!(
"Cannot map {} to subprogram generic",
resolved.describe()
@ -201,14 +201,14 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
Expression::Literal(Literal::String(string)) => {
if Operator::from_latin1(string.clone()).is_none() {
diagnostics.add(
&assoc.actual.pos(self.ctx),
assoc.actual.pos(self.ctx),
"Invalid operator symbol",
ErrorCode::InvalidOperatorSymbol,
);
}
}
_ => diagnostics.add(
&assoc.actual.pos(self.ctx),
assoc.actual.pos(self.ctx),
"Cannot map expression to subprogram generic",
ErrorCode::MismatchedKinds,
),
@ -218,7 +218,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
self.name_resolve(scope, assoc.actual.span, name, diagnostics)?;
}
_ => diagnostics.add(
&assoc.actual.pos(self.ctx),
assoc.actual.pos(self.ctx),
"Cannot map expression to package generic",
ErrorCode::MismatchedKinds,
),

View file

@ -51,7 +51,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
Ok(DisambiguatedType::Unambiguous(typ))
} else {
diagnostics.add(
&expr.pos(self.ctx),
expr.pos(self.ctx),
format!("Non-scalar {} cannot be used in a range", typ.describe()),
ErrorCode::NonScalarInRange,
);
@ -63,7 +63,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
)),
ExpressionType::String | ExpressionType::Null | ExpressionType::Aggregate => {
diagnostics.add(
&expr.pos(self.ctx),
expr.pos(self.ctx),
"Non-scalar expression cannot be used in a range",
ErrorCode::NonScalarInRange,
);
@ -94,7 +94,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
ent.return_type().unwrap()
} else {
diagnostics.add(
&attr.name.pos(self.ctx),
attr.name.pos(self.ctx),
format!(
"{} cannot be prefix of range attribute, array type or object is required",
resolved.describe()
@ -113,7 +113,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
| ResolvedName::Library(_)
| ResolvedName::Design(_) => {
diagnostics.add(
&attr.name.pos(self.ctx),
attr.name.pos(self.ctx),
format!(
"{} cannot be prefix of range attribute, array type or object is required",
resolved.describe()
@ -144,7 +144,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
} else {
diagnostics.add(
&attr.name.pos(self.ctx),
attr.name.pos(self.ctx),
format!(
"{} cannot be prefix of range attribute, array type or object is required",
resolved.describe()
@ -270,7 +270,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
Ok(typ)
} else {
diagnostics.add(
&drange.span().pos(self.ctx),
drange.span().pos(self.ctx),
format!(
"Non-discrete {} cannot be used in discrete range",
typ.describe()
@ -327,7 +327,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
if let Some(ref mut signature) = signature {
diagnostics.add(
&signature.pos(self.ctx),
signature.pos(self.ctx),
format!("Did not expect signature for '{attr} attribute"),
ErrorCode::UnexpectedSignature,
);

View file

@ -1291,16 +1291,15 @@ impl<'a> EntHierarchy<'a> {
fn public_symbols<'a>(ent: EntRef<'a>) -> Box<dyn Iterator<Item = EntRef<'a>> + 'a> {
match ent.kind() {
AnyEntKind::Design(d) => match d {
AnyEntKind::Design(
Design::Entity(_, region)
| Design::Package(_, region)
| Design::UninstPackage(_, region) => Box::new(
region
.immediates()
.flat_map(|ent| std::iter::once(ent).chain(public_symbols(ent))),
),
_ => Box::new(std::iter::empty()),
},
| Design::UninstPackage(_, region),
) => Box::new(
region
.immediates()
.flat_map(|ent| std::iter::once(ent).chain(public_symbols(ent))),
),
AnyEntKind::Type(t) => match t {
Type::Protected(region, is_body) if !is_body => Box::new(region.immediates()),
_ => Box::new(std::iter::empty()),

View file

@ -75,7 +75,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
Some(resolved) => resolved,
None => {
// Continue checking missing names even if procedure is not found
self.analyze_assoc_elems(scope, parameters, diagnostics)?;
self.analyze_assoc_elems(scope, &mut parameters.items, diagnostics)?;
return Ok(());
}
};
@ -86,7 +86,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
scope,
&fcall_span.pos(self.ctx),
des,
parameters,
&mut parameters.items,
SubprogramKind::Procedure,
names.entities().collect(),
diagnostics,
@ -99,7 +99,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
if !ent.is_procedure() {
let mut diagnostic = Diagnostic::new(
&name.pos(self.ctx),
name.pos(self.ctx),
"Invalid procedure call",
ErrorCode::InvalidCall,
);
@ -114,7 +114,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
diagnostics.push(diagnostic);
} else if ent.is_uninst_subprogram_body() {
diagnostics.add(
&name.pos(self.ctx),
name.pos(self.ctx),
format!("uninstantiated {} cannot be called", ent.describe()),
ErrorCode::InvalidCall,
)
@ -143,20 +143,20 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
)?;
} else {
diagnostics.add(
&name.pos(self.ctx),
name.pos(self.ctx),
format!("{} is not a procedure", resolved.describe_type()),
ErrorCode::MismatchedKinds,
);
self.analyze_assoc_elems(scope, parameters, diagnostics)?;
self.analyze_assoc_elems(scope, &mut parameters.items, diagnostics)?;
}
}
resolved => {
diagnostics.add(
&name.pos(self.ctx),
name.pos(self.ctx),
format!("{} is not a procedure", resolved.describe_type()),
ErrorCode::MismatchedKinds,
);
self.analyze_assoc_elems(scope, parameters, diagnostics)?;
self.analyze_assoc_elems(scope, &mut parameters.items, diagnostics)?;
}
};

View file

@ -67,7 +67,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
diagnostics,
)?;
}
if let Some(else_item) = else_item {
if let Some((else_item, _)) = else_item {
self.define_labels_for_sequential_part(
scope,
parent,
@ -122,7 +122,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
self.expr_with_ttyp(scope, ttyp, expression, diagnostics)?;
} else {
diagnostics.add(
&statement.statement.pos(self.ctx),
statement.statement.pos(self.ctx),
"Functions cannot return without a value",
ErrorCode::VoidReturn,
);
@ -131,7 +131,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
SequentialRoot::Procedure => {
if expression.is_some() {
diagnostics.add(
&statement.statement.pos(self.ctx),
statement.statement.pos(self.ctx),
"Procedures cannot return a value",
ErrorCode::NonVoidReturn,
);
@ -139,7 +139,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
SequentialRoot::Process => {
diagnostics.add(
&statement.statement.pos(self.ctx),
statement.statement.pos(self.ctx),
"Cannot return from a process",
ErrorCode::IllegalReturn,
);
@ -152,7 +152,9 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
condition_clause,
timeout_clause,
} = wait_stmt;
self.sensitivity_list_check(scope, sensitivity_clause, diagnostics)?;
if let Some(list) = sensitivity_clause {
self.sensitivity_list_check(scope, list, diagnostics)?;
}
if let Some(expr) = condition_clause {
self.boolean_expr(scope, expr, diagnostics)?;
}
@ -191,7 +193,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
self.check_loop_label(scope, parent, loop_label, diagnostics);
} else if !find_outer_loop(parent, None) {
diagnostics.add(
&statement_span.pos(self.ctx),
statement_span.pos(self.ctx),
"Exit can only be used inside a loop",
ErrorCode::ExitOutsideLoop,
)
@ -211,7 +213,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
self.check_loop_label(scope, parent, loop_label, diagnostics);
} else if !find_outer_loop(parent, None) {
diagnostics.add(
&statement_span.pos(self.ctx),
statement_span.pos(self.ctx),
"Next can only be used inside a loop",
ErrorCode::NextOutsideLoop,
)
@ -233,20 +235,23 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
self.boolean_expr(scope, condition, diagnostics)?;
self.analyze_sequential_part(scope, parent, item, diagnostics)?;
}
if let Some(else_item) = else_item {
if let Some((else_item, _)) = else_item {
self.analyze_sequential_part(scope, parent, else_item, diagnostics)?;
}
}
SequentialStatement::Case(ref mut case_stmt) => {
let CaseStatement {
is_matching: _,
expression,
alternatives,
end_label_pos: _,
..
} = case_stmt;
let ctyp = as_fatal(self.expr_unambiguous_type(scope, expression, diagnostics))?;
for alternative in alternatives.iter_mut() {
let Alternative { choices, item } = alternative;
let Alternative {
choices,
item,
span: _,
} = alternative;
self.choice_with_ttyp(scope, ctyp, choices, diagnostics)?;
self.analyze_sequential_part(scope, parent, item, diagnostics)?;
}
@ -255,7 +260,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
let LoopStatement {
iteration_scheme,
statements,
end_label_pos: _,
..
} = loop_stmt;
match iteration_scheme {
Some(IterationScheme::For(ref mut index, ref mut drange)) => {

View file

@ -428,7 +428,6 @@ mod test_mod {
"XXXX01LH",
),
(BitString::new(None, BaseSpecifier::UO, "27"), "010111"),
// (BitString::new(None, BaseSpecifier::UO, "2C"), "011CCC"), // TODO: is this an error in the spec?
(BitString::new(None, BaseSpecifier::SX, "3W"), "0011WWWW"),
(BitString::new(None, BaseSpecifier::D, "35"), "100011"),
(

View file

@ -360,7 +360,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
resolved_ent
} else {
diagnostics.add(
&instantiation.subprogram_name.pos(self.ctx),
instantiation.subprogram_name.pos(self.ctx),
format!(
"No uninstantiated subprogram exists with signature {}",
key.describe()
@ -373,7 +373,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
// There are multiple candidates
// and there is no signature to resolve
let mut err = Diagnostic::new(
&instantiation.subprogram_name.pos(self.ctx),
instantiation.subprogram_name.pos(self.ctx),
format!("Ambiguous instantiation of '{}'", overloaded.designator()),
ErrorCode::AmbiguousInstantiation,
);
@ -388,7 +388,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
_ => {
diagnostics.add(
&instantiation.subprogram_name.pos(self.ctx),
instantiation.subprogram_name.pos(self.ctx),
format!(
"{} does not denote an uninstantiated subprogram",
name.describe()
@ -402,7 +402,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
Ok(overloaded_ent)
} else {
diagnostics.add(
&instantiation.subprogram_name.pos(self.ctx),
instantiation.subprogram_name.pos(self.ctx),
format!("{} cannot be instantiated", overloaded_ent.describe()),
ErrorCode::MismatchedKinds,
);

View file

@ -64,7 +64,7 @@ end package body;
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
&code.s1("a1"),
code.s1("a1"),
"Deferred constants are only allowed in package declarations (not body)",
ErrorCode::IllegalDeferredConstant,
)],
@ -88,11 +88,11 @@ end package;
check_diagnostics(
diagnostics,
vec![Diagnostic::new(
&code.s("a1", 1),
code.s("a1", 1),
"Deferred constant 'a1' lacks corresponding full constant declaration in package body",
ErrorCode::MissingDeferredDeclaration
),Diagnostic::new(
&code.s("a1", 2),
code.s("a1", 2),
"Full declaration of deferred constant is only allowed in a package body",
ErrorCode::IllegalDeferredConstant
)],
@ -123,12 +123,12 @@ end package body;
diagnostics,
vec![
Diagnostic::new(
&code.s1("a1"),
code.s1("a1"),
"Deferred constant 'a1' lacks corresponding full constant declaration in package body",
ErrorCode::MissingDeferredDeclaration
),
Diagnostic::new(
&code.s1("b1"),
code.s1("b1"),
"Deferred constant 'b1' lacks corresponding full constant declaration in package body",
ErrorCode::MissingDeferredDeclaration
),

View file

@ -77,7 +77,7 @@ end package;
let diagnostics = builder.analyze();
check_diagnostics(
without_releated(&diagnostics),
without_related(&diagnostics),
vec![Diagnostic::new(
code.sa("bad_to_string is ", "to_string"),
"Could not find declaration of 'to_string' with given signature",

View file

@ -312,7 +312,7 @@ fn check_analysis_equal(got: &mut DesignRoot, expected: &mut DesignRoot) -> Vec<
check_diagnostics(got_diagnostics.clone(), expected_diagnostics);
// Check that all references are equal, ensures the incremental
// analysis has cleared refereces
// analysis has cleared references
let mut got_searcher = FindAnyReferences::default();
let _ = got.search(&mut got_searcher);

View file

@ -79,7 +79,11 @@ impl MockTokenAccess {
}
impl TokenAccess for MockTokenAccess {
fn get_token(&self, _id: TokenId) -> &Token {
fn get_token(&self, _id: TokenId) -> Option<&Token> {
Some(&self.token)
}
fn index(&self, _id: TokenId) -> &Token {
&self.token
}

View file

@ -33,12 +33,12 @@ end package body;
diagnostics,
vec![
Diagnostic::new(
&code.s1("a1"),
code.s1("a1"),
"Missing body for protected type 'a1'",
ErrorCode::MissingProtectedBodyType,
),
Diagnostic::new(
&code.s1("b1"),
code.s1("b1"),
"Missing body for protected type 'b1'",
ErrorCode::MissingProtectedBodyType,
),
@ -75,17 +75,17 @@ end package body;
diagnostics,
vec![
Diagnostic::new(
&code.s1("a1"),
code.s1("a1"),
"No declaration of protected type 'a1'",
ErrorCode::Unresolved,
),
Diagnostic::new(
&code.s1("b1"),
code.s1("b1"),
"No declaration of protected type 'b1'",
ErrorCode::Unresolved,
),
Diagnostic::new(
&code.s("b1", 2),
code.s("b1", 2),
"Missing body for protected type 'b1'",
ErrorCode::MissingProtectedBodyType,
),
@ -224,7 +224,7 @@ end package body;
let expected = vec![
duplicate(&code, "a1", 1, 2),
Diagnostic::new(
&code.s("b1", 2),
code.s("b1", 2),
"'b1' is not a protected type",
ErrorCode::TypeMismatch,
),

View file

@ -265,13 +265,10 @@ end procedure;
let (root, diagnostics) = builder.get_analyzed_root();
check_no_diagnostics(&diagnostics);
if false {
// @TODO uncomment when analyzing for loop type
assert_eq!(
root.search_reference_pos(code.source(), code.s1("theproc(i)").start()),
Some(code.s1("theproc").pos())
);
}
assert_eq!(
root.search_reference_pos(code.source(), code.s1("theproc(i)").start()),
Some(code.s1("theproc").pos())
);
}
#[test]

View file

@ -694,7 +694,7 @@ signal bad2 : natural := string'(\"hello\");
diagnostics,
vec![
Diagnostic::new(
code.s1("(\"hello\")"),
code.s1("\"hello\""),
"string literal does not match integer type 'INTEGER'",
ErrorCode::TypeMismatch,
),

View file

@ -303,7 +303,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
}
}
}
TypeDefinition::Array(ref mut array_indexes, ref mut subtype_indication) => {
TypeDefinition::Array(ref mut array_indexes, _, ref mut subtype_indication) => {
let mut indexes: Vec<Option<BaseType>> = Vec::with_capacity(array_indexes.len());
for index in array_indexes.iter_mut() {
indexes.push(as_fatal(self.analyze_array_index(
@ -399,11 +399,11 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
scope.add(primary, diagnostics);
for (secondary_unit_name, value) in physical.secondary_units.iter_mut() {
match self.resolve_physical_unit(scope, &mut value.unit) {
match self.resolve_physical_unit(scope, &mut value.item.unit) {
Ok(secondary_unit_type) => {
if secondary_unit_type.base_type() != phys_type {
diagnostics.add(
value.unit.item.pos(self.ctx),
value.item.unit.item.pos(self.ctx),
format!(
"Physical unit of type '{}' does not match {}",
secondary_unit_type.designator(),
@ -451,7 +451,7 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
UniversalType::Real
} else {
diagnostics.add(
&range.span().pos(self.ctx),
range.span().pos(self.ctx),
"Expected real or integer range",
ErrorCode::TypeMismatch,
);
@ -558,11 +558,11 @@ impl<'a, 't> AnalyzeContext<'a, 't> {
self.drange_with_ttyp(
scope,
(*index_typ).into(),
drange,
&mut drange.item,
diagnostics,
)?;
} else {
self.drange_unknown_type(scope, drange, diagnostics)?;
self.drange_unknown_type(scope, &mut drange.item, diagnostics)?;
}
} else {
diagnostics.add(

View file

@ -18,11 +18,11 @@ pub mod token_range;
pub(crate) use self::util::*;
use crate::ast::token_range::*;
pub(crate) use any_design_unit::*;
use crate::data::*;
use crate::named_entity::{EntityId, Reference};
use crate::syntax::{Token, TokenAccess, TokenId};
pub(crate) use any_design_unit::*;
use vhdl_lang::HasTokenSpan;
/// LRM 15.8 Bit string literals
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
@ -178,6 +178,7 @@ pub enum ExternalPath {
pub struct ExternalName {
pub class: ExternalObjectClass,
pub path: WithTokenSpan<ExternalPath>,
pub colon_token: TokenId,
pub subtype: SubtypeIndication,
}
@ -197,7 +198,7 @@ pub enum Name {
#[derive(PartialEq, Debug, Clone)]
pub struct CallOrIndexed {
pub name: WithTokenSpan<Name>,
pub parameters: Vec<AssociationElement>,
pub parameters: SeparatedList<AssociationElement>,
}
/// LRM 9.3.3 Aggregates
@ -286,7 +287,7 @@ pub enum Expression {
Unary(WithToken<WithRef<Operator>>, Box<WithTokenSpan<Expression>>),
/// LRM 9.3.3 Aggregates
Aggregate(Vec<ElementAssociation>),
Aggregate(Vec<WithTokenSpan<ElementAssociation>>),
/// LRM 9.3.5 Qualified expressions
Qualified(Box<QualifiedExpression>),
@ -299,6 +300,7 @@ pub enum Expression {
/// LRM 9.3.7 Allocators
New(Box<WithTokenSpan<Allocator>>),
Parenthesized(Box<WithTokenSpan<Expression>>),
}
/// An identifier together with the lexical source location it occurs in.
@ -328,6 +330,12 @@ pub struct RangeConstraint {
pub right_expr: Box<WithTokenSpan<Expression>>,
}
impl RangeConstraint {
pub fn direction_token(&self) -> TokenId {
self.left_expr.span.end_token + 1
}
}
#[derive(PartialEq, Debug, Clone)]
pub enum Range {
Range(RangeConstraint),
@ -346,7 +354,7 @@ pub enum SubtypeConstraint {
Range(Range),
/// Empty Vec means Open
Array(
Vec<DiscreteRange>,
Vec<WithTokenSpan<DiscreteRange>>,
Option<Box<WithTokenSpan<SubtypeConstraint>>>,
),
Record(Vec<ElementConstraint>),
@ -364,27 +372,44 @@ pub struct RecordElementResolution {
pub enum ResolutionIndication {
FunctionName(WithTokenSpan<Name>),
ArrayElement(WithTokenSpan<Name>),
Record(Vec<RecordElementResolution>),
Unresolved,
Record(WithTokenSpan<Vec<RecordElementResolution>>),
}
impl HasTokenSpan for ResolutionIndication {
fn get_start_token(&self) -> TokenId {
match self {
ResolutionIndication::FunctionName(name) => name.get_start_token(),
ResolutionIndication::ArrayElement(name) => name.get_start_token() - 1,
ResolutionIndication::Record(record) => record.get_start_token(),
}
}
fn get_end_token(&self) -> TokenId {
match self {
ResolutionIndication::FunctionName(name) => name.get_end_token(),
ResolutionIndication::ArrayElement(name) => name.get_end_token() + 1,
ResolutionIndication::Record(record) => record.get_end_token(),
}
}
}
/// LRM 6.3 Subtype declarations
#[derive(PartialEq, Debug, Clone)]
pub struct SubtypeIndication {
pub resolution: ResolutionIndication,
pub resolution: Option<ResolutionIndication>,
pub type_mark: WithTokenSpan<Name>,
pub constraint: Option<WithTokenSpan<SubtypeConstraint>>,
}
/// LRM 5.3 Array Types
#[derive(PartialEq, Debug, Clone)]
#[derive(PartialEq, Debug, Clone, TokenSpan)]
pub enum ArrayIndex {
/// Unbounded
/// {identifier} range <>
IndexSubtypeDefintion(WithTokenSpan<Name>),
/// Constraint
Discrete(DiscreteRange),
Discrete(WithTokenSpan<DiscreteRange>),
}
/// LRM 5.3.3 Record types
@ -392,6 +417,7 @@ pub enum ArrayIndex {
#[derive(PartialEq, Debug, Clone)]
pub struct ElementDeclaration {
pub idents: Vec<WithDecl<Ident>>,
pub colon_token: TokenId,
pub subtype: SubtypeIndication,
}
@ -470,6 +496,7 @@ impl HasDesignator for WithToken<WithRef<Designator>> {
pub struct AliasDeclaration {
pub designator: WithDecl<WithToken<Designator>>,
pub subtype_indication: Option<SubtypeIndication>,
pub is_token: TokenId,
pub name: WithTokenSpan<Name>,
pub signature: Option<WithTokenSpan<Signature>>,
}
@ -526,6 +553,7 @@ pub enum EntityClass {
pub struct AttributeSpecification {
pub ident: WithRef<Ident>,
pub entity_name: EntityName,
pub colon_token: TokenId,
pub entity_class: EntityClass,
pub expr: WithTokenSpan<Expression>,
}
@ -553,8 +581,9 @@ pub struct ProtectedTypeBody {
#[derive(PartialEq, Debug, Clone)]
pub struct PhysicalTypeDeclaration {
pub range: Range,
pub units_token: TokenId,
pub primary_unit: WithDecl<Ident>,
pub secondary_units: Vec<(WithDecl<Ident>, PhysicalLiteral)>,
pub secondary_units: Vec<(WithDecl<Ident>, WithTokenSpan<PhysicalLiteral>)>,
}
/// LRM 5.2.2 Enumeration types
@ -578,7 +607,7 @@ pub enum TypeDefinition {
// @TODO floating
/// LRM 5.3 Composite Types
/// LRM 5.3.2 Array types
Array(Vec<ArrayIndex>, SubtypeIndication),
Array(Vec<ArrayIndex>, TokenId, SubtypeIndication),
/// LRM 5.3.3 Record types
Record(Vec<ElementDeclaration>),
/// LRM 5.4 Access types
@ -603,6 +632,17 @@ pub struct TypeDeclaration {
pub end_ident_pos: Option<TokenId>,
}
impl TypeDeclaration {
pub fn is_token(&self) -> Option<TokenId> {
if matches!(self.def, TypeDefinition::Incomplete(_)) {
// incomplete types have no `is` token
None
} else {
Some(self.ident.tree.token + 1)
}
}
}
/// LRM 6.4.2 Object Declarations
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum ObjectClass {
@ -622,6 +662,7 @@ pub enum InterfaceType {
#[derive(PartialEq, Debug, Clone)]
pub struct ObjectDeclaration {
pub class: ObjectClass,
pub colon_token: TokenId,
pub idents: Vec<WithDecl<Ident>>,
pub subtype_indication: SubtypeIndication,
pub expression: Option<WithTokenSpan<Expression>>,
@ -629,10 +670,11 @@ pub struct ObjectDeclaration {
#[derive(PartialEq, Debug, Clone)]
pub struct FileDeclaration {
pub ident: WithDecl<Ident>,
pub idents: Vec<WithDecl<Ident>>,
pub colon_token: TokenId,
pub subtype_indication: SubtypeIndication,
pub open_info: Option<WithTokenSpan<Expression>>,
pub file_name: Option<WithTokenSpan<Expression>>,
pub open_info: Option<(TokenId, WithTokenSpan<Expression>)>,
pub file_name: Option<(TokenId, WithTokenSpan<Expression>)>,
}
#[derive(PartialEq, Eq, Debug, Clone)]
@ -674,7 +716,9 @@ pub struct FunctionSpecification {
pub struct SubprogramBody {
pub specification: SubprogramSpecification,
pub declarations: Vec<WithTokenSpan<Declaration>>,
pub begin_token: TokenId,
pub statements: Vec<LabeledSequentialStatement>,
pub end_token: TokenId,
pub end_ident_pos: Option<TokenId>,
}
@ -729,6 +773,7 @@ pub struct SubprogramDeclaration {
#[derive(PartialEq, Debug, Clone)]
pub struct InterfaceFileDeclaration {
pub idents: Vec<WithDecl<Ident>>,
pub colon_token: TokenId,
pub subtype_indication: SubtypeIndication,
}
@ -737,6 +782,7 @@ pub struct InterfaceFileDeclaration {
#[derive(PartialEq, Debug, Clone)]
pub struct InterfaceObjectDeclaration {
pub list_type: InterfaceType,
pub colon_token: TokenId,
pub idents: Vec<WithDecl<Ident>>,
pub mode: ModeIndication,
}
@ -749,7 +795,7 @@ pub enum ModeIndication {
#[derive(PartialEq, Debug, Clone)]
pub struct SimpleModeIndication {
pub mode: Option<Mode>,
pub mode: Option<WithToken<Mode>>,
pub class: ObjectClass,
pub subtype_indication: SubtypeIndication,
pub bus: bool,
@ -762,11 +808,12 @@ pub enum ModeViewIndicationKind {
Record,
}
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct ModeViewIndication {
pub kind: ModeViewIndicationKind,
pub name: WithTokenSpan<Name>,
pub subtype_indication: Option<SubtypeIndication>,
pub subtype_indication: Option<(TokenId, SubtypeIndication)>,
}
/// LRM 6.5.5 Interface package declaration
@ -783,7 +830,7 @@ pub enum InterfacePackageGenericMapAspect {
pub struct InterfacePackageDeclaration {
pub ident: WithDecl<Ident>,
pub package_name: WithTokenSpan<Name>,
pub generic_map: InterfacePackageGenericMapAspect,
pub generic_map: WithTokenSpan<InterfacePackageGenericMapAspect>,
}
#[derive(PartialEq, Debug, Clone)]
@ -825,8 +872,10 @@ pub enum Mode {
#[derive(PartialEq, Debug, Clone)]
pub struct ComponentDeclaration {
pub ident: WithDecl<Ident>,
pub is_token: Option<TokenId>,
pub generic_list: Option<InterfaceList>,
pub port_list: Option<InterfaceList>,
pub end_token: TokenId,
pub end_ident_pos: Option<TokenId>,
}
@ -849,14 +898,37 @@ pub enum Declaration {
impl Declaration {
pub fn declarations(&self) -> Vec<EntityId> {
todo!()
match self {
Declaration::Object(ObjectDeclaration { idents, .. })
| Declaration::File(FileDeclaration { idents, .. }) => {
idents.iter().flat_map(|ident| ident.decl.get()).collect()
}
Declaration::Type(TypeDeclaration { ident, .. })
| Declaration::Component(ComponentDeclaration { ident, .. })
| Declaration::View(ModeViewDeclaration { ident, .. })
| Declaration::Package(PackageInstantiation { ident, .. })
| Declaration::SubprogramInstantiation(SubprogramInstantiation { ident, .. })
| Declaration::Attribute(Attribute::Declaration(AttributeDeclaration {
ident, ..
})) => ident.decl.get().into_iter().collect(),
Declaration::Alias(alias) => alias.designator.decl.get().into_iter().collect(),
Declaration::SubprogramDeclaration(SubprogramDeclaration { specification, .. })
| Declaration::SubprogramBody(SubprogramBody { specification, .. }) => {
let designator = match specification {
SubprogramSpecification::Procedure(procedure) => &procedure.designator,
SubprogramSpecification::Function(function) => &function.designator,
};
designator.decl.get().into_iter().collect()
}
_ => vec![],
}
}
}
/// LRM 10.2 Wait statement
#[derive(PartialEq, Debug, Clone)]
pub struct WaitStatement {
pub sensitivity_clause: Vec<WithTokenSpan<Name>>,
pub sensitivity_clause: Option<Vec<WithTokenSpan<Name>>>,
pub condition_clause: Option<WithTokenSpan<Expression>>,
pub timeout_clause: Option<WithTokenSpan<Expression>>,
}
@ -880,7 +952,7 @@ pub struct ReportStatement {
#[derive(PartialEq, Debug, Clone)]
pub enum Target {
Name(Name),
Aggregate(Vec<ElementAssociation>),
Aggregate(Vec<WithTokenSpan<ElementAssociation>>),
}
/// LRM 10.5 Signal assignment statement
@ -890,11 +962,24 @@ pub struct WaveformElement {
pub after: Option<WithTokenSpan<Expression>>,
}
impl HasTokenSpan for WaveformElement {
fn get_start_token(&self) -> TokenId {
self.value.get_start_token()
}
fn get_end_token(&self) -> TokenId {
self.after
.as_ref()
.map(|expr| expr.get_end_token())
.unwrap_or(self.value.get_end_token())
}
}
/// LRM 10.5 Signal assignment statement
#[derive(PartialEq, Debug, Clone)]
pub enum Waveform {
Elements(Vec<WaveformElement>),
Unaffected,
Unaffected(TokenId),
}
/// LRM 10.5 Signal assignment statement
@ -910,7 +995,7 @@ pub enum DelayMechanism {
#[derive(PartialEq, Debug, Clone)]
pub struct SignalAssignment {
pub target: WithTokenSpan<Target>,
pub delay_mechanism: Option<DelayMechanism>,
pub delay_mechanism: Option<WithTokenSpan<DelayMechanism>>,
pub rhs: AssignmentRightHand<Waveform>,
}
@ -959,7 +1044,7 @@ pub struct Conditional<T> {
#[derive(PartialEq, Debug, Clone)]
pub struct Conditionals<T> {
pub conditionals: Vec<Conditional<T>>,
pub else_item: Option<T>,
pub else_item: Option<(T, TokenId)>,
}
/// LRM 10.8 If statement
@ -969,6 +1054,7 @@ pub struct IfStatement {
pub end_label_pos: Option<SrcPos>,
}
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct Alternative<T> {
pub choices: Vec<WithTokenSpan<Choice>>,
@ -987,6 +1073,7 @@ pub struct CaseStatement {
pub is_matching: bool,
pub expression: WithTokenSpan<Expression>,
pub alternatives: Vec<Alternative<Vec<LabeledSequentialStatement>>>,
pub end_token: TokenId,
pub end_label_pos: Option<SrcPos>,
}
@ -1001,7 +1088,9 @@ pub enum IterationScheme {
#[derive(PartialEq, Debug, Clone)]
pub struct LoopStatement {
pub iteration_scheme: Option<IterationScheme>,
pub loop_token: TokenId,
pub statements: Vec<LabeledSequentialStatement>,
pub end_token: TokenId,
pub end_label_pos: Option<SrcPos>,
}
@ -1058,8 +1147,11 @@ pub struct LabeledSequentialStatement {
pub struct BlockStatement {
pub guard_condition: Option<WithTokenSpan<Expression>>,
pub header: BlockHeader,
pub is_token: Option<TokenId>,
pub decl: Vec<WithTokenSpan<Declaration>>,
pub begin_token: TokenId,
pub statements: Vec<LabeledConcurrentStatement>,
pub end_token: TokenId,
pub end_label_pos: Option<SrcPos>,
}
@ -1083,9 +1175,12 @@ pub enum SensitivityList {
#[derive(PartialEq, Debug, Clone)]
pub struct ProcessStatement {
pub postponed: bool,
pub sensitivity_list: Option<SensitivityList>,
pub sensitivity_list: Option<WithTokenSpan<SensitivityList>>,
pub is_token: Option<TokenId>,
pub decl: Vec<WithTokenSpan<Declaration>>,
pub begin_token: TokenId,
pub statements: Vec<LabeledSequentialStatement>,
pub end_token: TokenId,
pub end_label_pos: Option<SrcPos>,
}
@ -1130,12 +1225,10 @@ impl InstantiatedUnit {
}
}
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct MapAspect {
// `generic` or `map`
pub start: TokenId,
pub list: SeparatedList<AssociationElement>,
pub closing_paren: TokenId,
}
impl MapAspect {
@ -1143,11 +1236,6 @@ impl MapAspect {
pub fn formals(&self) -> impl Iterator<Item = Option<EntityId>> + '_ {
self.list.formals()
}
/// Returns the span that this aspect encompasses
pub fn span(&self, ctx: &dyn TokenAccess) -> SrcPos {
ctx.get_span(self.start, self.closing_paren)
}
}
/// 11.7 Component instantiation statements
@ -1170,9 +1258,10 @@ impl InstantiationStatement {
#[derive(PartialEq, Debug, Clone)]
pub struct GenerateBody {
pub alternative_label: Option<WithDecl<Ident>>,
pub decl: Option<Vec<WithTokenSpan<Declaration>>>,
pub decl: Option<(Vec<WithTokenSpan<Declaration>>, TokenId)>,
pub statements: Vec<LabeledConcurrentStatement>,
pub end_label_pos: Option<SrcPos>,
pub end_token: Option<TokenId>,
pub end_label: Option<TokenId>,
}
/// 11.8 Generate statements
@ -1181,7 +1270,9 @@ pub struct GenerateBody {
pub struct ForGenerateStatement {
pub index_name: WithDecl<Ident>,
pub discrete_range: DiscreteRange,
pub generate_token: TokenId,
pub body: GenerateBody,
pub end_token: TokenId,
pub end_label_pos: Option<SrcPos>,
}
@ -1197,6 +1288,7 @@ pub struct IfGenerateStatement {
#[derive(PartialEq, Debug, Clone)]
pub struct CaseGenerateStatement {
pub sels: Selection<GenerateBody>,
pub end_token: TokenId,
pub end_label_pos: Option<SrcPos>,
}
@ -1205,14 +1297,17 @@ pub struct CaseGenerateStatement {
pub struct ModeViewDeclaration {
pub ident: WithDecl<Ident>,
pub typ: SubtypeIndication,
pub is_token: TokenId,
pub elements: Vec<ModeViewElement>,
pub end_token: TokenId,
pub end_ident_pos: Option<TokenId>,
}
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct ModeViewElement {
pub names: IdentList,
pub names: Vec<WithDecl<Ident>>,
pub colon_token: TokenId,
pub mode: ElementMode,
}
@ -1248,7 +1343,7 @@ pub struct LabeledConcurrentStatement {
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct LibraryClause {
pub name_list: IdentList,
pub name_list: Vec<WithRef<Ident>>,
}
/// Represents a token-separated list of some generic type `T`
@ -1280,21 +1375,27 @@ impl SeparatedList<AssociationElement> {
}
}
pub type IdentList = SeparatedList<WithRef<Ident>>;
pub type NameList = SeparatedList<WithTokenSpan<Name>>;
impl<T> SeparatedList<T> {
pub fn single(item: T) -> SeparatedList<T> {
SeparatedList {
items: vec![item],
tokens: vec![],
}
}
}
/// LRM 12.4. Use clauses
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct UseClause {
pub name_list: NameList,
pub name_list: Vec<WithTokenSpan<Name>>,
}
/// LRM 13.4 Context clauses
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct ContextReference {
pub name_list: NameList,
pub name_list: Vec<WithTokenSpan<Name>>,
}
/// LRM 13.4 Context clauses
@ -1311,6 +1412,7 @@ pub enum ContextItem {
pub struct ContextDeclaration {
pub ident: WithDecl<Ident>,
pub items: ContextClause,
pub end_token: TokenId,
pub end_ident_pos: Option<TokenId>,
}
@ -1341,6 +1443,7 @@ pub enum EntityAspect {
}
/// LRM 7.3.2 Binding indication
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct BindingIndication {
pub entity_aspect: Option<EntityAspect>,
@ -1349,13 +1452,16 @@ pub struct BindingIndication {
}
/// LRM 7.3 Configuration specification
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct ComponentSpecification {
pub instantiation_list: InstantiationList,
pub colon_token: TokenId,
pub component_name: WithTokenSpan<Name>,
}
/// LRM 7.3.4 Verification unit binding indication
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct VUnitBindingIndication {
pub vunit_list: Vec<WithTokenSpan<Name>>,
@ -1368,17 +1474,11 @@ pub struct ConfigurationSpecification {
pub spec: ComponentSpecification,
pub bind_ind: BindingIndication,
pub vunit_bind_inds: Vec<VUnitBindingIndication>,
pub end_token: Option<TokenId>,
}
/// LRM 3.4 Configuration declarations
#[derive(PartialEq, Debug, Clone)]
pub enum ConfigurationDeclarativeItem {
Use(UseClause),
// @TODO attribute
// @TODO group
}
/// LRM 3.4 Configuration declarations
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct ComponentConfiguration {
pub spec: ComponentSpecification,
@ -1395,6 +1495,7 @@ pub enum ConfigurationItem {
}
/// LRM 3.4 Configuration declarations
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
pub struct BlockConfiguration {
pub block_spec: WithTokenSpan<Name>,
@ -1409,9 +1510,10 @@ pub struct ConfigurationDeclaration {
pub context_clause: ContextClause,
pub ident: WithDecl<Ident>,
pub entity_name: WithTokenSpan<Name>,
pub decl: Vec<ConfigurationDeclarativeItem>,
pub decl: Vec<WithTokenSpan<Declaration>>,
pub vunit_bind_inds: Vec<VUnitBindingIndication>,
pub block_config: BlockConfiguration,
pub end_token: TokenId,
pub end_ident_pos: Option<TokenId>,
}
@ -1424,10 +1526,20 @@ pub struct EntityDeclaration {
pub generic_clause: Option<InterfaceList>,
pub port_clause: Option<InterfaceList>,
pub decl: Vec<WithTokenSpan<Declaration>>,
pub begin_token: Option<TokenId>,
pub statements: Vec<LabeledConcurrentStatement>,
/// The `end` token from the declaration `*end* entity foo;`
pub end_token: TokenId,
pub end_ident_pos: Option<TokenId>,
}
impl EntityDeclaration {
/// The `is` token from the declaration `entity foo *is*`
pub fn is_token(&self) -> TokenId {
self.span.start_token + 2
}
}
/// LRM 3.3 Architecture bodies
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
@ -1438,9 +1550,18 @@ pub struct ArchitectureBody {
pub begin_token: TokenId,
pub decl: Vec<WithTokenSpan<Declaration>>,
pub statements: Vec<LabeledConcurrentStatement>,
pub end_token: TokenId,
pub end_ident_pos: Option<TokenId>,
}
impl ArchitectureBody {
/// Location of the `is` token from
/// `architecture arch of ent is`
pub fn is_token(&self) -> TokenId {
self.span.start_token + 4
}
}
/// LRM 4.7 Package declarations
#[with_token_span]
#[derive(PartialEq, Debug, Clone)]
@ -1449,6 +1570,7 @@ pub struct PackageDeclaration {
pub ident: WithDecl<Ident>,
pub generic_clause: Option<InterfaceList>,
pub decl: Vec<WithTokenSpan<Declaration>>,
pub end_token: TokenId,
pub end_ident_pos: Option<TokenId>,
}
@ -1459,6 +1581,7 @@ pub struct PackageBody {
pub context_clause: ContextClause,
pub ident: WithDecl<Ident>,
pub decl: Vec<WithTokenSpan<Declaration>>,
pub end_token: TokenId,
pub end_ident_pos: Option<TokenId>,
}

View file

@ -243,7 +243,7 @@ impl Display for CallOrIndexed {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "{}", self.name)?;
let mut first = true;
for param in &self.parameters {
for param in &self.parameters.items {
if first {
write!(f, "({param}")?;
} else {
@ -365,7 +365,7 @@ impl Display for QualifiedExpression {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self.expr.item {
Expression::Aggregate(..) => write!(f, "{}'{}", self.type_mark, self.expr),
_ => write!(f, "{}'({})", self.type_mark, self.expr),
_ => write!(f, "{}'{}", self.type_mark, self.expr),
}
}
}
@ -452,6 +452,7 @@ impl Display for Expression {
Expression::Name(ref name) => write!(f, "{name}"),
Expression::Literal(ref literal) => write!(f, "{literal}"),
Expression::New(ref alloc) => write!(f, "new {alloc}"),
Expression::Parenthesized(expr) => write!(f, "({expr})"),
}
}
}
@ -566,7 +567,7 @@ impl Display for ResolutionIndication {
}
ResolutionIndication::Record(elem_resolutions) => {
let mut first = true;
for elem_resolution in elem_resolutions {
for elem_resolution in &elem_resolutions.item {
if first {
write!(f, "({elem_resolution}")?;
} else {
@ -580,16 +581,14 @@ impl Display for ResolutionIndication {
Ok(())
}
}
ResolutionIndication::Unresolved => Ok(()),
}
}
}
impl Display for SubtypeIndication {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self.resolution {
ResolutionIndication::Unresolved => (),
_ => write!(f, "{} ", self.resolution)?,
if let Some(resolution) = &self.resolution {
write!(f, "{resolution} ")?;
}
write!(f, "{}", self.type_mark)?;
match self.constraint {
@ -693,7 +692,7 @@ impl Display for TypeDefinition {
}
write!(f, "end units;")
}
TypeDefinition::Array(ref indexes, ref subtype_indication) => {
TypeDefinition::Array(ref indexes, _, ref subtype_indication) => {
write!(f, " is array (")?;
let mut first = true;
for index in indexes {
@ -739,7 +738,7 @@ impl Display for TypeDefinition {
impl Display for TypeDeclaration {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self.def {
match &self.def {
TypeDefinition::Subtype(..) => write!(f, "subtype")?,
_ => write!(f, "type")?,
}
@ -779,12 +778,20 @@ impl Display for ObjectDeclaration {
impl Display for FileDeclaration {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "file {} : {}", self.ident, self.subtype_indication)?;
write!(
f,
"file {} : {}",
self.idents
.iter()
.map(|ident| format!("{ident}"))
.join(", "),
self.subtype_indication
)?;
if let Some(ref expr) = self.open_info {
write!(f, " open {expr}")?;
write!(f, " open {}", expr.1)?;
}
match self.file_name {
Some(ref expr) => write!(f, " is {expr};"),
Some(ref expr) => write!(f, " is {};", expr.1),
None => write!(f, ";"),
}
}
@ -961,7 +968,7 @@ impl Display for ModeIndication {
impl Display for SimpleModeIndication {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
if let Some(mode) = self.mode {
if let Some(mode) = &self.mode {
write!(f, "{mode} ")?;
}
write!(f, "{}", self.subtype_indication)?;
@ -982,7 +989,7 @@ impl Display for ModeViewIndication {
ModeViewIndicationKind::Array => write!(f, "({})", self.name)?,
ModeViewIndicationKind::Record => write!(f, "{}", self.name)?,
}
if let Some(typ) = &self.subtype_indication {
if let Some((_, typ)) = &self.subtype_indication {
write!(f, " of {typ}")?;
}
Ok(())
@ -1046,7 +1053,7 @@ impl Display for InterfacePackageDeclaration {
"package {} is new {}\n generic map (",
self.ident, self.package_name
)?;
match &self.generic_map {
match &self.generic_map.item {
InterfacePackageGenericMapAspect::Map(assoc_list) => {
let mut first = true;
for assoc in &assoc_list.items {

View file

@ -178,7 +178,7 @@ fn search_conditionals<T: Search>(
return_if_found!(item.search(ctx, searcher));
}
}
if let Some(expr) = else_item {
if let Some((expr, _)) = else_item {
return_if_found!(expr.search(ctx, searcher));
}
NotFound
@ -191,7 +191,11 @@ fn search_alternatives<T: Search>(
ctx: &dyn TokenAccess,
) -> SearchResult {
for alternative in alternatives.iter() {
let Alternative { choices, item } = alternative;
let Alternative {
choices,
item,
span: _,
} = &alternative;
if item_before_choice {
return_if_found!(item.search(ctx, searcher));
return_if_found!(choices.search(ctx, searcher));
@ -220,6 +224,7 @@ fn search_selection<T: Search>(
searcher,
ctx,
));
NotFound
}
@ -268,6 +273,12 @@ impl Search for WithTokenSpan<Choice> {
}
}
impl Search for WithTokenSpan<ElementAssociation> {
fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult {
self.item.search(ctx, searcher)
}
}
impl Search for WithTokenSpan<Target> {
fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult {
match self.item {
@ -362,7 +373,7 @@ impl Search for LabeledSequentialStatement {
let LoopStatement {
iteration_scheme,
statements,
end_label_pos: _,
..
} = loop_stmt;
match iteration_scheme {
Some(IterationScheme::For(ref index, ref drange)) => {
@ -431,7 +442,8 @@ impl Search for GenerateBody {
alternative_label,
decl,
statements,
end_label_pos,
end_label: end_label_pos,
..
} = self;
if let Some(ref label) = alternative_label {
return_if_found!(searcher
@ -441,13 +453,15 @@ impl Search for GenerateBody {
)
.or_not_found());
}
return_if_found!(decl.search(ctx, searcher));
if let Some((decl, _)) = decl {
return_if_found!(decl.search(ctx, searcher));
}
return_if_found!(statements.search(ctx, searcher));
if let Some(ref label) = alternative_label {
if let Some(end_label_pos) = end_label_pos {
return_if_found!(searcher
.search_pos_with_ref(ctx, end_label_pos, &label.decl)
.search_pos_with_ref(ctx, ctx.get_pos(*end_label_pos), &label.decl)
.or_not_found());
}
}
@ -517,7 +531,9 @@ impl Search for LabeledConcurrentStatement {
end_label_pos: _,
..
} = process;
return_if_found!(sensitivity_list.search(ctx, searcher));
if let Some(sensitivity_list) = sensitivity_list {
return_if_found!(sensitivity_list.item.search(ctx, searcher));
}
return_if_found!(decl.search(ctx, searcher));
return_if_found!(statements.search(ctx, searcher));
}
@ -687,7 +703,9 @@ impl Search for WithTokenSpan<SubtypeConstraint> {
return_if_finished!(searcher.search_with_pos(ctx, &self.pos(ctx)));
match self.item {
SubtypeConstraint::Array(ref dranges, ref constraint) => {
return_if_found!(dranges.search(ctx, searcher));
for drange in dranges {
return_if_found!(&drange.item.search(ctx, searcher));
}
if let Some(ref constraint) = constraint {
return_if_found!(constraint.search(ctx, searcher));
}
@ -773,7 +791,7 @@ impl Search for TypeDeclaration {
)
.or_not_found());
match self.def {
match &self.def {
TypeDefinition::ProtectedBody(ref body) => {
return_if_found!(body.decl.search(ctx, searcher));
}
@ -805,14 +823,14 @@ impl Search for TypeDeclaration {
TypeDefinition::Access(ref subtype_indication) => {
return_if_found!(subtype_indication.search(ctx, searcher));
}
TypeDefinition::Array(ref indexes, ref subtype_indication) => {
TypeDefinition::Array(ref indexes, _, ref subtype_indication) => {
for index in indexes.iter() {
match index {
ArrayIndex::IndexSubtypeDefintion(ref type_mark) => {
return_if_found!(type_mark.search(ctx, searcher));
}
ArrayIndex::Discrete(ref drange) => {
return_if_found!(drange.search(ctx, searcher));
return_if_found!(drange.item.search(ctx, searcher));
}
}
}
@ -849,6 +867,7 @@ impl Search for TypeDeclaration {
TypeDefinition::Physical(ref physical) => {
let PhysicalTypeDeclaration {
range,
units_token: _,
primary_unit,
secondary_units,
} = physical;
@ -868,11 +887,13 @@ impl Search for TypeDeclaration {
ctx,
FoundDeclaration::new(
&ident.decl,
DeclarationItem::PhysicalTypeSecondary(ident, literal)
DeclarationItem::PhysicalTypeSecondary(ident, &literal.item)
)
)
.or_not_found());
return_if_found!(searcher.search_ident_ref(ctx, &literal.unit).or_not_found());
return_if_found!(searcher
.search_ident_ref(ctx, &literal.item.unit)
.or_not_found());
}
}
}
@ -917,6 +938,9 @@ fn search_pos_expr(
}
_ => NotFound,
},
Expression::Parenthesized(expr) => {
search_pos_expr(ctx, &expr.span.pos(ctx), &expr.item, searcher)
}
}
}
@ -985,7 +1009,7 @@ impl Search for Waveform {
return_if_found!(after.search(ctx, searcher));
}
}
Waveform::Unaffected => {}
Waveform::Unaffected(_) => {}
}
NotFound
}
@ -1078,8 +1102,8 @@ impl Search for Declaration {
Declaration::Attribute(Attribute::Specification(AttributeSpecification {
ident,
entity_name,
entity_class: _,
expr,
..
})) => {
return_if_found!(searcher.search_ident_ref(ctx, ident).or_not_found());
if let EntityName::Name(EntityTag {
@ -1108,10 +1132,10 @@ impl Search for Declaration {
)
.or_not_found());
let AliasDeclaration {
designator: _,
subtype_indication,
name,
signature,
..
} = alias;
return_if_found!(subtype_indication.search(ctx, searcher));
return_if_found!(name.search(ctx, searcher));
@ -1136,32 +1160,41 @@ impl Search for Declaration {
)
.or_not_found());
let ComponentDeclaration {
ident: _,
generic_list,
port_list,
end_ident_pos: _,
span: _,
..
} = component;
return_if_found!(generic_list.search(ctx, searcher));
return_if_found!(port_list.search(ctx, searcher));
if let Some(generic_list) = generic_list {
return_if_found!(generic_list.search(ctx, searcher));
}
if let Some(port_list) = port_list {
return_if_found!(port_list.search(ctx, searcher));
}
}
Declaration::File(file) => {
return_if_found!(searcher
.search_decl(
ctx,
FoundDeclaration::new(&file.ident.decl, DeclarationItem::File(file))
)
.or_not_found());
for ident in &file.idents {
return_if_found!(searcher
.search_decl(
ctx,
FoundDeclaration::new(&ident.decl, DeclarationItem::File(file))
)
.or_not_found());
}
let FileDeclaration {
ident: _,
idents: _,
colon_token: _,
subtype_indication,
open_info,
file_name,
} = file;
return_if_found!(subtype_indication.search(ctx, searcher));
return_if_found!(open_info.search(ctx, searcher));
return_if_found!(file_name.search(ctx, searcher));
if let Some((_, open_info)) = open_info {
return_if_found!(open_info.search(ctx, searcher));
}
if let Some((_, file_name)) = file_name {
return_if_found!(file_name.search(ctx, searcher));
}
}
Declaration::Package(ref package_instance) => {
@ -1178,12 +1211,7 @@ impl Search for Declaration {
FoundDeclaration::new(&view.ident.decl, DeclarationItem::View(view))
)
.or_not_found());
let ModeViewDeclaration {
ident: _,
typ,
elements,
end_ident_pos: _,
} = view;
let ModeViewDeclaration { typ, elements, .. } = view;
return_if_found!(typ.search(ctx, searcher));
return_if_found!(elements.search(ctx, searcher));
}
@ -1194,9 +1222,9 @@ impl Search for Declaration {
impl Search for ModeViewElement {
fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult {
for name in self.names.items.iter() {
for name in self.names.iter() {
return_if_found!(searcher
.search_pos_with_ref(ctx, name.item.pos(ctx), &name.reference)
.search_pos_with_ref(ctx, name.pos(ctx), &name.decl)
.or_not_found());
}
NotFound
@ -1223,7 +1251,9 @@ impl Search for SimpleModeIndication {
impl Search for ModeViewIndication {
fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult {
return_if_found!(self.name.search(ctx, searcher));
return_if_found!(self.subtype_indication.search(ctx, searcher));
if let Some((_, subtype)) = &self.subtype_indication {
return_if_found!(subtype.search(ctx, searcher));
}
NotFound
}
}
@ -1284,7 +1314,7 @@ impl Search for InterfaceDeclaration {
)
.or_not_found());
return_if_found!(package_instance.package_name.search(ctx, searcher));
match package_instance.generic_map {
match package_instance.generic_map.item {
InterfacePackageGenericMapAspect::Map(ref generic_map) => {
return_if_found!(generic_map.search(ctx, searcher));
}
@ -1355,7 +1385,7 @@ impl Search for SubprogramHeader {
impl Search for LibraryClause {
fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult {
for name in self.name_list.items.iter() {
for name in self.name_list.iter() {
return_if_found!(searcher
.search_pos_with_ref(ctx, name.item.pos(ctx), &name.reference)
.or_not_found());
@ -1409,8 +1439,12 @@ impl Search for EntityDeclaration {
FoundDeclaration::new(&self.ident.decl, DeclarationItem::Entity(self))
)
.or_not_found());
return_if_found!(self.generic_clause.search(ctx, searcher));
return_if_found!(self.port_clause.search(ctx, searcher));
if let Some(clause) = &self.generic_clause {
return_if_found!(clause.search(ctx, searcher));
}
if let Some(clause) = &self.port_clause {
return_if_found!(clause.search(ctx, searcher));
}
return_if_found!(self.decl.search(ctx, searcher));
self.statements.search(ctx, searcher)
}
@ -1502,10 +1536,9 @@ impl Search for ContextDeclaration {
impl Search for CaseStatement {
fn search(&self, ctx: &dyn TokenAccess, searcher: &mut impl Searcher) -> SearchResult {
let CaseStatement {
is_matching: _,
expression,
alternatives,
end_label_pos: _,
..
} = self;
return_if_found!(expression.search(ctx, searcher));
return_if_found!(search_alternatives(alternatives, false, searcher, ctx));

View file

@ -111,4 +111,11 @@ impl<T> WithTokenSpan<T> {
span: self.span.start_with(id),
}
}
pub fn as_ref(&self) -> WithTokenSpan<&T> {
WithTokenSpan {
item: &self.item,
span: self.span,
}
}
}

View file

@ -36,7 +36,7 @@ pub fn to_simple_name(ctx: &dyn TokenAccess, name: WithTokenSpan<Name>) -> Diagn
token: name.span.start_token,
}),
_ => Err(Diagnostic::new(
&name.span.pos(ctx),
name.span.pos(ctx),
"Expected simple name",
ErrorCode::SyntaxError,
)),
@ -84,6 +84,12 @@ impl<T: HasDesignator> HasDesignator for WithRef<T> {
}
}
impl HasIdent for WithRef<Ident> {
fn ident(&self) -> &Ident {
&self.item
}
}
impl Designator {
pub fn into_ref(self) -> WithRef<Designator> {
WithRef::new(self)
@ -369,9 +375,9 @@ impl CallOrIndexed {
ref mut parameters,
} = self;
let mut indexes: Vec<Index> = Vec::with_capacity(parameters.len());
let mut indexes: Vec<Index> = Vec::with_capacity(parameters.items.len());
for elem in parameters.iter_mut() {
for elem in parameters.items.iter_mut() {
if let ActualPart::Expression(ref mut expr) = &mut elem.actual.item {
indexes.push(Index {
pos: elem.actual.span,
@ -385,6 +391,7 @@ impl CallOrIndexed {
pub fn could_be_indexed_name(&self) -> bool {
self.parameters
.items
.iter()
.all(|assoc| assoc.formal.is_none() && !matches!(assoc.actual.item, ActualPart::Open))
}

View file

@ -10,8 +10,8 @@ use crate::ast::search::{
use crate::ast::{ConcurrentStatement, MapAspect, ObjectClass};
use crate::named_entity::{AsUnique, Region};
use crate::{
named_entity, AnyEntKind, CompletionItem, Design, EntityId, Overloaded, Position, Source,
TokenAccess,
named_entity, AnyEntKind, CompletionItem, Design, EntityId, HasTokenSpan, Overloaded, Position,
Source, TokenAccess,
};
use std::collections::HashSet;
@ -55,7 +55,7 @@ impl<'a> MapAspectSearcher<'a> {
ctx: &dyn TokenAccess,
kind: MapAspectKind,
) -> bool {
if !map.span(ctx).contains(self.cursor) {
if !map.get_span(ctx).contains(self.cursor) {
return false;
}
let formals_in_map: HashSet<EntityId> = HashSet::from_iter(map.formals().flatten());
@ -156,7 +156,7 @@ fn extract_objects_with_class(region: &Region, object_class: ObjectClass) -> Vec
/// # Arguments
///
/// * `object_class` - What to extract. `ObjectClass::Signal` extracts ports
/// while `ObjectClass::Constant` extracts constants.
/// while `ObjectClass::Constant` extracts constants.
fn extract_port_or_generic_names(
root: &DesignRoot,
id: EntityId,

View file

@ -350,6 +350,14 @@ impl<'a> ContentReader<'a> {
pub fn peek_char(&self) -> Option<char> {
self.get_char()
}
pub fn value_at(&self, line: usize, start: usize, stop: usize) -> Option<Latin1String> {
let line = self.contents.get_line(line)?;
if stop > line.len() {
return None;
}
Latin1String::from_utf8(&line[start..stop]).ok()
}
}
#[cfg(test)]

View file

@ -169,6 +169,18 @@ impl Latin1String {
}
Ok(Latin1String::from_vec(latin1_bytes))
}
pub fn push(&mut self, byte: u8) {
self.bytes.push(byte)
}
pub fn clear(&mut self) {
self.bytes.clear()
}
pub fn append(&mut self, other: &mut Latin1String) {
self.bytes.append(&mut other.bytes);
}
}
impl Default for Latin1String {

View file

@ -0,0 +1,133 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::ArchitectureBody;
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::{indented, HasTokenSpan, TokenSpan};
impl VHDLFormatter<'_> {
pub fn format_architecture(&self, arch: &ArchitectureBody, buffer: &mut Buffer) {
self.format_context_clause(&arch.context_clause, buffer);
if let Some(item) = arch.context_clause.last() {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
let span = arch.span();
// architecture <ident> of <ident> is
self.format_token_span(TokenSpan::new(span.start_token, arch.is_token()), buffer);
indented!(buffer, { self.format_declarations(&arch.decl, buffer) });
buffer.line_break();
self.format_token_id(arch.begin_token, buffer);
indented!(buffer, {
self.format_concurrent_statements(&arch.statements, buffer);
});
buffer.line_break();
// end [architecture] [name];
self.format_token_span(TokenSpan::new(arch.end_token, span.end_token - 1), buffer);
self.format_token_id(span.end_token, buffer);
}
}
#[cfg(test)]
mod test {
use crate::syntax::test::Code;
use vhdl_lang::formatting::test_utils::check_formatted;
fn check_architecture_formatted(input: &str) {
check_formatted(
input,
input,
Code::architecture_body,
|formatter, arch, buffer| formatter.format_architecture(arch, buffer),
)
}
#[test]
fn format_empty_architecture() {
check_architecture_formatted(
"\
architecture foo of bar is
begin
end foo;",
);
check_architecture_formatted(
"\
architecture foo of bar is
begin
end architecture foo;",
);
check_architecture_formatted(
"\
architecture foo of bar is
begin
end;",
);
}
#[test]
fn format_architecture_with_declarations() {
check_architecture_formatted(
"\
architecture foo of bar is
constant x: foo := bar;
begin
end foo;",
);
check_architecture_formatted(
"\
architecture foo of bar is
constant x: foo := bar;
signal y: bar := foobar;
begin
end foo;",
);
}
#[test]
fn format_full_architecture() {
check_architecture_formatted(
"\
architecture foo of bar is
constant x: foo := bar;
signal y: bar := foobar;
begin
bar: process(clk) is
variable z: baz;
begin
if rising_edge(clk) then
if rst = '1' then
foo <= '0';
else
foo <= bar and baz;
end if;
end if;
end process bar;
y <= x; -- An assignment
end foo;",
);
}
#[test]
fn format_architecture_preserve_whitespace() {
check_architecture_formatted(
"\
architecture foo of bar is
constant x: foo := bar;
constant baz: char := '1';
signal y: bar := foobar;
signal foobar: std_logic := 'Z';
shared variable sv: natural;
begin
end foo;",
);
}
}

View file

@ -0,0 +1,330 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::syntax::{Comment, Value};
use crate::{kind_str, Token};
use std::cmp::max;
use std::iter;
/// The Buffer is the (mostly) mutable object used to write tokens to a string.
/// It operates mostly on tokens and is capable of indenting,
/// de-indenting and keeping the indentation level.
pub struct Buffer {
inner: String,
/// insert an extra newline before pushing a token.
/// This is relevant when there is a trailing comment
insert_extra_newline: bool,
/// The current indentation level
indentation: usize,
/// The char used for indentation
indent_char: char,
/// The width used at each indentation level
indent_width: usize,
}
impl Buffer {
pub fn new() -> Buffer {
Buffer {
inner: String::new(),
insert_extra_newline: false,
indentation: 0,
indent_char: ' ',
indent_width: 4,
}
}
}
impl Default for Buffer {
fn default() -> Self {
Self::new()
}
}
/// Returns whether a leading comment is on the same line as the token, i.e.,
/// check the case
/// ```vhdl
/// /* some comment */ token
/// ```
fn leading_comment_is_on_token_line(comment: &Comment, token: &Token) -> bool {
if !comment.multi_line {
return false;
}
if comment.range.start.line != comment.range.end.line {
return false;
}
token.pos.start().line == comment.range.start.line
}
impl From<Buffer> for String {
fn from(value: Buffer) -> Self {
value.inner
}
}
impl Buffer {
pub fn as_str(&self) -> &str {
self.inner.as_str()
}
/// pushes a whitespace character to the buffer
pub fn push_whitespace(&mut self) {
if !self.insert_extra_newline {
self.push_ch(' ');
}
}
fn format_comment(&mut self, comment: &Comment) {
if !comment.multi_line {
self.push_str("--");
self.push_str(comment.value.trim_end())
} else {
self.push_str("/*");
self.push_str(&comment.value);
self.push_str("*/");
}
}
fn format_leading_comments(&mut self, comments: &[Comment]) {
for (i, comment) in comments.iter().enumerate() {
self.format_comment(comment);
if let Some(next_comment) = comments.get(i + 1) {
let number_of_line_breaks =
max(next_comment.range.start.line - comment.range.end.line, 1);
self.line_breaks(number_of_line_breaks);
} else {
self.line_break();
}
}
}
fn indent(&mut self) {
self.inner
.extend(iter::repeat(self.indent_char).take(self.indent_width * self.indentation));
}
/// Push a token to this buffer.
/// This takes care of all the leading and trailing comments attached to that token.
pub fn push_token(&mut self, token: &Token) {
if self.insert_extra_newline {
self.line_break();
}
self.insert_extra_newline = false;
if let Some(comments) = &token.comments {
// This is for example the case for situations like
// some_token /* comment in between */ some_other token
if comments.leading.len() == 1
&& leading_comment_is_on_token_line(&comments.leading[0], token)
{
self.format_comment(&comments.leading[0]);
self.push_ch(' ');
} else if !comments.leading.is_empty() {
self.format_leading_comments(comments.leading.as_slice());
}
}
match &token.value {
Value::Identifier(ident) => self.push_str(&ident.to_string()),
Value::String(string) => {
self.push_ch('"');
for byte in &string.bytes {
if *byte == b'"' {
self.push_ch('"');
self.push_ch('"');
} else {
self.push_ch(*byte as char);
}
}
self.push_ch('"');
}
Value::BitString(value, _) => self.push_str(&value.to_string()),
Value::AbstractLiteral(value, _) => self.push_str(&value.to_string()),
Value::Character(char) => {
self.push_ch('\'');
self.push_ch(*char as char);
self.push_ch('\'');
}
Value::Text(text) => self.push_str(&text.to_string()),
Value::None => self.push_str(kind_str(token.kind)),
}
if let Some(comments) = &token.comments {
if let Some(trailing_comment) = &comments.trailing {
self.push_ch(' ');
self.format_comment(trailing_comment);
self.insert_extra_newline = true
}
}
}
fn push_str(&mut self, value: &str) {
self.inner.push_str(value);
}
fn push_ch(&mut self, char: char) {
self.inner.push(char);
}
/// Increase the indentation level.
/// After this call, all new-line pushes will be preceded by an indentation,
/// specified via the `indent_char` and `indent_width` properties.
///
/// This call should always be matched with a `decrease_indent` call.
/// There is also the `indented` macro that combines the two calls.
pub fn increase_indent(&mut self) {
self.indentation += 1;
}
pub fn decrease_indent(&mut self) {
self.indentation -= 1;
}
/// Inserts a line break (i.e., newline) at the current position
pub fn line_break(&mut self) {
self.insert_extra_newline = false;
self.push_ch('\n');
self.indent();
}
/// Inserts multiple line breaks.
/// Note that this method must always be used (i.e., is different from
/// multiple `line_break` calls) as this method only indents the last line break
pub fn line_breaks(&mut self, count: u32) {
self.insert_extra_newline = false;
for _ in 0..count {
self.push_ch('\n');
}
self.indent();
}
}
#[cfg(test)]
mod tests {
use crate::analysis::tests::Code;
use crate::formatting::buffer::Buffer;
use std::iter::zip;
fn check_token_formatted(input: &str, expected: &[&str]) {
let code = Code::new(input);
let tokens = code.tokenize();
for (token, expected) in zip(tokens, expected) {
let mut buffer = Buffer::new();
buffer.push_token(&token);
assert_eq!(buffer.as_str(), *expected);
}
}
#[test]
fn format_simple_token() {
check_token_formatted("entity", &["entity"]);
check_token_formatted("foobar", &["foobar"]);
check_token_formatted("1 23 4E5 4e5", &["1", "23", "4E5", "4e5"]);
}
#[test]
fn preserves_identifier_casing() {
check_token_formatted("FooBar foobar", &["FooBar", "foobar"]);
}
#[test]
fn character_formatting() {
check_token_formatted("'a' 'Z' '''", &["'a'", "'Z'", "'''"]);
}
#[test]
fn string_formatting() {
check_token_formatted(
r#""ABC" "" "DEF" """" "Hello "" ""#,
&["\"ABC\"", "\"\"", "\"DEF\"", "\"\"\"\"", "\"Hello \"\" \""],
);
}
#[test]
fn bit_string_formatting() {
check_token_formatted(r#"B"10" 20B"8" X"2F""#, &["B\"10\"", "20B\"8\"", "X\"2F\""]);
}
#[test]
fn leading_comment() {
check_token_formatted(
"\
-- I am a comment
foobar
",
&["\
-- I am a comment
foobar"],
);
}
#[test]
fn multiple_leading_comments() {
check_token_formatted(
"\
-- I am a comment
-- So am I
foobar
",
&["\
-- I am a comment
-- So am I
foobar"],
);
}
#[test]
fn trailing_comments() {
check_token_formatted(
"\
foobar --After foobar comes foobaz
",
&["foobar --After foobar comes foobaz"],
);
}
#[test]
fn single_multiline_comment() {
check_token_formatted(
"\
/** Some documentation.
* This is a token named 'entity'
*/
entity
",
&["\
/** Some documentation.
* This is a token named 'entity'
*/
entity"],
);
}
#[test]
fn multiline_comment_and_simple_comment() {
check_token_formatted(
"\
/* I am a multiline comment */
-- And I am a single line comment
entity
",
&["\
/* I am a multiline comment */
-- And I am a single line comment
entity"],
);
}
#[test]
fn leading_comment_and_trailing_comment() {
check_token_formatted(
"\
-- Leading comment
entity -- Trailing comment
",
&["\
-- Leading comment
entity -- Trailing comment"],
);
}
}

View file

@ -0,0 +1,928 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{
AssignmentRightHand, BlockStatement, CaseGenerateStatement, ConcurrentAssertStatement,
ConcurrentSignalAssignment, ConcurrentStatement, Conditionals, ElementAssociation,
ForGenerateStatement, GenerateBody, Ident, IfGenerateStatement, InstantiatedUnit,
InstantiationStatement, LabeledConcurrentStatement, ProcessStatement, SensitivityList, Target,
Waveform, WaveformElement,
};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::Kind;
use crate::{HasTokenSpan, TokenAccess};
use vhdl_lang::ast::{Alternative, AssertStatement, ConcurrentProcedureCall};
use vhdl_lang::{indented, TokenSpan};
impl VHDLFormatter<'_> {
pub fn join_on_newline<T>(
&self,
items: &[T],
joiner: impl Fn(&Self, &T, &mut Buffer),
buffer: &mut Buffer,
) {
for item in items {
buffer.line_break();
joiner(self, item, buffer);
}
}
pub fn format_concurrent_statements(
&self,
statements: &[LabeledConcurrentStatement],
buffer: &mut Buffer,
) {
if statements.is_empty() {
return;
}
buffer.line_break();
for (i, item) in statements.iter().enumerate() {
self.format_labeled_concurrent_statement(item, buffer);
if i < statements.len() - 1 {
self.line_break_preserve_whitespace(item.statement.get_end_token(), buffer);
}
}
}
pub fn format_optional_label(&self, label: Option<&Ident>, buffer: &mut Buffer) {
if let Some(label) = label {
self.format_token_id(label.token, buffer);
// :
self.format_token_id(label.token + 1, buffer);
buffer.push_whitespace();
}
}
pub fn format_labeled_concurrent_statement(
&self,
statement: &LabeledConcurrentStatement,
buffer: &mut Buffer,
) {
self.format_optional_label(statement.label.tree.as_ref(), buffer);
self.format_concurrent_statement(&statement.statement, buffer);
}
pub fn format_concurrent_statement(
&self,
statement: &WithTokenSpan<ConcurrentStatement>,
buffer: &mut Buffer,
) {
use ConcurrentStatement::*;
let span = statement.span;
match &statement.item {
ProcedureCall(call) => self.format_procedure_call(call, span, buffer),
Block(block) => self.format_block_statement(block, span, buffer),
Process(process) => self.format_process_statement(process, span, buffer),
Assert(assert) => self.format_concurrent_assert_statement(assert, span, buffer),
Assignment(assignment) => self.format_assignment_statement(assignment, span, buffer),
Instance(instantiation_statement) => {
self.format_instantiation_statement(instantiation_statement, span, buffer)
}
ForGenerate(for_generate) => {
self.format_for_generate_statement(for_generate, span, buffer)
}
IfGenerate(if_generate) => self.format_if_generate_statement(if_generate, span, buffer),
CaseGenerate(case_generate) => {
self.format_case_generate_statement(case_generate, span, buffer)
}
}
}
pub fn format_procedure_call(
&self,
call: &ConcurrentProcedureCall,
span: TokenSpan,
buffer: &mut Buffer,
) {
if call.postponed {
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
}
self.format_call_or_indexed(&call.call.item, call.call.span, buffer);
// ;
self.format_token_id(span.end_token, buffer);
}
pub fn format_block_statement(
&self,
block: &BlockStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// block
self.format_token_id(span.start_token, buffer);
if let Some(guard_condition) = &block.guard_condition {
self.format_token_id(guard_condition.span.start_token - 1, buffer);
self.format_expression(guard_condition.as_ref(), buffer);
self.format_token_id(guard_condition.span.end_token + 1, buffer);
}
if let Some(is_token) = block.is_token {
buffer.push_whitespace();
self.format_token_id(is_token, buffer);
}
indented!(buffer, {
if let Some(generic_clause) = &block.header.generic_clause {
buffer.line_break();
self.format_interface_list(generic_clause, buffer);
}
if let Some(generic_map) = &block.header.generic_map {
buffer.line_break();
self.format_map_aspect(generic_map, buffer);
// ;
self.format_token_id(generic_map.span.end_token + 1, buffer);
}
if let Some(port_clause) = &block.header.port_clause {
buffer.line_break();
self.format_interface_list(port_clause, buffer);
}
if let Some(port_map) = &block.header.port_map {
buffer.line_break();
self.format_map_aspect(port_map, buffer);
// ;
self.format_token_id(port_map.span.end_token + 1, buffer);
}
self.format_declarations(&block.decl, buffer);
});
buffer.line_break();
self.format_token_id(block.begin_token, buffer);
buffer.line_break();
indented!(buffer, {
self.format_concurrent_statements(&block.statements, buffer)
});
self.format_token_span(
TokenSpan::new(block.end_token, block.span.end_token - 1),
buffer,
);
// ;
self.format_token_id(block.span.end_token, buffer);
}
pub fn format_process_statement(
&self,
process: &ProcessStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.token_with_opt_postponed(span, buffer);
if let Some(sensitivity_list) = &process.sensitivity_list {
match &sensitivity_list.item {
SensitivityList::Names(names) => {
self.format_token_id(sensitivity_list.span.start_token, buffer);
self.format_name_list(buffer, names);
self.format_token_id(sensitivity_list.span.end_token, buffer);
}
SensitivityList::All => self.join_token_span(sensitivity_list.span, buffer),
}
}
if let Some(is_token) = process.is_token {
buffer.push_whitespace();
self.format_token_id(is_token, buffer);
}
indented!(buffer, { self.format_declarations(&process.decl, buffer) });
buffer.line_break();
self.format_token_id(process.begin_token, buffer);
self.format_sequential_statements(&process.statements, buffer);
buffer.line_break();
self.format_token_span(
TokenSpan::new(process.end_token, process.span.end_token - 1),
buffer,
);
// ;
self.format_token_id(process.span.end_token, buffer);
}
fn token_with_opt_postponed(&self, span: TokenSpan, buffer: &mut Buffer) {
if self.tokens.index(span.start_token).kind == Kind::Postponed {
// postponed <x>
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 1),
buffer,
);
} else {
// <x>
self.format_token_id(span.start_token, buffer);
}
}
pub fn format_concurrent_assert_statement(
&self,
statement: &ConcurrentAssertStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.token_with_opt_postponed(span, buffer);
buffer.push_whitespace();
self.format_assert_statement(&statement.statement, buffer);
// ;
self.format_token_id(span.end_token, buffer);
}
pub fn format_assert_statement(&self, assert_statement: &AssertStatement, buffer: &mut Buffer) {
self.format_expression(assert_statement.condition.as_ref(), buffer);
if let Some(report) = &assert_statement.report {
buffer.push_whitespace();
self.format_token_id(report.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(report.as_ref(), buffer);
}
self.format_opt_severity(assert_statement.severity.as_ref(), buffer);
}
pub fn format_assignment_statement(
&self,
assignment_statement: &ConcurrentSignalAssignment,
span: TokenSpan,
buffer: &mut Buffer,
) {
if let AssignmentRightHand::Selected(selected) = &assignment_statement.assignment.rhs {
// with
self.format_token_id(selected.expression.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(selected.expression.as_ref(), buffer);
buffer.push_whitespace();
// select
self.format_token_id(selected.expression.span.end_token + 1, buffer);
buffer.push_whitespace();
}
self.format_target(&assignment_statement.assignment.target, buffer);
buffer.push_whitespace();
// <=
self.format_token_id(
assignment_statement.assignment.target.span.end_token + 1,
buffer,
);
buffer.push_whitespace();
if let Some(mechanism) = &assignment_statement.assignment.delay_mechanism {
self.format_delay_mechanism(mechanism, buffer);
buffer.push_whitespace();
}
self.format_assignment_right_hand(
&assignment_statement.assignment.rhs,
Self::format_waveform,
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_assignment_right_hand<T>(
&self,
right_hand: &AssignmentRightHand<T>,
formatter: impl Fn(&Self, &T, &mut Buffer),
buffer: &mut Buffer,
) {
use AssignmentRightHand::*;
match right_hand {
Simple(simple) => formatter(self, simple, buffer),
Conditional(conditionals) => {
self.format_assignment_right_hand_conditionals(conditionals, formatter, buffer)
}
Selected(selection) => {
for alternative in &selection.alternatives {
self.format_alternative(alternative, &formatter, buffer);
if self
.tokens
.get_token(alternative.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(alternative.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
}
}
}
pub fn format_alternative<T>(
&self,
alternative: &Alternative<T>,
formatter: &impl Fn(&Self, &T, &mut Buffer),
buffer: &mut Buffer,
) {
formatter(self, &alternative.item, buffer);
buffer.push_whitespace();
for (i, choice) in alternative.choices.iter().enumerate() {
if i == 0 {
// when
self.format_token_id(choice.span.start_token - 1, buffer);
buffer.push_whitespace();
}
self.format_choice(choice, buffer);
if i < alternative.choices.len() - 1 {
buffer.push_whitespace();
// |
self.format_token_id(choice.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
}
pub fn format_assignment_right_hand_conditionals<T>(
&self,
conditionals: &Conditionals<T>,
formatter: impl Fn(&Self, &T, &mut Buffer),
buffer: &mut Buffer,
) {
for cond in &conditionals.conditionals {
// item
formatter(self, &cond.item, buffer);
let condition = &cond.condition;
buffer.push_whitespace();
// when
self.format_token_id(condition.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(condition.as_ref(), buffer);
// [else]
if self
.tokens
.get_token(cond.condition.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Else)
{
buffer.push_whitespace();
self.format_token_id(cond.condition.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
if let Some((statements, _)) = &conditionals.else_item {
// else handled above
formatter(self, statements, buffer);
}
}
pub fn format_waveform(&self, waveform: &Waveform, buffer: &mut Buffer) {
match waveform {
Waveform::Elements(elements) => {
for (i, element) in elements.iter().enumerate() {
self.format_waveform_element(element, buffer);
if i < elements.len() - 1 {
self.format_token_id(element.get_end_token() + 1, buffer);
buffer.push_whitespace();
}
}
}
Waveform::Unaffected(token) => self.format_token_id(*token, buffer),
}
}
pub fn format_waveform_element(&self, element: &WaveformElement, buffer: &mut Buffer) {
self.format_expression(element.value.as_ref(), buffer);
if let Some(after) = &element.after {
buffer.push_whitespace();
self.format_token_id(after.get_start_token() - 1, buffer);
buffer.push_whitespace();
self.format_expression(after.as_ref(), buffer);
}
}
pub fn format_target(&self, target: &WithTokenSpan<Target>, buffer: &mut Buffer) {
match &target.item {
Target::Name(name) => self.format_name(WithTokenSpan::new(name, target.span), buffer),
Target::Aggregate(associations) => {
self.format_target_aggregate(associations, target.span, buffer)
}
}
}
pub fn format_target_aggregate(
&self,
associations: &[WithTokenSpan<ElementAssociation>],
span: TokenSpan,
buffer: &mut Buffer,
) {
// (
self.format_token_id(span.start_token, buffer);
self.format_element_associations(associations, buffer);
// )
self.format_token_id(span.end_token, buffer);
}
pub fn format_instantiation_statement(
&self,
statement: &InstantiationStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
if matches!(
self.tokens.index(span.start_token).kind,
Kind::Component | Kind::Entity | Kind::Configuration
) {
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
}
match &statement.unit {
InstantiatedUnit::Component(name) | InstantiatedUnit::Configuration(name) => {
self.format_name(name.as_ref(), buffer);
}
InstantiatedUnit::Entity(name, architecture) => {
self.format_name(name.as_ref(), buffer);
if let Some(arch) = architecture {
self.join_token_span(
TokenSpan::new(arch.item.token - 1, arch.item.token + 1),
buffer,
)
}
}
}
if let Some(generic_map) = &statement.generic_map {
indented!(buffer, {
buffer.line_break();
self.format_map_aspect(generic_map, buffer);
});
}
if let Some(port_map) = &statement.port_map {
indented!(buffer, {
buffer.line_break();
self.format_map_aspect(port_map, buffer);
});
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_for_generate_statement(
&self,
statement: &ForGenerateStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// for
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
// index
self.format_ident(&statement.index_name, buffer);
buffer.push_whitespace();
// in
self.format_token_id(statement.index_name.tree.token + 1, buffer);
buffer.push_whitespace();
self.format_discrete_range(&statement.discrete_range, buffer);
buffer.push_whitespace();
self.format_token_id(statement.generate_token, buffer);
self.format_generate_body(&statement.body, buffer);
buffer.line_break();
self.format_token_span(
TokenSpan::new(statement.end_token, span.end_token - 1),
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_if_generate_statement(
&self,
statement: &IfGenerateStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
for cond in &statement.conds.conditionals {
let condition = &cond.condition;
if let Some(label) = &cond.item.alternative_label {
// if | elsif
self.format_token_id(label.tree.token - 1, buffer);
buffer.push_whitespace();
// label
self.format_token_id(label.tree.token, buffer);
// :
self.format_token_id(label.tree.token + 1, buffer);
buffer.push_whitespace();
} else {
self.format_token_id(condition.span.start_token - 1, buffer);
buffer.push_whitespace();
}
self.format_expression(condition.as_ref(), buffer);
buffer.push_whitespace();
// generate
self.format_token_id(condition.span.end_token + 1, buffer);
self.format_generate_body(&cond.item, buffer);
buffer.line_break();
}
if let Some((statements, token)) = &statement.conds.else_item {
if let Some(label) = &statements.alternative_label {
// else
self.format_token_id(label.tree.token - 1, buffer);
buffer.push_whitespace();
// label
self.format_token_id(label.tree.token, buffer);
// :
self.format_token_id(label.tree.token + 1, buffer);
buffer.push_whitespace();
// generate
self.format_token_id(label.tree.token + 2, buffer);
} else {
// else
self.format_token_id(*token, buffer);
buffer.push_whitespace();
// generate
self.format_token_id(*token + 1, buffer);
}
self.format_generate_body(statements, buffer);
buffer.line_break();
}
if statement.end_label_pos.is_some() {
// end if <label>
self.format_token_span(
TokenSpan::new(span.end_token - 3, span.end_token - 1),
buffer,
)
} else {
// end if
self.format_token_span(
TokenSpan::new(span.end_token - 2, span.end_token - 1),
buffer,
)
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_case_generate_statement(
&self,
statement: &CaseGenerateStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// case
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_expression(statement.sels.expression.as_ref(), buffer);
buffer.push_whitespace();
// generate
self.format_token_id(statement.sels.expression.span.end_token + 1, buffer);
indented!(buffer, {
for alternative in &statement.sels.alternatives {
buffer.line_break();
for (i, choice) in alternative.choices.iter().enumerate() {
if i == 0 {
if let Some(label) = &alternative.item.alternative_label {
// when
self.format_token_id(label.tree.token - 1, buffer);
buffer.push_whitespace();
// <ident>
self.format_token_id(label.tree.token, buffer);
// :
self.format_token_id(label.tree.token + 1, buffer);
} else {
// when
self.format_token_id(choice.span.start_token - 1, buffer);
}
buffer.push_whitespace();
}
self.format_choice(choice, buffer);
if i < alternative.choices.len() - 1 {
buffer.push_whitespace();
// |
self.format_token_id(choice.span.end_token + 1, buffer);
buffer.push_whitespace();
}
if i == alternative.choices.len() - 1 {
buffer.push_whitespace();
// =>
self.format_token_id(choice.span.end_token + 1, buffer);
}
}
self.format_generate_body(&alternative.item, buffer);
}
});
buffer.line_break();
self.format_token_span(
TokenSpan::new(statement.end_token, span.end_token - 1),
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_generate_body(&self, generate_body: &GenerateBody, buffer: &mut Buffer) {
if let Some((decl, begin_token)) = &generate_body.decl {
indented!(buffer, { self.format_declarations(decl, buffer) });
buffer.line_break();
self.format_token_id(*begin_token, buffer);
}
indented!(buffer, {
self.format_concurrent_statements(&generate_body.statements, buffer)
});
if let Some(end_token) = generate_body.end_token {
buffer.line_break();
self.format_token_id(end_token, buffer);
if let Some(token) = generate_body.end_label {
buffer.push_whitespace();
self.format_token_id(token, buffer);
// ;
self.format_token_id(token + 1, buffer);
} else {
// ;
self.format_token_id(end_token + 1, buffer);
}
}
}
}
#[cfg(test)]
mod tests {
use crate::formatting::test_utils::check_formatted;
use crate::syntax::test::Code;
fn check_statement(input: &str) {
check_formatted(
input,
input,
Code::concurrent_statement,
|formatter, ast, buffer| formatter.format_labeled_concurrent_statement(ast, buffer),
)
}
#[test]
fn procedure_calls() {
check_statement("foo;");
check_statement("foo(clk);");
check_statement("foo(clk, bar);");
check_statement("foo(0);");
check_statement("foo(arg => 0);");
}
#[test]
fn blocks() {
check_statement(
"\
name: block
begin
end block name;",
);
check_statement(
"\
name: block is
begin
end block name;",
);
check_statement(
"\
name: block(cond = true)
begin
end block;",
);
check_statement(
"\
name: block(cond = true) is
begin
end block;",
);
check_statement(
"\
block(cond = true) is
begin
end block;",
);
check_statement(
"\
name: block is
generic (
gen: integer := 1
);
generic map (
gen => 1
);
port (
prt: integer := 1
);
port map (
prt => 2
);
begin
end block;",
);
}
#[test]
fn check_processes() {
check_statement(
"\
process
begin
end process;",
);
check_statement(
"\
name: process is
begin
end process name;",
);
check_statement(
"\
postponed process
begin
end process;",
);
check_statement(
"\
postponed process
begin
end postponed process;",
);
check_statement(
"\
process(clk, vec(1)) is
begin
end process;",
);
check_statement(
"\
process(all) is
variable foo: boolean;
begin
end process;",
);
}
#[test]
fn check_assert() {
check_statement("assert false;");
check_statement("assert cond = true;");
check_statement("postponed assert cond = true;");
check_statement("assert false report \"message\" severity error;");
}
#[test]
fn check_signal_assignment() {
check_statement("foo <= bar(2 to 3);");
check_statement("x <= bar(1 to 3) after 2 ns;");
check_statement("foo <= bar(1 to 3) after 2 ns, expr after 1 ns;");
}
#[test]
fn check_simple_instantiation_statement() {
check_statement("inst: component lib.foo.bar;");
check_statement("inst: configuration lib.foo.bar;");
check_statement("inst: entity lib.foo.bar;");
check_statement("inst: entity lib.foo.bar(arch);");
}
#[test]
fn check_instantiation_statement_generic_map() {
check_statement(
"\
inst: component lib.foo.bar
generic map (
const => 1
);",
);
check_statement(
"\
inst: component lib.foo.bar
port map (
clk => clk_foo
);",
);
check_statement(
"\
inst: component lib.foo.bar
generic map (
const => 1
)
port map (
clk => clk_foo
);",
);
}
#[test]
fn format_for_generate_statement() {
check_statement(
"\
gen: for idx in 0 to 1 generate
end generate;",
);
check_statement(
"\
gen: for idx in 0 to 1 generate
foo <= bar;
end generate;",
);
check_statement(
"\
gen: for idx in 0 to 1 generate
begin
foo <= bar;
end generate;",
);
check_statement(
"\
gen: for idx in 0 to 1 generate
begin
foo <= bar;
end;
end generate;",
);
check_statement(
"\
gen: for idx in 0 to 1 generate
signal foo: natural;
begin
foo <= bar;
end generate;",
);
check_statement(
"\
gen: for idx in 0 to 1 generate
signal foo: natural;
begin
foo <= bar;
end;
end generate;",
);
}
#[test]
fn format_if_generate_statement() {
check_statement(
"\
gen: if cond = true generate
end generate;",
);
check_statement(
"\
gen: if cond = true generate
begin
end generate;",
);
check_statement(
"\
gen: if cond = true generate
elsif cond2 = true generate
else generate
end generate;",
);
check_statement(
"\
gen: if cond = true generate
variable v1: boolean;
begin
foo1(clk);
elsif cond2 = true generate
variable v2: boolean;
begin
foo2(clk);
else generate
variable v3: boolean;
begin
foo3(clk);
end generate;",
);
check_statement(
"\
gen: if alt1: cond = true generate
end alt1;
elsif alt2: cond2 = true generate
end alt2;
else alt3: generate
end alt3;
end generate;",
);
}
#[test]
fn format_conditional_assignment() {
check_statement("foo(0) <= bar(1, 2) when cond = true;");
check_statement("foo(0) <= bar(1, 2) when cond = true else expr2 when cond2;");
check_statement("foo(0) <= bar(1, 2) when cond = true else expr2;");
check_statement("foo(0) <= bar(1, 2) after 2 ns when cond;");
}
#[test]
fn format_aggregate_assignments() {
check_statement("(foo, 1 => bar) <= integer_vector'(1, 2);");
}
#[test]
fn format_selected_assignments() {
check_statement(
"\
with x(0) + 1 select foo(0) <= bar(1, 2) when 0 | 1, def when others;",
);
check_statement(
"\
with x(0) + 1 select foo(0) <= transport bar(1, 2) after 2 ns when 0 | 1, def when others;",
);
}
#[test]
fn format_case_generate_statements() {
check_statement(
"\
gen: case expr(0) + 2 generate
when 1 | 2 =>
sig <= value;
when others =>
foo(clk);
end generate;",
);
check_statement(
"\
gen1: case expr(0) + 2 generate
when alt1: 1 | 2 =>
sig <= value;
when alt2: others =>
foo(clk);
end generate gen1;",
);
}
}

View file

@ -0,0 +1,378 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{
BindingIndication, BlockConfiguration, ComponentSpecification, ConfigurationDeclaration,
ConfigurationItem, ConfigurationSpecification, EntityAspect, VUnitBindingIndication,
};
use crate::formatting::buffer::Buffer;
use crate::syntax::Kind;
use crate::{indented, HasTokenSpan, TokenAccess, TokenSpan, VHDLFormatter};
use vhdl_lang::ast::{ComponentConfiguration, InstantiationList};
impl VHDLFormatter<'_> {
pub fn format_configuration(
&self,
configuration: &ConfigurationDeclaration,
buffer: &mut Buffer,
) {
self.format_context_clause(&configuration.context_clause, buffer);
if let Some(item) = configuration.context_clause.last() {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
// configuration cfg of entity_name is
self.format_token_span(
TokenSpan::new(
configuration.span.start_token,
configuration.span.start_token + 4,
),
buffer,
);
indented!(buffer, {
self.format_declarations(&configuration.decl, buffer);
self.format_v_unit_binding_indications(&configuration.vunit_bind_inds, buffer);
buffer.line_break();
self.format_block_configuration(&configuration.block_config, buffer);
});
buffer.line_break();
self.format_token_span(
TokenSpan::new(configuration.end_token, configuration.span.end_token - 1),
buffer,
);
self.format_token_id(configuration.span.end_token, buffer);
}
pub fn format_v_unit_binding_indications(
&self,
v_units: &[VUnitBindingIndication],
buffer: &mut Buffer,
) {
for v_unit_bind_ind in v_units {
buffer.line_break();
self.format_v_unit_indication(v_unit_bind_ind, buffer);
}
}
pub fn format_block_configuration(&self, config: &BlockConfiguration, buffer: &mut Buffer) {
if !config.use_clauses.is_empty() {
unreachable!("Not implemented on AST side")
}
// for
self.format_token_id(config.span.start_token, buffer);
buffer.push_whitespace();
self.format_name(config.block_spec.as_ref(), buffer);
indented!(buffer, {
for item in &config.items {
buffer.line_break();
match item {
ConfigurationItem::Block(block_configuration) => {
self.format_block_configuration(block_configuration, buffer)
}
ConfigurationItem::Component(component_configuration) => {
self.format_component_configuration(component_configuration, buffer)
}
}
}
});
buffer.line_break();
// end
self.format_token_id(config.span.end_token - 2, buffer);
buffer.push_whitespace();
// for
self.format_token_id(config.span.end_token - 1, buffer);
// ;
self.format_token_id(config.span.end_token, buffer);
}
pub fn format_component_configuration(
&self,
config: &ComponentConfiguration,
buffer: &mut Buffer,
) {
self.format_component_specification(&config.spec, buffer);
indented!(buffer, {
if let Some(binding_indication) = &config.bind_ind {
buffer.line_break();
self.format_binding_indication(binding_indication, buffer)
}
self.format_v_unit_binding_indications(&config.vunit_bind_inds, buffer);
if let Some(block_configuration) = &config.block_config {
buffer.line_break();
self.format_block_configuration(block_configuration, buffer);
}
});
buffer.line_break();
// end
self.format_token_id(config.span.end_token - 2, buffer);
buffer.push_whitespace();
// for
self.format_token_id(config.span.end_token - 1, buffer);
// ;
self.format_token_id(config.span.end_token, buffer);
}
pub fn format_binding_indication(&self, indication: &BindingIndication, buffer: &mut Buffer) {
// use
self.format_token_id(indication.span.start_token, buffer);
if let Some(aspect) = &indication.entity_aspect {
buffer.push_whitespace();
self.format_token_id(indication.span.start_token + 1, buffer);
buffer.push_whitespace();
match aspect {
EntityAspect::Entity(entity, architecture) => {
self.format_name(entity.as_ref(), buffer);
if let Some(arch) = architecture {
self.format_token_id(arch.token - 1, buffer);
self.format_token_id(arch.token, buffer);
self.format_token_id(arch.token + 1, buffer);
}
}
EntityAspect::Configuration(config) => {
self.format_name(config.as_ref(), buffer);
}
EntityAspect::Open => {}
}
}
if let Some(map_aspect) = &indication.generic_map {
indented!(buffer, {
buffer.line_break();
self.format_map_aspect(map_aspect, buffer);
});
}
if let Some(map_aspect) = &indication.port_map {
indented!(buffer, {
buffer.line_break();
self.format_map_aspect(map_aspect, buffer);
});
}
self.format_token_id(indication.span.end_token, buffer);
}
pub fn format_configuration_specification(
&self,
configuration: &ConfigurationSpecification,
buffer: &mut Buffer,
) {
self.format_component_specification(&configuration.spec, buffer);
indented!(buffer, {
buffer.line_break();
self.format_binding_indication(&configuration.bind_ind, buffer);
self.format_v_unit_binding_indications(&configuration.vunit_bind_inds, buffer);
});
if let Some(end_token) = configuration.end_token {
buffer.line_break();
self.format_token_id(end_token, buffer);
buffer.push_whitespace();
self.format_token_id(end_token + 1, buffer);
self.format_token_id(configuration.span.end_token, buffer);
}
}
pub fn format_component_specification(
&self,
spec: &ComponentSpecification,
buffer: &mut Buffer,
) {
// for
self.format_token_id(spec.span.start_token, buffer);
buffer.push_whitespace();
match &spec.instantiation_list {
InstantiationList::Labels(labels) => self.format_ident_list(labels, buffer),
InstantiationList::Others => self.format_token_id(spec.span.start_token + 1, buffer),
InstantiationList::All => self.format_token_id(spec.span.start_token + 1, buffer),
}
// :
self.format_token_id(spec.colon_token, buffer);
buffer.push_whitespace();
self.format_name(spec.component_name.as_ref(), buffer);
}
pub fn format_v_unit_indication(
&self,
v_unit_binding_indication: &VUnitBindingIndication,
buffer: &mut Buffer,
) {
// use
self.format_token_id(v_unit_binding_indication.span.start_token, buffer);
buffer.push_whitespace();
// v_unit
self.format_token_id(v_unit_binding_indication.span.start_token + 1, buffer);
buffer.push_whitespace();
for v_unit in &v_unit_binding_indication.vunit_list {
self.format_name(v_unit.as_ref(), buffer);
if self
.tokens
.get_token(v_unit.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(v_unit.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
self.format_token_id(v_unit_binding_indication.span.end_token, buffer);
}
}
#[cfg(test)]
mod test {
use crate::analysis::tests::Code;
use crate::formatting::test_utils::check_formatted;
fn check_design_unit_formatted(input: &str) {
check_formatted(
input,
input,
Code::design_file,
|formatter, file, buffer| {
formatter.format_any_design_unit(&file.design_units[0].1, buffer, true)
},
);
}
#[test]
fn check_configuration() {
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
end for;
end;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
use lib.foo.bar;
use lib2.foo.bar;
for rtl(0)
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for name(0 to 3)
end for;
for other_name
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for name(0 to 3)
for name(7 to 8)
end for;
end for;
for other_name
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
use lib.foo.bar;
use vunit baz.foobar;
for rtl(0)
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for inst: lib.pkg.comp
for arch
end for;
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for inst: lib.pkg.comp
use entity work.bar;
use vunit baz;
for arch
end for;
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for inst: lib.pkg.comp
use entity lib.use_name;
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for rtl(0)
for inst: lib.pkg.comp
end for;
for inst1, inst2, inst3: lib2.pkg.comp
end for;
for all: lib3.pkg.comp
end for;
for others: lib4.pkg.comp
end for;
end for;
end configuration cfg;",
);
}
#[test]
fn check_entity_aspect() {
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for foo
for inst: lib.pkg.comp
use entity lib.use_name;
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for foo
for inst: lib.pkg.comp
use entity lib.foo.name(arch);
end for;
end for;
end configuration cfg;",
);
check_design_unit_formatted(
"\
configuration cfg of entity_name is
for foo
for inst: lib.pkg.comp
use configuration lib.foo.name;
end for;
end for;
end configuration cfg;",
);
}
}

View file

@ -0,0 +1,167 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{DiscreteRange, SubtypeConstraint};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::Kind;
use crate::TokenAccess;
use vhdl_lang::ast::{ElementConstraint, Range, RangeConstraint};
impl VHDLFormatter<'_> {
pub fn format_subtype_constraint(
&self,
constraint: &WithTokenSpan<SubtypeConstraint>,
buffer: &mut Buffer,
) {
match &constraint.item {
SubtypeConstraint::Range(range) => {
self.format_token_id(constraint.span.start_token, buffer);
buffer.push_whitespace();
self.format_range(range, buffer)
}
SubtypeConstraint::Array(ranges, opt_constraint) => {
self.format_token_id(constraint.span.start_token, buffer);
if ranges.is_empty() {
// open
self.format_token_id(constraint.span.start_token + 1, buffer);
}
for range in ranges {
self.format_discrete_range(&range.item, buffer);
if self
.tokens
.get_token(range.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(range.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
if let Some(constraint) = opt_constraint {
self.format_token_id(constraint.span.start_token - 1, buffer);
self.format_subtype_constraint(constraint, buffer);
} else {
self.format_token_id(constraint.span.end_token, buffer);
}
}
SubtypeConstraint::Record(records) => {
self.format_token_id(constraint.span.start_token, buffer);
for record in records {
self.format_element_constraint(record, buffer);
if self
.tokens
.get_token(record.constraint.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(record.constraint.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
self.format_token_id(constraint.span.end_token, buffer);
}
}
}
pub fn format_element_constraint(&self, constraint: &ElementConstraint, buffer: &mut Buffer) {
self.format_token_id(constraint.ident.token, buffer);
self.format_subtype_constraint(&constraint.constraint, buffer);
}
pub fn format_range_constraint(&self, constraint: &RangeConstraint, buffer: &mut Buffer) {
self.format_expression(constraint.left_expr.as_ref().as_ref(), buffer);
buffer.push_whitespace();
self.format_token_id(constraint.direction_token(), buffer);
buffer.push_whitespace();
self.format_expression(constraint.right_expr.as_ref().as_ref(), buffer);
}
pub fn format_range(&self, range: &Range, buffer: &mut Buffer) {
match range {
Range::Range(constraint) => self.format_range_constraint(constraint, buffer),
Range::Attribute(attribute) => self.format_attribute_name(attribute, buffer),
}
}
pub fn format_discrete_range(&self, range: &DiscreteRange, buffer: &mut Buffer) {
match range {
DiscreteRange::Discrete(name, range) => {
self.format_name(name.as_ref(), buffer);
if let Some(range) = range {
buffer.push_whitespace();
// range
self.format_token_id(name.span.end_token + 1, buffer);
buffer.push_whitespace();
self.format_range(range, buffer);
}
}
DiscreteRange::Range(range) => self.format_range(range, buffer),
}
}
}
#[cfg(test)]
mod test {
use crate::formatting::buffer::Buffer;
use crate::formatting::test_utils::check_formatted;
use crate::formatting::VHDLFormatter;
use crate::syntax::test::Code;
fn check_range(input: &str) {
let code = Code::new(input);
let range = code.range();
let tokens = code.tokenize();
let formatter = VHDLFormatter::new(&tokens);
let mut buffer = Buffer::new();
formatter.format_range(&range, &mut buffer);
assert_eq!(buffer.as_str(), input);
}
#[test]
fn check_simple_range() {
check_range("0 to 5");
check_range("0 downto 5 - C_OFFSET");
}
fn check_subtype_indications(inputs: &[&str]) {
for input in inputs {
check_formatted(
input,
input,
Code::subtype_indication,
|formatter, subtype_indication, buffer| {
formatter.format_subtype_indication(subtype_indication, buffer)
},
)
}
}
#[test]
fn format_range_subtype_constraint() {
check_subtype_indications(&[
"integer range 0 to 2 - 1",
"integer range lib.foo.bar'range",
]);
}
#[test]
fn format_array_subtype_constraint() {
check_subtype_indications(&[
"integer_vector(2 - 1 downto 0)",
"integer_vector(lib.foo.bar)",
"integer_vector(lib.pkg.bar'range)",
"integer_vector(open)",
"integer_vector(2 - 1 downto 0, 11 to 14)",
"integer_vector(2 - 1 downto 0, 11 to 14)(foo to bar)",
]);
}
#[test]
fn format_record_subtype_constraint() {
check_subtype_indications(&["axi_m2s_t(tdata(2 - 1 downto 0), tuser(3 to 5))"]);
}
}

View file

@ -0,0 +1,51 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{ContextClause, ContextDeclaration, ContextItem};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::{HasTokenSpan, TokenSpan};
use vhdl_lang::indented;
impl VHDLFormatter<'_> {
pub fn format_context(&self, context: &ContextDeclaration, buffer: &mut Buffer) {
// context <name> is
self.format_token_span(
TokenSpan::new(context.span.start_token, context.span.start_token + 2),
buffer,
);
indented!(buffer, {
if !context.items.is_empty() {
buffer.line_break();
}
self.format_context_clause(&context.items, buffer);
});
buffer.line_break();
self.format_token_span(
TokenSpan::new(context.end_token, context.span.end_token - 1),
buffer,
);
self.format_token_id(context.span.end_token, buffer);
}
pub fn format_context_clause(&self, clause: &ContextClause, buffer: &mut Buffer) {
for (i, item) in clause.iter().enumerate() {
match item {
ContextItem::Use(use_clause) => self.format_use_clause(use_clause, buffer),
ContextItem::Library(library_clause) => {
self.format_library_clause(library_clause, buffer)
}
ContextItem::Context(context_reference) => {
self.format_context_reference(context_reference, buffer)
}
}
if i < clause.len() - 1 {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
}
}
}

View file

@ -0,0 +1,865 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{
ArrayIndex, ComponentDeclaration, ContextReference, ElementDeclaration, EntityName,
FileDeclaration, ModeViewDeclaration, ObjectDeclaration, PackageInstantiation,
ProtectedTypeDeclarativeItem, SubtypeIndication, TypeDeclaration, TypeDefinition,
};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::Kind;
use crate::{indented, HasTokenSpan, TokenAccess, TokenId, TokenSpan};
use vhdl_lang::ast::token_range::WithTokenSpan;
use vhdl_lang::ast::{
AliasDeclaration, Attribute, AttributeDeclaration, AttributeSpecification, Declaration,
LibraryClause, ObjectClass, PhysicalTypeDeclaration, ProtectedTypeBody,
ProtectedTypeDeclaration, UseClause,
};
impl VHDLFormatter<'_> {
pub(crate) fn format_declarations(
&self,
declarations: &[WithTokenSpan<Declaration>],
buffer: &mut Buffer,
) {
if declarations.is_empty() {
return;
}
buffer.line_break();
for (i, item) in declarations.iter().enumerate() {
self.format_declaration(item, buffer);
if i < declarations.len() - 1 {
self.line_break_preserve_whitespace(item.get_end_token(), buffer);
}
}
}
pub fn format_declaration(
&self,
declaration: &WithTokenSpan<Declaration>,
buffer: &mut Buffer,
) {
use Declaration::*;
match &declaration.item {
Object(object_decl) => {
self.format_object_declaration(object_decl, declaration.span, buffer)
}
File(file_decl) => self.format_file_declaration(file_decl, declaration.span, buffer),
Type(type_decl) => self.format_type_declaration(type_decl, declaration.span, buffer),
Component(component) => self.format_component_declaration(component, buffer),
Attribute(attribute) => self.format_attribute(attribute, declaration.span, buffer),
Alias(alias) => self.format_alias_declaration(alias, declaration.span, buffer),
SubprogramDeclaration(subprogram_declaration) => {
self.format_subprogram_declaration(subprogram_declaration, buffer)
}
SubprogramInstantiation(subprogram_instantiation) => {
self.format_subprogram_instantiation(subprogram_instantiation, buffer)
}
SubprogramBody(subprogram_body) => self.format_subprogram_body(subprogram_body, buffer),
Use(use_clause) => self.format_use_clause(use_clause, buffer),
Package(package_instantiation) => {
self.format_package_instance(package_instantiation, buffer)
}
Configuration(configuration) => {
self.format_configuration_specification(configuration, buffer)
}
View(view_declaration) => self.format_view(view_declaration, declaration.span, buffer),
}
}
pub fn format_component_declaration(
&self,
component: &ComponentDeclaration,
buffer: &mut Buffer,
) {
self.format_token_span(
TokenSpan::new(
component.span.start_token,
component.is_token.unwrap_or(component.span.start_token + 1),
),
buffer,
);
if let Some(generic_clause) = &component.generic_list {
indented!(buffer, {
buffer.line_break();
self.format_interface_list(generic_clause, buffer);
});
}
if let Some(port_clause) = &component.port_list {
indented!(buffer, {
buffer.line_break();
self.format_interface_list(port_clause, buffer);
});
}
buffer.line_break();
self.format_token_span(
TokenSpan::new(component.end_token, component.span.end_token - 1),
buffer,
);
self.format_token_id(component.span.end_token, buffer);
}
pub fn format_object_declaration(
&self,
object_decl: &ObjectDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_token_id(span.start_token, buffer);
if object_decl.class == ObjectClass::SharedVariable {
buffer.push_whitespace();
self.format_token_id(span.start_token + 1, buffer);
}
buffer.push_whitespace();
self.format_ident_list(&object_decl.idents, buffer);
self.format_token_id(object_decl.colon_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(&object_decl.subtype_indication, buffer);
self.format_default_expression(object_decl.expression.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_file_declaration(
&self,
file_decl: &FileDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_ident_list(&file_decl.idents, buffer);
self.format_token_id(file_decl.colon_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(&file_decl.subtype_indication, buffer);
if let Some((token, open_information)) = &file_decl.open_info {
buffer.push_whitespace();
self.format_token_id(*token, buffer);
buffer.push_whitespace();
self.format_expression(open_information.as_ref(), buffer);
}
if let Some((token, file_name)) = &file_decl.file_name {
buffer.push_whitespace();
self.format_token_id(*token, buffer);
buffer.push_whitespace();
self.format_expression(file_name.as_ref(), buffer);
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_type_declaration(
&self,
type_decl: &TypeDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_token_span(
TokenSpan::new(span.start_token, type_decl.ident.tree.token),
buffer,
);
if let Some(is_token) = type_decl.is_token() {
buffer.push_whitespace();
self.format_token_id(is_token, buffer);
}
if let Some(is_token) = type_decl.is_token() {
buffer.push_whitespace();
self.format_type_definition(
&type_decl.def,
TokenSpan::new(
is_token + 1,
type_decl.end_ident_pos.unwrap_or(span.end_token) - 1,
),
buffer,
);
}
if let Some(end_ident) = type_decl.end_ident_pos {
buffer.push_whitespace();
self.format_token_id(end_ident, buffer);
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_type_definition(
&self,
definition: &TypeDefinition,
span: TokenSpan,
buffer: &mut Buffer,
) {
use TypeDefinition::*;
match definition {
Enumeration(literals) => {
self.format_token_id(span.start_token, buffer);
for literal in literals {
self.format_token_id(literal.tree.token, buffer);
if self
.tokens
.get_token(literal.tree.token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(literal.tree.token + 1, buffer);
buffer.push_whitespace();
}
}
self.format_token_id(span.end_token, buffer);
}
Numeric(range) => {
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_range(range, buffer)
}
Physical(physical_type) => {
self.format_physical_type_declaration(physical_type, span, buffer)
}
Array(indices, of_token, subtype) => {
self.format_array_type_declaration(indices, *of_token, subtype, span, buffer)
}
Record(elements) => self.format_record_declaration(elements, span, buffer),
Access(subtype_indication) => {
// access
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(subtype_indication, buffer);
}
Incomplete(_) => {
// nothing to do
}
File(name) => {
// file of
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 1),
buffer,
);
buffer.push_whitespace();
self.format_name(name.as_ref(), buffer);
}
Protected(protected) => self.format_protected_type_declaration(protected, span, buffer),
ProtectedBody(protected_body) => {
self.format_protected_body_type_declaration(protected_body, span, buffer)
}
Subtype(subtype) => self.format_subtype_indication(subtype, buffer),
}
}
pub fn format_physical_type_declaration(
&self,
declaration: &PhysicalTypeDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
// range
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_range(&declaration.range, buffer);
indented!(buffer, {
buffer.line_break();
self.format_token_id(declaration.units_token, buffer);
indented!(buffer, {
buffer.line_break();
// primary_unit;
self.format_ident(&declaration.primary_unit, buffer);
self.format_token_id(declaration.primary_unit.tree.token + 1, buffer);
for (ident, literal) in &declaration.secondary_units {
buffer.line_break();
self.format_ident(ident, buffer);
buffer.push_whitespace();
// =
self.format_token_id(ident.tree.token + 1, buffer);
buffer.push_whitespace();
self.format_token_span(literal.span, buffer);
// ;
self.format_token_id(literal.span.end_token + 1, buffer);
}
});
buffer.line_break();
// end units
self.format_token_span(TokenSpan::new(span.end_token - 1, span.end_token), buffer);
});
}
pub fn format_array_type_declaration(
&self,
indices: &[ArrayIndex],
of_token: TokenId,
subtype: &SubtypeIndication,
span: TokenSpan,
buffer: &mut Buffer,
) {
// array
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
// (
self.format_token_id(span.start_token + 1, buffer);
for (i, index) in indices.iter().enumerate() {
let end_token = match index {
ArrayIndex::IndexSubtypeDefintion(name) => {
self.format_name(name.as_ref(), buffer);
buffer.push_whitespace();
self.format_token_span(
TokenSpan::new(name.span.end_token + 1, name.span.end_token + 2),
buffer,
);
name.span.end_token + 3
}
ArrayIndex::Discrete(discrete_range) => {
self.format_discrete_range(&discrete_range.item, buffer);
discrete_range.span.end_token + 1
}
};
if i < indices.len() - 1 {
self.format_token_id(end_token, buffer);
buffer.push_whitespace();
}
}
// )
self.format_token_id(of_token - 1, buffer);
buffer.push_whitespace();
// of
self.format_token_id(of_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(subtype, buffer);
}
pub fn format_record_declaration(
&self,
elements: &[ElementDeclaration],
span: TokenSpan,
buffer: &mut Buffer,
) {
// record
self.format_token_id(span.start_token, buffer);
let mut last_token = span.start_token;
indented!(buffer, {
for element in elements {
buffer.line_break();
self.format_element_declaration(element, buffer);
last_token = element.span.end_token;
}
});
buffer.line_break();
// end record
self.format_token_span(TokenSpan::new(last_token + 1, span.end_token), buffer)
}
pub fn format_element_declaration(
&self,
declaration: &ElementDeclaration,
buffer: &mut Buffer,
) {
self.format_ident_list(&declaration.idents, buffer);
// :
self.format_token_id(declaration.colon_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(&declaration.subtype, buffer);
// ;
self.format_token_id(declaration.span.end_token, buffer);
}
pub fn format_protected_type_declaration(
&self,
declaration: &ProtectedTypeDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
// protected
self.format_token_id(span.start_token, buffer);
let mut last_token = span.start_token;
indented!(buffer, {
for element in &declaration.items {
buffer.line_break();
match element {
ProtectedTypeDeclarativeItem::Subprogram(subprogram) => {
self.format_subprogram_declaration(subprogram, buffer);
last_token = subprogram.span.end_token;
}
}
}
});
buffer.line_break();
// end protected
self.format_token_span(TokenSpan::new(last_token + 1, span.end_token), buffer)
}
pub fn format_protected_body_type_declaration(
&self,
declaration: &ProtectedTypeBody,
span: TokenSpan,
buffer: &mut Buffer,
) {
// protected body
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 1),
buffer,
);
indented!(buffer, {
self.format_declarations(&declaration.decl, buffer)
});
buffer.line_break();
let last_token = declaration
.decl
.last()
.map(|last| last.span.end_token)
.unwrap_or(span.start_token + 1);
// end protected body
self.format_token_span(TokenSpan::new(last_token + 1, span.end_token), buffer)
}
pub fn format_attribute(&self, attribute: &Attribute, span: TokenSpan, buffer: &mut Buffer) {
use Attribute::*;
match attribute {
Specification(spec) => self.format_attribute_specification(spec, span, buffer),
Declaration(dec) => self.format_attribute_declaration(dec, span, buffer),
}
}
pub fn format_attribute_declaration(
&self,
attribute: &AttributeDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_token_span(
TokenSpan::new(span.start_token, attribute.ident.tree.token),
buffer,
);
// :
self.format_token_id(attribute.ident.tree.token + 1, buffer);
buffer.push_whitespace();
self.format_name(attribute.type_mark.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_attribute_specification(
&self,
attribute: &AttributeSpecification,
span: TokenSpan,
buffer: &mut Buffer,
) {
// attribute <name> of
self.format_token_span(
TokenSpan::new(span.start_token, attribute.ident.item.token + 1),
buffer,
);
buffer.push_whitespace();
match &attribute.entity_name {
EntityName::Name(name) => {
self.format_token_id(name.designator.token, buffer);
if let Some(signature) = &name.signature {
self.format_signature(signature, buffer);
}
}
EntityName::All | EntityName::Others => {
self.format_token_id(attribute.ident.item.token + 2, buffer)
}
}
// : <entity_class> is
self.format_token_span(
TokenSpan::new(attribute.colon_token, attribute.colon_token + 2),
buffer,
);
buffer.push_whitespace();
self.format_expression(attribute.expr.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_alias_declaration(
&self,
alias: &AliasDeclaration,
span: TokenSpan,
buffer: &mut Buffer,
) {
// alias <name>
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 1),
buffer,
);
if let Some(subtype) = &alias.subtype_indication {
// :
self.format_token_id(span.start_token + 2, buffer);
buffer.push_whitespace();
self.format_subtype_indication(subtype, buffer);
}
buffer.push_whitespace();
self.format_token_id(alias.is_token, buffer);
buffer.push_whitespace();
self.format_name(alias.name.as_ref(), buffer);
if let Some(signature) = &alias.signature {
self.format_signature(signature, buffer);
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_use_clause(&self, use_clause: &UseClause, buffer: &mut Buffer) {
// use
self.format_token_id(use_clause.get_start_token(), buffer);
buffer.push_whitespace();
self.format_name_list(buffer, &use_clause.name_list);
self.format_token_id(use_clause.get_end_token(), buffer);
}
pub fn format_library_clause(&self, library_clause: &LibraryClause, buffer: &mut Buffer) {
// use
self.format_token_id(library_clause.get_start_token(), buffer);
buffer.push_whitespace();
self.format_ident_list(&library_clause.name_list, buffer);
self.format_token_id(library_clause.get_end_token(), buffer);
}
pub fn format_context_reference(
&self,
context_reference: &ContextReference,
buffer: &mut Buffer,
) {
// use
self.format_token_id(context_reference.get_start_token(), buffer);
buffer.push_whitespace();
self.format_name_list(buffer, &context_reference.name_list);
self.format_token_id(context_reference.get_end_token(), buffer);
}
pub fn format_package_instance(&self, instance: &PackageInstantiation, buffer: &mut Buffer) {
self.format_context_clause(&instance.context_clause, buffer);
// package <name> is new
self.format_token_span(
TokenSpan::new(instance.get_start_token(), instance.get_start_token() + 3),
buffer,
);
buffer.push_whitespace();
self.format_name(instance.package_name.as_ref(), buffer);
if let Some(generic_map) = &instance.generic_map {
buffer.push_whitespace();
self.format_map_aspect(generic_map, buffer);
}
self.format_token_id(instance.get_end_token(), buffer);
}
pub fn format_view(&self, view: &ModeViewDeclaration, span: TokenSpan, buffer: &mut Buffer) {
// view <name> of
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 2),
buffer,
);
buffer.push_whitespace();
self.format_subtype_indication(&view.typ, buffer);
buffer.push_whitespace();
self.format_token_id(view.is_token, buffer);
indented!(buffer, {
self.join_on_newline(&view.elements, Self::format_mode_view_element, buffer);
});
buffer.line_break();
self.format_token_span(TokenSpan::new(view.end_token, span.end_token - 1), buffer);
self.format_token_id(span.end_token, buffer);
}
}
#[cfg(test)]
mod tests {
use crate::formatting::test_utils::{check_formatted, check_formatted_std};
use crate::VHDLStandard;
use crate::VHDLStandard::VHDL2019;
fn check_declaration(input: &str) {
check_formatted(
input,
input,
|code| code.declarative_part().into_iter().next().unwrap(),
|formatter, ast, buffer| formatter.format_declaration(ast, buffer),
);
}
fn check_declaration_std(input: &str, std: VHDLStandard) {
check_formatted_std(
input,
input,
std,
|code| code.declarative_part().into_iter().next().unwrap(),
|formatter, ast, buffer| formatter.format_declaration(ast, buffer),
);
}
#[test]
fn object_declarations() {
check_declaration("constant my_const: std_logic;");
check_declaration("variable my_var: std_logic;");
check_declaration("signal foo: std_logic;");
check_declaration("shared variable bar: std_logic;");
check_declaration("shared variable bar: std_logic := '0';");
}
#[test]
fn file_declarations() {
check_declaration("file my_file: text;");
check_declaration("file my_file: text is \"my_file.txt\";");
check_declaration("file my_file: text open mode is \"my_file.txt\";");
check_declaration("file FileID1, FileID2: text;");
}
#[test]
fn enum_declaration() {
check_declaration("type my_enum is (A);");
check_declaration("type my_enum is (A, B);");
check_declaration("type my_enum is ('0', '1', 'U', 'X');");
}
#[test]
fn numeric_type_declaration() {
check_declaration("type my_enum is range 0 to 5;");
}
#[test]
fn physical_types() {
check_declaration(
"\
type TIME is range -9223372036854775807 to 9223372036854775807
units
fs; -- femtosecond
end units;",
);
check_declaration(
"\
type TIME is range -9223372036854775807 to 9223372036854775807
units
fs; -- femtosecond
ps = 1000 fs; -- picosecond
ns = 1000 ps; -- nanosecond
us = 1000 ns; -- microsecond
ms = 1000 us; -- millisecond
sec = 1000 ms; -- second
min = 60 sec; -- minute
hr = 60 min; -- hour
end units;",
);
check_declaration(
"\
type TIME is range -9223372036854775807 to 9223372036854775807
units
fs; -- femtosecond
ps = fs; -- picosecond
end units;",
);
}
#[test]
fn array_type_definition() {
check_declaration("type my_array is array (natural range <>) of std_logic_vector;");
check_declaration("type foo is array (2 - 1 downto 0, integer range <>) of boolean;");
check_declaration("type foo is array (2 - 1 downto 0) of boolean;");
}
#[test]
fn record_type_definition() {
check_declaration(
"\
type x is record
end record;",
);
check_declaration(
"\
type foo is record
element: boolean;
end record;",
);
check_declaration(
"\
type foo is record
element: boolean;
other_element: std_logic_vector;
end foo;",
);
check_declaration(
"\
type dummy_rec is record
dummy: bit;
end record;",
);
}
#[test]
fn access_definition() {
check_declaration("type dummy_rec is access bit;");
}
#[test]
fn incomplete_type_definition() {
check_declaration("type incomplete;");
}
#[test]
fn file_definitions() {
check_declaration("type foo is file of character;");
}
#[test]
fn protected_declaration() {
check_declaration(
"type foo is protected
end protected;",
);
check_declaration(
"type foo is protected
end protected foo;",
);
check_declaration(
"type foo is protected
procedure proc;
function fun return ret;
end protected;",
);
}
#[test]
fn protected_body_declaration() {
check_declaration(
"type foo is protected body
end protected body;",
);
check_declaration(
"\
type foo is protected body
variable foo: natural;
procedure proc is
begin
end;
end protected body;",
);
}
#[test]
fn protected_subtype_declaration() {
check_declaration("subtype vec_t is integer_vector(2 - 1 downto 0);");
}
#[test]
fn component_declaration() {
check_declaration(
"\
component foo
end component;",
);
check_declaration(
"\
component foo is
end component;",
);
check_declaration(
"\
component foo is
end component foo;",
);
check_declaration(
"\
component foo is
generic (
foo: natural
);
end component;",
);
check_declaration(
"\
component foo is
port (
foo: natural
);
end component;",
);
}
#[test]
fn check_attribute_declaration() {
check_declaration("attribute foo: name;");
check_declaration("attribute attr_name of foo: signal is 0 + 1;");
check_declaration("attribute attr_name of \"**\": function is 0 + 1;");
check_declaration("attribute attr_name of all: signal is 0 + 1;");
check_declaration("attribute attr_name of foo[return natural]: function is 0 + 1;");
}
#[test]
fn check_alias_declaration() {
check_declaration("alias foo is name;");
check_declaration("alias foo: vector(0 to 1) is name;");
check_declaration("alias foo is name[return natural];");
check_declaration("alias \"and\" is name;");
check_declaration("alias 'c' is 'b';");
}
#[test]
fn check_use_clause() {
check_declaration("use foo;");
check_declaration("use foo, bar;");
check_declaration("use foo, bar, baz;");
}
#[test]
fn format_package_instance() {
check_declaration("package ident is new foo;");
check_declaration(
"package ident is new foo generic map (
foo => bar
);",
);
}
#[test]
fn format_view() {
check_declaration_std(
"\
view foo of bar is
end view;",
VHDL2019,
);
check_declaration_std(
"\
view foo of bar is
end view foo;",
VHDL2019,
);
check_declaration_std(
"\
view foo of bar is
baz: in;
end view;",
VHDL2019,
);
check_declaration_std(
"\
view foo of bar is
baz: in;
bar, baz: view foo;
end view;",
VHDL2019,
);
}
#[test]
fn format_configuration_specification() {
check_declaration(
"\
for all: lib.pkg.comp
use entity work.foo(rtl);",
);
check_declaration(
"\
for all: lib.pkg.comp
use entity work.foo(rtl);
end for;",
);
check_declaration(
"\
for all: lib.pkg.comp
use entity work.foo(rtl);
use vunit bar, baz;
end for;",
);
}
}

View file

@ -0,0 +1,304 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{AnyDesignUnit, AnyPrimaryUnit, AnySecondaryUnit, PackageBody};
use crate::formatting::buffer::Buffer;
use crate::HasTokenSpan;
use vhdl_lang::ast::PackageDeclaration;
use vhdl_lang::formatting::VHDLFormatter;
use vhdl_lang::{indented, TokenSpan};
impl VHDLFormatter<'_> {
pub fn format_any_design_unit(&self, unit: &AnyDesignUnit, buffer: &mut Buffer, is_last: bool) {
use AnyDesignUnit::*;
match unit {
Primary(primary) => self.format_any_primary_unit(primary, buffer),
Secondary(secondary) => self.format_any_secondary_unit(secondary, buffer),
}
if !is_last {
buffer.line_breaks(2);
}
}
pub fn format_any_primary_unit(&self, unit: &AnyPrimaryUnit, buffer: &mut Buffer) {
use AnyPrimaryUnit::*;
match unit {
Entity(entity) => self.format_entity(entity, buffer),
Configuration(configuration) => self.format_configuration(configuration, buffer),
Package(package) => self.format_package(package, buffer),
PackageInstance(package_instance) => {
self.format_package_instance(package_instance, buffer)
}
Context(context) => self.format_context(context, buffer),
}
}
pub fn format_any_secondary_unit(&self, unit: &AnySecondaryUnit, buffer: &mut Buffer) {
use AnySecondaryUnit::*;
match unit {
Architecture(architecture) => self.format_architecture(architecture, buffer),
PackageBody(body) => self.format_package_body(body, buffer),
}
}
pub fn format_package(&self, package: &PackageDeclaration, buffer: &mut Buffer) {
self.format_context_clause(&package.context_clause, buffer);
if let Some(item) = package.context_clause.last() {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
// package <ident> is
self.format_token_span(
TokenSpan::new(package.span.start_token, package.span.start_token + 2),
buffer,
);
indented!(buffer, {
if let Some(generic_clause) = &package.generic_clause {
buffer.line_break();
self.format_interface_list(generic_clause, buffer);
}
self.format_declarations(&package.decl, buffer);
});
buffer.line_break();
self.format_token_span(
TokenSpan::new(package.end_token, package.span.end_token - 1),
buffer,
);
self.format_token_id(package.span.end_token, buffer);
}
pub fn format_package_body(&self, body: &PackageBody, buffer: &mut Buffer) {
self.format_context_clause(&body.context_clause, buffer);
if let Some(item) = body.context_clause.last() {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
// package body <ident> is
self.format_token_span(
TokenSpan::new(body.span.start_token, body.span.start_token + 3),
buffer,
);
indented!(buffer, { self.format_declarations(&body.decl, buffer) });
buffer.line_break();
self.format_token_span(
TokenSpan::new(body.end_token, body.span.end_token - 1),
buffer,
);
self.format_token_id(body.span.end_token, buffer);
}
}
#[cfg(test)]
mod test {
use crate::analysis::tests::Code;
use vhdl_lang::formatting::test_utils::check_formatted;
fn check_package_formatted(input: &str) {
check_formatted(
input,
input,
Code::package_declaration,
|formatter, package, buffer| formatter.format_package(package, buffer),
);
}
#[test]
fn format_simple_package() {
check_package_formatted(
"\
package foo is
end package;",
);
check_package_formatted(
"\
package foo is
end package foo;",
);
}
#[test]
fn format_package_with_declarations() {
check_package_formatted(
"\
package pkg_name is
type foo;
constant bar: natural := 0;
end package;",
);
}
#[test]
fn format_package_with_generics() {
check_package_formatted(
"\
package pkg_name is
generic (
type foo;
type bar
);
end package;",
);
}
#[test]
fn format_package_with_context_clause() {
check_package_formatted(
"\
package pkg_name is
generic (
type foo;
type bar
);
end package;",
);
}
fn check_context_formatted(input: &str) {
check_formatted(
input,
input,
Code::context_declaration,
|formatter, package, buffer| formatter.format_context(package, buffer),
);
}
#[test]
fn check_simple_context() {
check_context_formatted(
"\
context ident is
end;",
);
check_context_formatted(
"\
context ident is
end context;",
);
check_context_formatted(
"\
context ident is
end ident;",
);
check_context_formatted(
"\
context ident is
end context ident;",
);
}
#[test]
fn check_context_items() {
check_context_formatted(
"\
context ident is
library foo;
use foo.bar;
context foo.ctx;
end context;",
);
}
fn check_design_unit_formatted(input: &str) {
check_formatted(
input,
input,
Code::design_file,
|formatter, file, buffer| {
formatter.format_any_design_unit(&file.design_units[0].1, buffer, true)
},
);
}
#[test]
fn design_unit_context_clause_preserve_whitespaces() {
check_design_unit_formatted(
"\
library lib;
use lib.foo.all;
package pkg_name is
end package;",
);
check_design_unit_formatted(
"\
library lib;
use lib.foo.all;
package pkg_name is
end package;",
);
check_design_unit_formatted(
"\
library lib;
use lib.foo.all;
package pkg_name is
end package;",
);
}
#[test]
fn check_package_body() {
check_design_unit_formatted(
"\
package body foo is
end package body;",
)
}
#[test]
fn check_whitespace_preservation_context() {
check_design_unit_formatted(
"\
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;
library third_party;
use third_party.baz;
use work.foo.bar;
package body foo is
end package body;",
)
}
#[test]
fn check_whitespace_preservation_tokens_with_comments() {
check_design_unit_formatted(
"\
library ieee;
-- This is a comment
-- This is another comment
-- Third comment
library third_party;
use third_party.baz;
package body foo is
end package body;",
)
}
#[test]
fn check_whitespace_preservation_within_comments() {
check_design_unit_formatted(
"\
-- This is a comment
-- This ine appears later
-- Third comment
library third_party;
use third_party.baz;
package body foo is
end package body;",
)
}
}

View file

@ -0,0 +1,187 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::EntityDeclaration;
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::{HasTokenSpan, TokenSpan};
use vhdl_lang::indented;
impl VHDLFormatter<'_> {
pub fn format_entity(&self, entity: &EntityDeclaration, buffer: &mut Buffer) {
self.format_context_clause(&entity.context_clause, buffer);
if let Some(item) = entity.context_clause.last() {
self.line_break_preserve_whitespace(item.span().end_token, buffer);
}
let span = entity.span();
// entity <ident> is
self.format_token_span(TokenSpan::new(span.start_token, entity.is_token()), buffer);
if let Some(generic_clause) = &entity.generic_clause {
indented!(buffer, {
buffer.line_break();
self.format_interface_list(generic_clause, buffer);
});
}
if let Some(port_clause) = &entity.port_clause {
indented!(buffer, {
buffer.line_break();
self.format_interface_list(port_clause, buffer);
});
}
indented!(buffer, { self.format_declarations(&entity.decl, buffer) });
if let Some(token) = entity.begin_token {
buffer.line_break();
self.format_token_id(token, buffer);
}
self.format_concurrent_statements(&entity.statements, buffer);
buffer.line_break();
// end [entity] [name];
self.format_token_span(TokenSpan::new(entity.end_token, span.end_token - 1), buffer);
self.format_token_id(span.end_token, buffer);
}
}
#[cfg(test)]
mod test {
use crate::analysis::tests::Code;
use vhdl_lang::formatting::test_utils::check_formatted;
fn check_entity_formatted(input: &str) {
check_formatted(
input,
input,
Code::entity_decl,
|formatter, entity, buffer| formatter.format_entity(entity, buffer),
);
}
#[test]
fn test_format_simple_entity() {
check_entity_formatted(
"\
entity my_ent is
end entity my_ent;",
);
check_entity_formatted(
"\
entity my_ent is
end my_ent;",
);
check_entity_formatted(
"\
entity my_ent is
end;",
);
check_entity_formatted(
"\
entity my_ent is
end entity;",
);
check_entity_formatted(
"\
entity my_ent is
begin
end entity;",
);
}
#[test]
fn test_entity_with_comments() {
check_entity_formatted(
"\
-- Some comment about the entity
entity my_ent is
end entity;",
);
check_entity_formatted(
"\
entity my_ent is -- trailing comment
end entity;",
);
check_entity_formatted(
"\
entity /* Why would you put a comment here? */ my_ent is
end entity;",
);
check_entity_formatted(
"\
entity /* Why would you put a comment here? */ my_ent is -- this is an entity
end entity;",
);
}
#[test]
fn test_entity_with_simple_generic() {
check_entity_formatted(
"\
entity foo is
-- Generics come here
generic (
foo: in std_logic --<This is it
);
end foo;",
);
}
#[test]
fn test_entity_generic_default_value() {
check_entity_formatted(
"\
entity foo is
generic (
foo: in std_logic := '1'
);
end foo;",
);
}
#[test]
fn test_entity_with_ports() {
check_entity_formatted(
"\
entity foo is
port (
foo: in std_logic := '1'
);
end foo;",
);
}
#[test]
fn test_entity_with_generics_and_ports() {
check_entity_formatted(
"\
entity foo is
generic (
a: in std_logic := '1'
);
port (
B: in std_logic := '1'
);
end foo;",
);
}
#[test]
fn test_entity_with_declarations() {
check_entity_formatted(
"\
entity foo is
port (
foo: in std_logic := '1'
);
constant x: foo := bar;
signal y: bar := foobar;
end foo;",
);
}
}

View file

@ -0,0 +1,286 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{
ElementAssociation, Expression, Operator, ResolutionIndication, SubtypeConstraint,
SubtypeIndication,
};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::HasTokenSpan;
use vhdl_lang::ast::{Allocator, QualifiedExpression};
impl VHDLFormatter<'_> {
pub fn format_expression(&self, expression: WithTokenSpan<&Expression>, buffer: &mut Buffer) {
let span = expression.span;
use Expression::*;
match &expression.item {
Binary(op, lhs, rhs) => {
self.format_expression(lhs.as_ref().as_ref(), buffer);
buffer.push_whitespace();
self.format_token_id(op.token, buffer);
buffer.push_whitespace();
self.format_expression(rhs.as_ref().as_ref(), buffer);
}
Unary(op, rhs) => {
self.format_token_id(op.token, buffer);
match op.item.item {
Operator::Minus | Operator::Plus | Operator::QueQue => {
// Leave as unary operator without whitespace
}
_ => buffer.push_whitespace(),
}
self.format_expression(rhs.as_ref().as_ref(), buffer);
}
Aggregate(aggregate) => {
self.format_token_id(span.start_token, buffer);
self.format_element_associations(aggregate, buffer);
self.format_token_id(span.end_token, buffer);
}
Qualified(qualified_expr) => self.format_qualified_expression(qualified_expr, buffer),
Name(name) => self.format_name(WithTokenSpan::new(name, span), buffer),
Literal(_) => self.format_token_span(span, buffer),
New(allocator) => self.format_allocator(allocator, buffer),
Parenthesized(expression) => {
self.format_token_id(span.start_token, buffer);
self.format_expression(expression.as_ref().as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
}
}
pub fn format_element_associations(
&self,
associations: &[WithTokenSpan<ElementAssociation>],
buffer: &mut Buffer,
) {
for (i, association) in associations.iter().enumerate() {
match &association.item {
ElementAssociation::Positional(expression) => {
self.format_expression(expression.as_ref(), buffer)
}
ElementAssociation::Named(choices, expression) => {
for (j, choice) in choices.iter().enumerate() {
self.format_choice(choice, buffer);
if j < choices.len() - 1 {
buffer.push_whitespace();
self.format_token_id(choice.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
buffer.push_whitespace();
self.format_token_id(expression.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(expression.as_ref(), buffer);
}
}
if i < associations.len() - 1 {
self.format_token_id(association.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
}
pub fn format_subtype_indication(&self, indication: &SubtypeIndication, buffer: &mut Buffer) {
if let Some(resolution) = &indication.resolution {
self.format_resolution_indication(resolution, buffer);
buffer.push_whitespace();
}
self.format_name(indication.type_mark.as_ref(), buffer);
if let Some(constraint) = &indication.constraint {
if matches!(constraint.item, SubtypeConstraint::Range(_)) {
buffer.push_whitespace();
}
self.format_subtype_constraint(constraint, buffer)
}
}
pub fn format_resolution_indication(
&self,
indication: &ResolutionIndication,
buffer: &mut Buffer,
) {
match &indication {
ResolutionIndication::FunctionName(name) => self.format_name(name.as_ref(), buffer),
ResolutionIndication::ArrayElement(element) => {
self.format_token_id(element.span.start_token - 1, buffer);
self.format_name(element.as_ref(), buffer);
self.format_token_id(element.span.end_token + 1, buffer);
}
ResolutionIndication::Record(record) => {
let span = record.span;
self.format_token_id(span.start_token, buffer);
for (i, element_resolution) in record.item.iter().enumerate() {
self.format_token_id(element_resolution.ident.token, buffer);
buffer.push_whitespace();
self.format_resolution_indication(&element_resolution.resolution, buffer);
if i < record.item.len() - 1 {
// ,
self.format_token_id(
element_resolution.resolution.get_end_token() + 1,
buffer,
);
buffer.push_whitespace();
}
}
self.format_token_id(span.end_token, buffer);
}
}
}
// Helper to format ` := <expression>`
pub(crate) fn format_default_expression(
&self,
expression: Option<&WithTokenSpan<Expression>>,
buffer: &mut Buffer,
) {
if let Some(expr) = expression {
buffer.push_whitespace();
self.format_token_id(expr.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(expr.as_ref(), buffer);
}
}
pub fn format_qualified_expression(
&self,
expression: &QualifiedExpression,
buffer: &mut Buffer,
) {
self.format_name(expression.type_mark.as_ref(), buffer);
// '
self.format_token_id(expression.type_mark.span.end_token + 1, buffer);
self.format_expression(expression.expr.as_ref(), buffer);
}
pub fn format_allocator(&self, allocator: &WithTokenSpan<Allocator>, buffer: &mut Buffer) {
// new
self.format_token_id(allocator.span.start_token - 1, buffer);
buffer.push_whitespace();
match &allocator.item {
Allocator::Qualified(expr) => self.format_qualified_expression(expr, buffer),
Allocator::Subtype(subtype) => self.format_subtype_indication(subtype, buffer),
}
}
}
#[cfg(test)]
mod test {
use crate::analysis::tests::Code;
use crate::ast::token_range::WithTokenSpan;
use crate::formatting::VHDLFormatter;
use vhdl_lang::formatting::buffer::Buffer;
use vhdl_lang::formatting::test_utils::check_formatted;
fn check_expression(input: &str) {
let code = Code::new(input);
let expression = code.expr();
let tokens = code.tokenize();
let formatter = VHDLFormatter::new(&tokens);
let mut buffer = Buffer::new();
formatter.format_expression(
WithTokenSpan::new(&expression.item, code.token_span()),
&mut buffer,
);
assert_eq!(buffer.as_str(), input);
}
#[test]
fn test_simple_expression() {
check_expression("name")
}
#[test]
fn test_parenthesized_expression() {
check_expression("(name)");
check_expression("(A) or (B)");
}
#[test]
fn formal_literal() {
check_expression("12387.44e7");
check_expression("7");
}
#[test]
fn binary_expressions() {
check_expression("1 + B");
check_expression("2 sll 2");
}
#[test]
fn unary_expressions() {
check_expression("+B");
check_expression("-2");
check_expression("not A")
}
#[test]
fn complex_expression() {
check_expression("A + B - C");
check_expression("(A * B) + C");
check_expression("((A * B) + C)");
}
#[test]
fn aggregate() {
check_expression("(1, 2)");
check_expression("(1 => 2, 3)");
check_expression("(others => 1, others => 2)");
check_expression("(1 downto 0 => 2)");
check_expression("(0 to 1 => 2)");
check_expression("(1 | 2 => 3)");
}
#[test]
fn qualified_expressions() {
check_expression("integer_vector'(0, 1)");
check_expression("foo'(1 + 2)");
check_expression("foo'(others => '1')");
}
#[test]
fn allocator_expressions() {
check_expression("new integer_vector'(0, 1)");
check_expression("new integer_vector");
check_expression("new integer_vector(0 to 1)");
check_expression("new integer_vector(foo'range)");
}
fn check_subtype_indication(input: &str) {
check_formatted(
input,
input,
Code::subtype_indication,
|formatter, subtype_indication, buffer| {
formatter.format_subtype_indication(subtype_indication, buffer)
},
);
}
#[test]
fn resolution_indication() {
check_subtype_indication("resolve std_logic");
check_subtype_indication("(resolve) integer_vector");
check_subtype_indication("(elem resolve) rec_t");
check_subtype_indication(
"(elem1 (resolve1), elem2 resolve2, elem3 (sub_elem sub_resolve)) rec_t",
);
}
#[test]
fn expression_with_comments() {
check_expression(
"\
-- Some comment
A & -- And
B & -- as well as
C",
)
}
}

View file

@ -0,0 +1,389 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{
ActualPart, AssociationElement, ElementMode, InterfaceDeclaration, InterfaceList,
InterfaceObjectDeclaration, InterfacePackageDeclaration, InterfacePackageGenericMapAspect,
InterfaceSubprogramDeclaration, MapAspect, ModeIndication, ModeViewElement,
ModeViewIndicationKind, SeparatedList, SimpleModeIndication, SubprogramDefault,
};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::Kind;
use crate::{indented, HasTokenSpan, TokenAccess};
use vhdl_lang::ast::token_range::WithTokenSpan;
use vhdl_lang::ast::{InterfaceFileDeclaration, ModeViewIndication};
use vhdl_lang::TokenSpan;
impl VHDLFormatter<'_> {
pub(crate) fn format_interface_list(&self, clause: &InterfaceList, buffer: &mut Buffer) {
let span = clause.span;
let end_token = if self.tokens.index(span.start_token).kind == Kind::LeftPar {
// We start with a `(` immediately
// applicable for parameters (though VHDL 2008 allows an optional `parameter` keyword)
span.start_token
} else {
// We start with a `generic`, `port` or `parameter` keyword
span.start_token + 1
};
// port (
// generic (
// parameter (
// (
self.format_token_span(TokenSpan::new(span.start_token, end_token), buffer);
indented!(buffer, {
for (i, item) in clause.items.iter().enumerate() {
buffer.line_break();
self.format_interface_declaration(item, buffer);
if i < clause.items.len() - 1 {
self.format_token_id(item.get_end_token() + 1, buffer);
}
}
});
if !clause.items.is_empty() {
buffer.line_break();
}
if self.tokens.index(span.end_token).kind == Kind::SemiColon {
// );
self.format_token_id(span.end_token - 1, buffer);
self.format_token_id(span.end_token, buffer);
} else {
self.format_token_id(span.end_token, buffer);
}
}
pub fn format_map_aspect_span(
&self,
list: &SeparatedList<AssociationElement>,
span: TokenSpan,
buffer: &mut Buffer,
) {
// port map (
// generic map (
self.format_token_span(
TokenSpan::new(span.start_token, span.start_token + 2),
buffer,
);
indented!(buffer, {
for (i, item) in list.items.iter().enumerate() {
buffer.line_break();
self.format_association_element(item, buffer);
if let Some(token) = list.tokens.get(i) {
self.format_token_id(*token, buffer);
}
}
});
if !list.items.is_empty() {
buffer.line_break();
}
// )
self.format_token_id(span.end_token, buffer);
}
pub fn format_map_aspect(&self, aspect: &MapAspect, buffer: &mut Buffer) {
self.format_map_aspect_span(&aspect.list, aspect.span, buffer);
}
pub fn format_association_element(&self, element: &AssociationElement, buffer: &mut Buffer) {
if let Some(formal) = &element.formal {
self.format_name(formal.as_ref(), buffer);
buffer.push_whitespace();
self.format_token_id(formal.span.end_token + 1, buffer);
buffer.push_whitespace();
}
self.format_actual_part(&element.actual, buffer)
}
pub fn format_actual_part(&self, actual_part: &WithTokenSpan<ActualPart>, buffer: &mut Buffer) {
match &actual_part.item {
ActualPart::Expression(expression) => {
self.format_expression(WithTokenSpan::new(expression, actual_part.span), buffer)
}
ActualPart::Open => self.format_token_span(actual_part.span, buffer),
}
}
pub fn format_interface_declaration(
&self,
declaration: &InterfaceDeclaration,
buffer: &mut Buffer,
) {
use InterfaceDeclaration::*;
match declaration {
Object(object) => self.format_interface_object(object, buffer),
File(file) => self.format_interface_file_declaration(file, buffer),
Type(type_decl) => {
self.format_token_id(type_decl.tree.token - 1, buffer);
buffer.push_whitespace();
self.format_ident(type_decl, buffer);
}
Subprogram(subprogram) => {
self.format_interface_subprogram_declaration(subprogram, buffer)
}
Package(package) => self.format_interface_package_declaration(package, buffer),
}
}
pub fn format_interface_file_declaration(
&self,
declaration: &InterfaceFileDeclaration,
buffer: &mut Buffer,
) {
self.format_token_id(declaration.span.start_token, buffer);
buffer.push_whitespace();
self.format_ident_list(&declaration.idents, buffer);
self.format_token_id(declaration.colon_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(&declaration.subtype_indication, buffer);
}
pub fn format_interface_subprogram_declaration(
&self,
subprogram: &InterfaceSubprogramDeclaration,
buffer: &mut Buffer,
) {
self.format_subprogram_specification(&subprogram.specification, buffer);
if let Some(default) = &subprogram.default {
buffer.push_whitespace();
// is
self.format_token_id(subprogram.specification.span().end_token + 1, buffer);
buffer.push_whitespace();
match default {
SubprogramDefault::Name(name) => self.format_name(name.as_ref(), buffer),
SubprogramDefault::Box => {
self.format_token_id(subprogram.specification.span().end_token + 2, buffer)
}
}
}
}
pub fn format_interface_package_declaration(
&self,
package: &InterfacePackageDeclaration,
buffer: &mut Buffer,
) {
// package <ident> is new
self.format_token_span(
TokenSpan::new(package.span.start_token, package.span.start_token + 3),
buffer,
);
buffer.push_whitespace();
self.format_name(package.package_name.as_ref(), buffer);
buffer.push_whitespace();
let span = package.generic_map.span();
match &package.generic_map.item {
InterfacePackageGenericMapAspect::Map(map) => {
self.format_map_aspect_span(map, span, buffer)
}
InterfacePackageGenericMapAspect::Box | InterfacePackageGenericMapAspect::Default => {
// generic
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
// map
self.format_token_id(span.start_token + 1, buffer);
buffer.push_whitespace();
//(
self.format_token_id(span.start_token + 2, buffer);
// <> | default
self.format_token_id(span.start_token + 3, buffer);
// )
self.format_token_id(span.start_token + 4, buffer);
}
}
}
pub fn format_interface_object(
&self,
object: &InterfaceObjectDeclaration,
buffer: &mut Buffer,
) {
// [signal] my_signal :
self.format_token_span(
TokenSpan::new(object.span.start_token, object.colon_token - 1),
buffer,
);
self.format_token_id(object.colon_token, buffer);
buffer.push_whitespace();
self.format_mode(&object.mode, buffer);
}
pub fn format_mode(&self, mode: &ModeIndication, buffer: &mut Buffer) {
use ModeIndication::*;
match mode {
Simple(simple) => self.format_simple_mode(simple, buffer),
View(mode) => self.format_mode_view_indication(mode, buffer),
}
}
pub fn format_mode_view_indication(&self, mode: &ModeViewIndication, buffer: &mut Buffer) {
// view
self.format_token_id(mode.span.start_token, buffer);
buffer.push_whitespace();
match &mode.kind {
ModeViewIndicationKind::Array => {
self.format_token_id(mode.name.span.start_token - 1, buffer);
self.format_name(mode.name.as_ref(), buffer);
self.format_token_id(mode.name.span.end_token + 1, buffer);
}
ModeViewIndicationKind::Record => {
self.format_name(mode.name.as_ref(), buffer);
}
}
if let Some((token, subtype)) = &mode.subtype_indication {
buffer.push_whitespace();
self.format_token_id(*token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(subtype, buffer);
}
}
pub fn format_element_mode(&self, mode: &ElementMode, buffer: &mut Buffer) {
match mode {
ElementMode::Simple(simple) => self.format_token_id(simple.token, buffer),
ElementMode::Record(name) => {
// view
self.format_token_id(name.get_start_token() - 1, buffer);
buffer.push_whitespace();
self.format_name(name.as_ref(), buffer)
}
ElementMode::Array(name) => {
//view (
self.format_token_span(
TokenSpan::new(name.get_start_token() - 2, name.get_start_token() - 1),
buffer,
);
self.format_name(name.as_ref(), buffer);
// )
self.format_token_id(name.get_end_token() + 1, buffer);
}
}
}
pub fn format_mode_view_element(&self, mode: &ModeViewElement, buffer: &mut Buffer) {
self.format_ident_list(&mode.names, buffer);
self.format_token_id(mode.colon_token, buffer);
buffer.push_whitespace();
self.format_element_mode(&mode.mode, buffer);
// ;
self.format_token_id(mode.span.end_token, buffer);
}
pub fn format_simple_mode(&self, mode: &SimpleModeIndication, buffer: &mut Buffer) {
if let Some(mode) = &mode.mode {
self.format_token_id(mode.token, buffer);
buffer.push_whitespace();
}
self.format_subtype_indication(&mode.subtype_indication, buffer);
self.format_default_expression(mode.expression.as_ref(), buffer);
}
}
#[cfg(test)]
mod tests {
use crate::analysis::tests::Code;
use crate::formatting::test_utils::{check_formatted, check_formatted_std};
use crate::VHDLStandard::VHDL2019;
fn check_generic(input: &str) {
check_formatted(
input,
input,
Code::generic,
|formatter, interface, buffer| {
formatter.format_interface_declaration(interface, buffer)
},
);
}
#[test]
fn format_simple_object() {
check_generic("my_generic: natural");
}
#[test]
fn format_simple_object_with_default() {
check_generic("my_generic: natural := 7");
}
#[test]
fn format_simple_object_with_explicit_mode() {
check_generic("my_generic: in natural");
}
#[test]
fn format_object_with_class() {
check_generic("constant my_generic: in natural");
check_generic("constant my_generic: natural");
}
fn check_element_mode(input: &str) {
check_formatted_std(
input,
input,
VHDL2019,
Code::element_mode,
|formatter, mode, buffer| formatter.format_element_mode(mode, buffer),
);
}
#[test]
fn format_element_mode() {
check_element_mode("in");
check_element_mode("out");
check_element_mode("inout");
check_element_mode("buffer");
check_element_mode("linkage");
check_element_mode("view foo");
check_element_mode("view (foo)");
}
fn check_port(input: &str) {
check_formatted_std(
input,
input,
VHDL2019,
Code::port,
|formatter, interface, buffer| {
formatter.format_interface_declaration(interface, buffer)
},
);
}
#[test]
fn format_mode_view_indication() {
check_port("signal foo: view bar");
check_port("signal foo: view (bar)");
check_port("signal foo: view bar of baz");
check_port("signal foo: view (bar) of baz");
}
#[test]
fn format_interface_file_declaration() {
check_port("file valid: text");
}
#[test]
fn format_interface_subprogram_declaration() {
check_generic("function foo return bar");
check_generic("procedure foo");
check_generic("impure function foo return bar");
check_generic("function foo return bar is lib.name");
check_generic("function foo return bar is <>");
}
#[test]
fn format_interface_package_declaration() {
check_generic(
"\
package foo is new lib.pkg generic map (
foo => bar
)",
);
check_generic("package foo is new lib.pkg generic map (<>)");
check_generic("package foo is new lib.pkg generic map (default)");
}
}

View file

@ -0,0 +1,122 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::DesignFile;
use crate::formatting::buffer::Buffer;
use crate::syntax::Kind;
use crate::{Token, TokenAccess};
use vhdl_lang::ast::HasIdent;
mod architecture;
mod buffer;
mod concurrent_statement;
mod configuration;
mod constraint;
mod context;
mod declaration;
mod design;
mod entity;
mod expression;
mod interface;
mod name;
mod sequential_statement;
mod statement;
mod subprogram;
mod token;
/// The formatter is the main entry point used for formatting a single
/// Design Unit from AST representation to string representation. In that sense,
/// the Formatter is the inverse to the Parser.
///
/// Most methods herein are called `format_<node>` where `node` is the AST node to format.
/// Rather than returning a string, the methods accept a mutable [Buffer] object that they
/// use to format.
///
/// The formatter is capable of retaining comment information as well as preserving newlines.
pub struct VHDLFormatter<'b> {
tokens: &'b Vec<Token>,
}
impl<'b> VHDLFormatter<'b> {
pub fn new(tokens: &'b Vec<Token>) -> VHDLFormatter<'b> {
VHDLFormatter { tokens }
}
/// Format a whole design file.
pub fn format_design_file(file: &DesignFile) -> String {
let mut result = Buffer::new();
for (i, (tokens, design_unit)) in file.design_units.iter().enumerate() {
let formatter = VHDLFormatter::new(tokens);
formatter.format_any_design_unit(
design_unit,
&mut result,
i == file.design_units.len() - 1,
);
}
result.into()
}
}
impl VHDLFormatter<'_> {
pub fn format_ident_list<T: HasIdent>(&self, idents: &[T], buffer: &mut Buffer) {
for ident in idents {
let token = ident.ident().token;
self.format_token_id(token, buffer);
if self
.tokens
.get_token(token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(token + 1, buffer);
buffer.push_whitespace();
}
}
}
}
/// indents the provided block and de-indents at the end.
#[macro_export]
macro_rules! indented {
($buffer:ident, $block:block) => {
$buffer.increase_indent();
$block
$buffer.decrease_indent();
};
}
#[cfg(test)]
pub mod test_utils {
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::test::Code;
use vhdl_lang::VHDLStandard;
pub(crate) fn check_formatted<T>(
input: &str,
expected: &str,
to_ast: impl FnOnce(&Code) -> T,
format: impl FnOnce(&VHDLFormatter, &T, &mut Buffer),
) {
check_formatted_std(input, expected, VHDLStandard::default(), to_ast, format)
}
pub(crate) fn check_formatted_std<T>(
input: &str,
expected: &str,
std: VHDLStandard,
to_ast: impl FnOnce(&Code) -> T,
format: impl FnOnce(&VHDLFormatter, &T, &mut Buffer),
) {
let code = Code::with_standard(input, std);
let ast_element = to_ast(&code);
let tokens = code.tokenize();
let formatter = VHDLFormatter::new(&tokens);
let mut buffer = Buffer::new();
format(&formatter, &ast_element, &mut buffer);
assert_eq!(buffer.as_str(), expected);
}
}

View file

@ -0,0 +1,192 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{CallOrIndexed, ExternalName, ExternalPath};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::Kind;
use crate::{TokenAccess, TokenSpan};
use vhdl_lang::ast::{AttributeName, Name};
impl VHDLFormatter<'_> {
pub fn format_name(&self, name: WithTokenSpan<&Name>, buffer: &mut Buffer) {
use Name::*;
let span = name.span;
match &name.item {
Designator(_) => self.join_token_span(span, buffer),
Selected(name, designator) => {
self.format_name(name.as_ref().as_ref(), buffer);
self.join_token_span(
TokenSpan::new(designator.token - 1, designator.token),
buffer,
);
}
SelectedAll(name) => {
self.format_name(name.as_ref().as_ref(), buffer);
self.join_token_span(TokenSpan::new(span.end_token - 1, span.end_token), buffer);
}
Slice(name, range) => {
self.format_name(name.as_ref().as_ref(), buffer);
self.format_token_id(name.span.end_token + 1, buffer);
self.format_discrete_range(range, buffer);
self.format_token_id(span.end_token, buffer);
}
Attribute(attr_name) => self.format_attribute_name(attr_name, buffer),
CallOrIndexed(call_or_indexed) => {
self.format_call_or_indexed(call_or_indexed, span, buffer)
}
External(external) => {
self.format_external_name(WithTokenSpan::new(external, span), buffer)
}
}
}
pub fn format_call_or_indexed(
&self,
call: &CallOrIndexed,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_name(call.name.as_ref(), buffer);
let open_paren = call.name.span.end_token + 1;
if self.tokens.index(open_paren).kind == Kind::LeftPar {
self.format_token_id(open_paren, buffer);
}
for (i, parameter) in call.parameters.items.iter().enumerate() {
self.format_association_element(parameter, buffer);
if let Some(token) = call.parameters.tokens.get(i) {
self.format_token_id(*token, buffer);
buffer.push_whitespace();
}
}
let close_paren = span.end_token;
if self.tokens.index(close_paren).kind == Kind::RightPar {
self.format_token_id(close_paren, buffer);
}
}
pub fn format_attribute_name(&self, name: &AttributeName, buffer: &mut Buffer) {
self.format_name(name.name.as_ref(), buffer);
if let Some(signature) = &name.signature {
self.format_signature(signature, buffer);
}
// '
self.format_token_id(name.attr.token - 1, buffer);
self.format_token_id(name.attr.token, buffer);
if let Some(expr) = &name.expr {
self.format_token_id(expr.span.start_token - 1, buffer);
self.format_expression(expr.as_ref().as_ref(), buffer);
self.format_token_id(expr.span.end_token + 1, buffer);
}
}
pub fn format_external_name(&self, name: WithTokenSpan<&ExternalName>, buffer: &mut Buffer) {
// <<
self.format_token_id(name.span.start_token, buffer);
buffer.push_whitespace();
// entity class
self.format_token_id(name.span.start_token + 1, buffer);
buffer.push_whitespace();
let path = &name.item.path;
match &path.item {
ExternalPath::Package(name) => {
// @
self.format_token_id(name.span.start_token - 1, buffer);
self.format_name(name.as_ref(), buffer)
}
ExternalPath::Absolute(name) => {
// .
self.format_token_id(name.span.start_token - 1, buffer);
self.format_name(name.as_ref(), buffer);
}
ExternalPath::Relative(name, up_levels) => {
for i in (1..=*up_levels).rev() {
// ^
self.format_token_id(name.span.start_token - (2 * i), buffer);
// .
self.format_token_id(name.span.start_token - (2 * i - 1), buffer);
}
self.format_name(name.as_ref(), buffer)
}
}
buffer.push_whitespace();
self.format_token_id(name.item.colon_token, buffer);
buffer.push_whitespace();
self.format_subtype_indication(&name.item.subtype, buffer);
buffer.push_whitespace();
// >>
self.format_token_id(name.span.end_token, buffer);
}
pub fn format_name_list(&self, buffer: &mut Buffer, names: &[WithTokenSpan<Name>]) {
for name in names {
self.format_name(name.as_ref(), buffer);
if self
.tokens
.get_token(name.span.end_token + 1)
.is_some_and(|token| token.kind == Kind::Comma)
{
self.format_token_id(name.span.end_token + 1, buffer);
buffer.push_whitespace();
}
}
}
}
#[cfg(test)]
pub mod tests {
use crate::syntax::test::Code;
use vhdl_lang::formatting::test_utils::check_formatted;
pub fn check_name(input: &str) {
check_formatted(input, input, Code::name, |formatter, ast, buffer| {
formatter.format_name(ast.as_ref(), buffer)
})
}
#[test]
fn simple_names() {
check_name("\"+\"");
check_name("\"AND\"");
check_name("\"and\"");
}
#[test]
fn selected_names() {
check_name("foo.bar.baz");
check_name("foo.all");
}
#[test]
fn slice_names() {
check_name("prefix(0 to 3)");
check_name("prefix(3 downto 0)");
}
#[test]
fn attribute_name() {
check_name("prefix'subtype");
check_name("prefix'element");
check_name("prefix'foo(expr + 1)");
check_name("prefix[return natural]'foo(expr + 1)");
}
#[test]
fn complex_names() {
check_name("prefix(foo(0)'range)");
}
#[test]
fn external_names() {
check_name("<< signal dut.gen(0) : std_logic >>");
check_name("<< signal .dut.gen(0) : std_logic >>");
check_name("<< signal @dut.gen(0) : std_logic >>");
check_name("<< signal ^.dut.gen(0) : std_logic >>");
check_name("<< signal ^.^.^.dut.gen(0) : std_logic >>");
}
}

View file

@ -0,0 +1,671 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{
AssignmentRightHand, CaseStatement, Choice, DelayMechanism, Expression, Ident, IterationScheme,
LabeledSequentialStatement, LoopStatement, ReportStatement, SequentialStatement,
SignalAssignment, WaitStatement, WithRef,
};
use crate::formatting::buffer::Buffer;
use crate::{HasTokenSpan, TokenSpan};
use vhdl_lang::ast::{
ExitStatement, IfStatement, NextStatement, SignalForceAssignment, SignalReleaseAssignment,
VariableAssignment,
};
use vhdl_lang::formatting::VHDLFormatter;
use vhdl_lang::indented;
impl VHDLFormatter<'_> {
pub fn format_sequential_statements(
&self,
statements: &[LabeledSequentialStatement],
buffer: &mut Buffer,
) {
if statements.is_empty() {
return;
}
indented!(buffer, {
buffer.line_break();
for (i, item) in statements.iter().enumerate() {
self.format_labeled_sequential_statement(item, buffer);
if i < statements.len() - 1 {
self.line_break_preserve_whitespace(item.statement.get_end_token(), buffer);
}
}
});
}
pub fn format_labeled_sequential_statement(
&self,
statement: &LabeledSequentialStatement,
buffer: &mut Buffer,
) {
self.format_optional_label(statement.label.tree.as_ref(), buffer);
self.format_sequential_statement(&statement.statement, buffer);
}
pub fn format_sequential_statement(
&self,
statement: &WithTokenSpan<SequentialStatement>,
buffer: &mut Buffer,
) {
use SequentialStatement::*;
let span = statement.span;
match &statement.item {
Wait(wait_statement) => self.format_wait_statement(wait_statement, span, buffer),
Assert(assert) => {
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_assert_statement(assert, buffer);
self.format_token_id(span.end_token, buffer);
}
Report(report) => self.format_report_statement(report, span, buffer),
VariableAssignment(variable_assignment) => {
self.format_variable_assignment(variable_assignment, span, buffer)
}
SignalAssignment(signal_assignment) => {
self.format_signal_assignment(signal_assignment, span, buffer)
}
SignalForceAssignment(signal_assignment) => {
self.format_signal_force_assignment(signal_assignment, span, buffer)
}
SignalReleaseAssignment(signal_assignment) => {
self.format_signal_release_assignment(signal_assignment, span, buffer)
}
ProcedureCall(call_or_indexed) => {
self.format_call_or_indexed(&call_or_indexed.item, call_or_indexed.span, buffer);
self.format_token_id(span.end_token, buffer);
}
If(if_statement) => {
self.format_if_statement(if_statement, span, buffer);
}
Case(case_statement) => {
self.format_case_statement(case_statement, span, buffer);
}
Loop(loop_statement) => {
self.format_loop_statement(loop_statement, span, buffer);
}
Next(next_statement) => self.format_next_statement(next_statement, span, buffer),
Exit(exit_statement) => self.format_exit_statement(exit_statement, span, buffer),
Return(stmt) => {
self.format_token_id(span.start_token, buffer);
if let Some(expr) = &stmt.expression {
buffer.push_whitespace();
self.format_expression(expr.as_ref(), buffer);
}
self.format_token_id(span.end_token, buffer);
}
Null => self.join_token_span(span, buffer),
}
}
pub fn format_wait_statement(
&self,
statement: &WaitStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// wait
self.format_token_id(span.start_token, buffer);
if let Some(name_list) = &statement.sensitivity_clause {
buffer.push_whitespace();
// on
self.format_token_id(span.start_token + 1, buffer);
buffer.push_whitespace();
self.format_name_list(buffer, name_list);
}
if let Some(condition_clause) = &statement.condition_clause {
buffer.push_whitespace();
self.format_token_id(condition_clause.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(condition_clause.as_ref(), buffer);
}
if let Some(timeout_clause) = &statement.timeout_clause {
buffer.push_whitespace();
self.format_token_id(timeout_clause.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(timeout_clause.as_ref(), buffer);
}
// ;
self.format_token_id(span.end_token, buffer);
}
pub fn format_report_statement(
&self,
report: &ReportStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// report
self.format_token_id(span.start_token, buffer);
buffer.push_whitespace();
self.format_expression(report.report.as_ref(), buffer);
self.format_opt_severity(report.severity.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_variable_assignment(
&self,
assignment: &VariableAssignment,
span: TokenSpan,
buffer: &mut Buffer,
) {
if let AssignmentRightHand::Selected(selected) = &assignment.rhs {
// with
self.format_token_id(selected.expression.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(selected.expression.as_ref(), buffer);
buffer.push_whitespace();
// select
self.format_token_id(selected.expression.span.end_token + 1, buffer);
buffer.push_whitespace();
}
self.format_target(&assignment.target, buffer);
buffer.push_whitespace();
self.format_token_id(assignment.target.span.end_token + 1, buffer);
buffer.push_whitespace();
self.format_assignment_right_hand(
&assignment.rhs,
|formatter, expr, buffer| formatter.format_expression(expr.as_ref(), buffer),
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_signal_assignment(
&self,
assignment: &SignalAssignment,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_target(&assignment.target, buffer);
buffer.push_whitespace();
self.format_token_id(assignment.target.span.end_token + 1, buffer);
buffer.push_whitespace();
if let Some(delay_mechanism) = &assignment.delay_mechanism {
self.format_delay_mechanism(delay_mechanism, buffer);
buffer.push_whitespace();
}
self.format_assignment_right_hand(&assignment.rhs, Self::format_waveform, buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_delay_mechanism(
&self,
delay_mechanism: &WithTokenSpan<DelayMechanism>,
buffer: &mut Buffer,
) {
match &delay_mechanism.item {
DelayMechanism::Transport => {
self.format_token_span(delay_mechanism.span, buffer);
}
DelayMechanism::Inertial { reject } => {
if let Some(reject) = reject {
self.format_token_id(delay_mechanism.span.start_token, buffer);
buffer.push_whitespace();
self.format_expression(reject.as_ref(), buffer);
buffer.push_whitespace();
}
// inertial
self.format_token_id(delay_mechanism.span.end_token, buffer);
}
}
}
pub fn format_signal_force_assignment(
&self,
assignment: &SignalForceAssignment,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_target(&assignment.target, buffer);
buffer.push_whitespace();
// <=
self.format_token_id(assignment.target.span.end_token + 1, buffer);
buffer.push_whitespace();
// force
self.format_token_id(assignment.target.span.end_token + 2, buffer);
buffer.push_whitespace();
if assignment.force_mode.is_some() {
self.format_token_id(assignment.target.span.end_token + 3, buffer);
buffer.push_whitespace();
}
self.format_assignment_right_hand(
&assignment.rhs,
|formatter, expr, buffer| formatter.format_expression(expr.as_ref(), buffer),
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_signal_release_assignment(
&self,
assignment: &SignalReleaseAssignment,
span: TokenSpan,
buffer: &mut Buffer,
) {
self.format_target(&assignment.target, buffer);
buffer.push_whitespace();
// <=
self.format_token_id(assignment.target.span.end_token + 1, buffer);
buffer.push_whitespace();
// release
self.format_token_id(assignment.target.span.end_token + 2, buffer);
if assignment.force_mode.is_some() {
buffer.push_whitespace();
self.format_token_id(assignment.target.span.end_token + 3, buffer);
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_if_statement(
&self,
statement: &IfStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
for cond in &statement.conds.conditionals {
let condition = &cond.condition;
// if | elsif
self.format_token_id(condition.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(condition.as_ref(), buffer);
buffer.push_whitespace();
// then
self.format_token_id(condition.span.end_token + 1, buffer);
self.format_sequential_statements(&cond.item, buffer);
buffer.line_break();
}
if let Some((statements, token)) = &statement.conds.else_item {
self.format_token_id(*token, buffer);
self.format_sequential_statements(statements, buffer);
buffer.line_break();
}
if statement.end_label_pos.is_some() {
// end if <label>
self.format_token_span(
TokenSpan::new(span.end_token - 3, span.end_token - 1),
buffer,
)
} else {
// end if
self.format_token_span(
TokenSpan::new(span.end_token - 2, span.end_token - 1),
buffer,
)
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_choice(&self, choice: &WithTokenSpan<Choice>, buffer: &mut Buffer) {
match &choice.item {
Choice::Expression(expr) => {
self.format_expression(WithTokenSpan::new(expr, choice.span), buffer)
}
Choice::DiscreteRange(range) => self.format_discrete_range(range, buffer),
Choice::Others => self.format_token_span(choice.span, buffer),
}
}
pub fn format_case_statement(
&self,
statement: &CaseStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// case
self.format_token_id(span.start_token, buffer);
if statement.is_matching {
// ?
self.format_token_id(span.start_token + 1, buffer);
}
buffer.push_whitespace();
self.format_expression(statement.expression.as_ref(), buffer);
buffer.push_whitespace();
// is
self.format_token_id(statement.expression.span.end_token + 1, buffer);
indented!(buffer, {
for alternative in &statement.alternatives {
buffer.line_break();
for (i, choice) in alternative.choices.iter().enumerate() {
if i == 0 {
// when
self.format_token_id(choice.span.start_token - 1, buffer);
buffer.push_whitespace();
}
self.format_choice(choice, buffer);
if i < alternative.choices.len() - 1 {
buffer.push_whitespace();
// |
self.format_token_id(choice.span.end_token + 1, buffer);
buffer.push_whitespace();
}
if i == alternative.choices.len() - 1 {
buffer.push_whitespace();
// =>
self.format_token_id(choice.span.end_token + 1, buffer);
}
}
self.format_sequential_statements(&alternative.item, buffer);
}
});
buffer.line_break();
if statement.is_matching {
self.format_token_span(
TokenSpan::new(statement.end_token, span.end_token - 2),
buffer,
);
self.format_token_id(span.end_token - 1, buffer);
} else {
self.format_token_span(
TokenSpan::new(statement.end_token, span.end_token - 1),
buffer,
);
}
self.format_token_id(span.end_token, buffer);
}
pub fn format_loop_statement(
&self,
statement: &LoopStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
if let Some(scheme) = &statement.iteration_scheme {
match scheme {
IterationScheme::While(expression) => {
// while
self.format_token_id(expression.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(expression.as_ref(), buffer);
buffer.push_whitespace();
}
IterationScheme::For(ident, range) => {
// for <ident> in
self.format_token_span(
TokenSpan::new(ident.tree.token - 1, ident.tree.token + 1),
buffer,
);
buffer.push_whitespace();
self.format_discrete_range(range, buffer);
buffer.push_whitespace();
}
}
}
self.format_token_id(statement.loop_token, buffer);
self.format_sequential_statements(&statement.statements, buffer);
buffer.line_break();
self.format_token_span(
TokenSpan::new(statement.end_token, span.end_token - 1),
buffer,
);
self.format_token_id(span.end_token, buffer);
}
pub fn format_next_statement(
&self,
statement: &NextStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// next
self.format_token_id(span.start_token, buffer);
self.format_opt_loop_label(statement.loop_label.as_ref(), buffer);
self.format_opt_condition(statement.condition.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
pub fn format_exit_statement(
&self,
statement: &ExitStatement,
span: TokenSpan,
buffer: &mut Buffer,
) {
// next
self.format_token_id(span.start_token, buffer);
self.format_opt_loop_label(statement.loop_label.as_ref(), buffer);
self.format_opt_condition(statement.condition.as_ref(), buffer);
self.format_token_id(span.end_token, buffer);
}
fn format_opt_loop_label(&self, loop_label: Option<&WithRef<Ident>>, buffer: &mut Buffer) {
if let Some(label) = loop_label {
buffer.push_whitespace();
self.format_token_id(label.item.token, buffer);
}
}
fn format_opt_condition(
&self,
condition: Option<&WithTokenSpan<Expression>>,
buffer: &mut Buffer,
) {
if let Some(condition) = &condition {
buffer.push_whitespace();
// when
self.format_token_id(condition.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(condition.as_ref(), buffer);
}
}
}
#[cfg(test)]
mod tests {
use crate::formatting::test_utils::check_formatted;
use crate::syntax::test::Code;
fn check_statement(input: &str) {
check_formatted(
input,
input,
Code::sequential_statement,
|formatter, ast, buffer| formatter.format_labeled_sequential_statement(ast, buffer),
)
}
fn check_statements(inputs: &[&str]) {
for input in inputs {
check_statement(input)
}
}
#[test]
fn null_statement() {
check_statement("null;");
}
#[test]
fn return_statement() {
check_statement("return;");
check_statement("return 2 + 2;");
}
#[test]
fn calls() {
check_statement("something;");
check_statement("something(arg);");
}
#[test]
fn assertions() {
check_statement("assert x;");
check_statement("assert x report y;");
check_statement("assert x report y severity NOTE;");
}
#[test]
fn wait_statement() {
check_statement("wait;");
check_statement("foo: wait;");
check_statement("wait on foo, bar;");
check_statement("wait until a = b;");
check_statement("wait for 2 ns;");
check_statement("wait on foo until bar for 2 ns;");
}
#[test]
fn report_statement() {
check_statements(&["report \"message\" severity error;", "report \"message\";"]);
}
#[test]
fn variable_assignment() {
check_statements(&["foo(0) := bar(1, 2);", "name: foo(0) := bar(1, 2);"]);
}
#[test]
fn signal_assignment() {
check_statements(&["foo(0) <= bar(1, 2);", "name: foo(0) <= bar(1, 2);"]);
}
#[test]
fn signal_force_assignment() {
check_statements(&[
"foo(0) <= force bar(1, 2);",
"foo(0) <= force out bar(1, 2);",
"foo(0) <= force in bar(1, 2);",
]);
}
#[test]
fn signal_release_assignment() {
check_statements(&[
"foo(0) <= release;",
"foo(0) <= release out;",
"foo(0) <= release in;",
]);
}
#[test]
fn if_statements() {
check_statements(&[
"\
if cond = true then
foo(1, 2);
x := 1;
end if;",
"\
mylabel: if cond = true then
foo(1, 2);
x := 1;
end if mylabel;",
"\
if cond = true then
foo(1, 2);
else
x := 1;
end if;",
"\
mylabel: if cond = true then
foo(1, 2);
else
x := 1;
end if mylabel;",
"\
if cond = true then
foo(1, 2);
elsif cond2 = false then
y := 2;
else
x := 1;
end if;",
"\
mylabel: if cond = true then
foo(1, 2);
elsif cond2 = false then
y := 2;
else
x := 1;
end if mylabel;",
]);
}
#[test]
fn case_statements() {
check_statements(&[
"\
case foo(1) is
when 1 | 2 =>
stmt1;
stmt2;
when others =>
stmt3;
stmt4;
end case;",
"\
case? foo(1) is
when others =>
null;
end case?;",
])
}
#[test]
fn check_loop() {
check_statements(&[
"\
lbl: loop
end loop lbl;",
"\
lbl: loop
stmt1;
stmt2;
end loop lbl;",
"\
while foo = true loop
stmt1;
stmt2;
end loop;",
"\
for idx in 0 to 3 loop
stmt1;
stmt2;
end loop;",
]);
}
#[test]
fn check_next_statement() {
check_statements(&[
"next;",
"next foo;",
"next when condition;",
"next foo when condition;",
]);
}
#[test]
fn check_exit_statement() {
check_statements(&[
"exit;",
"exit foo;",
"exit when condition;",
"exit foo when condition;",
]);
}
#[test]
fn check_delay_mechanisms() {
check_statements(&[
"foo(0) <= transport bar(1, 2);",
"bar <= reject 2 ns inertial bar(1, 2);",
"bar <= inertial bar(1, 2);",
]);
}
#[test]
fn format_selected_assignments() {
check_statement(
"\
with x(0) + 1 select foo(0) := bar(1, 2) when 0 | 1, def when others;",
);
}
}

View file

@ -0,0 +1,26 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::Expression;
use crate::formatting::buffer::Buffer;
use crate::VHDLFormatter;
impl VHDLFormatter<'_> {
pub(crate) fn format_opt_severity(
&self,
severity: Option<&WithTokenSpan<Expression>>,
buffer: &mut Buffer,
) {
if let Some(severity) = &severity {
buffer.push_whitespace();
self.format_token_id(severity.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_expression(severity.as_ref(), buffer);
}
}
}

View file

@ -0,0 +1,318 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{
Signature, SubprogramDeclaration, SubprogramHeader, SubprogramInstantiation,
SubprogramSpecification,
};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::{HasTokenSpan, TokenSpan};
use vhdl_lang::ast::{FunctionSpecification, ProcedureSpecification, SubprogramBody};
use vhdl_lang::indented;
impl VHDLFormatter<'_> {
pub fn format_subprogram_declaration(
&self,
declaration: &SubprogramDeclaration,
buffer: &mut Buffer,
) {
self.format_subprogram_specification(&declaration.specification, buffer);
// ;
self.format_token_id(declaration.span.end_token, buffer);
}
pub fn format_subprogram_specification(
&self,
specification: &SubprogramSpecification,
buffer: &mut Buffer,
) {
use SubprogramSpecification::*;
match specification {
Procedure(procedure_spec) => {
self.format_procedure_specification(procedure_spec, buffer)
}
Function(function_spec) => self.format_function_specification(function_spec, buffer),
}
}
pub fn format_procedure_specification(
&self,
specification: &ProcedureSpecification,
buffer: &mut Buffer,
) {
// procedure <name>
self.format_token_span(
TokenSpan::new(
specification.span.start_token,
specification.designator.tree.token,
),
buffer,
);
if let Some(header) = &specification.header {
self.format_subprogram_header(header, buffer);
}
if let Some(parameter) = &specification.parameter_list {
if specification.header.is_some() {
buffer.increase_indent();
buffer.line_break();
}
self.format_interface_list(parameter, buffer);
if specification.header.is_some() {
buffer.decrease_indent();
}
}
}
pub fn format_function_specification(
&self,
specification: &FunctionSpecification,
buffer: &mut Buffer,
) {
// function <name>
self.format_token_span(
TokenSpan::new(
specification.span.start_token,
specification.designator.tree.token,
),
buffer,
);
if let Some(header) = &specification.header {
self.format_subprogram_header(header, buffer);
}
if let Some(parameter) = &specification.parameter_list {
self.format_interface_list(parameter, buffer);
}
buffer.push_whitespace();
// return
self.format_token_id(specification.return_type.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_name(specification.return_type.as_ref(), buffer);
}
pub fn format_subprogram_header(&self, header: &SubprogramHeader, buffer: &mut Buffer) {
indented!(buffer, {
buffer.line_break();
self.format_interface_list(&header.generic_list, buffer);
});
if let Some(map_aspect) = &header.map_aspect {
buffer.push_whitespace();
self.format_map_aspect(map_aspect, buffer);
}
}
pub fn format_subprogram_body(&self, body: &SubprogramBody, buffer: &mut Buffer) {
self.format_subprogram_specification(&body.specification, buffer);
buffer.push_whitespace();
// is
self.format_token_id(body.specification.span().end_token + 1, buffer);
buffer.line_break();
indented!(buffer, {
self.format_declarations(&body.declarations, buffer);
});
self.format_token_id(body.begin_token, buffer);
self.format_sequential_statements(&body.statements, buffer);
buffer.line_break();
// end
self.format_token_span(
TokenSpan::new(body.end_token, body.span.end_token - 1),
buffer,
);
// ;
self.format_token_id(body.span.end_token, buffer);
}
pub fn format_signature(&self, signature: &WithTokenSpan<Signature>, buffer: &mut Buffer) {
self.format_token_id(signature.span.start_token, buffer);
match &signature.item {
Signature::Function(functions, return_type) => {
for (i, function) in functions.iter().enumerate() {
self.format_name(function.as_ref(), buffer);
if i < functions.len() - 1 {
// ,
self.format_token_id(function.span.end_token + 1, buffer);
}
buffer.push_whitespace();
}
// return
self.format_token_id(return_type.span.start_token - 1, buffer);
buffer.push_whitespace();
self.format_name(return_type.as_ref(), buffer);
}
Signature::Procedure(procedures) => {
self.format_name_list(buffer, procedures);
}
}
self.format_token_id(signature.span.end_token, buffer);
}
pub fn format_subprogram_instantiation(
&self,
instantiation: &SubprogramInstantiation,
buffer: &mut Buffer,
) {
// function <name> is new
self.format_token_span(
TokenSpan::new(
instantiation.span.start_token,
instantiation.span.start_token + 3,
),
buffer,
);
buffer.push_whitespace();
self.format_name(instantiation.subprogram_name.as_ref(), buffer);
if let Some(signature) = &instantiation.signature {
self.format_signature(signature, buffer);
}
if let Some(generic_map) = &instantiation.generic_map {
buffer.push_whitespace();
self.format_map_aspect(generic_map, buffer);
}
self.format_token_id(instantiation.span.end_token, buffer);
}
}
#[cfg(test)]
mod test {
use crate::formatting::test_utils::check_formatted;
fn check_signature(input: &str) {
check_formatted(
input,
input,
|code| code.signature(),
|formatter, ast, buffer| formatter.format_signature(ast, buffer),
);
}
#[test]
fn test_signature() {
check_signature("[return type_mark]");
check_signature("[foo return bar]");
check_signature("[type_mark]");
check_signature("[foo, foo2 return bar]");
}
fn check_subprogram_declaration(input: &str) {
check_formatted(
input,
input,
|code| code.subprogram_decl(),
|formatter, ast, buffer| formatter.format_subprogram_declaration(ast, buffer),
);
}
#[test]
fn test_subprogram_declaration_without_parameters() {
check_subprogram_declaration("procedure foo;");
check_subprogram_declaration("function foo return natural;");
check_subprogram_declaration("function \"+\" return natural;");
check_subprogram_declaration("impure function foo return natural;");
check_subprogram_declaration("pure function foo return natural;");
}
#[test]
fn test_subprogram_declaration_one_parameter() {
check_subprogram_declaration(
"\
procedure foo(
a: std_logic
);",
);
check_subprogram_declaration(
"\
function foo(
a: std_logic
) return std_logic;",
);
}
#[test]
fn test_subprogram_declaration_multiple_parameters() {
check_subprogram_declaration(
"\
procedure foo(
arg0: std_logic;
arg1: std_logic
);",
);
}
#[test]
fn test_subprogram_declaration_with_generics() {
check_subprogram_declaration(
"\
procedure foo
generic (
x: natural
);",
);
check_subprogram_declaration(
"\
procedure foo
generic (
x: natural
)
parameter (
a: std_logic
);",
);
check_subprogram_declaration(
"\
procedure foo
generic (
x: natural
)
(
a: std_logic
);",
);
}
fn check_declaration(input: &str) {
check_formatted(
input,
input,
|code| code.declarative_part().into_iter().next().unwrap(),
|formatter, ast, buffer| formatter.format_declaration(ast, buffer),
);
}
#[test]
fn test_subprogram_body() {
check_declaration(
"\
function \"+\"(
arg: natural
) return natural is
begin
end function \"+\";",
);
check_declaration(
"\
function foo(
arg: natural
) return natural is
begin
end function foo;",
);
}
#[test]
fn test_subprogram_instantiation() {
check_declaration("procedure my_proc is new proc;");
check_declaration("function my_func is new func;");
check_declaration("function my_func is new func[bit return bit_vector];");
check_declaration(
"\
function my_func is new func[bit return bit_vector] generic map (
x => x
);",
);
}
}

View file

@ -0,0 +1,50 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::WithDecl;
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::{TokenAccess, TokenId};
use std::cmp::max;
use vhdl_lang::ast::Ident;
use vhdl_lang::TokenSpan;
impl VHDLFormatter<'_> {
pub(crate) fn format_token_id(&self, id: TokenId, buffer: &mut Buffer) {
buffer.push_token(self.tokens.index(id));
}
pub(crate) fn format_token_span(&self, span: TokenSpan, buffer: &mut Buffer) {
for (index, id) in span.iter().enumerate() {
self.format_token_id(id, buffer);
if index < span.len() - 1 {
buffer.push_whitespace();
}
}
}
pub(crate) fn join_token_span(&self, span: TokenSpan, buffer: &mut Buffer) {
for id in span.iter() {
self.format_token_id(id, buffer);
}
}
pub(crate) fn format_ident(&self, ident: &WithDecl<Ident>, buffer: &mut Buffer) {
self.format_token_id(ident.tree.token, buffer)
}
pub(crate) fn line_break_preserve_whitespace(&self, token_id: TokenId, buffer: &mut Buffer) {
let current_line = self.tokens.get_pos(token_id).end().line;
if let Some(token) = self.tokens.get_token(token_id + 1) {
let next_line = token.full_range().start.line;
let numbers_of_whitespaces = max(next_line - current_line, 1);
buffer.line_breaks(numbers_of_whitespaces)
} else {
buffer.line_break();
}
}
}

View file

@ -21,6 +21,7 @@ mod project;
mod syntax;
mod completion;
mod formatting;
mod standard;
pub use crate::config::Config;
@ -28,6 +29,7 @@ pub use crate::data::{
Diagnostic, Latin1String, Message, MessageHandler, MessagePrinter, MessageType,
NullDiagnostics, NullMessages, Position, Range, Severity, SeverityMap, Source, SrcPos,
};
pub use formatting::VHDLFormatter;
pub use crate::analysis::EntHierarchy;
pub use crate::named_entity::{

View file

@ -6,8 +6,27 @@
use clap::Parser;
use itertools::Itertools;
use std::path::Path;
use vhdl_lang::{Config, Diagnostic, MessagePrinter, Project, Severity, SeverityMap};
use std::iter::zip;
use std::path::{Path, PathBuf};
use vhdl_lang::ast::DesignFile;
use vhdl_lang::{
Config, Diagnostic, MessagePrinter, Project, Severity, SeverityMap, Source, VHDLFormatter,
VHDLParser, VHDLStandard,
};
#[derive(Debug, clap::Args)]
#[group(required = true, multiple = false)]
pub struct Group {
/// Config file in TOML format containing libraries and settings
#[arg(short, long)]
config: Option<String>,
/// Format the passed file and write the contents to stdout.
///
/// This is experimental and the formatting behavior will change in the future.
#[arg(short, long)]
format: Option<String>,
}
/// Run vhdl analysis
#[derive(Parser, Debug)]
@ -22,23 +41,82 @@ struct Args {
#[arg(short = 'l', long)]
libraries: Option<String>,
/// Config file in TOML format containing libraries and settings
#[arg(short, long)]
config: String,
#[clap(flatten)]
group: Group,
}
fn main() {
let args = Args::parse();
if let Some(config_path) = args.group.config {
parse_and_analyze_project(config_path, args.num_threads, args.libraries);
} else if let Some(format) = args.group.format {
format_file(format);
}
}
fn format_file(format: String) {
let path = PathBuf::from(format);
let parser = VHDLParser::new(VHDLStandard::default());
let mut diagnostics = Vec::new();
let result = parser.parse_design_file(&path, &mut diagnostics);
match result {
Ok((_, design_file)) => {
if !diagnostics.is_empty() {
show_diagnostics(&diagnostics, &SeverityMap::default());
std::process::exit(1);
}
let result = VHDLFormatter::format_design_file(&design_file);
println!("{result}");
check_formatted_file(&path, parser, design_file, &result);
std::process::exit(0);
}
Err(err) => {
println!("{err}");
std::process::exit(1);
}
}
}
fn check_formatted_file(path: &Path, parser: VHDLParser, design_file: DesignFile, result: &str) {
let mut diagnostics: Vec<Diagnostic> = Vec::new();
let new_file = parser.parse_design_source(&Source::inline(path, result), &mut diagnostics);
if !diagnostics.is_empty() {
println!("Formatting failed as it resulted in a syntactically incorrect file.");
show_diagnostics(&diagnostics, &SeverityMap::default());
std::process::exit(1);
}
for ((tokens_a, _), (tokens_b, _)) in zip(new_file.design_units, design_file.design_units) {
for (a, b) in zip(tokens_a, tokens_b) {
if !a.equal_format(&b) {
println!("Token mismatch");
println!("New Token={a:#?}");
let contents = a.pos.source.contents();
let a_line = contents.get_line(a.pos.range.start.line as usize).unwrap();
println!(" {a_line}");
println!("Old Token={b:#?}");
let b_line = result.lines().nth(b.pos.range.start.line as usize).unwrap();
println!(" {b_line}");
break;
}
}
}
}
fn parse_and_analyze_project(
config_path: String,
num_threads: Option<usize>,
libraries: Option<String>,
) {
rayon::ThreadPoolBuilder::new()
.num_threads(args.num_threads.unwrap_or(0))
.num_threads(num_threads.unwrap_or(0))
.build_global()
.unwrap();
let mut config = Config::default();
let mut msg_printer = MessagePrinter::default();
config.load_external_config(&mut msg_printer, args.libraries.clone());
config.load_external_config(&mut msg_printer, libraries.clone());
config.append(
&Config::read_file_path(Path::new(&args.config)).expect("Failed to read config file"),
&Config::read_file_path(Path::new(&config_path)).expect("Failed to read config file"),
&mut msg_printer,
);

View file

@ -7,10 +7,9 @@
use crate::ast::ExternalObjectClass;
use crate::ast::{
AliasDeclaration, AnyDesignUnit, AnyPrimaryUnit, AnySecondaryUnit, Attribute,
AttributeDeclaration, AttributeSpecification, ComponentDeclaration, Designator,
FileDeclaration, HasIdent, Ident, InterfacePackageDeclaration, ModeViewDeclaration,
ObjectClass, PackageInstantiation, SubprogramBody, SubprogramInstantiation,
SubprogramSpecification, TypeDeclaration, WithDecl,
AttributeDeclaration, AttributeSpecification, ComponentDeclaration, Designator, HasIdent,
Ident, InterfacePackageDeclaration, ModeViewDeclaration, ObjectClass, PackageInstantiation,
SubprogramBody, SubprogramInstantiation, SubprogramSpecification, TypeDeclaration, WithDecl,
};
use crate::data::*;
mod types;
@ -715,12 +714,6 @@ impl HasEntityId for AliasDeclaration {
}
}
impl HasEntityId for FileDeclaration {
fn ent_id(&self) -> Option<EntityId> {
self.ident.decl.get()
}
}
impl HasEntityId for TypeDeclaration {
fn ent_id(&self) -> Option<EntityId> {
self.ident.decl.get()

View file

@ -27,7 +27,7 @@ pub fn parse_alias_declaration(
}
};
ctx.stream.expect_kind(Is)?;
let is_token = ctx.stream.expect_kind(Is)?;
let name = parse_name(ctx)?;
let signature = {
@ -44,6 +44,7 @@ pub fn parse_alias_declaration(
AliasDeclaration {
designator,
subtype_indication,
is_token,
name,
signature,
},
@ -65,6 +66,7 @@ mod tests {
AliasDeclaration {
designator: code.s1("foo").decl_designator(),
subtype_indication: None,
is_token: code.s1("is").token(),
name: code.s1("name").name(),
signature: None
},
@ -82,6 +84,7 @@ mod tests {
AliasDeclaration {
designator: code.s1("foo").decl_designator(),
subtype_indication: Some(code.s1("vector(0 to 1)").subtype_indication()),
is_token: code.s1("is").token(),
name: code.s1("name").name(),
signature: None
},
@ -99,6 +102,7 @@ mod tests {
AliasDeclaration {
designator: code.s1("foo").decl_designator(),
subtype_indication: None,
is_token: code.s1("is").token(),
name: code.s1("name").name(),
signature: Some(code.s1("[return natural]").signature())
},
@ -119,6 +123,7 @@ mod tests {
AliasDeclaration {
designator,
subtype_indication: None,
is_token: code.s1("is").token(),
name: code.s1("name").name(),
signature: None
},
@ -139,6 +144,7 @@ mod tests {
AliasDeclaration {
designator,
subtype_indication: None,
is_token: code.s1("is").token(),
name: code.s1("'b'").name(),
signature: None
},

View file

@ -96,7 +96,7 @@ pub fn parse_attribute(ctx: &mut ParsingContext<'_>) -> ParseResult<Vec<WithToke
},
Of => {
let entity_names = parse_entity_name_list(ctx)?;
ctx.stream.expect_kind(Colon)?;
let colon_token = ctx.stream.expect_kind(Colon)?;
let entity_class = parse_entity_class(ctx)?;
ctx.stream.expect_kind(Is)?;
let expr = parse_expression(ctx)?;
@ -111,6 +111,7 @@ pub fn parse_attribute(ctx: &mut ParsingContext<'_>) -> ParseResult<Vec<WithToke
entity_name,
entity_class,
expr: expr.clone(),
colon_token
}),
TokenSpan::new(start_token, end_token)
)
@ -152,6 +153,7 @@ mod tests {
designator: code.s1("foo").ref_designator(),
signature: None
}),
colon_token: code.s1(":").token(),
entity_class: EntityClass::Signal,
expr: code.s1("0+1").expr()
}),
@ -172,6 +174,7 @@ mod tests {
designator: code.s1("\"**\"").ref_designator(),
signature: None
}),
colon_token: code.s1(":").token(),
entity_class: EntityClass::Function,
expr: code.s1("0+1").expr()
}),
@ -193,6 +196,7 @@ mod tests {
designator: code.s1("foo").ref_designator(),
signature: None
}),
colon_token: code.s1(":").token(),
entity_class: EntityClass::Signal,
expr: code.s1("0+1").expr()
}),
@ -205,6 +209,7 @@ mod tests {
designator: code.s1("bar").ref_designator(),
signature: None
}),
colon_token: code.s1(":").token(),
entity_class: EntityClass::Signal,
expr: code.s1("0+1").expr()
}),
@ -223,6 +228,7 @@ mod tests {
Attribute::Specification(AttributeSpecification {
ident: WithRef::new(code.s1("attr_name").ident()),
entity_name: EntityName::All,
colon_token: code.s1(":").token(),
entity_class: EntityClass::Signal,
expr: code.s1("0+1").expr()
}),
@ -240,6 +246,7 @@ mod tests {
Attribute::Specification(AttributeSpecification {
ident: WithRef::new(code.s1("attr_name").ident()),
entity_name: EntityName::Others,
colon_token: code.s1(":").token(),
entity_class: EntityClass::Signal,
expr: code.s1("0+1").expr()
}),
@ -260,6 +267,7 @@ mod tests {
designator: code.s1("foo").ref_designator(),
signature: Some(code.s1("[return natural]").signature())
}),
colon_token: code.s1(":").token(),
entity_class: EntityClass::Function,
expr: code.s1("0+1").expr()
}),

View file

@ -89,31 +89,34 @@ pub fn parse_component_declaration(
) -> ParseResult<ComponentDeclaration> {
let start_token = ctx.stream.expect_kind(Component)?;
let ident = WithDecl::new(ctx.stream.expect_ident()?);
ctx.stream.pop_if_kind(Is);
let is_token = ctx.stream.pop_if_kind(Is);
let generic_list = parse_optional_generic_list(ctx)?;
let port_list = parse_optional_port_list(ctx)?;
ctx.stream.expect_kind(End)?;
let end_token = ctx.stream.expect_kind(End)?;
if ctx.standard < VHDL2019 {
ctx.stream.expect_kind(Component)?;
} else {
ctx.stream.pop_if_kind(Component);
}
let end_ident = ctx.stream.pop_optional_ident();
let end_token = expect_semicolon_or_last(ctx);
let semicolon_token = expect_semicolon_or_last(ctx);
Ok(ComponentDeclaration {
span: TokenSpan::new(start_token, end_token),
span: TokenSpan::new(start_token, semicolon_token),
is_token,
end_ident_pos: check_end_identifier_mismatch(ctx, &ident.tree, end_ident),
ident,
generic_list,
port_list,
end_token,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::syntax::test::Code;
use crate::VHDLStandard::VHDL2019;
use pretty_assertions::assert_eq;
@ -131,10 +134,12 @@ end component;
component,
ComponentDeclaration {
span: code.token_span(),
is_token: None,
ident: code.s1("foo").decl_ident(),
generic_list: None,
port_list: None,
end_ident_pos: None,
end_token: code.s1("end").token(),
}
);
@ -149,10 +154,12 @@ end component;
component,
ComponentDeclaration {
span: code.token_span(),
is_token: Some(code.s1("is").token()),
ident: code.s1("foo").decl_ident(),
generic_list: None,
port_list: None,
end_ident_pos: None,
end_token: code.s1("end").token(),
}
);
@ -167,10 +174,12 @@ end component foo;
component,
ComponentDeclaration {
span: code.token_span(),
is_token: Some(code.s1("is").token()),
ident: code.s1("foo").decl_ident(),
generic_list: None,
port_list: None,
end_ident_pos: Some(code.s("foo", 2).token())
end_ident_pos: Some(code.s("foo", 2).token()),
end_token: code.s1("end").token(),
}
);
}
@ -191,6 +200,7 @@ end component;
component,
ComponentDeclaration {
span: code.token_span(),
is_token: Some(code.s1("is").token()),
ident: code.s1("foo").decl_ident(),
generic_list: Some(InterfaceList {
interface_type: InterfaceType::Generic,
@ -198,7 +208,8 @@ end component;
span: code.between("generic", ");").token_span()
}),
port_list: None,
end_ident_pos: None
end_ident_pos: None,
end_token: code.s1("end").token(),
}
);
}
@ -219,6 +230,7 @@ end component;
component,
ComponentDeclaration {
span: code.token_span(),
is_token: Some(code.s1("is").token()),
ident: code.s1("foo").decl_ident(),
generic_list: None,
port_list: Some(InterfaceList {
@ -226,7 +238,8 @@ end component;
items: vec![code.s1("foo : natural").port()],
span: code.between("port", ");").token_span()
}),
end_ident_pos: None
end_ident_pos: None,
end_token: code.s1("end").token(),
}
);
}
@ -250,7 +263,7 @@ end
assert_eq!(
diagnostics,
vec![Diagnostic::syntax_error(
&code.s("generic", 2).pos(),
code.s("generic", 2).pos(),
"Duplicate generic clause"
)]
);

View file

@ -46,20 +46,23 @@ pub fn parse_block_statement(
_ => None,
}
};
ctx.stream.pop_if_kind(Is);
let is_token = ctx.stream.pop_if_kind(Is);
let header = parse_block_header(ctx)?;
let decl = parse_declarative_part(ctx)?;
ctx.stream.expect_kind(Begin)?;
let begin_token = ctx.stream.expect_kind(Begin)?;
let statements = parse_labeled_concurrent_statements(ctx)?;
ctx.stream.expect_kind(End)?;
let end_token = ctx.stream.expect_kind(End)?;
ctx.stream.expect_kind(Block)?;
let end_ident = ctx.stream.pop_optional_ident();
let end_tok = expect_semicolon_or_last(ctx);
Ok(BlockStatement {
guard_condition,
is_token,
header,
decl,
begin_token,
statements,
end_token,
end_label_pos: check_label_identifier_mismatch(ctx, label, end_ident),
span: TokenSpan::new(start_tok, end_tok),
})
@ -80,17 +83,17 @@ fn parse_block_header(ctx: &mut ParsingContext<'_>) -> ParseResult<BlockHeader>
if let Some(map_token) = ctx.stream.pop_if_kind(Map) {
if port_clause.is_some() || port_map.is_some() {
ctx.diagnostics.push(Diagnostic::syntax_error(
ctx.stream.get_token(map_token),
ctx.stream.index(map_token),
"Generic map must come before port clause and port map",
));
} else if generic_clause.is_none() {
ctx.diagnostics.push(Diagnostic::syntax_error(
ctx.stream.get_token(map_token),
ctx.stream.index(map_token),
"Generic map declared without preceding generic clause",
));
} else if generic_map.is_some() {
ctx.diagnostics.push(Diagnostic::syntax_error(
ctx.stream.get_token(map_token),
ctx.stream.index(map_token),
"Duplicate generic map",
));
}
@ -98,9 +101,8 @@ fn parse_block_header(ctx: &mut ParsingContext<'_>) -> ParseResult<BlockHeader>
expect_semicolon(ctx);
if generic_map.is_none() {
generic_map = Some(MapAspect {
start: token_id,
list,
closing_paren,
span: TokenSpan::new(token_id, closing_paren),
});
}
} else {
@ -127,12 +129,12 @@ fn parse_block_header(ctx: &mut ParsingContext<'_>) -> ParseResult<BlockHeader>
if let Some(map_token) = ctx.stream.pop_if_kind(Map) {
if port_clause.is_none() {
ctx.diagnostics.push(Diagnostic::syntax_error(
ctx.stream.get_token(map_token),
ctx.stream.index(map_token),
"Port map declared without preceeding port clause",
));
} else if port_map.is_some() {
ctx.diagnostics.push(Diagnostic::syntax_error(
ctx.stream.get_token(map_token),
ctx.stream.index(map_token),
"Duplicate port map",
));
}
@ -140,9 +142,8 @@ fn parse_block_header(ctx: &mut ParsingContext<'_>) -> ParseResult<BlockHeader>
expect_semicolon(ctx);
if port_map.is_none() {
port_map = Some(MapAspect {
start: token_id,
span: TokenSpan::new(token_id, closing_paren),
list,
closing_paren,
});
}
} else {
@ -183,19 +184,20 @@ pub fn parse_process_statement(
postponed: Option<TokenId>,
) -> ParseResult<ProcessStatement> {
let process_token = ctx.stream.expect_kind(Process)?;
let sensitivity_list = if ctx.stream.skip_if_kind(LeftPar) {
let sensitivity_list = if let Some(left_par) = ctx.stream.pop_if_kind(LeftPar) {
peek_token!(ctx.stream, token,
All => {
ctx.stream.skip();
ctx.stream.expect_kind(RightPar)?;
Some(SensitivityList::All)
let right_par = ctx.stream.expect_kind(RightPar)?;
Some(WithTokenSpan::new(SensitivityList::All, TokenSpan::new(left_par, right_par)))
},
RightPar => {
let right_par = ctx.stream.get_current_token_id();
ctx.stream.skip();
ctx.diagnostics.push(
Diagnostic::syntax_error(token, "Processes with sensitivity lists must contain at least one element.")
);
Some(SensitivityList::Names(Vec::new()))
Some(WithTokenSpan::new(SensitivityList::Names(Vec::new()), TokenSpan::new(left_par, right_par)))
},
Identifier => {
let mut names = Vec::with_capacity(1);
@ -203,8 +205,9 @@ pub fn parse_process_statement(
names.push(parse_name(ctx)?);
peek_token!(ctx.stream, token,
RightPar => {
let right_par = ctx.stream.get_current_token_id();
ctx.stream.skip();
break Some(SensitivityList::Names(names));
break Some(WithTokenSpan::new(SensitivityList::Names(names), TokenSpan::new(left_par, right_par)));
},
Comma => {
ctx.stream.skip();
@ -219,16 +222,16 @@ pub fn parse_process_statement(
None
};
ctx.stream.pop_if_kind(Is);
let is_token = ctx.stream.pop_if_kind(Is);
let decl = parse_declarative_part(ctx)?;
ctx.stream.expect_kind(Begin)?;
let begin_token = ctx.stream.expect_kind(Begin)?;
let statements = parse_labeled_sequential_statements(ctx)?;
ctx.stream.expect_kind(End)?;
let end_token = ctx.stream.expect_kind(End)?;
if let Some(token) = ctx.stream.pop_if_kind(Postponed) {
if postponed.is_none() {
ctx.diagnostics.push(Diagnostic::syntax_error(
ctx.stream.get_token(token),
ctx.stream.index(token),
"'postponed' at the end of non-postponed process.",
));
}
@ -239,8 +242,11 @@ pub fn parse_process_statement(
Ok(ProcessStatement {
postponed: postponed.is_some(),
sensitivity_list,
is_token,
decl,
begin_token,
statements,
end_token,
end_label_pos: check_label_identifier_mismatch(ctx, label, end_ident),
span: TokenSpan::new(postponed.unwrap_or(process_token), end_tok),
})
@ -261,7 +267,7 @@ fn to_procedure_call(
call: WithTokenSpan::new(
CallOrIndexed {
name: WithTokenSpan::new(name, target.span),
parameters: vec![],
parameters: SeparatedList::default(),
},
target.span,
),
@ -352,9 +358,8 @@ pub fn parse_map_aspect(
ctx.stream.expect_kind(Map)?;
let (list, closing_paren) = parse_association_list(ctx)?;
Ok(Some(MapAspect {
start: aspect,
span: TokenSpan::new(aspect, closing_paren),
list,
closing_paren,
}))
} else {
Ok(None)
@ -389,11 +394,11 @@ pub fn parse_instantiation_statement(
fn parse_optional_declarative_part(
ctx: &mut ParsingContext<'_>,
) -> ParseResult<Option<Vec<WithTokenSpan<Declaration>>>> {
) -> ParseResult<Option<(Vec<WithTokenSpan<Declaration>>, TokenId)>> {
if is_declarative_part(ctx)? {
let decls = parse_declarative_part(ctx)?;
ctx.stream.expect_kind(Begin)?;
Ok(Some(decls))
let begin_token = ctx.stream.expect_kind(Begin)?;
Ok(Some((decls, begin_token)))
} else {
Ok(None)
}
@ -405,30 +410,37 @@ fn parse_generate_body(
) -> ParseResult<GenerateBody> {
let decl = parse_optional_declarative_part(ctx)?;
let statements = parse_labeled_concurrent_statements(ctx)?;
let mut end_label_pos = None;
let mut end_label = None;
let end_token;
// Potential inner end [ alternative_label ];
if ctx.stream.next_kinds_are(&[End, SemiColon]) {
end_token = Some(ctx.stream.get_current_token_id());
// Inner end no label
ctx.stream.skip();
ctx.stream.skip();
} else if ctx.stream.next_kinds_are(&[End, Identifier]) {
end_token = Some(ctx.stream.get_current_token_id());
ctx.stream.skip();
// Inner with identifier
let end_ident = ctx.stream.expect_ident()?;
end_label_pos = check_label_identifier_mismatch(
end_label = Some(ctx.stream.get_last_token_id());
check_label_identifier_mismatch(
ctx,
alternative_label.as_ref().map(|label| &label.tree),
Some(end_ident),
);
expect_semicolon(ctx);
} else {
end_token = None
}
let body = GenerateBody {
alternative_label,
decl,
statements,
end_label_pos,
end_token,
end_label,
};
Ok(body)
@ -443,9 +455,9 @@ fn parse_for_generate_statement(
let index_name = WithDecl::new(ctx.stream.expect_ident()?);
ctx.stream.expect_kind(In)?;
let discrete_range = parse_discrete_range(ctx)?;
ctx.stream.expect_kind(Generate)?;
let generate_token = ctx.stream.expect_kind(Generate)?;
let body = parse_generate_body(ctx, None)?;
ctx.stream.expect_kind(End)?;
let end_token = ctx.stream.expect_kind(End)?;
ctx.stream.expect_kind(Generate)?;
let end_ident = ctx.stream.pop_optional_ident();
let end_tok = expect_semicolon_or_last(ctx);
@ -453,7 +465,9 @@ fn parse_for_generate_statement(
Ok(ForGenerateStatement {
index_name,
discrete_range,
generate_token,
body,
end_token,
end_label_pos: check_label_identifier_mismatch(ctx, label, end_ident),
span: TokenSpan::new(start_tok, end_tok),
})
@ -486,7 +500,7 @@ fn parse_if_generate_statement(
conditionals.push(conditional);
expect_token!(
ctx.stream, end_token,
ctx.stream, end_token, token_id,
End => {
else_branch = None;
break;
@ -510,7 +524,7 @@ fn parse_if_generate_statement(
);
let body = parse_generate_body(ctx, alternative_label)?;
ctx.stream.expect_kind(End)?;
else_branch = Some(body);
else_branch = Some((body, token_id));
break;
}
);
@ -541,7 +555,8 @@ fn parse_case_generate_statement(
ctx.stream.expect_kind(When)?;
let mut alternatives = Vec::with_capacity(2);
loop {
let end_token = loop {
let start_token = ctx.stream.get_current_token_id();
let alternative_label = {
if ctx.stream.next_kinds_are(&[Identifier, Colon]) {
let ident = ctx.stream.expect_ident()?;
@ -554,18 +569,20 @@ fn parse_case_generate_statement(
let choices = parse_choices(ctx)?;
ctx.stream.expect_kind(RightArrow)?;
let body = parse_generate_body(ctx, alternative_label)?;
let end_token = ctx.stream.get_last_token_id();
alternatives.push(Alternative {
choices,
item: body,
span: TokenSpan::new(start_token, end_token),
});
expect_token!(
ctx.stream, end_token,
End => break,
ctx.stream, end_token, end_token_id,
End => break end_token_id,
When => continue
);
}
};
ctx.stream.expect_kind(Generate)?;
let end_ident = ctx.stream.pop_optional_ident();
@ -577,6 +594,7 @@ fn parse_case_generate_statement(
alternatives,
},
end_label_pos: check_label_identifier_mismatch(ctx, label, end_ident),
end_token,
span: TokenSpan::new(start_tok, end_tok),
})
}
@ -822,6 +840,7 @@ end block;",
let block = BlockStatement {
guard_condition: None,
is_token: None,
header: BlockHeader {
generic_clause: None,
generic_map: None,
@ -829,6 +848,7 @@ end block;",
port_map: None,
},
decl: code.s1("constant const : natural := 0;").declarative_part(),
begin_token: code.s1("begin").token(),
statements: vec![LabeledConcurrentStatement {
label: Some(code.s1("name2").ident()).into(),
statement: WithTokenSpan::new(
@ -836,6 +856,7 @@ end block;",
code.s1("foo(clk);").token_span(),
),
}],
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span().skip_to(code.s1("block").token()),
};
@ -860,6 +881,7 @@ end block name;",
);
let block = BlockStatement {
guard_condition: None,
is_token: Some(code.s1("is").token()),
header: BlockHeader {
generic_clause: None,
generic_map: None,
@ -867,7 +889,9 @@ end block name;",
port_map: None,
},
decl: vec![],
begin_token: code.s1("begin").token(),
statements: vec![],
end_token: code.s1("end").token(),
end_label_pos: Some(code.s("name", 2).pos()),
span: code.token_span().skip_to(code.s1("block").token()),
};
@ -898,8 +922,11 @@ end block;",
port_clause: None,
port_map: None,
},
is_token: None,
decl: vec![],
begin_token: code.s1("begin").token(),
statements: vec![],
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span().skip_to(code.s1("block").token()),
};
@ -930,8 +957,11 @@ end block;",
port_clause: None,
port_map: None,
},
is_token: Some(code.s1("is").token()),
decl: vec![],
begin_token: code.s1("begin").token(),
statements: vec![],
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span().skip_to(code.s1("block").token()),
};
@ -974,8 +1004,11 @@ end block;",
}),
port_map: Some(code.s1("port map(prt => 2)").port_map_aspect()),
},
is_token: Some(code.s1("is").token()),
decl: vec![],
begin_token: code.s1("begin").token(),
statements: vec![],
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span().skip_to(code.s1("block").token()),
};
@ -1001,8 +1034,11 @@ end process;",
let process = ProcessStatement {
postponed: false,
sensitivity_list: None,
is_token: None,
decl: vec![],
begin_token: code.s1("begin").token(),
statements: vec![],
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span(),
};
@ -1025,8 +1061,11 @@ end process name;",
let process = ProcessStatement {
postponed: false,
sensitivity_list: None,
is_token: Some(code.s1("is").token()),
decl: vec![],
begin_token: code.s1("begin").token(),
statements: vec![],
end_token: code.s1("end").token(),
end_label_pos: Some(code.s("name", 2).pos()),
span: code.token_span().skip_to(code.s1("process").token()),
};
@ -1052,8 +1091,11 @@ end process;",
let process = ProcessStatement {
postponed: true,
sensitivity_list: None,
is_token: None,
decl: vec![],
begin_token: code.s1("begin").token(),
statements: vec![],
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span(),
};
@ -1076,8 +1118,11 @@ end postponed process;",
let process = ProcessStatement {
postponed: true,
sensitivity_list: None,
is_token: None,
decl: vec![],
begin_token: code.s1("begin").token(),
statements: vec![],
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span(),
};
@ -1101,8 +1146,11 @@ end postponed process;",
let process = ProcessStatement {
postponed: false,
sensitivity_list: None,
is_token: Some(code.s1("is").token()),
decl: Vec::new(),
begin_token: code.s1("begin").token(),
statements: Vec::new(),
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span(),
};
@ -1129,12 +1177,15 @@ end process;",
);
let process = ProcessStatement {
postponed: false,
sensitivity_list: Some(SensitivityList::Names(vec![
code.s1("clk").name(),
code.s1("vec(1)").name(),
])),
sensitivity_list: Some(WithTokenSpan::new(
SensitivityList::Names(vec![code.s1("clk").name(), code.s1("vec(1)").name()]),
code.between("(", "))").token_span(),
)),
is_token: Some(code.s1("is").token()),
decl: vec![],
begin_token: code.s1("begin").token(),
statements: vec![],
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span(),
};
@ -1157,9 +1208,15 @@ end process;",
let (stmt, diagnostics) = code.with_stream_diagnostics(parse_labeled_concurrent_statement);
let process = ProcessStatement {
postponed: false,
sensitivity_list: Some(SensitivityList::Names(Vec::new())),
sensitivity_list: Some(WithTokenSpan::new(
SensitivityList::Names(Vec::new()),
code.s1("()").token_span(),
)),
is_token: Some(code.s1("is").token()),
decl: Vec::new(),
begin_token: code.s1("begin").token(),
statements: Vec::new(),
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span(),
};
@ -1189,12 +1246,18 @@ end process;",
);
let process = ProcessStatement {
postponed: false,
sensitivity_list: Some(SensitivityList::All),
sensitivity_list: Some(WithTokenSpan::new(
SensitivityList::All,
code.s1("(all)").token_span(),
)),
is_token: Some(code.s1("is").token()),
decl: code.s1("variable foo : boolean;").declarative_part(),
begin_token: code.s1("begin").token(),
statements: vec![
code.s1("foo <= true;").sequential_statement(),
code.s1("wait;").sequential_statement(),
],
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span(),
};
@ -1300,6 +1363,7 @@ with x(0) + 1 select
alternatives: vec![Alternative {
choices: code.s1("0|1").choices(),
item: code.s1("bar(1,2) after 2 ns").waveform(),
span: code.s1("bar(1,2) after 2 ns when 0|1").token_span(),
}],
};
@ -1312,7 +1376,10 @@ with x(0) + 1 select
guarded: false,
assignment: SignalAssignment {
target: code.s1("foo(0)").name().map_into(Target::Name),
delay_mechanism: Some(DelayMechanism::Transport),
delay_mechanism: Some(WithTokenSpan::new(
DelayMechanism::Transport,
code.s1("transport").token_span()
)),
rhs: AssignmentRightHand::Selected(selection)
}
})
@ -1521,12 +1588,15 @@ end generate;",
let gen = ForGenerateStatement {
index_name: code.s1("idx").decl_ident(),
discrete_range: code.s1("0 to 1").discrete_range(),
generate_token: code.s1("generate").token(),
body: GenerateBody {
alternative_label: None,
decl: None,
statements: vec![],
end_label_pos: None,
end_token: None,
end_label: None,
},
end_token: code.s1("end").token(),
end_label_pos: None,
span: code.token_span().skip_to(code.s1("for").token()),
};
@ -1552,13 +1622,16 @@ end generate;",
let gen = ForGenerateStatement {
index_name: code.s1("idx").decl_ident(),
discrete_range: code.s1("0 to 1").discrete_range(),
generate_token: code.s1("generate").token(),
body: GenerateBody {
alternative_label: None,
decl: None,
statements: vec![code.s1("foo <= bar;").concurrent_statement()],
end_label_pos: None,
end_token: None,
end_label: None,
},
end_label_pos: None,
end_token: code.s1("end").token(),
span: code.token_span().skip_to(code.s1("for").token()),
};
let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement);
@ -1574,16 +1647,24 @@ end generate;",
#[test]
fn test_for_generate_empty_declarations() {
fn test(decl: Option<Vec<WithTokenSpan<Declaration>>>, code: Code) {
fn test(
decl: Option<Vec<WithTokenSpan<Declaration>>>,
code: Code,
body_end_token: Option<usize>,
end_token: usize,
) {
let gen = ForGenerateStatement {
index_name: code.s1("idx").decl_ident(),
discrete_range: code.s1("0 to 1").discrete_range(),
generate_token: code.s1("generate").token(),
body: GenerateBody {
alternative_label: None,
decl,
decl: decl.map(|val| (val, code.s1("begin").token())),
statements: vec![code.s1("foo <= bar;").concurrent_statement()],
end_label_pos: None,
end_token: body_end_token.map(|occ| code.s("end", occ).token()),
end_label: None,
},
end_token: code.s("end", end_token).token(),
end_label_pos: None,
span: code.token_span().skip_to(code.s1("for").token()),
};
@ -1606,6 +1687,8 @@ begin
foo <= bar;
end generate;",
),
None,
1,
);
test(
@ -1616,6 +1699,8 @@ gen: for idx in 0 to 1 generate
foo <= bar;
end generate;",
),
None,
1,
);
test(
@ -1628,21 +1713,29 @@ begin
end;
end generate;",
),
Some(1),
2,
);
}
#[test]
fn test_for_generate_declarations() {
fn test(code: Code) {
fn test(code: Code, end_occurrence: usize, body_end_token: Option<usize>) {
let gen = ForGenerateStatement {
index_name: code.s1("idx").decl_ident(),
discrete_range: code.s1("0 to 1").discrete_range(),
generate_token: code.s1("generate").token(),
body: GenerateBody {
alternative_label: None,
decl: Some(code.s1("signal foo : natural;").declarative_part()),
decl: Some((
code.s1("signal foo : natural;").declarative_part(),
code.s1("begin").token(),
)),
statements: vec![code.s1("foo <= bar;").concurrent_statement()],
end_label_pos: None,
end_token: body_end_token.map(|val| code.s("end", val).token()),
end_label: None,
},
end_token: code.s("end", end_occurrence).token(),
end_label_pos: None,
span: code.token_span().skip_to(code.s1("for").token()),
};
@ -1657,24 +1750,32 @@ end generate;",
);
}
test(Code::new(
"\
test(
Code::new(
"\
gen: for idx in 0 to 1 generate
signal foo : natural;
begin
foo <= bar;
end generate;",
));
),
1,
None,
);
test(Code::new(
"\
test(
Code::new(
"\
gen: for idx in 0 to 1 generate
signal foo : natural;
begin
foo <= bar;
end;
end generate;",
));
),
2,
Some(1),
);
}
#[test]
@ -1692,7 +1793,8 @@ end generate;",
alternative_label: None,
decl: None,
statements: vec![],
end_label_pos: None,
end_label: None,
end_token: None,
},
}],
else_item: None,
@ -1725,9 +1827,10 @@ end generate;",
condition: code.s1("cond = true").expr(),
item: GenerateBody {
alternative_label: None,
decl: Some(vec![]),
decl: Some((vec![], code.s1("begin").token())),
statements: vec![],
end_label_pos: None,
end_token: None,
end_label: None,
},
}],
else_item: None,
@ -1764,7 +1867,8 @@ end generate;",
alternative_label: None,
decl: None,
statements: vec![],
end_label_pos: None,
end_token: None,
end_label: None,
},
},
Conditional {
@ -1773,16 +1877,21 @@ end generate;",
alternative_label: None,
decl: None,
statements: vec![],
end_label_pos: None,
end_token: None,
end_label: None,
},
},
],
else_item: Some(GenerateBody {
alternative_label: None,
decl: None,
statements: vec![],
end_label_pos: None,
}),
else_item: Some((
GenerateBody {
alternative_label: None,
decl: None,
statements: vec![],
end_token: None,
end_label: None,
},
code.s1("else").token(),
)),
},
end_label_pos: None,
span: code.token_span().skip_to(code.s1("if").token()),
@ -1822,27 +1931,42 @@ end generate;",
condition: code.s1("cond = true").expr(),
item: GenerateBody {
alternative_label: None,
decl: Some(code.s1("variable v1 : boolean;").declarative_part()),
decl: Some((
code.s1("variable v1 : boolean;").declarative_part(),
code.s("begin", 1).token(),
)),
statements: vec![code.s1("foo1(clk);").concurrent_statement()],
end_label_pos: None,
end_token: None,
end_label: None,
},
},
Conditional {
condition: code.s1("cond2 = true").expr(),
item: GenerateBody {
alternative_label: None,
decl: Some(code.s1("variable v2 : boolean;").declarative_part()),
decl: Some((
code.s1("variable v2 : boolean;").declarative_part(),
code.s("begin", 2).token(),
)),
statements: vec![code.s1("foo2(clk);").concurrent_statement()],
end_label_pos: None,
end_token: None,
end_label: None,
},
},
],
else_item: Some(GenerateBody {
alternative_label: None,
decl: Some(code.s1("variable v3 : boolean;").declarative_part()),
statements: vec![code.s1("foo3(clk);").concurrent_statement()],
end_label_pos: None,
}),
else_item: Some((
GenerateBody {
alternative_label: None,
decl: Some((
code.s1("variable v3 : boolean;").declarative_part(),
code.s("begin", 3).token(),
)),
statements: vec![code.s1("foo3(clk);").concurrent_statement()],
end_token: None,
end_label: None,
},
code.s1("else").token(),
)),
},
end_label_pos: None,
span: code.token_span().skip_to(code.s1("if").token()),
@ -1878,7 +2002,8 @@ end generate;",
alternative_label: Some(code.s1("alt1").decl_ident()),
decl: None,
statements: vec![],
end_label_pos: None,
end_token: None,
end_label: None,
},
},
Conditional {
@ -1887,16 +2012,21 @@ end generate;",
alternative_label: None,
decl: None,
statements: vec![],
end_label_pos: None,
end_token: Some(code.s("end", 1).token()),
end_label: Some(code.s1("alt2").token()),
},
},
],
else_item: Some(GenerateBody {
alternative_label: Some(code.s1("alt3").decl_ident()),
decl: None,
statements: vec![],
end_label_pos: None,
}),
else_item: Some((
GenerateBody {
alternative_label: Some(code.s1("alt3").decl_ident()),
decl: None,
statements: vec![],
end_token: Some(code.s("end", 2).token()),
end_label: Some(code.s1("alt4").token()),
},
code.s1("else").token(),
)),
},
end_label_pos: None,
span: code.token_span().skip_to(code.s1("if").token()),
@ -1942,7 +2072,8 @@ end generate;",
alternative_label: Some(code.s1("alt1").decl_ident()),
decl: None,
statements: vec![],
end_label_pos: Some(code.s("alt1", 2).pos()),
end_token: Some(code.s("end", 1).token()),
end_label: Some(code.s("alt1", 2).token()),
},
},
Conditional {
@ -1951,16 +2082,21 @@ end generate;",
alternative_label: Some(code.s1("alt2").decl_ident()),
decl: None,
statements: vec![],
end_label_pos: Some(code.s("alt2", 2).pos()),
end_token: Some(code.s("end", 2).token()),
end_label: Some(code.s("alt2", 2).token()),
},
},
],
else_item: Some(GenerateBody {
alternative_label: Some(code.s1("alt3").decl_ident()),
decl: None,
statements: vec![],
end_label_pos: Some(code.s("alt3", 2).pos()),
}),
else_item: Some((
GenerateBody {
alternative_label: Some(code.s1("alt3").decl_ident()),
decl: None,
statements: vec![],
end_token: Some(code.s("end", 3).token()),
end_label: Some(code.s("alt3", 2).token()),
},
code.s1("else").token(),
)),
},
end_label_pos: None,
span: code.token_span().skip_to(code.s1("if").token()),
@ -1997,8 +2133,10 @@ end generate;",
alternative_label: None,
decl: None,
statements: vec![code.s1("sig <= value;").concurrent_statement()],
end_label_pos: None,
end_token: None,
end_label: None,
},
span: code.between("1 | 2", "value;").token_span(),
},
Alternative {
choices: code.s1("others").choices(),
@ -2006,12 +2144,15 @@ end generate;",
alternative_label: None,
decl: None,
statements: vec![code.s1("foo(clk);").concurrent_statement()],
end_label_pos: None,
end_token: None,
end_label: None,
},
span: code.between("others", "foo(clk);").token_span(),
},
],
},
end_label_pos: None,
end_token: code.s1("end").token(),
span: code.token_span().skip_to(code.s1("case").token()),
};
let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement);
@ -2046,8 +2187,10 @@ end generate gen1;",
alternative_label: Some(code.s1("alt1").decl_ident()),
decl: None,
statements: vec![code.s1("sig <= value;").concurrent_statement()],
end_label_pos: None,
end_token: None,
end_label: None,
},
span: code.between("alt1", ";").token_span(),
},
Alternative {
choices: code.s1("others").choices(),
@ -2055,12 +2198,15 @@ end generate gen1;",
alternative_label: Some(code.s1("alt2").decl_ident()),
decl: None,
statements: vec![code.s1("foo(clk);").concurrent_statement()],
end_label_pos: None,
end_token: None,
end_label: None,
},
span: code.between("alt2", ";").token_span(),
},
],
},
end_label_pos: Some(code.s("gen1", 2).pos()),
end_token: code.s1("end").token(),
span: code.token_span().skip_to(code.s1("case").token()),
};
let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement);

View file

@ -15,9 +15,10 @@ use crate::ast::*;
use crate::data::*;
use crate::syntax::recover::{expect_semicolon, expect_semicolon_or_last};
use vhdl_lang::syntax::parser::ParsingContext;
use vhdl_lang::TokenId;
/// LRM 7.3.2.2
fn parse_entity_aspect(ctx: &mut ParsingContext<'_>) -> ParseResult<EntityAspect> {
pub(crate) fn parse_entity_aspect(ctx: &mut ParsingContext<'_>) -> ParseResult<EntityAspect> {
let entity_aspect = expect_token!(
ctx.stream,
token,
@ -43,25 +44,27 @@ fn parse_entity_aspect(ctx: &mut ParsingContext<'_>) -> ParseResult<EntityAspect
fn parse_binding_indication_known_entity_aspect(
ctx: &mut ParsingContext<'_>,
entity_aspect: Option<EntityAspect>,
start_token: TokenId,
) -> ParseResult<BindingIndication> {
let (generic_map, port_map) = parse_generic_and_port_map(ctx)?;
expect_semicolon(ctx);
let semicolon_token = expect_semicolon_or_last(ctx);
Ok(BindingIndication {
entity_aspect,
generic_map,
port_map,
span: TokenSpan::new(start_token, semicolon_token),
})
}
/// LRM 7.3.2
fn parse_binding_indication(ctx: &mut ParsingContext<'_>) -> ParseResult<BindingIndication> {
let entity_aspect = if ctx.stream.skip_if_kind(Use) {
Some(parse_entity_aspect(ctx)?)
let (entity_aspect, token) = if let Some(use_token) = ctx.stream.pop_if_kind(Use) {
(Some(parse_entity_aspect(ctx)?), use_token)
} else {
None
(None, ctx.stream.get_current_token_id())
};
parse_binding_indication_known_entity_aspect(ctx, entity_aspect)
parse_binding_indication_known_entity_aspect(ctx, entity_aspect, token)
}
fn parse_component_configuration_known_spec(
@ -71,19 +74,20 @@ fn parse_component_configuration_known_spec(
let (bind_ind, vunit_bind_inds) = peek_token!(
ctx.stream,
token,
token_id,
End => (None, Vec::new()),
For => (None, Vec::new()),
Use => {
ctx.stream.skip();
if ctx.stream.peek_kind() == Some(Vunit) {
let vunit_bind_inds = parse_vunit_binding_indication_list_known_keyword(ctx)?;
let vunit_bind_inds = parse_vunit_binding_indication_list_known_keyword(ctx, token_id)?;
(None, vunit_bind_inds)
} else {
let aspect = parse_entity_aspect(ctx)?;
let bind_ind = parse_binding_indication_known_entity_aspect(ctx, Some(aspect))?;
let bind_ind = parse_binding_indication_known_entity_aspect(ctx, Some(aspect), token_id)?;
if ctx.stream.skip_if_kind(Use) {
(Some(bind_ind), parse_vunit_binding_indication_list_known_keyword(ctx)?)
if let Some(start_token) = ctx.stream.pop_if_kind(Use) {
(Some(bind_ind), parse_vunit_binding_indication_list_known_keyword(ctx, start_token)?)
} else {
(Some(bind_ind), Vec::new())
}
@ -94,21 +98,24 @@ fn parse_component_configuration_known_spec(
let block_config = expect_token!(
ctx.stream,
token,
token_id,
End => None,
For => {
let block_config = parse_block_configuration_known_keyword(ctx)?;
let block_config = parse_block_configuration_known_keyword(ctx, token_id)?;
ctx.stream.expect_kind(End)?;
Some(block_config)
}
);
ctx.stream.expect_kind(For)?;
expect_semicolon(ctx);
let semicolon_token = expect_semicolon_or_last(ctx);
let start_token = spec.span.start_token;
Ok(ComponentConfiguration {
spec,
bind_ind,
vunit_bind_inds,
block_config,
span: TokenSpan::new(start_token, semicolon_token),
})
}
@ -119,26 +126,33 @@ enum ComponentSpecificationOrName {
fn parse_component_specification_or_name(
ctx: &mut ParsingContext<'_>,
start_token: TokenId,
) -> ParseResult<ComponentSpecificationOrName> {
peek_token!(
ctx.stream, token,
All => {
ctx.stream.skip();
ctx.stream.expect_kind(Colon)?;
let colon_token = ctx.stream.expect_kind(Colon)?;
let component_name = parse_selected_name(ctx)?;
let end_token = component_name.span.end_token;
Ok(ComponentSpecificationOrName::ComponentSpec(ComponentSpecification {
instantiation_list: InstantiationList::All,
component_name,
colon_token,
span: TokenSpan::new(start_token, end_token),
}))
},
Others => {
ctx.stream.skip();
ctx.stream.expect_kind(Colon)?;
let colon_token = ctx.stream.expect_kind(Colon)?;
let component_name = parse_selected_name(ctx)?;
let end_token = component_name.span.end_token;
Ok(ComponentSpecificationOrName::ComponentSpec(ComponentSpecification {
instantiation_list: InstantiationList::Others,
component_name,
colon_token,
span: TokenSpan::new(start_token, end_token),
}))
},
Identifier => {
@ -146,30 +160,38 @@ fn parse_component_specification_or_name(
let sep_token = ctx.stream.peek_expect()?;
match sep_token.kind {
Colon => {
let colon_token = ctx.stream.get_current_token_id();
ctx.stream.skip();
let ident = to_simple_name(ctx.stream, name)?;
let component_name = parse_selected_name(ctx)?;
let end_token = component_name.span.end_token;
Ok(ComponentSpecificationOrName::ComponentSpec(ComponentSpecification {
instantiation_list: InstantiationList::Labels(vec![ident]),
component_name,
colon_token,
span: TokenSpan::new(start_token, end_token),
}))
}
Comma => {
ctx.stream.skip();
let mut idents = vec![to_simple_name(ctx.stream, name)?];
loop {
let colon_token = loop {
idents.push(ctx.stream.expect_ident()?);
expect_token!(
ctx.stream,
next_token,
next_token_id,
Comma => {},
Colon => break
Colon => break next_token_id
);
}
};
let component_name = parse_selected_name(ctx)?;
let end_token = component_name.span.end_token;
Ok(ComponentSpecificationOrName::ComponentSpec(ComponentSpecification {
instantiation_list: InstantiationList::Labels(idents),
component_name,
colon_token,
span: TokenSpan::new(start_token, end_token),
}))
}
_ => Ok(ComponentSpecificationOrName::Name(name))
@ -180,15 +202,16 @@ fn parse_component_specification_or_name(
fn parse_configuration_item_known_keyword(
ctx: &mut ParsingContext<'_>,
token: TokenId,
) -> ParseResult<ConfigurationItem> {
match parse_component_specification_or_name(ctx)? {
match parse_component_specification_or_name(ctx, token)? {
ComponentSpecificationOrName::ComponentSpec(component_spec) => {
Ok(ConfigurationItem::Component(
parse_component_configuration_known_spec(ctx, component_spec)?,
))
}
ComponentSpecificationOrName::Name(name) => Ok(ConfigurationItem::Block(
parse_block_configuration_known_name(ctx, name)?,
parse_block_configuration_known_name(ctx, name, token)?,
)),
}
}
@ -196,6 +219,7 @@ fn parse_configuration_item_known_keyword(
fn parse_block_configuration_known_name(
ctx: &mut ParsingContext<'_>,
name: WithTokenSpan<Name>,
start_token: TokenId,
) -> ParseResult<BlockConfiguration> {
let block_spec = name;
// @TODO use clauses
@ -206,32 +230,36 @@ fn parse_block_configuration_known_name(
expect_token!(
ctx.stream,
token,
token_id,
End => {
break;
},
For => {
items.push(parse_configuration_item_known_keyword(ctx)?);
items.push(parse_configuration_item_known_keyword(ctx, token_id)?);
}
);
}
ctx.stream.expect_kind(For)?;
expect_semicolon(ctx);
let semicolon = expect_semicolon_or_last(ctx);
Ok(BlockConfiguration {
block_spec,
use_clauses,
items,
span: TokenSpan::new(start_token, semicolon),
})
}
fn parse_block_configuration_known_keyword(
ctx: &mut ParsingContext<'_>,
token: TokenId,
) -> ParseResult<BlockConfiguration> {
let name = parse_name(ctx)?;
parse_block_configuration_known_name(ctx, name)
parse_block_configuration_known_name(ctx, name, token)
}
fn parse_vunit_binding_indication_list_known_keyword(
ctx: &mut ParsingContext<'_>,
start_token: TokenId,
) -> ParseResult<Vec<VUnitBindingIndication>> {
let mut indications = Vec::new();
loop {
@ -242,13 +270,16 @@ fn parse_vunit_binding_indication_list_known_keyword(
let vunit_bind_ind = loop {
vunit_list.push(parse_name(ctx)?);
peek_token!(
ctx.stream, token,
ctx.stream, token, token_id,
Comma => {
ctx.stream.skip();
},
SemiColon => {
ctx.stream.skip();
break VUnitBindingIndication { vunit_list };
break VUnitBindingIndication {
vunit_list,
span: TokenSpan::new(start_token, token_id)
};
}
);
};
@ -278,28 +309,27 @@ pub fn parse_configuration_declaration(
match token.kind {
Use => {
if ctx.stream.nth_kind_is(1, Vunit) {
let start_token = ctx.stream.get_current_token_id();
ctx.stream.skip();
break parse_vunit_binding_indication_list_known_keyword(ctx)?;
break parse_vunit_binding_indication_list_known_keyword(ctx, start_token)?;
}
decl.push(ConfigurationDeclarativeItem::Use(
parse_use_clause(ctx)?.item,
));
decl.push(parse_use_clause(ctx)?.map_into(Declaration::Use));
}
_ => break Vec::new(),
}
};
ctx.stream.expect_kind(For)?;
let block_config = parse_block_configuration_known_keyword(ctx)?;
let for_token = ctx.stream.expect_kind(For)?;
let block_config = parse_block_configuration_known_keyword(ctx, for_token)?;
ctx.stream.expect_kind(End)?;
let end_token = ctx.stream.expect_kind(End)?;
ctx.stream.pop_if_kind(Configuration);
let end_ident = ctx.stream.pop_optional_ident();
let end_token = expect_semicolon_or_last(ctx);
let last_token = expect_semicolon_or_last(ctx);
Ok(ConfigurationDeclaration {
span: TokenSpan::new(start_token, end_token),
span: TokenSpan::new(start_token, last_token),
context_clause: ContextClause::default(),
end_ident_pos: check_end_identifier_mismatch(ctx, &ident.tree, end_ident),
ident,
@ -307,6 +337,7 @@ pub fn parse_configuration_declaration(
decl,
vunit_bind_inds,
block_config,
end_token,
})
}
@ -315,30 +346,36 @@ pub fn parse_configuration_specification(
ctx: &mut ParsingContext<'_>,
) -> ParseResult<ConfigurationSpecification> {
let start_token = ctx.stream.expect_kind(For)?;
match parse_component_specification_or_name(ctx)? {
match parse_component_specification_or_name(ctx, start_token)? {
ComponentSpecificationOrName::ComponentSpec(spec) => {
let bind_ind = parse_binding_indication(ctx)?;
if ctx.stream.skip_if_kind(Use) {
let vunit_bind_inds = parse_vunit_binding_indication_list_known_keyword(ctx)?;
ctx.stream.expect_kind(End)?;
if let Some(use_token) = ctx.stream.pop_if_kind(Use) {
let vunit_bind_inds =
parse_vunit_binding_indication_list_known_keyword(ctx, use_token)?;
let end_token = ctx.stream.expect_kind(End)?;
ctx.stream.expect_kind(For)?;
let end_token = expect_semicolon_or_last(ctx);
let final_token = expect_semicolon_or_last(ctx);
Ok(ConfigurationSpecification {
span: TokenSpan::new(start_token, end_token),
span: TokenSpan::new(start_token, final_token),
spec,
bind_ind,
vunit_bind_inds,
end_token: Some(end_token),
})
} else {
if ctx.stream.skip_if_kind(End) {
let end_token = if let Some(token) = ctx.stream.pop_if_kind(End) {
ctx.stream.expect_kind(For)?;
expect_semicolon(ctx);
}
let end_token = ctx.stream.get_last_token_id();
Some(token)
} else {
None
};
let final_token = ctx.stream.get_last_token_id();
Ok(ConfigurationSpecification {
span: TokenSpan::new(start_token, end_token),
span: TokenSpan::new(start_token, final_token),
spec,
bind_ind,
end_token,
vunit_bind_inds: Vec::new(),
})
}
@ -378,7 +415,9 @@ end;
block_spec: code.s1("rtl(0)").name(),
use_clauses: vec![],
items: vec![],
span: code.between("for", "end for;").token_span(),
},
end_token: code.s("end", 2).token(),
end_ident_pos: None,
}
);
@ -407,7 +446,9 @@ end configuration cfg;
block_spec: code.s1("rtl(0)").name(),
use_clauses: vec![],
items: vec![],
span: code.between("for", "end for;").token_span(),
},
end_token: code.s("end", 2).token(),
end_ident_pos: Some(code.s("cfg", 2).token())
}
);
@ -432,15 +473,21 @@ end configuration cfg;
ident: code.s1("cfg").decl_ident(),
entity_name: code.s1("entity_name").name(),
decl: vec![
ConfigurationDeclarativeItem::Use(code.s1("use lib.foo.bar;").use_clause()),
ConfigurationDeclarativeItem::Use(code.s1("use lib2.foo.bar;").use_clause())
code.s1("use lib.foo.bar;")
.use_clause()
.map_into(Declaration::Use),
code.s1("use lib2.foo.bar;")
.use_clause()
.map_into(Declaration::Use)
],
vunit_bind_inds: Vec::new(),
block_config: BlockConfiguration {
block_spec: code.s1("rtl(0)").name(),
use_clauses: vec![],
items: vec![],
span: code.between("for", "end for;").token_span(),
},
end_token: code.s("end", 2).token(),
end_ident_pos: Some(code.s("cfg", 2).token())
}
);
@ -465,17 +512,21 @@ end configuration cfg;
context_clause: ContextClause::default(),
ident: code.s1("cfg").decl_ident(),
entity_name: code.s1("entity_name").name(),
decl: vec![ConfigurationDeclarativeItem::Use(
code.s1("use lib.foo.bar;").use_clause()
),],
decl: vec![code
.s1("use lib.foo.bar;")
.use_clause()
.map_into(Declaration::Use)],
vunit_bind_inds: vec![VUnitBindingIndication {
vunit_list: vec![code.s1("baz.foobar").name()]
vunit_list: vec![code.s1("baz.foobar").name()],
span: code.s1("use vunit baz.foobar;").token_span()
}],
block_config: BlockConfiguration {
block_spec: code.s1("rtl(0)").name(),
use_clauses: vec![],
items: vec![],
span: code.between("for", "end for;").token_span(),
},
end_token: code.s("end", 2).token(),
end_ident_pos: Some(code.s("cfg", 2).token())
}
);
@ -504,7 +555,9 @@ end configuration cfg;
block_spec: code.s1("rtl(0)").name(),
use_clauses: vec![],
items: vec![],
span: code.between("for", "end for;").token_span(),
},
end_token: code.s("end", 2).token(),
end_ident_pos: Some(code.s("cfg", 2).token())
}
);
@ -541,14 +594,31 @@ end configuration cfg;
block_spec: code.s1("name(0 to 3)").name(),
use_clauses: vec![],
items: vec![],
span: code
.s1("for name(0 to 3)
end for;")
.token_span(),
}),
ConfigurationItem::Block(BlockConfiguration {
block_spec: code.s1("other_name").name(),
use_clauses: vec![],
items: vec![],
span: code
.s1("for other_name
end for;")
.token_span(),
})
],
span: code
.s1("for rtl(0)
for name(0 to 3)
end for;
for other_name
end for;
end for;")
.token_span(),
},
end_token: code.s("end", 4).token(),
end_ident_pos: Some(code.s("cfg", 2).token())
}
);
@ -585,7 +655,9 @@ end configuration cfg;
instantiation_list: InstantiationList::Labels(vec![code
.s1("inst")
.ident()]),
component_name: code.s1("lib.pkg.comp").name()
colon_token: code.s1(":").token(),
component_name: code.s1("lib.pkg.comp").name(),
span: code.s1("for inst : lib.pkg.comp").token_span()
},
bind_ind: None,
vunit_bind_inds: Vec::new(),
@ -593,9 +665,28 @@ end configuration cfg;
block_spec: code.s1("arch").name(),
use_clauses: vec![],
items: vec![],
span: code
.s1("for arch
end for;")
.token_span(),
}),
}),],
span: code
.s1("for inst : lib.pkg.comp
for arch
end for;
end for;")
.token_span()
})],
span: code
.s1("for rtl(0)
for inst : lib.pkg.comp
for arch
end for;
end for;
end for;")
.token_span(),
},
end_token: code.s("end", 4).token(),
end_ident_pos: Some(code.s("cfg", 2).token())
}
);
@ -634,7 +725,9 @@ end configuration cfg;
instantiation_list: InstantiationList::Labels(vec![code
.s1("inst")
.ident()]),
component_name: code.s1("lib.pkg.comp").name()
colon_token: code.s1(":").token(),
component_name: code.s1("lib.pkg.comp").name(),
span: code.s1("for inst : lib.pkg.comp").token_span()
},
bind_ind: Some(BindingIndication {
entity_aspect: Some(EntityAspect::Entity(
@ -642,18 +735,43 @@ end configuration cfg;
None
)),
generic_map: None,
port_map: None
port_map: None,
span: code.s1("use entity work.bar;").token_span()
}),
vunit_bind_inds: vec![VUnitBindingIndication {
vunit_list: vec![code.s1("baz").name()]
},],
vunit_list: vec![code.s1("baz").name()],
span: code.s1("use vunit baz;").token_span()
}],
block_config: Some(BlockConfiguration {
block_spec: code.s1("arch").name(),
use_clauses: vec![],
items: vec![],
span: code
.s1("for arch
end for;")
.token_span(),
}),
}),],
span: code
.s1("for inst : lib.pkg.comp
use entity work.bar;
use vunit baz;
for arch
end for;
end for;")
.token_span(),
})],
span: code
.s1("for rtl(0)
for inst : lib.pkg.comp
use entity work.bar;
use vunit baz;
for arch
end for;
end for;
end for;")
.token_span(),
},
end_token: code.s("end", 4).token(),
end_ident_pos: Some(code.s("cfg", 2).token())
}
);
@ -689,7 +807,9 @@ end configuration cfg;
instantiation_list: InstantiationList::Labels(vec![code
.s1("inst")
.ident()]),
component_name: code.s1("lib.pkg.comp").name()
colon_token: code.s1(":").token(),
component_name: code.s1("lib.pkg.comp").name(),
span: code.s1("for inst : lib.pkg.comp").token_span()
},
bind_ind: Some(BindingIndication {
entity_aspect: Some(EntityAspect::Entity(
@ -698,11 +818,25 @@ end configuration cfg;
)),
generic_map: None,
port_map: None,
span: code.s1("use entity lib.use_name;").token_span()
}),
vunit_bind_inds: Vec::new(),
block_config: None,
}),],
span: code
.s1("for inst : lib.pkg.comp
use entity lib.use_name;
end for;")
.token_span(),
})],
span: code
.s1("for rtl(0)
for inst : lib.pkg.comp
use entity lib.use_name;
end for;
end for;")
.token_span(),
},
end_token: code.s("end", 3).token(),
end_ident_pos: Some(code.s("cfg", 2).token())
}
);
@ -744,11 +878,17 @@ end configuration cfg;
instantiation_list: InstantiationList::Labels(vec![code
.s1("inst")
.ident()]),
component_name: code.s1("lib.pkg.comp").name()
colon_token: code.s(":", 1).token(),
component_name: code.s1("lib.pkg.comp").name(),
span: code.s1("for inst : lib.pkg.comp").token_span()
},
bind_ind: None,
vunit_bind_inds: Vec::new(),
block_config: None,
span: code
.s1("for inst : lib.pkg.comp
end for;")
.token_span()
}),
ConfigurationItem::Component(ComponentConfiguration {
spec: ComponentSpecification {
@ -757,32 +897,65 @@ end configuration cfg;
code.s1("inst2").ident(),
code.s1("inst3").ident()
]),
component_name: code.s1("lib2.pkg.comp").name()
colon_token: code.s(":", 2).token(),
component_name: code.s1("lib2.pkg.comp").name(),
span: code
.s1("for inst1, inst2, inst3 : lib2.pkg.comp")
.token_span()
},
bind_ind: None,
vunit_bind_inds: Vec::new(),
block_config: None,
span: code
.s1("for inst1, inst2, inst3 : lib2.pkg.comp
end for;")
.token_span()
}),
ConfigurationItem::Component(ComponentConfiguration {
spec: ComponentSpecification {
instantiation_list: InstantiationList::All,
component_name: code.s1("lib3.pkg.comp").name()
component_name: code.s1("lib3.pkg.comp").name(),
colon_token: code.s(":", 3).token(),
span: code.s1("for all : lib3.pkg.comp").token_span()
},
bind_ind: None,
vunit_bind_inds: Vec::new(),
block_config: None,
span: code
.s1("for all : lib3.pkg.comp
end for;")
.token_span()
}),
ConfigurationItem::Component(ComponentConfiguration {
spec: ComponentSpecification {
instantiation_list: InstantiationList::Others,
component_name: code.s1("lib4.pkg.comp").name()
component_name: code.s1("lib4.pkg.comp").name(),
colon_token: code.s(":", 4).token(),
span: code.s1("for others : lib4.pkg.comp").token_span()
},
bind_ind: None,
vunit_bind_inds: Vec::new(),
block_config: None,
span: code
.s1("for others : lib4.pkg.comp
end for;")
.token_span()
})
],
span: code
.s1("for rtl(0)
for inst : lib.pkg.comp
end for;
for inst1, inst2, inst3 : lib2.pkg.comp
end for;
for all : lib3.pkg.comp
end for;
for others : lib4.pkg.comp
end for;
end for;")
.token_span(),
},
end_token: code.s("end", 6).token(),
end_ident_pos: Some(code.s("cfg", 2).token())
}
);
@ -835,6 +1008,8 @@ end configuration cfg;
spec: ComponentSpecification {
instantiation_list: InstantiationList::All,
component_name: code.s1("lib.pkg.comp").name(),
colon_token: code.s1(":").token(),
span: code.s1("for all : lib.pkg.comp").token_span()
},
bind_ind: BindingIndication {
entity_aspect: Some(EntityAspect::Entity(
@ -842,9 +1017,11 @@ end configuration cfg;
Some(code.s1("rtl").ident())
)),
generic_map: None,
port_map: None
port_map: None,
span: code.s1("use entity work.foo(rtl);").token_span()
},
vunit_bind_inds: Vec::new()
vunit_bind_inds: Vec::new(),
end_token: None,
}
);
}
@ -860,6 +1037,8 @@ end configuration cfg;
spec: ComponentSpecification {
instantiation_list: InstantiationList::All,
component_name: code.s1("lib.pkg.comp").name(),
colon_token: code.s1(":").token(),
span: code.s1("for all : lib.pkg.comp").token_span()
},
bind_ind: BindingIndication {
entity_aspect: Some(EntityAspect::Entity(
@ -867,9 +1046,11 @@ end configuration cfg;
Some(code.s1("rtl").ident())
)),
generic_map: None,
port_map: None
port_map: None,
span: code.s1("use entity work.foo(rtl);").token_span()
},
vunit_bind_inds: Vec::new()
vunit_bind_inds: Vec::new(),
end_token: Some(code.s1("end").token())
}
);
}
@ -887,6 +1068,8 @@ end configuration cfg;
spec: ComponentSpecification {
instantiation_list: InstantiationList::All,
component_name: code.s1("lib.pkg.comp").name(),
colon_token: code.s1(":").token(),
span: code.s1("for all : lib.pkg.comp").token_span()
},
bind_ind: BindingIndication {
entity_aspect: Some(EntityAspect::Entity(
@ -894,11 +1077,14 @@ end configuration cfg;
Some(code.s1("rtl").ident())
)),
generic_map: None,
port_map: None
port_map: None,
span: code.s1("use entity work.foo(rtl);").token_span()
},
vunit_bind_inds: vec![VUnitBindingIndication {
vunit_list: vec![code.s1("bar").name(), code.s1("baz").name()]
vunit_list: vec![code.s1("bar").name(), code.s1("baz").name()],
span: code.s1("use vunit bar, baz;").token_span()
}],
end_token: Some(code.s1("end").token()),
}
);
}

View file

@ -6,14 +6,15 @@
use super::common::check_end_identifier_mismatch;
use super::common::ParseResult;
use super::names::parse_name;
use super::names::{parse_identifier_list, parse_name};
use super::tokens::{Kind::*, TokenSpan};
use crate::ast::token_range::WithTokenSpan;
use crate::ast::*;
use crate::syntax::parser::ParsingContext;
use crate::syntax::recover;
use crate::syntax::recover::expect_semicolon;
use crate::syntax::separated_list::{parse_ident_list, parse_name_list};
use crate::syntax::separated_list::parse_name_list;
use itertools::Itertools;
#[derive(PartialEq, Debug)]
pub enum DeclarationOrReference {
Declaration(ContextDeclaration),
@ -23,7 +24,10 @@ pub enum DeclarationOrReference {
/// LRM 13. Design units and their analysis
pub fn parse_library_clause(ctx: &mut ParsingContext<'_>) -> ParseResult<LibraryClause> {
let library_token = ctx.stream.expect_kind(Library)?;
let name_list = parse_ident_list(ctx)?;
let name_list = parse_identifier_list(ctx)?
.into_iter()
.map(WithRef::new)
.collect_vec();
let semi_token = recover::expect_semicolon_or_last(ctx);
Ok(LibraryClause {
span: TokenSpan::new(library_token, semi_token),
@ -64,6 +68,7 @@ pub fn parse_context(ctx: &mut ParsingContext<'_>) -> ParseResult<DeclarationOrR
if ctx.stream.skip_if_kind(Is) {
let mut items = Vec::with_capacity(16);
let end_ident;
let end_token;
loop {
let token = ctx.stream.peek_expect()?;
try_init_token_kind!(
@ -72,6 +77,7 @@ pub fn parse_context(ctx: &mut ParsingContext<'_>) -> ParseResult<DeclarationOrR
Use => items.push(ContextItem::Use(parse_use_clause(ctx)?.item)),
Context => items.push(ContextItem::Context(parse_context_reference(ctx)?)),
End => {
end_token = ctx.stream.get_current_token_id();
ctx.stream.skip();
ctx.stream.pop_if_kind(Context);
end_ident = ctx.stream.pop_optional_ident();
@ -82,26 +88,24 @@ pub fn parse_context(ctx: &mut ParsingContext<'_>) -> ParseResult<DeclarationOrR
}
let ident = WithDecl::new(to_simple_name(ctx, name)?);
let end_token = ctx.stream.get_last_token_id();
let semicolon = ctx.stream.get_last_token_id();
Ok(DeclarationOrReference::Declaration(ContextDeclaration {
span: TokenSpan::new(context_token, end_token),
span: TokenSpan::new(context_token, semicolon),
end_ident_pos: check_end_identifier_mismatch(ctx, &ident.tree, end_ident),
ident,
end_token,
items,
}))
} else {
// Context reference
let mut items = vec![name];
let mut tokens = Vec::new();
while let Some(comma) = ctx.stream.pop_if_kind(Comma) {
while ctx.stream.pop_if_kind(Comma).is_some() {
items.push(parse_name(ctx)?);
tokens.push(comma);
}
let name_list = SeparatedList { items, tokens };
let semi_token = recover::expect_semicolon_or_last(ctx);
Ok(DeclarationOrReference::Reference(ContextReference {
span: TokenSpan::new(context_token, semi_token),
name_list,
name_list: items,
}))
}
}
@ -122,7 +126,7 @@ mod tests {
code.with_stream_no_diagnostics(parse_library_clause),
LibraryClause {
span: code.token_span(),
name_list: code.s1("foo").ident_list(),
name_list: vec![code.s1("foo").ident().into_ref()],
}
)
}
@ -134,7 +138,10 @@ mod tests {
code.with_stream_no_diagnostics(parse_library_clause),
LibraryClause {
span: code.token_span(),
name_list: code.s1("foo, bar").ident_list(),
name_list: vec![
code.s1("foo").ident().into_ref(),
code.s1("bar").ident().into_ref()
],
},
)
}
@ -147,7 +154,7 @@ mod tests {
WithTokenSpan::new(
UseClause {
span: code.token_span(),
name_list: code.s1("lib.foo").name_list(),
name_list: vec![code.s1("lib.foo").name()],
},
code.token_span()
),
@ -162,7 +169,7 @@ mod tests {
WithTokenSpan::new(
UseClause {
span: code.token_span(),
name_list: code.s1("foo.'a', lib.bar.all").name_list(),
name_list: vec![code.s1("foo.'a'").name(), code.s1("lib.bar.all").name()],
},
code.token_span()
),
@ -176,8 +183,8 @@ mod tests {
code.with_stream_no_diagnostics(parse_context),
DeclarationOrReference::Reference(ContextReference {
span: code.token_span(),
name_list: code.s1("lib.foo").name_list(),
},)
name_list: vec![code.s1("lib.foo").name()],
})
)
}
@ -210,6 +217,7 @@ end context ident;
span: code.token_span(),
ident: code.s1("ident").decl_ident(),
items: vec![],
end_token: code.s1("end").token(),
end_ident_pos: if has_end_ident {
Some(code.s("ident", 2).token())
} else {
@ -242,6 +250,7 @@ end context ident2;
span: code.token_span(),
ident: code.s1("ident").decl_ident(),
items: vec![],
end_token: code.s1("end").token(),
end_ident_pos: None,
})
);
@ -266,17 +275,18 @@ end context;
items: vec![
ContextItem::Library(LibraryClause {
span: TokenSpan::new(code.s("library", 1).token(), code.s(";", 1).token()),
name_list: code.s1("foo").ident_list(),
name_list: vec![code.s1("foo").ident().into_ref()],
}),
ContextItem::Use(UseClause {
span: code.s1("use foo.bar;").token_span(),
name_list: code.s1("foo.bar").name_list(),
name_list: vec![code.s1("foo.bar").name()],
}),
ContextItem::Context(ContextReference {
span: TokenSpan::new(code.s("context", 2).token(), code.s(";", 3).token()),
name_list: code.s1("foo.ctx").name_list(),
name_list: vec![code.s1("foo.ctx").name()],
}),
],
end_token: code.s1("end").token(),
end_ident_pos: None,
})
)

View file

@ -120,12 +120,8 @@ pub fn parse_declarative_part(
File | Shared | Constant | Signal | Variable | Attribute => {
let decls: ParseResult<Vec<WithTokenSpan<Declaration>>> = match token.kind {
File => parse_file_declaration(ctx).map(|decls| {
decls
.into_iter()
.map(|decl| decl.map_into(Declaration::File))
.collect()
}),
File => parse_file_declaration(ctx)
.map(|decl| vec![decl.map_into(Declaration::File)]),
Shared | Constant | Signal | Variable => parse_object_declaration(ctx)
.map(|decl| vec![decl.map_into(Declaration::Object)]),
Attribute => parse_attribute(ctx).map(|decls| {
@ -263,6 +259,7 @@ constant x: natural := 5;
Declaration::Object(ObjectDeclaration {
class: ObjectClass::Constant,
idents: vec![code.s1("x").decl_ident()],
colon_token: code.s(":", 2).token(),
subtype_indication: code.s1("natural").subtype_indication(),
expression: Some(code.s1("5").expr())
}),

View file

@ -34,23 +34,26 @@ pub fn parse_entity_declaration(ctx: &mut ParsingContext<'_>) -> ParseResult<Ent
let decl = parse_declarative_part(ctx)?;
let statements = if ctx.stream.skip_if_kind(Begin) {
let begin_token = ctx.stream.pop_if_kind(Begin);
let statements = if begin_token.is_some() {
parse_labeled_concurrent_statements(ctx)?
} else {
Vec::new()
};
ctx.stream.pop_if_kind(End);
let end_token = ctx.stream.expect_kind(End)?;
ctx.stream.pop_if_kind(Entity);
let end_ident = ctx.stream.pop_optional_ident();
let end_token = expect_semicolon_or_last(ctx);
let semicolon_token = expect_semicolon_or_last(ctx);
Ok(EntityDeclaration {
span: TokenSpan::new(start_token, end_token),
span: TokenSpan::new(start_token, semicolon_token),
context_clause: ContextClause::default(),
end_ident_pos: check_end_identifier_mismatch(ctx, &ident.tree, end_ident),
ident,
generic_clause,
port_clause,
begin_token,
decl,
end_token,
statements,
})
}
@ -67,20 +70,21 @@ pub fn parse_architecture_body(ctx: &mut ParsingContext<'_>) -> ParseResult<Arch
let begin_token = ctx.stream.expect_kind(Begin)?;
let statements = parse_labeled_concurrent_statements(ctx)?;
ctx.stream.expect_kind(End)?;
let end_token = ctx.stream.expect_kind(End)?;
ctx.stream.pop_if_kind(Architecture);
let end_ident = ctx.stream.pop_optional_ident();
let end_token = expect_semicolon_or_last(ctx);
let semicolon_token = expect_semicolon_or_last(ctx);
Ok(ArchitectureBody {
span: TokenSpan::new(start_token, end_token),
span: TokenSpan::new(start_token, semicolon_token),
context_clause: ContextClause::default(),
end_ident_pos: check_end_identifier_mismatch(ctx, &ident.tree, end_ident),
begin_token,
ident,
entity_name: entity_name.into_ref(),
decl,
end_token,
statements,
})
}
@ -93,15 +97,16 @@ pub fn parse_package_declaration(ctx: &mut ParsingContext<'_>) -> ParseResult<Pa
ctx.stream.expect_kind(Is)?;
let generic_clause = parse_optional_generic_list(ctx)?;
let decl = parse_declarative_part(ctx)?;
ctx.stream.expect_kind(End)?;
let end_token = ctx.stream.expect_kind(End)?;
ctx.stream.pop_if_kind(Package);
let end_ident = ctx.stream.pop_optional_ident();
let end_token = expect_semicolon_or_last(ctx);
let semicolon = expect_semicolon_or_last(ctx);
Ok(PackageDeclaration {
span: TokenSpan::new(start_token, end_token),
span: TokenSpan::new(start_token, semicolon),
context_clause: ContextClause::default(),
end_ident_pos: check_end_identifier_mismatch(ctx, &ident.tree, end_ident),
ident,
end_token,
generic_clause,
decl,
})
@ -115,18 +120,19 @@ pub fn parse_package_body(ctx: &mut ParsingContext<'_>) -> ParseResult<PackageBo
ctx.stream.expect_kind(Is)?;
let decl = parse_declarative_part(ctx)?;
ctx.stream.expect_kind(End)?;
let end_token = ctx.stream.expect_kind(End)?;
if ctx.stream.skip_if_kind(Package) {
ctx.stream.expect_kind(Body)?;
}
let end_ident = ctx.stream.pop_optional_ident();
let end_token = expect_semicolon_or_last(ctx);
let semicolon_token = expect_semicolon_or_last(ctx);
Ok(PackageBody {
span: TokenSpan::new(start_token, end_token),
span: TokenSpan::new(start_token, semicolon_token),
context_clause: ContextClause::default(),
decl,
end_ident_pos: check_end_identifier_mismatch(ctx, &ident, end_ident),
end_token,
ident: ident.into(),
})
}
@ -301,6 +307,7 @@ mod tests {
ident: Ident,
span: TokenSpan,
end_ident_pos: Option<TokenId>,
end_token: TokenId,
) -> AnyDesignUnit {
AnyDesignUnit::Primary(AnyPrimaryUnit::Entity(EntityDeclaration {
span,
@ -310,6 +317,8 @@ mod tests {
port_clause: None,
decl: vec![],
statements: vec![],
begin_token: None,
end_token,
end_ident_pos,
}))
}
@ -326,7 +335,12 @@ end entity;
design_file.design_units,
[(
code.tokenize(),
simple_entity(code.s1("myent").ident(), code.token_span(), None)
simple_entity(
code.s1("myent").ident(),
code.token_span(),
None,
code.s1("end").token()
)
)]
);
@ -344,6 +358,7 @@ end entity myent;
code.s1("myent").ident(),
code.token_span(),
Some(code.s("myent", 2).token()),
code.s1("end").token()
)
)]
);
@ -380,6 +395,8 @@ end entity;
decl: vec![],
statements: vec![],
end_ident_pos: None,
begin_token: None,
end_token: code.s1("end").token()
}
);
}
@ -410,6 +427,8 @@ end entity;
decl: vec![],
statements: vec![],
end_ident_pos: None,
begin_token: None,
end_token: code.s1("end").token()
}
);
}
@ -445,6 +464,8 @@ end entity;
decl: vec![],
statements: vec![],
end_ident_pos: None,
begin_token: None,
end_token: code.s1("end").token()
}
);
}
@ -469,6 +490,8 @@ end entity;
decl: vec![],
statements: vec![],
end_ident_pos: None,
begin_token: Some(code.s1("begin").token()),
end_token: code.s1("end").token()
}
);
}
@ -493,6 +516,8 @@ end entity;
decl: code.s1("constant foo : natural := 0;").declarative_part(),
statements: vec![],
end_ident_pos: None,
begin_token: None,
end_token: code.s1("end").token()
}
);
}
@ -515,9 +540,11 @@ end entity;
ident: code.s1("myent").decl_ident(),
generic_clause: None,
port_clause: None,
begin_token: Some(code.s1("begin").token()),
decl: vec![],
statements: vec![code.s1("check(clk, valid);").concurrent_statement()],
end_ident_pos: None,
end_token: code.s1("end").token()
}
);
}
@ -573,6 +600,7 @@ end;
code_myent.s1("myent").ident(),
code_myent.token_span(),
None,
code_myent.s1("end").token()
)
)
);
@ -584,6 +612,7 @@ end;
code_myent2.s1("myent2").ident(),
code_myent2.token_span(),
Some(code_myent2.s("myent2", 2).token()),
code_myent2.s1("end").token()
)
)
);
@ -595,6 +624,7 @@ end;
code_myent3.s1("myent3").ident(),
code_myent3.token_span(),
Some(code_myent3.s("myent3", 2).token()),
code_myent3.s1("end").token()
)
),
);
@ -605,7 +635,8 @@ end;
simple_entity(
code_myent4.s1("myent4").ident(),
code_myent4.token_span(),
None
None,
code_myent4.s1("end").token()
)
)
);
@ -619,6 +650,7 @@ end;
span: TokenSpan,
begin_token: TokenId,
end_ident_pos: Option<TokenId>,
end_token: TokenId,
) -> AnyDesignUnit {
AnyDesignUnit::Secondary(AnySecondaryUnit::Architecture(ArchitectureBody {
span,
@ -628,6 +660,7 @@ end;
entity_name: entity_name.into_ref(),
decl: Vec::new(),
statements: vec![],
end_token,
end_ident_pos,
}))
}
@ -651,6 +684,7 @@ end architecture;
code.token_span(),
code.s1("begin").token(),
None,
code.s1("end").token(),
)
)]
);
@ -675,6 +709,7 @@ end architecture arch_name;
code.token_span(),
code.s1("begin").token(),
Some(code.s("arch_name", 2).token()),
code.s1("end").token(),
)
)]
);
@ -699,6 +734,7 @@ end;
code.token_span(),
code.s1("begin").token(),
None,
code.s1("end").token(),
)
)]
);
@ -720,6 +756,7 @@ end package;
ident: code.s1("pkg_name").decl_ident(),
generic_clause: None,
decl: vec![],
end_token: code.s1("end").token(),
end_ident_pos: None,
}
);
@ -748,6 +785,7 @@ end package;
constant bar : natural := 0;
")
.declarative_part(),
end_token: code.s1("end").token(),
end_ident_pos: None,
}
);
@ -777,6 +815,7 @@ end package;
span: code.between("generic (", ");").token_span()
}),
decl: vec![],
end_token: code.s1("end").token(),
end_ident_pos: None,
}
);
@ -802,14 +841,16 @@ end entity;
span: code.s1_to_end("entity").token_span(),
context_clause: vec![
ContextItem::Library(code.s1("library lib;").library_clause()),
ContextItem::Use(code.s1("use lib.foo;").use_clause()),
ContextItem::Use(code.s1("use lib.foo;").use_clause().item),
],
ident: code.s1("myent").decl_ident(),
generic_clause: None,
port_clause: None,
decl: vec![],
begin_token: None,
statements: vec![],
end_ident_pos: None,
end_token: code.s1("end").token()
}))
)]
}
@ -927,14 +968,14 @@ end entity y;
let (tokens, unit) = &file.design_units[0];
let ent = unit.expect_entity();
let lib = ent.context_clause[0].expect_library_clause();
let tok = tokens.get_token(lib.get_start_token());
let tok = tokens.index(lib.get_start_token());
assert_eq!(tok.kind, Library);
assert_eq!(tok.pos, code.s1("library").pos());
let (tokens, unit) = &file.design_units[2];
let ent = unit.expect_entity();
let ctx_ref = ent.context_clause[0].expect_context_reference();
let tok = tokens.get_token(ctx_ref.get_start_token());
let tok = tokens.index(ctx_ref.get_start_token());
assert_eq!(tok.kind, Context);
assert_eq!(tok.pos, code.s1("context").pos());
}

View file

@ -12,7 +12,7 @@ use crate::ast::token_range::{WithToken, WithTokenSpan};
use crate::ast::{Literal, *};
use crate::data::Diagnostic;
use crate::syntax::TokenAccess;
use crate::{ast, TokenSpan};
use crate::{ast, HasTokenSpan, TokenId, TokenSpan};
use vhdl_lang::syntax::parser::ParsingContext;
impl WithTokenSpan<Name> {
@ -145,8 +145,9 @@ fn kind_to_binary_op(kind: Kind) -> Option<(Operator, usize)> {
pub fn parse_aggregate_initial_choices(
ctx: &mut ParsingContext<'_>,
start_token: TokenId,
choices: Vec<WithTokenSpan<Choice>>,
) -> ParseResult<WithTokenSpan<Vec<ElementAssociation>>> {
) -> ParseResult<WithTokenSpan<Vec<WithTokenSpan<ElementAssociation>>>> {
let mut choices = choices;
let mut result = Vec::new();
loop {
@ -157,8 +158,13 @@ pub fn parse_aggregate_initial_choices(
RightPar => {
if choices.len() == 1 {
if let Some(WithTokenSpan{item: Choice::Expression(expr), span}) = choices.pop() {
result.push(ElementAssociation::Positional(WithTokenSpan::new(expr, span)));
return Ok(WithTokenSpan::from(result, token_id))
result.push(
WithTokenSpan::new(
ElementAssociation::Positional(WithTokenSpan::new(expr, span)),
span,
)
);
return Ok(WithTokenSpan::new(result, TokenSpan::new(start_token, token_id)))
}
}
@ -167,7 +173,12 @@ pub fn parse_aggregate_initial_choices(
Comma => {
if choices.len() == 1 {
if let Some(WithTokenSpan{item: Choice::Expression(expr), span}) = choices.pop() {
result.push(ElementAssociation::Positional(WithTokenSpan::new(expr, span)));
result.push(
WithTokenSpan::new(
ElementAssociation::Positional(WithTokenSpan::new(expr, span)),
span
)
);
choices = parse_choices(ctx)?;
continue;
}
@ -177,14 +188,20 @@ pub fn parse_aggregate_initial_choices(
},
RightArrow => {
let rhs = parse_expression(ctx)?;
result.push(ElementAssociation::Named(choices, rhs));
let span = TokenSpan::new(choices[0].get_start_token(), rhs.get_end_token());
result.push(
WithTokenSpan::new(
ElementAssociation::Named(choices, rhs),
span,
)
);
expect_token!(
ctx.stream,
token,
token_id,
RightPar => {
return Ok(WithTokenSpan::from(result, token_id))
return Ok(WithTokenSpan::new(result, TokenSpan::new(start_token, token_id)))
},
Comma => {
choices = parse_choices(ctx)?;
@ -197,7 +214,7 @@ pub fn parse_aggregate_initial_choices(
pub fn parse_aggregate(
ctx: &mut ParsingContext<'_>,
) -> ParseResult<WithTokenSpan<Vec<ElementAssociation>>> {
) -> ParseResult<WithTokenSpan<Vec<WithTokenSpan<ElementAssociation>>>> {
let start_tok = ctx.stream.expect_kind(LeftPar)?;
if let Some(token) = ctx.stream.pop_if_kind(RightPar) {
return Ok(WithTokenSpan::from(
@ -206,7 +223,7 @@ pub fn parse_aggregate(
));
};
let choices = parse_choices(ctx)?;
parse_aggregate_initial_choices(ctx, choices)
parse_aggregate_initial_choices(ctx, start_tok, choices)
}
fn parse_half_range(
@ -280,7 +297,7 @@ fn parse_allocator(ctx: &mut ParsingContext<'_>) -> ParseResult<WithTokenSpan<Al
};
let subtype = SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark,
constraint,
};
@ -331,6 +348,7 @@ fn name_to_selected_name(name: Name) -> Option<Name> {
fn parse_expression_or_aggregate(
ctx: &mut ParsingContext<'_>,
start_token: TokenId,
) -> ParseResult<WithTokenSpan<Expression>> {
let mut choices = parse_choices(ctx)?;
@ -358,6 +376,7 @@ fn parse_expression_or_aggregate(
Comma | RightArrow => {
Ok(parse_aggregate_initial_choices(
ctx,
start_token,
vec![WithTokenSpan::new(Choice::Expression(expr), span)],
)?.map_into(Expression::Aggregate))
},
@ -365,17 +384,14 @@ fn parse_expression_or_aggregate(
// Was expression with parenthesis
RightPar => {
ctx.stream.skip();
// Lexical position between parenthesis
let expr = WithTokenSpan::from(
expr,
token_id
);
let expr = WithTokenSpan::new(Expression::Parenthesized(Box::new(WithTokenSpan::new(expr, span))), TokenSpan::new(start_token, token_id));
Ok(expr)
}
)
} else {
// Must be aggregate
Ok(parse_aggregate_initial_choices(ctx, choices)?.map_into(Expression::Aggregate))
Ok(parse_aggregate_initial_choices(ctx, start_token, choices)?
.map_into(Expression::Aggregate))
}
}
@ -391,7 +407,7 @@ fn parse_primary(ctx: &mut ParsingContext<'_>) -> ParseResult<WithTokenSpan<Expr
let name = parse_name(ctx)?;
if ctx.stream.skip_if_kind(Tick) {
let lpar = ctx.stream.expect_kind(LeftPar)?;
let expr = parse_expression_or_aggregate(ctx)?.start_with(lpar);
let expr = parse_expression_or_aggregate(ctx, lpar)?;
let span = name.span.combine(expr.span);
Ok(WithTokenSpan::new(
Expression::Qualified(Box::new(QualifiedExpression {
@ -450,7 +466,7 @@ fn parse_primary(ctx: &mut ParsingContext<'_>) -> ParseResult<WithTokenSpan<Expr
if let Some(unit_token) = ctx.stream.pop_if_kind(Identifier) {
let unit = ctx
.stream
.get_token(unit_token)
.index(unit_token)
.to_identifier_value(unit_token)?;
let span = TokenSpan::new(value.token, unit.token);
let physical = PhysicalLiteral {
@ -468,8 +484,9 @@ fn parse_primary(ctx: &mut ParsingContext<'_>) -> ParseResult<WithTokenSpan<Expr
}
LeftPar => {
let start_token = ctx.stream.get_current_token_id();
ctx.stream.skip();
parse_expression_or_aggregate(ctx).map(|expr| expr.start_with(token_id))
parse_expression_or_aggregate(ctx, start_token)
}
kind => {
@ -959,8 +976,14 @@ mod tests {
};
let assoc_list = vec![
ElementAssociation::Positional(one_expr),
ElementAssociation::Positional(two_expr),
WithTokenSpan::new(
ElementAssociation::Positional(one_expr),
code.s1("1").token_span(),
),
WithTokenSpan::new(
ElementAssociation::Positional(two_expr),
code.s1("2").token_span(),
),
];
let expr = WithTokenSpan {
item: Expression::Aggregate(assoc_list),
@ -982,9 +1005,9 @@ mod tests {
span: code.s1("2").token_span(),
};
let assoc_list = vec![ElementAssociation::Named(
vec![one_expr.map_into(Choice::Expression)],
two_expr,
let assoc_list = vec![WithTokenSpan::new(
ElementAssociation::Named(vec![one_expr.map_into(Choice::Expression)], two_expr),
code.s1("1 => 2").token_span(),
)];
let expr = WithTokenSpan {
item: Expression::Aggregate(assoc_list),
@ -1012,12 +1035,15 @@ mod tests {
span: code.s1("3").token_span(),
};
let assoc_list = vec![ElementAssociation::Named(
vec![
one_expr.map_into(Choice::Expression),
two_expr.map_into(Choice::Expression),
],
three_expr,
let assoc_list = vec![WithTokenSpan::new(
ElementAssociation::Named(
vec![
one_expr.map_into(Choice::Expression),
two_expr.map_into(Choice::Expression),
],
three_expr,
),
code.s1("1 | 2 => 3").token_span(),
)];
let expr = WithTokenSpan {
item: Expression::Aggregate(assoc_list),
@ -1035,12 +1061,15 @@ mod tests {
span: code.s1("1").token_span(),
};
let assoc_list = vec![ElementAssociation::Named(
vec![WithTokenSpan::new(
Choice::Others,
code.s1("others").token_span(),
)],
one_expr,
let assoc_list = vec![WithTokenSpan::new(
ElementAssociation::Named(
vec![WithTokenSpan::new(
Choice::Others,
code.s1("others").token_span(),
)],
one_expr,
),
code.s1("others => 1").token_span(),
)];
let expr = WithTokenSpan {
item: Expression::Aggregate(assoc_list),
@ -1084,9 +1113,16 @@ mod tests {
right_expr: Box::new(zero_expr),
}));
let assoc_list = vec![ElementAssociation::Named(
vec![WithTokenSpan::new(Choice::DiscreteRange(range), pos)],
two_expr,
let assoc_list = vec![WithTokenSpan::new(
ElementAssociation::Named(
vec![WithTokenSpan::new(Choice::DiscreteRange(range), pos)],
two_expr,
),
if *direction == Direction::Descending {
code.s1("1 downto 0 => 2").token_span()
} else {
code.s1("1 to 0 => 2").token_span()
},
)];
let expr = WithTokenSpan {
item: Expression::Aggregate(assoc_list),
@ -1111,19 +1147,25 @@ mod tests {
};
let assoc_list = vec![
ElementAssociation::Named(
vec![WithTokenSpan::new(
Choice::Others,
code.s("others", 1).token_span(),
)],
one_expr,
WithTokenSpan::new(
ElementAssociation::Named(
vec![WithTokenSpan::new(
Choice::Others,
code.s("others", 1).token_span(),
)],
one_expr,
),
code.s1("others => 1").token_span(),
),
ElementAssociation::Named(
vec![WithTokenSpan::new(
Choice::Others,
code.s("others", 2).token_span(),
)],
two_expr,
WithTokenSpan::new(
ElementAssociation::Named(
vec![WithTokenSpan::new(
Choice::Others,
code.s("others", 2).token_span(),
)],
two_expr,
),
code.s1("others => 2").token_span(),
),
];
let expr = WithTokenSpan {
@ -1151,8 +1193,14 @@ mod tests {
};
let assoc_list = vec![
ElementAssociation::Named(vec![one_expr.map_into(Choice::Expression)], two_expr),
ElementAssociation::Positional(three_expr),
WithTokenSpan::new(
ElementAssociation::Named(vec![one_expr.map_into(Choice::Expression)], two_expr),
code.s1("1 => 2").token_span(),
),
WithTokenSpan::new(
ElementAssociation::Positional(three_expr),
code.s1("3").token_span(),
),
];
let expr = WithTokenSpan {
item: Expression::Aggregate(assoc_list),
@ -1199,14 +1247,19 @@ mod tests {
Box::new(two),
Box::new(three),
),
span: code.s1("(2 + 3)").token_span(),
span: code.s1("2 + 3").token_span(),
};
let expr_add0_paren = WithTokenSpan::new(
Expression::Parenthesized(Box::new(expr_add0)),
code.s1("(2 + 3)").token_span(),
);
let expr_add1 = WithTokenSpan {
item: Expression::Binary(
WithToken::new(WithRef::new(Operator::Plus), code.s("+", 1).token()),
Box::new(one),
Box::new(expr_add0),
Box::new(expr_add0_paren),
),
span: code.token_span(),
};
@ -1239,13 +1292,18 @@ mod tests {
Box::new(one),
Box::new(two),
),
span: code.s1("(1 + 2)").token_span(),
span: code.s1("1 + 2").token_span(),
};
let expr_add0_paren = WithTokenSpan::new(
Expression::Parenthesized(Box::new(expr_add0)),
code.s1("(1 + 2)").token_span(),
);
let expr_add1 = WithTokenSpan {
item: Expression::Binary(
WithToken::new(WithRef::new(Operator::Plus), code.s("+", 2).token()),
Box::new(expr_add0),
Box::new(expr_add0_paren),
Box::new(three),
),
span: code.token_span(),
@ -1283,6 +1341,9 @@ mod tests {
panic!("Cannot format {lit:?}");
}
},
Expression::Parenthesized(ref expr) => {
format!("({})", fmt(ctx, expr))
}
_ => {
println!("{}", expr.pos(ctx).code_context());
panic!("Cannot format {expr:?}");
@ -1302,7 +1363,7 @@ mod tests {
assert_eq!(
code.with_partial_stream(parse_expression),
Err(Diagnostic::syntax_error(
&code.s1(",").pos(),
code.s1(",").pos(),
"Expected {expression}"
))
);
@ -1311,7 +1372,7 @@ mod tests {
assert_eq!(
code.with_partial_stream(parse_expression),
Err(Diagnostic::syntax_error(
&code.s1(")").pos(),
code.s1(")").pos(),
"Expected {expression}"
))
);
@ -1319,7 +1380,7 @@ mod tests {
assert_eq!(
code.with_partial_stream(parse_expression),
Err(Diagnostic::syntax_error(
&code.s(",", 2).pos(),
code.s(",", 2).pos(),
"Expected {expression}"
))
);
@ -1340,7 +1401,11 @@ mod tests {
assert_expression_is("1+2*3", "(Integer(1) Plus (Integer(2) Times Integer(3)))");
assert_expression_is("(1+2)*3", "((Integer(1) Plus Integer(2)) Times Integer(3))");
// The extra parenthesis signify the user-supplied parenthesis
assert_expression_is(
"(1+2)*3",
"(((Integer(1) Plus Integer(2))) Times Integer(3))",
);
// Multiplication has precedence over negation.
assert_expression_is("-1 * 2", "(Minus (Integer(1) Times Integer(2)))");

View file

@ -12,7 +12,7 @@ use super::object_declaration::parse_optional_assignment;
use super::subprogram::parse_subprogram_specification;
use super::subtype_indication::parse_subtype_indication;
use super::tokens::{Kind::*, *};
use crate::ast::token_range::WithToken;
use crate::ast::token_range::{WithToken, WithTokenSpan};
/// LRM 6.5 Interface declarations
use crate::ast::*;
use crate::data::*;
@ -71,7 +71,7 @@ fn parse_interface_file_declaration(
.into_iter()
.map(WithDecl::new)
.collect_vec();
ctx.stream.expect_kind(Colon)?;
let colon_token = ctx.stream.expect_kind(Colon)?;
let subtype = parse_subtype_indication(ctx)?;
if ctx.stream.next_kind_is(Open) {
@ -96,6 +96,7 @@ fn parse_interface_file_declaration(
Ok(InterfaceDeclaration::File(InterfaceFileDeclaration {
idents,
subtype_indication: subtype.clone(),
colon_token,
span: TokenSpan::new(start_token, end_token),
}))
}
@ -112,7 +113,7 @@ fn parse_interface_object_declaration(
.map(WithDecl::new)
.collect_vec();
ctx.stream.expect_kind(Colon)?;
let colon_token = ctx.stream.expect_kind(Colon)?;
let mode = if ctx.stream.next_kind_is(View) {
ModeIndication::View(parse_view_mode_indication(ctx)?)
} else {
@ -129,12 +130,13 @@ fn parse_interface_object_declaration(
list_type,
mode: mode.clone(),
idents,
colon_token,
span: TokenSpan::new(start_token, end_token),
}))
}
fn parse_view_mode_indication(ctx: &mut ParsingContext<'_>) -> ParseResult<ModeViewIndication> {
ctx.stream.expect_kind(View)?;
let start_token = ctx.stream.expect_kind(View)?;
let (name, kind) = if ctx.stream.pop_if_kind(LeftPar).is_some() {
let _name = parse_name(ctx)?;
ctx.stream.expect_kind(RightPar)?;
@ -142,15 +144,17 @@ fn parse_view_mode_indication(ctx: &mut ParsingContext<'_>) -> ParseResult<ModeV
} else {
(parse_name(ctx)?, ModeViewIndicationKind::Record)
};
let subtype_indication = if ctx.stream.pop_if_kind(Of).is_some() {
Some(parse_subtype_indication(ctx)?)
let subtype_indication = if let Some(of_token) = ctx.stream.pop_if_kind(Of) {
Some((of_token, parse_subtype_indication(ctx)?))
} else {
None
};
let end_token = ctx.stream.get_last_token_id();
Ok(ModeViewIndication {
subtype_indication,
name,
kind,
span: TokenSpan::new(start_token, end_token),
})
}
@ -163,7 +167,7 @@ fn parse_simple_mode_indication(
let object_class_tok = explicit_object_class.map(|class| class.token);
let mode_with_pos = parse_optional_mode(ctx)?;
let mode = mode_with_pos.as_ref().map(|mode| mode.item);
let mode_tok = mode_with_pos.map(|mode| mode.token);
let mode_tok = mode_with_pos.as_ref().map(|mode| mode.token);
let object_class = match (
list_type,
@ -209,7 +213,7 @@ fn parse_simple_mode_indication(
}
Ok(SimpleModeIndication {
mode,
mode: mode_with_pos,
class: object_class,
subtype_indication: subtype,
expression: expr,
@ -246,7 +250,7 @@ fn parse_interface_package(
ctx.stream.expect_kind(Is)?;
ctx.stream.expect_kind(New)?;
let package_name = parse_selected_name(ctx)?;
ctx.stream.expect_kind(Generic)?;
let generic_token = ctx.stream.expect_kind(Generic)?;
ctx.stream.expect_kind(Map)?;
let generic_map = {
@ -274,7 +278,7 @@ fn parse_interface_package(
Ok(InterfacePackageDeclaration {
ident: ident.into(),
package_name,
generic_map,
generic_map: WithTokenSpan::new(generic_map, TokenSpan::new(generic_token, last_token)),
span: TokenSpan::new(start_token, last_token),
})
}
@ -479,6 +483,7 @@ mod tests {
subtype_indication: code.s1("natural").subtype_indication(),
expression: None
}),
colon_token: code.s1(":").token(),
idents: vec![code.s1("foo").decl_ident(), code.s1("bar").decl_ident()],
span: code.between("constant", "natural").token_span()
})],
@ -502,6 +507,7 @@ mod tests {
subtype_indication: code.s1("std_logic").subtype_indication(),
expression: None
}),
colon_token: code.s1(":").token(),
idents: vec![code.s1("foo").decl_ident()],
span: code.token_span()
})
@ -515,6 +521,7 @@ mod tests {
code.with_stream(parse_parameter),
InterfaceDeclaration::File(InterfaceFileDeclaration {
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
subtype_indication: code.s1("text").subtype_indication(),
span: code.token_span()
})
@ -570,6 +577,7 @@ mod tests {
items: vec![InterfaceDeclaration::File(InterfaceFileDeclaration {
idents: vec![code.s1("valid").decl_ident()],
subtype_indication: code.s("text", 2).subtype_indication(),
colon_token: code.s(":", 2).token(),
span: code.s1("file valid : text").token_span()
})],
span: code.token_span()
@ -604,6 +612,7 @@ mod tests {
expression: None
}),
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
span: code.token_span()
})
);
@ -668,7 +677,7 @@ mod tests {
assert_matches!(
result.mode,
ModeIndication::Simple(SimpleModeIndication {
mode: Some(Mode::In),
mode: Some(WithToken { item: Mode::In, .. }),
class: ObjectClass::Constant,
..
})
@ -679,7 +688,10 @@ mod tests {
assert_matches!(
result.mode,
ModeIndication::Simple(SimpleModeIndication {
mode: Some(Mode::Out),
mode: Some(WithToken {
item: Mode::Out,
..
}),
class: ObjectClass::Variable,
..
})
@ -690,7 +702,10 @@ mod tests {
assert_matches!(
result.mode,
ModeIndication::Simple(SimpleModeIndication {
mode: Some(Mode::InOut),
mode: Some(WithToken {
item: Mode::InOut,
..
}),
class: ObjectClass::Variable,
..
})
@ -712,6 +727,7 @@ mod tests {
expression: None
}),
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
span: code.token_span()
})
);
@ -732,6 +748,7 @@ mod tests {
expression: None
}),
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
span: code.token_span()
})
);
@ -743,7 +760,7 @@ mod tests {
assert_eq!(
code.with_partial_stream(parse_generic),
Err(Diagnostic::syntax_error(
&code.s1("out").pos(),
code.s1("out").pos(),
"Interface constant declaration may only have mode=in"
))
);
@ -1033,8 +1050,11 @@ package foo is new lib.pkg
InterfaceDeclaration::Package(InterfacePackageDeclaration {
ident: code.s1("foo").decl_ident(),
package_name: code.s1("lib.pkg").name(),
generic_map: InterfacePackageGenericMapAspect::Map(
code.s1("(foo => bar)").association_list()
generic_map: WithTokenSpan::new(
InterfacePackageGenericMapAspect::Map(
code.s1("(foo => bar)").association_list()
),
code.between("generic", ")").token_span()
),
span: code.token_span()
})
@ -1053,7 +1073,10 @@ package foo is new lib.pkg
InterfaceDeclaration::Package(InterfacePackageDeclaration {
ident: code.s1("foo").decl_ident(),
package_name: code.s1("lib.pkg").name(),
generic_map: InterfacePackageGenericMapAspect::Box,
generic_map: WithTokenSpan::new(
InterfacePackageGenericMapAspect::Box,
code.between("generic", ")").token_span()
),
span: code.token_span()
})
);
@ -1071,7 +1094,10 @@ package foo is new lib.pkg
InterfaceDeclaration::Package(InterfacePackageDeclaration {
ident: code.s1("foo").decl_ident(),
package_name: code.s1("lib.pkg").name(),
generic_map: InterfacePackageGenericMapAspect::Default,
generic_map: WithTokenSpan::new(
InterfacePackageGenericMapAspect::Default,
code.between("generic", ")").token_span()
),
span: code.token_span()
})
);
@ -1131,6 +1157,7 @@ function foo() return bit;
expression: None
}),
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
span: code.token_span()
})
);
@ -1146,8 +1173,10 @@ function foo() return bit;
mode: ModeIndication::View(ModeViewIndication {
name: code.s1("bar").name(),
subtype_indication: None,
kind: ModeViewIndicationKind::Record
kind: ModeViewIndicationKind::Record,
span: code.s1("view bar").token_span(),
}),
colon_token: code.s1(":").token(),
idents: vec![code.s1("foo").decl_ident()],
span: code.token_span()
})
@ -1161,9 +1190,11 @@ function foo() return bit;
mode: ModeIndication::View(ModeViewIndication {
name: code.s1("bar").name(),
subtype_indication: None,
kind: ModeViewIndicationKind::Array
kind: ModeViewIndicationKind::Array,
span: code.s1("view (bar)").token_span(),
}),
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
span: code.token_span()
})
);
@ -1174,9 +1205,14 @@ function foo() return bit;
list_type: InterfaceType::Port,
mode: ModeIndication::View(ModeViewIndication {
name: code.s1("bar").name(),
subtype_indication: Some(code.s1("baz").subtype_indication()),
kind: ModeViewIndicationKind::Record
subtype_indication: Some((
code.s1("of").token(),
code.s1("baz").subtype_indication()
)),
kind: ModeViewIndicationKind::Record,
span: code.s1("view bar of baz").token_span(),
}),
colon_token: code.s1(":").token(),
idents: vec![code.s1("foo").decl_ident()],
span: code.token_span()
})
@ -1188,10 +1224,15 @@ function foo() return bit;
list_type: InterfaceType::Port,
mode: ModeIndication::View(ModeViewIndication {
name: code.s1("bar").name(),
subtype_indication: Some(code.s1("baz").subtype_indication()),
kind: ModeViewIndicationKind::Array
subtype_indication: Some((
code.s1("of").token(),
code.s1("baz").subtype_indication()
)),
kind: ModeViewIndicationKind::Array,
span: code.s1("view (bar) of baz").token_span(),
}),
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
span: code.token_span()
})
);

View file

@ -249,23 +249,25 @@ fn parse_function_call(
ctx: &mut ParsingContext<'_>,
prefix: WithTokenSpan<Name>,
first: AssociationElement,
first_token: TokenId,
) -> ParseResult<WithTokenSpan<Name>> {
let mut association_elements = Vec::new();
association_elements.push(first);
let mut list = SeparatedList::default();
list.items.push(first);
list.tokens.push(first_token);
loop {
association_elements.push(parse_association_element(ctx)?);
list.items.push(parse_association_element(ctx)?);
expect_token!(
ctx.stream,
token,
token_id,
Comma => {},
Comma => list.tokens.push(token_id),
RightPar => {
let span = TokenSpan::new(prefix.span.start_token, token_id);
return Ok(WithTokenSpan {
item: Name::CallOrIndexed(Box::new(CallOrIndexed {
name: prefix,
parameters: association_elements})),
parameters: list})),
span,
});
}
@ -372,12 +374,13 @@ fn parse_inner_external_name(ctx: &mut ParsingContext<'_>) -> ParseResult<Extern
}
);
ctx.stream.expect_kind(Colon)?;
let colon_token = ctx.stream.expect_kind(Colon)?;
let subtype = parse_subtype_indication(ctx)?;
Ok(ExternalName {
class,
path,
colon_token,
subtype,
})
}
@ -471,7 +474,7 @@ fn _parse_name(ctx: &mut ParsingContext<'_>) -> ParseResult<WithTokenSpan<Name>>
sep_token,
sep_token_id,
Comma => {
name = parse_function_call(ctx, name, assoc)?;
name = parse_function_call(ctx, name, assoc, sep_token_id)?;
},
To | Downto => {
let right_expr = parse_expression(ctx)?;
@ -502,7 +505,7 @@ fn _parse_name(ctx: &mut ParsingContext<'_>) -> ParseResult<WithTokenSpan<Name>>
Ok(range) => Name::Slice(Box::new(name), Box::new(DiscreteRange::Range(range))),
Err(assoc) => Name::CallOrIndexed(Box::new(CallOrIndexed {
name,
parameters: vec![assoc],
parameters: SeparatedList::single(assoc),
})),
};
@ -980,10 +983,10 @@ mod tests {
let foo_0 = WithTokenSpan {
item: Name::CallOrIndexed(Box::new(CallOrIndexed {
name: foo,
parameters: vec![AssociationElement {
parameters: SeparatedList::single(AssociationElement {
formal: None,
actual: code.s1("0").expr().map_into(ActualPart::Expression),
}],
}),
})),
span: code.s1("foo(0)").token_span(),
};
@ -1003,16 +1006,19 @@ mod tests {
let prefix_index = WithTokenSpan {
item: Name::CallOrIndexed(Box::new(CallOrIndexed {
name: prefix,
parameters: vec![
AssociationElement {
formal: None,
actual: code.s1("0").expr().map_into(ActualPart::Expression),
},
AssociationElement {
formal: None,
actual: code.s1("1").expr().map_into(ActualPart::Expression),
},
],
parameters: SeparatedList {
items: vec![
AssociationElement {
formal: None,
actual: code.s1("0").expr().map_into(ActualPart::Expression),
},
AssociationElement {
formal: None,
actual: code.s1("1").expr().map_into(ActualPart::Expression),
},
],
tokens: vec![code.s1(",").token()],
},
})),
span: code.s1("prefix(0, 1)").token_span(),
};
@ -1020,10 +1026,10 @@ mod tests {
let prefix_index_3 = WithTokenSpan {
item: Name::CallOrIndexed(Box::new(CallOrIndexed {
name: prefix_index,
parameters: vec![AssociationElement {
parameters: SeparatedList::single(AssociationElement {
formal: None,
actual: code.s1("3").expr().map_into(ActualPart::Expression),
}],
}),
})),
span: code.s1("prefix(0, 1)(3)").token_span(),
};
@ -1063,7 +1069,7 @@ mod tests {
let foo_call = WithTokenSpan {
item: Name::CallOrIndexed(Box::new(CallOrIndexed {
name: foo,
parameters: vec![assoc_elem],
parameters: SeparatedList::single(assoc_elem),
})),
span: code.s1("foo(arg => 0)").token_span(),
};
@ -1103,6 +1109,7 @@ mod tests {
ExternalPath::Relative(code.s1("dut.foo").name(), 0),
code.s1("dut.foo").token_span(),
),
colon_token: code.s1(":").token(),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(
@ -1120,6 +1127,7 @@ mod tests {
ExternalPath::Relative(code.s1("dut.gen(0)").name(), 1),
code.s1("^.dut.gen(0)").token_span(),
),
colon_token: code.s1(":").token(),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(
@ -1137,6 +1145,7 @@ mod tests {
ExternalPath::Relative(code.s1("dut.gen(0)").name(), 3),
code.s1("^.^.^.dut.gen(0)").token_span(),
),
colon_token: code.s1(":").token(),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(
@ -1154,6 +1163,7 @@ mod tests {
ExternalPath::Absolute(code.s1("dut.gen(0)").name()),
code.s1(".dut.gen(0)").token_span(),
),
colon_token: code.s1(":").token(),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(
@ -1171,6 +1181,7 @@ mod tests {
ExternalPath::Package(code.s1("lib.pkg").name()),
code.s1("@lib.pkg").token_span(),
),
colon_token: code.s1(":").token(),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(
@ -1194,6 +1205,7 @@ mod tests {
ExternalPath::Relative(code.s1("dut.foo").name(), 0),
code.s1("dut.foo").token_span(),
),
colon_token: code.s1(":").token(),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(

View file

@ -52,7 +52,7 @@ fn parse_object_declaration_kind(
.into_iter()
.map(WithDecl::new)
.collect_vec();
ctx.stream.expect_kind(Colon)?;
let colon_token = ctx.stream.expect_kind(Colon)?;
let subtype = parse_subtype_indication(ctx)?;
let opt_expression = parse_optional_assignment(ctx)?;
let end_token = expect_semicolon_or_last(ctx);
@ -60,6 +60,7 @@ fn parse_object_declaration_kind(
ObjectDeclaration {
class,
idents,
colon_token,
subtype_indication: subtype.clone(),
expression: opt_expression.clone(),
},
@ -85,23 +86,26 @@ pub fn parse_object_declaration(
pub fn parse_file_declaration(
ctx: &mut ParsingContext<'_>,
) -> ParseResult<Vec<WithTokenSpan<FileDeclaration>>> {
) -> ParseResult<WithTokenSpan<FileDeclaration>> {
let start_token = ctx.stream.expect_kind(File)?;
let idents = parse_identifier_list(ctx)?;
ctx.stream.expect_kind(Colon)?;
let idents = parse_identifier_list(ctx)?
.into_iter()
.map(WithDecl::new)
.collect_vec();
let colon_token = ctx.stream.expect_kind(Colon)?;
let subtype = parse_subtype_indication(ctx)?;
let open_info = {
if ctx.stream.skip_if_kind(Open) {
Some(parse_expression(ctx)?)
if let Some(token) = ctx.stream.pop_if_kind(Open) {
Some((token, parse_expression(ctx)?))
} else {
None
}
};
let file_name = {
if ctx.stream.skip_if_kind(Is) {
Some(parse_expression(ctx)?)
if let Some(token) = ctx.stream.pop_if_kind(Is) {
Some((token, parse_expression(ctx)?))
} else {
None
}
@ -113,26 +117,22 @@ pub fn parse_file_declaration(
if open_info.is_some() && file_name.is_none() {
if let Some(ident) = idents.first() {
return Err(Diagnostic::syntax_error(
ident.pos(ctx),
ident.tree.pos(ctx),
"file_declaration must have a file name specified if the file open expression is specified as well",
));
}
}
Ok(idents
.into_iter()
.map(|ident| {
WithTokenSpan::new(
FileDeclaration {
ident: ident.into(),
subtype_indication: subtype.clone(),
open_info: open_info.clone(),
file_name: file_name.clone(),
},
TokenSpan::new(start_token, end_token),
)
})
.collect())
Ok(WithTokenSpan::new(
FileDeclaration {
idents,
colon_token,
subtype_indication: subtype.clone(),
open_info: open_info.clone(),
file_name: file_name.clone(),
},
TokenSpan::new(start_token, end_token),
))
}
#[cfg(test)]
@ -152,6 +152,7 @@ mod tests {
ObjectDeclaration {
class: ObjectClass::Constant,
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
subtype_indication: code.s1("natural").subtype_indication(),
expression: None
},
@ -169,6 +170,7 @@ mod tests {
ObjectDeclaration {
class: ObjectClass::Signal,
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
subtype_indication: code.s1("natural").subtype_indication(),
expression: None
},
@ -186,6 +188,7 @@ mod tests {
ObjectDeclaration {
class: ObjectClass::Variable,
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
subtype_indication: code.s1("natural").subtype_indication(),
expression: None
},
@ -203,6 +206,7 @@ mod tests {
ObjectDeclaration {
class: ObjectClass::SharedVariable,
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
subtype_indication: code.s1("natural").subtype_indication(),
expression: None
},
@ -216,15 +220,16 @@ mod tests {
let code = Code::new("file foo : text;");
assert_eq!(
code.with_stream(parse_file_declaration),
vec![WithTokenSpan::new(
WithTokenSpan::new(
FileDeclaration {
ident: code.s1("foo").decl_ident(),
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
subtype_indication: code.s1("text").subtype_indication(),
open_info: None,
file_name: None
},
code.token_span()
)]
)
);
}
@ -233,15 +238,16 @@ mod tests {
let code = Code::new("file foo : text is \"file_name\";");
assert_eq!(
code.with_stream(parse_file_declaration),
vec![WithTokenSpan::new(
WithTokenSpan::new(
FileDeclaration {
ident: code.s1("foo").decl_ident(),
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
subtype_indication: code.s1("text").subtype_indication(),
open_info: None,
file_name: Some(code.s1("\"file_name\"").expr())
file_name: Some((code.s1("is").token(), code.s1("\"file_name\"").expr()))
},
code.token_span()
)]
)
);
}
@ -250,15 +256,16 @@ mod tests {
let code = Code::new("file foo : text open write_mode is \"file_name\";");
assert_eq!(
code.with_stream(parse_file_declaration),
vec![WithTokenSpan::new(
WithTokenSpan::new(
FileDeclaration {
ident: code.s1("foo").decl_ident(),
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
subtype_indication: code.s1("text").subtype_indication(),
open_info: Some(code.s1("write_mode").expr()),
file_name: Some(code.s1("\"file_name\"").expr())
open_info: Some((code.s1("open").token(), code.s1("write_mode").expr())),
file_name: Some((code.s1("is").token(), code.s1("\"file_name\"").expr()))
},
code.token_span()
)]
)
);
}
@ -283,6 +290,7 @@ mod tests {
ObjectDeclaration {
class: ObjectClass::Constant,
idents: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
subtype_indication: code.s1("natural").subtype_indication(),
expression: Some(code.s1("0").expr())
},
@ -301,6 +309,7 @@ mod tests {
ObjectDeclaration {
class: ObjectClass::Constant,
idents: vec![code.s1("foo").decl_ident(), code.s1("bar").decl_ident()],
colon_token: code.s1(":").token(),
subtype_indication: code.s1("natural").subtype_indication(),
expression: Some(code.s1("0").expr()),
},

View file

@ -26,10 +26,14 @@ pub(crate) struct ParsingContext<'a> {
}
impl TokenAccess for ParsingContext<'_> {
fn get_token(&self, id: TokenId) -> &Token {
fn get_token(&self, id: TokenId) -> Option<&Token> {
self.stream.get_token(id)
}
fn index(&self, id: TokenId) -> &Token {
self.stream.index(id)
}
fn get_token_slice(&self, start_id: TokenId, end_id: TokenId) -> &[Token] {
self.stream.get_token_slice(start_id, end_id)
}

View file

@ -98,7 +98,10 @@ pub fn parse_discrete_range(ctx: &mut ParsingContext<'_>) -> ParseResult<Discret
pub fn parse_array_index_constraint(ctx: &mut ParsingContext<'_>) -> ParseResult<ArrayIndex> {
match parse_name_or_range(ctx) {
Ok(NameOrRange::Range(range)) => Ok(ArrayIndex::Discrete(DiscreteRange::Range(range.item))),
Ok(NameOrRange::Range(range)) => Ok(ArrayIndex::Discrete(WithTokenSpan::new(
DiscreteRange::Range(range.item),
range.span,
))),
Ok(NameOrRange::Name(name)) => {
let type_mark = name_to_type_mark(ctx, name)?;
@ -106,14 +109,18 @@ pub fn parse_array_index_constraint(ctx: &mut ParsingContext<'_>) -> ParseResult
if ctx.stream.skip_if_kind(BOX) {
Ok(ArrayIndex::IndexSubtypeDefintion(type_mark))
} else {
Ok(ArrayIndex::Discrete(DiscreteRange::Discrete(
type_mark,
Some(parse_range(ctx)?.item),
let range = parse_range(ctx)?;
let span = type_mark.span.combine(range.span);
Ok(ArrayIndex::Discrete(WithTokenSpan::new(
DiscreteRange::Discrete(type_mark, Some(range.item)),
span,
)))
}
} else {
Ok(ArrayIndex::Discrete(DiscreteRange::Discrete(
type_mark, None,
let type_mark_span = type_mark.span;
Ok(ArrayIndex::Discrete(WithTokenSpan::new(
DiscreteRange::Discrete(type_mark, None),
type_mark_span,
)))
}
}
@ -241,7 +248,10 @@ mod tests {
let code = Code::new("0 to 1");
assert_eq!(
code.with_stream(parse_array_index_constraint),
ArrayIndex::Discrete(code.s1("0 to 1").discrete_range())
ArrayIndex::Discrete(WithTokenSpan::new(
code.s1("0 to 1").discrete_range(),
code.token_span()
))
);
}
@ -250,7 +260,10 @@ mod tests {
let code = Code::new("foo.bar range 0 to 1");
assert_eq!(
code.with_stream(parse_array_index_constraint),
ArrayIndex::Discrete(code.s1("foo.bar range 0 to 1").discrete_range())
ArrayIndex::Discrete(WithTokenSpan::new(
code.s1("foo.bar range 0 to 1").discrete_range(),
code.token_span()
))
);
}
@ -259,7 +272,10 @@ mod tests {
let code = Code::new("foo.bar");
assert_eq!(
code.with_stream(parse_array_index_constraint),
ArrayIndex::Discrete(code.s1("foo.bar").discrete_range())
ArrayIndex::Discrete(WithTokenSpan::new(
code.s1("foo.bar").discrete_range(),
code.token_span()
))
);
}
}

View file

@ -76,6 +76,7 @@ signal y: bit;
Declaration::Object(ObjectDeclaration {
class: ObjectClass::Signal,
idents: vec![code.s1("x").decl_ident()],
colon_token: code.s1(":").token(),
subtype_indication: code.s1("std_logic").subtype_indication(),
expression: Some(code.s1("a.").s1("a").expr())
}),
@ -85,6 +86,7 @@ signal y: bit;
Declaration::Object(ObjectDeclaration {
class: ObjectClass::Signal,
idents: vec![code.s1("y").decl_ident()],
colon_token: code.s(":", 3).token(),
subtype_indication: code.s1("bit").subtype_indication(),
expression: None
}),

View file

@ -4,7 +4,8 @@
//
// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::{IdentList, NameList, SeparatedList, WithRef};
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{Name, SeparatedList};
use crate::data::DiagnosticResult;
use crate::syntax::common::ParseResult;
use crate::syntax::names::parse_name;
@ -81,23 +82,15 @@ where
Ok(SeparatedList { items, tokens })
}
pub fn parse_name_list(ctx: &mut ParsingContext<'_>) -> DiagnosticResult<NameList> {
parse_list_with_separator(ctx, Comma, parse_name)
}
pub fn parse_ident_list(ctx: &mut ParsingContext<'_>) -> DiagnosticResult<IdentList> {
parse_list_with_separator(ctx, Comma, |ctx| {
ctx.stream.expect_ident().map(WithRef::new)
})
pub fn parse_name_list(ctx: &mut ParsingContext<'_>) -> DiagnosticResult<Vec<WithTokenSpan<Name>>> {
Ok(parse_list_with_separator(ctx, Comma, parse_name)?.items)
}
#[cfg(test)]
mod test {
use crate::ast::{IdentList, NameList, SeparatedList};
use crate::ast::SeparatedList;
use crate::syntax::names::parse_association_element;
use crate::syntax::separated_list::{
parse_ident_list, parse_list_with_separator_or_recover, parse_name_list,
};
use crate::syntax::separated_list::{parse_list_with_separator_or_recover, parse_name_list};
use crate::syntax::test::Code;
use crate::syntax::Kind;
use crate::syntax::Kind::RightPar;
@ -106,7 +99,7 @@ mod test {
#[test]
pub fn test_error_on_empty_list() {
let code = Code::new("");
let (res, diagnostics) = code.with_partial_stream_diagnostics(parse_ident_list);
let (res, diagnostics) = code.with_partial_stream_diagnostics(parse_name_list);
assert_eq!(
res,
Err(Diagnostic::syntax_error(code.eof_pos(), "Unexpected EOF"))
@ -118,24 +111,8 @@ mod test {
pub fn parse_single_element_list() {
let code = Code::new("abc");
assert_eq!(
code.parse_ok_no_diagnostics(parse_ident_list),
IdentList::single(code.s1("abc").ident().into_ref())
)
}
#[test]
pub fn parse_list_with_multiple_elements() {
let code = Code::new("abc, def, ghi");
assert_eq!(
code.parse_ok_no_diagnostics(parse_ident_list),
IdentList {
items: vec![
code.s1("abc").ident().into_ref(),
code.s1("def").ident().into_ref(),
code.s1("ghi").ident().into_ref()
],
tokens: vec![code.s(",", 1).token(), code.s(",", 2).token()]
}
code.parse_ok_no_diagnostics(parse_name_list),
vec![code.s1("abc").name()]
)
}
@ -144,27 +121,21 @@ mod test {
let code = Code::new("work.foo, lib.bar.all");
assert_eq!(
code.parse_ok_no_diagnostics(parse_name_list),
NameList {
items: vec![code.s1("work.foo").name(), code.s1("lib.bar.all").name()],
tokens: vec![code.s1(",").token()],
}
vec![code.s1("work.foo").name(), code.s1("lib.bar.all").name()]
)
}
#[test]
fn parse_extraneous_single_separators() {
let code = Code::new("a,,b,c");
let (res, diag) = code.with_stream_diagnostics(parse_ident_list);
let (res, diag) = code.with_stream_diagnostics(parse_name_list);
assert_eq!(
res,
IdentList {
items: vec![
code.s1("a").ident().into_ref(),
code.s1("b").ident().into_ref(),
code.s1("c").ident().into_ref()
],
tokens: vec![code.s(",", 1).token(), code.s(",", 3).token()]
}
vec![
code.s1("a").name(),
code.s1("b").name(),
code.s1("c").name()
]
);
assert_eq!(
diag,
@ -178,17 +149,14 @@ mod test {
#[test]
fn parse_extraneous_multiple_separators() {
let code = Code::new("a,,,,b,c");
let (res, diag) = code.with_stream_diagnostics(parse_ident_list);
let (res, diag) = code.with_stream_diagnostics(parse_name_list);
assert_eq!(
res,
IdentList {
items: vec![
code.s1("a").ident().into_ref(),
code.s1("b").ident().into_ref(),
code.s1("c").ident().into_ref()
],
tokens: vec![code.s(",", 1).token(), code.s(",", 5).token()]
}
vec![
code.s1("a").name(),
code.s1("b").name(),
code.s1("c").name()
]
);
assert_eq!(
diag,
@ -235,12 +203,15 @@ mod test {
fn parse_list_with_erroneous_elements() {
let code = Code::new("1,c,d");
let diag = code
.parse(parse_ident_list)
.parse(parse_name_list)
.0
.expect_err("Should not parse OK");
assert_eq!(
diag,
Diagnostic::syntax_error(code.s1("1"), "Expected '{identifier}'")
Diagnostic::syntax_error(
code.s1("1"),
"Expected '{identifier}', '{character}', '{string}' or 'all'"
)
);
}
}

View file

@ -18,6 +18,7 @@ use crate::data::*;
use crate::syntax::common::check_label_identifier_mismatch;
use crate::syntax::kinds_error;
use crate::syntax::recover::{expect_semicolon, expect_semicolon_or_last};
use crate::syntax::separated_list::parse_name_list;
use crate::HasTokenSpan;
use vhdl_lang::syntax::parser::ParsingContext;
use vhdl_lang::TokenSpan;
@ -25,15 +26,11 @@ use vhdl_lang::TokenSpan;
/// LRM 10.2 Wait statement
fn parse_wait_statement(ctx: &mut ParsingContext<'_>) -> ParseResult<WaitStatement> {
ctx.stream.expect_kind(Wait)?;
let mut sensitivity_clause = vec![];
if ctx.stream.skip_if_kind(On) {
loop {
sensitivity_clause.push(parse_name(ctx)?);
if !ctx.stream.skip_if_kind(Comma) {
break;
}
}
}
let sensitivity_clause = if ctx.stream.skip_if_kind(On) {
Some(parse_name_list(ctx)?)
} else {
None
};
let condition_clause = parse_optional(ctx, Until, parse_expression)?;
let timeout_clause = parse_optional(ctx, For, parse_expression)?;
@ -115,6 +112,7 @@ fn parse_if_statement(
expect_token!(
ctx.stream,
end_token,
token_id,
Elsif => {
conditionals.push(conditional);
continue;
@ -129,7 +127,7 @@ fn parse_if_statement(
end_token,
End => {
ctx.stream.expect_kind(If)?;
else_branch = Some(statements);
else_branch = Some((statements, token_id));
break;
}
);
@ -167,17 +165,21 @@ fn parse_case_statement(
let mut alternatives = Vec::new();
loop {
let start_token = ctx.stream.get_current_token_id();
let choices = parse_choices(ctx)?;
ctx.stream.expect_kind(RightArrow)?;
let statements = parse_labeled_sequential_statements(ctx)?;
let end_token = ctx.stream.get_last_token_id();
let alternative = Alternative {
choices,
item: statements,
span: TokenSpan::new(start_token, end_token),
};
expect_token!(
ctx.stream,
end_token,
end_token_id,
When => {
alternatives.push(alternative);
continue;
@ -194,6 +196,7 @@ fn parse_case_statement(
is_matching,
expression,
alternatives,
end_token: end_token_id,
end_label_pos,
});
}
@ -206,21 +209,21 @@ fn parse_loop_statement(
ctx: &mut ParsingContext<'_>,
label: Option<&Ident>,
) -> ParseResult<LoopStatement> {
let iteration_scheme = {
let (iteration_scheme, loop_token) = {
expect_token!(
ctx.stream, token,
Loop => None,
ctx.stream, token, token_id,
Loop => (None, token_id),
While => {
let expression = parse_expression(ctx)?;
ctx.stream.expect_kind(Loop)?;
Some(IterationScheme::While(expression))
let loop_token = ctx.stream.expect_kind(Loop)?;
(Some(IterationScheme::While(expression)), loop_token)
},
For => {
let ident = ctx.stream.expect_ident()?;
ctx.stream.expect_kind(In)?;
let discrete_range = parse_discrete_range(ctx)?;
ctx.stream.expect_kind(Loop)?;
Some(IterationScheme::For(ident.into(), discrete_range))
let loop_token = ctx.stream.expect_kind(Loop)?;
(Some(IterationScheme::For(ident.into(), discrete_range)), loop_token)
}
)
};
@ -230,13 +233,16 @@ fn parse_loop_statement(
expect_token!(
ctx.stream,
end_token,
end_token_id,
End => {
ctx.stream.expect_kind(Loop)?;
let end_label_pos = check_label_identifier_mismatch(ctx, label, ctx.stream.pop_optional_ident());
expect_semicolon(ctx);
Ok(LoopStatement {
iteration_scheme,
loop_token,
statements,
end_token: end_token_id,
end_label_pos,
})
}
@ -352,6 +358,7 @@ where
expect_token!(
ctx.stream,
token,
token_id,
SemiColon => {
break;
},
@ -361,7 +368,7 @@ where
ctx.stream,
token,
SemiColon => {
else_item = Some(item);
else_item = Some((item, token_id));
break;
},
When => {
@ -394,10 +401,16 @@ where
let mut alternatives = Vec::with_capacity(2);
loop {
let start_token = ctx.stream.get_current_token_id();
let item = parse_item(ctx)?;
ctx.stream.expect_kind(When)?;
let choices = parse_choices(ctx)?;
alternatives.push(Alternative { choices, item });
let end_token = ctx.stream.get_last_token_id();
alternatives.push(Alternative {
choices,
item,
span: TokenSpan::new(start_token, end_token),
});
expect_token!(
ctx.stream,
@ -485,7 +498,7 @@ fn parse_assignment_or_procedure_call(
SequentialStatement::ProcedureCall(
WithTokenSpan::from(CallOrIndexed {
name: WithTokenSpan::from(name, target.span),
parameters: vec![]
parameters: SeparatedList::default(),
}, target.span))
}
Target::Aggregate(..) => {
@ -644,7 +657,7 @@ mod tests {
None,
WithTokenSpan::from(
SequentialStatement::Wait(WaitStatement {
sensitivity_clause: vec![],
sensitivity_clause: None,
condition_clause: None,
timeout_clause: None,
}),
@ -663,7 +676,7 @@ mod tests {
Some(code.s1("foo").ident()),
WithTokenSpan::new(
SequentialStatement::Wait(WaitStatement {
sensitivity_clause: vec![],
sensitivity_clause: None,
condition_clause: None,
timeout_clause: None,
}),
@ -682,7 +695,10 @@ mod tests {
None,
WithTokenSpan::new(
SequentialStatement::Wait(WaitStatement {
sensitivity_clause: vec![code.s1("foo").name(), code.s1("bar").name()],
sensitivity_clause: Some(vec![
code.s1("foo").name(),
code.s1("bar").name()
]),
condition_clause: None,
timeout_clause: None,
}),
@ -701,7 +717,7 @@ mod tests {
None,
WithTokenSpan::new(
SequentialStatement::Wait(WaitStatement {
sensitivity_clause: vec![],
sensitivity_clause: None,
condition_clause: Some(code.s1("a = b").expr()),
timeout_clause: None,
}),
@ -720,7 +736,7 @@ mod tests {
None,
WithTokenSpan::new(
SequentialStatement::Wait(WaitStatement {
sensitivity_clause: vec![],
sensitivity_clause: None,
condition_clause: None,
timeout_clause: Some(code.s1("2 ns").expr()),
}),
@ -739,7 +755,7 @@ mod tests {
None,
WithTokenSpan::new(
SequentialStatement::Wait(WaitStatement {
sensitivity_clause: vec![code.s1("foo").name()],
sensitivity_clause: Some(vec![code.s1("foo").name()]),
condition_clause: Some(code.s1("bar").expr()),
timeout_clause: Some(code.s1("2 ns").expr()),
}),
@ -935,7 +951,10 @@ mod tests {
WithTokenSpan::new(
SequentialStatement::SignalAssignment(SignalAssignment {
target: code.s1("foo(0)").name().map_into(Target::Name),
delay_mechanism: Some(DelayMechanism::Transport),
delay_mechanism: Some(WithTokenSpan::new(
DelayMechanism::Transport,
code.s1("transport").token_span()
)),
rhs: AssignmentRightHand::Simple(code.s1("bar(1,2)").waveform()),
}),
code.token_span()
@ -1082,10 +1101,12 @@ with x(0) + 1 select
Alternative {
choices: code.s1("0|1").choices(),
item: code.s1("bar(1,2)").expr(),
span: code.s1("bar(1,2) when 0|1").token_span(),
},
Alternative {
choices: code.s1("others").choices(),
item: code.s1("def").expr(),
span: code.s1("def when others").token_span(),
},
],
};
@ -1149,7 +1170,7 @@ with x(0) + 1 select
condition: code.s1("cond = true").expr(),
item: code.s1("bar(1,2)").expr()
}],
else_item: Some(code.s1("expr2").expr())
else_item: Some((code.s1("expr2").expr(), code.s1("else").token()))
}),
}),
code.token_span()
@ -1229,10 +1250,12 @@ with x(0) + 1 select
Alternative {
choices: code.s1("0|1").choices(),
item: code.s1("bar(1,2) after 2 ns").waveform(),
span: code.s1("bar(1,2) after 2 ns when 0|1").token_span(),
},
Alternative {
choices: code.s1("others").choices(),
item: code.s1("def").waveform(),
span: code.s1("def when others").token_span(),
},
],
};
@ -1244,7 +1267,10 @@ with x(0) + 1 select
WithTokenSpan::new(
SequentialStatement::SignalAssignment(SignalAssignment {
target: code.s1("foo(0)").name().map_into(Target::Name),
delay_mechanism: Some(DelayMechanism::Transport),
delay_mechanism: Some(WithTokenSpan::new(
DelayMechanism::Transport,
code.s1("transport").token_span()
)),
rhs: AssignmentRightHand::Selected(selection),
}),
code.token_span()
@ -1268,10 +1294,12 @@ with x(0) + 1 select
Alternative {
choices: code.s1("0|1").choices(),
item: code.s1("bar(1,2)").expr(),
span: code.s1("bar(1,2) when 0|1").token_span(),
},
Alternative {
choices: code.s1("others").choices(),
item: code.s1("def").expr(),
span: code.s1("def when others").token_span(),
},
],
};
@ -1410,7 +1438,10 @@ end if;",
condition: code.s1("cond = true").expr(),
item: vec![code.s1("foo(1,2);").sequential_statement()]
}],
else_item: Some(vec![code.s1("x := 1;").sequential_statement()])
else_item: Some((
vec![code.s1("x := 1;").sequential_statement()],
code.s1("else").token()
))
},
end_label_pos: None,
}),
@ -1441,7 +1472,10 @@ end if mylabel;",
condition: code.s1("cond = true").expr(),
item: vec![code.s1("foo(1,2);").sequential_statement()]
}],
else_item: Some(vec![code.s1("x := 1;").sequential_statement()])
else_item: Some((
vec![code.s1("x := 1;").sequential_statement()],
code.s1("else").token()
))
},
end_label_pos: Some(code.s("mylabel", 2).pos()),
}),
@ -1479,7 +1513,10 @@ end if;",
item: vec![code.s1("y := 2;").sequential_statement()]
}
],
else_item: Some(vec![code.s1("x := 1;").sequential_statement()])
else_item: Some((
vec![code.s1("x := 1;").sequential_statement()],
code.s1("else").token()
))
},
end_label_pos: None,
}),
@ -1517,7 +1554,10 @@ end if mylabel;",
item: vec![code.s1("y := 2;").sequential_statement()]
}
],
else_item: Some(vec![code.s1("x := 1;").sequential_statement()])
else_item: Some((
vec![code.s1("x := 1;").sequential_statement()],
code.s1("else").token()
))
},
end_label_pos: Some(code.s("mylabel", 2).pos()),
}),
@ -1553,16 +1593,27 @@ end case;",
item: vec![
code.s1("stmt1;").sequential_statement(),
code.s1("stmt2;").sequential_statement()
]
],
span: code
.s1("1 | 2 =>
stmt1;
stmt2;")
.token_span()
},
Alternative {
choices: code.s1("others").choices(),
item: vec![
code.s1("stmt3;").sequential_statement(),
code.s1("stmt4;").sequential_statement(),
]
],
span: code
.s1("others =>
stmt3;
stmt4;")
.token_span()
}
],
end_token: code.s1("end").token(),
end_label_pos: None,
}),
code.token_span()
@ -1589,8 +1640,10 @@ end case?;",
expression: code.s1("foo(1)").expr(),
alternatives: vec![Alternative {
choices: code.s1("others").choices(),
item: vec![code.s1("null;").sequential_statement(),]
item: vec![code.s1("null;").sequential_statement()],
span: code.s1("others => null;").token_span()
}],
end_token: code.s1("end").token(),
end_label_pos: None,
}),
code.token_span()
@ -1615,10 +1668,12 @@ end loop lbl;",
WithTokenSpan::new(
SequentialStatement::Loop(LoopStatement {
iteration_scheme: None,
loop_token: code.s1("loop").token(),
statements: vec![
code.s1("stmt1;").sequential_statement(),
code.s1("stmt2;").sequential_statement()
],
end_token: code.s1("end").token(),
end_label_pos: Some(code.s("lbl", 2).pos()),
}),
code.pos_after("lbl: ").token_span()
@ -1645,10 +1700,12 @@ end loop;",
iteration_scheme: Some(IterationScheme::While(
code.s1("foo = true").expr()
)),
loop_token: code.s1("loop").token(),
statements: vec![
code.s1("stmt1;").sequential_statement(),
code.s1("stmt2;").sequential_statement()
],
end_token: code.s1("end").token(),
end_label_pos: None,
}),
code.token_span()
@ -1675,10 +1732,12 @@ end loop;",
code.s1("idx").decl_ident(),
code.s1("0 to 3").discrete_range()
)),
loop_token: code.s1("loop").token(),
statements: vec![
code.s1("stmt1;").sequential_statement(),
code.s1("stmt2;").sequential_statement()
],
end_token: code.s1("end").token(),
end_label_pos: None,
}),
code.token_span()

View file

@ -227,12 +227,13 @@ pub fn parse_subprogram_body(
}
};
let declarations = parse_declarative_part(ctx)?;
ctx.stream.expect_kind(Begin)?;
let begin_token = ctx.stream.expect_kind(Begin)?;
let statements = parse_labeled_sequential_statements(ctx)?;
expect_token!(
ctx.stream,
end_token,
end_token_id,
End => {
ctx.stream.pop_if_kind(end_kind);
@ -241,14 +242,16 @@ pub fn parse_subprogram_body(
} else {
None
};
let end_token = expect_semicolon_or_last(ctx);
let semicolon = expect_semicolon_or_last(ctx);
Ok(SubprogramBody {
span: TokenSpan::new(specification_start_token, end_token),
span: TokenSpan::new(specification_start_token, semicolon),
end_ident_pos: check_end_identifier_mismatch(ctx, specification.subpgm_designator(), end_ident),
begin_token,
specification,
declarations,
statements,
end_token: end_token_id
})
}
)
@ -663,8 +666,10 @@ end function;
let body = SubprogramBody {
span: code.token_span(),
specification,
begin_token: code.s1("begin").token(),
declarations,
statements,
end_token: code.s1("end").token(),
end_ident_pos: None,
};
assert_eq!(
@ -705,7 +710,9 @@ end function foo;
span: code.token_span(),
specification,
declarations: vec![],
begin_token: code.s1("begin").token(),
statements: vec![],
end_token: code.s1("end").token(),
end_ident_pos: Some(code.s("foo", 2).token()),
};
assert_eq!(
@ -730,7 +737,9 @@ end function \"+\";
span: code.token_span(),
specification,
declarations: vec![],
begin_token: code.s1("begin").token(),
statements: vec![],
end_token: code.s1("end").token(),
end_ident_pos: Some(code.s("\"+\"", 2).token()),
};
assert_eq!(
@ -771,7 +780,7 @@ end function \"+\";
header,
SubprogramHeader {
map_aspect: Some(MapAspect {
start: code.s("generic", 2).token(),
span: code.s1("generic map (x => 2, y => 0.4)").token_span(),
list: SeparatedList {
items: vec![
code.s1("x => 2").association_element(),
@ -779,7 +788,6 @@ end function \"+\";
],
tokens: vec![code.s1(",").token()]
},
closing_paren: code.s(")", 2).token()
}),
generic_list: InterfaceList {
interface_type: InterfaceType::Generic,
@ -872,8 +880,10 @@ end function;
let body = SubprogramBody {
span: code.token_span(),
specification,
begin_token: code.s1("begin").token(),
declarations,
statements,
end_token: code.s1("end").token(),
end_ident_pos: None,
};
assert_eq!(
@ -905,11 +915,13 @@ end procedure swap;
span: code.token_span(),
specification,
declarations: code.s1("variable temp : T;").declarative_part(),
begin_token: code.s1("begin").token(),
statements: vec![
code.s1("temp := a;").sequential_statement(),
code.s1("a := b;").sequential_statement(),
code.s1(" b := temp;").sequential_statement(),
],
end_token: code.s1("end").token(),
end_ident_pos: Some(code.s("swap", 2).token()),
};
assert_eq!(

View file

@ -25,7 +25,7 @@ fn parse_array_constraint(
ctx: &mut ParsingContext<'_>,
leftpar: TokenId,
// Open is None
initial: Option<DiscreteRange>,
initial: Option<WithTokenSpan<DiscreteRange>>,
) -> ParseResult<WithTokenSpan<SubtypeConstraint>> {
let mut discrete_ranges: Vec<_> = initial.into_iter().collect();
@ -35,8 +35,14 @@ fn parse_array_constraint(
RightPar => break sep_token_id,
Comma => {}
);
let start_token = ctx.stream.get_current_token_id();
let drange = parse_discrete_range(ctx)?;
let end_token = ctx.stream.get_last_token_id();
discrete_ranges.push(parse_discrete_range(ctx)?);
discrete_ranges.push(WithTokenSpan::new(
drange,
TokenSpan::new(start_token, end_token),
));
};
// Array element constraint
@ -68,7 +74,15 @@ fn parse_composite_constraint(
// Array constraint open
Ok(None)
} else {
parse_discrete_range(ctx).map(Some)
let start_token = ctx.stream.get_current_token_id();
let range = parse_discrete_range(ctx);
let end_token = ctx.stream.get_last_token_id();
range.map(|rng| {
Some(WithTokenSpan::new(
rng,
TokenSpan::new(start_token, end_token),
))
})
}
};
@ -136,7 +150,7 @@ pub fn parse_subtype_constraint(
pub fn parse_element_resolution_indication(
ctx: &mut ParsingContext<'_>,
) -> ParseResult<ResolutionIndication> {
ctx.stream.expect_kind(LeftPar)?;
let start_token = ctx.stream.expect_kind(LeftPar)?;
let first_ident = ctx.stream.expect_ident()?;
@ -181,8 +195,9 @@ pub fn parse_element_resolution_indication(
);
}
let last_token = ctx.stream.get_last_token_id();
ResolutionIndication::Record(element_resolutions)
ResolutionIndication::Record(WithTokenSpan::new(element_resolutions, TokenSpan::new(start_token, last_token)))
}
))
}
@ -192,16 +207,16 @@ pub fn parse_subtype_indication(ctx: &mut ParsingContext<'_>) -> ParseResult<Sub
if ctx.stream.peek_kind() == Some(LeftPar) {
let resolution = parse_element_resolution_indication(ctx)?;
let type_mark = parse_type_mark(ctx)?;
(resolution, type_mark)
(Some(resolution), type_mark)
} else {
let selected_name = parse_selected_name(ctx)?;
match ctx.stream.peek_kind() {
Some(Identifier) => (
ResolutionIndication::FunctionName(selected_name),
Some(ResolutionIndication::FunctionName(selected_name)),
parse_type_mark(ctx)?,
),
_ => (
ResolutionIndication::Unresolved,
None,
parse_type_mark_starting_with_name(ctx, selected_name)?,
),
}
@ -229,7 +244,7 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("std_logic").type_mark(),
constraint: None
}
@ -242,7 +257,9 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::FunctionName(code.s1("resolve").name()),
resolution: Some(ResolutionIndication::FunctionName(
code.s1("resolve").name()
)),
type_mark: code.s1("std_logic").type_mark(),
constraint: None
}
@ -255,7 +272,9 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::ArrayElement(code.s1("resolve").name()),
resolution: Some(ResolutionIndication::ArrayElement(
code.s1("resolve").name()
)),
type_mark: code.s1("integer_vector").type_mark(),
constraint: None
}
@ -276,7 +295,10 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Record(vec![elem_resolution]),
resolution: Some(ResolutionIndication::Record(WithTokenSpan::new(
vec![elem_resolution],
code.between("(", ")").token_span()
))),
type_mark: code.s1("rec_t").type_mark(),
constraint: None
}
@ -311,17 +333,20 @@ mod tests {
let elem3_resolution = RecordElementResolution {
ident: code.s1("elem3").ident(),
resolution: Box::new(ResolutionIndication::Record(vec![sub_elem_resolution])),
resolution: Box::new(ResolutionIndication::Record(WithTokenSpan::new(
vec![sub_elem_resolution],
code.s1("(sub_elem sub_resolve)").token_span(),
))),
};
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Record(vec![
elem1_resolution,
elem2_resolution,
elem3_resolution
]),
resolution: Some(ResolutionIndication::Record(WithTokenSpan::new(
vec![elem1_resolution, elem2_resolution, elem3_resolution],
code.s1("(elem1 (resolve1), elem2 resolve2, elem3 (sub_elem sub_resolve))")
.token_span()
))),
type_mark: code.s1("rec_t").type_mark(),
constraint: None
}
@ -334,7 +359,9 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::FunctionName(code.s1("lib.foo.resolve").name()),
resolution: Some(ResolutionIndication::FunctionName(
code.s1("lib.foo.resolve").name()
)),
type_mark: code.s1("std_logic").type_mark(),
constraint: None
}
@ -348,7 +375,7 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("lib.foo.bar").type_mark(),
constraint: None
}
@ -367,7 +394,7 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("integer").type_mark(),
constraint: Some(constraint)
}
@ -386,7 +413,7 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("integer").type_mark(),
constraint: Some(constraint)
}
@ -398,14 +425,20 @@ mod tests {
let code = Code::new("integer_vector(2-1 downto 0)");
let constraint = WithTokenSpan::new(
SubtypeConstraint::Array(vec![code.s1("2-1 downto 0").discrete_range()], None),
SubtypeConstraint::Array(
vec![WithTokenSpan::new(
code.s1("2-1 downto 0").discrete_range(),
code.s1("2-1 downto 0").token_span(),
)],
None,
),
code.s1("(2-1 downto 0)").token_span(),
);
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("integer_vector").type_mark(),
constraint: Some(constraint)
}
@ -417,14 +450,20 @@ mod tests {
let code = Code::new("integer_vector(lib.foo.bar)");
let constraint = WithTokenSpan::new(
SubtypeConstraint::Array(vec![code.s1("lib.foo.bar").discrete_range()], None),
SubtypeConstraint::Array(
vec![WithTokenSpan::new(
code.s1("lib.foo.bar").discrete_range(),
code.s1("lib.foo.bar").token_span(),
)],
None,
),
code.s1("(lib.foo.bar)").token_span(),
);
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("integer_vector").type_mark(),
constraint: Some(constraint)
}
@ -436,14 +475,20 @@ mod tests {
let code = Code::new("integer_vector(lib.pkg.bar'range)");
let constraint = WithTokenSpan::new(
SubtypeConstraint::Array(vec![code.s1("lib.pkg.bar'range").discrete_range()], None),
SubtypeConstraint::Array(
vec![WithTokenSpan::new(
code.s1("lib.pkg.bar'range").discrete_range(),
code.s1("lib.pkg.bar'range").token_span(),
)],
None,
),
code.s1("(lib.pkg.bar'range)").token_span(),
);
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("integer_vector").type_mark(),
constraint: Some(constraint)
}
@ -462,7 +507,7 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("integer_vector").type_mark(),
constraint: Some(constraint)
}
@ -476,8 +521,14 @@ mod tests {
let constraint = WithTokenSpan::new(
SubtypeConstraint::Array(
vec![
code.s1("2-1 downto 0").discrete_range(),
code.s1("11 to 14").discrete_range(),
WithTokenSpan::new(
code.s1("2-1 downto 0").discrete_range(),
code.s1("2-1 downto 0").token_span(),
),
WithTokenSpan::new(
code.s1("11 to 14").discrete_range(),
code.s1("11 to 14").token_span(),
),
],
None,
),
@ -487,7 +538,7 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("integer_vector").type_mark(),
constraint: Some(constraint)
}
@ -499,15 +550,27 @@ mod tests {
let code = Code::new("integer_vector(2-1 downto 0, 11 to 14)(foo to bar)");
let element_constraint = WithTokenSpan::new(
SubtypeConstraint::Array(vec![code.s1("foo to bar").discrete_range()], None),
SubtypeConstraint::Array(
vec![WithTokenSpan::new(
code.s1("foo to bar").discrete_range(),
code.s1("foo to bar").token_span(),
)],
None,
),
code.s1("(foo to bar)").token_span(),
);
let constraint = WithTokenSpan::new(
SubtypeConstraint::Array(
vec![
code.s1("2-1 downto 0").discrete_range(),
code.s1("11 to 14").discrete_range(),
WithTokenSpan::new(
code.s1("2-1 downto 0").discrete_range(),
code.s1("2-1 downto 0").token_span(),
),
WithTokenSpan::new(
code.s1("11 to 14").discrete_range(),
code.s1("11 to 14").token_span(),
),
],
Some(Box::new(element_constraint)),
),
@ -517,7 +580,7 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("integer_vector").type_mark(),
constraint: Some(constraint)
}
@ -531,7 +594,13 @@ mod tests {
let tdata_constraint = ElementConstraint {
ident: code.s1("tdata").ident(),
constraint: Box::new(WithTokenSpan::new(
SubtypeConstraint::Array(vec![code.s1("2-1 downto 0").discrete_range()], None),
SubtypeConstraint::Array(
vec![WithTokenSpan::new(
code.s1("2-1 downto 0").discrete_range(),
code.s1("2-1 downto 0").token_span(),
)],
None,
),
code.s1("(2-1 downto 0)").token_span(),
)),
};
@ -539,7 +608,13 @@ mod tests {
let tuser_constraint = ElementConstraint {
ident: code.s1("tuser").ident(),
constraint: Box::new(WithTokenSpan::new(
SubtypeConstraint::Array(vec![code.s1("3 to 5").discrete_range()], None),
SubtypeConstraint::Array(
vec![WithTokenSpan::new(
code.s1("3 to 5").discrete_range(),
code.s1("3 to 5").token_span(),
)],
None,
),
code.s1("(3 to 5)").token_span(),
)),
};
@ -547,7 +622,7 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("axi_m2s_t").type_mark(),
constraint: Some(WithTokenSpan::new(
SubtypeConstraint::Record(vec![tdata_constraint, tuser_constraint]),
@ -564,7 +639,7 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("obj'subtype").type_mark(),
constraint: None
}
@ -575,7 +650,7 @@ mod tests {
assert_eq!(
code.with_stream(parse_subtype_indication),
SubtypeIndication {
resolution: ResolutionIndication::Unresolved,
resolution: None,
type_mark: code.s1("obj.field'subtype").type_mark(),
constraint: None
}

View file

@ -20,7 +20,6 @@ use super::interface_declaration::{parse_generic, parse_parameter, parse_port};
use super::names::{parse_association_list, parse_designator, parse_name, parse_type_mark};
use super::object_declaration::{parse_file_declaration, parse_object_declaration};
use super::range::{parse_discrete_range, parse_range};
use super::separated_list::{parse_ident_list, parse_name_list};
use super::sequential_statement::parse_sequential_statement;
use super::subprogram::{
parse_signature, parse_subprogram_declaration, parse_subprogram_specification,
@ -126,15 +125,6 @@ impl CodeBuilder {
}
}
impl<T> SeparatedList<T> {
pub fn single(item: T) -> SeparatedList<T> {
SeparatedList {
items: vec![item],
tokens: vec![],
}
}
}
#[derive(Clone)]
pub struct Code {
pub symbols: Arc<Symbols>,
@ -582,7 +572,7 @@ impl Code {
pub fn character(&self) -> WithToken<u8> {
self.parse_ok_no_diagnostics(|ctx: &mut ParsingContext| {
let id = ctx.stream.expect_kind(Kind::Character)?;
ctx.stream.get_token(id).to_character_value(id)
ctx.stream.index(id).to_character_value(id)
})
}
@ -596,14 +586,6 @@ impl Code {
self.parse_ok_no_diagnostics(parse_name)
}
pub fn name_list(&self) -> SeparatedList<WithTokenSpan<Name>> {
self.parse_ok_no_diagnostics(parse_name_list)
}
pub fn ident_list(&self) -> SeparatedList<WithRef<Ident>> {
self.parse_ok_no_diagnostics(parse_ident_list)
}
pub fn type_mark(&self) -> WithTokenSpan<Name> {
self.parse_ok_no_diagnostics(parse_type_mark)
}
@ -626,9 +608,7 @@ impl Code {
}
pub fn file_decl(&self) -> FileDeclaration {
self.parse_ok_no_diagnostics(parse_file_declaration)
.remove(0)
.item
self.parse_ok_no_diagnostics(parse_file_declaration).item
}
pub fn alias_decl(&self) -> AliasDeclaration {
@ -676,7 +656,7 @@ impl Code {
WithTokenSpan::from(
CallOrIndexed {
name,
parameters: vec![],
parameters: SeparatedList::default(),
},
span,
)
@ -719,7 +699,7 @@ impl Code {
self.parse_ok_no_diagnostics(parse_waveform)
}
pub fn aggregate(&self) -> WithTokenSpan<Vec<ElementAssociation>> {
pub fn aggregate(&self) -> WithTokenSpan<Vec<WithTokenSpan<ElementAssociation>>> {
self.parse_ok_no_diagnostics(parse_aggregate)
}
@ -735,8 +715,8 @@ impl Code {
self.parse_ok_no_diagnostics(parse_choices)
}
pub fn use_clause(&self) -> UseClause {
self.parse_ok_no_diagnostics(parse_use_clause).item
pub fn use_clause(&self) -> WithTokenSpan<UseClause> {
self.parse_ok_no_diagnostics(parse_use_clause)
}
pub fn library_clause(&self) -> LibraryClause {
@ -868,8 +848,8 @@ fn diagnostics_to_map(diagnostics: Vec<Diagnostic>) -> HashMap<Diagnostic, usize
map
}
// Drop releated info when we do not want to test for it
pub fn without_releated(diagnostics: &[Diagnostic]) -> Vec<Diagnostic> {
// Drop related info when we do not want to test for it
pub fn without_related(diagnostics: &[Diagnostic]) -> Vec<Diagnostic> {
let mut diagnostics = diagnostics.to_vec();
for diagnostic in diagnostics.iter_mut() {
diagnostic.related.clear();
@ -955,13 +935,10 @@ fn value_to_string(value: &Value) -> String {
match value {
Value::Identifier(ident) => ident.name_utf8(),
Value::String(s) => String::from_utf8(s.chars().copied().collect_vec()).unwrap(),
Value::BitString(_) => {
panic!("value_to_string is currently not supported for BitString literals!")
Value::BitString(s, ..) => String::from_utf8(s.chars().copied().collect_vec()).unwrap(),
Value::AbstractLiteral(s, _) => {
String::from_utf8(s.chars().copied().collect_vec()).unwrap()
}
Value::AbstractLiteral(lit) => match lit {
AbstractLiteral::Integer(i) => i.to_string(),
AbstractLiteral::Real(f) => f.to_string(),
},
Value::Character(char) => format!("'{}'", String::from_utf8(vec![*char]).unwrap()),
Value::Text(text) => String::from_utf8(text.chars().copied().collect_vec()).unwrap(),
Value::None => "".into(),

View file

@ -412,8 +412,8 @@ pub fn kinds_str(kinds: &[Kind]) -> String {
pub enum Value {
Identifier(Symbol),
String(Latin1String),
BitString(ast::BitString),
AbstractLiteral(ast::AbstractLiteral),
BitString(Latin1String, ast::BitString),
AbstractLiteral(Latin1String, ast::AbstractLiteral),
Character(u8),
// Raw text that is not processed (i.e. tokenized) further. Used in tool directives
Text(Latin1String),
@ -458,6 +458,28 @@ impl From<TokenId> for TokenSpan {
}
}
impl AddAssign<usize> for TokenId {
fn add_assign(&mut self, rhs: usize) {
self.0 += rhs
}
}
impl Sub<usize> for TokenId {
type Output = TokenId;
fn sub(self, rhs: usize) -> Self::Output {
TokenId(self.0 - rhs)
}
}
impl Add<usize> for TokenId {
type Output = TokenId;
fn add(self, rhs: usize) -> Self::Output {
TokenId(self.0 + rhs)
}
}
/// AST elements for which it is necessary to get the underlying tokens can implement the `HasTokenSpan` trait.
/// The trait provides getters for the start and end token.
///
@ -469,6 +491,8 @@ impl From<TokenId> for TokenSpan {
///
/// Example:
/// ```rust
/// use vhdl_lang::ast::Name;
/// use vhdl_lang::ast::token_range::WithTokenSpan;
/// use vhdl_lang_macros::{with_token_span, TokenSpan};
///
/// // With `with_token_span` a field `info` of type `(TokenId, TokenId)` is inserted.
@ -476,19 +500,19 @@ impl From<TokenId> for TokenSpan {
/// #[with_token_span]
/// #[derive(PartialEq, Debug, Clone)]
/// pub struct UseClause {
/// pub name_list: ::vhdl_lang::ast::NameList,
/// pub name_list: Vec<WithTokenSpan<Name>>,
/// }
///
/// #[with_token_span]
/// #[derive(PartialEq, Debug, Clone)]
/// pub struct ContextReference {
/// pub name_list: ::vhdl_lang::ast::NameList,
/// pub name_list: Vec<WithTokenSpan<Name>>,
/// }
///
/// #[with_token_span]
/// #[derive(PartialEq, Debug, Clone)]
/// pub struct LibraryClause {
/// pub name_list: ::vhdl_lang::ast::IdentList,
/// pub name_list: Vec<::vhdl_lang::ast::WithRef<::vhdl_lang::ast::Ident>>,
/// }
///
/// // Enums can use the `TokenSpan` derive macro directly
@ -543,7 +567,12 @@ impl Debug for TokenSpan {
impl TokenSpan {
pub fn new(start_token: TokenId, end_token: TokenId) -> Self {
debug_assert!(start_token <= end_token);
debug_assert!(
start_token <= end_token,
"start token {:} is past end token {}",
start_token.0,
end_token.0
);
Self {
start_token,
end_token,
@ -601,6 +630,65 @@ impl TokenSpan {
}
}
struct TokenSpanIterator {
end: TokenId,
current: TokenId,
}
impl Iterator for TokenSpanIterator {
type Item = TokenId;
fn next(&mut self) -> Option<Self::Item> {
let old_current = self.current;
// Note: in this example, current may point to an
// invalid token (as it is greater than and).
// This is OK because the invalid ID is never returned.
if self.current > self.end {
None
} else {
self.current += 1;
Some(old_current)
}
}
}
#[test]
fn token_iterator() {
let span = TokenSpan {
start_token: TokenId(0),
end_token: TokenId(0),
};
let mut itr = span.iter();
assert_eq!(itr.next(), Some(TokenId(0)));
assert_eq!(itr.next(), None);
let span = TokenSpan {
start_token: TokenId(0),
end_token: TokenId(1),
};
let mut itr = span.iter();
assert_eq!(itr.next(), Some(TokenId(0)));
assert_eq!(itr.next(), Some(TokenId(1)));
assert_eq!(itr.next(), None);
}
impl TokenSpan {
pub fn iter(&self) -> impl Iterator<Item = TokenId> {
TokenSpanIterator {
current: self.start_token,
end: self.end_token,
}
}
pub fn len(&self) -> usize {
self.end_token.0 - self.start_token.0 + 1
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
/// A type that conforms to `TokenAccess` can be indexed using a `TokenId`.
/// Convenience methods exist to directly get the `SrcPos` for a given `TokenId`
/// or a span starting at a certain token and ending at another.
@ -608,14 +696,16 @@ impl TokenSpan {
/// Types such as `Vec` and `array` implement `TokenAccess`
pub trait TokenAccess {
/// Get a token by its ID
fn get_token(&self, id: TokenId) -> &Token;
fn get_token(&self, id: TokenId) -> Option<&Token>;
fn index(&self, id: TokenId) -> &Token;
/// Get a slice of tokens by using a start ID and an end ID
fn get_token_slice(&self, start_id: TokenId, end_id: TokenId) -> &[Token];
/// Get a token's position by its ID
fn get_pos(&self, id: TokenId) -> &SrcPos {
&self.get_token(id).pos
&self.index(id).pos
}
/// Get a span where the beginning of that span is the beginning of the token indexed by
@ -626,7 +716,11 @@ pub trait TokenAccess {
}
impl TokenAccess for Vec<Token> {
fn get_token(&self, id: TokenId) -> &Token {
fn get_token(&self, id: TokenId) -> Option<&Token> {
self.get(id.0)
}
fn index(&self, id: TokenId) -> &Token {
&self[id.0]
}
@ -636,7 +730,11 @@ impl TokenAccess for Vec<Token> {
}
impl TokenAccess for [Token] {
fn get_token(&self, id: TokenId) -> &Token {
fn get_token(&self, id: TokenId) -> Option<&Token> {
self.get(id.0)
}
fn index(&self, id: TokenId) -> &Token {
&self[id.0]
}
@ -661,6 +759,7 @@ pub struct Comment {
use crate::standard::VHDLStandard;
use std::convert::AsRef;
use std::fmt::{Debug, Display, Formatter};
use std::ops::{Add, AddAssign, Sub};
use strum::IntoStaticStr;
impl AsRef<SrcPos> for Token {
@ -716,7 +815,7 @@ impl Token {
pub fn to_bit_string(&self, id: TokenId) -> DiagnosticResult<WithToken<ast::BitString>> {
if let Token {
kind: BitString,
value: Value::BitString(value),
value: Value::BitString(_, value),
..
} = self
{
@ -732,7 +831,7 @@ impl Token {
) -> DiagnosticResult<WithToken<ast::AbstractLiteral>> {
if let Token {
kind: AbstractLiteral,
value: Value::AbstractLiteral(value),
value: Value::AbstractLiteral(_, value),
..
} = self
{
@ -766,6 +865,28 @@ impl Token {
))
}
}
/// Returns the full range of this token, respecting any potential comments.
/// Note that [Token::pos] only returns the position of the token itself.
pub fn full_range(&self) -> crate::data::Range {
let mut range = self.pos.range();
if let Some(comments) = &self.comments {
if let Some(comment) = comments.leading.first() {
range.start = comment.range.start
}
if let Some(trailing) = &comments.trailing {
range.end = trailing.range.end
}
}
range
}
/// return `true` when `self` is equal to `other` while ignoring all
/// changes that are attributed to their position in the source file
/// and all changes that only affect comments.
pub fn equal_format(&self, other: &Token) -> bool {
self.kind == other.kind && self.value == other.value
}
}
impl Operator {
@ -857,8 +978,9 @@ fn parse_integer(
reader: &mut ContentReader,
base: u64,
stop_on_suffix: bool,
) -> Result<u64, TokenError> {
) -> Result<(u64, Latin1String), TokenError> {
let mut result = Some(0_u64);
let mut result_str = Latin1String::empty();
let mut too_large_digit = None;
let mut invalid_character = None;
@ -879,10 +1001,12 @@ fn parse_integer(
b'a'..=b'f' => 10 + b - b'a',
b'A'..=b'F' => 10 + b - b'A',
b'_' => {
result_str.push(b);
reader.skip();
continue;
}
b'g'..=b'z' | b'G'..=b'Z' => {
result_str.push(b);
invalid_character = Some((b, reader.pos()));
reader.skip();
continue;
@ -896,6 +1020,7 @@ fn parse_integer(
too_large_digit = Some((b, reader.pos()));
}
result_str.push(b);
reader.skip();
result = result
@ -918,7 +1043,7 @@ fn parse_integer(
),
))
} else if let Some(result) = result {
Ok(result)
Ok((result, result_str))
} else {
Err(TokenError::range(
start,
@ -928,25 +1053,30 @@ fn parse_integer(
}
}
fn parse_exponent(reader: &mut ContentReader) -> Result<i32, TokenError> {
fn parse_exponent(reader: &mut ContentReader) -> Result<(i32, Latin1String), TokenError> {
let start = reader.pos();
let mut buffer = Latin1String::empty();
let negative = {
if reader.peek()? == Some(b'-') {
buffer.push(b'-');
reader.skip();
true
} else {
reader.skip_if(b'+')?;
if reader.skip_if(b'+')? {
buffer.push(b'+');
}
false
}
};
let exp = parse_integer(reader, 10, false)?;
let (exp, mut exp_name) = parse_integer(reader, 10, false)?;
buffer.append(&mut exp_name);
if negative {
if exp <= (-(i32::MIN as i64)) as u64 {
return Ok((-(exp as i64)) as i32);
return Ok(((-(exp as i64)) as i32, buffer));
}
} else if exp <= i32::MAX as u64 {
return Ok(exp as i32);
return Ok((exp as i32, buffer));
}
Err(TokenError::range(
@ -1084,8 +1214,9 @@ fn parse_multi_line_comment(reader: &mut ContentReader) -> Result<Comment, Token
fn parse_real_literal(
buffer: &mut Latin1String,
reader: &mut ContentReader,
) -> Result<f64, TokenError> {
buffer.bytes.clear();
) -> Result<(f64, Latin1String), TokenError> {
buffer.clear();
let mut text = Latin1String::empty();
let start = reader.pos();
while let Some(b) = reader.peek_lowercase()? {
match b {
@ -1096,10 +1227,12 @@ fn parse_real_literal(
break;
}
b'0'..=b'9' | b'a'..=b'd' | b'f' | b'A'..=b'F' | b'.' => {
text.push(b);
reader.skip();
buffer.bytes.push(b);
buffer.push(b);
}
b'_' => {
text.push(b);
reader.skip();
continue;
}
@ -1111,15 +1244,16 @@ fn parse_real_literal(
let string = unsafe { std::str::from_utf8_unchecked(&buffer.bytes) };
let result: Result<f64, TokenError> =
string.parse().map_err(|err: std::num::ParseFloatError| {
string
.parse::<f64>()
.map(|val| (val, text))
.map_err(|err: std::num::ParseFloatError| {
TokenError::range(start, reader.pos(), err.to_string())
});
result
})
}
fn exponentiate(value: u64, exp: u32) -> Option<u64> {
(10_u64).checked_pow(exp).and_then(|x| x.checked_mul(value))
10_u64.checked_pow(exp).and_then(|x| x.checked_mul(value))
}
/// LRM 15.5 Abstract literals
@ -1135,37 +1269,44 @@ fn parse_abstract_literal(
// Real
Some(b'.') => {
reader.set_state(state);
let real = parse_real_literal(buffer, reader)?;
let (real, mut text) = parse_real_literal(buffer, reader)?;
match reader.peek()? {
// Exponent
Some(b'e') | Some(b'E') => {
text.push(reader.peek().unwrap().unwrap());
reader.skip();
let exp = parse_exponent(reader)?;
let (exp, mut exp_text) = parse_exponent(reader)?;
text.append(&mut exp_text);
Ok((
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(
real * (10.0_f64).powi(exp),
)),
Value::AbstractLiteral(
text,
ast::AbstractLiteral::Real(real * 10_f64.powi(exp)),
),
))
}
_ => Ok((
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(real)),
Value::AbstractLiteral(text, ast::AbstractLiteral::Real(real)),
)),
}
}
// Integer exponent
Some(b'e') => {
let integer = initial?;
let mut text = Latin1String::empty();
let (integer, mut int_text) = initial?;
text.append(&mut int_text);
text.push(reader.peek().unwrap().unwrap());
reader.skip();
let exp = parse_exponent(reader)?;
let (exp, mut exp_text) = parse_exponent(reader)?;
text.append(&mut exp_text);
if exp >= 0 {
if let Some(value) = exponentiate(integer, exp as u32) {
Ok((
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Integer(value)),
Value::AbstractLiteral(text, ast::AbstractLiteral::Integer(value)),
))
} else {
Err(TokenError::range(
@ -1185,17 +1326,20 @@ fn parse_abstract_literal(
// Based integer
Some(b'#') => {
let base = initial?;
let (base, mut base_text) = initial?;
base_text.push(b'#');
reader.skip();
let base_result = parse_integer(reader, base, false);
if let Some(b'#') = reader.peek()? {
reader.skip();
let integer = base_result?;
let (integer, mut int_text) = base_result?;
base_text.append(&mut int_text);
base_text.push(b'#');
if (2..=16).contains(&base) {
Ok((
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Integer(integer)),
Value::AbstractLiteral(base_text, ast::AbstractLiteral::Integer(integer)),
))
} else {
Err(TokenError::range(
@ -1215,11 +1359,16 @@ fn parse_abstract_literal(
// Bit string literal
Some(b's') | Some(b'u') | Some(b'b') | Some(b'o') | Some(b'x') | Some(b'd') => {
let integer = initial?;
let (integer, _) = initial?;
if let Some(base_spec) = parse_base_specifier(reader)? {
// @TODO check overflow
parse_bit_string(buffer, reader, base_spec, Some(integer as u32))
parse_bit_string(
buffer,
reader,
base_spec,
Some(integer as u32),
state.pos().character as usize,
)
} else {
Err(TokenError::range(
state.pos(),
@ -1230,9 +1379,10 @@ fn parse_abstract_literal(
}
_ => {
// Plain integer
let (integer, integer_text) = initial?;
Ok((
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Integer(initial?)),
Value::AbstractLiteral(integer_text, ast::AbstractLiteral::Integer(integer)),
))
}
}
@ -1285,6 +1435,7 @@ fn parse_bit_string(
reader: &mut ContentReader,
base_specifier: BaseSpecifier,
bit_string_length: Option<u32>,
start: usize,
) -> Result<(Kind, Value), TokenError> {
let value = match parse_string(buffer, reader) {
Ok(value) => value,
@ -1294,13 +1445,21 @@ fn parse_bit_string(
}
};
let end_pos = reader.state().pos();
let actual_value = reader
.value_at(end_pos.line as usize, start, end_pos.character as usize)
.unwrap();
Ok((
BitString,
Value::BitString(ast::BitString {
length: bit_string_length,
base: base_specifier,
value,
}),
Value::BitString(
actual_value,
ast::BitString {
length: bit_string_length,
base: base_specifier,
value,
},
),
))
}
@ -1560,8 +1719,15 @@ impl<'a> Tokenizer<'a> {
let (kind, value) = match byte {
b'a'..=b'z' | b'A'..=b'Z' => {
let state = self.reader.state();
if let Some(base_spec) = maybe_base_specifier(&mut self.reader)? {
parse_bit_string(&mut self.buffer, &mut self.reader, base_spec, None)?
parse_bit_string(
&mut self.buffer,
&mut self.reader,
base_spec,
None,
state.pos().character as usize,
)?
} else {
parse_basic_identifier_or_keyword(
&mut self.buffer,
@ -1805,7 +1971,7 @@ impl<'a> Tokenizer<'a> {
Err(err) => {
self.state.start = self.reader.state();
Err(Diagnostic::syntax_error(
&self.source.pos(err.range.start, err.range.end),
self.source.pos(err.range.start, err.range.end),
err.message,
))
}
@ -1822,7 +1988,7 @@ impl<'a> Tokenizer<'a> {
if let Err(err) = read_until_newline(&mut self.buffer, &mut self.reader) {
self.state.start = self.reader.state();
return Err(Diagnostic::syntax_error(
&self.source.pos(err.range.start, err.range.end),
self.source.pos(err.range.start, err.range.end),
err.message,
));
}
@ -2014,24 +2180,39 @@ my_other_ident",
vec![
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Integer(100))
Value::AbstractLiteral(
Latin1String::new(b"100"),
ast::AbstractLiteral::Integer(100)
)
),
(Minus, Value::None),
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Integer(123))
Value::AbstractLiteral(
Latin1String::new(b"123"),
ast::AbstractLiteral::Integer(123)
)
),
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Integer(162))
Value::AbstractLiteral(
Latin1String::new(b"1_6_2"),
ast::AbstractLiteral::Integer(162)
)
),
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Integer(1000))
Value::AbstractLiteral(
Latin1String::new(b"1e3"),
ast::AbstractLiteral::Integer(1000)
)
),
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Integer(20000))
Value::AbstractLiteral(
Latin1String::new(b"2E4"),
ast::AbstractLiteral::Integer(20000)
)
),
]
);
@ -2047,11 +2228,11 @@ my_other_ident",
tokens,
vec![
Err(Diagnostic::syntax_error(
&code.s1(""),
code.s1(""),
"Found invalid latin-1 character '€'",
)),
Err(Diagnostic::syntax_error(
&code.s1("\u{1F4A3}"),
code.s1("\u{1F4A3}"),
"Found invalid latin-1 character '\u{1F4A3}'",
)),
]
@ -2066,7 +2247,7 @@ my_other_ident",
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.pos(),
code.pos(),
"Integer literals may not have negative exponent",
))]
);
@ -2079,32 +2260,53 @@ my_other_ident",
vec![
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(0.1))
Value::AbstractLiteral(
Latin1String::new(b"0.1"),
ast::AbstractLiteral::Real(0.1)
)
),
(Minus, Value::None),
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(22.33))
Value::AbstractLiteral(
Latin1String::new(b"2_2.3_3"),
ast::AbstractLiteral::Real(22.33)
)
),
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(2000.0))
Value::AbstractLiteral(
Latin1String::new(b"2.0e3"),
ast::AbstractLiteral::Real(2000.0)
)
),
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(333.0))
Value::AbstractLiteral(
Latin1String::new(b"3.33E2"),
ast::AbstractLiteral::Real(333.0)
)
),
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(0.021))
Value::AbstractLiteral(
Latin1String::new(b"2.1e-2"),
ast::AbstractLiteral::Real(0.021)
)
),
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(44.0))
Value::AbstractLiteral(
Latin1String::new(b"4.4e+1"),
ast::AbstractLiteral::Real(44.0)
)
),
(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(2500.0))
Value::AbstractLiteral(
Latin1String::new(b"2.5E+3"),
ast::AbstractLiteral::Real(2500.0)
)
),
]
);
@ -2116,7 +2318,10 @@ my_other_ident",
kind_value_tokenize("0.1000_0000_0000_0000_0000_0000_0000_0000"),
vec![(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(1e-1))
Value::AbstractLiteral(
Latin1String::new(b"0.1000_0000_0000_0000_0000_0000_0000_0000"),
ast::AbstractLiteral::Real(1e-1)
)
)]
);
}
@ -2127,7 +2332,10 @@ my_other_ident",
kind_value_tokenize("1000_0000_0000_0000_0000_0000_0000_0000.0"),
vec![(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(1e31))
Value::AbstractLiteral(
Latin1String::new(b"1000_0000_0000_0000_0000_0000_0000_0000.0"),
ast::AbstractLiteral::Real(1e31)
)
)]
);
}
@ -2139,7 +2347,10 @@ my_other_ident",
kind_value_tokenize("2.71828182845904523536"),
vec![(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Real(2.718_281_828_459_045))
Value::AbstractLiteral(
Latin1String::new(b"2.71828182845904523536"),
ast::AbstractLiteral::Real(2.718_281_828_459_045)
)
)]
);
}
@ -2205,7 +2416,7 @@ my_other_ident",
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.pos(),
code.pos(),
"Multi line string"
))]
);
@ -2219,7 +2430,7 @@ my_other_ident",
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.pos(),
code.pos(),
"Reached EOF before end quote",
))]
);
@ -2254,13 +2465,11 @@ my_other_ident",
("".to_owned(), None)
};
let code = format!("{length_str}{base_spec}\"{value}\"");
let mut code = format!("{length_str}{base_spec}\"{value}\"");
let code = if upper_case {
code.to_ascii_uppercase()
} else {
code
};
if upper_case {
code.make_ascii_uppercase()
}
let value = if upper_case {
value.to_ascii_uppercase()
@ -2268,17 +2477,22 @@ my_other_ident",
value.to_owned()
};
let original_code = code.clone();
let code = Code::new(code.as_str());
let tokens = code.tokenize();
assert_eq!(
tokens,
vec![Token {
kind: BitString,
value: Value::BitString(ast::BitString {
length: length_opt,
base,
value: Latin1String::from_utf8_unchecked(value.as_str()),
}),
value: Value::BitString(
Latin1String::from_utf8_unchecked(original_code.as_str()),
ast::BitString {
length: length_opt,
base,
value: Latin1String::from_utf8_unchecked(value.as_str()),
}
),
pos: code.pos(),
comments: None,
},]
@ -2295,7 +2509,7 @@ my_other_ident",
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.pos(),
code.pos(),
"Invalid bit string literal",
))]
);
@ -2305,7 +2519,7 @@ my_other_ident",
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.pos(),
code.pos(),
"Invalid bit string literal",
))]
);
@ -2317,21 +2531,30 @@ my_other_ident",
kind_value_tokenize("2#101#"),
vec![(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Integer(5))
Value::AbstractLiteral(
Latin1String::new(b"2#101#"),
ast::AbstractLiteral::Integer(5)
)
),]
);
assert_eq!(
kind_value_tokenize("8#321#"),
vec![(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Integer(3 * 8 * 8 + 2 * 8 + 1))
Value::AbstractLiteral(
Latin1String::new(b"8#321#"),
ast::AbstractLiteral::Integer(3 * 8 * 8 + 2 * 8 + 1)
)
),]
);
assert_eq!(
kind_value_tokenize("16#eEFfa#"),
vec![(
AbstractLiteral,
Value::AbstractLiteral(ast::AbstractLiteral::Integer(0xeeffa))
Value::AbstractLiteral(
Latin1String::new(b"16#eEFfa#"),
ast::AbstractLiteral::Integer(0xeeffa)
)
),]
);
}
@ -2344,7 +2567,7 @@ my_other_ident",
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.s1("k"),
code.s1("k"),
"Invalid integer character 'k'",
))]
);
@ -2352,13 +2575,24 @@ my_other_ident",
#[test]
fn tokenize_illegal_based_integer() {
// May not use digit larger than or equal base
let code = Code::new("3#3#");
let (tokens, _) = code.tokenize_result();
println!("{:?}", tokens);
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
code.s("3", 2),
"Illegal digit '3' for base 3",
))]
);
// Base may only be 2-16
let code = Code::new("1#0#");
let (tokens, _) = code.tokenize_result();
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.s1("1"),
code.s1("1"),
"Base must be at least 2 and at most 16, got 1",
))]
);
@ -2367,26 +2601,16 @@ my_other_ident",
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.s1("17"),
code.s1("17"),
"Base must be at least 2 and at most 16, got 17",
))]
);
// May not use digit larger than or equal base
let code = Code::new("3#3#");
let (tokens, _) = code.tokenize_result();
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.s("3", 2),
"Illegal digit '3' for base 3",
))]
);
let code = Code::new("15#f#");
let (tokens, _) = code.tokenize_result();
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.s1("f"),
code.s1("f"),
"Illegal digit 'f' for base 15",
))]
);
@ -2565,7 +2789,7 @@ comment
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.pos(),
code.pos(),
"Integer too large for 64-bit unsigned",
))]
);
@ -2576,7 +2800,7 @@ comment
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.pos(),
code.pos(),
"Integer too large for 64-bit unsigned",
))]
);
@ -2588,7 +2812,7 @@ comment
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.s1(&exponent_str),
code.s1(&exponent_str),
"Exponent too large for 32-bits signed",
))]
);
@ -2600,7 +2824,7 @@ comment
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.s1(&exponent_str),
code.s1(&exponent_str),
"Exponent too large for 32-bits signed",
))]
);
@ -2611,7 +2835,7 @@ comment
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.pos(),
code.pos(),
"Integer too large for 64-bit unsigned",
))]
);
@ -2623,7 +2847,10 @@ comment
tokens,
vec![Ok(Token {
kind: AbstractLiteral,
value: Value::AbstractLiteral(ast::AbstractLiteral::Integer(u64::MAX)),
value: Value::AbstractLiteral(
Latin1String::from_utf8_unchecked(&u64::MAX.to_string()),
ast::AbstractLiteral::Integer(u64::MAX)
),
pos: code.pos(),
comments: None,
})]
@ -2643,7 +2870,7 @@ comment
pos: code.s1("begin").pos(),
comments: None,
}),
Err(Diagnostic::syntax_error(&code.s1("!"), "Illegal token")),
Err(Diagnostic::syntax_error(code.s1("!"), "Illegal token")),
Ok(Token {
kind: End,
value: Value::None,
@ -2676,7 +2903,7 @@ comment
assert_eq!(
tokens,
vec![Err(Diagnostic::syntax_error(
&code.s1("/* final"),
code.s1("/* final"),
"Incomplete multi-line comment",
))]
);
@ -2783,7 +3010,10 @@ bar*/
tokens,
vec![Ok(Token {
kind: AbstractLiteral,
value: Value::AbstractLiteral(ast::AbstractLiteral::Integer(2)),
value: Value::AbstractLiteral(
Latin1String::new(b"2"),
ast::AbstractLiteral::Integer(2)
),
pos: code.s1("2").pos(),
comments: Some(Box::new(TokenComments {
leading: vec![Comment {

View file

@ -236,7 +236,7 @@ impl<'a> TokenStream<'a> {
pub fn pop_optional_ident(&self) -> Option<Ident> {
self.pop_if_kind(Identifier)
.map(|id| self.get_token(id).to_identifier_value(id).unwrap())
.map(|id| self.index(id).to_identifier_value(id).unwrap())
}
pub fn expect_ident(&self) -> DiagnosticResult<Ident> {
@ -290,10 +290,14 @@ impl<'a> TokenStream<'a> {
}
impl<'a> TokenAccess for TokenStream<'a> {
fn get_token(&self, id: TokenId) -> &Token {
fn get_token(&self, id: TokenId) -> Option<&Token> {
self.tokens[self.token_offset.get()..].get_token(id)
}
fn index(&self, id: TokenId) -> &Token {
self.tokens[self.token_offset.get()..].index(id)
}
fn get_token_slice(&self, start_id: TokenId, end_id: TokenId) -> &[Token] {
self.tokens[self.token_offset.get()..].get_token_slice(start_id, end_id)
}
@ -558,12 +562,12 @@ end arch;
let tokens = code.tokenize();
assert_eq!(
tokens[0],
stream.get_token(stream.get_current_token_id()).clone()
stream.index(stream.get_current_token_id()).clone()
);
stream.skip();
assert_eq!(
tokens[1],
stream.get_token(stream.get_current_token_id()).clone()
stream.index(stream.get_current_token_id()).clone()
);
stream
.skip_until(|kind| kind == SemiColon)
@ -571,12 +575,12 @@ end arch;
stream.slice_tokens();
assert_eq!(
tokens[3],
stream.get_token(stream.get_current_token_id()).clone()
stream.index(stream.get_current_token_id()).clone()
);
stream.skip();
assert_eq!(
tokens[4],
stream.get_token(stream.get_current_token_id()).clone()
stream.index(stream.get_current_token_id()).clone()
);
}
}

View file

@ -12,6 +12,7 @@ use super::range::{parse_array_index_constraint, parse_range};
use super::subprogram::parse_subprogram_declaration;
use super::subtype_indication::parse_subtype_indication;
use super::tokens::{Kind::*, TokenSpan};
use crate::ast::token_range::WithTokenSpan;
use crate::ast::*;
use crate::ast::{AbstractLiteral, Range};
use crate::named_entity::Reference;
@ -19,6 +20,7 @@ use crate::syntax::names::parse_type_mark;
use crate::syntax::recover::{expect_semicolon, expect_semicolon_or_last};
use itertools::Itertools;
use vhdl_lang::syntax::parser::ParsingContext;
use vhdl_lang::TokenId;
/// LRM 5.2.2 Enumeration types
fn parse_enumeration_type_definition(ctx: &mut ParsingContext<'_>) -> ParseResult<TypeDefinition> {
@ -64,9 +66,13 @@ fn parse_array_index_constraints(ctx: &mut ParsingContext<'_>) -> ParseResult<Ve
/// LRM 5.3.2 Array types
fn parse_array_type_definition(ctx: &mut ParsingContext<'_>) -> ParseResult<TypeDefinition> {
let index_constraints = parse_array_index_constraints(ctx)?;
ctx.stream.expect_kind(Of)?;
let of_token = ctx.stream.expect_kind(Of)?;
let element_subtype = parse_subtype_indication(ctx)?;
Ok(TypeDefinition::Array(index_constraints, element_subtype))
Ok(TypeDefinition::Array(
index_constraints,
of_token,
element_subtype,
))
}
/// LRM 5.3.3 Record types
@ -88,12 +94,13 @@ fn parse_record_type_definition(
.into_iter()
.map(WithDecl::new)
.collect_vec();
ctx.stream.expect_kind(Colon)?;
let colon_token = ctx.stream.expect_kind(Colon)?;
let subtype = parse_subtype_indication(ctx)?;
let end_token = expect_semicolon_or_last(ctx);
elem_decls.push(ElementDeclaration {
idents,
subtype: subtype.clone(),
colon_token,
span: TokenSpan::new(start_token, end_token),
});
}
@ -142,6 +149,7 @@ pub fn parse_protected_type_declaration(
fn parse_physical_type_definition(
ctx: &mut ParsingContext<'_>,
range: Range,
units_token: TokenId,
) -> ParseResult<(TypeDefinition, Option<Ident>)> {
let primary_unit = WithDecl::new(ctx.stream.expect_ident()?);
expect_semicolon(ctx);
@ -164,12 +172,13 @@ fn parse_physical_type_definition(
value_token_id,
AbstractLiteral => {
let value = value_token.to_abstract_literal(value_token_id)?.item;
let unit_token = ctx.stream.get_current_token_id();
let unit = ctx.stream.expect_ident()?;
PhysicalLiteral {value, unit: unit.into_ref()}
WithTokenSpan::new(PhysicalLiteral {value, unit: unit.into_ref()}, TokenSpan::new(value_token_id, unit_token))
},
Identifier => {
let unit = value_token.to_identifier_value(value_token_id)?;
PhysicalLiteral {value: AbstractLiteral::Integer(1), unit: unit.into_ref()}
WithTokenSpan::new(PhysicalLiteral {value: AbstractLiteral::Integer(1), unit: unit.into_ref()}, TokenSpan::new(value_token_id, value_token_id))
}
)
};
@ -187,6 +196,7 @@ fn parse_physical_type_definition(
Ok((
TypeDefinition::Physical(PhysicalTypeDeclaration {
range,
units_token,
primary_unit,
secondary_units,
}),
@ -229,13 +239,13 @@ pub fn parse_type_declaration(ctx: &mut ParsingContext<'_>) -> ParseResult<TypeD
Range => {
let constraint = parse_range(ctx)?.item;
expect_token!(
ctx.stream, token,
ctx.stream, token, token_id,
SemiColon => {
ctx.stream.back(); // The ';' is consumed at the end of the function
TypeDefinition::Numeric(constraint)
},
Units => {
let (def, end_ident) = parse_physical_type_definition(ctx, constraint)?;
let (def, end_ident) = parse_physical_type_definition(ctx, constraint, token_id)?;
end_ident_pos = check_end_identifier_mismatch(ctx, &ident.tree, end_ident);
def
}
@ -400,6 +410,7 @@ mod tests {
vec![ArrayIndex::IndexSubtypeDefintion(
code.s1("natural").type_mark(),
)],
code.s1("of").token(),
code.s1("boolean").subtype_indication(),
),
end_ident_pos: None,
@ -419,10 +430,11 @@ mod tests {
span: code.token_span(),
ident: code.s1("foo").decl_ident(),
def: TypeDefinition::Array(
vec![ArrayIndex::Discrete(DiscreteRange::Discrete(
code.s1("natural").type_mark(),
None,
vec![ArrayIndex::Discrete(WithTokenSpan::new(
DiscreteRange::Discrete(code.s1("natural").type_mark(), None),
code.s1("natural").token_span(),
))],
code.s1("of").token(),
code.s1("boolean").subtype_indication(),
),
end_ident_pos: None,
@ -442,10 +454,11 @@ mod tests {
span: code.token_span(),
ident: code.s1("foo").decl_ident(),
def: TypeDefinition::Array(
vec![ArrayIndex::Discrete(DiscreteRange::Discrete(
code.s1("lib.pkg.foo").type_mark(),
None,
vec![ArrayIndex::Discrete(WithTokenSpan::new(
DiscreteRange::Discrete(code.s1("lib.pkg.foo").type_mark(), None),
code.s1("lib.pkg.foo").token_span(),
))],
code.s1("of").token(),
code.s1("boolean").subtype_indication(),
),
end_ident_pos: None,
@ -465,9 +478,11 @@ mod tests {
span: code.token_span(),
ident: code.s1("foo").decl_ident(),
def: TypeDefinition::Array(
vec![ArrayIndex::Discrete(DiscreteRange::Range(
code.s1("arr_t'range").range(),
vec![ArrayIndex::Discrete(WithTokenSpan::new(
DiscreteRange::Range(code.s1("arr_t'range").range()),
code.s1("arr_t'range").token_span(),
))],
code.s1("of").token(),
code.s1("boolean").subtype_indication(),
),
end_ident_pos: None,
@ -483,12 +498,19 @@ mod tests {
fn parse_array_type_definition_with_constraint() {
let code = Code::new("type foo is array (2-1 downto 0) of boolean;");
let index = ArrayIndex::Discrete(DiscreteRange::Range(code.s1("2-1 downto 0").range()));
let index = ArrayIndex::Discrete(WithTokenSpan::new(
DiscreteRange::Range(code.s1("2-1 downto 0").range()),
code.s1("2-1 downto 0").token_span(),
));
let type_decl = TypeDeclaration {
span: code.token_span(),
ident: code.s1("foo").decl_ident(),
def: TypeDefinition::Array(vec![index], code.s1("boolean").subtype_indication()),
def: TypeDefinition::Array(
vec![index],
code.s1("of").token(),
code.s1("boolean").subtype_indication(),
),
end_ident_pos: None,
};
@ -502,7 +524,10 @@ mod tests {
fn parse_array_type_definition_mixed() {
let code = Code::new("type foo is array (2-1 downto 0, integer range <>) of boolean;");
let index0 = ArrayIndex::Discrete(DiscreteRange::Range(code.s1("2-1 downto 0").range()));
let index0 = ArrayIndex::Discrete(WithTokenSpan::new(
DiscreteRange::Range(code.s1("2-1 downto 0").range()),
code.s1("2-1 downto 0").token_span(),
));
let index1 = ArrayIndex::IndexSubtypeDefintion(code.s1("integer").type_mark());
@ -511,6 +536,7 @@ mod tests {
ident: code.s1("foo").decl_ident(),
def: TypeDefinition::Array(
vec![index0, index1],
code.s1("of").token(),
code.s1("boolean").subtype_indication(),
),
end_ident_pos: None,
@ -534,6 +560,7 @@ end record;",
let elem_decl = ElementDeclaration {
idents: vec![code.s1("element").decl_ident()],
subtype: code.s1("boolean").subtype_indication(),
colon_token: code.s1(":").token(),
span: code.s1("element : boolean;").token_span(),
};
@ -565,12 +592,14 @@ end foo;",
code.s1("element").decl_ident(),
code.s1("field").decl_ident(),
],
colon_token: code.s1(":").token(),
subtype: code.s1("boolean").subtype_indication(),
span: code.s1("element, field : boolean;").token_span(),
};
let elem_decl1 = ElementDeclaration {
idents: vec![code.s1("other_element").decl_ident()],
colon_token: code.s(":", 2).token(),
subtype: code.s1("std_logic_vector(0 to 1)").subtype_indication(),
span: code
.s1("other_element : std_logic_vector(0 to 1);")
@ -774,6 +803,7 @@ end units phys;
ident: code.s1("phys").decl_ident(),
def: TypeDefinition::Physical(PhysicalTypeDeclaration {
range: code.s1("0 to 15").range(),
units_token: code.s1("units").token(),
primary_unit: code.s1("primary_unit").decl_ident(),
secondary_units: vec![]
}),
@ -800,13 +830,17 @@ end units;
ident: code.s1("phys").decl_ident(),
def: TypeDefinition::Physical(PhysicalTypeDeclaration {
range: code.s1("0 to 15").range(),
units_token: code.s1("units").token(),
primary_unit: code.s1("primary_unit").decl_ident(),
secondary_units: vec![(
code.s1("secondary_unit").decl_ident(),
PhysicalLiteral {
value: AbstractLiteral::Integer(5),
unit: code.s("primary_unit", 2).ident().into_ref()
}
WithTokenSpan::new(
PhysicalLiteral {
value: AbstractLiteral::Integer(5),
unit: code.s("primary_unit", 2).ident().into_ref()
},
code.s1("5 primary_unit").token_span()
)
),]
}),
end_ident_pos: None,
@ -832,13 +866,17 @@ end units;
ident: code.s1("phys").decl_ident(),
def: TypeDefinition::Physical(PhysicalTypeDeclaration {
range: code.s1("0 to 15").range(),
units_token: code.s1("units").token(),
primary_unit: code.s1("primary_unit").decl_ident(),
secondary_units: vec![(
code.s1("secondary_unit").decl_ident(),
PhysicalLiteral {
value: AbstractLiteral::Integer(1),
unit: code.s("primary_unit", 2).ident().into_ref()
}
WithTokenSpan::new(
PhysicalLiteral {
value: AbstractLiteral::Integer(1),
unit: code.s("primary_unit", 2).ident().into_ref()
},
code.s("primary_unit", 2).token_span()
)
),]
}),
end_ident_pos: None,

View file

@ -8,12 +8,12 @@ use crate::ast::token_range::WithTokenSpan;
use crate::ast::{ElementMode, ModeViewDeclaration, ModeViewElement, WithDecl};
use crate::syntax::common::ParseResult;
use crate::syntax::interface_declaration::parse_optional_mode;
use crate::syntax::names::parse_name;
use crate::syntax::names::{parse_identifier_list, parse_name};
use crate::syntax::parser::ParsingContext;
use crate::syntax::recover::expect_semicolon_or_last;
use crate::syntax::separated_list::parse_ident_list;
use crate::syntax::subtype_indication::parse_subtype_indication;
use crate::syntax::Kind::*;
use itertools::Itertools;
use vhdl_lang::syntax::common::check_end_identifier_mismatch;
use vhdl_lang::TokenSpan;
@ -28,12 +28,12 @@ pub(crate) fn parse_mode_view_declaration(
let ident = WithDecl::new(ctx.stream.expect_ident()?);
ctx.stream.expect_kind(Of)?;
let typ = parse_subtype_indication(ctx)?;
ctx.stream.expect_kind(Is)?;
let is_token = ctx.stream.expect_kind(Is)?;
let mut elements = Vec::new();
while ctx.stream.peek_kind() != Some(End) {
elements.push(parse_mode_view_element_definition(ctx)?);
}
ctx.stream.expect_kind(End)?;
let end_token = ctx.stream.expect_kind(End)?;
ctx.stream.expect_kind(View)?;
let end_ident_pos =
check_end_identifier_mismatch(ctx, &ident.tree, ctx.stream.pop_optional_ident());
@ -42,7 +42,9 @@ pub(crate) fn parse_mode_view_declaration(
ModeViewDeclaration {
ident,
typ,
is_token,
elements,
end_token,
end_ident_pos,
},
TokenSpan::new(start_tok, end_tok),
@ -54,14 +56,18 @@ pub(crate) fn parse_mode_view_element_definition(
ctx: &mut ParsingContext<'_>,
) -> ParseResult<ModeViewElement> {
let start = ctx.stream.get_current_token_id();
let element_list = parse_ident_list(ctx)?;
ctx.stream.expect_kind(Colon)?;
let names = parse_identifier_list(ctx)?
.into_iter()
.map(WithDecl::new)
.collect_vec();
let colon_token = ctx.stream.expect_kind(Colon)?;
let mode = parse_element_mode_indication(ctx)?;
let end_token = expect_semicolon_or_last(ctx);
Ok(ModeViewElement {
span: TokenSpan::new(start, end_token),
mode,
names: element_list,
colon_token,
names,
})
}
@ -172,7 +178,8 @@ mod tests {
assert_eq!(
code.parse_ok_no_diagnostics(parse_mode_view_element_definition),
ModeViewElement {
names: code.s1("foo").ident_list(),
names: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
mode: code.s1("in").element_mode(),
span: code.token_span(),
}
@ -182,7 +189,12 @@ mod tests {
assert_eq!(
code.parse_ok_no_diagnostics(parse_mode_view_element_definition),
ModeViewElement {
names: code.s1("foo, bar, baz").ident_list(),
names: vec![
code.s1("foo").decl_ident(),
code.s1("bar").decl_ident(),
code.s1("baz").decl_ident()
],
colon_token: code.s1(":").token(),
mode: code.s1("linkage").element_mode(),
span: code.token_span(),
}
@ -192,7 +204,8 @@ mod tests {
assert_eq!(
code.parse_ok_no_diagnostics(parse_mode_view_element_definition),
ModeViewElement {
names: code.s1("foo").ident_list(),
names: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
mode: code.s1("view bar").element_mode(),
span: code.token_span(),
}
@ -202,7 +215,8 @@ mod tests {
assert_eq!(
code.parse_ok_no_diagnostics(parse_mode_view_element_definition),
ModeViewElement {
names: code.s1("foo").ident_list(),
names: vec![code.s1("foo").decl_ident()],
colon_token: code.s1(":").token(),
mode: code.s1("view (bar_array)").element_mode(),
span: code.token_span(),
}
@ -224,7 +238,9 @@ end view;
ModeViewDeclaration {
ident: code.s1("foo").decl_ident(),
typ: code.s1("bar").subtype_indication(),
is_token: code.s1("is").token(),
elements: Vec::new(),
end_token: code.s1("end").token(),
end_ident_pos: None,
},
code.token_span()
@ -244,7 +260,9 @@ end view foo;
ModeViewDeclaration {
ident: code.s1("foo").decl_ident(),
typ: code.s1("bar").subtype_indication(),
is_token: code.s1("is").token(),
elements: Vec::new(),
end_token: code.s1("end").token(),
end_ident_pos: Some(code.s("foo", 2).token()),
},
code.token_span()
@ -265,7 +283,9 @@ end view baz;
ModeViewDeclaration {
ident: code.s1("foo").decl_ident(),
typ: code.s1("bar").subtype_indication(),
is_token: code.s1("is").token(),
elements: Vec::new(),
end_token: code.s1("end").token(),
end_ident_pos: None
},
code.token_span()
@ -296,12 +316,14 @@ end view;
code.parse_ok_no_diagnostics(parse_mode_view_declaration),
WithTokenSpan::new(
ModeViewDeclaration {
typ: code.s1("bar").subtype_indication(),
ident: code.s1("foo").decl_ident(),
typ: code.s1("bar").subtype_indication(),
is_token: code.s1("is").token(),
elements: vec![
code.s1("baz: in;").mode_view_element(),
code.s1("foo: view some_view;").mode_view_element(),
],
end_token: code.s1("end").token(),
end_ident_pos: None,
},
code.token_span()

View file

@ -4,30 +4,44 @@
//
// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com
use crate::ast::token_range::WithTokenSpan;
use crate::ast::{DelayMechanism, Waveform, WaveformElement};
use crate::syntax::parser::ParsingContext;
use vhdl_lang::TokenSpan;
use super::common::{parse_optional, ParseResult};
use super::expression::parse_expression;
use super::tokens::Kind::*;
/// LRM 10.5 Signal assignment statement
pub fn parse_delay_mechanism(ctx: &mut ParsingContext<'_>) -> ParseResult<Option<DelayMechanism>> {
pub fn parse_delay_mechanism(
ctx: &mut ParsingContext<'_>,
) -> ParseResult<Option<WithTokenSpan<DelayMechanism>>> {
let token = ctx.stream.peek_expect()?;
let start_token = ctx.stream.get_current_token_id();
match token.kind {
Transport => {
ctx.stream.skip();
Ok(Some(DelayMechanism::Transport))
let span = TokenSpan::new(start_token, start_token);
Ok(Some(WithTokenSpan::new(DelayMechanism::Transport, span)))
}
Inertial => {
ctx.stream.skip();
Ok(Some(DelayMechanism::Inertial { reject: None }))
let span = TokenSpan::new(start_token, start_token);
Ok(Some(WithTokenSpan::new(
DelayMechanism::Inertial { reject: None },
span,
)))
}
Reject => {
ctx.stream.skip();
let reject = Some(parse_expression(ctx)?);
ctx.stream.expect_kind(Inertial)?;
Ok(Some(DelayMechanism::Inertial { reject }))
let end_token = ctx.stream.expect_kind(Inertial)?;
let span = TokenSpan::new(start_token, end_token);
Ok(Some(WithTokenSpan::new(
DelayMechanism::Inertial { reject },
span,
)))
}
_ => Ok(None),
}
@ -35,8 +49,8 @@ pub fn parse_delay_mechanism(ctx: &mut ParsingContext<'_>) -> ParseResult<Option
/// LRM 10.5 Signal assignment statement
pub fn parse_waveform(ctx: &mut ParsingContext<'_>) -> ParseResult<Waveform> {
if ctx.stream.skip_if_kind(Unaffected) {
return Ok(Waveform::Unaffected);
if let Some(token) = ctx.stream.pop_if_kind(Unaffected) {
return Ok(Waveform::Unaffected(token));
}
let mut elems = Vec::new();
@ -64,7 +78,10 @@ mod tests {
let code = Code::new("transport");
assert_eq!(
code.with_stream(parse_delay_mechanism),
Some(DelayMechanism::Transport)
Some(WithTokenSpan::new(
DelayMechanism::Transport,
code.token_span()
))
);
}
@ -73,7 +90,10 @@ mod tests {
let code = Code::new("inertial");
assert_eq!(
code.with_stream(parse_delay_mechanism),
Some(DelayMechanism::Inertial { reject: None })
Some(WithTokenSpan::new(
DelayMechanism::Inertial { reject: None },
code.token_span()
))
);
}
@ -82,9 +102,12 @@ mod tests {
let code = Code::new("reject 2 ns inertial");
assert_eq!(
code.with_stream(parse_delay_mechanism),
Some(DelayMechanism::Inertial {
reject: Some(code.s1("2 ns").expr())
})
Some(WithTokenSpan::new(
DelayMechanism::Inertial {
reject: Some(code.s1("2 ns").expr())
},
code.token_span()
))
);
}
@ -133,6 +156,9 @@ mod tests {
#[test]
fn test_unaffected_waveform() {
let code = Code::new("unaffected");
assert_eq!(code.with_stream(parse_waveform), Waveform::Unaffected);
assert_eq!(
code.with_stream(parse_waveform),
Waveform::Unaffected(code.token())
);
}
}

View file

@ -0,0 +1,81 @@
use std::error::Error;
use std::fs;
use std::iter::zip;
use std::path::{Path, PathBuf};
use vhdl_lang::{Diagnostic, SeverityMap, Source, VHDLFormatter, VHDLParser, VHDLStandard};
// excluded file contains PSL statements
const EXCLUDED_FILES: [&str; 1] = ["vunit/examples/vhdl/array_axis_vcs/src/fifo.vhd"];
fn format_file(path: &Path) -> Result<(), Box<dyn Error>> {
let severity_map = SeverityMap::default();
let parser = VHDLParser::new(VHDLStandard::default());
let mut diagnostics = Vec::new();
let (_, design_file) = parser.parse_design_file(path, &mut diagnostics)?;
if !diagnostics.is_empty() {
for diagnostic in diagnostics {
println!("{}", diagnostic.show(&severity_map).unwrap())
}
panic!("Found diagnostics with severity error in the example project");
}
let result = VHDLFormatter::format_design_file(&design_file);
let mut diagnostics: Vec<Diagnostic> = Vec::new();
let new_file = parser.parse_design_source(&Source::inline(path, &result), &mut diagnostics);
if !diagnostics.is_empty() {
for diagnostic in diagnostics {
println!("{}", diagnostic.show(&severity_map).unwrap())
}
panic!("Formatting failed! File was OK before, but is not after");
}
for ((tokens_a, _), (tokens_b, _)) in zip(new_file.design_units, design_file.design_units) {
for (a, b) in zip(tokens_a, tokens_b) {
if !a.equal_format(&b) {
println!("Token mismatch");
println!("New Token={a:#?}");
let contents = a.pos.source.contents();
let a_line = contents.get_line(a.pos.range.start.line as usize).unwrap();
println!(" {a_line}");
println!("Old Token={b:#?}");
let b_line = result.lines().nth(b.pos.range.start.line as usize).unwrap();
println!(" {b_line}");
panic!("Token Mismatch")
}
}
}
Ok(())
}
fn format_dir(path: &Path) -> Result<(), Box<dyn Error>> {
for entry in fs::read_dir(path)? {
let entry = entry?;
let file_type = entry.file_type()?;
if file_type.is_dir() {
format_dir(&entry.path())?
} else if let Some(extension) = entry.path().extension() {
if (extension == "vhd" || extension == "vhdl") && !is_file_excluded(&entry.path()) {
format_file(&entry.path())?
}
}
}
Ok(())
}
fn is_file_excluded(path: &Path) -> bool {
for file in EXCLUDED_FILES {
if path.ends_with(file) {
return true;
}
}
false
}
// Checks that all files in the example project are correctly formatted
// while retaining their token stream.
#[test]
fn formats_all_vhdl_files_without_producing_different_code() -> Result<(), Box<dyn Error>> {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("../example_project");
format_dir(&path)
}