use crate::resolve::type_chain_contains;
use crate::syntax::ast::{Block, Child, Decl, File, Node, TextNode, Value, Wire, WireBlock};
pub fn desugar(file: &File) -> File {
File {
stylesheet: file.stylesheet.clone(),
instances: file
.instances
.iter()
.map(|c| desugar_child(c, file))
.collect(),
wires: file.wires.iter().map(desugar_wire).collect(),
}
}
fn desugar_child(child: &Child, file: &File) -> Child {
match child {
Child::Box(n) => Child::Box(desugar_node(n, file)),
Child::Text(t) => Child::Text(t.clone()),
}
}
fn desugar_node(node: &Node, file: &File) -> Node {
let ty = node.ty.as_deref().unwrap_or("box");
let mut children: Vec<Child> = node
.block
.as_ref()
.map(|b| b.children.iter().map(|c| desugar_child(c, file)).collect())
.unwrap_or_default();
let is_icon = type_chain_contains(ty, "icon", file);
let is_container = type_chain_contains(ty, "group", file);
let leaf = children.is_empty();
if leaf
&& !is_icon
&& !is_container
&& let Some(id) = &node.id
{
children.push(Child::Text(TextNode {
text: id.clone(),
span: node.span,
}));
}
let decls = node
.block
.as_ref()
.map(|b| b.decls.clone())
.unwrap_or_default();
let wires = node
.block
.as_ref()
.map(|b| b.wires.clone())
.unwrap_or_default();
let block = if decls.is_empty() && children.is_empty() && wires.is_empty() {
node.block.clone() } else {
Some(Block {
decls,
children,
wires,
})
};
Node {
id: node.id.clone(),
ty: node.ty.clone(),
classes: node.classes.clone(),
block,
span: node.span,
}
}
fn desugar_wire(w: &Wire) -> Wire {
let Some(block) = &w.block else {
return w.clone();
};
let n = block.labels.len();
let has_along = block.decls.iter().any(|d| d.name == "along");
if n == 0 || has_along {
return w.clone();
}
let fractions: Vec<Value> = (0..n)
.map(|i| {
let f = (i as f64 + 1.0) / (n as f64 + 1.0);
Value::Number((f * 100.0).round() / 100.0)
})
.collect();
let mut decls = block.decls.clone();
decls.insert(
0,
Decl {
name: "along".to_string(),
groups: vec![fractions],
span: w.span,
},
);
Wire {
chain: w.chain.clone(),
op: w.op,
classes: w.classes.clone(),
block: Some(WireBlock {
decls,
labels: block.labels.clone(),
}),
span: w.span,
}
}
#[cfg(test)]
mod tests {
fn desugar(src: &str) -> String {
crate::desugar_source(src).expect("desugar")
}
#[test]
fn id_becomes_an_explicit_label() {
assert_eq!(desugar("cat |box|\n"), "cat |box| { \"cat\" }\n");
}
#[test]
fn an_explicit_label_is_left_alone() {
assert_eq!(
desugar("cat |box| { \"Cat\" }\n"),
"cat |box| { \"Cat\" }\n"
);
}
#[test]
fn icon_glyph_is_not_expanded() {
assert_eq!(desugar("home |icon|\n"), "home |icon|\n");
}
#[test]
fn a_container_keeps_its_children() {
let out = desugar("g |group| {\n a |box|\n}\n");
assert!(!out.contains("\"g\""), "{out}");
assert!(out.contains("a |box|"), "{out}");
}
#[test]
fn wire_labels_gain_an_explicit_along() {
assert_eq!(
desugar("a -> b { \"near a\" \"near b\" }\n"),
"a -> b { along: 0.33 0.67; \"near a\" \"near b\" }\n"
);
}
#[test]
fn an_explicit_along_is_left_alone() {
let out = desugar("a -> b { along: 0.2; \"x\" }\n");
assert!(out.contains("along: 0.2;"), "{out}");
assert_eq!(out.matches("along").count(), 1, "{out}");
}
}