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         {
133             return;
134         }
135         // find position in list
136         // Use iterators as we modify list in place
137         foreach (index, ref el; intervals_)
138         {
139             Interval r = el;
140             if (addition.equals(r))
141             {
142                 return;
143             }
144             if (addition.adjacent(r) || !addition.disjoint(r))
145             {
146                 // next to each other, make a single larger interval
147                 Interval bigger = addition.unionInterval(r);
148                 el = bigger;
149                 // make sure we didn't just create an interval that
150                 // should be merged with next interval in list
151                 while (++index < intervals_.length)
152                 {
153                     Interval next = intervals_[index];
154                     if (!bigger.adjacent(next) && bigger.disjoint(next))
155                     {
156                         continue;
157                     }
158                     // if we bump up against or overlap next, merge
159                     intervals_ = intervals_.remove(index);
160                     // move backwards to what we just set
161                     intervals_[--index] = bigger.unionInterval(next); // set to 3 merged ones
162                 }
163                 return;
164             }
165 
166             if (addition.startsBeforeDisjoint(r))
167             {
168                 // insert before r
169                 intervals_ = intervals_[0 .. index] ~ addition ~ intervals_[index .. $];
170                 return;
171             }
172             // if disjoint and after r, a future iteration will handle it
173         }
174         // ok, must be after last interval (and disjoint from last interval)
175         // just add it
176         intervals_ ~= addition;
177     }
178 
179     public IntervalSet addAll(IntSet set)
180     {
181         if (set is null)
182         {
183             return this;
184         }
185         if (typeid(typeof(set)) == typeid(IntervalSet*))
186         {
187             IntervalSet other = cast(IntervalSet) set;
188             // walk set and add each interval
189             auto n = other.intervals_.length;
190             for (auto i = 0; i < n; i++)
191             {
192                 Interval I = other.intervals_[i];
193                 this.add(I.a, I.b);
194             }
195         }
196         else
197         {
198             foreach (int value; set.toList)
199             {
200                 add(value);
201             }
202         }
203         return this;
204     }
205 
206     public IntervalSet complement(int minElement, int maxElement)
207     {
208         return this.complement(IntervalSet.of(minElement, maxElement));
209     }
210 
211     /**
212      * {@inheritDoc}
213      */
214     public IntervalSet complement(IntSet vocabulary)
215     {
216         if (vocabulary is null || vocabulary.isNil)
217         {
218             return null; // nothing in common with null set
219         }
220         IntervalSet vocabularyIS = new IntervalSet();
221         vocabularyIS.addAll(vocabulary);
222         return vocabularyIS.subtract(this);
223     }
224 
225     public IntervalSet complement(IntervalSet vocabulary)
226     {
227         if (vocabulary is null || vocabulary.isNil)
228         {
229             return null; // nothing in common with null set
230         }
231         IntervalSet vocabularyIS = vocabulary;
232         return vocabularyIS.subtract(this);
233     }
234 
235     public IntervalSet subtract(IntSet a)
236     {
237         if (!a)
238         {
239             return new IntervalSet(this);
240         }
241         if (cast(IntervalSet) a)
242         {
243             return subtract(this, cast(IntervalSet) a);
244         }
245 
246         IntervalSet other = new IntervalSet;
247         other.addAll(a);
248         return subtract(this, other);
249     }
250 
251     public IntervalSet or(IntSet a)
252     {
253         IntervalSet o = new IntervalSet();
254         o.addAll(this);
255         o.addAll(a);
256         return o;
257     }
258 
259     public IntervalSet and(IntSet other)
260     {
261         if (other is null)
262         { //|| !(other instanceof IntervalSet) ) {
263             return null; // nothing in common with null set
264         }
265 
266         auto myIntervals = this.intervals_;
267         auto theirIntervals = (cast(IntervalSet) other).intervals_;
268         IntervalSet intersection;
269         auto mySize = myIntervals.length;
270         auto theirSize = theirIntervals.length;
271         int i = 0;
272         int j = 0;
273         // iterate down both interval lists looking for nondisjoint intervals
274         while (i < mySize && j < theirSize)
275         {
276             Interval mine = myIntervals[i];
277             Interval theirs = theirIntervals[j];
278             //System.out.println("mine="+mine+" and theirs="+theirs);
279             if (mine.startsBeforeDisjoint(theirs))
280             {
281                 // move this iterator looking for interval that might overlap
282                 i++;
283             }
284             else if (theirs.startsBeforeDisjoint(mine))
285             {
286                 // move other iterator looking for interval that might overlap
287                 j++;
288             }
289             else if (mine.properlyContains(theirs))
290             {
291                 // overlap, add intersection, get next theirs
292                 if (intersection is null)
293                 {
294                     intersection = new IntervalSet();
295                 }
296                 intersection.add(mine.intersection(theirs));
297                 j++;
298             }
299             else if (theirs.properlyContains(mine))
300             {
301                 // overlap, add intersection, get next mine
302                 if (intersection is null)
303                 {
304                     intersection = new IntervalSet();
305                 }
306                 intersection.add(mine.intersection(theirs));
307                 i++;
308             }
309             else if (!mine.disjoint(theirs))
310             {
311                 // overlap, add intersection
312                 if (intersection is null)
313                 {
314                     intersection = new IntervalSet();
315                 }
316                 intersection.add(mine.intersection(theirs));
317                 // Move the iterator of lower range [a..b], but not
318                 // the upper range as it may contain elements that will collide
319                 // with the next iterator. So, if mine=[0..115] and
320                 // theirs=[115..200], then intersection is 115 and move mine
321                 // but not theirs as theirs may collide with the next range
322                 // in thisIter.
323                 // move both iterators to next ranges
324                 if (mine.startsAfterNonDisjoint(theirs))
325                 {
326                     j++;
327                 }
328                 else if (theirs.startsAfterNonDisjoint(mine))
329                 {
330                     i++;
331                 }
332             }
333         }
334         if (intersection is null)
335         {
336             return new IntervalSet();
337         }
338         return intersection;
339     }
340 
341     public bool contains(int el)
342     {
343         foreach (I; intervals_)
344         {
345             int a = I.a;
346             int b = I.b;
347             if (el < a)
348             {
349                 break; // list is sorted and el is before this interval; not here
350             }
351             if (el >= a && el <= b)
352             {
353                 return true; // found in this interval
354             }
355         }
356         return false;
357     }
358 
359     public bool isNil()
360     {
361         return intervals_ is null || intervals_.length == 0;
362     }
363 
364     public int getSingleElement()
365     {
366         if (intervals_ !is null && intervals_.length == 1)
367         {
368             Interval I = intervals_[0];
369             if (I.a == I.b)
370             {
371                 return I.a;
372             }
373         }
374         return TokenConstantDefinition.INVALID_TYPE;
375     }
376 
377     /**
378      * Returns the maximum value contained in the set.
379      *
380      *  @return the maximum value contained in the set. If the set is empty, this
381      *  method returns {@link Token#INVALID_TYPE}.
382      */
383     public int getMaxElement()
384     {
385         if (isNil)
386         {
387             return TokenConstantDefinition.INVALID_TYPE;
388         }
389         Interval last = intervals_[$ - 1];
390         return last.b;
391     }
392 
393     /**
394      * Returns the minimum value contained in the set.
395      *
396      *  @return the minimum value contained in the set. If the set is empty, this
397      *  method returns {@link Token#INVALID_TYPE}.
398      */
399     public int getMinElement()
400     {
401         if (isNil)
402         {
403             return TokenConstantDefinition.INVALID_TYPE;
404         }
405         return intervals_[0].a;
406     }
407 
408     /**
409      * combine all sets in the array returned the or'd value
410      */
411     public static IntervalSet or(IntervalSet[] sets)
412     {
413         IntervalSet r = new IntervalSet(1);
414         r.clear;
415         foreach (IntervalSet s; sets)
416         {
417             r.addAll(s);
418         }
419         return r;
420     }
421 
422     /**
423      * Compute the set difference between two interval sets. The specific
424      * operation is {@code left - right}. If either of the input sets is
425      * {@code null}, it is treated as though it was an empty set.
426      */
427     public IntervalSet subtract(IntervalSet left, IntervalSet right)
428     {
429         if (left is null || left.size == 0)
430         {
431             return new IntervalSet();
432         }
433         IntervalSet result = new IntervalSet(left);
434         if (right is null || right.isNil)
435         {
436             // right set has no elements; just return the copy of the current set
437             return result;
438         }
439 
440         int resultI = 0;
441         int rightI = 0;
442         while (resultI < result.intervals_.length && rightI < right.intervals_.length)
443         {
444             Interval resultInterval = result.intervals_[resultI];
445             Interval rightInterval = right.intervals_[rightI];
446 
447             // operation: (resultInterval - rightInterval) and update indexes
448 
449             if (rightInterval.b < resultInterval.a)
450             {
451                 rightI++;
452                 continue;
453             }
454 
455             if (rightInterval.a > resultInterval.b)
456             {
457                 resultI++;
458                 continue;
459             }
460 
461             Interval beforeCurrent = null;
462             Interval afterCurrent = null;
463             if (rightInterval.a > resultInterval.a)
464             {
465                 beforeCurrent = new Interval(resultInterval.a, rightInterval.a - 1);
466             }
467 
468             if (rightInterval.b < resultInterval.b)
469             {
470                 afterCurrent = new Interval(rightInterval.b + 1, resultInterval.b);
471             }
472 
473             if (beforeCurrent !is null)
474             {
475                 if (afterCurrent !is null)
476                 {
477                     // split the current interval into two
478                     result.intervals_[resultI] = beforeCurrent;
479                     result.intervals_ ~= afterCurrent;
480                     resultI++;
481                     rightI++;
482                     continue;
483                 }
484                 else
485                 {
486                     // replace the current interval
487                     result.intervals_[resultI] = beforeCurrent;
488                     resultI++;
489                     continue;
490                 }
491             }
492             else
493             {
494                 if (afterCurrent !is null)
495                 {
496                     // replace the current interval
497                     result.intervals_[resultI] = afterCurrent;
498                     rightI++;
499                     continue;
500                 }
501                 else
502                 {
503                     // remove the current interval (thus no need to increment resultI)
504                     //result.intervals.remove(resultI);
505                     result.intervals_ = result.intervals_[0 .. resultI].dup
506                         ~ result.intervals_[resultI + 1 .. $].dup;
507                     continue;
508                 }
509             }
510         }
511         // If rightI reached right.intervals.size(), no more intervals to subtract from result.
512         // If resultI reached result.intervals.size(), we would be subtracting from an empty set.
513         // Either way, we are done.
514         return result;
515     }
516 
517     public string elementName(Vocabulary vocabulary, int a)
518     {
519         if (a == TokenConstantDefinition.EOF)
520         {
521             return "<EOF>";
522         }
523         else if (a == TokenConstantDefinition.EPSILON)
524         {
525             return "<EPSILON>";
526         }
527         else
528         {
529             return vocabulary.getDisplayName(a);
530         }
531     }
532 
533     public int size()
534     {
535         int n = 0;
536         auto numIntervals = intervals_.length;
537         if (numIntervals == 1)
538         {
539             Interval firstInterval = intervals_[0];
540             return firstInterval.b - firstInterval.a + 1;
541         }
542         for (auto i = 0; i < numIntervals; i++)
543         {
544             Interval I = intervals_[i];
545             n += (I.b - I.a + 1);
546         }
547         return n;
548     }
549 
550     public IntegerList toIntegerList()
551     {
552         IntegerList values = new IntegerList();
553         auto n = intervals_.length;
554         for (auto i = 0; i < n; i++)
555         {
556             Interval I = intervals_[i];
557             int a = I.a;
558             int b = I.b;
559             for (int v = a; v <= b; v++)
560             {
561                 values.add(v);
562             }
563         }
564         return values;
565     }
566 
567     public int[] toList()
568     {
569         int[] values;
570         auto n = intervals_.length;
571         for (auto i = 0; i < n; i++)
572         {
573             Interval I = intervals_[i];
574             int a = I.a;
575             int b = I.b;
576             for (int v = a; v <= b; v++)
577             {
578                 values ~= v;
579             }
580         }
581         return values;
582     }
583 
584     public RedBlackTree!int toSet()
585     {
586         auto s = redBlackTree!int();
587         foreach (Interval I; intervals_)
588         {
589             int a = I.a;
590             int b = I.b;
591             for (int v = a; v <= b; v++)
592             {
593                 s.insert(v);
594             }
595         }
596         return s;
597     }
598 
599     /**
600      * Get the ith element of ordered set.  Used only by RandomPhrase so
601      * don't bother to implement if you're not doing that for a new
602      * ANTLR code gen target.
603      * @uml
604      * @safe
605      * @pure
606      */
607     public int get(int i) @safe pure
608     {
609         auto n = intervals_.length;
610         ulong index = 0;
611         for (auto j = 0; j < n; j++)
612         {
613             Interval I = intervals_[j];
614             int a = I.a;
615             int b = I.b;
616             for (int v = a; v <= b; v++)
617             {
618                 if (to!int(index) == i)
619                 {
620                     return v;
621                 }
622                 index++;
623             }
624         }
625         return -1;
626     }
627 
628     public int[] toArray()
629     {
630         return toIntegerList().toArray;
631     }
632 
633     public void remove(int el)
634     {
635         assert(!readonly, "can't alter readonly IntervalSet");
636         auto n = intervals_.length;
637         for (auto i = 0; i < n; i++)
638         {
639             Interval I = intervals_[i];
640             int a = I.a;
641             int b = I.b;
642             if (el < a)
643             {
644                 break; // list is sorted and el is before this interval; not here
645             }
646             // if whole interval x..x, remove i
647             if (el == a && el == b)
648             {
649                 intervals_ = intervals_[0 .. i] ~ intervals_[i + 1 .. $];
650                 break;
651             }
652             // if on left edge x..b, adjust left
653             if (el == a)
654             {
655                 I.a++;
656                 break;
657             }
658             // if on right edge a..x, adjust right
659             if (el == b)
660             {
661                 I.b--;
662                 break;
663             }
664             // if in middle a..x..b, split interval
665             if (el > a && el < b)
666             { // found in this interval
667                 int oldb = I.b;
668                 I.b = el - 1; // [a..x-1]
669                 add(el + 1, oldb); // add [x+1..b]
670             }
671         }
672     }
673 
674     /**
675      * @uml
676      * @safe
677      * @pure
678      */
679     public bool isReadonly() @safe pure
680     {
681         return readonly;
682     }
683 
684     /**
685      * @uml
686      * @safe
687      * @pure
688      */
689     public void setReadonly(bool readonly) @safe pure
690     {
691         assert(!this.readonly, "can't alter readonly IntervalSet");
692         this.readonly = readonly;
693     }
694 
695     /**
696      * @uml
697      * @override
698      */
699     public override bool opEquals(Object obj)
700     {
701         IntervalSet other = cast(IntervalSet) obj;
702         return intervals_ == other.intervals_;
703     }
704 
705     /**
706      * @uml
707      * @override
708      */
709     public override string toString()
710     {
711         return toString(false);
712     }
713 
714     public string toString(bool elemAreChar)
715     {
716         auto buf = appender!string;
717         if (intervals_ is null || intervals_.length == 0)
718         {
719             return "{}";
720         }
721         if (this.size() > 1)
722         {
723             buf.put("{");
724         }
725         foreach (index, I; this.intervals_)
726         {
727             int a = I.a;
728             int b = I.b;
729             if (a == b)
730             {
731                 if (a == TokenConstantDefinition.EOF)
732                     buf.put("<EOF>");
733                 else if (elemAreChar)
734                     buf.put("'" ~ to!string(a) ~ "'");
735                 else
736                     buf.put(to!string(a));
737             }
738             else
739             {
740                 if (elemAreChar)
741                     buf.put("'" ~ to!string(a) ~ "'..'" ~ to!string(b) ~ "'");
742                 else
743                     buf.put(to!string(a) ~ ".." ~ to!string(b));
744             }
745             if (index + 1 < intervals_.length)
746             {
747                 buf.put(", "); //  not last element
748             }
749         }
750         if (this.size() > 1)
751         {
752             buf.put("}");
753         }
754         return buf.data;
755 
756     }
757 
758     public string toString(Vocabulary vocabulary)
759     {
760         auto buf = appender!string;
761         if (intervals_ is null || intervals_.length == 0)
762         {
763             return "{}";
764         }
765         if (size() > 1)
766         {
767             buf.put("{");
768         }
769         foreach (index, I; this.intervals_)
770         {
771             int a = I.a;
772             int b = I.b;
773             if (a == b)
774             {
775                 buf.put(elementName(vocabulary, a));
776             }
777             else
778             {
779                 for (int i = a; i <= b; i++)
780                 {
781                     if (i > a)
782                         buf.put(", ");
783                     buf.put(elementName(vocabulary, i));
784                 }
785             }
786             if (index + 1 < intervals_.length)
787             {
788                 buf.put(", ");
789             }
790         }
791         if (size() > 1)
792         {
793             buf.put("}");
794         }
795         return buf.data;
796 
797     }
798 
799     /**
800      * @uml
801      * @final
802      */
803     public final Interval[] intervals()
804     {
805         return this.intervals_;
806     }
807 
808 }
809 
810 version (unittest)
811 {
812     import dshould : be, equal, not, should;
813     import std.typecons : tuple;
814     import unit_threaded;
815 
816     class Test
817     {
818 
819         @Tags("IntervalSet")
820         @("Empty")
821         unittest
822         {
823             IntervalSet s = new IntervalSet;
824             s.should.not.be(null);
825             s.intervals.length.should.equal(0);
826             s.isNil.should.equal(true);
827         }
828 
829         @Tags("IntervalSet")
830         @("One")
831         unittest
832         {
833             IntervalSet s = new IntervalSet;
834             s.add(30);
835             s.intervals.length.should.equal(1);
836             s.isNil.should.equal(false);
837             s.contains(30).should.equal(true);
838             s.contains(29).should.equal(false);
839             s.contains(31).should.equal(false);
840         }
841 
842         @Tags("IntervalSet")
843         @("Two")
844         unittest
845         {
846             IntervalSet s = new IntervalSet;
847             s.add(30);
848             s.add(40);
849             s.intervals.length.should.equal(2);
850             s.isNil.should.equal(false);
851             s.contains(30).should.equal(true);
852             s.contains(40).should.equal(true);
853             s.contains(35).should.equal(false);
854         }
855 
856         @Tags("IntervalSet")
857         @("Range")
858         unittest
859         {
860             IntervalSet s = new IntervalSet;
861             s.add(30, 41);
862             s.intervals.length.should.equal(1);
863             s.isNil.should.equal(false);
864             s.contains(30).should.equal(true);
865             s.contains(40).should.equal(true);
866             s.contains(35).should.equal(true);
867         }
868 
869         @Tags("IntervalSet")
870         @("Distinct1")
871         unittest
872         {
873             IntervalSet s = new IntervalSet;
874             s.add(30, 32);
875             s.add(40, 42);
876             s.intervals.length.should.equal(2);
877             s.isNil.should.equal(false);
878             s.contains(30).should.equal(true);
879             s.contains(40).should.equal(true);
880             s.contains(35).should.equal(false);
881         }
882 
883         @Tags("IntervalSet")
884         @("Distinct2")
885         unittest
886         {
887             IntervalSet s = new IntervalSet;
888             s.add(40, 42);
889             s.add(30, 32);
890             s.intervals.length.should.equal(2);
891             s.isNil.should.equal(false);
892             s.contains(30).should.equal(true);
893             s.contains(40).should.equal(true);
894             s.contains(35).should.equal(false);
895         }
896 
897         @Tags("IntervalSet")
898         @("Contiguous1")
899         unittest
900         {
901             IntervalSet s = new IntervalSet;
902             s.add(30, 36);
903             s.add(36, 41);
904             s.add(41, 44);
905             s.intervals.length.should.equal(1);
906             s.isNil.should.equal(false);
907             s.contains(30).should.equal(true);
908             s.contains(40).should.equal(true);
909             s.contains(43).should.equal(true);
910             s.contains(44).should.equal(true);
911             s.contains(45).should.equal(false);
912         }
913 
914         @Tags("IntervalSet")
915         @("Contiguous2")
916         unittest
917         {
918             IntervalSet s = new IntervalSet;
919             s.add(41, 44);
920             s.add(36, 41);
921             s.add(30, 36);
922             s.intervals.length.should.equal(1);
923             s.isNil.should.equal(false);
924             s.contains(30).should.equal(true);
925             s.contains(40).should.equal(true);
926             s.contains(43).should.equal(true);
927             s.contains(44).should.equal(true);
928             s.contains(45).should.equal(false);
929         }
930 
931         @Tags("IntervalSet")
932         @("Overlapping1")
933         unittest
934         {
935             IntervalSet s = new IntervalSet;
936             s.add(30, 40);
937             s.add(35, 44);
938             s.add(31, 36);
939             s.intervals.length.should.equal(1);
940             s.isNil.should.equal(false);
941             s.contains(30).should.equal(true);
942             s.contains(40).should.equal(true);
943             s.contains(43).should.equal(true);
944             s.contains(44).should.equal(true);
945             s.contains(45).should.equal(false);
946         }
947 
948         @Tags("IntervalSet")
949         @("Overlapping2")
950         unittest
951         {
952             IntervalSet s = new IntervalSet;
953             s.add(35, 44);
954             s.add(31, 36);
955             s.add(30, 40);
956             s.intervals.length.should.equal(1);
957             s.isNil.should.equal(false);
958             s.contains(30).should.equal(true);
959             s.contains(40).should.equal(true);
960             s.contains(43).should.equal(true);
961             s.contains(44).should.equal(true);
962             s.contains(45).should.equal(false);
963         }
964 
965         @Tags("IntervalSet")
966         @("Overlapping3")
967         unittest
968         {
969             IntervalSet s = new IntervalSet;
970             s.add(30, 32);
971             s.add(40, 42);
972             s.add(140, 144);
973             s.add(50, 52);
974             s.add(20, 61);
975             s.add(50, 52);
976             s.intervals.length.should.equal(2);
977             s.isNil.should.equal(false);
978             s.contains(20).should.equal(true);
979             s.contains(40).should.equal(true);
980             s.contains(43).should.equal(true);
981             s.contains(61).should.equal(true);
982             s.contains(4).should.equal(false);
983             s.contains(62).should.equal(false);
984             s.contains(139).should.equal(false);
985             s.contains(140).should.equal(true);
986             s.contains(144).should.equal(true);
987             s.contains(145).should.equal(false);
988             s.toString.should.equal("{20..61, 140..144}");
989         }
990 
991         @Tags("IntervalSet")
992         @("Complement")
993         unittest
994         {
995             IntervalSet s = new IntervalSet;
996             s.add(10, 21);
997             auto c = s.complement(1, 100);
998             c.intervals.length.should.equal(2);
999             c.toString.should.equal("{1..9, 22..100}");
1000             c.contains(1).should.equal(true);
1001             c.contains(40).should.equal(true);
1002             c.contains(22).should.equal(true);
1003             c.contains(10).should.equal(false);
1004             c.contains(20).should.equal(false);
1005         }
1006     }
1007 }