1 module funkwerk.stdx.data.json.taggedalgebraic.visit;
2 
3 import funkwerk.stdx.data.json.taggedalgebraic.taggedalgebraic;
4 import funkwerk.stdx.data.json.taggedalgebraic.taggedunion;
5 
6 import std.meta : anySatisfy;
7 import std.traits : 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 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 /** The same as `visit`, except that failure to handle types is checked at runtime.
132 
133 	Instead of failing to compile, `tryVisit` will throw an `Exception` if none
134 	of the handlers is able to handle the value contained in `tu`.
135 */
136 template tryVisit(VISITORS...)
137 	if (VISITORS.length > 0)
138 {
139 	auto tryVisit(TU)(auto ref TU tu)
140 		if (isInstanceOf!(TaggedUnion, TU))
141 	{
142 		final switch (tu.kind) {
143 			static foreach (k; EnumMembers!(TU.Kind)) {
144 				case k: {
145 					static if (isUnitType!(TU.FieldTypes[k]))
146 						alias T = void;
147 					else alias T = TU.FieldTypes[k];
148 					alias h = selectHandler!(T, VISITORS);
149 					static if (is(typeof(h) == typeof(null))) throw new Exception("Type "~T.stringof~" not handled by any visitor.");
150 					else static if (is(typeof(h) == string)) static assert(false, h);
151 					else static if (is(T == void)) return h();
152 					else return h(tu.value!k);
153 				}
154 			}
155 		}
156 	}
157 
158 	auto tryVisit(U)(auto ref TaggedAlgebraic!U ta)
159 	{
160 		return tryVisit(ta.get!(TaggedUnion!U));
161 	}
162 }
163 
164 ///
165 unittest {
166 	import std.exception : assertThrown;
167 
168 	union U {
169 		int number;
170 		string text;
171 	}
172 	alias TU = TaggedUnion!U;
173 
174 	auto tu = TU.number(42);
175 	tu.tryVisit!((int n) { assert(n == 42); });
176 	assertThrown(tu.tryVisit!((string s) { assert(false); }));
177 }
178 
179 // repeat from TaggedUnion
180 unittest {
181 	import std.exception : assertThrown;
182 
183 	union U {
184 		int number;
185 		string text;
186 	}
187 	alias TA = TaggedAlgebraic!U;
188 
189 	auto ta = TA(42);
190 	ta.tryVisit!((int n) { assert(n == 42); });
191 	assertThrown(ta.tryVisit!((string s) { assert(false); }));
192 }
193 
194 
195 private template validateHandlers(TU, VISITORS...)
196 {
197 	import std.traits : isSomeFunction;
198 
199 	alias Types = TU.FieldTypes;
200 
201 	static foreach (int i; 0 .. VISITORS.length) {
202 		static assert(!is(VISITORS[i]) || isSomeFunction!(VISITORS[i]),
203 			"Visitor at index "~i.stringof~" must be a function/delegate literal: "~VISITORS[i].stringof);
204 		static assert(anySatisfy!(matchesType!(VISITORS[i]), Types),
205 			"Visitor at index "~i.stringof~" does not match any type of "~TU.FieldTypes.stringof);
206 	}
207 }
208 
209 private template matchesType(alias fun) {
210 	import std.traits : ParameterTypeTuple, isSomeFunction;
211 
212 	template matchesType(T) {
213 		static if (isSomeFunction!fun) {
214 			alias Params = ParameterTypeTuple!fun;
215 			static if (Params.length == 0 && isUnitType!T) enum matchesType = true;
216 			else static if (Params.length == 1 && is(T == Params[0])) enum matchesType = true;
217 			else enum matchesType = false;
218 		} else static if (!isUnitType!T) {
219 			static if (__traits(compiles, fun!T) && isSomeFunction!(fun!T)) {
220 				alias Params = ParameterTypeTuple!(fun!T);
221 				static if (Params.length == 1 && is(T == Params[0])) enum matchesType = true;
222 				else enum matchesType = false;
223 			} else enum matchesType = false;
224 		} else enum matchesType = false;
225 	}
226 }
227 
228 unittest {
229 	class C {}
230 	alias mt1 = matchesType!((C c) => true);
231 	alias mt2 = matchesType!((c) { static assert(!is(typeof(c) == C)); });
232 	static assert(mt1!C);
233 	static assert(!mt1!int);
234 	static assert(mt2!int);
235 	static assert(!mt2!C);
236 }
237 
238 private template selectHandler(T, VISITORS...)
239 {
240 	import std.traits : ParameterTypeTuple, isSomeFunction;
241 
242 	template typedIndex(int i, int matched_index = -1) {
243 		static if (i < VISITORS.length) {
244 			alias fun = VISITORS[i];
245 			static if (isSomeFunction!fun) {
246 				alias Params = ParameterTypeTuple!fun;
247 				static if (Params.length > 1) enum typedIndex = "Visitor at index "~i.stringof~" must not take more than one parameter.";
248 				else static if (Params.length == 0 && is(T == void) || Params.length == 1 && is(T == Params[0])) {
249 					static if (matched_index >= 0) enum typedIndex = "Vistor at index "~i.stringof~" conflicts with visitor at index "~matched_index~".";
250 					else enum typedIndex = typedIndex!(i+1, i);
251 				} else enum typedIndex = typedIndex!(i+1, matched_index);
252 			} else enum typedIndex = typedIndex!(i+1, matched_index);
253 		} else enum typedIndex = matched_index;
254 	}
255 
256 	template genericIndex(int i, int matched_index = -1) {
257 		static if (i < VISITORS.length) {
258 			alias fun = VISITORS[i];
259 			static if (!isSomeFunction!fun) {
260 				static if (__traits(compiles, fun!T) && isSomeFunction!(fun!T)) {
261 					static if (ParameterTypeTuple!(fun!T).length == 1) {
262 						static if (matched_index >= 0) enum genericIndex = "Only one generic visitor allowed";
263 						else enum genericIndex = genericIndex!(i+1, i);
264 					} else enum genericIndex = "Generic visitor at index "~i.stringof~" must have a single parameter.";
265 				} else enum genericIndex = "Visitor at index "~i.stringof~" (or its template instantiation with type "~T.stringof~") must be a valid function or delegate.";
266 			} else enum genericIndex = genericIndex!(i+1, matched_index);
267 		} else enum genericIndex = matched_index;
268 	}
269 
270 	enum typed_index = typedIndex!0;
271 	static if (is(T == void)) enum generic_index = -1;
272 	else enum generic_index = genericIndex!0;
273 
274 	static if (is(typeof(typed_index) == string)) enum selectHandler = typed_index;
275 	else static if (is(typeof(generic_index == string))) enum selectHandler = generic_index;
276 	else static if (typed_index >= 0) alias selectHandler = VISITORS[typed_index];
277 	else static if (generic_index >= 0) alias selectHandler = VISITORS[generic_index];
278 	else enum selectHandler = null;
279 }