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 }