Skip to content

Commit

Permalink
Merge pull request #1876 from mgreter/bugfix/issue_1792
Browse files Browse the repository at this point in the history
Improve operating on numbers with complex units
  • Loading branch information
mgreter committed Jan 18, 2016
2 parents 0c62284 + a60d237 commit f561181
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 11 deletions.
129 changes: 125 additions & 4 deletions src/ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1526,7 +1526,7 @@ namespace Sass {
denominator_units().size() == 0;
}

bool Number::is_unitless()
bool Number::is_unitless() const
{ return numerator_units_.empty() && denominator_units_.empty(); }

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

}

void Number::convert(const std::string& prefered, bool strict)
// this does not cover all cases (multiple prefered units)
double Number::convert_factor(const Number& n) const
{
// abort if unit is empty
if (prefered.empty()) return;

// first make sure same units cancel each other out
// it seems that a map table will fit nicely to do this
// we basically construct exponents for each unit class
// std::map<std::string, int> exponents;
// initialize by summing up occurences in unit vectors
// for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[unit_to_class(numerator_units_[i])];
// for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[unit_to_class(denominator_units_[i])];

std::vector<std::string> l_miss_nums(0);
std::vector<std::string> l_miss_dens(0);
// create copy since we need these for state keeping
std::vector<std::string> r_nums = n.numerator_units_;
std::vector<std::string> r_dens = n.denominator_units_;

std::vector<std::string>::const_iterator l_num_it = numerator_units_.begin();
std::vector<std::string>::const_iterator l_num_end = numerator_units_.end();

bool l_unitless = is_unitless();
bool r_unitless = n.is_unitless();

// overall conversion
double factor = 1;

// process all left numerators
while (l_num_it != l_num_end)
{
// get and increment afterwards
const std::string l_num = *(l_num_it ++);

std::vector<std::string>::iterator r_num_it = r_nums.begin();
std::vector<std::string>::iterator r_num_end = r_nums.end();

// search for compatible numerator
while (r_num_it != r_num_end)
{
// get and increment afterwards
const std::string r_num = *(r_num_it);
// get possible converstion factor for units
double conversion = conversion_factor(l_num, r_num, false);
// skip incompatible numerator
if (conversion == 0) {
++ r_num_it;
continue;
}
// apply to global factor
factor *= conversion;
// remove item from vector
r_nums.erase(r_num_it);
// found it
break;
}
// maybe we did not find any
if (r_num_it == r_num_end) {
// left numerator is leftover
l_miss_nums.push_back(l_num);
}
}

std::vector<std::string>::const_iterator l_den_it = denominator_units_.begin();
std::vector<std::string>::const_iterator l_den_end = denominator_units_.end();

// process all left denominators
while (l_den_it != l_den_end)
{
// get and increment afterwards
const std::string l_den = *(l_den_it ++);

std::vector<std::string>::iterator r_den_it = r_dens.begin();
std::vector<std::string>::iterator r_den_end = r_dens.end();

// search for compatible denominator
while (r_den_it != r_den_end)
{
// get and increment afterwards
const std::string r_den = *(r_den_it);
// get possible converstion factor for units
double conversion = conversion_factor(l_den, r_den, false);
// skip incompatible denominator
if (conversion == 0) {
++ r_den_it;
continue;
}
// apply to global factor
factor *= conversion;
// remove item from vector
r_dens.erase(r_den_it);
// found it
break;
}
// maybe we did not find any
if (r_den_it == r_den_end) {
// left denominator is leftover
l_miss_dens.push_back(l_den);
}
}

// check left-overs (ToDo: might cancel out)
if (l_miss_nums.size() > 0 && !r_unitless) {
throw Exception::IncompatibleUnits(n, *this);
}
if (l_miss_dens.size() > 0 && !r_unitless) {
throw Exception::IncompatibleUnits(n, *this);
}
if (r_nums.size() > 0 && !l_unitless) {
throw Exception::IncompatibleUnits(n, *this);
}
if (r_dens.size() > 0 && !l_unitless) {
throw Exception::IncompatibleUnits(n, *this);
}

return factor;
}

// this does not cover all cases (multiple prefered units)
bool Number::convert(const std::string& prefered, bool strict)
{
// no conversion if unit is empty
if (prefered.empty()) return true;

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

// success?
return true;

}

// useful for making one number compatible with another
Expand Down
5 changes: 3 additions & 2 deletions src/ast.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1321,8 +1321,9 @@ namespace Sass {
static std::string type_name() { return "number"; }
std::string unit() const;

bool is_unitless();
void convert(const std::string& unit = "", bool strict = false);
bool is_unitless() const;
double convert_factor(const Number&) const;
bool convert(const std::string& unit = "", bool strict = false);
void normalize(const std::string& unit = "", bool strict = false);
// useful for making one number compatible with another
std::string find_convertible_unit() const;
Expand Down
9 changes: 5 additions & 4 deletions src/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1440,10 +1440,6 @@ namespace Sass {
tmp.normalize(l.find_convertible_unit(), strict);
std::string l_unit(l.unit());
std::string r_unit(tmp.unit());
if (l_unit != r_unit && !l_unit.empty() && !r_unit.empty() &&
(op == Sass_OP::ADD || op == Sass_OP::SUB)) {
throw Exception::IncompatibleUnits(l, r);
}
Number* v = SASS_MEMORY_NEW(mem, Number, l);
v->pstate(pstate ? *pstate : l.pstate());
if (l_unit.empty() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) {
Expand All @@ -1469,6 +1465,11 @@ namespace Sass {
v->numerator_units().push_back(r.denominator_units()[i]);
}
} else {
Number rh(r);
v->value(ops[op](lv, rh.value() * r.convert_factor(l)));
// v->normalize();
return v;

v->value(ops[op](lv, tmp.value()));
}
v->normalize();
Expand Down
42 changes: 41 additions & 1 deletion src/units.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ namespace Sass {
}
};

std::string get_unit_class(UnitType unit)
{
switch (unit & 0xFF00)
{
case UnitClass::LENGTH: return "LENGTH"; break;
case UnitClass::ANGLE: return "ANGLE"; break;
case UnitClass::TIME: return "TIME"; break;
case UnitClass::FREQUENCY: return "FREQUENCY"; break;
case UnitClass::RESOLUTION: return "RESOLUTION"; break;
default: return "INCOMMENSURABLE"; break;
}
};

UnitType string_to_unit(const std::string& s)
{
// size units
Expand Down Expand Up @@ -120,6 +133,33 @@ namespace Sass {
}
}

std::string unit_to_class(const std::string& s)
{
if (s == "px") return "LENGTH";
else if (s == "pt") return "LENGTH";
else if (s == "pc") return "LENGTH";
else if (s == "mm") return "LENGTH";
else if (s == "cm") return "LENGTH";
else if (s == "in") return "LENGTH";
// angle units
else if (s == "deg") return "ANGLE";
else if (s == "grad") return "ANGLE";
else if (s == "rad") return "ANGLE";
else if (s == "turn") return "ANGLE";
// time units
else if (s == "s") return "TIME";
else if (s == "ms") return "TIME";
// frequency units
else if (s == "Hz") return "FREQUENCY";
else if (s == "kHz") return "FREQUENCY";
// resolutions units
else if (s == "dpi") return "RESOLUTION";
else if (s == "dpcm") return "RESOLUTION";
else if (s == "dppx") return "RESOLUTION";
// for unknown units
return "CUSTOM:" + s;
}

// throws incompatibleUnits exceptions
double conversion_factor(const std::string& s1, const std::string& s2, bool strict)
{
Expand Down Expand Up @@ -151,7 +191,7 @@ namespace Sass {
}
}
// fallback
return 1;
return 0;
}

}
2 changes: 2 additions & 0 deletions src/units.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ namespace Sass {
enum Sass::UnitType string_to_unit(const std::string&);
const char* unit_to_string(Sass::UnitType unit);
enum Sass::UnitClass get_unit_type(Sass::UnitType unit);
std::string get_unit_class(Sass::UnitType unit);
std::string unit_to_class(const std::string&);
// throws incompatibleUnits exceptions
double conversion_factor(const std::string&, const std::string&, bool = true);

Expand Down

0 comments on commit f561181

Please sign in to comment.