use super::cascade::NodeFacts;
use super::ir::{
Along, AttrMap, MarkerKind, ResolvedEndpoint, ResolvedText, ResolvedValue, ResolvedWire,
VarEntry, VarKind, VarTable,
};
use super::merge::{collapse, resolve_markers};
use super::scene::{PathIndex, SceneCtx};
use super::value::resolve_groups;
use crate::ast::LineStyle;
use crate::error::Error;
use crate::syntax::ast::{Child, Endpoint, EndpointGroup, Wire};
pub fn resolve_wire(
w: &Wire,
ctx: &SceneCtx,
paths: &PathIndex,
path_prefix: &[String],
wire_defaults: &[(String, ResolvedValue)],
) -> Result<Vec<ResolvedWire>, Error> {
for class in &w.classes {
if !ctx.sheet.defines_class(class) {
return Err(Error::at(w.span, format!("unknown class '.{}'", class)));
}
}
let wire_facts = NodeFacts {
types: vec!["wire".to_string()],
classes: w.classes.clone(),
};
let mut ordered: Vec<(String, ResolvedValue)> = wire_defaults.to_vec();
ordered.extend(ctx.sheet.node_layers(&[], &wire_facts));
if let Some(block) = &w.block {
for d in &block.decls {
ordered.push((d.name.clone(), resolve_groups(&d.groups, d.span, ctx.vars)?));
}
}
let markers = resolve_markers(
&ordered,
MarkerKind::from_marker(w.op.start),
MarkerKind::from_marker(w.op.end),
w.span,
)?;
let mut attrs = collapse(&ordered);
inject_line_style(&mut attrs, w.op.line);
let along: Vec<f64> = attrs
.get("along")
.map(collect_fractions)
.unwrap_or_default();
attrs.map.remove("along");
let mut texts: Vec<ResolvedText> = Vec::new();
if let Some(block) = &w.block {
for (i, label) in block.labels.iter().enumerate() {
let pos = along.get(i).copied().map_or(Along::Auto, Along::Fraction);
let (text, map) = resolve_wire_label(label, ctx)?;
texts.push(ResolvedText {
text,
along: pos,
attrs: wire_text_attrs(map, ctx.vars),
});
}
}
let mut out = Vec::new();
for (fan_index, chain) in expand_chain(&w.chain).into_iter().enumerate() {
let mut endpoints = Vec::with_capacity(chain.len());
for ep in chain {
let qualified: Vec<String> = if path_prefix.is_empty() {
ep.path.clone()
} else {
let mut p = path_prefix.to_vec();
p.extend(ep.path.iter().cloned());
p
};
let path = paths
.resolve(&qualified)
.ok_or_else(|| endpoint_error(&ep, paths, path_prefix))?;
endpoints.push(ResolvedEndpoint {
path,
side: ep.side,
span: ep.span,
});
}
out.push(ResolvedWire {
endpoints,
attrs: attrs.clone(),
applied_styles: w.classes.clone(),
markers: markers.clone(),
texts: if fan_index == 0 {
texts.clone()
} else {
Vec::new()
},
span: w.span,
});
}
Ok(out)
}
fn inject_line_style(attrs: &mut AttrMap, line: LineStyle) {
let style = match line {
LineStyle::Solid => return,
LineStyle::Dashed => "dashed",
LineStyle::Dotted => "dotted",
LineStyle::Wavy => "wavy",
};
if attrs.get("stroke-style").is_none() {
attrs.insert("stroke-style", ResolvedValue::Ident(style.into()));
}
}
fn collect_fractions(v: &ResolvedValue) -> Vec<f64> {
match v {
ResolvedValue::Number(n) => vec![*n],
ResolvedValue::Tuple(xs) | ResolvedValue::List(xs) => {
xs.iter().filter_map(ResolvedValue::as_number).collect()
}
_ => Vec::new(),
}
}
fn resolve_wire_label(label: &Child, ctx: &SceneCtx) -> Result<(String, AttrMap), Error> {
match label {
Child::Text(t) => Ok((t.text.clone(), AttrMap::new())),
Child::Box(n) => {
let block = n.block.as_ref();
let text = block
.and_then(|b| {
b.children.iter().find_map(|c| match c {
Child::Text(t) => Some(t.text.clone()),
Child::Box(_) => None,
})
})
.unwrap_or_default();
let mut map = AttrMap::new();
if let Some(b) = block {
for d in &b.decls {
map.insert(d.name.clone(), resolve_groups(&d.groups, d.span, ctx.vars)?);
}
}
Ok((text, map))
}
}
}
fn wire_text_attrs(mut map: AttrMap, vars: &VarTable) -> AttrMap {
if map.get("font-size").is_none() {
map.insert("font-size", baked_layout_var(vars, "wire-font-size"));
}
map
}
fn baked_layout_var(vars: &VarTable, name: &str) -> ResolvedValue {
let baked = match vars.get(name) {
Some(VarEntry {
kind: VarKind::Layout,
value,
}) => Some(Box::new(value.clone())),
_ => None,
};
ResolvedValue::LiveVar {
name: name.to_string(),
raw: false,
baked,
}
}
fn expand_chain(chain: &[EndpointGroup]) -> Vec<Vec<Endpoint>> {
let mut acc: Vec<Vec<Endpoint>> = vec![Vec::new()];
for group in chain {
let mut next = Vec::with_capacity(acc.len() * group.endpoints.len());
for trail in &acc {
for ep in &group.endpoints {
let mut t = trail.clone();
t.push(ep.clone());
next.push(t);
}
}
acc = next;
}
acc
}
fn endpoint_error(ep: &Endpoint, paths: &PathIndex, scope: &[String]) -> Error {
let where_ = if scope.is_empty() {
"at scene root".to_string()
} else {
format!("in '{}'", scope.join("."))
};
let mut msg = format!("wire endpoint '{}' not found {}", ep.path.join("."), where_);
let suggestions = paths.suggest(ep.path.last().expect("non-empty path"), scope);
if !suggestions.is_empty() {
let quoted: Vec<String> = suggestions.iter().map(|s| format!("'{}'", s)).collect();
msg.push_str(&format!("; did you mean {}?", quoted.join(", ")));
}
Error::at(ep.span, msg)
}