#include "ast.hpp" #include "context.hpp" #include "to_string.hpp" #include #include #include namespace Sass { using namespace std; static Null sass_null(Sass::Null(ParserState("null"))); bool Compound_Selector::operator<(const Compound_Selector& rhs) const { To_String to_string; // ugly return const_cast(this)->perform(&to_string) < const_cast(rhs).perform(&to_string); } bool Complex_Selector::operator<(const Complex_Selector& rhs) const { To_String to_string; return const_cast(this)->perform(&to_string) < const_cast(rhs).perform(&to_string); } bool Complex_Selector::operator==(const Complex_Selector& rhs) const { // TODO: We have to access the tail directly using tail_ since ADD_PROPERTY doesn't provide a const version. const Complex_Selector* pOne = this; const Complex_Selector* pTwo = &rhs; // Consume any empty references at the beginning of the Complex_Selector if (pOne->combinator() == Complex_Selector::ANCESTOR_OF && pOne->head()->is_empty_reference()) { pOne = pOne->tail_; } if (pTwo->combinator() == Complex_Selector::ANCESTOR_OF && pTwo->head()->is_empty_reference()) { pTwo = pTwo->tail_; } while (pOne && pTwo) { if (pOne->combinator() != pTwo->combinator()) { return false; } if (*(pOne->head()) != *(pTwo->head())) { return false; } pOne = pOne->tail_; pTwo = pTwo->tail_; } return pOne == NULL && pTwo == NULL; } Compound_Selector* Compound_Selector::unify_with(Compound_Selector* rhs, Context& ctx) { Compound_Selector* unified = rhs; for (size_t i = 0, L = length(); i < L; ++i) { if (!unified) break; else unified = (*this)[i]->unify_with(unified, ctx); } return unified; } bool Simple_Selector::operator==(const Simple_Selector& rhs) const { // Compare the string representations for equality. // Cast away const here. To_String should take a const object, but it doesn't. Simple_Selector* pLHS = const_cast(this); Simple_Selector* pRHS = const_cast(&rhs); To_String to_string; return pLHS->perform(&to_string) == pRHS->perform(&to_string); } bool Simple_Selector::operator<(const Simple_Selector& rhs) const { // Use the string representation for ordering. // Cast away const here. To_String should take a const object, but it doesn't. Simple_Selector* pLHS = const_cast(this); Simple_Selector* pRHS = const_cast(&rhs); To_String to_string; return pLHS->perform(&to_string) < pRHS->perform(&to_string); } Compound_Selector* Simple_Selector::unify_with(Compound_Selector* rhs, Context& ctx) { To_String to_string(&ctx); for (size_t i = 0, L = rhs->length(); i < L; ++i) { if (perform(&to_string) == (*rhs)[i]->perform(&to_string)) return rhs; } // check for pseudo elements because they need to come last size_t i, L; bool found = false; if (typeid(*this) == typeid(Pseudo_Selector) || typeid(*this) == typeid(Wrapped_Selector)) { for (i = 0, L = rhs->length(); i < L; ++i) { if ((typeid(*(*rhs)[i]) == typeid(Pseudo_Selector) || typeid(*(*rhs)[i]) == typeid(Wrapped_Selector)) && (*rhs)[L-1]->is_pseudo_element()) { found = true; break; } } } else { for (i = 0, L = rhs->length(); i < L; ++i) { if (typeid(*(*rhs)[i]) == typeid(Pseudo_Selector) || typeid(*(*rhs)[i]) == typeid(Wrapped_Selector)) { found = true; break; } } } if (!found) { Compound_Selector* cpy = new (ctx.mem) Compound_Selector(*rhs); (*cpy) << this; return cpy; } Compound_Selector* cpy = new (ctx.mem) Compound_Selector(rhs->pstate()); for (size_t j = 0; j < i; ++j) { (*cpy) << (*rhs)[j]; } (*cpy) << this; for (size_t j = i; j < L; ++j) { (*cpy) << (*rhs)[j]; } return cpy; } Compound_Selector* Type_Selector::unify_with(Compound_Selector* rhs, Context& ctx) { // TODO: handle namespaces // if the rhs is empty, just return a copy of this if (rhs->length() == 0) { Compound_Selector* cpy = new (ctx.mem) Compound_Selector(rhs->pstate()); (*cpy) << this; return cpy; } // if this is a universal selector and rhs is not empty, just return the rhs if (name() == "*") { return new (ctx.mem) Compound_Selector(*rhs); } Simple_Selector* rhs_0 = (*rhs)[0]; // otherwise, this is a tag name if (typeid(*rhs_0) == typeid(Type_Selector)) { // if rhs is universal, just return this tagname + rhs's qualifiers if (static_cast(rhs_0)->name() == "*") { Compound_Selector* cpy = new (ctx.mem) Compound_Selector(rhs->pstate()); (*cpy) << this; for (size_t i = 1, L = rhs->length(); i < L; ++i) { (*cpy) << (*rhs)[i]; } return cpy; } // if rhs is another tag name and it matches this, return rhs else if (static_cast(rhs_0)->name() == name()) { return new (ctx.mem) Compound_Selector(*rhs); } // else the tag names don't match; return nil else { return 0; } } // else it's a tag name and a bunch of qualifiers -- just append them Compound_Selector* cpy = new (ctx.mem) Compound_Selector(rhs->pstate()); (*cpy) << this; (*cpy) += rhs; return cpy; } Compound_Selector* Selector_Qualifier::unify_with(Compound_Selector* rhs, Context& ctx) { if (name()[0] == '#') { for (size_t i = 0, L = rhs->length(); i < L; ++i) { Simple_Selector* rhs_i = (*rhs)[i]; if (typeid(*rhs_i) == typeid(Selector_Qualifier) && static_cast(rhs_i)->name()[0] == '#' && static_cast(rhs_i)->name() != name()) return 0; } } rhs->has_line_break(has_line_break()); return Simple_Selector::unify_with(rhs, ctx); } Compound_Selector* Pseudo_Selector::unify_with(Compound_Selector* rhs, Context& ctx) { if (is_pseudo_element()) { for (size_t i = 0, L = rhs->length(); i < L; ++i) { Simple_Selector* rhs_i = (*rhs)[i]; if (typeid(*rhs_i) == typeid(Pseudo_Selector) && static_cast(rhs_i)->is_pseudo_element() && static_cast(rhs_i)->name() != name()) { return 0; } } } return Simple_Selector::unify_with(rhs, ctx); } bool Compound_Selector::is_superselector_of(Compound_Selector* rhs) { To_String to_string; Simple_Selector* lbase = base(); Simple_Selector* rbase = rhs->base(); // Check if pseudo-elements are the same between the selectors set lpsuedoset, rpsuedoset; for (size_t i = 0, L = length(); i < L; ++i) { if ((*this)[i]->is_pseudo_element()) { string pseudo((*this)[i]->perform(&to_string)); pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving lpsuedoset.insert(pseudo); } } for (size_t i = 0, L = rhs->length(); i < L; ++i) { if ((*rhs)[i]->is_pseudo_element()) { string pseudo((*rhs)[i]->perform(&to_string)); pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving rpsuedoset.insert(pseudo); } } if (lpsuedoset != rpsuedoset) { return false; } // Check the Simple_Selectors set lset, rset; if (!lbase) // no lbase; just see if the left-hand qualifiers are a subset of the right-hand selector { for (size_t i = 0, L = length(); i < L; ++i) { Selector* lhs = (*this)[i]; // very special case for wrapped matches selector if (Wrapped_Selector* wrapped = dynamic_cast(lhs)) { if (wrapped->name() == ":matches(" || wrapped->name() == ":-moz-any(") { if (Selector_List* list = dynamic_cast(wrapped->selector())) { if (Compound_Selector* comp = dynamic_cast(rhs)) { if (list->is_superselector_of(comp)) return true; } } } } // match from here on as strings lset.insert(lhs->perform(&to_string)); } for (size_t i = 0, L = rhs->length(); i < L; ++i) { rset.insert((*rhs)[i]->perform(&to_string)); } return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); } else { // there's an lbase for (size_t i = 1, L = length(); i < L; ++i) { lset.insert((*this)[i]->perform(&to_string)); } if (rbase) { if (lbase->perform(&to_string) != rbase->perform(&to_string)) // if there's an rbase, make sure they match { return false; } else // the bases do match, so compare qualifiers { for (size_t i = 1, L = rhs->length(); i < L; ++i) { rset.insert((*rhs)[i]->perform(&to_string)); } return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); } } } // catch-all return false; } bool Compound_Selector::operator==(const Compound_Selector& rhs) const { To_String to_string; // Check if pseudo-elements are the same between the selectors set lpsuedoset, rpsuedoset; for (size_t i = 0, L = length(); i < L; ++i) { if ((*this)[i]->is_pseudo_element()) { string pseudo((*this)[i]->perform(&to_string)); pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving lpsuedoset.insert(pseudo); } } for (size_t i = 0, L = rhs.length(); i < L; ++i) { if (rhs[i]->is_pseudo_element()) { string pseudo(rhs[i]->perform(&to_string)); pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving rpsuedoset.insert(pseudo); } } if (lpsuedoset != rpsuedoset) { return false; } // Check the base const Simple_Selector* const lbase = base(); const Simple_Selector* const rbase = rhs.base(); if ((lbase && !rbase) || (!lbase && rbase) || ((lbase && rbase) && (*lbase != *rbase))) { return false; } // Check the rest of the SimpleSelectors // Use string representations. We can't create a set of Simple_Selector pointers because std::set == std::set is going to call == // on the pointers to determine equality. I don't know of a way to pass in a comparison object. The one you can specify as part of // the template type is used for ordering, but not equality. We also can't just put in non-pointer Simple_Selectors because the // class is intended to be subclassed, and we'd get splicing. set lset, rset; for (size_t i = 0, L = length(); i < L; ++i) { lset.insert((*this)[i]->perform(&to_string)); } for (size_t i = 0, L = rhs.length(); i < L; ++i) { rset.insert(rhs[i]->perform(&to_string)); } return lset == rset; } bool Complex_Selector_Pointer_Compare::operator() (const Complex_Selector* const pLeft, const Complex_Selector* const pRight) const { return *pLeft < *pRight; } bool Complex_Selector::is_superselector_of(Compound_Selector* rhs) { return base()->is_superselector_of(rhs); } bool Complex_Selector::is_superselector_of(Complex_Selector* rhs) { Complex_Selector* lhs = this; To_String to_string; // check for selectors with leading or trailing combinators if (!lhs->head() || !rhs->head()) { return false; } Complex_Selector* l_innermost = lhs->innermost(); if (l_innermost->combinator() != Complex_Selector::ANCESTOR_OF && !l_innermost->tail()) { return false; } Complex_Selector* r_innermost = rhs->innermost(); if (r_innermost->combinator() != Complex_Selector::ANCESTOR_OF && !r_innermost->tail()) { return false; } // more complex (i.e., longer) selectors are always more specific size_t l_len = lhs->length(), r_len = rhs->length(); if (l_len > r_len) { return false; } if (l_len == 1) { return lhs->head()->is_superselector_of(rhs->base()); } // we have to look one tail deeper, since we cary the // combinator around for it (which is important here) if (rhs->tail() && lhs->tail() && combinator() != Complex_Selector::ANCESTOR_OF) { Complex_Selector* lhs_tail = lhs->tail(); Complex_Selector* rhs_tail = rhs->tail(); if (lhs_tail->combinator() != rhs_tail->combinator()) return false; if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) return false; } bool found = false; Complex_Selector* marker = rhs; for (size_t i = 0, L = rhs->length(); i < L; ++i) { if (i == L-1) { return false; } if (lhs->head()->is_superselector_of(marker->head())) { found = true; break; } marker = marker->tail(); } if (!found) { return false; } /* Hmm, I hope I have the logic right: if lhs has a combinator: if !(marker has a combinator) return false if !(lhs.combinator == '~' ? marker.combinator != '>' : lhs.combinator == marker.combinator) return false return lhs.tail-without-innermost.is_superselector_of(marker.tail-without-innermost) else if marker has a combinator: if !(marker.combinator == ">") return false return lhs.tail.is_superselector_of(marker.tail) else return lhs.tail.is_superselector_of(marker.tail) */ if (lhs->combinator() != Complex_Selector::ANCESTOR_OF) { if (marker->combinator() == Complex_Selector::ANCESTOR_OF) { return false; } if (!(lhs->combinator() == Complex_Selector::PRECEDES ? marker->combinator() != Complex_Selector::PARENT_OF : lhs->combinator() == marker->combinator())) { return false; } return lhs->tail()->is_superselector_of(marker->tail()); } else if (marker->combinator() != Complex_Selector::ANCESTOR_OF) { if (marker->combinator() != Complex_Selector::PARENT_OF) { return false; } return lhs->tail()->is_superselector_of(marker->tail()); } else { return lhs->tail()->is_superselector_of(marker->tail()); } // catch-all return false; } size_t Complex_Selector::length() { // TODO: make this iterative if (!tail()) return 1; return 1 + tail()->length(); } Compound_Selector* Complex_Selector::base() { if (!tail()) return head(); else return tail()->base(); } Complex_Selector* Complex_Selector::context(Context& ctx) { if (!tail()) return 0; if (!head()) return tail()->context(ctx); Complex_Selector* cpy = new (ctx.mem) Complex_Selector(pstate(), combinator(), head(), tail()->context(ctx)); cpy->media_block(media_block()); cpy->last_block(last_block()); return cpy; } Complex_Selector* Complex_Selector::innermost() { if (!tail()) return this; else return tail()->innermost(); } Complex_Selector::Combinator Complex_Selector::clear_innermost() { Combinator c; if (!tail() || tail()->length() == 1) { c = combinator(); combinator(ANCESTOR_OF); tail(0); } else { c = tail()->clear_innermost(); } return c; } void Complex_Selector::set_innermost(Complex_Selector* val, Combinator c) { if (!tail()) { tail(val); combinator(c); } else { tail()->set_innermost(val, c); } } Complex_Selector* Complex_Selector::clone(Context& ctx) const { Complex_Selector* cpy = new (ctx.mem) Complex_Selector(*this); if (tail()) cpy->tail(tail()->clone(ctx)); return cpy; } Complex_Selector* Complex_Selector::cloneFully(Context& ctx) const { Complex_Selector* cpy = new (ctx.mem) Complex_Selector(*this); if (head()) { cpy->head(head()->clone(ctx)); } if (tail()) { cpy->tail(tail()->cloneFully(ctx)); } return cpy; } Compound_Selector* Compound_Selector::clone(Context& ctx) const { Compound_Selector* cpy = new (ctx.mem) Compound_Selector(*this); return cpy; } /* not used anymore - remove? Selector_Placeholder* Selector::find_placeholder() { return 0; }*/ void Selector_List::adjust_after_pushing(Complex_Selector* c) { if (c->has_reference()) has_reference(true); if (c->has_placeholder()) has_placeholder(true); #ifdef DEBUG To_String to_string; this->mCachedSelector(this->perform(&to_string)); #endif } // it's a superselector if every selector of the right side // list is a superselector of the given left side selector bool Complex_Selector::is_superselector_of(Selector_List *sub) { // Check every rhs selector against left hand list for(size_t i = 0, L = sub->length(); i < L; ++i) { if (!is_superselector_of((*sub)[i])) return false; } return true; } // it's a superselector if every selector of the right side // list is a superselector of the given left side selector bool Selector_List::is_superselector_of(Selector_List *sub) { // Check every rhs selector against left hand list for(size_t i = 0, L = sub->length(); i < L; ++i) { if (!is_superselector_of((*sub)[i])) return false; } return true; } // it's a superselector if every selector on the right side // is a superselector of any one of the left side selectors bool Selector_List::is_superselector_of(Compound_Selector *sub) { // Check every lhs selector against right hand for(size_t i = 0, L = length(); i < L; ++i) { if ((*this)[i]->is_superselector_of(sub)) return true; } return false; } // it's a superselector if every selector on the right side // is a superselector of any one of the left side selectors bool Selector_List::is_superselector_of(Complex_Selector *sub) { // Check every lhs selector against right hand for(size_t i = 0, L = length(); i < L; ++i) { if ((*this)[i]->is_superselector_of(sub)) return true; } return false; } /* not used anymore - remove? Selector_Placeholder* Selector_List::find_placeholder() { if (has_placeholder()) { for (size_t i = 0, L = length(); i < L; ++i) { if ((*this)[i]->has_placeholder()) return (*this)[i]->find_placeholder(); } } return 0; }*/ /* not used anymore - remove? Selector_Placeholder* Complex_Selector::find_placeholder() { if (has_placeholder()) { if (head() && head()->has_placeholder()) return head()->find_placeholder(); else if (tail() && tail()->has_placeholder()) return tail()->find_placeholder(); } return 0; }*/ /* not used anymore - remove? Selector_Placeholder* Compound_Selector::find_placeholder() { if (has_placeholder()) { for (size_t i = 0, L = length(); i < L; ++i) { if ((*this)[i]->has_placeholder()) return (*this)[i]->find_placeholder(); } // return this; } return 0; }*/ /* not used anymore - remove? Selector_Placeholder* Selector_Placeholder::find_placeholder() { return this; }*/ vector Compound_Selector::to_str_vec() { To_String to_string; vector result; result.reserve(length()); for (size_t i = 0, L = length(); i < L; ++i) { result.push_back((*this)[i]->perform(&to_string)); } return result; } Compound_Selector* Compound_Selector::minus(Compound_Selector* rhs, Context& ctx) { To_String to_string(&ctx); Compound_Selector* result = new (ctx.mem) Compound_Selector(pstate()); // not very efficient because it needs to preserve order for (size_t i = 0, L = length(); i < L; ++i) { bool found = false; string thisSelector((*this)[i]->perform(&to_string)); for (size_t j = 0, M = rhs->length(); j < M; ++j) { if (thisSelector == (*rhs)[j]->perform(&to_string)) { found = true; break; } } if (!found) (*result) << (*this)[i]; } return result; } void Compound_Selector::mergeSources(SourcesSet& sources, Context& ctx) { for (SourcesSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) { this->sources_.insert((*iterator)->clone(ctx)); } } /* not used anymore - remove? vector Complex_Selector::to_vector() { vector result; Compound_Selector* h = head(); Complex_Selector* t = tail(); if (h) result.push_back(h); while (t) { h = t->head(); t = t->tail(); if (h) result.push_back(h); } return result; }*/ Number::Number(ParserState pstate, double val, string u, bool zero) : Expression(pstate), value_(val), zero_(zero), numerator_units_(vector()), denominator_units_(vector()), hash_(0) { size_t l = 0, r = 0; if (!u.empty()) { bool nominator = true; while (true) { r = u.find_first_of("*/", l); string unit(u.substr(l, r - l)); if (nominator) numerator_units_.push_back(unit); else denominator_units_.push_back(unit); if (u[r] == '/') nominator = false; if (r == string::npos) break; else l = r + 1; } } concrete_type(NUMBER); } string Number::unit() const { stringstream u; for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) { if (i) u << '*'; u << numerator_units_[i]; } if (!denominator_units_.empty()) u << '/'; for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) { if (i) u << '*'; u << denominator_units_[i]; } return u.str(); } bool Number::is_unitless() { return numerator_units_.empty() && denominator_units_.empty(); } void Number::normalize(const string& prefered) { // 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 // has the advantage that they will be pre-sorted map exponents; // initialize by summing up occurences in unit vectors for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[numerator_units_[i]]; for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[denominator_units_[i]]; // the final conversion factor double factor = 1; // get the first entry of numerators // forward it when entry is converted vector::iterator nom_it = numerator_units_.begin(); vector::iterator nom_end = numerator_units_.end(); vector::iterator denom_it = denominator_units_.begin(); vector::iterator denom_end = denominator_units_.end(); // main normalization loop // should be close to optimal while (denom_it != denom_end) { // get and increment afterwards const string denom = *(denom_it ++); // skip already canceled out unit if (exponents[denom] >= 0) continue; // skip all units we don't know how to convert if (string_to_unit(denom) == UNKNOWN) continue; // now search for nominator while (nom_it != nom_end) { // get and increment afterwards const string nom = *(nom_it ++); // skip already canceled out unit if (exponents[nom] <= 0) continue; // skip all units we don't know how to convert if (string_to_unit(nom) == UNKNOWN) continue; // we now have two convertable units // add factor for current conversion factor *= conversion_factor(nom, denom); // update nominator/denominator exponent -- exponents[nom]; ++ exponents[denom]; // inner loop done break; } } // now we can build up the new unit arrays numerator_units_.clear(); denominator_units_.clear(); // build them by iterating over the exponents for (auto exp : exponents) { // maybe there is more effecient way to push // the same item multiple times to a vector? for(size_t i = 0, S = abs(exp.second); i < S; ++i) { // opted to have these switches in the inner loop // makes it more readable and should not cost much if (exp.second < 0) denominator_units_.push_back(exp.first); else if (exp.second > 0) numerator_units_.push_back(exp.first); } } // apply factor to value_ // best precision this way value_ *= factor; // maybe convert to other unit // easier implemented on its own try { convert(prefered); } catch (incompatibleUnits& err) { error(err.what(), pstate()); } catch (...) { throw; } } void Number::convert(const string& prefered) { // 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 // has the advantage that they will be pre-sorted map exponents; // initialize by summing up occurences in unit vectors for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[numerator_units_[i]]; for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[denominator_units_[i]]; // the final conversion factor double factor = 1; vector::iterator denom_it = denominator_units_.begin(); vector::iterator denom_end = denominator_units_.end(); // main normalization loop // should be close to optimal while (denom_it != denom_end) { // get and increment afterwards const string denom = *(denom_it ++); // check if conversion is needed if (denom == prefered) continue; // skip already canceled out unit if (exponents[denom] >= 0) continue; // skip all units we don't know how to convert if (string_to_unit(denom) == UNKNOWN) continue; // we now have two convertable units // add factor for current conversion factor *= conversion_factor(denom, prefered); // update nominator/denominator exponent ++ exponents[denom]; -- exponents[prefered]; } vector::iterator nom_it = numerator_units_.begin(); vector::iterator nom_end = numerator_units_.end(); // now search for nominator while (nom_it != nom_end) { // get and increment afterwards const string nom = *(nom_it ++); // check if conversion is needed if (nom == prefered) continue; // skip already canceled out unit if (exponents[nom] <= 0) continue; // skip all units we don't know how to convert if (string_to_unit(nom) == UNKNOWN) continue; // we now have two convertable units // add factor for current conversion factor *= conversion_factor(nom, prefered); // update nominator/denominator exponent -- exponents[nom]; ++ exponents[prefered]; } // now we can build up the new unit arrays numerator_units_.clear(); denominator_units_.clear(); // build them by iterating over the exponents for (auto exp : exponents) { // maybe there is more effecient way to push // the same item multiple times to a vector? for(size_t i = 0, S = abs(exp.second); i < S; ++i) { // opted to have these switches in the inner loop // makes it more readable and should not cost much if (exp.second < 0) denominator_units_.push_back(exp.first); else if (exp.second > 0) numerator_units_.push_back(exp.first); } } // apply factor to value_ // best precision this way value_ *= factor; } // useful for making one number compatible with another string Number::find_convertible_unit() const { for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) { string u(numerator_units_[i]); if (string_to_unit(u) != UNKNOWN) return u; } for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) { string u(denominator_units_[i]); if (string_to_unit(u) != UNKNOWN) return u; } return string(); } bool Number::operator== (Expression* rhs) const { try { Number l(pstate_, value_, unit()); Number& r = dynamic_cast(*rhs); l.normalize(find_convertible_unit()); r.normalize(find_convertible_unit()); return l.unit() == r.unit() && l.value() == r.value(); } catch (std::bad_cast&) {} catch (...) { throw; } return false; } bool Number::operator== (Expression& rhs) const { return operator==(&rhs); } bool List::operator==(Expression* rhs) const { try { List* r = dynamic_cast(rhs); if (!r || length() != r->length()) return false; if (separator() != r->separator()) return false; for (size_t i = 0, L = r->length(); i < L; ++i) if (*elements()[i] != *(*r)[i]) return false; return true; } catch (std::bad_cast&) {} catch (...) { throw; } return false; } bool List::operator== (Expression& rhs) const { return operator==(&rhs); } size_t List::size() const { if (!is_arglist_) return length(); // arglist expects a list of arguments // so we need to break before keywords for (size_t i = 0, L = length(); i < L; ++i) { if (Argument* arg = dynamic_cast((*this)[i])) { if (!arg->name().empty()) return i; } } return length(); } Expression* Hashed::at(Expression* k) const { if (elements_.count(k)) { return elements_.at(k); } else { return &sass_null; } } }