use crate::ir;
use crate::ir::ast::{Expression, TerminalType};
use crate::ir::visitor::{Visitable, Visitor};
use anyhow::Result;
use std::collections::HashMap;
struct NestedSubscriptChecker<'a> {
comp_name: &'a str,
shape: &'a [usize],
error: Option<String>,
}
impl<'a> NestedSubscriptChecker<'a> {
fn new(comp_name: &'a str, shape: &'a [usize]) -> Self {
Self {
comp_name,
shape,
error: None,
}
}
fn into_result(self) -> Result<()> {
match self.error {
Some(msg) => anyhow::bail!("{}", msg),
None => Ok(()),
}
}
}
impl Visitor for NestedSubscriptChecker<'_> {
fn enter_expression(&mut self, node: &Expression) {
if self.error.is_some() {
return;
}
let Expression::ComponentReference(comp_ref) = node else {
return;
};
let Some(first) = comp_ref.parts.first() else {
return;
};
if comp_ref.parts.len() < 2 || first.ident.text != self.comp_name {
return;
}
let Some(subs) = &first.subs else {
return;
};
for (dim_idx, sub) in subs.iter().enumerate() {
if dim_idx >= self.shape.len() {
self.error = Some(format!(
"Wrong number of subscripts in {}[...] ({} subscripts for {} dimensions)",
self.comp_name,
subs.len(),
self.shape.len()
));
return;
}
let ir::ast::Subscript::Expression(Expression::Terminal {
token,
terminal_type: TerminalType::UnsignedInteger,
}) = sub
else {
continue;
};
let Ok(idx) = token.text.parse::<usize>() else {
continue;
};
let dim_size = self.shape[dim_idx];
if idx < 1 || idx > dim_size {
self.error = Some(format!(
"Subscript '{}' for dimension {} (size = {}) of {} is out of bounds",
idx,
dim_idx + 1,
dim_size,
self.comp_name
));
return;
}
}
}
}
struct CardinalityArrayChecker<'a> {
comp_shapes: &'a HashMap<String, Vec<usize>>,
error: Option<String>,
}
impl<'a> CardinalityArrayChecker<'a> {
fn new(comp_shapes: &'a HashMap<String, Vec<usize>>) -> Self {
Self {
comp_shapes,
error: None,
}
}
fn into_result(self) -> Result<()> {
match self.error {
Some(msg) => anyhow::bail!("{}", msg),
None => Ok(()),
}
}
}
impl CardinalityArrayChecker<'_> {
fn check_array_arg(
&self,
comp_ref: &ir::ast::ComponentReference,
first: &ir::ast::ComponentRefPart,
name: &str,
) -> Option<String> {
let shape = self.comp_shapes.get(name)?;
if shape.is_empty() {
return None;
}
let num_subscripts = first
.subs
.as_ref()
.map_or(0, |s: &Vec<ir::ast::Subscript>| s.len());
if num_subscripts >= shape.len() {
return None;
}
let effective_shape: Vec<usize> = shape[num_subscripts..].to_vec();
let shape_str = effective_shape
.iter()
.map(|s: &usize| s.to_string())
.collect::<Vec<_>>()
.join(", ");
Some(format!(
"Type mismatch for positional argument 1 in cardinality(c={}). The argument has type:\n Connector[{}]\nexpected type:\n Connector",
comp_ref, shape_str
))
}
}
impl Visitor for CardinalityArrayChecker<'_> {
fn enter_expression(&mut self, node: &Expression) {
if self.error.is_some() {
return;
}
let Expression::FunctionCall { comp, args, .. } = node else {
return;
};
let is_cardinality = comp.parts.len() == 1
&& comp
.parts
.first()
.is_some_and(|p| p.ident.text == "cardinality");
if !is_cardinality || args.is_empty() {
return;
}
let Expression::ComponentReference(comp_ref) = &args[0] else {
return;
};
let Some(first) = comp_ref.parts.first() else {
return;
};
if comp_ref.parts.len() >= 2 {
if let Some(err) = self.check_array_arg(comp_ref, first, &first.ident.text) {
self.error = Some(err);
}
} else if comp_ref.parts.len() == 1 {
if let Some(err) = self.check_array_arg(comp_ref, first, &first.ident.text) {
self.error = Some(err);
}
}
}
}
pub(super) fn check_nested_component_subscripts(
fclass: &ir::ast::ClassDefinition,
comp_name: &str,
comp: &ir::ast::Component,
) -> Result<()> {
if comp.shape.is_empty() {
return Ok(());
}
let mut checker = NestedSubscriptChecker::new(comp_name, &comp.shape);
fclass.accept(&mut checker);
checker.into_result()
}
pub(super) fn check_cardinality_array_connectors(
fclass: &ir::ast::ClassDefinition,
comp_shapes: &HashMap<String, Vec<usize>>,
) -> Result<()> {
let mut checker = CardinalityArrayChecker::new(comp_shapes);
fclass.accept(&mut checker);
checker.into_result()
}