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 }