1 /**
2  * Var-like helper for visitor.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.utils.mapset.vars;
15 
16 static if (__VERSION__ >= 2083):
17 
18 import core.lifetime;
19 
20 import std.meta;
21 import std.traits;
22 
23 import ae.utils.mapset.mapset;
24 import ae.utils.mapset.visitor;
25 
26 /// A wrapper around a MapSetVisitor which allows interacting with it
27 /// using a more conventional interface.
28 struct MapSetVars(
29 	/// `MapSet` `DimName` parameter.
30 	/// Must also provide `tempVarStart` and `tempVarEnd`
31 	/// static fields for temporary variable allocation.
32 	VarName_,
33 	/// `MapSet` `DimValue` parameter.
34 	Value_,
35 	/// `MapSet` `nullValue` parameter.
36 	Value_ nullValue = Value_.init,
37 )
38 {
39 	@disable this(this); // Do not copy!
40 
41 	alias VarName = VarName_;
42 	alias Value = Value_;
43 
44 	alias Set = MapSet!(VarName, Value, nullValue);
45 	alias Visitor = MapSetVisitor!(VarName, Value, nullValue);
46 
47 	Visitor visitor;
48 	VarName varCounter;
49 
50 	private VarName allocateName()
51 	{
52 		assert(varCounter < VarName.tempVarEnd, "Too many temporary variables");
53 		return varCounter++;
54 	}
55 
56 	private void deallocate(VarName name)
57 	{
58 		// best-effort de-bump-the-pointer
59 		auto next = name; next++;
60 		if (next > name && next == varCounter)
61 			varCounter = name;
62 	}
63 
64 	bool next()
65 	{
66 		varCounter = VarName.tempVarStart;
67 		return visitor.next();
68 	}
69 
70 	Var get(VarName name)
71 	{
72 		return Var(&this, name);
73 	}
74 
75 	alias opIndex = get;
76 
77 	private struct Dispatcher
78 	{
79 		private MapSetVars* vars;
80 
81 		Var opDispatch(string name)()
82 		if (__traits(hasMember, VarName, name))
83 		{
84 			return vars.get(__traits(getMember, VarName, name));
85 		}
86 
87 		Var opDispatch(string name, V)(auto ref V v)
88 		if (__traits(hasMember, VarName, name) && is(typeof(vars.get(VarName.init) = v)))
89 		{
90 			return vars.get(__traits(getMember, VarName, name)) = v;
91 		}
92 	}
93 
94 	@property Dispatcher var() { return Dispatcher(&this); }
95 
96 	Var allocate(Value value = nullValue)
97 	{
98 		return Var(&this, allocateName(), value);
99 	}
100 
101 	Var inject(Value[] values)
102 	{
103 		auto var = Var(&this, allocateName());
104 		visitor.inject(var.name, values);
105 		return var;
106 	}
107 
108 	private Var eval(size_t n)(Repeat!(n, Var) vars, scope Value delegate(Repeat!(n, Value) values) fun)
109 	{
110 		foreach (ref var; vars)
111 			assert(var.vars is &this, "Cross-domain operation");
112 		VarName[n] inputs;
113 		foreach (i, ref var; vars)
114 			inputs[i] = var.name;
115 		auto result = allocate();
116 		visitor.multiTransform(inputs, [result.name],
117 			(scope Value[] inputValues, scope Value[] outputValues)
118 			{
119 				Repeat!(n, Value) values;
120 				static foreach (i; 0 .. n)
121 					values[i] = inputValues[i];
122 				outputValues[0] = fun(values);
123 			}
124 		);
125 		return result;
126 	}
127 
128 	struct Var
129 	{
130 		// --- Lifetime
131 
132 		MapSetVars* vars;
133 		VarName name;
134 
135 		private this(MapSetVars* vars, VarName name, Value value = nullValue)
136 		{
137 			this.vars = vars;
138 			this.name = name;
139 			if (value != nullValue)
140 				this = value;
141 		}
142 
143 		this(this)
144 		{
145 			auto newName = vars.allocateName();
146 			vars.visitor.copy(this.name, newName);
147 			this.name = newName;
148 		}
149 
150 		~this()
151 		{
152 			// Must not be in GC!
153 			if (vars && name >= VarName.tempVarStart && name < VarName.tempVarEnd)
154 			{
155 				vars.visitor.put(name, nullValue);
156 				vars.deallocate(name);
157 			}
158 		}
159 
160 		// --- Transformation operations
161 
162 		private enum bool unaryIsInjective(string op) = op == "+" || op == "-" || op == "~";
163 		private enum bool binaryIsInjective(string op) = op == "+" || op == "-" || op == "^" || op == "~";
164 
165 		Var opUnary(string op)()
166 		if (is(typeof(mixin(op ~ ` Value.init`)) : Value))
167 		{
168 			auto result = allocate();
169 			visitor.targetTransform!(unaryIsInjective!op)(this.name, result.name,
170 				(ref const Value inputValue, out Value outputValue)
171 				{
172 					mixin(`outputValue = ` ~ op ~ ` inputValue;`);
173 				}
174 			);
175 			return result;
176 		}
177 
178 		template opBinary(string op)
179 		if (is(typeof(mixin(`Value.init ` ~ op ~ ` Value.init`)) : Value))
180 		{
181 			Var opBinary(Var other)
182 			{
183 				assert(vars is other.vars, "Cross-domain operation");
184 				auto result = vars.allocate();
185 				vars.visitor.multiTransform([this.name, other.name], [result.name],
186 					(scope Value[] inputValues, scope Value[] outputValues)
187 					{
188 						mixin(`outputValues[0] = inputValues[0] ` ~ op ~ ` inputValues[1];`);
189 					}
190 				);
191 				return result;
192 			}
193 
194 			Var opBinary(Value other)
195 			{
196 				auto result = vars.allocate();
197 				vars.visitor.targetTransform!(binaryIsInjective!op)(this.name, result.name,
198 					(ref const Value inputValue, out Value outputValue)
199 					{
200 						mixin(`outputValue = inputValue ` ~ op ~ ` other;`);
201 					}
202 				);
203 				return result;
204 			}
205 		}
206 
207 		Var opBinaryRight(string op)(Value other)
208 		if (is(typeof(mixin(`Value.init ` ~ op ~ ` Value.init`)) : Value))
209 		{
210 			auto result = vars.allocate();
211 			vars.visitor.targetTransform!(binaryIsInjective!op)(this.name, result.name,
212 				(ref const Value inputValue, out Value outputValue)
213 				{
214 					mixin(`outputValue = other ` ~ op ~ ` inputValue;`);
215 				}
216 			);
217 			return result;
218 		}
219 
220 		void opOpAssign(string op)(Value other)
221 		if (is(typeof(mixin(`{ Value v; v ` ~ op ~ `= Value.init; }`))))
222 		{
223 			static if (binaryIsInjective!op)
224 				vars.visitor.injectiveTransform(name, (ref Value value) { mixin(`value ` ~ op ~ `= other;`); });
225 			else
226 				vars.visitor.         transform(name, (ref Value value) { mixin(`value ` ~ op ~ `= other;`); });
227 		}
228 
229 		alias opEquals = opBinary!"==";
230 
231 		static if (Value.min < 0)
232 		{
233 			static struct CompareResult
234 			{
235 				Var var;
236 				alias var this;
237 			}
238 
239 			CompareResult /*opCmp*/cmp(Var other)
240 			{
241 				auto result = vars.allocate();
242 				vars.visitor.multiTransform([this.name, other.name], [result.name],
243 					(scope Value[] inputValues, scope Value[] outputValues)
244 					{
245 						outputValues[0] =
246 							inputValues[0] < inputValues[1] ? -1 :
247 							inputValues[0] > inputValues[1] ? +1 :
248 							0;
249 					}
250 				);
251 				return CompareResult(result);
252 			}
253 
254 			CompareResult /*opCmp*/cmp(Value other)
255 			{
256 				auto result = vars.allocate();
257 				vars.visitor.multiTransform([this.name], [result.name],
258 					(scope Value[] inputValues, scope Value[] outputValues)
259 					{
260 						outputValues[0] =
261 							inputValues[0] < other ? -1 :
262 							inputValues[0] > other ? +1 :
263 							0;
264 					}
265 				);
266 				return CompareResult(result);
267 			}
268 		}
269 
270 		// D does not allow overloading comparison operators :( :( :( :( :(
271 		alias lt = opBinary!"<";
272 		alias gt = opBinary!">";
273 		alias le = opBinary!"<=";
274 		alias ge = opBinary!">=";
275 		alias eq = opBinary!"==";
276 		alias ne = opBinary!"!=";
277 
278 		// Can't overload ! either
279 		Var not() { return map(d => !d); }
280 
281 		/// Ternary.
282 		Var choose(Value ifTrue, Value ifFalse)
283 		{
284 			return map(d => d ? ifTrue : ifFalse);
285 		}
286 
287 		// --- Fundamental operations
288 
289 		Var opAssign(Var value)
290 		{
291 			if (!vars)
292 			{
293 				// Auto-allocate if uninitialized.
294 				// Note, we can't do the same thing for the Value opAssign.
295 				vars = value.vars;
296 				name = vars.allocateName();
297 			}
298 
299 			vars.visitor.copy(value.name, this.name);
300 			return this;
301 		}
302 
303 		Var opAssign(Value value)
304 		{
305 			vars.visitor.put(this.name, value);
306 			return this;
307 		}
308 
309 		Value resolve()
310 		{
311 			return vars.visitor.get(this.name);
312 		}
313 
314 		// Transform and copy one variable.
315 		Var map(scope Value delegate(Value) fun)
316 		{
317 			auto result = vars.allocate();
318 			vars.visitor.targetTransform(this.name, result.name,
319 				(ref const Value inputValue, out Value outputValue)
320 				{
321 					outputValue = fun(inputValue);
322 				}
323 			);
324 			return result;
325 		}
326 	}
327 }
328 
329 /// An example.
330 /// Note that, unlike the similar Visitor test case, this iterates only once,
331 /// thanks to the opBinary support.
332 debug(ae_unittest) unittest
333 {
334 	// Setup
335 
336 	enum VarName : uint { x, y, z, tempVarStart = 100, tempVarEnd = 200 }
337 	MapSetVars!(VarName, int) v;
338 	alias M = MapSetVars!(VarName, int).Set;
339 	M m = v.Set.unitSet;
340 	m = m.cartesianProduct(VarName.x, [1, 2, 3]);
341 	m = m.cartesianProduct(VarName.y, [10, 20, 30]);
342     v.visitor = v.Visitor(m);
343 
344 	// The algorithm that runs on the mapset
345 
346 	void theAlgorithm()
347 	{
348 		v.var.z = v.var.x + v.var.y;
349 	}
350 
351 	// The evaluation loop
352 
353 	M results;
354 	size_t numIterations;
355 	while (v.next())
356 	{
357 		theAlgorithm();
358 
359 		results = results.merge(v.visitor.currentSubset);
360 		numIterations++;
361 	}
362 
363 	assert(numIterations == 1);
364 	assert(results.all(VarName.z) == [11, 12, 13, 21, 22, 23, 31, 32, 33]);
365 }
366 
367 debug(ae_unittest) unittest
368 {
369 	enum VarName : uint { tempVarStart = 100, tempVarEnd = 200 }
370 	MapSetVars!(VarName, int) v;
371     v.visitor = v.Visitor(v.Set.unitSet);
372     v.next();
373 
374     auto a = v.allocate();
375     a = 5;
376     auto b = a;
377     a = 6;
378     assert(b.resolve == 5);
379 }
380 
381 debug(ae_unittest) unittest
382 {
383 	enum VarName : uint { tempVarStart = 100, tempVarEnd = 200 }
384 	MapSetVars!(VarName, int) v;
385     v.visitor = v.Visitor(v.Set.unitSet);
386     v.next();
387 
388     auto a = v.get(cast(VarName)0);
389 
390     v.get(a.name) = 5;
391     assert(a.resolve == 5);
392 
393     v.get(a.name) = v.allocate(7);
394     assert(a.resolve == 7);
395 }
396 
397 debug(ae_unittest) unittest
398 {
399 	enum VarName : uint { tempVarStart = 100, tempVarEnd = 200 }
400 	MapSetVars!(VarName, int) v;
401     v.visitor = v.Visitor(v.Set.unitSet);
402     v.next();
403 
404     auto a = v.get(cast(VarName)0);
405     v.Var b;
406     b = a;
407 }
408 
409 /// Array indexing
410 Var at(T)(T[] array, Var index)
411 if (is(T : Value))
412 {
413     return index.map(value => array[value]);
414 }
415 
416 debug(ae_unittest) unittest
417 {
418 	enum VarName : uint { tempVarStart = 100, tempVarEnd = 200 }
419 	MapSetVars!(VarName, int) v;
420     v.visitor = v.Visitor(v.Set.unitSet);
421     v.next();
422 
423     auto a = v.get(cast(VarName)0);
424     v.Var b;
425     b = a;
426 }
427 
428 template varCall(alias fun)
429 // if (valueLike!(ReturnType!fun) && allSatisfy!(valueLike, Parameters!fun))
430 {
431 	template isValidVarArgs(VarArgs...)
432 	{
433 		alias Vars = __traits(parent, VarArgs[0]);
434 		enum isVar(VarArg) = is(VarArg == Vars.Var);
435 		enum isValidVarArgs = allSatisfy!(isVar, VarArgs);
436 	}
437 
438     // Var varCall(Repeat!(Parameters!fun.length, Var) vars)
439     VarArgs[0] varCall(VarArgs...)(VarArgs varArgs)
440 	if (isValidVarArgs!VarArgs)
441     {
442 		alias Vars = __traits(parent, VarArgs[0]);
443 		alias Var = Vars.Var;
444 		auto vars = varArgs[0].vars;
445         return vars.eval!(Parameters!fun.length)(varArgs,
446             (Repeat!(Parameters!fun.length, Vars.Value) values)
447             {
448                 Parameters!fun params;
449                 foreach (i, ref param; params)
450                     param = cast(typeof(param)) values[i]; // Value -> Integer
451                 return cast(Vars.Value)fun(params);
452             });
453     }
454 }
455 
456 debug(ae_unittest) unittest
457 {
458     static int fun(int i) { return i + 1; }
459 
460 	enum VarName : uint { tempVarStart = 100, tempVarEnd = 200 }
461 	MapSetVars!(VarName, int) v;
462     v.visitor = v.Visitor(v.Set.unitSet);
463     v.next();
464 
465     auto a = v.allocate();
466     a = 5;
467     auto b = varCall!fun(a);
468     assert(b.resolve == 6);
469 }