use super::common::ParseResult;
use super::expression::{parse_expression, parse_expression_initial_token};
use super::subprogram::parse_signature;
use super::subtype_indication::parse_subtype_indication;
use super::tokens::{Kind::*, Token, TokenStream, Value};
use crate::ast;
use crate::ast::*;
use crate::data::{Diagnostic, WithPos};
pub fn parse_designator(stream: &mut TokenStream) -> ParseResult<WithPos<Designator>> {
let token = stream.expect()?;
Ok(try_token_kind!(
token,
Identifier => token.expect_ident()?.map_into(Designator::Identifier),
StringLiteral => token.expect_operator_symbol()?.map_into(Designator::OperatorSymbol),
Character => token.expect_character()?.map_into(Designator::Character)
))
}
pub fn parse_selected_name(stream: &mut TokenStream) -> ParseResult<WithPos<SelectedName>> {
let mut name = parse_designator(stream)?
.into_ref()
.map_into(SelectedName::Designator);
loop {
if !stream.skip_if_kind(Dot)? {
break;
}
let suffix = parse_designator(stream)?.into_ref();
let pos = suffix.pos.combine(&name.pos);
name = WithPos::from(SelectedName::Selected(Box::new(name), suffix), pos);
}
Ok(name)
}
pub fn parse_type_mark(stream: &mut TokenStream) -> ParseResult<WithPos<TypeMark>> {
let name = parse_selected_name(stream)?;
parse_type_mark_starting_with_name(stream, name)
}
pub fn parse_type_mark_starting_with_name(
stream: &mut TokenStream,
name: WithPos<SelectedName>,
) -> ParseResult<WithPos<TypeMark>> {
let state = stream.state();
if stream.pop_if_kind(Tick)?.is_some() {
let subtype_token = stream.expect()?;
if subtype_token.kind == Subtype {
return Ok(WithPos {
pos: subtype_token.pos.combine_into(&name.pos),
item: TypeMark {
name,
attr: Some(TypeAttribute::Subtype),
},
});
} else if subtype_token.kind == Identifier
&& matches!(subtype_token.value, Value::Identifier(ref sym) if sym == stream.element_sym())
{
return Ok(WithPos {
pos: subtype_token.pos.combine_into(&name.pos),
item: TypeMark {
name,
attr: Some(TypeAttribute::Element),
},
});
}
stream.set_state(state);
};
Ok(WithPos {
pos: name.pos.clone(),
item: TypeMark { name, attr: None },
})
}
pub fn into_selected_name(name: WithPos<Name>) -> ParseResult<WithPos<SelectedName>> {
match name.item {
Name::Selected(prefix, suffix) => {
let pos = suffix.pos.combine(&prefix.pos);
Ok(WithPos::from(
SelectedName::Selected(Box::new(into_selected_name(*prefix)?), suffix),
pos,
))
}
Name::Designator(designator) => Ok(WithPos::from(
SelectedName::Designator(designator),
name.pos,
)),
_ => Err(Diagnostic::error(&name, "Expected selected name")),
}
}
pub fn expression_to_ident(name: WithPos<Expression>) -> ParseResult<Ident> {
let name = expression_to_name(name)?;
to_simple_name(name)
}
pub fn parse_identifier_list(stream: &mut TokenStream) -> ParseResult<Vec<Ident>> {
let mut idents = Vec::new();
loop {
idents.push(stream.expect_ident()?);
if let Some(token) = stream.peek()? {
if token.kind == Comma {
stream.move_after(&token);
continue;
}
}
break;
}
Ok(idents)
}
fn expression_to_name(expr: WithPos<Expression>) -> ParseResult<WithPos<Name>> {
match expr.item {
Expression::Name(name) => Ok(WithPos {
item: *name,
pos: expr.pos,
}),
Expression::Literal(Literal::String(val)) => {
if let Some(op) = Operator::from_latin1(val) {
Ok(WithPos {
item: Name::Designator(Designator::OperatorSymbol(op).into_ref()),
pos: expr.pos,
})
} else {
Err(Diagnostic::error(expr.pos, "Invalid operator symbol"))
}
}
Expression::Literal(Literal::Character(val)) => Ok(WithPos {
item: Name::Designator(Designator::Character(val).into_ref()),
pos: expr.pos,
}),
_ => Err(Diagnostic::error(&expr, "Expected name")),
}
}
fn actual_to_expression(actual: WithPos<ActualPart>) -> ParseResult<WithPos<Expression>> {
match actual.item {
ActualPart::Expression(expr) => Ok(WithPos::from(expr, actual.pos)),
_ => Err(Diagnostic::error(&actual, "Expected expression")),
}
}
fn actual_part_to_name(actual: WithPos<ActualPart>) -> ParseResult<WithPos<Name>> {
match actual.item {
ActualPart::Expression(expr) => expression_to_name(WithPos::from(expr, actual.pos)),
_ => Err(Diagnostic::error(&actual, "Expected name")),
}
}
fn assoc_to_expression(assoc: AssociationElement) -> ParseResult<WithPos<Expression>> {
match assoc.formal {
Some(name) => Err(Diagnostic::error(&name, "Expected expression")),
None => actual_to_expression(assoc.actual),
}
}
fn parse_actual_part(stream: &mut TokenStream) -> ParseResult<WithPos<ActualPart>> {
let token = stream.expect()?;
if token.kind == Open {
Ok(WithPos::from(ActualPart::Open, token))
} else {
Ok(parse_expression_initial_token(stream, token)?.map_into(ActualPart::Expression))
}
}
fn parse_association_element(stream: &mut TokenStream) -> ParseResult<AssociationElement> {
let actual = parse_actual_part(stream)?;
if stream.skip_if_kind(RightArrow)? {
Ok(AssociationElement {
formal: Some(actual_part_to_name(actual)?),
actual: parse_actual_part(stream)?,
})
} else {
Ok(AssociationElement {
formal: None,
actual,
})
}
}
pub fn parse_association_list(stream: &mut TokenStream) -> ParseResult<Vec<AssociationElement>> {
stream.expect_kind(LeftPar)?;
parse_association_list_no_leftpar(stream)
}
pub fn parse_association_list_no_leftpar(
stream: &mut TokenStream,
) -> ParseResult<Vec<AssociationElement>> {
let mut association_elements = Vec::with_capacity(1);
loop {
association_elements.push(parse_association_element(stream)?);
let token = stream.expect()?;
try_token_kind!(
token,
Comma => {},
RightPar => {
return Ok(association_elements);
}
)
}
}
fn parse_function_call(
stream: &mut TokenStream,
prefix: WithPos<Name>,
first: AssociationElement,
) -> ParseResult<WithPos<Name>> {
let mut association_elements = Vec::new();
association_elements.push(first);
loop {
association_elements.push(parse_association_element(stream)?);
let token = stream.expect()?;
try_token_kind!(
token,
Comma => {},
RightPar => {
let pos = token.pos.combine_into(&prefix);
return Ok(WithPos {
item: Name::CallOrIndexed(Box::new(CallOrIndexed {
name: prefix,
parameters: association_elements})),
pos,
});
}
)
}
}
fn parse_attribute_name(
stream: &mut TokenStream,
name: WithPos<Name>,
signature: Option<WithPos<Signature>>,
) -> ParseResult<WithPos<Name>> {
let attr = stream.expect_attribute_designator()?;
let (expression, pos) = {
if stream.skip_if_kind(LeftPar)? {
let ret = Some(parse_expression(stream)?);
let rpar_token = stream.expect_kind(RightPar)?;
(ret, rpar_token.pos.combine_into(&name))
} else {
(None, attr.pos.combine(&name))
}
};
Ok(WithPos {
item: Name::Attribute(Box::new(AttributeName {
name,
attr,
signature,
expr: expression.map(Box::new),
})),
pos,
})
}
enum DesignatorOrAll {
Designator(Designator),
All,
}
fn to_suffix(token: Token) -> ParseResult<WithPos<DesignatorOrAll>> {
let name = {
try_token_kind!(
token,
Identifier => token.expect_ident()?.map_into(|ident| DesignatorOrAll::Designator(Designator::Identifier(ident))),
Character => token.expect_character()?.map_into(|byte| DesignatorOrAll::Designator(Designator::Character(byte))),
StringLiteral => token.expect_operator_symbol()?.map_into(|string| DesignatorOrAll::Designator(Designator::OperatorSymbol(string))),
All => WithPos::from(DesignatorOrAll::All, token)
)
};
Ok(name)
}
fn parse_inner_external_name(stream: &mut TokenStream) -> ParseResult<ExternalName> {
let token = stream.expect()?;
let class = try_token_kind!(
token,
Signal => ExternalObjectClass::Signal,
Constant => ExternalObjectClass::Constant,
Variable => ExternalObjectClass::Variable);
let token = stream.expect()?;
let path = try_token_kind!(
token,
CommAt => {
let path_name = parse_name(stream)?;
let path_pos = path_name.pos.clone().combine_into(&token);
WithPos::from(ExternalPath::Package(path_name), path_pos)
},
Dot => {
let path_name = parse_name(stream)?;
let path_pos = path_name.pos.clone().combine_into(&token);
WithPos::from(ExternalPath::Absolute(path_name), path_pos)
},
Circ => {
stream.expect_kind(Dot)?;
let mut up_levels = 1;
while stream.skip_if_kind(Circ)? {
stream.expect_kind(Dot)?;
up_levels += 1;
}
let path_name = parse_name(stream)?;
let path_pos = path_name.pos.clone().combine_into(&token);
WithPos::from(ExternalPath::Relative(path_name, up_levels), path_pos)
},
Identifier => {
let path_name = parse_name_initial_token(stream, token)?;
let path_pos = path_name.pos.clone();
WithPos::from(ExternalPath::Relative(path_name, 0), path_pos)
}
);
stream.expect_kind(Colon)?;
let subtype = parse_subtype_indication(stream)?;
Ok(ExternalName {
class,
path,
subtype,
})
}
pub fn parse_name_initial_token(
stream: &mut TokenStream,
token: Token,
) -> ParseResult<WithPos<Name>> {
let mut name = {
if token.kind == LtLt {
stream.move_after(&token);
let external_name = Name::External(Box::new(parse_inner_external_name(stream)?));
let end_token = stream.expect_kind(GtGt)?;
WithPos::from(external_name, token.pos.combine_into(&end_token))
} else {
let suffix = to_suffix(token)?;
match suffix.item {
DesignatorOrAll::Designator(designator) => {
WithPos::from(Name::Designator(designator.into_ref()), suffix.pos)
}
DesignatorOrAll::All => {
return Err(Diagnostic::error(
suffix.pos,
"Illegal prefix 'all' for name",
));
}
}
}
};
while let Some(token) = stream.peek()? {
match token.kind {
Dot => {
stream.move_after(&token);
let suffix_token = stream.expect()?;
let pos = name.pos.combine(&suffix_token);
let suffix = to_suffix(suffix_token)?;
match suffix.item {
DesignatorOrAll::Designator(designator) => {
name = WithPos {
item: Name::Selected(
Box::new(name),
WithPos::from(designator.into_ref(), suffix.pos),
),
pos,
}
}
DesignatorOrAll::All => {
name = WithPos {
item: Name::SelectedAll(Box::new(name)),
pos,
}
}
}
}
LeftSquare => {
let state = stream.state();
let signature = Some(parse_signature(stream)?);
if !stream.skip_if_kind(Tick)? {
stream.set_state(state);
break;
}
name = parse_attribute_name(stream, name, signature)?;
}
Tick => {
if stream.next_kinds_are(&[Tick, LeftPar])? {
break;
}
stream.move_after(&token);
let signature = None;
name = parse_attribute_name(stream, name, signature)?;
}
LeftPar => {
stream.move_after(&token);
let assoc = parse_association_element(stream)?;
let sep_token = stream.expect()?;
try_token_kind!(
sep_token,
Comma => {
name = parse_function_call(stream, name, assoc)?;
},
To | Downto => {
let right_expr = parse_expression(stream)?;
let direction = {
if sep_token.kind == To {
Direction::Ascending
} else {
Direction::Descending
}
};
let rpar_token = stream.expect_kind(RightPar)?;
let pos = rpar_token.pos.combine_into(&name);
let discrete_range =
DiscreteRange::Range(ast::Range::Range(RangeConstraint {
left_expr: Box::new(assoc_to_expression(assoc)?),
direction,
right_expr: Box::new(right_expr),
}));
name = WithPos {
item: Name::Slice(Box::new(name), Box::new(discrete_range)),
pos,
};
},
RightPar => {
let pos = sep_token.pos.combine_into(&name);
let item = match into_range(assoc) {
Ok(range) => Name::Slice(Box::new(name), Box::new(DiscreteRange::Range(range))),
Err(assoc) => Name::CallOrIndexed(Box::new(CallOrIndexed {
name,
parameters: vec![assoc],
})),
};
name = WithPos::new(item, pos);
}
)
}
_ => {
break;
}
}
}
Ok(name)
}
pub fn into_range(assoc: AssociationElement) -> Result<ast::Range, AssociationElement> {
if assoc.formal.is_some() {
return Err(assoc);
}
if let ActualPart::Expression(Expression::Name(ref name)) = &assoc.actual.item {
if let Name::Attribute(attr) = name.as_ref() {
if attr.as_range().is_some() {
if let ActualPart::Expression(Expression::Name(name)) = assoc.actual.item {
if let Name::Attribute(attr) = *name {
return Ok(ast::Range::Attribute(attr));
}
}
unreachable!();
} else {
Err(assoc)
}
} else {
Err(assoc)
}
} else {
Err(assoc)
}
}
pub fn parse_name(stream: &mut TokenStream) -> ParseResult<WithPos<Name>> {
let initial_token = stream.expect()?;
parse_name_initial_token(stream, initial_token)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::syntax::test::Code;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_selected_name_single() {
let code = Code::new("foo");
assert_eq!(
code.with_stream(parse_selected_name),
code.s1("foo")
.ident()
.map_into(|sym| SelectedName::Designator(Designator::Identifier(sym).into_ref()))
);
}
#[test]
#[allow(clippy::disallowed_names)]
fn test_parse_selected_name_multiple() {
let code = Code::new("foo.bar.baz");
let baz = code
.s1("baz")
.ident()
.map_into(Designator::Identifier)
.into_ref();
let bar = code
.s1("bar")
.ident()
.map_into(Designator::Identifier)
.into_ref();
let foo = code
.s1("foo")
.ident()
.map_into(Designator::Identifier)
.into_ref()
.map_into(SelectedName::Designator);
let foo_bar = WithPos::from(
SelectedName::Selected(Box::new(foo), bar),
code.s1("foo.bar").pos(),
);
let foo_bar_baz = WithPos::from(
SelectedName::Selected(Box::new(foo_bar), baz),
code.s1("foo.bar.baz").pos(),
);
assert_eq!(code.with_stream(parse_selected_name), foo_bar_baz);
}
#[test]
fn test_identifier_list() {
let code = Code::new("foo, bar, baz");
assert_eq!(
code.with_stream(parse_identifier_list),
vec![
code.s1("foo").ident(),
code.s1("bar").ident(),
code.s1("baz").ident()
]
);
}
#[test]
fn test_simple_name() {
let code = Code::new("foo");
assert_eq!(
code.with_stream(parse_name),
WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("foo")).into_ref()),
pos: code.s1("foo").pos()
}
);
}
#[test]
fn test_characer_name() {
let code = Code::new("'a'");
assert_eq!(
code.with_stream(parse_name),
WithPos {
item: Name::Designator(Designator::Character(b'a').into_ref()),
pos: code.s1("'a'").pos()
}
);
}
#[test]
fn test_operator_symbol() {
let code = Code::new("\"+\"");
assert_eq!(
code.with_stream(parse_name),
WithPos {
item: Name::Designator(Designator::OperatorSymbol(Operator::Plus).into_ref()),
pos: code.s1("\"+\"").pos()
}
);
let code = Code::new("\"AND\"");
assert_eq!(
code.with_stream(parse_name),
WithPos {
item: Name::Designator(Designator::OperatorSymbol(Operator::And).into_ref()),
pos: code.s1("\"AND\"").pos()
}
);
let code = Code::new("\"and\"");
assert_eq!(
code.with_stream(parse_name),
WithPos {
item: Name::Designator(Designator::OperatorSymbol(Operator::And).into_ref()),
pos: code.s1("\"and\"").pos()
}
);
}
#[test]
#[allow(clippy::disallowed_names)]
fn test_selected_name() {
let code = Code::new("foo.bar.baz");
let foo = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("foo")).into_ref()),
pos: code.s1("foo").pos(),
};
let bar = WithPos {
item: Designator::Identifier(code.symbol("bar")),
pos: code.s1("bar").pos(),
};
let baz = WithPos {
item: Designator::Identifier(code.symbol("baz")),
pos: code.s1("baz").pos(),
};
let foo_bar = WithPos {
item: Name::Selected(Box::new(foo), bar.into_ref()),
pos: code.s1("foo.bar").pos(),
};
let foo_bar_baz = WithPos {
item: Name::Selected(Box::new(foo_bar), baz.into_ref()),
pos: code.s1("foo.bar.baz").pos(),
};
assert_eq!(code.with_stream(parse_name), foo_bar_baz);
}
#[test]
#[allow(clippy::disallowed_names)]
fn test_selected_name_all() {
let code = Code::new("foo.all");
let foo = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("foo")).into_ref()),
pos: code.s1("foo").pos(),
};
let foo_all = WithPos {
item: Name::SelectedAll(Box::new(foo)),
pos: code.s1("foo.all").pos(),
};
assert_eq!(code.with_stream(parse_name), foo_all);
}
#[test]
fn test_slice_name_range_to() {
let code = Code::new("prefix(0 to 3)");
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
let slice = WithPos {
item: Name::Slice(
Box::new(prefix),
Box::new(code.s1("0 to 3").discrete_range()),
),
pos: code.s1("prefix(0 to 3)").pos(),
};
assert_eq!(code.with_stream(parse_name), slice);
}
#[test]
fn test_slice_name_range_downto() {
let code = Code::new("prefix(3 downto 0)");
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
let slice = WithPos {
item: Name::Slice(
Box::new(prefix),
Box::new(code.s1("3 downto 0").discrete_range()),
),
pos: code.s1("prefix(3 downto 0)").pos(),
};
assert_eq!(code.with_stream(parse_name), slice);
}
#[test]
fn test_slice_range_attribute() {
let code = Code::new("prefix(foo(0)'range)");
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
let slice = WithPos {
item: Name::Slice(
Box::new(prefix),
Box::new(code.s1("foo(0)'range").discrete_range()),
),
pos: code.s1("prefix(foo(0)'range)").pos(),
};
assert_eq!(code.with_stream(parse_name), slice);
}
#[test]
fn test_attribute_name() {
let code = Code::new("prefix'foo");
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
let attr = WithPos {
item: Name::Attribute(Box::new(AttributeName {
name: prefix,
attr: code.s1("foo").ident().map_into(AttributeDesignator::Ident),
signature: None,
expr: None,
})),
pos: code.s1("prefix'foo").pos(),
};
assert_eq!(code.with_stream(parse_name), attr);
}
#[test]
fn test_attribute_name_range() {
let code = Code::new("prefix'range");
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
let attr = WithPos {
item: Name::Attribute(Box::new(AttributeName {
name: prefix,
attr: WithPos {
item: AttributeDesignator::Range(RangeAttribute::Range),
pos: code.s1("range").pos(),
},
signature: None,
expr: None,
})),
pos: code.s1("prefix'range").pos(),
};
assert_eq!(code.with_stream(parse_name), attr);
}
#[test]
fn test_attribute_name_subtype() {
let code = Code::new("prefix'subtype");
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
let attr = WithPos {
item: Name::Attribute(Box::new(AttributeName {
name: prefix,
attr: WithPos {
item: AttributeDesignator::Type(TypeAttribute::Subtype),
pos: code.s1("subtype").pos(),
},
signature: None,
expr: None,
})),
pos: code.s1("prefix'subtype").pos(),
};
assert_eq!(code.with_stream(parse_name), attr);
}
#[test]
fn test_attribute_name_element() {
let code = Code::new("prefix'element");
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
let attr = WithPos {
item: Name::Attribute(Box::new(AttributeName {
name: prefix,
attr: WithPos {
item: AttributeDesignator::Type(TypeAttribute::Element),
pos: code.s1("element").pos(),
},
signature: None,
expr: None,
})),
pos: code.s1("prefix'element").pos(),
};
assert_eq!(code.with_stream(parse_name), attr);
}
#[test]
fn test_type_mark_without_subtype() {
let code = Code::new("prefix");
let name = code.s1("prefix").selected_name();
assert_eq!(
code.with_stream(parse_type_mark),
WithPos {
pos: name.pos.clone(),
item: TypeMark { name, attr: None },
}
);
}
#[test]
fn test_type_mark_with_subtype() {
let code = Code::new("prefix'subtype");
assert_eq!(
code.with_stream(parse_type_mark),
WithPos {
pos: code.pos(),
item: TypeMark {
name: code.s1("prefix").selected_name(),
attr: Some(TypeAttribute::Subtype)
},
}
);
}
#[test]
fn test_type_mark_with_element() {
let code = Code::new("prefix'element");
assert_eq!(
code.with_stream(parse_type_mark),
WithPos {
pos: code.pos(),
item: TypeMark {
name: code.s1("prefix").selected_name(),
attr: Some(TypeAttribute::Element)
},
}
);
}
#[test]
fn test_attribute_name_expression() {
let code = Code::new("prefix'foo(expr+1)");
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
let attr = WithPos {
item: Name::Attribute(Box::new(AttributeName {
name: prefix,
attr: code.s1("foo").ident().map_into(AttributeDesignator::Ident),
signature: None,
expr: Some(Box::new(code.s1("expr+1").expr())),
})),
pos: code.s1("prefix'foo(expr+1)").pos(),
};
assert_eq!(code.with_stream(parse_name), attr);
}
#[test]
fn test_attribute_name_signature_expression() {
let code = Code::new("prefix[return natural]'foo(expr+1)");
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
let attr = WithPos {
item: Name::Attribute(Box::new(AttributeName {
name: prefix,
attr: code.s1("foo").ident().map_into(AttributeDesignator::Ident),
signature: Some(code.s1("[return natural]").signature()),
expr: Some(Box::new(code.s1("expr+1").expr())),
})),
pos: code.s1("prefix[return natural]'foo(expr+1)").pos(),
};
assert_eq!(code.with_stream(parse_name), attr);
}
#[test]
fn test_name_signature_no_attribute_name() {
let code = Code::new("prefix[return natural]");
let name = code.with_partial_stream(|stream| {
let result = parse_name(stream);
stream.expect_kind(LeftSquare)?;
result
});
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
assert_eq!(name, Ok(prefix));
}
#[test]
fn test_qualified_expression_is_not_name() {
let code = Code::new("prefix'(");
let name = code.with_stream(|stream| {
let result = parse_name(stream);
stream.expect_kind(Tick)?;
stream.expect_kind(LeftPar)?;
result
});
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
assert_eq!(name, prefix);
}
#[test]
#[allow(clippy::disallowed_names)]
fn test_function_call_no_formal() {
let code = Code::new("foo(0)");
let foo = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("foo")).into_ref()),
pos: code.s1("foo").pos(),
};
let foo_0 = WithPos {
item: Name::CallOrIndexed(Box::new(CallOrIndexed {
name: foo,
parameters: vec![AssociationElement {
formal: None,
actual: code.s1("0").expr().map_into(ActualPart::Expression),
}],
})),
pos: code.s1("foo(0)").pos(),
};
assert_eq!(code.with_stream(parse_name), foo_0);
}
#[test]
fn test_function_call_many() {
let code = Code::new("prefix(0, 1)(3).suffix");
let prefix = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("prefix")).into_ref()),
pos: code.s1("prefix").pos(),
};
let prefix_index = WithPos {
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),
},
],
})),
pos: code.s1("prefix(0, 1)").pos(),
};
let prefix_index_3 = WithPos {
item: Name::CallOrIndexed(Box::new(CallOrIndexed {
name: prefix_index,
parameters: vec![AssociationElement {
formal: None,
actual: code.s1("3").expr().map_into(ActualPart::Expression),
}],
})),
pos: code.s1("prefix(0, 1)(3)").pos(),
};
let suffix = WithPos {
item: Designator::Identifier(code.symbol("suffix")),
pos: code.s1("suffix").pos(),
};
let prefix_index_3_suffix = WithPos {
item: Name::Selected(Box::new(prefix_index_3), suffix.into_ref()),
pos: code.s1("prefix(0, 1)(3).suffix").pos(),
};
assert_eq!(code.with_stream(parse_name), prefix_index_3_suffix);
}
#[test]
#[allow(clippy::disallowed_names)]
fn test_function_call() {
let code = Code::new("foo(arg => 0)");
let foo = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("foo")).into_ref()),
pos: code.s1("foo").pos(),
};
let arg = WithPos {
item: Name::Designator(Designator::Identifier(code.symbol("arg")).into_ref()),
pos: code.s1("arg").pos(),
};
let assoc_elem = AssociationElement {
formal: Some(arg),
actual: code.s1("0").expr().map_into(ActualPart::Expression),
};
let foo_call = WithPos {
item: Name::CallOrIndexed(Box::new(CallOrIndexed {
name: foo,
parameters: vec![assoc_elem],
})),
pos: code.s1("foo(arg => 0)").pos(),
};
assert_eq!(code.with_stream(parse_name), foo_call);
}
#[test]
fn test_association_list_actual_part_open() {
let code = Code::new("(open, arg => open)");
let elem1 = AssociationElement {
formal: None,
actual: WithPos::new(ActualPart::Open, code.s1("open").pos()),
};
let elem2 = AssociationElement {
formal: Some(code.s1("arg").name()),
actual: WithPos::new(ActualPart::Open, code.s("open", 2)),
};
assert_eq!(code.with_stream(parse_association_list), vec![elem1, elem2]);
}
#[test]
fn test_external_name_implicit_relative() {
let code = Code::new("<< signal dut.foo : std_logic >>");
let external_name = ExternalName {
class: ExternalObjectClass::Signal,
path: WithPos::new(
ExternalPath::Relative(code.s1("dut.foo").name(), 0),
code.s1("dut.foo").pos(),
),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(
code.with_stream(parse_name),
WithPos::new(Name::External(Box::new(external_name)), code)
);
}
#[test]
fn test_external_name_explicit_relative() {
let code = Code::new("<< signal ^.dut.gen(0) : std_logic >>");
let external_name = ExternalName {
class: ExternalObjectClass::Signal,
path: WithPos::new(
ExternalPath::Relative(code.s1("dut.gen(0)").name(), 1),
code.s1("^.dut.gen(0)").pos(),
),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(
code.with_stream(parse_name),
WithPos::new(Name::External(Box::new(external_name)), code)
);
}
#[test]
fn test_external_name_explicit_relative_multiple_levels() {
let code = Code::new("<< signal ^.^.^.dut.gen(0) : std_logic >>");
let external_name = ExternalName {
class: ExternalObjectClass::Signal,
path: WithPos::new(
ExternalPath::Relative(code.s1("dut.gen(0)").name(), 3),
code.s1("^.^.^.dut.gen(0)").pos(),
),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(
code.with_stream(parse_name),
WithPos::new(Name::External(Box::new(external_name)), code)
);
}
#[test]
fn test_external_name_absolute() {
let code = Code::new("<< signal .dut.gen(0) : std_logic >>");
let external_name = ExternalName {
class: ExternalObjectClass::Signal,
path: WithPos::new(
ExternalPath::Absolute(code.s1("dut.gen(0)").name()),
code.s1(".dut.gen(0)").pos(),
),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(
code.with_stream(parse_name),
WithPos::new(Name::External(Box::new(external_name)), code)
);
}
#[test]
fn test_external_name_package() {
let code = Code::new("<< signal @lib.pkg : std_logic >>");
let external_name = ExternalName {
class: ExternalObjectClass::Signal,
path: WithPos::new(
ExternalPath::Package(code.s1("lib.pkg").name()),
code.s1("@lib.pkg").pos(),
),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(
code.with_stream(parse_name),
WithPos::new(Name::External(Box::new(external_name)), code)
);
}
#[test]
fn test_external_name_object_classes() {
let combinations = [
("constant", ExternalObjectClass::Constant),
("signal", ExternalObjectClass::Signal),
("variable", ExternalObjectClass::Variable),
];
for (string, class) in combinations.iter().cloned() {
let code = Code::new(&format!("<< {string} dut.foo : std_logic >>"));
let external_name = ExternalName {
class,
path: WithPos::new(
ExternalPath::Relative(code.s1("dut.foo").name(), 0),
code.s1("dut.foo").pos(),
),
subtype: code.s1("std_logic").subtype_indication(),
};
assert_eq!(
code.with_stream(parse_name),
WithPos::new(Name::External(Box::new(external_name)), code)
);
}
}
#[test]
fn test_simple_all_is_illegal() {
let code = Code::new("all");
assert_eq!(
code.with_partial_stream(parse_name),
Err(Diagnostic::error(
code.s1("all"),
"Illegal prefix 'all' for name"
))
);
}
#[test]
fn test_all_is_illegal_prefix() {
let code = Code::new("all.foo");
assert_eq!(
code.with_partial_stream(parse_name),
Err(Diagnostic::error(
code.s1("all"),
"Illegal prefix 'all' for name"
))
);
}
}