1 // 
2 // May destructuring variables for itarable types and tuples
3 // 
4 
5 module vest.utils.tie;
6 
7 import std.range      : empty, popFront, front;
8 import std.traits     : isIterable;
9 import std.typecons   : Tuple, tuple, isTuple;
10 import std.functional : forward;
11 import std.meta       : staticMap;
12 
13 auto tie(Args...)(ref Args args) 
14 {
15     static assert(Args.length, "don't call with emprty args");
16     return TieInstance!(Args)(args);
17 }
18 
19 private alias ptr_t(T) = T*;
20 
21 /// Helper structure to collect pointers to arguments
22 private struct PointersSeq(Args...)
23 {
24     alias arg_ptrs_t(T...) = Tuple!(staticMap!(ptr_t, T));
25     arg_ptrs_t!(Args) items;
26     
27     this(ref Args args)
28     {
29         foreach(index, Arg; Args) {
30             items[index] = &args[index];
31         }
32     }
33     void set(size_t index, T)(auto ref T rhs)
34     {
35         static assert(index < Args.length);
36         *items[index] = cast(Args[index]) rhs;
37     }
38 }
39 
40 /// Helper structure.
41 /// Due to the overload of assignment operators allows to use expression tie(x, y, ...) = ...
42 private struct TieInstance(Args...)
43 {
44     PointersSeq!(Args) items;
45 
46     // In the constructor retrive arguments pointers
47     this(ref Args args)
48     {
49         items = PointersSeq!(Args)(args);
50     }
51 
52     // overloading assignment operator (empty Tuple)
53     void opAssign(Tuple!() rhs) {}
54 
55     // overloading assignment operator
56     void opAssign(Types...)(auto ref Tuple!(Types) rhs)
57     {
58         foreach(index, T; Types) {
59             static if(index < Args.length) {
60                 items.set!index( rhs[index] );
61             }
62         }
63     }
64 
65     // Helper method to iterate at compile time
66     pragma(inline, true)
67     private void applyTravers(size_t index, T)(auto ref T rhs)
68         if(isIterable!T && !isTuple!T)
69     {
70         if(rhs.empty) return;
71 
72         items.set!index(rhs.front);
73         
74         rhs.popFront();
75         static if(index < Args.length-1) {
76             applyTravers!(index+1, T)(forward!rhs);
77         }
78         static assert(index < Args.length);
79     }
80 
81     // overloading assignment operator for iterable types
82     void opAssign(T)(auto ref T rhs)
83         if(isIterable!T && !isTuple!T)
84     {   
85         applyTravers!(0, T)( forward!rhs );
86     }
87 }
88 
89 
90 
91 // to run tests: dmd -unittest -main  vest/utils/tie.d && ./vest/utils/tie
92 // or: cd source 
93 // rdmd -unittest -main  vest/utils/tie
94 nothrow unittest {
95     
96     // Traversable
97     string a, b, c, d, e;
98     tie(a,b,c,d,e) = ["foo1","foo2","foo3","foo4","foo5"];
99     assert([a,b,c,d,e] == ["foo1","foo2","foo3","foo4","foo5"]);
100 
101     tie(a,b,c) = ["bar1","bar2"];
102     assert([a,b,c] == ["bar1", "bar2", "foo3"]);
103 
104     tie(a,b,c);
105     assert([a,b,c] == ["bar1", "bar2", "foo3"]);
106 
107     tie(c,c,c,d,e) = ["hru1","hru2"];
108     assert([a,b,c,d,e] == ["bar1","bar2","hru2","foo4","foo5"]);
109 
110     size_t i, j, k;
111 
112     tie(i,j,k) = [1,2];
113     assert([i,j,k] == [1, 2, size_t.init]);
114 
115     tie(i,j,k) = [50,60,70,80,90];
116     assert([i,j,k] == [50,60,70]);
117 
118     tie(i,j,k) = [-1,3.14];
119     assert([i,j,k] == [size_t.max, 3, 70]);
120 
121     double pi;
122     int l;
123 
124     tie(i,pi,l) = [3.14, 3.14, 3.14];
125     assert(tuple(i,pi,l) == tuple(size_t(3), double(3.14), 3));
126 
127     tie(i,pi,l) = [size_t.max, size_t.max, size_t.max];
128     assert( tuple(i,pi,l) == tuple(size_t.max, cast(double) size_t.max, cast(int) size_t.max ) );
129 
130     // Tuples
131     int    x;
132     string y;
133     char   z;
134     size_t u,v,w;
135 
136     tie(x,y,z) = tuple(26, " hello ", 'a', 777, 3.14);
137     assert(x == 26);
138     assert(y == " hello ");
139     assert(z == 'a');
140     
141     tie(x,y,z) = tuple();
142     assert(x == 26);
143     assert(y == " hello ");
144     assert(z == 'a');
145 
146     tie(x, y, z) = tuple(15, " world ");
147     assert(x == 15);
148     assert(y == " world ");
149     assert(z == 'a');
150 
151 
152     tie(x, y, z);
153     assert(x == 15);
154     assert(y == " world ");
155     assert(z == 'a');
156 
157     tie(x, y, z) = tuple();
158     assert(x == 15);
159     assert(y == " world ");
160     assert(z == 'a');
161 
162     tie(x) = tuple(50);
163     assert(x == 50);
164 
165     //tie() = tuple(15, " hello "); <-- don't call with emprty args
166 
167     tie(x,y,z,u,v,w) = tuple(-5, " hi ", 'b', 48, 49, 50, 777, 3.14);
168     assert(tuple(x,y,z, [u,v,w]) == tuple(-5, " hi ", 'b', [48, 49, 50]));
169 
170     tie(u,v,w,y) = tuple(15,16,17);
171     assert([u,v,w] == [15,16,17]);
172 
173 
174     tie(v,v,v,y,x,z) = tuple(25,26,27);
175     assert([u,v,w] == [15,27,17]);
176 
177     tie(v,u) = tuple(u,v);
178     assert([u,v] == [27,15]);
179 }
180 
181 unittest {
182     import std.range : iota;
183     size_t i, j, k, l, m;
184     tie(i,j,k) = iota(50, 91, 10);
185     assert([i,j,k] == [50,60,70]);
186 
187     struct TestItrbl
188     {
189         int a = 0;
190         @property bool empty() {return a >= 10;}
191         @property int front()  {return a;}
192         void popFront() {++a;}
193     }
194 
195     tie(i,j,k,l,m) = TestItrbl();
196     assert([i,j,k,l,m] == [0,1,2,3,4]);
197 
198 }