use super::*;
impl State {
pub(super) fn eval_pos(&mut self, pos: &Position) -> ER<Point> {
match pos {
Position::Pair(x, y) => Ok(Point::new(self.expr_dim(x)?, self.expr_dim(y)?)),
Position::Place(loc) => self.eval_loc(loc),
Position::Between { frac, a, b, .. } => {
let f = self.eval_expr(frac)?;
let pa = self.eval_pos(a)?;
let pb = self.eval_pos(b)?;
Ok(pa.lerp(pb, f))
}
Position::Sum(sign, a, b) => {
let pa = self.eval_pos(a)?;
let pb = self.eval_pos(b)?;
Ok(match sign {
Sign::Plus => pa + pb,
Sign::Minus => pa - pb,
})
}
Position::Scale(p, s, div) => {
let pp = self.eval_pos(p)?;
let sv = self.eval_expr(s)?;
if *div {
if sv == 0.0 {
return err("division by zero in position");
}
Ok(pp / sv)
} else {
Ok(pp * sv)
}
}
}
}
fn eval_loc(&mut self, loc: &Location) -> ER<Point> {
match loc {
Location::Place(p) => self.place_point(p),
Location::Paren(pos) => self.eval_pos(pos),
Location::ParenPair(p1, p2) => {
Ok(Point::new(self.eval_pos(p1)?.x, self.eval_pos(p2)?.y))
}
}
}
pub(super) fn place_point(&mut self, p: &Place) -> ER<Point> {
match p {
Place::Here => Ok(self.pos),
Place::Name {
name,
subscript,
span,
} => {
let key = self.indexed_name(name, subscript.as_deref())?;
Ok(self.resolve_label(&key, span.as_ref())?.center)
}
Place::Nth { count, obj, span } => {
let idx = self.nth_index(count, obj, span.as_ref())?;
Ok(self.placed[idx].center)
}
Place::Corner(inner, c) => {
let pl = self.resolve_obj(inner)?;
Ok(pl.corner(*c))
}
Place::CornerOf(c, inner) => {
let pl = self.resolve_obj(inner)?;
Ok(pl.corner(*c))
}
Place::Member(..) => Ok(self.resolve_obj(p)?.center),
}
}
pub(super) fn resolve_obj(&mut self, p: &Place) -> ER<Placed> {
match p {
Place::Name {
name,
subscript,
span,
} => {
let key = self.indexed_name(name, subscript.as_deref())?;
self.resolve_label(&key, span.as_ref())
}
Place::Nth { count, obj, span } => {
let idx = self.nth_index(count, obj, span.as_ref())?;
Ok(self.placed[idx].clone())
}
Place::Corner(inner, _) | Place::CornerOf(_, inner) => self.resolve_obj(inner),
Place::Member(base, sub) => {
let b = self.resolve_obj(base)?;
let key = match sub.as_ref() {
Place::Name {
name, subscript, ..
} => self.indexed_name(name, subscript.as_deref())?,
_ => return err("a block sub-label must be a name"),
};
b.members.get(&key).cloned().ok_or_else(|| EvalError {
msg: format!("no sub-label `{key}` in that block"),
info: None,
})
}
Place::Here => err("`Here` is a point, not an object"),
}
}
pub(super) fn place_index(&mut self, p: &Place) -> ER<usize> {
match p {
Place::Name {
name,
subscript,
span,
} => {
let key = self.indexed_name(name, subscript.as_deref())?;
self.label_index(&key, span.as_ref())
}
Place::Nth { count, obj, span } => self.nth_index(count, obj, span.as_ref()),
Place::Corner(inner, _) | Place::CornerOf(_, inner) => self.place_index(inner),
Place::Here => err("`Here` is a point, not an object"),
Place::Member(_, _) => err("block sub-labels (B.A) are not supported yet"),
}
}
fn label_index(&self, name: &str, span: Option<&Span>) -> ER<usize> {
self.labels
.get(name)
.copied()
.ok_or_else(|| unknown_label_error(name, span))
}
fn resolve_label(&self, key: &str, span: Option<&Span>) -> ER<Placed> {
if let Some(&idx) = self.labels.get(key) {
Ok(self.placed[idx].clone())
} else if let Some(pl) = self.outer_labels.get(key) {
Ok(pl.clone())
} else {
Err(unknown_label_error(key, span))
}
}
pub(super) fn label_key(&mut self, label: &Label) -> ER<String> {
self.indexed_name(&label.name, label.subscript.as_ref())
}
pub(super) fn indexed_name(&mut self, name: &str, subscript: Option<&Expr>) -> ER<String> {
match subscript {
Some(Expr::Index(items)) => {
let mut parts = Vec::with_capacity(items.len());
for e in items {
parts.push(fmt_num(self.eval_expr(e)?));
}
Ok(format!("{name}[{}]", parts.join(",")))
}
Some(e) => Ok(format!("{name}[{}]", fmt_num(self.eval_expr(e)?))),
None => Ok(name.to_string()),
}
}
fn nth_index(&mut self, count: &Nth, obj: &PrimObj, span: Option<&Span>) -> ER<usize> {
let want = primobj_kind(obj);
let matches: Vec<usize> = self
.placed
.iter()
.enumerate()
.filter(|(_, pl)| want.is_none_or(|w| pl.kind == w))
.map(|(i, _)| i)
.collect();
if matches.is_empty() {
return match want {
Some(w) => err(format!("no {:?} object to reference", want_name(w))),
None => err("no object to reference".to_string()),
};
}
let idx = match count {
Nth::Last => *matches.last().unwrap(),
Nth::Count(e, from_last) => {
let n = self.eval_expr(e)?.round() as i64;
if n < 1 {
return err("ordinal must be >= 1");
}
let k = (n - 1) as usize;
if *from_last {
if k >= matches.len() {
return err_diag(ordinal_diagnostic(n, matches.len(), span));
}
matches[matches.len() - 1 - k]
} else {
if k >= matches.len() {
return err_diag(ordinal_diagnostic(n, matches.len(), span));
}
matches[k]
}
}
};
Ok(idx)
}
pub(super) fn eval_stringexpr(&mut self, se: &StringExpr) -> ER<String> {
Ok(match se {
StringExpr::Lit(s) => s.clone(),
StringExpr::Concat(a, b) => {
format!("{}{}", self.eval_stringexpr(a)?, self.eval_stringexpr(b)?)
}
StringExpr::Arg(n) => format!("${n}"), StringExpr::Sprintf(fmt, args) => {
let f = self.eval_stringexpr(fmt)?;
let mut vals = Vec::with_capacity(args.len());
for e in args {
vals.push(self.eval_printf_arg(e)?);
}
sprintf_fmt(&f, &vals)
}
StringExpr::SvgFont(_) => String::new(),
StringExpr::Rgb(args) => {
let mut c = [0u32; 3];
for (i, e) in args.iter().enumerate() {
let v = self.eval_expr(e)?;
if !v.is_finite() || !(0.0..=255.0).contains(&v.round()) {
return err("rgb() component out of range 0-255");
}
c[i] = v.round() as u32;
}
format!("#{:02x}{:02x}{:02x}", c[0], c[1], c[2])
}
StringExpr::ColorNum(e) => {
let v = self.eval_expr(e)?;
num_to_color(v)?
}
})
}
fn eval_printf_arg(&mut self, e: &Expr) -> ER<PrintfArg> {
match e {
Expr::Str(se) => Ok(PrintfArg::Str(self.eval_stringexpr(se)?)),
_ => Ok(PrintfArg::Num(self.eval_expr(e)?)),
}
}
fn expr_str(&mut self, e: &Expr) -> ER<String> {
match e {
Expr::Str(se) => self.eval_stringexpr(se),
_ => Ok(fmt_num(self.eval_expr(e)?)),
}
}
pub(super) fn eval_expr(&mut self, e: &Expr) -> ER<f64> {
let value = match e {
Expr::Num(v) => *v,
Expr::Str(_) => return err("a string is only valid as an `==`/`!=` operand"),
Expr::Index(_) => return err("a comma subscript is only valid inside `name[...]`"),
Expr::Var(name, subscript) => {
let key = self.indexed_name(name, subscript.as_deref())?;
self.vars.get(&key).copied().ok_or_else(|| EvalError {
msg: format!("variable not found `{key}`"),
info: None,
})?
}
Expr::Env(v) => self.env.get(*v),
Expr::Unary(op, a) => {
let x = self.eval_expr(a)?;
match op {
UnOp::Neg => -x,
UnOp::Pos => x,
UnOp::Not => bool_f(x == 0.0),
}
}
Expr::Bin(..) => self.eval_bin_expr(e)?,
Expr::Func1(f, a) => {
let x = self.eval_expr(a)?;
match f {
Func1::Abs => x.abs(),
Func1::Acos => x.acos(),
Func1::Asin => x.asin(),
Func1::Cos => x.cos(),
Func1::Exp => 10f64.powf(x),
Func1::Expe => x.exp(),
Func1::Int => x.trunc(),
Func1::Log => x.log10(),
Func1::Loge => x.ln(),
Func1::Sign => {
if x >= 0.0 {
1.0
} else {
-1.0
}
}
Func1::Sin => x.sin(),
Func1::Sqrt => x.sqrt(),
Func1::Tan => x.tan(),
Func1::Floor => x.floor(),
}
}
Expr::Func2(f, a, b) => {
let x = self.eval_expr(a)?;
let y = self.eval_expr(b)?;
match f {
Func2::Atan2 => x.atan2(y),
Func2::Max => x.max(y),
Func2::Min => x.min(y),
Func2::Pmod => x.rem_euclid(y),
}
}
Expr::Rand(seed) => {
let seed = match seed {
Some(e) => Some(self.eval_expr(e)?),
None => None,
};
self.rand(seed)
}
Expr::Assign(name, subscript, v) => {
let val = self.eval_expr(v)?;
let key = self.indexed_name(name, subscript.as_deref())?;
self.vars.insert(key, val);
val
}
Expr::DotX(loc) => {
let x = self.eval_loc(loc)?.x;
self.to_user_dim(x)
}
Expr::DotY(loc) => {
let y = self.eval_loc(loc)?.y;
self.to_user_dim(y)
}
Expr::PlaceAttr(place, param) => {
let pl = self.resolve_obj(place)?;
match param {
token::Param::Width => self.to_user_dim(pl.attr_width()),
token::Param::Height => self.to_user_dim(pl.attr_height()),
token::Param::Radius => self.to_user_dim(pl.attr_radius()),
token::Param::Diameter => self.to_user_dim(pl.attr_diameter()),
token::Param::Length => self.to_user_dim(pl.attr_length()),
token::Param::Thickness => pl.thick,
}
}
};
finite(value, "numeric expression")
}
fn eval_bin_expr(&mut self, e: &Expr) -> ER<f64> {
let mut chain = Vec::new();
let mut left = e;
while let Expr::Bin(op, a, b) = left {
chain.push((*op, b.as_ref()));
left = a.as_ref();
}
let mut left_expr = Some(left);
let mut acc = 0.0;
for (op, right) in chain.into_iter().rev() {
let current_left = left_expr.take();
let string_cmp = matches!(op, BinOp::Eq | BinOp::Ne)
&& (matches!(current_left, Some(Expr::Str(_))) || matches!(right, Expr::Str(_)));
acc = if string_cmp {
let sa = match current_left {
Some(left) => self.expr_str(left)?,
None => fmt_num(acc),
};
let sb = self.expr_str(right)?;
bool_f(if matches!(op, BinOp::Eq) {
sa == sb
} else {
sa != sb
})
} else {
let x = match current_left {
Some(left) => self.eval_expr(left)?,
None => acc,
};
let y = self.eval_expr(right)?;
eval_numeric_bin(op, x, y)?
};
acc = finite(acc, "numeric expression")?;
}
Ok(acc)
}
fn rand(&mut self, seed: Option<f64>) -> f64 {
if let Some(seed) = seed {
self.rng.seed(seed.trunc() as i64);
}
self.rng.next_f64()
}
}