#include "sass.hpp" #include "ast.hpp" #include "context.hpp" #include "node.hpp" #include "eval.hpp" #include "extend.hpp" #include "emitter.hpp" #include "color_maps.hpp" #include "ast_fwd_decl.hpp" #include #include #include #include #include #include #include namespace Sass { static Null sass_null(ParserState("null")); bool Wrapped_Selector::find ( bool (*f)(AST_Node_Obj) ) { // check children first if (selector_) { if (selector_->find(f)) return true; } // execute last return f(this); } bool Selector_List::find ( bool (*f)(AST_Node_Obj) ) { // check children first for (Complex_Selector_Obj sel : elements()) { if (sel->find(f)) return true; } // execute last return f(this); } bool Compound_Selector::find ( bool (*f)(AST_Node_Obj) ) { // check children first for (Simple_Selector_Obj sel : elements()) { if (sel->find(f)) return true; } // execute last return f(this); } bool Complex_Selector::find ( bool (*f)(AST_Node_Obj) ) { // check children first if (head_ && head_->find(f)) return true; if (tail_ && tail_->find(f)) return true; // execute last return f(this); } bool Supports_Operator::needs_parens(Supports_Condition_Obj cond) const { if (Supports_Operator_Obj op = Cast(cond)) { return op->operand() != operand(); } return Cast(cond) != NULL; } bool Supports_Negation::needs_parens(Supports_Condition_Obj cond) const { return Cast(cond) || Cast(cond); } void str_rtrim(std::string& str, const std::string& delimiters = " \f\n\r\t\v") { str.erase( str.find_last_not_of( delimiters ) + 1 ); } void String_Constant::rtrim() { str_rtrim(value_); } void String_Schema::rtrim() { if (!empty()) { if (String_Ptr str = Cast(last())) str->rtrim(); } } void Argument::set_delayed(bool delayed) { if (value_) value_->set_delayed(delayed); is_delayed(delayed); } void Arguments::set_delayed(bool delayed) { for (Argument_Obj arg : elements()) { if (arg) arg->set_delayed(delayed); } is_delayed(delayed); } bool At_Root_Query::exclude(std::string str) { bool with = feature() && unquote(feature()->to_string()).compare("with") == 0; List_Ptr l = static_cast(value().ptr()); std::string v; if (with) { if (!l || l->length() == 0) return str.compare("rule") != 0; for (size_t i = 0, L = l->length(); i < L; ++i) { v = unquote((*l)[i]->to_string()); if (v.compare("all") == 0 || v == str) return false; } return true; } else { if (!l || !l->length()) return str.compare("rule") == 0; for (size_t i = 0, L = l->length(); i < L; ++i) { v = unquote((*l)[i]->to_string()); if (v.compare("all") == 0 || v == str) return true; } return false; } } void AST_Node::update_pstate(const ParserState& pstate) { pstate_.offset += pstate - pstate_ + pstate.offset; } bool Simple_Selector::is_ns_eq(const Simple_Selector& r) const { // https://github.com/sass/sass/issues/2229 if ((has_ns_ == r.has_ns_) || (has_ns_ && ns_.empty()) || (r.has_ns_ && r.ns_.empty()) ) { if (ns_.empty() && r.ns() == "*") return false; else if (r.ns().empty() && ns() == "*") return false; else return ns() == r.ns(); } return false; } bool Compound_Selector::operator< (const Compound_Selector& rhs) const { size_t L = std::min(length(), rhs.length()); for (size_t i = 0; i < L; ++i) { Simple_Selector_Obj l = (*this)[i]; Simple_Selector_Obj r = rhs[i]; if (!l && !r) return false; else if (!r) return false; else if (!l) return true; else if (*l != *r) { return *l < *r; } } // just compare the length now return length() < rhs.length(); } bool Compound_Selector::has_parent_ref() const { for (Simple_Selector_Obj s : *this) { if (s && s->has_parent_ref()) return true; } return false; } bool Compound_Selector::has_real_parent_ref() const { for (Simple_Selector_Obj s : *this) { if (s && s->has_real_parent_ref()) return true; } return false; } bool Complex_Selector::has_parent_ref() const { return (head() && head()->has_parent_ref()) || (tail() && tail()->has_parent_ref()); } bool Complex_Selector::has_real_parent_ref() const { return (head() && head()->has_real_parent_ref()) || (tail() && tail()->has_real_parent_ref()); } bool Complex_Selector::operator< (const Complex_Selector& rhs) const { // const iterators for tails Complex_Selector_Ptr_Const l = this; Complex_Selector_Ptr_Const r = &rhs; Compound_Selector_Ptr l_h = NULL; Compound_Selector_Ptr r_h = NULL; if (l) l_h = l->head(); if (r) r_h = r->head(); // process all tails while (true) { #ifdef DEBUG // skip empty ancestor first if (l && l->is_empty_ancestor()) { l_h = NULL; l = l->tail(); if(l) l_h = l->head(); continue; } // skip empty ancestor first if (r && r->is_empty_ancestor()) { r_h = NULL; r = r->tail(); if (r) r_h = r->head(); continue; } #endif // check for valid selectors if (!l) return !!r; if (!r) return false; // both are null else if (!l_h && !r_h) { // check combinator after heads if (l->combinator() != r->combinator()) { return l->combinator() < r->combinator(); } // advance to next tails l = l->tail(); r = r->tail(); // fetch the next headers l_h = NULL; r_h = NULL; if (l) l_h = l->head(); if (r) r_h = r->head(); } // one side is null else if (!r_h) return true; else if (!l_h) return false; // heads ok and equal else if (*l_h == *r_h) { // check combinator after heads if (l->combinator() != r->combinator()) { return l->combinator() < r->combinator(); } // advance to next tails l = l->tail(); r = r->tail(); // fetch the next headers l_h = NULL; r_h = NULL; if (l) l_h = l->head(); if (r) r_h = r->head(); } // heads are not equal else return *l_h < *r_h; } } bool Complex_Selector::operator== (const Complex_Selector& rhs) const { // const iterators for tails Complex_Selector_Ptr_Const l = this; Complex_Selector_Ptr_Const r = &rhs; Compound_Selector_Ptr l_h = NULL; Compound_Selector_Ptr r_h = NULL; if (l) l_h = l->head(); if (r) r_h = r->head(); // process all tails while (true) { #ifdef DEBUG // skip empty ancestor first if (l && l->is_empty_ancestor()) { l_h = NULL; l = l->tail(); if (l) l_h = l->head(); continue; } // skip empty ancestor first if (r && r->is_empty_ancestor()) { r_h = NULL; r = r->tail(); if (r) r_h = r->head(); continue; } #endif // check the pointers if (!r) return !l; if (!l) return !r; // both are null if (!l_h && !r_h) { // check combinator after heads if (l->combinator() != r->combinator()) { return l->combinator() < r->combinator(); } // advance to next tails l = l->tail(); r = r->tail(); // fetch the next heads l_h = NULL; r_h = NULL; if (l) l_h = l->head(); if (r) r_h = r->head(); } // equals if other head is empty else if ((!l_h && !r_h) || (!l_h && r_h->empty()) || (!r_h && l_h->empty()) || (l_h && r_h && *l_h == *r_h)) { // check combinator after heads if (l->combinator() != r->combinator()) { return l->combinator() == r->combinator(); } // advance to next tails l = l->tail(); r = r->tail(); // fetch the next heads l_h = NULL; r_h = NULL; if (l) l_h = l->head(); if (r) r_h = r->head(); } // abort else break; } // unreachable return false; } Compound_Selector_Ptr Compound_Selector::unify_with(Compound_Selector_Ptr rhs) { if (empty()) return rhs; Compound_Selector_Obj unified = SASS_MEMORY_COPY(rhs); for (size_t i = 0, L = length(); i < L; ++i) { if (unified.isNull()) break; unified = at(i)->unify_with(unified); } return unified.detach(); } bool Complex_Selector::operator== (const Selector& rhs) const { if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; throw std::runtime_error("invalid selector base classes to compare"); } bool Complex_Selector::operator< (const Selector& rhs) const { if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; throw std::runtime_error("invalid selector base classes to compare"); } bool Compound_Selector::operator== (const Selector& rhs) const { if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; throw std::runtime_error("invalid selector base classes to compare"); } bool Compound_Selector::operator< (const Selector& rhs) const { if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; throw std::runtime_error("invalid selector base classes to compare"); } bool Selector_Schema::operator== (const Selector& rhs) const { if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; throw std::runtime_error("invalid selector base classes to compare"); } bool Selector_Schema::operator< (const Selector& rhs) const { if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; throw std::runtime_error("invalid selector base classes to compare"); } bool Simple_Selector::operator== (const Selector& rhs) const { if (Simple_Selector_Ptr_Const sp = Cast(&rhs)) return *this == *sp; return false; } bool Simple_Selector::operator< (const Selector& rhs) const { if (Simple_Selector_Ptr_Const sp = Cast(&rhs)) return *this < *sp; return false; } bool Simple_Selector::operator== (const Simple_Selector& rhs) const { // solve the double dispatch problem by using RTTI information via dynamic cast if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs == rhs; } else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs == rhs; } else if (const Element_Selector* lhs = Cast(this)) {return *lhs == rhs; } else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs == rhs; } else if (name_ == rhs.name_) { return is_ns_eq(rhs); } else return false; } bool Simple_Selector::operator< (const Simple_Selector& rhs) const { // solve the double dispatch problem by using RTTI information via dynamic cast if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs < rhs; } else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs < rhs; } else if (const Element_Selector* lhs = Cast(this)) {return *lhs < rhs; } else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs < rhs; } if (is_ns_eq(rhs)) { return name_ < rhs.name_; } return ns_ < rhs.ns_; } bool Selector_List::operator== (const Selector& rhs) const { // solve the double dispatch problem by using RTTI information via dynamic cast if (Selector_List_Ptr_Const sl = Cast(&rhs)) { return *this == *sl; } else if (Complex_Selector_Ptr_Const cpx = Cast(&rhs)) { return *this == *cpx; } else if (Compound_Selector_Ptr_Const cpd = Cast(&rhs)) { return *this == *cpd; } // no compare method return this == &rhs; } // Selector lists can be compared to comma lists bool Selector_List::operator==(const Expression& rhs) const { // solve the double dispatch problem by using RTTI information via dynamic cast if (List_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } if (Selector_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } // compare invalid (maybe we should error?) return false; } bool Selector_List::operator== (const Selector_List& rhs) const { // for array access size_t i = 0, n = 0; size_t iL = length(); size_t nL = rhs.length(); // create temporary vectors and sort them std::vector l_lst = this->elements(); std::vector r_lst = rhs.elements(); std::sort(l_lst.begin(), l_lst.end(), OrderNodes()); std::sort(r_lst.begin(), r_lst.end(), OrderNodes()); // process loop while (true) { // first check for valid index if (i == iL) return iL == nL; else if (n == nL) return iL == nL; // the access the vector items Complex_Selector_Obj l = l_lst[i]; Complex_Selector_Obj r = r_lst[n]; // skip nulls if (!l) ++i; else if (!r) ++n; // do the check else if (*l != *r) { return false; } // advance ++i; ++n; } // there is no break?! } bool Selector_List::operator< (const Selector& rhs) const { if (Selector_List_Ptr_Const sp = Cast(&rhs)) return *this < *sp; return false; } bool Selector_List::operator< (const Selector_List& rhs) const { size_t l = rhs.length(); if (length() < l) l = length(); for (size_t i = 0; i < l; i ++) { if (*at(i) < *rhs.at(i)) return true; } return false; } Compound_Selector_Ptr Simple_Selector::unify_with(Compound_Selector_Ptr rhs) { for (size_t i = 0, L = rhs->length(); i < L; ++i) { if (to_string() == rhs->at(i)->to_string()) return rhs; } // check for pseudo elements because they are always 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 ((Cast((*rhs)[i]) || Cast((*rhs)[i])) && (*rhs)[L-1]->is_pseudo_element()) { found = true; break; } } } else { for (i = 0, L = rhs->length(); i < L; ++i) { if (Cast((*rhs)[i]) || Cast((*rhs)[i])) { found = true; break; } } } if (!found) { rhs->append(this); return rhs; } rhs->elements().insert(rhs->elements().begin() + i, this); return rhs; } Simple_Selector_Ptr Element_Selector::unify_with(Simple_Selector_Ptr rhs) { // check if ns can be extended // true for no ns or universal if (has_universal_ns()) { // but dont extend with universal // true for valid ns and universal if (!rhs->is_universal_ns()) { // overwrite the name if star is given as name if (this->name() == "*") { this->name(rhs->name()); } // now overwrite the namespace name and flag this->ns(rhs->ns()); this->has_ns(rhs->has_ns()); // return copy return this; } } // namespace may changed, check the name now // overwrite star (but not with another star) if (name() == "*" && rhs->name() != "*") { // simply set the new name this->name(rhs->name()); // return copy return this; } // return original return this; } Compound_Selector_Ptr Element_Selector::unify_with(Compound_Selector_Ptr rhs) { // TODO: handle namespaces // if the rhs is empty, just return a copy of this if (rhs->length() == 0) { rhs->append(this); return rhs; } Simple_Selector_Ptr rhs_0 = rhs->at(0); // otherwise, this is a tag name if (name() == "*") { if (typeid(*rhs_0) == typeid(Element_Selector)) { // if rhs is universal, just return this tagname + rhs's qualifiers Element_Selector_Ptr ts = Cast(rhs_0); rhs->at(0) = this->unify_with(ts); return rhs; } else if (Cast(rhs_0) || Cast(rhs_0)) { // qualifier is `.class`, so we can prefix with `ns|*.class` if (has_ns() && !rhs_0->has_ns()) { if (ns() != "*") rhs->elements().insert(rhs->begin(), this); } return rhs; } return rhs; } if (typeid(*rhs_0) == typeid(Element_Selector)) { // if rhs is universal, just return this tagname + rhs's qualifiers if (rhs_0->name() != "*" && rhs_0->ns() != "*" && rhs_0->name() != name()) return 0; // otherwise create new compound and unify first simple selector rhs->at(0) = this->unify_with(rhs_0); return rhs; } // else it's a tag name and a bunch of qualifiers -- just append them if (name() != "*") rhs->elements().insert(rhs->begin(), this); return rhs; } Compound_Selector_Ptr Class_Selector::unify_with(Compound_Selector_Ptr rhs) { rhs->has_line_break(has_line_break()); return Simple_Selector::unify_with(rhs); } Compound_Selector_Ptr Id_Selector::unify_with(Compound_Selector_Ptr rhs) { for (size_t i = 0, L = rhs->length(); i < L; ++i) { if (Id_Selector_Ptr sel = Cast(rhs->at(i))) { if (sel->name() != name()) return 0; } } rhs->has_line_break(has_line_break()); return Simple_Selector::unify_with(rhs); } Compound_Selector_Ptr Pseudo_Selector::unify_with(Compound_Selector_Ptr rhs) { if (is_pseudo_element()) { for (size_t i = 0, L = rhs->length(); i < L; ++i) { if (Pseudo_Selector_Ptr sel = Cast(rhs->at(i))) { if (sel->is_pseudo_element() && sel->name() != name()) return 0; } } } return Simple_Selector::unify_with(rhs); } bool Attribute_Selector::operator< (const Attribute_Selector& rhs) const { if (is_ns_eq(rhs)) { if (name() == rhs.name()) { if (matcher() == rhs.matcher()) { bool no_lhs_val = value().isNull(); bool no_rhs_val = rhs.value().isNull(); if (no_lhs_val && no_rhs_val) return false; // equal else if (no_lhs_val) return true; // lhs is null else if (no_rhs_val) return false; // rhs is null return *value() < *rhs.value(); // both are given } else { return matcher() < rhs.matcher(); } } else { return name() < rhs.name(); } } else { return ns() < rhs.ns(); } } bool Attribute_Selector::operator< (const Simple_Selector& rhs) const { if (Attribute_Selector_Ptr_Const w = Cast(&rhs)) { return *this < *w; } if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } bool Attribute_Selector::operator== (const Attribute_Selector& rhs) const { // get optional value state bool no_lhs_val = value().isNull(); bool no_rhs_val = rhs.value().isNull(); // both are null, therefore equal if (no_lhs_val && no_rhs_val) { return (name() == rhs.name()) && (matcher() == rhs.matcher()) && (is_ns_eq(rhs)); } // both are defined, evaluate if (no_lhs_val == no_rhs_val) { return (name() == rhs.name()) && (matcher() == rhs.matcher()) && (is_ns_eq(rhs)) && (*value() == *rhs.value()); } // not equal return false; } bool Attribute_Selector::operator== (const Simple_Selector& rhs) const { if (Attribute_Selector_Ptr_Const w = Cast(&rhs)) { return is_ns_eq(rhs) && name() == rhs.name() && *this == *w; } return false; } bool Element_Selector::operator< (const Element_Selector& rhs) const { if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } bool Element_Selector::operator< (const Simple_Selector& rhs) const { if (Element_Selector_Ptr_Const w = Cast(&rhs)) { return *this < *w; } if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } bool Element_Selector::operator== (const Element_Selector& rhs) const { return is_ns_eq(rhs) && name() == rhs.name(); } bool Element_Selector::operator== (const Simple_Selector& rhs) const { if (Element_Selector_Ptr_Const w = Cast(&rhs)) { return is_ns_eq(rhs) && name() == rhs.name() && *this == *w; } return false; } bool Pseudo_Selector::operator== (const Pseudo_Selector& rhs) const { if (is_ns_eq(rhs) && name() == rhs.name()) { String_Obj lhs_ex = expression(); String_Obj rhs_ex = rhs.expression(); if (rhs_ex && lhs_ex) return *lhs_ex == *rhs_ex; else return lhs_ex.ptr() == rhs_ex.ptr(); } else return false; } bool Pseudo_Selector::operator== (const Simple_Selector& rhs) const { if (Pseudo_Selector_Ptr_Const w = Cast(&rhs)) { return *this == *w; } return is_ns_eq(rhs) && name() == rhs.name(); } bool Pseudo_Selector::operator< (const Pseudo_Selector& rhs) const { if (is_ns_eq(rhs) && name() == rhs.name()) { String_Obj lhs_ex = expression(); String_Obj rhs_ex = rhs.expression(); if (rhs_ex && lhs_ex) return *lhs_ex < *rhs_ex; else return lhs_ex.ptr() < rhs_ex.ptr(); } if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } bool Pseudo_Selector::operator< (const Simple_Selector& rhs) const { if (Pseudo_Selector_Ptr_Const w = Cast(&rhs)) { return *this < *w; } if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } bool Wrapped_Selector::operator== (const Wrapped_Selector& rhs) const { if (is_ns_eq(rhs) && name() == rhs.name()) { return *(selector()) == *(rhs.selector()); } else return false; } bool Wrapped_Selector::operator== (const Simple_Selector& rhs) const { if (Wrapped_Selector_Ptr_Const w = Cast(&rhs)) { return *this == *w; } return is_ns_eq(rhs) && name() == rhs.name(); } bool Wrapped_Selector::operator< (const Wrapped_Selector& rhs) const { if (is_ns_eq(rhs) && name() == rhs.name()) { return *(selector()) < *(rhs.selector()); } if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } bool Wrapped_Selector::operator< (const Simple_Selector& rhs) const { if (Wrapped_Selector_Ptr_Const w = Cast(&rhs)) { return *this < *w; } if (is_ns_eq(rhs)) { return name() < rhs.name(); } return ns() < rhs.ns(); } bool Wrapped_Selector::is_superselector_of(Wrapped_Selector_Obj sub) { if (this->name() != sub->name()) return false; if (this->name() == ":current") return false; if (Selector_List_Obj rhs_list = Cast(sub->selector())) { if (Selector_List_Obj lhs_list = Cast(selector())) { return lhs_list->is_superselector_of(rhs_list); } } error("is_superselector expected a Selector_List", sub->pstate()); return false; } bool Compound_Selector::is_superselector_of(Selector_List_Obj rhs, std::string wrapped) { for (Complex_Selector_Obj item : rhs->elements()) { if (is_superselector_of(item, wrapped)) return true; } return false; } bool Compound_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapped) { if (rhs->head()) return is_superselector_of(rhs->head(), wrapped); return false; } bool Compound_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) { Compound_Selector_Ptr lhs = this; Simple_Selector_Ptr lbase = lhs->base(); Simple_Selector_Ptr rbase = rhs->base(); // Check if pseudo-elements are the same between the selectors std::set lpsuedoset, rpsuedoset; for (size_t i = 0, L = length(); i < L; ++i) { if ((*this)[i]->is_pseudo_element()) { std::string pseudo((*this)[i]->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()) { std::string pseudo((*rhs)[i]->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; } // would like to replace this without stringification // https://github.com/sass/sass/issues/2229 // SimpleSelectorSet lset, rset; std::set lset, rset; if (lbase && rbase) { if (lbase->to_string() == rbase->to_string()) { for (size_t i = 1, L = length(); i < L; ++i) { lset.insert((*this)[i]->to_string()); } for (size_t i = 1, L = rhs->length(); i < L; ++i) { rset.insert((*rhs)[i]->to_string()); } return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); } return false; } for (size_t i = 0, iL = length(); i < iL; ++i) { Selector_Obj wlhs = (*this)[i]; // very special case for wrapped matches selector if (Wrapped_Selector_Obj wrapped = Cast(wlhs)) { if (wrapped->name() == ":not") { if (Selector_List_Obj not_list = Cast(wrapped->selector())) { if (not_list->is_superselector_of(rhs, wrapped->name())) return false; } else { throw std::runtime_error("wrapped not selector is not a list"); } } if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { wlhs = wrapped->selector(); if (Selector_List_Obj list = Cast(wrapped->selector())) { if (Compound_Selector_Obj comp = Cast(rhs)) { if (!wrapping.empty() && wrapping != wrapped->name()) return false; if (wrapping.empty() || wrapping != wrapped->name()) {; if (list->is_superselector_of(comp, wrapped->name())) return true; } } } } Simple_Selector_Ptr rhs_sel = NULL; if (rhs->elements().size() > i) rhs_sel = (*rhs)[i]; if (Wrapped_Selector_Ptr wrapped_r = Cast(rhs_sel)) { if (wrapped->name() == wrapped_r->name()) { if (wrapped->is_superselector_of(wrapped_r)) { continue; }} } } // match from here on as strings lset.insert(wlhs->to_string()); } for (size_t n = 0, nL = rhs->length(); n < nL; ++n) { Selector_Obj r = (*rhs)[n]; if (Wrapped_Selector_Obj wrapped = Cast(r)) { if (wrapped->name() == ":not") { if (Selector_List_Obj ls = Cast(wrapped->selector())) { ls->remove_parent_selectors(); if (is_superselector_of(ls, wrapped->name())) return false; } } if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { if (!wrapping.empty()) { if (wrapping != wrapped->name()) return false; } if (Selector_List_Obj ls = Cast(wrapped->selector())) { ls->remove_parent_selectors(); return (is_superselector_of(ls, wrapped->name())); } } } rset.insert(r->to_string()); } //for (auto l : lset) { cerr << "l: " << l << endl; } //for (auto r : rset) { cerr << "r: " << r << endl; } if (lset.empty()) return true; // return true if rset contains all the elements of lset return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); } // create complex selector (ancestor of) from compound selector Complex_Selector_Obj Compound_Selector::to_complex() { // create an intermediate complex selector return SASS_MEMORY_NEW(Complex_Selector, pstate(), Complex_Selector::ANCESTOR_OF, this, 0); } Selector_List_Ptr Complex_Selector::unify_with(Complex_Selector_Ptr other) { // get last tails (on the right side) Complex_Selector_Obj l_last = this->last(); Complex_Selector_Obj r_last = other->last(); // check valid pointers (assertion) SASS_ASSERT(l_last, "lhs is null"); SASS_ASSERT(r_last, "rhs is null"); // Not sure about this check, but closest way I could check // was to see if this is a ruby 'SimpleSequence' equivalent. // It seems to do the job correctly as some specs react to this if (l_last->combinator() != Combinator::ANCESTOR_OF) return 0; if (r_last->combinator() != Combinator::ANCESTOR_OF ) return 0; // get the headers for the last tails Compound_Selector_Obj l_last_head = l_last->head(); Compound_Selector_Obj r_last_head = r_last->head(); // check valid head pointers (assertion) SASS_ASSERT(l_last_head, "lhs head is null"); SASS_ASSERT(r_last_head, "rhs head is null"); // get the unification of the last compound selectors Compound_Selector_Obj unified = r_last_head->unify_with(l_last_head); // abort if we could not unify heads if (unified == 0) return 0; // check for universal (star: `*`) selector bool is_universal = l_last_head->is_universal() || r_last_head->is_universal(); if (is_universal) { // move the head l_last->head(0); r_last->head(unified); } // create nodes from both selectors Node lhsNode = complexSelectorToNode(this); Node rhsNode = complexSelectorToNode(other); // overwrite universal base if (!is_universal) { // create some temporaries to convert to node Complex_Selector_Obj fake = unified->to_complex(); Node unified_node = complexSelectorToNode(fake); // add to permutate the list? rhsNode.plus(unified_node); } // do some magic we inherit from node and extend Node node = subweave(lhsNode, rhsNode); Selector_List_Obj result = SASS_MEMORY_NEW(Selector_List, pstate()); NodeDequePtr col = node.collection(); // move from collection to list for (NodeDeque::iterator it = col->begin(), end = col->end(); it != end; it++) { result->append(nodeToComplexSelector(Node::naiveTrim(*it))); } // only return if list has some entries return result->length() ? result.detach() : 0; } bool Compound_Selector::operator== (const Compound_Selector& rhs) const { // for array access size_t i = 0, n = 0; size_t iL = length(); size_t nL = rhs.length(); // create temporary vectors and sort them std::vector l_lst = this->elements(); std::vector r_lst = rhs.elements(); std::sort(l_lst.begin(), l_lst.end(), OrderNodes()); std::sort(r_lst.begin(), r_lst.end(), OrderNodes()); // process loop while (true) { // first check for valid index if (i == iL) return iL == nL; else if (n == nL) return iL == nL; // the access the vector items Simple_Selector_Obj l = l_lst[i]; Simple_Selector_Obj r = r_lst[n]; // skip nulls if (!l) ++i; if (!r) ++n; // do the check now else if (*l != *r) { return false; } // advance now ++i; ++n; } // there is no break?! } bool Complex_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) { return last()->head() && last()->head()->is_superselector_of(rhs, wrapping); } bool Complex_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapping) { Complex_Selector_Ptr lhs = this; // check for selectors with leading or trailing combinators if (!lhs->head() || !rhs->head()) { return false; } Complex_Selector_Obj l_innermost = lhs->innermost(); if (l_innermost->combinator() != Complex_Selector::ANCESTOR_OF) { return false; } Complex_Selector_Obj r_innermost = rhs->innermost(); if (r_innermost->combinator() != Complex_Selector::ANCESTOR_OF) { 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->last()->head(), wrapping); } // 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_Obj lhs_tail = lhs->tail(); Complex_Selector_Obj rhs_tail = rhs->tail(); if (lhs_tail->combinator() != rhs_tail->combinator()) return false; if (lhs_tail->head() && !rhs_tail->head()) return false; if (!lhs_tail->head() && rhs_tail->head()) return false; if (lhs_tail->head() && rhs_tail->head()) { if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) return false; } } bool found = false; Complex_Selector_Obj marker = rhs; for (size_t i = 0, L = rhs->length(); i < L; ++i) { if (i == L-1) { return false; } if (lhs->head() && marker->head() && lhs->head()->is_superselector_of(marker->head(), wrapping)) { 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()); } return lhs->tail()->is_superselector_of(marker->tail()); } size_t Complex_Selector::length() const { // TODO: make this iterative if (!tail()) return 1; return 1 + tail()->length(); } // append another complex selector at the end // check if we need to append some headers // then we need to check for the combinator // only then we can safely set the new tail void Complex_Selector::append(Complex_Selector_Obj ss) { Complex_Selector_Obj t = ss->tail(); Combinator c = ss->combinator(); String_Obj r = ss->reference(); Compound_Selector_Obj h = ss->head(); if (ss->has_line_feed()) has_line_feed(true); if (ss->has_line_break()) has_line_break(true); // append old headers if (h && h->length()) { if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { error("Invalid parent selector", pstate_); } else if (last()->head_ && last()->head_->length()) { Compound_Selector_Obj rh = last()->head(); size_t i; size_t L = h->length(); if (Cast(h->first())) { if (Class_Selector_Ptr cs = Cast(rh->last())) { Class_Selector_Ptr sqs = SASS_MEMORY_COPY(cs); sqs->name(sqs->name() + (*h)[0]->name()); sqs->pstate((*h)[0]->pstate()); (*rh)[rh->length()-1] = sqs; rh->pstate(h->pstate()); for (i = 1; i < L; ++i) rh->append((*h)[i]); } else if (Id_Selector_Ptr is = Cast(rh->last())) { Id_Selector_Ptr sqs = SASS_MEMORY_COPY(is); sqs->name(sqs->name() + (*h)[0]->name()); sqs->pstate((*h)[0]->pstate()); (*rh)[rh->length()-1] = sqs; rh->pstate(h->pstate()); for (i = 1; i < L; ++i) rh->append((*h)[i]); } else if (Element_Selector_Ptr ts = Cast(rh->last())) { Element_Selector_Ptr tss = SASS_MEMORY_COPY(ts); tss->name(tss->name() + (*h)[0]->name()); tss->pstate((*h)[0]->pstate()); (*rh)[rh->length()-1] = tss; rh->pstate(h->pstate()); for (i = 1; i < L; ++i) rh->append((*h)[i]); } else if (Placeholder_Selector_Ptr ps = Cast(rh->last())) { Placeholder_Selector_Ptr pss = SASS_MEMORY_COPY(ps); pss->name(pss->name() + (*h)[0]->name()); pss->pstate((*h)[0]->pstate()); (*rh)[rh->length()-1] = pss; rh->pstate(h->pstate()); for (i = 1; i < L; ++i) rh->append((*h)[i]); } else { last()->head_->concat(h); } } else { last()->head_->concat(h); } } else { last()->head_->concat(h); } } else { // std::cerr << "has no or empty head\n"; } if (last()) { if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { Complex_Selector_Ptr inter = SASS_MEMORY_NEW(Complex_Selector, pstate()); inter->reference(r); inter->combinator(c); inter->tail(t); last()->tail(inter); } else { if (last()->combinator() == ANCESTOR_OF) { last()->combinator(c); last()->reference(r); } last()->tail(t); } } } Selector_List_Obj Selector_List::eval(Eval& eval) { Selector_List_Obj list = schema() ? eval(schema()) : eval(this); list->schema(schema()); return list; } Selector_List_Ptr Selector_List::resolve_parent_refs(std::vector& pstack, bool implicit_parent) { if (!this->has_parent_ref()) return this; Selector_List_Ptr ss = SASS_MEMORY_NEW(Selector_List, pstate()); Selector_List_Ptr ps = pstack.back(); for (size_t pi = 0, pL = ps->length(); pi < pL; ++pi) { for (size_t si = 0, sL = this->length(); si < sL; ++si) { Selector_List_Obj rv = at(si)->resolve_parent_refs(pstack, implicit_parent); ss->concat(rv); } } return ss; } Selector_List_Ptr Complex_Selector::resolve_parent_refs(std::vector& pstack, bool implicit_parent) { Complex_Selector_Obj tail = this->tail(); Compound_Selector_Obj head = this->head(); Selector_List_Ptr parents = pstack.back(); if (!this->has_real_parent_ref() && !implicit_parent) { Selector_List_Ptr retval = SASS_MEMORY_NEW(Selector_List, pstate()); retval->append(this); return retval; } // first resolve_parent_refs the tail (which may return an expanded list) Selector_List_Obj tails = tail ? tail->resolve_parent_refs(pstack, implicit_parent) : 0; if (head && head->length() > 0) { Selector_List_Obj retval; // we have a parent selector in a simple compound list // mix parent complex selector into the compound list if (Cast((*head)[0])) { retval = SASS_MEMORY_NEW(Selector_List, pstate()); // it turns out that real parent references reach // across @at-root rules, which comes unexpected if (parents == NULL && head->has_real_parent_ref()) { int i = pstack.size() - 1; while (!parents && i > -1) { parents = pstack.at(i--); } } if (parents && parents->length()) { if (tails && tails->length() > 0) { for (size_t n = 0, nL = tails->length(); n < nL; ++n) { for (size_t i = 0, iL = parents->length(); i < iL; ++i) { Complex_Selector_Obj t = (*tails)[n]; Complex_Selector_Obj parent = (*parents)[i]; Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); ss->tail(t ? SASS_MEMORY_CLONE(t) : NULL); Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); // remove parent selector from sequence if (h->length()) { h->erase(h->begin()); ss->head(h); } else { ss->head(NULL); } // adjust for parent selector (1 char) if (h->length()) { ParserState state(h->at(0)->pstate()); state.offset.column += 1; state.column -= 1; (*h)[0]->pstate(state); } // keep old parser state s->pstate(pstate()); // append new tail s->append(ss); retval->append(s); } } } // have no tails but parents // loop above is inside out else { for (size_t i = 0, iL = parents->length(); i < iL; ++i) { Complex_Selector_Obj parent = (*parents)[i]; Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); // this is only if valid if the parent has no trailing op // otherwise we cannot append more simple selectors to head if (parent->last()->combinator() != ANCESTOR_OF) { throw Exception::InvalidParent(parent, ss); } ss->tail(tail ? SASS_MEMORY_CLONE(tail) : NULL); Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); // remove parent selector from sequence if (h->length()) { h->erase(h->begin()); ss->head(h); } else { ss->head(NULL); } // \/ IMO ruby sass bug \/ ss->has_line_feed(false); // adjust for parent selector (1 char) if (h->length()) { ParserState state(h->at(0)->pstate()); state.offset.column += 1; state.column -= 1; (*h)[0]->pstate(state); } // keep old parser state s->pstate(pstate()); // append new tail s->append(ss); retval->append(s); } } } // have no parent but some tails else { if (tails && tails->length() > 0) { for (size_t n = 0, nL = tails->length(); n < nL; ++n) { Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); cpy->tail(SASS_MEMORY_CLONE(tails->at(n))); cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); for (size_t i = 1, L = this->head()->length(); i < L; ++i) cpy->head()->append((*this->head())[i]); if (!cpy->head()->length()) cpy->head(0); retval->append(cpy->skip_empty_reference()); } } // have no parent nor tails else { Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); for (size_t i = 1, L = this->head()->length(); i < L; ++i) cpy->head()->append((*this->head())[i]); if (!cpy->head()->length()) cpy->head(0); retval->append(cpy->skip_empty_reference()); } } } // no parent selector in head else { retval = this->tails(tails); } for (Simple_Selector_Obj ss : head->elements()) { if (Wrapped_Selector_Ptr ws = Cast(ss)) { if (Selector_List_Ptr sl = Cast(ws->selector())) { if (parents) ws->selector(sl->resolve_parent_refs(pstack, implicit_parent)); } } } return retval.detach(); } // has no head return this->tails(tails); } Selector_List_Ptr Complex_Selector::tails(Selector_List_Ptr tails) { Selector_List_Ptr rv = SASS_MEMORY_NEW(Selector_List, pstate_); if (tails && tails->length()) { for (size_t i = 0, iL = tails->length(); i < iL; ++i) { Complex_Selector_Obj pr = SASS_MEMORY_CLONE(this); pr->tail(tails->at(i)); rv->append(pr); } } else { rv->append(this); } return rv; } // return the last tail that is defined Complex_Selector_Obj Complex_Selector::first() { // declare variables used in loop Complex_Selector_Obj cur = this; Compound_Selector_Obj head; // processing loop while (cur) { // get the head head = cur->head_; // abort (and return) if it is not a parent selector if (!head || head->length() != 1 || !Cast((*head)[0])) { break; } // advance to next cur = cur->tail_; } // result return cur; } // return the last tail that is defined Complex_Selector_Obj Complex_Selector::last() { Complex_Selector_Ptr cur = this; Complex_Selector_Ptr nxt = cur; // loop until last while (nxt) { cur = nxt; nxt = cur->tail(); } return cur; } Complex_Selector::Combinator Complex_Selector::clear_innermost() { Combinator c; if (!tail() || tail()->tail() == 0) { c = combinator(); combinator(ANCESTOR_OF); tail(0); } else { c = tail()->clear_innermost(); } return c; } void Complex_Selector::set_innermost(Complex_Selector_Obj val, Combinator c) { if (!tail()) { tail(val); combinator(c); } else { tail()->set_innermost(val, c); } } void Complex_Selector::cloneChildren() { if (head()) head(SASS_MEMORY_CLONE(head())); if (tail()) tail(SASS_MEMORY_CLONE(tail())); } void Compound_Selector::cloneChildren() { for (size_t i = 0, l = length(); i < l; i++) { at(i) = SASS_MEMORY_CLONE(at(i)); } } void Selector_List::cloneChildren() { for (size_t i = 0, l = length(); i < l; i++) { at(i) = SASS_MEMORY_CLONE(at(i)); } } void Wrapped_Selector::cloneChildren() { selector(SASS_MEMORY_CLONE(selector())); } // remove parent selector references // basically unwraps parsed selectors void Selector_List::remove_parent_selectors() { // Check every rhs selector against left hand list for(size_t i = 0, L = length(); i < L; ++i) { if (!(*this)[i]->head()) continue; if ((*this)[i]->head()->is_empty_reference()) { // simply move to the next tail if we have "no" combinator if ((*this)[i]->combinator() == Complex_Selector::ANCESTOR_OF) { if ((*this)[i]->tail()) { if ((*this)[i]->has_line_feed()) { (*this)[i]->tail()->has_line_feed(true); } (*this)[i] = (*this)[i]->tail(); } } // otherwise remove the first item from head else { (*this)[i]->head()->erase((*this)[i]->head()->begin()); } } } } size_t Wrapped_Selector::hash() { if (hash_ == 0) { hash_combine(hash_, Simple_Selector::hash()); if (selector_) hash_combine(hash_, selector_->hash()); } return hash_; } bool Wrapped_Selector::has_parent_ref() const { // if (has_reference()) return true; if (!selector()) return false; return selector()->has_parent_ref(); } bool Wrapped_Selector::has_real_parent_ref() const { // if (has_reference()) return true; if (!selector()) return false; return selector()->has_real_parent_ref(); } unsigned long Wrapped_Selector::specificity() const { return selector_ ? selector_->specificity() : 0; } bool Selector_List::has_parent_ref() const { for (Complex_Selector_Obj s : elements()) { if (s && s->has_parent_ref()) return true; } return false; } bool Selector_List::has_real_parent_ref() const { for (Complex_Selector_Obj s : elements()) { if (s && s->has_real_parent_ref()) return true; } return false; } bool Selector_Schema::has_parent_ref() const { if (String_Schema_Obj schema = Cast(contents())) { return schema->length() > 0 && Cast(schema->at(0)) != NULL; } return false; } bool Selector_Schema::has_real_parent_ref() const { if (String_Schema_Obj schema = Cast(contents())) { Parent_Selector_Obj p = Cast(schema->at(0)); return schema->length() > 0 && p && p->is_real_parent_ref(); } return false; } void Selector_List::adjust_after_pushing(Complex_Selector_Obj c) { // if (c->has_reference()) has_reference(true); } // 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_Obj sub, std::string wrapping) { // 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], wrapping)) 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_Obj sub, std::string wrapping) { // 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], wrapping)) 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_Obj sub, std::string wrapping) { // Check every lhs selector against right hand for(size_t i = 0, L = length(); i < L; ++i) { if ((*this)[i]->is_superselector_of(sub, wrapping)) 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_Obj sub, std::string wrapping) { // 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; } Selector_List_Ptr Selector_List::unify_with(Selector_List_Ptr rhs) { std::vector unified_complex_selectors; // Unify all of children with RHS's children, storing the results in `unified_complex_selectors` for (size_t lhs_i = 0, lhs_L = length(); lhs_i < lhs_L; ++lhs_i) { Complex_Selector_Obj seq1 = (*this)[lhs_i]; for(size_t rhs_i = 0, rhs_L = rhs->length(); rhs_i < rhs_L; ++rhs_i) { Complex_Selector_Ptr seq2 = rhs->at(rhs_i); Selector_List_Obj result = seq1->unify_with(seq2); if( result ) { for(size_t i = 0, L = result->length(); i < L; ++i) { unified_complex_selectors.push_back( (*result)[i] ); } } } } // Creates the final Selector_List by combining all the complex selectors Selector_List_Ptr final_result = SASS_MEMORY_NEW(Selector_List, pstate()); for (auto itr = unified_complex_selectors.begin(); itr != unified_complex_selectors.end(); ++itr) { final_result->append(*itr); } return final_result; } void Selector_List::populate_extends(Selector_List_Obj extendee, Subset_Map& extends) { Selector_List_Ptr extender = this; for (auto complex_sel : extendee->elements()) { Complex_Selector_Obj c = complex_sel; // Ignore any parent selectors, until we find the first non Selectorerence head Compound_Selector_Obj compound_sel = c->head(); Complex_Selector_Obj pIter = complex_sel; while (pIter) { Compound_Selector_Obj pHead = pIter->head(); if (pHead && Cast(pHead->elements()[0]) == NULL) { compound_sel = pHead; break; } pIter = pIter->tail(); } if (!pIter->head() || pIter->tail()) { error("nested selectors may not be extended", c->pstate()); } compound_sel->is_optional(extendee->is_optional()); for (size_t i = 0, L = extender->length(); i < L; ++i) { extends.put(compound_sel, std::make_pair((*extender)[i], compound_sel)); } } }; void Compound_Selector::append(Simple_Selector_Ptr element) { Vectorized::append(element); pstate_.offset += element->pstate().offset; } Compound_Selector_Ptr Compound_Selector::minus(Compound_Selector_Ptr rhs) { Compound_Selector_Ptr result = SASS_MEMORY_NEW(Compound_Selector, pstate()); // result->has_parent_reference(has_parent_reference()); // not very efficient because it needs to preserve order for (size_t i = 0, L = length(); i < L; ++i) { bool found = false; std::string thisSelector((*this)[i]->to_string()); for (size_t j = 0, M = rhs->length(); j < M; ++j) { if (thisSelector == (*rhs)[j]->to_string()) { found = true; break; } } if (!found) result->append((*this)[i]); } return result; } void Compound_Selector::mergeSources(ComplexSelectorSet& sources) { for (ComplexSelectorSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) { this->sources_.insert(SASS_MEMORY_CLONE(*iterator)); } } Argument_Obj Arguments::get_rest_argument() { if (this->has_rest_argument()) { for (Argument_Obj arg : this->elements()) { if (arg->is_rest_argument()) { return arg; } } } return NULL; } Argument_Obj Arguments::get_keyword_argument() { if (this->has_keyword_argument()) { for (Argument_Obj arg : this->elements()) { if (arg->is_keyword_argument()) { return arg; } } } return NULL; } void Arguments::adjust_after_pushing(Argument_Obj a) { if (!a->name().empty()) { if (has_keyword_argument()) { error("named arguments must precede variable-length argument", a->pstate()); } has_named_arguments(true); } else if (a->is_rest_argument()) { if (has_rest_argument()) { error("functions and mixins may only be called with one variable-length argument", a->pstate()); } if (has_keyword_argument_) { error("only keyword arguments may follow variable arguments", a->pstate()); } has_rest_argument(true); } else if (a->is_keyword_argument()) { if (has_keyword_argument()) { error("functions and mixins may only be called with one keyword argument", a->pstate()); } has_keyword_argument(true); } else { if (has_rest_argument()) { error("ordinal arguments must precede variable-length arguments", a->pstate()); } if (has_named_arguments()) { error("ordinal arguments must precede named arguments", a->pstate()); } } } bool Ruleset::is_invisible() const { if (Selector_List_Ptr sl = Cast(selector())) { for (size_t i = 0, L = sl->length(); i < L; ++i) if (!(*sl)[i]->has_placeholder()) return false; } return true; } bool Media_Block::is_invisible() const { for (size_t i = 0, L = block()->length(); i < L; ++i) { Statement_Obj stm = block()->at(i); if (!stm->is_invisible()) return false; } return true; } Number::Number(ParserState pstate, double val, std::string u, bool zero) : Value(pstate), value_(val), zero_(zero), numerator_units_(std::vector()), denominator_units_(std::vector()), hash_(0) { size_t l = 0; size_t r; if (!u.empty()) { bool nominator = true; while (true) { r = u.find_first_of("*/", l); std::string unit(u.substr(l, r == std::string::npos ? r : r - l)); if (!unit.empty()) { if (nominator) numerator_units_.push_back(unit); else denominator_units_.push_back(unit); } if (r == std::string::npos) break; // ToDo: should error for multiple slashes // if (!nominator && u[r] == '/') error(...) if (u[r] == '/') nominator = false; l = r + 1; } } concrete_type(NUMBER); } std::string Number::unit() const { std::string 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; } bool Number::is_valid_css_unit() const { return numerator_units().size() <= 1 && denominator_units().size() == 0; } bool Number::is_unitless() const { return numerator_units_.empty() && denominator_units_.empty(); } void Number::normalize(const std::string& prefered, bool strict) { // no conversion if unit is empty if (prefered.empty() && numerator_units_.size() == 0 && denominator_units_.size() == 0) 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 std::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 std::vector::iterator nom_it = numerator_units_.begin(); std::vector::iterator nom_end = numerator_units_.end(); std::vector::iterator denom_it = denominator_units_.begin(); std::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 std::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 std::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, strict); // 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.first.empty()) { 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, strict); } catch (Exception::IncompatibleUnits& err) { error(err.what(), pstate()); } catch (...) { throw; } } // this does not cover all cases (multiple prefered units) double Number::convert_factor(const Number& n) const { // 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 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 l_miss_nums(0); std::vector l_miss_dens(0); // create copy since we need these for state keeping std::vector r_nums(n.numerator_units_); std::vector r_dens(n.denominator_units_); std::vector::const_iterator l_num_it = numerator_units_.begin(); std::vector::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::iterator r_num_it = r_nums.begin(); std::vector::iterator r_num_end = r_nums.end(); bool found = false; // 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 numerator found = true; break; } // maybe we did not find any // left numerator is leftover if (!found) l_miss_nums.push_back(l_num); } std::vector::const_iterator l_den_it = denominator_units_.begin(); std::vector::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::iterator r_den_it = r_dens.begin(); std::vector::iterator r_den_end = r_dens.end(); bool found = false; // 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 denominator found = true; break; } // maybe we did not find any // left denominator is leftover if (!found) 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 // we basically construct exponents for each unit // has the advantage that they will be pre-sorted std::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; std::vector::iterator denom_it = denominator_units_.begin(); std::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 std::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, strict); // update nominator/denominator exponent ++ exponents[denom]; -- exponents[prefered]; } std::vector::iterator nom_it = numerator_units_.begin(); std::vector::iterator nom_end = numerator_units_.end(); // now search for nominator while (nom_it != nom_end) { // get and increment afterwards const std::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, strict); // 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.first.empty()) { 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; // success? return true; } // useful for making one number compatible with another std::string Number::find_convertible_unit() const { for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) { std::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) { std::string u(denominator_units_[i]); if (string_to_unit(u) != UNKNOWN) return u; } return std::string(); } bool Custom_Warning::operator== (const Expression& rhs) const { if (Custom_Warning_Ptr_Const r = Cast(&rhs)) { return message() == r->message(); } return false; } bool Custom_Error::operator== (const Expression& rhs) const { if (Custom_Error_Ptr_Const r = Cast(&rhs)) { return message() == r->message(); } return false; } bool Number::operator== (const Expression& rhs) const { if (Number_Ptr_Const r = Cast(&rhs)) { size_t lhs_units = numerator_units_.size() + denominator_units_.size(); size_t rhs_units = r->numerator_units_.size() + r->denominator_units_.size(); // unitless and only having one unit seems equivalent (will change in future) if (!lhs_units || !rhs_units) { return std::fabs(value() - r->value()) < NUMBER_EPSILON; } return (numerator_units_ == r->numerator_units_) && (denominator_units_ == r->denominator_units_) && std::fabs(value() - r->value()) < NUMBER_EPSILON; } return false; } bool Number::operator< (const Number& rhs) const { size_t lhs_units = numerator_units_.size() + denominator_units_.size(); size_t rhs_units = rhs.numerator_units_.size() + rhs.denominator_units_.size(); // unitless and only having one unit seems equivalent (will change in future) if (!lhs_units || !rhs_units) { return value() < rhs.value(); } Number tmp_r(&rhs); // copy tmp_r.normalize(find_convertible_unit()); std::string l_unit(unit()); std::string r_unit(tmp_r.unit()); if (unit() != tmp_r.unit()) { error("cannot compare numbers with incompatible units", pstate()); } return value() < tmp_r.value(); } bool String_Quoted::operator== (const Expression& rhs) const { if (String_Quoted_Ptr_Const qstr = Cast(&rhs)) { return (value() == qstr->value()); } else if (String_Constant_Ptr_Const cstr = Cast(&rhs)) { return (value() == cstr->value()); } return false; } bool String_Constant::is_invisible() const { return value_.empty() && quote_mark_ == 0; } bool String_Constant::operator== (const Expression& rhs) const { if (String_Quoted_Ptr_Const qstr = Cast(&rhs)) { return (value() == qstr->value()); } else if (String_Constant_Ptr_Const cstr = Cast(&rhs)) { return (value() == cstr->value()); } return false; } bool String_Schema::is_left_interpolant(void) const { return length() && first()->is_left_interpolant(); } bool String_Schema::is_right_interpolant(void) const { return length() && last()->is_right_interpolant(); } bool String_Schema::operator== (const Expression& rhs) const { if (String_Schema_Ptr_Const r = Cast(&rhs)) { if (length() != r->length()) return false; for (size_t i = 0, L = length(); i < L; ++i) { Expression_Obj rv = (*r)[i]; Expression_Obj lv = (*this)[i]; if (!lv || !rv) return false; if (!(*lv == *rv)) return false; } return true; } return false; } bool Boolean::operator== (const Expression& rhs) const { if (Boolean_Ptr_Const r = Cast(&rhs)) { return (value() == r->value()); } return false; } bool Color::operator== (const Expression& rhs) const { if (Color_Ptr_Const r = Cast(&rhs)) { return r_ == r->r() && g_ == r->g() && b_ == r->b() && a_ == r->a(); } return false; } bool List::operator== (const Expression& rhs) const { if (List_Ptr_Const r = Cast(&rhs)) { if (length() != r->length()) return false; if (separator() != r->separator()) return false; for (size_t i = 0, L = length(); i < L; ++i) { Expression_Obj rv = r->at(i); Expression_Obj lv = this->at(i); if (!lv || !rv) return false; if (!(*lv == *rv)) return false; } return true; } return false; } bool Map::operator== (const Expression& rhs) const { if (Map_Ptr_Const r = Cast(&rhs)) { if (length() != r->length()) return false; for (auto key : keys()) { Expression_Obj lv = at(key); Expression_Obj rv = r->at(key); if (!rv || !lv) return false; if (!(*lv == *rv)) return false; } return true; } return false; } bool Null::operator== (const Expression& rhs) const { return rhs.concrete_type() == NULL_VAL; } 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) { Expression_Obj obj = this->at(i); if (Argument_Ptr arg = Cast(obj)) { if (!arg->name().empty()) return i; } } return length(); } Expression_Obj Hashed::at(Expression_Obj k) const { if (elements_.count(k)) { return elements_.at(k); } else { return NULL; } } bool Binary_Expression::is_left_interpolant(void) const { return is_interpolant() || (left() && left()->is_left_interpolant()); } bool Binary_Expression::is_right_interpolant(void) const { return is_interpolant() || (right() && right()->is_right_interpolant()); } const std::string AST_Node::to_string(Sass_Inspect_Options opt) const { Sass_Output_Options out(opt); Emitter emitter(out); Inspect i(emitter); i.in_declaration = true; // ToDo: inspect should be const const_cast(this)->perform(&i); return i.get_buffer(); } const std::string AST_Node::to_string() const { return to_string({ NESTED, 5 }); } std::string String_Quoted::inspect() const { return quote(value_, '*'); } std::string String_Constant::inspect() const { return quote(value_, '*'); } ////////////////////////////////////////////////////////////////////////////////////////// // Additional method on Lists to retrieve values directly or from an encompassed Argument. ////////////////////////////////////////////////////////////////////////////////////////// Expression_Obj List::value_at_index(size_t i) { Expression_Obj obj = this->at(i); if (is_arglist_) { if (Argument_Ptr arg = Cast(obj)) { return arg->value(); } else { return obj; } } else { return obj; } } ////////////////////////////////////////////////////////////////////////////////////////// // Convert map to (key, value) list. ////////////////////////////////////////////////////////////////////////////////////////// List_Obj Map::to_list(ParserState& pstate) { List_Obj ret = SASS_MEMORY_NEW(List, pstate, length(), SASS_COMMA); for (auto key : keys()) { List_Obj l = SASS_MEMORY_NEW(List, pstate, 2); l->append(key); l->append(at(key)); ret->append(l); } return ret; } ////////////////////////////////////////////////////////////////////////////////////////// // Copy implementations ////////////////////////////////////////////////////////////////////////////////////////// #ifdef DEBUG_SHARED_PTR #define IMPLEMENT_AST_OPERATORS(klass) \ klass##_Ptr klass::copy(std::string file, size_t line) const { \ klass##_Ptr cpy = new klass(this); \ cpy->trace(file, line); \ return cpy; \ } \ klass##_Ptr klass::clone(std::string file, size_t line) const { \ klass##_Ptr cpy = copy(file, line); \ cpy->cloneChildren(); \ return cpy; \ } \ #else #define IMPLEMENT_AST_OPERATORS(klass) \ klass##_Ptr klass::copy() const { \ return new klass(this); \ } \ klass##_Ptr klass::clone() const { \ klass##_Ptr cpy = copy(); \ cpy->cloneChildren(); \ return cpy; \ } \ #endif IMPLEMENT_AST_OPERATORS(Supports_Operator); IMPLEMENT_AST_OPERATORS(Supports_Negation); IMPLEMENT_AST_OPERATORS(Compound_Selector); IMPLEMENT_AST_OPERATORS(Complex_Selector); IMPLEMENT_AST_OPERATORS(Element_Selector); IMPLEMENT_AST_OPERATORS(Class_Selector); IMPLEMENT_AST_OPERATORS(Id_Selector); IMPLEMENT_AST_OPERATORS(Pseudo_Selector); IMPLEMENT_AST_OPERATORS(Wrapped_Selector); IMPLEMENT_AST_OPERATORS(Selector_List); IMPLEMENT_AST_OPERATORS(Ruleset); IMPLEMENT_AST_OPERATORS(Media_Block); IMPLEMENT_AST_OPERATORS(Custom_Warning); IMPLEMENT_AST_OPERATORS(Custom_Error); IMPLEMENT_AST_OPERATORS(List); IMPLEMENT_AST_OPERATORS(Map); IMPLEMENT_AST_OPERATORS(Number); IMPLEMENT_AST_OPERATORS(Binary_Expression); IMPLEMENT_AST_OPERATORS(String_Schema); IMPLEMENT_AST_OPERATORS(String_Constant); IMPLEMENT_AST_OPERATORS(String_Quoted); IMPLEMENT_AST_OPERATORS(Boolean); IMPLEMENT_AST_OPERATORS(Color); IMPLEMENT_AST_OPERATORS(Null); IMPLEMENT_AST_OPERATORS(Parent_Selector); IMPLEMENT_AST_OPERATORS(Import); IMPLEMENT_AST_OPERATORS(Import_Stub); IMPLEMENT_AST_OPERATORS(Function_Call); IMPLEMENT_AST_OPERATORS(Directive); IMPLEMENT_AST_OPERATORS(At_Root_Block); IMPLEMENT_AST_OPERATORS(Supports_Block); IMPLEMENT_AST_OPERATORS(While); IMPLEMENT_AST_OPERATORS(Each); IMPLEMENT_AST_OPERATORS(For); IMPLEMENT_AST_OPERATORS(If); IMPLEMENT_AST_OPERATORS(Mixin_Call); IMPLEMENT_AST_OPERATORS(Extension); IMPLEMENT_AST_OPERATORS(Media_Query); IMPLEMENT_AST_OPERATORS(Media_Query_Expression); IMPLEMENT_AST_OPERATORS(Debug); IMPLEMENT_AST_OPERATORS(Error); IMPLEMENT_AST_OPERATORS(Warning); IMPLEMENT_AST_OPERATORS(Assignment); IMPLEMENT_AST_OPERATORS(Return); IMPLEMENT_AST_OPERATORS(At_Root_Query); IMPLEMENT_AST_OPERATORS(Variable); IMPLEMENT_AST_OPERATORS(Comment); IMPLEMENT_AST_OPERATORS(Attribute_Selector); IMPLEMENT_AST_OPERATORS(Supports_Interpolation); IMPLEMENT_AST_OPERATORS(Supports_Declaration); IMPLEMENT_AST_OPERATORS(Supports_Condition); IMPLEMENT_AST_OPERATORS(Parameters); IMPLEMENT_AST_OPERATORS(Parameter); IMPLEMENT_AST_OPERATORS(Arguments); IMPLEMENT_AST_OPERATORS(Argument); IMPLEMENT_AST_OPERATORS(Unary_Expression); IMPLEMENT_AST_OPERATORS(Function_Call_Schema); IMPLEMENT_AST_OPERATORS(Block); IMPLEMENT_AST_OPERATORS(Content); IMPLEMENT_AST_OPERATORS(Trace); IMPLEMENT_AST_OPERATORS(Keyframe_Rule); IMPLEMENT_AST_OPERATORS(Bubble); IMPLEMENT_AST_OPERATORS(Selector_Schema); IMPLEMENT_AST_OPERATORS(Placeholder_Selector); IMPLEMENT_AST_OPERATORS(Definition); IMPLEMENT_AST_OPERATORS(Declaration); }