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