1 module nxt.ties;
2 
3 import std.typecons: tuple, Tuple;
4 import std.meta: staticMap;
5 import std.exception: enforce, assertThrown;
6 
7 //version = chatty; // print stuff on stdout in unittests. comment this out to make them silent
8 version (chatty) import std.stdio;
9 alias pointerOf(T) = T*;
10 template sameTypes(Ts...)
11 {
12 	static if (Ts.length <= 1)
13 		enum sameTypes = true;
14 	else
15 		enum sameTypes = is(Ts[0] == Ts[1]) && sameTypes!(Ts[1 .. $]);
16 }
17 
18 static assert(sameTypes!(int, int, int));
19 static assert(!sameTypes!(int, bool, int));
20 static assert(!sameTypes!(int, bool, string));
21 
22 auto tie(Ts...)(ref Ts vars)
23 {
24 	struct Tie
25 	{
26 		staticMap!(pointerOf, Ts) pnts;
27 
28 		this(ref Ts vars)
29 		{
30 			foreach (i, t; Ts)
31 			{
32 				pnts[i] = &vars[i];
33 			}
34 		}
35 
36 		void opAssign( Tuple!Ts xs )
37 		{
38 			foreach (i, t; Ts)
39 			{
40 				*pnts[i] = xs[i];
41 			}
42 		}
43 
44 		static if (sameTypes!Ts)
45 		{
46 			import std.conv : text;
47 			import std.range.primitives : front, popFront, ElementType, hasLength, empty, isInputRange;
48 
49 			void opAssign(Ts[0][] xs) // redundant but more effective
50 			{
51 				enforce(xs.length == Ts.length,
52 						`tie(...) = ...: array must have ` ~ Ts.length.text ~ ` elements.`);
53 				foreach (i, t; Ts)
54 				{
55 					*pnts[i] = xs[i];
56 				}
57 			}
58 
59 			void opAssign(R)(R xs) if (isInputRange!R &&
60 									   is(ElementType!R == Ts[0]))
61 			{
62 				static if (hasLength!R)
63 				{
64 					enforce(xs.length >= Ts.length, `tie(...) = ...: range must have at least ` ~ Ts.length.text ~ ` elements.`);
65 				}
66 				foreach (i, t; Ts)
67 				{
68 					enforce(!xs.empty, `tie(...) = ...: range must have at least ` ~ Ts.length.text ~ ` elements.`);
69 					*pnts[i] = xs.front;
70 					xs.popFront();
71 				}
72 			}
73 
74 			void opIndexAssign(R)(R xs) if (isInputRange!R &&
75 											is(ElementType!R == Ts[0]))
76 			{
77 				foreach (i, t; Ts)
78 				{
79 					if (xs.empty) { return; }
80 					*pnts[i] = xs.front;
81 					xs.popFront();
82 				}
83 			}
84 		}
85 	}
86 
87 	return Tie(vars);
88 }
89 
90 @trusted R into(R, Ts...)(Tuple!Ts xs, scope R delegate(Ts) f)
91 {
92 	return f(xs.expand);
93 }
94 
95 /** Destructured Tuple Assignment in Expression $(EXPR).
96 
97 	Underscore variables are ignored.
98 
99 	See_Also: http://forum.dlang.org/thread/hwuiieudmbdvixjejdvi@forum.dlang.org
100  */
101 template let(string expr)
102 {
103 	mixin({
104 			import nxt.algorithm.searching : findSplitAmong;
105 			import std.string: indexOfAny, indexOfNeither;
106 			import std.ascii: whitespace;
107 
108 			// skip whitespace
109 			enum wsSkip = expr.indexOfNeither(whitespace);
110 
111 			enum expr_ = expr[wsSkip .. $];
112 
113 			enum sym0EndIx = expr_.indexOfAny(whitespace ~ `,`);
114 			enum sym0 = expr_[0 .. sym0EndIx];
115 
116 			static if (sym0 == `auto`)
117 			{
118 				enum qual = sym0;
119 				enum var0Ix = qual.length;
120 			}
121 			else static if (sym0 == `const`)
122 			{
123 				enum qual = sym0;
124 				enum var0Ix = qual.length;
125 			}
126 			else static if (sym0 == `immutable`)
127 			{
128 				enum qual = sym0;
129 				enum var0Ix = qual.length;
130 			}
131 			else
132 			{
133 				enum qual = ""; // variables must be defined previously
134 				enum var0Ix = 0;
135 			}
136 
137 			enum split = expr_[var0Ix .. $].findSplitAmong!('=');
138 
139 			mixin(`struct S { int ` ~ split[0] ~ `; }`);
140 			string code = `auto v = ` ~ split[2] ~ `;`; // the right-hand side of the assignment
141 			foreach (i, _; typeof(S.tupleof))
142 			{
143 				enum var = S.tupleof[i].stringof;
144 				static if (var != `_`) // ignore underscored
145 				{
146 					code ~= qual ~ ` ` ~ var ~ ` = v[` ~ i.stringof ~ `]; `;
147 				}
148 			}
149 			return code;
150 		}());
151 }
152 
153 pure @safe nothrow @nogc unittest {
154 	import std.algorithm.searching: findSplit;
155 	mixin let!q{ auto c, _, d = `11-12`.findSplit(`-`) };
156 	assert(c == `11`);
157 	assert(d == `12`);
158 	static assert(__traits(compiles, c == c));
159 	static assert(!__traits(compiles, _ == _)); // assert that it was ignored
160 }
161 
162 pure @safe nothrow @nogc unittest {
163 	import std.algorithm.searching: findSplit;
164 	mixin let!q{ auto c, _, d = `11-12`.findSplit(`-`) };
165 	assert(c == `11`);
166 	assert(d == `12`);
167 	static assert(__traits(compiles, c == c));
168 	static assert(!__traits(compiles, _ == _)); // assert that it was ignored
169 }
170 
171 pure @safe nothrow @nogc unittest {
172 	mixin let!q{ auto i, d, s, c = tuple(42, 3.14, `pi`, 'x') };
173 	assert(i == 42);
174 	assert(d == 3.14);
175 	assert(s == `pi`);
176 	assert(c == 'x');
177 	static assert(is(typeof(i) == int));
178 	static assert(is(typeof(d) == double));
179 	static assert(is(typeof(s) == string));
180 	static assert(is(typeof(c) == char));
181 }
182 
183 pure @safe nothrow @nogc unittest {
184 	mixin let!q{ const i, d, s, c = tuple(42, 3.14, `pi`, 'x') };
185 	assert(i == 42);
186 	assert(d == 3.14);
187 	assert(s == `pi`);
188 	assert(c == 'x');
189 	static assert(is(typeof(i) == const(int)));
190 	static assert(is(typeof(d) == const(double)));
191 	static assert(is(typeof(s) == const(string)));
192 	static assert(is(typeof(c) == const(char)));
193 }
194 
195 pure @safe nothrow @nogc unittest {
196 	mixin let!q{ immutable i, d, s, c = tuple(42, 3.14, `pi`, 'x') };
197 	assert(i == 42);
198 	assert(d == 3.14);
199 	assert(s == `pi`);
200 	assert(c == 'x');
201 	static assert(is(typeof(i) == immutable(int)));
202 	static assert(is(typeof(d) == immutable(double)));
203 	static assert(is(typeof(s) == immutable(string)));
204 	static assert(is(typeof(c) == immutable(char)));
205 }
206 
207 pure unittest {
208 	alias T = Tuple!(int, double, string, char);
209 	T f() { return tuple(42, 3.14, `pi`, 'x'); }
210 	int i;
211 	double d;
212 	string s;
213 	char ch;
214 	tie(i, d, s, ch) = f;
215 }
216 
217 version (unittest)
218 @safe pure nothrow auto sampleTuple(int x)
219 {
220 	return tuple(x, `bottles`);
221 }
222 
223 unittest {
224 	const x = q{1, 2};
225 	import std.stdio;
226 }
227 
228 pure unittest						// with tuple
229 {
230 	int n; string what;
231 	tie(n, what) = sampleTuple(99);
232 	version (chatty) writeln(`n=`, n, ` what=`, what);
233 	assert(n == 99); assert(what == `bottles`);
234 	version (chatty) writeln(`tie(...) = tuple ok`);
235 }
236 
237 pure unittest						// with array
238 {
239 	int n, k, i;
240 	tie(n, k, i) = [3,4,5];
241 	version (chatty) writeln(`n=`, n, ` k=`, k, ` i=`, i);
242 	assert(n == 3); assert(k == 4); assert(i == 5);
243 
244 	assertThrown( tie(n, k, i) = [3,5] ); //throw if not enough data
245 
246 	// tie(...)[] = ... uses available data and doesn't throw if there are not enough elements
247 	n = 1; k = 1; i = 1;
248 	tie(n, k, i)[] = [3,5];
249 	assert(n == 3); assert(k == 5); assert(i == 1);
250 
251 	tie(n, k, i) = tuple(10, 20, 30);
252 	version (chatty) writeln(`n=`, n, ` k=`, k, ` i=`, i);
253 	assert(n == 10);
254 	assert(k == 20);
255 	assert(i == 30);
256 	version (chatty) writeln(`tie(...) = array ok`);
257 }
258 
259 pure unittest						// with range
260 {
261 	import std.algorithm, std.conv;
262 	string[] argv = [`prog`, `100`, `200`];
263 	int x, y;
264 	tie(x,y) = argv[1..$].map!(to!int);
265 	version (chatty) writeln(`x=`, x, ` y=`, y);
266 	assert(x == 100);
267 	assert(y == 200);
268 
269 	assertThrown( tie(x,y) = argv[2..$].map!(to!int) ); //throw if not enough data
270 
271 	// tie(...)[] = ... uses available data and doesn't throw if there are not enough elements
272 	x = 1; y = 1;
273 	tie(x,y)[] = argv[2..$].map!(to!int);
274 	assert(x == 200);
275 	assert(y == 1);
276 	version (chatty) writeln(`tie(...) = range ok`);
277 }
278 
279 @safe unittest						// into
280 {
281 	version (chatty) tuple(99, `bottles`).into( (int n, string s) => writeln(n, ` `, s) );
282 	const x = sampleTuple(10).into( (int n, string s) => n + s.length );
283 	assert(x == 17);
284 	version (chatty) writeln(`into ok`);
285 }