pub(crate) mod bundles;
mod classes;
mod labels;
pub(crate) mod scene;
mod types;
use crate::error::Error;
use crate::resolve::NodeKind;
use crate::span::Span;
use crate::syntax::ast::{Child, Decl, File, Link, Node, Rule, SelUnit, Selector, StyleItem};
use bundles::root_defaults;
use classes::{class_defs, is_lini_class, lini_class, merge_decls, worn_classes};
use std::collections::{BTreeSet, HashMap};
use types::{Types, is_template};
type Bodies = HashMap<String, (Vec<Child>, Vec<Link>)>;
pub fn desugar(file: &File) -> Result<File, Error> {
let types = Types::build(file)?;
let mut element_rules: HashMap<String, Vec<Decl>> = HashMap::new();
let mut bodies: Bodies = HashMap::new();
let mut extra_order: Vec<String> = Vec::new();
let mut user_root: Vec<Decl> = Vec::new();
let mut user_vars: Vec<Decl> = Vec::new();
let mut user_rules: Vec<Rule> = Vec::new();
let mut user_funcs: Vec<crate::syntax::ast::FuncDef> = Vec::new();
for item in &file.stylesheet {
match item {
StyleItem::RootDecl(d) => user_root.push(d.clone()),
StyleItem::Func(f) => user_funcs.push(f.clone()),
StyleItem::Var(d) => user_vars.push(d.clone()),
StyleItem::Define(d) => {
element_rules
.entry(d.name.clone())
.or_default()
.extend(d.style.iter().cloned());
bodies.insert(d.name.clone(), (d.children.clone(), d.links.clone()));
push_unique(&mut extra_order, &d.name);
}
StyleItem::Rule(r) => match r.selector.units.as_slice() {
[SelUnit::Type { name, id: None }] => element_rules
.entry(name.clone())
.or_default()
.extend(r.decls.iter().cloned()),
[SelUnit::Class(c)] if is_lini_class(c) => {
let x = c.strip_prefix("lini-").unwrap().to_string();
element_rules
.entry(x.clone())
.or_default()
.extend(r.decls.iter().cloned());
if NodeKind::parse(&x).is_none() && !is_template(&x) {
push_unique(&mut extra_order, &x);
}
}
_ => user_rules.push(rewrite_selector(r, &types)?),
},
}
}
let mut instances = Vec::new();
for child in &file.instances {
instances.push(lower_child(child, &types, &bodies)?);
}
let declared = scene::declared_ids(&instances);
for (id, span) in scene::auto_created_ids(&file.links, &declared) {
instances.push(Child::Box(lower_node(
&scene::auto_box(&id, span),
&types,
&bodies,
)?));
}
let mut present: BTreeSet<String> = BTreeSet::new();
for c in &instances {
mark_present(c, &mut present);
}
let mut stylesheet: Vec<StyleItem> = Vec::new();
for d in merge_decls(root_defaults(), &user_root) {
stylesheet.push(StyleItem::RootDecl(d));
}
for d in user_vars {
stylesheet.push(StyleItem::Var(d));
}
for f in user_funcs {
stylesheet.push(StyleItem::Func(f));
}
for r in class_defs(&present, &element_rules, &extra_order) {
stylesheet.push(StyleItem::Rule(r));
}
for r in user_rules {
stylesheet.push(StyleItem::Rule(r));
}
Ok(File {
stylesheet,
stylesheet_span: Span::empty(),
instances,
links: file.links.iter().map(labels::lower_link).collect(),
})
}
fn lower_child(child: &Child, types: &Types, bodies: &Bodies) -> Result<Child, Error> {
match child {
Child::Box(n) => Ok(Child::Box(lower_node(n, types, bodies)?)),
Child::Text(t) => Ok(Child::Text(t.clone())),
}
}
fn lower_node(node: &Node, types: &Types, bodies: &Bodies) -> Result<Node, Error> {
let ty = node.ty.as_deref().unwrap_or("box");
let info = types.resolve(ty, node.span)?;
let kind = info.kind;
let already = NodeKind::parse(ty).is_some()
&& node.classes.iter().any(|c| *c == lini_class(kind.as_str()));
let classes = if already {
node.classes.clone()
} else {
let mut cs = worn_classes(&info);
cs.extend(node.classes.iter().cloned());
cs
};
let new_ty = if already {
node.ty.clone()
} else {
Some(kind.as_str().to_string())
};
let mut children = Vec::new();
if !already {
for name in &info.chain {
if let Some((body, _)) = bodies.get(name) {
for c in body {
children.push(lower_child(c, types, bodies)?);
}
}
}
}
for c in &node.children {
children.push(lower_child(c, types, bodies)?);
}
let text_capable = !matches!(
kind,
NodeKind::Line | NodeKind::Poly | NodeKind::Path | NodeKind::Image
);
let is_icon = kind == NodeKind::Icon;
let is_container = info.chain.iter().any(|n| n == "group");
let mut style = node.style.clone();
let mut kept_label = None;
if let Some(label) = node.label.as_ref().filter(|l| !l.text.is_empty()) {
if is_icon {
if style.iter().any(|d| d.name == "symbol") {
return Err(Error::at(
node.span,
"an icon's symbol is its label or 'symbol:', not both",
));
}
style.push(labels::symbol_decl(&label.text, node.span));
} else if is_container {
let caption = lower_node(&labels::caption_node(label), types, bodies)?;
children.insert(0, Child::Box(caption));
} else if text_capable {
children.insert(0, Child::Text(label.clone()));
} else {
kept_label = Some(label.clone());
}
}
let mut links = Vec::new();
if !already {
for name in &info.chain {
if let Some((_, body)) = bodies.get(name) {
for w in body {
links.push(labels::lower_link(w));
}
}
}
}
for w in &node.links {
links.push(labels::lower_link(w));
}
if !already {
let declared = scene::declared_ids(&children);
for (auto_id, auto_span) in scene::auto_created_ids(&node.links, &declared) {
let created = lower_node(&scene::auto_box(&auto_id, auto_span), types, bodies)?;
children.push(Child::Box(created));
}
}
Ok(Node {
id: node.id.clone(),
ty: new_ty,
label: kept_label,
classes,
style,
style_span: node.style_span,
children,
links,
span: node.span,
})
}
fn rewrite_selector(rule: &Rule, types: &Types) -> Result<Rule, Error> {
let mut units = Vec::with_capacity(rule.selector.units.len());
for unit in &rule.selector.units {
match unit {
SelUnit::Type { name, id } => {
let class = if is_lini_class(name) {
name.clone()
} else if types.is_known(name) {
lini_class(name)
} else {
return Err(Error::at(
rule.span,
format!("unknown type '{}' in selector", name),
));
};
match id {
Some(_) => units.push(SelUnit::Type {
name: class,
id: id.clone(),
}),
None => units.push(SelUnit::Class(class)),
}
}
SelUnit::Class(c) => units.push(SelUnit::Class(c.clone())),
SelUnit::Id(i) => units.push(SelUnit::Id(i.clone())),
}
}
Ok(Rule {
selector: Selector { units },
decls: rule.decls.clone(),
span: rule.span,
})
}
fn mark_present(child: &Child, present: &mut BTreeSet<String>) {
if let Child::Box(n) = child {
for c in &n.classes {
if let Some(x) = c.strip_prefix("lini-") {
present.insert(x.to_string());
}
}
for ch in &n.children {
mark_present(ch, present);
}
}
}
fn push_unique(v: &mut Vec<String>, name: &str) {
if !v.iter().any(|x| x == name) {
v.push(name.to_string());
}
}