use std::collections::HashSet;
use sv_lang::kinds::SyntaxKind;
use sv_lang::nodes::{
DPIExportSyntax, DPIImportSyntax, DataDeclarationSyntax, DataTypeSyntax, NetDeclarationSyntax,
PortDeclarationSyntax, PortHeaderSyntax, UdpDeclarationSyntax,
};
use sv_lang::{AstNode, Node, SyntaxTree};
fn line_of(tree: &SyntaxTree, offset: usize) -> usize {
let text = tree.text();
1 + text.as_bytes()[..offset.min(text.len())]
.iter()
.filter(|&&b| b == b'\n')
.count()
}
pub(crate) fn check_udp_tables(trees: &[SyntaxTree]) -> Result<(), String> {
for tree in trees {
for udp in tree.root().find_all::<UdpDeclarationSyntax>() {
if udp.body().entries().is_empty() {
let name = udp.name().map(|t| t.text().to_owned()).unwrap_or_default();
return Err(format!(
"line {}: error: the table of primitive `{name}` has no entry; a UDP \
table needs at least one (IEEE 1800-2023 29.3)\n",
line_of(tree, udp.syntax().byte_range().start)
));
}
}
}
Ok(())
}
fn type_has_range(ty: &DataTypeSyntax<'_>) -> bool {
match ty {
DataTypeSyntax::IntegerType(t) => !t.dimensions().is_empty(),
DataTypeSyntax::ImplicitType(t) => !t.dimensions().is_empty(),
_ => false,
}
}
pub(crate) fn check_nonansi_port_ranges(trees: &[SyntaxTree]) -> Result<(), String> {
for tree in trees {
for node in tree.root().descendants() {
let Some(port) = PortDeclarationSyntax::cast(node) else {
continue;
};
let (keyword, ty) = match port.header() {
PortHeaderSyntax::NetPortHeader(h) => (h.net_type(), h.data_type()),
PortHeaderSyntax::VariablePortHeader(h) => (h.var_keyword(), h.data_type()),
PortHeaderSyntax::InterfacePortHeader(_) => continue,
};
let untyped_scalar = keyword.is_none()
&& matches!(ty, DataTypeSyntax::ImplicitType(ref t) if t.dimensions().is_empty());
if !untyped_scalar {
continue;
}
let names: HashSet<String> = port
.declarators()
.iter()
.filter_map(|d| d.name().map(|t| t.text().to_owned()))
.collect();
let Some(parent) = node.parent().filter(|p| {
matches!(
p.kind(),
SyntaxKind::ModuleDeclaration
| SyntaxKind::InterfaceDeclaration
| SyntaxKind::ProgramDeclaration
)
}) else {
continue;
};
for sibling in parent.children() {
if sibling.kind() == SyntaxKind::PortDeclaration {
continue;
}
let (vector, decls): (bool, Vec<String>) =
if let Some(d) = DataDeclarationSyntax::cast(sibling) {
(
type_has_range(&d.r#type()),
d.declarators()
.iter()
.filter_map(|x| x.name().map(|t| t.text().to_owned()))
.collect(),
)
} else if let Some(d) = NetDeclarationSyntax::cast(sibling) {
(
type_has_range(&d.r#type()),
d.declarators()
.iter()
.filter_map(|x| x.name().map(|t| t.text().to_owned()))
.collect(),
)
} else {
continue;
};
if let Some(name) = decls.iter().find(|n| names.contains(*n))
&& vector
{
return Err(format!(
"line {}: error: port `{name}` is declared without a range and \
redeclared as a vector; the two ranges must be identical \
(IEEE 1800-2023 23.2.2.1)\n",
line_of(tree, Node::byte_range(&sibling).start)
));
}
}
}
}
Ok(())
}
pub(crate) fn check_dpi_spec_strings(trees: &[SyntaxTree]) -> Result<Vec<String>, String> {
let mut warnings = Vec::new();
let mut seen: std::collections::HashMap<String, String> = std::collections::HashMap::new();
for tree in trees {
let text = tree.text();
for node in tree.root().descendants() {
let (spec, cid) = if let Some(i) = DPIImportSyntax::cast(node) {
let cid = match i.c_identifier() {
Some(t) => t.text().to_owned(),
None => text[i.method().name().syntax().byte_range()]
.trim()
.to_owned(),
};
(i.spec_string(), cid)
} else if let Some(e) = DPIExportSyntax::cast(node) {
let cid = e
.c_identifier()
.or_else(|| e.name())
.map(|t| t.text().to_owned())
.unwrap_or_default();
(e.spec_string(), cid)
} else {
continue;
};
let Some(spec) = spec else { continue };
let version = spec.text().trim_matches('"').to_owned();
let line = line_of(tree, node.byte_range().start);
if version == "DPI" {
warnings.push(format!(
"line {line}: warning: \"DPI\" is deprecated and should be replaced with \
\"DPI-C\"; use of the \"DPI-C\" string may require changes in the DPI \
application's C code (IEEE 1800-2023 35.5.4, H.14)\n"
));
}
if let Some(prev) = seen.insert(cid.clone(), version.clone())
&& prev != version
{
return Err(format!(
"line {line}: error: `{cid}` is declared with both \"{prev}\" and \
\"{version}\"; all declarations of one c_identifier shall use the same \
DPI version string (IEEE 1800-2023 35.4)\n"
));
}
}
}
Ok(warnings)
}