4 #include "backtrace.hpp"
8 #include "sass_util.hpp"
9 #include "remove_placeholders.hpp"
18 - The print* functions print to cerr. This allows our testing frameworks (like sass-spec) to ignore the output, which
19 is very helpful when debugging. The format of the output is mainly to wrap things in square brackets to match what
20 ruby already outputs (to make comparisons easier).
22 - For the direct porting effort, we're trying to port method-for-method until we get all the tests passing.
23 Where applicable, I've tried to include the ruby code above the function for reference until all our tests pass.
24 The ruby code isn't always directly portable, so I've tried to include any modified ruby code that was actually
27 - DO NOT try to optimize yet. We get a tremendous benefit out of comparing the output of each stage of the extend to the ruby
28 output at the same stage. This makes it much easier to determine where problems are. Try to keep as close to
29 the ruby code as you can until we have all the sass-spec tests passing. Then, we should optimize. However, if you see
30 something that could probably be optimized, let's not forget it. Add a // TODO: or // IMPROVEMENT: comment.
32 - Coding conventions in this file (these may need to be changed before merging back into master)
33 - Very basic hungarian notation:
34 p prefix for pointers (pSelector)
35 no prefix for value types and references (selector)
36 - Use STL iterators where possible
37 - prefer verbose naming over terse naming
38 - use typedefs for STL container types for make maintenance easier
40 - You may see a lot of comments that say "// TODO: is this the correct combinator?". See the comment referring to combinators
41 in extendCompoundSelector for a more extensive explanation of my confusion. I think our divergence in data model from ruby
42 sass causes this to be necessary.
47 - wrap the contents of the print functions in DEBUG preprocesser conditionals so they will be optimized away in non-debug mode.
49 - consider making the extend* functions member functions to avoid passing around ctx and subset_map map around. This has the
50 drawback that the implementation details of the operator are then exposed to the outside world, which is not ideal and
51 can cause additional compile time dependencies.
53 - mark the helper methods in this file static to given them compilation unit linkage.
55 - implement parent directive matching
57 - fix compilation warnings for unused Extend members if we really don't need those references anymore.
67 // TODO: move the ast specific ostream operators into ast.hpp/ast.cpp
68 std::ostream& operator<<(std::ostream& os, const Complex_Selector::Combinator combinator) {
70 case Complex_Selector::ANCESTOR_OF: os << "\" \""; break;
71 case Complex_Selector::PARENT_OF: os << "\">\""; break;
72 case Complex_Selector::PRECEDES: os << "\"~\""; break;
73 case Complex_Selector::ADJACENT_TO: os << "\"+\""; break;
74 case Complex_Selector::REFERENCE: os << "\"/\""; break;
81 std::ostream& operator<<(std::ostream& os, Compound_Selector& compoundSelector) {
82 for (size_t i = 0, L = compoundSelector.length(); i < L; ++i) {
83 if (i > 0) os << ", ";
84 os << compoundSelector[i]->to_string();
89 std::ostream& operator<<(std::ostream& os, Simple_Selector& simpleSelector) {
90 os << simpleSelector.to_string();
94 // Print a string representation of a Compound_Selector
95 static void printSimpleSelector(Simple_Selector* pSimpleSelector, const char* message=NULL, bool newline=true) {
101 if (pSimpleSelector) {
102 std::cerr << "[" << *pSimpleSelector << "]";
108 std::cerr << std::endl;
112 // Print a string representation of a Compound_Selector
113 static void printCompoundSelector(Compound_Selector_Ptr pCompoundSelector, const char* message=NULL, bool newline=true) {
116 std::cerr << message;
119 if (pCompoundSelector) {
120 std::cerr << "[" << *pCompoundSelector << "]";
126 std::cerr << std::endl;
131 std::ostream& operator<<(std::ostream& os, Complex_Selector& complexSelector) {
134 Complex_Selector_Ptr pIter = &complexSelector;
137 if (pIter->combinator() != Complex_Selector::ANCESTOR_OF) {
142 os << pIter->combinator();
151 os << pIter->head()->to_string();
156 pIter = pIter->tail();
164 // Print a string representation of a Complex_Selector
165 static void printComplexSelector(Complex_Selector_Ptr pComplexSelector, const char* message=NULL, bool newline=true) {
168 std::cerr << message;
171 if (pComplexSelector) {
172 std::cerr << *pComplexSelector;
178 std::cerr << std::endl;
182 static void printSelsNewSeqPairCollection(SubSetMapLookups& collection, const char* message=NULL, bool newline=true) {
185 std::cerr << message;
189 for(SubSetMapLookup& pair : collection) {
196 Compound_Selector_Ptr pSels = pair.first;
197 Complex_Selector_Ptr pNewSelector = pair.second;
198 std::cerr << "[" << *pSels << "], ";
199 printComplexSelector(pNewSelector, NULL, false);
204 std::cerr << std::endl;
208 // Print a string representation of a ComplexSelectorSet
209 static void printSourcesSet(ComplexSelectorSet& sources, Context& ctx, const char* message=NULL, bool newline=true) {
212 std::cerr << message;
215 // Convert to a deque of strings so we can sort since order doesn't matter in a set. This should cut down on
216 // the differences we see when debug printing.
217 typedef std::deque<std::string> SourceStrings;
218 SourceStrings sourceStrings;
219 for (ComplexSelectorSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) {
220 Complex_Selector_Ptr pSource = *iterator;
221 std::stringstream sstream;
222 sstream << complexSelectorToNode(pSource, ctx);
223 sourceStrings.push_back(sstream.str());
226 // Sort to get consistent output
227 std::sort(sourceStrings.begin(), sourceStrings.end());
229 std::cerr << "ComplexSelectorSet[";
230 for (SourceStrings::iterator iterator = sourceStrings.begin(), iteratorEnd = sourceStrings.end(); iterator != iteratorEnd; ++iterator) {
231 std::string source = *iterator;
232 if (iterator != sourceStrings.begin()) {
240 std::cerr << std::endl;
245 std::ostream& operator<<(std::ostream& os, SubSetMapPairs& entries) {
246 os << "SUBSET_MAP_ENTRIES[";
248 for (SubSetMapPairs::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) {
249 Complex_Selector_Obj pExtComplexSelector = iterator->first; // The selector up to where the @extend is (ie, the thing to merge)
250 Compound_Selector_Obj pExtCompoundSelector = iterator->second; // The stuff after the @extend
252 if (iterator != entries.begin()) {
258 if (pExtComplexSelector) {
259 std::cerr << *pExtComplexSelector;
266 if (pExtCompoundSelector) {
267 std::cerr << *pExtCompoundSelector;
282 static bool parentSuperselector(Complex_Selector_Ptr pOne, Complex_Selector_Ptr pTwo, Context& ctx) {
283 // TODO: figure out a better way to create a Complex_Selector from scratch
284 // TODO: There's got to be a better way. This got ugly quick...
285 Position noPosition(-1, -1, -1);
286 Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp");
287 Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/);
288 fakeHead->elements().push_back(fakeParent);
289 Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/);
291 pOne->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF);
292 pTwo->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF);
294 bool isSuperselector = pOne->is_superselector_of(pTwo);
296 pOne->clear_innermost();
297 pTwo->clear_innermost();
299 return isSuperselector;
302 void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out, Context& ctx) {
303 for (NodeDeque::iterator iter = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) {
305 out.push_back(nodeToComplexSelector(child, ctx));
309 Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque, Context& ctx) {
310 Node result = Node::createCollection();
312 for (ComplexSelectorDeque::const_iterator iter = deque.begin(), iterEnd = deque.end(); iter != iterEnd; iter++) {
313 Complex_Selector_Obj pChild = *iter;
314 result.collection()->push_back(complexSelectorToNode(pChild, ctx));
320 class LcsCollectionComparator {
322 LcsCollectionComparator(Context& ctx) : mCtx(ctx) {}
326 bool operator()(Complex_Selector_Obj pOne, Complex_Selector_Obj pTwo, Complex_Selector_Obj& pOut) const {
328 This code is based on the following block from ruby sass' subweave
331 next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)
332 next s2 if parent_superselector?(s1, s2)
333 next s1 if parent_superselector?(s2, s1)
337 if (*pOne == *pTwo) {
342 if (pOne->combinator() != Complex_Selector::ANCESTOR_OF || pTwo->combinator() != Complex_Selector::ANCESTOR_OF) {
346 if (parentSuperselector(pOne, pTwo, mCtx)) {
351 if (parentSuperselector(pTwo, pOne, mCtx)) {
362 This is the equivalent of ruby's Sass::Util.lcs_backtrace.
364 # Computes a single longest common subsequence for arrays x and y.
365 # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS
367 void lcs_backtrace(const LCSTable& c, ComplexSelectorDeque& x, ComplexSelectorDeque& y, int i, int j, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) {
368 //DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j)
369 // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output
371 if (i == 0 || j == 0) {
372 DEBUG_PRINTLN(LCS, "RETURNING EMPTY")
377 Complex_Selector_Obj pCompareOut;
378 if (comparator(x[i], y[j], pCompareOut)) {
379 DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE")
380 lcs_backtrace(c, x, y, i - 1, j - 1, comparator, out);
381 out.push_back(pCompareOut);
385 if (c[i][j - 1] > c[i - 1][j]) {
386 DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE")
387 lcs_backtrace(c, x, y, i, j - 1, comparator, out);
391 DEBUG_PRINTLN(LCS, "FINAL RETURN")
392 lcs_backtrace(c, x, y, i - 1, j, comparator, out);
397 This is the equivalent of ruby's Sass::Util.lcs_table.
399 # Calculates the memoization table for the Least Common Subsequence algorithm.
400 # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS
402 void lcs_table(const ComplexSelectorDeque& x, const ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, LCSTable& out) {
403 //DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y)
404 // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output
406 LCSTable c(x.size(), std::vector<int>(y.size()));
408 // These shouldn't be necessary since the vector will be initialized to 0 already.
409 // x.size.times {|i| c[i][0] = 0}
410 // y.size.times {|j| c[0][j] = 0}
412 for (size_t i = 1; i < x.size(); i++) {
413 for (size_t j = 1; j < y.size(); j++) {
414 Complex_Selector_Obj pCompareOut;
416 if (comparator(x[i], y[j], pCompareOut)) {
417 c[i][j] = c[i - 1][j - 1] + 1;
419 c[i][j] = std::max(c[i][j - 1], c[i - 1][j]);
428 This is the equivalent of ruby's Sass::Util.lcs.
430 # Computes a single longest common subsequence for `x` and `y`.
431 # If there are more than one longest common subsequences,
432 # the one returned is that which starts first in `x`.
434 # @param x [NodeCollection]
435 # @param y [NodeCollection]
436 # @comparator An equality check between elements of `x` and `y`.
437 # @return [NodeCollection] The LCS
439 http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
441 void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, Context& ctx, ComplexSelectorDeque& out) {
442 //DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y)
443 // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output
449 lcs_table(x, y, comparator, table);
451 return lcs_backtrace(table, x, y, static_cast<int>(x.size()) - 1, static_cast<int>(y.size()) - 1, comparator, out);
456 This is the equivalent of ruby's Sequence.trim.
458 The following is the modified version of the ruby code that was more portable to C++. You
459 should be able to drop it into ruby 3.2.19 and get the same results from ruby sass.
461 # Avoid truly horrific quadratic behavior. TODO: I think there
462 # may be a way to get perfect trimming without going quadratic.
463 return seqses if seqses.size > 100
465 # Keep the results in a separate array so we can be sure we aren't
466 # comparing against an already-trimmed selector. This ensures that two
467 # identical selectors don't mutually trim one another.
470 # This is n^2 on the sequences, but only comparing between
471 # separate sequences should limit the quadratic behavior.
472 seqses.each_with_index do |seqs1, i|
477 for seq in _sources(seq1) do
478 max_spec = [max_spec, seq.specificity].max
482 isMoreSpecificOuter = false
483 for seqs2 in result do
484 if seqs1.equal?(seqs2) then
488 # Second Law of Extend: the specificity of a generated selector
489 # should never be less than the specificity of the extending
492 # See https://github.com/nex3/sass/issues/324.
493 isMoreSpecificInner = false
495 isMoreSpecificInner = _specificity(seq2) >= max_spec && _superselector?(seq2, seq1)
496 if isMoreSpecificInner then
501 if isMoreSpecificInner then
502 isMoreSpecificOuter = true
507 if !isMoreSpecificOuter then
508 tempResult.push(seq1)
512 result[i] = tempResult
519 - IMPROVEMENT: We could probably work directly in the output trimmed deque.
521 static Node trim(Node& seqses, Context& ctx, bool isReplace) {
522 // See the comments in the above ruby code before embarking on understanding this function.
524 // Avoid poor performance in extreme cases.
525 if (seqses.collection()->size() > 100) {
530 DEBUG_PRINTLN(TRIM, "TRIM: " << seqses)
533 Node result = Node::createCollection();
536 DEBUG_PRINTLN(TRIM, "RESULT INITIAL: " << result)
538 // Normally we use the standard STL iterators, but in this case, we need to access the result collection by index since we're
539 // iterating the input collection, computing a value, and then setting the result in the output collection. We have to keep track
540 // of the index manually.
543 for (NodeDeque::iterator seqsesIter = seqses.collection()->begin(), seqsesIterEnd = seqses.collection()->end(); seqsesIter != seqsesIterEnd; ++seqsesIter) {
544 Node& seqs1 = *seqsesIter;
546 DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1 << " " << toTrimIndex)
548 Node tempResult = Node::createCollection();
549 tempResult.got_line_feed = seqs1.got_line_feed;
551 for (NodeDeque::iterator seqs1Iter = seqs1.collection()->begin(), seqs1EndIter = seqs1.collection()->end(); seqs1Iter != seqs1EndIter; ++seqs1Iter) {
552 Node& seq1 = *seqs1Iter;
554 Complex_Selector_Obj pSeq1 = nodeToComplexSelector(seq1, ctx);
556 // Compute the maximum specificity. This requires looking at the "sources" of the sequence. See SimpleSequence.sources in the ruby code
557 // for a good description of sources.
559 // TODO: I'm pretty sure there's a bug in the sources code. It was implemented for sass-spec's 182_test_nested_extend_loop test.
560 // While the test passes, I compared the state of each trim call to verify correctness. The last trim call had incorrect sources. We
561 // had an extra source that the ruby version did not have. Without a failing test case, this is going to be extra hard to find. My
562 // best guess at this point is that we're cloning an object somewhere and maintaining the sources when we shouldn't be. This is purely
564 unsigned long maxSpecificity = isReplace ? pSeq1->specificity() : 0;
565 ComplexSelectorSet sources = pSeq1->sources();
567 DEBUG_PRINTLN(TRIM, "TRIM SEQ1: " << seq1)
568 DEBUG_EXEC(TRIM, printSourcesSet(sources, ctx, "TRIM SOURCES: "))
570 for (ComplexSelectorSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) {
571 const Complex_Selector_Obj& pCurrentSelector = *sourcesSetIterator;
572 maxSpecificity = std::max(maxSpecificity, pCurrentSelector->specificity());
575 DEBUG_PRINTLN(TRIM, "MAX SPECIFICITY: " << maxSpecificity)
577 bool isMoreSpecificOuter = false;
581 for (NodeDeque::iterator resultIter = result.collection()->begin(), resultIterEnd = result.collection()->end(); resultIter != resultIterEnd; ++resultIter) {
582 Node& seqs2 = *resultIter;
584 DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1)
585 DEBUG_PRINTLN(TRIM, "SEQS2: " << seqs2)
587 // Do not compare the same sequence to itself. The ruby call we're trying to
588 // emulate is: seqs1.equal?(seqs2). equal? is an object comparison, not an equivalency comparision.
589 // Since we have the same pointers in seqes and results, we can do a pointer comparision. seqs1 is
590 // derived from seqses and seqs2 is derived from result.
591 if (seqs1.collection() == seqs2.collection()) {
592 DEBUG_PRINTLN(TRIM, "CONTINUE")
596 bool isMoreSpecificInner = false;
598 for (NodeDeque::iterator seqs2Iter = seqs2.collection()->begin(), seqs2IterEnd = seqs2.collection()->end(); seqs2Iter != seqs2IterEnd; ++seqs2Iter) {
599 Node& seq2 = *seqs2Iter;
601 Complex_Selector_Obj pSeq2 = nodeToComplexSelector(seq2, ctx);
603 DEBUG_PRINTLN(TRIM, "SEQ2 SPEC: " << pSeq2->specificity())
604 DEBUG_PRINTLN(TRIM, "IS SPEC: " << pSeq2->specificity() << " >= " << maxSpecificity << " " << (pSeq2->specificity() >= maxSpecificity ? "true" : "false"))
605 DEBUG_PRINTLN(TRIM, "IS SUPER: " << (pSeq2->is_superselector_of(pSeq1) ? "true" : "false"))
607 isMoreSpecificInner = pSeq2->specificity() >= maxSpecificity && pSeq2->is_superselector_of(pSeq1);
609 if (isMoreSpecificInner) {
610 DEBUG_PRINTLN(TRIM, "FOUND MORE SPECIFIC")
615 // If we found something more specific, we're done. Let the outer loop know and stop iterating.
616 if (isMoreSpecificInner) {
617 isMoreSpecificOuter = true;
624 if (!isMoreSpecificOuter) {
625 DEBUG_PRINTLN(TRIM, "PUSHING: " << seq1)
626 tempResult.collection()->push_back(seq1);
631 DEBUG_PRINTLN(TRIM, "RESULT BEFORE ASSIGN: " << result)
632 DEBUG_PRINTLN(TRIM, "TEMP RESULT: " << toTrimIndex << " " << tempResult)
633 (*result.collection())[toTrimIndex] = tempResult;
637 DEBUG_PRINTLN(TRIM, "RESULT: " << result)
645 static bool parentSuperselector(const Node& one, const Node& two, Context& ctx) {
646 // TODO: figure out a better way to create a Complex_Selector from scratch
647 // TODO: There's got to be a better way. This got ugly quick...
648 Position noPosition(-1, -1, -1);
649 Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp");
650 Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/);
651 fakeHead->elements().push_back(fakeParent);
652 Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/);
654 Complex_Selector_Obj pOneWithFakeParent = nodeToComplexSelector(one, ctx);
655 pOneWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF);
656 Complex_Selector_Obj pTwoWithFakeParent = nodeToComplexSelector(two, ctx);
657 pTwoWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF);
659 return pOneWithFakeParent->is_superselector_of(pTwoWithFakeParent);
663 class ParentSuperselectorChunker {
665 ParentSuperselectorChunker(Node& lcs, Context& ctx) : mLcs(lcs), mCtx(ctx) {}
669 bool operator()(const Node& seq) const {
670 // {|s| parent_superselector?(s.first, lcs.first)}
671 if (seq.collection()->size() == 0) return false;
672 return parentSuperselector(seq.collection()->front(), mLcs.collection()->front(), mCtx);
676 class SubweaveEmptyChunker {
678 bool operator()(const Node& seq) const {
681 return seq.collection()->empty();
686 # Takes initial subsequences of `seq1` and `seq2` and returns all
687 # orderings of those subsequences. The initial subsequences are determined
690 # Destructively removes the initial subsequences of `seq1` and `seq2`.
692 # For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|`
693 # denoting the boundary of the initial subsequence), this would return
694 # `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and
697 # @param seq1 [Array]
698 # @param seq2 [Array]
699 # @yield [a] Used to determine when to cut off the initial subsequences.
700 # Called repeatedly for each sequence until it returns true.
701 # @yieldparam a [Array] A final subsequence of one input sequence after
702 # cutting off some initial subsequence.
703 # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence
705 # @return [Array<Array>] All possible orderings of the initial subsequences.
706 def chunks(seq1, seq2)
708 chunk1 << seq1.shift until yield seq1
710 chunk2 << seq2.shift until yield seq2
711 return [] if chunk1.empty? && chunk2.empty?
712 return [chunk2] if chunk1.empty?
713 return [chunk1] if chunk2.empty?
714 [chunk1 + chunk2, chunk2 + chunk1]
717 template<typename ChunkerType>
718 static Node chunks(Node& seq1, Node& seq2, const ChunkerType& chunker) {
719 Node chunk1 = Node::createCollection();
720 while (seq1.collection()->size() && !chunker(seq1)) {
721 chunk1.collection()->push_back(seq1.collection()->front());
722 seq1.collection()->pop_front();
725 Node chunk2 = Node::createCollection();
726 while (!chunker(seq2)) {
727 chunk2.collection()->push_back(seq2.collection()->front());
728 seq2.collection()->pop_front();
731 if (chunk1.collection()->empty() && chunk2.collection()->empty()) {
732 DEBUG_PRINTLN(CHUNKS, "RETURNING BOTH EMPTY")
733 return Node::createCollection();
736 if (chunk1.collection()->empty()) {
737 Node chunk2Wrapper = Node::createCollection();
738 chunk2Wrapper.collection()->push_back(chunk2);
739 DEBUG_PRINTLN(CHUNKS, "RETURNING ONE EMPTY")
740 return chunk2Wrapper;
743 if (chunk2.collection()->empty()) {
744 Node chunk1Wrapper = Node::createCollection();
745 chunk1Wrapper.collection()->push_back(chunk1);
746 DEBUG_PRINTLN(CHUNKS, "RETURNING TWO EMPTY")
747 return chunk1Wrapper;
750 Node perms = Node::createCollection();
752 Node firstPermutation = Node::createCollection();
753 firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end());
754 firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end());
755 perms.collection()->push_back(firstPermutation);
757 Node secondPermutation = Node::createCollection();
758 secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end());
759 secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end());
760 perms.collection()->push_back(secondPermutation);
762 DEBUG_PRINTLN(CHUNKS, "RETURNING PERM")
768 static Node groupSelectors(Node& seq, Context& ctx) {
769 Node newSeq = Node::createCollection();
771 Node tail = Node::createCollection();
774 while (!tail.collection()->empty()) {
775 Node head = Node::createCollection();
778 head.collection()->push_back(tail.collection()->front());
779 tail.collection()->pop_front();
780 } while (!tail.collection()->empty() && (head.collection()->back().isCombinator() || tail.collection()->front().isCombinator()));
782 newSeq.collection()->push_back(head);
789 static void getAndRemoveInitialOps(Node& seq, Node& ops) {
790 NodeDeque& seqCollection = *(seq.collection());
791 NodeDeque& opsCollection = *(ops.collection());
793 while (seqCollection.size() > 0 && seqCollection.front().isCombinator()) {
794 opsCollection.push_back(seqCollection.front());
795 seqCollection.pop_front();
800 static void getAndRemoveFinalOps(Node& seq, Node& ops) {
801 NodeDeque& seqCollection = *(seq.collection());
802 NodeDeque& opsCollection = *(ops.collection());
804 while (seqCollection.size() > 0 && seqCollection.back().isCombinator()) {
805 opsCollection.push_back(seqCollection.back()); // Purposefully reversed to match ruby code
806 seqCollection.pop_back();
812 def merge_initial_ops(seq1, seq2)
814 ops1 << seq1.shift while seq1.first.is_a?(String)
815 ops2 << seq2.shift while seq2.first.is_a?(String)
818 newline ||= !!ops1.shift if ops1.first == "\n"
819 newline ||= !!ops2.shift if ops2.first == "\n"
821 # If neither sequence is a subsequence of the other, they cannot be
822 # merged successfully
823 lcs = Sass::Util.lcs(ops1, ops2)
824 return unless lcs == ops1 || lcs == ops2
825 return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)
828 static Node mergeInitialOps(Node& seq1, Node& seq2, Context& ctx) {
829 Node ops1 = Node::createCollection();
830 Node ops2 = Node::createCollection();
832 getAndRemoveInitialOps(seq1, ops1);
833 getAndRemoveInitialOps(seq2, ops2);
835 // TODO: Do we have this information available to us?
837 // newline ||= !!ops1.shift if ops1.first == "\n"
838 // newline ||= !!ops2.shift if ops2.first == "\n"
840 // If neither sequence is a subsequence of the other, they cannot be merged successfully
841 DefaultLcsComparator lcsDefaultComparator;
842 Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx);
844 if (!(opsLcs == ops1 || opsLcs == ops2)) {
845 return Node::createNil();
848 // TODO: more newline logic
849 // return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)
851 return (ops1.collection()->size() > ops2.collection()->size() ? ops1 : ops2);
856 def merge_final_ops(seq1, seq2, res = [])
859 # This code looks complicated, but it's actually just a bunch of special
860 # cases for interactions between different combinators.
861 op1, op2 = ops1.first, ops2.first
865 if op1 == '~' && op2 == '~'
866 if sel1.superselector?(sel2)
867 res.unshift sel2, '~'
868 elsif sel2.superselector?(sel1)
869 res.unshift sel1, '~'
871 merged = sel1.unify(sel2.members, sel2.subject?)
873 [sel1, '~', sel2, '~'],
874 [sel2, '~', sel1, '~'],
875 ([merged, '~'] if merged)
878 elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~')
880 tilde_sel, plus_sel = sel1, sel2
882 tilde_sel, plus_sel = sel2, sel1
885 if tilde_sel.superselector?(plus_sel)
886 res.unshift plus_sel, '+'
888 merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?)
890 [tilde_sel, '~', plus_sel, '+'],
891 ([merged, '+'] if merged)
894 elsif op1 == '>' && %w[~ +].include?(op2)
895 res.unshift sel2, op2
897 elsif op2 == '>' && %w[~ +].include?(op1)
898 res.unshift sel1, op1
901 return unless merged = sel1.unify(sel2.members, sel2.subject?)
902 res.unshift merged, op1
904 # Unknown selector combinators can't be unified
907 return merge_final_ops(seq1, seq2, res)
909 seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last)
910 res.unshift seq1.pop, op1
911 return merge_final_ops(seq1, seq2, res)
913 seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last)
914 res.unshift seq2.pop, op2
915 return merge_final_ops(seq1, seq2, res)
919 static Node mergeFinalOps(Node& seq1, Node& seq2, Context& ctx, Node& res) {
921 Node ops1 = Node::createCollection();
922 Node ops2 = Node::createCollection();
924 getAndRemoveFinalOps(seq1, ops1);
925 getAndRemoveFinalOps(seq2, ops2);
927 // TODO: do we have newlines to remove?
928 // ops1.reject! {|o| o == "\n"}
929 // ops2.reject! {|o| o == "\n"}
931 if (ops1.collection()->empty() && ops2.collection()->empty()) {
935 if (ops1.collection()->size() > 1 || ops2.collection()->size() > 1) {
936 DefaultLcsComparator lcsDefaultComparator;
937 Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx);
939 // If there are multiple operators, something hacky's going on. If one is a supersequence of the other, use that, otherwise give up.
941 if (!(opsLcs == ops1 || opsLcs == ops2)) {
942 return Node::createNil();
945 if (ops1.collection()->size() > ops2.collection()->size()) {
946 res.collection()->insert(res.collection()->begin(), ops1.collection()->rbegin(), ops1.collection()->rend());
948 res.collection()->insert(res.collection()->begin(), ops2.collection()->rbegin(), ops2.collection()->rend());
954 if (!ops1.collection()->empty() && !ops2.collection()->empty()) {
956 Node op1 = ops1.collection()->front();
957 Node op2 = ops2.collection()->front();
959 Node sel1 = seq1.collection()->back();
960 seq1.collection()->pop_back();
962 Node sel2 = seq2.collection()->back();
963 seq2.collection()->pop_back();
965 if (op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::PRECEDES) {
967 if (sel1.selector()->is_superselector_of(sel2.selector())) {
969 res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/);
970 res.collection()->push_front(sel2);
972 } else if (sel2.selector()->is_superselector_of(sel1.selector())) {
974 res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/);
975 res.collection()->push_front(sel1);
979 DEBUG_PRINTLN(ALL, "sel1: " << sel1)
980 DEBUG_PRINTLN(ALL, "sel2: " << sel2)
982 Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result
983 // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?)
984 Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head(), ctx);
985 pMergedWrapper->head(pMerged);
987 DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: "))
989 Node newRes = Node::createCollection();
991 Node firstPerm = Node::createCollection();
992 firstPerm.collection()->push_back(sel1);
993 firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));
994 firstPerm.collection()->push_back(sel2);
995 firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));
996 newRes.collection()->push_back(firstPerm);
998 Node secondPerm = Node::createCollection();
999 secondPerm.collection()->push_back(sel2);
1000 secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));
1001 secondPerm.collection()->push_back(sel1);
1002 secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));
1003 newRes.collection()->push_back(secondPerm);
1006 Node mergedPerm = Node::createCollection();
1007 mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper, ctx));
1008 mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));
1009 newRes.collection()->push_back(mergedPerm);
1012 res.collection()->push_front(newRes);
1014 DEBUG_PRINTLN(ALL, "RESULT: " << res)
1018 } else if (((op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::ADJACENT_TO)) || ((op1.combinator() == Complex_Selector::ADJACENT_TO && op2.combinator() == Complex_Selector::PRECEDES))) {
1020 Node tildeSel = sel1;
1022 Node plusSel = sel2;
1024 if (op1.combinator() != Complex_Selector::PRECEDES) {
1031 if (tildeSel.selector()->is_superselector_of(plusSel.selector())) {
1033 res.collection()->push_front(plusOp);
1034 res.collection()->push_front(plusSel);
1038 DEBUG_PRINTLN(ALL, "PLUS SEL: " << plusSel)
1039 DEBUG_PRINTLN(ALL, "TILDE SEL: " << tildeSel)
1041 Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(plusSel.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result
1042 // TODO: does subject matter? Ruby: merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?)
1043 Compound_Selector_Ptr pMerged = plusSel.selector()->head()->unify_with(tildeSel.selector()->head(), ctx);
1044 pMergedWrapper->head(pMerged);
1046 DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: "))
1048 Node newRes = Node::createCollection();
1050 Node firstPerm = Node::createCollection();
1051 firstPerm.collection()->push_back(tildeSel);
1052 firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));
1053 firstPerm.collection()->push_back(plusSel);
1054 firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO));
1055 newRes.collection()->push_back(firstPerm);
1058 Node mergedPerm = Node::createCollection();
1059 mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper, ctx));
1060 mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO));
1061 newRes.collection()->push_back(mergedPerm);
1064 res.collection()->push_front(newRes);
1066 DEBUG_PRINTLN(ALL, "RESULT: " << res)
1069 } else if (op1.combinator() == Complex_Selector::PARENT_OF && (op2.combinator() == Complex_Selector::PRECEDES || op2.combinator() == Complex_Selector::ADJACENT_TO)) {
1071 res.collection()->push_front(op2);
1072 res.collection()->push_front(sel2);
1074 seq1.collection()->push_back(sel1);
1075 seq1.collection()->push_back(op1);
1077 } else if (op2.combinator() == Complex_Selector::PARENT_OF && (op1.combinator() == Complex_Selector::PRECEDES || op1.combinator() == Complex_Selector::ADJACENT_TO)) {
1079 res.collection()->push_front(op1);
1080 res.collection()->push_front(sel1);
1082 seq2.collection()->push_back(sel2);
1083 seq2.collection()->push_back(op2);
1085 } else if (op1.combinator() == op2.combinator()) {
1087 DEBUG_PRINTLN(ALL, "sel1: " << sel1)
1088 DEBUG_PRINTLN(ALL, "sel2: " << sel2)
1090 Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result
1091 // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?)
1092 Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head(), ctx);
1093 pMergedWrapper->head(pMerged);
1095 DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: "))
1098 return Node::createNil();
1101 res.collection()->push_front(op1);
1102 res.collection()->push_front(Node::createSelector(pMergedWrapper, ctx));
1104 DEBUG_PRINTLN(ALL, "RESULT: " << res)
1107 return Node::createNil();
1110 return mergeFinalOps(seq1, seq2, ctx, res);
1112 } else if (!ops1.collection()->empty()) {
1114 Node op1 = ops1.collection()->front();
1116 if (op1.combinator() == Complex_Selector::PARENT_OF && !seq2.collection()->empty() && seq2.collection()->back().selector()->is_superselector_of(seq1.collection()->back().selector())) {
1117 seq2.collection()->pop_back();
1120 // TODO: consider unshift(NodeCollection, Node)
1121 res.collection()->push_front(op1);
1122 res.collection()->push_front(seq1.collection()->back());
1123 seq1.collection()->pop_back();
1125 return mergeFinalOps(seq1, seq2, ctx, res);
1127 } else { // !ops2.collection()->empty()
1129 Node op2 = ops2.collection()->front();
1131 if (op2.combinator() == Complex_Selector::PARENT_OF && !seq1.collection()->empty() && seq1.collection()->back().selector()->is_superselector_of(seq2.collection()->back().selector())) {
1132 seq1.collection()->pop_back();
1135 res.collection()->push_front(op2);
1136 res.collection()->push_front(seq2.collection()->back());
1137 seq2.collection()->pop_back();
1139 return mergeFinalOps(seq1, seq2, ctx, res);
1147 This is the equivalent of ruby's Sequence.subweave.
1149 Here is the original subweave code for reference during porting.
1151 def subweave(seq1, seq2)
1152 return [seq2] if seq1.empty?
1153 return [seq1] if seq2.empty?
1155 seq1, seq2 = seq1.dup, seq2.dup
1156 return unless init = merge_initial_ops(seq1, seq2)
1157 return unless fin = merge_final_ops(seq1, seq2)
1158 seq1 = group_selectors(seq1)
1159 seq2 = group_selectors(seq2)
1160 lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2|
1162 next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)
1163 next s2 if parent_superselector?(s1, s2)
1164 next s1 if parent_superselector?(s2, s1)
1169 diff << chunks(seq1, seq2) {|s| parent_superselector?(s.first, lcs.first)} << [lcs.shift]
1173 diff << chunks(seq1, seq2) {|s| s.empty?}
1174 diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]}
1175 diff.reject! {|c| c.empty?}
1177 result = Sass::Util.paths(diff).map {|p| p.flatten}.reject {|p| path_has_two_subjects?(p)}
1182 Node Extend::subweave(Node& one, Node& two, Context& ctx) {
1183 // Check for the simple cases
1184 if (one.collection()->size() == 0) {
1185 Node out = Node::createCollection();
1186 out.collection()->push_back(two);
1189 if (two.collection()->size() == 0) {
1190 Node out = Node::createCollection();
1191 out.collection()->push_back(one);
1197 Node seq1 = Node::createCollection();
1199 Node seq2 = Node::createCollection();
1202 DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE ONE: " << seq1)
1203 DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE TWO: " << seq2)
1205 Node init = mergeInitialOps(seq1, seq2, ctx);
1207 return Node::createNil();
1210 DEBUG_PRINTLN(SUBWEAVE, "INIT: " << init)
1212 Node res = Node::createCollection();
1213 Node fin = mergeFinalOps(seq1, seq2, ctx, res);
1215 return Node::createNil();
1218 DEBUG_PRINTLN(SUBWEAVE, "FIN: " << fin)
1221 // Moving this line up since fin isn't modified between now and when it happened before
1222 // fin.map {|sel| sel.is_a?(Array) ? sel : [sel]}
1224 for (NodeDeque::iterator finIter = fin.collection()->begin(), finEndIter = fin.collection()->end();
1225 finIter != finEndIter; ++finIter) {
1227 Node& childNode = *finIter;
1229 if (!childNode.isCollection()) {
1230 Node wrapper = Node::createCollection();
1231 wrapper.collection()->push_back(childNode);
1232 childNode = wrapper;
1237 DEBUG_PRINTLN(SUBWEAVE, "FIN MAPPED: " << fin)
1241 Node groupSeq1 = groupSelectors(seq1, ctx);
1242 DEBUG_PRINTLN(SUBWEAVE, "SEQ1: " << groupSeq1)
1244 Node groupSeq2 = groupSelectors(seq2, ctx);
1245 DEBUG_PRINTLN(SUBWEAVE, "SEQ2: " << groupSeq2)
1248 ComplexSelectorDeque groupSeq1Converted;
1249 nodeToComplexSelectorDeque(groupSeq1, groupSeq1Converted, ctx);
1251 ComplexSelectorDeque groupSeq2Converted;
1252 nodeToComplexSelectorDeque(groupSeq2, groupSeq2Converted, ctx);
1254 ComplexSelectorDeque out;
1255 LcsCollectionComparator collectionComparator(ctx);
1256 lcs(groupSeq2Converted, groupSeq1Converted, collectionComparator, ctx, out);
1257 Node seqLcs = complexSelectorDequeToNode(out, ctx);
1259 DEBUG_PRINTLN(SUBWEAVE, "SEQLCS: " << seqLcs)
1262 Node initWrapper = Node::createCollection();
1263 initWrapper.collection()->push_back(init);
1264 Node diff = Node::createCollection();
1265 diff.collection()->push_back(initWrapper);
1267 DEBUG_PRINTLN(SUBWEAVE, "DIFF INIT: " << diff)
1270 while (!seqLcs.collection()->empty()) {
1271 ParentSuperselectorChunker superselectorChunker(seqLcs, ctx);
1272 Node chunksResult = chunks(groupSeq1, groupSeq2, superselectorChunker);
1273 diff.collection()->push_back(chunksResult);
1275 Node lcsWrapper = Node::createCollection();
1276 lcsWrapper.collection()->push_back(seqLcs.collection()->front());
1277 seqLcs.collection()->pop_front();
1278 diff.collection()->push_back(lcsWrapper);
1280 if (groupSeq1.collection()->size()) groupSeq1.collection()->pop_front();
1281 if (groupSeq2.collection()->size()) groupSeq2.collection()->pop_front();
1284 DEBUG_PRINTLN(SUBWEAVE, "DIFF POST LCS: " << diff)
1287 DEBUG_PRINTLN(SUBWEAVE, "CHUNKS: ONE=" << groupSeq1 << " TWO=" << groupSeq2)
1290 SubweaveEmptyChunker emptyChunker;
1291 Node chunksResult = chunks(groupSeq1, groupSeq2, emptyChunker);
1292 diff.collection()->push_back(chunksResult);
1295 DEBUG_PRINTLN(SUBWEAVE, "DIFF POST CHUNKS: " << diff)
1298 diff.collection()->insert(diff.collection()->end(), fin.collection()->begin(), fin.collection()->end());
1300 DEBUG_PRINTLN(SUBWEAVE, "DIFF POST FIN MAPPED: " << diff)
1302 // JMA - filter out the empty nodes (use a new collection, since iterator erase() invalidates the old collection)
1303 Node diffFiltered = Node::createCollection();
1304 for (NodeDeque::iterator diffIter = diff.collection()->begin(), diffEndIter = diff.collection()->end();
1305 diffIter != diffEndIter; ++diffIter) {
1306 Node& node = *diffIter;
1307 if (node.collection() && !node.collection()->empty()) {
1308 diffFiltered.collection()->push_back(node);
1311 diff = diffFiltered;
1313 DEBUG_PRINTLN(SUBWEAVE, "DIFF POST REJECT: " << diff)
1316 Node pathsResult = paths(diff, ctx);
1318 DEBUG_PRINTLN(SUBWEAVE, "PATHS: " << pathsResult)
1321 // We're flattening in place
1322 for (NodeDeque::iterator pathsIter = pathsResult.collection()->begin(), pathsEndIter = pathsResult.collection()->end();
1323 pathsIter != pathsEndIter; ++pathsIter) {
1325 Node& child = *pathsIter;
1326 child = flatten(child, ctx);
1329 DEBUG_PRINTLN(SUBWEAVE, "FLATTENED: " << pathsResult)
1334 rejected = mapped.reject {|p| path_has_two_subjects?(p)}
1335 $stderr.puts "REJECTED: #{rejected}"
1343 // disabled to avoid clang warning [-Wunused-function]
1344 static Node subweaveNaive(const Node& one, const Node& two, Context& ctx) {
1345 Node out = Node::createCollection();
1347 // Check for the simple cases
1349 out.collection()->push_back(two.klone(ctx));
1350 } else if (two.isNil()) {
1351 out.collection()->push_back(one.klone(ctx));
1353 // Do the naive implementation. pOne = A B and pTwo = C D ...yields... A B C D and C D A B
1354 // See https://gist.github.com/nex3/7609394 for details.
1356 Node firstPerm = one.klone(ctx);
1357 Node twoCloned = two.klone(ctx);
1358 firstPerm.plus(twoCloned);
1359 out.collection()->push_back(firstPerm);
1361 Node secondPerm = two.klone(ctx);
1362 Node oneCloned = one.klone(ctx);
1363 secondPerm.plus(oneCloned );
1364 out.collection()->push_back(secondPerm);
1373 This is the equivalent of ruby's Sequence.weave.
1375 The following is the modified version of the ruby code that was more portable to C++. You
1376 should be able to drop it into ruby 3.2.19 and get the same results from ruby sass.
1379 # This function works by moving through the selector path left-to-right,
1380 # building all possible prefixes simultaneously. These prefixes are
1381 # `befores`, while the remaining parenthesized suffixes is `afters`.
1386 current = afters.shift.dup
1387 last_current = [current.pop]
1391 for before in befores do
1392 sub = subweave(before, current)
1398 tempResult.push(seqs + last_current)
1402 befores = tempResult
1415 current = afters.shift.dup
1417 last_current = [current.pop]
1422 for before in befores do
1423 sub = subweave(before, current)
1431 toPush = seqs + last_current
1433 tempResult.push(seqs + last_current)
1438 befores = tempResult
1445 static Node weave(Node& path, Context& ctx) {
1447 DEBUG_PRINTLN(WEAVE, "WEAVE: " << path)
1449 Node befores = Node::createCollection();
1450 befores.collection()->push_back(Node::createCollection());
1452 Node afters = Node::createCollection();
1455 while (!afters.collection()->empty()) {
1456 Node current = afters.collection()->front().klone(ctx);
1457 afters.collection()->pop_front();
1458 DEBUG_PRINTLN(WEAVE, "CURRENT: " << current)
1459 if (current.collection()->size() == 0) continue;
1461 Node last_current = Node::createCollection();
1462 last_current.collection()->push_back(current.collection()->back());
1463 current.collection()->pop_back();
1464 DEBUG_PRINTLN(WEAVE, "CURRENT POST POP: " << current)
1465 DEBUG_PRINTLN(WEAVE, "LAST CURRENT: " << last_current)
1467 Node tempResult = Node::createCollection();
1469 for (NodeDeque::iterator beforesIter = befores.collection()->begin(), beforesEndIter = befores.collection()->end(); beforesIter != beforesEndIter; beforesIter++) {
1470 Node& before = *beforesIter;
1472 Node sub = Extend::subweave(before, current, ctx);
1474 DEBUG_PRINTLN(WEAVE, "SUB: " << sub)
1477 return Node::createCollection();
1480 for (NodeDeque::iterator subIter = sub.collection()->begin(), subEndIter = sub.collection()->end(); subIter != subEndIter; subIter++) {
1481 Node& seqs = *subIter;
1483 Node toPush = Node::createCollection();
1485 toPush.plus(last_current);
1487 tempResult.collection()->push_back(toPush);
1492 befores = tempResult;
1501 // This forward declaration is needed since extendComplexSelector calls extendCompoundSelector, which may recursively
1502 // call extendComplexSelector again.
1503 static Node extendComplexSelector(
1504 Complex_Selector_Ptr pComplexSelector,
1506 Subset_Map& subset_map,
1507 std::set<Compound_Selector> seen, bool isReplace, bool isOriginal);
1512 This is the equivalent of ruby's SimpleSequence.do_extend.
1514 // TODO: I think I have some modified ruby code to put here. Check.
1518 - Previous TODO: Do we need to group the results by extender?
1519 - What does subject do in?: next unless unified = seq.members.last.unify(self_without_sel, subject?)
1520 - IMPROVEMENT: The search for uniqueness at the end is not ideal since it's has to loop over everything...
1521 - IMPROVEMENT: Check if the final search for uniqueness is doing anything that extendComplexSelector isn't already doing...
1523 template<typename KeyType>
1524 class GroupByToAFunctor {
1526 KeyType operator()(SubSetMapPair& extPair) const {
1527 Complex_Selector_Obj pSelector = extPair.first;
1531 static Node extendCompoundSelector(
1532 Compound_Selector_Ptr pSelector,
1534 Subset_Map& subset_map,
1535 std::set<Compound_Selector> seen, bool isReplace) {
1537 DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND: "))
1538 // TODO: Ruby has another loop here to skip certain members?
1540 Node extendedSelectors = Node::createCollection();
1541 // extendedSelectors.got_line_feed = true;
1543 SubSetMapPairs entries = subset_map.get_v(pSelector);
1545 GroupByToAFunctor<Complex_Selector_Obj> extPairKeyFunctor;
1546 SubSetMapResults arr;
1547 group_by_to_a(entries, extPairKeyFunctor, arr);
1549 SubSetMapLookups holder;
1552 for (SubSetMapResults::iterator groupedIter = arr.begin(), groupedIterEnd = arr.end(); groupedIter != groupedIterEnd; groupedIter++) {
1553 SubSetMapResult& groupedPair = *groupedIter;
1555 Complex_Selector_Obj seq = groupedPair.first;
1556 SubSetMapPairs& group = groupedPair.second;
1558 DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(seq, "SEQ: "))
1560 // changing this makes aua
1561 Compound_Selector_Obj pSels = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate());
1562 for (SubSetMapPairs::iterator groupIter = group.begin(), groupIterEnd = group.end(); groupIter != groupIterEnd; groupIter++) {
1563 SubSetMapPair& pair = *groupIter;
1564 Compound_Selector_Obj pCompound = pair.second;
1565 for (size_t index = 0; index < pCompound->length(); index++) {
1566 Simple_Selector_Obj pSimpleSelector = (*pCompound)[index];
1567 pSels->append(pSimpleSelector);
1568 pCompound->extended(true);
1572 DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSels, "SELS: "))
1574 Complex_Selector_Ptr pExtComplexSelector = seq; // The selector up to where the @extend is (ie, the thing to merge)
1576 // TODO: This can return a Compound_Selector with no elements. Should that just be returning NULL?
1577 // RUBY: self_without_sel = Sass::Util.array_minus(members, sels)
1578 Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(pSels, ctx);
1580 DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "MEMBERS: "))
1581 DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "SELF_WO_SEL: "))
1583 Compound_Selector_Obj pInnermostCompoundSelector = pExtComplexSelector->last()->head();
1585 if (!pInnermostCompoundSelector) {
1586 pInnermostCompoundSelector = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate());
1588 Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(pSelectorWithoutExtendSelectors, ctx);
1590 DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pInnermostCompoundSelector, "LHS: "))
1591 DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "RHS: "))
1592 DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pUnifiedSelector, "UNIFIED: "))
1594 // RUBY: next unless unified
1595 if (!pUnifiedSelector || pUnifiedSelector->length() == 0) {
1599 // TODO: implement the parent directive match (if necessary based on test failures)
1600 // next if group.map {|e, _| check_directives_match!(e, parent_directives)}.none?
1602 // TODO: This seems a little fishy to me. See if it causes any problems. From the ruby, we should be able to just
1603 // get rid of the last Compound_Selector and replace it with this one. I think the reason this code is more
1604 // complex is that Complex_Selector contains a combinator, but in ruby combinators have already been filtered
1605 // out and aren't operated on.
1606 Complex_Selector_Obj pNewSelector = SASS_MEMORY_CLONE(pExtComplexSelector); // ->first();
1608 Complex_Selector_Obj pNewInnerMost = SASS_MEMORY_NEW(Complex_Selector, pSelector->pstate(), Complex_Selector::ANCESTOR_OF, pUnifiedSelector, NULL);
1610 Complex_Selector::Combinator combinator = pNewSelector->clear_innermost();
1611 pNewSelector->set_innermost(pNewInnerMost, combinator);
1614 ComplexSelectorSet debugSet;
1615 debugSet = pNewSelector->sources();
1616 if (debugSet.size() > 0) {
1617 throw std::runtime_error("The new selector should start with no sources. Something needs to be cloned to fix this.");
1619 debugSet = pExtComplexSelector->sources();
1620 if (debugSet.size() > 0) {
1621 throw std::runtime_error("The extension selector from our subset map should not have sources. These will bleed to the new selector. Something needs to be cloned to fix this.");
1626 // if (pSelector && pSelector->has_line_feed()) pNewInnerMost->has_line_feed(true);
1627 // Set the sources on our new Complex_Selector to the sources of this simple sequence plus the thing we're extending.
1628 DEBUG_PRINTLN(EXTEND_COMPOUND, "SOURCES SETTING ON NEW SEQ: " << complexSelectorToNode(pNewSelector, ctx))
1630 DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, ctx, "SOURCES NEW SEQ BEGIN: "))
1632 // I actually want to create a copy here (performance!)
1633 ComplexSelectorSet newSourcesSet = pSelector->sources(); // XXX
1634 DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, ctx, "SOURCES THIS EXTEND: "))
1636 newSourcesSet.insert(pExtComplexSelector);
1637 DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, ctx, "SOURCES WITH NEW SOURCE: "))
1639 // RUBY: new_seq.add_sources!(sources + [seq])
1640 pNewSelector->addSources(newSourcesSet, ctx);
1642 DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet newSet = pNewSelector->sources(); printSourcesSet(newSet, ctx, "SOURCES ON NEW SELECTOR AFTER ADD: "))
1643 DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(pSelector->sources(), ctx, "SOURCES THIS EXTEND WHICH SHOULD BE SAME STILL: "))
1646 if (pSels->has_line_feed()) pNewSelector->has_line_feed(true);
1648 holder.push_back(std::make_pair(pSels, pNewSelector));
1652 for (SubSetMapLookups::iterator holderIter = holder.begin(), holderIterEnd = holder.end(); holderIter != holderIterEnd; holderIter++) {
1653 SubSetMapLookup& pair = *holderIter;
1655 Compound_Selector_Obj pSels = pair.first;
1656 Complex_Selector_Obj pNewSelector = pair.second;
1659 // RUBY??: next [] if seen.include?(sels)
1660 if (seen.find(*pSels) != seen.end()) {
1665 std::set<Compound_Selector> recurseSeen(seen);
1666 recurseSeen.insert(*pSels);
1669 DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector, ctx))
1670 Node recurseExtendedSelectors = extendComplexSelector(pNewSelector, ctx, subset_map, recurseSeen, isReplace, false); // !:isOriginal
1672 DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND RETURN: " << recurseExtendedSelectors)
1674 for (NodeDeque::iterator iterator = recurseExtendedSelectors.collection()->begin(), endIterator = recurseExtendedSelectors.collection()->end();
1675 iterator != endIterator; ++iterator) {
1676 Node newSelector = *iterator;
1678 // DEBUG_PRINTLN(EXTEND_COMPOUND, "EXTENDED AT THIS POINT: " << extendedSelectors)
1679 // DEBUG_PRINTLN(EXTEND_COMPOUND, "SELECTOR EXISTS ALREADY: " << newSelector << " " << extendedSelectors.contains(newSelector, false /*simpleSelectorOrderDependent*/));
1681 if (!extendedSelectors.contains(newSelector, false /*simpleSelectorOrderDependent*/)) {
1682 // DEBUG_PRINTLN(EXTEND_COMPOUND, "ADDING NEW SELECTOR")
1683 extendedSelectors.collection()->push_back(newSelector);
1688 DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND END: "))
1690 return extendedSelectors;
1694 static bool complexSelectorHasExtension(
1695 Complex_Selector_Ptr pComplexSelector,
1697 Subset_Map& subset_map,
1698 std::set<Compound_Selector>& seen) {
1700 bool hasExtension = false;
1702 Complex_Selector_Obj pIter = pComplexSelector;
1704 while (!hasExtension && pIter) {
1705 Compound_Selector_Obj pHead = pIter->head();
1708 SubSetMapPairs entries = subset_map.get_v(pHead);
1709 for (SubSetMapPair ext : entries) {
1710 // check if both selectors have the same media block parent
1711 // if (ext.first->media_block() == pComplexSelector->media_block()) continue;
1712 if (ext.second->media_block() == 0) continue;
1713 if (pHead->media_block() &&
1714 ext.second->media_block()->media_queries() &&
1715 pHead->media_block()->media_queries()
1717 std::string query_left(ext.second->media_block()->media_queries()->to_string(ctx.c_options));
1718 std::string query_right(pHead->media_block()->media_queries()->to_string(ctx.c_options));
1719 if (query_left == query_right) continue;
1722 // fail if one goes across media block boundaries
1723 std::stringstream err;
1724 std::string cwd(Sass::File::get_cwd());
1725 ParserState pstate(ext.second->pstate());
1726 std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd));
1727 err << "You may not @extend an outer selector from within @media.\n";
1728 err << "You may only @extend selectors within the same directive.\n";
1729 err << "From \"@extend " << ext.second->to_string(ctx.c_options) << "\"";
1730 err << " on line " << pstate.line+1 << " of " << rel_path << "\n";
1731 error(err.str(), pComplexSelector->pstate());
1733 if (entries.size() > 0) hasExtension = true;
1736 pIter = pIter->tail();
1739 return hasExtension;
1744 This is the equivalent of ruby's Sequence.do_extend.
1746 // TODO: I think I have some modified ruby code to put here. Check.
1750 - check to automatically include combinators doesn't transfer over to libsass' data model where
1751 the combinator and compound selector are one unit
1752 next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)
1754 static Node extendComplexSelector(
1755 Complex_Selector_Ptr pComplexSelector,
1757 Subset_Map& subset_map,
1758 std::set<Compound_Selector> seen, bool isReplace, bool isOriginal) {
1760 Node complexSelector = complexSelectorToNode(pComplexSelector, ctx);
1761 DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector)
1763 Node extendedNotExpanded = Node::createCollection();
1765 for (NodeDeque::iterator complexSelIter = complexSelector.collection()->begin(),
1766 complexSelIterEnd = complexSelector.collection()->end();
1767 complexSelIter != complexSelIterEnd; ++complexSelIter)
1770 Node& sseqOrOp = *complexSelIter;
1772 DEBUG_PRINTLN(EXTEND_COMPLEX, "LOOP: " << sseqOrOp)
1774 // If it's not a selector (meaning it's a combinator), just include it automatically
1775 // RUBY: next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)
1776 if (!sseqOrOp.isSelector()) {
1777 // Wrap our Combinator in two collections to match ruby. This is essentially making a collection Node
1778 // with one collection child. The collection child represents a Complex_Selector that is only a combinator.
1779 Node outer = Node::createCollection();
1780 Node inner = Node::createCollection();
1781 outer.collection()->push_back(inner);
1782 inner.collection()->push_back(sseqOrOp);
1783 extendedNotExpanded.collection()->push_back(outer);
1787 Compound_Selector_Obj pCompoundSelector = sseqOrOp.selector()->head();
1789 // RUBY: extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen)
1790 Node extended = extendCompoundSelector(pCompoundSelector, ctx, subset_map, seen, isReplace);
1791 if (sseqOrOp.got_line_feed) extended.got_line_feed = true;
1792 DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED: " << extended)
1795 // Prepend the Compound_Selector based on the choices logic; choices seems to be extend but with an ruby Array instead of a Sequence
1796 // due to the member mapping: choices = extended.map {|seq| seq.members}
1797 Complex_Selector_Obj pJustCurrentCompoundSelector = sseqOrOp.selector();
1799 // RUBY: extended.first.add_sources!([self]) if original && !has_placeholder?
1800 if (isOriginal && !pComplexSelector->has_placeholder()) {
1801 ComplexSelectorSet srcset;
1802 srcset.insert(pComplexSelector);
1803 pJustCurrentCompoundSelector->addSources(srcset, ctx);
1804 DEBUG_PRINTLN(EXTEND_COMPLEX, "ADD SOURCES: " << *pComplexSelector)
1807 bool isSuperselector = false;
1808 for (NodeDeque::iterator iterator = extended.collection()->begin(), endIterator = extended.collection()->end();
1809 iterator != endIterator; ++iterator) {
1810 Node& childNode = *iterator;
1811 Complex_Selector_Obj pExtensionSelector = nodeToComplexSelector(childNode, ctx);
1812 if (pExtensionSelector->is_superselector_of(pJustCurrentCompoundSelector)) {
1813 isSuperselector = true;
1818 if (!isSuperselector) {
1819 if (sseqOrOp.got_line_feed) pJustCurrentCompoundSelector->has_line_feed(sseqOrOp.got_line_feed);
1820 extended.collection()->push_front(complexSelectorToNode(pJustCurrentCompoundSelector, ctx));
1823 DEBUG_PRINTLN(EXTEND_COMPLEX, "CHOICES UNSHIFTED: " << extended)
1825 // Aggregate our current extensions
1826 extendedNotExpanded.collection()->push_back(extended);
1830 DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED NOT EXPANDED: " << extendedNotExpanded)
1834 // Ruby Equivalent: paths
1835 Node paths = Sass::paths(extendedNotExpanded, ctx);
1837 DEBUG_PRINTLN(EXTEND_COMPLEX, "PATHS: " << paths)
1841 // Ruby Equivalent: weave
1842 Node weaves = Node::createCollection();
1844 for (NodeDeque::iterator pathsIter = paths.collection()->begin(), pathsEndIter = paths.collection()->end(); pathsIter != pathsEndIter; ++pathsIter) {
1845 Node& path = *pathsIter;
1846 Node weaved = weave(path, ctx);
1847 weaved.got_line_feed = path.got_line_feed;
1848 weaves.collection()->push_back(weaved);
1851 DEBUG_PRINTLN(EXTEND_COMPLEX, "WEAVES: " << weaves)
1855 // Ruby Equivalent: trim
1856 Node trimmed = trim(weaves, ctx, isReplace);
1858 DEBUG_PRINTLN(EXTEND_COMPLEX, "TRIMMED: " << trimmed)
1861 // Ruby Equivalent: flatten
1862 Node extendedSelectors = flatten(trimmed, ctx, 1);
1864 DEBUG_PRINTLN(EXTEND_COMPLEX, ">>>>> EXTENDED: " << extendedSelectors)
1867 DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX END: " << complexSelector)
1870 return extendedSelectors;
1876 This is the equivalent of ruby's CommaSequence.do_extend.
1878 Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething) {
1879 std::set<Compound_Selector> seen;
1880 return extendSelectorList(pSelectorList, ctx, subset_map, isReplace, extendedSomething, seen);
1884 This is the equivalent of ruby's CommaSequence.do_extend.
1886 Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething, std::set<Compound_Selector>& seen) {
1888 Selector_List_Obj pNewSelectors = SASS_MEMORY_NEW(Selector_List, pSelectorList->pstate(), pSelectorList->length());
1890 extendedSomething = false;
1892 for (size_t index = 0, length = pSelectorList->length(); index < length; index++) {
1893 Complex_Selector_Obj pSelector = (*pSelectorList)[index];
1895 // ruby sass seems to keep a list of things that have extensions and then only extend those. We don't currently do that.
1896 // Since it's not that expensive to check if an extension exists in the subset map and since it can be relatively expensive to
1897 // run through the extend code (which does a data model transformation), check if there is anything to extend before doing
1898 // the extend. We might be able to optimize extendComplexSelector, but this approach keeps us closer to ruby sass (which helps
1900 if (!complexSelectorHasExtension(pSelector, ctx, subset_map, seen)) {
1901 pNewSelectors->append(pSelector);
1905 extendedSomething = true;
1907 Node extendedSelectors = extendComplexSelector(pSelector, ctx, subset_map, seen, isReplace, true);
1908 if (!pSelector->has_placeholder()) {
1909 if (!extendedSelectors.contains(complexSelectorToNode(pSelector, ctx), true /*simpleSelectorOrderDependent*/)) {
1910 pNewSelectors->append(pSelector);
1915 for (NodeDeque::iterator iterator = extendedSelectors.collection()->begin(), iteratorBegin = extendedSelectors.collection()->begin(), iteratorEnd = extendedSelectors.collection()->end(); iterator != iteratorEnd; ++iterator) {
1916 // When it is a replace, skip the first one, unless there is only one
1917 if(isReplace && iterator == iteratorBegin && extendedSelectors.collection()->size() > 1 ) continue;
1919 Node& childNode = *iterator;
1920 pNewSelectors->append(nodeToComplexSelector(childNode, ctx));
1924 Remove_Placeholders remove_placeholders;
1925 // it seems that we have to remove the place holders early here
1926 // normally we do this as the very last step (compare to ruby sass)
1927 pNewSelectors = remove_placeholders.remove_placeholders(pNewSelectors);
1929 // unwrap all wrapped selectors with inner lists
1930 for (Complex_Selector_Obj cur : pNewSelectors->elements()) {
1934 if (cur->head() && seen.find(*cur->head()) == seen.end()) {
1935 std::set<Compound_Selector> recseen(seen);
1936 recseen.insert(*cur->head());
1937 // create a copy since we add multiple items if stuff get unwrapped
1938 Compound_Selector_Obj cpy_head = SASS_MEMORY_NEW(Compound_Selector, cur->pstate());
1939 for (Simple_Selector_Obj hs : *cur->head()) {
1940 if (Wrapped_Selector_Obj ws = Cast<Wrapped_Selector>(hs)) {
1941 ws->selector(SASS_MEMORY_CLONE(ws->selector()));
1942 if (Selector_List_Obj sl = Cast<Selector_List>(ws->selector())) {
1943 // special case for ruby ass
1945 // this seems inconsistent but it is how ruby sass seems to remove parentheses
1946 cpy_head->append(SASS_MEMORY_NEW(Element_Selector, hs->pstate(), ws->name()));
1948 // has wrapped selectors
1950 // extend the inner list of wrapped selector
1951 Selector_List_Obj ext_sl = extendSelectorList(sl, ctx, subset_map, recseen);
1952 for (size_t i = 0; i < ext_sl->length(); i += 1) {
1953 if (Complex_Selector_Obj ext_cs = ext_sl->at(i)) {
1954 // create clones for wrapped selector and the inner list
1955 Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws);
1956 Selector_List_Obj cpy_ws_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate());
1957 // remove parent selectors from inner selector
1958 if (ext_cs->first() && ext_cs->first()->head()->length() > 0) {
1959 Wrapped_Selector_Ptr ext_ws = Cast<Wrapped_Selector>(ext_cs->first()->head()->first());
1960 if (ext_ws/* && ext_cs->length() == 1*/) {
1961 Selector_List_Obj ws_cs = Cast<Selector_List>(ext_ws->selector());
1962 Compound_Selector_Obj ws_ss = ws_cs->first()->head();
1964 Cast<Pseudo_Selector>(ws_ss->first()) ||
1965 Cast<Element_Selector>(ws_ss->first()) ||
1966 Cast<Placeholder_Selector>(ws_ss->first())
1969 cpy_ws_sl->append(ext_cs->first());
1971 // assign list to clone
1972 cpy_ws->selector(cpy_ws_sl);
1974 cpy_head->append(cpy_ws);
1979 cpy_head->append(hs);
1982 cpy_head->append(hs);
1986 cur->head(cpy_head);
1992 return pNewSelectors.detach();
1997 bool shouldExtendBlock(Block_Obj b) {
1999 // If a block is empty, there's no reason to extend it since any rules placed on this block
2000 // won't have any output. The main benefit of this is for structures like:
2008 // We end up visiting two rulesets (one with the selector .a and the other with the selector .a .b).
2009 // In this case, we don't want to try to pull rules onto .a since they won't get output anyway since
2010 // there are no child statements. However .a .b should have extensions applied.
2012 for (size_t i = 0, L = b->length(); i < L; ++i) {
2013 Statement_Obj stm = b->at(i);
2015 if (Cast<Ruleset>(stm)) {
2016 // Do nothing. This doesn't count as a statement that causes extension since we'll
2017 // iterate over this rule set in a future visit and try to extend it.
2029 // Extend a ruleset by extending the selectors and updating them on the ruleset. The block's rules don't need to change.
2030 template <typename ObjectType>
2031 static void extendObjectWithSelectorAndBlock(ObjectType* pObject, Context& ctx, Subset_Map& subset_map) {
2033 DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << Cast<Selector_List>(pObject->selector())->to_string(ctx.c_options))
2035 // Ruby sass seems to filter nodes that don't have any content well before we get here. I'm not sure the repercussions
2036 // of doing so, so for now, let's just not extend things that won't be output later.
2037 if (!shouldExtendBlock(pObject->block())) {
2038 DEBUG_PRINTLN(EXTEND_OBJECT, "RETURNING WITHOUT EXTEND ATTEMPT")
2042 bool extendedSomething = false;
2043 Selector_List_Obj pNewSelectorList = Extend::extendSelectorList(Cast<Selector_List>(pObject->selector()), ctx, subset_map, false, extendedSomething);
2045 if (extendedSomething && pNewSelectorList) {
2046 DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << Cast<Selector_List>(pObject->selector())->to_string(ctx.c_options))
2047 DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->to_string(ctx.c_options))
2048 pNewSelectorList->remove_parent_selectors();
2049 pObject->selector(pNewSelectorList);
2051 DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND DID NOT TRY TO EXTEND ANYTHING")
2057 Extend::Extend(Context& ctx, Subset_Map& ssm)
2058 : ctx(ctx), subset_map(ssm)
2061 void Extend::operator()(Block_Ptr b)
2063 for (size_t i = 0, L = b->length(); i < L; ++i) {
2064 Statement_Obj stm = b->at(i);
2067 // do final check if everything was extended
2068 // we set `extended` flag on extended selectors
2070 // debug_subset_map(subset_map);
2071 for(auto const &it : subset_map.values()) {
2072 Complex_Selector_Ptr sel = NULL;
2073 Compound_Selector_Ptr ext = NULL;
2074 if (it.first) sel = it.first->first();
2075 if (it.second) ext = it.second;
2076 if (ext && (ext->extended() || ext->is_optional())) continue;
2077 std::string str_sel(sel->to_string({ NESTED, 5 }));
2078 std::string str_ext(ext->to_string({ NESTED, 5 }));
2079 // debug_ast(sel, "sel: ");
2080 // debug_ast(ext, "ext: ");
2081 error("\"" + str_sel + "\" failed to @extend \"" + str_ext + "\".\n"
2082 "The selector \"" + str_ext + "\" was not found.\n"
2083 "Use \"@extend " + str_ext + " !optional\" if the"
2084 " extend should be able to fail.", ext->pstate());
2090 void Extend::operator()(Ruleset_Ptr pRuleset)
2092 extendObjectWithSelectorAndBlock( pRuleset, ctx, subset_map);
2093 pRuleset->block()->perform(this);
2096 void Extend::operator()(Supports_Block_Ptr pFeatureBlock)
2098 pFeatureBlock->block()->perform(this);
2101 void Extend::operator()(Media_Block_Ptr pMediaBlock)
2103 pMediaBlock->block()->perform(this);
2106 void Extend::operator()(Directive_Ptr a)
2108 // Selector_List_Ptr ls = Cast<Selector_List>(a->selector());
2109 // selector_stack.push_back(ls);
2110 if (a->block()) a->block()->perform(this);
2111 // exp.selector_stack.pop_back();