From b3b5163113d4e597b7196ab68ba30d95374643fb Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Wed, 18 Mar 2020 10:08:40 -0400 Subject: [PATCH] Implement unit conversions in numeric addition --- Cargo.toml | 1 + src/units.rs | 164 ++++++++++++++++++++++++++++++++++++++++++-- src/value/number.rs | 8 +++ src/value/ops.rs | 13 +++- tests/macros.rs | 6 +- tests/number.rs | 2 +- tests/units.rs | 125 +++++++++++++++++++++++++++++++++ 7 files changed, 308 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e28028d..ff2e83e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,4 @@ nightly = [] [dev-dependencies] tempfile = "3" +paste = "0.1" diff --git a/src/units.rs b/src/units.rs index 6dd29b8..2953180 100644 --- a/src/units.rs +++ b/src/units.rs @@ -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>> = + 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, Box), } - #[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 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"), diff --git a/src/value/number.rs b/src/value/number.rs index 49d0e7d..104b459 100644 --- a/src/value/number.rs +++ b/src/value/number.rs @@ -90,6 +90,14 @@ macro_rules! from_integer { }; } +impl From for Number { + fn from(b: f64) -> Self { + Number { + val: BigRational::from_float(b).unwrap(), + } + } +} + from_integer!(u16); from_integer!(usize); from_integer!(i32); diff --git a/src/value/ops.rs b/src/value/ops.rs index a82cdeb..4faa25d 100644 --- a/src/value/ops.rs +++ b/src/value/ops.rs @@ -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) => { diff --git a/tests/macros.rs b/tests/macros.rs index 035d7d4..447250a 100644 --- a/tests/macros.rs +++ b/tests/macros.rs @@ -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) diff --git a/tests/number.rs b/tests/number.rs index 6460b23..2e59757 100644 --- a/tests/number.rs +++ b/tests/number.rs @@ -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"); diff --git a/tests/units.rs b/tests/units.rs index 7c24626..9634213 100644 --- a/tests/units.rs +++ b/tests/units.rs @@ -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");