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