forked from sass/libsass
/
extend.cpp
1960 lines (1419 loc) · 67.9 KB
/
extend.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "extend.hpp"
#include "context.hpp"
#include "contextualize.hpp"
#include "to_string.hpp"
#include "backtrace.hpp"
#include "paths.hpp"
#include "parser.hpp"
#ifndef SASS_AST
#include "node.hpp"
#endif
#include "sass_util.hpp"
#include "debug.hpp"
#include <iostream>
#include <deque>
/*
NOTES:
- The print* functions print to cerr. This allows our testing frameworks (like sass-spec) to ignore the output, which
is very helpful when debugging. The format of the output is mainly to wrap things in square brackets to match what
ruby already outputs (to make comparisons easier).
- For the direct porting effort, we're trying to port method-for-method until we get all the tests passing.
Where applicable, I've tried to include the ruby code above the function for reference until all our tests pass.
The ruby code isn't always directly portable, so I've tried to include any modified ruby code that was actually
used for the porting.
- 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
output at the same stage. This makes it much easier to determine where problems are. Try to keep as close to
the ruby code as you can until we have all the sass-spec tests passing. Then, we should optimize. However, if you see
something that could probably be optimized, let's not forget it. Add a // TODO: or // IMPROVEMENT: comment.
- Coding conventions in this file (these may need to be changed before merging back into master)
- Very basic hungarian notation:
p prefix for pointers (pSelector)
no prefix for value types and references (selector)
- Use STL iterators where possible
- prefer verbose naming over terse naming
- use typedefs for STL container types for make maintenance easier
- You may see a lot of comments that say "// TODO: is this the correct combinator?". See the comment referring to combinators
in extendCompoundSelector for a more extensive explanation of my confusion. I think our divergence in data model from ruby
sass causes this to be necessary.
GLOBAL TODOS:
- wrap the contents of the print functions in DEBUG preprocesser conditionals so they will be optimized away in non-debug mode.
- consider making the extend* functions member functions to avoid passing around ctx and subsetMap map around. This has the
drawback that the implementation details of the operator are then exposed to the outside world, which is not ideal and
can cause additional compile time dependencies.
- mark the helper methods in this file static to given them compilation unit linkage.
- implement parent directive matching
- fix compilation warnings for unused Extend members if we really don't need those references anymore.
*/
namespace Sass {
typedef pair<Complex_Selector*, Compound_Selector*> ExtensionPair;
typedef vector<ExtensionPair> SubsetMapEntries;
#ifdef DEBUG
// TODO: move the ast specific ostream operators into ast.hpp/ast.cpp
ostream& operator<<(ostream& os, const Complex_Selector::Combinator combinator) {
switch (combinator) {
case Complex_Selector::ANCESTOR_OF: os << "\" \""; break;
case Complex_Selector::PARENT_OF: os << "\">\""; break;
case Complex_Selector::PRECEDES: os << "\"~\""; break;
case Complex_Selector::ADJACENT_TO: os << "\"+\""; break;
}
return os;
}
ostream& operator<<(ostream& os, Compound_Selector& compoundSelector) {
To_String to_string;
os << compoundSelector.perform(&to_string);
return os;
}
// Print a string representation of a Compound_Selector
static void printCompoundSelector(Compound_Selector* pCompoundSelector, const char* message=NULL, bool newline=true) {
To_String to_string;
if (message) {
cerr << message;
}
if (pCompoundSelector) {
cerr << *pCompoundSelector;
} else {
cerr << "NULL";
}
if (newline) {
cerr << endl;
}
}
ostream& operator<<(ostream& os, Complex_Selector& complexSelector) {
To_String to_string;
os << "[";
Complex_Selector* pIter = &complexSelector;
bool first = true;
while (pIter) {
if (pIter->combinator() != Complex_Selector::ANCESTOR_OF) {
if (!first) {
os << ", ";
}
first = false;
os << pIter->combinator();
}
if (!first) {
os << ", ";
}
first = false;
if (pIter->head()) {
os << pIter->head()->perform(&to_string);
} else {
os << "NULL_HEAD";
}
pIter = pIter->tail();
}
os << "]";
return os;
}
// Print a string representation of a Complex_Selector
static void printComplexSelector(Complex_Selector* pComplexSelector, const char* message=NULL, bool newline=true) {
To_String to_string;
if (message) {
cerr << message;
}
if (pComplexSelector) {
cerr << *pComplexSelector;
} else {
cerr << "NULL";
}
if (newline) {
cerr << endl;
}
}
// Print a string representation of a SourcesSet
static void printSourcesSet(SourcesSet& sources, Context& ctx, const char* message=NULL, bool newline=true) {
To_String to_string;
if (message) {
cerr << message;
}
// Convert to a deque of strings so we can sort since order doesn't matter in a set. This should cut down on
// the differences we see when debug printing.
typedef deque<string> SourceStrings;
SourceStrings sourceStrings;
for (SourcesSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) {
Complex_Selector* pSource = *iterator;
stringstream sstream;
sstream << complexSelectorToNode(pSource, ctx);
sourceStrings.push_back(sstream.str());
}
// Sort to get consistent output
std::sort(sourceStrings.begin(), sourceStrings.end());
cerr << "SourcesSet[";
for (SourceStrings::iterator iterator = sourceStrings.begin(), iteratorEnd = sourceStrings.end(); iterator != iteratorEnd; ++iterator) {
string source = *iterator;
if (iterator != sourceStrings.begin()) {
cerr << ", ";
}
cerr << source;
}
cerr << "]";
if (newline) {
cerr << endl;
}
}
ostream& operator<<(ostream& os, SubsetMapEntries& entries) {
os << "SUBSET_MAP_ENTRIES[";
for (SubsetMapEntries::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) {
Complex_Selector* pExtComplexSelector = iterator->first; // The selector up to where the @extend is (ie, the thing to merge)
Compound_Selector* pExtCompoundSelector = iterator->second; // The stuff after the @extend
if (iterator != entries.begin()) {
os << ", ";
}
os << "(";
if (pExtComplexSelector) {
cerr << *pExtComplexSelector;
} else {
cerr << "NULL";
}
os << " -> ";
if (pExtCompoundSelector) {
cerr << *pExtCompoundSelector;
} else {
cerr << "NULL";
}
os << ")";
}
os << "]";
return os;
}
#endif
static bool parentSuperselector(Complex_Selector* pOne, Complex_Selector* pTwo, Context& ctx) {
// TODO: figure out a better way to create a Complex_Selector from scratch
// TODO: There's got to be a better way. This got ugly quick...
Position noPosition;
Type_Selector fakeParent("", noPosition, "temp");
Compound_Selector fakeHead("", noPosition, 1 /*size*/);
fakeHead.elements().push_back(&fakeParent);
Complex_Selector fakeParentContainer("", noPosition, Complex_Selector::ANCESTOR_OF, &fakeHead /*head*/, NULL /*tail*/);
pOne->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);
pTwo->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);
bool isSuperselector = pOne->is_superselector_of(pTwo);
pOne->clear_innermost();
pTwo->clear_innermost();
return isSuperselector;
}
void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out, Context& ctx) {
for (NodeDeque::iterator iter = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) {
Node& child = *iter;
out.push_back(nodeToComplexSelector(child, ctx));
}
}
Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque, Context& ctx) {
Node result = Node::createCollection();
for (ComplexSelectorDeque::const_iterator iter = deque.begin(), iterEnd = deque.end(); iter != iterEnd; iter++) {
Complex_Selector* pChild = *iter;
result.collection()->push_back(complexSelectorToNode(pChild, ctx));
}
return result;
}
class LcsCollectionComparator {
public:
LcsCollectionComparator(Context& ctx) : mCtx(ctx) {}
Context& mCtx;
bool operator()(Complex_Selector* pOne, Complex_Selector* pTwo, Complex_Selector*& pOut) const {
/*
This code is based on the following block from ruby sass' subweave
do |s1, s2|
next s1 if s1 == s2
next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)
next s2 if parent_superselector?(s1, s2)
next s1 if parent_superselector?(s2, s1)
end
*/
if (selectors_equal(*pOne, *pTwo, true /*simpleSelectorOrderDependent*/)) {
pOut = pOne;
return true;
}
if (pOne->combinator() != Complex_Selector::ANCESTOR_OF || pTwo->combinator() != Complex_Selector::ANCESTOR_OF) {
return false;
}
if (parentSuperselector(pOne, pTwo, mCtx)) {
pOut = pTwo;
return true;
}
if (parentSuperselector(pTwo, pOne, mCtx)) {
pOut = pOne;
return true;
}
return false;
}
};
/*
This is the equivalent of ruby's Sass::Util.lcs_backtrace.
# Computes a single longest common subsequence for arrays x and y.
# Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS
*/
void lcs_backtrace(const LCSTable& c, ComplexSelectorDeque& x, ComplexSelectorDeque& y, int i, int j, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) {
//DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j)
// TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output
if (i == 0 || j == 0) {
DEBUG_PRINTLN(LCS, "RETURNING EMPTY")
return;
}
Complex_Selector* pCompareOut = NULL;
if (comparator(x[i], y[j], pCompareOut)) {
DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE")
lcs_backtrace(c, x, y, i - 1, j - 1, comparator, out);
out.push_back(pCompareOut);
return;
}
if (c[i][j - 1] > c[i - 1][j]) {
DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE")
lcs_backtrace(c, x, y, i, j - 1, comparator, out);
return;
}
DEBUG_PRINTLN(LCS, "FINAL RETURN")
lcs_backtrace(c, x, y, i - 1, j, comparator, out);
return;
}
/*
This is the equivalent of ruby's Sass::Util.lcs_table.
# Calculates the memoization table for the Least Common Subsequence algorithm.
# Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS
*/
void lcs_table(const ComplexSelectorDeque& x, const ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, LCSTable& out) {
//DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y)
// TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output
LCSTable c(x.size(), vector<int>(y.size()));
// These shouldn't be necessary since the vector will be initialized to 0 already.
// x.size.times {|i| c[i][0] = 0}
// y.size.times {|j| c[0][j] = 0}
for (size_t i = 1; i < x.size(); i++) {
for (size_t j = 1; j < y.size(); j++) {
Complex_Selector* pCompareOut = NULL;
if (comparator(x[i], y[j], pCompareOut)) {
c[i][j] = c[i - 1][j - 1] + 1;
} else {
c[i][j] = max(c[i][j - 1], c[i - 1][j]);
}
}
}
out = c;
}
/*
This is the equivalent of ruby's Sass::Util.lcs.
# Computes a single longest common subsequence for `x` and `y`.
# If there are more than one longest common subsequences,
# the one returned is that which starts first in `x`.
# @param x [NodeCollection]
# @param y [NodeCollection]
# @comparator An equality check between elements of `x` and `y`.
# @return [NodeCollection] The LCS
http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
*/
void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, Context& ctx, ComplexSelectorDeque& out) {
//DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y)
// TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output
x.push_front(NULL);
y.push_front(NULL);
LCSTable table;
lcs_table(x, y, comparator, table);
return lcs_backtrace(table, x, y, x.size() - 1, y.size() - 1, comparator, out);
}
/*
This is the equivalent of ruby's Sequence.trim.
The following is the modified version of the ruby code that was more portable to C++. You
should be able to drop it into ruby 3.2.19 and get the same results from ruby sass.
# Avoid truly horrific quadratic behavior. TODO: I think there
# may be a way to get perfect trimming without going quadratic.
return seqses if seqses.size > 100
# Keep the results in a separate array so we can be sure we aren't
# comparing against an already-trimmed selector. This ensures that two
# identical selectors don't mutually trim one another.
result = seqses.dup
# This is n^2 on the sequences, but only comparing between
# separate sequences should limit the quadratic behavior.
seqses.each_with_index do |seqs1, i|
tempResult = []
for seq1 in seqs1 do
max_spec = 0
for seq in _sources(seq1) do
max_spec = [max_spec, seq.specificity].max
end
isMoreSpecificOuter = false
for seqs2 in result do
if seqs1.equal?(seqs2) then
next
end
# Second Law of Extend: the specificity of a generated selector
# should never be less than the specificity of the extending
# selector.
#
# See https://github.com/nex3/sass/issues/324.
isMoreSpecificInner = false
for seq2 in seqs2 do
isMoreSpecificInner = _specificity(seq2) >= max_spec && _superselector?(seq2, seq1)
if isMoreSpecificInner then
break
end
end
if isMoreSpecificInner then
isMoreSpecificOuter = true
break
end
end
if !isMoreSpecificOuter then
tempResult.push(seq1)
end
end
result[i] = tempResult
end
result
*/
/*
- IMPROVEMENT: We could probably work directly in the output trimmed deque.
*/
static Node trim(Node& seqses, Context& ctx) {
// See the comments in the above ruby code before embarking on understanding this function.
// Avoid poor performance in extreme cases.
if (seqses.collection()->size() > 100) {
return seqses;
}
DEBUG_PRINTLN(TRIM, "TRIM: " << seqses)
Node result = Node::createCollection();
result.plus(seqses);
DEBUG_PRINTLN(TRIM, "RESULT INITIAL: " << result)
// Normally we use the standard STL iterators, but in this case, we need to access the result collection by index since we're
// iterating the input collection, computing a value, and then setting the result in the output collection. We have to keep track
// of the index manually.
int toTrimIndex = 0;
for (NodeDeque::iterator seqsesIter = seqses.collection()->begin(), seqsesIterEnd = seqses.collection()->end(); seqsesIter != seqsesIterEnd; ++seqsesIter) {
Node& seqs1 = *seqsesIter;
DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1 << " " << toTrimIndex)
Node tempResult = Node::createCollection();
for (NodeDeque::iterator seqs1Iter = seqs1.collection()->begin(), seqs1EndIter = seqs1.collection()->end(); seqs1Iter != seqs1EndIter; ++seqs1Iter) {
Node& seq1 = *seqs1Iter;
Complex_Selector* pSeq1 = nodeToComplexSelector(seq1, ctx);
// Compute the maximum specificity. This requires looking at the "sources" of the sequence. See SimpleSequence.sources in the ruby code
// for a good description of sources.
//
// 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.
// While the test passes, I compared the state of each trim call to verify correctness. The last trim call had incorrect sources. We
// 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
// 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
// a guess though.
int maxSpecificity = 0;
SourcesSet sources = pSeq1->sources();
DEBUG_PRINTLN(TRIM, "TRIMASDF SEQ1: " << seq1)
DEBUG_EXEC(TRIM, printSourcesSet(sources, ctx, "TRIMASDF SOURCES: "))
for (SourcesSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) {
const Complex_Selector* const pCurrentSelector = *sourcesSetIterator;
maxSpecificity = max(maxSpecificity, pCurrentSelector->specificity());
}
DEBUG_PRINTLN(TRIM, "MAX SPECIFICITY: " << maxSpecificity)
bool isMoreSpecificOuter = false;
int resultIndex = 0;
for (NodeDeque::iterator resultIter = result.collection()->begin(), resultIterEnd = result.collection()->end(); resultIter != resultIterEnd; ++resultIter) {
Node& seqs2 = *resultIter;
DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1)
DEBUG_PRINTLN(TRIM, "SEQS2: " << seqs2)
// Do not compare the same sequence to itself. The ruby call we're trying to
// emulate is: seqs1.equal?(seqs2). equal? is an object comparison, not an equivalency comparision.
// Since we have the same pointers in seqes and results, we can do a pointer comparision. seqs1 is
// derived from seqses and seqs2 is derived from result.
if (seqs1.collection() == seqs2.collection()) {
DEBUG_PRINTLN(TRIM, "CONTINUE")
continue;
}
bool isMoreSpecificInner = false;
for (NodeDeque::iterator seqs2Iter = seqs2.collection()->begin(), seqs2IterEnd = seqs2.collection()->end(); seqs2Iter != seqs2IterEnd; ++seqs2Iter) {
Node& seq2 = *seqs2Iter;
Complex_Selector* pSeq2 = nodeToComplexSelector(seq2, ctx);
DEBUG_PRINTLN(TRIM, "SEQ2 SPEC: " << pSeq2->specificity())
DEBUG_PRINTLN(TRIM, "IS SPEC: " << pSeq2->specificity() << " >= " << maxSpecificity << " " << (pSeq2->specificity() >= maxSpecificity ? "true" : "false"))
DEBUG_PRINTLN(TRIM, "IS SUPER: " << (pSeq2->is_superselector_of(pSeq1) ? "true" : "false"))
isMoreSpecificInner = pSeq2->specificity() >= maxSpecificity && pSeq2->is_superselector_of(pSeq1);
if (isMoreSpecificInner) {
DEBUG_PRINTLN(TRIM, "FOUND MORE SPECIFIC")
break;
}
}
// If we found something more specific, we're done. Let the outer loop know and stop iterating.
if (isMoreSpecificInner) {
isMoreSpecificOuter = true;
break;
}
resultIndex++;
}
if (!isMoreSpecificOuter) {
DEBUG_PRINTLN(TRIM, "PUSHING: " << seq1)
tempResult.collection()->push_back(seq1);
}
}
DEBUG_PRINTLN(TRIM, "RESULT BEFORE ASSIGN: " << result)
DEBUG_PRINTLN(TRIM, "TEMP RESULT: " << toTrimIndex << " " << tempResult)
(*result.collection())[toTrimIndex] = tempResult;
toTrimIndex++;
DEBUG_PRINTLN(TRIM, "RESULT: " << result)
}
return result;
}
static bool parentSuperselector(const Node& one, const Node& two, Context& ctx) {
// TODO: figure out a better way to create a Complex_Selector from scratch
// TODO: There's got to be a better way. This got ugly quick...
Position noPosition;
Type_Selector fakeParent("", noPosition, "temp");
Compound_Selector fakeHead("", noPosition, 1 /*size*/);
fakeHead.elements().push_back(&fakeParent);
Complex_Selector fakeParentContainer("", noPosition, Complex_Selector::ANCESTOR_OF, &fakeHead /*head*/, NULL /*tail*/);
Complex_Selector* pOneWithFakeParent = nodeToComplexSelector(one, ctx);
pOneWithFakeParent->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);
Complex_Selector* pTwoWithFakeParent = nodeToComplexSelector(two, ctx);
pTwoWithFakeParent->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);
return pOneWithFakeParent->is_superselector_of(pTwoWithFakeParent);
}
class ParentSuperselectorChunker {
public:
ParentSuperselectorChunker(Node& lcs, Context& ctx) : mLcs(lcs), mCtx(ctx) {}
Node& mLcs;
Context& mCtx;
bool operator()(const Node& seq) const {
// {|s| parent_superselector?(s.first, lcs.first)}
return parentSuperselector(seq.collection()->front(), mLcs.collection()->front(), mCtx);
}
};
class SubweaveEmptyChunker {
public:
bool operator()(const Node& seq) const {
// {|s| s.empty?}
return seq.collection()->empty();
}
};
/*
# Takes initial subsequences of `seq1` and `seq2` and returns all
# orderings of those subsequences. The initial subsequences are determined
# by a block.
#
# Destructively removes the initial subsequences of `seq1` and `seq2`.
#
# For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|`
# denoting the boundary of the initial subsequence), this would return
# `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and
# `(3 4 5)`.
#
# @param seq1 [Array]
# @param seq2 [Array]
# @yield [a] Used to determine when to cut off the initial subsequences.
# Called repeatedly for each sequence until it returns true.
# @yieldparam a [Array] A final subsequence of one input sequence after
# cutting off some initial subsequence.
# @yieldreturn [Boolean] Whether or not to cut off the initial subsequence
# here.
# @return [Array<Array>] All possible orderings of the initial subsequences.
def chunks(seq1, seq2)
chunk1 = []
chunk1 << seq1.shift until yield seq1
chunk2 = []
chunk2 << seq2.shift until yield seq2
return [] if chunk1.empty? && chunk2.empty?
return [chunk2] if chunk1.empty?
return [chunk1] if chunk2.empty?
[chunk1 + chunk2, chunk2 + chunk1]
end
*/
template<typename ChunkerType>
static Node chunks(Node& seq1, Node& seq2, const ChunkerType& chunker) {
Node chunk1 = Node::createCollection();
while (!chunker(seq1)) {
chunk1.collection()->push_back(seq1.collection()->front());
seq1.collection()->pop_front();
}
Node chunk2 = Node::createCollection();
while (!chunker(seq2)) {
chunk2.collection()->push_back(seq2.collection()->front());
seq2.collection()->pop_front();
}
if (chunk1.collection()->empty() && chunk2.collection()->empty()) {
DEBUG_PRINTLN(CHUNKS, "RETURNING BOTH EMPTY")
return Node::createCollection();
}
if (chunk1.collection()->empty()) {
Node chunk2Wrapper = Node::createCollection();
chunk2Wrapper.collection()->push_back(chunk2);
DEBUG_PRINTLN(CHUNKS, "RETURNING ONE EMPTY")
return chunk2Wrapper;
}
if (chunk2.collection()->empty()) {
Node chunk1Wrapper = Node::createCollection();
chunk1Wrapper.collection()->push_back(chunk1);
DEBUG_PRINTLN(CHUNKS, "RETURNING TWO EMPTY")
return chunk1Wrapper;
}
Node perms = Node::createCollection();
Node firstPermutation = Node::createCollection();
firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end());
firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end());
perms.collection()->push_back(firstPermutation);
Node secondPermutation = Node::createCollection();
secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end());
secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end());
perms.collection()->push_back(secondPermutation);
DEBUG_PRINTLN(CHUNKS, "RETURNING PERM")
return perms;
}
static Node groupSelectors(Node& seq, Context& ctx) {
Node newSeq = Node::createCollection();
Node tail = Node::createCollection();
tail.plus(seq);
while (!tail.collection()->empty()) {
Node head = Node::createCollection();
do {
head.collection()->push_back(tail.collection()->front());
tail.collection()->pop_front();
} while (!tail.collection()->empty() && (head.collection()->back().isCombinator() || tail.collection()->front().isCombinator()));
newSeq.collection()->push_back(head);
}
return newSeq;
}
static void getAndRemoveInitialOps(Node& seq, Node& ops) {
NodeDeque& seqCollection = *(seq.collection());
NodeDeque& opsCollection = *(ops.collection());
while (seqCollection.size() > 0 && seqCollection.front().isCombinator()) {
opsCollection.push_back(seqCollection.front());
seqCollection.pop_front();
}
}
static void getAndRemoveFinalOps(Node& seq, Node& ops) {
NodeDeque& seqCollection = *(seq.collection());
NodeDeque& opsCollection = *(ops.collection());
while (seqCollection.size() > 0 && seqCollection.back().isCombinator()) {
opsCollection.push_back(seqCollection.back()); // Purposefully reversed to match ruby code
seqCollection.pop_back();
}
}
/*
def merge_initial_ops(seq1, seq2)
ops1, ops2 = [], []
ops1 << seq1.shift while seq1.first.is_a?(String)
ops2 << seq2.shift while seq2.first.is_a?(String)
newline = false
newline ||= !!ops1.shift if ops1.first == "\n"
newline ||= !!ops2.shift if ops2.first == "\n"
# If neither sequence is a subsequence of the other, they cannot be
# merged successfully
lcs = Sass::Util.lcs(ops1, ops2)
return unless lcs == ops1 || lcs == ops2
return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)
end
*/
static Node mergeInitialOps(Node& seq1, Node& seq2, Context& ctx) {
Node ops1 = Node::createCollection();
Node ops2 = Node::createCollection();
getAndRemoveInitialOps(seq1, ops1);
getAndRemoveInitialOps(seq2, ops2);
// TODO: Do we have this information available to us?
// newline = false
// newline ||= !!ops1.shift if ops1.first == "\n"
// newline ||= !!ops2.shift if ops2.first == "\n"
// If neither sequence is a subsequence of the other, they cannot be merged successfully
DefaultLcsComparator lcsDefaultComparator;
Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx);
if (!(opsLcs == ops1 || opsLcs == ops2)) {
return Node::createNil();
}
// TODO: more newline logic
// return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)
return (ops1.collection()->size() > ops2.collection()->size() ? ops1 : ops2);
}
/*
def merge_final_ops(seq1, seq2, res = [])
# This code looks complicated, but it's actually just a bunch of special
# cases for interactions between different combinators.
op1, op2 = ops1.first, ops2.first
if op1 && op2
sel1 = seq1.pop
sel2 = seq2.pop
if op1 == '~' && op2 == '~'
if sel1.superselector?(sel2)
res.unshift sel2, '~'
elsif sel2.superselector?(sel1)
res.unshift sel1, '~'
else
merged = sel1.unify(sel2.members, sel2.subject?)
res.unshift [
[sel1, '~', sel2, '~'],
[sel2, '~', sel1, '~'],
([merged, '~'] if merged)
].compact
end
elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~')
if op1 == '~'
tilde_sel, plus_sel = sel1, sel2
else
tilde_sel, plus_sel = sel2, sel1
end
if tilde_sel.superselector?(plus_sel)
res.unshift plus_sel, '+'
else
merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?)
res.unshift [
[tilde_sel, '~', plus_sel, '+'],
([merged, '+'] if merged)
].compact
end
elsif op1 == '>' && %w[~ +].include?(op2)
res.unshift sel2, op2
seq1.push sel1, op1
elsif op2 == '>' && %w[~ +].include?(op1)
res.unshift sel1, op1
seq2.push sel2, op2
elsif op1 == op2
return unless merged = sel1.unify(sel2.members, sel2.subject?)
res.unshift merged, op1
else
# Unknown selector combinators can't be unified
return
end
return merge_final_ops(seq1, seq2, res)
elsif op1
seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last)
res.unshift seq1.pop, op1
return merge_final_ops(seq1, seq2, res)
else # op2
seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last)
res.unshift seq2.pop, op2
return merge_final_ops(seq1, seq2, res)
end
end
*/
static Node mergeFinalOps(Node& seq1, Node& seq2, Context& ctx, Node& res) {
Node ops1 = Node::createCollection();
Node ops2 = Node::createCollection();
getAndRemoveFinalOps(seq1, ops1);
getAndRemoveFinalOps(seq2, ops2);
// TODO: do we have newlines to remove?
// ops1.reject! {|o| o == "\n"}
// ops2.reject! {|o| o == "\n"}
if (ops1.collection()->empty() && ops2.collection()->empty()) {
return res;
}
if (ops1.collection()->size() > 1 || ops2.collection()->size() > 1) {
DefaultLcsComparator lcsDefaultComparator;
Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx);
// If there are multiple operators, something hacky's going on. If one is a supersequence of the other, use that, otherwise give up.
if (!(opsLcs == ops1 || opsLcs == ops2)) {
return Node::createNil();
}
if (ops1.collection()->size() > ops2.collection()->size()) {
res.collection()->insert(res.collection()->begin(), ops1.collection()->rbegin(), ops1.collection()->rend());
} else {
res.collection()->insert(res.collection()->begin(), ops2.collection()->rbegin(), ops2.collection()->rend());
}
return res;
}
if (!ops1.collection()->empty() && !ops2.collection()->empty()) {
Node op1 = ops1.collection()->front();
Node op2 = ops2.collection()->front();
Node sel1 = seq1.collection()->back();
seq1.collection()->pop_back();
Node sel2 = seq2.collection()->back();
seq2.collection()->pop_back();
if (op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::PRECEDES) {
if (sel1.selector()->is_superselector_of(sel2.selector())) {
res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/);
res.collection()->push_front(sel2);
} else if (sel2.selector()->is_superselector_of(sel1.selector())) {
res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/);
res.collection()->push_front(sel1);
} else {
DEBUG_PRINTLN(ALL, "sel1: " << sel1)
DEBUG_PRINTLN(ALL, "sel2: " << sel2)
Complex_Selector* pMergedWrapper = sel1.selector()->clone(ctx); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result
// TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?)
Compound_Selector* pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head(), ctx);
pMergedWrapper->head(pMerged);
DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: "))
Node newRes = Node::createCollection();
Node firstPerm = Node::createCollection();
firstPerm.collection()->push_back(sel1);
firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));
firstPerm.collection()->push_back(sel2);
firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));
newRes.collection()->push_back(firstPerm);
Node secondPerm = Node::createCollection();
secondPerm.collection()->push_back(sel2);
secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));
secondPerm.collection()->push_back(sel1);
secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));
newRes.collection()->push_back(secondPerm);
if (pMerged) {
Node mergedPerm = Node::createCollection();
mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper, ctx));
mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));
newRes.collection()->push_back(mergedPerm);
}
res.collection()->push_front(newRes);
DEBUG_PRINTLN(ALL, "RESULT: " << res)
}
} else if (((op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::ADJACENT_TO)) || ((op1.combinator() == Complex_Selector::ADJACENT_TO && op2.combinator() == Complex_Selector::PRECEDES))) {
Node tildeSel = sel1;
Node tildeOp = op1;
Node plusSel = sel2;
Node plusOp = op2;
if (op1.combinator() != Complex_Selector::PRECEDES) {
tildeSel = sel2;
tildeOp = op2;
plusSel = sel1;
plusOp = op1;
}
if (tildeSel.selector()->is_superselector_of(plusSel.selector())) {
res.collection()->push_front(plusOp);
res.collection()->push_front(plusSel);
} else {