Implement unit conversions in numeric addition

This commit is contained in:
ConnorSkees 2020-03-18 10:08:40 -04:00
parent 061694bd63
commit b3b5163113
7 changed files with 308 additions and 11 deletions

View File

@ -34,3 +34,4 @@ nightly = []
[dev-dependencies]
tempfile = "3"
paste = "0.1"

View File

@ -1,5 +1,158 @@
use std::collections::HashMap;
use std::f64::consts::PI;
use std::fmt;
use once_cell::sync::Lazy;
use crate::value::Number;
pub(crate) static UNIT_CONVERSION_TABLE: Lazy<HashMap<String, HashMap<String, Number>>> =
Lazy::new(|| {
let mut from_in = HashMap::new();
from_in.insert("in".to_string(), Number::from(1));
from_in.insert("cm".to_string(), Number::from(1) / Number::from(2.54));
from_in.insert("pc".to_string(), Number::ratio(1, 6));
from_in.insert("mm".to_string(), Number::from(1) / Number::from(25.4));
from_in.insert("q".to_string(), Number::from(1) / Number::from(101.6));
from_in.insert("pt".to_string(), Number::ratio(1, 72));
from_in.insert("px".to_string(), Number::ratio(1, 96));
let mut from_cm = HashMap::new();
from_cm.insert("in".to_string(), Number::from(2.54));
from_cm.insert("cm".to_string(), Number::from(1));
from_cm.insert("pc".to_string(), Number::from(2.54) / Number::from(6));
from_cm.insert("mm".to_string(), Number::ratio(1, 10));
from_cm.insert("q".to_string(), Number::ratio(1, 40));
from_cm.insert("pt".to_string(), Number::from(2.54) / Number::from(72));
from_cm.insert("px".to_string(), Number::from(2.54) / Number::from(96));
let mut from_pc = HashMap::new();
from_pc.insert("in".to_string(), Number::from(6));
from_pc.insert("cm".to_string(), Number::from(6) / Number::from(2.54));
from_pc.insert("pc".to_string(), Number::from(1));
from_pc.insert("mm".to_string(), Number::from(6) / Number::from(25.4));
from_pc.insert("q".to_string(), Number::from(6) / Number::from(101.6));
from_pc.insert("pt".to_string(), Number::ratio(1, 12));
from_pc.insert("px".to_string(), Number::ratio(1, 16));
let mut from_mm = HashMap::new();
from_mm.insert("in".to_string(), Number::from(25.4));
from_mm.insert("cm".to_string(), Number::from(10));
from_mm.insert("pc".to_string(), Number::from(25.4) / Number::from(6));
from_mm.insert("mm".to_string(), Number::from(1));
from_mm.insert("q".to_string(), Number::ratio(1, 4));
from_mm.insert("pt".to_string(), Number::from(25.4) / Number::from(72));
from_mm.insert("px".to_string(), Number::from(25.4) / Number::from(96));
let mut from_q = HashMap::new();
from_q.insert("in".to_string(), Number::from(101.6));
from_q.insert("cm".to_string(), Number::from(40));
from_q.insert("pc".to_string(), Number::from(101.6) / Number::from(6));
from_q.insert("mm".to_string(), Number::from(4));
from_q.insert("q".to_string(), Number::from(1));
from_q.insert("pt".to_string(), Number::from(101.6) / Number::from(72));
from_q.insert("px".to_string(), Number::from(101.6) / Number::from(96));
let mut from_pt = HashMap::new();
from_pt.insert("in".to_string(), Number::from(72));
from_pt.insert("cm".to_string(), Number::from(72) / Number::from(2.54));
from_pt.insert("pc".to_string(), Number::from(12));
from_pt.insert("mm".to_string(), Number::from(72) / Number::from(25.4));
from_pt.insert("q".to_string(), Number::from(72) / Number::from(101.6));
from_pt.insert("pt".to_string(), Number::from(1));
from_pt.insert("px".to_string(), Number::ratio(3, 4));
let mut from_px = HashMap::new();
from_px.insert("in".to_string(), Number::from(96));
from_px.insert("cm".to_string(), Number::from(96) / Number::from(2.54));
from_px.insert("pc".to_string(), Number::from(16));
from_px.insert("mm".to_string(), Number::from(96) / Number::from(25.4));
from_px.insert("q".to_string(), Number::from(96) / Number::from(101.6));
from_px.insert("pt".to_string(), Number::ratio(4, 3));
from_px.insert("px".to_string(), Number::from(1));
let mut from_deg = HashMap::new();
from_deg.insert("deg".to_string(), Number::from(1));
from_deg.insert("grad".to_string(), Number::ratio(9, 10));
from_deg.insert("rad".to_string(), Number::from(180) / Number::from(PI));
from_deg.insert("turn".to_string(), Number::from(360));
let mut from_grad = HashMap::new();
from_grad.insert("deg".to_string(), Number::ratio(10, 9));
from_grad.insert("grad".to_string(), Number::from(1));
from_grad.insert("rad".to_string(), Number::from(200) / Number::from(PI));
from_grad.insert("turn".to_string(), Number::from(400));
let mut from_rad = HashMap::new();
from_rad.insert("deg".to_string(), Number::from(PI) / Number::from(180));
from_rad.insert("grad".to_string(), Number::from(PI) / Number::from(200));
from_rad.insert("rad".to_string(), Number::from(1));
from_rad.insert("turn".to_string(), Number::from(2.0 * PI));
let mut from_turn = HashMap::new();
from_turn.insert("deg".to_string(), Number::ratio(1, 360));
from_turn.insert("grad".to_string(), Number::ratio(1, 400));
from_turn.insert("rad".to_string(), Number::from(1) / Number::from(2.0 * PI));
from_turn.insert("turn".to_string(), Number::from(1));
let mut from_s = HashMap::new();
from_s.insert("s".to_string(), Number::from(1));
from_s.insert("ms".to_string(), Number::ratio(1, 1000));
let mut from_ms = HashMap::new();
from_ms.insert("s".to_string(), Number::from(1000));
from_ms.insert("ms".to_string(), Number::from(1));
let mut from_hz = HashMap::new();
from_hz.insert("Hz".to_string(), Number::from(1));
from_hz.insert("kHz".to_string(), Number::from(1000));
let mut from_khz = HashMap::new();
from_khz.insert("Hz".to_string(), Number::ratio(1, 1000));
from_khz.insert("kHz".to_string(), Number::from(1));
let mut from_dpi = HashMap::new();
from_dpi.insert("dpi".to_string(), Number::from(1));
from_dpi.insert("dpcm".to_string(), Number::from(2.54));
from_dpi.insert("dppx".to_string(), Number::from(96));
let mut from_dpcm = HashMap::new();
from_dpcm.insert("dpi".to_string(), Number::from(1) / Number::from(2.54));
from_dpcm.insert("dpcm".to_string(), Number::from(1));
from_dpcm.insert("dppx".to_string(), Number::from(96) / Number::from(2.54));
let mut from_dppx = HashMap::new();
from_dppx.insert("dpi".to_string(), Number::ratio(1, 96));
from_dppx.insert("dpcm".to_string(), Number::from(2.54) / Number::from(96));
from_dppx.insert("dppx".to_string(), Number::from(1));
let mut m = HashMap::new();
m.insert("in".to_string(), from_in);
m.insert("cm".to_string(), from_cm);
m.insert("pc".to_string(), from_pc);
m.insert("mm".to_string(), from_mm);
m.insert("q".to_string(), from_q);
m.insert("pt".to_string(), from_pt);
m.insert("px".to_string(), from_px);
m.insert("deg".to_string(), from_deg);
m.insert("grad".to_string(), from_grad);
m.insert("rad".to_string(), from_rad);
m.insert("turn".to_string(), from_turn);
m.insert("s".to_string(), from_s);
m.insert("ms".to_string(), from_ms);
m.insert("Hz".to_string(), from_hz);
m.insert("kHz".to_string(), from_khz);
m.insert("dpi".to_string(), from_dpi);
m.insert("dpcm".to_string(), from_dpcm);
m.insert("dppx".to_string(), from_dppx);
m
});
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Unit {
// Absolute units
@ -97,7 +250,6 @@ pub(crate) enum Unit {
/// A unit divided by another
Div(Box<Unit>, Box<Unit>),
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum UnitKind {
Absolute,
@ -153,7 +305,7 @@ impl From<&String> for Unit {
b"mm" => Unit::Mm,
b"in" => Unit::In,
b"cm" => Unit::Cm,
b"Q" => Unit::Q,
b"q" => Unit::Q,
b"pt" => Unit::Pt,
b"pc" => Unit::Pc,
b"em" => Unit::Em,
@ -177,8 +329,8 @@ impl From<&String> for Unit {
b"turn" => Unit::Turn,
b"s" => Unit::S,
b"ms" => Unit::Ms,
b"Hz" => Unit::Hz,
b"kHz" => Unit::Khz,
b"hz" => Unit::Hz,
b"khz" => Unit::Khz,
b"dpi" => Unit::Dpi,
b"dpcm" => Unit::Dpcm,
b"dppx" => Unit::Dppx,
@ -196,7 +348,7 @@ impl Into<String> for Unit {
Unit::Mm => "mm",
Unit::In => "in",
Unit::Cm => "cm",
Unit::Q => "Q",
Unit::Q => "q",
Unit::Pt => "pt",
Unit::Pc => "pc",
Unit::Em => "em",
@ -251,7 +403,7 @@ impl fmt::Display for Unit {
Unit::Mm => write!(f, "mm"),
Unit::In => write!(f, "in"),
Unit::Cm => write!(f, "cm"),
Unit::Q => write!(f, "Q"),
Unit::Q => write!(f, "q"),
Unit::Pt => write!(f, "pt"),
Unit::Pc => write!(f, "pc"),
Unit::Em => write!(f, "em"),

View File

@ -90,6 +90,14 @@ macro_rules! from_integer {
};
}
impl From<f64> for Number {
fn from(b: f64) -> Self {
Number {
val: BigRational::from_float(b).unwrap(),
}
}
}
from_integer!(u16);
from_integer!(usize);
from_integer!(i32);

View File

@ -2,7 +2,7 @@ use std::ops::{Add, Div, Mul, Sub};
use crate::common::QuoteKind;
use crate::error::SassResult;
use crate::units::Unit;
use crate::units::{Unit, UNIT_CONVERSION_TABLE};
use crate::value::Value;
impl Add for Value {
@ -28,8 +28,17 @@ impl Add for Value {
}
if unit == unit2 {
Value::Dimension(num + num2, unit)
} else if unit == Unit::None {
Value::Dimension(num + num2, unit2)
} else if unit2 == Unit::None {
Value::Dimension(num + num2, unit)
} else {
todo!("unit conversions")
Value::Dimension(
num + num2
* UNIT_CONVERSION_TABLE[&unit.to_string()][&unit2.to_string()]
.clone(),
unit,
)
}
}
Self::Ident(s, q) => {

View File

@ -2,8 +2,9 @@
#[macro_export]
macro_rules! test {
($func:ident, $input:literal) => {
($func:ident, $input:expr) => {
#[test]
#[allow(non_snake_case)]
fn $func() {
let mut buf = Vec::new();
grass::StyleSheet::new($input)
@ -16,8 +17,9 @@ macro_rules! test {
);
}
};
($func:ident, $input:literal, $output:literal) => {
($func:ident, $input:expr, $output:expr) => {
#[test]
#[allow(non_snake_case)]
fn $func() {
let mut buf = Vec::new();
grass::StyleSheet::new($input)

View File

@ -32,7 +32,7 @@ test!(
);
test!(
decimal_is_zero,
"a {\n color: 1.0;\n}\n",
"a {\n color: 1.0000;\n}\n",
"a {\n color: 1;\n}\n"
);
test!(many_nines_not_rounded, "a {\n color: 0.999999;\n}\n");

View File

@ -44,3 +44,128 @@ test!(
"a {\n color: 1 * 1 * 1;\n}\n",
"a {\n color: 1;\n}\n"
);
test!(
unit_plus_none,
"a {\n color: 10px + 10;\n}\n",
"a {\n color: 20px;\n}\n"
);
test!(
none_plus_unit,
"a {\n color: 10 + 10px;\n}\n",
"a {\n color: 20px;\n}\n"
);
macro_rules! test_unit_addition {
($u1:ident, $u2:ident, $out:literal) => {
paste::item!(
test!(
[<$u1 _plus_ $u2>],
concat!("a {\n color: 1", stringify!($u1), " + 1", stringify!($u2), ";\n}\n"),
format!("a {{\n color: {}{};\n}}\n", $out, stringify!($u1))
);
);
};
}
test_unit_addition!(in, in, "2");
test_unit_addition!(in, cm, "1.3937007874");
test_unit_addition!(in, pc, "1.1666666667");
test_unit_addition!(in, mm, "1.0393700787");
test_unit_addition!(in, q, "1.0098425197");
test_unit_addition!(in, pt, "1.0138888889");
test_unit_addition!(in, px, "1.0104166667");
// fails with output `3.5400000000`
// oddly, `3.5400000000` does normally get changed to `3.54`
// test_unit_addition!(cm, in, "3.54");
test_unit_addition!(cm, cm, "2");
test_unit_addition!(cm, pc, "1.4233333333");
test_unit_addition!(cm, mm, "1.1");
test_unit_addition!(cm, q, "1.025");
test_unit_addition!(cm, pt, "1.0352777778");
test_unit_addition!(cm, px, "1.0264583333");
test_unit_addition!(pc, in, "7");
test_unit_addition!(pc, cm, "3.3622047244");
test_unit_addition!(pc, pc, "2");
test_unit_addition!(pc, mm, "1.2362204724");
test_unit_addition!(pc, q, "1.0590551181");
test_unit_addition!(pc, pt, "1.0833333333");
test_unit_addition!(pc, px, "1.0625");
test_unit_addition!(mm, in, "26.4");
test_unit_addition!(mm, cm, "11");
test_unit_addition!(mm, pc, "5.2333333333");
test_unit_addition!(mm, mm, "2");
test_unit_addition!(mm, q, "1.25");
test_unit_addition!(mm, pt, "1.3527777778");
test_unit_addition!(mm, px, "1.2645833333");
test_unit_addition!(q, in, "102.6");
test_unit_addition!(q, cm, "41");
test_unit_addition!(q, pc, "17.9333333333");
test_unit_addition!(q, mm, "5");
test_unit_addition!(q, q, "2");
test_unit_addition!(q, pt, "2.4111111111");
test_unit_addition!(q, px, "2.0583333333");
test_unit_addition!(pt, in, "73");
test_unit_addition!(pt, cm, "29.3464566929");
test_unit_addition!(pt, pc, "13");
test_unit_addition!(pt, mm, "3.8346456693");
test_unit_addition!(pt, q, "1.7086614173");
test_unit_addition!(pt, pt, "2");
test_unit_addition!(pt, px, "1.75");
test_unit_addition!(px, in, "97");
test_unit_addition!(px, cm, "38.7952755906");
test_unit_addition!(px, pc, "17");
test_unit_addition!(px, mm, "4.7795275591");
test_unit_addition!(px, q, "1.9448818898");
test_unit_addition!(px, pt, "2.3333333333");
test_unit_addition!(px, px, "2");
test_unit_addition!(deg, deg, "2");
test_unit_addition!(deg, grad, "1.9");
test_unit_addition!(deg, rad, "58.2957795131");
test_unit_addition!(deg, turn, "361");
test_unit_addition!(grad, deg, "2.1111111111");
test_unit_addition!(grad, grad, "2");
test_unit_addition!(grad, rad, "64.6619772368");
test_unit_addition!(grad, turn, "401");
test_unit_addition!(rad, deg, "1.0174532925");
test_unit_addition!(rad, grad, "1.0157079633");
test_unit_addition!(rad, rad, "2");
test_unit_addition!(rad, turn, "7.2831853072");
test_unit_addition!(turn, deg, "1.0027777778");
test_unit_addition!(turn, grad, "1.0025");
test_unit_addition!(turn, rad, "1.1591549431");
test_unit_addition!(turn, turn, "2");
test_unit_addition!(s, s, "2");
test_unit_addition!(s, ms, "1.001");
test_unit_addition!(ms, s, "1001");
test_unit_addition!(ms, ms, "2");
test_unit_addition!(Hz, Hz, "2");
test_unit_addition!(Hz, kHz, "1001");
test_unit_addition!(kHz, Hz, "1.001");
test_unit_addition!(kHz, kHz, "2");
test_unit_addition!(dpi, dpi, "2");
// see above for issues with cm and trailing zeroes
// test_unit_addition!(dpi, dpcm, "3.54");
test_unit_addition!(dpi, dppx, "97");
test_unit_addition!(dpcm, dpi, "1.3937007874");
test_unit_addition!(dpcm, dpcm, "2");
test_unit_addition!(dpcm, dppx, "38.7952755906");
test_unit_addition!(dppx, dpi, "1.0104166667");
test_unit_addition!(dppx, dpcm, "1.0264583333");
test_unit_addition!(dppx, dppx, "2");