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