// Helper to infer free indices from implicit tensor equation (RHS)
// Returns sorted list of unique variable names used as indices but not bound in scope
fn infer_free_indices(&self, expr: &Expr) -> Vec<String> {
let mut indices = std::collections::HashSet::new();
self.collect_indices(expr, &mut indices);
// Filter out variables that are defined in current scope (e.g. loops)
// If a variable is NOT in scope, it is a free index (implicit dimension)
let mut free_indices: Vec<String> = indices
.into_iter()
.filter(|idx| {
// If variable exists in scope, it's a bound value/loop var, NOT a free dimension
!self.variable_exists(idx)
})
.collect();
free_indices.sort();
free_indices
}
fn collect_indices(&self, expr: &Expr, indices: &mut std::collections::HashSet<String>) {
match expr {
Expr::IndexAccess(_, idxs) => {
for idx in idxs {
if let Expr::Variable(name) = idx {
indices.insert(name.clone());
}
// Recursive check? Indices usually simple vars.
}
}
Expr::BinOp(lhs, _, rhs) => {
self.collect_indices(lhs, indices);
self.collect_indices(rhs, indices);
}
Expr::UnOp(_, val) => {
self.collect_indices(val, indices);
}
Expr::FnCall(_, args) | Expr::MethodCall(_, _, args) | Expr::StaticMethodCall(_, _, args) => {
for arg in args {
self.collect_indices(arg, indices);
}
}
Expr::TensorLiteral(elems) => {
for elem in elems {
self.collect_indices(elem, indices);
}
}
Expr::IfExpr(cond, _, _) => {
self.collect_indices(cond, indices);
}
_ => {}
}
}
fn variable_exists(&self, name: &str) -> bool {
for scope in self.variables.iter().rev() {
if scope.contains_key(name) {
return true;
}
}
false
}