use super::cascade::Stylesheet;
use super::ir::{ResolvedValue, ShapeKind, VarTable};
use super::value::resolve_groups;
use crate::error::Error;
use crate::span::Span;
use crate::syntax::ast::{Child, Define, Wire};
use std::collections::HashMap;
const MAX_INHERITANCE_DEPTH: usize = 16;
pub(super) const TEMPLATES: &[(&str, &str)] = &[
("plain", "box"),
("group", "box"),
("caption", "plain"),
("badge", "box"),
("note", "box"),
("row", "plain"),
("column", "plain"),
("table", "group"),
];
pub fn is_template(name: &str) -> bool {
TEMPLATES.iter().any(|(n, _)| *n == name)
}
pub(crate) fn template_base(name: &str) -> Option<&'static str> {
TEMPLATES.iter().find(|(n, _)| *n == name).map(|(_, b)| *b)
}
struct DefineEntry {
base: String,
decls: Vec<(String, ResolvedValue)>,
body_children: Vec<Child>,
body_wires: Vec<Wire>,
span: Span,
}
pub struct Types<'a> {
user: HashMap<String, DefineEntry>,
sheet: &'a Stylesheet,
}
#[derive(Debug)]
pub struct ResolvedType {
pub kind: ShapeKind,
pub type_chain: Vec<String>,
pub defaults: Vec<(String, ResolvedValue)>,
pub body_children: Vec<Child>,
pub body_wires: Vec<Wire>,
}
impl<'a> Types<'a> {
pub fn build(
defines: &[&Define],
sheet: &'a Stylesheet,
vars: &VarTable,
) -> Result<Self, Error> {
let mut user = HashMap::new();
for d in defines {
if is_builtin_type(&d.name) {
return Err(Error::at(
d.span,
format!("'{}' shadows a built-in type", d.name),
));
}
if user.contains_key(&d.name) {
return Err(Error::at(d.span, format!("duplicate type '{}'", d.name)));
}
let mut decls = Vec::with_capacity(d.body.decls.len());
for decl in &d.body.decls {
decls.push((
decl.name.clone(),
resolve_groups(&decl.groups, decl.span, vars)?,
));
}
user.insert(
d.name.clone(),
DefineEntry {
base: d.base.clone(),
decls,
body_children: d.body.children.clone(),
body_wires: d.body.wires.clone(),
span: d.span,
},
);
}
let types = Self { user, sheet };
for d in defines {
types.walk(&d.name, d.span, &mut Vec::new(), 0)?;
}
Ok(types)
}
pub fn is_known(&self, name: &str) -> bool {
ShapeKind::parse(name).is_some() || is_template(name) || self.user.contains_key(name)
}
pub fn resolve(&self, name: &str, span: Span) -> Result<ResolvedType, Error> {
self.walk(name, span, &mut Vec::new(), 0)
}
fn walk(
&self,
name: &str,
span: Span,
visiting: &mut Vec<String>,
depth: usize,
) -> Result<ResolvedType, Error> {
if depth > MAX_INHERITANCE_DEPTH {
return Err(Error::at(
span,
format!("'{}' exceeds max inheritance depth (16)", name),
));
}
if visiting.iter().any(|n| n == name) {
return Err(Error::at(
span,
format!("cycle in '{} -> {}'", visiting.join(" -> "), name),
));
}
if let Some(kind) = ShapeKind::parse(name) {
return Ok(ResolvedType {
kind,
type_chain: Vec::new(),
defaults: self.sheet.element_decls(name),
body_children: Vec::new(),
body_wires: Vec::new(),
});
}
if let Some((_, base)) = TEMPLATES.iter().find(|(n, _)| *n == name) {
visiting.push(name.to_string());
let mut r = self.walk(base, span, visiting, depth + 1)?;
visiting.pop();
r.defaults.extend(template_attrs(name));
r.defaults.extend(self.sheet.element_decls(name));
r.type_chain.insert(0, name.to_string());
return Ok(r);
}
if let Some(entry) = self.user.get(name) {
visiting.push(name.to_string());
let mut r = self.walk(&entry.base, entry.span, visiting, depth + 1)?;
visiting.pop();
r.defaults.extend(entry.decls.iter().cloned());
r.defaults.extend(self.sheet.element_decls(name));
r.type_chain.insert(0, name.to_string());
r.body_children.extend(entry.body_children.iter().cloned());
r.body_wires.extend(entry.body_wires.iter().cloned());
return Ok(r);
}
Err(Error::at(span, format!("unknown type '{}'", name)))
}
}
fn is_builtin_type(name: &str) -> bool {
ShapeKind::parse(name).is_some()
|| is_template(name)
|| matches!(
name,
"wire" | "rect" | "circle" | "top" | "bottom" | "left" | "right"
)
}
pub(super) fn template_attrs(name: &str) -> Vec<(String, ResolvedValue)> {
let live = |var: &str| ResolvedValue::LiveVar {
name: var.into(),
raw: false,
baked: None,
};
let num = ResolvedValue::Number;
let ident = |s: &str| ResolvedValue::Ident(s.into());
let pair = |a: f64, b: f64| ResolvedValue::Tuple(vec![num(a), num(b)]);
let attr = |n: &str, v: ResolvedValue| (n.to_string(), v);
match name {
"plain" => vec![
attr("stroke", ident("none")),
attr("fill", ident("none")),
attr("padding", num(0.0)),
],
"group" => vec![
attr("stroke", live("group-stroke")),
attr("fill", live("group-fill")),
attr("radius", num(6.0)),
],
"caption" => vec![attr("mount", ident("in")), attr("font-size", num(13.0))],
"badge" => vec![
attr("mount", ident("on")),
attr("side", ident("top")),
attr("align", ident("end")),
attr("radius", num(999.0)),
attr("padding", pair(2.0, 8.0)),
attr("shadow", num(2.0)),
attr("fill", live("accent")),
attr("color", live("on-accent")),
attr("font-size", num(11.0)),
attr("layer", num(10.0)),
],
"note" => vec![
attr("radius", num(2.0)),
attr("shadow", num(2.0)),
attr("stroke", ident("none")),
attr("fill", live("note-bg")),
],
"row" => vec![attr("layout", ident("row"))],
"column" => vec![attr("layout", ident("column"))],
"table" => vec![
attr("layout", ident("grid")),
attr("divider", ident("all")),
attr("gap", num(0.0)),
attr("padding", pair(4.0, 8.0)),
attr("fill", ident("none")),
attr("stroke", live("stroke")),
],
_ => Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::syntax::ast::{Rule, StyleItem};
fn resolve_result(src: &str, name: &str) -> Result<ResolvedType, Error> {
let toks = crate::lexer::lex(src).expect("lex");
let file = crate::syntax::parser::parse(&toks).expect("parse");
let vars = VarTable::new();
let rules: Vec<&Rule> = file
.stylesheet
.iter()
.filter_map(|it| match it {
StyleItem::Rule(r) => Some(r),
_ => None,
})
.collect();
let sheet = Stylesheet::build(&rules, &vars).expect("sheet");
let defines: Vec<&Define> = file
.stylesheet
.iter()
.filter_map(|it| match it {
StyleItem::Define(d) => Some(d),
_ => None,
})
.collect();
let types = Types::build(&defines, &sheet, &vars)?;
types.resolve(name, Span::empty())
}
fn resolve_ok(src: &str, name: &str) -> ResolvedType {
resolve_result(src, name).expect("resolve")
}
fn build_err(src: &str) -> String {
match resolve_result(src, "box") {
Err(e) => e.message,
Ok(_) => panic!("expected an error building {src:?}"),
}
}
fn has_number(t: &ResolvedType, name: &str, want: f64) -> bool {
t.defaults
.iter()
.any(|(n, v)| n == name && matches!(v, ResolvedValue::Number(x) if *x == want))
}
#[test]
fn primitive_resolves_to_its_kind_with_no_chain() {
let t = resolve_ok("", "box");
assert_eq!(t.kind, ShapeKind::Box);
assert!(t.type_chain.is_empty());
}
#[test]
fn template_resolves_to_its_base_primitive_with_chain_and_bundle() {
let t = resolve_ok("", "group");
assert_eq!(t.kind, ShapeKind::Box);
assert_eq!(t.type_chain, vec!["group"]);
assert!(has_number(&t, "radius", 6.0)); }
#[test]
fn table_chain_is_table_then_group_over_rect() {
let t = resolve_ok("", "table");
assert_eq!(t.kind, ShapeKind::Box);
assert_eq!(t.type_chain, vec!["table", "group"]);
}
#[test]
fn caption_resolves_over_plain() {
let t = resolve_ok("", "caption");
assert_eq!(t.kind, ShapeKind::Box);
assert_eq!(t.type_chain, vec!["caption", "plain"]);
}
#[test]
fn user_define_resolves_to_base_and_carries_its_decls() {
let t = resolve_ok("treat::box { radius: 5; }\n", "treat");
assert_eq!(t.kind, ShapeKind::Box);
assert_eq!(t.type_chain, vec!["treat"]);
assert!(has_number(&t, "radius", 5.0));
}
#[test]
fn element_rule_layers_into_the_type_cascade() {
let t = resolve_ok("box { radius: 4; }\n", "box");
assert!(has_number(&t, "radius", 4.0));
}
#[test]
fn derived_define_overrides_base_by_order() {
let t = resolve_ok("panel::group { radius: 10; }\n", "panel");
assert_eq!(t.type_chain, vec!["panel", "group"]);
let last_radius = t
.defaults
.iter()
.rev()
.find(|(n, _)| n == "radius")
.unwrap();
assert!(matches!(last_radius.1, ResolvedValue::Number(x) if x == 10.0));
}
#[test]
fn unknown_type_errors() {
let e = resolve_result("", "ghost").unwrap_err();
assert!(
e.message.contains("unknown type 'ghost'"),
"got: {}",
e.message
);
}
#[test]
fn define_shadowing_a_builtin_errors() {
assert!(build_err("rect::oval { }\n").contains("shadows a built-in"));
}
#[test]
fn inheritance_cycle_errors() {
assert!(build_err("a::b { }\nb::a { }\n").contains("cycle"));
}
#[test]
fn inheritance_depth_over_16_errors() {
let mut src = String::from("t0::box { }\n");
for i in 1..=17 {
src.push_str(&format!("t{}::t{} {{ }}\n", i, i - 1));
}
assert!(build_err(&src).contains("max inheritance depth"));
}
}