1 /** 2 Provides functionality for unpacking values from collections into local variables, 3 similar to C++'s `std::tie`. 4 5 Authors: Tony J. Hudgins 6 Copyright: Copyright © 2017, Tony J. Hudgins 7 License: MIT 8 */ 9 module dext.let; 10 11 /++ 12 Unpacks forward ranges, input ranges, static arrays, 13 dynamic arrays, tuples, and user-defined types (via deconstructor methods) 14 into the specified variables. 15 16 Authors: Tony J. Hudgins 17 Copyright: Copyright © 2017, Tony J. Hudgins 18 License: MIT 19 20 Examples: 21 --------- 22 // unpack an array 23 auto nums = [ 1, 2, 3 ]; 24 25 int a, b, c; 26 let( a, b, c ) = nums; // access the array indices and assign them to the a, b, and c variables 27 28 // unpack a struct with a built-in deconstructor 29 struct Point 30 { 31 immutable int x; 32 immutable int y; 33 34 void deconstruct( int* x, int* y ) 35 { 36 *x = this.x; 37 *y = this.y; 38 } 39 } 40 41 auto pt = Point( 5, 6 ); 42 int x, y; 43 let( x, y ) = pt; // call Point.deconstruct() with pointers to the x and y variables 44 --------- 45 +/ 46 auto let( Ts... )( ref Ts locals ) 47 { 48 import core.exception : RangeError; 49 import std.meta : allSatisfy, staticMap; 50 import std.range.primitives : ElementType, isForwardRange, isInputRange; 51 import std..string : format; 52 import std.traits : CommonType, fullyQualifiedName, isArray, isCallable, 53 isFunctionPointer, isPointer, isStaticArray, Parameters, 54 ReturnType, Unqual; 55 import std.typecons : Tuple; 56 57 alias toPointer( T ) = T*; 58 alias TPointers = staticMap!( toPointer, Ts ); 59 alias TCommon = CommonType!Ts; 60 61 enum hasDeconstructorMethod( T ) = is( typeof( { 62 static assert( __traits( hasMember, T, "deconstruct" ) ); 63 64 enum method = &__traits( getMember, T, "deconstruct" ); 65 static assert( isCallable!method ); 66 static assert( is( ReturnType!method == void ) ); 67 68 alias locals = Parameters!method; 69 static assert( locals.length == Ts.length ); 70 static assert( allSatisfy!( isPointer, locals ) ); 71 static assert( is( locals == TPointers ) ); 72 } ) ); 73 74 static struct LetAssigner 75 { 76 private { 77 TPointers pointers; 78 } 79 80 this( Ts... )( ref Ts locals ) 81 { 82 foreach( i, ref x; locals ) 83 this.pointers[i] = &x; 84 } 85 86 void opAssign( Tuple!Ts tup ) 87 { 88 foreach( i, _; Ts ) 89 *this.pointers[i] = tup[i]; 90 } 91 92 static if( !is( TCommon == void ) ) 93 { 94 void opAssign( TCommon[] arr ) 95 { 96 if( arr.length < Ts.length ) 97 throw new RangeError( 98 "Array has too few items to unpack (expecting at least %u)".format( Ts.length ) 99 ); 100 101 foreach( i, t; Ts ) 102 *this.pointers[i] = cast(t)arr[i]; 103 } 104 } 105 106 void opAssign( R )( R r ) 107 if( !isArray!R && isInputRange!R && 108 !isForwardRange!R && !is( TCommon == void ) && 109 is( ElementType!R : TCommon ) 110 ) 111 { 112 this.rangeImpl( r ); 113 } 114 115 void opAssign( R )( R r ) 116 if( !isArray!R && isForwardRange!R && 117 !is( TCommon == void ) && is( ElementType!R : TCommon ) 118 ) 119 { 120 this.rangeImpl( r.save() ); 121 } 122 123 private void rangeImpl( R )( R r ) 124 { 125 foreach( i, t; Ts ) 126 { 127 if( r.empty ) 128 throw new RangeError( 129 "Range has too few items to unpack (expecting at least %u)".format( Ts.length ) 130 ); 131 132 *this.pointers[i] = cast(t)r.front; 133 r.popFront(); 134 } 135 } 136 137 void opAssign( T )( T value ) if( hasDeconstructorMethod!T ) 138 { 139 value.deconstruct( this.pointers ); 140 } 141 } 142 143 return LetAssigner( locals ); 144 } 145 146 @system unittest 147 { 148 import std.range : iota; 149 150 struct Point 151 { 152 int x, y; 153 154 void deconstruct( int* x, int* y ) 155 { 156 *x = this.x; 157 *y = this.y; 158 } 159 } 160 161 int[] dynamic = [ 1, 2 ]; 162 float[2] static_ = [ 3f, 4f ]; 163 auto nums = iota( 0, 500 ); 164 auto pt = Point( 10, 15 ); 165 166 // test dynamic arrays 167 int a, b; 168 let( a, b ) = dynamic; 169 170 assert( a == dynamic[0] ); 171 assert( b == dynamic[1] ); 172 173 // test static arrays 174 float c, d; 175 let( c, d ) = static_; 176 177 assert( c == static_[0] ); 178 assert( d == static_[1] ); 179 180 // test ranges 181 int i, j, k; 182 let( i, j, k ) = nums; 183 184 assert( i == 0 ); 185 assert( j == 1 ); 186 assert( k == 2 ); 187 188 // test deconstructor 189 int x, y; 190 let( x, y ) = pt; 191 192 assert( x == pt.x ); 193 assert( y == pt.y ); 194 }