1 /*
2  * Copyright (c) 2012-2019 The ANTLR Project. All rights reserved.
3  * Use of this file is governed by the BSD 3-clause license that
4  * can be found in the LICENSE.txt file in the project root.
5  */
6 
7 module antlr.v4.runtime.misc.IntervalSet;
8 
9 import antlr.v4.runtime.TokenConstantDefinition;
10 import antlr.v4.runtime.Vocabulary;
11 import antlr.v4.runtime.misc;
12 import std.algorithm;
13 import std.array;
14 import std.container.rbtree;
15 import std.conv;
16 import std.stdio;
17 
18 /**
19  * This class implements the {@link IntSet} backed by a sorted array of
20  * non-overlapping intervals. It is particularly efficient for representing
21  * large collections of numbers, where the majority of elements appear as part
22  * of a sequential range of numbers that are all part of the set. For example,
23  * the set { 1, 2, 3, 4, 7, 8 } may be represented as { [1, 4], [7, 8] }.
24  *
25  * <p>
26  * This class is able to represent sets containing any combination of values in
27  * the range {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}
28  * (inclusive).</p>
29  */
30 class IntervalSet : IntSet
31 {
32 
33     public static IntervalSet COMPLETE_CHAR_SET;
34 
35     public static IntervalSet EMPTY_SET;
36 
37     private bool readonly;
38 
39     /**
40      * The list of sorted, disjoint intervals.
41      */
42     private Interval[] intervals_;
43 
44     public this()
45     {
46         COMPLETE_CHAR_SET = IntervalSet.of(char.min, char.min);
47         COMPLETE_CHAR_SET.setReadonly(true);
48         EMPTY_SET = new IntervalSet(1);
49         EMPTY_SET.clear;
50         EMPTY_SET.setReadonly(true);
51     }
52 
53     public this(Interval[] intervals)
54     {
55         this.intervals_ = intervals;
56     }
57 
58     public this(IntervalSet set)
59     {
60         this();
61         addAll(set);
62     }
63 
64     public this(int[] els ...)
65     {
66         foreach (int e; els)
67             add(e);
68     }
69 
70     /**
71      * Create a set with a single element, el.
72      */
73     public static IntervalSet of(int a)
74     {
75         IntervalSet s = new IntervalSet();
76         s.add(a);
77         return s;
78     }
79 
80     /**
81      * Create a set with all ints within range [a..b] (inclusive)
82      */
83     public static IntervalSet of(int a, int b)
84     {
85         IntervalSet s = new IntervalSet(1);
86         s.clear;
87         s.add(a, b);
88         return s;
89     }
90 
91     public void clear()
92     {
93         assert(!readonly, "can't alter readonly IntervalSet");
94         intervals_.length = 0;
95     }
96 
97     /**
98      * Add a single element to the set.  An isolated element is stored
99      * as a range el..el.
100      * @uml
101      * @override
102      */
103     public override void add(int el)
104     {
105         assert(!readonly, "can't alter readonly IntervalSet");
106         add(el, el);
107     }
108 
109     /**
110      * Add interval; i.e., add all integers from a to b to set.
111      * If b&lt;a, do nothing.
112      * Keep list in sorted order (by left range value).
113      * If overlap, combine ranges.  For example,
114      * If this is {1..5, 10..20}, adding 6..7 yields
115      * {1..5, 6..7, 10..20}.  Adding 4..8 yields {1..8, 10..20}.
116      */
117     public void add(int a, int b)
118     {
119         assert(!readonly, "can't alter readonly IntervalSet");
120         add(Interval.of(a ,b));
121     }
122 
123     /**
124      * copy on write so we can cache a..a intervals and sets of that
125      */
126     protected void add(Interval addition)
127     {
128         assert(!readonly, "can't alter readonly IntervalSet");
129         debug (IntervalSet)
130             writefln("add %1$s to %2$s", addition, intervals_);
131         if (addition.b < addition.a) {
132             return;
133         }
134         // find position in list
135         // Use iterators as we modify list in place
136         foreach (index, ref el; intervals_) {
137             Interval r = el;
138             if (addition.equals(r)) {
139                 return;
140             }
141             if (addition.adjacent(r) || !addition.disjoint(r)) {
142                 // next to each other, make a single larger interval
143                 Interval bigger = addition.unionInterval(r);
144                 el = bigger;
145                 // make sure we didn't just create an interval that
146                 // should be merged with next interval in list
147                 while (++index < intervals_.length) {
148                     Interval next = intervals_[index];
149                     if (!bigger.adjacent(next) && bigger.disjoint(next)) {
150                         continue;
151                     }
152                     // if we bump up against or overlap next, merge
153                     intervals_ = intervals_.remove(index);
154                     // move backwards to what we just set
155                     intervals_[--index] = bigger.unionInterval(next); // set to 3 merged ones
156                 }
157                 return;
158             }
159 
160             if (addition.startsBeforeDisjoint(r)) {
161                 // insert before r
162                 intervals_ = intervals_[0..index] ~ addition ~
163                     intervals_[index..$];
164                 return;
165             }
166             // if disjoint and after r, a future iteration will handle it
167         }
168         // ok, must be after last interval (and disjoint from last interval)
169         // just add it
170         intervals_ ~= addition;
171     }
172 
173     public IntervalSet addAll(IntSet set)
174     {
175         if (set is null) {
176             return this;
177         }
178         if (typeid(typeof(set)) == typeid(IntervalSet*)) {
179             IntervalSet other = cast(IntervalSet)set;
180             // walk set and add each interval
181             auto n = other.intervals_.length;
182             for (auto i = 0; i < n; i++) {
183                 Interval I = other.intervals_[i];
184                 this.add(I.a,I.b);
185             }
186         }
187         else {
188             foreach (int value; set.toList) {
189                 add(value);
190             }
191         }
192         return this;
193     }
194 
195     public IntervalSet complement(int minElement, int maxElement)
196     {
197         return this.complement(IntervalSet.of(minElement, maxElement));
198     }
199 
200     /**
201      * {@inheritDoc}
202      */
203     public IntervalSet complement(IntSet vocabulary)
204     {
205         if (vocabulary is null || vocabulary.isNil) {
206             return null; // nothing in common with null set
207         }
208         IntervalSet vocabularyIS = new IntervalSet();
209         vocabularyIS.addAll(vocabulary);
210         return vocabularyIS.subtract(this);
211     }
212 
213     public IntervalSet complement(IntervalSet vocabulary)
214     {
215         if (vocabulary is null || vocabulary.isNil) {
216             return null; // nothing in common with null set
217         }
218         IntervalSet vocabularyIS = vocabulary;
219         return vocabularyIS.subtract(this);
220     }
221 
222     public IntervalSet subtract(IntSet a)
223     {
224         if (!a) {
225             return new IntervalSet(this);
226         }
227         if (cast(IntervalSet)a) {
228             return subtract(this, cast(IntervalSet)a);
229         }
230 
231         IntervalSet other = new IntervalSet;
232         other.addAll(a);
233         return subtract(this, other);
234     }
235 
236     public IntervalSet or(IntSet a)
237     {
238         IntervalSet o = new IntervalSet();
239         o.addAll(this);
240         o.addAll(a);
241         return o;
242     }
243 
244     public IntervalSet and(IntSet other)
245     {
246         if (other is null ) { //|| !(other instanceof IntervalSet) ) {
247             return null; // nothing in common with null set
248         }
249 
250         auto myIntervals = this.intervals_;
251         auto theirIntervals = (cast(IntervalSet)other).intervals_;
252         IntervalSet intersection;
253         auto mySize = myIntervals.length;
254         auto theirSize = theirIntervals.length;
255         int i = 0;
256         int j = 0;
257         // iterate down both interval lists looking for nondisjoint intervals
258         while (i < mySize && j < theirSize) {
259             Interval mine = myIntervals[i];
260             Interval theirs = theirIntervals[j];
261             //System.out.println("mine="+mine+" and theirs="+theirs);
262             if (mine.startsBeforeDisjoint(theirs) ) {
263                 // move this iterator looking for interval that might overlap
264                 i++;
265             }
266             else if ( theirs.startsBeforeDisjoint(mine) ) {
267                 // move other iterator looking for interval that might overlap
268                 j++;
269             }
270             else if ( mine.properlyContains(theirs) ) {
271                 // overlap, add intersection, get next theirs
272                 if (intersection is null) {
273                     intersection = new IntervalSet();
274                 }
275                 intersection.add(mine.intersection(theirs));
276                 j++;
277             }
278             else if (theirs.properlyContains(mine)) {
279                 // overlap, add intersection, get next mine
280                 if (intersection is null) {
281                     intersection = new IntervalSet();
282                 }
283                 intersection.add(mine.intersection(theirs));
284                 i++;
285             }
286             else if ( !mine.disjoint(theirs) ) {
287                 // overlap, add intersection
288                 if (intersection is null) {
289                     intersection = new IntervalSet();
290                 }
291                 intersection.add(mine.intersection(theirs));
292                 // Move the iterator of lower range [a..b], but not
293                 // the upper range as it may contain elements that will collide
294                 // with the next iterator. So, if mine=[0..115] and
295                 // theirs=[115..200], then intersection is 115 and move mine
296                 // but not theirs as theirs may collide with the next range
297                 // in thisIter.
298                 // move both iterators to next ranges
299                 if ( mine.startsAfterNonDisjoint(theirs) ) {
300                     j++;
301                 }
302                 else if ( theirs.startsAfterNonDisjoint(mine) ) {
303                     i++;
304                 }
305             }
306         }
307         if (intersection is null) {
308             return new IntervalSet();
309         }
310         return intersection;
311     }
312 
313     public bool contains(int el)
314     {
315         foreach (I; intervals_) {
316             int a = I.a;
317             int b = I.b;
318             if (el < a) {
319                 break; // list is sorted and el is before this interval; not here
320             }
321             if (el >= a && el <= b) {
322                 return true; // found in this interval
323             }
324         }
325         return false;
326     }
327 
328     public bool isNil()
329     {
330         return intervals_ is null || intervals_.length == 0;
331     }
332 
333     public int getSingleElement()
334     {
335         if (intervals_ !is null && intervals_.length == 1 ) {
336             Interval I = intervals_[0];
337             if (I.a == I.b) {
338                 return I.a;
339             }
340         }
341         return TokenConstantDefinition.INVALID_TYPE;
342     }
343 
344     /**
345      * Returns the maximum value contained in the set.
346      *
347      *  @return the maximum value contained in the set. If the set is empty, this
348      *  method returns {@link Token#INVALID_TYPE}.
349      */
350     public int getMaxElement()
351     {
352         if (isNil) {
353             return TokenConstantDefinition.INVALID_TYPE;
354         }
355         Interval last = intervals_[$-1];
356         return last.b;
357     }
358 
359     /**
360      * Returns the minimum value contained in the set.
361      *
362      *  @return the minimum value contained in the set. If the set is empty, this
363      *  method returns {@link Token#INVALID_TYPE}.
364      */
365     public int getMinElement()
366     {
367         if (isNil) {
368             return TokenConstantDefinition.INVALID_TYPE;
369         }
370         return intervals_[0].a;
371     }
372 
373     /**
374      * combine all sets in the array returned the or'd value
375      */
376     public static IntervalSet or(IntervalSet[] sets)
377     {
378         IntervalSet r = new IntervalSet(1);
379         r.clear;
380         foreach (IntervalSet s; sets) {
381             r.addAll(s);
382         }
383         return r;
384     }
385 
386     /**
387      * Compute the set difference between two interval sets. The specific
388      * operation is {@code left - right}. If either of the input sets is
389      * {@code null}, it is treated as though it was an empty set.
390      */
391     public IntervalSet subtract(IntervalSet left, IntervalSet right)
392     {
393         if (left is null || left.size == 0) {
394             return new IntervalSet();
395         }
396         IntervalSet result = new IntervalSet(left);
397         if (right is null || right.isNil) {
398             // right set has no elements; just return the copy of the current set
399             return result;
400         }
401 
402         int resultI = 0;
403         int rightI = 0;
404         while (resultI < result.intervals_.length && rightI < right.intervals_.length) {
405             Interval resultInterval = result.intervals_[resultI];
406             Interval rightInterval = right.intervals_[rightI];
407 
408             // operation: (resultInterval - rightInterval) and update indexes
409 
410             if (rightInterval.b < resultInterval.a) {
411                 rightI++;
412                 continue;
413             }
414 
415             if (rightInterval.a > resultInterval.b) {
416                 resultI++;
417                 continue;
418             }
419 
420             Interval beforeCurrent = null;
421             Interval afterCurrent = null;
422             if (rightInterval.a > resultInterval.a) {
423                 beforeCurrent = new Interval(resultInterval.a, rightInterval.a - 1);
424             }
425 
426             if (rightInterval.b < resultInterval.b) {
427                 afterCurrent = new Interval(rightInterval.b + 1, resultInterval.b);
428             }
429 
430             if (beforeCurrent !is null) {
431                 if (afterCurrent !is null) {
432                     // split the current interval into two
433                     result.intervals_[resultI] = beforeCurrent;
434                     result.intervals_ ~= afterCurrent;
435                     resultI++;
436                     rightI++;
437                     continue;
438                 }
439                 else {
440                     // replace the current interval
441                     result.intervals_[resultI] = beforeCurrent;
442                     resultI++;
443                     continue;
444                 }
445             }
446             else {
447                 if (afterCurrent !is null) {
448                     // replace the current interval
449                     result.intervals_[resultI] = afterCurrent;
450                     rightI++;
451                     continue;
452                 }
453                 else {
454                     // remove the current interval (thus no need to increment resultI)
455                     //result.intervals.remove(resultI);
456                     result.intervals_ = result.intervals_[0..resultI].dup ~
457                         result.intervals_[resultI+1..$].dup;
458                     continue;
459                 }
460             }
461         }
462         // If rightI reached right.intervals.size(), no more intervals to subtract from result.
463         // If resultI reached result.intervals.size(), we would be subtracting from an empty set.
464         // Either way, we are done.
465         return result;
466     }
467 
468     public string elementName(Vocabulary vocabulary, int a)
469     {
470         if (a == TokenConstantDefinition.EOF) {
471             return "<EOF>";
472         }
473         else if (a == TokenConstantDefinition.EPSILON) {
474             return "<EPSILON>";
475         }
476         else {
477             return vocabulary.getDisplayName(a);
478         }
479     }
480 
481     public int size()
482     {
483         int n = 0;
484         auto numIntervals = intervals_.length;
485         if (numIntervals == 1) {
486             Interval firstInterval = intervals_[0];
487             return firstInterval.b - firstInterval.a + 1;
488         }
489         for (auto i = 0; i < numIntervals; i++) {
490             Interval I = intervals_[i];
491             n += (I.b - I.a + 1);
492         }
493         return n;
494     }
495 
496     public IntegerList toIntegerList()
497     {
498         IntegerList values = new IntegerList();
499         auto n = intervals_.length;
500         for (auto i = 0; i < n; i++) {
501             Interval I = intervals_[i];
502             int a = I.a;
503             int b = I.b;
504             for (int v = a; v <= b; v++) {
505                 values.add(v);
506             }
507         }
508         return values;
509     }
510 
511     public int[] toList()
512     {
513         int[] values;
514         auto n = intervals_.length;
515         for (auto i = 0; i < n; i++) {
516             Interval I = intervals_[i];
517             int a = I.a;
518             int b = I.b;
519             for (int v = a ; v <= b; v++) {
520                 values ~= v;
521             }
522         }
523         return values;
524     }
525 
526     public RedBlackTree!int toSet()
527     {
528         auto s = new RedBlackTree!int();
529         foreach (Interval I; intervals_) {
530             int a = I.a;
531             int b = I.b;
532             for (int v=a; v<=b; v++) {
533                 s.insert(v);
534             }
535         }
536         return s;
537     }
538 
539     /**
540      * Get the ith element of ordered set.  Used only by RandomPhrase so
541      * don't bother to implement if you're not doing that for a new
542      * ANTLR code gen target.
543      * @uml
544      * @safe
545      * @pure
546      */
547     public int get(int i) @safe pure
548     {
549         auto n = intervals_.length;
550         ulong index = 0;
551         for (auto j = 0; j < n; j++) {
552             Interval I = intervals_[j];
553             int a = I.a;
554             int b = I.b;
555             for (int v = a; v <= b; v++) {
556                 if (to!int(index) == i ) {
557                     return v;
558                 }
559                 index++;
560             }
561         }
562         return -1;
563     }
564 
565     public int[] toArray()
566     {
567         return toIntegerList().toArray;
568     }
569 
570     public void remove(int el)
571     {
572         assert(!readonly, "can't alter readonly IntervalSet");
573         auto n = intervals_.length;
574         for (auto i = 0; i < n; i++) {
575             Interval I = intervals_[i];
576             int a = I.a;
577             int b = I.b;
578             if (el < a) {
579                 break; // list is sorted and el is before this interval; not here
580             }
581             // if whole interval x..x, remove i
582             if (el == a && el == b ) {
583                 intervals_ = intervals_[0..i] ~ intervals_[i+1..$];
584                 break;
585             }
586             // if on left edge x..b, adjust left
587             if (el == a) {
588                 I.a++;
589                 break;
590             }
591             // if on right edge a..x, adjust right
592             if (el == b) {
593                 I.b--;
594                 break;
595             }
596             // if in middle a..x..b, split interval
597             if (el >a && el < b) { // found in this interval
598                 int oldb = I.b;
599                 I.b = el-1;      // [a..x-1]
600                 add(el+1, oldb); // add [x+1..b]
601             }
602         }
603     }
604 
605     /**
606      * @uml
607      * @safe
608      * @pure
609      */
610     public bool isReadonly() @safe pure
611     {
612         return readonly;
613     }
614 
615     /**
616      * @uml
617      * @safe
618      * @pure
619      */
620     public void setReadonly(bool readonly) @safe pure
621     {
622         assert(!this.readonly, "can't alter readonly IntervalSet");
623         this.readonly = readonly;
624     }
625 
626     /**
627      * @uml
628      * @override
629      */
630     public override bool opEquals(Object obj)
631     {
632         IntervalSet other = cast(IntervalSet)obj;
633         return intervals_ == other.intervals_;
634     }
635 
636     /**
637      * @uml
638      * @override
639      */
640     public override string toString()
641     {
642         return toString(false);
643     }
644 
645     public string toString(bool elemAreChar)
646     {
647         auto buf = appender!string;
648         if (intervals_ is null || intervals_.length == 0) {
649             return "{}";
650         }
651         if (this.size() > 1) {
652             buf.put("{");
653         }
654         foreach (index, I; this.intervals_) {
655             int a = I.a;
656             int b = I.b;
657             if (a == b) {
658                 if (a == TokenConstantDefinition.EOF) buf.put("<EOF>");
659                 else if (elemAreChar) buf.put("'" ~ to!string(a) ~ "'");
660                 else buf.put(to!string(a));
661             }
662             else {
663                 if (elemAreChar) buf.put("'" ~ to!string(a) ~ "'..'" ~ to!string(b) ~ "'");
664                 else buf.put(to!string(a) ~ ".." ~ to!string(b));
665             }
666             if (index + 1 < intervals_.length) {
667                 buf.put(", "); //  not last element
668             }
669         }
670         if (this.size() > 1) {
671             buf.put("}");
672         }
673         return buf.data;
674 
675     }
676 
677     public string toString(Vocabulary vocabulary)
678     {
679         auto buf = appender!string;
680         if (intervals_ is null || intervals_.length == 0) {
681             return "{}";
682         }
683         if (size() > 1) {
684             buf.put("{");
685         }
686         foreach (index, I; this.intervals_) {
687             int a = I.a;
688             int b = I.b;
689             if ( a==b ) {
690                 buf.put(elementName(vocabulary, a));
691             }
692             else {
693                 for (int i=a; i<=b; i++) {
694                     if ( i>a ) buf.put(", ");
695                     buf.put(elementName(vocabulary, i));
696                 }
697             }
698             if (index + 1 < intervals_.length) {
699                 buf.put(", ");
700             }
701         }
702         if (size() > 1) {
703             buf.put("}");
704         }
705         return buf.data;
706 
707     }
708 
709     /**
710      * @uml
711      * @final
712      */
713     public final Interval[] intervals()
714     {
715         return this.intervals_;
716     }
717 
718 }
719 
720 version(unittest) {
721     import dshould : be, equal, not, should;
722     import unit_threaded;
723 
724     class Test {
725 
726         @Tags("IntervalSet")
727         @("Empty")
728         unittest {
729             IntervalSet s = new IntervalSet;
730             s.should.not.be(null);
731             s.intervals.length.should.equal(0);
732             s.isNil.should.equal(true);
733         }
734 
735         @Tags("IntervalSet")
736         @("One")
737         unittest {
738             IntervalSet s = new IntervalSet;
739             s.add(30);
740             s.intervals.length.should.equal(1);
741             s.isNil.should.equal(false);
742             s.contains(30).should.equal(true);
743             s.contains(29).should.equal(false);
744             s.contains(31).should.equal(false);
745         }
746 
747         @Tags("IntervalSet")
748         @("Two")
749         unittest {
750             IntervalSet s = new IntervalSet;
751             s.add(30);
752             s.add(40);
753             s.intervals.length.should.equal(2);
754             s.isNil.should.equal(false);
755             s.contains(30).should.equal(true);
756             s.contains(40).should.equal(true);
757             s.contains(35).should.equal(false);
758         }
759 
760         @Tags("IntervalSet")
761         @("Range")
762         unittest {
763             IntervalSet s = new IntervalSet;
764             s.add(30, 41);
765             s.intervals.length.should.equal(1);
766             s.isNil.should.equal(false);
767             s.contains(30).should.equal(true);
768             s.contains(40).should.equal(true);
769             s.contains(35).should.equal(true);
770         }
771 
772         @Tags("IntervalSet")
773         @("Distinct1")
774         unittest {
775             IntervalSet s = new IntervalSet;
776             s.add(30, 32);
777             s.add(40, 42);
778             s.intervals.length.should.equal(2);
779             s.isNil.should.equal(false);
780             s.contains(30).should.equal(true);
781             s.contains(40).should.equal(true);
782             s.contains(35).should.equal(false);
783         }
784 
785         @Tags("IntervalSet")
786         @("Distinct2")
787         unittest {
788             IntervalSet s = new IntervalSet;
789             s.add(40, 42);
790             s.add(30, 32);
791             s.intervals.length.should.equal(2);
792             s.isNil.should.equal(false);
793             s.contains(30).should.equal(true);
794             s.contains(40).should.equal(true);
795             s.contains(35).should.equal(false);
796         }
797 
798         @Tags("IntervalSet")
799         @("Contiguous1")
800         unittest {
801             IntervalSet s = new IntervalSet;
802             s.add(30, 36);
803             s.add(36, 41);
804             s.add(41, 44);
805             s.intervals.length.should.equal(1);
806             s.isNil.should.equal(false);
807             s.contains(30).should.equal(true);
808             s.contains(40).should.equal(true);
809             s.contains(43).should.equal(true);
810             s.contains(44).should.equal(true);
811             s.contains(45).should.equal(false);
812         }
813 
814         @Tags("IntervalSet")
815         @("Contiguous2")
816         unittest {
817             IntervalSet s = new IntervalSet;
818             s.add(41, 44);
819             s.add(36, 41);
820             s.add(30, 36);
821             s.intervals.length.should.equal(1);
822             s.isNil.should.equal(false);
823             s.contains(30).should.equal(true);
824             s.contains(40).should.equal(true);
825             s.contains(43).should.equal(true);
826             s.contains(44).should.equal(true);
827             s.contains(45).should.equal(false);
828         }
829 
830         @Tags("IntervalSet")
831         @("Overlapping1")
832         unittest {
833             IntervalSet s = new IntervalSet;
834             s.add(30, 40);
835             s.add(35, 44);
836             s.add(31, 36);
837             s.intervals.length.should.equal(1);
838             s.isNil.should.equal(false);
839             s.contains(30).should.equal(true);
840             s.contains(40).should.equal(true);
841             s.contains(43).should.equal(true);
842             s.contains(44).should.equal(true);
843             s.contains(45).should.equal(false);
844         }
845 
846         @Tags("IntervalSet")
847         @("Overlapping2")
848         unittest {
849             IntervalSet s = new IntervalSet;
850             s.add(35, 44);
851             s.add(31, 36);
852             s.add(30, 40);
853             s.intervals.length.should.equal(1);
854             s.isNil.should.equal(false);
855             s.contains(30).should.equal(true);
856             s.contains(40).should.equal(true);
857             s.contains(43).should.equal(true);
858             s.contains(44).should.equal(true);
859             s.contains(45).should.equal(false);
860         }
861 
862         @Tags("IntervalSet")
863         @("Overlapping3")
864         unittest {
865             IntervalSet s = new IntervalSet;
866             s.add(30, 32);
867             s.add(40, 42);
868             s.add(140, 144);
869             s.add(50, 52);
870             s.add(20, 61);
871             s.add(50, 52);
872             s.intervals.length.should.equal(2);
873             s.isNil.should.equal(false);
874             s.contains(20).should.equal(true);
875             s.contains(40).should.equal(true);
876             s.contains(43).should.equal(true);
877             s.contains(61).should.equal(true);
878             s.contains(4).should.equal(false);
879             s.contains(62).should.equal(false);
880             s.contains(139).should.equal(false);
881             s.contains(140).should.equal(true);
882             s.contains(144).should.equal(true);
883             s.contains(145).should.equal(false);
884             s.toString.should.equal("{20..61, 140..144}");
885         }
886 
887         @Tags("IntervalSet")
888         @("Complement")
889         unittest {
890             IntervalSet s = new IntervalSet;
891             s.add(10, 21);
892             auto c = s.complement(1, 100);
893             c.intervals.length.should.equal(2);
894             c.toString.should.equal("{1..9, 22..100}");
895             c.contains(1).should.equal(true);
896             c.contains(40).should.equal(true);
897             c.contains(22).should.equal(true);
898             c.contains(10).should.equal(false);
899             c.contains(20).should.equal(false);
900         }
901     }
902 }