use super::*;
pub(crate) fn eval_div(l: Value, r: Value, slash: bool, pos: Pos) -> Result<Value, Error> {
if let Some(e) = callable_value_error(&l, &r, pos) {
return Err(e);
}
if !slash {
if let (Value::Number(a), Value::Number(b)) = (&l, &r) {
return divide_numbers(a, b, pos);
}
}
if slash {
if let (Value::Number(a), Value::Number(b)) = (l.clone().without_slash(), r.clone().without_slash()) {
let repr = format!("{}/{}", slash_repr(&l), slash_repr(&r));
return Ok(Value::Slash(a.div(&b), repr));
}
}
match (l.clone().without_slash(), r.clone().without_slash()) {
(Value::Number(a), Value::Number(b)) => divide_numbers(&a, &b, pos),
(lv @ Value::Color(_), rv @ (Value::Number(_) | Value::Color(_))) => {
Err(undefined_op(&lv, "/", &rv, pos))
}
_ => {
if let Some(e) = serialize_pair_error(&l, &r, pos) {
return Err(e);
}
Ok(Value::Str(SassStr {
text: format!("{}/{}", slash_repr(&l), slash_repr(&r)).into(),
quoted: false,
}))
}
}
}
pub(super) fn divide_numbers(a: &Number, b: &Number, _pos: Pos) -> Result<Value, Error> {
Ok(Value::Number(a.div(b)))
}
pub(super) fn slash_repr(v: &Value) -> String {
match v {
Value::Slash(_, repr) => repr.clone(),
other => other.to_css(false),
}
}
pub(super) fn eval_binary(op: BinOp, l: Value, r: Value, pos: Pos) -> Result<Value, Error> {
match op {
BinOp::Add => binary_add(l, r, pos),
BinOp::Sub => binary_sub(l, r, pos),
BinOp::Mod => num_binop(l, r, pos, "%", sass_modulo),
BinOp::Mul => binary_mul(l, r, pos),
BinOp::Eq => Ok(Value::Bool(l.sass_eq(&r))),
BinOp::Neq => Ok(Value::Bool(!l.sass_eq(&r))),
BinOp::Lt => num_compare(l, r, pos, "<", |a, b| a < b),
BinOp::Gt => num_compare(l, r, pos, ">", |a, b| a > b),
BinOp::Le => num_compare(l, r, pos, "<=", |a, b| a <= b),
BinOp::Ge => num_compare(l, r, pos, ">=", |a, b| a >= b),
BinOp::And | BinOp::Or => Err(Error::unpositioned(
"internal: and/or are short-circuited in eval_expr",
)),
BinOp::SingleEq => Ok(Value::Str(SassStr {
text: format!("{}={}", l.to_css(false), r.to_css(false)).into(),
quoted: false,
})),
}
}
pub(super) fn num_compare(
l: Value,
r: Value,
pos: Pos,
sym: &str,
f: impl Fn(f64, f64) -> bool,
) -> Result<Value, Error> {
match (l, r) {
(Value::Number(a), Value::Number(b)) => {
let (av, bv, _) = coerce_pair(&a, &b, pos)?;
Ok(Value::Bool(f(av, bv)))
}
(l, r) => Err(undefined_op(&l, sym, &r, pos)),
}
}
pub(super) fn binary_add(l: Value, r: Value, pos: Pos) -> Result<Value, Error> {
if let (Value::Number(a), Value::Number(b)) = (&l, &r) {
let (av, bv, proto) = coerce_pair(a, b, pos)?;
return Ok(Value::Number(proto.copy_units(av + bv)));
}
if color_arith_undefined(&l, &r) {
return Err(undefined_op(&l, "+", &r, pos));
}
if let Some(e) = callable_value_error(&l, &r, pos) {
return Err(e);
}
let calc_with_nonstring = (matches!(&l, Value::Calc(_)) && !matches!(&r, Value::Str(_)))
|| (matches!(&r, Value::Calc(_)) && !matches!(&l, Value::Str(_)));
if calc_with_nonstring {
return Err(undefined_op(&l, "+", &r, pos));
}
if let Some(e) = serialize_pair_error(&l, &r, pos) {
return Err(e);
}
let quoted = match &l {
Value::Str(s) => s.quoted,
_ => matches!(&r, Value::Str(s) if s.quoted),
};
let text = format!("{}{}", concat_str(&l), concat_str(&r));
Ok(Value::Str(SassStr {
text: text.into(),
quoted,
}))
}
pub(super) fn binary_sub(l: Value, r: Value, pos: Pos) -> Result<Value, Error> {
if let (Value::Number(a), Value::Number(b)) = (&l, &r) {
let (av, bv, proto) = coerce_pair(a, b, pos)?;
return Ok(Value::Number(proto.copy_units(av - bv)));
}
if color_arith_undefined(&l, &r) {
return Err(undefined_op(&l, "-", &r, pos));
}
if let Some(e) = callable_value_error(&l, &r, pos) {
return Err(e);
}
if matches!(&l, Value::Calc(_)) || matches!(&r, Value::Calc(_)) {
return Err(undefined_op(&l, "-", &r, pos));
}
if let Some(e) = serialize_pair_error(&l, &r, pos) {
return Err(e);
}
let text = format!("{}-{}", l.to_css(false), r.to_css(false));
Ok(Value::Str(SassStr {
text: text.into(),
quoted: false,
}))
}
pub(super) fn binary_mul(l: Value, r: Value, pos: Pos) -> Result<Value, Error> {
match (l, r) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a.mul(&b))),
(l, r) => Err(undefined_op(&l, "*", &r, pos)),
}
}
pub(super) fn sass_modulo(a: f64, b: f64) -> f64 {
if a.is_infinite() {
return f64::NAN;
}
if b.is_infinite() {
return if a.is_sign_negative() == b.is_sign_negative() {
a
} else {
f64::NAN
};
}
if b == 0.0 {
return f64::NAN;
}
a - b * (a / b).floor()
}
pub(super) fn num_binop(
l: Value,
r: Value,
pos: Pos,
sym: &str,
f: impl Fn(f64, f64) -> f64,
) -> Result<Value, Error> {
match (l, r) {
(Value::Number(a), Value::Number(b)) => {
let (av, bv, proto) = coerce_pair(&a, &b, pos)?;
Ok(Value::Number(proto.copy_units(f(av, bv))))
}
(l, r) => Err(undefined_op(&l, sym, &r, pos)),
}
}
pub(super) fn coerce_pair(a: &Number, b: &Number, pos: Pos) -> Result<(f64, f64, Number), Error> {
let incompatible = || {
Err(Error::at(
format!(
"{} and {} have incompatible units.",
a.to_css(false),
b.to_css(false)
),
pos,
))
};
if a.has_complex_units() || b.has_complex_units() {
if b.is_unitless() {
return Ok((a.value, b.value, a.clone()));
}
if a.is_unitless() {
return Ok((a.value, b.value, b.clone()));
}
return match crate::value::unit_lists_factor(
(b.numer_units(), b.denom_units()),
(a.numer_units(), a.denom_units()),
) {
Some(factor) => Ok((a.value, b.value * factor, a.clone())),
None => incompatible(),
};
}
if a.unit() == b.unit() || b.is_unitless() {
return Ok((a.value, b.value, a.clone()));
}
if a.is_unitless() {
return Ok((a.value, b.value, b.clone()));
}
match crate::value::convert_factor(b.unit(), a.unit()) {
Some(factor) => Ok((a.value, b.value * factor, a.clone())),
None => incompatible(),
}
}
pub(super) fn concat_str(v: &Value) -> String {
match v {
Value::Str(s) => s.text.to_string(),
other => other.to_css(false),
}
}
pub(super) fn color_arith_undefined(l: &Value, r: &Value) -> bool {
let numeric = |v: &Value| matches!(v, Value::Color(_) | Value::Number(_));
(matches!(l, Value::Color(_)) && numeric(r)) || (matches!(r, Value::Color(_)) && numeric(l))
}
pub(super) fn serialize_pair_error(l: &Value, r: &Value, pos: Pos) -> Option<Error> {
css_value_error_msg(l)
.or_else(|| css_value_error_msg(r))
.map(|msg| Error::at(msg, pos))
}
pub(super) fn callable_value_error(l: &Value, r: &Value, pos: Pos) -> Option<Error> {
for v in [l, r] {
let inspect = match v {
Value::Function(f) => Some(f.inspect()),
Value::Mixin(m) => Some(m.inspect()),
_ => None,
};
if let Some(s) = inspect {
return Some(Error::at(format!("{s} isn't a valid CSS value."), pos));
}
}
None
}
pub(super) fn undefined_op(l: &Value, sym: &str, r: &Value, pos: Pos) -> Error {
Error::at(
format!(
"Undefined operation \"{} {} {}\".",
l.to_css(false),
sym,
r.to_css(false)
),
pos,
)
}