The OpenD Programming Language

1 module taggedalgebraic.visit;
2 
3 import taggedalgebraic.taggedalgebraic;
4 import taggedalgebraic.taggedunion;
5 
6 import std.meta : anySatisfy, staticMap;
7 import std.traits : Unqual, EnumMembers, isInstanceOf;
8 
9 
10 /** Dispatches the value contained on a `TaggedUnion` or `TaggedAlgebraic` to a
11 	set of visitors.
12 
13 	A visitor can have one of three forms:
14 
15 	$(UL
16 		$(LI function or delegate taking a single typed parameter)
17 		$(LI function or delegate taking no parameters)
18 		$(LI function or delegate template taking any single parameter)
19 	)
20 
21 	....
22 */
23 template visit(VISITORS...)
24 	if (VISITORS.length > 0)
25 {
26 	auto visit(TU)(auto ref TU tu)
27 		if (isInstanceOf!(TaggedUnion, TU))
28 	{
29 		alias val = validateHandlers!(TU, VISITORS);
30 
31 		final switch (tu.kind) {
32 			static foreach (k; EnumMembers!(TU.Kind)) {
33 				case k: {
34 					static if (isUnitType!(TU.FieldTypes[k]))
35 						alias T = void;
36 					else alias T = TU.FieldTypes[k];
37 					alias h = selectHandler!(T, VISITORS);
38 					static if (is(typeof(h) == typeof(null))) static assert(false, "No visitor defined for type "~T.stringof);
39 					else static if (is(typeof(h) == string)) static assert(false, h);
40 					else static if (is(T == void)) return h();
41 					else return h(tu.value!k);
42 				}
43 			}
44 		}
45 	}
46 
47 	auto visit(U)(auto ref TaggedAlgebraic!U ta)
48 	{
49 		return visit(ta.get!(TaggedUnion!U));
50 	}
51 }
52 
53 ///
54 unittest {
55 	static if (__VERSION__ >= 2081) {
56 		import std.conv : to;
57 
58 		union U {
59 			int number;
60 			string text;
61 		}
62 		alias TU = TaggedUnion!U;
63 
64 		auto tu = TU.number(42);
65 		tu.visit!(
66 			(int n) { assert(n == 42); },
67 			(string s) { assert(false); }
68 		);
69 
70 		assert(tu.visit!((v) => to!int(v)) == 42);
71 
72 		tu.setText("43");
73 
74 		assert(tu.visit!((v) => to!int(v)) == 43);
75 	}
76 }
77 
78 unittest {
79 	// repeat test from TaggedUnion
80 	union U {
81 		Void none;
82 		int count;
83 		float length;
84 	}
85 	TaggedAlgebraic!U u;
86 
87 	//
88 	static assert(is(typeof(u.visit!((int) {}, (float) {}, () {}))));
89 	static assert(is(typeof(u.visit!((_) {}, () {}))));
90 	static assert(is(typeof(u.visit!((_) {}, (float) {}, () {}))));
91 	static assert(is(typeof(u.visit!((float) {}, (_) {}, () {}))));
92 
93 	static assert(!is(typeof(u.visit!((_) {})))); // missing void handler
94 	static assert(!is(typeof(u.visit!(() {})))); // missing value handler
95 
96 	static assert(!is(typeof(u.visit!((_) {}, () {}, (string) {})))); // invalid typed handler
97 	static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, () {})))); // duplicate void handler
98 	static assert(!is(typeof(u.visit!((_) {}, () {}, (_) {})))); // duplicate generic handler
99 	static assert(!is(typeof(u.visit!((int) {}, (float) {}, (float) {}, () {})))); // duplicate typed handler
100 
101 	// TODO: error out for superfluous generic handlers
102 	//static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, (_) {})))); // superfluous generic handler
103 }
104 
105 unittest {
106 	union U {
107 		Void none;
108 		int count;
109 		float length;
110 	}
111 	TaggedUnion!U u;
112 
113 	//
114 	static assert(is(typeof(u.visit!((int) {}, (float) {}, () {}))));
115 	static assert(is(typeof(u.visit!((_) {}, () {}))));
116 	static assert(is(typeof(u.visit!((_) {}, (float) {}, () {}))));
117 	static assert(is(typeof(u.visit!((float) {}, (_) {}, () {}))));
118 
119 	static assert(!is(typeof(u.visit!((_) {})))); // missing void handler
120 	static assert(!is(typeof(u.visit!(() {})))); // missing value handler
121 
122 	static assert(!is(typeof(u.visit!((_) {}, () {}, (string) {})))); // invalid typed handler
123 	static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, () {})))); // duplicate void handler
124 	static assert(!is(typeof(u.visit!((_) {}, () {}, (_) {})))); // duplicate generic handler
125 	static assert(!is(typeof(u.visit!((int) {}, (float) {}, (float) {}, () {})))); // duplicate typed handler
126 
127 	// TODO: error out for superfluous generic handlers
128 	//static assert(!is(typeof(u.visit!((int) {}, (float) {}, () {}, (_) {})))); // superfluous generic handler
129 }
130 
131 unittest {
132 	// make sure that the generic handler is not instantiated with types for
133 	// which it doesn't compile
134 	class C {}
135 	union U { int i; C c; }
136 	TaggedUnion!U u;
137 	u.visit!(
138 		(C c) => c !is null,
139 		(v) {
140 			static assert(is(typeof(v) == int));
141 			return v != 0;
142 		}
143 	);
144 }
145 
146 unittest {
147 	static struct S {
148 		Void nothing;
149 		int num;
150 		int[] nums;
151 	}
152 	alias TU = TaggedUnion!S;
153 
154 	TU tu = [1];
155 	tu.visit!(
156 		() { assert(false); },
157 		(int num) { assert(false); },
158 		(int[] num) { assert(num == [1]); }
159 	);
160 	tu.visit!(
161 		() { assert(false); },
162 		(const int num) { assert(false); },
163 		(const int[] num) { assert(num == [1]); }
164 	);
165 	tu.visit!(
166 		() { assert(false); },
167 		(const int num) { assert(false); },
168 		(const(int)[] num) { assert(num == [1]); }
169 	);
170 
171 	const(TU) tuc = TU([1]);
172 	tuc.visit!(
173 		() { assert(false); },
174 		(int num) { assert(false); },
175 		(const(int)[] num) { assert(num == [1]); }
176 	);
177 	tuc.visit!(
178 		() { assert(false); },
179 		(const(int) num) { assert(false); },
180 		(const(int[]) num) { assert(num == [1]); }
181 	);
182 }
183 
184 
185 /** The same as `visit`, except that failure to handle types is checked at runtime.
186 
187 	Instead of failing to compile, `tryVisit` will throw an `Exception` if none
188 	of the handlers is able to handle the value contained in `tu`.
189 */
190 template tryVisit(VISITORS...)
191 	if (VISITORS.length > 0)
192 {
193 	auto tryVisit(TU)(auto ref TU tu)
194 		if (isInstanceOf!(TaggedUnion, TU))
195 	{
196 		final switch (tu.kind) {
197 			static foreach (k; EnumMembers!(TU.Kind)) {
198 				case k: {
199 					static if (isUnitType!(TU.FieldTypes[k]))
200 						alias T = void;
201 					else alias T = TU.FieldTypes[k];
202 					alias h = selectHandler!(T, VISITORS);
203 					static if (is(typeof(h) == typeof(null))) throw new Exception("Type "~T.stringof~" not handled by any visitor.");
204 					else static if (is(typeof(h) == string)) static assert(false, h);
205 					else static if (is(T == void)) return h();
206 					else return h(tu.value!k);
207 				}
208 			}
209 		}
210 	}
211 
212 	auto tryVisit(U)(auto ref TaggedAlgebraic!U ta)
213 	{
214 		return tryVisit(ta.get!(TaggedUnion!U));
215 	}
216 }
217 
218 ///
219 unittest {
220 	import std.exception : assertThrown;
221 
222 	union U {
223 		int number;
224 		string text;
225 	}
226 	alias TU = TaggedUnion!U;
227 
228 	auto tu = TU.number(42);
229 	tu.tryVisit!((int n) { assert(n == 42); });
230 	assertThrown(tu.tryVisit!((string s) { assert(false); }));
231 }
232 
233 // repeat from TaggedUnion
234 unittest {
235 	import std.exception : assertThrown;
236 
237 	union U {
238 		int number;
239 		string text;
240 	}
241 	alias TA = TaggedAlgebraic!U;
242 
243 	auto ta = TA(42);
244 	ta.tryVisit!((int n) { assert(n == 42); });
245 	assertThrown(ta.tryVisit!((string s) { assert(false); }));
246 }
247 
248 
249 private template validateHandlers(TU, VISITORS...)
250 {
251 	import std.traits : CopyConstness, isSomeFunction;
252 
253 	alias ApplyConst(T) = CopyConstness!(TU, T);
254 	alias Types = staticMap!(ApplyConst, TU.FieldTypes);
255 
256 	static foreach (int i; 0 .. VISITORS.length) {
257 		static if (isSomeFunction!(VISITORS[i])) {
258 			static assert(anySatisfy!(matchesType!(VISITORS[i]), Types),
259 				"Visitor at index "~i.stringof~" does not match any type of "~Types.stringof);
260 		} else {
261 			static assert(__traits(isTemplate, VISITORS[i]),
262 				"Visitor at index "~i.stringof~" must be a function/delegate literal: "~VISITORS[i].stringof);
263 		}
264 	}
265 }
266 
267 private template matchesType(alias fun) {
268 	import std.traits : ParameterTypeTuple, isSomeFunction;
269 
270 	template matchesType(T) {
271 		static if (isSomeFunction!fun) {
272 			alias Params = ParameterTypeTuple!fun;
273 			static if (Params.length == 0 && isUnitType!(Unqual!T)) enum matchesType = true;
274 			else static if (Params.length == 1 && isMatch!(Params[0], T)) enum matchesType = true;
275 			else enum matchesType = false;
276 		} else static if (!isUnitType!(Unqual!T)) {
277 			static if (__traits(compiles, fun!T) && isSomeFunction!(fun!T)) {
278 				alias Params = ParameterTypeTuple!(fun!T);
279 				static if (Params.length == 1 && isMatch!(Params[0], T)) enum matchesType = true;
280 				else enum matchesType = false;
281 			} else enum matchesType = false;
282 		} else enum matchesType = false;
283 	}
284 }
285 
286 unittest {
287 	class C {}
288 	alias mt1 = matchesType!((C c) => true);
289 	alias mt2 = matchesType!((c) { static assert(!is(typeof(c) == C)); });
290 	static assert(mt1!C);
291 	static assert(!mt1!int);
292 	static assert(mt2!int);
293 	static assert(!mt2!C);
294 }
295 
296 private template selectHandler(T, VISITORS...)
297 {
298 	import std.traits : ParameterTypeTuple, isSomeFunction;
299 
300 	template typedIndex(int i, int matched_index = -1) {
301 		static if (i < VISITORS.length) {
302 			alias fun = VISITORS[i];
303 			static if (isSomeFunction!fun) {
304 				alias Params = ParameterTypeTuple!fun;
305 				static if (Params.length > 1) enum typedIndex = "Visitor at index "~i.stringof~" must not take more than one parameter.";
306 				else static if (Params.length == 0 && is(Unqual!T == void) || Params.length == 1 && isMatch!(Params[0], T)) {
307 					static if (matched_index >= 0) enum typedIndex = "Vistor at index "~i.stringof~" conflicts with visitor at index "~matched_index~".";
308 					else enum typedIndex = typedIndex!(i+1, i);
309 				} else enum typedIndex = typedIndex!(i+1, matched_index);
310 			} else enum typedIndex = typedIndex!(i+1, matched_index);
311 		} else enum typedIndex = matched_index;
312 	}
313 
314 	template genericIndex(int i, int matched_index = -1) {
315 		static if (i < VISITORS.length) {
316 			alias fun = VISITORS[i];
317 			static if (!isSomeFunction!fun) {
318 				static if (!__traits(isTemplate, fun)) enum genericIndex = "Visitor at index " ~ i.stringof ~ " is neither a function, nor a template.";
319 				else static if (__traits(compiles, fun!T) && isSomeFunction!(fun!T)) {
320 					static if (ParameterTypeTuple!(fun!T).length == 1) {
321 						static if (matched_index >= 0) enum genericIndex = "Only one generic visitor allowed";
322 						else enum genericIndex = genericIndex!(i+1, i);
323 					} else enum genericIndex = "Generic visitor at index "~i.stringof~" must have a single parameter.";
324 				} else enum genericIndex = genericIndex!(i+1, i); // let this fail within the template instantiation instead of here
325 			} else enum genericIndex = genericIndex!(i+1, matched_index);
326 		} else enum genericIndex = matched_index;
327 	}
328 
329 	enum typed_index = typedIndex!0;
330 	static if (is(T == void) || (is(typeof(typed_index) == string) && typed_index != -1))
331 		enum generic_index = -1;
332 	else enum generic_index = genericIndex!0;
333 
334 	static if (is(typeof(typed_index) == string)) enum selectHandler = typed_index;
335 	else static if (is(typeof(generic_index) == string)) enum selectHandler = generic_index;
336 	else static if (typed_index >= 0) alias selectHandler = VISITORS[typed_index];
337 	else static if (generic_index >= 0) alias selectHandler = VISITORS[generic_index];
338 	else enum selectHandler = null;
339 }
340 
341 private enum isMatch(PT, T) = is(Unqual!(immutable(T)) == Unqual!(immutable(PT))) && is(T : PT);