use std::collections::HashMap;
use crate::diag::Diagnostic;
use crate::ir::{Device, Element, Model};
use crate::span::Span;
#[derive(Debug, Default)]
pub(crate) struct Scope {
pub elements: Vec<Element>,
pub models: Vec<Model>,
pub subckts: Vec<SubcktDef>,
}
#[derive(Debug)]
pub(crate) struct SubcktDef {
pub name: String,
pub ports: Vec<String>,
pub scope: Scope,
pub span: Span,
}
const MAX_DEPTH: usize = 64;
const MAX_WORK: usize = 1 << 20;
pub(crate) fn expand(top: &Scope, diags: &mut Vec<Diagnostic>) -> (Vec<Element>, Vec<Model>) {
let mut ex = Expander {
diags,
elements: Vec::new(),
models: top.models.clone(),
work: 0,
overflowed: false,
};
let mut defs: Vec<&[SubcktDef]> = vec![&top.subckts];
let empty_nodes = HashMap::new();
let empty_models = HashMap::new();
for el in &top.elements {
ex.emit(el, &mut defs, "", &empty_nodes, &empty_models, 0);
}
(ex.elements, ex.models)
}
struct Expander<'a> {
diags: &'a mut Vec<Diagnostic>,
elements: Vec<Element>,
models: Vec<Model>,
work: usize,
overflowed: bool,
}
impl Expander<'_> {
fn charge(&mut self, span: Span) -> bool {
if self.overflowed {
return false;
}
self.work += 1;
if self.work > MAX_WORK {
self.overflowed = true;
self.diags.push(Diagnostic::error(
format!("subcircuit expansion exceeds {MAX_WORK} steps; giving up"),
span,
));
return false;
}
true
}
fn emit<'s>(
&mut self,
el: &'s Element,
defs: &mut Vec<&'s [SubcktDef]>,
path: &str,
node_map: &HashMap<String, String>,
model_map: &HashMap<String, String>,
depth: usize,
) {
if !self.charge(el.span) {
return;
}
if let Device::SubcktCall { subname } = &el.device {
self.instantiate(el, subname, defs, path, node_map, model_map, depth);
return;
}
let mut out = el.clone();
out.name = rename(&el.name, path);
for n in &mut out.nodes {
*n = map_node(n, path, node_map);
}
match &mut out.device {
Device::Mutual { l1, l2, .. } => {
*l1 = rename(l1, path);
*l2 = rename(l2, path);
}
Device::Cccs { vname, .. }
| Device::Ccvs { vname, .. }
| Device::SwitchI { vname, .. } => {
*vname = rename(vname, path);
}
Device::Behavioral { expr, .. } => {
expr.visit_refs_mut(&mut |n| *n = map_node(n, path, node_map), &mut |d| {
*d = rename(d, path)
});
}
_ => {}
}
if let Some(m) = device_model_mut(&mut out.device)
&& let Some(renamed) = model_map.get(m.as_str())
{
*m = renamed.clone();
}
self.elements.push(out);
}
#[allow(clippy::too_many_arguments)]
fn instantiate<'s>(
&mut self,
call: &'s Element,
subname: &str,
defs: &mut Vec<&'s [SubcktDef]>,
path: &str,
node_map: &HashMap<String, String>,
model_map: &HashMap<String, String>,
depth: usize,
) {
if depth >= MAX_DEPTH {
self.diags.push(Diagnostic::error(
format!("subcircuits nested deeper than {MAX_DEPTH} levels (recursive?)"),
call.span,
));
return;
}
let Some(def) = defs
.iter()
.rev()
.copied()
.find_map(|scope| scope.iter().find(|d| d.name == subname))
else {
self.diags.push(Diagnostic::error(
format!("no such subcircuit `{subname}`"),
call.span,
));
return;
};
if def.ports.len() != call.nodes.len() {
self.diags.push(Diagnostic::error(
format!(
"subcircuit `{subname}` has {} ports but the call connects {}",
def.ports.len(),
call.nodes.len()
),
call.span,
));
return;
}
let inst_path = if path.is_empty() {
call.name.clone()
} else {
format!("{path}.{}", call.name)
};
let mut inner_nodes = HashMap::new();
for (port, actual) in def.ports.iter().zip(&call.nodes) {
inner_nodes.insert(port.clone(), map_node(actual, path, node_map));
}
let mut inner_models = model_map.clone();
for m in &def.scope.models {
if !self.charge(m.span) {
return;
}
let new_name = format!("{inst_path}.{}", m.name);
inner_models.insert(m.name.clone(), new_name.clone());
let mut renamed = m.clone();
renamed.name = new_name;
self.models.push(renamed);
}
defs.push(&def.scope.subckts);
for el in &def.scope.elements {
self.emit(el, defs, &inst_path, &inner_nodes, &inner_models, depth + 1);
}
defs.pop();
}
}
fn rename(name: &str, path: &str) -> String {
if path.is_empty() {
return name.to_string();
}
let letter = name.chars().next().unwrap_or('?');
format!("{letter}.{path}.{name}")
}
fn map_node(node: &str, path: &str, node_map: &HashMap<String, String>) -> String {
if node == "0" {
return "0".to_string();
}
if let Some(mapped) = node_map.get(node) {
return mapped.clone();
}
if path.is_empty() {
node.to_string()
} else {
format!("{path}.{node}")
}
}
fn device_model_mut(device: &mut Device) -> Option<&mut String> {
match device {
Device::Resistor { model, .. } | Device::Capacitor { model, .. } => model.as_mut(),
Device::Diode { model, .. }
| Device::Bjt { model, .. }
| Device::Jfet { model, .. }
| Device::Mesfet { model, .. }
| Device::Mosfet { model, .. }
| Device::SwitchV { model, .. }
| Device::SwitchI { model, .. }
| Device::TlineLossy { model }
| Device::Urc { model, .. } => Some(model),
_ => None,
}
}