diff --git a/ast.cpp b/ast.cpp index 7f7091e9fc..75b16c963e 100644 --- a/ast.cpp +++ b/ast.cpp @@ -1,5 +1,7 @@ #include "ast.hpp" #include "context.hpp" +#include "node.hpp" +#include "extend.hpp" #include "to_string.hpp" #include #include @@ -420,6 +422,43 @@ namespace Sass { // catch-all return false; } + + Selector_List* Complex_Selector::unify_with(Complex_Selector* other, Context& ctx) { + To_String to_string; + + Compound_Selector* thisBase = base(); + Compound_Selector* rhsBase = other->base(); + + if( thisBase == 0 || rhsBase == 0 ) return 0; + + // Not sure about this check, but closest way I could check to see if this is a ruby 'SimpleSequence' equivalent + if( tail()->combinator() != Combinator::ANCESTOR_OF || other->tail()->combinator() != Combinator::ANCESTOR_OF ) return 0; + + Compound_Selector* unified = rhsBase->unify_with(thisBase, ctx); + if( unified == 0 ) return 0; + + Node lhsNode = complexSelectorToNode(this, ctx); + Node rhsNode = complexSelectorToNode(other, ctx); + + // Create a temp Complex_Selector, turn it into a Node, and combine it with the existing RHS node + Complex_Selector* fakeComplexSelector = new (ctx.mem) Complex_Selector(ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, unified, NULL); + Node unifiedNode = complexSelectorToNode(fakeComplexSelector, ctx); + rhsNode.plus(unifiedNode); + + Node node = Extend::subweave(lhsNode, rhsNode, ctx); + + Selector_List* result = new (ctx.mem) Selector_List(pstate()); + for (NodeDeque::iterator iter = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { + Node childNode = *iter; + childNode = Node::naiveTrim(childNode, ctx); + + Complex_Selector* childNodeAsComplexSelector = nodeToComplexSelector(childNode, ctx); + if( childNodeAsComplexSelector ) { (*result) << childNodeAsComplexSelector; } + } + + return result->length() ? result : 0; + } + size_t Complex_Selector::length() { @@ -558,6 +597,69 @@ namespace Sass { } return false; } + + Selector_List* Selector_List::unify_with(Selector_List* rhs, Context& ctx) { + + 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* seq1 = (*this)[lhs_i]; + for(size_t rhs_i = 0, rhs_L = rhs->length(); rhs_i < rhs_L; ++rhs_i) { + Complex_Selector* seq2 = (*rhs)[rhs_i]; + + Selector_List* result = seq1->unify_with(seq2, ctx); + 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* final_result = new (ctx.mem) Selector_List(pstate()); + for (auto itr = unified_complex_selectors.begin(); itr != unified_complex_selectors.end(); ++itr) { + *final_result << *itr; + } + + return final_result; + } + + void Selector_List::populate_extends(Selector_List* extendee, Context& ctx, ExtensionSubsetMap& extends) { + To_String to_string; + + Selector_List* extender = this; + for (auto complex_sel : extendee->elements()) { + Complex_Selector* c = complex_sel; + + + // Ignore any parent selectors, until we find the first non Selector_Reference head + Compound_Selector* compound_sel = c->head(); + Complex_Selector* pIter = complex_sel; + while (pIter) { + Compound_Selector* pHead = pIter->head(); + if (pHead && dynamic_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) { + // let's test this out + cerr << "REGISTERING EXTENSION REQUEST: " << (*extender)[i]->perform(&to_string) << " <- " << compound_sel->perform(&to_string) << endl; + extends.put(compound_sel->to_str_vec(), make_pair((*extender)[i], compound_sel)); + } + } + }; + /* not used anymore - remove? Selector_Placeholder* Selector_List::find_placeholder() diff --git a/ast.hpp b/ast.hpp index bbf70f8f98..584c48ac89 100644 --- a/ast.hpp +++ b/ast.hpp @@ -164,6 +164,7 @@ namespace Sass { size_t length() const { return elements_.size(); } bool empty() const { return elements_.empty(); } T last() { return elements_.back(); } + T first() { return elements_.front(); } T& operator[](size_t i) { return elements_[i]; } const T& operator[](size_t i) const { return elements_[i]; } Vectorized& operator<<(T element) @@ -2006,6 +2007,9 @@ namespace Sass { bool is_superselector_of(Complex_Selector* sub); bool is_superselector_of(Selector_List* sub); // virtual Selector_Placeholder* find_placeholder(); + + Selector_List* unify_with(Complex_Selector* rhs, Context& ctx); + Combinator clear_innermost(); void set_innermost(Complex_Selector*, Combinator); virtual unsigned long specificity() const @@ -2074,6 +2078,8 @@ namespace Sass { typedef deque ComplexSelectorDeque; + typedef Subset_Map > ExtensionSubsetMap; + /////////////////////////////////// // Comma-separated selector groups. /////////////////////////////////// @@ -2092,6 +2098,10 @@ namespace Sass { bool is_superselector_of(Compound_Selector* sub); bool is_superselector_of(Complex_Selector* sub); bool is_superselector_of(Selector_List* sub); + + Selector_List* unify_with(Selector_List*, Context&); + void populate_extends(Selector_List*, Context&, ExtensionSubsetMap&); + virtual unsigned long specificity() { unsigned long sum = 0; diff --git a/context.cpp b/context.cpp index 5620764072..06b0e8cd20 100644 --- a/context.cpp +++ b/context.cpp @@ -540,7 +540,14 @@ namespace Sass { register_function(ctx, inspect_sig, inspect, env); register_function(ctx, unique_id_sig, unique_id, env); // Selector functions + register_function(ctx, selector_nest_sig, selector_nest, env); + register_function(ctx, selector_append_sig, selector_append, env); + register_function(ctx, selector_extend_sig, selector_extend, env); + register_function(ctx, selector_replace_sig, selector_replace, env); + register_function(ctx, selector_unify_sig, selector_unify, env); register_function(ctx, is_superselector_sig, is_superselector, env); + register_function(ctx, simple_selectors_sig, simple_selectors, env); + register_function(ctx, selector_parse_sig, selector_parse, env); } void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) diff --git a/extend.cpp b/extend.cpp index ede8e8d73c..65fb6ef273 100644 --- a/extend.cpp +++ b/extend.cpp @@ -1134,7 +1134,7 @@ namespace Sass { result end */ - static Node subweave(Node& one, Node& two, Context& ctx) { + Node Extend::subweave(Node& one, Node& two, Context& ctx) { // Check for the simple cases if (one.collection()->size() == 0) { Node out = Node::createCollection(); @@ -1423,7 +1423,7 @@ namespace Sass { for (NodeDeque::iterator beforesIter = befores.collection()->begin(), beforesEndIter = befores.collection()->end(); beforesIter != beforesEndIter; beforesIter++) { Node& before = *beforesIter; - Node sub = subweave(before, current, ctx); + Node sub = Extend::subweave(before, current, ctx); DEBUG_PRINTLN(WEAVE, "SUB: " << sub) @@ -1854,7 +1854,7 @@ namespace Sass { /* This is the equivalent of ruby's CommaSequence.do_extend. */ - static Selector_List* extendSelectorList(Selector_List* pSelectorList, Context& ctx, ExtensionSubsetMap& subsetMap, bool& extendedSomething) { + Selector_List* Extend::extendSelectorList(Selector_List* pSelectorList, Context& ctx, ExtensionSubsetMap& subsetMap, bool isReplace, bool& extendedSomething) { To_String to_string(&ctx); @@ -1886,7 +1886,10 @@ namespace Sass { } } - for (NodeDeque::iterator iterator = extendedSelectors.collection()->begin(), iteratorEnd = extendedSelectors.collection()->end(); iterator != iteratorEnd; ++iterator) { + for (NodeDeque::iterator iterator = extendedSelectors.collection()->begin(), iteratorBegin = extendedSelectors.collection()->begin(), iteratorEnd = extendedSelectors.collection()->end(); iterator != iteratorEnd; ++iterator) { + // When it is a replace, skip the first one, unless there is only one + if(isReplace && iterator == iteratorBegin && extendedSelectors.collection()->size() > 1 ) continue; + Node& childNode = *iterator; *pNewSelectors << nodeToComplexSelector(childNode, ctx); } @@ -1943,7 +1946,7 @@ namespace Sass { } bool extendedSomething = false; - Selector_List* pNewSelectorList = extendSelectorList(static_cast(pObject->selector()), ctx, subsetMap, extendedSomething); + Selector_List* pNewSelectorList = Extend::extendSelectorList(static_cast(pObject->selector()), ctx, subsetMap, false, extendedSomething); if (extendedSomething && pNewSelectorList) { DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << static_cast(pObject->selector())->perform(&to_string)) diff --git a/extend.hpp b/extend.hpp index 5b282a9f20..f44e9c84cb 100644 --- a/extend.hpp +++ b/extend.hpp @@ -14,6 +14,7 @@ namespace Sass { using namespace std; class Context; + class Node; typedef Subset_Map > ExtensionSubsetMap; @@ -38,6 +39,9 @@ namespace Sass { template void fallback(U x) { return fallback_impl(x); } + + static Node subweave(Node& one, Node& two, Context& ctx); + static Selector_List* extendSelectorList(Selector_List* pSelectorList, Context& ctx, ExtensionSubsetMap& subsetMap, bool isReplace, bool& extendedSomething); }; } diff --git a/functions.cpp b/functions.cpp index 446fa1c9eb..10eb1ab551 100644 --- a/functions.cpp +++ b/functions.cpp @@ -6,6 +6,7 @@ #include "constants.hpp" #include "to_string.hpp" #include "inspect.hpp" +#include "extend.hpp" #include "eval.hpp" #include "util.hpp" #include "utf8_string.hpp" @@ -117,6 +118,38 @@ namespace Sass { } return val; } + +#define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, backtrace, ctx) + + template + T* get_arg_sel(const string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx); + + template <> + Selector_List* get_arg_sel(const string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) { + To_String to_string(&ctx, false); + Expression* exp = ARG(argname, Expression); + string exp_src = exp->perform(&to_string) + "{"; + return Parser::parse_selector(exp_src.c_str(), ctx); + } + + template <> + Complex_Selector* get_arg_sel(const string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) { + To_String to_string(&ctx, false); + Expression* exp = ARG(argname, Expression); + string exp_src = exp->perform(&to_string) + "{"; + Selector_List* sel_list = Parser::parse_selector(exp_src.c_str(), ctx); + return (sel_list->length() > 0) ? sel_list->first() : 0; + } + + template <> + Compound_Selector* get_arg_sel(const string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) { + To_String to_string(&ctx, false); + Expression* exp = ARG(argname, Expression); + string exp_src = exp->perform(&to_string) + "{"; + Selector_List* sel_list = Parser::parse_selector(exp_src.c_str(), ctx); + + return (sel_list->length() > 0) ? sel_list->first()->tail()->head() : 0; + } #ifdef __MINGW32__ uint64_t GetSeed() @@ -1563,6 +1596,196 @@ namespace Sass { } // return v; } + Signature selector_nest_sig = "selector-nest($selectors...)"; + BUILT_IN(selector_nest) + { + To_String to_string; + List* arglist = ARG("$selectors", List); + + // Not enough parameters + if( arglist->length() == 0 ) + error("$selectors: At least one selector must be passed", pstate); + + // Parse args into vector of selectors + vector parsedSelectors; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression* exp = dynamic_cast(arglist->value_at_index(i)); + string exp_src = exp->perform(&to_string) + "{"; + Selector_List* sel = Parser::parse_selector(exp_src.c_str(), ctx); + parsedSelectors.push_back(sel); + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return new (ctx.mem) Null(pstate); + } + + // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. + std::vector::iterator itr = parsedSelectors.begin(); + Selector_List* result = *itr; + ++itr; + + for(;itr != parsedSelectors.end(); ++itr) { + Selector_List* child = *itr; + vector newElements; + + // For every COMPLEX_SELECTOR in `child` + // For every COMPLEX_SELECTOR in `result` + // let parentSeqClone equal a copy of result->elements[i] + // let childSeq equal child->elements[j] + // Set childSeq as the new innermost tail of parentSeqClone + // Add parentSeqClone to the newElements + // Replace result->elements with newElements + for (size_t i = 0, resultLen = result->length(); i < resultLen; ++i) { + for (size_t j = 0, childLen = child->length(); j < childLen; ++j) { + Complex_Selector* parentSeqClone = (*result)[i]->cloneFully(ctx); + Complex_Selector* childSeq = (*child)[j]; + + parentSeqClone->innermost()->tail(childSeq); // Set seq as the new tail of parentSeqClone + newElements.push_back(parentSeqClone); + } + } + + result->elements(newElements); + } + + + Listize listize(ctx); + return result->perform(&listize); + } + + Signature selector_append_sig = "selector-append($selectors...)"; + BUILT_IN(selector_append) + { + To_String to_string; + List* arglist = ARG("$selectors", List); + + // Not enough parameters + if( arglist->length() == 0 ) + error("$selectors: At least one selector must be passed", pstate); + + // Parse args into vector of selectors + vector parsedSelectors; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression* exp = dynamic_cast(arglist->value_at_index(i)); + string exp_src = exp->perform(&to_string) + "{"; + Selector_List* sel = Parser::parse_selector(exp_src.c_str(), ctx); + parsedSelectors.push_back(sel); + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return new (ctx.mem) Null(pstate); + } + + // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. + std::vector::iterator itr = parsedSelectors.begin(); + Selector_List* result = *itr; + ++itr; + + for(;itr != parsedSelectors.end(); ++itr) { + Selector_List* child = *itr; + vector newElements; + + // For every COMPLEX_SELECTOR in `result` + // For every COMPLEX_SELECTOR in `child` + // let parentSeqClone equal a copy of result->elements[i] + // let childSeq equal child->elements[j] + // Append all of childSeq head elements into parentSeqClone + // Set the innermost tail of parentSeqClone, to childSeq's tail + // Replace result->elements with newElements + for (size_t i = 0, resultLen = result->length(); i < resultLen; ++i) { + for (size_t j = 0, childLen = child->length(); j < childLen; ++j) { + Complex_Selector* parentSeqClone = (*result)[i]->cloneFully(ctx); + Complex_Selector* childSeq = (*child)[j]; + Complex_Selector* base = childSeq->tail(); + + // Must be a simple sequence + if( childSeq->combinator() != Complex_Selector::Combinator::ANCESTOR_OF ) { + string msg("Can't append `"); + msg += childSeq->perform(&to_string); + msg += "` to `"; + msg += parentSeqClone->perform(&to_string);; + msg += "`"; + error(msg, pstate, backtrace); + } + + // Cannot be a Universal selector + Type_Selector* pType = dynamic_cast(base->head()->first()); + if(pType && pType->name() == "*") { + string msg("Can't append `"); + msg += childSeq->perform(&to_string); + msg += "` to `"; + msg += parentSeqClone->perform(&to_string);; + msg += "`"; + error(msg, pstate, backtrace); + } + + // TODO: Add check for namespace stuff + + // append any selectors in childSeq's head + *(parentSeqClone->innermost()->head()) += (base->head()); + + // Set parentSeqClone new tail + parentSeqClone->innermost()->tail( base->tail() ); + + newElements.push_back(parentSeqClone); + } + } + + result->elements(newElements); + } + + Listize listize(ctx); + return result->perform(&listize); + } + + Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)"; + BUILT_IN(selector_extend) + { + To_String to_string; + + Selector_List* selector = ARGSEL("$selector", Selector_List, p_contextualize); + Selector_List* extendee = ARGSEL("$extendee", Selector_List, p_contextualize); + Selector_List* extender = ARGSEL("$extender", Selector_List, p_contextualize); + + ExtensionSubsetMap subset_map; + extender->populate_extends(extendee, ctx, subset_map); + + bool extendedSomething; + Selector_List* result = Extend::extendSelectorList(selector, ctx, subset_map, false, extendedSomething); + + Listize listize(ctx); + return result->perform(&listize); + } + + Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)"; + BUILT_IN(selector_replace) + { + Selector_List* selector = ARGSEL("$selector", Selector_List, p_contextualize); + Selector_List* original = ARGSEL("$original", Selector_List, p_contextualize); + Selector_List* replacement = ARGSEL("$replacement", Selector_List, p_contextualize); + + ExtensionSubsetMap subset_map; + replacement->populate_extends(original, ctx, subset_map); + + bool extendedSomething; + Selector_List* result = Extend::extendSelectorList(selector, ctx, subset_map, true, extendedSomething); + + Listize listize(ctx); + return result->perform(&listize); + } + + Signature selector_unify_sig = "selector-unify($selector1, $selector2)"; + BUILT_IN(selector_unify) + { + Selector_List* selector1 = ARGSEL("$selector1", Selector_List, p_contextualize); + Selector_List* selector2 = ARGSEL("$selector2", Selector_List, p_contextualize); + + Selector_List* result = selector1->unify_with(selector2, ctx); + Listize listize(ctx); + return result->perform(&listize); + } Signature is_superselector_sig = "is-superselector($super, $sub)"; BUILT_IN(is_superselector) @@ -1577,6 +1800,37 @@ namespace Sass { bool result = sel_sup->is_superselector_of(sel_sub); return new (ctx.mem) Boolean(pstate, result); } + + Signature simple_selectors_sig = "simple_selectors($selector)"; + BUILT_IN(simple_selectors) + { + Compound_Selector* sel = ARGSEL("$selector", Compound_Selector, p_contextualize); + + To_String to_string; + List* l = new (ctx.mem) List(sel->pstate(), sel->length(), List::COMMA); + + for (size_t i = 0, L = sel->length(); i < L; ++i) { + Simple_Selector* ss = (*sel)[i]; + string ss_string = ss->perform(&to_string) ; + + *l << new (ctx.mem) String_Constant(ss->pstate(), ss_string); + } + + + return l; + } + + Signature selector_parse_sig = "selector-parse($selector)"; + BUILT_IN(selector_parse) + { + To_String to_string(&ctx, false); + Expression* exp = ARG("$selector", Expression); + string sel_src = exp->perform(&to_string) + "{"; + Selector_List* sel = Parser::parse_selector(sel_src.c_str(), ctx); + + Listize listize(ctx); + return sel->perform(&listize); + } Signature unique_id_sig = "unique-id()"; BUILT_IN(unique_id) diff --git a/functions.hpp b/functions.hpp index c0307f6ed4..8b993e9176 100644 --- a/functions.hpp +++ b/functions.hpp @@ -101,7 +101,14 @@ namespace Sass { extern Signature keywords_sig; extern Signature set_nth_sig; extern Signature unique_id_sig; + extern Signature selector_nest_sig; + extern Signature selector_append_sig; + extern Signature selector_extend_sig; + extern Signature selector_replace_sig; + extern Signature selector_unify_sig; extern Signature is_superselector_sig; + extern Signature simple_selectors_sig; + extern Signature selector_parse_sig; BUILT_IN(rgb); BUILT_IN(rgba_4); @@ -176,7 +183,15 @@ namespace Sass { BUILT_IN(keywords); BUILT_IN(set_nth); BUILT_IN(unique_id); + BUILT_IN(selector_nest); + BUILT_IN(selector_append); + BUILT_IN(selector_extend); + BUILT_IN(selector_replace); + BUILT_IN(selector_unify); BUILT_IN(is_superselector); + BUILT_IN(simple_selectors); + BUILT_IN(selector_parse); + } } diff --git a/node.cpp b/node.cpp index 7ed2d55505..c963f4cbb7 100644 --- a/node.cpp +++ b/node.cpp @@ -247,5 +247,30 @@ namespace Sass { return pFirst; } + // A very naive trim function, which removes duplicates in a node + // This is only used in Complex_Selector::unify_with for now, may need modifications to fit other needs + Node Node::naiveTrim(Node& seqses, Context& ctx) { + + Node result = Node::createCollection(); + + To_String to_string; + std::set< Complex_Selector*, Complex_Selector_Pointer_Compare > sel_set; + + // Add all selectors we don't already have, everything else just add it blindly + for (NodeDeque::iterator seqsesIter = seqses.collection()->begin(), seqsesIterEnd = seqses.collection()->end(); seqsesIter != seqsesIterEnd; ++seqsesIter) { + Node& seqs1 = *seqsesIter; + if( seqs1.isSelector() ) { + auto found = sel_set.find( seqs1.selector() ); + if( found == sel_set.end() ) { + sel_set.insert(seqs1.selector()); + result.collection()->push_back(seqs1); + } + } else { + result.collection()->push_back(seqs1); + } + } + + return result; + } } diff --git a/node.hpp b/node.hpp index 6aa1156819..faaa95eea7 100644 --- a/node.hpp +++ b/node.hpp @@ -69,6 +69,7 @@ namespace Sass { static Node createCollection(const NodeDeque& values); static Node createNil(); + static Node naiveTrim(Node& seqses, Context& ctx); Node clone(Context& ctx) const;