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.find_split_ex : 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 {
155     import std.algorithm.searching: findSplit;
156     mixin let!q{ auto c, _, d = `11-12`.findSplit(`-`) };
157     assert(c == `11`);
158     assert(d == `12`);
159     static assert(__traits(compiles, c == c));
160     static assert(!__traits(compiles, _ == _)); // assert that it was ignored
161 }
162 
163 pure @safe nothrow @nogc unittest
164 {
165     import std.algorithm.searching: findSplit;
166     mixin let!q{ auto c, _, d = `11-12`.findSplit(`-`) };
167     assert(c == `11`);
168     assert(d == `12`);
169     static assert(__traits(compiles, c == c));
170     static assert(!__traits(compiles, _ == _)); // assert that it was ignored
171 }
172 
173 pure @safe nothrow @nogc unittest
174 {
175     mixin let!q{ auto i, d, s, c = tuple(42, 3.14, `pi`, 'x') };
176     assert(i == 42);
177     assert(d == 3.14);
178     assert(s == `pi`);
179     assert(c == 'x');
180     static assert(is(typeof(i) == int));
181     static assert(is(typeof(d) == double));
182     static assert(is(typeof(s) == string));
183     static assert(is(typeof(c) == char));
184 }
185 
186 pure @safe nothrow @nogc unittest
187 {
188     mixin let!q{ const i, d, s, c = tuple(42, 3.14, `pi`, 'x') };
189     assert(i == 42);
190     assert(d == 3.14);
191     assert(s == `pi`);
192     assert(c == 'x');
193     static assert(is(typeof(i) == const(int)));
194     static assert(is(typeof(d) == const(double)));
195     static assert(is(typeof(s) == const(string)));
196     static assert(is(typeof(c) == const(char)));
197 }
198 
199 pure @safe nothrow @nogc unittest
200 {
201     mixin let!q{ immutable i, d, s, c = tuple(42, 3.14, `pi`, 'x') };
202     assert(i == 42);
203     assert(d == 3.14);
204     assert(s == `pi`);
205     assert(c == 'x');
206     static assert(is(typeof(i) == immutable(int)));
207     static assert(is(typeof(d) == immutable(double)));
208     static assert(is(typeof(s) == immutable(string)));
209     static assert(is(typeof(c) == immutable(char)));
210 }
211 
212 pure unittest
213 {
214     alias T = Tuple!(int, double, string, char);
215     T f() { return tuple(42, 3.14, `pi`, 'x'); }
216     int i;
217     double d;
218     string s;
219     char ch;
220     tie(i, d, s, ch) = f;
221 }
222 
223 version(unittest)
224 @safe pure nothrow auto sampleTuple(int x)
225 {
226     return tuple(x, `bottles`);
227 }
228 
229 unittest
230 {
231     const x = q{1, 2};
232     import std.stdio;
233 }
234 
235 pure unittest                        // with tuple
236 {
237     int n; string what;
238     tie(n, what) = sampleTuple(99);
239     version(chatty) writeln(`n=`, n, ` what=`, what);
240     assert(n == 99); assert(what == `bottles`);
241     version(chatty) writeln(`tie(...) = tuple ok`);
242 }
243 
244 pure unittest                        // with array
245 {
246     int n, k, i;
247     tie(n, k, i) = [3,4,5];
248     version(chatty) writeln(`n=`, n, ` k=`, k, ` i=`, i);
249     assert(n == 3); assert(k == 4); assert(i == 5);
250 
251     assertThrown( tie(n, k, i) = [3,5] ); //throw if not enough data
252 
253     // tie(...)[] = ... uses available data and doesn't throw if there are not enough elements
254     n = 1; k = 1; i = 1;
255     tie(n, k, i)[] = [3,5];
256     assert(n == 3); assert(k == 5); assert(i == 1);
257 
258     tie(n, k, i) = tuple(10, 20, 30);
259     version(chatty) writeln(`n=`, n, ` k=`, k, ` i=`, i);
260     assert(n == 10);
261     assert(k == 20);
262     assert(i == 30);
263     version(chatty) writeln(`tie(...) = array ok`);
264 }
265 
266 pure unittest                        // with range
267 {
268     import std.algorithm, std.conv;
269     string[] argv = [`prog`, `100`, `200`];
270     int x, y;
271     tie(x,y) = argv[1..$].map!(to!int);
272     version(chatty) writeln(`x=`, x, ` y=`, y);
273     assert(x == 100);
274     assert(y == 200);
275 
276     assertThrown( tie(x,y) = argv[2..$].map!(to!int) ); //throw if not enough data
277 
278     // tie(...)[] = ... uses available data and doesn't throw if there are not enough elements
279     x = 1; y = 1;
280     tie(x,y)[] = argv[2..$].map!(to!int);
281     assert(x == 200);
282     assert(y == 1);
283     version(chatty) writeln(`tie(...) = range ok`);
284 }
285 
286 @safe unittest                        // into
287 {
288     version(chatty) tuple(99, `bottles`).into( (int n, string s) => writeln(n, ` `, s) );
289     const x = sampleTuple(10).into( (int n, string s) => n + s.length );
290     assert(x == 17);
291     version(chatty) writeln(`into ok`);
292 }