partially implement inverse units

This commit is contained in:
Connor Skees 2020-07-04 11:04:51 -04:00
parent fb24d4db4f
commit b28309147e
4 changed files with 185 additions and 26 deletions

View File

@ -424,19 +424,8 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
Value::Dimension(num * num2, unit2) Value::Dimension(num * num2, unit2)
} else if unit2 == Unit::None { } else if unit2 == Unit::None {
Value::Dimension(num * num2, unit) 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 { } else {
Value::Dimension( Value::Dimension(num * num2, unit * unit2)
num * num2,
Unit::Mul(vec![unit, unit2].into_boxed_slice()),
)
} }
} }
_ => { _ => {
@ -486,20 +475,20 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
), ),
Value::Dimension(num, unit) => match right { Value::Dimension(num, unit) => match right {
Value::Dimension(num2, unit2) => { Value::Dimension(num2, unit2) => {
if !unit.comparable(&unit2) { // `unit(1em / 1em)` => `""`
return Err((
format!("Incompatible units {} and {}.", unit2, unit),
self.span,
)
.into());
}
if unit == unit2 { if unit == unit2 {
Value::Dimension(num / num2, Unit::None) Value::Dimension(num / num2, Unit::None)
// `unit(1 / 1em)` => `"em^-1"`
} else if unit == Unit::None { } else if unit == Unit::None {
todo!("inverse units") Value::Dimension(num / num2, Unit::None / unit2)
// `unit(1em / 1)` => `"em"`
} else if unit2 == Unit::None { } else if unit2 == Unit::None {
Value::Dimension(num / num2, unit) Value::Dimension(num / num2, unit)
} else {
// `unit(1in / 1px)` => `""`
} else if unit.comparable(&unit2) {
Value::Dimension( Value::Dimension(
num / (num2 num / (num2
* UNIT_CONVERSION_TABLE[unit.to_string().as_str()] * UNIT_CONVERSION_TABLE[unit.to_string().as_str()]
@ -507,6 +496,12 @@ impl<'a, 'b: 'a> ValueVisitor<'a, 'b> {
.clone()), .clone()),
Unit::None, 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) => { Value::String(s, q) => {

View File

@ -1,4 +1,7 @@
use std::fmt; use std::{
fmt,
ops::{Div, Mul},
};
pub(crate) use conversion::UNIT_CONVERSION_TABLE; pub(crate) use conversion::UNIT_CONVERSION_TABLE;
@ -102,6 +105,9 @@ pub(crate) enum Unit {
/// Units multiplied together /// Units multiplied together
Mul(Box<[Unit]>), Mul(Box<[Unit]>),
/// Units divided by each other
Div(Box<DivUnit>),
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum UnitKind { pub(crate) enum UnitKind {
@ -116,6 +122,113 @@ pub(crate) enum UnitKind {
None, 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<Unit> 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<Unit> for DivUnit {
// type Output = Unit;
// fn div(self, rhs: Unit) -> Self::Output {
// todo!()
// }
// }
impl Mul<Unit> 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<Unit> 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 { impl Unit {
pub fn comparable(&self, other: &Unit) -> bool { pub fn comparable(&self, other: &Unit) -> bool {
if other == &Unit::None { if other == &Unit::None {
@ -150,7 +263,9 @@ impl Unit {
Unit::Hz | Unit::Khz => UnitKind::Frequency, Unit::Hz | Unit::Khz => UnitKind::Frequency,
Unit::Dpi | Unit::Dpcm | Unit::Dppx | Unit::X => UnitKind::Resolution, Unit::Dpi | Unit::Dpcm | Unit::Dppx | Unit::X => UnitKind::Resolution,
Unit::None => UnitKind::None, 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::<Vec<String>>() .collect::<Vec<String>>()
.join("*") .join("*")
), ),
Unit::Div(u) => write!(f, "{}", u),
} }
} }
} }

View File

@ -125,7 +125,7 @@ impl Value {
Ok(match self { Ok(match self {
Value::Important => Cow::const_str("!important"), Value::Important => Cow::const_str("!important"),
Value::Dimension(num, unit) => match unit { 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()); return Err((format!("{}{} isn't a valid CSS value.", num, unit), span).into());
} }
_ => Cow::owned(format!("{}{}", num, unit)), _ => Cow::owned(format!("{}{}", num, unit)),

View File

@ -89,6 +89,55 @@ test!(
"a {\n color: 2߄;\n}\n", "a {\n color: 2߄;\n}\n",
"@charset \"UTF-8\";\na {\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!( error!(
display_single_mul, display_single_mul,
"a {\n color: 1rem * 1px;\n}\n", "Error: 1rem*px isn't a valid CSS value." "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: 1rem*px*rad*foo isn't a valid CSS value."
); );
error!( error!(
#[ignore] display_single_div_with_none_numerator_percent,
none_div_unit,
"a {\n color: (35 / 7%);\n}\n", "Error: 5%^-1 isn't a valid CSS value." "a {\n color: (35 / 7%);\n}\n", "Error: 5%^-1 isn't a valid CSS value."
); );