Skip to content

Commit f561181

Browse files
committed
Merge pull request #1876 from mgreter/bugfix/issue_1792
Improve operating on numbers with complex units
2 parents 0c62284 + a60d237 commit f561181

File tree

5 files changed

+176
-11
lines changed

5 files changed

+176
-11
lines changed

src/ast.cpp

Lines changed: 125 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,7 +1526,7 @@ namespace Sass {
15261526
denominator_units().size() == 0;
15271527
}
15281528

1529-
bool Number::is_unitless()
1529+
bool Number::is_unitless() const
15301530
{ return numerator_units_.empty() && denominator_units_.empty(); }
15311531

15321532
void Number::normalize(const std::string& prefered, bool strict)
@@ -1614,10 +1614,128 @@ namespace Sass {
16141614

16151615
}
16161616

1617-
void Number::convert(const std::string& prefered, bool strict)
1617+
// this does not cover all cases (multiple prefered units)
1618+
double Number::convert_factor(const Number& n) const
16181619
{
1619-
// abort if unit is empty
1620-
if (prefered.empty()) return;
1620+
1621+
// first make sure same units cancel each other out
1622+
// it seems that a map table will fit nicely to do this
1623+
// we basically construct exponents for each unit class
1624+
// std::map<std::string, int> exponents;
1625+
// initialize by summing up occurences in unit vectors
1626+
// for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[unit_to_class(numerator_units_[i])];
1627+
// for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[unit_to_class(denominator_units_[i])];
1628+
1629+
std::vector<std::string> l_miss_nums(0);
1630+
std::vector<std::string> l_miss_dens(0);
1631+
// create copy since we need these for state keeping
1632+
std::vector<std::string> r_nums = n.numerator_units_;
1633+
std::vector<std::string> r_dens = n.denominator_units_;
1634+
1635+
std::vector<std::string>::const_iterator l_num_it = numerator_units_.begin();
1636+
std::vector<std::string>::const_iterator l_num_end = numerator_units_.end();
1637+
1638+
bool l_unitless = is_unitless();
1639+
bool r_unitless = n.is_unitless();
1640+
1641+
// overall conversion
1642+
double factor = 1;
1643+
1644+
// process all left numerators
1645+
while (l_num_it != l_num_end)
1646+
{
1647+
// get and increment afterwards
1648+
const std::string l_num = *(l_num_it ++);
1649+
1650+
std::vector<std::string>::iterator r_num_it = r_nums.begin();
1651+
std::vector<std::string>::iterator r_num_end = r_nums.end();
1652+
1653+
// search for compatible numerator
1654+
while (r_num_it != r_num_end)
1655+
{
1656+
// get and increment afterwards
1657+
const std::string r_num = *(r_num_it);
1658+
// get possible converstion factor for units
1659+
double conversion = conversion_factor(l_num, r_num, false);
1660+
// skip incompatible numerator
1661+
if (conversion == 0) {
1662+
++ r_num_it;
1663+
continue;
1664+
}
1665+
// apply to global factor
1666+
factor *= conversion;
1667+
// remove item from vector
1668+
r_nums.erase(r_num_it);
1669+
// found it
1670+
break;
1671+
}
1672+
// maybe we did not find any
1673+
if (r_num_it == r_num_end) {
1674+
// left numerator is leftover
1675+
l_miss_nums.push_back(l_num);
1676+
}
1677+
}
1678+
1679+
std::vector<std::string>::const_iterator l_den_it = denominator_units_.begin();
1680+
std::vector<std::string>::const_iterator l_den_end = denominator_units_.end();
1681+
1682+
// process all left denominators
1683+
while (l_den_it != l_den_end)
1684+
{
1685+
// get and increment afterwards
1686+
const std::string l_den = *(l_den_it ++);
1687+
1688+
std::vector<std::string>::iterator r_den_it = r_dens.begin();
1689+
std::vector<std::string>::iterator r_den_end = r_dens.end();
1690+
1691+
// search for compatible denominator
1692+
while (r_den_it != r_den_end)
1693+
{
1694+
// get and increment afterwards
1695+
const std::string r_den = *(r_den_it);
1696+
// get possible converstion factor for units
1697+
double conversion = conversion_factor(l_den, r_den, false);
1698+
// skip incompatible denominator
1699+
if (conversion == 0) {
1700+
++ r_den_it;
1701+
continue;
1702+
}
1703+
// apply to global factor
1704+
factor *= conversion;
1705+
// remove item from vector
1706+
r_dens.erase(r_den_it);
1707+
// found it
1708+
break;
1709+
}
1710+
// maybe we did not find any
1711+
if (r_den_it == r_den_end) {
1712+
// left denominator is leftover
1713+
l_miss_dens.push_back(l_den);
1714+
}
1715+
}
1716+
1717+
// check left-overs (ToDo: might cancel out)
1718+
if (l_miss_nums.size() > 0 && !r_unitless) {
1719+
throw Exception::IncompatibleUnits(n, *this);
1720+
}
1721+
if (l_miss_dens.size() > 0 && !r_unitless) {
1722+
throw Exception::IncompatibleUnits(n, *this);
1723+
}
1724+
if (r_nums.size() > 0 && !l_unitless) {
1725+
throw Exception::IncompatibleUnits(n, *this);
1726+
}
1727+
if (r_dens.size() > 0 && !l_unitless) {
1728+
throw Exception::IncompatibleUnits(n, *this);
1729+
}
1730+
1731+
return factor;
1732+
}
1733+
1734+
// this does not cover all cases (multiple prefered units)
1735+
bool Number::convert(const std::string& prefered, bool strict)
1736+
{
1737+
// no conversion if unit is empty
1738+
if (prefered.empty()) return true;
16211739

16221740
// first make sure same units cancel each other out
16231741
// it seems that a map table will fit nicely to do this
@@ -1699,6 +1817,9 @@ namespace Sass {
16991817
// best precision this way
17001818
value_ *= factor;
17011819

1820+
// success?
1821+
return true;
1822+
17021823
}
17031824

17041825
// useful for making one number compatible with another

src/ast.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,8 +1321,9 @@ namespace Sass {
13211321
static std::string type_name() { return "number"; }
13221322
std::string unit() const;
13231323

1324-
bool is_unitless();
1325-
void convert(const std::string& unit = "", bool strict = false);
1324+
bool is_unitless() const;
1325+
double convert_factor(const Number&) const;
1326+
bool convert(const std::string& unit = "", bool strict = false);
13261327
void normalize(const std::string& unit = "", bool strict = false);
13271328
// useful for making one number compatible with another
13281329
std::string find_convertible_unit() const;

src/eval.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,10 +1440,6 @@ namespace Sass {
14401440
tmp.normalize(l.find_convertible_unit(), strict);
14411441
std::string l_unit(l.unit());
14421442
std::string r_unit(tmp.unit());
1443-
if (l_unit != r_unit && !l_unit.empty() && !r_unit.empty() &&
1444-
(op == Sass_OP::ADD || op == Sass_OP::SUB)) {
1445-
throw Exception::IncompatibleUnits(l, r);
1446-
}
14471443
Number* v = SASS_MEMORY_NEW(mem, Number, l);
14481444
v->pstate(pstate ? *pstate : l.pstate());
14491445
if (l_unit.empty() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) {
@@ -1469,6 +1465,11 @@ namespace Sass {
14691465
v->numerator_units().push_back(r.denominator_units()[i]);
14701466
}
14711467
} else {
1468+
Number rh(r);
1469+
v->value(ops[op](lv, rh.value() * r.convert_factor(l)));
1470+
// v->normalize();
1471+
return v;
1472+
14721473
v->value(ops[op](lv, tmp.value()));
14731474
}
14741475
v->normalize();

src/units.cpp

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,19 @@ namespace Sass {
6262
}
6363
};
6464

65+
std::string get_unit_class(UnitType unit)
66+
{
67+
switch (unit & 0xFF00)
68+
{
69+
case UnitClass::LENGTH: return "LENGTH"; break;
70+
case UnitClass::ANGLE: return "ANGLE"; break;
71+
case UnitClass::TIME: return "TIME"; break;
72+
case UnitClass::FREQUENCY: return "FREQUENCY"; break;
73+
case UnitClass::RESOLUTION: return "RESOLUTION"; break;
74+
default: return "INCOMMENSURABLE"; break;
75+
}
76+
};
77+
6578
UnitType string_to_unit(const std::string& s)
6679
{
6780
// size units
@@ -120,6 +133,33 @@ namespace Sass {
120133
}
121134
}
122135

136+
std::string unit_to_class(const std::string& s)
137+
{
138+
if (s == "px") return "LENGTH";
139+
else if (s == "pt") return "LENGTH";
140+
else if (s == "pc") return "LENGTH";
141+
else if (s == "mm") return "LENGTH";
142+
else if (s == "cm") return "LENGTH";
143+
else if (s == "in") return "LENGTH";
144+
// angle units
145+
else if (s == "deg") return "ANGLE";
146+
else if (s == "grad") return "ANGLE";
147+
else if (s == "rad") return "ANGLE";
148+
else if (s == "turn") return "ANGLE";
149+
// time units
150+
else if (s == "s") return "TIME";
151+
else if (s == "ms") return "TIME";
152+
// frequency units
153+
else if (s == "Hz") return "FREQUENCY";
154+
else if (s == "kHz") return "FREQUENCY";
155+
// resolutions units
156+
else if (s == "dpi") return "RESOLUTION";
157+
else if (s == "dpcm") return "RESOLUTION";
158+
else if (s == "dppx") return "RESOLUTION";
159+
// for unknown units
160+
return "CUSTOM:" + s;
161+
}
162+
123163
// throws incompatibleUnits exceptions
124164
double conversion_factor(const std::string& s1, const std::string& s2, bool strict)
125165
{
@@ -151,7 +191,7 @@ namespace Sass {
151191
}
152192
}
153193
// fallback
154-
return 1;
194+
return 0;
155195
}
156196

157197
}

src/units.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ namespace Sass {
6161
enum Sass::UnitType string_to_unit(const std::string&);
6262
const char* unit_to_string(Sass::UnitType unit);
6363
enum Sass::UnitClass get_unit_type(Sass::UnitType unit);
64+
std::string get_unit_class(Sass::UnitType unit);
65+
std::string unit_to_class(const std::string&);
6466
// throws incompatibleUnits exceptions
6567
double conversion_factor(const std::string&, const std::string&, bool = true);
6668

0 commit comments

Comments
 (0)