1 module nxt.class_range;
2 
3 import std.traits : isArray;
4 import std.range.primitives : isInputRange, ElementType;
5 
6 /** Upcast all elements in `x` of type `T` to the type `U`, where `U` is a
7  * superclass of `T`.
8  *
9  * TODO move to phobos-next
10  */
11 inout(U)[] upcastElementsTo(U, T)(scope inout(T)[] x) @trusted
12 if (is(T == class) &&
13     is(U == class)
14     // TODO also check that `T` is a subclass of `U`
15     )
16 {
17     return cast(typeof(return))x;
18 }
19 
20 /// ditto
21 auto upcastElementsTo(U, R)(inout(R) x)
22     @trusted
23 if (!isArray!R &&
24     is(U == class) &&
25     isInputRange!R && is(ElementType!R == class)
26     // TODO also check that `ElementType!R` is a subclass of `U`
27     )
28 {
29     import std.algorithm.iteration : map;
30     return x.map!(_ => cast(U)_);
31 }
32 
33 private struct DowncastingFilterResult(Subclass, Range)
34 {
35     import core.internal.traits : Unqual;
36 
37     alias R = Unqual!Range;
38     R _input;
39     private bool _primed;
40 
41     alias pred = _ => cast(Subclass)_ !is null;
42 
43     private void prime()
44     {
45         import std.range.primitives : empty, front, popFront;
46         if (_primed) return;
47         while (!_input.empty && !pred(_input.front))
48         {
49             _input.popFront();
50         }
51         _primed = true;
52     }
53 
54     this(R r)
55     {
56         _input = r;
57     }
58 
59     private this(R r, bool primed)
60     {
61         _input = r;
62         _primed = primed;
63     }
64 
65     auto opSlice() { return this; }
66 
67     import std.range.primitives : isInfinite;
68     static if (isInfinite!Range)
69     {
70         enum bool empty = false;
71     }
72     else
73     {
74         @property bool empty()
75         {
76             import std.range.primitives : empty;
77             prime(); return _input.empty;
78         }
79     }
80 
81     void popFront()
82     {
83         import std.range.primitives : front, popFront, empty;
84         do
85         {
86             _input.popFront();
87         } while (!_input.empty && !pred(_input.front));
88         _primed = true;
89     }
90 
91     @property Subclass front()
92     {
93         import std.range.primitives : front;
94         prime();
95         assert(!empty, "Attempting to fetch the front of an empty filter.");
96         return cast(typeof(return))_input.front;
97     }
98 
99     import std.range.primitives : isForwardRange;
100     static if (isForwardRange!R)
101     {
102         @property auto save()
103         {
104             import std.range.primitives : save;
105             return typeof(this)(_input.save, _primed);
106         }
107     }
108 }
109 
110 /** Variant of std.algorithm.iteration : that filters out all elements of
111  * `range` that are instances of `Subclass`.
112  */
113 template castFilter(Subclass)
114 {
115     import std.range.primitives : isInputRange, ElementType;
116     auto castFilter(Range)(Range range)
117     if (isInputRange!(Range) &&
118         is(ElementType!Range == class) &&
119         is(Subclass : ElementType!Range))
120     {
121         return DowncastingFilterResult!(Subclass, Range)(range);
122     }
123 }
124 
125 ///
126 @safe pure unittest
127 {
128     class X
129     {
130         this(int x)
131         {
132             this.x = x;
133         }
134         int x;
135     }
136 
137     class Y : X
138     {
139         this(int x)
140         {
141             super(x);
142         }
143     }
144 
145     auto y = castFilter!Y([new X(42), new Y(43)]);
146     auto yf = y.front;
147     static assert(is(typeof(yf) == Y));
148     y.popFront();
149     assert(y.empty);
150 }