diff --git a/src/parse/value/eval.rs b/src/parse/value/eval.rs index 947c2ab..fae3f5c 100644 --- a/src/parse/value/eval.rs +++ b/src/parse/value/eval.rs @@ -424,19 +424,8 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { Value::Dimension(num * num2, unit2) } else if unit2 == Unit::None { Value::Dimension(num * num2, unit) - } else if let Unit::Mul(u) = unit { - let mut unit1 = u.into_vec(); - unit1.push(unit2); - Value::Dimension(num * num2, Unit::Mul(unit1.into_boxed_slice())) - } else if let Unit::Mul(u2) = unit2 { - let mut u = vec![unit]; - u.append(&mut u2.into_vec()); - Value::Dimension(num * num2, Unit::Mul(u.into_boxed_slice())) } else { - Value::Dimension( - num * num2, - Unit::Mul(vec![unit, unit2].into_boxed_slice()), - ) + Value::Dimension(num * num2, unit * unit2) } } _ => { @@ -486,20 +475,20 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { ), Value::Dimension(num, unit) => match right { Value::Dimension(num2, unit2) => { - if !unit.comparable(&unit2) { - return Err(( - format!("Incompatible units {} and {}.", unit2, unit), - self.span, - ) - .into()); - } + // `unit(1em / 1em)` => `""` if unit == unit2 { Value::Dimension(num / num2, Unit::None) + + // `unit(1 / 1em)` => `"em^-1"` } else if unit == Unit::None { - todo!("inverse units") + Value::Dimension(num / num2, Unit::None / unit2) + + // `unit(1em / 1)` => `"em"` } else if unit2 == Unit::None { Value::Dimension(num / num2, unit) - } else { + + // `unit(1in / 1px)` => `""` + } else if unit.comparable(&unit2) { Value::Dimension( num / (num2 * UNIT_CONVERSION_TABLE[unit.to_string().as_str()] @@ -507,6 +496,12 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> { .clone()), Unit::None, ) + // `unit(1em / 1px)` => `"em/px"` + // todo: this should probably be its own variant + // within the `Value` enum + } else { + // todo: remember to account for `Mul` and `Div` + todo!("non-comparable inverse units") } } Value::String(s, q) => { diff --git a/src/unit/mod.rs b/src/unit/mod.rs index 4064179..97ef6c6 100644 --- a/src/unit/mod.rs +++ b/src/unit/mod.rs @@ -1,4 +1,7 @@ -use std::fmt; +use std::{ + fmt, + ops::{Div, Mul}, +}; pub(crate) use conversion::UNIT_CONVERSION_TABLE; @@ -102,6 +105,9 @@ pub(crate) enum Unit { /// Units multiplied together Mul(Box<[Unit]>), + + /// Units divided by each other + Div(Box), } #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub(crate) enum UnitKind { @@ -116,6 +122,113 @@ pub(crate) enum UnitKind { None, } +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct DivUnit { + numer: Unit, + denom: Unit, +} + +impl DivUnit { + pub const fn new(numer: Unit, denom: Unit) -> Self { + Self { numer, denom } + } +} + +impl fmt::Display for DivUnit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.numer == Unit::None { + write!(f, "{}^-1", self.denom) + } else { + write!(f, "{}/{}", self.numer, self.denom) + } + } +} + +#[allow(clippy::match_same_arms)] +impl Mul for DivUnit { + type Output = Unit; + fn mul(self, rhs: Unit) -> Self::Output { + match rhs { + Unit::Mul(..) => todo!(), + Unit::Div(..) => todo!(), + Unit::None => todo!(), + _ => { + if self.denom == rhs { + self.numer + } else { + match self.denom { + Unit::Mul(..) => todo!(), + Unit::Div(..) => unreachable!(), + _ => match self.numer { + Unit::Mul(..) => todo!(), + Unit::Div(..) => unreachable!(), + Unit::None => { + let numer = Unit::Mul(vec![rhs].into_boxed_slice()); + Unit::Div(Box::new(DivUnit::new(numer, self.denom))) + } + _ => { + let numer = Unit::Mul(vec![self.numer, rhs].into_boxed_slice()); + Unit::Div(Box::new(DivUnit::new(numer, self.denom))) + } + }, + } + } + } + } + } +} + +// impl Div for DivUnit { +// type Output = Unit; +// fn div(self, rhs: Unit) -> Self::Output { +// todo!() +// } +// } + +impl Mul for Unit { + type Output = Unit; + fn mul(self, rhs: Unit) -> Self::Output { + match self { + Unit::Mul(u) => match rhs { + Unit::Mul(u2) => { + let mut unit1 = u.into_vec(); + unit1.extend_from_slice(&*u2); + Unit::Mul(unit1.into_boxed_slice()) + } + Unit::Div(..) => todo!(), + _ => { + let mut unit1 = u.into_vec(); + unit1.push(rhs); + Unit::Mul(unit1.into_boxed_slice()) + } + }, + Unit::Div(div) => *div * rhs, + _ => match rhs { + Unit::Mul(u2) => { + let mut unit1 = vec![self]; + unit1.extend_from_slice(&*u2); + Unit::Mul(unit1.into_boxed_slice()) + } + Unit::Div(..) => todo!(), + _ => Unit::Mul(vec![self, rhs].into_boxed_slice()), + }, + } + } +} + +impl Div for Unit { + type Output = Unit; + fn div(self, rhs: Unit) -> Self::Output { + if let Unit::Div(..) = self { + todo!() + } else if let Unit::Div(..) = rhs { + todo!() + } else { + Unit::Div(Box::new(DivUnit::new(self, rhs))) + } + } +} + impl Unit { pub fn comparable(&self, other: &Unit) -> bool { if other == &Unit::None { @@ -150,7 +263,9 @@ impl Unit { Unit::Hz | Unit::Khz => UnitKind::Frequency, Unit::Dpi | Unit::Dpcm | Unit::Dppx | Unit::X => UnitKind::Resolution, Unit::None => UnitKind::None, - Unit::Fr | Unit::Percent | Unit::Unknown(..) | Unit::Mul(..) => UnitKind::Other, + Unit::Fr | Unit::Percent | Unit::Unknown(..) | Unit::Mul(..) | Unit::Div(..) => { + UnitKind::Other + } } } } @@ -246,6 +361,7 @@ impl fmt::Display for Unit { .collect::>() .join("*") ), + Unit::Div(u) => write!(f, "{}", u), } } } diff --git a/src/value/mod.rs b/src/value/mod.rs index 4bef71f..1c62baa 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -125,7 +125,7 @@ impl Value { Ok(match self { Value::Important => Cow::const_str("!important"), Value::Dimension(num, unit) => match unit { - Unit::Mul(..) => { + Unit::Mul(..) | Unit::Div(..) => { return Err((format!("{}{} isn't a valid CSS value.", num, unit), span).into()); } _ => Cow::owned(format!("{}{}", num, unit)), diff --git a/tests/units.rs b/tests/units.rs index a1063c8..bb28e38 100644 --- a/tests/units.rs +++ b/tests/units.rs @@ -89,6 +89,55 @@ test!( "a {\n color: 2߄;\n}\n", "@charset \"UTF-8\";\na {\n color: 2߄;\n}\n" ); +test!( + unit_div_same, + "a {\n color: unit(1em / 1em);\n}\n", + "a {\n color: \"\";\n}\n" +); +test!( + unit_div_first_none, + "a {\n color: unit(1 / 1em);\n}\n", + "a {\n color: \"em^-1\";\n}\n" +); +test!( + unit_div_second_none, + "a {\n color: unit(1em / 1);\n}\n", + "a {\n color: \"em\";\n}\n" +); +test!( + unit_div_comparable, + "a {\n color: unit(1in / 1px);\n color: (1in / 1px);\n}\n", + "a {\n color: \"\";\n color: 96;\n}\n" +); +test!( + unit_mul_times_mul, + "a {\n color: unit((1em * 1px) * (1em * 1px));\n}\n", + "a {\n color: \"em*px*em*px\";\n}\n" +); +test!( + unit_single_times_mul, + "a {\n color: unit(1in * (1em * 1px));\n}\n", + "a {\n color: \"in*em*px\";\n}\n" +); +test!( + unit_div_lhs_mul_uncomparable, + "a {\n color: unit((1 / 1in) * 1em);\n}\n", + "a {\n color: \"em/in\";\n}\n" +); +test!( + unit_div_lhs_mul_same, + "a {\n color: unit((1 / 1in) * 1in);\n}\n", + "a {\n color: \"\";\n}\n" +); +error!( + display_single_div_with_none_numerator, + "a {\n color: (1 / 1em);\n}\n", "Error: 1em^-1 isn't a valid CSS value." +); +error!( + #[ignore = "non-comparable inverse units"] + display_single_div_with_non_comparable_numerator, + "a {\n color: (1px / 1em);\n}\n", "Error: 1px/em isn't a valid CSS value." +); error!( display_single_mul, "a {\n color: 1rem * 1px;\n}\n", "Error: 1rem*px isn't a valid CSS value." @@ -99,8 +148,7 @@ error!( "Error: 1rem*px*rad*foo isn't a valid CSS value." ); error!( - #[ignore] - none_div_unit, + display_single_div_with_none_numerator_percent, "a {\n color: (35 / 7%);\n}\n", "Error: 5%^-1 isn't a valid CSS value." );